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