1 // $Id: xxRoot.cc 5749 2014-10-11 19:42:10Z flaterco $
2
3 /* xxRoot XTide "root" window (control panel, top-level logic)
4
5 Copyright (C) 1998 David Flater.
6
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or
10 (at your option) any later version.
11
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include "xtide.hh"
22 #include "xxGraphMode.hh"
23 #include "xxDisclaimer.hh"
24 #include "xxReconfigurable.hh"
25 #include "xxLocationList.hh"
26 #include "xxGlobe.hh"
27 #include "xxMap.hh"
28 #include "xxHorizDialog.hh"
29 #include "xxUnsignedChooser.hh"
30 #include "xxMultiChoice.hh"
31 #include "xxErrorBox.hh"
32 #include "xxHelpBox.hh"
33 #include "xxTextMode.hh"
34
35
36 static const unsigned controlPanelInitialHeight (900U);
37
38 // Completely wild guess of how much room is needed by the window dressing on
39 // an xxHelpBox (pixels).
40 static const unsigned windowDressingWidth (32U);
41
42
errorCallback(const Dstr & errorMessage,Error::ErrType fatality)43 static void errorCallback (const Dstr &errorMessage, Error::ErrType fatality) {
44 xxroot->newErrorBox (errorMessage, fatality);
45 }
46
47
dismissCallback(Widget w unusedParameter,XtPointer client_data unusedParameter,XtPointer call_data unusedParameter)48 static void dismissCallback (Widget w unusedParameter,
49 XtPointer client_data unusedParameter,
50 XtPointer call_data unusedParameter) {
51 xxroot->unrealize();
52 }
53
54
applyCallback(Widget w unusedParameter,XtPointer client_data unusedParameter,XtPointer call_data unusedParameter)55 static void applyCallback (Widget w unusedParameter,
56 XtPointer client_data unusedParameter,
57 XtPointer call_data unusedParameter) {
58 xxroot->apply (xxRoot::justApply);
59 }
60
61
changeUserDefaults(const Settings & newUserDefaults)62 static void changeUserDefaults (const Settings &newUserDefaults) {
63 Global::settings = Settings();
64 Global::settings.applyXResources();
65 Global::settings.apply (newUserDefaults);
66 Global::settings.applyCommandLine();
67 Global::settings.fixUpDeprecatedSettings();
68 xxX::installColors();
69 }
70
71
apply(ApplyProtocol protocol)72 void xxRoot::apply (ApplyProtocol protocol) {
73
74 // Build new settings for ~/.xtide.xml.
75 Settings newUserDefaults;
76 newUserDefaults.nullify();
77
78 // There are several ways to implement this, all of which involve
79 // messy nested logic because the association between dialog classes
80 // and configurable interpretations is not simple. This way at
81 // least has no duplicated code and at most two redundant
82 // operations (the failed dynamic casts).
83
84 for (BetterMap<const Dstr, xxRedrawable*>::iterator it = dialogs.begin();
85 it != dialogs.end();
86 ++it) {
87 xxRedrawable *redrawable (it->second);
88 assert (redrawable);
89 assert (newUserDefaults.count(it->first) == 1);
90 Configurable &configurable (newUserDefaults[it->first]);
91 assert (!configurable.caption.isNull());
92 assert (configurable.kind == Configurable::settingKind);
93
94 xxMultiChoice *multiChoice (dynamic_cast<xxMultiChoice*>(redrawable));
95 if (multiChoice) {
96 const unsigned choice (multiChoice->choice());
97 switch (configurable.interpretation) {
98 case Configurable::booleanInterp:
99 switch (choice) {
100 case 0:
101 configurable.isNull = false;
102 configurable.c = 'y';
103 break;
104 case 1:
105 configurable.isNull = false;
106 configurable.c = 'n';
107 break;
108 case 2:
109 break;
110 default:
111 assert (false);
112 }
113 break;
114 case Configurable::glDoubleInterp:
115 assert (choice < 14);
116 switch (choice) {
117 case 12:
118 configurable.isNull = false;
119 configurable.d = 360.0;
120 break;
121 case 13:
122 break;
123 default:
124 configurable.isNull = false;
125 configurable.d = (double)choice * 30.0 - 180.0;
126 break;
127 }
128 break;
129 case Configurable::unitInterp:
130 switch (choice) {
131 case 0:
132 configurable.isNull = false;
133 configurable.s = "ft";
134 break;
135 case 1:
136 configurable.isNull = false;
137 configurable.s = "m";
138 break;
139 case 2:
140 configurable.isNull = false;
141 configurable.s = "x";
142 break;
143 case 3:
144 break;
145 default:
146 assert (false);
147 }
148 break;
149 case Configurable::gsInterp:
150 switch (choice) {
151 case 0:
152 configurable.isNull = false;
153 configurable.c = 'd';
154 break;
155 case 1:
156 configurable.isNull = false;
157 configurable.c = 'l';
158 break;
159 case 2:
160 configurable.isNull = false;
161 configurable.c = 's';
162 break;
163 case 3:
164 break;
165 default:
166 assert (false);
167 }
168 break;
169 default:
170 assert (false);
171 }
172
173 } else {
174 xxUnsignedChooser *unsignedChooser (
175 dynamic_cast<xxUnsignedChooser*>(redrawable));
176 if (unsignedChooser) {
177 const unsigned choice (unsignedChooser->choice());
178 assert (configurable.interpretation == Configurable::posIntInterp);
179 if (choice != UINT_MAX) {
180 assert (choice > 0);
181 configurable.isNull = false;
182 configurable.u = choice;
183 }
184 } else {
185 xxHorizDialog *horizDialog (dynamic_cast<xxHorizDialog*>(redrawable));
186 assert (horizDialog);
187 const Dstr value (horizDialog->val());
188 switch (configurable.interpretation) {
189 case Configurable::posDoubleInterp:
190 case Configurable::nonnegativeDoubleInterp:
191 case Configurable::opacityDoubleInterp:
192 {
193 Global::GetDoubleReturn gdr (Global::getDouble (
194 value,
195 configurable.interpretation,
196 configurable.d));
197 switch (gdr) {
198 case Global::inputNotOK:
199 return;
200 case Global::emptyInput:
201 break;
202 case Global::inputOK:
203 configurable.isNull = false;
204 break;
205 default:
206 assert (false);
207 }
208 }
209 break;
210 case Configurable::textInterp:
211 if (value.length()) {
212 configurable.isNull = false;
213 configurable.s = value;
214 }
215 break;
216 case Configurable::colorInterp:
217 if (value.length()) {
218 // Sanity check color
219 uint8_t r, g, b;
220 if (!(Colors::parseColor (value, r, g, b, Error::nonfatal)))
221 return;
222 configurable.isNull = false;
223 configurable.s = value;
224 }
225 break;
226 case Configurable::timeFormatInterp:
227 if (value.length()) {
228 // Sanity check strftime format string
229 if (value.strchr('"') != -1 ||
230 value.strchr('>') != -1 ||
231 value.strchr('<') != -1) {
232 Global::barf (Error::XMLPARSE,
233 "Please don't use nasty characters like \", >, and < in your date/time formats.",
234 Error::nonfatal);
235 return;
236 }
237 configurable.isNull = false;
238 configurable.s = value;
239 }
240 break;
241 case Configurable::eventMaskInterp:
242 if (value.length()) {
243 if (!Global::isValidEventMask (value)) {
244 Global::barf (Error::BAD_EVENTMASK, Error::nonfatal);
245 return;
246 }
247 configurable.isNull = false;
248 configurable.s = value;
249 }
250 break;
251 default:
252 assert (false);
253 }
254 }
255 }
256 }
257
258 changeUserDefaults (newUserDefaults);
259 globalRedraw();
260
261 // Provided that the world did not explode as a result of applying those
262 // settings, only then might they be saved.
263 if (protocol == applyAndSave)
264 newUserDefaults.save();
265 }
266
267
saveCallback(Widget w unusedParameter,XtPointer client_data unusedParameter,XtPointer call_data unusedParameter)268 static void saveCallback (Widget w unusedParameter,
269 XtPointer client_data unusedParameter,
270 XtPointer call_data unusedParameter) {
271 xxroot->apply (xxRoot::applyAndSave);
272 }
273
274
helpCallback(Widget w unusedParameter,XtPointer client_data unusedParameter,XtPointer call_data unusedParameter)275 static void helpCallback (Widget w unusedParameter,
276 XtPointer client_data unusedParameter,
277 XtPointer call_data unusedParameter) {
278 Dstr helpstring ("\
279 XTide Control Panel\n\
280 \n\
281 The Control Panel is used to change global XTide settings. These settings\n\
282 take precedence over compiled-in defaults and X application defaults, but\n\
283 they are overridden by settings made on the command line. Therefore, any\n\
284 settings that you make in the Control Panel will not have any visible effect\n\
285 if you have also made these settings on the command line.\n\
286 \n\
287 Some settings have dialog boxes for you to type in; others have pull-down\n\
288 menus or '+' and '-' controls. In each case, there is a way to leave the\n\
289 field blank if you want to keep the inherited settings. Either delete all\n\
290 text in the dialog box, or choose the \"(blank)\" option on the pull-down\n\
291 menu, or lay on the '-' button until the field reads \"(blank)\".\n\
292 \n\
293 The settings identified as \"default\" settings (widths, heights, and\n\
294 aspect ratio) will not affect existing windows. Widths and heights will\n\
295 affect all new windows; aspect ratio will only affect new windows that\n\
296 are created from the location chooser. (This is because the aspect is\n\
297 preserved from one tide window to any new windows that it spawns.)\n\
298 \n\
299 Use the Apply button to apply the new settings to the current XTide session\n\
300 without making them permanent. Use Save to make them permanent.\n\
301 \n\
302 If necessary, resize the Control Panel to keep all of the settings visible.\n\
303 \n\
304 About colors: When entering colors, use either standard X11 color names\n\
305 or 24-bit hex specs of the form rgb:hh/hh/hh. Do not use more or less bits;\n\
306 it will not work.\n\
307 \n\
308 About the event mask: Events to suppress (p = phase of moon, S =\n\
309 sunrise, s = sunset, M = moonrise, m = moonset), or x to suppress\n\
310 none. E.g, to suppress all sun and moon events, set eventmask to the\n\
311 value pSsMm.\n\
312 \n\
313 About date/time formats: Please refer to the man page for strftime to see\n\
314 how these formats work. Here is how to get XTide to use 24-hour time instead\n\
315 of AM/PM:\n\
316 Hour format %H\n\
317 Time format %H:%M %Z");
318 (void) xxroot->newHelpBox (helpstring);
319 }
320
321
322 // This function is passed to Settings::applyXResources.
323
324 static XrmDatabase database;
getResource(const Dstr & name,Dstr & val_out)325 static const bool getResource (const Dstr &name, Dstr &val_out) {
326 Dstr n1 ("xtide*"), n2 ("XTide*");
327 n1 += name;
328 n2 += name;
329 XrmValue xv;
330 char *str_type[20]; // Waste.
331 if (XrmGetResource (database, n1.aschar(), n2.aschar(), str_type, &xv)) {
332 val_out = (char *)xv.addr;
333 return true;
334 }
335 return false;
336 }
337
338
xxRoot(int argc,char ** argv)339 xxRoot::xxRoot (int argc, char **argv):
340 xxWindow (argc, argv),
341 popupCount(0) {
342
343 setTitle ("Control Panel");
344
345 // Switches recognized by X11 are removed from the command line by
346 // the xxWidget constructor.
347 database = XtScreenDatabase (xxX::screen);
348 Global::settings.applyXResources (getResource);
349 Global::settings.applyUserDefaults();
350 Global::settings.applyCommandLine (argc, argv);
351 Global::settings.fixUpDeprecatedSettings();
352 xxX::connectPostSettingsHook (popup->widget());
353
354 Global::setErrorCallback (&errorCallback);
355
356 {
357 // Can't do this until after the settings have been processed.
358 Arg args[2] = {
359 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
360 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]}
361 };
362 XtSetValues (popup->widget(), args, 2);
363 }{
364 // Slightly different than what xxWindow would provide.
365 Arg formArgs[3] = {
366 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
367 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
368 {XtNvSpace, (XtArgVal)0}
369 };
370 Widget formWidget = xxX::createXtWidget ("", formWidgetClass,
371 popup->widget(), formArgs, 3);
372 container = xxX::wrap (formWidget);
373 }{
374 Arg labelArgs[6] = {
375 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
376 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
377 {(char*)XtNleft, (XtArgVal)XawChainLeft},
378 {(char*)XtNright, (XtArgVal)XawChainLeft},
379 {(char*)XtNtop, (XtArgVal)XawChainTop},
380 {(char*)XtNbottom, (XtArgVal)XawChainTop}
381 };
382 Widget labelWidget = xxX::createXtWidget (
383 "-------------- XTide Control Panel --------------",
384 labelWidgetClass, container->widget(), labelArgs, 6);
385 label = new xxWidget (labelWidget);
386 }{
387 Arg vpArgs[10] = {
388 {XtNheight, (XtArgVal)controlPanelInitialHeight},
389 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
390 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
391 {XtNallowVert, (XtArgVal)1},
392 {XtNforceBars, (XtArgVal)1},
393 {(char*)XtNfromVert, (XtArgVal)label->widget()},
394 {(char*)XtNleft, (XtArgVal)XawChainLeft},
395 {(char*)XtNright, (XtArgVal)XawChainRight},
396 {(char*)XtNtop, (XtArgVal)XawChainTop},
397 {(char*)XtNbottom, (XtArgVal)XawChainBottom}
398 };
399
400 Widget viewportWidget = xxX::createXtWidget ("", viewportWidgetClass,
401 container->widget(), vpArgs, 10);
402 viewport = new xxWidget (viewportWidget);
403 xxX::fixBorder (viewportWidget);
404
405 require (scrollbarWidget = XtNameToWidget (viewportWidget, "vertical"));
406 Arg sbArgs[2] = {
407 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
408 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]}
409 };
410 XtSetValues (scrollbarWidget, sbArgs, 2);
411 setRudeness (scrollbarWidget);
412 }{
413 Arg boxArgs[4] = {
414 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
415 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
416 {XtNvSpace, (XtArgVal)0},
417 {XtNorientation, (XtArgVal)XtorientVertical}
418 };
419 Widget boxWidget = xxX::createXtWidget ("", boxWidgetClass,
420 viewport->widget(), boxArgs, 4);
421 viewBox = new xxWidget (boxWidget);
422 }
423
424 // Get current settings, but convert the deprecated ones.
425 Settings settings;
426 settings.nullify();
427 settings.applyUserDefaults();
428 settings.fixUpDeprecatedSettings();
429
430 static constString toggleChoices[] = {"Yes",
431 "No",
432 "(blank)",
433 NULL};
434
435 static constString unitsChoices[] = {"Feet",
436 "Meters",
437 "No preference",
438 "(blank)",
439 NULL};
440
441 static constString glChoices[] = {"180",
442 "150 W",
443 "120 W",
444 "90 W",
445 "60 W",
446 "30 W",
447 "0",
448 "30 E",
449 "60 E",
450 "90 E",
451 "120 E",
452 "150 E",
453 "Max stations",
454 "(blank)",
455 NULL};
456
457 static constString gsChoices[] = {"d",
458 "l",
459 "s",
460 "(blank)",
461 NULL};
462
463 // Create all of the dialogs.
464 for (ConfigurablesMap::iterator it = settings.begin();
465 it != settings.end();
466 ++it) {
467 Configurable &configurable (it->second);
468 if (!configurable.caption.isNull()) {
469 if (configurable.kind == Configurable::settingKind) {
470 switch (configurable.interpretation) {
471 case Configurable::booleanInterp:
472 {
473 unsigned firstChoice;
474 if (configurable.isNull)
475 firstChoice = 2;
476 else if (configurable.c == 'n')
477 firstChoice = 1;
478 else
479 firstChoice = 0;
480 dialogs[it->first] = new xxMultiChoice (
481 *viewBox,
482 configurable.caption.aschar(),
483 toggleChoices,
484 firstChoice);
485 }
486 break;
487 case Configurable::posIntInterp:
488 dialogs[it->first] = new xxUnsignedChooser (
489 *viewBox,
490 configurable.caption.aschar(),
491 configurable.u,
492 configurable.isNull,
493 configurable.minValue);
494 break;
495 case Configurable::posDoubleInterp:
496 case Configurable::nonnegativeDoubleInterp:
497 case Configurable::opacityDoubleInterp:
498 {
499 char temp[80];
500 if (configurable.isNull)
501 strcpy (temp, "");
502 else
503 sprintf (temp, "%0.2f", configurable.d);
504 dialogs[it->first] = new xxHorizDialog (
505 *viewBox,
506 configurable.caption.aschar(),
507 temp);
508 }
509 break;
510 case Configurable::glDoubleInterp:
511 {
512 unsigned firstChoice = 13;
513 if (!configurable.isNull) {
514 if (configurable.d == 360.0)
515 firstChoice = 12;
516 else
517 firstChoice = (unsigned) ((configurable.d + 180.0) / 30.0);
518 }
519 dialogs[it->first] = new xxMultiChoice (
520 *viewBox,
521 configurable.caption.aschar(),
522 glChoices,
523 firstChoice);
524 }
525 break;
526 case Configurable::colorInterp:
527 case Configurable::eventMaskInterp:
528 case Configurable::timeFormatInterp:
529 case Configurable::textInterp:
530 dialogs[it->first] = new xxHorizDialog (
531 *viewBox,
532 configurable.caption.aschar(),
533 configurable.isNull ? "" : configurable.s.aschar());
534 break;
535 case Configurable::unitInterp:
536 {
537 unsigned firstChoice;
538 if (configurable.isNull)
539 firstChoice = 3;
540 else if (configurable.s == "ft")
541 firstChoice = 0;
542 else if (configurable.s == "m")
543 firstChoice = 1;
544 else
545 firstChoice = 2;
546 dialogs[it->first] = new xxMultiChoice (
547 *viewBox,
548 configurable.caption.aschar(),
549 unitsChoices,
550 firstChoice);
551 }
552 break;
553 case Configurable::gsInterp:
554 {
555 unsigned firstChoice;
556 if (configurable.isNull)
557 firstChoice = 3;
558 else if (configurable.c == 'd')
559 firstChoice = 0;
560 else if (configurable.c == 'l')
561 firstChoice = 1;
562 else
563 firstChoice = 2;
564 dialogs[it->first] = new xxMultiChoice (
565 *viewBox,
566 configurable.caption.aschar(),
567 gsChoices,
568 firstChoice);
569 }
570 break;
571 default:
572 assert (false);
573 }
574 }
575 }
576 }
577
578 {
579 Arg applyArgs[9] = {
580 {XtNvisual, (XtArgVal)xxX::visual},
581 {XtNcolormap, (XtArgVal)xxX::colormap},
582 {XtNbackground, (XtArgVal)xxX::pixels[Colors::button]},
583 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
584 {(char*)XtNfromVert, (XtArgVal)viewport->widget()},
585 {(char*)XtNleft, (XtArgVal)XawChainLeft},
586 {(char*)XtNright, (XtArgVal)XawChainLeft},
587 {(char*)XtNtop, (XtArgVal)XawChainBottom},
588 {(char*)XtNbottom, (XtArgVal)XawChainBottom}
589 };
590 Widget buttonWidget = xxX::createXtWidget ("Apply", commandWidgetClass,
591 container->widget(), applyArgs, 9);
592 XtAddCallback (buttonWidget, XtNcallback, applyCallback,
593 (XtPointer)this);
594 applyButton = new xxWidget (buttonWidget);
595 }{
596 Arg saveArgs[10] = {
597 {XtNvisual, (XtArgVal)xxX::visual},
598 {XtNcolormap, (XtArgVal)xxX::colormap},
599 {XtNbackground, (XtArgVal)xxX::pixels[Colors::button]},
600 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
601 {(char*)XtNfromVert, (XtArgVal)viewport->widget()},
602 {(char*)XtNfromHoriz, (XtArgVal)applyButton->widget()},
603 {(char*)XtNleft, (XtArgVal)XawChainLeft},
604 {(char*)XtNright, (XtArgVal)XawChainLeft},
605 {(char*)XtNtop, (XtArgVal)XawChainBottom},
606 {(char*)XtNbottom, (XtArgVal)XawChainBottom}
607 };
608 Widget buttonWidget = xxX::createXtWidget ("Save", commandWidgetClass,
609 container->widget(), saveArgs, 10);
610 XtAddCallback (buttonWidget, XtNcallback, saveCallback,
611 (XtPointer)this);
612 saveButton = new xxWidget (buttonWidget);
613 }{
614 Arg buttonArgs[10] = {
615 {XtNvisual, (XtArgVal)xxX::visual},
616 {XtNcolormap, (XtArgVal)xxX::colormap},
617 {XtNbackground, (XtArgVal)xxX::pixels[Colors::button]},
618 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
619 {(char*)XtNfromVert, (XtArgVal)viewport->widget()},
620 {(char*)XtNfromHoriz, (XtArgVal)saveButton->widget()},
621 {(char*)XtNleft, (XtArgVal)XawChainLeft},
622 {(char*)XtNright, (XtArgVal)XawChainLeft},
623 {(char*)XtNtop, (XtArgVal)XawChainBottom},
624 {(char*)XtNbottom, (XtArgVal)XawChainBottom}
625 };
626 Widget buttonWidget = xxX::createXtWidget ("Dismiss", commandWidgetClass,
627 container->widget(), buttonArgs, 10);
628 XtAddCallback (buttonWidget, XtNcallback, dismissCallback,
629 (XtPointer)this);
630 dismissButton = new xxWidget (buttonWidget);
631 }{
632 Arg buttonArgs[10] = {
633 {XtNvisual, (XtArgVal)xxX::visual},
634 {XtNcolormap, (XtArgVal)xxX::colormap},
635 {XtNbackground, (XtArgVal)xxX::pixels[Colors::button]},
636 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]},
637 {(char*)XtNfromVert, (XtArgVal)viewport->widget()},
638 {(char*)XtNfromHoriz, (XtArgVal)dismissButton->widget()},
639 {(char*)XtNleft, (XtArgVal)XawChainLeft},
640 {(char*)XtNright, (XtArgVal)XawChainLeft},
641 {(char*)XtNtop, (XtArgVal)XawChainBottom},
642 {(char*)XtNbottom, (XtArgVal)XawChainBottom}
643 };
644 Widget buttonWidget = xxX::createXtWidget ("?", commandWidgetClass,
645 container->widget(), buttonArgs, 10);
646 XtAddCallback (buttonWidget, XtNcallback, helpCallback,
647 (XtPointer)this);
648 helpButton = new xxWidget (buttonWidget);
649 }
650
651 obeyMouseWheel (popup->widget());
652 obeyMouseWheel (scrollbarWidget);
653 obeyMouseWheel (viewBox->widget());
654 }
655
656
dup()657 void xxRoot::dup() {
658 ++popupCount;
659 }
660
661
dup(xxWindow * child)662 void xxRoot::dup (xxWindow *child) {
663 dup();
664 children.insert (child);
665 }
666
667
release()668 void xxRoot::release () {
669 assert (popupCount > 0);
670 if (--popupCount == 0)
671 exit (0);
672 }
673
674
release(xxWindow * child)675 void xxRoot::release (xxWindow *child) {
676 release ();
677 children.erase (child);
678 }
679
680
globalRedraw()681 void xxRoot::globalRedraw() {
682 for (std::set<xxWindow*>::iterator it = children.begin();
683 it != children.end();
684 ++it)
685 (*it)->globalRedraw();
686
687 for (BetterMap<const Dstr, xxRedrawable*>::iterator it = dialogs.begin();
688 it != dialogs.end();
689 ++it)
690 it->second->globalRedraw();
691
692 Arg buttonArgs[2] = {
693 {XtNbackground, (XtArgVal)xxX::pixels[Colors::button]},
694 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]}
695 };
696 assert (applyButton);
697 XtSetValues (applyButton->widget(), buttonArgs, 2);
698 assert (saveButton);
699 XtSetValues (saveButton->widget(), buttonArgs, 2);
700 assert (helpButton);
701 XtSetValues (helpButton->widget(), buttonArgs, 2);
702 assert (dismissButton);
703 XtSetValues (dismissButton->widget(), buttonArgs, 2);
704
705 Arg args[2] = {
706 {XtNbackground, (XtArgVal)xxX::pixels[Colors::background]},
707 {XtNforeground, (XtArgVal)xxX::pixels[Colors::foreground]}
708 };
709 assert (label);
710 XtSetValues (label->widget(), args, 2);
711 assert (viewBox);
712 XtSetValues (viewBox->widget(), args, 2);
713 assert (viewport);
714 XtSetValues (viewport->widget(), args, 2);
715 assert (container.get());
716 XtSetValues (container->widget(), args, 2);
717 assert (popup.get());
718 XtSetValues (popup->widget(), args, 2);
719 XtSetValues (scrollbarWidget, args, 2);
720 }
721
722
run()723 void xxRoot::run() {
724 if (!Global::disclaimerDisabled()) {
725 (void) new xxDisclaimer (*popup, true);
726 handleXEvents (returnWhenIdle);
727 }
728
729 if (Global::settings["l"].isNull)
730 newChooser ();
731 else
732 commandLineWindows ();
733
734 handleXEvents (loopForever);
735 }
736
737
newHelpBox(const Dstr & help)738 xxHelpBox * const xxRoot::newHelpBox (const Dstr &help) {
739 return new xxHelpBox (*popup, help);
740 }
741
742
newGraph(Station * station,Timestamp t)743 xxGraphMode * const xxRoot::newGraph (Station *station, Timestamp t) {
744 return new xxGraphMode (*popup, station, t);
745 }
746
747
newGraph(const StationRef & stationRef)748 xxGraphMode * const xxRoot::newGraph (const StationRef &stationRef) {
749 return newGraph (stationRef.load(), (time_t)time(NULL));
750 }
751
752
newPlain(Station * station,Timestamp t)753 xxTextMode * const xxRoot::newPlain (Station *station, Timestamp t) {
754 return new xxTextMode (*popup, station, Mode::plain, t);
755 }
756
757
newRaw(Station * station,Timestamp t)758 xxTextMode * const xxRoot::newRaw (Station *station, Timestamp t) {
759 return new xxTextMode (*popup, station, Mode::raw, t);
760 }
761
762
newMediumRare(Station * station,Timestamp t)763 xxTextMode * const xxRoot::newMediumRare (Station *station, Timestamp t) {
764 return new xxTextMode (*popup, station, Mode::mediumRare, t);
765 }
766
767
newClock(Station * station,xxClock::ButtonsStyle buttonsStyle)768 xxClock * const xxRoot::newClock (Station *station,
769 xxClock::ButtonsStyle buttonsStyle) {
770 return new xxClock (*popup, station, buttonsStyle);
771 }
772
773
newClock(Station * station)774 xxClock * const xxRoot::newClock (Station *station) {
775 return newClock (station,
776 (Global::settings["cb"].c == 'n' ? xxClock::noButtons :
777 xxClock::buttons));
778 }
779
780
commandLineWindows()781 void xxRoot::commandLineWindows () {
782 Settings &settings (Global::settings);
783 Timestamp t ((time_t)time(NULL));
784 Mode::Mode mode;
785 {
786 const Configurable &m (settings["m"]);
787 if (m.isNull)
788 mode = Mode::clock;
789 else
790 mode = (Mode::Mode)m.c;
791 }{
792 const Configurable &l (settings["l"]);
793 const Configurable &ml (settings["ml"]);
794 DstrVector::const_iterator it (l.v.begin());
795 DstrVector::const_iterator stop (l.v.end());
796 while (it != stop) {
797 StationRef *stationRef (Global::stationIndex().getStationRefByName(*it));
798 if (stationRef) {
799 const Configurable &b (settings["b"]);
800 if (!b.isNull) {
801 t = Timestamp (b.s, stationRef->timezone);
802 if (t.isNull())
803 Global::cant_mktime (b.s, stationRef->timezone, Error::fatal);
804 }
805 Station *station (stationRef->load());
806
807 if (!ml.isNull) {
808 station->markLevel = ml.p;
809 if (ml.p.Units() != station->predictUnits())
810 station->markLevel.Units (station->predictUnits());
811 }
812
813 xxWindow *d (NULL);
814 if (mode == Mode::graph)
815 d = newGraph (station, t);
816 else if (mode == Mode::plain)
817 d = newPlain (station, t);
818 else if (mode == Mode::raw)
819 d = newRaw (station, t);
820 else if (mode == Mode::mediumRare)
821 d = newMediumRare (station, t);
822 else if (mode == Mode::about) {
823 d = newAbout (station);
824 delete station;
825 } else
826 d = newClock (station);
827 assert (d);
828
829 // Apply -geometry to first one only.
830 if (it == l.v.begin())
831 d->move (settings["X"].s);
832
833 } else {
834 Dstr details ("Could not find: ");
835 details += *it;
836 Global::barf (Error::STATION_NOT_FOUND, details);
837 }
838 ++it;
839 }
840 }
841 }
842
843
newChooser()844 void xxRoot::newChooser () {
845 if (Global::settings["fe"].c == 'n')
846 (void) new xxGlobe (*popup);
847 else
848 (void) new xxMap (*popup);
849 }
850
851
newMap()852 void xxRoot::newMap () {
853 (void) new xxMap (*popup);
854 }
855
856
newGlobe()857 void xxRoot::newGlobe () {
858 (void) new xxGlobe (*popup);
859 }
860
861
lineTooLong(const Dstr & line,const unsigned widthLimit)862 static bool lineTooLong (const Dstr &line, const unsigned widthLimit) {
863 return (xxX::stringWidth (xxX::monoFontStruct, line) > widthLimit);
864 }
865
866
867 // This function is not very efficient, but its use is usually avoided.
wrapLongLine(Dstr & line,const unsigned widthLimit)868 static void wrapLongLine (Dstr &line,
869 const unsigned widthLimit) {
870 Dstr whatFitsOnScreen, wrappedLine;
871 while (line.length()) {
872 whatFitsOnScreen += line[0];
873 if (lineTooLong (whatFitsOnScreen, widthLimit)) {
874 whatFitsOnScreen -= whatFitsOnScreen.length()-1;
875 if (wrappedLine.length())
876 wrappedLine += '\n';
877 wrappedLine += whatFitsOnScreen;
878 whatFitsOnScreen = (char*)NULL;
879 } else
880 line /= 1;
881 }
882 // Don't worry about eating blank lines here--if it were blank it
883 // would not have been too long in the first place.
884 if (whatFitsOnScreen.length()) {
885 if (wrappedLine.length())
886 wrappedLine += '\n';
887 wrappedLine += whatFitsOnScreen;
888 }
889 line = wrappedLine;
890 }
891
892
wrapLongLines(Dstr & verbiage)893 static void wrapLongLines (Dstr &verbiage) {
894 assert (WidthOfScreen(xxX::screen) > 0 &&
895 (unsigned)WidthOfScreen(xxX::screen) > windowDressingWidth);
896 const unsigned widthLimit (WidthOfScreen(xxX::screen) - windowDressingWidth);
897 Dstr formattedVerbiage, line;
898 verbiage.getline (line);
899 while (!line.isNull()) {
900 // Skip wrapping for short enough lines (which should be most of them)
901 if (lineTooLong (line, widthLimit))
902 wrapLongLine (line, widthLimit);
903 if (formattedVerbiage.length())
904 formattedVerbiage += '\n';
905 formattedVerbiage += line;
906 verbiage.getline (line);
907 }
908 verbiage = formattedVerbiage;
909 }
910
911
newAbout(const Station * station)912 xxHelpBox * const xxRoot::newAbout (const Station *station) {
913 assert (station);
914 Dstr verbiage;
915 station->aboutMode (verbiage, Format::text, "ISO-8859-1");
916 // If necessary, wrap text to make the pop-up fit on the screen. If this
917 // is not done, a station with long descriptive text that is not formatted
918 // with linebreaks results in a ludicrously wide window that cannot then be
919 // resized.
920 wrapLongLines (verbiage);
921 return newHelpBox (verbiage);
922 }
923
924
newAboutXTide()925 void const xxRoot::newAboutXTide () {
926 (void) new xxDisclaimer (*popup, false);
927 }
928
929
newErrorBox(const Dstr & errmsg,Error::ErrType fatality)930 void xxRoot::newErrorBox (const Dstr &errmsg, Error::ErrType fatality) {
931 static bool snakeBit (false); // Hide double-barfs.
932 if (!snakeBit)
933 (void) new xxErrorBox (*popup, errmsg, fatality);
934 if (fatality == Error::fatal) {
935 snakeBit = true;
936
937 // This is a bad thing. We have to grind crank on the X server to
938 // get the error box to appear, but this might also cause
939 // previously queued events to do stuff--possibly even leading to
940 // another fatal error. The callback that led to the current sad
941 // state of affairs is being held hostage on the stack and did not
942 // get to finish whatever it was doing.
943 handleXEvents (xxRoot::loopForever);
944 }
945 }
946
947
948 // Similar to, but critically different from, xxWindow::realize.
realize()949 void xxRoot::realize() {
950 if (!_isRealized) {
951 Widget popupWidget (popup->widget());
952 XtRealizeWidget (popupWidget);
953 XSetWMProtocols (xxX::display, XtWindow(popupWidget), &xxX::killAtom, 1);
954 _isRealized = true;
955 setTitle_NET_WM();
956 dup();
957 }
958 }
959
960
961 // Similar to, but critically different from, xxWindow::unrealize.
unrealize()962 void xxRoot::unrealize() {
963 if (_isRealized) {
964 XtUnrealizeWidget (popup->widget());
965 _isRealized = false;
966 release();
967
968 // Since the window vanishes, the Dismiss button misses the
969 // LeaveWindow event. The next time the window is realized,
970 // Dismiss is still stuck in a set and/or highlighted state.
971 // This fixes that.
972 XtCallActionProc (dismissButton->widget(), "reset", NULL, NULL, 0);
973 }
974 }
975
976
dismiss()977 xxWindow * const xxRoot::dismiss() {
978 unrealize();
979 // ISO/IEC 14882:2003 5.3.5 specifies that deleting a null pointer
980 // shall have no effect.
981 return NULL;
982 }
983
984
handleXEvents(HandleXEventsReturnProtocol protocol)985 void xxRoot::handleXEvents (HandleXEventsReturnProtocol protocol) {
986 XEvent foo;
987 std::map<Window, XEvent> procrastinatedResizes;
988 do {
989 bool blockOnce (protocol == loopForever);
990 while (XtAppPending(xxX::appContext) || blockOnce) {
991 blockOnce = false;
992 XtAppNextEvent (xxX::appContext, &foo);
993 if (foo.type == ConfigureNotify)
994 // Newer window managers like Metacity, Compiz, KWin, Xfwm, and
995 // Openbox got the idea to deliver a stream of resize events while a
996 // window is being resized instead of just one for the final size.
997 // This was not in the contract, but now we have to deal with it to
998 // avoid looking stupid. Put off resizes and discard those that
999 // become redundant before we get around to them.
1000 procrastinatedResizes[foo.xconfigure.window] = foo;
1001 else
1002 XtDispatchEvent (&foo);
1003 }
1004 // Catch up on resizes when there's nothing better to do.
1005 for (std::map<Window, XEvent>::iterator it (procrastinatedResizes.begin());
1006 it != procrastinatedResizes.end();
1007 ++it)
1008 XtDispatchEvent (&(it->second));
1009 procrastinatedResizes.clear();
1010 } while (protocol == loopForever);
1011 }
1012