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