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