1/*
2 * Copyright 2008, Haiku.
3 * Distributed under the terms of the MIT license.
4 *
5 * Authors:
6 *		Michael Pfeiffer <laplace@users.sourceforge.net>
7 */
8
9#include "PPDConfigView.h"
10#include "PPDParser.h"
11#include "StatementListVisitor.h"
12#include "UIUtils.h"
13
14#include <Box.h>
15#include <CheckBox.h>
16#include <Menu.h>
17#include <MenuField.h>
18#include <MenuItem.h>
19#include <RadioButton.h>
20#include <ScrollView.h>
21#include <StringView.h>
22#include <Window.h>
23
24// margin
25const float kLeftMargin = 3.0;
26const float kRightMargin = 3.0;
27const float kTopMargin = 3.0;
28const float kBottomMargin = 3.0;
29
30// space between views
31const float kHorizontalSpace = 8.0;
32const float kVerticalSpace = 8.0;
33
34// Message what values
35const uint32 kMsgBooleanChanged   = 'bool';
36const uint32 kMsgStringChanged    = 'strc';
37
38#include <stdio.h>
39
40class DefaultValueExtractor : public StatementListVisitor
41{
42	BMessage fDefaultValues;
43
44public:
45	void DoDefault(Statement* statement)
46	{
47		const char* keyword = statement->GetKeywordString();
48		const char* value = statement->GetValueString();
49		if (keyword != NULL && value != NULL) {
50			fDefaultValues.AddString(keyword, value);
51		}
52	}
53
54	const BMessage& GetDefaultValues()
55	{
56		return fDefaultValues;
57	}
58};
59
60class CategoryBuilder : public StatementListVisitor
61{
62	BOutlineListView* fCategories;
63
64public:
65	CategoryBuilder(BOutlineListView* categories)
66		: fCategories(categories)
67	{}
68
69	void AddStatement(const char* text, Statement* statement)
70	{
71		if (text != NULL) {
72			BStringItem* item = new CategoryItem(text, statement, GetLevel());
73			fCategories->AddItem(item);
74		}
75	}
76
77	void BeginGroup(GroupStatement* group)
78	{
79		const char* translation = group->GetGroupTranslation();
80		const char* name = group->GetGroupName();
81
82		const char* text = NULL;
83		if (translation != NULL) {
84			text = translation;
85		} else {
86			text = name;
87		}
88
89		AddStatement(text, group->GetStatement());
90	}
91};
92
93
94/*
95DetailsBuilder adds views to the details view and sets the
96value to the one specified in the settings.
97
98The group type determines the view to be used for user input:
99 Type     Input
100 ---------------------
101 Boolean  BRadioButton
102 PickOne  BMenuField
103 PickMany BCheckBox
104 Unknown  BCheckBox
105*/
106class DetailsBuilder : public StatementListVisitor
107{
108	BView*          fParent;
109	BView*          fDetails;
110	BRect           fBounds;
111	const char*     fKeyword;
112	const char*     fValue;
113	GroupStatement  fGroup;
114	BMenu*          fMenu;
115	BMenuField*     fMenuField;
116	const BMessage& fSettings;
117
118	void AddView(BView* view);
119
120	BMessage* GetMessage(uint32 what, const char* option);
121
122public:
123	DetailsBuilder(BView* parent, BView* details, BRect bounds, Statement* group, const BMessage& settings);
124
125	BRect GetBounds() { return fBounds; }
126
127	void Visit(StatementList* list);
128
129	void DoValue(Statement* statement);
130};
131
132void DetailsBuilder::AddView(BView* view)
133{
134	if (view != NULL) {
135		fDetails->AddChild(view);
136		view->ResizeToPreferred();
137
138		fBounds.OffsetBy(0, view->Bounds().Height()+1);
139
140		BControl* control = dynamic_cast<BControl*>(view);
141		if (control != NULL) {
142			control->SetTarget(fParent);
143		}
144	}
145}
146
147DetailsBuilder::DetailsBuilder(BView* parent, BView* details, BRect bounds, Statement* group, const BMessage& settings)
148	: fParent(parent)
149	, fDetails(details)
150	, fBounds(bounds)
151	, fGroup(group)
152	, fMenu(NULL)
153	, fMenuField(NULL)
154	, fSettings(settings)
155{
156	fKeyword = fGroup.GetGroupName();
157
158	if (fKeyword == NULL) return;
159
160	fValue = settings.FindString(fKeyword);
161
162	const char* label = fGroup.GetGroupTranslation();
163	if (label == NULL) {
164		label = fKeyword;
165	}
166
167	BView* view = NULL;
168	if (fGroup.GetType() == GroupStatement::kPickOne) {
169		fMenu = new BMenu("<pick one>");
170		fMenu->SetRadioMode(true);
171		fMenu->SetLabelFromMarked(true);
172		fMenuField = new BMenuField(fBounds, "menuField", label, fMenu);
173		view = fMenuField;
174	} else if (fGroup.GetType() == GroupStatement::kBoolean) {
175		BMessage* message = GetMessage(kMsgBooleanChanged, "");
176		BCheckBox* cb = new BCheckBox(fBounds, "", label, message);
177		view = cb;
178		cb->SetValue((fValue != NULL && strcmp(fValue, "True") == 0)
179			? B_CONTROL_ON
180			: B_CONTROL_OFF);
181	}
182
183	AddView(view);
184}
185
186void DetailsBuilder::Visit(StatementList* list)
187{
188	if (fKeyword == NULL) return;
189
190	StatementListVisitor::Visit(list);
191}
192
193BMessage* DetailsBuilder::GetMessage(uint32 what, const char* option)
194{
195	BMessage* message = new BMessage(what);
196	message->AddString("keyword", fKeyword);
197
198	if (option != NULL) {
199		message->AddString("option", option);
200	}
201	return message;
202}
203
204void DetailsBuilder::DoValue(Statement* statement)
205{
206	if (GetLevel() != 0) return;
207	if (strcmp(fKeyword, statement->GetKeywordString()) != 0) return;
208
209	const char* text = NULL;
210	const char* option = statement->GetOptionString();
211	if (statement->GetTranslationString() != NULL) {
212		text = statement->GetTranslationString();
213	} else if (option != NULL) {
214		text = option;
215	}
216
217	if (text == NULL) return;
218
219	BView* view = NULL;
220	BMessage* message = NULL;
221
222	if (fGroup.GetType() == GroupStatement::kPickMany ||
223		fGroup.GetType() == GroupStatement::kUnknown) {
224		message = GetMessage(kMsgStringChanged, option);
225		view = new BCheckBox(fBounds, "", text, message);
226	} else if (fGroup.GetType() == GroupStatement::kPickOne) {
227		message = GetMessage(kMsgStringChanged, option);
228		BMenuItem* item = new BMenuItem(text, message);
229		item->SetTarget(fParent);
230		fMenu->AddItem(item);
231		if (fValue != NULL && option != NULL && strcmp(fValue, option) == 0) {
232			item->SetMarked(true);
233		}
234	}
235
236	AddView(view);
237}
238
239#define kBoxHeight       20
240#define kBoxBottomMargin 4
241#define kBoxLeftMargin   8
242#define kBoxRightMargin  8
243
244#define kItemLeftMargin   5
245#define kItemRightMargin  5
246
247class PPDBuilder : public StatementListVisitor
248{
249	BView*    fParent;
250	BView*    fView;
251	BRect     fBounds;
252	BMessage& fSettings;
253	BList     fNestedBoxes;
254
255	bool IsTop()
256	{
257		return fNestedBoxes.CountItems() == 0;
258	}
259
260	void Push(BView* view)
261	{
262		fNestedBoxes.AddItem(view);
263	}
264
265	void Pop()
266	{
267		fNestedBoxes.RemoveItem((int32)fNestedBoxes.CountItems()-1);
268	}
269
270	BView* GetView()
271	{
272		if (IsTop()) {
273			return fView;
274		} else {
275			return (BView*)fNestedBoxes.ItemAt(fNestedBoxes.CountItems()-1);
276		}
277	}
278
279
280
281	BRect GetControlBounds()
282	{
283		if (IsTop()) {
284			BRect bounds(fBounds);
285			bounds.left  += kItemLeftMargin /** GetLevel()*/;
286			bounds.right -= kItemRightMargin /** GetLevel()*/;
287			return bounds;
288		}
289
290		BView* box = GetView();
291		BRect bounds(box->Bounds());
292		bounds.top = bounds.bottom - kBoxBottomMargin;
293		bounds.bottom = bounds.top + kBoxHeight;
294		bounds.left += kBoxLeftMargin;
295		bounds.right -= kBoxRightMargin;
296		return bounds;
297	}
298
299	bool IsUIGroup(GroupStatement* group)
300	{
301		return group->IsUIGroup() || group->IsJCL();
302	}
303
304	void UpdateParentHeight(float height)
305	{
306		if (IsTop()) {
307			fBounds.OffsetBy(0, height);
308		} else {
309			BView* parent = GetView();
310			parent->ResizeBy(0, height);
311		}
312	}
313
314public:
315	PPDBuilder(BView* parent, BView* view, BMessage& settings)
316		: fParent(parent)
317		, fView(view)
318		, fBounds(view->Bounds())
319		, fSettings(settings)
320	{
321		RemoveChildren(view);
322		fBounds.OffsetTo(0, 0);
323		fBounds.left += kLeftMargin;
324		fBounds.top += kTopMargin;
325		fBounds.right -= kRightMargin;
326	}
327
328	BRect GetBounds()
329	{
330		return BRect(0, 0, fView->Bounds().Width(), fBounds.top);
331	}
332
333	void AddUIGroup(const char* text, Statement* statement)
334	{
335		if (statement->GetChildren() == NULL) return;
336		DetailsBuilder builder(fParent, GetView(), GetControlBounds(), statement, fSettings);
337		builder.Visit(statement->GetChildren());
338
339		if (IsTop()) {
340			fBounds.OffsetTo(fBounds.left, builder.GetBounds().top);
341		} else {
342			BView* box = GetView();
343			box->ResizeTo(box->Bounds().Width(), builder.GetBounds().top + kBoxBottomMargin);
344		}
345	}
346
347	void OpenGroup(const char* text)
348	{
349		if (text != NULL) {
350			BBox* box = new BBox(GetControlBounds(), text);
351			box->SetLabel(text);
352			GetView()->AddChild(box);
353			Push(box);
354			box->ResizeTo(box->Bounds().Width(), kBoxHeight);
355		}
356	}
357
358	void CloseGroup()
359	{
360		if (!IsTop()) {
361			BView* box = GetView();
362			Pop();
363			UpdateParentHeight(box->Bounds().Height());
364		}
365	}
366
367	void BeginGroup(GroupStatement* group)
368	{
369		const char* translation = group->GetGroupTranslation();
370		const char* name = group->GetGroupName();
371
372		const char* text = NULL;
373		if (translation != NULL) {
374			text = translation;
375		} else {
376			text = name;
377		}
378
379		if (IsUIGroup(group)) {
380			AddUIGroup(text, group->GetStatement());
381		} else {
382			OpenGroup(text);
383		}
384	}
385
386	void EndGroup(GroupStatement* group)
387	{
388		if (!IsUIGroup(group)) {
389			CloseGroup();
390		}
391	}
392};
393
394PPDConfigView::PPDConfigView(BRect bounds, const char *name, uint32 resizeMask, uint32 flags)
395	: BView(bounds, name, resizeMask, flags)
396	, fPPD(NULL)
397{
398	// add category outline list view
399	bounds.OffsetTo(0, 0);
400	BRect listBounds(bounds.left + kLeftMargin, bounds.top + kTopMargin,
401		bounds.right - kHorizontalSpace, bounds.bottom - kBottomMargin);
402	listBounds.right -= B_V_SCROLL_BAR_WIDTH;
403	listBounds.bottom -= B_H_SCROLL_BAR_HEIGHT;
404
405	BStringView* label = new BStringView(listBounds, "printer-settings", "Printer Settings:");
406	AddChild(label);
407	label->ResizeToPreferred();
408
409	listBounds.top += label->Bounds().bottom + 5;
410
411	// add details view
412	fDetails = new BView(listBounds, "details", B_FOLLOW_ALL_SIDES, B_WILL_DRAW);
413	fDetails->SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
414
415	BScrollView* scrollView = new BScrollView("details-scroll-view",
416		fDetails, B_FOLLOW_ALL_SIDES, 0, true, true);
417
418	AddChild(scrollView);
419}
420
421void SetScrollBar(BScrollBar* scroller, float contents, float client)
422{
423	if (scroller != NULL) {
424		float extent = contents - client;
425		if (extent >= 0) {
426			scroller->SetRange(0, extent);
427			scroller->SetProportion(1-extent / contents);
428			scroller->SetSteps(20, client);
429		} else {
430			scroller->SetRange(0, 0);
431		}
432	}
433}
434
435void PPDConfigView::FillCategories()
436{
437	if (fPPD == NULL) return;
438
439	PPDBuilder builder(this, fDetails, fSettings);
440	builder.Visit(fPPD);
441	BScrollBar* scroller = fDetails->ScrollBar(B_VERTICAL);
442	SetScrollBar(scroller, builder.GetBounds().Height(), fDetails->Bounds().Height());
443	scroller = fDetails->ScrollBar(B_HORIZONTAL);
444	SetScrollBar(scroller, builder.GetBounds().Width(), fDetails->Bounds().Width());
445}
446
447void PPDConfigView::FillDetails(Statement* statement)
448{
449	RemoveChildren(fDetails);
450
451	if (statement == NULL) {
452		return;
453	}
454
455	StatementList* children= statement->GetChildren();
456	if (children == NULL) {
457		return;
458	}
459
460	BRect bounds(fDetails->Bounds());
461	bounds.OffsetTo(kLeftMargin, kTopMargin);
462	DetailsBuilder builder(this, fDetails, bounds, statement, fSettings);
463	builder.Visit(children);
464}
465
466void PPDConfigView::BooleanChanged(BMessage* msg)
467{
468	const char* keyword = msg->FindString("keyword");
469
470	int32 value;
471	if (msg->FindInt32("be:value", &value) == B_OK) {
472		const char* option;
473		if (value) {
474			option = "True";
475		} else {
476			option = "False";
477		}
478		fSettings.ReplaceString(keyword, option);
479	}
480}
481
482void PPDConfigView::StringChanged(BMessage* msg)
483{
484	const char* keyword = msg->FindString("keyword");
485	const char* option = msg->FindString("option");
486
487	if (keyword != NULL && keyword != NULL) {
488		fSettings.ReplaceString(keyword, option);
489	}
490}
491
492void PPDConfigView::MessageReceived(BMessage* msg)
493{
494	switch (msg->what) {
495		case kMsgBooleanChanged: BooleanChanged(msg);
496			break;
497		case kMsgStringChanged: StringChanged(msg);
498			break;
499	}
500
501	BView::MessageReceived(msg);
502}
503
504void PPDConfigView::SetupSettings(const BMessage& currentSettings)
505{
506	DefaultValueExtractor extractor;
507	extractor.Visit(fPPD);
508
509	const BMessage &defaultValues(extractor.GetDefaultValues());
510	fSettings.MakeEmpty();
511
512	char* name;
513	type_code code;
514	for (int32 index = 0; defaultValues.GetInfo(B_STRING_TYPE, index, &name, &code) == B_OK; index ++) {
515		const char* value = currentSettings.FindString(name);
516		if (value == NULL) {
517			value = defaultValues.FindString(name);
518		}
519		if (value != NULL) {
520			fSettings.AddString(name, value);
521		}
522	}
523}
524
525void PPDConfigView::Set(const char* file, const BMessage& currentSettings)
526{
527	delete fPPD;
528
529	PPDParser parser(file);
530	fPPD = parser.ParseAll();
531	if (fPPD == NULL) {
532		fprintf(stderr, "Parsing error (%s): %s\n", file, parser.GetErrorMessage());
533	}
534
535	SetupSettings(currentSettings);
536	FillCategories();
537}
538