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
25 const float kLeftMargin = 3.0;
26 const float kRightMargin = 3.0;
27 const float kTopMargin = 3.0;
28 const float kBottomMargin = 3.0;
29
30 // space between views
31 const float kHorizontalSpace = 8.0;
32 const float kVerticalSpace = 8.0;
33
34 // Message what values
35 const uint32 kMsgBooleanChanged = 'bool';
36 const uint32 kMsgStringChanged = 'strc';
37
38 #include <stdio.h>
39
40 class DefaultValueExtractor : public StatementListVisitor
41 {
42 BMessage fDefaultValues;
43
44 public:
DoDefault(Statement * statement)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
GetDefaultValues()54 const BMessage& GetDefaultValues()
55 {
56 return fDefaultValues;
57 }
58 };
59
60 class CategoryBuilder : public StatementListVisitor
61 {
62 BOutlineListView* fCategories;
63
64 public:
CategoryBuilder(BOutlineListView * categories)65 CategoryBuilder(BOutlineListView* categories)
66 : fCategories(categories)
67 {}
68
AddStatement(const char * text,Statement * statement)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
BeginGroup(GroupStatement * group)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 /*
95 DetailsBuilder adds views to the details view and sets the
96 value to the one specified in the settings.
97
98 The 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 */
106 class 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
122 public:
123 DetailsBuilder(BView* parent, BView* details, BRect bounds, Statement* group, const BMessage& settings);
124
GetBounds()125 BRect GetBounds() { return fBounds; }
126
127 void Visit(StatementList* list);
128
129 void DoValue(Statement* statement);
130 };
131
AddView(BView * view)132 void 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
DetailsBuilder(BView * parent,BView * details,BRect bounds,Statement * group,const BMessage & settings)147 DetailsBuilder::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
Visit(StatementList * list)186 void DetailsBuilder::Visit(StatementList* list)
187 {
188 if (fKeyword == NULL) return;
189
190 StatementListVisitor::Visit(list);
191 }
192
GetMessage(uint32 what,const char * option)193 BMessage* 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
DoValue(Statement * statement)204 void 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
247 class PPDBuilder : public StatementListVisitor
248 {
249 BView* fParent;
250 BView* fView;
251 BRect fBounds;
252 BMessage& fSettings;
253 BList fNestedBoxes;
254
IsTop()255 bool IsTop()
256 {
257 return fNestedBoxes.CountItems() == 0;
258 }
259
Push(BView * view)260 void Push(BView* view)
261 {
262 fNestedBoxes.AddItem(view);
263 }
264
Pop()265 void Pop()
266 {
267 fNestedBoxes.RemoveItem((int32)fNestedBoxes.CountItems()-1);
268 }
269
GetView()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
GetControlBounds()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
IsUIGroup(GroupStatement * group)299 bool IsUIGroup(GroupStatement* group)
300 {
301 return group->IsUIGroup() || group->IsJCL();
302 }
303
UpdateParentHeight(float height)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
314 public:
PPDBuilder(BView * parent,BView * view,BMessage & settings)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
GetBounds()328 BRect GetBounds()
329 {
330 return BRect(0, 0, fView->Bounds().Width(), fBounds.top);
331 }
332
AddUIGroup(const char * text,Statement * statement)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
OpenGroup(const char * text)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
CloseGroup()358 void CloseGroup()
359 {
360 if (!IsTop()) {
361 BView* box = GetView();
362 Pop();
363 UpdateParentHeight(box->Bounds().Height());
364 }
365 }
366
BeginGroup(GroupStatement * group)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
EndGroup(GroupStatement * group)386 void EndGroup(GroupStatement* group)
387 {
388 if (!IsUIGroup(group)) {
389 CloseGroup();
390 }
391 }
392 };
393
PPDConfigView(BRect bounds,const char * name,uint32 resizeMask,uint32 flags)394 PPDConfigView::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
SetScrollBar(BScrollBar * scroller,float contents,float client)421 void 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
FillCategories()435 void 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
FillDetails(Statement * statement)447 void 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
BooleanChanged(BMessage * msg)466 void 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
StringChanged(BMessage * msg)482 void 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
MessageReceived(BMessage * msg)492 void 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
SetupSettings(const BMessage & currentSettings)504 void 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
Set(const char * file,const BMessage & currentSettings)525 void 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