1 /* ************************************************************************
3 qooxdoo - the new era of web development
8 2006 STZ-IDA, Germany, http://www.stz-ida.de
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.
16 * Til Schneider (til132)
18 ************************************************************************ */
20 /* ************************************************************************
24 ************************************************************************ */
27 * Shows a whole meta column. This includes a {@link TablePaneHeader},
28 * a {@link TablePane} and the needed scroll bars. This class handles the
29 * virtual scrolling and does all the mouse event handling.
31 * @param table {Table} the table the scroller belongs to.
33 qx.OO.defineClass("qx.ui.table.TablePaneScroller", qx.ui.layout.VerticalBoxLayout,
35 qx.ui.layout.VerticalBoxLayout.call(this);
40 this._verScrollBar = new qx.ui.core.ScrollBar(false);
41 this._horScrollBar = new qx.ui.core.ScrollBar(true);
43 var scrollBarWidth = this._verScrollBar.getPreferredBoxWidth();
45 this._verScrollBar.setWidth("auto");
46 this._horScrollBar.setHeight("auto");
47 this._horScrollBar.setPaddingRight(scrollBarWidth);
48 //this._verScrollBar.setMergeEvents(true);
50 this._horScrollBar.addEventListener("changeValue", this._onScrollX, this);
51 this._verScrollBar.addEventListener("changeValue", this._onScrollY, this);
54 this._header = this.getTable().getNewTablePaneHeader()(this);
55 this._header.set({ width:"auto", height:"auto" });
57 this._headerClipper = new qx.ui.layout.CanvasLayout;
58 this._headerClipper.setDimension("1*", "auto");
59 this._headerClipper.setOverflow("hidden");
60 this._headerClipper.add(this._header);
62 this._spacer = new qx.ui.basic.Terminator;
63 this._spacer.setWidth(scrollBarWidth);
65 this._top = new qx.ui.layout.HorizontalBoxLayout;
66 this._top.setHeight("auto");
67 this._top.add(this._headerClipper, this._spacer);
70 this._tablePane = this.getTable().getNewTablePane()(this);
71 this._tablePane.set({ width:"auto", height:"auto" });
73 this._focusIndicator = new qx.ui.layout.HorizontalBoxLayout;
74 this._focusIndicator.setAppearance("table-focus-indicator");
75 this._focusIndicator.hide();
77 // Workaround: If the _focusIndicator has no content if always gets a too
79 var dummyContent = new qx.ui.basic.Terminator;
80 dummyContent.setWidth(0);
81 this._focusIndicator.add(dummyContent);
83 this._paneClipper = new qx.ui.layout.CanvasLayout;
84 this._paneClipper.setWidth("1*");
85 this._paneClipper.setOverflow("hidden");
86 this._paneClipper.add(this._tablePane, this._focusIndicator);
87 this._paneClipper.addEventListener("mousewheel", this._onmousewheel, this);
89 // add all child widgets
90 var scrollerBody = new qx.ui.layout.HorizontalBoxLayout;
91 scrollerBody.setHeight("1*");
92 scrollerBody.add(this._paneClipper, this._verScrollBar);
94 this.add(this._top, scrollerBody, this._horScrollBar);
96 // init event handlers
97 this.addEventListener("mousemove", this._onmousemove, this);
98 this.addEventListener("mousedown", this._onmousedown, this);
99 this.addEventListener("mouseup", this._onmouseup, this);
100 this.addEventListener("click", this._onclick, this);
101 this.addEventListener("dblclick", this._ondblclick, this);
102 this.addEventListener("mouseout", this._onmouseout, this);
105 /** Whether to show the horizontal scroll bar */
106 qx.OO.addProperty({ name:"horizontalScrollBarVisible", type:"boolean", defaultValue:true });
108 /** Whether to show the vertical scroll bar */
109 qx.OO.addProperty({ name:"verticalScrollBarVisible", type:"boolean", defaultValue:true });
111 /** The table pane model. */
112 qx.OO.addProperty({ name:"tablePaneModel", type:"object", instance:"qx.ui.table.TablePaneModel" });
114 /** The current position of the the horizontal scroll bar. */
115 qx.OO.addProperty({ name:"scrollX", type:"number", allowNull:false, defaultValue:0 });
117 /** The current position of the the vertical scroll bar. */
118 qx.OO.addProperty({ name:"scrollY", type:"number", allowNull:false, defaultValue:0 });
121 * Whether column resize should be live. If false, during resize only a line is
122 * shown and the real resize happens when the user releases the mouse button.
124 qx.OO.addProperty({ name:"liveResize", type:"boolean", defaultValue:false });
127 * Whether the focus should moved when the mouse is moved over a cell. If false
128 * the focus is only moved on mouse clicks.
130 qx.OO.addProperty({ name:"focusCellOnMouseMove", type:"boolean", defaultValue:false });
133 * Whether to handle selections via the selection manager before setting the
134 * focus. The traditional behavior is to handle selections after setting the
135 * focus, but setting the focus means redrawing portions of the table, and
136 * some subclasses may want to modify the data to be displayed based on the
139 qx.OO.addProperty({ name:"selectBeforeFocus", type:"boolean", defaultValue:false });
143 qx.Proto._modifyHorizontalScrollBarVisible = function(propValue, propOldValue, propData) {
144 // Workaround: We can't use setDisplay, because the scroll bar needs its
145 // correct height in order to check its value. When using
146 // setDisplay(false) the height isn't relayouted any more
148 this._horScrollBar.setHeight("auto");
150 this._horScrollBar.setHeight(0);
152 this._horScrollBar.setVisibility(propValue);
154 // NOTE: We have to flush the queues before updating the content so the new
155 // layout has been applied and _updateContent is able to work with
157 qx.ui.core.Widget.flushGlobalQueues();
158 this._updateContent();
165 qx.Proto._modifyVerticalScrollBarVisible = function(propValue, propOldValue, propData) {
166 // Workaround: See _modifyHorizontalScrollBarVisible
168 this._verScrollBar.setWidth("auto");
170 this._verScrollBar.setWidth(0);
172 this._verScrollBar.setVisibility(propValue);
174 var scrollBarWidth = propValue ? this._verScrollBar.getPreferredBoxWidth() : 0;
175 this._horScrollBar.setPaddingRight(scrollBarWidth);
176 this._spacer.setWidth(scrollBarWidth);
183 qx.Proto._modifyTablePaneModel = function(propValue, propOldValue, propData) {
184 if (propOldValue != null) {
185 propOldValue.removeEventListener("modelChanged", this._onPaneModelChanged, this);
187 propValue.addEventListener("modelChanged", this._onPaneModelChanged, this);
194 qx.Proto._modifyScrollX = function(propValue, propOldValue, propData) {
195 this._horScrollBar.setValue(propValue);
201 qx.Proto._modifyScrollY = function(propValue, propOldValue, propData) {
202 this._verScrollBar.setValue(propValue);
208 * Returns the table this scroller belongs to.
210 * @return {Table} the table.
212 qx.Proto.getTable = function() {
218 * Event handler. Called when the visibility of a column has changed.
220 * @param evt {Map} the event.
222 qx.Proto._onColVisibilityChanged = function(evt) {
223 this._updateHorScrollBarMaximum();
224 this._updateFocusIndicator();
229 * Event handler. Called when the width of a column has changed.
231 * @param evt {Map} the event.
233 qx.Proto._onColWidthChanged = function(evt) {
234 this._header._onColWidthChanged(evt);
235 this._tablePane._onColWidthChanged(evt);
237 var data = evt.getData();
238 var paneModel = this.getTablePaneModel();
239 var x = paneModel.getX(data.col);
241 // The change was in this scroller
242 this._updateHorScrollBarMaximum();
243 this._updateFocusIndicator();
249 * Event handler. Called when the column order has changed.
251 * @param evt {Map} the event.
253 qx.Proto._onColOrderChanged = function(evt) {
254 this._header._onColOrderChanged(evt);
255 this._tablePane._onColOrderChanged(evt);
257 this._updateHorScrollBarMaximum();
262 * Event handler. Called when the table model has changed.
264 * @param evt {Map} the event.
266 qx.Proto._onTableModelDataChanged = function(evt) {
267 this._tablePane._onTableModelDataChanged(evt);
269 var rowCount = this.getTable().getTableModel().getRowCount();
270 if (rowCount != this._lastRowCount) {
271 this._lastRowCount = rowCount;
273 this._updateVerScrollBarMaximum();
274 if (this.getFocusedRow() >= rowCount) {
276 this.setFocusedCell(null, null);
278 this.setFocusedCell(this.getFocusedColumn(), rowCount - 1);
286 * Event handler. Called when the selection has changed.
288 * @param evt {Map} the event.
290 qx.Proto._onSelectionChanged = function(evt) {
291 this._tablePane._onSelectionChanged(evt);
296 * Event handler. Called when the table gets or looses the focus.
298 * @param evt {Map} the event.
300 qx.Proto._onFocusChanged = function(evt) {
301 this._focusIndicator.setState("tableHasFocus", this.getTable().getFocused());
303 this._tablePane._onFocusChanged(evt);
308 * Event handler. Called when the table model meta data has changed.
310 * @param evt {Map} the event.
312 qx.Proto._onTableModelMetaDataChanged = function(evt) {
313 this._header._onTableModelMetaDataChanged(evt);
314 this._tablePane._onTableModelMetaDataChanged(evt);
319 * Event handler. Called when the pane model has changed.
321 * @param evt {Map} the event.
323 qx.Proto._onPaneModelChanged = function(evt) {
324 this._header._onPaneModelChanged(evt);
325 this._tablePane._onPaneModelChanged(evt);
330 * Updates the maximum of the horizontal scroll bar, so it corresponds to the
331 * total width of the columns in the table pane.
333 qx.Proto._updateHorScrollBarMaximum = function() {
334 this._horScrollBar.setMaximum(this.getTablePaneModel().getTotalWidth());
339 * Updates the maximum of the vertical scroll bar, so it corresponds to the
340 * number of rows in the table.
342 qx.Proto._updateVerScrollBarMaximum = function() {
343 var rowCount = this.getTable().getTableModel().getRowCount();
344 var rowHeight = this.getTable().getRowHeight();
346 if (this.getTable().getKeepFirstVisibleRowComplete()) {
347 this._verScrollBar.setMaximum((rowCount + 1) * rowHeight);
349 this._verScrollBar.setMaximum(rowCount * rowHeight);
355 * Event handler. Called when the table property "keepFirstVisibleRowComplete"
358 qx.Proto._onKeepFirstVisibleRowCompleteChanged = function() {
359 this._updateVerScrollBarMaximum();
360 this._updateContent();
365 qx.Proto._changeInnerHeight = function(newValue, oldValue) {
366 // The height has changed -> Update content
367 this._postponedUpdateContent();
369 return qx.ui.layout.VerticalBoxLayout.prototype._changeInnerHeight.call(this, newValue, oldValue);
374 qx.Proto._afterAppear = function() {
375 qx.ui.layout.VerticalBoxLayout.prototype._afterAppear.call(this);
378 this.getElement().onselectstart = qx.lang.Function.returnFalse;
380 this._updateContent();
381 this._header._updateContent();
382 this._updateHorScrollBarMaximum();
383 this._updateVerScrollBarMaximum();
388 * Event handler. Called when the horizontal scroll bar moved.
390 * @param evt {Map} the event.
392 qx.Proto._onScrollX = function(evt) {
393 // Workaround: See _updateContent
394 this._header.setLeft(-evt.getData());
396 this._paneClipper.setScrollLeft(evt.getData());
397 this.setScrollX(evt.getData());
402 * Event handler. Called when the vertical scroll bar moved.
404 * @param evt {Map} the event.
406 qx.Proto._onScrollY = function(evt) {
407 this._postponedUpdateContent();
408 this.setScrollY(evt.getData());
413 * Event handler. Called when the user moved the mouse wheel.
415 * @param evt {Map} the event.
417 qx.Proto._onmousewheel = function(evt) {
418 var table = this.getTable();
420 if (! table.getEnabled()) {
424 this._verScrollBar.setValue(this._verScrollBar.getValue()
425 - evt.getWheelDelta() * table.getRowHeight());
428 if (this._lastMousePageX && this.getFocusCellOnMouseMove()) {
429 this._focusCellAtPagePos(this._lastMousePageX, this._lastMousePageY);
435 * Event handler. Called when the user moved the mouse.
437 * @param evt {Map} the event.
439 qx.Proto._onmousemove = function(evt) {
440 var table = this.getTable();
442 if (! table.getEnabled()) {
446 var tableModel = table.getTableModel();
447 var columnModel = table.getTableColumnModel();
449 var useResizeCursor = false;
450 var mouseOverColumn = null;
452 var pageX = evt.getPageX();
453 var pageY = evt.getPageY();
455 // Workaround: In onmousewheel the event has wrong coordinates for pageX
456 // and pageY. So we remember the last move event.
457 this._lastMousePageX = pageX;
458 this._lastMousePageY = pageY;
460 if (this._resizeColumn != null) {
461 // We are currently resizing -> Update the position
462 var minColumnWidth = qx.ui.table.TablePaneScroller.MIN_COLUMN_WIDTH;
463 var newWidth = Math.max(minColumnWidth, this._lastResizeWidth + pageX - this._lastResizeMousePageX);
465 if (this.getLiveResize()) {
466 columnModel.setColumnWidth(this._resizeColumn, newWidth);
468 this._header.setColumnWidth(this._resizeColumn, newWidth);
470 var paneModel = this.getTablePaneModel();
471 this._showResizeLine(paneModel.getColumnLeft(this._resizeColumn) + newWidth);
474 useResizeCursor = true;
475 this._lastResizeMousePageX += newWidth - this._lastResizeWidth;
476 this._lastResizeWidth = newWidth;
477 } else if (this._moveColumn != null) {
478 // We are moving a column
480 // Check whether we moved outside the click tolerance so we can start
481 // showing the column move feedback
482 // (showing the column move feedback prevents the onclick event)
483 var clickTolerance = qx.ui.table.TablePaneScroller.CLICK_TOLERANCE;
484 if (this._header.isShowingColumnMoveFeedback()
485 || pageX > this._lastMoveMousePageX + clickTolerance
486 || pageX < this._lastMoveMousePageX - clickTolerance)
488 this._lastMoveColPos += pageX - this._lastMoveMousePageX;
490 this._header.showColumnMoveFeedback(this._moveColumn, this._lastMoveColPos);
492 // Get the responsible scroller
493 var targetScroller = this._table.getTablePaneScrollerAtPageX(pageX);
494 if (this._lastMoveTargetScroller && this._lastMoveTargetScroller != targetScroller) {
495 this._lastMoveTargetScroller.hideColumnMoveFeedback();
497 if (targetScroller != null) {
498 this._lastMoveTargetX = targetScroller.showColumnMoveFeedback(pageX);
500 this._lastMoveTargetX = null;
503 this._lastMoveTargetScroller = targetScroller;
504 this._lastMoveMousePageX = pageX;
507 // This is a normal mouse move
508 var row = this._getRowForPagePos(pageX, pageY);
510 // The mouse is over the header
511 var resizeCol = this._getResizeColumnForPageX(pageX);
512 if (resizeCol != -1) {
513 // The mouse is over a resize region -> Show the right cursor
514 useResizeCursor = true;
516 var col = this._getColumnForPageX(pageX);
517 if (col != null && tableModel.isColumnSortable(col)) {
518 mouseOverColumn = col;
521 } else if (row != null) {
522 // The mouse is over the data -> update the focus
523 if (this.getFocusCellOnMouseMove()) {
524 this._focusCellAtPagePos(pageX, pageY);
529 // Workaround: Setting the cursor to the right widget doesn't work
530 //this._header.setCursor(useResizeCursor ? "e-resize" : null);
531 this.getTopLevelWidget().setGlobalCursor(useResizeCursor ? qx.ui.table.TablePaneScroller.CURSOR_RESIZE_HORIZONTAL : null);
533 this._header.setMouseOverColumn(mouseOverColumn);
538 * Event handler. Called when the user pressed a mouse button.
540 * @param evt {Map} the event.
542 qx.Proto._onmousedown = function(evt) {
543 var table = this.getTable();
545 if (! table.getEnabled()) {
549 var tableModel = table.getTableModel();
550 var columnModel = table.getTableColumnModel();
552 var pageX = evt.getPageX();
553 var pageY = evt.getPageY();
554 var row = this._getRowForPagePos(pageX, pageY);
556 // mouse is in header
557 var resizeCol = this._getResizeColumnForPageX(pageX);
558 if (resizeCol != -1) {
559 // The mouse is over a resize region -> Start resizing
560 this._resizeColumn = resizeCol;
561 this._lastResizeMousePageX = pageX;
562 this._lastResizeWidth = columnModel.getColumnWidth(this._resizeColumn);
563 this.setCapture(true);
565 // The mouse is not in a resize region
566 var col = this._getColumnForPageX(pageX);
568 // Prepare column moving
569 this._moveColumn = col;
570 this._lastMoveMousePageX = pageX;
571 this._lastMoveColPos = this.getTablePaneModel().getColumnLeft(col);
572 this.setCapture(true);
575 } else if (row != null) {
576 var selectBeforeFocus = this.getSelectBeforeFocus();
578 if (selectBeforeFocus) {
579 table._getSelectionManager().handleMouseDown(row, evt);
582 // The mouse is over the data -> update the focus
583 if (! this.getFocusCellOnMouseMove()) {
584 this._focusCellAtPagePos(pageX, pageY);
587 if (! selectBeforeFocus) {
588 table._getSelectionManager().handleMouseDown(row, evt);
595 * Event handler. Called when the user released a mouse button.
597 * @param evt {Map} the event.
599 qx.Proto._onmouseup = function(evt) {
600 var table = this.getTable();
602 if (! table.getEnabled()) {
606 var columnModel = table.getTableColumnModel();
607 var paneModel = this.getTablePaneModel();
609 if (this._resizeColumn != null) {
610 // We are currently resizing -> Finish resizing
611 if (! this.getLiveResize()) {
612 this._hideResizeLine();
613 columnModel.setColumnWidth(this._resizeColumn, this._lastResizeWidth);
616 this._resizeColumn = null;
617 this.setCapture(false);
619 this.getTopLevelWidget().setGlobalCursor(null);
620 } else if (this._moveColumn != null) {
621 // We are moving a column -> Drop the column
622 this._header.hideColumnMoveFeedback();
623 if (this._lastMoveTargetScroller) {
624 this._lastMoveTargetScroller.hideColumnMoveFeedback();
627 if (this._lastMoveTargetX != null) {
628 var fromVisXPos = paneModel.getFirstColumnX() + paneModel.getX(this._moveColumn);
629 var toVisXPos = this._lastMoveTargetX;
630 if (toVisXPos != fromVisXPos && toVisXPos != fromVisXPos + 1) {
631 // The column was really moved to another position
632 // (and not moved before or after itself, which is a noop)
634 // Translate visible positions to overall positions
635 var fromCol = columnModel.getVisibleColumnAtX(fromVisXPos);
636 var toCol = columnModel.getVisibleColumnAtX(toVisXPos);
637 var fromOverXPos = columnModel.getOverallX(fromCol);
638 var toOverXPos = (toCol != null) ? columnModel.getOverallX(toCol) : columnModel.getOverallColumnCount();
640 if (toOverXPos > fromOverXPos) {
641 // Don't count the column itself
646 columnModel.moveColumn(fromOverXPos, toOverXPos);
650 this._moveColumn = null;
651 this._lastMoveTargetX = null;
652 this.setCapture(false);
654 // This is a normal mouse up
655 var row = this._getRowForPagePos(evt.getPageX(), evt.getPageY());
656 if (row != -1 && row != null) {
657 table._getSelectionManager().handleMouseUp(row, evt);
664 * Event handler. Called when the user clicked a mouse button.
666 * @param evt {Map} the event.
668 qx.Proto._onclick = function(evt) {
669 var table = this.getTable();
671 if (! table.getEnabled()) {
675 var tableModel = table.getTableModel();
677 var pageX = evt.getPageX();
678 var pageY = evt.getPageY();
679 var row = this._getRowForPagePos(pageX, pageY);
681 // mouse is in header
682 var resizeCol = this._getResizeColumnForPageX(pageX);
683 if (resizeCol == -1) {
684 // mouse is not in a resize region
685 var col = this._getColumnForPageX(pageX);
686 if (col != null && tableModel.isColumnSortable(col)) {
688 var sortCol = tableModel.getSortColumnIndex();
689 var ascending = (col != sortCol) ? true : !tableModel.isSortAscending();
691 tableModel.sortByColumn(col, ascending);
692 table.getSelectionModel().clearSelection();
695 } else if (row != null) {
696 table._getSelectionManager().handleClick(row, evt);
702 * Event handler. Called when the user double clicked a mouse button.
704 * @param evt {Map} the event.
706 qx.Proto._ondblclick = function(evt) {
707 if (! this.isEditing()) {
708 this._focusCellAtPagePos(evt.getPageX(), evt.getPageY());
715 * Event handler. Called when the mouse moved out.
717 * @param evt {Map} the event.
719 qx.Proto._onmouseout = function(evt) {
720 var table = this.getTable();
722 if (! table.getEnabled()) {
727 // Workaround: See _onmousemove
728 this._lastMousePageX = null;
729 this._lastMousePageY = null;
732 // Reset the resize cursor when the mouse leaves the header
733 // If currently a column is resized then do nothing
734 // (the cursor will be reset on mouseup)
735 if (this._resizeColumn == null) {
736 this.getTopLevelWidget().setGlobalCursor(null);
739 this._header.setMouseOverColumn(null);
744 * Shows the resize line.
746 * @param x {Integer} the position where to show the line (in pixels, relative to
747 * the left side of the pane).
749 qx.Proto._showResizeLine = function(x) {
750 var resizeLine = this._resizeLine;
751 if (resizeLine == null) {
752 resizeLine = new qx.ui.basic.Terminator;
753 resizeLine.setBackgroundColor("#D6D5D9");
754 resizeLine.setWidth(3);
755 this._paneClipper.add(resizeLine);
756 qx.ui.core.Widget.flushGlobalQueues();
758 this._resizeLine = resizeLine;
761 resizeLine._applyRuntimeLeft(x - 2); // -1 for the width
762 resizeLine._applyRuntimeHeight(this._paneClipper.getBoxHeight() + this._paneClipper.getScrollTop());
764 this._resizeLine.removeStyleProperty("visibility");
769 * Hides the resize line.
771 qx.Proto._hideResizeLine = function() {
772 this._resizeLine.setStyleProperty("visibility", "hidden");
777 * Shows the feedback shown while a column is moved by the user.
779 * @param pageX {Integer} the x position of the mouse in the page (in pixels).
780 * @return {Integer} the visible x position of the column in the whole table.
782 qx.Proto.showColumnMoveFeedback = function(pageX) {
783 var paneModel = this.getTablePaneModel();
784 var columnModel = this.getTable().getTableColumnModel();
785 var paneLeftX = qx.html.Location.getClientBoxLeft(this._tablePane.getElement());
786 var colCount = paneModel.getColumnCount();
790 var currX = paneLeftX;
791 for (var xPos = 0; xPos < colCount; xPos++) {
792 var col = paneModel.getColumnAtX(xPos);
793 var colWidth = columnModel.getColumnWidth(col);
795 if (pageX < currX + colWidth / 2) {
800 targetXPos = xPos + 1;
801 targetX = currX - paneLeftX;
804 // Ensure targetX is visible
805 var clipperLeftX = qx.html.Location.getClientBoxLeft(this._paneClipper.getElement());
806 var clipperWidth = this._paneClipper.getBoxWidth();
807 var scrollX = clipperLeftX - paneLeftX;
808 // NOTE: +2/-1 because of feedback width
809 targetX = qx.lang.Number.limit(targetX, scrollX + 2, scrollX + clipperWidth - 1);
811 this._showResizeLine(targetX);
813 // Return the overall target x position
814 return paneModel.getFirstColumnX() + targetXPos;
819 * Hides the feedback shown while a column is moved by the user.
821 qx.Proto.hideColumnMoveFeedback = function() {
822 this._hideResizeLine();
827 * Sets the focus to the cell that's located at the page position
828 * <code>pageX</code>/<code>pageY</code>. If there is no cell at that position,
831 * @param pageX {Integer} the x position in the page (in pixels).
832 * @param pageY {Integer} the y position in the page (in pixels).
834 qx.Proto._focusCellAtPagePos = function(pageX, pageY) {
835 var row = this._getRowForPagePos(pageX, pageY);
836 if (row != -1 && row != null) {
837 // The mouse is over the data -> update the focus
838 var col = this._getColumnForPageX(pageX);
840 this._table.setFocusedCell(col, row);
847 * Sets the currently focused cell.
849 * @param col {Integer} the model index of the focused cell's column.
850 * @param row {Integer} the model index of the focused cell's row.
852 qx.Proto.setFocusedCell = function(col, row) {
853 if (!this.isEditing()) {
854 this._tablePane.setFocusedCell(col, row, this._updateContentPlanned);
856 this._focusedCol = col;
857 this._focusedRow = row;
859 // Move the focus indicator
860 if (! this._updateContentPlanned) {
861 this._updateFocusIndicator();
868 * Returns the column of currently focused cell.
870 * @return {Integer} the model index of the focused cell's column.
872 qx.Proto.getFocusedColumn = function() {
873 return this._focusedCol;
878 * Returns the row of currently focused cell.
880 * @return {Integer} the model index of the focused cell's column.
882 qx.Proto.getFocusedRow = function() {
883 return this._focusedRow;
888 * Scrolls a cell visible.
890 * @param col {Integer} the model index of the column the cell belongs to.
891 * @param row {Integer} the model index of the row the cell belongs to.
893 qx.Proto.scrollCellVisible = function(col, row) {
894 var paneModel = this.getTablePaneModel();
895 var xPos = paneModel.getX(col);
898 var columnModel = this.getTable().getTableColumnModel();
900 var colLeft = paneModel.getColumnLeft(col);
901 var colWidth = columnModel.getColumnWidth(col);
902 var rowHeight = this.getTable().getRowHeight();
903 var rowTop = row * rowHeight;
905 var scrollX = this.getScrollX();
906 var scrollY = this.getScrollY();
907 var viewWidth = this._paneClipper.getBoxWidth();
908 var viewHeight = this._paneClipper.getBoxHeight();
910 // NOTE: We don't use qx.lang.Number.limit, because min should win if max < min
911 var minScrollX = Math.min(colLeft, colLeft + colWidth - viewWidth);
912 var maxScrollX = colLeft;
913 this.setScrollX(Math.max(minScrollX, Math.min(maxScrollX, scrollX)));
915 var minScrollY = rowTop + rowHeight - viewHeight;
916 if (this.getTable().getKeepFirstVisibleRowComplete()) {
917 minScrollY += rowHeight - 1;
919 var maxScrollY = rowTop;
920 this.setScrollY(Math.max(minScrollY, Math.min(maxScrollY, scrollY)));
926 * Returns whether currently a cell is editing.
928 * @return whether currently a cell is editing.
930 qx.Proto.isEditing = function() {
931 return this._cellEditor != null;
936 * Starts editing the currently focused cell. Does nothing if already editing
937 * or if the column is not editable.
939 * @return {Boolean} whether editing was started
941 qx.Proto.startEditing = function() {
942 var tableModel = this.getTable().getTableModel();
943 var col = this._focusedCol;
945 if (!this.isEditing() && (col != null) && tableModel.isColumnEditable(col)) {
946 var row = this._focusedRow;
947 var xPos = this.getTablePaneModel().getX(col);
948 var value = tableModel.getValue(col, row);
950 this._cellEditorFactory = this.getTable().getTableColumnModel().getCellEditorFactory(col);
951 var cellInfo = { col:col, row:row, xPos:xPos, value:value }
952 this._cellEditor = this._cellEditorFactory.createCellEditor(cellInfo);
953 this._cellEditor.set({ width:"100%", height:"100%" });
955 this._focusIndicator.add(this._cellEditor);
956 this._focusIndicator.addState("editing");
958 this._cellEditor.addEventListener("changeFocused", this._onCellEditorFocusChanged, this);
960 // Workaround: Calling focus() directly has no effect
961 var editor = this._cellEditor;
962 window.setTimeout(function() {
974 * Stops editing and writes the editor's value to the model.
976 qx.Proto.stopEditing = function() {
978 this.cancelEditing();
983 * Writes the editor's value to the model.
985 qx.Proto.flushEditor = function() {
986 if (this.isEditing()) {
987 var value = this._cellEditorFactory.getCellEditorValue(this._cellEditor);
988 this.getTable().getTableModel().setValue(this._focusedCol, this._focusedRow, value);
996 * Stops editing without writing the editor's value to the model.
998 qx.Proto.cancelEditing = function() {
999 if (this.isEditing()) {
1000 this._focusIndicator.remove(this._cellEditor);
1001 this._focusIndicator.removeState("editing");
1002 this._cellEditor.dispose();
1004 this._cellEditor.removeEventListener("changeFocused", this._onCellEditorFocusChanged, this);
1005 this._cellEditor = null;
1006 this._cellEditorFactory = null;
1012 * Event handler. Called when the focused state of the cell editor changed.
1014 * @param evt {Map} the event.
1016 qx.Proto._onCellEditorFocusChanged = function(evt) {
1017 if (!this._cellEditor.getFocused()) {
1024 * Returns the model index of the column the mouse is over or null if the mouse
1025 * is not over a column.
1027 * @param pageX {Integer} the x position of the mouse in the page (in pixels).
1028 * @return {Integer} the model index of the column the mouse is over.
1030 qx.Proto._getColumnForPageX = function(pageX) {
1031 var headerLeftX = qx.html.Location.getClientBoxLeft(this._header.getElement());
1033 var columnModel = this.getTable().getTableColumnModel();
1034 var paneModel = this.getTablePaneModel();
1035 var colCount = paneModel.getColumnCount();
1036 var currX = headerLeftX;
1037 for (var x = 0; x < colCount; x++) {
1038 var col = paneModel.getColumnAtX(x);
1039 var colWidth = columnModel.getColumnWidth(col);
1042 if (pageX < currX) {
1052 * Returns the model index of the column that should be resized when dragging
1053 * starts here. Returns -1 if the mouse is in no resize region of any column.
1055 * @param pageX {Integer} the x position of the mouse in the page (in pixels).
1056 * @return {Integer} the column index.
1058 qx.Proto._getResizeColumnForPageX = function(pageX) {
1059 var headerLeftX = qx.html.Location.getClientBoxLeft(this._header.getElement());
1061 var columnModel = this.getTable().getTableColumnModel();
1062 var paneModel = this.getTablePaneModel();
1063 var colCount = paneModel.getColumnCount();
1064 var currX = headerLeftX;
1065 var regionRadius = qx.ui.table.TablePaneScroller.RESIZE_REGION_RADIUS;
1066 for (var x = 0; x < colCount; x++) {
1067 var col = paneModel.getColumnAtX(x);
1068 var colWidth = columnModel.getColumnWidth(col);
1071 if (pageX >= (currX - regionRadius) && pageX <= (currX + regionRadius)) {
1081 * Returns the model index of the row the mouse is currently over. Returns -1 if
1082 * the mouse is over the header. Returns null if the mouse is not over any
1085 * @param pageX {Integer} the mouse x position in the page.
1086 * @param pageY {Integer} the mouse y position in the page.
1087 * @return {Integer} the model index of the row the mouse is currently over.
1089 qx.Proto._getRowForPagePos = function(pageX, pageY) {
1090 var paneClipperElem = this._paneClipper.getElement();
1091 var paneClipperLeftX = qx.html.Location.getClientBoxLeft(paneClipperElem);
1092 var paneClipperRightX = qx.html.Location.getClientBoxRight(paneClipperElem);
1093 if (pageX < paneClipperLeftX || pageX > paneClipperRightX) {
1094 // There was no cell or header cell hit
1098 var paneClipperTopY = qx.html.Location.getClientBoxTop(paneClipperElem);
1099 var paneClipperBottomY = qx.html.Location.getClientBoxBottom(paneClipperElem);
1100 if (pageY >= paneClipperTopY && pageY <= paneClipperBottomY) {
1101 // This event is in the pane -> Get the row
1102 var rowHeight = this.getTable().getRowHeight();
1104 var scrollY = this._verScrollBar.getValue();
1105 if (this.getTable().getKeepFirstVisibleRowComplete()) {
1106 scrollY = Math.floor(scrollY / rowHeight) * rowHeight;
1109 var tableY = scrollY + pageY - paneClipperTopY;
1110 var row = Math.floor(tableY / rowHeight);
1112 var rowCount = this.getTable().getTableModel().getRowCount();
1113 return (row < rowCount) ? row : null;
1116 var headerElem = this._headerClipper.getElement();
1117 if (pageY >= qx.html.Location.getClientBoxTop(headerElem)
1118 && pageY <= qx.html.Location.getClientBoxBottom(headerElem)
1119 && pageX <= qx.html.Location.getClientBoxRight(headerElem))
1121 // This event is in the pane -> Return -1 for the header
1130 * Sets the widget that should be shown in the top right corner.
1132 * The widget will not be disposed, when this table scroller is disposed. So the
1133 * caller has to dispose it.
1135 * @param widget {qx.ui.core.Widget} The widget to set. May be null.
1137 qx.Proto.setTopRightWidget = function(widget) {
1138 var oldWidget = this._topRightWidget;
1139 if (oldWidget != null) {
1140 this._top.remove(oldWidget);
1143 if (widget != null) {
1144 this._top.remove(this._spacer);
1145 this._top.add(widget);
1146 } else if (oldWidget != null) {
1147 this._top.add(this._spacer);
1150 this._topRightWidget = widget;
1155 * Returns the header.
1157 * @return {TablePaneHeader} the header.
1159 qx.Proto.getHeader = function() {
1160 return this._header;
1165 * Returns the table pane.
1167 * @return {TablePane} the table pane.
1169 qx.Proto.getTablePane = function() {
1170 return this._tablePane;
1175 * Returns which scrollbars are needed.
1177 * @param forceHorizontal {Boolean ? false} Whether to show the horizontal
1179 * @param preventVertical {Boolean ? false} Whether tp show the vertical scrollbar
1181 * @return {Integer} which scrollbars are needed. This may be any combination of
1182 * {@link #HORIZONTAL_SCROLLBAR} or {@link #VERTICAL_SCROLLBAR}
1185 qx.Proto.getNeededScrollBars = function(forceHorizontal, preventVertical) {
1186 var barWidth = this._verScrollBar.getPreferredBoxWidth();
1188 // Get the width and height of the view (without scroll bars)
1189 var viewWidth = this._paneClipper.getInnerWidth();
1190 if (this.getVerticalScrollBarVisible()) {
1191 viewWidth += barWidth;
1193 var viewHeight = this._paneClipper.getInnerHeight();
1194 if (this.getHorizontalScrollBarVisible()) {
1195 viewHeight += barWidth;
1198 // Get the (virtual) width and height of the pane
1199 var paneWidth = this.getTablePaneModel().getTotalWidth();
1200 var paneHeight = this.getTable().getRowHeight() * this.getTable().getTableModel().getRowCount();
1202 // Check which scrollbars are needed
1203 var horNeeded = false;
1204 var verNeeded = false;
1205 if (paneWidth > viewWidth) {
1207 if (paneHeight > viewHeight - barWidth) {
1210 } else if (paneHeight > viewHeight) {
1212 if (!preventVertical && (paneWidth > viewWidth - barWidth)) {
1218 var horBar = qx.ui.table.TablePaneScroller.HORIZONTAL_SCROLLBAR;
1219 var verBar = qx.ui.table.TablePaneScroller.VERTICAL_SCROLLBAR;
1220 return ((forceHorizontal || horNeeded) ? horBar : 0)
1221 | ((preventVertical || !verNeeded) ? 0 : verBar);
1226 * Does a postponed update of the content.
1228 * @see #_updateContent
1230 qx.Proto._postponedUpdateContent = function() {
1231 if (! this._updateContentPlanned) {
1233 window.setTimeout(function() {
1234 self._updateContent();
1235 self._updateContentPlanned = false;
1236 qx.ui.core.Widget.flushGlobalQueues();
1238 this._updateContentPlanned = true;
1244 * Updates the content. Sets the right section the table pane should show and
1245 * does the scrolling.
1247 qx.Proto._updateContent = function() {
1248 var paneHeight = this._paneClipper.getInnerHeight();
1249 var scrollX = this._horScrollBar.getValue();
1250 var scrollY = this._verScrollBar.getValue();
1251 var rowHeight = this.getTable().getRowHeight();
1253 var firstRow = Math.floor(scrollY / rowHeight);
1254 var oldFirstRow = this._tablePane.getFirstVisibleRow();
1255 this._tablePane.setFirstVisibleRow(firstRow);
1257 var rowCount = Math.ceil(paneHeight / rowHeight);
1259 if (! this.getTable().getKeepFirstVisibleRowComplete()) {
1260 // NOTE: We don't consider paneOffset, because this may cause alternating
1261 // adding and deleting of one row when scolling. Instead we add one row
1264 paneOffset = scrollY % rowHeight;
1266 this._tablePane.setVisibleRowCount(rowCount);
1268 if (firstRow != oldFirstRow) {
1269 this._updateFocusIndicator();
1272 // Workaround: We can't use scrollLeft for the header because IE
1273 // automatically scrolls the header back, when a column is
1275 this._header.setLeft(-scrollX);
1276 this._paneClipper.setScrollLeft(scrollX);
1277 this._paneClipper.setScrollTop(paneOffset);
1279 //this.debug("paneHeight:"+paneHeight+",rowHeight:"+rowHeight+",firstRow:"+firstRow+",rowCount:"+rowCount+",paneOffset:"+paneOffset);
1284 * Updates the location and the visibility of the focus indicator.
1286 qx.Proto._updateFocusIndicator = function() {
1287 var table = this.getTable();
1289 if (! table.getEnabled()) {
1293 if (this._focusedCol == null) {
1294 this._focusIndicator.hide();
1296 var xPos = this.getTablePaneModel().getX(this._focusedCol);
1298 this._focusIndicator.hide();
1300 var columnModel = table.getTableColumnModel();
1301 var paneModel = this.getTablePaneModel();
1303 var firstRow = this._tablePane.getFirstVisibleRow();
1304 var rowHeight = table.getRowHeight();
1306 this._focusIndicator.setHeight(rowHeight + 3);
1307 this._focusIndicator.setWidth(columnModel.getColumnWidth(this._focusedCol) + 3);
1308 this._focusIndicator.setTop((this._focusedRow - firstRow) * rowHeight - 2);
1309 this._focusIndicator.setLeft(paneModel.getColumnLeft(this._focusedCol) - 2);
1311 this._focusIndicator.show();
1313 // Force redisplay of the focus indicator right away. Without this, it
1314 // waits until the mouse stops moving for a while before updating, and
1315 // appears as if it is slow to respond.
1316 qx.ui.core.Widget.flushGlobalQueues();
1323 qx.Proto.dispose = function() {
1324 if (this.getDisposed()) {
1328 if (this.getElement() != null) {
1329 this.getElement().onselectstart = null;
1332 this._verScrollBar.dispose();
1333 this._horScrollBar.dispose();
1334 this._header.dispose();
1335 this._headerClipper.dispose();
1336 this._spacer.dispose();
1337 this._top.dispose();
1338 this._tablePane.dispose();
1339 this._paneClipper.dispose();
1341 if (this._resizeLine != null) {
1342 this._resizeLine.dispose();
1345 this.removeEventListener("mousemove", this._onmousemove, this);
1346 this.removeEventListener("mousedown", this._onmousedown, this);
1347 this.removeEventListener("mouseup", this._onmouseup, this);
1348 this.removeEventListener("click", this._onclick, this);
1349 this.removeEventListener("dblclick", this._ondblclick, this);
1350 this.removeEventListener("mouseout", this._onmouseout, this);
1352 var tablePaneModel = this.getTablePaneModel();
1353 if (tablePaneModel != null) {
1354 tablePaneModel.removeEventListener("modelChanged", this._onPaneModelChanged, this);
1357 return qx.ui.layout.VerticalBoxLayout.prototype.dispose.call(this);
1361 /** {int} The minimum width a colum could get in pixels. */
1362 qx.Class.MIN_COLUMN_WIDTH = 10;
1364 /** {int} The radius of the resize region in pixels. */
1365 qx.Class.RESIZE_REGION_RADIUS = 5;
1368 * (int) The number of pixels the mouse may move between mouse down and mouse up
1369 * in order to count as a click.
1371 qx.Class.CLICK_TOLERANCE = 5;
1374 * (int) The mask for the horizontal scroll bar.
1375 * May be combined with {@link #VERTICAL_SCROLLBAR}.
1377 * @see #getNeededScrollBars
1379 qx.Class.HORIZONTAL_SCROLLBAR = 1;
1382 * (int) The mask for the vertical scroll bar.
1383 * May be combined with {@link #HORIZONTAL_SCROLLBAR}.
1385 * @see #getNeededScrollBars
1387 qx.Class.VERTICAL_SCROLLBAR = 2;
1390 * (string) The correct value for the CSS style attribute "cursor" for the
1391 * horizontal resize cursor.
1393 qx.Class.CURSOR_RESIZE_HORIZONTAL = (qx.core.Client.getInstance().isGecko() && (qx.core.Client.getInstance().getMajor() > 1 || qx.core.Client.getInstance().getMinor() >= 8)) ? "ew-resize" : "e-resize";