diff options
author | Derrell Lipman <derrell@samba.org> | 2006-12-30 05:09:59 +0000 |
---|---|---|
committer | Gerald (Jerry) Carter <jerry@samba.org> | 2007-10-10 14:30:29 -0500 |
commit | 1170417ceeb8c49a46cda522a38eaa71c9cae30c (patch) | |
tree | dbf4c3fdcdb1af928dbd55f642ea40f00d09283a /webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table | |
parent | 23ccdca41670085da4486841b0d4900b4c8b02f3 (diff) | |
download | samba-1170417ceeb8c49a46cda522a38eaa71c9cae30c.tar.gz samba-1170417ceeb8c49a46cda522a38eaa71c9cae30c.tar.bz2 samba-1170417ceeb8c49a46cda522a38eaa71c9cae30c.zip |
r20414: Start to make SWAT usable by others. This is just a start...
(This used to be commit 26a34037a7ca6fbd05c5a6f7c2d5973e34bc6918)
Diffstat (limited to 'webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table')
25 files changed, 6651 insertions, 0 deletions
diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/AbstractDataCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/AbstractDataCellRenderer.js new file mode 100644 index 0000000000..d3d7950bd5 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/AbstractDataCellRenderer.js @@ -0,0 +1,127 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * An abstract data cell renderer that does the basic coloring + * (borders, selected look, ...). + */ +qx.OO.defineClass("qx.ui.table.AbstractDataCellRenderer", qx.ui.table.DataCellRenderer, +function() { + qx.ui.table.DataCellRenderer.call(this); +}); + + +// overridden +qx.Proto.createDataCellHtml = function(cellInfo) { + var AbstractDataCellRenderer = qx.ui.table.AbstractDataCellRenderer; + return AbstractDataCellRenderer.MAIN_DIV_START + this._getCellStyle(cellInfo) + + AbstractDataCellRenderer.MAIN_DIV_START_END + + this._getContentHtml(cellInfo) + AbstractDataCellRenderer.MAIN_DIV_END; +} + + +// overridden +qx.Proto.updateDataCellElement = function(cellInfo, cellElement) { + cellElement.innerHTML = this._getContentHtml(cellInfo); +} + + +/** + * Returns the CSS styles that should be applied to the main div of this cell. + * + * @param cellInfo {Map} The information about the cell. + * See {@link #createDataCellHtml}. + * @return the CSS styles of the main div. + */ +qx.Proto._getCellStyle = function(cellInfo) { + return cellInfo.style + qx.ui.table.AbstractDataCellRenderer.MAIN_DIV_STYLE; +} + + +/** + * Returns the HTML that should be used inside the main div of this cell. + * + * @param cellInfo {Map} The information about the cell. + * See {@link #createDataCellHtml}. + * @return {string} the inner HTML of the main div. + */ +qx.Proto._getContentHtml = function(cellInfo) { + return cellInfo.value; +} + + +qx.Proto.createDataCellHtml_array_join = function(cellInfo, htmlArr) { + var AbstractDataCellRenderer = qx.ui.table.AbstractDataCellRenderer; + + if (qx.ui.table.TablePane.USE_TABLE) { + htmlArr.push(AbstractDataCellRenderer.TABLE_TD); + htmlArr.push(cellInfo.styleHeight); + htmlArr.push("px"); + } else { + htmlArr.push(AbstractDataCellRenderer.ARRAY_JOIN_MAIN_DIV_LEFT); + htmlArr.push(cellInfo.styleLeft); + htmlArr.push(AbstractDataCellRenderer.ARRAY_JOIN_MAIN_DIV_WIDTH); + htmlArr.push(cellInfo.styleWidth); + htmlArr.push(AbstractDataCellRenderer.ARRAY_JOIN_MAIN_DIV_HEIGHT); + htmlArr.push(cellInfo.styleHeight); + htmlArr.push("px"); + } + + this._createCellStyle_array_join(cellInfo, htmlArr); + + htmlArr.push(AbstractDataCellRenderer.ARRAY_JOIN_MAIN_DIV_START_END); + + this._createContentHtml_array_join(cellInfo, htmlArr); + + if (qx.ui.table.TablePane.USE_TABLE) { + htmlArr.push(AbstractDataCellRenderer.TABLE_TD_END); + } else { + htmlArr.push(AbstractDataCellRenderer.ARRAY_JOIN_MAIN_DIV_END); + } +} + + +qx.Proto._createCellStyle_array_join = function(cellInfo, htmlArr) { + htmlArr.push(qx.ui.table.AbstractDataCellRenderer.MAIN_DIV_STYLE); +} + + +qx.Proto._createContentHtml_array_join = function(cellInfo, htmlArr) { + htmlArr.push(cellInfo.value); +} + + +qx.Class.MAIN_DIV_START = '<div style="'; +qx.Class.MAIN_DIV_START_END = '">'; +qx.Class.MAIN_DIV_END = '</div>'; +qx.Class.MAIN_DIV_STYLE = ';overflow:hidden;white-space:nowrap;border-right:1px solid #eeeeee;border-bottom:1px solid #eeeeee;padding-left:2px;padding-right:2px;cursor:default' + + (qx.sys.Client.getInstance().isMshtml() ? '' : ';-moz-user-select:none;'); + +qx.Class.ARRAY_JOIN_MAIN_DIV_LEFT = '<div style="position:absolute;left:'; +qx.Class.ARRAY_JOIN_MAIN_DIV_WIDTH = 'px;top:0px;width:'; +qx.Class.ARRAY_JOIN_MAIN_DIV_HEIGHT = 'px;height:'; +qx.Class.ARRAY_JOIN_MAIN_DIV_START_END = '">'; +qx.Class.ARRAY_JOIN_MAIN_DIV_END = '</div>'; + +qx.Class.TABLE_TD = '<td style="height:'; +qx.Class.TABLE_TD_END = '</td>';
\ No newline at end of file diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/AbstractTableModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/AbstractTableModel.js new file mode 100644 index 0000000000..99470e9361 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/AbstractTableModel.js @@ -0,0 +1,150 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * An abstract table model that performs the column handling, so subclasses only + * need to care for row handling. + */ +qx.OO.defineClass("qx.ui.table.AbstractTableModel", qx.ui.table.TableModel, +function() { + qx.ui.table.TableModel.call(this); + + this._columnIdArr = []; + this._columnNameArr = []; + this._columnIndexMap = {}; +}); + + +// overridden +qx.Proto.getColumnCount = function() { + return this._columnIdArr.length; +} + + +// overridden +qx.Proto.getColumnIndexById = function(columnId) { + return this._columnIndexMap[columnId]; +} + + +// overridden +qx.Proto.getColumnId = function(columnIndex) { + return this._columnIdArr[columnIndex]; +} + + +// overridden +qx.Proto.getColumnName = function(columnIndex) { + return this._columnNameArr[columnIndex]; +} + + +/** + * Sets the column IDs. These IDs may be used internally to identify a column. + * <p> + * Note: This will clear previously set column names. + * </p> + * + * @param columnIdArr {string[]} the IDs of the columns. + * @see #setColumns + */ +qx.Proto.setColumnIds = function(columnIdArr) { + this._columnIdArr = columnIdArr; + + // Create the reverse map + this._columnIndexMap = {}; + for (var i = 0; i < columnIdArr.length; i++) { + this._columnIndexMap[columnIdArr[i]] = i; + } + this._columnNameArr = new Array(columnIdArr.length); + + // Inform the listeners + if (!this._internalChange) { + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); + } +} + + +/** + * Sets the column names. These names will be shown to the user. + * <p> + * Note: The column IDs have to be defined before. + * </p> + * + * @param columnNameArr {string[]} the names of the columns. + * @see #setColumnIds + */ +qx.Proto.setColumnNamesByIndex = function(columnNameArr) { + if (this._columnIdArr.length != columnNameArr.length) { + throw new Error("this._columnIdArr and columnNameArr have different length: " + + this._columnIdArr.length + " != " + columnNameArr.length); + } + this._columnNameArr = columnNameArr; + + // Inform the listeners + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); +} + + +/** + * Sets the column names. These names will be shown to the user. + * <p> + * Note: The column IDs have to be defined before. + * </p> + * + * @param columnNameMap {Map} a map containing the column IDs as keys and the + * column name as values. + * @see #setColumnIds + */ +qx.Proto.setColumnNamesById = function(columnNameMap) { + this._columnNameArr = new Array(this._columnIdArr.length); + for (var i = 0; i < this._columnIdArr.length; ++i) { + this._columnNameArr[i] = columnNameMap[this._columnIdArr[i]]; + } +} + + +/** + * Sets the columns. + * + * @param columnNameArr {string[]} The column names. These names will be shown to + * the user. + * @param columnIdArr {string[] ? null} The column IDs. These IDs may be used + * internally to identify a column. If null, the column names are used as + * IDs. + */ +qx.Proto.setColumns = function(columnNameArr, columnIdArr) { + if (columnIdArr == null) { + columnIdArr = columnNameArr; + } + + if (columnIdArr.length != columnNameArr.length) { + throw new Error("columnIdArr and columnNameArr have different length: " + + columnIdArr.length + " != " + columnNameArr.length); + } + + this._internalChange = true; + this.setColumnIds(columnIdArr); + this._internalChange = false; + this.setColumnNamesByIndex(columnNameArr); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/BooleanDataCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/BooleanDataCellRenderer.js new file mode 100644 index 0000000000..13df2cd2f4 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/BooleanDataCellRenderer.js @@ -0,0 +1,48 @@ +/* ************************************************************************ + + 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) + * Carsten Lergenmueller (carstenl) + +************************************************************************ */ + +/* ************************************************************************ + +#module(ui_table) + +************************************************************************ */ + +/** + * A data cell renderer for boolean values. + */ +qx.OO.defineClass("qx.ui.table.BooleanDataCellRenderer", qx.ui.table.IconDataCellRenderer, +function() { + qx.ui.table.IconDataCellRenderer.call(this); + + this._iconUrlTrue = qx.manager.object.AliasManager.getInstance().resolvePath("widget/table/boolean-true.png"); + this._iconUrlFalse = qx.manager.object.AliasManager.getInstance().resolvePath("widget/table/boolean-false.png"); + this._iconUrlNull = qx.manager.object.AliasManager.getInstance().resolvePath("static/image/blank.gif"); + +}); + +//overridden +qx.Proto._identifyImage = function(cellInfo) { + var IconDataCellRenderer = qx.ui.table.IconDataCellRenderer; + var imageHints = { imageWidth:11, imageHeight:11 }; + switch (cellInfo.value) { + case true: imageHints.url = this._iconUrlTrue; break; + case false: imageHints.url = this._iconUrlFalse; break; + default: imageHints.url = this._iconUrlNull; break; + } + return imageHints; +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/CellEditorFactory.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/CellEditorFactory.js new file mode 100644 index 0000000000..817954f40f --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/CellEditorFactory.js @@ -0,0 +1,62 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A factory creating widgets to use for editing table cells. + */ +qx.OO.defineClass("qx.ui.table.CellEditorFactory", qx.core.Object, +function() { + qx.core.Object.call(this); +}); + + +/** + * Creates a cell editor. + * <p> + * The cellInfo map contains the following properties: + * <ul> + * <li>value (var): the cell's value.</li> + * <li>row (int): the model index of the row the cell belongs to.</li> + * <li>col (int): the model index of the column the cell belongs to.</li> + * <li>xPos (int): the x position of the cell in the table pane.</li> + * </ul> + * + * @param cellInfo {Map} A map containing the information about the cell to + * create. + * @return {qx.ui.core.Widget} the widget that should be used as cell editor. + */ +qx.Proto.createCellEditor = function(cellInfo) { + throw new Error("createCellEditor is abstract"); +} + + +/** + * Returns the current value of a cell editor. + * + * @param cellEditor {qx.ui.core.Widget} The cell editor formally created by + * {@link #createCellEditor}. + * @return {var} the current value from the editor. + */ +qx.Proto.getCellEditorValue = function(cellEditor) { + throw new Error("getCellEditorValue is abstract"); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/CheckBoxCellEditorFactory.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/CheckBoxCellEditorFactory.js new file mode 100644 index 0000000000..d5609a4b77 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/CheckBoxCellEditorFactory.js @@ -0,0 +1,43 @@ +/* ************************************************************************ + + qooxdoo - the new era of web development + + http://qooxdoo.org + + Copyright: + 2006 by David Perez + + License: + LGPL 2.1: http://www.gnu.org/licenses/lgpl.html + + Authors: + * David Perez (david-perez) + +************************************************************************ */ + +/* ************************************************************************ + +#module(ui_table) + +************************************************************************ */ + +/** + * For editing boolean data in a checkbox. It is advisable to use this in conjuntion with BooleanDataCellRenderer. + */ +qx.OO.defineClass("qx.ui.table.CheckBoxCellEditorFactory", qx.ui.table.CellEditorFactory, function() { + qx.ui.table.CellEditorFactory.call(this); +}); + +// overridden +qx.Proto.createCellEditor = function(cellInfo) { + var editor = new qx.ui.form.CheckBox; + with (editor) { + setChecked(cellInfo.value); + } + return editor; +} + +// overridden +qx.Proto.getCellEditorValue = function(cellEditor) { + return cellEditor.getChecked(); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DataCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DataCellRenderer.js new file mode 100644 index 0000000000..46f808df32 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DataCellRenderer.js @@ -0,0 +1,80 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A cell renderer for data cells. + */ +qx.OO.defineClass("qx.ui.table.DataCellRenderer", qx.core.Object, +function() { + qx.core.Object.call(this); +}); + + +/** + * Creates the HTML for a data cell. + * <p> + * The cellInfo map contains the following properties: + * <ul> + * <li>value (var): the cell's value.</li> + * <li>rowData (var): contains the row data for the row, the cell belongs to. + * The kind of this object depends on the table model, see + * {@link TableModel#getRowData()}</li> + * <li>row (int): the model index of the row the cell belongs to.</li> + * <li>col (int): the model index of the column the cell belongs to.</li> + * <li>table (qx.ui.table.Table): the table the cell belongs to.</li> + * <li>xPos (int): the x position of the cell in the table pane.</li> + * <li>selected (boolean): whether the cell is selected.</li> + * <li>focusedCol (boolean): whether the cell is in the same column as the + * focused cell.</li> + * <li>focusedRow (boolean): whether the cell is in the same row as the + * focused cell.</li> + * <li>editable (boolean): whether the cell is editable.</li> + * <li>style (string): The CSS styles that should be applied to the outer HTML + * element.</li> + * </ul> + * + * @param cellInfo {Map} A map containing the information about the cell to + * create. + * @return {string} the HTML of the data cell. + */ +qx.Proto.createDataCellHtml = function(cellInfo) { + throw new Error("createDataCellHtml is abstract"); +} + + +/** + * Updates a data cell. + * + * @param cellInfo {Map} A map containing the information about the cell to + * create. This map has the same structure as in {@link #createDataCell}. + * @param cellElement {element} the DOM element that renders the data cell. This + * is the same element formally created by the HTML from {@link #createDataCell}. + */ +qx.Proto.updateDataCellElement = function(cellInfo, cellElement) { + throw new Error("updateDataCellElement is abstract"); +} + + +qx.Proto.createDataCellHtml_array_join = function(cellInfo, htmlArr) { + throw new Error("createDataCellHtml_array_join is abstract"); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DataRowRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DataRowRenderer.js new file mode 100644 index 0000000000..9cd4c86961 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DataRowRenderer.js @@ -0,0 +1,54 @@ +/* ************************************************************************
+
+ 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)
+
+************************************************************************ */
+
+/**
+ * A cell renderer for data rows.
+ */
+qx.OO.defineClass("qx.ui.table.DataRowRenderer", qx.core.Object,
+function() {
+ qx.core.Object.call(this);
+});
+
+
+/**
+ * Updates a data row.
+ * <p>
+ * The rowInfo map contains the following properties:
+ * <ul>
+ * <li>rowData (var): contains the row data for the row.
+ * The kind of this object depends on the table model, see
+ * {@link TableModel#getRowData()}</li>
+ * <li>row (int): the model index of the row.</li>
+ * <li>selected (boolean): whether a cell in this row is selected.</li>
+ * <li>focusedRow (boolean): whether the focused cell is in this row.</li>
+ * <li>table (qx.ui.table.Table): the table the row belongs to.</li>
+ * </ul>
+ *
+ * @param rowInfo {Map} A map containing the information about the row to
+ * update. This map has the same structure as in {@link #createDataCell}.
+ * @param cellElement {element} the DOM element that renders the data rot. This
+ * is the same element formally created by the HTML from {@link #createDataCell}.
+ */
+qx.Proto.updateDataRowElement = function(rowInfo, rowElement) {
+ throw new Error("updateDataRowElement is abstract");
+}
diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultDataCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultDataCellRenderer.js new file mode 100644 index 0000000000..4de4341037 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultDataCellRenderer.js @@ -0,0 +1,189 @@ +/* ************************************************************************ + + 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) + +// This is needed because of the instantiation at the end of this file. +// I don't think this is a good idea. (wpbasti) +#require(qx.util.format.NumberFormat) + +************************************************************************ */ + +/** + * The default data cell renderer. + */ +qx.OO.defineClass("qx.ui.table.DefaultDataCellRenderer", qx.ui.table.AbstractDataCellRenderer, +function() { + qx.ui.table.AbstractDataCellRenderer.call(this); +}); + + +/** + * Whether the alignment should automatically be set according to the cell value. + * If true numbers will be right-aligned. + */ +qx.OO.addProperty({ name:"useAutoAlign", type:"boolean", defaultValue:true, allowNull:false }); + + +// overridden +qx.Proto._getCellStyle = function(cellInfo) { + var style = qx.ui.table.AbstractDataCellRenderer.prototype._getCellStyle(cellInfo); + + var stylesToApply = this._getStyleFlags(cellInfo); + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ALIGN_RIGHT){ + style += ";text-align:right"; + } + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_BOLD){ + style += ";font-weight:bold"; + } + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ITALIC){ + style += ";font-style:italic"; + } + + return style; +} + +/** + * Determines the styles to apply to the cell + * + * @param cellInfo {Object} cellInfo of the cell + * @return the sum of any of the STYLEFLAGS defined below + */ +qx.Proto._getStyleFlags = function(cellInfo) { + if (this.getUseAutoAlign()) { + if (typeof cellInfo.value == "number") { + return qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ALIGN_RIGHT; + } + } +} + + +// overridden +qx.Proto._getContentHtml = function(cellInfo) { + return qx.ui.table.DefaultDataCellRenderer.escapeHtml(this._formatValue(cellInfo)); +} + + +// overridden +qx.Proto.updateDataCellElement = function(cellInfo, cellElement) { + var style = qx.ui.table.AbstractDataCellRenderer.prototype._getCellStyle(cellInfo); + + var stylesToApply = this._getStyleFlags(cellInfo); + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ALIGN_RIGHT){ + cellElement.style.textAlign = "right"; + } else { + cellElement.style.textAlign = ""; + } + + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_BOLD){ + cellElement.style.fontWeight = "bold"; + } else { + cellElement.style.fontWeight = ""; + } + + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ITALIC){ + cellElement.style.fontStyle = "ital"; + } else { + cellElement.style.fontStyle = ""; + } + + var textNode = cellElement.firstChild; + if (textNode != null) { + textNode.nodeValue = this._formatValue(cellInfo); + } else { + cellElement.innerHTML = qx.ui.table.DefaultDataCellRenderer.escapeHtml(this._formatValue(cellInfo)); + } +} + + +/** + * Formats a value. + * + * @param cellInfo {Map} A map containing the information about the cell to + * create. This map has the same structure as in + * {@link DataCellRenderer#createDataCell}. + * @return {string} the formatted value. + */ +qx.Proto._formatValue = function(cellInfo) { + var value = cellInfo.value; + if (value == null) { + return ""; + } else if (typeof value == "number") { + return qx.ui.table.DefaultDataCellRenderer._numberFormat.format(value); + } else if (value instanceof Date) { + return qx.util.format.DateFormat.getDateInstance().format(value); + } else { + return value; + } +} + + +qx.Proto._createCellStyle_array_join = function(cellInfo, htmlArr) { + qx.ui.table.AbstractDataCellRenderer.prototype._createCellStyle_array_join(cellInfo, htmlArr); + + var stylesToApply = this._getStyleFlags(cellInfo); + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ALIGN_RIGHT){ + htmlArr.push(";text-align:right"); + } + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_BOLD){ + htmlArr.push(";font-weight:bold"); + } + if (stylesToApply & qx.ui.table.DefaultDataCellRenderer.STYLEFLAG_ITALIC){ + htmlArr.push(";font-style:italic"); + } +} + + +qx.Proto._createContentHtml_array_join = function(cellInfo, htmlArr) { + htmlArr.push(qx.ui.table.DefaultDataCellRenderer.escapeHtml(this._formatValue(cellInfo))); +} + + +/** + * Escapes special HTML characters by their entities. + * + * @param html {string} The HTML to escape. + * @return {string} The escaped string showing HTML code as plain text. + */ +qx.Class.escapeHtml = function(html) { + return html.replace(/[<>&]/gi, qx.ui.table.DefaultDataCellRenderer._escapeHtmlReplacer); +} + + +/** + * Helper method for {@link #escapeHtml}. + */ +qx.Class._escapeHtmlReplacer = function(str) { + switch(str) { + case "<": return "<"; + case ">": return ">"; + case "&": return "&"; + } +} + + +qx.Class._numberFormat = new qx.util.format.NumberFormat(); +qx.Class._numberFormat.setMaximumFractionDigits(2); + +qx.Class.STYLEFLAG_ALIGN_RIGHT = 1; +qx.Class.STYLEFLAG_BOLD = 2; +qx.Class.STYLEFLAG_ITALIC = 4; + + + diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultDataRowRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultDataRowRenderer.js new file mode 100644 index 0000000000..8fd2198cd4 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultDataRowRenderer.js @@ -0,0 +1,106 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * The default data row renderer. + */ +qx.OO.defineClass("qx.ui.table.DefaultDataRowRenderer", qx.ui.table.DataRowRenderer, +function() { + qx.ui.table.DataRowRenderer.call(this); +}); + + +/** Whether the focused row should be highlighted. */ +qx.OO.addProperty({ name:"highlightFocusRow", type:"boolean", allowNull:false, defaultValue:true}); + +/** + * Whether the focused row and the selection should be grayed out when the table + * hasn't the focus. + */ +qx.OO.addProperty({ name:"visualizeFocusedState", type:"boolean", allowNull:false, defaultValue:true}); + + +// overridden +qx.Proto.updateDataRowElement = function(rowInfo, rowElem) { + var clazz = qx.ui.table.DefaultDataRowRenderer; + + if (rowInfo.focusedRow && this.getHighlightFocusRow()) { + if (rowInfo.table.getFocused() || !this.getVisualizeFocusedState()) { + rowElem.style.backgroundColor = rowInfo.selected ? clazz.BGCOL_FOCUSED_SELECTED : clazz.BGCOL_FOCUSED; + } else { + rowElem.style.backgroundColor = rowInfo.selected ? clazz.BGCOL_FOCUSED_SELECTED_BLUR : clazz.BGCOL_FOCUSED_BLUR; + } + } else { + if (rowInfo.selected) { + if (rowInfo.table.getFocused() || !this.getVisualizeFocusedState()) { + rowElem.style.backgroundColor = clazz.BGCOL_SELECTED; + } else { + rowElem.style.backgroundColor = clazz.BGCOL_SELECTED_BLUR; + } + } else { + rowElem.style.backgroundColor = (rowInfo.row % 2 == 0) ? clazz.BGCOL_EVEN : clazz.BGCOL_ODD; + } + } + rowElem.style.color = rowInfo.selected ? clazz.COL_SELECTED : clazz.COL_NORMAL; +} + + +qx.Proto._createRowStyle_array_join = function(rowInfo, htmlArr) { + var clazz = qx.ui.table.DefaultDataRowRenderer; + + htmlArr.push(clazz.ARRAY_JOIN_BG_COLOR); + if (rowInfo.focusedRow && this.getHighlightFocusRow()) { + if (rowInfo.table.getFocused() || !this.getVisualizeFocusedState()) { + htmlArr.push(rowInfo.selected ? clazz.BGCOL_FOCUSED_SELECTED : clazz.BGCOL_FOCUSED); + } else { + htmlArr.push(rowInfo.selected ? clazz.BGCOL_FOCUSED_SELECTED_BLUR : clazz.BGCOL_FOCUSED_BLUR); + } + } else { + if (rowInfo.selected) { + if (rowInfo.table.getFocused() || !this.getVisualizeFocusedState()) { + htmlArr.push(clazz.BGCOL_SELECTED); + } else { + htmlArr.push(clazz.BGCOL_SELECTED_BLUR); + } + } else { + htmlArr.push((rowInfo.row % 2 == 0) ? clazz.BGCOL_EVEN : clazz.BGCOL_ODD); + } + } + htmlArr.push(clazz.ARRAY_JOIN_COLOR); + htmlArr.push(rowInfo.selected ? clazz.COL_SELECTED : clazz.COL); +} + + +qx.Class.BGCOL_FOCUSED_SELECTED = "#5a8ad3"; +qx.Class.BGCOL_FOCUSED_SELECTED_BLUR = "#b3bac6"; +qx.Class.BGCOL_FOCUSED = "#ddeeff"; +qx.Class.BGCOL_FOCUSED_BLUR = "#dae0e7"; +qx.Class.BGCOL_SELECTED = "#335ea8"; +qx.Class.BGCOL_SELECTED_BLUR = "#989ea8"; +qx.Class.BGCOL_EVEN = "#faf8f3"; +qx.Class.BGCOL_ODD = "white"; +qx.Class.COL_SELECTED = "white"; +qx.Class.COL_NORMAL = "black"; + +qx.Class.ARRAY_JOIN_BG_COLOR = ";background-color:"; +qx.Class.ARRAY_JOIN_COLOR = ';color:'; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultHeaderCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultHeaderCellRenderer.js new file mode 100644 index 0000000000..060b095a14 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/DefaultHeaderCellRenderer.js @@ -0,0 +1,63 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * The default header cell renderer. + */ +qx.OO.defineClass("qx.ui.table.DefaultHeaderCellRenderer", qx.ui.table.HeaderCellRenderer, +function() { + qx.ui.table.HeaderCellRenderer.call(this); +}); + + +// overridden +qx.Proto.createHeaderCell = function(cellInfo) { + var widget = new qx.ui.basic.Atom(); + widget.setAppearance("table-header-cell"); + + this.updateHeaderCell(cellInfo, widget); + + return widget; +} + + +// overridden +qx.Proto.updateHeaderCell = function(cellInfo, cellWidget) { + var DefaultHeaderCellRenderer = qx.ui.table.DefaultHeaderCellRenderer; + + cellWidget.setLabel(cellInfo.name); + + cellWidget.setIcon(cellInfo.sorted ? (cellInfo.sortedAscending ? "widget/table/ascending.png" : "widget/table/descending.png") : null); + cellWidget.setState(DefaultHeaderCellRenderer.STATE_SORTED, cellInfo.sorted); + cellWidget.setState(DefaultHeaderCellRenderer.STATE_SORTED_ASCENDING, cellInfo.sortedAscending); +} + +/** + * (string) The state which will be set for header cells of sorted columns. + */ +qx.Class.STATE_SORTED = "sorted"; + +/** + * (string) The state which will be set when sorting is ascending. + */ +qx.Class.STATE_SORTED_ASCENDING = "sortedAscending"; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/HeaderCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/HeaderCellRenderer.js new file mode 100644 index 0000000000..2108778efb --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/HeaderCellRenderer.js @@ -0,0 +1,69 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A cell renderer for header cells. + */ +qx.OO.defineClass("qx.ui.table.HeaderCellRenderer", qx.core.Object, +function() { + qx.core.Object.call(this); +}); + + +/** + * Creates a header cell. + * <p> + * The cellInfo map contains the following properties: + * <ul> + * <li>col (int): the model index of the column.</li> + * <li>xPos (int): the x position of the column in the table pane.</li> + * <li>name (string): the name of the column.</li> + * <li>editable (boolean): whether the column is editable.</li> + * <li>sorted (boolean): whether the column is sorted.</li> + * <li>sortedAscending (boolean): whether sorting is ascending.</li> + * </ul> + * + * @param cellInfo {Map} A map containing the information about the cell to + * create. + * @return {qx.ui.core.Widget} the widget that renders the header cell. + */ +qx.Proto.createHeaderCell = function(cellInfo) { + throw new Error("createHeaderCell is abstract"); +} + + +/** + * Updates a header cell. + * + * @param cellInfo {Map} A map containing the information about the cell to + * create. This map has the same structure as in {@link #createHeaderCell}. + * @param cellWidget {qx.ui.core.Widget} the widget that renders the header cell. This is + * the same widget formally created by {@link #createHeaderCell}. + */ +qx.Proto.updateHeaderCell = function(cellInfo, cellWidget) { + throw new Error("updateHeaderCell is abstract"); +} + + +/** The preferred height of cells created by this header renderer. */ +qx.OO.addProperty({ name:"prefferedCellHeight", type:"number", defaultValue:16, allowNull:false }); diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/IconDataCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/IconDataCellRenderer.js new file mode 100644 index 0000000000..b4a717527b --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/IconDataCellRenderer.js @@ -0,0 +1,182 @@ +/* ************************************************************************ + + 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) + * Carsten Lergenmueller (carstenl) + +************************************************************************ */ + +/* ************************************************************************ + +#module(ui_table) + +************************************************************************ */ + +/** + * A data cell renderer for boolean values. + */ +qx.OO.defineClass("qx.ui.table.IconDataCellRenderer", qx.ui.table.AbstractDataCellRenderer, +function() { + qx.ui.table.AbstractDataCellRenderer.call(this); + this.IMG_BLANK_URL = qx.manager.object.AliasManager.getInstance().resolvePath("static/image/blank.gif"); +}); + + +/** + * Identifies the Image to show. + * + * @param cellInfo {Map} The information about the cell. + * See {@link #createDataCellHtml}. + * @return {Map} A map having the following attributes: + * <ul> + * <li>"url": (type string) must be the URL of the image to show.</li> + * <li>"imageWidth": (type int) the width of the image in pixels.</li> + * <li>"imageHeight": (type int) the height of the image in pixels.</li> + * <li>"tooltip": (type string) must be the image tooltip text.</li> + * </ul> + */ +qx.Proto._identifyImage = function(cellInfo) { + throw new Error("_identifyImage is abstract"); +} + + +/** + * Retrieves the image infos. + * + * @param cellInfo {Map} The information about the cell. + * See {@link #createDataCellHtml}. + * @return {Map} Map with an "url" attribute (type string) + * holding the URL of the image to show + * and a "tooltip" attribute + * (type string) being the tooltip text (or null if none was specified) + * + */ +qx.Proto._getImageInfos= function(cellInfo) { + // Query the subclass about image and tooltip + var urlAndTooltipMap = this._identifyImage(cellInfo); + + // If subclass refuses to give map, construct it + if (urlAndTooltipMap == null || typeof urlAndTooltipMap == "string"){ + urlAndTooltipMap = {url:urlAndTooltipMap, tooltip:null}; + } + + // If subclass gave null as url, replace with url to empty image + if (urlAndTooltipMap.url == null){ + urlAndTooltipMap.url = this.IMG_BLANK_URL; + } + + return urlAndTooltipMap; +} + +// overridden +qx.Proto._getCellStyle = function(cellInfo) { + var style = qx.ui.table.AbstractDataCellRenderer.prototype._getCellStyle(cellInfo); + style += qx.ui.table.IconDataCellRenderer.MAIN_DIV_STYLE; + return style; +} + + +// overridden +qx.Proto._getContentHtml = function(cellInfo) { + var IconDataCellRenderer = qx.ui.table.IconDataCellRenderer; + + var urlAndToolTip = this._getImageInfos(cellInfo); + var html = IconDataCellRenderer.IMG_START; + if (qx.sys.Client.getInstance().isMshtml() && /\.png$/i.test(urlAndToolTip.url)) { + html += qx.manager.object.AliasManager.getInstance().resolvePath("static/image/blank.gif") + + '" style="filter:' + "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + urlAndToolTip.url + "',sizingMethod='scale')"; + } else { + html += urlAndToolTip.url + '" style="'; + } + + if (urlAndToolTip.imageWidth && urlAndToolTip.imageHeight) { + html += ';width:' + urlAndToolTip.imageWidth + 'px' + + ';height:' + urlAndToolTip.imageHeight + 'px'; + } + + var tooltip = urlAndToolTip.tooltip; + if (tooltip != null){ + html += IconDataCellRenderer.IMG_TITLE_START + tooltip; + } + html += IconDataCellRenderer.IMG_END; + return html; +} + + +// overridden +qx.Proto.updateDataCellElement = function(cellInfo, cellElement) { + // Set image and tooltip text + var urlAndToolTip = this._getImageInfos(cellInfo); + var img = cellElement.firstChild; + if (qx.sys.Client.getInstance().isMshtml()) { + if (/\.png$/i.test(urlAndToolTip.url)) { + img.src = qx.manager.object.AliasManager.getInstance().resolvePath("static/image/blank.gif"); + img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + urlAndToolTip.url + "',sizingMethod='scale')"; + } else { + img.src = urlAndToolTip.url; + img.style.filter = ""; + } + } else { + img.src = urlAndToolTip.url; + } + + if (urlAndToolTip.imageWidth && urlAndToolTip.imageHeight) { + img.style.width = urlAndToolTip.imageWidth + "px"; + img.style.height = urlAndToolTip.imageHeight + "px"; + } + + if (urlAndToolTip.tooltip != null){ + img.text = urlAndToolTip.tooltip; + } +} + + +// overridden +qx.Proto._createCellStyle_array_join = function(cellInfo, htmlArr) { + qx.ui.table.AbstractDataCellRenderer.prototype._createCellStyle_array_join(cellInfo, htmlArr); + + htmlArr.push(qx.ui.table.IconDataCellRenderer.MAIN_DIV_STYLE); +} + +qx.Proto._createContentHtml_array_join = function(cellInfo, htmlArr) { + var IconDataCellRenderer = qx.ui.table.IconDataCellRenderer; + + if (qx.ui.table.TablePane.USE_TABLE) { + htmlArr.push(IconDataCellRenderer.TABLE_DIV); + htmlArr.push(cellInfo.styleHeight - 2); // -1 for the border, -1 for the padding + htmlArr.push(IconDataCellRenderer.TABLE_DIV_CLOSE); + } + + htmlArr.push(IconDataCellRenderer.IMG_START); + var urlAndToolTip = this._getImageInfos(cellInfo); + htmlArr.push(urlAndToolTip.url); + var tooltip = urlAndToolTip.tooltip; + if (tooltip != null){ + IconDataCellRenderer.IMG_TITLE_START; + htmlArr.push(tooltip); + } + htmlArr.push(IconDataCellRenderer.IMG_END); + + if (qx.ui.table.TablePane.USE_TABLE) { + htmlArr.push(IconDataCellRenderer.TABLE_DIV_END); + } +} + +qx.Class.MAIN_DIV_STYLE = ';text-align:center;padding-top:1px;'; +qx.Class.IMG_START = '<img src="'; +qx.Class.IMG_END = '"/>'; +qx.Class.IMG_TITLE_START = '" title="'; +qx.Class.TABLE_DIV = '<div style="overflow:hidden;height:'; +qx.Class.TABLE_DIV_CLOSE = 'px">'; +qx.Class.TABLE_DIV_END = '</div>'; + diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/IconHeaderCellRenderer.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/IconHeaderCellRenderer.js new file mode 100644 index 0000000000..51e653f5c4 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/IconHeaderCellRenderer.js @@ -0,0 +1,84 @@ +/* ************************************************************************ + + 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) + * Carsten Lergenmueller (carstenl) + +************************************************************************ */ + +/* ************************************************************************ + +#module(ui_table) + +************************************************************************ */ + +/** + * A header cell renderer which renders an icon (only). The icon cannot be combined + * with text. + * + * @param iconUrl {string} URL to the icon to show + * @param tooltip {string ? ""} Text of the tooltip to show if the mouse hovers over the + * icon + * + */ +qx.OO.defineClass("qx.ui.table.IconHeaderCellRenderer", qx.ui.table.DefaultHeaderCellRenderer, +function(iconUrl, tooltip) { + qx.ui.table.DefaultHeaderCellRenderer.call(this); + if (iconUrl == null){ + iconUrl = ""; + } + this.setIconUrl(iconUrl); + this.setToolTip(tooltip); +}); + +/** + * URL of the icon to show + */ +qx.OO.addProperty({ name:"iconUrl", type:"string", defaultValue:"", allowNull:false }); + +/** + * ToolTip to show if the mouse hovers of the icon + */ +qx.OO.addProperty({ name:"toolTip", type:"string", defaultValue:null, allowNull:true }); + +// overridden +qx.Proto.updateHeaderCell = function(cellInfo, cellWidget) { + qx.ui.table.DefaultHeaderCellRenderer.prototype.updateHeaderCell.call(this, cellInfo, cellWidget); + + // Set URL to icon + var img = cellWidget.getUserData("qx_ui_table_IconHeaderCellRenderer_icon"); + if (img == null){ + img = new qx.ui.basic.Image(); + cellWidget.setUserData("qx_ui_table_IconHeaderCellRenderer_icon", img); + cellWidget.addAtBegin(img); + } + img.setSource(this.getIconUrl()); + + // Set image tooltip if given + var widgetToolTip = cellWidget.getToolTip(); + if (this.getToolTip() != null){ + + //Create tooltip if necessary + if (true || widgetToolTip == null ){ + widgetToolTip = new qx.ui.popup.ToolTip(this.getToolTip()); + cellWidget.setToolTip(widgetToolTip); + //this.debug("Creating tooltip"); + } + + //Set tooltip text + widgetToolTip.getAtom().setLabel(this.getToolTip()); + //this.debug("Setting tooltip text " + this.getToolTip()); + } + +} + diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/RemoteTableModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/RemoteTableModel.js new file mode 100644 index 0000000000..ebd1be8f53 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/RemoteTableModel.js @@ -0,0 +1,435 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A table model that loads its data from a backend. + * <p> + * Only those rows are loaded that are near the area the user is currently + * viewing. If the user scrolls, the rows he will see soon are loaded + * asynchroniously in the background. All loaded data is managed in a cache that + * automatically removes the last resently used rows when it gets full. + * <p> + * This class is abstract: The actual loading of row data must be done by + * subclasses. + */ +qx.OO.defineClass("qx.ui.table.RemoteTableModel", qx.ui.table.AbstractTableModel, +function() { + qx.ui.table.AbstractTableModel.call(this); + + this._sortColumnIndex = -1; + this._sortAscending = true; + this._rowCount = -1; + + this._lruCounter = 0; + this._firstLoadingBlock = -1; + this._firstRowToLoad = -1; + this._lastRowToLoad = -1; + this._ignoreCurrentRequest = false; + + this._rowBlockCache = {}; + this._rowBlockCount = 0; +}); + + +/** The number of rows that are stored in one cache block. */ +qx.OO.addProperty({ name:"blockSize", type:"number", defaultValue:50, allowNull:false }); + +/** The maximum number of row blocks kept in the cache. */ +qx.OO.addProperty({ name:"maxCachedBlockCount", type:"number", defaultValue:15, allowNull:false }); + +/** + * Whether to clear the cache when some rows are removed. + * If false the rows are removed locally in the cache. + */ +qx.OO.addProperty({ name:"clearCacheOnRemove", type:"boolean", defaultValue:false, allowNull:false }); + + +// overridden +qx.Proto.getRowCount = function() { + if (this._rowCount == -1) { + this._loadRowCount(); + + // NOTE: _loadRowCount may set this._rowCount + return (this._rowCount == -1) ? 0 : this._rowCount; + } else { + return this._rowCount; + } +} + + +/** + * Loads the row count from the server. + * <p> + * Implementing classes have to call {@link _onRowDataLoaded()} when the server + * response arrived. That method has to be called! Even when there was an error. + */ +qx.Proto._loadRowCount = function() { + throw new Error("_loadRowCount is abstract"); +}; + + +/** + * Sets the row count. + * <p> + * Has to be called by {@link _loadRowCount()}. + * + * @param rowCount {int} the number of rows in this model or null if loading. + */ +qx.Proto._onRowCountLoaded = function(rowCount) { + this.debug("row count loaded: " + rowCount); + if (rowCount == null) { + rowCount = 0; + } + this._rowCount = rowCount; + + // Inform the listeners + var data = { firstRow:0, lastRow:rowCount - 1, firstColumn:0, lastColumn:this.getColumnCount() - 1 }; + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); +}; + + +/** + * Reloads the model and clears the local cache. + */ +qx.Proto.reloadData = function() { + this.clearCache(); + + // If there is currently a request on its way, then this request will bring + // obsolete data -> Ignore it + if (this._firstLoadingBlock != -1) { + this._ignoreCurrentRequest = true; + } + + // NOTE: This will inform the listeners as soon as the new row count is known + this._loadRowCount(); +}; + + +/** + * Clears the cache. + */ +qx.Proto.clearCache = function() { + this._rowBlockCache = {}; + this._rowBlockCount = 0; +}; + + +// overridden +qx.Proto.prefetchRows = function(firstRowIndex, lastRowIndex) { + // this.debug("Prefetch wanted: " + firstRowIndex + ".." + lastRowIndex); + if (this._firstLoadingBlock == -1) { + var blockSize = this.getBlockSize(); + var totalBlockCount = Math.ceil(this._rowCount / blockSize); + + // There is currently no request running -> Start a new one + // NOTE: We load one more block above and below to have a smooth + // scrolling into the next block without blank cells + var firstBlock = parseInt(firstRowIndex / blockSize) - 1; + if (firstBlock < 0) { + firstBlock = 0; + } + var lastBlock = parseInt(lastRowIndex / blockSize) + 1; + if (lastBlock >= totalBlockCount) { + lastBlock = totalBlockCount - 1; + } + + // Check which blocks we have to load + var firstBlockToLoad = -1; + var lastBlockToLoad = -1; + for (var block = firstBlock; block <= lastBlock; block++) { + if (this._rowBlockCache[block] == null || this._rowBlockCache[block].isDirty) { + // We don't have this block + if (firstBlockToLoad == -1) { + firstBlockToLoad = block; + } + lastBlockToLoad = block; + } + } + + // Load the blocks + if (firstBlockToLoad != -1) { + this._firstRowToLoad = -1; + this._lastRowToLoad = -1; + + this._firstLoadingBlock = firstBlockToLoad; + + this.debug("Starting server request. rows: " + firstRowIndex + ".." + lastRowIndex + ", blocks: " + firstBlockToLoad + ".." + lastBlockToLoad); + this._loadRowData(firstBlockToLoad * blockSize, (lastBlockToLoad + 1) * blockSize - 1); + } + } else { + // There is already a request running -> Remember this request + // so it can be executed after the current one is finished. + this._firstRowToLoad = firstRowIndex; + this._lastRowToLoad = lastRowIndex; + } +}; + + +/** + * Loads some row data from the server. + * <p> + * Implementing classes have to call {@link _onRowDataLoaded()} when the server + * response arrived. That method has to be called! Even when there was an error. + * + * @param firstRow {int} The index of the first row to load. + * @param lastRow {int} The index of the last row to load. + */ +qx.Proto._loadRowData = function(firstRow, lastRow) { + throw new Error("_loadRowCount is abstract"); +}; + + +/** + * Sets row data. + * <p> + * Has to be called by {@link _loadRowData()}. + * + * @param rowDataArr {Map[]} the loaded row data or null if there was an error. + */ +qx.Proto._onRowDataLoaded = function(rowDataArr) { + if (rowDataArr != null && ! this._ignoreCurrentRequest) { + var blockSize = this.getBlockSize(); + var blockCount = Math.ceil(rowDataArr.length / blockSize); + if (blockCount == 1) { + // We got one block -> Use the rowData directly + this._setRowBlockData(this._firstLoadingBlock, rowDataArr); + } else { + // We got more than one block -> We've to split the rowData + for (var i = 0; i < blockCount; i++) { + var rowOffset = i * blockSize; + var blockRowData = []; + var mailCount = Math.min(blockSize, rowDataArr.length - rowOffset); + for (var row = 0; row < mailCount; row++) { + blockRowData.push(rowDataArr[rowOffset + row]); + } + + this._setRowBlockData(this._firstLoadingBlock + i, blockRowData); + } + } + this.debug("Got server answer. blocks: " + this._firstLoadingBlock + ".." + (this._firstLoadingBlock + blockCount - 1) + ". mail count: " + rowDataArr.length + " block count:" + blockCount); + + // Inform the listeners + var data = { + firstRow:this._firstLoadingBlock * blockSize, + lastRow:(this._firstLoadingBlock + blockCount + 1) * blockSize - 1, + firstColumn:0, + lastColumn:this.getColumnCount() - 1 + }; + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); + } + + // We're not loading any blocks any more + this._firstLoadingBlock = -1; + this._ignoreCurrentRequest = false; + + // Check whether we have to start a new request + if (this._firstRowToLoad != -1) { + this.prefetchRows(this._firstRowToLoad, this._lastRowToLoad); + } +}; + + +/** + * Sets the data of one block. + * + * @param block {int} the index of the block. + * @param rowDataArr {var[][]} the data to set. + */ +qx.Proto._setRowBlockData = function(block, rowDataArr) { + if (this._rowBlockCache[block] == null) { + // This is a new block -> Check whether we have to remove another block first + this._rowBlockCount++; + + while (this._rowBlockCount > this.getMaxCachedBlockCount()) { + // Find the last recently used block + // NOTE: We never remove block 0 and 1 + var lruBlock; + var minLru = this._lruCounter; + for (var currBlock in this._rowBlockCache) { + var currLru = this._rowBlockCache[currBlock].lru; + if (currLru < minLru && currBlock > 1) { + minLru = currLru; + lruBlock = currBlock; + } + } + + // Remove that block + this.debug("Removing block: " + lruBlock + ". current LRU: " + this._lruCounter); + delete this._rowBlockCache[lruBlock]; + this._rowBlockCount--; + } + } + + this._rowBlockCache[block] = { lru:++this._lruCounter, rowDataArr:rowDataArr }; +}; + + +/** + * Removes a rows from the model. + * + * @param rowIndex {int} the index of the row to remove. + */ +qx.Proto.removeRow = function(rowIndex) { + if (this.getClearCacheOnRemove()) { + this.clearCache(); + + // Inform the listeners + var data = { firstRow:0, lastRow:rowCount - 1, firstColumn:0, lastColumn:this.getColumnCount() - 1 }; + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); + } else { + var blockSize = this.getBlockSize(); + var blockCount = Math.ceil(this.getRowCount() / blockSize); + var startBlock = parseInt(rowIndex / blockSize); + + // Remove the row and move the rows of all following blocks + for (var block = startBlock; block <= blockCount; block++) { + var blockData = this._rowBlockCache[block]; + if (blockData != null) { + // Remove the row in the start block + // NOTE: In the other blocks the first row is removed + // (This is the row that was) + var removeIndex = 0; + if (block == startBlock) { + removeIndex = rowIndex - block * blockSize; + } + blockData.rowDataArr.splice(removeIndex, 1); + + if (block == blockCount - 1) { + // This is the last block + if (blockData.rowDataArr.length == 0) { + // It is empty now -> Remove it + delete this._rowBlockCache[block]; + } + } else { + // Try to copy the first row of the next block to the end of this block + // so this block can stays clean + var nextBlockData = this._rowBlockCache[block + 1]; + if (nextBlockData != null) { + blockData.rowDataArr.push(nextBlockData.rowDataArr[0]); + } else { + // There is no row to move -> Mark this block as dirty + blockData.isDirty = true; + } + } + } + } + + if (this._rowCount != -1) { + this._rowCount--; + } + + // Inform the listeners + if (this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED)) { + var data = { firstRow:rowIndex, lastRow:this.getRowCount() - 1, firstColumn:0, lastColumn:this.getColumnCount() - 1 }; + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); + } + } +}; + + +/** + * <p>See overridden method for details.</p> + * + * @param rowIndex {int} the model index of the row. + * @return {Object} Map containing a value for each column. + */ +qx.Proto.getRowData = function(rowIndex) { + var blockSize = this.getBlockSize(); + var block = parseInt(rowIndex / blockSize); + var blockData = this._rowBlockCache[block]; + if (blockData == null) { + // This block is not (yet) loaded + return null; + } else { + var rowData = blockData.rowDataArr[rowIndex - (block * blockSize)]; + + // Update the last recently used counter + if (blockData.lru != this._lruCounter) { + blockData.lru = ++this._lruCounter; + } + + return rowData; + } +}; + + +// overridden +qx.Proto.getValue = function(columnIndex, rowIndex) { + var rowData = this.getRowData(rowIndex); + if (rowData == null) { + return null; + } else { + var columnId = this.getColumnId(columnIndex); + return rowData[columnId]; + } +}; + + +/** + * Sets whether a column is sortable. + * + * @param columnIndex {int} the column of which to set the sortable state. + * @param sortable {boolean} whether the column should be sortable. + */ +qx.Proto.setColumnSortable = function(columnIndex, sortable) { + if (sortable != this.isColumnSortable(columnIndex)) { + if (this._sortableColArr == null) { + this._sortableColArr = []; + } + this._sortableColArr[columnIndex] = sortable; + + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); + } +} + + +// overridden +qx.Proto.isColumnSortable = function(columnIndex) { + return this._sortableColArr ? (this._sortableColArr[columnIndex] == true) : false; +} + + +// overridden +qx.Proto.sortByColumn = function(columnIndex, ascending) { + if (this._sortColumnIndex != columnIndex || this._sortAscending != ascending) { + this._sortColumnIndex = columnIndex; + this._sortAscending = ascending; + + this.clearCache(); + + // Inform the listeners + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); + } +}; + + +// overridden +qx.Proto.getSortColumnIndex = function() { + return this._sortColumnIndex; +} + + +// overridden +qx.Proto.isSortAscending = function() { + return this._sortAscending; +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SelectionManager.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SelectionManager.js new file mode 100644 index 0000000000..715b0d9d96 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SelectionManager.js @@ -0,0 +1,163 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A selection manager. This is a helper class that handles all selection + * related events and updates a SelectionModel. + * <p> + * Widgets that support selection should use this manager. This way the only + * thing the widget has to do is mapping mouse or key events to indexes and + * call the corresponding handler method. + * + * @see SelectionModel + */ +qx.OO.defineClass("qx.ui.table.SelectionManager", qx.core.Object, +function() { + qx.core.Object.call(this); +}); + + +/** + * The selection model where to set the selection changes. + */ +qx.OO.addProperty({ name:"selectionModel", type:"object", instance:"qx.ui.table.SelectionModel" }); + + +/** + * Handles the mouse down event. + * + * @param index {int} the index the mouse is pointing at. + * @param evt {Map} the mouse event. + */ +qx.Proto.handleMouseDown = function(index, evt) { + if (evt.isLeftButtonPressed()) { + var selectionModel = this.getSelectionModel(); + if (!selectionModel.isSelectedIndex(index)) { + // This index is not selected -> We react when the mouse is pressed (because of drag and drop) + this._handleSelectEvent(index, evt); + this._lastMouseDownHandled = true; + } else { + // This index is already selected -> We react when the mouse is released (because of drag and drop) + this._lastMouseDownHandled = false; + } + } else if (evt.isRightButtonPressed() && evt.getModifiers() == 0) { + var selectionModel = this.getSelectionModel(); + if (!selectionModel.isSelectedIndex(index)) { + // This index is not selected -> Set the selection to this index + selectionModel.setSelectionInterval(index, index); + } + } +} + + +/** + * Handles the mouse up event. + * + * @param index {int} the index the mouse is pointing at. + * @param evt {Map} the mouse event. + */ +qx.Proto.handleMouseUp = function(index, evt) { + if (evt.isLeftButtonPressed() && !this._lastMouseDownHandled) { + this._handleSelectEvent(index, evt); + } +} + + +/** + * Handles the mouse click event. + * + * @param index {int} the index the mouse is pointing at. + * @param evt {Map} the mouse event. + */ +qx.Proto.handleClick = function(index, evt) { +} + + +/** + * Handles the key down event that is used as replacement for mouse clicks + * (Normally space). + * + * @param index {int} the index that is currently focused. + * @param evt {Map} the key event. + */ +qx.Proto.handleSelectKeyDown = function(index, evt) { + this._handleSelectEvent(index, evt); +}; + + +/** + * Handles a key down event that moved the focus (E.g. up, down, home, end, ...). + * + * @param index {int} the index that is currently focused. + * @param evt {Map} the key event. + */ +qx.Proto.handleMoveKeyDown = function(index, evt) { + var selectionModel = this.getSelectionModel(); + switch (evt.getModifiers()) { + case 0: + selectionModel.setSelectionInterval(index, index); + break; + case qx.event.type.DomEvent.SHIFT_MASK: + var anchor = selectionModel.getAnchorSelectionIndex(); + if (anchor == -1) { + selectionModel.setSelectionInterval(index, index); + } else { + selectionModel.setSelectionInterval(anchor, index); + } + break; + } +} + + +/** + * Handles a select event. + * + * @param index {int} the index the event is pointing at. + * @param evt {Map} the mouse event. + */ +qx.Proto._handleSelectEvent = function(index, evt) { + var selectionModel = this.getSelectionModel(); + if (evt.getShiftKey()) { + var leadIndex = selectionModel.getLeadSelectionIndex(); + if (index != leadIndex || selectionModel.isSelectionEmpty()) { + // The lead selection index was changed + var anchorIndex = selectionModel.getAnchorSelectionIndex(); + if (anchorIndex == -1) { + anchorIndex = index; + } + if (evt.isCtrlOrCommandPressed()) { + selectionModel.addSelectionInterval(anchorIndex, index); + } else { + selectionModel.setSelectionInterval(anchorIndex, index); + } + } + } else if (evt.isCtrlOrCommandPressed()) { + if (selectionModel.isSelectedIndex(index)) { + selectionModel.removeSelectionInterval(index, index); + } else { + selectionModel.addSelectionInterval(index, index); + } + } else { + selectionModel.setSelectionInterval(index, index); + } +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SelectionModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SelectionModel.js new file mode 100644 index 0000000000..fb0f6b7317 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SelectionModel.js @@ -0,0 +1,427 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A selection model. + * + * @event changeSelection {qx.event.type.Event} Fired when the selection has + * changed. + */ +qx.OO.defineClass("qx.ui.table.SelectionModel", qx.core.Target, +function() { + qx.core.Target.call(this); + + this._selectedRangeArr = []; + this._anchorSelectionIndex = -1; + this._leadSelectionIndex = -1; + this.hasBatchModeRefCount = 0; + this._hadChangeEventInBatchMode = false; +}); + + +/** {int} The selection mode "none". Nothing can ever be selected. */ +qx.Class.NO_SELECTION = 1; + +/** {int} The selection mode "single". This mode only allows one selected item. */ +qx.Class.SINGLE_SELECTION = 2; + +/** + * (int) The selection mode "single interval". This mode only allows one + * continuous interval of selected items. + */ +qx.Class.SINGLE_INTERVAL_SELECTION = 3; + +/** + * (int) The selection mode "multiple interval". This mode only allows any + * selection. + */ +qx.Class.MULTIPLE_INTERVAL_SELECTION = 4; + + +/** + * (int) the selection mode. + */ +qx.OO.addProperty({ name:"selectionMode", type:"number", + defaultValue:qx.Class.SINGLE_SELECTION, + allowNull:false, + possibleValues:[ qx.Class.NO_SELECTION, + qx.Class.SINGLE_SELECTION, + qx.Class.SINGLE_INTERVAL_SELECTION, + qx.Class.MULTIPLE_INTERVAL_SELECTION ] }); + +// selectionMode property modifier +qx.Proto._modifySelectionMode = function(selectionMode) { + if (selectionMode == qx.ui.table.SelectionModel.NO_SELECTION) { + this.clearSelection(); + } + return true; +} + + +/** + * <p>Activates / Deactivates batch mode. In batch mode, no change events will be thrown but + * will be collected instead. When batch mode is turned off again and any events have + * been collected, one event is thrown to inform the listeners.</p> + * + * <p>This method supports nested calling, i. e. batch mode can be turned more than once. + * In this case, batch mode will not end until it has been turned off once for each + * turning on.</p> + * + * @param batchMode {boolean} true to activate batch mode, false to deactivate + * @return {boolean} true if batch mode is active, false otherwise + * @throws Error if batch mode is turned off once more than it has been turned on + */ +qx.Proto.setBatchMode = function(batchMode) { + if (batchMode){ + this.hasBatchModeRefCount += 1; + } else { + if (this.hasBatchModeRefCount == 0){ + throw new Error("Try to turn off batch mode althoug it was not turned on.") + } + this.hasBatchModeRefCount -= 1; + if (this._hadChangeEventInBatchMode){ + this._hadChangeEventInBatchMode = false; + this._fireChangeSelection(); + } + } + return this.hasBatchMode(); +} + + +/** + * <p>Returns whether batch mode is active. See setter for a description of batch mode.</p> + * + * @return {boolean} true if batch mode is active, false otherwise + */ +qx.Proto.hasBatchMode = function() { + return this.hasBatchModeRefCount > 0; +} + + +/** + * Returns the first argument of the last call to {@link #setSelectionInterval()}, + * {@link #addSelectionInterval()} or {@link #removeSelectionInterval()}. + * + * @return {int} the ancor selection index. + */ +qx.Proto.getAnchorSelectionIndex = function() { + return this._anchorSelectionIndex; +} + + +/** + * Returns the second argument of the last call to {@link #setSelectionInterval()}, + * {@link #addSelectionInterval()} or {@link #removeSelectionInterval()}. + * + * @return {int} the lead selection index. + */ +qx.Proto.getLeadSelectionIndex = function() { + return this._leadSelectionIndex; +} + + +/** + * Clears the selection. + */ +qx.Proto.clearSelection = function() { + if (! this.isSelectionEmpty()) { + this._clearSelection(); + this._fireChangeSelection(); + } +} + + +/** + * Returns whether the selection is empty. + * + * @return {boolean} whether the selection is empty. + */ +qx.Proto.isSelectionEmpty = function() { + return this._selectedRangeArr.length == 0; +} + + +/** + * Returns the number of selected items. + * + * @return {int} the number of selected items. + */ +qx.Proto.getSelectedCount = function() { + var selectedCount = 0; + for (var i = 0; i < this._selectedRangeArr.length; i++) { + var range = this._selectedRangeArr[i]; + selectedCount += range.maxIndex - range.minIndex + 1; + } + + return selectedCount; +} + + +/** + * Returns whether a index is selected. + * + * @param index {int} the index to check. + * @return {boolean} whether the index is selected. + */ +qx.Proto.isSelectedIndex = function(index) { + for (var i = 0; i < this._selectedRangeArr.length; i++) { + var range = this._selectedRangeArr[i]; + + if (index >= range.minIndex && index <= range.maxIndex) { + return true; + } + } + + return false; +} + + +/** + * Returns the selected ranges as an array. Each array element has a + * <code>minIndex</code> and a <code>maxIndex</code> property. + * + * @return {Map[]} the selected ranges. + */ +qx.Proto.getSelectedRanges = function() { + // clone the selection array and the individual elements - this prevents the + // caller from messing with the internal model + var retVal = []; + for (var i = 0; i < this._selectedRangeArr.length; i++) { + retVal.push({minIndex: this._selectedRangeArr[i].minIndex, + maxIndex: this._selectedRangeArr[i].maxIndex}); + } + return retVal; +} + + +/** + * Calls a iterator function for each selected index. + * <p> + * Usage Example: + * <pre> + * var selectedRowData = []; + * mySelectionModel.iterateSelection(function(index) { + * selectedRowData.push(myTableModel.getRowData(index)); + * }); + * </pre> + * + * @param iterator {Function} the function to call for each selected index. + * Gets the current index as parameter. + * @param object {var ? null} the object to use when calling the handler. + * (this object will be available via "this" in the iterator) + */ +qx.Proto.iterateSelection = function(iterator, object) { + for (var i = 0; i < this._selectedRangeArr.length; i++) { + for (var j = this._selectedRangeArr[i].minIndex; j <= this._selectedRangeArr[i].maxIndex; j++) { + iterator.call(object, j); + } + } +}; + + +/** + * Sets the selected interval. This will clear the former selection. + * + * @param fromIndex {int} the first index of the selection (including). + * @param toIndex {int} the last index of the selection (including). + */ +qx.Proto.setSelectionInterval = function(fromIndex, toIndex) { + var SelectionModel = qx.ui.table.SelectionModel; + + switch(this.getSelectionMode()) { + case SelectionModel.NO_SELECTION: + return; + case SelectionModel.SINGLE_SELECTION: + fromIndex = toIndex; + break; + } + + this._clearSelection(); + this._addSelectionInterval(fromIndex, toIndex); + + this._fireChangeSelection(); +} + + +/** + * Adds a selection interval to the current selection. + * + * @param fromIndex {int} the first index of the selection (including). + * @param toIndex {int} the last index of the selection (including). + */ +qx.Proto.addSelectionInterval = function(fromIndex, toIndex) { + var SelectionModel = qx.ui.table.SelectionModel; + switch (this.getSelectionMode()) { + case SelectionModel.NO_SELECTION: + return; + case SelectionModel.MULTIPLE_INTERVAL_SELECTION: + this._addSelectionInterval(fromIndex, toIndex); + this._fireChangeSelection(); + break; + default: + this.setSelectionInterval(fromIndex, toIndex); + break; + } +} + + +/** + * Removes a interval from the current selection. + * + * @param fromIndex {int} the first index of the interval (including). + * @param toIndex {int} the last index of the interval (including). + */ +qx.Proto.removeSelectionInterval = function(fromIndex, toIndex) { + this._anchorSelectionIndex = fromIndex; + this._leadSelectionIndex = toIndex; + + var minIndex = Math.min(fromIndex, toIndex); + var maxIndex = Math.max(fromIndex, toIndex); + + // Crop the affected ranges + for (var i = 0; i < this._selectedRangeArr.length; i++) { + var range = this._selectedRangeArr[i]; + + if (range.minIndex > maxIndex) { + // We are done + break; + } else if (range.maxIndex >= minIndex) { + // This range is affected + var minIsIn = (range.minIndex >= minIndex) && (range.minIndex <= maxIndex); + var maxIsIn = (range.maxIndex >= minIndex) && (range.maxIndex <= maxIndex); + + if (minIsIn && maxIsIn) { + // This range is removed completely + this._selectedRangeArr.splice(i, 1); + + // Check this index another time + i--; + } else if (minIsIn) { + // The range is cropped from the left + range.minIndex = maxIndex + 1; + } else if (maxIsIn) { + // The range is cropped from the right + range.maxIndex = minIndex - 1; + } else { + // The range is split + var newRange = { minIndex:maxIndex + 1, maxIndex:range.maxIndex } + this._selectedRangeArr.splice(i + 1, 0, newRange); + + range.maxIndex = minIndex - 1; + + // We are done + break; + } + } + } + + //this._dumpRanges(); + + this._fireChangeSelection(); +} + + +/** + * Clears the selection, but doesn't inform the listeners. + */ +qx.Proto._clearSelection = function() { + this._selectedRangeArr = []; +} + + +/** + * Adds a selection interval to the current selection, but doesn't inform + * the listeners. + * + * @param fromIndex {int} the first index of the selection (including). + * @param toIndex {int} the last index of the selection (including). + */ +qx.Proto._addSelectionInterval = function(fromIndex, toIndex) { + this._anchorSelectionIndex = fromIndex; + this._leadSelectionIndex = toIndex; + + var minIndex = Math.min(fromIndex, toIndex); + var maxIndex = Math.max(fromIndex, toIndex); + + // Find the index where the new range should be inserted + var newRangeIndex = 0; + for (; newRangeIndex < this._selectedRangeArr.length; newRangeIndex++) { + var range = this._selectedRangeArr[newRangeIndex]; + if (range.minIndex > minIndex) { + break; + } + } + + // Add the new range + this._selectedRangeArr.splice(newRangeIndex, 0, { minIndex:minIndex, maxIndex:maxIndex }); + + // Merge overlapping ranges + var lastRange = this._selectedRangeArr[0]; + for (var i = 1; i < this._selectedRangeArr.length; i++) { + var range = this._selectedRangeArr[i]; + + if (lastRange.maxIndex + 1 >= range.minIndex) { + // The ranges are overlapping -> merge them + lastRange.maxIndex = Math.max(lastRange.maxIndex, range.maxIndex); + + // Remove the current range + this._selectedRangeArr.splice(i, 1); + + // Check this index another time + i--; + } else { + lastRange = range; + } + } + + //this._dumpRanges(); +} + + +/** + * Logs the current ranges for debug perposes. + */ +qx.Proto._dumpRanges = function() { + var text = "Ranges:"; + for (var i = 0; i < this._selectedRangeArr.length; i++) { + var range = this._selectedRangeArr[i]; + text += " [" + range.minIndex + ".." + range.maxIndex + "]"; + } + this.debug(text); +} + + +/** + * Fires the "changeSelection" event to all registered listeners. If the selection model + * currently is in batch mode, only one event will be thrown when batch mode is ended. + */ +qx.Proto._fireChangeSelection = function() { + //In batch mode, remember event but do not throw (yet) + if (this.hasBatchMode()){ + this._hadChangeEventInBatchMode = true; + + //If not in batch mode, throw event + } else if (this.hasEventListeners("changeSelection")) { + this.dispatchEvent(new qx.event.type.Event("changeSelection"), true); + } +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SimpleTableModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SimpleTableModel.js new file mode 100644 index 0000000000..ef6ef2fecc --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/SimpleTableModel.js @@ -0,0 +1,335 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A simple table model that provides an API for changing the model data. + */ +qx.OO.defineClass("qx.ui.table.SimpleTableModel", qx.ui.table.AbstractTableModel, +function() { + qx.ui.table.AbstractTableModel.call(this); + + this._rowArr = []; + this._sortColumnIndex = -1; + this._sortAscending; + + this._editableColArr = null; +}); + + +/** + * <p>See overridden method for details.</p> + * + * @param rowIndex {int} the model index of the row. + * @return {Array} Array containing a value for each column. + */ +qx.Proto.getRowData = function(rowIndex) { + return this._rowArr[rowIndex]; +}; + + +/** + * Returns the data of one row as map containing the column IDs as key and the + * cell values as value. + * + * @param rowIndex {int} the model index of the row. + * @return {Map} a Map containing the column values. + */ +qx.Proto.getRowDataAsMap = function(rowIndex) { + var columnArr = this._rowArr[rowIndex]; + var map = {}; + for (var col = 0; col < this.getColumnCount(); col++) { + map[this.getColumnId(col)] = columnArr[col]; + } + return map; +}; + + +/** + * Sets all columns editable or not editable. + * + * @param editable {boolean} whether all columns are editable. + */ +qx.Proto.setEditable = function(editable) { + this._editableColArr = []; + for (var col = 0; col < this.getColumnCount(); col++) { + this._editableColArr[col] = editable; + } + + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); +} + + +/** + * Sets whether a column is editable. + * + * @param columnIndex {int} the column of which to set the editable state. + * @param editable {boolean} whether the column should be editable. + */ +qx.Proto.setColumnEditable = function(columnIndex, editable) { + if (editable != this.isColumnEditable(columnIndex)) { + if (this._editableColArr == null) { + this._editableColArr = []; + } + this._editableColArr[columnIndex] = editable; + + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); + } +} + + +// overridden +qx.Proto.isColumnEditable = function(columnIndex) { + return this._editableColArr ? (this._editableColArr[columnIndex] == true) : false; +} + + +// overridden +qx.Proto.isColumnSortable = function(columnIndex) { + return true; +} + + +// overridden +qx.Proto.sortByColumn = function(columnIndex, ascending) { + // NOTE: We use different comperators for ascending and descending, + // because comperators should be really fast. + var comperator; + if (ascending) { + comperator = function(row1, row2) { + var obj1 = row1[columnIndex]; + var obj2 = row2[columnIndex]; + return (obj1 > obj2) ? 1 : ((obj1 == obj2) ? 0 : -1); + } + } else { + comperator = function(row1, row2) { + var obj1 = row1[columnIndex]; + var obj2 = row2[columnIndex]; + return (obj1 < obj2) ? 1 : ((obj1 == obj2) ? 0 : -1); + } + } + + this._rowArr.sort(comperator); + + this._sortColumnIndex = columnIndex; + this._sortAscending = ascending; + + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); +} + + +/** + * Clears the sorting. + */ +qx.Proto._clearSorting = function() { + if (this._sortColumnIndex != -1) { + this._sortColumnIndex = -1; + this._sortAscending = true; + + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED); + } +} + + +// overridden +qx.Proto.getSortColumnIndex = function() { + return this._sortColumnIndex; +} + + +// overridden +qx.Proto.isSortAscending = function() { + return this._sortAscending; +} + + +// overridden +qx.Proto.getRowCount = function() { + return this._rowArr.length; +} + + +// overridden +qx.Proto.getValue = function(columnIndex, rowIndex) { + if (rowIndex < 0 || rowIndex >= this._rowArr.length) { + throw new Error("this._rowArr out of bounds: " + rowIndex + " (0.." + this._rowArr.length + ")"); + } + + return this._rowArr[rowIndex][columnIndex]; +} + + +// overridden +qx.Proto.setValue = function(columnIndex, rowIndex, value) { + if (this._rowArr[rowIndex][columnIndex] != value) { + this._rowArr[rowIndex][columnIndex] = value; + + // Inform the listeners + if (this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED)) { + var data = { firstRow:rowIndex, lastRow:rowIndex, + firstColumn:columnIndex, lastColumn:columnIndex } + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); + } + + if (columnIndex == this._sortColumnIndex) { + this._clearSorting(); + } + } +} + + +/** + * Sets the whole data in a bulk. + * + * @param rowArr {var[][]} An array containing an array for each row. Each + * row-array contains the values in that row in the order of the columns + * in this model. + */ +qx.Proto.setData = function(rowArr) { + this._rowArr = rowArr; + + // Inform the listeners + if (this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED)) { + this.createDispatchEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED); + } + + this._clearSorting(); +} + + +/** + * Returns the data of this model. + * <p> + * Warning: Do not alter this array! If you want to change the data use + * {@link #setData}, {@link #setDataAsMapArray} or {@link #setValue} instead. + * + * @return {var[][]} An array containing an array for each row. Each + * row-array contains the values in that row in the order of the columns + * in this model. + */ +qx.Proto.getData = function() { + return this._rowArr; +}; + + +/** + * Sets the whole data in a bulk. + * + * @param mapArr {Map[]} An array containing a map for each row. Each + * row-map contains the column IDs as key and the cell values as value. + */ +qx.Proto.setDataAsMapArray = function(mapArr) { + this.setData(this._mapArray2RowArr(mapArr)); +}; + + +/** + * Adds some rows to the model. + * <p> + * Warning: The given array will be altered! + * + * @param rowArr {var[][]} An array containing an array for each row. Each + * row-array contains the values in that row in the order of the columns + * in this model. + * @param startIndex {int ? null} The index where to insert the new rows. If null, + * the rows are appended to the end. + */ +qx.Proto.addRows = function(rowArr, startIndex) { + if (startIndex == null) { + startIndex = this._rowArr.length; + } + + // Prepare the rowArr so it can be used for apply + rowArr.splice(0, 0, startIndex, 0); + + // Insert the new rows + Array.prototype.splice.apply(this._rowArr, rowArr); + + // Inform the listeners + if (this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED)) { + var data = { firstRow:startIndex, lastRow:this._rowArr.length - 1, firstColumn:0, lastColumn:this.getColumnCount() - 1 }; + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); + } + + this._clearSorting(); +}; + + +/** + * Adds some rows to the model. + * <p> + * Warning: The given array (mapArr) will be altered! + * + * @param mapArr {Map[]} An array containing a map for each row. Each + * row-map contains the column IDs as key and the cell values as value. + * @param startIndex {int ? null} The index where to insert the new rows. If null, + * the rows are appended to the end. + */ +qx.Proto.addRowsAsMapArray = function(mapArr, startIndex) { + this.addRows(this._mapArray2RowArr(mapArr), startIndex); +}; + + +/** + * Removes some rows from the model. + * + * @param startIndex {int} the index of the first row to remove. + * @param howMany {int} the number of rows to remove. + */ +qx.Proto.removeRows = function(startIndex, howMany) { + this._rowArr.splice(startIndex, howMany); + + // Inform the listeners + if (this.hasEventListeners(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED)) { + var data = { firstRow:startIndex, lastRow:this._rowArr.length - 1, firstColumn:0, lastColumn:this.getColumnCount() - 1 }; + this.dispatchEvent(new qx.event.type.DataEvent(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, data), true); + } + + this._clearSorting(); +}; + + +/** + * Creates an array of maps to an array of arrays. + * + * @param mapArr {Map[]} An array containing a map for each row. Each + * row-map contains the column IDs as key and the cell values as value. + * @return {var[][]} An array containing an array for each row. Each + * row-array contains the values in that row in the order of the columns + * in this model. + */ +qx.Proto._mapArray2RowArr = function(mapArr) { + var rowCount = mapArr.length; + var columnCount = this.getColumnCount(); + var dataArr = new Array(rowCount); + var columnArr; + var j; + for (var i = 0; i < rowCount; ++i) { + columnArr = new Array(columnCount); + for (var j = 0; j < columnCount; ++j) { + columnArr[j] = mapArr[i][this.getColumnId(j)]; + } + dataArr[i] = columnArr; + } + + return dataArr; +}; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/Table.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/Table.js new file mode 100644 index 0000000000..360662e718 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/Table.js @@ -0,0 +1,1062 @@ +/* ************************************************************************ + + 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) +#require(qx.ui.table.DefaultDataRowRenderer) + +************************************************************************ */ + +/** + * A table. + * + * @param tableModel {qx.ui.table.TableModel} The table + * model to read the data from. + */ +qx.OO.defineClass("qx.ui.table.Table", qx.ui.layout.VerticalBoxLayout, +function(tableModel) { + qx.ui.layout.VerticalBoxLayout.call(this); + + // Create the child widgets + this._scrollerParent = new qx.ui.layout.HorizontalBoxLayout; + this._scrollerParent.setDimension("100%", "1*"); + this._scrollerParent.setSpacing(1); + + this._statusBar = new qx.ui.basic.Label; + this._statusBar.setAppearance("table-focus-statusbar"); + this._statusBar.setDimension("100%", "auto"); + + this.add(this._scrollerParent, this._statusBar); + + this._columnVisibilityBt = new qx.ui.toolbar.Button(null, "widget/table/selectColumnOrder.png"); + this._columnVisibilityBt.addEventListener("execute", this._onColumnVisibilityBtExecuted, this); + + // Create the models + this._selectionManager = new qx.ui.table.SelectionManager; + + this.setSelectionModel(new qx.ui.table.SelectionModel); + this.setTableColumnModel(new qx.ui.table.TableColumnModel); + this.setTableModel(tableModel); + + // Update the status bar + this._updateStatusBar(); + + // create the main meta column + this.setMetaColumnCounts([ -1 ]); + + // Make focusable + this.setTabIndex(1); + this.addEventListener("keydown", this._onkeydown); + this.addEventListener("keypress", this._onkeypress); + this.addEventListener("changeFocused", this._onFocusChanged); + + this._focusedCol = 0; + this._focusedRow = 0; +}); + + +/** The default row renderer to use when {@link #dataRowRenderer} is null. */ +qx.Class.DEFAULT_DATA_ROW_RENDERER = new qx.ui.table.DefaultDataRowRenderer(); + + +/** The selection model. */ +qx.OO.addProperty({ name:"selectionModel", type:"object", instance : "qx.ui.table.SelectionModel" }); + +/** The table model. */ +qx.OO.addProperty({ name:"tableModel", type:"object", instance : "qx.ui.table.TableModel" }); + +/** The table column model. */ +qx.OO.addProperty({ name:"tableColumnModel", type:"object", instance : "qx.ui.table.TableColumnModel" }); + +/** The height of the table rows. */ +qx.OO.addProperty({ name:"rowHeight", type:"number", defaultValue:15 }); + +/** Whether to show the status bar */ +qx.OO.addProperty({ name:"statusBarVisible", type:"boolean", defaultValue:true }); + +/** Whether to show the column visibility button */ +qx.OO.addProperty({ name:"columnVisibilityButtonVisible", type:"boolean", defaultValue:true }); + +/** + * {int[]} The number of columns per meta column. If the last array entry is -1, + * this meta column will get the remaining columns. + */ +qx.OO.addProperty({ name:"metaColumnCounts", type:"object" }); + +/** + * 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 }); + +/** + * Whether the table should keep the first visible row complete. If set to false, + * the first row may be rendered partial, depending on the vertical scroll value. + */ +qx.OO.addProperty({ name:"keepFirstVisibleRowComplete", type:"boolean", defaultValue:true }); + +/** + * Whether the table cells should be updated when only the selection or the + * focus changed. This slows down the table update but allows to react on a + * changed selection or a changed focus in a cell renderer. + */ +qx.OO.addProperty({ name:"alwaysUpdateCells", type:"boolean", defaultValue:false }); + +/** The height of the header cells. */ +qx.OO.addProperty({ name:"headerCellHeight", type:"number", defaultValue:16, allowNull:false }); + +/** The renderer to use for styling the rows. */ +qx.OO.addProperty({ name:"dataRowRenderer", type:"object", instance:"qx.ui.table.DataRowRenderer", defaultValue:qx.Class.DEFAULT_DATA_ROW_RENDERER, allowNull:false }); + + +// property modifier +qx.Proto._modifySelectionModel = function(propValue, propOldValue, propData) { + this._selectionManager.setSelectionModel(propValue); + + if (propOldValue != null) { + propOldValue.removeEventListener("changeSelection", this._onSelectionChanged, this); + } + propValue.addEventListener("changeSelection", this._onSelectionChanged, this); + + return true; +} + + +// property modifier +qx.Proto._modifyTableModel = function(propValue, propOldValue, propData) { + this.getTableColumnModel().init(propValue.getColumnCount()); + + if (propOldValue != null) { + propOldValue.removeEventListener(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED, this._onTableModelMetaDataChanged, this); + propOldValue.removeEventListener(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, this._onTableModelDataChanged, this); + } + propValue.addEventListener(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED, this._onTableModelMetaDataChanged, this); + propValue.addEventListener(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, this._onTableModelDataChanged, this); + + return true; +} + + +// property modifier +qx.Proto._modifyTableColumnModel = function(propValue, propOldValue, propData) { + if (propOldValue != null) { + propOldValue.removeEventListener("visibilityChanged", this._onColVisibilityChanged, this); + propOldValue.removeEventListener("widthChanged", this._onColWidthChanged, this); + propOldValue.removeEventListener("orderChanged", this._onColOrderChanged, this); + } + propValue.addEventListener("visibilityChanged", this._onColVisibilityChanged, this); + propValue.addEventListener("widthChanged", this._onColWidthChanged, this); + propValue.addEventListener("orderChanged", this._onColOrderChanged, this); + + return true; +}; + + +// property modifier +qx.Proto._modifyStatusBarVisible = function(propValue, propOldValue, propData) { + this._statusBar.setDisplay(propValue); + + if (propValue) { + this._updateStatusBar(); + } + return true; +}; + + +// property modifier +qx.Proto._modifyColumnVisibilityButtonVisible = function(propValue, propOldValue, propData) { + this._columnVisibilityBt.setDisplay(propValue); + + return true; +}; + + +// property modifier +qx.Proto._modifyMetaColumnCounts = function(propValue, propOldValue, propData) { + var metaColumnCounts = propValue; + var scrollerArr = this._getPaneScrollerArr(); + + // Remove the panes not needed any more + this._cleanUpMetaColumns(metaColumnCounts.length); + + // Update the old panes + var leftX = 0; + for (var i = 0; i < scrollerArr.length; i++) { + var paneScroller = scrollerArr[i]; + var paneModel = paneScroller.getTablePaneModel(); + paneModel.setFirstColumnX(leftX); + paneModel.setMaxColumnCount(metaColumnCounts[i]); + leftX += metaColumnCounts[i]; + } + + // Add the new panes + if (metaColumnCounts.length > scrollerArr.length) { + var selectionModel = this.getSelectionModel(); + var tableModel = this.getTableModel(); + var columnModel = this.getTableColumnModel(); + + for (var i = scrollerArr.length; i < metaColumnCounts.length; i++) { + var paneModel = new qx.ui.table.TablePaneModel(columnModel); + paneModel.setFirstColumnX(leftX); + paneModel.setMaxColumnCount(metaColumnCounts[i]); + leftX += metaColumnCounts[i]; + + var paneScroller = new qx.ui.table.TablePaneScroller(this); + paneScroller.setTablePaneModel(paneModel); + + // Register event listener for vertical scrolling + paneScroller.addEventListener("changeScrollY", this._onScrollY, this); + + this._scrollerParent.add(paneScroller); + } + } + + // Update all meta columns + for (var i = 0; i < scrollerArr.length; i++) { + var paneScroller = scrollerArr[i]; + var isLast = (i == (scrollerArr.length - 1)); + + // Set the right header height + paneScroller.getHeader().setHeight(this.getHeaderCellHeight()); + + // Put the _columnVisibilityBt in the top right corner of the last meta column + paneScroller.setTopRightWidget(isLast ? this._columnVisibilityBt : null); + } + + this._updateScrollerWidths(); + this._updateScrollBarVisibility(); + + return true; +} + + +// property modifier +qx.Proto._modifyFocusCellOnMouseMove = function(propValue, propOldValue, propData) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i].setFocusCellOnMouseMove(propValue); + } + return true; +}; + + +// property modifier +qx.Proto._modifyKeepFirstVisibleRowComplete = function(propValue, propOldValue, propData) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onKeepFirstVisibleRowCompleteChanged(); + } + return true; +}; + + +// property modifier +qx.Proto._modifyHeaderCellHeight = function(propValue, propOldValue, propData) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i].getHeader().setHeight(propValue); + } + return true; +}; + + +/** + * Returns the selection manager. + * + * @return {SelectionManager} the selection manager. + */ +qx.Proto._getSelectionManager = function() { + return this._selectionManager; +}; + + +/** + * Returns an array containing all TablePaneScrollers in this table. + * + * @return {TablePaneScroller[]} all TablePaneScrollers in this table. + */ +qx.Proto._getPaneScrollerArr = function() { + return this._scrollerParent.getChildren(); +} + + +/** + * Returns a TablePaneScroller of this table. + * + * @param metaColumn {int} the meta column to get the TablePaneScroller for. + * @return {TablePaneScroller} the TablePaneScroller. + */ +qx.Proto.getPaneScroller = function(metaColumn) { + return this._getPaneScrollerArr()[metaColumn]; +} + + +/** + * Cleans up the meta columns. + * + * @param fromMetaColumn {int} the first meta column to clean up. All following + * meta columns will be cleaned up, too. All previous meta columns will + * stay unchanged. If 0 all meta columns will be cleaned up. + */ +qx.Proto._cleanUpMetaColumns = function(fromMetaColumn) { + var scrollerArr = this._getPaneScrollerArr(); + if (scrollerArr != null) { + for (var i = scrollerArr.length - 1; i >= fromMetaColumn; i--) { + var paneScroller = scrollerArr[i]; + paneScroller.removeEventListener("changeScrollY", this._onScrollY, this); + this._scrollerParent.remove(paneScroller); + paneScroller.dispose(); + } + } +} + + +/** + * Event handler. Called when the selection has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onSelectionChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onSelectionChanged(evt); + } + + this._updateStatusBar(); +} + + +/** + * Event handler. Called when the table model meta data has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onTableModelMetaDataChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onTableModelMetaDataChanged(evt); + } + + this._updateStatusBar(); +} + + +/** + * Event handler. Called when the table model data has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onTableModelDataChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onTableModelDataChanged(evt); + } + + var rowCount = this.getTableModel().getRowCount(); + if (rowCount != this._lastRowCount) { + this._lastRowCount = rowCount; + + this._updateScrollBarVisibility(); + this._updateStatusBar(); + } +}; + + +/** + * Event handler. Called when a TablePaneScroller has been scrolled vertically. + * + * @param evt {Map} the event. + */ +qx.Proto._onScrollY = function(evt) { + if (! this._internalChange) { + this._internalChange = true; + + // Set the same scroll position to all meta columns + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i].setScrollY(evt.getData()); + } + + this._internalChange = false; + } +} + + +/** + * Event handler. Called when a key was pressed. + * + * @param evt {Map} the event. + */ +qx.Proto._onkeydown = function(evt) { + var identifier = evt.getKeyIdentifier(); + + var consumed = false; + var oldFocusedRow = this._focusedRow; + if (this.isEditing()) { + // Editing mode + if (evt.getModifiers() == 0) { + consumed = true; + switch (identifier) { + case "Enter": + this.stopEditing(); + var oldFocusedRow = this._focusedRow; + this.moveFocusedCell(0, 1); + if (this._focusedRow != oldFocusedRow) { + this.startEditing(); + } + break; + case "Escape": + this.cancelEditing(); + this.focus(); + break; + default: + consumed = false; + break; + } + } + } else { + // No editing mode + + // Handle keys that are independant from the modifiers + consumed = true; + switch (identifier) { + case "Home": + this.setFocusedCell(this._focusedCol, 0, true); + break; + case "End": + var rowCount = this.getTableModel().getRowCount(); + this.setFocusedCell(this._focusedCol, rowCount - 1, true); + break; + default: + consumed = false; + break; + } + + // Handle keys that depend on modifiers + if (evt.getModifiers() == 0) { + consumed = true; + switch (identifier) { + case "F2": + case "Enter": + this.startEditing(); + break; + default: + consumed = false; + break; + } + } else if (evt.getCtrlKey()) { + consumed = true; + switch (identifier) { + case "A": // Ctrl + A + var rowCount = this.getTableModel().getRowCount(); + if (rowCount > 0) { + this.getSelectionModel().setSelectionInterval(0, rowCount - 1); + } + break; + default: + consumed = false; + break; + } + } + } + + if (oldFocusedRow != this._focusedRow) { + // The focus moved -> Let the selection manager handle this event + this._selectionManager.handleMoveKeyDown(this._focusedRow, evt); + } + + if (consumed) { + evt.preventDefault(); + evt.stopPropagation(); + } +}; + + +qx.Proto._onkeypress = function(evt) +{ + if (this.isEditing()) { return } + // No editing mode + var oldFocusedRow = this._focusedRow; + var consumed = true; + + // Handle keys that are independant from the modifiers + var identifier = evt.getKeyIdentifier(); + switch (identifier) { + case "Space": + this._selectionManager.handleSelectKeyDown(this._focusedRow, evt); + break; + + case "Left": + this.moveFocusedCell(-1, 0); + break; + + case "Right": + this.moveFocusedCell(1, 0); + break; + + case "Up": + this.moveFocusedCell(0, -1); + break; + + case "Down": + this.moveFocusedCell(0, 1); + break; + + case "PageUp": + case "PageDown": + var scroller = this.getPaneScroller(0); + var pane = scroller.getTablePane(); + var rowCount = pane.getVisibleRowCount() - 1; + var rowHeight = this.getRowHeight(); + var direction = (identifier == "PageUp") ? -1 : 1; + scroller.setScrollY(scroller.getScrollY() + direction * rowCount * rowHeight); + this.moveFocusedCell(0, direction * rowCount); + break; + + default: + consumed = false; + } + if (oldFocusedRow != this._focusedRow) { + // The focus moved -> Let the selection manager handle this event + this._selectionManager.handleMoveKeyDown(this._focusedRow, evt); + } + + if (consumed) { + evt.preventDefault(); + evt.stopPropagation(); + } +}; + + +/** + * Event handler. Called when the table gets the focus. + */ +qx.Proto._onFocusChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onFocusChanged(evt); + } +}; + + +/** + * Event handler. Called when the visibility of a column has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColVisibilityChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onColVisibilityChanged(evt); + } + + this._updateScrollerWidths(); + this._updateScrollBarVisibility(); +} + + +/** + * Event handler. Called when the width of a column has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColWidthChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onColWidthChanged(evt); + } + + this._updateScrollerWidths(); + this._updateScrollBarVisibility(); +} + + +/** + * Event handler. Called when the column order has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColOrderChanged = function(evt) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i]._onColOrderChanged(evt); + } + + // A column may have been moved between meta columns + this._updateScrollerWidths(); + this._updateScrollBarVisibility(); +} + + +/** + * Gets the TablePaneScroller at a certain x position in the page. If there is + * no TablePaneScroller at this postion, null is returned. + * + * @param pageX {int} the position in the page to check (in pixels). + * @return {TablePaneScroller} the TablePaneScroller or null. + * + * @see TablePaneScrollerPool + */ +qx.Proto.getTablePaneScrollerAtPageX = function(pageX) { + var metaCol = this._getMetaColumnAtPageX(pageX); + return (metaCol != -1) ? this.getPaneScroller(metaCol) : null; +} + + +/** + * 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. + * @param scrollVisible {boolean ? false} whether to scroll the new focused cell + * visible. + * + * @see TablePaneScrollerPool + */ +qx.Proto.setFocusedCell = function(col, row, scrollVisible) { + if (!this.isEditing() && (col != this._focusedCol || row != this._focusedRow)) { + this._focusedCol = col; + this._focusedRow = row; + + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + scrollerArr[i].setFocusedCell(col, row); + } + + if (scrollVisible) { + this.scrollCellVisible(col, row); + } + } +} + + +/** + * Returns the column of the 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 the currently focused cell. + * + * @return {int} the model index of the focused cell's column. + */ +qx.Proto.getFocusedRow = function() { + return this._focusedRow; +}; + + +/** + * Moves the focus. + * + * @param deltaX {int} The delta by which the focus should be moved on the x axis. + * @param deltaY {int} The delta by which the focus should be moved on the y axis. + */ +qx.Proto.moveFocusedCell = function(deltaX, deltaY) { + var col = this._focusedCol; + var row = this._focusedRow; + + if (deltaX != 0) { + var columnModel = this.getTableColumnModel(); + var x = columnModel.getVisibleX(col); + var colCount = columnModel.getVisibleColumnCount(); + x = qx.lang.Number.limit(x + deltaX, 0, colCount - 1); + col = columnModel.getVisibleColumnAtX(x); + } + + if (deltaY != 0) { + var tableModel = this.getTableModel(); + row = qx.lang.Number.limit(row + deltaY, 0, tableModel.getRowCount() - 1); + } + + this.setFocusedCell(col, row, true); +} + + +/** + * 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 columnModel = this.getTableColumnModel(); + var x = columnModel.getVisibleX(col); + + var metaColumn = this._getMetaColumnAtColumnX(x); + if (metaColumn != -1) { + this.getPaneScroller(metaColumn).scrollCellVisible(col, row); + } +} + + +/** + * Returns whether currently a cell is editing. + * + * @return whether currently a cell is editing. + */ +qx.Proto.isEditing = function() { + if (this._focusedCol != null) { + var x = this.getTableColumnModel().getVisibleX(this._focusedCol); + var metaColumn = this._getMetaColumnAtColumnX(x); + return this.getPaneScroller(metaColumn).isEditing(); + } +} + + +/** + * 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() { + if (this._focusedCol != null) { + var x = this.getTableColumnModel().getVisibleX(this._focusedCol); + var metaColumn = this._getMetaColumnAtColumnX(x); + return this.getPaneScroller(metaColumn).startEditing(); + } + return false; +} + + +/** + * Stops editing and writes the editor's value to the model. + */ +qx.Proto.stopEditing = function() { + if (this._focusedCol != null) { + var x = this.getTableColumnModel().getVisibleX(this._focusedCol); + var metaColumn = this._getMetaColumnAtColumnX(x); + this.getPaneScroller(metaColumn).stopEditing(); + } +} + + +/** + * Stops editing without writing the editor's value to the model. + */ +qx.Proto.cancelEditing = function() { + if (this._focusedCol != null) { + var x = this.getTableColumnModel().getVisibleX(this._focusedCol); + var metaColumn = this._getMetaColumnAtColumnX(x); + this.getPaneScroller(metaColumn).cancelEditing(); + } +} + + +/** + * Gets the meta column at a certain x position in the page. If there is no + * meta column at this postion, -1 is returned. + * + * @param pageX {int} the position in the page to check (in pixels). + * @return {int} the index of the meta column or -1. + */ +qx.Proto._getMetaColumnAtPageX = function(pageX) { + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + var elem = scrollerArr[i].getElement(); + if (pageX >= qx.dom.Location.getPageBoxLeft(elem) + && pageX <= qx.dom.Location.getPageBoxRight(elem)) + { + return i; + } + } + + return -1; +} + + +/** + * Returns the meta column a column is shown in. If the column is not shown at + * all, -1 is returned. + * + * @param visXPos {int} the visible x position of the column. + * @return {int} the meta column the column is shown in. + */ +qx.Proto._getMetaColumnAtColumnX = function(visXPos) { + var metaColumnCounts = this.getMetaColumnCounts(); + var rightXPos = 0; + for (var i = 0; i < metaColumnCounts.length; i++) { + var counts = metaColumnCounts[i]; + rightXPos += counts; + + if (counts == -1 || visXPos < rightXPos) { + return i; + } + } + + return -1; +} + + +/** + * Updates the text shown in the status bar. + */ +qx.Proto._updateStatusBar = function() { + if (this.getStatusBarVisible()) { + var selectedRowCount = this.getSelectionModel().getSelectedCount(); + var rowCount = this.getTableModel().getRowCount(); + + var text; + if (selectedRowCount == 0) { + text = rowCount + ((rowCount == 1) ? " row" : " rows"); + } else { + text = selectedRowCount + " of " + rowCount + + ((rowCount == 1) ? " row" : " rows") + " selected"; + } + this._statusBar.setHtml(text); + } +} + + +/** + * Updates the widths of all scrollers. + */ +qx.Proto._updateScrollerWidths = function() { +/* no longer needed, per Til, and removing it does not appear to add problems. + * qx.ui.core.Widget.flushGlobalQueues(); + */ + + // Give all scrollers except for the last one the wanted width + // (The last one has a flex with) + var scrollerArr = this._getPaneScrollerArr(); + for (var i = 0; i < scrollerArr.length; i++) { + var isLast = (i == (scrollerArr.length - 1)); + var width = isLast ? "1*" : scrollerArr[i].getTablePaneModel().getTotalWidth(); + scrollerArr[i].setWidth(width); + } +} + + +/** + * Updates the visibility of the scrollbars in the meta columns. + */ +qx.Proto._updateScrollBarVisibility = function() { + if (this.isSeeable()) { + var horBar = qx.ui.table.TablePaneScroller.HORIZONTAL_SCROLLBAR; + var verBar = qx.ui.table.TablePaneScroller.VERTICAL_SCROLLBAR; + var scrollerArr = this._getPaneScrollerArr(); + + // Check which scroll bars are needed + var horNeeded = false; + var verNeeded = false; + for (var i = 0; i < scrollerArr.length; i++) { + var isLast = (i == (scrollerArr.length - 1)); + + // Only show the last vertical scrollbar + var bars = scrollerArr[i].getNeededScrollBars(horNeeded, !isLast); + + if (bars & horBar) { + horNeeded = true; + } + if (isLast && (bars & verBar)) { + verNeeded = true; + } + } + + // Set the needed scrollbars + for (var i = 0; i < scrollerArr.length; i++) { + var isLast = (i == (scrollerArr.length - 1)); + + // Only show the last vertical scrollbar + scrollerArr[i].setHorizontalScrollBarVisible(horNeeded); + scrollerArr[i].setVerticalScrollBarVisible(isLast && verNeeded); + } + } +} + + +/** + * Event handler. Called when the column visibiliy button was executed. + */ +qx.Proto._onColumnVisibilityBtExecuted = function() { + if ((this._columnVisibilityMenuCloseTime == null) + || (new Date().getTime() > this._columnVisibilityMenuCloseTime + 200)) + { + this._toggleColumnVisibilityMenu(); + } +} + + +/** + * Toggels the visibility of the menu used to change the visibility of columns. + */ +qx.Proto._toggleColumnVisibilityMenu = function() { + if (this._columnVisibilityMenu == null || !this._columnVisibilityMenu.isSeeable()) { + // Show the menu + + // Create the new menu + var menu = new qx.ui.menu.Menu; + + menu.addEventListener("disappear", function(evt) { + this._columnVisibilityMenuCloseTime = new Date().getTime(); + }, this); + + var tableModel = this.getTableModel(); + var columnModel = this.getTableColumnModel(); + for (var x = 0; x < columnModel.getOverallColumnCount(); x++) { + var col = columnModel.getOverallColumnAtX(x); + var visible = columnModel.isColumnVisible(col); + var cmd = { col:col } + var bt = new qx.ui.menu.CheckBox(tableModel.getColumnName(col), null, visible); + + var handler = this._createColumnVisibilityCheckBoxHandler(col); + bt._handler = handler; + bt.addEventListener("execute", handler, this); + + menu.add(bt); + } + + menu.setParent(this.getTopLevelWidget()); + + this._columnVisibilityMenu = menu; + + // Show the menu + var btElem = this._columnVisibilityBt.getElement(); + menu.setRestrictToPageOnOpen(false); + menu.setTop(qx.dom.Location.getClientBoxBottom(btElem)); + menu.setLeft(-1000); + + // NOTE: We have to show the menu in a timeout, otherwise it won't be shown + // at all. + window.setTimeout(function() { + menu.show(); + qx.ui.core.Widget.flushGlobalQueues(); + + menu.setLeft(qx.dom.Location.getClientBoxRight(btElem) - menu.getOffsetWidth()); + qx.ui.core.Widget.flushGlobalQueues(); + }, 0); + } else { + // hide the menu + menu.hide(); + this._cleanupColumnVisibilityMenu(); + } +} + + +/** + * Cleans up the column visibility menu. + */ +qx.Proto._cleanupColumnVisibilityMenu = function() { + if (this._columnVisibilityMenu != null && ! this._columnVisibilityMenu.getDisposed()) { + this._columnVisibilityMenu.dispose(); + this._columnVisibilityMenu = null; + } +} + + +/** + * Creates a handler for a check box of the column visibility menu. + * + * @param col {int} the model index of column to create the handler for. + */ +qx.Proto._createColumnVisibilityCheckBoxHandler = function(col) { + return function(evt) { + var columnModel = this.getTableColumnModel(); + columnModel.setColumnVisible(col, !columnModel.isColumnVisible(col)); + } +} + + +/** + * Sets the width of a column. + * + * @param col {int} the model index of column. + * @param width {int} the new width in pixels. + */ +qx.Proto.setColumnWidth = function(col, width) { + this.getTableColumnModel().setColumnWidth(col, width); +} + + +// overridden +qx.Proto._changeInnerWidth = function(newValue, oldValue) { + var self = this; + window.setTimeout(function() { + self._updateScrollBarVisibility(); + qx.ui.core.Widget.flushGlobalQueues(); + }, 0); + + return qx.ui.layout.VerticalBoxLayout.prototype._changeInnerWidth.call(this, newValue, oldValue); +} + + +// overridden +qx.Proto._changeInnerHeight = function(newValue, oldValue) { + var self = this; + window.setTimeout(function() { + self._updateScrollBarVisibility(); + qx.ui.core.Widget.flushGlobalQueues(); + }, 0); + + return qx.ui.layout.VerticalBoxLayout.prototype._changeInnerHeight.call(this, newValue, oldValue); +} + + +// overridden +qx.Proto._afterAppear = function() { + qx.ui.layout.VerticalBoxLayout.prototype._afterAppear.call(this); + + this._updateScrollBarVisibility(); +} + + +// overridden +qx.Proto.dispose = function() { + if (this.getDisposed()) { + return true; + } + + if (this._tableModel) { + this._tableModel.removeEventListener(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED, this._onTableModelMetaDataChanged, this); + } + + this._columnVisibilityBt.removeEventListener("execute", this._onColumnVisibilityBtExecuted, this); + this._columnVisibilityBt.dispose(); + + this._cleanupColumnVisibilityMenu(); + + this._cleanUpMetaColumns(0); + + var selectionModel = this.getSelectionModel(); + if (selectionModel != null) { + selectionModel.removeEventListener("changeSelection", this._onSelectionChanged, this); + } + + var tableModel = this.getTableModel(); + if (tableModel != null) { + tableModel.removeEventListener(qx.ui.table.TableModel.EVENT_TYPE_META_DATA_CHANGED, this._onTableModelMetaDataChanged, this); + tableModel.removeEventListener(qx.ui.table.TableModel.EVENT_TYPE_DATA_CHANGED, this._onTableModelDataChanged, this); + } + + var tableColumnModel = this.getTableColumnModel(); + if (tableColumnModel) { + tableColumnModel.removeEventListener("visibilityChanged", this._onColVisibilityChanged, this); + tableColumnModel.removeEventListener("widthChanged", this._onColWidthChanged, this); + tableColumnModel.removeEventListener("orderChanged", this._onColOrderChanged, this); + } + + this.removeEventListener("keydown", this._onkeydown); + this.removeEventListener("keypress", this._onkeypress); + + return qx.ui.layout.VerticalBoxLayout.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TableColumnModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TableColumnModel.js new file mode 100644 index 0000000000..334187a268 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TableColumnModel.js @@ -0,0 +1,399 @@ +/* ************************************************************************ + + 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) + +// These are needed because of their instantiation at bottom. I don't think this +// is a good idea. (wpbasti) +#require(qx.ui.table.DefaultHeaderCellRenderer) +#require(qx.ui.table.DefaultDataCellRenderer) +#require(qx.ui.table.TextFieldCellEditorFactory) + +************************************************************************ */ + +/** + * A model that contains all meta data about columns, such as width, renderers, + * visibility and order. + * + * @event widthChanged {qx.event.type.DataEvent} Fired when the width of a + * column has changed. The data property of the event is a map having the + * following attributes: + * <ul> + * <li>col: The model index of the column the width of which has changed.</li> + * <li>newWidth: The new width of the column in pixels.</li> + * <li>oldWidth: The old width of the column in pixels.</li> + * </ul> + * @event visibilityChangedPre {qx.event.type.DataEvent} Fired when the + * visibility of a column has changed. This event is equal to + * "visibilityChanged", but is fired right before. + * @event visibilityChanged {qx.event.type.DataEvent} Fired when the + * visibility of a column has changed. The data property of the + * event is a map having the following attributes: + * <ul> + * <li>col: The model index of the column the visibility of which has changed.</li> + * <li>visible: Whether the column is now visible.</li> + * </ul> + * @event orderChanged {qx.event.type.DataEvent} Fired when the column order + * has changed. The data property of the + * event is a map having the following attributes: + * <ul> + * <li>col: The model index of the column that was moved.</li> + * <li>fromOverXPos: The old overall x position of the column.</li> + * <li>toOverXPos: The new overall x position of the column.</li> + * </ul> + * + * @see com.ptvag.webcomponent.ui.table.TableModel + */ +qx.OO.defineClass("qx.ui.table.TableColumnModel", qx.core.Target, +function() { + qx.core.Target.call(this); +}); + + +/** + * Initializes the column model. + * + * @param colCount {int} the number of columns the model should have. + */ +qx.Proto.init = function(colCount) { + this._columnDataArr = []; + + var width = qx.ui.table.TableColumnModel.DEFAULT_WIDTH; + var headerRenderer = qx.ui.table.TableColumnModel.DEFAULT_HEADER_RENDERER; + var dataRenderer = qx.ui.table.TableColumnModel.DEFAULT_DATA_RENDERER; + var editorFactory = qx.ui.table.TableColumnModel.DEFAULT_EDITOR_FACTORY; + this._overallColumnArr = []; + this._visibleColumnArr = []; + for (var col = 0; col < colCount; col++) { + this._columnDataArr[col] = { width:width, headerRenderer:headerRenderer, + dataRenderer:dataRenderer, editorFactory:editorFactory } + this._overallColumnArr[col] = col; + this._visibleColumnArr[col] = col; + } + + this._colToXPosMap = null; +} + + +/** + * Sets the width of a column. + * + * @param col {int} the model index of the column. + * @param width {int} the new width the column should get in pixels. + */ +qx.Proto.setColumnWidth = function(col, width) { + var oldWidth = this._columnDataArr[col].width; + if (oldWidth != width) { + this._columnDataArr[col].width = width; + if (this.hasEventListeners("widthChanged")) { + var data = { col:col, newWidth:width, oldWidth:oldWidth } + this.dispatchEvent(new qx.event.type.DataEvent("widthChanged", data), true); + } + } +} + + +/** + * Returns the width of a column. + * + * @param col {int} the model index of the column. + * @return {int} the width of the column in pixels. + */ +qx.Proto.getColumnWidth = function(col) { + return this._columnDataArr[col].width; +} + + +/** + * Sets the header renderer of a column. + * + * @param col {int} the model index of the column. + * @param renderer {HeaderCellRenderer} the new header renderer the column + * should get. + */ +qx.Proto.setHeaderCellRenderer = function(col, renderer) { + this._columnDataArr[col].headerRenderer = renderer; +} + + +/** + * Returns the header renderer of a column. + * + * @param col {int} the model index of the column. + * @return {HeaderCellRenderer} the header renderer of the column. + */ +qx.Proto.getHeaderCellRenderer = function(col) { + return this._columnDataArr[col].headerRenderer; +} + + +/** + * Sets the data renderer of a column. + * + * @param col {int} the model index of the column. + * @param renderer {DataCellRenderer} the new data renderer the column should get. + */ +qx.Proto.setDataCellRenderer = function(col, renderer) { + this._columnDataArr[col].dataRenderer = renderer; +} + + +/** + * Returns the data renderer of a column. + * + * @param col {int} the model index of the column. + * @return {DataCellRenderer} the data renderer of the column. + */ +qx.Proto.getDataCellRenderer = function(col) { + return this._columnDataArr[col].dataRenderer; +} + + +/** + * Sets the cell editor factory of a column. + * + * @param col {int} the model index of the column. + * @param factory {CellEditorFactory} the new cell editor factory the column should get. + */ +qx.Proto.setCellEditorFactory = function(col, factory) { + this._columnDataArr[col].editorFactory = factory; +} + + +/** + * Returns the cell editor factory of a column. + * + * @param col {int} the model index of the column. + * @return {CellEditorFactory} the cell editor factory of the column. + */ +qx.Proto.getCellEditorFactory = function(col) { + return this._columnDataArr[col].editorFactory; +} + + +/** + * Returns the map that translates model indexes to x positions. + * <p> + * The returned map contains for a model index (int) a map having two + * properties: overX (the overall x position of the column, int) and + * visX (the visible x position of the column, int). visX is missing for + * hidden columns. + * + * @return the "column to x postion" map. + */ +qx.Proto._getColToXPosMap = function() { + if (this._colToXPosMap == null) { + this._colToXPosMap = {}; + for (var overX = 0; overX < this._overallColumnArr.length; overX++) { + var col = this._overallColumnArr[overX]; + this._colToXPosMap[col] = { overX:overX } + } + for (var visX = 0; visX < this._visibleColumnArr.length; visX++) { + var col = this._visibleColumnArr[visX]; + this._colToXPosMap[col].visX = visX; + } + } + return this._colToXPosMap; +} + + +/** + * Returns the number of visible columns. + * + * @return {int} the number of visible columns. + */ +qx.Proto.getVisibleColumnCount = function() { + return this._visibleColumnArr.length; +} + + +/** + * Returns the model index of a column at a certain visible x position. + * + * @param visXPos {int} the visible x position of the column. + * @return {int} the model index of the column. + */ +qx.Proto.getVisibleColumnAtX = function(visXPos) { + return this._visibleColumnArr[visXPos]; +} + + +/** + * Returns the visible x position of a column. + * + * @param col {int} the model index of the column. + * @return {int} the visible x position of the column. + */ +qx.Proto.getVisibleX = function(col) { + return this._getColToXPosMap()[col].visX; +} + + +/** + * Returns the overall number of columns (including hidden columns). + * + * @return {int} the overall number of columns. + */ +qx.Proto.getOverallColumnCount = function() { + return this._overallColumnArr.length; +} + + +/** + * Returns the model index of a column at a certain overall x position. + * + * @param overXPos {int} the overall x position of the column. + * @return {int} the model index of the column. + */ +qx.Proto.getOverallColumnAtX = function(overXPos) { + return this._overallColumnArr[overXPos]; +} + + +/** + * Returns the overall x position of a column. + * + * @param col {int} the model index of the column. + * @return {int} the overall x position of the column. + */ +qx.Proto.getOverallX = function(col) { + return this._getColToXPosMap()[col].overX; +} + + +/** + * Returns whether a certain column is visible. + * + * @param col {int} the model index of the column. + * @return {boolean} whether the column is visible. + */ +qx.Proto.isColumnVisible = function(col) { + return (this._getColToXPosMap()[col].visX != null); +} + + +/** + * Sets whether a certain column is visible. + * + * @param col {int} the model index of the column. + * @param visible {boolean} whether the column should be visible. + */ +qx.Proto.setColumnVisible = function(col, visible) { + if (visible != this.isColumnVisible(col)) { + if (visible) { + var colToXPosMap = this._getColToXPosMap(); + + var overX = colToXPosMap[col].overX; + if (overX == null) { + throw new Error("Showing column failed: " + col + + ". The column is not added to this TablePaneModel."); + } + + // get the visX of the next visible column after the column to show + var nextVisX; + for (var x = overX + 1; x < this._overallColumnArr.length; x++) { + var currCol = this._overallColumnArr[x]; + var currVisX = colToXPosMap[currCol].visX; + if (currVisX != null) { + nextVisX = currVisX; + break; + } + } + + // If there comes no visible column any more, then show the column + // at the end + if (nextVisX == null) { + nextVisX = this._visibleColumnArr.length; + } + + // Add the column to the visible columns + this._visibleColumnArr.splice(nextVisX, 0, col); + } else { + var visX = this.getVisibleX(col); + this._visibleColumnArr.splice(visX, 1); + } + + // Invalidate the _colToXPosMap + this._colToXPosMap = null; + + // Inform the listeners + if (! this._internalChange) { + if (this.hasEventListeners("visibilityChangedPre")) { + var data = { col:col, visible:visible } + this.dispatchEvent(new qx.event.type.DataEvent("visibilityChangedPre", data), true); + } + if (this.hasEventListeners("visibilityChanged")) { + var data = { col:col, visible:visible } + this.dispatchEvent(new qx.event.type.DataEvent("visibilityChanged", data), true); + } + } + + //this.debug("setColumnVisible col:"+col+",visible:"+visible+",this._overallColumnArr:"+this._overallColumnArr+",this._visibleColumnArr:"+this._visibleColumnArr); + } +} + + +/** + * Moves a column. + * + * @param fromOverXPos {int} the overall x postion of the column to move. + * @param toOverXPos {int} the overall x postion of where the column should be + * moved to. + */ +qx.Proto.moveColumn = function(fromOverXPos, toOverXPos) { + this._internalChange = true; + + var col = this._overallColumnArr[fromOverXPos]; + var visible = this.isColumnVisible(col); + + if (visible) { + this.setColumnVisible(col, false); + } + + this._overallColumnArr.splice(fromOverXPos, 1); + this._overallColumnArr.splice(toOverXPos, 0, col); + + // Invalidate the _colToXPosMap + this._colToXPosMap = null; + + if (visible) { + this.setColumnVisible(col, true); + } + + this._internalChange = false; + + // Inform the listeners + if (this.hasEventListeners("orderChanged")) { + var data = { col:col, fromOverXPos:fromOverXPos, toOverXPos:toOverXPos } + this.dispatchEvent(new qx.event.type.DataEvent("orderChanged", data), true); + } +} + + +/** {int} the default width of a column in pixels. */ +qx.Class.DEFAULT_WIDTH = 100; + +/** {DefaultDataCellRenderer} the default header cell renderer. */ +qx.Class.DEFAULT_HEADER_RENDERER = new qx.ui.table.DefaultHeaderCellRenderer; + +/** {DefaultDataCellRenderer} the default data cell renderer. */ +qx.Class.DEFAULT_DATA_RENDERER = new qx.ui.table.DefaultDataCellRenderer; + +/** {TextFieldCellEditorFactory} the default editor factory. */ +qx.Class.DEFAULT_EDITOR_FACTORY = new qx.ui.table.TextFieldCellEditorFactory; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TableModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TableModel.js new file mode 100644 index 0000000000..6bf4a55291 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TableModel.js @@ -0,0 +1,243 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * The data model of a table. + * + * @event dataChanged {qx.event.type.DataEvent} Fired when the table data changed + * (the stuff shown in the table body). The data property of the event + * may be null or a map having the following attributes: + * <ul> + * <li>firstRow: The index of the first row that has changed.</li> + * <li>lastRow: The index of the last row that has changed.</li> + * <li>firstColumn: The model index of the first column that has changed.</li> + * <li>lastColumn: The model index of the last column that has changed.</li> + * </ul> + * @event metaDataChanged {qx.event.type.Event} Fired when the meta data changed + * (the stuff shown in the table header). + */ +qx.OO.defineClass("qx.ui.table.TableModel", qx.core.Target, +function() { + qx.core.Target.call(this); +}); + + +/** + * Returns the number of rows in the model. + * + * @return {int} the number of rows. + */ +qx.Proto.getRowCount = function() { + throw new Error("getRowCount is abstract"); +} + + +/** + * <p>Returns the data of one row. This function may be overriden by models which hold + * all data of a row in one object. By using this function, clients have a way of + * quickly retrieving the entire row data.</p> + * + * <p><b>Important:</b>Models which do not have their row data accessible in one object + * may return null.</p> + * + * @param rowIndex {int} the model index of the row. + * @return {Object} the row data as an object or null if the model does not support row data + * objects. The details on the object returned are determined by the model + * implementation only. + */ +qx.Proto.getRowData = function(rowIndex) { + return null; +} + + +/** + * Returns the number of columns in the model. + * + * @return {int} the number of columns. + */ +qx.Proto.getColumnCount = function() { + throw new Error("getColumnCount is abstract"); +} + + +/** + * Returns the ID of column. The ID may be used to identify columns + * independent from their index in the model. E.g. for being aware of added + * columns when saving the width of a column. + * + * @param columnIndex {int} the index of the column. + * @return {string} the ID of the column. + */ +qx.Proto.getColumnId = function(columnIndex) { + throw new Error("getColumnId is abstract"); +} + + +/** + * Returns the index of a column. + * + * @param columnId {string} the ID of the column. + * @return {int} the index of the column. + */ +qx.Proto.getColumnIndexById = function(columnId) { + throw new Error("getColumnIndexById is abstract"); +} + + +/** + * Returns the name of a column. This name will be shown to the user in the + * table header. + * + * @param columnIndex {int} the index of the column. + * @return {string} the name of the column. + */ +qx.Proto.getColumnName = function(columnIndex) { + throw new Error("getColumnName is abstract"); +} + + +/** + * Returns whether a column is editable. + * + * @param columnIndex {int} the column to check. + * @return {boolean} whether the column is editable. + */ +qx.Proto.isColumnEditable = function(columnIndex) { + return false; +} + + +/** + * Returns whether a column is sortable. + * + * @param columnIndex {int} the column to check. + * @return {boolean} whether the column is sortable. + */ +qx.Proto.isColumnSortable = function(columnIndex) { + return false; +} + + +/** + * Sorts the model by a column. + * + * @param columnIndex {int} the column to sort by. + * @param ascending {boolean} whether to sort ascending. + */ +qx.Proto.sortByColumn = function(columnIndex, ascending) { +} + + +/** + * Returns the column index the model is sorted by. If the model is not sorted + * -1 is returned. + * + * @return {int} the column index the model is sorted by. + */ +qx.Proto.getSortColumnIndex = function() { + return -1; +} + + +/** + * Returns whether the model is sorted ascending. + * + * @return {boolean} whether the model is sorted ascending. + */ +qx.Proto.isSortAscending = function() { + return true; +} + + +/** + * Prefetches some rows. This is a hint to the model that the specified rows + * will be read soon. + * + * @param firstRowIndex {int} the index of first row. + * @param lastRowIndex {int} the index of last row. + */ +qx.Proto.prefetchRows = function(firstRowIndex, lastRowIndex) { +} + + +/** + * Returns a cell value by column index. + * + * @param columnIndex {int} the index of the column. + * @param rowIndex {int} the index of the row. + * @return {var} The value of the cell. + * @see #getValueById{} + */ +qx.Proto.getValue = function(columnIndex, rowIndex) { + throw new Error("getValue is abstract"); +} + + +/** + * Returns a cell value by column ID. + * <p> + * Whenever you have the choice, use {@link #getValue()} instead, + * because this should be faster. + * + * @param columnId {string} the ID of the column. + * @param rowIndex {int} the index of the row. + * @return {var} the value of the cell. + */ +qx.Proto.getValueById = function(columnId, rowIndex) { + return this.getValue(this.getColumnIndexById(columnId), rowIndex); +} + + +/** + * Sets a cell value by column index. + * + * @param columnIndex {int} The index of the column. + * @param rowIndex {int} the index of the row. + * @param value {var} The new value. + * @see #setValueById{} + */ +qx.Proto.setValue = function(columnIndex, rowIndex, value) { + throw new Error("setValue is abstract"); +} + + +/** + * Sets a cell value by column ID. + * <p> + * Whenever you have the choice, use {@link #setValue()} instead, + * because this should be faster. + * + * @param columnId {string} The ID of the column. + * @param rowIndex {int} The index of the row. + * @param value {var} The new value. + */ +qx.Proto.setValueById = function(columnId, rowIndex, value) { + return this.setValue(this.getColumnIndexById(columnId), rowIndex, value); +} + + +/** {string} The type of the event fired when the data changed. */ +qx.Class.EVENT_TYPE_DATA_CHANGED = "dataChanged"; + +/** {string} The type of the event fired when the meta data changed. */ +qx.Class.EVENT_TYPE_META_DATA_CHANGED = "metaDataChanged"; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePane.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePane.js new file mode 100644 index 0000000000..41db2ab274 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePane.js @@ -0,0 +1,486 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * The table pane that shows a certain section from a table. This class handles + * the display of the data part of a table and is therefore the base for virtual + * scrolling. + * + * @param paneScroller {TablePaneScroller} the TablePaneScroller the header belongs to. + */ +qx.OO.defineClass("qx.ui.table.TablePane", qx.ui.basic.Terminator, +function(paneScroller) { + qx.ui.basic.Terminator.call(this); + + this._paneScroller = paneScroller; + + this.debug("USE_ARRAY_JOIN:" + qx.ui.table.TablePane.USE_ARRAY_JOIN + ", USE_TABLE:" + qx.ui.table.TablePane.USE_TABLE); + + this._lastColCount = 0; + this._lastRowCount = 0; +}); + +/** The index of the first row to show. */ +qx.OO.addProperty({ name:"firstVisibleRow", type:"number", defaultValue:0 }); + +/** The number of rows to show. */ +qx.OO.addProperty({ name:"visibleRowCount", type:"number", defaultValue:0 }); + + +// property modifier +qx.Proto._modifyFirstVisibleRow = function(propValue, propOldValue, propData) { + this._updateContent(); + return true; +} + + +// property modifier +qx.Proto._modifyVisibleRowCount = function(propValue, propOldValue, propData) { + this._updateContent(); + return true; +} + + +// overridden +qx.Proto._afterAppear = function() { + qx.ui.basic.Terminator.prototype._afterAppear.call(this); + + if (this._updateWantedWhileInvisible) { + // We are visible now and an update was wanted while we were invisible + // -> Do the update now + this._updateContent(); + this._updateWantedWhileInvisible = false; + } +}; + + +/** + * Returns the TablePaneScroller this pane belongs to. + * + * @return {TablePaneScroller} the TablePaneScroller. + */ +qx.Proto.getPaneScroller = function() { + return this._paneScroller; +}; + + +/** + * Returns the table this pane belongs to. + * + * @return {Table} the table. + */ +qx.Proto.getTable = function() { + return this._paneScroller.getTable(); +}; + + +/** + * 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. + * @param massUpdate {boolean ? false} Whether other updates are planned as well. + * If true, no repaint will be done. + */ +qx.Proto.setFocusedCell = function(col, row, massUpdate) { + if (col != this._focusedCol || row != this._focusedRow) { + var oldCol = this._focusedCol; + var oldRow = this._focusedRow; + this._focusedCol = col; + this._focusedRow = row; + + // Update the focused row background + if (row != oldRow && !massUpdate) { + // NOTE: Only the old and the new row need update + this._updateContent(false, oldRow, true); + this._updateContent(false, row, true); + } + } +} + + +/** + * Event handler. Called when the selection has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onSelectionChanged = function(evt) { + this._updateContent(false, null, true); +} + + +/** + * Event handler. Called when the table gets or looses the focus. + */ +qx.Proto._onFocusChanged = function(evt) { + this._updateContent(false, null, true); +}; + + +/** + * Event handler. Called when the width of a column has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColWidthChanged = function(evt) { + this._updateContent(true); +} + + +/** + * Event handler. Called the column order has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColOrderChanged = function(evt) { + this._updateContent(true); +} + + +/** + * Event handler. Called when the pane model has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onPaneModelChanged = function(evt) { + this._updateContent(true); +} + + +/** + * Event handler. Called when the table model data has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onTableModelDataChanged = function(evt) { + var data = evt.getData ? evt.getData() : null; + + var firstRow = this.getFirstVisibleRow(); + var rowCount = this.getVisibleRowCount(); + if (data == null || data.lastRow == -1 + || data.lastRow >= firstRow && data.firstRow < firstRow + rowCount) + { + // The change intersects this pane + this._updateContent(); + } +} + + +/** + * Event handler. Called when the table model meta data has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onTableModelMetaDataChanged = function(evt) { + this._updateContent(); +} + + +/** + * Updates the content of the pane. + * + * @param completeUpdate {boolean ? false} if true a complete update is performed. + * On a complete update all cell widgets are recreated. + * @param onlyRow {int ? null} if set only the specified row will be updated. + * @param onlySelectionOrFocusChanged {boolean ? false} if true, cell values won't + * be updated. Only the row background will. + */ +qx.Proto._updateContent = function(completeUpdate, onlyRow, + onlySelectionOrFocusChanged) +{ + if (! this.isSeeable()) { + this._updateWantedWhileInvisible = true; + return; + } + + if (qx.ui.table.TablePane.USE_ARRAY_JOIN) { + this._updateContent_array_join(completeUpdate, onlyRow, onlySelectionOrFocusChanged); + } else { + this._updateContent_orig(completeUpdate, onlyRow, onlySelectionOrFocusChanged); + } +} + + +qx.Proto._updateContent_array_join = function(completeUpdate, onlyRow, + onlySelectionOrFocusChanged) +{ + var TablePane = qx.ui.table.TablePane; + + var table = this.getTable(); + + var selectionModel = table.getSelectionModel(); + var tableModel = table.getTableModel(); + var columnModel = table.getTableColumnModel(); + var paneModel = this.getPaneScroller().getTablePaneModel(); + var rowRenderer = table.getDataRowRenderer(); + + var colCount = paneModel.getColumnCount(); + var rowHeight = table.getRowHeight(); + + var firstRow = this.getFirstVisibleRow(); + var rowCount = this.getVisibleRowCount(); + var modelRowCount = tableModel.getRowCount(); + if (firstRow + rowCount > modelRowCount) { + rowCount = Math.max(0, modelRowCount - firstRow); + } + + var cellInfo = { table:table }; + cellInfo.styleHeight = rowHeight; + + var htmlArr = []; + var rowWidth = paneModel.getTotalWidth(); + + if (TablePane.USE_TABLE) { + htmlArr.push('<table cellspacing\="0" cellpadding\="0" style\="table-layout:fixed;font-family:\'Segoe UI\', Corbel, Calibri, Tahoma, \'Lucida Sans Unicode\', sans-serif;font-size:11px;width:'); + htmlArr.push(rowWidth); + htmlArr.push('px"><colgroup>'); + + for (var x = 0; x < colCount; x++) { + var col = paneModel.getColumnAtX(x); + + htmlArr.push(); + htmlArr.push(columnModel.getColumnWidth(col)); + htmlArr.push('"/>'); + } + + htmlArr.push('</colgroup><tbody>'); + } + + tableModel.prefetchRows(firstRow, firstRow + rowCount - 1); + for (var y = 0; y < rowCount; y++) { + var row = firstRow + y; + + cellInfo.row = row; + cellInfo.selected = selectionModel.isSelectedIndex(row); + cellInfo.focusedRow = (this._focusedRow == row); + cellInfo.rowData = tableModel.getRowData(row); + + // Update this row + if (TablePane.USE_TABLE) { + htmlArr.push('<tr style\="height:'); + htmlArr.push(rowHeight); + } else { + htmlArr.push('<div style\="position:absolute;font-family:\'Segoe UI\', Corbel, Calibri, Tahoma, \'Lucida Sans Unicode\', sans-serif;font-size:11px;left:0px;top:'); + htmlArr.push(y * rowHeight); + htmlArr.push('px;width:'); + htmlArr.push(rowWidth); + htmlArr.push('px;height:'); + htmlArr.push(rowHeight); + htmlArr.push('px;background-color:'); + } + + rowRenderer._createRowStyle_array_join(cellInfo, htmlArr); + + htmlArr.push('">'); + + var left = 0; + for (var x = 0; x < colCount; x++) { + var col = paneModel.getColumnAtX(x); + cellInfo.xPos = x; + cellInfo.col = col; + cellInfo.editable = tableModel.isColumnEditable(col); + cellInfo.focusedCol = (this._focusedCol == col); + cellInfo.value = tableModel.getValue(col, row); + var cellWidth = columnModel.getColumnWidth(col); + + cellInfo.styleLeft = left; + cellInfo.styleWidth = cellWidth; + + var cellRenderer = columnModel.getDataCellRenderer(col); + cellRenderer.createDataCellHtml_array_join(cellInfo, htmlArr); + + left += cellWidth; + } + + if (TablePane.USE_TABLE) { + htmlArr.push('</tr>'); + } else { + htmlArr.push('</div>'); + } + } + + if (TablePane.USE_TABLE) { + htmlArr.push('</tbody></table>'); + } + + var elem = this.getElement(); + // this.debug(">>>" + htmlArr.join("") + "<<<") + elem.innerHTML = htmlArr.join(""); + + this.setHeight(rowCount * rowHeight); + + this._lastColCount = colCount; + this._lastRowCount = rowCount; +} + + +qx.Proto._updateContent_orig = function(completeUpdate, onlyRow, + onlySelectionOrFocusChanged) +{ + var TablePane = qx.ui.table.TablePane; + + var table = this.getTable(); + + var alwaysUpdateCells = table.getAlwaysUpdateCells(); + + var selectionModel = table.getSelectionModel(); + var tableModel = table.getTableModel(); + var columnModel = table.getTableColumnModel(); + var paneModel = this.getPaneScroller().getTablePaneModel(); + var rowRenderer = table.getDataRowRenderer(); + + var colCount = paneModel.getColumnCount(); + var rowHeight = table.getRowHeight(); + + var firstRow = this.getFirstVisibleRow(); + var rowCount = this.getVisibleRowCount(); + var modelRowCount = tableModel.getRowCount(); + if (firstRow + rowCount > modelRowCount) { + rowCount = Math.max(0, modelRowCount - firstRow); + } + + // Remove the rows that are not needed any more + if (completeUpdate || this._lastRowCount > rowCount) { + var firstRowToRemove = completeUpdate ? 0 : rowCount; + this._cleanUpRows(firstRowToRemove); + } + + if (TablePane.USE_TABLE) { + throw new Error("Combination of USE_TABLE==true and USE_ARRAY_JOIN==false is not yet implemented"); + } + + var elem = this.getElement(); + var childNodes = elem.childNodes; + var cellInfo = { table:table }; + tableModel.prefetchRows(firstRow, firstRow + rowCount - 1); + for (var y = 0; y < rowCount; y++) { + var row = firstRow + y; + if ((onlyRow != null) && (row != onlyRow)) { + continue; + } + + cellInfo.row = row; + cellInfo.selected = selectionModel.isSelectedIndex(row); + cellInfo.focusedRow = (this._focusedRow == row); + cellInfo.rowData = tableModel.getRowData(row); + + // Update this row + var rowElem; + var recyleRowElem; + if (y < childNodes.length) { + rowElem = childNodes[y]; + recyleRowElem = true + } else { + var rowElem = document.createElement("div"); + + //rowElem.style.position = "relative"; + rowElem.style.position = "absolute"; + rowElem.style.left = "0px"; + rowElem.style.top = (y * rowHeight) + "px"; + + rowElem.style.height = rowHeight + "px"; + rowElem.style.fontFamily = TablePane.CONTENT_ROW_FONT_FAMILY; + rowElem.style.fontSize = TablePane.CONTENT_ROW_FONT_SIZE; + elem.appendChild(rowElem); + recyleRowElem = false; + } + + rowRenderer.updateDataRowElement(cellInfo, rowElem); + + if (alwaysUpdateCells || !recyleRowElem || !onlySelectionOrFocusChanged) { + var html = ""; + var left = 0; + for (var x = 0; x < colCount; x++) { + var col = paneModel.getColumnAtX(x); + cellInfo.xPos = x; + cellInfo.col = col; + cellInfo.editable = tableModel.isColumnEditable(col); + cellInfo.focusedCol = (this._focusedCol == col); + cellInfo.value = tableModel.getValue(col, row); + var width = columnModel.getColumnWidth(col); + cellInfo.style = 'position:absolute;left:' + left + + 'px;top:0px;width:' + width + + 'px; height:' + rowHeight + "px"; + + var cellRenderer = columnModel.getDataCellRenderer(col); + if (recyleRowElem) { + var cellElem = rowElem.childNodes[x]; + cellRenderer.updateDataCellElement(cellInfo, cellElem); + } else { + html += cellRenderer.createDataCellHtml(cellInfo); + } + + left += width; + } + if (! recyleRowElem) { + rowElem.style.width = left + "px"; + rowElem.innerHTML = html; + } + } + } + + this.setHeight(rowCount * rowHeight); + + this._lastColCount = colCount; + this._lastRowCount = rowCount; +} + + +/** + * Cleans up the row widgets. + * + * @param firstRowToRemove {int} the visible index of the first row to clean up. + * All following rows will be cleaned up, too. + */ +qx.Proto._cleanUpRows = function(firstRowToRemove) { + var elem = this.getElement(); + if (elem) { + var childNodes = this.getElement().childNodes; + var paneModel = this.getPaneScroller().getTablePaneModel(); + var colCount = paneModel.getColumnCount(); + for (var y = childNodes.length - 1; y >= firstRowToRemove; y--) { + elem.removeChild(childNodes[y]); + } + } +} + + +// overridden +qx.Proto.dispose = function() { + if (this.getDisposed()) { + return true; + } + + this._cleanUpRows(0); + + return qx.ui.basic.Terminator.prototype.dispose.call(this); +} + + +qx.Class.USE_ARRAY_JOIN = false; +qx.Class.USE_TABLE = false; + + +qx.Class.CONTENT_ROW_FONT_FAMILY = '"Segoe UI", Corbel, Calibri, Tahoma, "Lucida Sans Unicode", sans-serif'; +qx.Class.CONTENT_ROW_FONT_SIZE = "11px"; + diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneHeader.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneHeader.js new file mode 100644 index 0000000000..657950293f --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneHeader.js @@ -0,0 +1,276 @@ +/* ************************************************************************ + + 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 the header of a table. + * + * @param paneScroller {TablePaneScroller} the TablePaneScroller the header belongs to. + */ +qx.OO.defineClass("qx.ui.table.TablePaneHeader", qx.ui.layout.HorizontalBoxLayout, +function(paneScroller) { + qx.ui.layout.HorizontalBoxLayout.call(this); + + this._paneScroller = paneScroller; +}); + + +/** + * Returns the TablePaneScroller this header belongs to. + * + * @return {TablePaneScroller} the TablePaneScroller. + */ +qx.Proto.getPaneScroller = function() { + return this._paneScroller; +}; + + +/** + * Returns the table this header belongs to. + * + * @return {Table} the table. + */ +qx.Proto.getTable = function() { + return this._paneScroller.getTable(); +}; + + +/** + * Event handler. Called when the width of a column has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColWidthChanged = function(evt) { + var data = evt.getData(); + this.setColumnWidth(data.col, data.newWidth); +} + + +/** + * Event handler. Called the column order has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColOrderChanged = function(evt) { + this._updateContent(true); +} + + +/** + * Event handler. Called when the pane model has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onPaneModelChanged = function(evt) { + this._updateContent(true); +} + + +/** + * Event handler. Called when the table model meta data has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onTableModelMetaDataChanged = function(evt) { + this._updateContent(); +} + + +/** + * Sets the column width. This overrides the width from the column model. + * + * @param col {int} the column to change the width for. + * @param width {int} the new width. + */ +qx.Proto.setColumnWidth = function(col, width) { + var x = this.getPaneScroller().getTablePaneModel().getX(col); + var children = this.getChildren(); + if (children[x] != null) { + children[x].setWidth(width); + } +} + + +/** + * Sets the column the mouse is currently over. + * + * @param col {int} the model index of the column the mouse is currently over or + * null if the mouse is over no column. + */ +qx.Proto.setMouseOverColumn = function(col) { + if (col != this._lastMouseOverColumn) { + var paneModel = this.getPaneScroller().getTablePaneModel(); + var children = this.getChildren(); + + if (this._lastMouseOverColumn != null) { + var widget = children[paneModel.getX(this._lastMouseOverColumn)]; + if (widget != null) { + widget.removeState("mouseover"); + } + } + if (col != null) { + children[paneModel.getX(col)].addState("mouseover"); + } + + this._lastMouseOverColumn = col; + } +} + + +/** + * Shows the feedback shown while a column is moved by the user. + * + * @param col {int} the model index of the column to show the move feedback for. + * @param x {int} the x position the left side of the feeback should have + * (in pixels, relative to the left side of the header). + */ +qx.Proto.showColumnMoveFeedback = function(col, x) { + var elem = this.getElement(); + if (this._moveFeedback == null) { + var xPos = this.getPaneScroller().getTablePaneModel().getX(col); + var cellWidget = this.getChildren()[xPos]; + + // Create the feedback + // Workaround: Since a cloned widget throws an exception when it is + // added to another component we have to create a new one + // using the renderer + //this._moveFeedback = cellWidget.clone(); + var tableModel = this.getTable().getTableModel(); + var columnModel = this.getTable().getTableColumnModel(); + var cellInfo = { xPos:xPos, col:col, name:tableModel.getColumnName(col) } + var cellRenderer = columnModel.getHeaderCellRenderer(col); + this._moveFeedback = cellRenderer.createHeaderCell(cellInfo); + + // Configure the feedback + with (this._moveFeedback) { + setWidth(cellWidget.getBoxWidth()); + setHeight(cellWidget.getBoxHeight()); + setZIndex(1000000); + setOpacity(0.8); + setTop(qx.dom.Location.getClientBoxTop(elem)); + } + this.getTopLevelWidget().add(this._moveFeedback); + } + + this._moveFeedback.setLeft(qx.dom.Location.getClientBoxLeft(elem) + x); +} + + +/** + * Hides the feedback shown while a column is moved by the user. + */ +qx.Proto.hideColumnMoveFeedback = function() { + if (this._moveFeedback != null) { + this.getTopLevelWidget().remove(this._moveFeedback); + this._moveFeedback.dispose(); + this._moveFeedback = null; + } +} + + +/** + * Returns whether the column move feedback is currently shown. + */ +qx.Proto.isShowingColumnMoveFeedback = function() { + return this._moveFeedback != null; +} + + +/** + * Updates the content of the header. + * + * @param completeUpdate {boolean} if true a complete update is performed. On a + * complete update all header widgets are recreated. + */ +qx.Proto._updateContent = function(completeUpdate) { + var tableModel = this.getTable().getTableModel(); + var columnModel = this.getTable().getTableColumnModel(); + var paneModel = this.getPaneScroller().getTablePaneModel(); + + var children = this.getChildren(); + var oldColCount = children.length; + var colCount = paneModel.getColumnCount(); + + var sortedColum = tableModel.getSortColumnIndex(); + + // Remove all widgets on the complete update + if (completeUpdate) { + this._cleanUpCells(); + } + + // Update the header + var cellInfo = {}; + cellInfo.sortedAscending = tableModel.isSortAscending(); + for (var x = 0; x < colCount; x++) { + var col = paneModel.getColumnAtX(x); + + var colWidth = columnModel.getColumnWidth(col); + + // TODO: Get real cell renderer + var cellRenderer = columnModel.getHeaderCellRenderer(col); + + cellInfo.xPos = x; + cellInfo.col = col; + cellInfo.name = tableModel.getColumnName(col); + cellInfo.editable = tableModel.isColumnEditable(col); + cellInfo.sorted = (col == sortedColum); + + // Get the cached widget + var cachedWidget = children[x]; + + // Create or update the widget + if (cachedWidget == null) { + // We have no cached widget -> create it + cachedWidget = cellRenderer.createHeaderCell(cellInfo); + cachedWidget.set({ width:colWidth, height:"100%" }); + + this.add(cachedWidget); + } else { + // This widget already created before -> recycle it + cellRenderer.updateHeaderCell(cellInfo, cachedWidget); + } + } +} + + +/** + * Cleans up all header cells. + */ +qx.Proto._cleanUpCells = function() { + var children = this.getChildren(); + for (var x = children.length - 1; x >= 0; x--) { + var cellWidget = children[x]; + //this.debug("disposed:" + cellWidget.getDisposed() + ",has parent: " + (cellWidget.getParent() != null) + ",x:"+x); + this.remove(cellWidget); + cellWidget.dispose(); + } +} + + +// overridden +qx.Proto.dispose = function() { + if (this.getDisposed()) { + return true; + } + + return qx.ui.layout.HorizontalBoxLayout.prototype.dispose.call(this); +} diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneModel.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneModel.js new file mode 100644 index 0000000000..d53da59251 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TablePaneModel.js @@ -0,0 +1,179 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * The model of a table pane. This model works as proxy to a + * {@link TableColumnModel} and manages the visual order of the columns shown in + * a {@link TablePane}. + * + * @param tableColumnModel {TableColumnModel} The TableColumnModel of which this + * model is the proxy. + * + * @event modelChanged {qx.event.type.Event} Fired when the model changed. + */ +qx.OO.defineClass("qx.ui.table.TablePaneModel", qx.core.Target, +function(tableColumnModel) { + qx.core.Target.call(this); + + tableColumnModel.addEventListener("visibilityChangedPre", this._onColVisibilityChanged, this); + + this._tableColumnModel = tableColumnModel; +}); + + +/** The visible x position of the first column this model should contain. */ +qx.OO.addProperty({ name : "firstColumnX", type : "number", defaultValue : 0 }); + +/** + * The maximum number of columns this model should contain. If -1 this model will + * contain all remaining columns. + */ +qx.OO.addProperty({ name : "maxColumnCount", type : "number", defaultValue : -1 }); + + +// property modifier +qx.Proto._modifyFirstColumnX = function(propValue, propOldValue, propData) { + this._columnCount = null; + this.createDispatchEvent(qx.ui.table.TablePaneModel.EVENT_TYPE_MODEL_CHANGED); + return true; +} + + +// property modifier +qx.Proto._modifyMaxColumnCount = function(propValue, propOldValue, propData) { + this._columnCount = null; + this.createDispatchEvent(qx.ui.table.TablePaneModel.EVENT_TYPE_MODEL_CHANGED); + return true; +} + + +/** + * Event handler. Called when the visibility of a column has changed. + * + * @param evt {Map} the event. + */ +qx.Proto._onColVisibilityChanged = function(evt) { + this._columnCount = null; + + // TODO: Check whether the column is in this model (This is a little bit + // tricky, because the column could _have been_ in this model, but is + // not in it after the change) + this.createDispatchEvent(qx.ui.table.TablePaneModel.EVENT_TYPE_MODEL_CHANGED); +} + + +/** + * Returns the number of columns in this model. + * + * @return {int} the number of columns in this model. + */ +qx.Proto.getColumnCount = function() { + if (this._columnCount == null) { + var firstX = this.getFirstColumnX(); + var maxColCount = this.getMaxColumnCount(); + var totalColCount = this._tableColumnModel.getVisibleColumnCount(); + + if (maxColCount == -1 || (firstX + maxColCount) > totalColCount) { + this._columnCount = totalColCount - firstX; + } else { + this._columnCount = maxColCount; + } + } + return this._columnCount; +} + + +/** + * Returns the model index of the column at the position <code>xPos</code>. + * + * @param xPos {int} the x postion in the table pane of the column. + * @return {int} the model index of the column. + */ +qx.Proto.getColumnAtX = function(xPos) { + var firstX = this.getFirstColumnX(); + return this._tableColumnModel.getVisibleColumnAtX(firstX + xPos); +} + + +/** + * Returns the x position of the column <code>col</code>. + * + * @param col {int} the model index of the column. + * @return {int} the x postion in the table pane of the column. + */ +qx.Proto.getX = function(col) { + var firstX = this.getFirstColumnX(); + var maxColCount = this.getMaxColumnCount(); + + var x = this._tableColumnModel.getVisibleX(col) - firstX; + if (x >= 0 && (maxColCount == -1 || x < maxColCount)) { + return x; + } else { + return -1; + } +} + + +/** + * Gets the position of the left side of a column (in pixels, relative to the + * left side of the table pane). + * <p> + * This value corresponds to the sum of the widths of all columns left of the + * column. + * + * @param col {int} the model index of the column. + * @return the position of the left side of the column. + */ +qx.Proto.getColumnLeft = function(col) { + var left = 0; + var colCount = this.getColumnCount(); + for (var x = 0; x < colCount; x++) { + var currCol = this.getColumnAtX(x); + if (currCol == col) { + return left; + } + + left += this._tableColumnModel.getColumnWidth(currCol); + } + return -1; +} + + +/** + * Returns the total width of all columns in the model. + * + * @return {int} the total width of all columns in the model. + */ +qx.Proto.getTotalWidth = function() { + var totalWidth = 0; + var colCount = this.getColumnCount(); + for (var x = 0; x < colCount; x++) { + var col = this.getColumnAtX(x); + totalWidth += this._tableColumnModel.getColumnWidth(col); + } + return totalWidth; +} + + +/** {string} The type of the event fired when the model changed. */ +qx.Class.EVENT_TYPE_MODEL_CHANGED = "modelChanged"; 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"; diff --git a/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TextFieldCellEditorFactory.js b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TextFieldCellEditorFactory.js new file mode 100644 index 0000000000..6878ce7470 --- /dev/null +++ b/webapps/qooxdoo-0.6.3-sdk/frontend/framework/source/class/qx/ui/table/TextFieldCellEditorFactory.js @@ -0,0 +1,58 @@ +/* ************************************************************************ + + 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) + +************************************************************************ */ + +/** + * A cell editor factory creating text fields. + */ +qx.OO.defineClass("qx.ui.table.TextFieldCellEditorFactory", qx.ui.table.CellEditorFactory, +function() { + qx.ui.table.CellEditorFactory.call(this); +}); + + +// overridden +qx.Proto.createCellEditor = function(cellInfo) { + var cellEditor = new qx.ui.form.TextField; + cellEditor.setAppearance("table-editor-textfield"); + cellEditor.originalValue = cellInfo.value; + cellEditor.setValue("" + cellInfo.value); + + cellEditor.addEventListener("appear", function() { + this.selectAll(); + }); + + return cellEditor; +} + + +// overridden +qx.Proto.getCellEditorValue = function(cellEditor) { + // Workaround: qx.ui.form.TextField.getValue() delivers the old value, so we use the + // value property of the DOM element directly + var value = cellEditor.getElement().value; + + if (typeof cellEditor.originalValue == "number") { + value = parseFloat(value); + } + return value; +} |