+package org.simantics.browsing.ui.nattable;\r
+\r
+import java.util.HashMap;\r
+import java.util.Map;\r
+\r
+import org.eclipse.core.runtime.Assert;\r
+import org.eclipse.jface.util.Util;\r
+import org.eclipse.jface.viewers.ColumnLayoutData;\r
+import org.eclipse.jface.viewers.ColumnPixelData;\r
+import org.eclipse.jface.viewers.ColumnWeightData;\r
+import org.eclipse.nebula.widgets.nattable.NatTable;\r
+import org.eclipse.nebula.widgets.nattable.coordinate.Range;\r
+import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;\r
+import org.eclipse.nebula.widgets.nattable.layer.ILayerListener;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ColumnDeleteEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ColumnInsertEvent;\r
+import org.eclipse.nebula.widgets.nattable.layer.event.ILayerEvent;\r
+import org.eclipse.nebula.widgets.nattable.resize.event.ColumnResizeEvent;\r
+import org.eclipse.swt.graphics.Point;\r
+import org.eclipse.swt.graphics.Rectangle;\r
+import org.eclipse.swt.widgets.Composite;\r
+import org.eclipse.swt.widgets.Layout;\r
+import org.eclipse.swt.widgets.Scrollable;\r
+\r
+\r
+/**\r
+ * Modified org.eclipse.jface.layout.AbstractColumnLayout and TreeColumnLayout to NatTable compatible.\r
+ * \r
+ * @author Marko Luukkainen <marko.luukkainen@vtt.fi>\r
+ *\r
+ */\r
+public class NatTableColumnLayout extends Layout implements ILayerListener{\r
+ private static int COLUMN_TRIM;\r
+ static {\r
+ if (Util.isWindows()) {\r
+ COLUMN_TRIM = 4;\r
+ } else if (Util.isMac()) {\r
+ COLUMN_TRIM = 24;\r
+ } else {\r
+ COLUMN_TRIM = 3;\r
+ }\r
+ }\r
+ \r
+ NatTable natTable;\r
+ GEColumnHeaderDataProvider columnHeaderDataProvider;\r
+ DefaultRowHeaderDataLayer rowHeaderDataLayer;\r
+ Map<Integer, ColumnLayoutData> layoutDatas = new HashMap<>();\r
+ \r
+ private boolean inupdateMode = false;\r
+\r
+ private boolean relayout = true;\r
+ \r
+ public NatTableColumnLayout(NatTable natTable, GEColumnHeaderDataProvider columnHeaderDataProvider) {\r
+ this.natTable = natTable;\r
+ this.columnHeaderDataProvider = columnHeaderDataProvider;\r
+ this.natTable.addLayerListener(this);\r
+ }\r
+ \r
+ public NatTableColumnLayout(NatTable natTable, GEColumnHeaderDataProvider columnHeaderDataProvider, DefaultRowHeaderDataLayer rowHeaderDataLayer) {\r
+ this.natTable = natTable;\r
+ this.columnHeaderDataProvider = columnHeaderDataProvider;\r
+ this.natTable.addLayerListener(this);\r
+ this.rowHeaderDataLayer = rowHeaderDataLayer;\r
+ }\r
+ \r
+ public void setColumnData(int column, ColumnLayoutData data) {\r
+ layoutDatas.put(column, data);\r
+ }\r
+ \r
+ protected void setColumnWidths(Scrollable tree, int[] widths) {\r
+ for (int i=0; i < widths.length; i++) {\r
+ columnHeaderDataProvider.getDataLayer().setColumnWidthByPosition(i, widths[i]);\r
+ }\r
+ }\r
+ \r
+ @Override\r
+ public void handleLayerEvent(ILayerEvent event) {\r
+ if (inupdateMode)\r
+ return;\r
+ if (event instanceof ColumnResizeEvent) {\r
+ ColumnResizeEvent evt = (ColumnResizeEvent)event;\r
+ for (Range r : evt.getColumnPositionRanges()) {\r
+ int colIndex = evt.getLayer().getColumnIndexByPosition(r.start);\r
+ int w = columnHeaderDataProvider.getDataLayer().getColumnWidthByPosition(colIndex);\r
+ setColumnData(colIndex, new ColumnPixelData(w));\r
+ }\r
+ update();\r
+ } else if (event instanceof ColumnInsertEvent ||\r
+ event instanceof ColumnDeleteEvent) {\r
+ update();\r
+ } \r
+ }\r
+ \r
+ boolean updateCalled = false;\r
+ \r
+ private void update() {\r
+ if (updateCalled)\r
+ return;\r
+ updateCalled = true;\r
+ natTable.getDisplay().asyncExec(new Runnable() {\r
+ \r
+ @Override\r
+ public void run() {\r
+ if (!natTable.isDisposed()) {\r
+ natTable.update();\r
+ natTable.getParent().layout();\r
+ }\r
+ updateCalled = false;\r
+ }\r
+ });\r
+ }\r
+\r
+ \r
+ @Override\r
+ protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {\r
+ return computeTableTreeSize(getControl(composite), wHint, hHint);\r
+ }\r
+ \r
+ Scrollable getControl(Composite composite) {\r
+ return natTable;\r
+ }\r
+ \r
+ \r
+ @Override\r
+ protected void layout(Composite composite, boolean flushCache) {\r
+ Rectangle area = composite.getClientArea();\r
+ Scrollable table = getControl(composite);\r
+ int tableWidth = table.getSize().x;\r
+ int trim = computeTrim(area, table, tableWidth);\r
+ int width = Math.max(0, area.width - trim);\r
+ if (rowHeaderDataLayer != null)\r
+ width -= rowHeaderDataLayer.getWidth();\r
+\r
+ if (width > 1)\r
+ layoutTableTree(table, width, area, tableWidth < area.width);\r
+\r
+ // For the first time we need to relayout because Scrollbars are not\r
+ // calculate appropriately\r
+ if (relayout) {\r
+ relayout = false;\r
+ composite.layout();\r
+ }\r
+ \r
+ }\r
+ \r
+ protected ColumnLayoutData getLayoutData(Scrollable tableTree,\r
+ int columnIndex) {\r
+ return layoutDatas.get(columnIndex);\r
+ }\r
+ \r
+ protected int getColumnCount(Scrollable tableTree) {\r
+ return columnHeaderDataProvider.getColumnCount();\r
+ }\r
+ \r
+ private Point computeTableTreeSize(Scrollable scrollable, int wHint,\r
+ int hHint) {\r
+ Point result = scrollable.computeSize(wHint, hHint);\r
+\r
+ int width = 0;\r
+ int size = getColumnCount(scrollable);\r
+ if (rowHeaderDataLayer != null)\r
+ width += rowHeaderDataLayer.getWidth();\r
+ for (int i = 0; i < size; ++i) {\r
+ ColumnLayoutData layoutData = getLayoutData(scrollable, i);\r
+ if (layoutData instanceof ColumnPixelData) {\r
+ ColumnPixelData col = (ColumnPixelData) layoutData;\r
+ width += col.width;\r
+ if (col.addTrim) {\r
+ width += getColumnTrim();\r
+ }\r
+ } else if (layoutData instanceof ColumnWeightData) {\r
+ ColumnWeightData col = (ColumnWeightData) layoutData;\r
+ width += col.minimumWidth;\r
+ } else {\r
+ Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$\r
+ }\r
+ }\r
+ if (width > result.x)\r
+ result.x = width;\r
+\r
+ return result;\r
+ }\r
+ \r
+ private void layoutTableTree(final Scrollable scrollable, final int width,\r
+ final Rectangle area, final boolean increase) {\r
+ final int numberOfColumns = getColumnCount(scrollable);\r
+ final int[] widths = new int[numberOfColumns];\r
+\r
+ final int[] weightColumnIndices = new int[numberOfColumns];\r
+ int numberOfWeightColumns = 0;\r
+\r
+ int fixedWidth = 0;\r
+ int totalWeight = 0;\r
+\r
+ // First calc space occupied by fixed columns\r
+ for (int i = 0; i < numberOfColumns; i++) {\r
+ ColumnLayoutData col = getLayoutData(scrollable, i);\r
+ if (col instanceof ColumnPixelData) {\r
+ ColumnPixelData cpd = (ColumnPixelData) col;\r
+ int pixels = cpd.width;\r
+ if (cpd.addTrim) {\r
+ pixels += getColumnTrim();\r
+ }\r
+ widths[i] = pixels;\r
+ fixedWidth += pixels;\r
+ } else if (col instanceof ColumnWeightData) {\r
+ ColumnWeightData cw = (ColumnWeightData) col;\r
+ weightColumnIndices[numberOfWeightColumns] = i;\r
+ numberOfWeightColumns++;\r
+ totalWeight += cw.weight;\r
+ } else {\r
+ Assert.isTrue(false, "Unknown column layout data"); //$NON-NLS-1$\r
+ }\r
+ }\r
+\r
+ boolean recalculate;\r
+ do {\r
+ recalculate = false;\r
+ for (int i = 0; i < numberOfWeightColumns; i++) {\r
+ int colIndex = weightColumnIndices[i];\r
+ ColumnWeightData cw = (ColumnWeightData) getLayoutData(\r
+ scrollable, colIndex);\r
+ final int minWidth = cw.minimumWidth;\r
+ final int allowedWidth = totalWeight == 0 ? 0\r
+ : (width - fixedWidth) * cw.weight / totalWeight;\r
+ if (allowedWidth < minWidth) {\r
+ /*\r
+ * if the width assigned by weight is less than the minimum,\r
+ * then treat this column as fixed, remove it from weight\r
+ * calculations, and recalculate other weights.\r
+ */\r
+ numberOfWeightColumns--;\r
+ totalWeight -= cw.weight;\r
+ fixedWidth += minWidth;\r
+ widths[colIndex] = minWidth;\r
+ System.arraycopy(weightColumnIndices, i + 1,\r
+ weightColumnIndices, i, numberOfWeightColumns - i);\r
+ recalculate = true;\r
+ break;\r
+ }\r
+ widths[colIndex] = allowedWidth;\r
+ }\r
+ } while (recalculate);\r
+\r
+ if (increase) {\r
+ scrollable.setSize(area.width, area.height);\r
+ }\r
+\r
+ inupdateMode = true;\r
+ setColumnWidths(scrollable, widths);\r
+ scrollable.update();\r
+ inupdateMode = false;\r
+\r
+ if (!increase) {\r
+ scrollable.setSize(area.width, area.height);\r
+ }\r
+ }\r
+ \r
+ private int computeTrim(Rectangle area, Scrollable scrollable,\r
+ int currentWidth) {\r
+ int trim;\r
+\r
+ if (currentWidth > 1) {\r
+ trim = currentWidth - scrollable.getClientArea().width;\r
+ } else {\r
+ // initially, the table has no extend and no client area - use the\r
+ // border with\r
+ // plus some padding as educated guess\r
+ trim = 2 * scrollable.getBorderWidth() + 1;\r
+ }\r
+\r
+ return trim;\r
+ }\r
+ \r
+ protected int getColumnTrim() {\r
+ return COLUMN_TRIM;\r
+ }\r
+ \r
+ \r
+ \r
+\r
+}\r