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