/*
 * Copyright 2002-2011, Haiku. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Michael Pfeiffer
 */
 
 
#include "ConfigWindow.h"
 
#include <limits.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
 
#include <Application.h>
#include <Autolock.h>
#include <Catalog.h>
#include <Debug.h>
#include <GroupLayout.h>
#include <GroupLayoutBuilder.h>
#include <IconUtils.h>
#include <Layout.h>
#include <Locale.h>
#include <Resources.h>
#include <Window.h>
 
#include "pr_server.h"
#include "Printer.h"
#include "PrintServerApp.h"
#include "PrintUtils.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ConfigWindow"
 
 
static const float a0_width = 2380.0;
static const float a0_height = 3368.0;
static const float a1_width = 1684.0;
static const float a1_height = 2380.0;
static const float a2_width = 1190.0;
static const float a2_height = 1684.0;
static const float a3_width = 842.0;
static const float a3_height = 1190.0;
static const float a4_width = 595.0;
static const float a4_height = 842.0;
static const float a5_width = 421.0;
static const float a5_height = 595.0;
static const float a6_width = 297.0;
static const float a6_height = 421.0;
static const float b5_width = 501.0;
static const float b5_height = 709.0;
static const float letter_width = 612.0;
static const float letter_height = 792.0;
static const float legal_width  = 612.0;
static const float legal_height  = 1008.0;
static const float ledger_width = 1224.0;
static const float ledger_height = 792.0;
static const float tabloid_width = 792.0;
static const float tabloid_height = 1224.0;
static const float jis_b5_width = 516.0;
static const float jis_b5_height = 729.0;
 
 
static struct PageFormat
{
	const char  *label;
	float width;
	float height;
} pageFormat[] =
{
	{B_TRANSLATE_MARK_COMMENT("Letter", "ANSI A (letter), a North American "
		"paper size"), letter_width, letter_height },
	{B_TRANSLATE_MARK_COMMENT("Legal", "A North American paper size (216 x 356"
		" mm, or 8.5 x 14 in)"), legal_width,  legal_height },
	{B_TRANSLATE_MARK_COMMENT("Ledger", "ANSI B (ledger), a North American "
		"paper size"), ledger_width, ledger_height },
	{B_TRANSLATE_MARK_COMMENT("Tabloid", "ANSI B (tabloid), a North American "
		"paper size"), tabloid_width, tabloid_height },
	{B_TRANSLATE_MARK_COMMENT("A0", "ISO 216 paper size"),
		a0_width, a0_height },
	{B_TRANSLATE_MARK_COMMENT("A1", "ISO 216 paper size"),
		a1_width, a1_height },
	{B_TRANSLATE_MARK_COMMENT("A2", "ISO 216 paper size"),
		a2_width, a2_height },
	{B_TRANSLATE_MARK_COMMENT("A3", "ISO 216 paper size"),
		a3_width, a3_height },
	{B_TRANSLATE_MARK_COMMENT("A4", "ISO 216 paper size"),
		a4_width, a4_height },
	{B_TRANSLATE_MARK_COMMENT("A5", "ISO 216 paper size"),
		a5_width, a5_height },
	{B_TRANSLATE_MARK_COMMENT("A6", "ISO 216 paper size"),
		a6_width, a6_height },
	{B_TRANSLATE_MARK_COMMENT("B5", "ISO 216 paper size"),
		b5_width, b5_height },
	{B_TRANSLATE_MARK_COMMENT("B5 (JIS)", "JIS P0138 B5, a Japanese "
		"paper size"), jis_b5_width, jis_b5_height },
};
 
 
static void
GetPageFormat(float w, float h, BString& label)
{
	w = floor(w + 0.5); h = floor(h + 0.5);
	for (uint i = 0; i < sizeof(pageFormat) / sizeof(struct PageFormat); i ++) {
		struct PageFormat& pf = pageFormat[i];
		if ((pf.width == w && pf.height == h) || (pf.width == h
			&& pf.height == w)) {
			label = B_TRANSLATE_NOCOLLECT(pf.label);
			return;
		}
	}
 
	float unit = 72.0; // currently inches only
	label << (w / unit) << "x" << (h / unit) << " in.";
}
 
 
static BGroupLayoutBuilder
LeftAlign(BView* view)
{
	return BGroupLayoutBuilder(B_HORIZONTAL)
		.Add(view)
		.AddGlue()
		.SetInsets(0, 0, 0, 0);
}
 
 
ConfigWindow::ConfigWindow(config_setup_kind kind, Printer* defaultPrinter,
	BMessage* settings, AutoReply* sender)
	:
	BWindow(ConfigWindow::GetWindowFrame(),
		B_TRANSLATE("Page setup"),
		B_TITLED_WINDOW,
		B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_AUTO_UPDATE_SIZE_LIMITS
		| B_CLOSE_ON_ESCAPE),
	fKind(kind),
	fDefaultPrinter(defaultPrinter),
	fSettings(settings),
	fSender(sender),
	fCurrentPrinter(NULL),
	fPageFormatText(NULL),
	fJobSetupText(NULL)
{
	MimeTypeForSender(settings, fSenderMimeType);
	PrinterForMimeType();
 
	if (kind == kJobSetup)
		SetTitle(B_TRANSLATE("Print setup"));
 
	BView* panel = new BBox(Bounds(), "temporary", B_FOLLOW_ALL, B_WILL_DRAW);
	AddChild(panel);
 
	// print selection pop up menu
	BPopUpMenu* menu = new BPopUpMenu(B_TRANSLATE("Select a printer"));
	SetupPrintersMenu(menu);
 
	fPrinters = new BMenuField(B_TRANSLATE("Printer:"), menu);
 
	// page format button
	fPageSetup = AddPictureButton(panel, "Paper setup",
		"PAGE_SETUP", MSG_PAGE_SETUP);
 
	// add description to button
	BStringView *pageFormatTitle = new BStringView("paperSetupTitle",
		B_TRANSLATE("Paper setup:"));
	fPageFormatText = new BStringView("pageSetupText", "");
 
	// page selection button
	fJobSetup = NULL;
	BStringView* jobSetupTitle = NULL;
	if (kind == kJobSetup) {
		fJobSetup = AddPictureButton(panel, "Job setup",
			"JOB_SETUP", MSG_JOB_SETUP);
		// add description to button
		jobSetupTitle = new BStringView("jobSetupTitle",
			B_TRANSLATE("Print job setup:"));
		fJobSetupText = new BStringView("jobSetupText", "");
	}
 
	// separator line
	BBox* separator = new BBox("separator");
	separator->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1));
 
	// Cancel & OK button
	BButton* cancel = new BButton("Cancel", B_TRANSLATE("Cancel"),
		new BMessage(B_QUIT_REQUESTED));
	fOk = new BButton("OK", B_TRANSLATE("OK"), new BMessage(MSG_OK));
 
	RemoveChild(panel);
 
	SetLayout(new BGroupLayout(B_VERTICAL));
	BGroupLayoutBuilder builder(B_VERTICAL);
 
	builder
		.Add(fPrinters)
		.Add(BGroupLayoutBuilder(B_HORIZONTAL, 10)
				.Add(fPageSetup)
				.Add(BGroupLayoutBuilder(B_VERTICAL, 0)
						.Add(LeftAlign(pageFormatTitle))
						.Add(LeftAlign(fPageFormatText))
						.SetInsets(0, 0, 0, 0)
				)
				.AddGlue()
		);
 
	if (fJobSetup != NULL) {
		builder
			.Add(BGroupLayoutBuilder(B_HORIZONTAL, 10)
					.Add(fJobSetup)
					.Add(BGroupLayoutBuilder(B_VERTICAL, 0)
							.Add(LeftAlign(jobSetupTitle))
							.Add(LeftAlign(fJobSetupText))
							.SetInsets(0, 0, 0, 0)
					)
					.AddGlue()
			);
	}
 
	builder
		.AddGlue()
		.Add(separator)
		.Add(BGroupLayoutBuilder(B_HORIZONTAL)
			.AddGlue()
			.Add(cancel)
			.Add(fOk)
		)
		.SetInsets(5, 5, 5, 5);
 
	AddChild(builder);
 
	AddShortcut('a', 0, new BMessage(B_ABOUT_REQUESTED));
 
	SetDefaultButton(fOk);
 
	fPrinters->MakeFocus(true);
 
	UpdateSettings(true);
}
 
 
ConfigWindow::~ConfigWindow()
{
	if (fCurrentPrinter)
		fCurrentPrinter->Release();
	release_sem(fFinished);
}
 
 
void
ConfigWindow::Go()
{
	sem_id sid = create_sem(0, "finished");
	if (sid >= 0) {
		fFinished = sid;
		Show();
		acquire_sem(sid);
		delete_sem(sid);
	} else {
		Quit();
	}
}
 
 
void
ConfigWindow::MessageReceived(BMessage* m)
{
	switch (m->what) {
		case MSG_PAGE_SETUP:
			Setup(kPageSetup);
			break;
		case MSG_JOB_SETUP:
			Setup(kJobSetup);
			break;
		case MSG_PRINTER_SELECTED:
		{
			BString printer;
			if (m->FindString("name", &printer) == B_OK) {
				UpdateAppSettings(fSenderMimeType.String(), printer.String());
				PrinterForMimeType();
				UpdateSettings(true);
			}
			break;
		}
		case MSG_OK:
			UpdateSettings(false);
			if (fKind == kPageSetup)
				fSender->SetReply(&fPageSettings);
			else
				fSender->SetReply(&fJobSettings);
			Quit();
			break;
		case B_ABOUT_REQUESTED: AboutRequested();
			break;
		default:
			BWindow::MessageReceived(m);
	}
}
 
 
void
ConfigWindow::AboutRequested()
{
	BString text = B_TRANSLATE("Printer server");
	text <<	"\n"
		"© 2001-2010 Haiku, Inc.\n"
		"\n"
		"\tIthamar R. Adema\n"
		"\tMichael Pfeiffer\n";
 
	BAlert *about = new BAlert("About printer server", text.String(),
		B_TRANSLATE("OK"));
	about->SetFlags(about->Flags() | B_CLOSE_ON_ESCAPE);
	about->Go();
}
 
 
void
ConfigWindow::FrameMoved(BPoint p)
{
	BRect frame = GetWindowFrame();
	frame.OffsetTo(p);
	SetWindowFrame(frame);
}
 
 
BRect
ConfigWindow::GetWindowFrame()
{
	BRect frame(0, 0, 10, 10);
	BAutolock lock(gLock);
	if (lock.IsLocked())
		frame.OffsetBy(Settings::GetSettings()->ConfigWindowFrame().LeftTop());
 
	frame.OffsetBy(30, 30);
	return frame;
}
 
 
void
ConfigWindow::SetWindowFrame(BRect r)
{
	BAutolock lock(gLock);
	if (lock.IsLocked())
		Settings::GetSettings()->SetConfigWindowFrame(r);
}
 
 
BButton*
ConfigWindow::AddPictureButton(BView* panel, const char* name,
	const char* picture, uint32 what)
{
	BResources *res = BApplication::AppResources();
	if (res == NULL)
		return NULL;
 
	size_t length;
	const void *bits = res->LoadResource('VICN', picture, &length);
	BButton* button = NULL;
 
	BBitmap* onBM = new BBitmap(BRect(0, 0, 24, 24), B_RGBA32);
 
	if (onBM != NULL) {
		if (BIconUtils::GetVectorIcon((uint8*)bits, length, onBM) != B_OK) {
			delete onBM;
			return NULL;
		}
 
		button = new BButton(name, new BMessage(what));
		button->SetIcon(onBM, B_TRIM_ICON_BITMAP_KEEP_ASPECT);
		button->SetViewColor(B_TRANSPARENT_COLOR);
		button->SetLabel(NULL);
	}
 
	delete onBM;
 
	return button;
}
 
 
void
ConfigWindow::PrinterForMimeType()
{
	BAutolock lock(gLock);
	if (fCurrentPrinter) {
		fCurrentPrinter->Release();
		fCurrentPrinter = NULL;
	}
 
	if (lock.IsLocked()) {
		Settings* s = Settings::GetSettings();
		AppSettings* app = s->FindAppSettings(fSenderMimeType.String());
		if (app)
			fPrinterName = app->GetPrinter();
		else
			fPrinterName = fDefaultPrinter ? fDefaultPrinter->Name() : "";
		fCurrentPrinter = Printer::Find(fPrinterName);
		if (fCurrentPrinter)
			fCurrentPrinter->Acquire();
	}
}
 
 
void
ConfigWindow::SetupPrintersMenu(BMenu* menu)
{
	// clear menu
	while (menu->CountItems() != 0)
		delete menu->RemoveItem((int32)0);
 
	// fill menu with printer names
	BAutolock lock(gLock);
	if (lock.IsLocked()) {
		BString n;
		BMessage* m;
		BMenuItem* item;
		for (int i = 0; i < Printer::CountPrinters(); i ++) {
			Printer::At(i)->GetName(n);
			m = new BMessage(MSG_PRINTER_SELECTED);
			m->AddString("name", n.String());
			menu->AddItem(item = new BMenuItem(n.String(), m));
			if (n == fPrinterName)
				item->SetMarked(true);
		}
	}
}
 
 
void
ConfigWindow::UpdateAppSettings(const char* mime, const char* printer)
{
	BAutolock lock(gLock);
	if (lock.IsLocked()) {
		Settings* s = Settings::GetSettings();
		AppSettings* app = s->FindAppSettings(mime);
		if (app)
			app->SetPrinter(printer);
		else
			s->AddAppSettings(new AppSettings(mime, printer));
	}
}
 
 
void
ConfigWindow::UpdateSettings(bool read)
{
	BAutolock lock(gLock);
	if (lock.IsLocked()) {
		Settings* s = Settings::GetSettings();
		PrinterSettings* p = s->FindPrinterSettings(fPrinterName.String());
		if (p == NULL) {
			p = new PrinterSettings(fPrinterName.String());
			s->AddPrinterSettings(p);
		}
		ASSERT(p != NULL);
		if (read) {
			fPageSettings = *p->GetPageSettings();
			fJobSettings = *p->GetJobSettings();
		} else {
			p->SetPageSettings(&fPageSettings);
			p->SetJobSettings(&fJobSettings);
		}
	}
	UpdateUI();
}
 
 
void
ConfigWindow::UpdateUI()
{
	if (fCurrentPrinter == NULL) {
		fPageSetup->SetEnabled(false);
		if (fJobSetup) {
			fJobSetup->SetEnabled(false);
			fJobSetupText->SetText(B_TRANSLATE("Undefined"));
		}
		fOk->SetEnabled(false);
		fPageFormatText->SetText(B_TRANSLATE("Undefined"));
	} else {
		fPageSetup->SetEnabled(true);
 
		if (fJobSetup)
			fJobSetup->SetEnabled(fKind == kJobSetup
				&& !fPageSettings.IsEmpty());
 
		fOk->SetEnabled((fKind == kJobSetup && !fJobSettings.IsEmpty())
			|| (fKind == kPageSetup && !fPageSettings.IsEmpty()));
 
		// display information about page format
		BRect paperRect;
		BString pageFormat;
		if (fPageSettings.FindRect(PSRV_FIELD_PAPER_RECT, &paperRect) == B_OK) {
			GetPageFormat(paperRect.Width(), paperRect.Height(), pageFormat);
 
			int32 orientation = 0;
			fPageSettings.FindInt32(PSRV_FIELD_ORIENTATION, &orientation);
			if (orientation == 0)
				pageFormat << ", " << B_TRANSLATE("Portrait");
			else
				pageFormat << ", " << B_TRANSLATE("Landscape");
		} else
			pageFormat << B_TRANSLATE("Undefined");
 
		fPageFormatText->SetText(pageFormat.String());
 
		// display information about job
		if (fKind == kJobSetup) {
			BString job;
			int32 first, last, copies;
			if (fJobSettings.FindInt32(PSRV_FIELD_FIRST_PAGE, &first) == B_OK
				&& fJobSettings.FindInt32(PSRV_FIELD_LAST_PAGE, &last) ==
				B_OK) {
 
				bool printRange = first >= 1 && first <= last && last != INT_MAX;
				char number[12];
				if (fJobSettings.FindInt32(PSRV_FIELD_COPIES, &copies)
					== B_OK && copies > 1) {
					if (printRange) {
						job = B_TRANSLATE("Page %1 to %2, %3 copies");
						snprintf(number, sizeof(number), "%d", (int)first);
						job.ReplaceFirst("%1", number);
						snprintf(number, sizeof(number), "%d", (int)last);
						job.ReplaceFirst("%2", number);
						snprintf(number, sizeof(number), "%d", (int)copies);
						job.ReplaceFirst("%3", number);
					} else {
						job = B_TRANSLATE("All pages, %1 copies");
						snprintf(number, sizeof(number), "%d", (int)copies);
						job.ReplaceFirst("%1", number);
					}
				} else {
					if (printRange) {
						job = B_TRANSLATE("Page %1 to %2");
						snprintf(number, sizeof(number), "%d", (int)first);
						job.ReplaceFirst("%1", number);
						snprintf(number, sizeof(number), "%d", (int)last);
						job.ReplaceFirst("%2", number);
					} else
						job = B_TRANSLATE("All pages");
				}
			} else
				job << B_TRANSLATE("Undefined");
 
			fJobSetupText->SetText(job.String());
		}
	}
}
 
 
void
ConfigWindow::Setup(config_setup_kind kind)
{
	if (fCurrentPrinter) {
		Hide();
		if (kind == kPageSetup) {
			BMessage settings = fPageSettings;
			if (fCurrentPrinter->ConfigurePage(settings) == B_OK) {
				fPageSettings = settings;
				if (!fJobSettings.IsEmpty())
					AddFields(&fJobSettings, &fPageSettings);
			}
		} else {
			BMessage settings;
			if (fJobSettings.IsEmpty()) settings = fPageSettings;
			else settings = fJobSettings;
 
			if (fCurrentPrinter->ConfigureJob(settings) == B_OK)
				fJobSettings = settings;
		}
		UpdateUI();
		Show();
	}
}

V773 Visibility scope of the 'about' pointer was exited without releasing the memory. A memory leak is possible.