1 /********************************************************************************
2 *                                                                               *
3 *                         T a b   B o o k   W i d g e t                         *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1997,2020 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This library is free software; you can redistribute it and/or modify          *
9 * it under the terms of the GNU Lesser General Public License as published by   *
10 * the Free Software Foundation; either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This library is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU Lesser General Public License for more details.                           *
17 *                                                                               *
18 * You should have received a copy of the GNU Lesser General Public License      *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>          *
20 ********************************************************************************/
21 #include "xincs.h"
22 #include "fxver.h"
23 #include "fxdefs.h"
24 #include "fxmath.h"
25 #include "fxkeys.h"
26 #include "FXArray.h"
27 #include "FXHash.h"
28 #include "FXMutex.h"
29 #include "FXStream.h"
30 #include "FXString.h"
31 #include "FXSize.h"
32 #include "FXPoint.h"
33 #include "FXRectangle.h"
34 #include "FXStringDictionary.h"
35 #include "FXSettings.h"
36 #include "FXRegistry.h"
37 #include "FXAccelTable.h"
38 #include "FXFont.h"
39 #include "FXEvent.h"
40 #include "FXWindow.h"
41 #include "FXDCWindow.h"
42 #include "FXApp.h"
43 #include "FXIcon.h"
44 #include "FXTabBook.h"
45 
46 
47 /*
48   Notes:
49   - Should focus go to tab items?
50   - Should callbacks come from tab items?
51   - Should redesign this stuff a little.
52   - Tab items should observe various border styles.
53   - TAB/TABTAB should go into content, arrow keys navigate between tabs.
54   - FXTabBook: pane's hints make no sense to observe
55   - We hide the panes in FXTabBook.  This way, we don't have to change
56     the position of each pane when the FXTabBook itself changes.
57     Only the active pane needs to be resized, leading to much faster
58     layouts.
59 */
60 
61 #define TABBOOK_MASK       (TABBOOK_SIDEWAYS|TABBOOK_BOTTOMTABS)
62 #define REVEAL_PIXELS      20
63 
64 using namespace FX;
65 
66 /*******************************************************************************/
67 
68 namespace FX {
69 
70 FXDEFMAP(FXTabBook) FXTabBookMap[]={
71   FXMAPFUNC(SEL_PAINT,0,FXTabBook::onPaint),
72   FXMAPFUNC(SEL_FOCUS_NEXT,0,FXTabBook::onFocusNext),
73   FXMAPFUNC(SEL_FOCUS_PREV,0,FXTabBook::onFocusPrev),
74   FXMAPFUNC(SEL_FOCUS_UP,0,FXTabBook::onFocusUp),
75   FXMAPFUNC(SEL_FOCUS_DOWN,0,FXTabBook::onFocusDown),
76   FXMAPFUNC(SEL_FOCUS_LEFT,0,FXTabBook::onFocusLeft),
77   FXMAPFUNC(SEL_FOCUS_RIGHT,0,FXTabBook::onFocusRight),
78   FXMAPFUNC(SEL_COMMAND,FXTabBar::ID_OPEN_ITEM,FXTabBook::onCmdOpenItem),
79   };
80 
81 
82 // Object implementation
FXIMPLEMENT(FXTabBook,FXTabBar,FXTabBookMap,ARRAYNUMBER (FXTabBookMap))83 FXIMPLEMENT(FXTabBook,FXTabBar,FXTabBookMap,ARRAYNUMBER(FXTabBookMap))
84 
85 
86 // Make a tab book
87 FXTabBook::FXTabBook(FXComposite* p,FXObject* tgt,FXSelector sel,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb):FXTabBar(p,tgt,sel,opts,x,y,w,h,pl,pr,pt,pb){
88   }
89 
90 
91 // Get width
getDefaultWidth()92 FXint FXTabBook::getDefaultWidth(){
93   FXint w,wtabs,maxtabw,wpnls,t,ntabs;
94   FXWindow *tab,*pane;
95   FXuint hints;
96 
97   // Left or right tabs
98   if(options&TABBOOK_SIDEWAYS){
99     wtabs=wpnls=0;
100     for(tab=getFirst(); tab && tab->getNext(); tab=tab->getNext()->getNext()){
101       pane=tab->getNext();
102       if(tab->shown()){
103         hints=tab->getLayoutHints();
104         if(hints&LAYOUT_FIX_WIDTH) t=tab->getWidth()-2; else t=tab->getDefaultWidth()-2;
105         if(t>wtabs) wtabs=t;
106         t=pane->getDefaultWidth();
107         if(t>wpnls) wpnls=t;
108         }
109       }
110     w=wtabs+wpnls;
111     }
112 
113   // Top or bottom tabs
114   else{
115     wtabs=wpnls=maxtabw=ntabs=0;
116     for(tab=getFirst(); tab && tab->getNext(); tab=tab->getNext()->getNext()){
117       pane=tab->getNext();
118       if(tab->shown()){
119         hints=tab->getLayoutHints();
120         if(hints&LAYOUT_FIX_WIDTH) t=tab->getWidth(); else t=tab->getDefaultWidth();
121         if(t>maxtabw) maxtabw=t;
122         wtabs+=t;
123         t=pane->getDefaultWidth();
124         if(t>wpnls) wpnls=t;
125         ntabs++;
126         }
127       }
128     if(options&PACK_UNIFORM_WIDTH) wtabs=ntabs*maxtabw;
129     wtabs+=5;
130     w=FXMAX(wtabs,wpnls);
131     }
132   return w+padleft+padright+(border<<1);
133   }
134 
135 
136 // Get height
getDefaultHeight()137 FXint FXTabBook::getDefaultHeight(){
138   FXint h,htabs,maxtabh,hpnls,t,ntabs;
139   FXWindow *tab,*pane;
140   FXuint hints;
141 
142   // Left or right tabs
143   if(options&TABBOOK_SIDEWAYS){
144     htabs=hpnls=maxtabh=ntabs=0;
145     for(tab=getFirst(); tab && tab->getNext(); tab=tab->getNext()->getNext()){
146       pane=tab->getNext();
147       if(tab->shown()){
148         hints=tab->getLayoutHints();
149         if(hints&LAYOUT_FIX_HEIGHT) t=tab->getHeight(); else t=tab->getDefaultHeight();
150         if(t>maxtabh) maxtabh=t;
151         htabs+=t;
152         t=pane->getDefaultHeight();
153         if(t>hpnls) hpnls=t;
154         ntabs++;
155         }
156       }
157     if(options&PACK_UNIFORM_HEIGHT) htabs=ntabs*maxtabh;
158     htabs+=5;
159     h=FXMAX(htabs,hpnls);
160     }
161 
162   // Top or bottom tabs
163   else{
164     htabs=hpnls=0;
165     for(tab=getFirst(); tab && tab->getNext(); tab=tab->getNext()->getNext()){
166       pane=tab->getNext();
167       if(tab->shown()){
168         hints=tab->getLayoutHints();
169         if(hints&LAYOUT_FIX_HEIGHT) t=tab->getHeight()-2; else t=tab->getDefaultHeight()-2;
170         if(t>htabs) htabs=t;
171         t=pane->getDefaultHeight();
172         if(t>hpnls) hpnls=t;
173         }
174       }
175     h=htabs+hpnls;
176     }
177   return h+padtop+padbottom+(border<<1);
178   }
179 
180 
181 // Recalculate layout
layout()182 void FXTabBook::layout(){
183   FXint i,xx,yy,x,y,w,h,px,py,pw,ph,maxtabw,maxtabh,cumw,cumh,newcurrent;
184   FXWindow *raisepane=NULL;
185   FXWindow *raisetab=NULL;
186   FXWindow *pane,*tab;
187   FXuint hints;
188 
189   newcurrent=-1;
190 
191   // Measure tabs again
192   maxtabw=maxtabh=0;
193   for(tab=getFirst(),i=0; tab && tab->getNext(); tab=tab->getNext()->getNext(),i++){
194     if(tab->shown()){
195       hints=tab->getLayoutHints();
196       if(newcurrent<0 || i<=current) newcurrent=i;
197       if(hints&LAYOUT_FIX_WIDTH) w=tab->getWidth(); else w=tab->getDefaultWidth();
198       if(hints&LAYOUT_FIX_HEIGHT) h=tab->getHeight(); else h=tab->getDefaultHeight();
199       if(w>maxtabw) maxtabw=w;
200       if(h>maxtabh) maxtabh=h;
201       }
202     }
203 
204   // Changes current only if old current no longer visible
205   current=newcurrent;
206 
207   // Left or right tabs
208   if(options&TABBOOK_SIDEWAYS){
209 
210     // Place panel
211     px=(options&TABBOOK_BOTTOMTABS) ? border+padleft : border+padleft+maxtabw-2;
212     py=border+padtop;
213     pw=width-padleft-padright-(border<<1)-maxtabw+2;
214     ph=height-padtop-padbottom-(border<<1);
215 
216     // Scroll as appropriate
217     for(tab=getFirst(),cumh=i=0; tab && tab->getNext(); tab=tab->getNext()->getNext(),i++){
218       if(tab->shown()){
219         hints=tab->getLayoutHints();
220         if(hints&LAYOUT_FIX_HEIGHT) h=tab->getHeight();
221         else if(options&PACK_UNIFORM_HEIGHT) h=maxtabh;
222         else h=tab->getDefaultHeight();
223         if(i==current){
224           if(tab->getNext()->getNext()){
225             if(cumh+shift+h>ph-REVEAL_PIXELS-2) shift=ph-cumh-h-REVEAL_PIXELS-2;
226             }
227           else{
228             if(cumh+shift+h>ph-2) shift=ph-cumh-h-2;
229             }
230           if(tab->getPrev()){
231             if(cumh+shift<REVEAL_PIXELS+2) shift=REVEAL_PIXELS+2-cumh;
232             }
233           else{
234             if(cumh+shift<2) shift=2-cumh;
235             }
236           }
237         cumh+=h;
238         }
239       }
240 
241     // Adjust shift based on space
242     if(shift<ph-cumh-2) shift=ph-cumh-2;
243     if(shift>0) shift=0;
244 
245     // Place all of the children
246     for(tab=getFirst(),yy=py+shift,i=0; tab && tab->getNext(); tab=tab->getNext()->getNext(),i++){
247       pane=tab->getNext();
248       if(tab->shown()){
249         pane->position(px,py,pw,ph);
250         hints=tab->getLayoutHints();
251         if(hints&LAYOUT_FIX_WIDTH) w=tab->getWidth();
252         else if(options&PACK_UNIFORM_WIDTH) w=maxtabw;
253         else w=tab->getDefaultWidth();
254         if(hints&LAYOUT_FIX_HEIGHT) h=tab->getHeight();
255         else if(options&PACK_UNIFORM_HEIGHT) h=maxtabh;
256         else h=tab->getDefaultHeight();
257         if(i<current){
258           y=yy+2;
259           if(y+h>py+ph-2) y=py+ph-2-h;
260           if(y<py+2) y=py+2;
261           if(options&TABBOOK_BOTTOMTABS)
262             tab->position(px+pw-4,y+2,w,h);
263           else
264             tab->position(px-w+4,y+2,w,h);
265           tab->raise();
266           pane->hide();
267           yy+=h;
268           }
269         else if(i>current){
270           y=yy+2;
271           if(y+h>py+ph-2) y=py+ph-h-2;
272           if(y<py+2) y=py+2;
273           if(options&TABBOOK_BOTTOMTABS)
274             tab->position(px+pw-4,y+2,w,h);
275           else
276             tab->position(px-w+4,y+2,w,h);
277           tab->lower();
278           pane->hide();
279           yy+=h;
280           }
281         else{
282           y=yy;
283           if(y+h>py+ph-2) y=py+ph-h-2;
284           if(y<py) y=py;
285           if(options&TABBOOK_BOTTOMTABS)
286             tab->position(px+pw-2,y,w,h);
287           else
288             tab->position(px-w+2,y,w,h);
289           pane->show();
290           raisepane=pane;
291           raisetab=tab;
292           yy+=h-3;
293           }
294         }
295       else{
296         pane->hide();
297         }
298       }
299 
300     // Hide spurious last tab
301     if(tab) tab->resize(0,0);
302     }
303 
304   // Top or bottom tabs
305   else{
306 
307     // Place panel
308     px=border+padleft;
309     py=(options&TABBOOK_BOTTOMTABS) ? border+padtop : border+padtop+maxtabh-2;
310     pw=width-padleft-padright-(border<<1);
311     ph=height-padtop-padbottom-(border<<1)-maxtabh+2;
312 
313     // Scroll as appropriate
314     for(tab=getFirst(),cumw=i=0; tab && tab->getNext(); tab=tab->getNext()->getNext(),i++){
315       if(tab->shown()){
316         hints=tab->getLayoutHints();
317         if(hints&LAYOUT_FIX_WIDTH) w=tab->getWidth();
318         else if(options&PACK_UNIFORM_WIDTH) w=maxtabw;
319         else w=tab->getDefaultWidth();
320         if(i==current){
321           if(tab->getNext()->getNext()){
322             if(cumw+shift+w>pw-REVEAL_PIXELS-2) shift=pw-cumw-w-REVEAL_PIXELS-2;
323             }
324           else{
325             if(cumw+shift+w>pw-2) shift=pw-cumw-w-2;
326             }
327           if(tab->getPrev()){
328             if(cumw+shift<REVEAL_PIXELS+2) shift=REVEAL_PIXELS+2-cumw;
329             }
330           else{
331             if(cumw+shift<2) shift=2-cumw;
332             }
333           }
334         cumw+=w;
335         }
336       }
337 
338     // Adjust shift based on space
339     if(shift<pw-cumw-2) shift=pw-cumw-2;
340     if(shift>0) shift=0;
341 
342     // Place all of the children
343     for(tab=getFirst(),xx=px+shift,i=0; tab && tab->getNext(); tab=tab->getNext()->getNext(),i++){
344       pane=tab->getNext();
345       if(tab->shown()){
346         pane->position(px,py,pw,ph);
347         hints=tab->getLayoutHints();
348         if(hints&LAYOUT_FIX_WIDTH) w=tab->getWidth();
349         else if(options&PACK_UNIFORM_WIDTH) w=maxtabw;
350         else w=tab->getDefaultWidth();
351         if(hints&LAYOUT_FIX_HEIGHT) h=tab->getHeight();
352         else if(options&PACK_UNIFORM_HEIGHT) h=maxtabh;
353         else h=tab->getDefaultHeight();
354         if(i<current){
355           x=xx+2;
356           if(x+w>px+pw-2) x=px+pw-2-w;
357           if(x<px+2) x=px+2;
358           if(options&TABBOOK_BOTTOMTABS)
359             tab->position(x,py+ph-4,w,h);
360           else
361             tab->position(x,py-h+4,w,h);
362           tab->raise();
363           pane->hide();
364           xx+=w;
365           }
366         else if(i>current){
367           x=xx+2;
368           if(x+w>px+pw-2) x=px+pw-w-2;
369           if(x<px+2) x=px+2;
370           if(options&TABBOOK_BOTTOMTABS)
371             tab->position(x,py+ph-4,w,h);
372           else
373             tab->position(x,py-h+4,w,h);
374           tab->lower();
375           pane->hide();
376           xx+=w;
377           }
378         else{
379           x=xx;
380           if(x+w>px+pw-2) x=px+pw-w-2;
381           if(x<px) x=px;
382           if(options&TABBOOK_BOTTOMTABS)
383             tab->position(x,py+ph-2,w,h);
384           else
385             tab->position(x,py-h+2,w,h);
386           pane->show();
387           raisepane=pane;
388           raisetab=tab;
389           xx+=w-3;
390           }
391         }
392       else{
393         pane->hide();
394         }
395       }
396 
397     // Hide spurious last tab
398     if(tab) tab->resize(0,0);
399     }
400 
401   // Raise tab over panel and panel over all other tabs
402   if(raisepane) raisepane->raise();
403   if(raisetab) raisetab->raise();
404 
405   flags&=~FLAG_DIRTY;
406   }
407 
408 
409 // The sender of the message is the item to open up
onCmdOpenItem(FXObject * sender,FXSelector,void *)410 long FXTabBook::onCmdOpenItem(FXObject* sender,FXSelector,void*){
411   setCurrent(indexOfChild((FXWindow*)sender)>>1,true);
412   return 1;
413   }
414 
415 
416 // Set current subwindow
setCurrent(FXint index,FXbool notify)417 void FXTabBook::setCurrent(FXint index,FXbool notify){
418   if(index!=current && 0<=index && index<(numChildren()>>1)){
419     current=index;
420     recalc();
421     if(notify && target){ target->tryHandle(this,FXSEL(SEL_COMMAND,message),(void*)(FXival)current); }
422     }
423   }
424 
425 
426 // Handle repaint
onPaint(FXObject *,FXSelector,void * ptr)427 long FXTabBook::onPaint(FXObject*,FXSelector,void* ptr){
428   FXEvent *ev=(FXEvent*)ptr;
429   FXDCWindow dc(this,ev);
430   dc.setForeground(backColor);
431   dc.fillRectangle(ev->rect.x,ev->rect.y,ev->rect.w,ev->rect.h);
432   drawFrame(dc,0,0,width,height);
433   return 1;
434   }
435 
436 
437 // Focus moved to next tab
onFocusNext(FXObject *,FXSelector,void * ptr)438 long FXTabBook::onFocusNext(FXObject*,FXSelector,void* ptr){
439   FXWindow *child=getFocus();
440   FXint which;
441   if(child){
442     child=child->getNext();
443     if(!child) return 0;
444     which=indexOfChild(child);
445     if(which&1){
446       child=child->getNext();
447       which++;
448       }
449     }
450   else{
451     child=getFirst();
452     which=0;
453     }
454   while(child && child->getNext() && !(child->shown() && child->isEnabled())){
455     child=child->getNext()->getNext();
456     which+=2;
457     }
458   if(child){
459     setCurrent(which>>1,true);
460     child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
461     return 1;
462     }
463   return 0;
464   }
465 
466 
467 // Focus moved to previous
onFocusPrev(FXObject *,FXSelector,void * ptr)468 long FXTabBook::onFocusPrev(FXObject*,FXSelector,void* ptr){
469   FXWindow *child=getFocus();
470   FXint which;
471   if(child){
472     child=child->getPrev();
473     if(!child) return 0;
474     which=indexOfChild(child);
475     }
476   else{
477     child=getLast();
478     if(!child) return 0;
479     which=indexOfChild(child);
480     }
481   if(which&1){
482     child=child->getPrev();
483     }
484   while(child && child->getPrev() && !(child->shown() && child->isEnabled())){
485     child=child->getPrev()->getPrev();
486     which-=2;
487     }
488   if(child){
489     setCurrent(which>>1,true);
490     child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr);
491     return 1;
492     }
493   return 0;
494   }
495 
496 
497 // Focus moved up
onFocusUp(FXObject *,FXSelector,void * ptr)498 long FXTabBook::onFocusUp(FXObject*,FXSelector,void* ptr){
499   if(options&TABBOOK_SIDEWAYS){
500     return handle(this,FXSEL(SEL_FOCUS_PREV,0),ptr);
501     }
502   if(getFocus()){
503     FXWindow *child=NULL;
504     if(indexOfChild(getFocus())&1){     // We're on a panel
505       if(!(options&TABBOOK_BOTTOMTABS)) child=getFocus()->getPrev();
506       }
507     else{                               // We're on a tab
508       if(options&TABBOOK_BOTTOMTABS) child=getFocus()->getNext();
509       }
510     if(child){
511       if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
512       if(child->handle(this,FXSEL(SEL_FOCUS_UP,0),ptr)) return 1;
513       }
514     }
515   return 0;
516   }
517 
518 
519 // Focus moved down
onFocusDown(FXObject *,FXSelector,void * ptr)520 long FXTabBook::onFocusDown(FXObject*,FXSelector,void* ptr){
521   if(options&TABBOOK_SIDEWAYS){
522     return handle(this,FXSEL(SEL_FOCUS_NEXT,0),ptr);
523     }
524   if(getFocus()){
525     FXWindow *child=NULL;
526     if(indexOfChild(getFocus())&1){     // We're on a panel
527       if(options&TABBOOK_BOTTOMTABS) child=getFocus()->getPrev();
528       }
529     else{                               // We're on a tab
530       if(!(options&TABBOOK_BOTTOMTABS)) child=getFocus()->getNext();
531       }
532     if(child){
533       if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
534       if(child->handle(this,FXSEL(SEL_FOCUS_DOWN,0),ptr)) return 1;
535       }
536     }
537   return 0;
538   }
539 
540 
541 // Focus moved left
onFocusLeft(FXObject *,FXSelector,void * ptr)542 long FXTabBook::onFocusLeft(FXObject*,FXSelector,void* ptr){
543   if(!(options&TABBOOK_SIDEWAYS)){
544     return handle(this,FXSEL(SEL_FOCUS_PREV,0),ptr);
545     }
546   if(getFocus()){
547     FXWindow *child=NULL;
548     if(indexOfChild(getFocus())&1){     // We're on a panel
549       if(!(options&TABBOOK_BOTTOMTABS)) child=getFocus()->getPrev();
550       }
551     else{                               // We're on a tab
552       if(options&TABBOOK_BOTTOMTABS) child=getFocus()->getNext();
553       }
554     if(child){
555       if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
556       if(child->handle(this,FXSEL(SEL_FOCUS_LEFT,0),ptr)) return 1;
557       }
558     }
559   return 0;
560   }
561 
562 
563 // Focus moved right
onFocusRight(FXObject *,FXSelector,void * ptr)564 long FXTabBook::onFocusRight(FXObject*,FXSelector,void* ptr){
565   if(!(options&TABBOOK_SIDEWAYS)){
566     return handle(this,FXSEL(SEL_FOCUS_NEXT,0),ptr);
567     }
568   if(getFocus()){
569     FXWindow *child=NULL;
570     if(indexOfChild(getFocus())&1){     // We're on a panel
571       if(options&TABBOOK_BOTTOMTABS) child=getFocus()->getPrev();
572       }
573     else{                               // We're on a tab
574       if(!(options&TABBOOK_BOTTOMTABS)) child=getFocus()->getNext();
575       }
576     if(child){
577       if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
578       if(child->handle(this,FXSEL(SEL_FOCUS_RIGHT,0),ptr)) return 1;
579       }
580     }
581   return 0;
582   }
583 
584 }
585