Merge remote-tracking branch 'origin/master' into for-steve
[sxemacs] / src / ui / lwlib / xlwtabs.c
1  /* Tabs Widget for SXEmacs.
2     Copyright (C) 1999 Edward A. Falk
3
4     This file is part of SXEmacs.
5
6 SXEmacs is free software: you can redistribute it and/or modify it
7 under the terms of the GNU General Public License as published by the
8 Free Software Foundation, either version 3 of the License, or (at your
9 option) any later version.
10
11 SXEmacs is distributed in the hope that it will be
12 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program.  If not, see <http://www.gnu.org/licenses/>. */
18
19  /* Synched up with: Tabs.c 1.27.
20
21     #### This file contains essential XEmacs related fixes to the original
22     verison of the Tabs widget. Be VERY careful about syncing if you ever
23     update to a more recent version. In general this is probably now a
24     bad idea. */
25
26  /*
27   * Tabs.c - Index Tabs composite widget
28   *
29   * Author: Edward A. Falk
30   *        falk@falconer.vip.best.com
31   *
32   * Date: July 29, 1997
33   *
34   *
35   * Overall layout of this widget is as follows:
36   *
37   *   ________ ,---------. _________
38   *  |  label ||  Label  ||  Label  |  \ tabs
39   *  |________||         ||_________|  /
40   *  |+----------------------------+|  \
41   *  ||                            ||  |
42   *  ||  child widget window       ||   > frame
43   *  |+----------------------------+|  |
44   *  +------------------------------+  /
45   *
46   * The height of the tabs includes the shadow width, top and bottom
47   * margins, and the height of the text.
48   *
49   * The height of the frame includes the top and bottom shadow width and the
50   * size of the child widget window.
51   *
52   * The tabs overlap the frame and each other vertically by the shadow
53   * width, so that when the topmost tab is drawn, it obliterates part of
54   * the frame.
55   */
56
57 /*
58  * TODO: min child height = tab height
59  */
60
61 #include        <config.h>
62 #include        <stdio.h>
63
64 #include        <X11/Xlib.h>
65 #include        <X11/IntrinsicP.h>
66 #include        <X11/StringDefs.h>
67
68 #include        "lwlib-internal.h"
69 #include        "ui/X11/xmu.h"
70 #include        "xlwtabsP.h"
71 #include        "xlwgcs.h"
72
73 #define MIN_WID         10
74 #define MIN_HGT         10
75 #define INDENT          3       /* tabs indented from edge by this much */
76 #define SPACING         0       /* distance between tabs */
77 #define SHADWID         1       /* default shadow width */
78 #define TABDELTA        2       /* top tab grows this many pixels */
79 #define TABLDELTA       2       /* top tab label offset this many pixels */
80
81 /****************************************************************
82  *
83  * IndexTabs Resources
84  *
85  ****************************************************************/
86
87 static char defaultTranslations[] = "\
88         <BtnUp>:                select()        \n\
89         <FocusIn>:              highlight()     \n\
90         <FocusOut>:             unhighlight()   \n\
91         <Key>Page_Up:           page(up)        \n\
92         <Key>KP_Page_Up:        page(up)        \n\
93         <Key>Prior:             page(up)        \n\
94         <Key>KP_Prior:          page(up)        \n\
95         <Key>Page_Down:         page(down)      \n\
96         <Key>KP_Page_Down:      page(down)      \n\
97         <Key>Next:              page(down)      \n\
98         <Key>KP_Next:           page(down)      \n\
99         <Key>Home:              page(home)      \n\
100         <Key>KP_Home:           page(home)      \n\
101         <Key>End:               page(end)       \n\
102         <Key>KP_End:            page(end)       \n\
103         <Key>Up:                highlight(up)   \n\
104         <Key>KP_Up:             highlight(up)   \n\
105         <Key>Down:              highlight(down) \n\
106         <Key>KP_Down:           highlight(down) \n\
107         <Key> :                 page(select)    \n\
108          ";
109
110 static char accelTable[] = "    #augment\n\
111         <Key>Page_Up:           page(up)        \n\
112         <Key>KP_Page_Up:        page(up)        \n\
113         <Key>Prior:             page(up)        \n\
114         <Key>KP_Prior:          page(up)        \n\
115         <Key>Page_Down:         page(down)      \n\
116         <Key>KP_Page_Down:      page(down)      \n\
117         <Key>Next:              page(down)      \n\
118         <Key>KP_Next:           page(down)      \n\
119         <Key>Home:              page(home)      \n\
120         <Key>KP_Home:           page(home)      \n\
121         <Key>End:               page(end)       \n\
122         <Key>KP_End:            page(end)       \n\
123         <Key>Up:                highlight(up)   \n\
124         <Key>KP_Up:             highlight(up)   \n\
125         <Key>Down:              highlight(down) \n\
126         <Key>KP_Down:           highlight(down) \n\
127         <Key> :                 page(select)    \n\
128          ";
129 static XtAccelerators defaultAccelerators;      /* #### Never used */
130
131 #define offset(field)   XtOffsetOf(TabsRec, tabs.field)
132 static XtResource resources[] = {
133
134         {XtNselectInsensitive, XtCSelectInsensitive, XtRBoolean,
135          sizeof(Boolean),
136          offset(selectInsensitive), XtRImmediate, (XtPointer) True}
137         ,
138         {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
139          offset(font), XtRString, (XtPointer) XtDefaultFont}
140         ,
141         {XtNinternalWidth, XtCWidth, XtRDimension, sizeof(Dimension),
142          offset(internalWidth), XtRImmediate, (XtPointer) 4}
143         ,
144         {XtNinternalHeight, XtCHeight, XtRDimension, sizeof(Dimension),
145          offset(internalHeight), XtRImmediate, (XtPointer) 4}
146         ,
147         {XtNborderWidth, XtCBorderWidth, XtRDimension, sizeof(Dimension),
148          XtOffsetOf(RectObjRec, rectangle.border_width), XtRImmediate,
149          (XtPointer) 0}
150         ,
151         {XtNtopWidget, XtCTopWidget, XtRWidget, sizeof(Widget),
152          offset(topWidget), XtRImmediate, NULL}
153         ,
154         {XtNhighlightWidget, XtCHighlightWidget, XtRWidget, sizeof(Widget),
155          offset(hilight), XtRImmediate, NULL}
156         ,
157         {XtNcallback, XtCCallback, XtRCallback, sizeof(XtPointer),
158          offset(callbacks), XtRCallback, NULL}
159         ,
160         {XtNpopdownCallback, XtCCallback, XtRCallback, sizeof(XtPointer),
161          offset(popdownCallbacks), XtRCallback, NULL}
162         ,
163         {XtNbeNiceToColormap, XtCBeNiceToColormap, XtRBoolean, sizeof(Boolean),
164          offset(be_nice_to_cmap), XtRImmediate, (XtPointer) True}
165         ,
166         {XtNtopShadowContrast, XtCTopShadowContrast, XtRInt, sizeof(int),
167          offset(top_shadow_contrast), XtRImmediate, (XtPointer) 20},
168         {XtNbottomShadowContrast, XtCBottomShadowContrast, XtRInt, sizeof(int),
169          offset(bot_shadow_contrast), XtRImmediate, (XtPointer) 40},
170         {XtNinsensitiveContrast, XtCInsensitiveContrast, XtRInt, sizeof(int),
171          offset(insensitive_contrast), XtRImmediate, (XtPointer) 33},
172         {XtNaccelerators, XtCAccelerators, XtRAcceleratorTable,
173          sizeof(XtTranslations),
174          XtOffsetOf(TabsRec, core.accelerators), XtRString, accelTable}
175         ,
176 };
177 #undef  offset
178
179         /* constraint resources */
180
181 #define offset(field)   XtOffsetOf(TabsConstraintsRec, tabs.field)
182 static XtResource tabsConstraintResources[] = {
183         {XtNtabLabel, XtCLabel, XtRString, sizeof(String),
184          offset(label), XtRString, NULL}
185         ,
186         {XtNtabLeftBitmap, XtCLeftBitmap, XtRBitmap, sizeof(Pixmap),
187          offset(left_bitmap), XtRImmediate, None}
188         ,
189         {XtNtabForeground, XtCForeground, XtRPixel, sizeof(Pixel),
190          offset(foreground), XtRString, (XtPointer) XtDefaultForeground}
191         ,
192         {XtNresizable, XtCResizable, XtRBoolean, sizeof(Boolean),
193          offset(resizable), XtRImmediate, (XtPointer) True}
194         ,
195 };
196 #undef  offset
197
198 #if     !NeedFunctionPrototypes
199
200         /* FORWARD REFERENCES: */
201
202         /* member functions */
203
204 static void TabsClassInit();
205 static void TabsInit();
206 static void TabsResize();
207 static void TabsExpose();
208 static void TabsDestroy();
209 static void TabsRealize();
210 static Boolean TabsSetValues();
211 static Boolean TabsAcceptFocus();
212 static XtGeometryResult TabsQueryGeometry();
213 static XtGeometryResult TabsGeometryManager();
214 static void TabsChangeManaged();
215 static void TabsConstraintInitialize();
216 static Boolean TabsConstraintSetValues();
217
218         /* action procs */
219
220 static void TabsSelect();
221 static void TabsPage();
222 static void TabsHighlight();
223 static void TabsUnhighlight();
224
225         /* internal privates */
226
227 static void TabsAllocGCs();     /* get rendering GCs */
228 static void TabsFreeGCs();      /* return rendering GCs */
229 static void DrawTabs();         /* draw all tabs */
230 static void DrawTab();          /* draw one index tab */
231 static void DrawFrame();        /* draw frame around contents */
232 static void DrawTrim();         /* draw trim around a tab */
233 static void DrawBorder();       /* draw border */
234 static void DrawHighlight();    /* draw highlight */
235 static void UndrawTab();        /* undraw interior of a tab */
236 static void TabWidth();         /* recompute tab size */
237 static void GetPreferredSizes();        /* query all children for their sizes */
238 static void MaxChild();         /* find max preferred child size */
239 static int PreferredSize();     /* compute preferred size */
240 static int PreferredSize2();    /* compute preferred size */
241 static int PreferredSize3();    /* compute preferred size */
242 static void MakeSizeRequest();  /* try to change size */
243 static void getBitmapInfo();
244 static int TabLayout();         /* lay out tabs */
245 static void TabsShuffleRows();  /* bring current tab to bottom row */
246
247 static void TabsAllocFgGC();
248 static void TabsAllocGreyGC();
249
250 #else
251
252 static void TabsClassInit(void);
253 static void TabsInit(Widget req, Widget new, ArgList, Cardinal * nargs);
254 static void TabsConstraintInitialize(Widget, Widget, ArgList, Cardinal *);
255 static void TabsRealize(Widget, Mask *, XSetWindowAttributes *);
256 static void TabsDestroy(Widget w);
257 static void TabsResize(Widget w);
258 static void TabsExpose(Widget w, XEvent * event, Region region);
259 static Boolean TabsSetValues(Widget, Widget, Widget, ArgList, Cardinal *);
260 static Boolean TabsAcceptFocus(Widget, Time *);
261 static Boolean TabsConstraintSetValues(Widget, Widget, Widget,
262                                        ArgList, Cardinal *);
263 static XtGeometryResult TabsQueryGeometry(Widget,
264                                           XtWidgetGeometry *,
265                                           XtWidgetGeometry *);
266 static XtGeometryResult TabsGeometryManager(Widget, XtWidgetGeometry *,
267                                             XtWidgetGeometry *);
268 static void TabsChangeManaged(Widget w);
269
270 static void TabsSelect(Widget, XEvent *, String *, Cardinal *);
271 static void TabsPage(Widget, XEvent *, String *, Cardinal *);
272 static void TabsHighlight(Widget, XEvent *, String *, Cardinal *);
273 static void TabsUnhighlight(Widget, XEvent *, String *, Cardinal *);
274
275 static void DrawTabs(TabsWidget tw, Bool labels);
276 static void DrawTab(TabsWidget tw, Widget child, Bool labels);
277 static void DrawFrame(TabsWidget tw);
278 static void DrawTrim(TabsWidget, int x, int y,
279                      int wid, int hgt, Bool bottom, Bool undraw);
280 static void DrawBorder(TabsWidget tw, Widget child, Bool undraw);
281 static void DrawHighlight(TabsWidget tw, Widget child, Bool undraw);
282 static void UndrawTab(TabsWidget tw, Widget child);
283
284 static void TabWidth(Widget w);
285 static int TabLayout(TabsWidget, Dimension wid, Dimension hgt,
286                      Dimension * r_hgt, Bool query_only);
287 static void GetPreferredSizes(TabsWidget);
288 static void MaxChild(TabsWidget, Widget except, Dimension, Dimension);
289 static void TabsShuffleRows(TabsWidget tw);
290 static int PreferredSize(TabsWidget,
291                          Dimension * reply_width, Dimension * reply_height,
292                          Dimension * reply_cw, Dimension * reply_ch);
293 static int PreferredSize2(TabsWidget, Dimension cw, Dimension ch,
294                           Dimension * rw, Dimension * rh);
295 static int PreferredSize3(TabsWidget, Dimension wid, Dimension hgt,
296                           Dimension * rw, Dimension * rh);
297 static void MakeSizeRequest(TabsWidget);
298
299 static void TabsAllocGCs(TabsWidget);
300 static void TabsFreeGCs(TabsWidget);
301 static void getBitmapInfo(TabsWidget tw, TabsConstraints tab);
302 static void TabsAllocFgGC(TabsWidget tw);
303 static void TabsAllocGreyGC(TabsWidget tw);
304
305 #endif
306
307 #define AddRect(i,xx,yy,w,h)    \
308   do{rects[(i)].x=(xx); rects[i].y=(yy);        \
309      rects[i].width=(w); rects[i].height=(h);}while(0)
310
311 static XtActionsRec actionsList[] = {
312         {"select", TabsSelect},
313         {"page", TabsPage},
314         {"highlight", TabsHighlight},
315         {"unhighlight", TabsUnhighlight},
316 };
317
318 /****************************************************************
319 *
320 * Full class record constant
321 *
322 ****************************************************************/
323
324 #ifndef NEED_MOTIF
325 #define SuperClass      (&constraintClassRec)
326 #else
327 #define SuperClass      (&xmManagerClassRec)
328 #endif
329
330 TabsClassRec tabsClassRec = {
331         {
332 /* core_class fields      */
333          /* superclass         */ (WidgetClass) SuperClass,
334          /* class_name         */ "Tabs",
335          /* widget_size        */ sizeof(TabsRec),
336          /* class_initialize   */ TabsClassInit,
337                                         /* class_part_init    */ NULL,
338                                         /* TODO? */
339          /* class_inited       */ FALSE,
340          /* initialize         */ TabsInit,
341          /* initialize_hook    */ NULL,
342          /* realize            */ TabsRealize,
343          /* actions            */ actionsList,
344          /* num_actions        */ XtNumber(actionsList),
345          /* resources          */ resources,
346          /* num_resources      */ XtNumber(resources),
347          /* xrm_class          */ NULLQUARK,
348          /* compress_motion    */ TRUE,
349 #if XtSpecificationRelease < 6
350          /* compress_exposure  */ XtExposeCompressMaximal,
351 #else
352          /* compress_exposure  */ XtExposeCompressMaximal | XtExposeNoRegion,
353 #endif
354          /* compress_enterleave */ TRUE,
355          /* visible_interest   */ TRUE,
356          /* destroy            */ TabsDestroy,
357          /* resize             */ TabsResize,
358          /* expose             */ TabsExpose,
359          /* set_values         */ TabsSetValues,
360          /* set_values_hook    */ NULL,
361          /* set_values_almost  */ XtInheritSetValuesAlmost,
362          /* get_values_hook    */ NULL,
363          /* accept_focus       */ TabsAcceptFocus,
364          /* version            */ XtVersion,
365          /* callback_private   */ NULL,
366          /* tm_table           */ defaultTranslations,
367          /* query_geometry     */ TabsQueryGeometry,
368          /* display_accelerator */ XtInheritDisplayAccelerator,
369          /* extension          */ NULL
370          }
371         ,
372         {
373 /* composite_class fields */
374          /* geometry_manager   */ TabsGeometryManager,
375          /* change_managed     */ TabsChangeManaged,
376                                                         /* insert_child       */ XtInheritInsertChild,
377                                                         /* TODO? */
378                                                         /* delete_child       */ XtInheritDeleteChild,
379                                                         /* TODO? */
380          /* extension          */ NULL
381          }
382         ,
383         {
384 /* constraint_class fields */
385          /* subresources       */ tabsConstraintResources,
386          /* subresource_count  */ XtNumber(tabsConstraintResources),
387          /* constraint_size    */ sizeof(TabsConstraintsRec),
388          /* initialize         */ TabsConstraintInitialize,
389          /* destroy            */ NULL,
390          /* set_values         */ TabsConstraintSetValues,
391          /* extension          */ NULL,
392          }
393         ,
394 #ifdef  NEED_MOTIF
395 /* Manager Class fields */
396         {
397          /* translations             */ NULL,
398          /* syn_resources            */ NULL,
399          /* num_syn_resources        */ 0,
400          /* syn_constraint_resources */ NULL,
401          /* num_syn_constraint_resources */ 0,
402          /* parent_process           */ XmInheritParentProcess,
403          /* extension                */ NULL
404          }
405         ,
406 #endif
407         {
408 /* Tabs class fields */
409          /* extension          */ NULL,
410          }
411 };
412
413 WidgetClass tabsWidgetClass = (WidgetClass) & tabsClassRec;
414
415 #define TabsNumChildren(tw) (((TabsWidget)tw)->composite.num_children)
416 #define TabVisible(tab) \
417         (XtIsManaged(tab) && \
418          ((TabsConstraints)((tab)->core.constraints))->tabs.visible)
419 \f
420 /****************************************************************
421  *
422  * Member Procedures
423  *
424  ****************************************************************/
425
426 static void TabsClassInit(void)
427 {
428         defaultAccelerators = XtParseAcceleratorTable(accelTable);
429         /* TODO: register converter for labels? */
430 }
431
432         /* Init a newly created tabs widget.  Compute height of tabs
433          * and optionally compute size of widget. */
434
435 /* ARGSUSED */
436
437 static void
438 TabsInit(Widget request, Widget new, ArgList args, Cardinal * num_args)
439 {
440         TabsWidget newTw = (TabsWidget) new;
441
442         newTw->tabs.numRows = 0;
443         newTw->tabs.realRows = 0;
444
445         GetPreferredSizes(newTw);
446
447         /* height is easy, it's the same for all tabs:
448          *  TODO: font height + height of tallest bitmap.
449          */
450         newTw->tabs.tab_height = 2 * newTw->tabs.internalHeight + SHADWID;
451
452         if (newTw->tabs.font != NULL)
453                 newTw->tabs.tab_height += newTw->tabs.font->max_bounds.ascent +
454                     newTw->tabs.font->max_bounds.descent;
455
456         /* GC allocation is deferred until XtRealize() */
457
458         /* if size not explicitly set, set it to our preferred size now. */
459
460         if (request->core.width == 0 || request->core.height == 0) {
461                 Dimension w, h;
462                 PreferredSize(newTw, &w, &h, NULL, NULL);
463                 if (request->core.width == 0)
464                         new->core.width = w;
465                 if (request->core.height == 0)
466                         new->core.height = h;
467                 XtClass(new)->core_class.resize(new);
468         }
469
470         /* defer GC allocation, etc., until Realize() time. */
471         newTw->tabs.foregroundGC =
472             newTw->tabs.backgroundGC =
473             newTw->tabs.greyGC = newTw->tabs.topGC = newTw->tabs.botGC = None;
474
475         newTw->tabs.grey50 = None;
476
477         newTw->tabs.needs_layout = False;
478
479         newTw->tabs.hilight = NULL;
480
481 #ifdef  NEED_MOTIF
482         newTw->manager.navigation_type = XmTAB_GROUP;
483         newTw->manager.traversal_on = True;
484 #endif
485 }
486
487         /* Init the constraint part of a new tab child.  Compute the
488          * size of the tab.
489          */
490 /* ARGSUSED */
491 static void
492 TabsConstraintInitialize(Widget request, Widget new,
493                          ArgList args, Cardinal * num_args)
494 {
495         TabsConstraints tab = (TabsConstraints) new->core.constraints;
496         tab->tabs.greyAlloc = False;    /* defer allocation of pixel */
497         tab->tabs.visible = False;
498
499         getBitmapInfo((TabsWidget) XtParent(new), tab);
500         TabWidth(new);
501 }
502
503         /* Called when tabs widget first realized.  Create the window
504          * and allocate the GCs
505          */
506
507 static void
508 TabsRealize(Widget w, Mask * valueMask, XSetWindowAttributes * attributes)
509 {
510         TabsWidget tw = (TabsWidget) w;
511
512         attributes->bit_gravity = NorthWestGravity;
513         *valueMask |= CWBitGravity;
514
515         SuperClass->core_class.realize(w, valueMask, attributes);
516
517         TabsAllocGCs(tw);
518 }
519
520 static void TabsDestroy(Widget w)
521 {
522         TabsFreeGCs((TabsWidget) w);
523 }
524
525         /* Parent has resized us.  This will require that the tabs be
526          * laid out again.
527          */
528
529 static void TabsResize(Widget w)
530 {
531         TabsWidget tw = (TabsWidget) w;
532         int i;
533         int num_children = tw->composite.num_children;
534         Widget *childP;
535         TabsConstraints tab;    /* #### unused */
536         Dimension cw, ch, bw;
537
538         /* Our size has now been dictated by the parent.  Lay out the
539          * tabs, lay out the frame, lay out the children.  Remember
540          * that the tabs overlap each other and the frame by shadowWidth.
541          * Also, the top tab is larger than the others, so if there's only
542          * one row, the widget must be made taller to accommodate this.
543          *
544          * Once the tabs are laid out, if there is more than one
545          * row, we may need to shuffle the rows to bring the top tab
546          * to the bottom row.
547          */
548
549         tw->tabs.needs_layout = False;
550
551         if (num_children > 0 && tw->composite.children != NULL) {
552                 /* Loop through the tabs and assign rows & x positions */
553                 (void)TabLayout(tw, tw->core.width, tw->core.height, NULL,
554                                 False);
555                 num_children = TabsNumChildren(tw);
556
557                 /* assign a top widget, bring it to bottom row. */
558                 TabsShuffleRows(tw);
559
560                 /* now assign child positions & sizes.  Positions are all the
561                  * same: just inside the frame.  Sizes are also all the same.
562                  */
563
564                 tw->tabs.child_width = cw = tw->core.width - 2 * SHADWID;
565                 tw->tabs.child_height = ch =
566                     tw->core.height < (tw->tabs.tab_total + 2 * SHADWID) ? 0 :
567                     tw->core.height - tw->tabs.tab_total - 2 * SHADWID;
568
569                 for (i = 0, childP = tw->composite.children;
570                      i < num_children; ++i, ++childP)
571                         if (XtIsManaged(*childP)) {
572                                 tab =
573                                     (TabsConstraints) (*childP)->core.
574                                     constraints;
575                                 bw = (*childP)->core.border_width;
576                                 /* Don't do anything if we can't see any of the child. */
577                                 if (ch >= bw * 2 && ch > 0 && cw >= bw * 2
578                                     && cw > 0)
579                                         XtConfigureWidget(*childP, SHADWID,
580                                                           tw->tabs.tab_total +
581                                                           SHADWID, cw - bw * 2,
582                                                           ch - bw * 2, bw);
583                         }
584                 if (XtIsRealized(w)) {
585                         XClearWindow(XtDisplay((Widget) tw),
586                                      XtWindow((Widget) tw));
587                         /* should not be necessary to explicitly repaint after a
588                          * resize, but XEmacs folks tell me it is.
589                          */
590                         XtClass(tw)->core_class.expose((Widget) tw, NULL, None);
591                 }
592         }
593 }                               /* Resize */
594
595         /* Redraw entire Tabs widget */
596
597 /* ARGSUSED */
598 static void TabsExpose(Widget w, XEvent * event, Region region)
599 {
600         TabsWidget tw = (TabsWidget) w;
601
602         if (tw->tabs.needs_layout)
603                 XtClass(w)->core_class.resize(w);
604
605         DrawTabs(tw, True);
606 }
607
608         /* Called when any Tabs widget resources are changed. */
609
610 /* ARGSUSED */
611 static Boolean
612 TabsSetValues(Widget current, Widget request, Widget new,
613               ArgList args, Cardinal * num_args)
614 {
615         TabsWidget curtw = (TabsWidget) current;
616         TabsWidget tw = (TabsWidget) new;
617         Boolean needRedraw = False;
618         Widget *childP;
619         unsigned int i;
620
621         if (tw->tabs.font != curtw->tabs.font ||
622             tw->tabs.internalWidth != curtw->tabs.internalWidth ||
623             tw->tabs.internalHeight != curtw->tabs.internalHeight) {
624                 tw->tabs.tab_height = 2 * tw->tabs.internalHeight + SHADWID;
625
626                 if (tw->tabs.font != NULL)
627                         tw->tabs.tab_height +=
628                             tw->tabs.font->max_bounds.ascent +
629                             tw->tabs.font->max_bounds.descent;
630
631                 /* Tab size has changed.  Resize all tabs and request a new size */
632                 for (i = 0, childP = tw->composite.children;
633                      i < tw->composite.num_children; ++i, ++childP)
634                         if (XtIsManaged(*childP))
635                                 TabWidth(*childP);
636                 PreferredSize(tw, &tw->core.width, &tw->core.height, NULL,
637                               NULL);
638                 needRedraw = True;
639                 tw->tabs.needs_layout = True;
640         }
641
642         /* TODO: if any color changes, need to recompute GCs and redraw */
643
644         if (tw->core.background_pixel != curtw->core.background_pixel ||
645             tw->core.background_pixmap != curtw->core.background_pixmap ||
646             tw->tabs.font != curtw->tabs.font)
647                 if (XtIsRealized(new)) {
648                         TabsFreeGCs(tw);
649                         TabsAllocGCs(tw);
650                         needRedraw = True;
651                 }
652
653         if (tw->core.sensitive != curtw->core.sensitive)
654                 needRedraw = True;
655
656         /* Highlit widget changed */
657         if (tw->tabs.hilight != curtw->tabs.hilight) {
658                 needRedraw = True;
659         }
660
661         /* If top widget changes, need to change stacking order, redraw tabs.
662          * Window system will handle the redraws.
663          */
664
665         if (tw->tabs.topWidget != curtw->tabs.topWidget) {
666                 if (XtIsRealized(tw->tabs.topWidget)) {
667                         Widget w = tw->tabs.topWidget;
668                         TabsConstraints tab =
669                             (TabsConstraints) w->core.constraints;
670
671                         XRaiseWindow(XtDisplay(w), XtWindow(w));
672 #ifdef  NEED_MOTIF
673                         XtVaSetValues(curtw->tabs.topWidget, XmNtraversalOn,
674                                       False, 0);
675                         XtVaSetValues(w, XmNtraversalOn, True, 0);
676 #endif
677
678                         if ((int)tab->tabs.row != (int)tw->tabs.numRows - 1) {
679                                 TabsShuffleRows(tw);
680                         }
681                         needRedraw = True;
682                 } else
683                         tw->tabs.needs_layout = True;
684         }
685
686         return needRedraw;
687 }
688
689         /* Called when any child constraint resources change. */
690
691 /* ARGSUSED */
692 static Boolean
693 TabsConstraintSetValues(Widget current, Widget request, Widget new,
694                         ArgList args, Cardinal * num_args)
695 {
696         TabsWidget tw = (TabsWidget) XtParent(new);
697         TabsConstraints ctab = (TabsConstraints) current->core.constraints;
698         TabsConstraints tab = (TabsConstraints) new->core.constraints;
699
700         /* if label changes, need to re-layout the entire widget */
701         /* if foreground changes, need to redraw tab label */
702
703         /* TODO: only need resize of new bitmap has different dimensions
704          * from old bitmap.
705          */
706
707         if (tab->tabs.label != ctab->tabs.label ||      /* Tab size has changed. */
708             tab->tabs.left_bitmap != ctab->tabs.left_bitmap) {
709                 TabWidth(new);
710                 tw->tabs.needs_layout = True;
711
712                 if (tab->tabs.left_bitmap != ctab->tabs.left_bitmap)
713                         getBitmapInfo(tw, tab);
714
715                 /* If there are no subclass ConstraintSetValues procedures remaining
716                  * to be invoked, and if the preferred size has changed, ask
717                  * for a resize.
718                  */
719                 if (XtClass((Widget) tw) == tabsWidgetClass)
720                         MakeSizeRequest(tw);
721         }
722
723         /* The child widget itself never needs a redisplay, but the parent
724          * Tabs widget might.
725          */
726
727         if (XtIsRealized(new)) {
728                 if (tw->tabs.needs_layout) {
729                         XClearWindow(XtDisplay((Widget) tw),
730                                      XtWindow((Widget) tw));
731                         XtClass(tw)->core_class.expose((Widget) tw, NULL, None);
732                 }
733
734                 else if (tab->tabs.foreground != ctab->tabs.foreground)
735                         DrawTab(tw, new, True);
736         }
737
738         return False;
739 }
740
741 static Boolean TabsAcceptFocus(Widget w, Time * t)
742 {
743         if (!w->core.being_destroyed && XtIsRealized(w) &&
744             XtIsSensitive(w) && XtIsManaged(w) && w->core.visible) {
745                 Widget p;
746                 for (p = XtParent(w); !XtIsShell(p); p = XtParent(p)) ;
747                 XtSetKeyboardFocus(p, w);
748                 return True;
749         } else
750                 return False;
751 }
752
753 /*
754  * Return preferred size.  Happily accept anything >= our preferred size.
755  * (TODO: is that the right thing to do?  Should we always return "almost"
756  * if offered more than we need?)
757  */
758
759 static XtGeometryResult
760 TabsQueryGeometry(Widget w,
761                   XtWidgetGeometry * intended, XtWidgetGeometry * preferred)
762 {
763         register TabsWidget tw = (TabsWidget) w;
764         XtGeometryMask mode = intended->request_mode;
765
766         preferred->request_mode = CWWidth | CWHeight;
767         PreferredSize(tw, &preferred->width, &preferred->height, NULL, NULL);
768
769         if ((!(mode & CWWidth) || intended->width == w->core.width) &&
770             (!(mode & CWHeight) || intended->height == w->core.height))
771                 return XtGeometryNo;
772
773         if ((!(mode & CWWidth) || intended->width >= preferred->width) &&
774             (!(mode & CWHeight) || intended->height >= preferred->height))
775                 return XtGeometryYes;
776
777         return XtGeometryAlmost;
778 }
779
780 /*
781  * Geometry Manager; called when a child wants to be resized.
782  */
783
784 static XtGeometryResult
785 TabsGeometryManager(Widget w, XtWidgetGeometry * req, XtWidgetGeometry * reply)
786 {
787         TabsWidget tw = (TabsWidget) XtParent(w);
788         Dimension s = SHADWID;
789         TabsConstraints tab = (TabsConstraints) w->core.constraints;
790         XtGeometryResult result;
791         Dimension rw, rh;
792
793         /* Position request always denied */
794
795         if (((req->request_mode & CWX) && req->x != w->core.x) ||
796             ((req->request_mode & CWY) && req->y != w->core.y) ||
797             !tab->tabs.resizable)
798                 return XtGeometryNo;
799
800         /* Make all three fields in the request valid */
801         if (!(req->request_mode & CWWidth))
802                 req->width = w->core.width;
803         if (!(req->request_mode & CWHeight))
804                 req->height = w->core.height;
805         if (!(req->request_mode & CWBorderWidth))
806                 req->border_width = w->core.border_width;
807
808         if (req->width == w->core.width &&
809             req->height == w->core.height &&
810             req->border_width == w->core.border_width)
811                 return XtGeometryNo;
812
813         rw = req->width + 2 * req->border_width;
814         rh = req->height + 2 * req->border_width;
815
816         /* find out how big the children want to be now */
817         MaxChild(tw, w, rw, rh);
818
819         /* Size changes must see if the new size can be accommodated.
820          * The Tabs widget keeps all of its children the same
821          * size.  A request to shrink will be accepted only if the
822          * new size is still big enough for all other children.  A
823          * request to shrink that is not big enough for all children
824          * returns an "almost" response with the new proposed size
825          * or a "no" response if unable to shrink at all.
826          *
827          * A request to grow will be accepted only if the Tabs parent can
828          * grow to accommodate.
829          *
830          * TODO:
831          * We could get fancy here and re-arrange the tabs if it is
832          * necessary to compromise with the parent, but we'll save that
833          * for another day.
834          */
835
836         if (req->request_mode & (CWWidth | CWHeight | CWBorderWidth)) {
837                 Dimension cw, ch;       /* children's preferred size */
838                 Dimension aw, ah;       /* available size we can give child */
839                 Dimension th;   /* space used by tabs */
840                 Dimension wid, hgt;     /* Tabs widget size */
841
842                 cw = tw->tabs.max_cw;
843                 ch = tw->tabs.max_ch;
844
845                 /* find out what *my* resulting preferred size would be */
846
847                 PreferredSize2(tw, cw, ch, &wid, &hgt);
848
849                 /* Would my size change?  If so, ask to be resized. */
850
851                 if (wid != tw->core.width || hgt != tw->core.height) {
852                         Dimension oldWid = tw->core.width, oldHgt =
853                             tw->core.height;
854                         XtWidgetGeometry myrequest, myreply;
855
856                         myrequest.width = wid;
857                         myrequest.height = hgt;
858                         myrequest.request_mode = CWWidth | CWHeight;
859
860                         assert(wid > 0 && hgt > 0);
861                         /* If child is only querying, or if we're going to have to
862                          * offer the child a compromise, then make this a query only.
863                          */
864
865                         if ((req->request_mode & XtCWQueryOnly) || rw < cw
866                             || rh < ch)
867                                 myrequest.request_mode |= XtCWQueryOnly;
868
869                         result =
870                             XtMakeGeometryRequest((Widget) tw, &myrequest,
871                                                   &myreply);
872
873                         /* !$@# Athena Box widget changes the core size even if QueryOnly
874                          * is set.  I'm convinced this is a bug.  At any rate, to work
875                          * around the bug, we need to restore the core size after every
876                          * query geometry request.  This is only partly effective,
877                          * as there may be other boxes further up the tree.
878                          */
879                         if (myrequest.request_mode & XtCWQueryOnly) {
880                                 tw->core.width = oldWid;
881                                 tw->core.height = oldHgt;
882                         }
883
884                         /* based on the parent's response, determine what the
885                          * resulting Tabs widget size would be.
886                          */
887
888                         switch (result) {
889                         case XtGeometryYes:
890                         case XtGeometryDone:
891                                 tw->tabs.needs_layout = True;
892                                 break;
893
894                         case XtGeometryNo:
895                                 wid = tw->core.width;
896                                 hgt = tw->core.height;
897                                 break;
898
899                         case XtGeometryAlmost:
900                                 wid = myreply.width;
901                                 hgt = myreply.height;
902                                 tw->tabs.needs_layout = True;
903                                 break;
904                         default:
905                                 break;
906                         }
907                 }
908
909                 /* Within the constraints imposed by the parent, what is
910                  * the max size we can give the child?
911                  */
912                 (void)TabLayout(tw, wid, hgt, &th, True);
913                 aw = wid - 2 * s;
914                 ah = hgt - th - 2 * s;
915
916                 /* OK, make our decision.  If requested size is >= max sibling
917                  * preferred size, AND requested size <= available size, then
918                  * we accept.  Otherwise, we offer a compromise.
919                  */
920
921                 if (rw == aw && rh == ah) {
922                         /* Acceptable.  If this wasn't a query, change *all* children
923                          * to this size.
924                          */
925                         if (req->request_mode & XtCWQueryOnly)
926                                 return XtGeometryYes;
927                         else {
928                                 Widget *childP = tw->composite.children;
929                                 int i, bw;
930                                 w->core.border_width = req->border_width;
931                                 for (i = TabsNumChildren(tw); --i >= 0;
932                                      ++childP)
933                                         if (TabVisible(*childP)) {
934                                                 bw = (*childP)->core.
935                                                     border_width;
936                                                 XtConfigureWidget(*childP, s,
937                                                                   tw->tabs.
938                                                                   tab_total + s,
939                                                                   rw - 2 * bw,
940                                                                   rh - 2 * bw,
941                                                                   bw);
942                                         }
943 #ifdef  COMMENT
944                                 /* TODO: under what conditions will we need to redraw? */
945                                 XClearWindow(XtDisplay((Widget) tw),
946                                              XtWindow((Widget) tw));
947                                 XtClass(tw)->core_class.expose((Widget) tw,
948                                                                NULL, NULL);
949 #endif                          /* COMMENT */
950                                 return XtGeometryDone;
951                         }
952                 }
953
954                 /* Cannot grant child's request.  Describe what we *can* do
955                  * and return counter-offer.
956                  */
957                 reply->width = aw - 2 * req->border_width;
958                 reply->height = ah - 2 * req->border_width;
959                 reply->border_width = req->border_width;
960                 reply->request_mode = CWWidth | CWHeight | CWBorderWidth;
961                 return XtGeometryAlmost;
962         }
963
964         return XtGeometryYes;
965 }
966
967         /* The number of children we manage has changed; recompute
968          * size from scratch.
969          */
970
971 static void TabsChangeManaged(Widget w)
972 {
973         TabsWidget tw = (TabsWidget) w;
974         Widget *childP = tw->composite.children;
975         int i;
976
977         if (tw->tabs.topWidget != NULL &&
978             (!XtIsManaged(tw->tabs.topWidget) ||
979              tw->tabs.topWidget->core.being_destroyed))
980                 tw->tabs.topWidget = NULL;
981
982         /* Check whether the highlight tab is still valid. */
983         if (tw->tabs.hilight != NULL &&
984             (!XtIsManaged(tw->tabs.hilight) ||
985              tw->tabs.hilight->core.being_destroyed))
986                 tw->tabs.hilight = NULL;
987
988         GetPreferredSizes(tw);
989         MakeSizeRequest(tw);
990
991         XtClass(w)->core_class.resize(w);
992         if (XtIsRealized(w)) {
993                 Display *dpy = XtDisplay(w);
994                 XClearWindow(dpy, XtWindow(w));
995                 XtClass(w)->core_class.expose(w, NULL, NULL);
996
997                 /* make sure the top widget stays on top.  This requires
998                  * making sure that all new children are realized first.
999                  */
1000                 if (tw->tabs.topWidget != NULL
1001                     && XtIsRealized(tw->tabs.topWidget)) {
1002                         for (i = TabsNumChildren(tw); --i >= 0; ++childP)
1003                                 if (!XtIsRealized(*childP))
1004                                         XtRealizeWidget(*childP);
1005
1006                         XRaiseWindow(dpy, XtWindow(tw->tabs.topWidget));
1007                 }
1008         }
1009 #ifdef  NEED_MOTIF
1010         /* Only top widget may receive input */
1011
1012         for (childP = tw->composite.children, i = tw->composite.num_children;
1013              --i >= 0; ++childP) {
1014                 XtVaSetValues(*childP, XmNtraversalOn, False, 0);
1015         }
1016
1017         if (tw->tabs.topWidget != NULL)
1018                 XtVaSetValues(tw->tabs.topWidget, XmNtraversalOn, True, 0);
1019 #endif
1020 }
1021 \f
1022 /****************************************************************
1023  *
1024  * Action Procedures
1025  *
1026  ****************************************************************/
1027
1028         /* User clicks on a tab, figure out which one it was. */
1029
1030 /* ARGSUSED */
1031 static void
1032 TabsSelect(Widget w, XEvent * event, String * params, Cardinal * num_params)
1033 {
1034         TabsWidget tw = (TabsWidget) w;
1035         Widget *childP;
1036         Position x, y;
1037         Dimension h = tw->tabs.tab_height;
1038         unsigned int i;
1039
1040 #ifdef  NEED_MOTIF
1041         XmProcessTraversal(w, XmTRAVERSE_CURRENT);
1042 #endif
1043
1044         /* TODO: is there an Xmu function or something to do this instead? */
1045         switch (event->type) {
1046         case ButtonPress:
1047         case ButtonRelease:
1048                 x = event->xbutton.x;
1049                 y = event->xbutton.y;
1050                 break;
1051         case KeyPress:
1052         case KeyRelease:
1053                 x = event->xkey.x;
1054                 y = event->xkey.y;
1055                 break;
1056         default:
1057                 return;
1058         }
1059
1060         /* TODO: determine which tab was clicked, if any.  Set that
1061          * widget to be top of stacking order with XawTabsSetTop().
1062          */
1063         for (i = 0, childP = tw->composite.children;
1064              i < TabsNumChildren(tw); ++i, ++childP)
1065                 if (TabVisible(*childP)) {
1066                         TabsConstraints tab =
1067                             (TabsConstraints) (*childP)->core.constraints;
1068                         if (x > tab->tabs.x && x < tab->tabs.x + tab->tabs.width
1069                             && y > tab->tabs.y && y < tab->tabs.y + h) {
1070                                 if (*childP != tw->tabs.topWidget
1071                                     && (XtIsSensitive(*childP)
1072                                         || tw->tabs.selectInsensitive))
1073                                         XawTabsSetTop(*childP, True);
1074                                 break;
1075                         }
1076                 }
1077 }
1078
1079         /* User hits a key */
1080
1081 static void
1082 TabsPage(Widget w, XEvent * event, String * params, Cardinal * num_params)
1083 {
1084         TabsWidget tw = (TabsWidget) w;
1085         Widget newtop = NULL;
1086         Widget *childP;
1087         int idx;
1088         int nc = TabsNumChildren(tw);
1089
1090         if (nc <= 0)
1091                 return;
1092
1093         if (*num_params < 1) {
1094                 XtAppWarning(XtWidgetToApplicationContext(w),
1095                              "Tabs: page() action called with no arguments");
1096                 return;
1097         }
1098
1099         if (tw->tabs.topWidget == NULL)
1100                 tw->tabs.topWidget = tw->composite.children[0];
1101
1102         for (idx = 0, childP = tw->composite.children; idx < nc;
1103              ++idx, ++childP)
1104                 if (tw->tabs.topWidget == *childP)
1105                         break;
1106
1107         switch (params[0][0]) {
1108         case 'u':               /* up */
1109         case 'U':
1110                 if (--idx < 0)
1111                         idx = nc - 1;
1112                 newtop = tw->composite.children[idx];
1113                 break;
1114
1115         case 'd':               /* down */
1116         case 'D':
1117                 if (++idx >= nc)
1118                         idx = 0;
1119                 newtop = tw->composite.children[idx];
1120                 break;
1121
1122         case 'h':
1123         case 'H':
1124         default:
1125                 newtop = tw->composite.children[0];
1126                 break;
1127
1128         case 'e':
1129         case 'E':
1130                 newtop = tw->composite.children[nc - 1];
1131                 break;
1132
1133         case 's':               /* selected */
1134         case 'S':
1135                 if ((newtop = tw->tabs.hilight) == NULL)
1136                         return;
1137                 break;
1138         }
1139
1140         XawTabsSetTop(newtop, True);
1141 }
1142
1143         /* User hits up/down key */
1144
1145 static void
1146 TabsHighlight(Widget w, XEvent * event, String * params, Cardinal * num_params)
1147 {
1148         TabsWidget tw = (TabsWidget) w;
1149         Widget newhl = NULL;
1150         Widget *childP;
1151         int idx;
1152         int nc = TabsNumChildren(tw);
1153
1154         if (nc <= 0)
1155                 return;
1156
1157         if (*num_params < 1) {
1158                 if (tw->tabs.hilight != NULL)
1159                         DrawHighlight(tw, tw->tabs.hilight, False);
1160                 return;
1161         }
1162
1163         if (tw->tabs.hilight == NULL)
1164                 newhl = tw->composite.children[0];
1165
1166         else {
1167                 /* find index of currently highlit child */
1168                 for (idx = 0, childP = tw->composite.children; idx < nc;
1169                      ++idx, ++childP)
1170                         if (tw->tabs.hilight == *childP)
1171                                 break;
1172
1173                 switch (params[0][0]) {
1174                 case 'u':       /* up */
1175                 case 'U':
1176                         if (--idx < 0)
1177                                 idx = nc - 1;
1178                         newhl = tw->composite.children[idx];
1179                         break;
1180
1181                 case 'd':       /* down */
1182                 case 'D':
1183                         if (++idx >= nc)
1184                                 idx = 0;
1185                         newhl = tw->composite.children[idx];
1186                         break;
1187
1188                 case 'h':
1189                 case 'H':
1190                         newhl = tw->composite.children[0];
1191                         break;
1192
1193                 case 'e':
1194                 case 'E':
1195                         newhl = tw->composite.children[nc - 1];
1196                         break;
1197
1198                 default:
1199                         newhl = tw->tabs.hilight;
1200                         break;
1201                 }
1202         }
1203
1204         XawTabsSetHighlight(w, newhl);
1205 }
1206
1207 static void
1208 TabsUnhighlight(Widget w, XEvent * event, String * params,
1209                 Cardinal * num_params)
1210 {
1211         TabsWidget tw = (TabsWidget) w;
1212         int nc = tw->composite.num_children;
1213
1214         if (nc <= 0)
1215                 return;
1216
1217         if (tw->tabs.hilight != NULL)
1218                 DrawHighlight(tw, tw->tabs.hilight, True);
1219 }
1220 \f
1221 /****************************************************************
1222  *
1223  * Public Procedures
1224  *
1225  ****************************************************************/
1226
1227         /* Set the top tab, optionally call all callbacks. */
1228 void XawTabsSetTop(Widget w, Bool callCallbacks)
1229 {
1230         TabsWidget tw = (TabsWidget) w->core.parent;
1231         TabsConstraints tab;
1232         Widget oldtop = tw->tabs.topWidget;
1233
1234         if (!XtIsSubclass(w->core.parent, tabsWidgetClass)) {
1235                 char line[256];
1236                 int n = snprintf(line, sizeof(line),
1237                         "XawTabsSetTop: widget \"%.64s\" is not the child of a tabs widget.",
1238                         XtName(w));
1239                 assert(n >= 0 && (size_t)n < sizeof(line));
1240                 XtAppWarning(XtWidgetToApplicationContext(w), line);
1241                 return;
1242         }
1243
1244         if (callCallbacks)
1245                 XtCallCallbackList(w, tw->tabs.popdownCallbacks,
1246                                    (XtPointer) tw->tabs.topWidget);
1247
1248         if (!XtIsRealized(w)) {
1249                 tw->tabs.topWidget = w;
1250                 tw->tabs.needs_layout = True;
1251                 tw->tabs.hilight = NULL;        /* The highlight tab might disappear. */
1252                 return;
1253         }
1254
1255         XRaiseWindow(XtDisplay(w), XtWindow(w));
1256 #ifdef  NEED_MOTIF
1257         XtVaSetValues(oldtop, XmNtraversalOn, False, 0);
1258         XtVaSetValues(w, XmNtraversalOn, True, 0);
1259 #endif
1260
1261         tab = (TabsConstraints) w->core.constraints;
1262
1263         /* Unhighlight before we start messing with the stacking order. */
1264         if (tw->tabs.hilight != NULL) {
1265                 DrawHighlight(tw, tw->tabs.hilight, True);
1266                 tw->tabs.hilight = NULL;
1267         }
1268
1269         if (tab->tabs.row == 0) {
1270                 /* Easy case; undraw current top, undraw new top, assign new
1271                  * top, redraw all borders.
1272                  * We *could* just erase and execute a full redraw, but I like to
1273                  * reduce screen flicker.
1274                  */
1275                 UndrawTab(tw, oldtop);  /* undraw old */
1276                 DrawBorder(tw, oldtop, True);
1277                 UndrawTab(tw, w);       /* undraw new */
1278                 DrawBorder(tw, w, True);
1279                 tw->tabs.topWidget = w;
1280                 DrawTab(tw, oldtop, True);      /* redraw old */
1281                 DrawTab(tw, w, True);   /* redraw new */
1282                 DrawTabs(tw, False);
1283         } else {
1284                 tw->tabs.topWidget = w;
1285                 TabsShuffleRows(tw);
1286                 XClearWindow(XtDisplay((Widget) tw), XtWindow((Widget) tw));
1287                 XtClass(tw)->core_class.expose((Widget) tw, NULL, None);
1288         }
1289
1290         XawTabsSetHighlight((Widget) tw, w);
1291
1292         if (callCallbacks)
1293                 XtCallCallbackList(w, tw->tabs.callbacks, (XtPointer) w);
1294 }
1295
1296         /* Set the top tab, optionally call all callbacks. */
1297 void XawTabsSetHighlight(Widget t, Widget w)
1298 {
1299         TabsWidget tw = (TabsWidget) t;
1300
1301         if (!XtIsSubclass(t, tabsWidgetClass))
1302                 return;
1303
1304         if (XtIsRealized(t) && w != tw->tabs.hilight) {
1305                 if (w != NULL)
1306                         DrawHighlight(tw, w, False);
1307         }
1308
1309         tw->tabs.hilight = w;
1310 }
1311 \f
1312 /****************************************************************
1313  *
1314  * Private Procedures
1315  *
1316  ****************************************************************/
1317
1318 static void TabsAllocGCs(TabsWidget tw)
1319 {
1320         TabsAllocFgGC(tw);
1321         TabsAllocGreyGC(tw);
1322         tw->tabs.backgroundGC = AllocBackgroundGC((Widget) tw, None);
1323         tw->tabs.topGC = AllocTopShadowGC((Widget) tw,
1324                                           tw->tabs.top_shadow_contrast,
1325                                           tw->tabs.be_nice_to_cmap);
1326         tw->tabs.botGC =
1327             AllocBotShadowGC((Widget) tw, tw->tabs.bot_shadow_contrast,
1328                              tw->tabs.be_nice_to_cmap);
1329 }
1330
1331 static void TabsFreeGCs(TabsWidget tw)
1332 {
1333         Widget w = (Widget) tw;
1334
1335         XtReleaseGC(w, tw->tabs.foregroundGC);
1336         XtReleaseGC(w, tw->tabs.greyGC);
1337         XtReleaseGC(w, tw->tabs.backgroundGC);
1338         XtReleaseGC(w, tw->tabs.topGC);
1339         XtReleaseGC(w, tw->tabs.botGC);
1340 #ifdef HAVE_XMU
1341         XmuReleaseStippledPixmap(XtScreen(w), tw->tabs.grey50);
1342 #endif
1343 }
1344
1345         /* Redraw entire Tabs widget */
1346
1347 static void DrawTabs(TabsWidget tw, Bool labels)
1348 {
1349         Widget *childP;
1350         unsigned int i;
1351         int j;
1352         Dimension s = SHADWID;
1353         Dimension th = tw->tabs.tab_height;
1354         Position y;
1355         TabsConstraints tab;
1356
1357         if (!XtIsRealized((Widget) tw))
1358                 return;
1359
1360         /* draw tabs and frames by row except for the top tab, which
1361          * is drawn last.  (This is inefficiently written, but should not
1362          * be too slow as long as there are not a lot of rows.)
1363          */
1364
1365         y = tw->tabs.numRows == 1 ? TABDELTA : 0;
1366         for (i = 0; i < tw->tabs.numRows; ++i, y += th) {
1367                 for (j = TabsNumChildren(tw), childP = tw->composite.children;
1368                      --j >= 0; ++childP)
1369                         if (TabVisible(*childP)) {
1370                                 tab =
1371                                     (TabsConstraints) (*childP)->core.
1372                                     constraints;
1373                                 if (tab->tabs.row == (int)i
1374                                     && *childP != tw->tabs.topWidget)
1375                                         DrawTab(tw, *childP, labels);
1376                         }
1377                 if (i != tw->tabs.numRows - 1)
1378                         DrawTrim(tw, 0, y + th, tw->core.width, th + s, False,
1379                                  False);
1380         }
1381
1382         DrawFrame(tw);
1383
1384         /* and now the top tab */
1385         if (tw->tabs.topWidget != NULL)
1386                 DrawTab(tw, tw->tabs.topWidget, labels);
1387 }
1388
1389 /* Draw one tab.  Corners are rounded very slightly. */
1390
1391 static void DrawTab(TabsWidget tw, Widget child, Bool labels)
1392 {
1393         GC gc;
1394         int x, y;
1395
1396         if (!XtIsRealized((Widget) tw))
1397                 return;
1398
1399         DrawBorder(tw, child, False);
1400
1401         if (labels) {
1402                 TabsConstraints tab = (TabsConstraints) child->core.constraints;
1403                 Display *dpy = XtDisplay((Widget) tw);
1404                 Window win = XtWindow((Widget) tw);
1405                 String lbl = tab->tabs.label != NULL ?
1406                     tab->tabs.label : XtName(child);
1407
1408                 if (XtIsSensitive(child)) {
1409                         gc = tw->tabs.foregroundGC;
1410                         XSetForeground(dpy, gc, tab->tabs.foreground);
1411                 } else {
1412                         /* grey pixel allocation deferred until now */
1413                         if (!tab->tabs.greyAlloc) {
1414                                 if (tw->tabs.be_nice_to_cmap
1415                                     || tw->core.depth == 1)
1416                                         tab->tabs.grey = tab->tabs.foreground;
1417                                 else
1418                                         tab->tabs.grey =
1419                                             AllocGreyPixel((Widget) tw,
1420                                                            tab->tabs.foreground,
1421                                                            tw->core.
1422                                                            background_pixel,
1423                                                            tw->tabs.
1424                                                            insensitive_contrast);
1425                                 tab->tabs.greyAlloc = True;
1426                         }
1427                         gc = tw->tabs.greyGC;
1428                         XSetForeground(dpy, gc, tab->tabs.grey);
1429                 }
1430
1431                 x = tab->tabs.x;
1432                 y = tab->tabs.y;
1433                 if (child == tw->tabs.topWidget)
1434                         y -= TABLDELTA;
1435
1436                 if (tab->tabs.left_bitmap != None && tab->tabs.lbm_width > 0) {
1437                         if (tab->tabs.lbm_depth == 1)
1438                                 XCopyPlane(dpy, tab->tabs.left_bitmap, win, gc,
1439                                            0, 0, tab->tabs.lbm_width,
1440                                            tab->tabs.lbm_height,
1441                                            x + tab->tabs.lbm_x,
1442                                            y + tab->tabs.lbm_y, 1L);
1443                         else
1444                                 XCopyArea(dpy, tab->tabs.left_bitmap, win, gc,
1445                                           0, 0, tab->tabs.lbm_width,
1446                                           tab->tabs.lbm_height,
1447                                           x + tab->tabs.lbm_x,
1448                                           y + tab->tabs.lbm_y);
1449                 }
1450
1451                 if (lbl != NULL && tw->tabs.font != NULL)
1452                         XDrawString(dpy, win, gc,
1453                                     x + tab->tabs.l_x, y + tab->tabs.l_y,
1454                                     lbl, (int)strlen(lbl));
1455         }
1456
1457         if (child == tw->tabs.hilight)
1458                 DrawHighlight(tw, child, False);
1459 }
1460
1461         /* draw frame all the way around the child windows. */
1462
1463 static void DrawFrame(TabsWidget tw)
1464 {
1465         GC topgc = tw->tabs.topGC;
1466         GC botgc = tw->tabs.botGC;
1467         Dimension s = SHADWID;
1468         Dimension ch = tw->tabs.child_height;
1469         if (ch > 0)
1470                 Draw3dBox((Widget) tw, 0, tw->tabs.tab_total,
1471                           tw->core.width, ch + 2 * s, s, topgc, botgc);
1472         else {
1473                 Widget w = tw->tabs.topWidget;
1474                 if (w != NULL) {
1475                         TabsConstraints tab =
1476                             (TabsConstraints) w->core.constraints;
1477                         Draw3dBox((Widget) tw, 0, tw->core.height - 2 * s,
1478                                   tab->tabs.x, 2 * s, s, topgc, botgc);
1479                         Draw3dBox((Widget) tw, tab->tabs.x + tab->tabs.width,
1480                                   tw->core.height - 2 * s,
1481                                   tw->core.width - tab->tabs.x -
1482                                   tab->tabs.width, 2 * s, s, topgc, botgc);
1483                 } else
1484                         Draw3dBox((Widget) tw, 0, tw->core.height - 2 * s,
1485                                   tw->core.width, 2 * s, s, topgc, botgc);
1486         }
1487 }
1488
1489         /* draw trim around a tab or underneath a row of tabs */
1490
1491 static void DrawTrim(TabsWidget tw,     /* widget */
1492                      int x,     /* upper-left corner */
1493                      int y, int wid,    /* total size */
1494                      int hgt, Bool bottom,      /* draw bottom? */
1495                      Bool undraw)
1496 {                               /* undraw all */
1497         Display *dpy = XtDisplay((Widget) tw);
1498         Window win = XtWindow((Widget) tw);
1499         GC bggc = tw->tabs.backgroundGC;
1500         GC topgc = undraw ? bggc : tw->tabs.topGC;
1501         GC botgc = undraw ? bggc : tw->tabs.botGC;
1502         if (bottom)
1503                 XDrawLine(dpy, win, bggc, x, y + hgt - 1, x + wid - 1, y + hgt - 1);    /* bottom */
1504         XDrawLine(dpy, win, topgc, x, y + 2, x, y + hgt - 2);   /* left */
1505         XDrawPoint(dpy, win, topgc, x + 1, y + 1);      /* corner */
1506         XDrawLine(dpy, win, topgc, x + 2, y, x + wid - 3, y);   /* top */
1507         XDrawLine(dpy, win, botgc, x + wid - 2, y + 1, x + wid - 2, y + hgt - 2);       /* right */
1508         XDrawLine(dpy, win, botgc, x + wid - 1, y + 2, x + wid - 1, y + hgt - 2);       /* right */
1509 }
1510
1511 /* Draw one tab border. */
1512
1513 static void DrawBorder(TabsWidget tw, Widget child, Bool undraw)
1514 {
1515         TabsConstraints tab = (TabsConstraints) child->core.constraints;
1516         Position x = tab->tabs.x;
1517         Position y = tab->tabs.y;
1518         Dimension twid = tab->tabs.width;
1519         Dimension thgt = tw->tabs.tab_height;
1520
1521         /* top tab requires a little special attention; it overlaps
1522          * neighboring tabs slightly, so the background must be cleared
1523          * in the region of the overlap to partially erase those neighbors.
1524          * TODO: is this worth doing with regions instead?
1525          */
1526         if (child == tw->tabs.topWidget) {
1527                 Display *dpy = XtDisplay((Widget) tw);
1528                 Window win = XtWindow((Widget) tw);
1529                 GC bggc = tw->tabs.backgroundGC;
1530                 XRectangle rects[3];
1531                 x -= TABDELTA;
1532                 y -= TABDELTA;
1533                 twid += TABDELTA * 2;
1534                 thgt += TABDELTA;
1535                 AddRect(0, x, y + 1, twid, TABDELTA);
1536                 AddRect(1, x + 1, y, TABDELTA, thgt);
1537                 AddRect(2, x + twid - TABDELTA - 1, y, TABDELTA, thgt);
1538                 XFillRectangles(dpy, win, bggc, rects, 3);
1539         }
1540
1541         DrawTrim(tw, x, y, twid, thgt + 1, child == tw->tabs.topWidget, undraw);
1542 }
1543
1544 /* Draw highlight around tab that has focus */
1545
1546 static void DrawHighlight(TabsWidget tw, Widget child, Bool undraw)
1547 {
1548         TabsConstraints tab = (TabsConstraints) child->core.constraints;
1549         Display *dpy = XtDisplay((Widget) tw);
1550         Window win = XtWindow((Widget) tw);
1551         GC gc;
1552         Position x = tab->tabs.x;
1553         Position y = tab->tabs.y;
1554         Dimension wid = tab->tabs.width;
1555         Dimension hgt = tw->tabs.tab_height;
1556         XPoint points[6];
1557
1558         /* top tab does not have a highlight */
1559
1560         if (child == tw->tabs.topWidget)
1561                 return;
1562
1563         if (undraw)
1564                 gc = tw->tabs.backgroundGC;
1565
1566         else if (XtIsSensitive(child)) {
1567                 gc = tw->tabs.foregroundGC;
1568                 XSetForeground(dpy, gc, tab->tabs.foreground);
1569         } else {
1570                 gc = tw->tabs.greyGC;
1571                 XSetForeground(dpy, gc, tab->tabs.grey);
1572         }
1573
1574         points[0].x = x + 1;
1575         points[0].y = y + hgt - 1;
1576         points[1].x = x + 1;
1577         points[1].y = y + 2;
1578         points[2].x = x + 2;
1579         points[2].y = y + 1;
1580         points[3].x = x + wid - 4;
1581         points[3].y = y + 1;
1582         points[4].x = x + wid - 3;
1583         points[4].y = y + 2;
1584         points[5].x = x + wid - 3;
1585         points[5].y = y + hgt - 1;
1586
1587         XDrawLines(dpy, win, gc, points, 6, CoordModeOrigin);
1588 }
1589
1590 /* Undraw one tab interior */
1591
1592 static void UndrawTab(TabsWidget tw, Widget child)
1593 {
1594         TabsConstraints tab = (TabsConstraints) child->core.constraints;
1595         Position x = tab->tabs.x;
1596         Position y = tab->tabs.y;
1597         Dimension twid = tab->tabs.width;
1598         Dimension thgt = tw->tabs.tab_height;
1599         Display *dpy = XtDisplay((Widget) tw);
1600         Window win = XtWindow((Widget) tw);
1601         GC bggc = tw->tabs.backgroundGC;
1602
1603         XFillRectangle(dpy, win, bggc, x, y, twid, thgt);
1604 }
1605 \f
1606         /* GEOMETRY UTILITIES */
1607
1608         /* Overview:
1609          *
1610          *  MaxChild(): ask all children (except possibly one) their
1611          *  preferred sizes, set max_cw, max_ch accordingly.
1612          *
1613          *  GetPreferredSizes(): ask all children their preferred sizes,
1614          *  set max_cw, max_ch accordingly.
1615          *
1616          *  PreferredSize(): given max_cw, max_ch, return tabs widget
1617          *  preferred size.  Iterate with other widths in order to get
1618          *  a reasonable aspect ratio.
1619          *
1620          *  PreferredSize2(): Given child dimensions, return Tabs
1621          *  widget dimensions.
1622          *
1623          *  PreferredSize3(): Same, except given child dimensions plus
1624          *  shadow.
1625          */
1626
1627         /* Compute the width of one child's tab.  Positions will be computed
1628          * elsewhere.
1629          *
1630          *      height: font height + vertical_space*2 + shadowWid*2
1631          *      width:  string width + horizontal_space*2 + shadowWid*2
1632          *
1633          * All tabs are the same height, so that is computed elsewhere.
1634          */
1635
1636 static void TabWidth(Widget w)
1637 {
1638         TabsConstraints tab = (TabsConstraints) w->core.constraints;
1639         TabsWidget tw = (TabsWidget) XtParent(w);
1640         String lbl = tab->tabs.label != NULL ? tab->tabs.label : XtName(w);
1641         XFontStruct *font = tw->tabs.font;
1642         int iw = tw->tabs.internalWidth;
1643
1644         tab->tabs.width = iw + SHADWID * 2;
1645         tab->tabs.l_x = tab->tabs.lbm_x = SHADWID + iw;
1646
1647         if (tab->tabs.left_bitmap != None) {
1648                 tab->tabs.width += tab->tabs.lbm_width + iw;
1649                 tab->tabs.l_x += tab->tabs.lbm_width + iw;
1650                 tab->tabs.lbm_y =
1651                     (tw->tabs.tab_height - tab->tabs.lbm_height) / 2;
1652         }
1653
1654         if (lbl != NULL && font != NULL) {
1655                 tab->tabs.width += XTextWidth(font, lbl, (int)strlen(lbl)) + iw;
1656                 tab->tabs.l_y = (tw->tabs.tab_height +
1657                                  tw->tabs.font->max_bounds.ascent -
1658                                  tw->tabs.font->max_bounds.descent) / 2;
1659         }
1660 }
1661
1662         /* Lay out tabs to fit in given width.  Compute x,y position and
1663          * row number for each tab.  Return number of rows and total height
1664          * required by all tabs.  If there is only one row, add TABDELTA
1665          * height to the total.  Rows are assigned bottom to top.
1666          *
1667          * Tabs are indented from the edges by INDENT.
1668          *
1669          * TODO: if they require more than two rows and the total height:width
1670          * ratio is more than 2:1, then try something else.
1671          */
1672
1673 static int
1674 TabLayout(TabsWidget tw,
1675           Dimension wid,
1676           Dimension hgt, Dimension * reply_height, Bool query_only)
1677 {
1678         int i, row, done = 0, display_rows = 0;
1679         int num_children = tw->composite.num_children;
1680         Widget *childP;
1681         Dimension w;
1682         Position x, y;
1683         TabsConstraints tab;
1684
1685         /* Algorithm: loop through children, assign X positions.  If a tab
1686          * would extend beyond the right edge, start a new row.  After all
1687          * rows are assigned, make a second pass and assign Y positions.
1688          */
1689
1690         if (num_children > 0) {
1691                 /* Loop through the tabs and see how much space they need. */
1692
1693                 row = 0;
1694                 x = INDENT;
1695                 y = 0;
1696                 wid -= INDENT;
1697                 for (i = num_children, childP = tw->composite.children;
1698                      --i >= 0; ++childP)
1699                         if (XtIsManaged(*childP)) {
1700                                 tab =
1701                                     (TabsConstraints) (*childP)->core.
1702                                     constraints;
1703                                 w = tab->tabs.width;
1704
1705                                 if (x + w > wid) {      /* new row */
1706                                         if (y + tw->tabs.tab_height > hgt
1707                                             && !done) {
1708                                                 display_rows = row;
1709                                                 done = 1;
1710                                         }
1711                                         ++row;
1712                                         x = INDENT;
1713                                         y += tw->tabs.tab_height;
1714                                 }
1715                                 if (!query_only) {
1716                                         tab->tabs.x = x;
1717                                         tab->tabs.y = y;
1718                                         tab->tabs.row = row;
1719                                 }
1720                                 x += w + SPACING;
1721                                 if (!query_only && !done)
1722                                         tab->tabs.visible = 1;
1723
1724                         }
1725                 /* If there was only one row, increase the height by TABDELTA */
1726                 if (++display_rows == 1) {
1727                         row++;
1728                         y = TABDELTA;
1729                         if (!query_only)
1730                                 for (i = num_children, childP =
1731                                      tw->composite.children; --i >= 0; ++childP)
1732                                         if (XtIsManaged(*childP)) {
1733                                                 tab =
1734                                                     (TabsConstraints)
1735                                                     (*childP)->core.constraints;
1736                                                 tab->tabs.y = y;
1737                                         }
1738                 }
1739                 y += tw->tabs.tab_height;
1740         } else
1741                 display_rows = row = y = 0;
1742
1743         if (!query_only) {
1744                 tw->tabs.tab_total = y;
1745                 tw->tabs.numRows = display_rows;
1746                 tw->tabs.realRows = row;
1747         }
1748
1749         if (reply_height != NULL)
1750                 *reply_height = y;
1751
1752         return display_rows;
1753 }
1754
1755         /* Find max preferred child size.  Returned sizes include child
1756          * border widths.
1757          */
1758
1759 static void GetPreferredSizes(TabsWidget tw)
1760 {
1761         MaxChild(tw, NULL, 0, 0);
1762 }
1763
1764         /* Find max preferred child size.  Returned sizes include child
1765          * border widths.  If except is non-null, don't ask that one.
1766          */
1767
1768 static void MaxChild(TabsWidget tw, Widget except, Dimension cw, Dimension ch)
1769 {
1770         int i;
1771         Widget *childP = tw->composite.children;
1772         XtWidgetGeometry preferred;
1773
1774         for (i = tw->composite.num_children; --i >= 0; ++childP)
1775                 if (TabVisible(*childP) /*XtIsManaged(*childP) */ &&*childP !=
1776                     except) {
1777                         (void)XtQueryGeometry(*childP, NULL, &preferred);
1778                         cw = Max(cw,
1779                                  preferred.width + preferred.border_width * 2);
1780                         ch = Max(ch,
1781                                  preferred.height + preferred.border_width * 2);
1782                 }
1783
1784         tw->tabs.max_cw = cw;
1785         tw->tabs.max_ch = ch;
1786 }
1787
1788         /* rotate row numbers to bring current widget to bottom row,
1789          * compute y positions for all tabs
1790          */
1791
1792 static void TabsShuffleRows(TabsWidget tw)
1793 {
1794         TabsConstraints tab;
1795         int move;
1796         int real_rows, display_rows;
1797         Widget *childP;
1798         Dimension th = tw->tabs.tab_height;
1799         Position bottom;
1800         int i;
1801
1802         /* There must be a top widget.  If not, assign one. */
1803         if (tw->tabs.topWidget == NULL && tw->composite.children != NULL)
1804                 for (i = TabsNumChildren(tw), childP = tw->composite.children;
1805                      --i >= 0; ++childP)
1806                         if (XtIsManaged(*childP)) {
1807                                 tw->tabs.topWidget = *childP;
1808                                 break;
1809                         }
1810
1811         if (tw->tabs.topWidget != NULL) {
1812                 display_rows = tw->tabs.numRows;
1813                 real_rows = tw->tabs.realRows;
1814                 assert(display_rows <= real_rows);
1815
1816                 if (real_rows > 1) {
1817                         tab =
1818                             (TabsConstraints) tw->tabs.topWidget->core.
1819                             constraints;
1820                         assert(tab != NULL);
1821
1822                         /* How far to move top row. The selected tab must be on
1823                            the bottom row of the *visible* rows. */
1824                         move = (real_rows + 1 - display_rows) - tab->tabs.row;
1825                         if (move < 0)
1826                                 move = real_rows - move;
1827                         bottom = tw->tabs.tab_total - th;
1828
1829                         for (i = tw->composite.num_children, childP =
1830                              tw->composite.children; --i >= 0; ++childP)
1831                                 if (XtIsManaged(*childP)) {
1832                                         tab =
1833                                             (TabsConstraints) (*childP)->core.
1834                                             constraints;
1835                                         tab->tabs.row =
1836                                             (tab->tabs.row + move) % real_rows;
1837                                         tab->tabs.y =
1838                                             bottom - tab->tabs.row * th;
1839                                         tab->tabs.visible =
1840                                             (tab->tabs.row < display_rows);
1841                                 }
1842                 }
1843         }
1844 }
1845
1846         /* Find preferred size.  Ask children, find size of largest,
1847          * add room for tabs & return.  This can get a little involved,
1848          * as we don't want to have too many rows of tabs; we may widen
1849          * the widget to reduce # of rows.
1850          *
1851          * This function requires that max_cw, max_ch already be set.
1852          */
1853 static int PreferredSize(TabsWidget tw, Dimension * reply_width,        /* total widget size */
1854                          Dimension * reply_height, Dimension * reply_cw,        /* child widget size */
1855                          Dimension * reply_ch)
1856 {
1857         Dimension cw, ch;       /* child width, height */
1858         Dimension wid, hgt;
1859         Dimension rwid, rhgt;
1860         int nrow;
1861
1862         wid = cw = tw->tabs.max_cw;
1863         hgt = ch = tw->tabs.max_ch;
1864
1865         nrow = PreferredSize2(tw, wid, hgt, &rwid, &rhgt);
1866
1867         /* Check for absurd results (more than 2 rows, high aspect
1868          * ratio).  Try wider size if needed.
1869          * TODO: make sure this terminates.
1870          */
1871
1872         if (nrow > 2 && rhgt > rwid) {
1873                 Dimension w0, w1;
1874                 int maxloop = 20;
1875
1876                 /* step 1: start doubling size until it's too big */
1877                 do {
1878                         w0 = wid;
1879                         wid = Max(wid * 2, wid + 20);
1880                         nrow = PreferredSize2(tw, wid, hgt, &rwid, &rhgt);
1881                 } while (nrow > 2 && rhgt > rwid);
1882                 w1 = wid;
1883
1884                 /* step 2: use Newton's method to find ideal size.  Stop within
1885                  * 8 pixels.
1886                  */
1887                 while (--maxloop > 0 && w1 > w0 + 8) {
1888                         wid = (w0 + w1) / 2;
1889                         nrow = PreferredSize2(tw, wid, hgt, &rwid, &rhgt);
1890                         if (nrow > 2 && rhgt > rwid)
1891                                 w0 = wid;
1892                         else
1893                                 w1 = wid;
1894                 }
1895                 wid = w1;
1896         }
1897
1898         *reply_width = rwid;
1899         *reply_height = rhgt;
1900         if (reply_cw != NULL)
1901                 *reply_cw = cw;
1902         if (reply_ch != NULL)
1903                 *reply_ch = ch;
1904         return nrow;
1905 }
1906
1907         /* Find preferred size, given size of children. */
1908
1909 static int PreferredSize2(TabsWidget tw, Dimension cw,  /* child width, height */
1910                           Dimension ch, Dimension * reply_width,        /* total widget size */
1911                           Dimension * reply_height)
1912 {
1913         Dimension s = SHADWID;
1914         int ret;
1915
1916         /* make room for shadow frame */
1917         cw += s * 2;
1918         ch += s * 2;
1919
1920         ret = PreferredSize3(tw, cw, ch, reply_width, reply_height);
1921
1922         assert(*reply_width > 0 && *reply_height > 0);
1923         return ret;
1924 }
1925
1926         /* Find preferred size, given size of children+shadow. */
1927
1928 static int PreferredSize3(TabsWidget tw, Dimension wid, /* child width, height */
1929                           Dimension hgt, Dimension * reply_width,       /* total widget size */
1930                           Dimension * reply_height)
1931 {
1932         Dimension th;           /* space used by tabs */
1933         int nrows;
1934
1935         if (tw->composite.num_children > 0)
1936                 nrows = TabLayout(tw, wid, hgt, &th, True);
1937         else {
1938                 th = 0;
1939                 nrows = 0;
1940         }
1941
1942         *reply_width = Max(wid, MIN_WID);
1943         *reply_height = Max(th + hgt, MIN_HGT);
1944
1945         return nrows;
1946 }
1947
1948 static void MakeSizeRequest(TabsWidget tw)
1949 {
1950         Widget w = (Widget) tw;
1951         XtWidgetGeometry request, reply;
1952         XtGeometryResult result;
1953         Dimension cw, ch;
1954
1955         request.request_mode = CWWidth | CWHeight;
1956         PreferredSize(tw, &request.width, &request.height, &cw, &ch);
1957
1958         if (request.width == tw->core.width &&
1959             request.height == tw->core.height)
1960                 return;
1961
1962         result = XtMakeGeometryRequest(w, &request, &reply);
1963
1964         if (result == XtGeometryAlmost) {
1965                 /* Bugger.  Didn't get what we want, but were offered a
1966                  * compromise.  If the width was too small, recompute
1967                  * based on the too-small width and try again.
1968                  * If the height was too small, make a wild-ass guess
1969                  * at a wider width and try again.
1970                  */
1971
1972                 if (reply.width < request.width
1973                     && reply.height >= request.height) {
1974                         Dimension s = SHADWID;
1975                         ch += s * 2;
1976                         PreferredSize3(tw, reply.width, ch, &request.width,
1977                                        &request.height);
1978                         result = XtMakeGeometryRequest(w, &request, &reply);
1979                         if (result == XtGeometryAlmost)
1980                                 (void)XtMakeGeometryRequest(w, &reply, NULL);
1981                 }
1982
1983                 else
1984                         (void)XtMakeGeometryRequest(w, &reply, NULL);
1985         }
1986 }
1987
1988 static void getBitmapInfo(TabsWidget tw, TabsConstraints tab)
1989 {
1990         Window root;
1991         int x, y;
1992         unsigned int bw;
1993
1994         if (tab->tabs.left_bitmap == None ||
1995             !XGetGeometry(XtDisplay(tw), tab->tabs.left_bitmap, &root, &x, &y,
1996                           &tab->tabs.lbm_width, &tab->tabs.lbm_height,
1997                           &bw, &tab->tabs.lbm_depth))
1998                 tab->tabs.lbm_width = tab->tabs.lbm_height = 0;
1999 }
2000 \f
2001         /* Code copied & modified from Gcs.c.  This version has dynamic
2002          * foreground.
2003          */
2004
2005 static void TabsAllocFgGC(TabsWidget tw)
2006 {
2007         Widget w = (Widget) tw;
2008         XGCValues values;
2009
2010         values.background = tw->core.background_pixel;
2011         values.font = tw->tabs.font->fid;
2012         values.line_style = LineOnOffDash;
2013         values.line_style = LineSolid;
2014
2015         tw->tabs.foregroundGC =
2016             XtAllocateGC(w, w->core.depth,
2017                          GCBackground | GCFont | GCLineStyle, &values,
2018                          GCForeground,
2019                          GCSubwindowMode | GCGraphicsExposures | GCDashOffset |
2020                          GCDashList | GCArcMode);
2021 }
2022
2023 static void TabsAllocGreyGC(TabsWidget tw)
2024 {
2025         Widget w = (Widget) tw;
2026         XGCValues values;
2027
2028         values.background = tw->core.background_pixel;
2029         values.font = tw->tabs.font->fid;
2030 #ifdef HAVE_XMU
2031         if (tw->tabs.be_nice_to_cmap || w->core.depth == 1) {
2032                 values.fill_style = FillStippled;
2033                 tw->tabs.grey50 =
2034                     values.stipple =
2035                     XmuCreateStippledPixmap(XtScreen(w), 1L, 0L, 1);
2036
2037                 tw->tabs.greyGC =
2038                     XtAllocateGC(w, w->core.depth,
2039                                  GCBackground | GCFont | GCStipple |
2040                                  GCFillStyle, &values, GCForeground,
2041                                  GCSubwindowMode | GCGraphicsExposures |
2042                                  GCDashOffset | GCDashList | GCArcMode);
2043         } else
2044 #endif
2045         {
2046                 tw->tabs.greyGC =
2047                     XtAllocateGC(w, w->core.depth,
2048                                  GCFont, &values,
2049                                  GCForeground,
2050                                  GCBackground | GCSubwindowMode |
2051                                  GCGraphicsExposures | GCDashOffset | GCDashList
2052                                  | GCArcMode);
2053         }
2054 }