xref: /haiku/src/libs/print/libprint/JobSetupDlg.cpp (revision a3e794ae459fec76826407f8ba8c94cd3535f128)
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 JobSetupView::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 
167 BRadioButton*
168 JobSetupView::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 
179 void
180 JobSetupView::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 
193 void
194 JobSetupView::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 
452 bool
453 JobSetupView::IsHalftoneConfigurationNeeded()
454 {
455 	return fPrinterCap->Supports(PrinterCap::kHalftone);
456 }
457 
458 
459 void
460 JobSetupView::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 
508 void
509 JobSetupView::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 
542 void
543 JobSetupView::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 
579 void
580 JobSetupView::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 
611 void
612 JobSetupView::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 
651 void
652 JobSetupView::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 
691 string
692 JobSetupView::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 
703 template<typename Predicate>
704 void
705 JobSetupView::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 
748 void
749 JobSetupView::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 
759 void
760 JobSetupView::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 
768 int
769 JobSetupView::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 
785 void
786 JobSetupView::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 
800 void
801 JobSetupView::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 
833 void
834 JobSetupView::UpdateHalftonePreview()
835 {
836 	if (!IsHalftoneConfigurationNeeded())
837 		return;
838 
839 	fHalftone->Preview(Gamma(), InkDensity(), DitherType(),
840 		Color() != JobData::kMonochrome);
841 }
842 
843 
844 void
845 JobSetupView::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 
855 void
856 JobSetupView::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 
866 JobData::Color
867 JobSetupView::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 
879 Halftone::DitherType
880 JobSetupView::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 
888 float
889 JobSetupView::Gamma()
890 {
891 	const float value = (float)fGamma->Value();
892 	return pow(2.0, value / 100.0);
893 }
894 
895 
896 float
897 JobSetupView::InkDensity()
898 {
899 	const float value = (float)(127 - fInkDensity->Value());
900 	return value;
901 }
902 
903 
904 JobData::PaperSource
905 JobSetupView::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 
917 bool
918 JobSetupView::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 
1013 JobSetupDlg::JobSetupDlg(JobData* jobData, PrinterData* printerData,
1014 	const PrinterCap* printerCap)
1015 	:
1016 	DialogWindow(BRect(100, 100, 200, 200), "PrintJob 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 
1034 void
1035 JobSetupDlg::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