r20517: re-add cleaned-up webapps
[kai/samba.git] / webapps / qooxdoo-0.6.3-sdk / frontend / framework / source / class / qx / ui / listview / ListViewPane.js
1 /* ************************************************************************
2
3    qooxdoo - the new era of web development
4
5    http://qooxdoo.org
6
7    Copyright:
8      2004-2006 by 1&1 Internet AG, Germany, http://www.1and1.org
9
10    License:
11      LGPL 2.1: http://www.gnu.org/licenses/lgpl.html
12
13    Authors:
14      * Sebastian Werner (wpbasti)
15      * Andreas Ecker (ecker)
16
17 ************************************************************************ */
18
19 /* ************************************************************************
20
21 #module(ui_listview)
22
23 ************************************************************************ */
24
25 qx.OO.defineClass("qx.ui.listview.ListViewPane", qx.ui.layout.GridLayout,
26 function(vData, vColumns)
27 {
28   qx.ui.layout.GridLayout.call(this);
29
30   // ************************************************************************
31   //   DATA
32   // ************************************************************************
33   // Add aliases for data tables
34   this._data = vData;
35   this._columns = vColumns;
36
37
38   // ************************************************************************
39   //   INITIALIZE MANAGER
40   // ************************************************************************
41   this._manager = new qx.manager.selection.VirtualSelectionManager(this);
42
43
44   // ************************************************************************
45   //   MOUSE EVENT LISTENER
46   // ************************************************************************
47   // Add handling for mouse wheel events
48   // Needed because the virtual scroll area does not fire browser
49   // understandable events above this pane.
50   this.addEventListener("mousewheel", this._onmousewheel);
51
52   this.addEventListener("mouseover", this._onmouseover);
53   this.addEventListener("mousedown", this._onmousedown);
54   this.addEventListener("mouseup", this._onmouseup);
55   this.addEventListener("click", this._onclick);
56   this.addEventListener("dblclick", this._ondblclick);
57
58
59   // ************************************************************************
60   //   KEY EVENT LISTENER
61   // ************************************************************************
62   this.addEventListener("keypress", this._onkeypress);
63 });
64
65 qx.OO.changeProperty({ name : "appearance",
66                        type : "string",
67                        defaultValue : "list-view-pane"
68                      });
69
70 qx.Proto._rowHeight = 16;
71
72
73
74
75
76
77 /*
78 ---------------------------------------------------------------------------
79   UTILITIES
80 ---------------------------------------------------------------------------
81 */
82
83 qx.Proto.getView = function() {
84   return this.getParent().getParent();
85 }
86
87
88
89
90
91
92 /*
93 ---------------------------------------------------------------------------
94   UPDATER
95 ---------------------------------------------------------------------------
96 */
97
98 qx.Proto._lastRowCount = 0;
99
100 qx.Proto._updateLayout = function(vUpdate)
101 {
102   // this.debug("InnerHeight: " + this._computeInnerHeight());
103   // this.debug("BoxHeight: " + this._computeBoxHeight());
104   // return
105
106   var vColumns = this._columns;
107   var vRowCount = Math.ceil(this.getInnerHeight() / this._rowHeight);
108   var vData = this._data;
109   var vCell;
110
111   // this.debug("Row-Count: " + this._lastRowCount + " => " + vRowCount);
112
113   // Sync cells: Add new ones and configure them
114   if (vRowCount > this._lastRowCount)
115   {
116     for (var i=this._lastRowCount, j=0; i<vRowCount; i++, j=0)
117     {
118       for (var vCol in vColumns)
119       {
120         vCell = new vColumns[vCol].contentClass;
121
122         this.add(vCell, j++, i);
123
124         if (vColumns[vCol].align) {
125           vCell.setStyleProperty("textAlign",
126                                  vColumns[vCol].align);
127         }
128       }
129     }
130   }
131
132   // Sync cells: Remove existing ones and dispose them
133   else if (this._lastRowCount > vRowCount)
134   {
135     var vChildren = this.getChildren();
136     var vChildrenLength = vChildren.length - 1;
137
138     for (var i=this._lastRowCount; i>vRowCount; i--)
139     {
140       for (var vCol in vColumns)
141       {
142         vCell = vChildren[vChildrenLength--];
143         this.remove(vCell);
144         vCell.dispose();
145       }
146     }
147   }
148
149   // Update row and column count
150   this.setRowCount(vRowCount);
151   if (!vUpdate) {
152     this.setColumnCount(qx.lang.Object.getLength(vColumns));
153   }
154
155   // Apply height to all rows
156   for (var i=0; i<vRowCount; i++) {
157     this.setRowHeight(i, this._rowHeight);
158   }
159
160   if (!vUpdate)
161   {
162     // Apply width and alignment to all columns
163     var vCount = 0;
164     for (var vCol in vColumns)
165     {
166       this.setColumnHorizontalAlignment(vCount, vColumns[vCol].align);
167       this.setColumnWidth(vCount, vColumns[vCol].width);
168
169       vCount++;
170     }
171   }
172
173   // Store last row count
174   this._lastRowCount = vRowCount;
175 }
176
177 qx.Proto._currentScrollTop = -1;
178
179 qx.Proto._updateRendering = function(vForce)
180 {
181   if (this._updatingRendering) {
182     return;
183   }
184
185   var vScrollTop = (this._initialLayoutDone
186                     ? this.getView().getScroll().getScrollTop()
187                     : 0);
188
189   this._updatingRendering = true;
190   this._currentScrollTop = vScrollTop;
191
192   for (var i=0; i<this._rowCount; i++) {
193     this._updateRow(i);
194   }
195
196   delete this._updatingRendering;
197 }
198
199 qx.Proto._updateRow = function(vRelativeRow)
200 {
201   var vData = this._data;
202   var vRowOffset = Math.floor(this._currentScrollTop / this._rowHeight);
203
204   var vColumnCount = this.getColumnCount();
205   var vColumns = this._columns;
206
207   var vChildren = this.getVisibleChildren();
208   var vChild, vEntry, vCol;
209
210   var j=0;
211
212   for (vCol in vColumns)
213   {
214     vEntry = vData[vRowOffset+vRelativeRow];
215     vChild = vChildren[vColumnCount*vRelativeRow+(j++)];
216
217     if (vChild)
218     {
219       if (vEntry && vEntry._selected) {
220         vChild.addState("selected");
221       } else {
222         vChild.removeState("selected");
223       }
224       vChild.set(vEntry
225                  ? vEntry[vCol]
226                  : vColumns[vCol].empty || vColumns[vCol].contentClass.empty);
227     }
228   }
229 }
230
231 qx.Proto._onscroll = function(e) {
232   this._updateRendering();
233 }
234
235
236
237
238
239 /*
240 ---------------------------------------------------------------------------
241   DIMENSION CACHE
242 ---------------------------------------------------------------------------
243 */
244
245 qx.Proto._changeInnerHeight = function(vNew, vOld)
246 {
247   this._updateLayout(true);
248   this._updateRendering(true);
249
250   return qx.ui.layout.GridLayout.prototype._changeInnerHeight.call(this,
251                                                                    vNew,
252                                                                    vOld);
253 }
254
255
256
257
258
259
260 /*
261 ---------------------------------------------------------------------------
262   MANAGER BINDING
263 ---------------------------------------------------------------------------
264 */
265
266 qx.Proto.getManager = function() {
267   return this._manager;
268 }
269
270 qx.Proto.getListViewTarget = function(e)
271 {
272   var vEventTop = e.getPageY();
273   var vPaneTop = qx.dom.Location.getPageInnerTop(this.getElement());
274   var vItemNo = Math.floor(this._currentScrollTop / this._rowHeight) +
275                 Math.floor((vEventTop - vPaneTop) / this._rowHeight);
276
277   return this._data[vItemNo];
278 }
279
280 qx.Proto.getSelectedItem = function() {
281   return this.getSelectedItems()[0];
282 }
283
284 qx.Proto.getSelectedItems = function() {
285   return this._manager.getSelectedItems();
286 }
287
288 qx.Proto.getData = function() {
289   return this._data;
290 }
291
292 // use static row height
293 qx.Proto.getItemHeight = function(vItem) {
294   return this._rowHeight;
295 }
296
297 // use the full inner width of the pane
298 qx.Proto.getItemWidth = function(vItem) {
299   return qx.dom.Dimension.getInnerWidth(this.getElement());
300 }
301
302 qx.Proto.getItemLeft = function(vItem) {
303   return 0;
304 }
305
306 qx.Proto.getItemTop = function(vItem) {
307   return this._data.indexOf(vItem) * this._rowHeight;
308 }
309
310
311
312
313 /*
314 ---------------------------------------------------------------------------
315   MOUSE EVENT HANDLER
316 ---------------------------------------------------------------------------
317 */
318
319 qx.Proto._onmousewheel = function(e)
320 {
321   var vScroll = this.getView().getScroll();
322   vScroll.setScrollTop(vScroll.getScrollTop() - (e.getWheelDelta() * 20));
323 }
324
325 qx.Proto._onmouseover = function(e)
326 {
327   var vTarget = this.getListViewTarget(e);
328   if (vTarget) {
329     this._manager.handleMouseOver(vTarget, e);
330   }
331 }
332
333 qx.Proto._onmousedown = function(e)
334 {
335   var vTarget = this.getListViewTarget(e);
336   if (vTarget) {
337     this._manager.handleMouseDown(vTarget, e);
338   }
339 }
340
341 qx.Proto._onmouseup = function(e)
342 {
343   var vTarget = this.getListViewTarget(e);
344   if (vTarget) {
345     this._manager.handleMouseUp(vTarget, e);
346   }
347 }
348
349 qx.Proto._onclick = function(e)
350 {
351   var vTarget = this.getListViewTarget(e);
352   if (vTarget) {
353     this._manager.handleClick(vTarget, e);
354   }
355 }
356
357 qx.Proto._ondblclick = function(e)
358 {
359   var vTarget = this.getListViewTarget(e);
360   if (vTarget) {
361     this._manager.handleDblClick(vTarget, e);
362   }
363 }
364
365
366
367
368
369
370 /*
371 ---------------------------------------------------------------------------
372   KEY EVENT HANDLER
373 ---------------------------------------------------------------------------
374 */
375
376 qx.Proto._onkeypress = function(e)
377 {
378   this._manager.handleKeyPress(e);
379   e.preventDefault();
380 }
381
382
383
384
385
386
387 /*
388 ---------------------------------------------------------------------------
389   MANAGER SELECTION
390 ---------------------------------------------------------------------------
391 */
392
393 qx.Proto._updateSelectionState = function(vItem, vIsSelected)
394 {
395   vItem._selected = vIsSelected;
396   this._updateItem(vItem);
397 }
398
399 qx.Proto._updateAnchorState = function(vItem, vIsAnchor)
400 {
401   vItem._anchor = vIsAnchor;
402   this._updateItem(vItem);
403 }
404
405 qx.Proto._updateLeadState = function(vItem, vIsLead)
406 {
407   vItem._lead = vIsLead;
408   this._updateItem(vItem);
409 }
410
411 qx.Proto.scrollItemIntoView = function(vItem, vAlignLeftTop)
412 {
413   this.scrollItemIntoViewX(vItem, vAlignLeftTop);
414   this.scrollItemIntoViewY(vItem, vAlignLeftTop);
415 }
416
417 qx.Proto.scrollItemIntoViewX = function(vItem, vAlignLeft) {
418   // this.error("Not implemented in qx.ui.listview.ListViewPane!");
419 }
420
421 qx.Proto.scrollItemIntoViewY = function(vItem, vAlignTop)
422 {
423   var vItems = this._data;
424   var vOffset = vItems.indexOf(vItem) * this._rowHeight;
425   var vHeight = this._rowHeight;
426
427   // normalize client height (we want that the item is fully visible)
428   var vParentHeight = (Math.floor(this.getClientHeight() / this._rowHeight) *
429                        this._rowHeight);
430   var vParentScrollTop = this._currentScrollTop;
431
432   var vNewScrollTop = null;
433
434   if (vAlignTop)
435   {
436     vNewScrollTop = vOffset;
437   }
438   else if (vAlignTop == false)
439   {
440     vNewScrollTop = vOffset + vHeight - vParentHeight;
441   }
442   else if (vHeight > vParentHeight || vOffset < vParentScrollTop)
443   {
444     vNewScrollTop = vOffset;
445   }
446   else if ((vOffset + vHeight) > (vParentScrollTop + vParentHeight))
447   {
448     vNewScrollTop = vOffset + vHeight - vParentHeight;
449   }
450
451   if (vNewScrollTop != null) {
452     this.getView().getScroll().setScrollTop(vNewScrollTop);
453   }
454 }
455
456 qx.Proto.setScrollTop = function(vScrollTop)
457 {
458   this.getView().getScroll().setScrollTop(vScrollTop);
459   this._updateRendering();
460 }
461
462 qx.Proto.getScrollTop = function() {
463   return this._currentScrollTop;
464 }
465
466 qx.Proto.setScrollLeft = function() {
467   this.error("Not implemented in qx.ui.listview.ListViewPane!");
468 }
469
470 qx.Proto.getScrollLeft = function() {
471   return 0;
472 }
473
474 qx.Proto.isItemVisible = function(vItem)
475 {
476   var vIndex = this._data.indexOf(vItem);
477   var vRowStart = Math.floor(this._currentScrollTop / this._rowHeight);
478   var vRowLength = Math.ceil(this.getClientHeight() / this._rowHeight);
479
480   return vIndex >= vRowStart && vIndex <= (vRowStart + vRowLength);
481 }
482
483 qx.Proto.getRelativeItemPosition = function(vItem)
484 {
485   var vIndex = this._data.indexOf(vItem);
486   var vRowStart = Math.floor(this._currentScrollTop / this._rowHeight);
487
488   return vIndex - vRowStart;
489 }
490
491 qx.Proto._updateItem = function(vItem)
492 {
493   var vIndex = this._data.indexOf(vItem);
494   var vRowStart = Math.floor(this._currentScrollTop / this._rowHeight);
495   var vRowLength = Math.ceil(this.getClientHeight() / this._rowHeight);
496
497   if (vIndex < vRowStart || vIndex > (vRowStart + vRowLength)) {
498     return;
499   }
500
501   this._updateRow(vIndex - vRowStart);
502 }
503
504
505
506
507
508
509 /*
510 ---------------------------------------------------------------------------
511   DISPOSER
512 ---------------------------------------------------------------------------
513 */
514
515 qx.Proto.dispose = function()
516 {
517   if (this.getDisposed()) {
518     return;
519   }
520
521
522   // ************************************************************************
523   //   MOUSE EVENT LISTENER
524   // ************************************************************************
525   this.removeEventListener("mousewheel", this._onmousewheel);
526   this.removeEventListener("mouseover", this._onmouseover);
527   this.removeEventListener("mousedown", this._onmousedown);
528   this.removeEventListener("mouseup", this._onmouseup);
529   this.removeEventListener("click", this._onclick);
530   this.removeEventListener("dblclick", this._ondblclick);
531
532
533   // ************************************************************************
534   //   KEY EVENT LISTENER
535   // ************************************************************************
536   this.removeEventListener("keypress", this._onkeypress);
537
538
539   // ************************************************************************
540   //   DATA
541   // ************************************************************************
542   delete this._data;
543   delete this._columns;
544
545
546   // ************************************************************************
547   //   MANAGER
548   // ************************************************************************
549   if (this._manager)
550   {
551     this._manager.dispose();
552     this._manager = null;
553   }
554
555   return qx.ui.layout.GridLayout.prototype.dispose.call(this);
556 }