1/*
2 * JobSetupDlg.cpp
3 * Copyright 1999-2000 Y.Takagi. All Rights Reserved.
4 */
5
6#include <cstdio>
7#include <cstring>
8#include <cstdlib>
9#include <string>
10#include <fcntl.h>
11#include <unistd.h>
12#include <sys/stat.h>
13#include <math.h>
14
15#include <Alert.h>
16#include <Bitmap.h>
17#include <Box.h>
18#include <Button.h>
19#include <CheckBox.h>
20#include <Debug.h>
21#include <GridView.h>
22#include <GroupLayout.h>
23#include <GroupLayoutBuilder.h>
24#include <Looper.h>
25#include <MessageFilter.h>
26#include <MenuField.h>
27#include <MenuItem.h>
28#include <Message.h>
29#include <Point.h>
30#include <PopUpMenu.h>
31#include <PrintJob.h>
32#include <RadioButton.h>
33#include <Rect.h>
34#include <Slider.h>
35#include <String.h>
36#include <TextControl.h>
37#include <TextView.h>
38#include <View.h>
39
40#include "HalftoneView.h"
41#include "JobSetupDlg.h"
42#include "JobData.h"
43#include "JSDSlider.h"
44#include "PagesView.h"
45#include "PrinterData.h"
46#include "PrinterCap.h"
47#include "DbgMsg.h"
48
49
50using namespace std;
51
52
53struct NupCap : public EnumCap {
54	NupCap(const string &label, bool isDefault, int nup)
55		:
56		EnumCap(label, isDefault),
57		fNup(nup)
58	{}
59
60	int32	ID() const { return fNup; }
61
62	int	fNup;
63};
64
65
66struct DitherCap : public EnumCap {
67	DitherCap(const string &label, bool isDefault,
68		Halftone::DitherType ditherType)
69		:
70		EnumCap(label, isDefault),
71		fDitherType(ditherType)
72	{}
73
74	int32	ID() const { return fDitherType; }
75
76	Halftone::DitherType fDitherType;
77};
78
79
80static const NupCap gNup1("1", true,  1);
81static const NupCap gNup2("2",   false, 2);
82static const NupCap gNup4("4",   false, 4);
83static const NupCap gNup8("8",   false, 8);
84static const NupCap gNup9("9",   false, 9);
85static const NupCap gNup16("16", false, 16);
86static const NupCap gNup25("25", false, 25);
87static const NupCap gNup32("32", false, 32);
88static const NupCap gNup36("36", false, 36);
89
90
91static const DitherCap gDitherType1("Crosshatch", false, Halftone::kType1);
92static const DitherCap gDitherType2("Grid", false, Halftone::kType2);
93static const DitherCap gDitherType3("Stipple", false, Halftone::kType3);
94static const DitherCap gDitherFloydSteinberg("Floyd-Steinberg", false,
95	Halftone::kTypeFloydSteinberg);
96
97
98const BaseCap *gNups[] = {
99	&gNup1,
100	&gNup2,
101	&gNup4,
102	&gNup8,
103	&gNup9,
104	&gNup16,
105	&gNup25,
106	&gNup32,
107	&gNup36
108};
109
110
111const BaseCap *gDitherTypes[] = {
112	&gDitherType1,
113	&gDitherType2,
114	&gDitherType3,
115	&gDitherFloydSteinberg
116};
117
118
119static const char* kCategoryID = "id";
120
121
122enum {
123	kMsgRangeAll = 'JSdl',
124	kMsgRangeSelection,
125	kMsgCancel,
126	kMsgOK,
127	kMsgQuality,
128	kMsgCollateChanged,
129	kMsgReverseChanged,
130	kMsgDuplexChanged,
131	kMsgIntSliderChanged,
132	kMsgDoubleSliderChanged,
133	kMsgNone = 0
134};
135
136
137JobSetupView::JobSetupView(JobData* jobData, PrinterData* printerData,
138	const PrinterCap *printerCap)
139	:
140	BView("jobSetup", B_WILL_DRAW),
141	fCopies(NULL),
142	fFromPage(NULL),
143	fToPage(NULL),
144	fJobData(jobData),
145	fPrinterData(printerData),
146	fPrinterCap(printerCap),
147	fColorType(NULL),
148	fDitherType(NULL),
149	fGamma(NULL),
150	fInkDensity(NULL),
151	fHalftone(NULL),
152	fAll(NULL),
153	fCollate(NULL),
154	fReverse(NULL),
155	fPages(NULL),
156	fPaperFeed(NULL),
157	fDuplex(NULL),
158	fNup(NULL),
159	fAllPages(NULL),
160	fOddNumberedPages(NULL),
161	fEvenNumberedPages(NULL)
162{
163	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
164}
165
166
167BRadioButton*
168JobSetupView::CreatePageSelectionItem(const char* name, const char* label,
169	JobData::PageSelection pageSelection)
170{
171	BRadioButton* button = new BRadioButton(name, label, NULL);
172	if (fJobData->GetPageSelection() == pageSelection) {
173		button->SetValue(B_CONTROL_ON);
174	}
175	return button;
176}
177
178
179void
180JobSetupView::AllowOnlyDigits(BTextView* textView, int maxDigits)
181{
182	int num;
183	for (num = 0; num <= 255; num++) {
184		textView->DisallowChar(num);
185	}
186	for (num = 0; num <= 9; num++) {
187		textView->AllowChar('0' + num);
188	}
189	textView->SetMaxBytes(maxDigits);
190}
191
192
193void
194JobSetupView::AttachedToWindow()
195{
196	// quality
197	BBox* qualityBox = new BBox("quality");
198	qualityBox->SetLabel("Quality");
199
200	// color
201	fColorType = new BPopUpMenu("color");
202	fColorType->SetRadioMode(true);
203	FillCapabilityMenu(fColorType, kMsgQuality, PrinterCap::kColor,
204		fJobData->GetColor());
205	BMenuField* colorMenuField = new BMenuField("color", "Color:", fColorType);
206	fColorType->SetTargetForItems(this);
207
208	if (IsHalftoneConfigurationNeeded())
209		CreateHalftoneConfigurationUI();
210
211	// page range
212
213	BBox* pageRangeBox = new BBox("pageRange");
214	pageRangeBox->SetLabel("Page range");
215
216	fAll = new BRadioButton("all", "Print all Pages", new BMessage(kMsgRangeAll));
217
218	BRadioButton *range = new BRadioButton("selection", "Print selected Pages:",
219		new BMessage(kMsgRangeSelection));
220
221	fFromPage = new BTextControl("from", "From:", "", NULL);
222	fFromPage->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
223	AllowOnlyDigits(fFromPage->TextView(), 6);
224
225	fToPage = new BTextControl("to", "To:", "", NULL);
226	fToPage->SetAlignment(B_ALIGN_LEFT, B_ALIGN_RIGHT);
227	AllowOnlyDigits(fToPage->TextView(), 6);
228
229	int first_page = fJobData->GetFirstPage();
230	int last_page  = fJobData->GetLastPage();
231
232	if (first_page <= 1 && last_page <= 0) {
233		fAll->SetValue(B_CONTROL_ON);
234	} else {
235		range->SetValue(B_CONTROL_ON);
236		if (first_page < 1)
237			first_page = 1;
238		if (first_page > last_page)
239			last_page = -1;
240
241		BString oss1;
242		oss1 << first_page;
243		fFromPage->SetText(oss1.String());
244
245		BString oss2;
246		oss2 << last_page;
247		fToPage->SetText(oss2.String());
248	}
249
250	fAll->SetTarget(this);
251	range->SetTarget(this);
252
253	// paper source
254	fPaperFeed = new BPopUpMenu("");
255	fPaperFeed->SetRadioMode(true);
256	FillCapabilityMenu(fPaperFeed, kMsgNone, PrinterCap::kPaperSource,
257		fJobData->GetPaperSource());
258	BMenuField* paperSourceMenufield = new BMenuField("paperSource",
259		"Paper source:", fPaperFeed);
260
261	// Pages per sheet
262	fNup = new BPopUpMenu("");
263	fNup->SetRadioMode(true);
264	FillCapabilityMenu(fNup, kMsgNone, gNups,
265		sizeof(gNups) / sizeof(gNups[0]), (int)fJobData->GetNup());
266	BMenuField* pagesPerSheet = new BMenuField("pagesPerSheet",
267		"Pages per sheet:", fNup);
268
269	// duplex
270	if (fPrinterCap->Supports(PrinterCap::kPrintStyle)) {
271		fDuplex = new BCheckBox("duplex", "Duplex",
272			new BMessage(kMsgDuplexChanged));
273		if (fJobData->GetPrintStyle() != JobData::kSimplex) {
274			fDuplex->SetValue(B_CONTROL_ON);
275		}
276		fDuplex->SetTarget(this);
277	} else {
278		fDuplex = NULL;
279	}
280
281	// copies
282	fCopies = new BTextControl("copies", "Number of copies:", "", NULL);
283	AllowOnlyDigits(fCopies->TextView(), 3);
284
285	BString copies;
286	copies << fJobData->GetCopies();
287	fCopies->SetText(copies.String());
288
289	// collate
290	fCollate = new BCheckBox("collate", "Collate",
291		new BMessage(kMsgCollateChanged));
292	if (fJobData->GetCollate()) {
293		fCollate->SetValue(B_CONTROL_ON);
294	}
295	fCollate->SetTarget(this);
296
297	// reverse
298	fReverse = new BCheckBox("reverse", "Reverse order",
299		new BMessage(kMsgReverseChanged));
300	if (fJobData->GetReverse()) {
301		fReverse->SetValue(B_CONTROL_ON);
302	}
303	fReverse->SetTarget(this);
304
305	// pages view
306	// TODO make layout API compatible
307	fPages = new PagesView(BRect(0, 0, 150, 40), "pages", B_FOLLOW_ALL,
308		B_WILL_DRAW);
309	fPages->SetCollate(fJobData->GetCollate());
310	fPages->SetReverse(fJobData->GetReverse());
311	fPages->SetExplicitMinSize(BSize(150, 40));
312	fPages->SetExplicitMaxSize(BSize(150, 40));
313
314	// page selection
315	BBox* pageSelectionBox = new BBox("pageSelection");
316	pageSelectionBox->SetLabel("Page selection");
317
318	fAllPages = CreatePageSelectionItem("allPages", "All pages",
319		JobData::kAllPages);
320	fOddNumberedPages = CreatePageSelectionItem("oddPages",
321		"Odd-numbered pages", JobData::kOddNumberedPages);
322	fEvenNumberedPages = CreatePageSelectionItem("evenPages",
323		"Even-numbered pages", JobData::kEvenNumberedPages);
324
325	fPreview = new BCheckBox("preview", "Show preview before printing", NULL);
326	if (fJobData->GetShowPreview())
327		fPreview->SetValue(B_CONTROL_ON);
328
329	// separator line
330	BBox *separator = new BBox("separator");
331	separator->SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, 1));
332
333	// buttons
334	BButton* cancel = new BButton("cancel", "Cancel",
335		new BMessage(kMsgCancel));
336	BButton* ok = new BButton("ok", "OK", new BMessage(kMsgOK));
337	ok->MakeDefault(true);
338
339	if (IsHalftoneConfigurationNeeded()) {
340		BGroupView* halftoneGroup = new BGroupView(B_VERTICAL, 0);
341		BGroupLayout* halftoneLayout = halftoneGroup->GroupLayout();
342		halftoneLayout->AddView(fHalftone);
343		fHalftoneBox->AddChild(halftoneGroup);
344	}
345
346	BGridView* qualityGrid = new BGridView();
347	BGridLayout* qualityGridLayout = qualityGrid->GridLayout();
348	qualityGridLayout->AddItem(colorMenuField->CreateLabelLayoutItem(), 0, 0);
349	qualityGridLayout->AddItem(colorMenuField->CreateMenuBarLayoutItem(), 1, 0);
350	if (IsHalftoneConfigurationNeeded()) {
351		qualityGridLayout->AddItem(fDitherMenuField->CreateLabelLayoutItem(),
352			0, 1);
353		qualityGridLayout->AddItem(fDitherMenuField->CreateMenuBarLayoutItem(),
354			1, 1);
355		qualityGridLayout->AddView(fGamma, 0, 2, 2);
356		qualityGridLayout->AddView(fInkDensity, 0, 3, 2);
357		qualityGridLayout->AddView(fHalftoneBox, 0, 4, 2);
358	} else {
359		AddDriverSpecificSettings(qualityGridLayout, 1);
360	}
361	qualityGridLayout->SetSpacing(0, 0);
362	qualityGridLayout->SetInsets(5, 5, 5, 5);
363	qualityBox->AddChild(qualityGrid);
364	// TODO put qualityGrid in a scroll view
365	// the layout of the box surrounding the scroll view using the following
366	// code is not correct; the box still has the size of the qualityGird;
367	// and the scroll view is vertically centered inside the box!
368	//BScrollView* qualityScroller = new BScrollView("qualityScroller",
369	//	qualityGrid, 0, false, true);
370	//qualityScroller->SetExplicitMaxSize(BSize(500, 500));
371	//qualityBox->AddChild(qualityScroller);
372
373	BGridView* pageRangeGrid = new BGridView();
374	BGridLayout* pageRangeLayout = pageRangeGrid->GridLayout();
375	pageRangeLayout->AddItem(fFromPage->CreateLabelLayoutItem(), 0, 0);
376	pageRangeLayout->AddItem(fFromPage->CreateTextViewLayoutItem(), 1, 0);
377	pageRangeLayout->AddItem(fToPage->CreateLabelLayoutItem(), 0, 1);
378	pageRangeLayout->AddItem(fToPage->CreateTextViewLayoutItem(), 1, 1);
379	pageRangeLayout->SetInsets(0, 0, 0, 0);
380	pageRangeLayout->SetSpacing(0, 0);
381
382	BGroupView* pageRangeGroup = new BGroupView(B_VERTICAL, 0);
383	BGroupLayout* pageRangeGroupLayout = pageRangeGroup->GroupLayout();
384	pageRangeGroupLayout->AddView(fAll);
385	pageRangeGroupLayout->AddView(range);
386	pageRangeGroupLayout->AddView(pageRangeGrid);
387	pageRangeGroupLayout->SetInsets(5, 5, 5, 5);
388	pageRangeBox->AddChild(pageRangeGroup);
389
390	BGridView* settings = new BGridView();
391	BGridLayout* settingsLayout = settings->GridLayout();
392	settingsLayout->AddItem(paperSourceMenufield->CreateLabelLayoutItem(), 0,
393		0);
394	settingsLayout->AddItem(paperSourceMenufield->CreateMenuBarLayoutItem(), 1,
395		0);
396	settingsLayout->AddItem(pagesPerSheet->CreateLabelLayoutItem(), 0, 1);
397	settingsLayout->AddItem(pagesPerSheet->CreateMenuBarLayoutItem(), 1, 1);
398	int row = 2;
399	if (fDuplex != NULL) {
400		settingsLayout->AddView(fDuplex, 0, row, 2);
401		row ++;
402	}
403	settingsLayout->AddItem(fCopies->CreateLabelLayoutItem(), 0, row);
404	settingsLayout->AddItem(fCopies->CreateTextViewLayoutItem(), 1, row);
405	settingsLayout->SetSpacing(0, 0);
406
407
408	BGroupView* pageSelectionGroup = new BGroupView(B_VERTICAL, 0);
409	BGroupLayout* groupLayout = pageSelectionGroup->GroupLayout();
410	groupLayout->AddView(fAllPages);
411	groupLayout->AddView(fOddNumberedPages);
412	groupLayout->AddView(fEvenNumberedPages);
413	groupLayout->SetInsets(5, 5, 5, 5);
414	pageSelectionBox->AddChild(pageSelectionGroup);
415
416	SetLayout(new BGroupLayout(B_VERTICAL));
417	AddChild(BGroupLayoutBuilder(B_VERTICAL, 0)
418		.AddGroup(B_HORIZONTAL, 10, 1.0f)
419			.AddGroup(B_VERTICAL, 10, 1.0f)
420				.Add(qualityBox)
421				.Add(pageRangeBox)
422				.AddGlue()
423			.End()
424			.AddGroup(B_VERTICAL, 0, 1.0f)
425				.Add(settings)
426				.AddStrut(5)
427				.Add(fCollate)
428				.Add(fReverse)
429				.Add(fPages)
430				.AddStrut(5)
431				.Add(pageSelectionBox)
432				.AddGlue()
433			.End()
434		.End()
435		.Add(fPreview)
436		.AddGlue()
437		.Add(separator)
438		.AddGroup(B_HORIZONTAL, 10, 1.0f)
439			.AddGlue()
440			.Add(cancel)
441			.Add(ok)
442		.End()
443		.SetInsets(0, 0, 0, 0)
444	);
445
446	UpdateHalftonePreview();
447
448	UpdateButtonEnabledState();
449}
450
451
452bool
453JobSetupView::IsHalftoneConfigurationNeeded()
454{
455	return fPrinterCap->Supports(PrinterCap::kHalftone);
456}
457
458
459void
460JobSetupView::CreateHalftoneConfigurationUI()
461{
462	// dither type
463	fDitherType = new BPopUpMenu("");
464	fDitherType->SetRadioMode(true);
465	FillCapabilityMenu(fDitherType, kMsgQuality, gDitherTypes,
466		sizeof(gDitherTypes) / sizeof(gDitherTypes[0]),
467		(int)fJobData->GetDitherType());
468	fDitherMenuField = new BMenuField("dithering", "Dot Pattern:",
469		fDitherType);
470	fDitherType->SetTargetForItems(this);
471
472	// halftone preview view
473	fHalftoneBox = new BBox("halftoneBox");
474	fHalftoneBox->SetBorder(B_PLAIN_BORDER);
475
476	// TODO make layout compatible
477	BSize size(240, 14 * 4);
478	BRect rect(0, 0, size.width, size.height);
479	fHalftone = new HalftoneView(rect, "halftone",
480		B_FOLLOW_ALL, B_WILL_DRAW);
481	fHalftone->SetExplicitMinSize(size);
482	fHalftone->SetExplicitMaxSize(size);
483
484	// gamma
485	fGamma = new JSDSlider("gamma", "Gamma", new BMessage(kMsgQuality),
486		-300, 300);
487
488	fGamma->SetLimitLabels("Lighter", "Darker");
489	fGamma->SetValue((int32)(100 * log(fJobData->GetGamma()) / log(2.0)));
490	fGamma->SetHashMarks(B_HASH_MARKS_BOTH);
491	fGamma->SetHashMarkCount(7);
492	fGamma->SetModificationMessage(new BMessage(kMsgQuality));
493	fGamma->SetTarget(this);
494
495	// ink density
496	fInkDensity = new JSDSlider("inkDensity", "Ink usage",
497		new BMessage(kMsgQuality), 0, 127);
498
499	fInkDensity->SetLimitLabels("Min", "Max");
500	fInkDensity->SetValue((int32)fJobData->GetInkDensity());
501	fInkDensity->SetHashMarks(B_HASH_MARKS_BOTH);
502	fInkDensity->SetHashMarkCount(10);
503	fInkDensity->SetModificationMessage(new BMessage(kMsgQuality));
504	fInkDensity->SetTarget(this);
505}
506
507
508void
509JobSetupView::AddDriverSpecificSettings(BGridLayout* gridLayout, int row)
510{
511	if (!fPrinterCap->Supports(PrinterCap::kDriverSpecificCapabilities))
512		return;
513
514	int count = fPrinterCap->CountCap(PrinterCap::kDriverSpecificCapabilities);
515	const BaseCap** capabilities = fPrinterCap->GetCaps(
516		PrinterCap::kDriverSpecificCapabilities);
517
518	for (int i = 0; i < count; i ++) {
519		const DriverSpecificCap* capability =
520			static_cast<const DriverSpecificCap*>(capabilities[i]);
521
522		switch (capability->fType) {
523			case DriverSpecificCap::kList:
524				AddPopUpMenu(capability, gridLayout, row);
525				break;
526			case DriverSpecificCap::kBoolean:
527				AddCheckBox(capability, gridLayout, row);
528				break;
529			case DriverSpecificCap::kIntRange:
530			case DriverSpecificCap::kIntDimension:
531				AddIntSlider(capability, gridLayout, row);
532				break;
533			case DriverSpecificCap::kDoubleRange:
534				AddDoubleSlider(capability, gridLayout, row);
535				break;
536
537		}
538	}
539}
540
541
542void
543JobSetupView::AddPopUpMenu(const DriverSpecificCap* capability,
544	BGridLayout* gridLayout, int& row)
545{
546	const char* label = capability->fLabel.c_str();
547	BPopUpMenu* popUpMenu = new BPopUpMenu(label);
548	popUpMenu->SetRadioMode(true);
549
550	PrinterCap::CapID category = static_cast<PrinterCap::CapID>(
551		capability->ID());
552
553	const BaseCap** categoryCapabilities = fPrinterCap->GetCaps(category);
554
555	int categoryCount = fPrinterCap->CountCap(category);
556
557	string value = GetDriverSpecificValue(category, capability->Key());
558	PrinterCap::KeyPredicate predicate(value.c_str());
559
560	FillCapabilityMenu(popUpMenu, kMsgNone, categoryCapabilities,
561		categoryCount, predicate);
562
563	BString menuLabel = label;
564	menuLabel << ":";
565	BMenuField* menuField = new BMenuField(label, menuLabel.String(),
566		popUpMenu);
567	popUpMenu->SetTargetForItems(this);
568
569	gridLayout->AddItem(menuField->CreateLabelLayoutItem(),
570		0, row);
571	gridLayout->AddItem(menuField->CreateMenuBarLayoutItem(),
572		1, row);
573	row ++;
574
575	fDriverSpecificPopUpMenus[category] = popUpMenu;
576}
577
578
579void
580JobSetupView::AddCheckBox(const DriverSpecificCap* capability,
581	BGridLayout* gridLayout, int& row)
582{
583	PrinterCap::CapID category = static_cast<PrinterCap::CapID>(
584		capability->ID());
585	const BooleanCap* booleanCap = fPrinterCap->FindBooleanCap(category);
586	if (booleanCap == NULL) {
587		fprintf(stderr, "Internal error: BooleanCap for '%s' not found!\n",
588			capability->Label());
589		return;
590	}
591
592	const char* key = capability->Key();
593	BString name;
594	name << "pds_" << key;
595	BCheckBox* checkBox = new BCheckBox(name.String(), capability->Label(),
596		NULL);
597
598	bool value = booleanCap->DefaultValue();
599	if (fJobData->Settings().HasBoolean(key))
600		value = fJobData->Settings().GetBoolean(key);
601	if (value)
602		checkBox->SetValue(B_CONTROL_ON);
603
604	gridLayout->AddView(checkBox, 0, row, 2);
605	row ++;
606
607	fDriverSpecificCheckBoxes[capability->Key()] = checkBox;
608}
609
610
611void
612JobSetupView::AddIntSlider(const DriverSpecificCap* capability,
613	BGridLayout* gridLayout, int& row)
614{
615	PrinterCap::CapID category = static_cast<PrinterCap::CapID>(
616		capability->ID());
617	const IntRangeCap* range = fPrinterCap->FindIntRangeCap(category);
618	if (range == NULL) {
619		fprintf(stderr, "Internal error: IntRangeCap for '%s' not found!\n",
620			capability->Label());
621		return;
622	}
623
624	const char* label = capability->Label();
625	const char* key = capability->Key();
626	BString name;
627	name << "pds_" << key;
628	BMessage* message = new BMessage(kMsgIntSliderChanged);
629	message->AddInt32(kCategoryID, category);
630	BSlider* slider = new BSlider(name.String(), label,
631		message, 0, 1000, B_HORIZONTAL);
632	slider->SetModificationMessage(new BMessage(*message));
633	slider->SetTarget(this);
634
635	int32 value = range->DefaultValue();
636	if (fJobData->Settings().HasInt(key))
637		value = fJobData->Settings().GetInt(key);
638	float position = (value - range->Lower()) /
639		(range->Upper() - range->Lower());
640	slider->SetPosition(position);
641
642	gridLayout->AddView(slider, 0, row, 2);
643	row ++;
644
645	IntRange intRange(label, key, range, slider);
646	fDriverSpecificIntSliders[category] = intRange;
647	intRange.UpdateLabel();
648}
649
650
651void
652JobSetupView::AddDoubleSlider(const DriverSpecificCap* capability,
653	BGridLayout* gridLayout, int& row)
654{
655	PrinterCap::CapID category = static_cast<PrinterCap::CapID>(
656		capability->ID());
657	const DoubleRangeCap* range = fPrinterCap->FindDoubleRangeCap(category);
658	if (range == NULL) {
659		fprintf(stderr, "Internal error: DoubleRangeCap for '%s' not found!\n",
660			capability->Label());
661		return;
662	}
663
664	const char* label = capability->Label();
665	const char* key = capability->Key();
666	BString name;
667	name << "pds_" << key;
668	BMessage* message = new BMessage(kMsgDoubleSliderChanged);
669	message->AddInt32(kCategoryID, category);
670	BSlider* slider = new BSlider(name.String(), label,
671		message, 0, 1000, B_HORIZONTAL);
672	slider->SetModificationMessage(new BMessage(*message));
673	slider->SetTarget(this);
674
675	double value = range->DefaultValue();
676	if (fJobData->Settings().HasDouble(key))
677		value = fJobData->Settings().GetDouble(key);
678	float position = static_cast<float>((value - range->Lower()) /
679		(range->Upper() - range->Lower()));
680	slider->SetPosition(position);
681
682	gridLayout->AddView(slider, 0, row, 2);
683	row ++;
684
685	DoubleRange doubleRange(label, key, range, slider);
686	fDriverSpecificDoubleSliders[category] = doubleRange;
687	doubleRange.UpdateLabel();
688}
689
690
691string
692JobSetupView::GetDriverSpecificValue(PrinterCap::CapID category,
693	const char* key)
694{
695	if (fJobData->Settings().HasString(key))
696		return fJobData->Settings().GetString(key);
697
698	const EnumCap* defaultCapability = fPrinterCap->GetDefaultCap(category);
699	return defaultCapability->fKey;
700}
701
702
703template<typename Predicate>
704void
705JobSetupView::FillCapabilityMenu(BPopUpMenu* menu, uint32 message,
706	const BaseCap** capabilities, int count, Predicate& predicate)
707{
708	bool marked = false;
709
710	BMenuItem* firstItem = NULL;
711	BMenuItem* defaultItem = NULL;
712	BMenuItem* item = NULL;
713	while (count--) {
714		const EnumCap* capability = dynamic_cast<const EnumCap*>(*capabilities);
715		if (message != kMsgNone)
716			item = new BMenuItem(capability->fLabel.c_str(),
717				new BMessage(message));
718		else
719			item = new BMenuItem(capability->fLabel.c_str(), NULL);
720
721		menu->AddItem(item);
722
723		if (firstItem == NULL)
724			firstItem = item;
725
726		if (capability->fIsDefault)
727			defaultItem = item;
728
729
730		if (predicate(capability)) {
731			item->SetMarked(true);
732			marked = true;
733		}
734
735		capabilities++;
736	}
737
738	if (marked)
739		return;
740
741	if (defaultItem != NULL)
742		defaultItem->SetMarked(true);
743	else if (firstItem != NULL)
744		firstItem->SetMarked(true);
745}
746
747
748void
749JobSetupView::FillCapabilityMenu(BPopUpMenu* menu, uint32 message,
750	PrinterCap::CapID category, int id)
751{
752	PrinterCap::IDPredicate predicate(id);
753	int count = fPrinterCap->CountCap(category);
754	const BaseCap **capabilities = fPrinterCap->GetCaps(category);
755	FillCapabilityMenu(menu, message, capabilities, count, predicate);
756}
757
758
759void
760JobSetupView::FillCapabilityMenu(BPopUpMenu* menu, uint32 message,
761	const BaseCap** capabilities, int count, int id)
762{
763	PrinterCap::IDPredicate predicate(id);
764	FillCapabilityMenu(menu, message, capabilities, count, predicate);
765}
766
767
768int
769JobSetupView::GetID(const BaseCap** capabilities, int count, const char* label,
770	int defaultValue)
771{
772	while (count--) {
773		const EnumCap* capability =
774			dynamic_cast<const EnumCap*>(*capabilities);
775		if (capability == NULL)
776			break;
777
778		if (capability->fLabel == label)
779			return capability->ID();
780	}
781	return defaultValue;
782}
783
784
785void
786JobSetupView::UpdateButtonEnabledState()
787{
788	bool pageRangeEnabled = fAll->Value() != B_CONTROL_ON;
789	fFromPage->SetEnabled(pageRangeEnabled);
790	fToPage->SetEnabled(pageRangeEnabled);
791
792	bool pageSelectionEnabled = fDuplex == NULL ||
793		fDuplex->Value() != B_CONTROL_ON;
794	fAllPages->SetEnabled(pageSelectionEnabled);
795	fOddNumberedPages->SetEnabled(pageSelectionEnabled);
796	fEvenNumberedPages->SetEnabled(pageSelectionEnabled);
797}
798
799
800void
801JobSetupView::MessageReceived(BMessage* message)
802{
803	switch (message->what) {
804	case kMsgRangeAll:
805	case kMsgRangeSelection:
806	case kMsgDuplexChanged:
807		UpdateButtonEnabledState();
808		break;
809
810	case kMsgQuality:
811		UpdateHalftonePreview();
812		break;
813
814	case kMsgCollateChanged:
815		fPages->SetCollate(fCollate->Value() == B_CONTROL_ON);
816		break;
817
818	case kMsgReverseChanged:
819		fPages->SetReverse(fReverse->Value() == B_CONTROL_ON);
820		break;
821
822	case kMsgIntSliderChanged:
823		UpdateIntSlider(message);
824		break;
825
826	case kMsgDoubleSliderChanged:
827		UpdateDoubleSlider(message);
828		break;
829	}
830}
831
832
833void
834JobSetupView::UpdateHalftonePreview()
835{
836	if (!IsHalftoneConfigurationNeeded())
837		return;
838
839	fHalftone->Preview(Gamma(), InkDensity(), DitherType(),
840		Color() != JobData::kMonochrome);
841}
842
843
844void
845JobSetupView::UpdateIntSlider(BMessage* message)
846{
847	int32 id;
848	if (message->FindInt32(kCategoryID, &id) != B_OK)
849		return;
850	PrinterCap::CapID capID = static_cast<PrinterCap::CapID>(id);
851	fDriverSpecificIntSliders[capID].UpdateLabel();
852}
853
854
855void
856JobSetupView::UpdateDoubleSlider(BMessage* message)
857{
858	int32 id;
859	if (message->FindInt32(kCategoryID, &id) != B_OK)
860		return;
861	PrinterCap::CapID capID = static_cast<PrinterCap::CapID>(id);
862	fDriverSpecificDoubleSliders[capID].UpdateLabel();
863}
864
865
866JobData::Color
867JobSetupView::Color()
868{
869	const char *label = fColorType->FindMarked()->Label();
870	const BaseCap* capability = fPrinterCap->FindCap(PrinterCap::kColor, label);
871	if (capability == NULL)
872		return JobData::kMonochrome;
873
874	const ColorCap* colorCap = static_cast<const ColorCap*>(capability);
875	return colorCap->fColor;
876}
877
878
879Halftone::DitherType
880JobSetupView::DitherType()
881{
882	const char *label = fDitherType->FindMarked()->Label();
883	int id = GetID(gDitherTypes, sizeof(gDitherTypes) / sizeof(gDitherTypes[0]),
884		label, Halftone::kTypeFloydSteinberg);
885	return static_cast<Halftone::DitherType>(id);
886}
887
888float
889JobSetupView::Gamma()
890{
891	const float value = (float)fGamma->Value();
892	return pow(2.0, value / 100.0);
893}
894
895
896float
897JobSetupView::InkDensity()
898{
899	const float value = (float)(127 - fInkDensity->Value());
900	return value;
901}
902
903
904JobData::PaperSource
905JobSetupView::PaperSource()
906{
907	const char *label = fPaperFeed->FindMarked()->Label();
908	const BaseCap* capability = fPrinterCap->FindCap(PrinterCap::kPaperSource,
909		label);
910
911	if (capability == NULL)
912		capability = fPrinterCap->GetDefaultCap(PrinterCap::kPaperSource);
913	return static_cast<const PaperSourceCap*>(capability)->fPaperSource;
914
915}
916
917bool
918JobSetupView::UpdateJobData()
919{
920	fJobData->SetShowPreview(fPreview->Value() == B_CONTROL_ON);
921	fJobData->SetColor(Color());
922	if (IsHalftoneConfigurationNeeded()) {
923		fJobData->SetGamma(Gamma());
924		fJobData->SetInkDensity(InkDensity());
925		fJobData->SetDitherType(DitherType());
926	}
927
928	int first_page;
929	int last_page;
930
931	if (B_CONTROL_ON == fAll->Value()) {
932		first_page = 1;
933		last_page  = -1;
934	} else {
935		first_page = atoi(fFromPage->Text());
936		last_page  = atoi(fToPage->Text());
937	}
938
939	fJobData->SetFirstPage(first_page);
940	fJobData->SetLastPage(last_page);
941
942	fJobData->SetPaperSource(PaperSource());
943
944	fJobData->SetNup(GetID(gNups, sizeof(gNups) / sizeof(gNups[0]),
945		fNup->FindMarked()->Label(), 1));
946
947	if (fPrinterCap->Supports(PrinterCap::kPrintStyle)) {
948		fJobData->SetPrintStyle((B_CONTROL_ON == fDuplex->Value())
949			? JobData::kDuplex : JobData::kSimplex);
950	}
951
952	fJobData->SetCopies(atoi(fCopies->Text()));
953
954	fJobData->SetCollate(B_CONTROL_ON == fCollate->Value());
955	fJobData->SetReverse(B_CONTROL_ON == fReverse->Value());
956
957	JobData::PageSelection pageSelection = JobData::kAllPages;
958	if (fOddNumberedPages->Value() == B_CONTROL_ON)
959		pageSelection = JobData::kOddNumberedPages;
960	if (fEvenNumberedPages->Value() == B_CONTROL_ON)
961		pageSelection = JobData::kEvenNumberedPages;
962	fJobData->SetPageSelection(pageSelection);
963
964	{
965		std::map<PrinterCap::CapID, BPopUpMenu*>::iterator it =
966			fDriverSpecificPopUpMenus.begin();
967		for(; it != fDriverSpecificPopUpMenus.end(); it++) {
968			PrinterCap::CapID category = it->first;
969			BPopUpMenu* popUpMenu = it->second;
970			const char* key = fPrinterCap->FindCap(
971				PrinterCap::kDriverSpecificCapabilities, (int)category)->Key();
972			const char* label = popUpMenu->FindMarked()->Label();
973			const char* value = static_cast<const EnumCap*>(fPrinterCap->
974				FindCap(category, label))->Key();
975			fJobData->Settings().SetString(key, value);
976		}
977	}
978
979	{
980		std::map<string, BCheckBox*>::iterator it =
981			fDriverSpecificCheckBoxes.begin();
982		for(; it != fDriverSpecificCheckBoxes.end(); it++) {
983			const char* key = it->first.c_str();
984			BCheckBox* checkBox = it->second;
985			bool value = checkBox->Value() == B_CONTROL_ON;
986			fJobData->Settings().SetBoolean(key, value);
987		}
988	}
989
990	{
991		std::map<PrinterCap::CapID, IntRange>::iterator it =
992			fDriverSpecificIntSliders.begin();
993		for(; it != fDriverSpecificIntSliders.end(); it++) {
994			IntRange& range = it->second;
995			fJobData->Settings().SetInt(range.Key(), range.Value());
996		}
997	}
998
999	{
1000		std::map<PrinterCap::CapID, DoubleRange>::iterator it =
1001			fDriverSpecificDoubleSliders.begin();
1002		for(; it != fDriverSpecificDoubleSliders.end(); it++) {
1003			DoubleRange& range = it->second;
1004			fJobData->Settings().SetDouble(range.Key(), range.Value());
1005		}
1006	}
1007
1008	fJobData->Save();
1009	return true;
1010}
1011
1012
1013JobSetupDlg::JobSetupDlg(JobData* jobData, PrinterData* printerData,
1014	const PrinterCap* printerCap)
1015	:
1016	DialogWindow(BRect(100, 100, 200, 200), "Print job setup",
1017		B_TITLED_WINDOW_LOOK, B_MODAL_APP_WINDOW_FEEL,
1018		B_NOT_RESIZABLE | B_NOT_MINIMIZABLE | B_NOT_ZOOMABLE
1019			| B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS
1020			| B_CLOSE_ON_ESCAPE)
1021{
1022	SetResult(B_ERROR);
1023	AddShortcut('W', B_COMMAND_KEY, new BMessage(B_QUIT_REQUESTED));
1024
1025	fJobSetup = new JobSetupView(jobData, printerData, printerCap);
1026	SetLayout(new BGroupLayout(B_VERTICAL));
1027	AddChild(BGroupLayoutBuilder(B_VERTICAL, 0)
1028		.Add(fJobSetup)
1029		.SetInsets(10, 10, 10, 10)
1030	);
1031}
1032
1033
1034void
1035JobSetupDlg::MessageReceived(BMessage* message)
1036{
1037	switch (message->what) {
1038	case kMsgOK:
1039		fJobSetup->UpdateJobData();
1040		SetResult(B_NO_ERROR);
1041		PostMessage(B_QUIT_REQUESTED);
1042		break;
1043
1044	case kMsgCancel:
1045		PostMessage(B_QUIT_REQUESTED);
1046		break;
1047
1048	default:
1049		DialogWindow::MessageReceived(message);
1050		break;
1051	}
1052}
1053