X11 Work Bench Toolkit  1.0
dialog_impl.c
Go to the documentation of this file.
1 
2 // _ _ _ _ _ //
3 // __| |(_) __ _ | | ___ __ _ (_) _ __ ___ _ __ | | ___ //
4 // / _` || | / _` || | / _ \ / _` | | || '_ ` _ \ | '_ \ | | / __| //
5 // | (_| || || (_| || || (_) || (_| | | || | | | | || |_) || | _| (__ //
6 // \__,_||_| \__,_||_| \___/ \__, |_____|_||_| |_| |_|| .__/ |_|(_)\___| //
7 // |___/|_____| |_| //
8 // //
9 // implementation of standard modal dialogs //
10 // //
12 
13 /*****************************************************************************
14 
15  X11workbench - X11 programmer's 'work bench' application and toolkit
16  Copyright (c) 2010-2016 by Bob Frazier (aka 'Big Bad Bombastic Bob')
17  all rights reserved
18 
19  DISCLAIMER: The X11workbench application and toolkit software are supplied
20  'as-is', with no warranties, either implied or explicit.
21  Any claims to alleged functionality or features should be
22  considered 'preliminary', and might not function as advertised.
23 
24  BSD-like license:
25 
26  There is no restriction as to what you can do with this software, so long
27  as you include the above copyright notice and DISCLAIMER for any distributed
28  work that is equal to or derived from this one, along with this paragraph
29  that explains the terms of the license if the source is also being made
30  available. A "derived work" describes a work that uses a significant portion
31  of the source files or algorithms that are included with this one.
32  Specifically excluded from this are files that were generated by the software,
33  or anything that is included with the software that is part of another package
34  (such as files that were created or added during the 'configure' process).
35  Specifically included is the use of part or all of any of the X11 workbench
36  toolkit source or header files in your distributed application. If you do not
37  ship the source, the above copyright statement is still required to be placed
38  in a reasonably prominent place, such as documentation, splash screens, and/or
39  'about the application' dialog boxes.
40 
41  Use and distribution are in accordance with GPL, LGPL, and/or the above
42  BSD-like license. See COPYING and README files for more information.
43 
44 
45  Additional information at http://sourceforge.net/projects/X11workbench
46 
47 ******************************************************************************/
48 
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <unistd.h>
60 #include <memory.h>
61 #include <string.h>
62 #include <strings.h>
63 #include <signal.h>
64 #include <time.h>
65 
66 #ifndef XK_Delete /* moslty for interix */
67 #define XK_MISCELLANY /* mostly for interix */
68 #include <X11/keysymdef.h> // some platforms don't automatically include this with X headers
69 #endif // XK_Delete
70 
71 #include "window_helper.h"
72 #include "pixmap_helper.h" // pixmap helpers, including pre-defined icons
73 #include "dialog_window.h"
74 #include "dialog_controls.h"
75 #include "conf_help.h"
76 #include "file_help.h"
77 #include "draw_text.h"
78 
79 
80 #define THIS_SUBSYSTEM DebugSubSystem_Dialog
81 
82 //-------------------------------------------------------------------
83 //
84 // alteration of 'gleam' behavior for 'Splash' dialog
85 // default is the 'wide' gleam that uses 1/r^2
86 //
87 //#define GLEAM_OLD /* use this to do the 'old' gleam behavior */
88 //#define GLEAM_NARROW /* use this to produce the 'narrow' gleam */
89 //
90 //------------------------------------------------------------------
91 
92 
94 // MESSAGE BOX
96 
97 struct _MESSAGE_BOX_
98 {
99  int iType;
100  const char *szTitle;
101  const char *szMessage;
102 };
103 
104 static int GetMessageBoxIconPixmapID(int iMBIconMask)
105 {
106  switch(iMBIconMask & MessageBox_ICON_MASK)
107  {
108  case MessageBox_Error:
109  return ID_ICON_STOP;
110  case MessageBox_Warning:
111  return ID_ICON_WARN;
112  case MessageBox_Info:
113  return ID_ICON_OK;
114  case MessageBox_Asterisk:
115  return ID_ICON_SPLAT;
116  case MessageBox_Question:
117  return ID_ICON_WHAT;
119  return ID_ICON_WHAT_BOLD;
120  case MessageBox_WTF:
121  return ID_ICON_WTF;
122  case MessageBox_Bang:
123  return ID_ICON_BANG;
124  case MessageBox_Triangle:
125  return ID_ICON_TRIANGLE;
127  return ID_ICON_DEATH;
129  return ID_ICON_SKULL;
130  case MessageBox_ThumbsUp:
131  return ID_ICON_THUMBUP;
133  return ID_ICON_THUMBDOWN;
135  return ID_ICON_FINGER;
137  return ID_ICON_BEAR;
138  case MessageBox_Barney:
139  return ID_ICON_BARNEY;
140  case MessageBox_App:
141  return ID_ICON_APP;
142 
143 // default:
144 // pixmap = None;
145  }
146 
147  return -1;
148 }
149 
150 static int MessageBoxCallback(Window wID, XEvent *pEvent)
151 {
153 struct _MESSAGE_BOX_ *pUserData = (struct _MESSAGE_BOX_ *)(pDlg ? pDlg->pUserData : NULL);
154 //#ifndef NO_DEBUG
155 //WB_UINT64 ullTime = WBGetTimeIndex();
156 //#endif // NO_DEBUG
157 
158 
159  if(pEvent->type == ClientMessage && pEvent->xclient.message_type == aDIALOG_INIT)
160  {
161  if(!pDlg)
162  {
163  WB_ERROR_PRINT("MessageBoxCallback - no WBDialogWindow structure in DIALOG_INIT for %d (%08xH) %p %08xH %08xH\n",
164  (unsigned int)wID, (unsigned int)wID, WBGetWindowData(wID, 0), DIALOG_WINDOW_TAG, ((WBDialogWindow *)WBGetWindowData(wID, 0))->ulTag);
165  return 0; // can't process any messages now
166  }
167  else
168  {
169  // assigning the correct icon
170 
171  Window wIDIcon = DLGGetDialogControl(pDlg, 1000); // ID 1000 for icon
172  WBDialogControl *pCtrl = DLGGetDialogControlStruct(wIDIcon);
173 
174  if(pCtrl)
175  {
176  Pixmap pixmap2 = None;
177  Pixmap pixmap = PXM_GetIconPixmap(GetMessageBoxIconPixmapID(pUserData->iType & MessageBox_ICON_MASK),
178  NULL, &pixmap2);
179 
180  if(pixmap != None)
181  {
182  WBDialogControlSetIconPixmap(pCtrl, pixmap, pixmap2);
183  }
184  }
185  }
186 
187  // assign the caption text to the caption window (which varies and must be assigned at run time)
188 
189  DLGSetControlCaption((WBDialogWindow *)pDlg, 1001, pUserData->szMessage);
190 
191 // WB_ERROR_PRINT("TEMPORARY: %s line %d delta tick %lld\n", __FUNCTION__, __LINE__, (WBGetTimeIndex() - ullTime));
192 
193  return 1;
194  }
195 
196  if(!pDlg)
197  {
198  WB_WARN_PRINT("MessageBoxCallback - no WBDialogWindow structure\n");
199  return 0; // can't process any messages now
200  }
201 
202  if(pEvent->type == ClientMessage && pEvent->xclient.message_type == aCONTROL_NOTIFY)
203  {
205  "%s - MessageBox ClientMessage CONTROL_NOTIFY\n", __FUNCTION__);
206 
207  switch(pEvent->xclient.data.l[1]) // control ID
208  {
209  case IDOK:
210  case IDCANCEL:
211  if(pEvent->xclient.data.l[0] == aBUTTON_PRESS)
212  {
213  WBEndModal(wID, pEvent->xclient.data.l[1]);
214  }
215  break;
216 
217  default:
218  WB_WARN_PRINT("%s - MessageBox ClientMessage CONTROL_NOTIFY client id=%lx\n",
219  __FUNCTION__, pEvent->xclient.data.l[1]);
220  }
221  }
222 
223 
224 
225 
226 
227 
228  return 0;
229 }
230 
231 int DLGMessageBox(int iType, Window wIDOwner, const char *szTitle, const char *szMessage)
232 {
233 static const char szOKBox[]=
234  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
235  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
236  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
237  " CONTROL:DefPushButton ID:IDOK TITLE:OK X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
238  "END_DIALOG\n";
239 static const char szNoBox[]=
240  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
241  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
242  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
243  " CONTROL:DefPushButton ID:IDCANCEL TITLE:No X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
244  "END_DIALOG\n";
245 static const char szYesBox[]=
246  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
247  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
248  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
249  " CONTROL:DefPushButton ID:IDYES TITLE:Yes X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
250  "END_DIALOG\n";
251 static const char szCancelBox[]=
252  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
253  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
254  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
255  " CONTROL:DefPushButton ID:IDCANCEL TITLE:Cancel X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
256  "END_DIALOG\n";
257 static const char szAbortBox[]=
258  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
259  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
260  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
261  " CONTROL:DefPushButton ID:IDABORT TITLE:Abort X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
262  "END_DIALOG\n";
263 static const char szRetryBox[]=
264  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
265  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
266  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
267  " CONTROL:DefPushButton ID:IDRETRY TITLE:Retry X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
268  "END_DIALOG\n";
269 static const char szIgnoreBox[]=
270  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK Box\"\n"
271  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
272  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
273  " CONTROL:DefPushButton ID:IDIGNORE TITLE:Ignore X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
274  "END_DIALOG\n";
275 static const char szOKCancelBox[]=
276  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"OK/Cancel Box\"\n"
277  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
278  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
279  " CONTROL:DefPushButton ID:IDOK TITLE:OK X:40 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
280  " CONTROL:CancelButton ID:IDCANCEL TITLE:Cancel X:120 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
281  "END_DIALOG\n";
282 static const char szYesNoBox[]=
283  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"Yes/No Box\"\n"
284  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
285  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
286  " CONTROL:DefPushButton ID:IDYES TITLE:_Yes X:40 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
287  " CONTROL:CancelButton ID:IDNO TITLE:_No X:120 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
288  "END_DIALOG\n";
289 static const char szYesNoCancelBox[]=
290  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"Yes/No Box\"\n"
291  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
292  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
293  " CONTROL:DefPushButton ID:IDYES TITLE:_Yes X:20 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
294  " CONTROL:PushButton ID:IDNO TITLE:_No X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
295  " CONTROL:CancelButton ID:IDCANCEL TITLE:Cancel X:140 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
296  "END_DIALOG\n";
297 static const char szAbortRetryBox[]=
298  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"Yes/No Box\"\n"
299  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
300  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
301  " CONTROL:DefPushButton ID:IDABORT TITLE:_Abort X:40 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
302  " CONTROL:CancelButton ID:IDRETRY TITLE:_Retry X:120 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
303  "END_DIALOG\n";
304 static const char szAbortRetryIgnoreBox[]=
305  "BEGIN_DIALOG FONT:Variable HEIGHT:50 WIDTH:200 TITLE:\"Yes/No Box\"\n"
306  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
307  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
308  " CONTROL:DefPushButton ID:IDYES TITLE:_Abort X:20 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
309  " CONTROL:PushButton ID:IDRETRY TITLE:_Retry X:80 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
310  " CONTROL:CancelButton ID:IDIGNORE TITLE:_Ignore X:140 Y:28 WIDTH:40 HEIGHT:18 VISIBLE\n"
311  "END_DIALOG\n";
312 
313 struct _MESSAGE_BOX_ mbox;
314 const char *pRes;
315 WBDialogWindow *pDlg;
316 WB_GEOM geomParent;
317 Window wIDDlg;
318 int iRval, iX, iY;
319 
320 
321 // for now I determine the standard message box size THIS way
322 #define MESSAGE_BOX_WIDTH 400
323 #define MESSAGE_BOX_HEIGHT 100
324 #define MESSAGE_BOX_OFFSET 50
325 
326  mbox.iType = iType;
327  mbox.szTitle = szTitle;
328  mbox.szMessage = szMessage;
329 
330  bzero(&geomParent, sizeof(geomParent));
331 
332  if(wIDOwner != None)
333  {
334  WBGetWindowGeom0(wIDOwner, &geomParent); // parent geometry in absolute coordinates
335 
336  iX = geomParent.x + geomParent.border + MESSAGE_BOX_OFFSET;
337  iY = geomParent.y + geomParent.border + MESSAGE_BOX_OFFSET;
338  }
339  else
340  {
341  // center in screen with slight random offset (so that every window won't always appear in exactly the same place)
342  iY = (DisplayHeight(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()))
343  - MESSAGE_BOX_HEIGHT + MESSAGE_BOX_OFFSET - (int)(WBGetTimeIndex() % (2 * MESSAGE_BOX_OFFSET)))
344  / 2;
345 
346  iX = (DisplayWidth(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()))
347  - MESSAGE_BOX_WIDTH + MESSAGE_BOX_OFFSET - (int)((~WBGetTimeIndex()) % (2 * MESSAGE_BOX_OFFSET)))
348  / 2;
349  }
350 
351  switch(iType & MessageBox_BUTTON_MASK)
352  {
353  case MessageBox_OK:
354  pRes = szOKBox;
355  break;
356 
357  case MessageBox_Yes:
358  pRes = szYesBox;
359  break;
360 
361  case MessageBox_No:
362  pRes = szNoBox;
363  break;
364 
365  case MessageBox_Cancel:
366  pRes = szCancelBox;
367  break;
368 
369  case MessageBox_Abort:
370  pRes = szAbortBox;
371  break;
372 
373  case MessageBox_Retry:
374  pRes = szRetryBox;
375  break;
376 
377  case MessageBox_Ignore:
378  pRes = szIgnoreBox;
379  break;
380 
382  pRes = szOKCancelBox;
383  break;
384 
386  pRes = szYesNoBox;
387  break;
388 
390  pRes = szYesNoCancelBox;
391  break;
392 
394  pRes = szAbortRetryBox;
395  break;
396 
398  pRes = szAbortRetryIgnoreBox;
399  break;
400 
401  default:
402  if((iType & MessageBox_Abort) || (iType & MessageBox_Retry) || (iType & MessageBox_Ignore))
403  {
404  pRes = szAbortRetryIgnoreBox;
405  }
406  else if((iType & MessageBox_Yes) || (iType & MessageBox_No) || (iType & MessageBox_Cancel))
407  {
408  pRes = szYesNoCancelBox;
409  }
410  else
411  {
412  pRes = szOKBox;
413  }
414  }
415 
416 
417 // WB_ERROR_PRINT("TEMPORARY: %s - calling DLGCreateDialogWindow\n", __FUNCTION__);
418 
419  pDlg = DLGCreateDialogWindow(szTitle,pRes, iX, iY,
420  MESSAGE_BOX_WIDTH,
421  MESSAGE_BOX_HEIGHT, // TODO: derive from ???
422  MessageBoxCallback,
423  WBDialogWindow_VISIBLE, &mbox);
424 
425  if(pDlg) // TODO: manage this stuff as part of 'DLGCreateDialogWindow' instead
426  {
427  wIDDlg = pDlg->wID;
428 
429  if(wIDOwner != None)
430  {
431  Atom a1;
432  unsigned int ai1[3];
433 
434  DLGAssignOwner(pDlg, wIDOwner);
435 
436  a1 = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE", False);
437  ai1[0] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
438  ai1[1] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_PAGER", False);
439 
440  XChangeProperty(WBGetWindowDisplay(wIDDlg), wIDDlg, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)ai1, 2);
441 
442  a1 = XInternAtom(WBGetWindowDisplay(wIDDlg), "WM_TRANSIENT_FOR", False);
443  XChangeProperty(WBGetWindowDisplay(wIDDlg), wIDDlg, a1, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wIDOwner, 1);
444  }
445 
446  WBSetWindowIcon(wIDDlg, GetMessageBoxIconPixmapID(iType & MessageBox_ICON_MASK));
447 
448 // WB_ERROR_PRINT("TEMPORARY: %s - calling WBShowModal\n", __FUNCTION__);
449 
450  iRval = WBShowModal(wIDDlg, 0);
451 
452  return iRval;
453  }
454 
455  return -1; // by default return -1 on error
456 }
457 
458 
460 // INPUT BOX
462 
463 struct _INPUT_BOX_
464 {
465  const char *szTitle;
466  const char *szMessage;
467  char *szRval;
468 };
469 
470 
471 static int InputBoxCallback(Window wID, XEvent *pEvent)
472 {
474 struct _INPUT_BOX_ *pUserData = (struct _INPUT_BOX_ *)(pDlg ? pDlg->pUserData : NULL);
475 
476 
477  if(!pDlg)
478  return 0; // can't process any messages now
479 
480  if(pEvent->type == ClientMessage && pEvent->xclient.message_type == aDIALOG_INIT)
481  {
482  // assigning the correct icon
483  {
484  WBDialogControl *pCtrl = DLGGetDialogControlStructFromID(pDlg, 1000); // ID 1000 for icon
485 
486  if(pCtrl)
487  {
488  Pixmap pixmap2 = None;
489  Pixmap pixmap = PXM_GetIconPixmap(ID_ICON_WHAT, NULL, &pixmap2);
490 
491  if(pixmap != None)
492  {
493  WBDialogControlSetIconPixmap(pCtrl, pixmap, pixmap2);
494  }
495  }
496  }
497 
498  // assign the caption text to the caption window (which varies and must be assigned at run time)
499 
500  DLGSetControlCaption((WBDialogWindow *)pDlg, 1001, pUserData->szMessage);
501 
502  if(pUserData->szRval)
503  {
504  DLGSetControlCaption(pDlg, 1002, pUserData->szRval);
505  }
506 
507  return 1;
508  }
509 
510  if(pEvent->type == ClientMessage && pEvent->xclient.message_type == aCONTROL_NOTIFY)
511  {
513  "%s - MessageBox ClientMessage CONTROL_NOTIFY\n", __FUNCTION__);
514 
515  switch(pEvent->xclient.data.l[1]) // control ID
516  {
517  case IDOK:
518  case IDCANCEL:
519  if(pEvent->xclient.data.l[0] == aBUTTON_PRESS)
520  {
521  const char *pText = DLGGetControlCaption(pDlg, 1002);
522 
523  if(pUserData->szRval)
524  {
525  WBFree(pUserData->szRval);
526  }
527  if(pText)
528  {
529  pUserData->szRval = WBCopyString(pText);
530  }
531  else
532  {
533  WB_ERROR_PRINT("TEMPORARY: %s - NULL 'pText' for edit control\n", __FUNCTION__);
534  pUserData->szRval = NULL; // empty string
535  }
536 
537  WBEndModal(wID, pEvent->xclient.data.l[1]);
538  }
539  break;
540 
541  case 1002:
543  "%s - INPUT BOX 'EDIT' NOTIFICATION %ld\n",
544  __FUNCTION__, pEvent->xclient.data.l[0]);
545 
546  // TODO: update window text
547  break;
548 
549  default:
550  WB_WARN_PRINT("%s - MessageBox ClientMessage CONTROL_NOTIFY client id=%lx\n",
551  __FUNCTION__, pEvent->xclient.data.l[0]);
552  }
553  }
554 
555  return 0;
556 }
557 
558 char *DLGInputBox(Window wIDOwner, const char *szTitle, const char *szPrompt, const char *szDefault,
559  int iWidth, int iMaxChar)
560 {
561 static const char szInputDialogRes[]=
562  "BEGIN_DIALOG FONT:Variable HEIGHT:60 WIDTH:200 TITLE:\"User Input\"\n"
563  " CONTROL:Icon ID:1000 X:2 Y:2 HEIGHT:20 WIDTH:20 VISIBLE\n"
564  " CONTROL:Text ID:1001 X:24 Y:2 HEIGHT:20 WIDTH:172 VISIBLE\n"
565  " CONTROL:Edit ID:1002 X:2 Y:22 WIDTH:196 HEIGHT:16 VISIBLE\n"
566  " CONTROL:DefPushButton ID:IDOK TITLE:OK X:40 Y:40 WIDTH:40 HEIGHT:18 VISIBLE\n"
567  " CONTROL:CancelButton ID:IDCANCEL TITLE:Cancel X:120 Y:40 WIDTH:40 HEIGHT:18 VISIBLE\n"
568  "END_DIALOG\n";
569 WBDialogWindow *pDlg;
570 struct _INPUT_BOX_ sRval;
571 int iRval;
572 Window wIDDlg;
573 
574 
575  sRval.szTitle = szTitle;
576  sRval.szMessage = szPrompt;
577  if(szDefault && *szDefault)
578  {
579  sRval.szRval = WBCopyString(szDefault); // a copy of the default value
580  }
581  else
582  {
583  sRval.szRval = NULL;
584  }
585 
586  pDlg = DLGCreateDialogWindow(szTitle,szInputDialogRes,
587  100,100,300,60,InputBoxCallback,
588  WBDialogWindow_VISIBLE,&sRval);
589 
590  if(pDlg) // TODO: manage this stuff as part of 'DLGCreateDialogWindow' instead
591  {
592  wIDDlg = pDlg->wID;
593 
594  if(wIDOwner != None)
595  {
596  Atom a1;
597  unsigned int ai1[3];
598 
599  DLGAssignOwner(pDlg, wIDOwner);
600 
601  a1 = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE", False);
602  ai1[0] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
603  ai1[1] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_PAGER", False);
604 
605  XChangeProperty(WBGetWindowDisplay(wIDDlg), wIDDlg, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)ai1, 2);
606 
607  a1 = XInternAtom(WBGetWindowDisplay(wIDDlg), "WM_TRANSIENT_FOR", False);
608  XChangeProperty(WBGetWindowDisplay(wIDDlg), wIDDlg, a1, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wIDOwner, 1);
609  }
610 
611  iRval = WBShowModal(pDlg->wID, 0);
612 
613  if(iRval == IDOK)
614  {
615  return sRval.szRval;
616  }
617  else if(iRval != IDCANCEL)
618  {
619  WB_ERROR_PRINT("TEMPORARY - %s - iRval is %d\n", __FUNCTION__, iRval);
620  }
621  }
622 
623  // it ends up here on 'cancel', etc. - so if a buffer was allocated, free it
624 
625  if(sRval.szRval)
626  {
627  WBFree(sRval.szRval);
628  }
629 
630  return NULL;
631 }
632 
633 
634 
636 // FILE DIALOG
638 
639 struct _FILE_DIALOG_
640 {
641  const char *szDefPath;
642  const char *szDefName;
643  const char *szExtAndDescList;
644  char *szPathName; // WBAlloc'd
645 };
646 
647 
648 #define FILE_DIALOG_PATH_TREE_CONTROL 1000
649 #define FILE_DIALOG_FILE_LIST_CONTROL 1001
650 #define FILE_DIALOG_FILE_NAME_CONTROL 1002
651 
652 static int FileDialogCallback(Window wID, XEvent *pEvent)
653 {
655 struct _FILE_DIALOG_ *pUserData = (struct _FILE_DIALOG_ *)(pDlg ? pDlg->pUserData : NULL);
656 //Display *pDisplay = WBGetWindowDisplay(wID);
657 char *p1, *p2;
658 
659 
660  if(!pDlg)
661  return 0; // can't process any messages now
662 
663  if(pEvent->type == ClientMessage && pEvent->xclient.message_type == aDIALOG_INIT)
664  {
665  if(!pUserData->szPathName || !*(pUserData->szPathName) ||
666  (!strchr(pUserData->szPathName, '/') && !WBIsDirectory(pUserData->szPathName)))
667  {
668  if(!pUserData->szDefPath || !*(pUserData->szDefPath))
669  {
670  // set the 'DLGC_PATH' property for appropriate controls
671  DLGSetControlProperty(pDlg, FILE_DIALOG_FILE_LIST_CONTROL,
672  aDLGC_PATH, "."); // set path to '.' if not specified already
673  }
674  else
675  {
676  DLGSetControlProperty(pDlg, FILE_DIALOG_FILE_LIST_CONTROL,
677  aDLGC_PATH, pUserData->szDefPath);
678  }
679  }
680  else
681  {
682  p1 = WBCopyString(pUserData->szDefPath);
683  if(!p1)
684  {
685  WB_ERROR_PRINT("%s - no memory to copy path string (a)\n", __FUNCTION__);
686  }
687  else
688  {
689  p2 = strrchr(p1, '/');
690  if(p2)
691  {
692  p2[1] = 0; // keep the '/'
693  }
694 
695  DLGSetControlProperty(pDlg, FILE_DIALOG_FILE_LIST_CONTROL,
696  aDLGC_PATH, p1);
697 
698  WBFree(p1);
699  }
700  }
701 
702  return 1;
703  }
704 
705  if(pEvent->type != ClientMessage)
706  {
707  return 0; // unhandled at this time
708  }
709 
710  // EVERYTHING AT THIS POINT IS A CLIENT MESSAGE
711 
712  if(pEvent->xclient.message_type == aCONTROL_NOTIFY)
713  {
714  // l[0] == message
715  // l[1] == control ID
716  // l[2] ==
717  switch(pEvent->xclient.data.l[1])
718  {
719  case IDOK:
720  case IDCANCEL:
721  if(pEvent->xclient.data.l[0] == aBUTTON_PRESS)
722  {
723  if(pEvent->xclient.data.l[1] == IDOK)
724  {
725  const char *pPath = DLGGetControlCaption(pDlg, FILE_DIALOG_FILE_NAME_CONTROL);
726  const char *pDir = DLGGetControlProperty(pDlg, FILE_DIALOG_FILE_LIST_CONTROL, aDLGC_PATH);
727 
728  if(pUserData->szPathName)
729  {
730  WBFree(pUserData->szPathName);
731  }
732 
733  if(pPath && pPath[0] == '/')
734  {
735  pUserData->szPathName = WBCopyString(pPath); // absolute path assignment
736  }
737  else
738  {
739  if(pDir && *pDir)
740  {
741  pUserData->szPathName = WBCopyString(pDir);
742 
743  if(pUserData->szPathName && *pUserData->szPathName &&
744  pUserData->szPathName[strlen(pUserData->szPathName) - 1] != '/')
745  {
746  WBCatString(&(pUserData->szPathName), "/");
747  }
748  }
749  else
750  {
751  pUserData->szPathName = WBCopyString("./");
752  }
753 
754 // WB_ERROR_PRINT("TEMPORARY %s ---> dir=\"%s\" path=\"%s\" \n", __FUNCTION__, pUserData->szPathName, pPath);
755 
756  if(pUserData->szPathName && pPath)
757  {
758  WBCatString(&(pUserData->szPathName), pPath);
759  }
760 
761  // check for '/../' within path - if present, canonicalize it
762  if(pUserData->szPathName &&
763  (strstr(pUserData->szPathName, "/../") ||
764  (strlen(pUserData->szPathName) > 3 &&
765  (!memcmp(pUserData->szPathName, "../", 3) ||
766  !memcmp(pUserData->szPathName + strlen(pUserData->szPathName) - 3, "/..", 3)))))
767  {
768  p1 = WBGetCanonicalPath(pUserData->szPathName);
769  if(p1)
770  {
771  WBFree(pUserData->szPathName);
772  pUserData->szPathName = p1;
773  }
774  else
775  {
776  WB_ERROR_PRINT("ERROR - %s - Unable to get canonical path for \"%s\"\n", __FUNCTION__, pUserData->szPathName);
777  }
778  }
779  }
780 
781  if(pUserData->szPathName && *pUserData->szPathName &&
782  (pUserData->szPathName[strlen(pUserData->szPathName) - 1] == '/' || WBIsDirectory(pUserData->szPathName)))
783  {
784  // if it ends in a '/' it's supposed to be a DIRECTORY and I must change to it
785  p1 = WBCopyString(pUserData->szPathName);
786  if(p1 && (!*p1 || p1[strlen(p1) - 1] != '/'))
787  {
788  WBCatString(&p1, "/"); // make sure it ends in '/'
789  }
790 
791  if(!p1)
792  {
793  WB_ERROR_PRINT("%s - no memory to copy path string (b)\n", __FUNCTION__);
794  }
795  else
796  {
797  // TODO: do I do a chdir() ?
798 
799  DLGSetControlProperty(pDlg, FILE_DIALOG_FILE_LIST_CONTROL,
800  aDLGC_PATH, p1);
801 
802 // WB_ERROR_PRINT("TEMPORARY %s ---> new path=\"%s\"\n", __FUNCTION__, p1);
803 
804  // also I want to make sure that the file name control contains the new path
805  DLGSetControlCaption(pDlg, FILE_DIALOG_FILE_NAME_CONTROL, p1);
806 
807  WBFree(p1);
808  }
809 
810  return 1; // handled (do not close the dialog box)
811  }
812  }
813 
814  WBEndModal(wID, pEvent->xclient.data.l[1]);
815  return 1; // handled!
816  }
817  break;
818 
819  case FILE_DIALOG_FILE_LIST_CONTROL:
820  if(pEvent->xclient.data.l[0] == aLIST_NOTIFY) // list control notifications
821  {
822  if(pEvent->xclient.data.l[2] == WB_LIST_SELCHANGE)
823  {
824  // assign the textbox value to the selected text
825  if(pEvent->xclient.data.l[3] >= 0)
826  {
827  WBDialogControl *pCtrl = DLGGetDialogControlStructFromID(pDlg, pEvent->xclient.data.l[1]);
828  if(pCtrl)
829  {
830  const char *pText = DLGGetControlListText(pCtrl, pEvent->xclient.data.l[3]);
831 
832  if(pText)
833  {
834 // WB_WARN_PRINT("%s - assigning control text \"%s\"\n", __FUNCTION__, pText);
835  if(*pText == '@')
836  {
837  char *p1 = WBCopyString(pText + 1);
838 
839  if(p1)
840  {
841  WBCatString(&p1, "/");
842  if(p1)
843  {
844  DLGSetControlCaption(pDlg, FILE_DIALOG_FILE_NAME_CONTROL, p1);
845  WBFree(p1);
846  }
847  }
848  }
849  else // assume '~'
850  {
851  DLGSetControlCaption(pDlg, FILE_DIALOG_FILE_NAME_CONTROL, pText + 1);
852  }
853  }
854  }
855  }
856 
857  return 1; // handled
858  }
859  else if(pEvent->xclient.data.l[2] == WB_LIST_DBLCLICK)
860  {
861  // NOTE: pEvent->xclient.data.l[3] contains the selection index
862 
863  // Rather than allowing the default handler to deal with it, post an 'OK' button notification
864  // so that the same code deals with THIS as with the OK button
865 
866 // XClientMessageEvent evt = {
867 // .type=ClientMessage,
868 // .serial=0,
869 // .send_event=0,
870 // .display=pDisplay,
871 // .window=wID,
872 // .message_type=aCONTROL_NOTIFY,
873 // .format=32
874 // };
875 // evt.data.l[0] = aBUTTON_PRESS;
876 // evt.data.l[1] = IDOK;
877 // evt.data.l[2] = 0;
878 // evt.data.l[3] = 0;
879 // evt.data.l[4] = 0;
880 //
881 // WBPostPriorityEvent(wID, (XEvent *)&evt);
882 
883  // build a 'click' notification event to make sure I capture the correct name
884  DLGNotifyDlg(pDlg, aCONTROL_NOTIFY, aLIST_NOTIFY, FILE_DIALOG_FILE_LIST_CONTROL,
885  WB_LIST_SELCHANGE, pEvent->xclient.data.l[3], 0); // duplicates a 'SEL CHANGE" event
886 
887  {
888 #ifndef NO_DEBUG
889  char *p1 = WBGetAtomName(WBGetWindowDisplay(wID), (Atom)pEvent->xclient.data.l[0]);
890  WB_WARN_PRINT("%s - LIST_NOTIFY WB_LIST_DBLCLICK control notification message %ld (%s) %ld (%08lxH), %ld (%08lxH)\n",
891  __FUNCTION__, pEvent->xclient.data.l[0], p1,
892  pEvent->xclient.data.l[1], pEvent->xclient.data.l[1],
893  pEvent->xclient.data.l[2], pEvent->xclient.data.l[2]);
894  if(p1)
895  {
896  WBFree(p1);
897  }
898 #endif // NO_DEBUG
899  }
900 
901  DLGNotifyDlgAsync(pDlg, aCONTROL_NOTIFY, aBUTTON_PRESS, IDOK, 0, 0, 0); // post a button press event
902 
903  return 1;
904  }
905 
906  return 0; // not handled
907  }
908  break;
909 
910  default:
911  {
912 #ifndef NO_DEBUG
913  char *p1 = WBGetAtomName(WBGetWindowDisplay(wID), (Atom)pEvent->xclient.data.l[0]);
914 
915  WB_WARN_PRINT("%s - TODO: control notification message %ld (%s) %ld (%08lxH), %ld (%08lxH)\n",
916  __FUNCTION__, pEvent->xclient.data.l[0], p1,
917  pEvent->xclient.data.l[1], pEvent->xclient.data.l[1],
918  pEvent->xclient.data.l[2], pEvent->xclient.data.l[2]);
919 
920  if(p1)
921  {
922  WBFree(p1);
923  }
924 #endif // NO_DEBUG
925  }
926  }
927  }
928  else if(pEvent->xclient.message_type == aGOTFOCUS)
929  {
930  return 0; // for now
931  }
932  else if(pEvent->xclient.message_type == aLOSTFOCUS)
933  {
934  return 0; // for now
935  }
936  else
937  {
938 #ifndef NO_DEBUG
939  char *p1 = WBGetAtomName(WBGetWindowDisplay(wID), (Atom)pEvent->xclient.message_type);
940  char *p2 = WBGetAtomName(WBGetWindowDisplay(wID), (Atom)pEvent->xclient.data.l[0]);
941 
942  WB_WARN_PRINT("%s - unhandled notification %s %ld (%s) %ld (%08lxH), %ld (%08lxH)\n",
943  __FUNCTION__, p1,
944  pEvent->xclient.data.l[0], p2,
945  pEvent->xclient.data.l[1], pEvent->xclient.data.l[1],
946  pEvent->xclient.data.l[2], pEvent->xclient.data.l[2]);
947 
948  if(p1)
949  {
950  WBFree(p1);
951  }
952  if(p2)
953  {
954  WBFree(p2);
955  }
956 #endif // NO_DEBUG
957  }
958 
959  return 0;
960 }
961 
962 char *DLGFileDialog(int iType, Window wIDOwner, const char *szDefPath, const char *szDefName,
963  const char *szExtAndDescList)
964 {
965 static const char szFileDialogRes[]=
966  "BEGIN_DIALOG FONT:Variable HEIGHT:250 WIDTH:260 TITLE:\"File Dialog\"\n"
967  " CONTROL:PathTree ID:1000 X:2 Y:2 HEIGHT:200 WIDTH:116 VISIBLE\n"
968  " CONTROL:FileList ID:1001 X:122 Y:2 HEIGHT:200 WIDTH:136 VISIBLE\n"
969  " CONTROL:Edit ID:1002 X:2 Y:206 WIDTH:296 HEIGHT:16 VISIBLE\n"
970  " CONTROL:DefPushButton ID:IDOK TITLE:OK X:40 Y:230 WIDTH:40 HEIGHT:18 VISIBLE\n"
971  " CONTROL:CancelButton ID:IDCANCEL TITLE:Cancel X:180 Y:230 WIDTH:40 HEIGHT:18 VISIBLE\n"
972  "END_DIALOG\n";
973 WBDialogWindow *pDlg;
974 struct _FILE_DIALOG_ data;
975 int iRval;
976 Window wIDDlg;
977 
978 
979  data.szDefPath = szDefPath;
980  data.szDefName = szDefName;
981  data.szExtAndDescList = szExtAndDescList;
982  data.szPathName = NULL;
983 
984  pDlg = DLGCreateDialogWindow("File Select",szFileDialogRes,
985  100,100,300,100,FileDialogCallback,
986  WBDialogWindow_VISIBLE,&data);
987 
988  if(pDlg) // TODO: manage this stuff as part of 'DLGCreateDialogWindow' instead
989  {
990  wIDDlg = pDlg->wID;
991 
992  if(wIDOwner != None)
993  {
994  Atom a1;
995  unsigned int ai1[3];
996 
997  DLGAssignOwner(pDlg, wIDOwner);
998 
999  a1 = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE", False);
1000  ai1[0] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
1001  ai1[1] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_PAGER", False);
1002 
1003  XChangeProperty(WBGetWindowDisplay(wIDDlg), wIDDlg, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)ai1, 2);
1004 
1005  a1 = XInternAtom(WBGetWindowDisplay(wIDDlg), "WM_TRANSIENT_FOR", False);
1006  XChangeProperty(WBGetWindowDisplay(wIDDlg), wIDDlg, a1, XA_WINDOW, 32, PropModeReplace, (unsigned char *)&wIDOwner, 1);
1007  }
1008 
1009  iRval = WBShowModal(pDlg->wID, 0);
1010 
1011  if(iRval == IDOK)
1012  {
1013  if(data.szPathName)
1014  {
1015  return data.szPathName;
1016  }
1017  else
1018  {
1019  return NULL; // for now, to prevent page fault
1020  }
1021  }
1022  }
1023 
1024  // it ends up here on 'cancel', etc. - so if a buffer was allocated, free it
1025 
1026  if(data.szPathName)
1027  {
1028  WBFree(data.szPathName);
1029  }
1030 
1031  return NULL;
1032 }
1033 
1034 // construct a splash screen based on the size of the pixmap and position
1035 // the copyright text in the lower 1/3 of the splash screen, centered, then
1036 // pass a 'diagonal flash' across it, and close the screen after a total of
1037 // 5 seconds, returning back to the application.
1038 
1039 typedef struct _SPLASH_
1040 {
1041  Pixmap pixmap, pixmap2;
1042  char *szCopyright;
1043  int iW, iH; // width/height of bitmap
1044  int iDepth; // depth, needed to create compatible pixmaps
1045  int nIter; // total # of iterations thus far
1046  XFontSet fontSet;//Struct *pFont;
1047  int nGleam; // current gleam center position
1048  WB_GEOM geomBorder;
1049  XStandardColormap cmap;
1050  XImage *pImage;
1051  void *pImageData;
1052  unsigned long cbImageData;
1053  unsigned long clrText, clrBlack, clrWhite; // pixel colors
1054 } SPLASH;
1055 
1056 static int splash_callback(Window wID, XEvent *pEvent);
1057 static int SplashDoExposeEvent(XExposeEvent *pEvent, Display *pDisplay,
1058  Window wID, struct _SPLASH_ *pData);
1059 
1060 #define SPLASH_FRAMERATE 30 /* make this configurable? */
1061 #define SPLASH_TIME 1500 /* milliseconds */
1062 
1063 void DLGSplashScreen(char *aXPM[], const char *szCopyright, unsigned long clrText)
1064 {
1065 Window wID;//, wIDTemp;
1066 XSetWindowAttributes xswa; /* Temporary Set Window Attribute struct */
1067 int iX, iY, iW, iH;
1068 XSizeHints xsh; /* Size hints for window manager */
1069 XWMHints xwmh;
1070 XPM_ATTRIBUTES xattr;
1071 SPLASH data;
1072 Atom a1;
1073 unsigned int ai1[3];
1074 
1075 
1076  bzero(&data, sizeof(data));
1077  bzero(&xattr, sizeof(xattr));
1078 
1080  aXPM, &(data.pixmap), &(data.pixmap2), &xattr))
1081  {
1082  WB_ERROR_PRINT("XPM_CREATE_PIXMAP_FROM_DATA ERROR\n");
1083  return;
1084  }
1085 
1086  iW = xattr.width + 4; // border width is 2
1087  iH = xattr.height + 4; // border width is 2
1088  data.iDepth = xattr.depth;
1089  if(!data.iDepth)
1090  {
1091  data.iDepth = DefaultDepth(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1092  }
1093 
1094  if(!iW || !iH || data.pixmap == None)
1095  {
1096  WB_ERROR_PRINT("%s - iW=%d, iH=%d, data.pixmap=%d (%08xH)\n",
1097  __FUNCTION__, iW, iH, (int)data.pixmap, (int)data.pixmap);
1098  return;
1099  }
1100 
1101 // WB_ERROR_PRINT("TEMPORARY %s - iW=%d, iH=%d, data.pixmap=%d (%08xH)\n",
1102 // __FUNCTION__, iW, iH, (int)data.pixmap, (int)data.pixmap);
1103 
1104  // TODO: choose font, calculate bounds of text area
1105  // save font/bounds data and/or combine text into pixmap with transparent background
1106 
1107 
1108  iX = DisplayWidth(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1109  iY = DisplayHeight(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1110 
1111  iX = (iX - iW) / 2;
1112  iY = (iY - iH) / 2; // centered
1113 
1114  data.szCopyright = WBCopyString(szCopyright);
1115  data.clrText = clrText;
1116  data.clrBlack = BlackPixel(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1117  data.clrWhite = WhitePixel(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1118  data.iW = xattr.width;
1119  data.iH = xattr.height;
1120  data.fontSet = None; // must do this
1121  data.nGleam = 0;
1122  data.pImage = NULL;
1123  data.pImageData = NULL;
1124  data.cbImageData = 0;
1125 
1126  bzero(&xswa, sizeof(xswa));
1127 
1128  xswa.border_pixel = data.clrBlack;
1129  xswa.background_pixel = data.clrWhite;
1130  xswa.colormap = DefaultColormap(WBGetDefaultDisplay(), DefaultScreen(WBGetDefaultDisplay()));
1131  xswa.bit_gravity = CenterGravity;
1132 
1133  wID = WBCreateWindow(WBGetDefaultDisplay(), None,//DefaultRootWindow(WBGetDefaultDisplay()),
1134  splash_callback, "Splash",
1135  iX, iY, iW, iH, 0,
1136  InputOutput,
1137  CWBorderPixel | CWBackPixel | CWColormap | CWBitGravity | CWOverrideRedirect,
1138  &xswa);
1139 
1140  if(wID <= 0)
1141  {
1142  return;
1143  }
1144 
1145  WBSetWindowData(wID, 0, (void *)&data);
1146  WBCreateWindowDefaultGC(wID, clrText, xswa.background_pixel);
1147 
1148  bzero(&xsh, sizeof(xsh));
1149 
1150  xsh.flags = (USPosition | USSize | PBaseSize | PMinSize | PMaxSize | PWinGravity);
1151  xsh.x = iX;
1152  xsh.y = iY;
1153  xsh.width = xsh.base_width = xsh.min_width = xsh.max_width = iW;
1154  xsh.height = xsh.base_height = xsh.min_height = xsh.max_height = iH;
1155  xsh.win_gravity = NorthWestGravity; // StaticGravity
1156 
1157  bzero(&xwmh, sizeof(xwmh));
1158  xwmh.flags = InputHint;
1159  xwmh.input = 0; // never take focus
1160 
1161  // set title, size hints, and 'WM_HINTS' hints (so WM knows where to put the window and how to set focus)
1162  WBSetWMProperties(wID, "splashwindow", &xsh, &xwmh, NULL);
1163 
1164  // before mapping the window, set some properties
1165  a1 = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_WINDOW_TYPE", False);
1166  ai1[0] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_WINDOW_TYPE_SPLASH", False);
1167  XChangeProperty(WBGetDefaultDisplay(), wID, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)ai1, 1);
1168 
1169 
1170  a1 = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE", False);
1171  ai1[0] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_MODAL", False);
1172  ai1[1] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
1173  ai1[2] = XInternAtom(WBGetDefaultDisplay(), "_NET_WM_STATE_SKIP_PAGER", False);
1174  XChangeProperty(WBGetDefaultDisplay(), wID, a1, XA_ATOM, 32, PropModeReplace, (unsigned char *)ai1, 3);
1175 
1176  // must EXPLICITLY allow PAINTING (and other stuff) i.e. ExposureMask
1177  XSelectInput(WBGetDefaultDisplay(), wID, WB_STANDARD_INPUT_MASK);
1178 
1180 
1181  if(CreateTimer(WBGetDefaultDisplay(), wID, 1000000 / SPLASH_FRAMERATE, 1, 1)) // periodic timer at 'frame rate'
1182  {
1183  WBDestroyWindow(wID);
1184  }
1185  else
1186  {
1187  WBMapWindow(WBGetDefaultDisplay(), wID); // make window visible
1188 
1189  WBShowModal(wID, -1); // timer will cause window to go away automatically
1190  }
1191 
1192  if(data.szCopyright)
1193  {
1194  WBFree((void *)data.szCopyright);
1195  }
1196 
1197  if(data.pImageData)
1198  {
1199  WBFree(data.pImageData);
1200  }
1201 
1203  if(data.pImage)
1204  {
1205  XDestroyImage(data.pImage);
1206  }
1207 
1208  if(data.pixmap != None)
1209  {
1210  XFreePixmap(WBGetDefaultDisplay(), data.pixmap);
1211  data.pixmap = None;
1212  }
1213 
1214  if(data.pixmap2 != None)
1215  {
1216  XFreePixmap(WBGetDefaultDisplay(), data.pixmap2);
1217  data.pixmap2 = None;
1218  }
1219 
1220  if(data.fontSet)
1221  {
1222  XFreeFontSet(WBGetDefaultDisplay(), data.fontSet);
1223  }
1225 }
1226 
1227 static int splash_callback(Window wID, XEvent *pEvent)
1228 {
1229 Display *pDisplay = WBGetWindowDisplay(wID);
1230 struct _SPLASH_ *pData = (struct _SPLASH_ *)WBGetWindowData(wID, 0);
1231 
1232 
1233  if(pData && pEvent->type == Expose)
1234  {
1235  return SplashDoExposeEvent((XExposeEvent *)pEvent, pDisplay, wID, pData);
1236  }
1237 
1238  if(pEvent->type == ClientMessage &&
1239  pEvent->xclient.message_type == aWM_TIMER)
1240  {
1241  if(!pData)
1242  {
1243  DeleteTimer(WBGetDefaultDisplay(), wID, 1);
1245  WBDestroyWindow(wID);
1246  return 1;
1247  }
1248 
1249  pData->nIter ++;
1250 
1251  if(pData->nIter * 1000 >= SPLASH_FRAMERATE * (SPLASH_TIME + 1000))
1252  {
1253  DeleteTimer(WBGetDefaultDisplay(), wID, 1);
1254  WBSetWindowData(wID, 0, NULL);
1255  WBDestroyWindow(wID);
1256  }
1257  else
1258  {
1259  WBInvalidateGeom(wID, NULL, 1);
1260  }
1261 
1262  return 1;
1263  }
1264 
1265  // special handling for 'destroy'
1266  if(pEvent->type == DestroyNotify &&
1267  pEvent->xdestroywindow.window == wID)
1268  {
1270  "%s - DestroyNotify\n", __FUNCTION__);
1271 
1272  WBSetWindowData(wID, 0, NULL);
1273 
1274  return 1;
1275  }
1276 
1277  return 0; // not handled
1278 }
1279 
1280 static int SplashDoExposeEvent(XExposeEvent *pEvent, Display *pDisplay,
1281  Window wID, struct _SPLASH_ *pData)
1282 {
1283 XFontSet fontSet;
1284 GC gc;
1285 Pixmap pxTemp;
1286 XGCValues xgcv;
1287 WB_GEOM geomText;
1288 int iX, iY, iTimeStart, iTimeEnd;
1289 
1290 
1291  if(!pDisplay)
1292  {
1293  pDisplay = WBGetDefaultDisplay();
1294  }
1295 
1296 // gc = WBBeginPaint(wID, pEvent, &geomPaint); // gnome b0rks this - window has absolute coordinates!
1297 
1298  if(pData->fontSet == None && pData->szCopyright && *(pData->szCopyright))
1299  {
1301  pData->szCopyright, &geomText);
1302 
1303  if(fontSet == None)
1304  {
1305  fontSet = WBFontSetFromFont(WBGetDefaultDisplay(), WBGetDefaultFont()); // makes a copy of the font set, basically
1306  }
1307 
1308  pData->fontSet = fontSet;
1309  }
1310 
1311  fontSet = pData->fontSet; // cache it for later
1312 
1313 
1314  bzero(&xgcv, sizeof(xgcv));
1315 // if(pFont)
1316 // {
1317 // xgcv.font = pData->pFont->fid;
1318 // xgcv.fill_style = FillSolid;
1319 // }
1320  xgcv.foreground = pData->clrText;
1321  xgcv.background = pData->clrWhite;
1322  xgcv.line_width = 1;
1323  xgcv.function = GXcopy; // copy
1324  xgcv.cap_style = CapProjecting;
1325 
1326  gc = XCreateGC(pDisplay, wID,
1327  (/*(xgcv.font ? GCFont | GCFillStyle : 0) | */GCForeground | GCBackground | GCCapStyle | GCFunction | GCLineWidth),
1328  &xgcv);
1329 
1330  if(!gc)
1331  {
1332  WB_WARN_PRINT("%s - * BUG * line %d\n", __FUNCTION__, __LINE__);
1333  return 0;
1334  }
1335 
1336  // now we get to create a window-compatible pixmap compatible with the window size
1337  // NOTE: XListDepths and XDefaultDepth may be needed to convert pixmaps to something that's compatible
1338 
1339  WBGetWindowGeom(wID, &(pData->geomBorder));
1340  pData->geomBorder.x = pData->geomBorder.y = 0; // force this (for now, gnome has absolute coordinates for splash window!)
1341 
1342 // not currently being used - later if I need it, uncomment - gcc in linux barphs on unused assigned vars
1343 // xrct.x = pData->geomBorder.x;
1344 // xrct.y = pData->geomBorder.y;
1345 // xrct.width = pData->geomBorder.width;
1346 // xrct.height = pData->geomBorder.height;
1347 
1348 
1349  if(pData->pixmap2 != None) // first part was already done
1350  {
1351  pxTemp = pData->pixmap2;
1352  }
1353  else
1354  {
1355  pxTemp = XCreatePixmap(pDisplay, wID, pData->iW + 4, pData->iH + 4,
1356  DefaultDepth(pDisplay, DefaultScreen(pDisplay)));
1357 
1358  if(pxTemp == None)
1359  {
1360  WB_ERROR_PRINT("%s - * BUG * line %d\n", __FUNCTION__, __LINE__);
1361  XFreeGC(pDisplay, gc);
1362  return 0;
1363  }
1364 
1365  if(pData->pixmap != None) // just in case
1366  {
1367  XCopyArea(pDisplay, pData->pixmap, pxTemp, gc,
1368  0, 0, pData->iW, pData->iH,
1369  pData->geomBorder.x + 2, pData->geomBorder.y + 2);
1370  }
1371  else
1372  {
1373  // TODO: erase the background of the drawable, WBEraseBackground maybe?
1374  }
1375 
1376  // this fixes the border areas properly
1377 
1378  xgcv.line_width = 3;
1379  xgcv.function = GXcopy; // copy
1380  xgcv.cap_style = CapProjecting;
1381 
1382  XChangeGC(pDisplay, gc, GCCapStyle | GCFunction | GCLineWidth, &xgcv);
1383 
1384  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, 0,0, pData->iW + 3, 0);
1385  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, pData->iW + 3, 0, pData->iW + 3, pData->iH + 3);
1386  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, pData->iW + 3, pData->iH + 3, 0, pData->iH + 3);
1387  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, 0, pData->iH + 3, 0, 0);
1388 
1389  pData->pixmap2 = pxTemp; // temporarily cache it here (I'll juggle it after allocating 2nd pixmap)
1390 
1391  // next, I must create a *new* temporary pixmap as my 'working' pixmap. the previous one is the 'reference' pixmap
1392 
1393  pxTemp = XCreatePixmap(pDisplay, wID, pData->iW + 4, pData->iH + 4,
1394  DefaultDepth(pDisplay, DefaultScreen(pDisplay)));
1395 
1396  if(pxTemp == None)
1397  {
1398  WB_ERROR_PRINT("%s - * BUG * line %d\n", __FUNCTION__, __LINE__);
1399 
1400  XFreePixmap(WBGetDefaultDisplay(), pData->pixmap2); // restartability
1401  pData->pixmap2 = None;
1402 
1403  XFreeGC(pDisplay, gc);
1404  return 0;
1405  }
1406 
1407  if(pData->pixmap != None) // just in case, test for it
1408  {
1409  XFreePixmap(WBGetDefaultDisplay(), pData->pixmap);
1410  }
1411 
1412  pData->pixmap = pData->pixmap2; // NOW, ref pixmap (with image and border) goes into 'pixmap'
1413  pData->pixmap2 = pxTemp; // and the 'working' copy into 'pixmap2' (which is also 'pxTemp')
1414 
1415  // make an exact duplicate without any clipping regions
1416  XCopyArea(pDisplay, pData->pixmap, pxTemp, gc,
1417  0, 0, pData->iW + 4, pData->iH + 4, 0, 0); // make a good copy of it at least once
1418  }
1419 
1420  if(pData->nIter <= 1)
1421  {
1422 // WBClearWindow(wID, gc); GC doesn't have a clip region yet, don't do this
1423  XClearWindow(pDisplay, wID); // erase background
1424  }
1425  else if(pData->nIter == SPLASH_FRAMERATE / 2) // after first half second
1426  {
1427  geomText.x = pData->geomBorder.x + 2;
1428  geomText.y = pData->geomBorder.y + 2;
1429  geomText.width = pData->geomBorder.width - 4;
1430  geomText.height = pData->geomBorder.height - 4;
1431 
1432  geomText.y += (geomText.height * 2) / 3;
1433  geomText.height -= (geomText.height * 2) / 3; // bottom 1/3
1434 
1435  // copyright string is 1 or 2 lines, for now use whatever font I end up with and draw lines separately
1436 
1437  if(fontSet != None)
1438  {
1439  WB_RECT rctBounds;
1440  rctBounds.left = geomText.x;
1441  rctBounds.top = geomText.y;
1442  rctBounds.right = rctBounds.left + geomText.width;
1443  rctBounds.bottom = rctBounds.top + geomText.height;
1444 
1445  DTDrawMultiLineText(fontSet, pData->szCopyright, pDisplay, gc, pData->pixmap,
1446  -8, 0, &rctBounds, DTAlignment_VCENTER | DTAlignment_HCENTER);
1447 #if 0
1448  p1 = pData->szCopyright;
1449  p2 = strchr(p1, '\n');
1450 
1451  if(p2)
1452  {
1453  *(p2++) = 0;
1454  }
1455 
1456  if(!p2 || !*p2)
1457  {
1458  iX = geomText.x + (geomText.width - XTextWidth(pFont, p1, strlen(p1))) / 2;
1459  iY = geomText.y + (geomText.height - pFont->max_bounds.ascent + pFont->max_bounds.descent) / 2
1460  + pFont->max_bounds.ascent; // bottom of text
1461 
1462  XDrawString(pDisplay, pxTemp ? pxTemp : wID, gc, iX, iY, p1, strlen(p1));
1463  }
1464  else
1465  {
1466  iX = geomText.x + (geomText.width - XTextWidth(pFont, p1, strlen(p1))) / 2;
1467  iY = geomText.y + (geomText.height - 2 * (pFont->max_bounds.ascent + pFont->max_bounds.descent)) / 2
1468  + pFont->max_bounds.ascent; // bottom of text
1469 
1470  XDrawString(pDisplay, pxTemp ? pxTemp : wID, gc, iX, iY, p1, strlen(p1));
1471 
1472  iX = geomText.x + (geomText.width - XTextWidth(pFont, p2, strlen(p2))) / 2;
1473  iY += pFont->max_bounds.ascent + pFont->max_bounds.descent;
1474 
1475  XDrawString(pDisplay, pxTemp ? pxTemp : wID, gc, iX, iY, p2, strlen(p2));
1476  }
1477 
1478  if(p2)
1479  {
1480  *(p2 - 1) = '\n'; // restore it for next time
1481  }
1482 #endif // 0
1483  }
1484  else
1485  {
1486  fprintf(stderr, "NO FONT, iter=%d\n", pData->nIter);
1487  }
1488 
1489  // make an exact duplicate without any clipping regions
1490 
1491  XCopyArea(pDisplay, pData->pixmap, pxTemp, gc,
1492  0, 0, pData->iW + 4, pData->iH + 4, 0, 0);
1493 
1494  // now, grab an XImage for it
1495 
1496  pData->pImage = XGetImage(pDisplay, pxTemp, 0, 0, pData->iW + 4, pData->iH + 4,
1497  0xffffffff, XYPixmap);
1498  }
1499 
1500  // TODO: consider creating clip regions to improve performance
1501 
1502  if(pData->nIter >= SPLASH_FRAMERATE / 2) // after first half second
1503  {
1504  iTimeStart = 1250 * SPLASH_FRAMERATE; // 1.5 seconds' worth in msecs, not seconds
1505  iTimeEnd = (SPLASH_TIME + 1000) * SPLASH_FRAMERATE
1506  - 500 * SPLASH_FRAMERATE; // 1/2 sec before end
1507 
1508  if(iTimeStart + 1000 * SPLASH_FRAMERATE / 2 > iTimeEnd)
1509  {
1510  iTimeStart = iTimeEnd - SPLASH_FRAMERATE / 2;
1511  }
1512 
1513  // drawing the 'gleam' diagonally from upper left to lower right
1514  // TODO: make this optional?
1515 
1516  if(pData->nIter * 1000 > iTimeEnd)
1517  {
1518  if(pData->pImage)
1519  {
1520  XPutImage(pDisplay, pData->pixmap2, gc, pData->pImage, 0, 0, 0, 0, pData->iW + 4, pData->iH + 4);
1521 
1522  XDestroyImage(pData->pImage); // destroy it now that I'm done with it
1523  pData->pImage = NULL; // no longer stored (already cleaned up)
1524  }
1525  }
1526  else if(pData->nIter * 1000 >= iTimeStart && pData->nIter * 1000 <= iTimeEnd)
1527  {
1528  int iDelta = iTimeEnd - iTimeStart + 1;
1529  int iTemp;
1530  XImage *pI; // the image I'll be manipulating
1531 
1532  iTemp = pData->nIter * 1000L - iTimeStart;
1533  iTemp = (int)(((long long)iTemp * (long long)iTemp) / (iTimeEnd - iTimeStart));
1534 
1535  // get the starting points and end points
1536  iX = 2 * (pData->iW * iTemp / iDelta + pData->iW / (2 * iDelta) / 1000); // x pos of top
1537  iY = 2 * (pData->iH * iTemp / iDelta + pData->iH / (2 * iDelta) / 1000); // y pos of left
1538 
1539  // will draw the line from 0,iY to iX,0
1540 
1541  if(!pData->pImage)
1542  {
1543  pData->pImage = XGetImage(pDisplay, pData->pixmap2, 0, 0, pData->iW + 4, pData->iH + 4,
1544  0xffffffff, // I've tried 0, 1, and THIS value - no apparent difference
1545  XYPixmap); // TODO: use ZPixmap instead?
1546  }
1547 
1548  pI = pData->pImage;
1549 
1550  if(pI && !pData->pImageData)
1551  {
1552  // this formula can be found in the xorg-server source:
1553  // length = ximage->bytes_per_line * ximage->height;
1554  // this is from 'xnestGetImage' in hw/xnest/GCOps.c
1555  // note that they don't include 'depth' in that. when I exclude 'depth', it doesn't work
1556  // TODO: do I need to pay attention to PADDING? docs and source suggest 'no'
1557 
1558 // WB_ERROR_PRINT("TEMPORARY: %s - bytes_per_line=%d, height=%d, depth=%d\n", __FUNCTION__,
1559 // pI->bytes_per_line, pI->height, pI->depth);
1560 
1561  pData->cbImageData = PXM_GetImageDataLength(pI);
1562  pData->pImageData = WBAlloc(pData->cbImageData + 4);
1563 
1564  if(pData->pImageData)
1565  {
1566  memcpy(pData->pImageData, PXM_GetImageDataPtr(pI), pData->cbImageData);
1567  }
1568  }
1569 
1570  if(!pI)
1571  {
1572  // don't do anything
1573  }
1574  else
1575  {
1576 #if defined(GLEAM_OLD)
1577 #define GLEAM_WIDTH 10
1578  static int aLuma[GLEAM_WIDTH + 1] = { 255, 249, 231, 202, 167, 128, 88, 53, 24, 6, 0 }; // 'luma' values for "the gleam" based on offset from center
1579 #elif defined(GLEAM_NARROW)
1580 #define GLEAM_WIDTH 17
1581  static int aLuma[GLEAM_WIDTH + 1] = { 255, 202, 161, 128, 101, 80, 64, 51, 40,
1582  32, 25, 20, 16, 13, 10, 8, 6, 5 }; // similar but 1/r^2 version (no cos)
1583 #else // WIDE gleam
1584 #define GLEAM_WIDTH 29
1585  static int aLuma[GLEAM_WIDTH + 1] = { 255,225,198,175,154,136,120,106,93,82,72,64,
1586  56,50,44,39,34,30,26,23,21,18,16,14,12,11,10,8,7,7 }; // a bit wider, more obvious
1587 #endif // GLEAM_OLD, GLEAM_NARROW
1588 
1589  int iX0, iY0, iX1, i1, i2, iW, iL, iMaxX, iMaxY;
1590 
1591  // NOW I get the fun of directly manipulating my image. W00T!
1592 
1593  // effectively I do this: XDrawLine(pDisplay, pxTemp, gc, -2, iY, iX, -2) and it's 19 pixels wide
1594 
1595  // So the line has a width of '2*GLEAM_WIDTH + 1' pixels. The pixels represent a white reflection
1596  // centering at the coordinates I specified above, that is the line from -2, iY to iX, -2 . This
1597  // actually SHOULD be offset by 2 pixels so that it does not affect the border, but that's less iomportant
1598 
1599  // draw the line. 19 pixels wide is actually +/- 9. We start with a single pixel-width line, then
1600  // do a for loop from/to +/- GLEAM_WIDTH on the X axis, keeping Y constant. if Y did not change, skip it.
1601 
1602  iMaxX = pData->iW;
1603  iMaxY = pData->iH;
1604 
1605  for(iX0 = -GLEAM_WIDTH, iY0 = iY + GLEAM_WIDTH; iX0 < iMaxX + GLEAM_WIDTH && iY0 > -GLEAM_WIDTH; iX0++)
1606  {
1607  i1 = (int)(((iX - iX0) * (long long)iMaxY) / iMaxX); // a muldiv conversion (where I should be)
1608 
1609  if(iY0 > i1) // so I don't repeat what I've done
1610  {
1611  for(; iY0 > i1; iY0--) // remember, top < bottom so if I start at the bottom, must SUBTRACT
1612  {
1613  // +/- GLEAM_WIDTH pixels
1614  i2 = iX0 + GLEAM_WIDTH;
1615  for(iX1=iX0 - GLEAM_WIDTH, iW = -GLEAM_WIDTH; iX1 <= i2; iX1++, iW++)
1616  {
1617  // is my current iX1, iY0 inside the desired rectangle? If so, calculate the
1618  // new color and assign it to this point.
1619 
1620  if(iX1 > 2 && iX1 < iMaxX &&
1621  iY0 > 2 && iY0 < iMaxY)
1622  {
1623  XColor clrTemp, clrPixel;
1624  int iY, iU, iV, iR, iG, iB;
1625 
1626  clrPixel.pixel = XGetPixel(pI, iX1 + 2, iY0 + 2);
1627  PXM_PixelToRGB(&(pData->cmap), &clrPixel);
1628 
1629  PXM_RGBToYUV(clrPixel.red >> 8, clrPixel.green >> 8, clrPixel.blue >> 8,
1630  &iY, &iU, &iV);
1631 
1632  iL = aLuma[abs(iW)]; // the 'Luma' constant, 0-255 (with 255 = 'white')
1633 
1634  // new pixel luma will be: luma * (1 + iL / 128) (maxed at 255)
1635  // if new luma is > 255, reduce iU and iV (delta from 128) by the 'factor'
1636  // such that iU = 128 + (iU - 128) * factor [etc.]
1637  // and the 'factor' would be 255 / iU (the new value)
1638 
1639  iY = ((short)iY * ((short)256 + (short)iL)) / (short)256; // >> 6;/// (short)128;
1640 
1641  if(iY > 255)
1642  {
1643  iU = (short)128 + (((short)iU - (short)128) * (short)256) / (short)iY;
1644  iV = (short)128 + (((short)iV - (short)128) * (short)256) / (short)iY;
1645  iY = 255;
1646  }
1647 
1648  PXM_YUVToRGB(iY, iU, iV, &iR, &iG, &iB);
1649 
1650  clrTemp.red = iR << 8;
1651  clrTemp.green = iG << 8;
1652  clrTemp.blue = iB << 8;
1653  clrTemp.flags = DoRed | DoGreen | DoBlue;
1654 
1655  PXM_RGBToPixel(&(pData->cmap), &clrTemp);
1656 
1657  XPutPixel(pI, iX1 + 2, iY0 + 2, clrTemp.pixel);
1658  }
1659  }
1660  }
1661  }
1662  }
1663 
1664  // TODO: assign clipping region to gc
1665 
1666 // llTick -= WBGetTimeIndex();
1667 
1668  XPutImage(pDisplay, pData->pixmap2, gc, pI, 0, 0, 0, 0, pData->iW + 4, pData->iH + 4);
1669  XFlush(pDisplay); // make sure
1670 
1671 // llTick += WBGetTimeIndex();
1672 
1673  if(pData->pImageData)
1674  {
1675  // restore previous image data now that I'm done messing with it
1676  memcpy(PXM_GetImageDataPtr(pI), pData->pImageData, pData->cbImageData); // restore previous image data
1677  }
1678  else
1679  {
1680  XDestroyImage(pI);
1681  pData->pImage = NULL; // no longer stored (a fallback)
1682  }
1683 
1684 // WB_ERROR_PRINT("TEMPORARY: %s - pixel stuff takes %llu millis\n", __FUNCTION__, llTick);
1685  }
1686 
1687 #if 0
1688  // using some interesting raster ops, 'highlight' the pixels along the line of
1689  // (0,iY),(iX,0) or (iX,pData->iH-1),(pData->iW-1,iY)
1690 
1691  XSetForeground(pDisplay, gc, pData->clrWhite & 0x404040);
1692 
1693  xgcv.line_width = 19;
1694  xgcv.function = GXor; // a or b
1695  xgcv.cap_style = CapProjecting;
1696 
1697  XChangeGC(pDisplay, gc, GCCapStyle | GCFunction | GCLineWidth, &xgcv);
1698 
1699  if(!bInvert)
1700  {
1701  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, -2, iY, iX, -2);
1702  }
1703  else
1704  {
1705  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, iX, pData->iH + 4, pData->iW + 4, iY);
1706  }
1707 
1708  XSetForeground(pDisplay, gc, pData->clrWhite & 0x808080);
1709 
1710  xgcv.line_width = 11;
1711  xgcv.function = GXor; // a or b
1712  xgcv.cap_style = CapProjecting;
1713 
1714  XChangeGC(pDisplay, gc, GCCapStyle | GCFunction | GCLineWidth, &xgcv);
1715 
1716  if(!bInvert)
1717  {
1718  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, -2, iY, iX, -2);
1719  }
1720  else
1721  {
1722  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, iX, pData->iH + 4, pData->iW + 4, iY);
1723  }
1724 
1725  XSetForeground(pDisplay, gc, pData->clrWhite & 0xc0c0c0);
1726 
1727  xgcv.line_width = 5;
1728  xgcv.function = GXor; // a or b
1729  xgcv.cap_style = CapProjecting;
1730 
1731  XChangeGC(pDisplay, gc, GCCapStyle | GCFunction | GCLineWidth, &xgcv);
1732 
1733  if(!bInvert)
1734  {
1735  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, -2, iY, iX, -2);
1736  }
1737  else
1738  {
1739  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, iX, pData->iH + 4, pData->iW + 4, iY);
1740  }
1741 
1742  XSetForeground(pDisplay, gc, pData->clrWhite);
1743 
1744  xgcv.line_width = 1;
1745  xgcv.function = GXcopy; // copy
1746  xgcv.cap_style = CapProjecting;
1747 
1748  XChangeGC(pDisplay, gc, GCCapStyle | GCFunction | GCLineWidth, &xgcv);
1749 
1750  if(!bInvert)
1751  {
1752  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, -2, iY, iX, -2);
1753  }
1754  else
1755  {
1756  XDrawLine(pDisplay, pxTemp ? pxTemp : wID, gc, iX, pData->iH + 4, pData->iW + 4, iY);
1757  }
1758 #endif // 0
1759  }
1760  }
1761 
1762  if(pxTemp) // using the 2nd pixmap to do the work, thus making the whole screen update at once
1763  {
1764  XCopyArea(pDisplay, pxTemp, wID, gc, 0, 0, pData->iW + 4, pData->iH + 4, pData->geomBorder.x, pData->geomBorder.y);
1765  }
1766 
1767  XFreeGC(pDisplay, gc);
1768  XSync(pDisplay, 0); // force update NOW
1769  WBValidateGeom(wID, NULL);
1770 // WBEndPaint(wID, gc);
1771 
1772  return 1; // processed
1773 }
1774 
1775