1 /********************************************************************************
2 *                                                                               *
3 *                   M a t r i x   C o n t a i n e r   O b j e c t               *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1997,2021 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 "FXArray.h"
26 #include "FXHash.h"
27 #include "FXMutex.h"
28 #include "FXStream.h"
29 #include "FXString.h"
30 #include "FXSize.h"
31 #include "FXPoint.h"
32 #include "FXRectangle.h"
33 #include "FXStringDictionary.h"
34 #include "FXSettings.h"
35 #include "FXRegistry.h"
36 #include "FXEvent.h"
37 #include "FXWindow.h"
38 #include "FXApp.h"
39 #include "FXMatrix.h"
40 
41 
42 /*
43   Notes:
44   - Need to observe FILL vs CENTER options.
45   - Filled items should shrink as well as stretch.
46   - Stretch should be proportional.
47   - Center mode, non-stretch should be observed.
48   - Row/Column stretchable iff all elements in row/column have row/column
49     stretch hint.
50   - Row/Column may be 0 pixels wide
51   - We should probably change layout so hidden children do not
52     affect numbering (but if all children in a row/column are
53     hidden, make the whole row disappear!).
54   - Navigating around
55   - Packing order:
56 
57     MATRIX_BY_ROWS:
58     [1] [4] [7]
59     [2] [5] [8]
60     [3] [6] [9]
61 
62     MATRIX_BY_COLUMNS:
63     [1] [2] [3]
64     [4] [5] [6]
65     [7] [8] [9]
66 
67   - Possible solution for spanning rows/columns: have a table
68     containing mapping from FXWindow* to (nr,nc) span.  Consult
69     table during layout.  All children not listed are 1x1, special
70     API add/remove items to the table.  Each layout, any item in the
71     table for which there is no corresponding child is removed.
72     Advantage: no need to add any special info into FXWindow.  Also,
73     no need for any special API to add item into matrix.
74 
75 */
76 
77 #define MAXNUM    512        // Maximum number of columns/rows
78 
79 using namespace FX;
80 
81 /*******************************************************************************/
82 
83 namespace FX {
84 
85 // Map
86 FXDEFMAP(FXMatrix) FXMatrixMap[]={
87   FXMAPFUNC(SEL_FOCUS_UP,0,FXMatrix::onFocusUp),
88   FXMAPFUNC(SEL_FOCUS_DOWN,0,FXMatrix::onFocusDown),
89   FXMAPFUNC(SEL_FOCUS_LEFT,0,FXMatrix::onFocusLeft),
90   FXMAPFUNC(SEL_FOCUS_RIGHT,0,FXMatrix::onFocusRight),
91   };
92 
93 
94 // Object implementation
FXIMPLEMENT(FXMatrix,FXPacker,FXMatrixMap,ARRAYNUMBER (FXMatrixMap))95 FXIMPLEMENT(FXMatrix,FXPacker,FXMatrixMap,ARRAYNUMBER(FXMatrixMap))
96 
97 
98 // Make a vertical one
99 FXMatrix::FXMatrix(FXComposite* p,FXint n,FXuint opts,FXint x,FXint y,FXint w,FXint h,FXint pl,FXint pr,FXint pt,FXint pb,FXint hs,FXint vs):FXPacker(p,opts,x,y,w,h,pl,pr,pt,pb,hs,vs){
100   num=FXCLAMP(1,n,MAXNUM);
101   }
102 
103 
104 // Find child at given row, column
childAtRowCol(FXint r,FXint c) const105 FXWindow* FXMatrix::childAtRowCol(FXint r,FXint c) const {
106   if(options&MATRIX_BY_COLUMNS){
107     return (0<=c && c<num) ? childAtIndex(num*r+c) : NULL;
108     }
109   else{
110     return (0<=r && r<num) ? childAtIndex(r+num*c) : NULL;
111     }
112   }
113 
114 
115 // Get child's row
rowOfChild(const FXWindow * child) const116 FXint FXMatrix::rowOfChild(const FXWindow* child) const {
117   FXint i=indexOfChild(child);
118   return (options&MATRIX_BY_COLUMNS) ? i/num : i%num;
119   }
120 
121 
122 // Get child's column
colOfChild(const FXWindow * child) const123 FXint FXMatrix::colOfChild(const FXWindow* child) const {
124   FXint i=indexOfChild(child);
125   return (options&MATRIX_BY_COLUMNS) ? i%num : i/num;
126   }
127 
128 
129 // Focus moved up
onFocusUp(FXObject *,FXSelector,void * ptr)130 long FXMatrix::onFocusUp(FXObject*,FXSelector,void* ptr){
131   FXWindow *child;
132   FXint r,c;
133   if(getFocus()){
134     r=rowOfChild(getFocus());
135     c=colOfChild(getFocus());
136     while((child=childAtRowCol(--r,c))!=NULL){
137       if(child->shown()){
138         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
139         if(child->handle(this,FXSEL(SEL_FOCUS_UP,0),ptr)) return 1;
140         }
141       }
142     }
143   else{
144     child=getLast();
145     while(child){
146       if(child->shown()){
147         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
148         if(child->handle(this,FXSEL(SEL_FOCUS_UP,0),ptr)) return 1;
149         }
150       child=child->getPrev();
151       }
152     }
153   return 0;
154   }
155 
156 
157 // Focus moved down
onFocusDown(FXObject *,FXSelector,void * ptr)158 long FXMatrix::onFocusDown(FXObject*,FXSelector,void* ptr){
159   FXWindow *child;
160   FXint r,c;
161   if(getFocus()){
162     r=rowOfChild(getFocus());
163     c=colOfChild(getFocus());
164     while((child=childAtRowCol(++r,c))!=NULL){
165       if(child->shown()){
166         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
167         if(child->handle(this,FXSEL(SEL_FOCUS_DOWN,0),ptr)) return 1;
168         }
169       }
170     }
171   else{
172     child=getFirst();
173     while(child){
174       if(child->shown()){
175         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
176         if(child->handle(this,FXSEL(SEL_FOCUS_DOWN,0),ptr)) return 1;
177         }
178       child=child->getNext();
179       }
180     }
181   return 0;
182   }
183 
184 
185 // Focus moved to left
onFocusLeft(FXObject *,FXSelector,void * ptr)186 long FXMatrix::onFocusLeft(FXObject*,FXSelector,void* ptr){
187   FXWindow *child;
188   FXint r,c;
189   if(getFocus()){
190     r=rowOfChild(getFocus());
191     c=colOfChild(getFocus());
192     while((child=childAtRowCol(r,--c))!=NULL){
193       if(child->shown()){
194         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
195         if(child->handle(this,FXSEL(SEL_FOCUS_LEFT,0),ptr)) return 1;
196         }
197       }
198     }
199   else{
200     child=getLast();
201     while(child){
202       if(child->shown()){
203         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
204         if(child->handle(this,FXSEL(SEL_FOCUS_LEFT,0),ptr)) return 1;
205         }
206       child=child->getPrev();
207       }
208     }
209   return 0;
210   }
211 
212 
213 // Focus moved to right
onFocusRight(FXObject *,FXSelector,void * ptr)214 long FXMatrix::onFocusRight(FXObject*,FXSelector,void* ptr){
215   FXWindow *child;
216   FXint r,c;
217   if(getFocus()){
218     r=rowOfChild(getFocus());
219     c=colOfChild(getFocus());
220     while((child=childAtRowCol(r,++c))!=NULL){
221       if(child->shown()){
222         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
223         if(child->handle(this,FXSEL(SEL_FOCUS_RIGHT,0),ptr)) return 1;
224         }
225       }
226     }
227   else{
228     child=getFirst();
229     while(child){
230       if(child->shown()){
231         if(child->handle(this,FXSEL(SEL_FOCUS_SELF,0),ptr)) return 1;
232         if(child->handle(this,FXSEL(SEL_FOCUS_RIGHT,0),ptr)) return 1;
233         }
234       child=child->getNext();
235       }
236     }
237   return 0;
238   }
239 
240 
241 // Set number of rows (if possible)
setNumRows(FXint nr)242 void FXMatrix::setNumRows(FXint nr){
243   if(nr<1 || nr>=MAXNUM){ fxerror("%s::setNumRows: bad number of rows specified.\n",getClassName()); }
244   if(!(options&MATRIX_BY_COLUMNS) && num!=nr){
245     num=nr;
246     recalc();
247     }
248   }
249 
250 
251 // Get number of rows
getNumRows() const252 FXint FXMatrix::getNumRows() const {
253   return (num && (options&MATRIX_BY_COLUMNS)) ? (numChildren()+num-1)/num : num;
254   }
255 
256 
257 // Set number of columns (if possible)
setNumColumns(FXint nc)258 void FXMatrix::setNumColumns(FXint nc){
259   if(nc<1 || nc>=MAXNUM){ fxerror("%s::setNumColumns: bad number of columns specified.\n",getClassName()); }
260   if((options&MATRIX_BY_COLUMNS) && num!=nc){
261     num=nc;
262     recalc();
263     }
264   }
265 
266 
267 // Get number of columns
getNumColumns() const268 FXint FXMatrix::getNumColumns() const {
269   return (num && !(options&MATRIX_BY_COLUMNS)) ? (numChildren()+num-1)/num : num;
270   }
271 
272 
273 // Compute minimum width based on child layout hints
getDefaultWidth()274 FXint FXMatrix::getDefaultWidth(){
275   FXint c,n,w,nzcol=0,wmax=0,mw=0;
276   FXWindow *child;
277   FXuint hints;
278   FXint colw[MAXNUM];
279   for(c=0; c<MAXNUM; c++) colw[c]=0;
280   if(options&PACK_UNIFORM_WIDTH) mw=maxChildWidth();
281   for(child=getFirst(),n=0; child; child=child->getNext(),n++){
282     if(child->shown()){
283       hints=child->getLayoutHints();
284       if(hints&LAYOUT_FIX_WIDTH) w=child->getWidth();
285       else if(options&PACK_UNIFORM_WIDTH) w=mw;
286       else w=child->getDefaultWidth();
287       c=(options&MATRIX_BY_COLUMNS)?n%num:n/num;
288       FXASSERT(c<MAXNUM);
289       if(w>colw[c]){
290         if(colw[c]==0) nzcol++;                             // Count non-zero columns
291         wmax+=w-colw[c];
292         colw[c]=w;
293         }
294       }
295     }
296   if(nzcol>1) wmax+=(nzcol-1)*hspacing;
297   return padleft+padright+wmax+(border<<1);
298   }
299 
300 
301 
302 // Compute minimum height based on child layout hints
getDefaultHeight()303 FXint FXMatrix::getDefaultHeight(){
304   FXint r,n,h,nzrow=0,hmax=0,mh=0;
305   FXWindow *child;
306   FXuint hints;
307   FXint rowh[MAXNUM];
308   for(r=0; r<MAXNUM; r++) rowh[r]=0;
309   if(options&PACK_UNIFORM_HEIGHT) mh=maxChildHeight();
310   for(child=getFirst(),n=0; child; child=child->getNext(),n++){
311     if(child->shown()){
312       hints=child->getLayoutHints();
313       if(hints&LAYOUT_FIX_HEIGHT) h=child->getHeight();
314       else if(options&PACK_UNIFORM_HEIGHT) h=mh;
315       else h=child->getDefaultHeight();
316       r=(options&MATRIX_BY_COLUMNS)?n/num:n%num;
317       FXASSERT(r<MAXNUM);
318       if(h>rowh[r]){
319         if(rowh[r]==0) nzrow++;                             // Count non-zero rows
320         hmax+=h-rowh[r];
321         rowh[r]=h;
322         }
323       }
324     }
325   if(nzrow>1) hmax+=(nzrow-1)*vspacing;
326   return padtop+padbottom+hmax+(border<<1);
327   }
328 
329 
330 // Recalculate layout
layout()331 void FXMatrix::layout(){
332   FXint ncol,nrow,nzcol,nzrow,r,c,x,y,w,h,n,e,t;
333   FXint rowh[MAXNUM],colw[MAXNUM];
334   FXuchar srow[MAXNUM],scol[MAXNUM];
335   FXint left,right,top,bottom,cw,rh;
336   FXint mw=0,mh=0;
337   FXint hremain,vremain;
338   FXint hsumexpand,hnumexpand;
339   FXint vsumexpand,vnumexpand;
340   FXWindow *child;
341   FXuint hints;
342 
343   // Placement rectangle; right/bottom non-inclusive
344   left=border+padleft;
345   right=width-border-padright;
346   top=border+padtop;
347   bottom=height-border-padbottom;
348   hremain=right-left;
349   vremain=bottom-top;
350 
351   // Non-zero rows/columns
352   nzrow=0;
353   nzcol=0;
354 
355   // Clear column/row sizes
356   for(n=0; n<MAXNUM; n++){
357     colw[n]=rowh[n]=0;          // Columns may be 0 size
358     srow[n]=scol[n]=1;
359     }
360 
361   // Get maximum child size
362   if(options&PACK_UNIFORM_WIDTH) mw=maxChildWidth();
363   if(options&PACK_UNIFORM_HEIGHT) mh=maxChildHeight();
364 
365   // Find expandable columns and rows
366   for(child=getFirst(),n=0; child; child=child->getNext(),n++){
367     if(child->shown()){
368       hints=child->getLayoutHints();
369       if(options&MATRIX_BY_COLUMNS){r=n/num;c=n%num;}else{r=n%num;c=n/num;}
370       FXASSERT(r<MAXNUM && c<MAXNUM);
371       if(hints&LAYOUT_FIX_WIDTH) w=child->getWidth();
372       else if(options&PACK_UNIFORM_WIDTH) w=mw;
373       else w=child->getDefaultWidth();
374       if(hints&LAYOUT_FIX_HEIGHT) h=child->getHeight();
375       else if(options&PACK_UNIFORM_HEIGHT) h=mh;
376       else h=child->getDefaultHeight();
377       FXASSERT(w>=0);
378       FXASSERT(h>=0);
379       if(w>colw[c]){ if(colw[c]==0) nzcol++; colw[c]=w; }
380       if(h>rowh[r]){ if(rowh[r]==0) nzrow++; rowh[r]=h; }
381       if(!(hints&LAYOUT_FILL_COLUMN)) scol[c]=0;
382       if(!(hints&LAYOUT_FILL_ROW)) srow[r]=0;
383       }
384     }
385 
386   // Get number of rows and columns
387   if(options&MATRIX_BY_COLUMNS){
388     ncol=num;
389     nrow=(n+num-1)/num;
390     }
391   else{
392     ncol=(n+num-1)/num;
393     nrow=num;
394     }
395 
396   // Find stretch in columns
397   for(c=hsumexpand=hnumexpand=0; c<ncol; c++){
398     if(colw[c]){
399       if(scol[c]){
400         hsumexpand+=colw[c];
401         hnumexpand++;
402         }
403       else{
404         hremain-=colw[c];
405         }
406       }
407     }
408 
409   // Find stretch in rows
410   for(r=vsumexpand=vnumexpand=0; r<nrow; r++){
411     if(rowh[r]){
412       if(srow[r]){
413         vsumexpand+=rowh[r];
414         vnumexpand++;
415         }
416       else{
417         vremain-=rowh[r];
418         }
419       }
420     }
421 
422   // Subtract spacing for non-zero rows/columns
423   if(nzcol>1) hremain-=(nzcol-1)*hspacing;
424   if(nzrow>1) vremain-=(nzrow-1)*vspacing;
425 
426   // Disburse space horizontally
427   for(c=e=0,x=border+padleft; c<ncol; c++){
428     w=colw[c];
429     colw[c]=x;
430     if(w){
431       if(scol[c]){
432         if(hsumexpand>0){                         // Divide proportionally
433           t=w*hremain;
434           w=t/hsumexpand;
435           e+=t%hsumexpand;
436           if(e>=hsumexpand){w++;e-=hsumexpand;}
437           }
438         else{                                     // Divide equally
439           FXASSERT(hnumexpand>0);
440           w=hremain/hnumexpand;
441           e+=hremain%hnumexpand;
442           if(e>=hnumexpand){w++;e-=hnumexpand;}
443           }
444         }
445       x+=w+hspacing;
446       }
447     }
448   colw[ncol]=x;
449 
450   // Disburse space vertically
451   for(r=e=0,y=border+padtop; r<nrow; r++){
452     h=rowh[r];
453     rowh[r]=y;
454     if(h){
455       if(srow[r]){
456         if(vsumexpand>0){                         // Divide proportionally
457           t=h*vremain;
458           h=t/vsumexpand;
459           e+=t%vsumexpand;
460           if(e>=vsumexpand){h++;e-=vsumexpand;}
461           }
462         else{                                     // Divide equally
463           FXASSERT(vnumexpand>0);
464           h=vremain/vnumexpand;
465           e+=vremain%vnumexpand;
466           if(e>=vnumexpand){h++;e-=vnumexpand;}
467           }
468         }
469       y+=h+vspacing;
470       }
471     }
472   rowh[nrow]=y;
473 
474   // Do the layout
475   for(child=getFirst(),n=0; child; child=child->getNext(),n++){
476     if(child->shown()){
477       hints=child->getLayoutHints();
478       if(options&MATRIX_BY_COLUMNS){r=n/num;c=n%num;}else{r=n%num;c=n/num;}
479       cw=colw[c+1]-colw[c]-hspacing;
480       rh=rowh[r+1]-rowh[r]-vspacing;
481 
482       if(hints&LAYOUT_FIX_WIDTH) w=child->getWidth();
483       else if(hints&LAYOUT_FILL_X) w=cw;
484       else if(options&PACK_UNIFORM_WIDTH) w=mw;
485       else w=child->getDefaultWidth();
486 
487       if(hints&LAYOUT_CENTER_X) x=colw[c]+(cw-w)/2;
488       else if(hints&LAYOUT_RIGHT) x=colw[c]+cw-w;
489       else x=colw[c];
490 
491       if(hints&LAYOUT_FIX_HEIGHT) h=child->getHeight();
492       else if(hints&LAYOUT_FILL_Y) h=rh;
493       else if(options&PACK_UNIFORM_HEIGHT) h=mh;
494       else h=child->getDefaultHeight();
495 
496       if(hints&LAYOUT_CENTER_Y) y=rowh[r]+(rh-h)/2;
497       else if(hints&LAYOUT_BOTTOM) y=rowh[r]+rh-h;
498       else y=rowh[r];
499 
500       child->position(x,y,w,h);
501       }
502     }
503   flags&=~FLAG_DIRTY;
504   }
505 
506 
507 // Change matrix style
setMatrixStyle(FXuint style)508 void FXMatrix::setMatrixStyle(FXuint style){
509   FXuint opts=(options&~MATRIX_BY_COLUMNS) | (style&MATRIX_BY_COLUMNS);
510   if(opts!=options){
511     options=opts;
512     recalc();
513     update();
514     }
515   }
516 
517 
518 // Return matrix style
getMatrixStyle() const519 FXuint FXMatrix::getMatrixStyle() const {
520   return options&MATRIX_BY_COLUMNS;
521   }
522 
523 }
524 
525