r25048: From the archives (patch found in one of my old working trees):
[jelmer/samba4-debian.git] / webapps / qooxdoo-0.6.5-sdk / frontend / framework / source / class / qx / ui / table / DefaultResizeBehavior.js
1 /* ************************************************************************
2
3    qooxdoo - the new era of web development
4
5    http://qooxdoo.org
6
7    Copyright:
8      2007 Derrell Lipman
9
10    License:
11      LGPL: http://www.gnu.org/licenses/lgpl.html
12      EPL: http://www.eclipse.org/org/documents/epl-v10.php
13      See the LICENSE file in the project's top-level directory for details.
14
15    Authors:
16      * Derrell Lipman (derrell)
17
18 ************************************************************************ */
19
20 /* ************************************************************************
21
22 #module(table)
23
24 ************************************************************************ */
25
26 /**
27  * The default resize behavior.  Until a resize model is loaded, the default
28  * behavior is to:
29  * <ol>
30  *   <li>
31  *     Upon the table initially appearing, and upon any window resize, divide
32  *     the table space equally between the visible columns.
33  *   </li>
34  *   <li>
35  *     When a column is increased in width, all columns to its right are
36  *     pushed to the right with no change to their widths.  This may push some
37  *     columns off the right edge of the table, causing a horizontal scroll
38  *     bar to appear.
39  *   </li>
40  *   <li>
41  *     When a column is decreased in width, if the total width of all columns
42  *     is <i>greater than</i> the table width, no additional column wiidth
43  *     changes are made.
44  *   </li>
45  *   <li>
46  *     When a column is decreased in width, if the total width of all columns
47  *     is <i>less than</i> the width of the table, the visible column
48  *     immediately to the right of the column which decreased in width has its
49  *     width increased to fill the remaining space.
50  *   </li>
51  * </ol>
52  *
53  * A resize model may be loaded to provide more guidance on how to adjust
54  * column width upon each of the events: initial appear, window resize, and
55  * column resize. *** TO BE FILLED IN ***
56  */
57 qx.OO.defineClass("qx.ui.table.DefaultResizeBehavior",
58                   qx.ui.table.AbstractResizeBehavior,
59 function()
60 {
61   qx.ui.table.AbstractResizeBehavior.call(this);
62 });
63
64
65 /*
66  * A function to instantiate a resize behavior column data object.
67  */
68 qx.OO.addProperty(
69   {
70     name :
71       "newResizeBehaviorColumnData",
72     type :
73       "function",
74     setOnlyOnce :
75       true,
76     defaultValue:
77       function(obj)
78       {
79         return new qx.ui.table.ResizeBehaviorColumnData();
80       }
81   });
82
83
84 /**
85  * Set the width of a column.
86  *
87  * @param col {Integer}
88  *   The column whose width is to be set
89  *
90  * @param width {Integer, String}
91  *   The width of the specified column.  The width may be specified as integer
92  *   number of pixels (e.g. 100), a string representing percentage of the
93  *   inner width of the Table (e.g. "25%"), or a string representing a flex
94  *   width (e.g. "1*").
95  */
96 qx.Proto.setWidth = function(col, width)
97 {
98   // Ensure the column is within range
99   if (col >= this._resizeColumnData.length)
100   {
101     throw new Error("Column number out of range");
102   }
103
104   // Set the new width
105   this._resizeColumnData[col].setWidth(width);
106 };
107
108
109 /**
110  * Set the minimum width of a column.
111  *
112  * @param col {Integer}
113  *   The column whose minimum width is to be set
114  *
115  * @param width {Integer}
116  *   The minimum width of the specified column.
117  */
118 qx.Proto.setMinWidth = function(col, width)
119 {
120   // Ensure the column is within range
121   if (col >= this._resizeColumnData.length)
122   {
123     throw new Error("Column number out of range");
124   }
125
126   // Set the new width
127   this._resizeColumnData[col].setMinWidth(width);
128 };
129
130
131 /**
132  * Set the maximum width of a column.
133  *
134  * @param col {Integer}
135  *   The column whose maximum width is to be set
136  *
137  * @param width {Integer}
138  *   The maximum width of the specified column.
139  */
140 qx.Proto.setMaxWidth = function(col, width)
141 {
142   // Ensure the column is within range
143   if (col >= this._resizeColumnData.length)
144   {
145     throw new Error("Column number out of range");
146   }
147
148   // Set the new width
149   this._resizeColumnData[col].setMaxWidth(width);
150 };
151
152
153 /**
154  * Set any or all of the width, minimum width, and maximum width of a column
155  * in a single call.
156  *
157  * @param map {Map}
158  *   A map containing any or all of the property names "width", "minWidth",
159  *   and "maxWidth".  The property values are as described for
160  *   {@link #setWidth}, {@link #setMinWidth} and {@link #setMaxWidth}
161  *   respectively.
162  */
163 qx.Proto.set = function(col, map)
164 {
165   for (var prop in map)
166   {
167     switch(prop)
168     {
169     case "width":
170       this.setWidth(col, map[prop]);
171       break;
172
173     case "minWidth":
174       this.setMinWidth(col, map[prop]);
175       break;
176
177     case "maxWidth":
178       this.setMaxWidth(col, map[prop]);
179       break;
180
181     default:
182       throw new Error("Unknown property: " + prop);
183     }
184   }
185 };
186
187
188 // overloaded
189 qx.Proto.onAppear = function(tableColumnModel, event)
190 {
191   // Get the initial available width so we know whether a resize caused an
192   // increase or decrease in the available space.
193   this._width = this._getAvailableWidth(tableColumnModel);
194
195   // Calculate column widths
196   this._computeColumnsFlexWidth(tableColumnModel, event);
197 };
198
199
200 // overloaded
201 qx.Proto.onTableWidthChanged = function(tableColumnModel, event)
202 {
203   // Calculate column widths
204   this._computeColumnsFlexWidth(tableColumnModel, event);
205 };
206
207
208 // overloaded
209 qx.Proto.onColumnWidthChanged = function(tableColumnModel, event)
210 {
211   // Extend the next column to fill blank space
212   this._extendNextColumn(tableColumnModel, event);
213 };
214
215
216 // overloaded
217 qx.Proto.onVisibilityChanged = function(tableColumnModel, event)
218 {
219   // Extend the last column to fill blank space
220   this._extendLastColumn(tableColumnModel, event);
221 };
222
223
224 // overloaded
225 qx.Proto._setNumColumns = function(numColumns)
226 {
227   // Are there now fewer (or the same number of) columns than there were
228   // previously?
229   if (numColumns <= this._resizeColumnData.length)
230   {
231     // Yup.  Delete the extras.
232     this._resizeColumnData.splice(numColumns);
233     return;
234   }
235
236   // There are more columns than there were previously.  Allocate more.
237   for (var i = this._resizeColumnData.length; i < numColumns; i++)
238   {
239     this._resizeColumnData[i] = this.getNewResizeBehaviorColumnData()();
240     this._resizeColumnData[i]._columnNumber = i;
241   }
242 };
243
244
245 /**
246  * Computes the width of all flexible children (based loosely on the method of
247  * the same name in HorizontalBoxLayoutImpl).
248  *
249  * @param tableColumnModel {qx.ui.table.ResizeTableColumnModel}
250  *   The table column model in use.
251  *
252  * @param event
253  *   The event object.
254  */
255 qx.Proto._computeColumnsFlexWidth = function(tableColumnModel, event)
256 {
257   // Semi-permanent configuration settings
258   var debug = true;
259
260   if (debug)
261   {
262     this.debug("computeColumnsFlexWidth");
263   }
264
265   var visibleColumns = tableColumnModel._visibleColumnArr;
266   var visibleColumnsLength = visibleColumns.length;
267   var columnData;
268   var flexibleColumns = [ ];
269   var widthUsed = 0;
270   var i;
271
272   // Determine the available width
273   var width = this._getAvailableWidth(tableColumnModel);
274
275
276   // *************************************************************
277   // 1. Compute the sum of all static sized columns and find
278   //    all flexible columns.
279   // *************************************************************
280   for (i = 0; i < visibleColumnsLength; i++)
281   {
282     // Get the current column's column data
283     columnData = this._resizeColumnData[visibleColumns[i]];
284
285     // Is this column width type "auto"?
286     if (columnData._computedWidthTypeAuto)
287     {
288       // Yup.  Convert it to a Flex "1*"
289       columnData._computedWidthTypeAuto = false;
290       columnData._computedWidthTypeFlex = true;
291       columnData._computedWidthParsed = 1;
292     }
293
294     // Is this column a flex width?
295     if (columnData._computedWidthTypeFlex)
296     {
297       // Yup.  Save it for future processing.
298       flexibleColumns.push(columnData);
299     }
300     else if (columnData._computedWidthTypePercent)
301     {
302       // We can calculate the width of a Percent type right now.  Convert it
303       // to a Flex type that's already calculated (no further calculation
304       // required).
305       columnData._computedWidthPercentValue =
306         Math.round(width * (columnData._computedWidthParsed / 100));
307       widthUsed += columnData._computedWidthPercentValue;
308     }
309     else
310     {
311       // We have a fixed width.  Track width already allocated.
312       widthUsed += columnData.getWidth();
313     }
314   }
315
316   if (debug)
317   {
318     this.debug("Width: " + widthUsed + "/" + width);
319     this.debug("Flexible Count: " + flexibleColumns.length);
320   }
321
322
323   // *************************************************************
324   // 2. Compute the sum of all flexible column widths
325   // *************************************************************
326   var widthRemaining = width - widthUsed;
327   var flexibleColumnsLength = flexibleColumns.length;
328   var prioritySum = 0;
329
330   for (i = 0; i < flexibleColumnsLength; i++)
331   {
332     prioritySum += flexibleColumns[i]._computedWidthParsed;
333   }
334
335
336   // *************************************************************
337   // 3. Calculating the size of each 'part'.
338   // *************************************************************
339   var partWidth = widthRemaining / prioritySum;
340
341   // *************************************************************
342   // 4. Adjust flexible columns, taking min/max values into account
343   // *************************************************************
344   
345   bSomethingChanged = true;
346   for (flexibleColumnsLength = flexibleColumns.length;
347        bSomethingChanged && flexibleColumnsLength > 0;
348        flexibleColumnsLength = flexibleColumns.length)
349   {
350     // Assume nothing will change
351     bSomethingChanged = false;
352
353     for (i = flexibleColumnsLength - 1; i >= 0; i--)
354     {
355       columnData = flexibleColumns[i];
356
357       computedFlexibleWidth =
358         columnData._computedWidthFlexValue =
359         columnData._computedWidthParsed * partWidth;
360
361       // If the part is not within its specified min/max range, adjust it.
362       var min = columnData.getMinWidthValue();
363       var max = columnData.getMaxWidthValue();
364       if (min && computedFlexibleWidth < min)
365       {
366         columnData._computedWidthFlexValue = Math.round(min);
367         widthUsed += columnData._computedWidthFlexValue;
368         qx.lang.Array.removeAt(flexibleColumns, i);
369         bSomethingChanged = true;
370
371         // Don't round fixed-width columns (in step 5)
372         columnData = null;
373       }
374       else if (max && computedFlexibleWidth > max)
375       {
376         columnData._computedWidthFlexValue = Math.round(max);
377         widthUsed += columnData._computedWidthFlexValue;
378         qx.lang.Array.removeAt(flexibleColumns, i);
379         bSomethingChanged = true;
380
381         // Don't round fixed-width columns (in step 5)
382         columnData = null;
383       }
384     }
385   }
386
387   // If any flexible columns remain, then allocate the remaining space to them
388   if (flexibleColumns.length > 0)
389   {
390     // Recalculate the priority sum of the remaining flexible columns
391     prioritySum = 0;
392     for (i = 0; i < flexibleColumnsLength; i++)
393     {
394       prioritySum += flexibleColumns[i]._computedWidthParsed;
395     }
396
397     // Recalculate the width remaining and part width
398     widthRemaining = width - widthUsed;
399     partWidth = widthRemaining / prioritySum;
400
401     // If there's no width remaining...
402     if (widthRemaining <= 0)
403     {
404       // ... then use minimum width * priority for all remaining columns
405       for (i = 0; i < flexibleColumnsLength; i++)
406       {
407         columnData = flexibleColumns[i];
408
409         computedFlexibleWidth =
410           columnData._computedWidthFlexValue =
411           (qx.ui.table.DefaultResizeBehavior.MIN_WIDTH *
412            flexibleColumns[i]._computedWidthParsed);
413         columnData._computedWidthFlexValue = Math.round(computedFlexibleWidth);
414         widthUsed += columnData._computedWidthFlexValue;
415       }
416     }
417     else
418     {
419       // Assign widths of remaining flexible columns
420       for (i = 0; i < flexibleColumnsLength; i++)
421       {
422         columnData = flexibleColumns[i];
423
424         computedFlexibleWidth =
425         columnData._computedWidthFlexValue =
426         columnData._computedWidthParsed * partWidth;
427
428         // If the computed width is less than our hard-coded minimum...
429         if (computedFlexibleWidth <
430             qx.ui.table.DefaultResizeBehavior.MIN_WIDTH)
431         {
432           // ... then use the hard-coded minimum
433           computedFlexibleWidth = qx.ui.table.DefaultResizeBehavior.MIN_WIDTH;
434         }
435
436         columnData._computedWidthFlexValue = Math.round(computedFlexibleWidth);
437         widthUsed += columnData._computedWidthFlexValue;
438       }
439     }
440   }
441
442   // *************************************************************
443   // 5. Fix rounding errors
444   // *************************************************************
445   if (columnData != null && widthRemaining > 0)
446   {
447     columnData._computedWidthFlexValue += width - widthUsed;
448   }
449
450   // *************************************************************
451   // 6. Set the column widths to what we have calculated
452   // *************************************************************
453   for (i = 0; i < visibleColumnsLength; i++)
454   {
455     var colWidth;
456
457     // Get the current column's column data
458     columnData = this._resizeColumnData[visibleColumns[i]];
459
460     // Is this column a flex width?
461     if (columnData._computedWidthTypeFlex)
462     {
463       // Yup.  Set the width to the calculated width value based on flex
464       colWidth = columnData._computedWidthFlexValue;
465     }
466     else if (columnData._computedWidthTypePercent)
467     {
468       // Set the width to the calculated width value based on percent
469       colWidth = columnData._computedWidthPercentValue;
470     }
471     else
472     {
473       colWidth = columnData.getWidth();
474     }
475
476     // Now that we've calculated the width, set it.
477     tableColumnModel.setColumnWidth(visibleColumns[i], colWidth);
478
479     if (debug)
480     {
481       this.debug("col " + columnData._columnNumber + ": width=" + colWidth);
482     }
483   }
484 };
485
486
487 /**
488  * Extend the visible column to right of the column which just changed width,
489  * to fill any available space within the inner width of the table.  This
490  * means that if the sum of the widths of all columns exceeds the inner width
491  * of the table, no change is made.  If, on the other hand, the sum of the
492  * widths of all columns is less than the inner width of the table, the
493  * visible column to the right of the column which just changed width is
494  * extended to take up the width available within the inner width of the
495  * table.
496  *
497  * @param tableColumnModel {qx.ui.table.ResizeTableColumnModel}
498  *   The table column model in use.
499  *
500  * @param event
501  *   The event object.
502  */
503 qx.Proto._extendNextColumn = function(tableColumnModel, event)
504 {
505   // Event data properties: col, oldWidth, newWidth
506   var data = event.getData();
507
508   var visibleColumns = tableColumnModel._visibleColumnArr;
509
510   // Determine the available width
511   var width = this._getAvailableWidth(tableColumnModel);
512
513   // Determine the number of visible columns
514   var numColumns = visibleColumns.length;
515
516   // Did this column become longer than it was?
517   if (data.newWidth > data.oldWidth)
518   {
519     // Yup.  Don't resize anything else.  The other columns will just get
520     // pushed off and require scrollbars be added (if not already there).
521     return;
522   }
523
524   // This column became shorter.  See if we no longer take up the full space
525   // that's available to us.
526   var i;
527   var nextCol;
528   var widthUsed = 0;
529   for (i = 0; i < numColumns; i++)
530   {
531     widthUsed +=
532       tableColumnModel.getColumnWidth(visibleColumns[i]);
533   }
534
535   // If the used width is less than the available width...
536   if (widthUsed < width)
537   {
538     // ... then determine the next visible column
539     for (i = 0; i < visibleColumns.length; i++)
540     {
541       if (visibleColumns[i] == data.col)
542       {
543         nextCol = visibleColumns[i + 1];
544         break;
545       }
546     }
547
548     if (nextCol)
549     {
550       // Make the next column take up the available space.
551       var oldWidth = tableColumnModel.getColumnWidth(nextCol);
552       var newWidth = (width - (widthUsed -
553                                tableColumnModel.getColumnWidth(nextCol)));
554       tableColumnModel.setColumnWidth(nextCol, newWidth);
555     }
556   }
557 };
558
559
560 /**
561  * If a column was just made invisible, extend the last column to fill any
562  * available space within the inner width of the table.  This means that if
563  * the sum of the widths of all columns exceeds the inner width of the table,
564  * no change is made.  If, on the other hand, the sum of the widths of all
565  * columns is less than the inner width of the table, the last column is
566  * extended to take up the width available within the inner width of the
567  * table.
568  *
569  * @param tableColumnModel {qx.ui.table.ResizeTableColumnModel}
570  *   The table column model in use.
571  *
572  * @param event
573  *   The event object.
574  */
575 qx.Proto._extendLastColumn = function(tableColumnModel, event)
576 {
577   // Event data properties: col, visible
578   var data = event.getData();
579
580   // If the column just became visible, don't make any width changes
581   if (data.visible)
582   {
583     return;
584   }
585
586   // Get the array of visible columns
587   var visibleColumns = tableColumnModel._visibleColumnArr;
588
589   // Determine the available width
590   var width = this._getAvailableWidth(tableColumnModel);
591
592   // Determine the number of visible columns
593   var numColumns = visibleColumns.length;
594
595   // See if we no longer take up the full space that's available to us.
596   var i;
597   var lastCol;
598   var widthUsed = 0;
599   for (i = 0; i < numColumns; i++)
600   {
601     widthUsed +=
602       tableColumnModel.getColumnWidth(visibleColumns[i]);
603   }
604
605   // If the used width is less than the available width...
606   if (widthUsed < width)
607   {
608     // ... then get the last visible column
609     lastCol = visibleColumns[visibleColumns.length - 1];
610
611     // Make the last column take up the available space.
612     var oldWidth = tableColumnModel.getColumnWidth(lastCol);
613     var newWidth = (width - (widthUsed -
614                              tableColumnModel.getColumnWidth(lastCol)));
615     tableColumnModel.setColumnWidth(lastCol, newWidth);
616   }
617 };
618
619
620
621 qx.Class.MIN_WIDTH = 10;