wip ttable
[fanfix.git] / src / jexer / TTableWidget.java
index 480e8289c2e8def0f1be139675bb963544857c60..1107a4e684bdb09dc7f7a203a220b11374a15408 100644 (file)
@@ -31,6 +31,7 @@ package jexer;
 import java.util.ArrayList;
 import java.util.List;
 
+import jexer.bits.CellAttributes;
 import jexer.event.TKeypressEvent;
 import jexer.event.TMenuEvent;
 import jexer.menu.TMenu;
@@ -113,12 +114,22 @@ public class TTableWidget extends TWidget {
     /**
      * If true, highlight the entire row of the currently-selected cell.
      */
-    private boolean highlightRow = false;
+    private boolean highlightRow = true;
 
     /**
      * If true, highlight the entire column of the currently-selected cell.
      */
-    private boolean highlightColumn = false;
+    private boolean highlightColumn = true;
+
+    /**
+     * If true, show the row labels as the first column.
+     */
+    private boolean showRowLabels = true;
+
+    /**
+     * If true, show the column labels as the first row.
+     */
+    private boolean showColumnLabels = true;
 
     /**
      * Column represents a column of cells.
@@ -145,6 +156,24 @@ public class TTableWidget extends TWidget {
          */
         private Border border = Border.NONE;
 
+        /**
+         * Constructor sets label to lettered column.
+         *
+         * @param col column number to use for this column.  Column 0 will be
+         * "A", column 1 will be "B", column 26 will be "AA", and so on.
+         */
+        Column(int col) {
+            StringBuilder sb = new StringBuilder();
+            for (;;) {
+                sb.append((char) ('A' + (col % 26)));
+                if (col < 26) {
+                    break;
+                }
+                col /= 26;
+            }
+            label = sb.reverse().toString();
+        }
+
         /**
          * Add an entry to this column.
          *
@@ -190,6 +219,15 @@ public class TTableWidget extends TWidget {
          */
         private Border border = Border.NONE;
 
+        /**
+         * Constructor sets label to numbered row.
+         *
+         * @param row row number to use for this row
+         */
+        Row(final int row) {
+            label = Integer.toString(row);
+        }
+
         /**
          * Add an entry to this column.
          *
@@ -272,8 +310,6 @@ public class TTableWidget extends TWidget {
 
             field = addField(0, 0, width - 1, false);
             field.setEnabled(false);
-            field.setActiveColorKey("ttable.active");
-            field.setInactiveColorKey("ttable.inactive");
             field.setBackgroundChar(' ');
         }
 
@@ -330,25 +366,26 @@ public class TTableWidget extends TWidget {
         public void draw() {
             TTableWidget table = (TTableWidget) getParent();
 
-            field.setActiveColorKey("ttable.active");
-            field.setInactiveColorKey("ttable.inactive");
-
             if (isAbsoluteActive()) {
-                if (table.selectedColumn == column) {
-                    if ((table.selectedRow == row)
-                        || (table.highlightColumn == true)
-                    ) {
-                        field.setActiveColorKey("ttable.active");
-                        field.setInactiveColorKey("ttable.active");
-                    }
-                } else if (table.selectedRow == row) {
-                    if ((table.selectedColumn == column)
-                        || (table.highlightRow == true)
-                    ) {
-                        field.setActiveColorKey("ttable.active");
-                        field.setInactiveColorKey("ttable.active");
-                    }
+                if (isEditing) {
+                    field.setActiveColorKey("tfield.active");
+                    field.setInactiveColorKey("tfield.inactive");
+                } else {
+                    field.setActiveColorKey("ttable.selected");
+                    field.setInactiveColorKey("ttable.selected");
                 }
+            } else if (((table.selectedColumn == column)
+                    && ((table.selectedRow == row)
+                        || (table.highlightColumn == true)))
+                || ((table.selectedRow == row)
+                    && ((table.selectedColumn == column)
+                        || (table.highlightRow == true)))
+            ) {
+                field.setActiveColorKey("ttable.active");
+                field.setInactiveColorKey("ttable.active");
+            } else {
+                field.setActiveColorKey("ttable.active");
+                field.setInactiveColorKey("ttable.inactive");
             }
 
             super.draw();
@@ -397,8 +434,8 @@ public class TTableWidget extends TWidget {
         super(parent, x, y, width, height);
 
         // Initialize the starting row and column.
-        rows.add(new Row());
-        columns.add(new Column());
+        rows.add(new Row(0));
+        columns.add(new Column(0));
 
         // Place a grid of cells that fit in this space.
         int row = 0;
@@ -412,16 +449,18 @@ public class TTableWidget extends TWidget {
                 rows.get(row).add(cell);
                 columns.get(column).add(cell);
                 if ((i == 0) && (j + columns.get(0).width < width)) {
-                    columns.add(new Column());
+                    columns.add(new Column(column + 1));
                 }
                 column++;
             }
             if (i + rows.get(0).height < height) {
-                rows.add(new Row());
+                rows.add(new Row(row + 1));
             }
             row++;
         }
         activate(columns.get(selectedColumn).get(selectedRow));
+
+        alignGrid();
     }
 
     // ------------------------------------------------------------------------
@@ -442,7 +481,8 @@ public class TTableWidget extends TWidget {
             // grid context.
             return;
         }
-        
+
+        // If editing, pass to that cell and do nothing else.
         if (getSelectedCell().isEditing) {
             super.onKeypress(keypress);
             return;
@@ -486,6 +526,10 @@ public class TTableWidget extends TWidget {
             // Pass to the Cell.
             super.onKeypress(keypress);
         }
+
+        // We may have scrolled off screen.  Reset positions as needed to
+        // make the newly selected cell visible.
+        alignGrid();
     }
 
     /**
@@ -519,6 +563,12 @@ public class TTableWidget extends TWidget {
                 cell.setWidth(columns.get(selectedColumn).width);
                 cell.field.setWidth(columns.get(selectedColumn).width - 1);
             }
+            for (int i = selectedColumn + 1; i < columns.size(); i++) {
+                for (Cell cell: columns.get(i).cells) {
+                    cell.setX(cell.getX() - 1);
+                }
+            }
+            alignGrid();
             break;
         case TMenu.MID_TABLE_COLUMN_WIDEN:
             columns.get(selectedColumn).width++;
@@ -526,6 +576,12 @@ public class TTableWidget extends TWidget {
                 cell.setWidth(columns.get(selectedColumn).width);
                 cell.field.setWidth(columns.get(selectedColumn).width - 1);
             }
+            for (int i = selectedColumn + 1; i < columns.size(); i++) {
+                for (Cell cell: columns.get(i).cells) {
+                    cell.setX(cell.getX() + 1);
+                }
+            }
+            alignGrid();
             break;
         case TMenu.MID_TABLE_FILE_SAVE_CSV:
         case TMenu.MID_TABLE_FILE_SAVE_TEXT:
@@ -539,6 +595,42 @@ public class TTableWidget extends TWidget {
     // TWidget ----------------------------------------------------------------
     // ------------------------------------------------------------------------
 
+    /**
+     * Draw the table row/column headings, and borders.
+     */
+    @Override
+    public void draw() {
+        CellAttributes headingColor = getTheme().getColor("ttable.heading");
+        CellAttributes borderColor = getTheme().getColor("ttable.border");
+
+        // Column labels.
+        if (showColumnLabels == true) {
+            int x = 0;
+            if (showRowLabels == true) {
+                x += 8;
+            }
+            for (int i = left; i < columns.size(); i++) {
+                putStringXY(x + (i * 8), 0, String.format(" %-6s ",
+                        columns.get(i).label), headingColor);
+            }
+        }
+
+        // Row labels.
+        if (showRowLabels == true) {
+            int y = 0;
+            if (showColumnLabels == true) {
+                y++;
+            }
+            for (int i = top; i < rows.size(); i++) {
+                putStringXY(0, y + i, String.format(" %-6s ",
+                        rows.get(i).label), headingColor);
+            }
+        }
+
+        // Now draw the window borders.
+        super.draw();
+    }
+
     // ------------------------------------------------------------------------
     // TTable -----------------------------------------------------------------
     // ------------------------------------------------------------------------
@@ -583,4 +675,192 @@ public class TTableWidget extends TWidget {
         return rows.get(selectedRow);
     }
 
+    /**
+     * Get the full horizontal width of this table.
+     *
+     * @return the width required to render the entire table
+     */
+    public int getMaximumWidth() {
+        int totalWidth = 0;
+        if (showRowLabels == true) {
+            // For now, all row labels are 8 cells wide.  TODO: make this
+            // adjustable.
+            totalWidth += 8;
+        }
+        for (Cell cell: getSelectedRow().cells) {
+            totalWidth += cell.getWidth() + 1;
+        }
+        return totalWidth;
+    }
+
+    /**
+     * Get the full vertical height of this table.
+     *
+     * @return the height required to render the entire table
+     */
+    public int getMaximumHeight() {
+        int totalHeight = 0;
+        if (showColumnLabels == true) {
+            // For now, all column labels are 1 cell tall.  TODO: make this
+            // adjustable.
+            totalHeight += 1;
+        }
+        for (Cell cell: getSelectedColumn().cells) {
+            totalHeight += cell.getHeight();
+            // TODO: handle top/bottom borders.
+        }
+        return totalHeight;
+    }
+
+    /**
+     * Align the grid so that the selected cell is fully visible.
+     */
+    private void alignGrid() {
+
+        // Adjust X locations to be visible -----------------------------------
+
+        // Determine if we need to shift left or right.
+        int width = getMaximumWidth();
+        int leftCellX = 0;
+        if (showRowLabels == true) {
+            // For now, all row labels are 8 cells wide.  TODO: make this
+            // adjustable.
+            leftCellX += 8;
+        }
+        Row row = getSelectedRow();
+        Cell selectedColumnCell = null;
+        for (int i = 0; i < row.cells.size(); i++) {
+            if (i == selectedColumn) {
+                selectedColumnCell = row.cells.get(i);
+                break;
+            }
+            leftCellX += row.cells.get(i).getWidth() + 1;
+        }
+        // There should always be a selected column.
+        assert (selectedColumnCell != null);
+
+        while (leftCellX + selectedColumnCell.getWidth() + 1 > getWidth()) {
+            leftCellX -= (getWidth() - selectedColumnCell.getWidth() - 1);
+        }
+        if (leftCellX < 0) {
+            leftCellX = 0;
+        }
+
+        /*
+         * leftCellX now contains the basic left offset necessary to draw the
+         * cells such that the selected cell (column) is fully visible within
+         * this widget's given width.  Or, if the widget is too narrow to
+         * display the full cell, leftCellX is 0.
+         *
+         * Now reset all of the X positions of the other cells so that the
+         * selected cell X is leftCellX.
+         */
+        for (int y = 0; y < rows.size(); y++) {
+            // All cells to the left of selected cell.
+            int newCellX = leftCellX;
+            left = selectedColumn;
+            for (int x = selectedColumn - 1; x >= 0; x--) {
+                newCellX -= rows.get(y).cells.get(x).getWidth() - 1;
+                if (newCellX - rows.get(y).cells.get(x).getWidth() - 1 > 0) {
+                    rows.get(y).cells.get(x).setVisible(true);
+                    rows.get(y).cells.get(x).setX(newCellX);
+                    left--;
+                } else {
+                    // This cell won't be visible.
+                    rows.get(y).cells.get(x).setVisible(false);
+                }
+            }
+
+            // Selected cell.
+            rows.get(y).cells.get(selectedColumn).setX(leftCellX);
+
+            // All cells to the right of selected cell.
+            newCellX = leftCellX + selectedColumnCell.getWidth() + 1;
+            for (int x = selectedColumn + 1; x < columns.size(); x++) {
+                if (newCellX < getWidth()) {
+                    rows.get(y).cells.get(x).setVisible(true);
+                    rows.get(y).cells.get(x).setX(newCellX);
+                } else {
+                    // This cell won't be visible.
+                    rows.get(y).cells.get(x).setVisible(false);
+                }
+                newCellX += rows.get(y).cells.get(x).getWidth() + 1;
+            }
+        }
+
+        // Adjust Y locations to be visible -----------------------------------
+        // The same logic as above, but applied to the column Y.
+
+        // Determine if we need to shift up or down.
+        int height = getMaximumHeight();
+        int topCellY = 0;
+        if (showColumnLabels == true) {
+            // For now, all column labels are 1 cell high.  TODO: make this
+            // adjustable.
+            topCellY += 1;
+        }
+        Column column = getSelectedColumn();
+        Cell selectedRowCell = null;
+        for (int i = 0; i < column.cells.size(); i++) {
+            if (i == selectedRow) {
+                selectedRowCell = column.cells.get(i);
+                break;
+            }
+            topCellY += column.cells.get(i).getHeight();
+            // TODO: if a border is selected, add 1 to topCellY.
+        }
+        // There should always be a selected row.
+        assert (selectedRowCell != null);
+
+        while (topCellY + selectedRowCell.getHeight() > getHeight()) {
+            topCellY -= (getHeight() - selectedRowCell.getHeight());
+        }
+        if (topCellY < 0) {
+            topCellY = 0;
+        }
+
+        /*
+         * topCellY now contains the basic top offset necessary to draw the
+         * cells such that the selected cell (row) is fully visible within
+         * this widget's given height.  Or, if the widget is too short to
+         * display the full cell, topCellY is 0.
+         *
+         * Now reset all of the Y positions of the other cells so that the
+         * selected cell Y is topCellY.
+         */
+        for (int x = 0; x < columns.size(); x++) {
+            // All cells above the selected cell.
+            int newCellY = topCellY;
+            top = selectedRow;
+            for (int y = selectedRow - 1; y >= 0; y--) {
+                newCellY -= rows.get(y).cells.get(x).getHeight();
+                if (newCellY - rows.get(y).cells.get(x).getHeight() > 0) {
+                    rows.get(y).cells.get(x).setVisible(true);
+                    rows.get(y).cells.get(x).setY(newCellY);
+                    top--;
+                } else {
+                    // This cell won't be visible.
+                    rows.get(y).cells.get(x).setVisible(false);
+                }
+            }
+
+            // Selected cell.
+            columns.get(x).cells.get(selectedColumn).setY(topCellY);
+
+            // All cells below of selected cell.
+            newCellY = topCellY + selectedRowCell.getHeight();
+            for (int y = selectedRow + 1; y < rows.size(); y++) {
+                if (newCellY < getHeight()) {
+                    rows.get(y).cells.get(x).setVisible(true);
+                    rows.get(y).cells.get(x).setY(newCellY);
+                } else {
+                    // This cell won't be visible.
+                    rows.get(y).cells.get(x).setVisible(false);
+                }
+                newCellY += rows.get(y).cells.get(x).getHeight();
+            }
+        }
+
+    }
+
 }