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