X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fjexer%2FTWidget.java;h=d60efd8d3a321236e2069199c6db2fb364505d69;hb=d442b96e0b1453c21abf7f34bfaf73dcbed2a7ee;hp=8a49c816ac1ceb8ab847cadeef41f25b3cc30d9f;hpb=5d26b50423486b1e680e3adfc0054f6fd0bbcefe;p=fanfix.git diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 8a49c81..d60efd8 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -36,6 +36,7 @@ import java.util.ArrayList; import jexer.backend.Screen; import jexer.bits.Cell; import jexer.bits.CellAttributes; +import jexer.bits.Clipboard; import jexer.bits.ColorTheme; import jexer.event.TCommandEvent; import jexer.event.TInputEvent; @@ -43,6 +44,7 @@ import jexer.event.TKeypressEvent; import jexer.event.TMenuEvent; import jexer.event.TMouseEvent; import jexer.event.TResizeEvent; +import jexer.layout.LayoutManager; import jexer.menu.TMenu; import jexer.ttree.TTreeItem; import jexer.ttree.TTreeView; @@ -136,6 +138,11 @@ public abstract class TWidget implements Comparable { */ private int cursorY = 0; + /** + * Layout manager. + */ + private LayoutManager layout = null; + // ------------------------------------------------------------------------ // Constructors ----------------------------------------------------------- // ------------------------------------------------------------------------ @@ -178,17 +185,7 @@ public abstract class TWidget implements Comparable { * @param enabled if true assume enabled */ protected TWidget(final TWidget parent, final boolean enabled) { - this.enabled = enabled; - this.parent = parent; - this.window = parent.window; - children = new ArrayList(); - - // Do not add TStatusBars, they are drawn by TApplication. - if (this instanceof TStatusBar) { - // NOP - } else { - parent.addChild(this); - } + this(parent, enabled, 0, 0, 0, 0); } /** @@ -213,20 +210,17 @@ public abstract class TWidget implements Comparable { this.enabled = enabled; this.parent = parent; - this.window = parent.window; children = new ArrayList(); - // Do not add TStatusBars, they are drawn by TApplication. - if (this instanceof TStatusBar) { - // NOP - } else { - parent.addChild(this); - } - this.x = x; this.y = y; this.width = width; this.height = height; + + if (parent != null) { + this.window = parent.window; + parent.addChild(this); + } } /** @@ -306,6 +300,7 @@ public abstract class TWidget implements Comparable { * @param keypress keystroke event */ public void onKeypress(final TKeypressEvent keypress) { + assert (parent != null); if ((children.size() == 0) || (this instanceof TTreeView) @@ -575,6 +570,14 @@ public abstract class TWidget implements Comparable { if (resize.getType() == TResizeEvent.Type.WIDGET) { width = resize.getWidth(); height = resize.getHeight(); + if (layout != null) { + if (this instanceof TWindow) { + layout.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + width - 2, height - 2)); + } else { + layout.onResize(resize); + } + } } else { // Let children see the screen resize for (TWidget widget: children) { @@ -589,9 +592,8 @@ public abstract class TWidget implements Comparable { * @param command command event */ public void onCommand(final TCommandEvent command) { - // Default: do nothing, pass to children instead - for (TWidget widget: children) { - widget.onCommand(command); + if (activeChild != null) { + activeChild.onCommand(command); } } @@ -700,6 +702,112 @@ public abstract class TWidget implements Comparable { return children; } + /** + * Remove this widget from its parent container. close() will be called + * before it is removed. + */ + public final void remove() { + remove(true); + } + + /** + * Remove this widget from its parent container. + * + * @param doClose if true, call the close() method before removing the + * child + */ + public final void remove(final boolean doClose) { + if (parent != null) { + parent.remove(this, doClose); + } + } + + /** + * Remove a child widget from this container. + * + * @param child the child widget to remove + */ + public final void remove(final TWidget child) { + remove(child, true); + } + + /** + * Remove a child widget from this container. + * + * @param child the child widget to remove + * @param doClose if true, call the close() method before removing the + * child + */ + public final void remove(final TWidget child, final boolean doClose) { + if (!children.contains(child)) { + throw new IndexOutOfBoundsException("child widget is not in " + + "list of children of this parent"); + } + if (doClose) { + child.close(); + } + children.remove(child); + child.parent = null; + child.window = null; + if (layout != null) { + layout.remove(this); + } + } + + /** + * Set this widget's parent to a different widget. + * + * @param newParent new parent widget + * @param doClose if true, call the close() method before removing the + * child from its existing parent widget + */ + public final void setParent(final TWidget newParent, + final boolean doClose) { + + if (parent != null) { + parent.remove(this, doClose); + window = null; + } + assert (parent == null); + assert (window == null); + parent = newParent; + setWindow(parent.window); + parent.addChild(this); + } + + /** + * Set this widget's window to a specific window. + * + * Having a null parent with a specified window is only used within Jexer + * by TStatusBar because TApplication routes events directly to it and + * calls its draw() method. Any other non-parented widgets will require + * similar special case functionality to receive events or be drawn to + * screen. + * + * @param window the window to use + */ + public final void setWindow(final TWindow window) { + this.window = window; + for (TWidget child: getChildren()) { + child.setWindow(window); + } + } + + /** + * Remove a child widget from this container, and all of its children + * recursively from their parent containers. + * + * @param child the child widget to remove + * @param doClose if true, call the close() method before removing each + * child + */ + public final void removeAll(final TWidget child, final boolean doClose) { + remove(child, doClose); + for (TWidget w: child.children) { + child.removeAll(w, doClose); + } + } + /** * Get active flag. * @@ -768,7 +876,7 @@ public abstract class TWidget implements Comparable { * * @return widget width */ - public final int getWidth() { + public int getWidth() { return this.width; } @@ -777,8 +885,12 @@ public abstract class TWidget implements Comparable { * * @param width new widget width */ - public final void setWidth(final int width) { + public void setWidth(final int width) { this.width = width; + if (layout != null) { + layout.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + width, height)); + } } /** @@ -786,7 +898,7 @@ public abstract class TWidget implements Comparable { * * @return widget height */ - public final int getHeight() { + public int getHeight() { return this.height; } @@ -795,8 +907,12 @@ public abstract class TWidget implements Comparable { * * @param height new widget height */ - public final void setHeight(final int height) { + public void setHeight(final int height) { this.height = height; + if (layout != null) { + layout.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + width, height)); + } } /** @@ -810,10 +926,44 @@ public abstract class TWidget implements Comparable { public final void setDimensions(final int x, final int y, final int width, final int height) { - setX(x); - setY(y); + this.x = x; + this.y = y; + // Call the functions so that subclasses can choose how to handle it. setWidth(width); setHeight(height); + if (layout != null) { + layout.onResize(new TResizeEvent(TResizeEvent.Type.WIDGET, + width, height)); + } + } + + /** + * Get the layout manager. + * + * @return the layout manager, or null if not set + */ + public LayoutManager getLayoutManager() { + return layout; + } + + /** + * Set the layout manager. + * + * @param layout the new layout manager + */ + public void setLayoutManager(LayoutManager layout) { + if (this.layout != null) { + for (TWidget w: children) { + this.layout.remove(w); + } + this.layout = null; + } + this.layout = layout; + if (this.layout != null) { + for (TWidget w: children) { + this.layout.add(w); + } + } } /** @@ -896,6 +1046,13 @@ public abstract class TWidget implements Comparable { return false; } + assert (window != null); + + if (window instanceof TDesktop) { + // Desktop doesn't have a window border. + return cursorVisible; + } + // If cursor is out of my window's bounds, it is not visible. if ((getCursorAbsoluteX() >= window.getAbsoluteX() + window.getWidth() - 1) @@ -948,19 +1105,37 @@ public abstract class TWidget implements Comparable { /** * Get this TWidget's parent TApplication. * - * @return the parent TApplication + * @return the parent TApplication, or null if not assigned */ public TApplication getApplication() { - return window.getApplication(); + if (window != null) { + return window.getApplication(); + } + return null; } /** * Get the Screen. * - * @return the Screen + * @return the Screen, or null if not assigned */ public Screen getScreen() { - return window.getScreen(); + if (window != null) { + return window.getScreen(); + } + return null; + } + + /** + * Get the Clipboard. + * + * @return the Clipboard, or null if not assigned + */ + public Clipboard getClipboard() { + if (window != null) { + return window.getApplication().getClipboard(); + } + return null; } /** @@ -975,7 +1150,8 @@ public abstract class TWidget implements Comparable { * @return difference between this.tabOrder and that.tabOrder, or * difference between this.z and that.z, or String.compareTo(text) */ - public final int compareTo(final TWidget that) { + @Override + public int compareTo(final TWidget that) { if ((this instanceof TWindow) && (that instanceof TWindow) ) { @@ -1000,7 +1176,7 @@ public abstract class TWidget implements Comparable { if (parent == this) { return active; } - return (active && parent.isAbsoluteActive()); + return (active && (parent == null ? true : parent.isAbsoluteActive())); } /** @@ -1074,6 +1250,25 @@ public abstract class TWidget implements Comparable { return window.getApplication().getTheme(); } + /** + * See if this widget can be drawn onto a screen. + * + * @return true if this widget is part of the hierarchy that can draw to + * a screen + */ + public final boolean isDrawable() { + if ((window == null) + || (window.getScreen() == null) + || (parent == null) + ) { + return false; + } + if (parent == this) { + return true; + } + return (parent.isDrawable()); + } + /** * Draw my specific widget. When called, the screen rectangle I draw * into is already setup (offset and clipping). @@ -1086,6 +1281,10 @@ public abstract class TWidget implements Comparable { * Called by parent to render to TWindow. Note package private access. */ final void drawChildren() { + if (!isDrawable()) { + return; + } + // Set my clipping rectangle assert (window != null); assert (getScreen() != null); @@ -1102,10 +1301,16 @@ public abstract class TWidget implements Comparable { int absoluteRightEdge = window.getAbsoluteX() + window.getWidth(); int absoluteBottomEdge = window.getAbsoluteY() + window.getHeight(); - if (!(this instanceof TWindow) && !(this instanceof TVScroller)) { + if (!(this instanceof TWindow) + && !(this instanceof TVScroller) + && !(window instanceof TDesktop) + ) { absoluteRightEdge -= 1; } - if (!(this instanceof TWindow) && !(this instanceof THScroller)) { + if (!(this instanceof TWindow) + && !(this instanceof THScroller) + && !(window instanceof TDesktop) + ) { absoluteBottomEdge -= 1; } int myRightEdge = getAbsoluteX() + width; @@ -1131,6 +1336,12 @@ public abstract class TWidget implements Comparable { // Draw me draw(); + if (!isDrawable()) { + // An action taken by a draw method unhooked me from the UI. + // Bail out. + return; + } + assert (visible == true); // Continue down the chain. Draw the active child last so that it @@ -1138,6 +1349,11 @@ public abstract class TWidget implements Comparable { for (TWidget widget: children) { if (widget.isVisible() && (widget != activeChild)) { widget.drawChildren(); + if (!isDrawable()) { + // An action taken by a draw method unhooked me from the UI. + // Bail out. + return; + } } } if (activeChild != null) { @@ -1158,7 +1374,7 @@ public abstract class TWidget implements Comparable { * * @param child TWidget to add */ - private void addChild(final TWidget child) { + public void addChild(final TWidget child) { children.add(child); if ((child.enabled) @@ -1174,6 +1390,9 @@ public abstract class TWidget implements Comparable { for (int i = 0; i < children.size(); i++) { children.get(i).tabOrder = i; } + if (layout != null) { + layout.add(child); + } } /** @@ -1185,6 +1404,29 @@ public abstract class TWidget implements Comparable { children.get(i).tabOrder = i; } } + + /** + * Remove and {@link TWidget#close()} the given child from this {@link TWidget}. + *

+ * Will also reorder the tab values of the remaining children. + * + * @param child the child to remove + * + * @return TRUE if the child was removed, FALSE if it was not found + */ + public boolean removeChild(final TWidget child) { + if (children.remove(child)) { + child.close(); + child.parent = null; + child.window = null; + + resetTabOrder(); + + return true; + } + + return false; + } /** * Switch the active child. @@ -1209,9 +1451,9 @@ public abstract class TWidget implements Comparable { if (activeChild != null) { activeChild.active = false; } - child.active = true; - activeChild = child; } + child.active = true; + activeChild = child; } } @@ -1251,6 +1493,31 @@ public abstract class TWidget implements Comparable { } } + /** + * Make this widget the active child of its parent. Note that this is + * not final since TWindow overrides activate(). + */ + public void activate() { + if (enabled) { + if (parent != null) { + parent.activate(this); + } + } + } + + /** + * Make this widget, all of its parents, the active child. + */ + public final void activateAll() { + activate(); + if (parent == this) { + return; + } + if (parent != null) { + parent.activateAll(); + } + } + /** * Switch the active widget with the next in the tab order. * @@ -1264,6 +1531,8 @@ public abstract class TWidget implements Comparable { return; } + assert (parent != null); + // If there is only one child, make it active if it is enabled. if (children.size() == 1) { if (children.get(0).enabled == true) { @@ -1351,6 +1620,153 @@ public abstract class TWidget implements Comparable { return this; } + /** + * Insert a vertical split between this widget and parent, and optionally + * put another widget in the other side of the split. + * + * @param newWidgetOnLeft if true, the new widget (if specified) will be + * on the left pane, and this widget will be placed on the right pane + * @param newWidget the new widget to add to the other pane, or null + * @return the new split pane widget + */ + public TSplitPane splitVertical(final boolean newWidgetOnLeft, + final TWidget newWidget) { + + TSplitPane splitPane = new TSplitPane(null, x, y, width, height, true); + TWidget myParent = parent; + remove(false); + if (myParent instanceof TSplitPane) { + // TSplitPane has a left/right/top/bottom link to me somewhere, + // replace it with a link to splitPane. + ((TSplitPane) myParent).replaceWidget(this, splitPane); + } + splitPane.setParent(myParent, false); + if (newWidgetOnLeft) { + splitPane.setLeft(newWidget); + splitPane.setRight(this); + } else { + splitPane.setLeft(this); + splitPane.setRight(newWidget); + } + if (newWidget != null) { + newWidget.activateAll(); + } else { + activateAll(); + } + + assert (parent != null); + assert (window != null); + assert (splitPane.getWindow() != null); + assert (splitPane.getParent() != null); + assert (splitPane.isActive() == true); + assert (parent == splitPane); + if (newWidget != null) { + assert (newWidget.parent == parent); + assert (newWidget.active == true); + assert (active == false); + } else { + assert (active == true); + } + return splitPane; + } + + /** + * Insert a horizontal split between this widget and parent, and + * optionally put another widget in the other side of the split. + * + * @param newWidgetOnTop if true, the new widget (if specified) will be + * on the top pane, and this widget's children will be placed on the + * bottom pane + * @param newWidget the new widget to add to the other pane, or null + * @return the new split pane widget + */ + public TSplitPane splitHorizontal(final boolean newWidgetOnTop, + final TWidget newWidget) { + + TSplitPane splitPane = new TSplitPane(null, x, y, width, height, false); + TWidget myParent = parent; + remove(false); + if (myParent instanceof TSplitPane) { + // TSplitPane has a left/right/top/bottom link to me somewhere, + // replace it with a link to splitPane. + ((TSplitPane) myParent).replaceWidget(this, splitPane); + } + splitPane.setParent(myParent, false); + if (newWidgetOnTop) { + splitPane.setTop(newWidget); + splitPane.setBottom(this); + } else { + splitPane.setTop(this); + splitPane.setBottom(newWidget); + } + if (newWidget != null) { + newWidget.activateAll(); + } else { + activateAll(); + } + + assert (parent != null); + assert (window != null); + assert (splitPane.getWindow() != null); + assert (splitPane.getParent() != null); + assert (splitPane.isActive() == true); + assert (parent == splitPane); + if (newWidget != null) { + assert (newWidget.parent == parent); + assert (newWidget.active == true); + assert (active == false); + } else { + assert (active == true); + } + return splitPane; + } + + /** + * Generate a human-readable string for this widget. + * + * @return a human-readable string + */ + @Override + public String toString() { + return String.format("%s(%8x) position (%d, %d) geometry %dx%d " + + "active %s enabled %s visible %s", getClass().getName(), + hashCode(), x, y, width, height, active, enabled, visible); + } + + /** + * Generate a string for this widget's hierarchy. + * + * @param prefix a prefix to use for this widget's place in the hierarchy + * @return a pretty-printable string of this hierarchy + */ + protected String toPrettyString(final String prefix) { + StringBuilder sb = new StringBuilder(prefix); + sb.append(toString()); + String newPrefix = ""; + for (int i = 0; i < prefix.length(); i++) { + newPrefix += " "; + } + for (int i = 0; i < children.size(); i++) { + TWidget child= children.get(i); + sb.append("\n"); + if (i == children.size() - 1) { + sb.append(child.toPrettyString(newPrefix + " \u2514\u2500")); + } else { + sb.append(child.toPrettyString(newPrefix + " \u251c\u2500")); + } + } + return sb.toString(); + } + + /** + * Generate a string for this widget's hierarchy. + * + * @return a pretty-printable string of this hierarchy + */ + public String toPrettyString() { + return toPrettyString(""); + } + // ------------------------------------------------------------------------ // Passthru for Screen functions ------------------------------------------ // ------------------------------------------------------------------------ @@ -1399,7 +1815,7 @@ public abstract class TWidget implements Comparable { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - protected final void putAll(final char ch, final CellAttributes attr) { + protected final void putAll(final int ch, final CellAttributes attr) { getScreen().putAll(ch, attr); } @@ -1422,7 +1838,7 @@ public abstract class TWidget implements Comparable { * @param ch character to draw * @param attr attributes to use (bold, foreColor, backColor) */ - protected final void putCharXY(final int x, final int y, final char ch, + protected final void putCharXY(final int x, final int y, final int ch, final CellAttributes attr) { getScreen().putCharXY(x, y, ch, attr); @@ -1435,7 +1851,7 @@ public abstract class TWidget implements Comparable { * @param y row coordinate. 0 is the top-most row. * @param ch character to draw */ - protected final void putCharXY(final int x, final int y, final char ch) { + protected final void putCharXY(final int x, final int y, final int ch) { getScreen().putCharXY(x, y, ch); } @@ -1475,7 +1891,7 @@ public abstract class TWidget implements Comparable { * @param attr attributes to use (bold, foreColor, backColor) */ protected final void vLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { getScreen().vLineXY(x, y, n, ch, attr); } @@ -1490,7 +1906,7 @@ public abstract class TWidget implements Comparable { * @param attr attributes to use (bold, foreColor, backColor) */ protected final void hLineXY(final int x, final int y, final int n, - final char ch, final CellAttributes attr) { + final int ch, final CellAttributes attr) { getScreen().hLineXY(x, y, n, ch, attr); } @@ -1689,18 +2105,18 @@ public abstract class TWidget implements Comparable { * @param values the possible values for the box, shown in the drop-down * @param valuesIndex the initial index in values, or -1 for no default * value - * @param valuesHeight the height of the values drop-down when it is - * visible + * @param maxValuesHeight the maximum height of the values drop-down when + * it is visible * @param updateAction action to call when a new value is selected from * the list or enter is pressed in the edit field * @return the new combobox */ public final TComboBox addComboBox(final int x, final int y, final int width, final List values, final int valuesIndex, - final int valuesHeight, final TAction updateAction) { + final int maxValuesHeight, final TAction updateAction) { return new TComboBox(this, x, y, width, values, valuesIndex, - valuesHeight, updateAction); + maxValuesHeight, updateAction); } /** @@ -1764,6 +2180,21 @@ public abstract class TWidget implements Comparable { return new TRadioGroup(this, x, y, label); } + /** + * Convenience function to add a radio button group to this + * container/window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param width width of group + * @param label label to display on the group box + */ + public final TRadioGroup addRadioGroup(final int x, final int y, + final int width, final String label) { + + return new TRadioGroup(this, x, y, width, label); + } + /** * Convenience function to add a text field to this container/window. * @@ -2232,6 +2663,31 @@ public abstract class TWidget implements Comparable { moveAction); } + /** + * Convenience function to add a list to this container/window. + * + * @param strings list of strings to show. This is allowed to be null + * and set later with setList() or by subclasses. + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + * @param enterAction action to perform when an item is selected + * @param moveAction action to perform when the user navigates to a new + * item with arrow/page keys + * @param singleClickAction action to perform when the user clicks on an + * item + */ + public TList addList(final List strings, final int x, + final int y, final int width, final int height, + final TAction enterAction, final TAction moveAction, + final TAction singleClickAction) { + + return new TList(this, strings, x, y, width, height, enterAction, + moveAction, singleClickAction); + } + + /** * Convenience function to add an image to this container/window. * @@ -2304,4 +2760,35 @@ public abstract class TWidget implements Comparable { gridRows); } + /** + * Convenience function to add a panel to this container/window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + * @return the new panel + */ + public final TPanel addPanel(final int x, final int y, final int width, + final int height) { + + return new TPanel(this, x, y, width, height); + } + + /** + * Convenience function to add a split pane to this container/window. + * + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + * @param vertical if true, split vertically + * @return the new split pane + */ + public final TSplitPane addSplitPane(final int x, final int y, + final int width, final int height, final boolean vertical) { + + return new TSplitPane(this, x, y, width, height, vertical); + } + }