From 3649b9210ea425f398ba8c24f9509669cf72aa96 Mon Sep 17 00:00:00 2001 From: Kevin Lamonte Date: Thu, 2 Apr 2015 21:26:32 -0400 Subject: [PATCH] color chooser widget --- README.md | 5 - src/jexer/TDirectoryList.java | 282 +--------- src/jexer/TEditColorThemeWindow.java | 757 +++++++++++++++++++++++++++ src/jexer/TList.java | 447 ++++++++++++++++ src/jexer/TWidget.java | 55 ++ src/jexer/TWindow.java | 2 +- src/jexer/bits/ColorTheme.java | 27 +- src/jexer/demos/DemoApplication.java | 7 + src/jexer/demos/DemoMainWindow.java | 16 +- 9 files changed, 1325 insertions(+), 273 deletions(-) create mode 100644 src/jexer/TEditColorThemeWindow.java create mode 100644 src/jexer/TList.java diff --git a/README.md b/README.md index 14eaa40..58688a1 100644 --- a/README.md +++ b/README.md @@ -221,11 +221,6 @@ Roadmap Many tasks remain before calling this version 1.0: -0.0.3 - -- TListBox -- TColorPicker - 0.0.4 - TStatusBar diff --git a/src/jexer/TDirectoryList.java b/src/jexer/TDirectoryList.java index 50cc88b..a2c9fe3 100644 --- a/src/jexer/TDirectoryList.java +++ b/src/jexer/TDirectoryList.java @@ -34,26 +34,16 @@ import java.io.File; import java.util.ArrayList; import java.util.List; -import jexer.bits.CellAttributes; -import jexer.event.TKeypressEvent; -import jexer.event.TMouseEvent; -import static jexer.TKeypress.*; - /** * TDirectoryList shows the files within a directory. */ -public final class TDirectoryList extends TWidget { +public final class TDirectoryList extends TList { /** * Files in the directory. */ private List files; - /** - * Selected file. - */ - private int selectedFile = -1; - /** * Root path containing files to display. */ @@ -66,7 +56,25 @@ public final class TDirectoryList extends TWidget { */ public void setPath(final String path) { this.path = new File(path); - reflow(); + + List newStrings = new ArrayList(); + files.clear(); + + // Build a list of files in this directory + File [] newFiles = this.path.listFiles(); + if (newFiles != null) { + for (int i = 0; i < newFiles.length; i++) { + if (newFiles[i].getName().startsWith(".")) { + continue; + } + if (newFiles[i].isDirectory()) { + continue; + } + files.add(newFiles[i]); + newStrings.add(renderFile(files.size() - 1)); + } + } + setList(newStrings); } /** @@ -75,40 +83,10 @@ public final class TDirectoryList extends TWidget { * @return the path */ public File getPath() { + path = files.get(getSelectedIndex()); return path; } - /** - * Vertical scrollbar. - */ - private TVScroller vScroller; - - /** - * Horizontal scrollbar. - */ - private THScroller hScroller; - - /** - * Maximum width of a single line. - */ - private int maxLineWidth; - - /** - * The action to perform when the user selects an item. - */ - private TAction action = null; - - /** - * Perform user selection action. - */ - public void dispatch() { - assert (selectedFile >= 0); - assert (selectedFile < files.size()); - if (action != null) { - action.DO(); - } - } - /** * Format one of the entries for drawing on the screen. * @@ -124,70 +102,6 @@ public final class TDirectoryList extends TWidget { return String.format("%-20s %5dk", name, (file.length() / 1024)); } - /** - * Resize for a new width/height. - */ - public void reflow() { - - // Reset the lines - selectedFile = -1; - maxLineWidth = 0; - files.clear(); - - // Build a list of files in this directory - File [] newFiles = path.listFiles(); - if (newFiles != null) { - for (int i = 0; i < newFiles.length; i++) { - if (newFiles[i].getName().startsWith(".")) { - continue; - } - if (newFiles[i].isDirectory()) { - continue; - } - files.add(newFiles[i]); - } - } - - for (int i = 0; i < files.size(); i++) { - String line = renderFile(i); - if (line.length() > maxLineWidth) { - maxLineWidth = line.length(); - } - } - - // Start at the top - if (vScroller == null) { - vScroller = new TVScroller(this, getWidth() - 1, 0, - getHeight() - 1); - } else { - vScroller.setX(getWidth() - 1); - vScroller.setHeight(getHeight() - 1); - } - vScroller.setBottomValue(files.size() - getHeight() - 1); - vScroller.setTopValue(0); - vScroller.setValue(0); - if (vScroller.getBottomValue() < 0) { - vScroller.setBottomValue(0); - } - vScroller.setBigChange(getHeight() - 1); - - // Start at the left - if (hScroller == null) { - hScroller = new THScroller(this, 0, getHeight() - 1, - getWidth() - 1); - } else { - hScroller.setY(getHeight() - 1); - hScroller.setWidth(getWidth() - 1); - } - hScroller.setRightValue(maxLineWidth - getWidth() + 1); - hScroller.setLeftValue(0); - hScroller.setValue(0); - if (hScroller.getRightValue() < 0) { - hScroller.setRightValue(0); - } - hScroller.setBigChange(getWidth() - 1); - } - /** * Public constructor. * @@ -218,159 +132,9 @@ public final class TDirectoryList extends TWidget { public TDirectoryList(final TWidget parent, final String path, final int x, final int y, final int width, final int height, final TAction action) { - super(parent, x, y, width, height); - this.path = new File(path); - this.action = action; + super(parent, null, x, y, width, height, action); files = new ArrayList(); - reflow(); - } - - /** - * Draw the files list. - */ - @Override - public void draw() { - CellAttributes color = null; - int begin = vScroller.getValue(); - int topY = 0; - for (int i = begin; i < files.size(); i++) { - String line = renderFile(i); - if (hScroller.getValue() < line.length()) { - line = line.substring(hScroller.getValue()); - } else { - line = ""; - } - if (i == selectedFile) { - color = getTheme().getColor("tdirectorylist.selected"); - } else if (isAbsoluteActive()) { - color = getTheme().getColor("tdirectorylist"); - } else { - color = getTheme().getColor("tdirectorylist.inactive"); - } - String formatString = "%-" + Integer.toString(getWidth() - 1) + "s"; - getScreen().putStringXY(0, topY, String.format(formatString, line), - color); - topY++; - if (topY >= getHeight() - 1) { - break; - } - } - - if (isAbsoluteActive()) { - color = getTheme().getColor("tdirectorylist"); - } else { - color = getTheme().getColor("tdirectorylist.inactive"); - } - - // Pad the rest with blank lines - for (int i = topY; i < getHeight() - 1; i++) { - getScreen().hLineXY(0, i, getWidth() - 1, ' ', color); - } - } - - /** - * Handle mouse press events. - * - * @param mouse mouse button press event - */ - @Override - public void onMouseDown(final TMouseEvent mouse) { - if (mouse.isMouseWheelUp()) { - vScroller.decrement(); - return; - } - if (mouse.isMouseWheelDown()) { - vScroller.increment(); - return; - } - - if ((mouse.getX() < getWidth() - 1) - && (mouse.getY() < getHeight() - 1)) { - if (vScroller.getValue() + mouse.getY() < files.size()) { - selectedFile = vScroller.getValue() + mouse.getY(); - } - path = files.get(selectedFile); - dispatch(); - return; - } - - // Pass to children - super.onMouseDown(mouse); - } - - /** - * Handle mouse release events. - * - * @param mouse mouse button release event - */ - @Override - public void onMouseUp(final TMouseEvent mouse) { - // Pass to children - super.onMouseDown(mouse); - } - - /** - * Handle keystrokes. - * - * @param keypress keystroke event - */ - @Override - public void onKeypress(final TKeypressEvent keypress) { - if (keypress.equals(kbLeft)) { - hScroller.decrement(); - } else if (keypress.equals(kbRight)) { - hScroller.increment(); - } else if (keypress.equals(kbUp)) { - if (files.size() > 0) { - if (selectedFile >= 0) { - if (selectedFile > 0) { - selectedFile--; - } - } else { - selectedFile = files.size() - 1; - } - path = files.get(selectedFile); - } - } else if (keypress.equals(kbDown)) { - if (files.size() > 0) { - if (selectedFile >= 0) { - if (selectedFile < files.size() - 1) { - selectedFile++; - } - } else { - selectedFile = 0; - } - path = files.get(selectedFile); - } - } else if (keypress.equals(kbPgUp)) { - vScroller.bigDecrement(); - } else if (keypress.equals(kbPgDn)) { - vScroller.bigIncrement(); - } else if (keypress.equals(kbHome)) { - vScroller.toTop(); - if (files.size() > 0) { - selectedFile = 0; - path = files.get(selectedFile); - } - } else if (keypress.equals(kbEnd)) { - vScroller.toBottom(); - if (files.size() > 0) { - selectedFile = files.size() - 1; - path = files.get(selectedFile); - } - } else if (keypress.equals(kbTab)) { - getParent().switchWidget(true); - } else if (keypress.equals(kbShiftTab) || keypress.equals(kbBackTab)) { - getParent().switchWidget(false); - } else if (keypress.equals(kbEnter)) { - if (selectedFile >= 0) { - path = files.get(selectedFile); - dispatch(); - } - } else { - // Pass other keys (tab etc.) on - super.onKeypress(keypress); - } + setPath(path); } } diff --git a/src/jexer/TEditColorThemeWindow.java b/src/jexer/TEditColorThemeWindow.java new file mode 100644 index 0000000..d46ef54 --- /dev/null +++ b/src/jexer/TEditColorThemeWindow.java @@ -0,0 +1,757 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import java.util.List; + +import jexer.bits.Color; +import jexer.bits.ColorTheme; +import jexer.bits.CellAttributes; +import jexer.bits.GraphicsChars; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import static jexer.TKeypress.*; + +/** + * TEditColorThemeWindow provides an easy UI for users to alter the running + * color theme. + * + */ +public final class TEditColorThemeWindow extends TWindow { + + /** + * The foreground color picker. + */ + class ForegroundPicker extends TWidget { + + /** + * The selected color. + */ + Color color; + + /** + * The bold flag. + */ + boolean bold; + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + */ + public ForegroundPicker(final TWidget parent, final int x, + final int y, final int width, final int height) { + + super(parent, x, y, width, height); + } + + /** + * Get the X grid coordinate for this color. + * + * @param color the Color value + * @return the X coordinate + */ + private int getXColorPosition(final Color color) { + if (color.equals(Color.BLACK)) { + return 2; + } else if (color.equals(Color.BLUE)) { + return 5; + } else if (color.equals(Color.GREEN)) { + return 8; + } else if (color.equals(Color.CYAN)) { + return 11; + } else if (color.equals(Color.RED)) { + return 2; + } else if (color.equals(Color.MAGENTA)) { + return 5; + } else if (color.equals(Color.YELLOW)) { + return 8; + } else if (color.equals(Color.WHITE)) { + return 11; + } + throw new IllegalArgumentException("Invalid color: " + color); + } + + /** + * Get the Y grid coordinate for this color. + * + * @param color the Color value + * @param bold if true use bold color + * @return the Y coordinate + */ + private int getYColorPosition(final Color color, final boolean bold) { + int dotY = 1; + if (color.equals(Color.RED)) { + dotY = 2; + } else if (color.equals(Color.MAGENTA)) { + dotY = 2; + } else if (color.equals(Color.YELLOW)) { + dotY = 2; + } else if (color.equals(Color.WHITE)) { + dotY = 2; + } + if (bold) { + dotY += 2; + } + return dotY; + } + + /** + * Get the bold value based on Y grid coordinate. + * + * @param dotY the Y coordinate + * @return the bold value + */ + private boolean getBoldFromPosition(final int dotY) { + if (dotY > 2) { + return true; + } + return false; + } + + /** + * Get the color based on (X, Y) grid coordinate. + * + * @param dotX the X coordinate + * @param dotY the Y coordinate + * @return the Color value + */ + private Color getColorFromPosition(final int dotX, final int dotY) { + int y = dotY; + if (y > 2) { + y -= 2; + } + if ((1 <= dotX) && (dotX <= 3) && (y == 1)) { + return Color.BLACK; + } + if ((4 <= dotX) && (dotX <= 6) && (y == 1)) { + return Color.BLUE; + } + if ((7 <= dotX) && (dotX <= 9) && (y == 1)) { + return Color.GREEN; + } + if ((10 <= dotX) && (dotX <= 12) && (y == 1)) { + return Color.CYAN; + } + if ((1 <= dotX) && (dotX <= 3) && (y == 2)) { + return Color.RED; + } + if ((4 <= dotX) && (dotX <= 6) && (y == 2)) { + return Color.MAGENTA; + } + if ((7 <= dotX) && (dotX <= 9) && (y == 2)) { + return Color.YELLOW; + } + if ((10 <= dotX) && (dotX <= 12) && (y == 2)) { + return Color.WHITE; + } + + throw new IllegalArgumentException("Invalid coordinates: " + + dotX + ", " + dotY); + } + + /** + * Draw the foreground colors grid. + */ + @Override + public void draw() { + CellAttributes border = getWindow().getBorder(); + CellAttributes background = getWindow().getBackground(); + CellAttributes attr = new CellAttributes(); + + getScreen().drawBox(0, 0, getWidth(), getHeight(), border, + background, 1, false); + + attr.setTo(getTheme().getColor("twindow.background.modal")); + if (isActive()) { + attr.setForeColor(getTheme().getColor("tlabel").getForeColor()); + attr.setBold(getTheme().getColor("tlabel").isBold()); + } + getScreen().putStringXY(1, 0, " Foreground ", attr); + + // Have to draw the colors manually because the int value matches + // SGR, not CGA. + attr.reset(); + attr.setReverse(true); + attr.setForeColor(Color.BLACK); + putStringXY(1, 1, " ", attr); + attr.setForeColor(Color.BLUE); + putStringXY(4, 1, " ", attr); + attr.setForeColor(Color.GREEN); + putStringXY(7, 1, " ", attr); + attr.setForeColor(Color.CYAN); + putStringXY(10, 1, " ", attr); + attr.setForeColor(Color.RED); + putStringXY(1, 2, " ", attr); + attr.setForeColor(Color.MAGENTA); + putStringXY(4, 2, " ", attr); + attr.setForeColor(Color.YELLOW); + putStringXY(7, 2, " ", attr); + attr.setForeColor(Color.WHITE); + putStringXY(10, 2, " ", attr); + + attr.setBold(true); + attr.setForeColor(Color.BLACK); + putStringXY(1, 3, " ", attr); + attr.setForeColor(Color.BLUE); + putStringXY(4, 3, " ", attr); + attr.setForeColor(Color.GREEN); + putStringXY(7, 3, " ", attr); + attr.setForeColor(Color.CYAN); + putStringXY(10, 3, " ", attr); + attr.setForeColor(Color.RED); + putStringXY(1, 4, " ", attr); + attr.setForeColor(Color.MAGENTA); + putStringXY(4, 4, " ", attr); + attr.setForeColor(Color.YELLOW); + putStringXY(7, 4, " ", attr); + attr.setForeColor(Color.WHITE); + putStringXY(10, 4, " ", attr); + + // Draw the dot + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (color.equals(Color.BLACK) && !bold) { + // Use white-on-black for black. All other colors use + // black-on-whatever. + attr.reset(); + getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07], + attr); + } else { + getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07]); + } + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (keypress.equals(kbRight)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (dotX < 10) { + dotX += 3; + } + color = getColorFromPosition(dotX, dotY); + } else if (keypress.equals(kbLeft)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (dotX > 3) { + dotX -= 3; + } + color = getColorFromPosition(dotX, dotY); + } else if (keypress.equals(kbUp)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (dotY > 1) { + dotY--; + } + color = getColorFromPosition(dotX, dotY); + bold = getBoldFromPosition(dotY); + } else if (keypress.equals(kbDown)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (dotY < 4) { + dotY++; + } + color = getColorFromPosition(dotX, dotY); + bold = getBoldFromPosition(dotY); + } else { + // Pass to my parent + super.onKeypress(keypress); + return; + } + + // Save this update to the local theme. + ((TEditColorThemeWindow) getWindow()).saveToEditTheme(); + } + + /** + * Handle mouse press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (mouse.isMouseWheelUp()) { + // Do this like kbUp + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (dotY > 1) { + dotY--; + } + color = getColorFromPosition(dotX, dotY); + bold = getBoldFromPosition(dotY); + } else if (mouse.isMouseWheelDown()) { + // Do this like kbDown + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color, bold); + if (dotY < 4) { + dotY++; + } + color = getColorFromPosition(dotX, dotY); + bold = getBoldFromPosition(dotY); + } else if ((mouse.getX() > 0) + && (mouse.getX() < getWidth() - 1) + && (mouse.getY() > 0) + && (mouse.getY() < getHeight() - 1) + ) { + color = getColorFromPosition(mouse.getX(), mouse.getY()); + bold = getBoldFromPosition(mouse.getY()); + } else { + // Let parent class handle it. + super.onMouseDown(mouse); + return; + } + + // Save this update to the local theme. + ((TEditColorThemeWindow) getWindow()).saveToEditTheme(); + } + + } + + /** + * The background color picker. + */ + class BackgroundPicker extends TWidget { + + /** + * The selected color. + */ + Color color; + + /** + * Public constructor. + * + * @param parent parent widget + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + */ + public BackgroundPicker(final TWidget parent, final int x, + final int y, final int width, final int height) { + + super(parent, x, y, width, height); + } + + /** + * Get the X grid coordinate for this color. + * + * @param color the Color value + * @return the X coordinate + */ + private int getXColorPosition(final Color color) { + if (color.equals(Color.BLACK)) { + return 2; + } else if (color.equals(Color.BLUE)) { + return 5; + } else if (color.equals(Color.GREEN)) { + return 8; + } else if (color.equals(Color.CYAN)) { + return 11; + } else if (color.equals(Color.RED)) { + return 2; + } else if (color.equals(Color.MAGENTA)) { + return 5; + } else if (color.equals(Color.YELLOW)) { + return 8; + } else if (color.equals(Color.WHITE)) { + return 11; + } + throw new IllegalArgumentException("Invalid color: " + color); + } + + /** + * Get the Y grid coordinate for this color. + * + * @param color the Color value + * @return the Y coordinate + */ + private int getYColorPosition(final Color color) { + int dotY = 1; + if (color.equals(Color.RED)) { + dotY = 2; + } else if (color.equals(Color.MAGENTA)) { + dotY = 2; + } else if (color.equals(Color.YELLOW)) { + dotY = 2; + } else if (color.equals(Color.WHITE)) { + dotY = 2; + } + return dotY; + } + + /** + * Get the color based on (X, Y) grid coordinate. + * + * @param dotX the X coordinate + * @param dotY the Y coordinate + * @return the Color value + */ + private Color getColorFromPosition(final int dotX, final int dotY) { + if ((1 <= dotX) && (dotX <= 3) && (dotY == 1)) { + return Color.BLACK; + } + if ((4 <= dotX) && (dotX <= 6) && (dotY == 1)) { + return Color.BLUE; + } + if ((7 <= dotX) && (dotX <= 9) && (dotY == 1)) { + return Color.GREEN; + } + if ((10 <= dotX) && (dotX <= 12) && (dotY == 1)) { + return Color.CYAN; + } + if ((1 <= dotX) && (dotX <= 3) && (dotY == 2)) { + return Color.RED; + } + if ((4 <= dotX) && (dotX <= 6) && (dotY == 2)) { + return Color.MAGENTA; + } + if ((7 <= dotX) && (dotX <= 9) && (dotY == 2)) { + return Color.YELLOW; + } + if ((10 <= dotX) && (dotX <= 12) && (dotY == 2)) { + return Color.WHITE; + } + + throw new IllegalArgumentException("Invalid coordinates: " + + dotX + ", " + dotY); + } + + /** + * Draw the background colors grid. + */ + @Override + public void draw() { + CellAttributes border = getWindow().getBorder(); + CellAttributes background = getWindow().getBackground(); + CellAttributes attr = new CellAttributes(); + + getScreen().drawBox(0, 0, getWidth(), getHeight(), border, + background, 1, false); + + attr.setTo(getTheme().getColor("twindow.background.modal")); + if (isActive()) { + attr.setForeColor(getTheme().getColor("tlabel").getForeColor()); + attr.setBold(getTheme().getColor("tlabel").isBold()); + } + getScreen().putStringXY(1, 0, " Background ", attr); + + // Have to draw the colors manually because the int value matches + // SGR, not CGA. + attr.reset(); + attr.setReverse(true); + attr.setForeColor(Color.BLACK); + putStringXY(1, 1, " ", attr); + attr.setForeColor(Color.BLUE); + putStringXY(4, 1, " ", attr); + attr.setForeColor(Color.GREEN); + putStringXY(7, 1, " ", attr); + attr.setForeColor(Color.CYAN); + putStringXY(10, 1, " ", attr); + attr.setForeColor(Color.RED); + putStringXY(1, 2, " ", attr); + attr.setForeColor(Color.MAGENTA); + putStringXY(4, 2, " ", attr); + attr.setForeColor(Color.YELLOW); + putStringXY(7, 2, " ", attr); + attr.setForeColor(Color.WHITE); + putStringXY(10, 2, " ", attr); + + // Draw the dot + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (color.equals(Color.BLACK)) { + // Use white-on-black for black. All other colors use + // black-on-whatever. + attr.reset(); + getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07], + attr); + } else { + getScreen().putCharXY(dotX, dotY, GraphicsChars.CP437[0x07]); + } + + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (keypress.equals(kbRight)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (dotX < 10) { + dotX += 3; + } + color = getColorFromPosition(dotX, dotY); + } else if (keypress.equals(kbLeft)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (dotX > 3) { + dotX -= 3; + } + color = getColorFromPosition(dotX, dotY); + } else if (keypress.equals(kbUp)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (dotY == 2) { + dotY--; + } + color = getColorFromPosition(dotX, dotY); + } else if (keypress.equals(kbDown)) { + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (dotY == 1) { + dotY++; + } + color = getColorFromPosition(dotX, dotY); + } else { + // Pass to my parent + super.onKeypress(keypress); + } + + // Save this update to the local theme. + ((TEditColorThemeWindow) getWindow()).saveToEditTheme(); + } + + /** + * Handle mouse press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (mouse.isMouseWheelUp()) { + // Do this like kbUp + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (dotY == 2) { + dotY--; + } + color = getColorFromPosition(dotX, dotY); + } else if (mouse.isMouseWheelDown()) { + // Do this like kbDown + int dotX = getXColorPosition(color); + int dotY = getYColorPosition(color); + if (dotY == 1) { + dotY++; + } + color = getColorFromPosition(dotX, dotY); + return; + } else if ((mouse.getX() > 0) + && (mouse.getX() < getWidth() - 1) + && (mouse.getY() > 0) + && (mouse.getY() < getHeight() - 1) + ) { + color = getColorFromPosition(mouse.getX(), mouse.getY()); + } else { + // Let parent class handle it. + super.onMouseDown(mouse); + return; + } + + // Save this update to the local theme. + ((TEditColorThemeWindow) getWindow()).saveToEditTheme(); + } + + } + + /** + * The current editing theme. + */ + private ColorTheme editTheme; + + /** + * The left-side list of colors pane. + */ + private TList colorNames; + + /** + * The foreground color. + */ + private ForegroundPicker foreground; + + /** + * The background color. + */ + private BackgroundPicker background; + + /** + * Set various widgets/values to the editing theme color. + * + * @param colorName name of color from theme + */ + private void refreshFromTheme(final String colorName) { + CellAttributes attr = editTheme.getColor(colorName); + foreground.color = attr.getForeColor(); + foreground.bold = attr.isBold(); + background.color = attr.getBackColor(); + } + + /** + * Examines foreground, background, and colorNames and sets the color in + * editTheme. + */ + private void saveToEditTheme() { + String colorName = colorNames.getSelected(); + if (colorName == null) { + return; + } + CellAttributes attr = editTheme.getColor(colorName); + attr.setForeColor(foreground.color); + attr.setBold(foreground.bold); + attr.setBackColor(background.color); + editTheme.setColor(colorName, attr); + } + + /** + * Public constructor. The file open box will be centered on screen. + * + * @param application the TApplication that manages this window + */ + public TEditColorThemeWindow(final TApplication application) { + + // Register with the TApplication + super(application, "Colors", 0, 0, 60, 18, MODAL); + + // Initialize with the first color + List colors = getTheme().getColorNames(); + assert (colors.size() > 0); + editTheme = new ColorTheme(); + for (String key: colors) { + CellAttributes attr = new CellAttributes(); + attr.setTo(getTheme().getColor(key)); + editTheme.setColor(key, attr); + } + + colorNames = addList(colors, 2, 2, 38, getHeight() - 7, + new TAction() { + // When the user presses Enter + public void DO() { + refreshFromTheme(colorNames.getSelected()); + } + }, + new TAction() { + // When the user navigates with keyboard + public void DO() { + refreshFromTheme(colorNames.getSelected()); + } + } + ); + foreground = new ForegroundPicker(this, 42, 1, 14, 6); + background = new BackgroundPicker(this, 42, 7, 14, 4); + refreshFromTheme(colors.get(0)); + colorNames.setSelectedIndex(0); + + addButton(" &OK ", getWidth() - 37, getHeight() - 4, + new TAction() { + public void DO() { + ColorTheme global = getTheme(); + List colors = editTheme.getColorNames(); + for (String key: colors) { + CellAttributes attr = new CellAttributes(); + attr.setTo(editTheme.getColor(key)); + global.setColor(key, attr); + } + getApplication().closeWindow(TEditColorThemeWindow.this); + } + } + ); + + addButton("&Cancel", getWidth() - 25, getHeight() - 4, + new TAction() { + public void DO() { + getApplication().closeWindow(TEditColorThemeWindow.this); + } + } + ); + + // Default to the color list + activate(colorNames); + + } + + /** + * Draw me on screen. + */ + @Override + public void draw() { + super.draw(); + CellAttributes attr = new CellAttributes(); + + // Draw the label on colorNames + attr.setTo(getTheme().getColor("twindow.background.modal")); + if (colorNames.isActive()) { + attr.setForeColor(getTheme().getColor("tlabel").getForeColor()); + attr.setBold(getTheme().getColor("tlabel").isBold()); + } + getScreen().putStringXY(3, 2, "Color Name", attr); + + // Draw the sample text box + attr.reset(); + attr.setForeColor(foreground.color); + attr.setBold(foreground.bold); + attr.setBackColor(background.color); + getScreen().putStringXY(getWidth() - 17, getHeight() - 6, + "Text Text Text", attr); + getScreen().putStringXY(getWidth() - 17, getHeight() - 5, + "Text Text Text", attr); + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + // Escape - behave like cancel + if (keypress.equals(kbEsc)) { + getApplication().closeWindow(this); + return; + } + + // Pass to my parent + super.onKeypress(keypress); + } + +} diff --git a/src/jexer/TList.java b/src/jexer/TList.java new file mode 100644 index 0000000..e069db7 --- /dev/null +++ b/src/jexer/TList.java @@ -0,0 +1,447 @@ +/* + * Jexer - Java Text User Interface + * + * License: LGPLv3 or later + * + * This module is licensed under the GNU Lesser General Public License + * Version 3. Please see the file "COPYING" in this directory for more + * information about the GNU Lesser General Public License Version 3. + * + * Copyright (C) 2015 Kevin Lamonte + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 3 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * http://www.gnu.org/licenses/, or write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + * @author Kevin Lamonte [kevin.lamonte@gmail.com] + * @version 1 + */ +package jexer; + +import java.util.ArrayList; +import java.util.List; + +import jexer.bits.CellAttributes; +import jexer.event.TKeypressEvent; +import jexer.event.TMouseEvent; +import static jexer.TKeypress.*; + +/** + * TList shows a list of strings, and lets the user select one. + */ +public class TList extends TWidget { + + /** + * The list of strings to display. + */ + private List strings; + + /** + * Selected string. + */ + private int selectedString = -1; + + /** + * Get the selection index. + * + * @return -1 if nothing is selected, otherwise the index into the list + */ + public final int getSelectedIndex() { + return selectedString; + } + + /** + * Set the selected string index. + * + * @param index -1 to unselect, otherwise the index into the list + */ + public final void setSelectedIndex(final int index) { + selectedString = index; + } + + /** + * Get the selected string. + * + * @return the selected string, or null of nothing is selected yet + */ + public final String getSelected() { + if ((selectedString >= 0) && (selectedString <= strings.size() - 1)) { + return strings.get(selectedString); + } + return null; + } + + /** + * Set the new list of strings to display. + * + * @param list new list of strings + */ + public final void setList(final List list) { + strings.clear(); + strings.addAll(list); + reflow(); + } + + /** + * Vertical scrollbar. + */ + private TVScroller vScroller; + + /** + * Get the vertical scrollbar. This is used by subclasses. + * + * @return the vertical scrollbar + */ + public final TVScroller getVScroller() { + return vScroller; + } + + /** + * Horizontal scrollbar. + */ + private THScroller hScroller; + + /** + * Get the horizontal scrollbar. This is used by subclasses. + * + * @return the horizontal scrollbar + */ + public final THScroller getHScroller() { + return hScroller; + } + + /** + * Maximum width of a single line. + */ + private int maxLineWidth; + + /** + * The action to perform when the user selects an item (clicks or enter). + */ + private TAction enterAction = null; + + /** + * The action to perform when the user navigates with keyboard. + */ + private TAction moveAction = null; + + /** + * Perform user selection action. + */ + public void dispatchEnter() { + assert (selectedString >= 0); + assert (selectedString < strings.size()); + if (enterAction != null) { + enterAction.DO(); + } + } + + /** + * Perform list movement action. + */ + public void dispatchMove() { + assert (selectedString >= 0); + assert (selectedString < strings.size()); + if (moveAction != null) { + moveAction.DO(); + } + } + + /** + * Resize for a new width/height. + */ + public void reflow() { + + // Reset the lines + selectedString = -1; + maxLineWidth = 0; + + for (int i = 0; i < strings.size(); i++) { + String line = strings.get(i); + if (line.length() > maxLineWidth) { + maxLineWidth = line.length(); + } + } + + // Start at the top + if (vScroller == null) { + vScroller = new TVScroller(this, getWidth() - 1, 0, + getHeight() - 1); + } else { + vScroller.setX(getWidth() - 1); + vScroller.setHeight(getHeight() - 1); + } + vScroller.setBottomValue(strings.size() - getHeight() + 1); + vScroller.setTopValue(0); + vScroller.setValue(0); + if (vScroller.getBottomValue() < 0) { + vScroller.setBottomValue(0); + } + vScroller.setBigChange(getHeight() - 1); + + // Start at the left + if (hScroller == null) { + hScroller = new THScroller(this, 0, getHeight() - 1, + getWidth() - 1); + } else { + hScroller.setY(getHeight() - 1); + hScroller.setWidth(getWidth() - 1); + } + hScroller.setRightValue(maxLineWidth - getWidth() + 1); + hScroller.setLeftValue(0); + hScroller.setValue(0); + if (hScroller.getRightValue() < 0) { + hScroller.setRightValue(0); + } + hScroller.setBigChange(getWidth() - 1); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @param strings list of strings to show + * @param x column relative to parent + * @param y row relative to parent + * @param width width of text area + * @param height height of text area + */ + public TList(final TWidget parent, final List strings, final int x, + final int y, final int width, final int height) { + + this(parent, strings, x, y, width, height, null); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @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 + */ + public TList(final TWidget parent, final List strings, final int x, + final int y, final int width, final int height, + final TAction enterAction) { + + super(parent, x, y, width, height); + this.enterAction = enterAction; + this.strings = new ArrayList(); + if (strings != null) { + this.strings.addAll(strings); + } + reflow(); + } + + /** + * Public constructor. + * + * @param parent parent widget + * @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 + */ + public TList(final TWidget parent, final List strings, final int x, + final int y, final int width, final int height, + final TAction enterAction, final TAction moveAction) { + + super(parent, x, y, width, height); + this.enterAction = enterAction; + this.moveAction = moveAction; + this.strings = new ArrayList(); + if (strings != null) { + this.strings.addAll(strings); + } + reflow(); + } + + /** + * Draw the files list. + */ + @Override + public void draw() { + CellAttributes color = null; + int begin = vScroller.getValue(); + int topY = 0; + for (int i = begin; i < strings.size(); i++) { + String line = strings.get(i); + if (hScroller.getValue() < line.length()) { + line = line.substring(hScroller.getValue()); + } else { + line = ""; + } + if (i == selectedString) { + color = getTheme().getColor("tlist.selected"); + } else if (isAbsoluteActive()) { + color = getTheme().getColor("tlist"); + } else { + color = getTheme().getColor("tlist.inactive"); + } + String formatString = "%-" + Integer.toString(getWidth() - 1) + "s"; + getScreen().putStringXY(0, topY, String.format(formatString, line), + color); + topY++; + if (topY >= getHeight() - 1) { + break; + } + } + + if (isAbsoluteActive()) { + color = getTheme().getColor("tlist"); + } else { + color = getTheme().getColor("tlist.inactive"); + } + + // Pad the rest with blank lines + for (int i = topY; i < getHeight() - 1; i++) { + getScreen().hLineXY(0, i, getWidth() - 1, ' ', color); + } + } + + /** + * Handle mouse press events. + * + * @param mouse mouse button press event + */ + @Override + public void onMouseDown(final TMouseEvent mouse) { + if (mouse.isMouseWheelUp()) { + vScroller.decrement(); + return; + } + if (mouse.isMouseWheelDown()) { + vScroller.increment(); + return; + } + + if ((mouse.getX() < getWidth() - 1) + && (mouse.getY() < getHeight() - 1)) { + if (vScroller.getValue() + mouse.getY() < strings.size()) { + selectedString = vScroller.getValue() + mouse.getY(); + } + dispatchEnter(); + return; + } + + // Pass to children + super.onMouseDown(mouse); + } + + /** + * Handle keystrokes. + * + * @param keypress keystroke event + */ + @Override + public void onKeypress(final TKeypressEvent keypress) { + if (keypress.equals(kbLeft)) { + hScroller.decrement(); + } else if (keypress.equals(kbRight)) { + hScroller.increment(); + } else if (keypress.equals(kbUp)) { + if (strings.size() > 0) { + if (selectedString >= 0) { + if (selectedString > 0) { + if (selectedString - vScroller.getValue() == 0) { + vScroller.decrement(); + } + selectedString--; + } + } else { + selectedString = strings.size() - 1; + } + } + if (selectedString >= 0) { + dispatchMove(); + } + } else if (keypress.equals(kbDown)) { + if (strings.size() > 0) { + if (selectedString >= 0) { + if (selectedString < strings.size() - 1) { + selectedString++; + if (selectedString - vScroller.getValue() == getHeight() - 1) { + vScroller.increment(); + } + } + } else { + selectedString = 0; + } + } + if (selectedString >= 0) { + dispatchMove(); + } + } else if (keypress.equals(kbPgUp)) { + vScroller.bigDecrement(); + if (selectedString >= 0) { + selectedString -= getHeight() - 1; + if (selectedString < 0) { + selectedString = 0; + } + } + if (selectedString >= 0) { + dispatchMove(); + } + } else if (keypress.equals(kbPgDn)) { + vScroller.bigIncrement(); + if (selectedString >= 0) { + selectedString += getHeight() - 1; + if (selectedString > strings.size() - 1) { + selectedString = strings.size() - 1; + } + } + if (selectedString >= 0) { + dispatchMove(); + } + } else if (keypress.equals(kbHome)) { + vScroller.toTop(); + if (strings.size() > 0) { + selectedString = 0; + } + if (selectedString >= 0) { + dispatchMove(); + } + } else if (keypress.equals(kbEnd)) { + vScroller.toBottom(); + if (strings.size() > 0) { + selectedString = strings.size() - 1; + } + if (selectedString >= 0) { + dispatchMove(); + } + } else if (keypress.equals(kbTab)) { + getParent().switchWidget(true); + } else if (keypress.equals(kbShiftTab) || keypress.equals(kbBackTab)) { + getParent().switchWidget(false); + } else if (keypress.equals(kbEnter)) { + if (selectedString >= 0) { + dispatchEnter(); + } + } else { + // Pass other keys (tab etc.) on + super.onKeypress(keypress); + } + } + +} diff --git a/src/jexer/TWidget.java b/src/jexer/TWidget.java index 564cc52..dffa9e1 100644 --- a/src/jexer/TWidget.java +++ b/src/jexer/TWidget.java @@ -1370,4 +1370,59 @@ public abstract class TWidget implements Comparable { return new TDirectoryList(this, path, x, y, width, height, action); } + /** + * Convenience function to add a directory list to this container/window. + * + * @param strings list of strings to show + * @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 directory list + */ + public final TList addList(final List strings, final int x, + final int y, final int width, final int height) { + + return new TList(this, strings, x, y, width, height, null); + } + + /** + * Convenience function to add a directory list to this container/window. + * + * @param strings list of strings to show + * @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 + * @return the new directory list + */ + public final TList addList(final List strings, final int x, + final int y, final int width, final int height, + final TAction enterAction) { + + return new TList(this, strings, x, y, width, height, enterAction); + } + + /** + * Convenience function to add a directory list to this container/window. + * + * @param strings list of strings to show + * @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 + * @return the new directory list + */ + public final TList addList(final List strings, final int x, + final int y, final int width, final int height, + final TAction enterAction, final TAction moveAction) { + + return new TList(this, strings, x, y, width, height, enterAction, + moveAction); + } + } diff --git a/src/jexer/TWindow.java b/src/jexer/TWindow.java index bfb0901..c7b59c8 100644 --- a/src/jexer/TWindow.java +++ b/src/jexer/TWindow.java @@ -398,7 +398,7 @@ public class TWindow extends TWidget { * * @return the border color */ - private CellAttributes getBorder() { + public CellAttributes getBorder() { if (!isModal() && (inWindowMove || inWindowResize || inKeyboardResize) ) { diff --git a/src/jexer/bits/ColorTheme.java b/src/jexer/bits/ColorTheme.java index a8c0c89..eaa351c 100644 --- a/src/jexer/bits/ColorTheme.java +++ b/src/jexer/bits/ColorTheme.java @@ -34,6 +34,9 @@ import java.io.BufferedReader; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; import java.util.SortedMap; import java.util.StringTokenizer; import java.util.TreeMap; @@ -68,6 +71,18 @@ public final class ColorTheme { return attr; } + /** + * Retrieve all the names in the theme. + * + * @return a list of names + */ + public List getColorNames() { + Set keys = colors.keySet(); + List names = new ArrayList(keys.size()); + names.addAll(keys); + return names; + } + /** * Set the color for a named theme color. * @@ -241,7 +256,7 @@ public final class ColorTheme { color.setBold(false); colors.put("tbutton.inactive", color); color = new CellAttributes(); - color.setForeColor(Color.WHITE); + color.setForeColor(Color.CYAN); color.setBackColor(Color.GREEN); color.setBold(true); colors.put("tbutton.active", color); @@ -402,27 +417,27 @@ public final class ColorTheme { color.setBold(true); colors.put("ttreeview.inactive", color); - // TText text + // TList color = new CellAttributes(); color.setForeColor(Color.WHITE); color.setBackColor(Color.BLUE); color.setBold(false); - colors.put("tdirectorylist", color); + colors.put("tlist", color); color = new CellAttributes(); color.setForeColor(Color.BLACK); color.setBackColor(Color.CYAN); color.setBold(false); - colors.put("tdirectorylist.selected", color); + colors.put("tlist.selected", color); color = new CellAttributes(); color.setForeColor(Color.BLACK); color.setBackColor(Color.CYAN); color.setBold(false); - colors.put("tdirectorylist.unreadable", color); + colors.put("tlist.unreadable", color); color = new CellAttributes(); color.setForeColor(Color.BLACK); color.setBackColor(Color.BLUE); color.setBold(true); - colors.put("tdirectorylist.inactive", color); + colors.put("tlist.inactive", color); // TEditor color = new CellAttributes(); diff --git a/src/jexer/demos/DemoApplication.java b/src/jexer/demos/DemoApplication.java index f0bdacd..dd25f02 100644 --- a/src/jexer/demos/DemoApplication.java +++ b/src/jexer/demos/DemoApplication.java @@ -60,6 +60,7 @@ public class DemoApplication extends TApplication { item = demoMenu.addItem(2002, "&Normal"); TSubMenu subMenu = demoMenu.addSubMenu("Sub-&Menu"); item = demoMenu.addItem(2010, "N&ormal A&&D"); + item = demoMenu.addItem(2050, "Co&lors..."); item = subMenu.addItem(2000, "&Checkable (sub)"); item.setCheckable(true); @@ -105,6 +106,12 @@ public class DemoApplication extends TApplication { */ @Override public boolean onMenu(final TMenuEvent menu) { + + if (menu.getId() == 2050) { + new TEditColorThemeWindow(this); + return true; + } + if (menu.getId() == TMenu.MID_OPEN_FILE) { try { String filename = fileOpenBox("."); diff --git a/src/jexer/demos/DemoMainWindow.java b/src/jexer/demos/DemoMainWindow.java index 9a83464..72ee43f 100644 --- a/src/jexer/demos/DemoMainWindow.java +++ b/src/jexer/demos/DemoMainWindow.java @@ -77,7 +77,7 @@ public class DemoMainWindow extends TWindow { private DemoMainWindow(final TApplication parent, final int flags) { // Construct a demo window. X and Y don't matter because it will be // centered on screen. - super(parent, "Demo Window", 0, 0, 60, 23, flags); + super(parent, "Demo Window", 0, 0, 60, 24, flags); int row = 1; @@ -113,7 +113,7 @@ public class DemoMainWindow extends TWindow { addPasswordField(35, row++, 15, false); addLabel("Fixed-width password:", 1, row); addPasswordField(35, row++, 15, true, "hunter2"); - row += 2; + row += 1; if (!isModal()) { addLabel("Radio buttons and checkboxes", 1, row); @@ -179,6 +179,18 @@ public class DemoMainWindow extends TWindow { } row += 2; + if (!isModal()) { + addLabel("Color editor", 1, row); + addButton("Co&lors", 35, row, + new TAction() { + public void DO() { + new TEditColorThemeWindow(getApplication()); + } + } + ); + } + row += 2; + progressBar = addProgressBar(1, row, 22, 0); row++; timerLabel = addLabel("Timer", 1, row); -- 2.27.0