summaryrefslogtreecommitdiff
path: root/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneScroller.js
diff options
context:
space:
mode:
Diffstat (limited to 'webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneScroller.js')
-rw-r--r--webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneScroller.js1331
1 files changed, 1331 insertions, 0 deletions
diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneScroller.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneScroller.js
new file mode 100644
index 0000000000..d6f7773148
--- /dev/null
+++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneScroller.js
@@ -0,0 +1,1331 @@
+/* ************************************************************************
+
+ qooxdoo - the new era of web development
+
+ http://qooxdoo.org
+
+ Copyright:
+ 2006 by STZ-IDA, Germany, http://www.stz-ida.de
+
+ License:
+ LGPL 2.1: http://www.gnu.org/licenses/lgpl.html
+
+ Authors:
+ * Til Schneider (til132)
+
+************************************************************************ */
+
+/* ************************************************************************
+
+#module(ui_table)
+
+************************************************************************ */
+
+/**
+ * Shows a whole meta column. This includes a {@link TablePaneHeader},
+ * a {@link TablePane} and the needed scroll bars. This class handles the
+ * virtual scrolling and does all the mouse event handling.
+ *
+ * @param table {Table} the table the scroller belongs to.
+ */
+qx.OO.defineClass("qx.ui.table.TablePaneScroller", qx.ui.layout.VerticalBoxLayout,
+function(table) {
+ qx.ui.layout.VerticalBoxLayout.call(this);
+
+ this._table = table;
+
+ // init scrollbars
+ this._verScrollBar = new qx.ui.core.ScrollBar(false);
+ this._horScrollBar = new qx.ui.core.ScrollBar(true);
+
+ var scrollBarWidth = this._verScrollBar.getPreferredBoxWidth();
+
+ this._verScrollBar.setWidth("auto");
+ this._horScrollBar.setHeight("auto");
+ this._horScrollBar.setPaddingRight(scrollBarWidth);
+ //this._verScrollBar.setMergeEvents(true);
+
+ this._horScrollBar.addEventListener("changeValue", this._onScrollX, this);
+ this._verScrollBar.addEventListener("changeValue", this._onScrollY, this);
+
+ // init header
+ this._header = new qx.ui.table.TablePaneHeader(this);
+ this._header.set({ width:"auto", height:"auto" });
+
+ this._headerClipper = new qx.ui.layout.CanvasLayout;
+ with (this._headerClipper) {
+ setDimension("1*", "auto");
+ setOverflow("hidden");
+ add(this._header);
+ }
+
+ this._spacer = new qx.ui.basic.Terminator;
+ this._spacer.setWidth(scrollBarWidth);
+
+ this._top = new qx.ui.layout.HorizontalBoxLayout;
+ with (this._top) {
+ setHeight("auto");
+ add(this._headerClipper, this._spacer);
+ }
+
+ // init pane
+ this._tablePane = new qx.ui.table.TablePane(this);
+ this._tablePane.set({ width:"auto", height:"auto" });
+
+ this._focusIndicator = new qx.ui.layout.HorizontalBoxLayout;
+ this._focusIndicator.setAppearance("table-focus-indicator");
+ this._focusIndicator.hide();
+
+ // Workaround: If the _focusIndicator has no content if always gets a too
+ // high hight in IE.
+ var dummyContent = new qx.ui.basic.Terminator;
+ dummyContent.setWidth(0);
+ this._focusIndicator.add(dummyContent);
+
+ this._paneClipper = new qx.ui.layout.CanvasLayout;
+ with (this._paneClipper) {
+ setWidth("1*");
+ setOverflow("hidden");
+ add(this._tablePane, this._focusIndicator);
+ addEventListener("mousewheel", this._onmousewheel, this);
+ }
+
+ // add all child widgets
+ var scrollerBody = new qx.ui.layout.HorizontalBoxLayout;
+ scrollerBody.setHeight("1*");
+ scrollerBody.add(this._paneClipper, this._verScrollBar);
+
+ this.add(this._top, scrollerBody, this._horScrollBar);
+
+ // init event handlers
+ this.addEventListener("mousemove", this._onmousemove, this);
+ this.addEventListener("mousedown", this._onmousedown, this);
+ this.addEventListener("mouseup", this._onmouseup, this);
+ this.addEventListener("click", this._onclick, this);
+ this.addEventListener("dblclick", this._ondblclick, this);
+ this.addEventListener("mouseout", this._onmouseout, this);
+});
+
+/** Whether to show the horizontal scroll bar */
+qx.OO.addProperty({ name:"horizontalScrollBarVisible", type:"boolean", defaultValue:true });
+
+/** Whether to show the vertical scroll bar */
+qx.OO.addProperty({ name:"verticalScrollBarVisible", type:"boolean", defaultValue:true });
+
+/** The table pane model. */
+qx.OO.addProperty({ name:"tablePaneModel", type:"object", instance:"qx.ui.table.TablePaneModel" });
+
+/** The current position of the the horizontal scroll bar. */
+qx.OO.addProperty({ name:"scrollX", type:"number", allowNull:false, defaultValue:0 });
+
+/** The current position of the the vertical scroll bar. */
+qx.OO.addProperty({ name:"scrollY", type:"number", allowNull:false, defaultValue:0 });
+
+/**
+ * Whether column resize should be live. If false, during resize only a line is
+ * shown and the real resize happens when the user releases the mouse button.
+ */
+qx.OO.addProperty({ name:"liveResize", type:"boolean", defaultValue:false });
+
+/**
+ * Whether the focus should moved when the mouse is moved over a cell. If false
+ * the focus is only moved on mouse clicks.
+ */
+qx.OO.addProperty({ name:"focusCellOnMouseMove", type:"boolean", defaultValue:false });
+
+
+// property modifier
+qx.Proto._modifyHorizontalScrollBarVisible = function(propValue, propOldValue, propData) {
+ // Workaround: We can't use setDisplay, because the scroll bar needs its
+ // correct height in order to check its value. When using
+ // setDisplay(false) the height isn't relayouted any more
+ if (propValue) {
+ this._horScrollBar.setHeight("auto");
+ } else {
+ this._horScrollBar.setHeight(0);
+ }
+ this._horScrollBar.setVisibility(propValue);
+
+ // NOTE: We have to flush the queues before updating the content so the new
+ // layout has been applied and _updateContent is able to work with
+ // correct values.
+ qx.ui.core.Widget.flushGlobalQueues();
+ this._updateContent();
+
+ return true;
+}
+
+
+// property modifier
+qx.Proto._modifyVerticalScrollBarVisible = function(propValue, propOldValue, propData) {
+ // Workaround: See _modifyHorizontalScrollBarVisible
+ if (propValue) {
+ this._verScrollBar.setWidth("auto");
+ } else {
+ this._verScrollBar.setWidth(0);
+ }
+ this._verScrollBar.setVisibility(propValue);
+
+ var scrollBarWidth = propValue ? this._verScrollBar.getPreferredBoxWidth() : 0;
+ this._horScrollBar.setPaddingRight(scrollBarWidth);
+ this._spacer.setWidth(scrollBarWidth);
+
+ return true;
+}
+
+
+// property modifier
+qx.Proto._modifyTablePaneModel = function(propValue, propOldValue, propData) {
+ if (propOldValue != null) {
+ propOldValue.removeEventListener("modelChanged", this._onPaneModelChanged, this);
+ }
+ propValue.addEventListener("modelChanged", this._onPaneModelChanged, this);
+
+ return true;
+}
+
+
+// property modifier
+qx.Proto._modifyScrollX = function(propValue, propOldValue, propData) {
+ this._horScrollBar.setValue(propValue);
+ return true;
+}
+
+
+// property modifier
+qx.Proto._modifyScrollY = function(propValue, propOldValue, propData) {
+ this._verScrollBar.setValue(propValue);
+ return true;
+}
+
+
+/**
+ * Returns the table this scroller belongs to.
+ *
+ * @return {Table} the table.
+ */
+qx.Proto.getTable = function() {
+ return this._table;
+};
+
+
+/**
+ * Event handler. Called when the visibility of a column has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onColVisibilityChanged = function(evt) {
+ this._updateHorScrollBarMaximum();
+ this._updateFocusIndicator();
+}
+
+
+/**
+ * Event handler. Called when the width of a column has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onColWidthChanged = function(evt) {
+ this._header._onColWidthChanged(evt);
+ this._tablePane._onColWidthChanged(evt);
+
+ var data = evt.getData();
+ var paneModel = this.getTablePaneModel();
+ var x = paneModel.getX(data.col);
+ if (x != -1) {
+ // The change was in this scroller
+ this._updateHorScrollBarMaximum();
+ this._updateFocusIndicator();
+ }
+}
+
+
+/**
+ * Event handler. Called when the column order has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onColOrderChanged = function(evt) {
+ this._header._onColOrderChanged(evt);
+ this._tablePane._onColOrderChanged(evt);
+
+ this._updateHorScrollBarMaximum();
+}
+
+
+/**
+ * Event handler. Called when the table model has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onTableModelDataChanged = function(evt) {
+ this._tablePane._onTableModelDataChanged(evt);
+
+ var rowCount = this.getTable().getTableModel().getRowCount();
+ if (rowCount != this._lastRowCount) {
+ this._lastRowCount = rowCount;
+
+ this._updateVerScrollBarMaximum();
+ if (this.getFocusedRow() >= rowCount) {
+ if (rowCount == 0) {
+ this.setFocusedCell(null, null);
+ } else {
+ this.setFocusedCell(this.getFocusedColumn(), rowCount - 1);
+ }
+ }
+ }
+}
+
+
+/**
+ * Event handler. Called when the selection has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onSelectionChanged = function(evt) {
+ this._tablePane._onSelectionChanged(evt);
+};
+
+
+/**
+ * Event handler. Called when the table gets or looses the focus.
+ */
+qx.Proto._onFocusChanged = function(evt) {
+ this._focusIndicator.setState("tableHasFocus", this.getTable().getFocused());
+
+ this._tablePane._onFocusChanged(evt);
+};
+
+
+/**
+ * Event handler. Called when the table model meta data has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onTableModelMetaDataChanged = function(evt) {
+ this._header._onTableModelMetaDataChanged(evt);
+ this._tablePane._onTableModelMetaDataChanged(evt);
+};
+
+
+/**
+ * Event handler. Called when the pane model has changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onPaneModelChanged = function(evt) {
+ this._header._onPaneModelChanged(evt);
+ this._tablePane._onPaneModelChanged(evt);
+};
+
+
+/**
+ * Updates the maximum of the horizontal scroll bar, so it corresponds to the
+ * total width of the columns in the table pane.
+ */
+qx.Proto._updateHorScrollBarMaximum = function() {
+ this._horScrollBar.setMaximum(this.getTablePaneModel().getTotalWidth());
+}
+
+
+/**
+ * Updates the maximum of the vertical scroll bar, so it corresponds to the
+ * number of rows in the table.
+ */
+qx.Proto._updateVerScrollBarMaximum = function() {
+ var rowCount = this.getTable().getTableModel().getRowCount();
+ var rowHeight = this.getTable().getRowHeight();
+
+ if (this.getTable().getKeepFirstVisibleRowComplete()) {
+ this._verScrollBar.setMaximum((rowCount + 1) * rowHeight);
+ } else {
+ this._verScrollBar.setMaximum(rowCount * rowHeight);
+ }
+}
+
+
+/**
+ * Event handler. Called when the table property "keepFirstVisibleRowComplete"
+ * changed.
+ */
+qx.Proto._onKeepFirstVisibleRowCompleteChanged = function() {
+ this._updateVerScrollBarMaximum();
+ this._updateContent();
+};
+
+
+// overridden
+qx.Proto._changeInnerHeight = function(newValue, oldValue) {
+ // The height has changed -> Update content
+ this._postponedUpdateContent();
+
+ return qx.ui.layout.VerticalBoxLayout.prototype._changeInnerHeight.call(this, newValue, oldValue);
+}
+
+
+// overridden
+qx.Proto._afterAppear = function() {
+ qx.ui.layout.VerticalBoxLayout.prototype._afterAppear.call(this);
+
+ var self = this;
+ this.getElement().onselectstart = qx.util.Return.returnFalse;
+
+ this._updateContent();
+ this._header._updateContent();
+ this._updateHorScrollBarMaximum();
+ this._updateVerScrollBarMaximum();
+}
+
+
+/**
+ * Event handler. Called when the horizontal scroll bar moved.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onScrollX = function(evt) {
+ // Workaround: See _updateContent
+ this._header.setLeft(-evt.getData());
+
+ this._paneClipper.setScrollLeft(evt.getData());
+ this.setScrollX(evt.getData());
+}
+
+
+/**
+ * Event handler. Called when the vertical scroll bar moved.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onScrollY = function(evt) {
+ this._postponedUpdateContent();
+ this.setScrollY(evt.getData());
+}
+
+
+/**
+ * Event handler. Called when the user moved the mouse wheel.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onmousewheel = function(evt) {
+ this._verScrollBar.setValue(this._verScrollBar.getValue()
+ - evt.getWheelDelta() * this.getTable().getRowHeight());
+
+ // Update the focus
+ if (this._lastMousePageX && this.getFocusCellOnMouseMove()) {
+ this._focusCellAtPagePos(this._lastMousePageX, this._lastMousePageY);
+ }
+}
+
+
+/**
+ * Event handler. Called when the user moved the mouse.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onmousemove = function(evt) {
+ var tableModel = this.getTable().getTableModel();
+ var columnModel = this.getTable().getTableColumnModel();
+
+ var useResizeCursor = false;
+ var mouseOverColumn = null;
+
+ var pageX = evt.getPageX();
+ var pageY = evt.getPageY();
+
+ // Workaround: In onmousewheel the event has wrong coordinates for pageX
+ // and pageY. So we remember the last move event.
+ this._lastMousePageX = pageX;
+ this._lastMousePageY = pageY;
+
+ if (this._resizeColumn != null) {
+ // We are currently resizing -> Update the position
+ var minColumnWidth = qx.ui.table.TablePaneScroller.MIN_COLUMN_WIDTH;
+ var newWidth = Math.max(minColumnWidth, this._lastResizeWidth + pageX - this._lastResizeMousePageX);
+
+ if (this.getLiveResize()) {
+ columnModel.setColumnWidth(this._resizeColumn, newWidth);
+ } else {
+ this._header.setColumnWidth(this._resizeColumn, newWidth);
+
+ var paneModel = this.getTablePaneModel();
+ this._showResizeLine(paneModel.getColumnLeft(this._resizeColumn) + newWidth);
+ }
+
+ useResizeCursor = true;
+ this._lastResizeMousePageX += newWidth - this._lastResizeWidth;
+ this._lastResizeWidth = newWidth;
+ } else if (this._moveColumn != null) {
+ // We are moving a column
+
+ // Check whether we moved outside the click tolerance so we can start
+ // showing the column move feedback
+ // (showing the column move feedback prevents the onclick event)
+ var clickTolerance = qx.ui.table.TablePaneScroller.CLICK_TOLERANCE;
+ if (this._header.isShowingColumnMoveFeedback()
+ || pageX > this._lastMoveMousePageX + clickTolerance
+ || pageX < this._lastMoveMousePageX - clickTolerance)
+ {
+ this._lastMoveColPos += pageX - this._lastMoveMousePageX;
+
+ this._header.showColumnMoveFeedback(this._moveColumn, this._lastMoveColPos);
+
+ // Get the responsible scroller
+ var targetScroller = this._table.getTablePaneScrollerAtPageX(pageX);
+ if (this._lastMoveTargetScroller && this._lastMoveTargetScroller != targetScroller) {
+ this._lastMoveTargetScroller.hideColumnMoveFeedback();
+ }
+ if (targetScroller != null) {
+ this._lastMoveTargetX = targetScroller.showColumnMoveFeedback(pageX);
+ } else {
+ this._lastMoveTargetX = null;
+ }
+
+ this._lastMoveTargetScroller = targetScroller;
+ this._lastMoveMousePageX = pageX;
+ }
+ } else {
+ // This is a normal mouse move
+ var row = this._getRowForPagePos(pageX, pageY);
+ if (row == -1) {
+ // The mouse is over the header
+ var resizeCol = this._getResizeColumnForPageX(pageX);
+ if (resizeCol != -1) {
+ // The mouse is over a resize region -> Show the right cursor
+ useResizeCursor = true;
+ } else {
+ var col = this._getColumnForPageX(pageX);
+ if (col != null && tableModel.isColumnSortable(col)) {
+ mouseOverColumn = col;
+ }
+ }
+ } else if (row != null) {
+ // The mouse is over the data -> update the focus
+ if (this.getFocusCellOnMouseMove()) {
+ this._focusCellAtPagePos(pageX, pageY);
+ }
+ }
+ }
+
+ // Workaround: Setting the cursor to the right widget doesn't work
+ //this._header.setCursor(useResizeCursor ? "e-resize" : null);
+ this.getTopLevelWidget().setGlobalCursor(useResizeCursor ? qx.ui.table.TablePaneScroller.CURSOR_RESIZE_HORIZONTAL : null);
+
+ this._header.setMouseOverColumn(mouseOverColumn);
+}
+
+
+/**
+ * Event handler. Called when the user pressed a mouse button.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onmousedown = function(evt) {
+ var tableModel = this.getTable().getTableModel();
+ var columnModel = this.getTable().getTableColumnModel();
+
+ var pageX = evt.getPageX();
+ var pageY = evt.getPageY();
+ var row = this._getRowForPagePos(pageX, pageY);
+ if (row == -1) {
+ // mouse is in header
+ var resizeCol = this._getResizeColumnForPageX(pageX);
+ if (resizeCol != -1) {
+ // The mouse is over a resize region -> Start resizing
+ this._resizeColumn = resizeCol;
+ this._lastResizeMousePageX = pageX;
+ this._lastResizeWidth = columnModel.getColumnWidth(this._resizeColumn);
+ this.setCapture(true);
+ } else {
+ // The mouse is not in a resize region
+ var col = this._getColumnForPageX(pageX);
+ if (col != null) {
+ // Prepare column moving
+ this._moveColumn = col;
+ this._lastMoveMousePageX = pageX;
+ this._lastMoveColPos = this.getTablePaneModel().getColumnLeft(col);
+ this.setCapture(true);
+ }
+ }
+ } else if (row != null) {
+ // The mouse is over the data -> update the focus
+ if (! this.getFocusCellOnMouseMove()) {
+ this._focusCellAtPagePos(pageX, pageY);
+ }
+
+ this.getTable()._getSelectionManager().handleMouseDown(row, evt);
+ }
+}
+
+
+/**
+ * Event handler. Called when the user released a mouse button.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onmouseup = function(evt) {
+ var columnModel = this.getTable().getTableColumnModel();
+ var paneModel = this.getTablePaneModel();
+
+ if (this._resizeColumn != null) {
+ // We are currently resizing -> Finish resizing
+ if (! this.getLiveResize()) {
+ this._hideResizeLine();
+ columnModel.setColumnWidth(this._resizeColumn, this._lastResizeWidth);
+ }
+
+ this._resizeColumn = null;
+ this.setCapture(false);
+
+ this.getTopLevelWidget().setGlobalCursor(null);
+ } else if (this._moveColumn != null) {
+ // We are moving a column -> Drop the column
+ this._header.hideColumnMoveFeedback();
+ if (this._lastMoveTargetScroller) {
+ this._lastMoveTargetScroller.hideColumnMoveFeedback();
+ }
+
+ if (this._lastMoveTargetX != null) {
+ var fromVisXPos = paneModel.getFirstColumnX() + paneModel.getX(this._moveColumn);
+ var toVisXPos = this._lastMoveTargetX;
+ if (toVisXPos != fromVisXPos && toVisXPos != fromVisXPos + 1) {
+ // The column was really moved to another position
+ // (and not moved before or after itself, which is a noop)
+
+ // Translate visible positions to overall positions
+ var fromCol = columnModel.getVisibleColumnAtX(fromVisXPos);
+ var toCol = columnModel.getVisibleColumnAtX(toVisXPos);
+ var fromOverXPos = columnModel.getOverallX(fromCol);
+ var toOverXPos = (toCol != null) ? columnModel.getOverallX(toCol) : columnModel.getOverallColumnCount();
+
+ if (toOverXPos > fromOverXPos) {
+ // Don't count the column itself
+ toOverXPos--;
+ }
+
+ // Move the column
+ columnModel.moveColumn(fromOverXPos, toOverXPos);
+ }
+ }
+
+ this._moveColumn = null;
+ this._lastMoveTargetX = null;
+ this.setCapture(false);
+ } else {
+ // This is a normal mouse up
+ var row = this._getRowForPagePos(evt.getPageX(), evt.getPageY());
+ if (row != -1 && row != null) {
+ this.getTable()._getSelectionManager().handleMouseUp(row, evt);
+ }
+ }
+}
+
+
+/**
+ * Event handler. Called when the user clicked a mouse button.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onclick = function(evt) {
+ var tableModel = this.getTable().getTableModel();
+
+ var pageX = evt.getPageX();
+ var pageY = evt.getPageY();
+ var row = this._getRowForPagePos(pageX, pageY);
+ if (row == -1) {
+ // mouse is in header
+ var resizeCol = this._getResizeColumnForPageX(pageX);
+ if (resizeCol == -1) {
+ // mouse is not in a resize region
+ var col = this._getColumnForPageX(pageX);
+ if (col != null && tableModel.isColumnSortable(col)) {
+ // Sort that column
+ var sortCol = tableModel.getSortColumnIndex();
+ var ascending = (col != sortCol) ? true : !tableModel.isSortAscending();
+
+ tableModel.sortByColumn(col, ascending);
+ this.getTable().getSelectionModel().clearSelection();
+ }
+ }
+ } else if (row != null) {
+ this.getTable()._getSelectionManager().handleClick(row, evt);
+ }
+}
+
+
+/**
+ * Event handler. Called when the user double clicked a mouse button.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._ondblclick = function(evt) {
+ if (! this.isEditing()) {
+ this._focusCellAtPagePos(evt.getPageX(), evt.getPageY());
+ this.startEditing();
+ }
+}
+
+
+/**
+ * Event handler. Called when the mouse moved out.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onmouseout = function(evt) {
+ /*
+ // Workaround: See _onmousemove
+ this._lastMousePageX = null;
+ this._lastMousePageY = null;
+ */
+
+ // Reset the resize cursor when the mouse leaves the header
+ // If currently a column is resized then do nothing
+ // (the cursor will be reset on mouseup)
+ if (this._resizeColumn == null) {
+ this.getTopLevelWidget().setGlobalCursor(null);
+ }
+
+ this._header.setMouseOverColumn(null);
+}
+
+
+/**
+ * Shows the resize line.
+ *
+ * @param x {int} the position where to show the line (in pixels, relative to
+ * the left side of the pane).
+ */
+qx.Proto._showResizeLine = function(x) {
+ var resizeLine = this._resizeLine;
+ if (resizeLine == null) {
+ resizeLine = new qx.ui.basic.Terminator;
+ resizeLine.setBackgroundColor("#D6D5D9");
+ resizeLine.setWidth(3);
+ this._paneClipper.add(resizeLine);
+ qx.ui.core.Widget.flushGlobalQueues();
+
+ this._resizeLine = resizeLine;
+ }
+
+ resizeLine._applyRuntimeLeft(x - 2); // -1 for the width
+ resizeLine._applyRuntimeHeight(this._paneClipper.getBoxHeight() + this._paneClipper.getScrollTop());
+
+ this._resizeLine.removeStyleProperty("visibility");
+}
+
+
+/**
+ * Hides the resize line.
+ */
+qx.Proto._hideResizeLine = function() {
+ this._resizeLine.setStyleProperty("visibility", "hidden");
+}
+
+
+/**
+ * Shows the feedback shown while a column is moved by the user.
+ *
+ * @param pageX {int} the x position of the mouse in the page (in pixels).
+ * @return {int} the visible x position of the column in the whole table.
+ */
+qx.Proto.showColumnMoveFeedback = function(pageX) {
+ var paneModel = this.getTablePaneModel();
+ var columnModel = this.getTable().getTableColumnModel();
+ var paneLeftX = qx.dom.Location.getClientBoxLeft(this._tablePane.getElement());
+ var colCount = paneModel.getColumnCount();
+
+ var targetXPos = 0;
+ var targetX = 0;
+ var currX = paneLeftX;
+ for (var xPos = 0; xPos < colCount; xPos++) {
+ var col = paneModel.getColumnAtX(xPos);
+ var colWidth = columnModel.getColumnWidth(col);
+
+ if (pageX < currX + colWidth / 2) {
+ break;
+ }
+
+ currX += colWidth;
+ targetXPos = xPos + 1;
+ targetX = currX - paneLeftX;
+ }
+
+ // Ensure targetX is visible
+ var clipperLeftX = qx.dom.Location.getClientBoxLeft(this._paneClipper.getElement());
+ var clipperWidth = this._paneClipper.getBoxWidth();
+ var scrollX = clipperLeftX - paneLeftX;
+ // NOTE: +2/-1 because of feedback width
+ targetX = qx.lang.Number.limit(targetX, scrollX + 2, scrollX + clipperWidth - 1);
+
+ this._showResizeLine(targetX);
+
+ // Return the overall target x position
+ return paneModel.getFirstColumnX() + targetXPos;
+}
+
+
+/**
+ * Hides the feedback shown while a column is moved by the user.
+ */
+qx.Proto.hideColumnMoveFeedback = function() {
+ this._hideResizeLine();
+}
+
+
+/**
+ * Sets the focus to the cell that's located at the page position
+ * <code>pageX</code>/<code>pageY</code>. If there is no cell at that position,
+ * nothing happens.
+ *
+ * @param pageX {int} the x position in the page (in pixels).
+ * @param pageY {int} the y position in the page (in pixels).
+ */
+qx.Proto._focusCellAtPagePos = function(pageX, pageY) {
+ var row = this._getRowForPagePos(pageX, pageY);
+ if (row != -1 && row != null) {
+ // The mouse is over the data -> update the focus
+ var col = this._getColumnForPageX(pageX);
+ if (col != null) {
+ this._table.setFocusedCell(col, row);
+ }
+ }
+}
+
+
+/**
+ * Sets the currently focused cell.
+ *
+ * @param col {int} the model index of the focused cell's column.
+ * @param row {int} the model index of the focused cell's row.
+ */
+qx.Proto.setFocusedCell = function(col, row) {
+ if (!this.isEditing()) {
+ this._tablePane.setFocusedCell(col, row, this._updateContentPlanned);
+
+ this._focusedCol = col;
+ this._focusedRow = row;
+
+ // Move the focus indicator
+ if (! this._updateContentPlanned) {
+ this._updateFocusIndicator();
+ }
+ }
+}
+
+
+/**
+ * Returns the column of currently focused cell.
+ *
+ * @return {int} the model index of the focused cell's column.
+ */
+qx.Proto.getFocusedColumn = function() {
+ return this._focusedCol;
+};
+
+
+/**
+ * Returns the row of currently focused cell.
+ *
+ * @return {int} the model index of the focused cell's column.
+ */
+qx.Proto.getFocusedRow = function() {
+ return this._focusedRow;
+};
+
+
+/**
+ * Scrolls a cell visible.
+ *
+ * @param col {int} the model index of the column the cell belongs to.
+ * @param row {int} the model index of the row the cell belongs to.
+ */
+qx.Proto.scrollCellVisible = function(col, row) {
+ var paneModel = this.getTablePaneModel();
+ var xPos = paneModel.getX(col);
+
+ if (xPos != -1) {
+ var columnModel = this.getTable().getTableColumnModel();
+
+ var colLeft = paneModel.getColumnLeft(col);
+ var colWidth = columnModel.getColumnWidth(col);
+ var rowHeight = this.getTable().getRowHeight();
+ var rowTop = row * rowHeight;
+
+ var scrollX = this.getScrollX();
+ var scrollY = this.getScrollY();
+ var viewWidth = this._paneClipper.getBoxWidth();
+ var viewHeight = this._paneClipper.getBoxHeight();
+
+ // NOTE: We don't use qx.lang.Number.limit, because min should win if max < min
+ var minScrollX = Math.min(colLeft, colLeft + colWidth - viewWidth);
+ var maxScrollX = colLeft;
+ this.setScrollX(Math.max(minScrollX, Math.min(maxScrollX, scrollX)));
+
+ var minScrollY = rowTop + rowHeight - viewHeight;
+ if (this.getTable().getKeepFirstVisibleRowComplete()) {
+ minScrollY += rowHeight - 1;
+ }
+ var maxScrollY = rowTop;
+ this.setScrollY(Math.max(minScrollY, Math.min(maxScrollY, scrollY)));
+ }
+}
+
+
+/**
+ * Returns whether currently a cell is editing.
+ *
+ * @return whether currently a cell is editing.
+ */
+qx.Proto.isEditing = function() {
+ return this._cellEditor != null;
+}
+
+
+/**
+ * Starts editing the currently focused cell. Does nothing if already editing
+ * or if the column is not editable.
+ *
+ * @return {boolean} whether editing was started
+ */
+qx.Proto.startEditing = function() {
+ var tableModel = this.getTable().getTableModel();
+ var col = this._focusedCol;
+
+ if (!this.isEditing() && (col != null) && tableModel.isColumnEditable(col)) {
+ var row = this._focusedRow;
+ var xPos = this.getTablePaneModel().getX(col);
+ var value = tableModel.getValue(col, row);
+
+ this._cellEditorFactory = this.getTable().getTableColumnModel().getCellEditorFactory(col);
+ var cellInfo = { col:col, row:row, xPos:xPos, value:value }
+ this._cellEditor = this._cellEditorFactory.createCellEditor(cellInfo);
+ this._cellEditor.set({ width:"100%", height:"100%" });
+
+ this._focusIndicator.add(this._cellEditor);
+ this._focusIndicator.addState("editing");
+
+ this._cellEditor.addEventListener("changeFocused", this._onCellEditorFocusChanged, this);
+
+ // Workaround: Calling focus() directly has no effect
+ var editor = this._cellEditor;
+ window.setTimeout(function() {
+ editor.focus();
+ }, 0);
+
+ return true;
+ }
+
+ return false;
+}
+
+
+/**
+ * Stops editing and writes the editor's value to the model.
+ */
+qx.Proto.stopEditing = function() {
+ this.flushEditor();
+ this.cancelEditing();
+}
+
+
+/**
+ * Writes the editor's value to the model.
+ */
+qx.Proto.flushEditor = function() {
+ if (this.isEditing()) {
+ var value = this._cellEditorFactory.getCellEditorValue(this._cellEditor);
+ this.getTable().getTableModel().setValue(this._focusedCol, this._focusedRow, value);
+
+ this._table.focus();
+ }
+}
+
+
+/**
+ * Stops editing without writing the editor's value to the model.
+ */
+qx.Proto.cancelEditing = function() {
+ if (this.isEditing()) {
+ this._focusIndicator.remove(this._cellEditor);
+ this._focusIndicator.removeState("editing");
+ this._cellEditor.dispose();
+
+ this._cellEditor.removeEventListener("changeFocused", this._onCellEditorFocusChanged, this);
+ this._cellEditor = null;
+ this._cellEditorFactory = null;
+ }
+}
+
+
+/**
+ * Event handler. Called when the focused state of the cell editor changed.
+ *
+ * @param evt {Map} the event.
+ */
+qx.Proto._onCellEditorFocusChanged = function(evt) {
+ if (!this._cellEditor.getFocused()) {
+ this.stopEditing();
+ }
+}
+
+
+/**
+ * Returns the model index of the column the mouse is over or null if the mouse
+ * is not over a column.
+ *
+ * @param pageX {int} the x position of the mouse in the page (in pixels).
+ * @return {int} the model index of the column the mouse is over.
+ */
+qx.Proto._getColumnForPageX = function(pageX) {
+ var headerLeftX = qx.dom.Location.getClientBoxLeft(this._header.getElement());
+
+ var columnModel = this.getTable().getTableColumnModel();
+ var paneModel = this.getTablePaneModel();
+ var colCount = paneModel.getColumnCount();
+ var currX = headerLeftX;
+ for (var x = 0; x < colCount; x++) {
+ var col = paneModel.getColumnAtX(x);
+ var colWidth = columnModel.getColumnWidth(col);
+ currX += colWidth;
+
+ if (pageX < currX) {
+ return col;
+ }
+ }
+
+ return null;
+}
+
+
+/**
+ * Returns the model index of the column that should be resized when dragging
+ * starts here. Returns -1 if the mouse is in no resize region of any column.
+ *
+ * @param pageX {int} the x position of the mouse in the page (in pixels).
+ * @return {int} the column index.
+ */
+qx.Proto._getResizeColumnForPageX = function(pageX) {
+ var headerLeftX = qx.dom.Location.getClientBoxLeft(this._header.getElement());
+
+ var columnModel = this.getTable().getTableColumnModel();
+ var paneModel = this.getTablePaneModel();
+ var colCount = paneModel.getColumnCount();
+ var currX = headerLeftX;
+ var regionRadius = qx.ui.table.TablePaneScroller.RESIZE_REGION_RADIUS;
+ for (var x = 0; x < colCount; x++) {
+ var col = paneModel.getColumnAtX(x);
+ var colWidth = columnModel.getColumnWidth(col);
+ currX += colWidth;
+
+ if (pageX >= (currX - regionRadius) && pageX <= (currX + regionRadius)) {
+ return col;
+ }
+ }
+
+ return -1;
+}
+
+
+/**
+ * Returns the model index of the row the mouse is currently over. Returns -1 if
+ * the mouse is over the header. Returns null if the mouse is not over any
+ * column.
+ *
+ * @param pageX {int} the mouse x position in the page.
+ * @param pageY {int} the mouse y position in the page.
+ * @return {int} the model index of the row the mouse is currently over.
+ */
+qx.Proto._getRowForPagePos = function(pageX, pageY) {
+ var paneClipperElem = this._paneClipper.getElement();
+ var paneClipperLeftX = qx.dom.Location.getClientBoxLeft(paneClipperElem);
+ var paneClipperRightX = qx.dom.Location.getClientBoxRight(paneClipperElem);
+ if (pageX < paneClipperLeftX || pageX > paneClipperRightX) {
+ // There was no cell or header cell hit
+ return null;
+ }
+
+ var paneClipperTopY = qx.dom.Location.getClientBoxTop(paneClipperElem);
+ var paneClipperBottomY = qx.dom.Location.getClientBoxBottom(paneClipperElem);
+ if (pageY >= paneClipperTopY && pageY <= paneClipperBottomY) {
+ // This event is in the pane -> Get the row
+ var rowHeight = this.getTable().getRowHeight();
+
+ var scrollY = this._verScrollBar.getValue();
+ if (this.getTable().getKeepFirstVisibleRowComplete()) {
+ scrollY = Math.floor(scrollY / rowHeight) * rowHeight;
+ }
+
+ var tableY = scrollY + pageY - paneClipperTopY;
+ var row = Math.floor(tableY / rowHeight);
+
+ var rowCount = this.getTable().getTableModel().getRowCount();
+ return (row < rowCount) ? row : null;
+ }
+
+ var headerElem = this._headerClipper.getElement();
+ if (pageY >= qx.dom.Location.getClientBoxTop(headerElem)
+ && pageY <= qx.dom.Location.getClientBoxBottom(headerElem)
+ && pageX <= qx.dom.Location.getClientBoxRight(headerElem))
+ {
+ // This event is in the pane -> Return -1 for the header
+ return -1;
+ }
+
+ return null;
+}
+
+
+/**
+ * Sets the widget that should be shown in the top right corner.
+ * <p>
+ * The widget will not be disposed, when this table scroller is disposed. So the
+ * caller has to dispose it.
+ *
+ * @param widget {qx.ui.core.Widget} The widget to set. May be null.
+ */
+qx.Proto.setTopRightWidget = function(widget) {
+ var oldWidget = this._topRightWidget;
+ if (oldWidget != null) {
+ this._top.remove(oldWidget);
+ }
+
+ if (widget != null) {
+ this._top.remove(this._spacer);
+ this._top.add(widget);
+ } else if (oldWidget != null) {
+ this._top.add(this._spacer);
+ }
+
+ this._topRightWidget = widget;
+}
+
+
+/**
+ * Returns the header.
+ *
+ * @return {TablePaneHeader} the header.
+ */
+qx.Proto.getHeader = function() {
+ return this._header;
+}
+
+
+/**
+ * Returns the table pane.
+ *
+ * @return {TablePane} the table pane.
+ */
+qx.Proto.getTablePane = function() {
+ return this._tablePane;
+}
+
+
+/**
+ * Returns which scrollbars are needed.
+ *
+ * @param forceHorizontal {boolean ? false} Whether to show the horizontal
+ * scrollbar always.
+ * @param preventVertical {boolean ? false} Whether tp show the vertical scrollbar
+ * never.
+ * @return {int} which scrollbars are needed. This may be any combination of
+ * {@link #HORIZONTAL_SCROLLBAR} or {@link #VERTICAL_SCROLLBAR}
+ * (combined by OR).
+ */
+qx.Proto.getNeededScrollBars = function(forceHorizontal, preventVertical) {
+ var barWidth = this._verScrollBar.getPreferredBoxWidth();
+
+ // Get the width and height of the view (without scroll bars)
+ var viewWidth = this._paneClipper.getInnerWidth();
+ if (this.getVerticalScrollBarVisible()) {
+ viewWidth += barWidth;
+ }
+ var viewHeight = this._paneClipper.getInnerHeight();
+ if (this.getHorizontalScrollBarVisible()) {
+ viewHeight += barWidth;
+ }
+
+ // Get the (virtual) width and height of the pane
+ var paneWidth = this.getTablePaneModel().getTotalWidth();
+ var paneHeight = this.getTable().getRowHeight() * this.getTable().getTableModel().getRowCount();
+
+ // Check which scrollbars are needed
+ var horNeeded = false;
+ var verNeeded = false;
+ if (paneWidth > viewWidth) {
+ horNeeded = true;
+ if (paneHeight > viewHeight - barWidth) {
+ verNeeded = true;
+ }
+ } else if (paneHeight > viewHeight) {
+ verNeeded = true;
+ if (!preventVertical && (paneWidth > viewWidth - barWidth)) {
+ horNeeded = true;
+ }
+ }
+
+ // Create the mask
+ var horBar = qx.ui.table.TablePaneScroller.HORIZONTAL_SCROLLBAR;
+ var verBar = qx.ui.table.TablePaneScroller.VERTICAL_SCROLLBAR;
+ return ((forceHorizontal || horNeeded) ? horBar : 0)
+ | ((preventVertical || !verNeeded) ? 0 : verBar);
+}
+
+
+/**
+ * Does a postponed update of the content.
+ *
+ * @see #_updateContent
+ */
+qx.Proto._postponedUpdateContent = function() {
+ if (! this._updateContentPlanned) {
+ var self = this;
+ window.setTimeout(function() {
+ self._updateContent();
+ self._updateContentPlanned = false;
+ qx.ui.core.Widget.flushGlobalQueues();
+ }, 0);
+ this._updateContentPlanned = true;
+ }
+}
+
+
+/**
+ * Updates the content. Sets the right section the table pane should show and
+ * does the scrolling.
+ */
+qx.Proto._updateContent = function() {
+ var paneHeight = this._paneClipper.getInnerHeight();
+ var scrollX = this._horScrollBar.getValue();
+ var scrollY = this._verScrollBar.getValue();
+ var rowHeight = this.getTable().getRowHeight();
+
+ var firstRow = Math.floor(scrollY / rowHeight);
+ var oldFirstRow = this._tablePane.getFirstVisibleRow();
+ this._tablePane.setFirstVisibleRow(firstRow);
+
+ var rowCount = Math.ceil(paneHeight / rowHeight);
+ var paneOffset = 0;
+ if (! this.getTable().getKeepFirstVisibleRowComplete()) {
+ // NOTE: We don't consider paneOffset, because this may cause alternating
+ // adding and deleting of one row when scolling. Instead we add one row
+ // in every case.
+ rowCount++;
+ paneOffset = scrollY % rowHeight;
+ }
+ this._tablePane.setVisibleRowCount(rowCount);
+
+ if (firstRow != oldFirstRow) {
+ this._updateFocusIndicator();
+ }
+
+ // Workaround: We can't use scrollLeft for the header because IE
+ // automatically scrolls the header back, when a column is
+ // resized.
+ this._header.setLeft(-scrollX);
+ this._paneClipper.setScrollLeft(scrollX);
+ this._paneClipper.setScrollTop(paneOffset);
+
+ //this.debug("paneHeight:"+paneHeight+",rowHeight:"+rowHeight+",firstRow:"+firstRow+",rowCount:"+rowCount+",paneOffset:"+paneOffset);
+}
+
+
+/**
+ * Updates the location and the visibility of the focus indicator.
+ */
+qx.Proto._updateFocusIndicator = function() {
+ if (this._focusedCol == null) {
+ this._focusIndicator.hide();
+ } else {
+ var xPos = this.getTablePaneModel().getX(this._focusedCol);
+ if (xPos == -1) {
+ this._focusIndicator.hide();
+ } else {
+ var columnModel = this.getTable().getTableColumnModel();
+ var paneModel = this.getTablePaneModel();
+
+ var firstRow = this._tablePane.getFirstVisibleRow();
+ var rowHeight = this.getTable().getRowHeight();
+
+ this._focusIndicator.setHeight(rowHeight + 3);
+ this._focusIndicator.setWidth(columnModel.getColumnWidth(this._focusedCol) + 3);
+ this._focusIndicator.setTop((this._focusedRow - firstRow) * rowHeight - 2);
+ this._focusIndicator.setLeft(paneModel.getColumnLeft(this._focusedCol) - 2);
+
+ this._focusIndicator.show();
+ }
+ }
+}
+
+
+// overridden
+qx.Proto.dispose = function() {
+ if (this.getDisposed()) {
+ return true;
+ }
+
+ if (this.getElement() != null) {
+ this.getElement().onselectstart = null;
+ }
+
+ this._verScrollBar.dispose();
+ this._horScrollBar.dispose();
+ this._header.dispose();
+ this._headerClipper.dispose();
+ this._spacer.dispose();
+ this._top.dispose();
+ this._tablePane.dispose();
+ this._paneClipper.dispose();
+
+ if (this._resizeLine != null) {
+ this._resizeLine.dispose();
+ }
+
+ this.removeEventListener("mousemove", this._onmousemove, this);
+ this.removeEventListener("mousedown", this._onmousedown, this);
+ this.removeEventListener("mouseup", this._onmouseup, this);
+ this.removeEventListener("click", this._onclick, this);
+ this.removeEventListener("dblclick", this._ondblclick, this);
+ this.removeEventListener("mouseout", this._onmouseout, this);
+
+ var tablePaneModel = this.getTablePaneModel();
+ if (tablePaneModel != null) {
+ tablePaneModel.removeEventListener("modelChanged", this._onPaneModelChanged, this);
+ }
+
+ return qx.ui.layout.VerticalBoxLayout.prototype.dispose.call(this);
+}
+
+
+/** {int} The minimum width a colum could get in pixels. */
+qx.Class.MIN_COLUMN_WIDTH = 10;
+
+/** {int} The radius of the resize region in pixels. */
+qx.Class.RESIZE_REGION_RADIUS = 5;
+
+/**
+ * (int) The number of pixels the mouse may move between mouse down and mouse up
+ * in order to count as a click.
+ */
+qx.Class.CLICK_TOLERANCE = 5;
+
+/**
+ * (int) The mask for the horizontal scroll bar.
+ * May be combined with {@link #VERTICAL_SCROLLBAR}.
+ *
+ * @see #getNeededScrollBars
+ */
+qx.Class.HORIZONTAL_SCROLLBAR = 1;
+
+/**
+ * (int) The mask for the vertical scroll bar.
+ * May be combined with {@link #HORIZONTAL_SCROLLBAR}.
+ *
+ * @see #getNeededScrollBars
+ */
+qx.Class.VERTICAL_SCROLLBAR = 2;
+
+/**
+ * (string) The correct value for the CSS style attribute "cursor" for the
+ * horizontal resize cursor.
+ */
+qx.Class.CURSOR_RESIZE_HORIZONTAL = (qx.sys.Client.getInstance().isGecko() && (qx.sys.Client.getInstance().getMajor() > 1 || qx.sys.Client.getInstance().getMinor() >= 8)) ? "ew-resize" : "e-resize";