diff --git a/.deepsource.toml b/.deepsource.toml new file mode 100644 index 0000000..f52b724 --- /dev/null +++ b/.deepsource.toml @@ -0,0 +1,16 @@ +version = 1 + +[[analyzers]] +name = "test-coverage" +enabled = true + +[[analyzers]] +name = "java" +enabled = true + + [analyzers.meta] + runtime_version = "11" + +[[transformers]] +name = "google-java-format" +enabled = true \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3957d67..335f2c8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,6 @@ *.log *.class *.jar +/.idea +*.iml +/target diff --git a/build.xml b/build.xml deleted file mode 100644 index 86cab7f..0000000 --- a/build.xml +++ /dev/null @@ -1,151 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1db70dd --- /dev/null +++ b/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + org.peterriewe + hexgui + 1.0-SNAPSHOT + + hexgui + + http://www.example.com + + + UTF-8 + 11 + 11 + + + + + junit + junit + 4.11 + test + + + + + + + + + maven-clean-plugin + 3.1.0 + + + + maven-resources-plugin + 3.0.2 + + + maven-compiler-plugin + 3.8.0 + + + maven-surefire-plugin + 2.22.1 + + + maven-jar-plugin + 3.0.2 + + + maven-install-plugin + 2.5.2 + + + maven-deploy-plugin + 2.8.2 + + + + maven-site-plugin + 3.7.1 + + + maven-project-info-reports-plugin + 3.0.0 + + + + + diff --git a/src/hexgui/Main.java b/src/hexgui/Main.java deleted file mode 100644 index 6e2ccce..0000000 --- a/src/hexgui/Main.java +++ /dev/null @@ -1,115 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui; - -import hexgui.gui.HexGui; -import hexgui.util.Options; -import hexgui.version.Version; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; -import java.io.*; -import java.util.*; - -public final class Main -{ - final static String LOOKANDFEEL = "System"; - - private static void initLookAndFeel() { - String lookAndFeel = null; - - if (LOOKANDFEEL != null) { - if (LOOKANDFEEL.equals("Metal")) { - lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); - } else if (LOOKANDFEEL.equals("System")) { - lookAndFeel = UIManager.getSystemLookAndFeelClassName(); - } else if (LOOKANDFEEL.equals("Motif")) { - lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; - } else if (LOOKANDFEEL.equals("GTK+")) { //new in 1.4.2 - lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; - } else { - System.err.println("Unexpected value of LOOKANDFEEL specified: " - + LOOKANDFEEL); - lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); - } - - try { - UIManager.setLookAndFeel(lookAndFeel); - } catch (ClassNotFoundException e) { - System.err.println("Couldn't find class for specified look and feel:" - + lookAndFeel); - System.err.println("Did you include the L&F library in the class path?"); - System.err.println("Using the default look and feel."); - } catch (UnsupportedLookAndFeelException e) { - System.err.println("Can't use the specified look and feel (" - + lookAndFeel - + ") on this platform."); - System.err.println("Using the default look and feel."); - } catch (Exception e) { - System.err.println("Couldn't get specified look and feel (" - + lookAndFeel - + "), for some reason."); - System.err.println("Using the default look and feel."); - e.printStackTrace(); - } - } - } - - private static void createAndShowGUI(File file, String command) { - initLookAndFeel(); - JFrame.setDefaultLookAndFeelDecorated(true); - HexGui app = new HexGui(file, command); - } - - public static void main(String[] args) throws Exception { - try - { - String options[] = { - "config:", - "program:", - "help", - "version" - }; - Options opt = Options.parse(args, options); - if (opt.contains("help")) { - String helpText = - "Usage: hexgui [options] [file]\n" + - "Graphical user interface for Hex programs\n" + - "using the Hex Text Protocol.\n" + - "\n" + - "-config file Read command line arguments from file\n" + - "-help Display this help and exit\n" + - "-program Command for Hex program to attach\n" + - "-version Print version and exit\n"; - System.out.print(helpText); - return; - } - if (opt.contains("version")) { - System.out.println("HexGui " + Version.id + " " - + Version.date); - return; - } - final String command = opt.get("program", null); - ArrayList arguments = opt.getArguments(); - final File file; - if (arguments.size() == 0) - file = null; - else if (arguments.size() == 1) - file = new File(arguments.get(0)); - else - throw new Exception("Only one argument allowed"); - javax.swing.SwingUtilities.invokeLater(new Runnable() { - public void run() { - createAndShowGUI(file, command); - } - }); - } - catch (Exception e) - { - System.err.println(e.getMessage()); - } - } -} diff --git a/src/hexgui/MainWrapper.java b/src/hexgui/MainWrapper.java deleted file mode 100644 index ac266a6..0000000 --- a/src/hexgui/MainWrapper.java +++ /dev/null @@ -1,30 +0,0 @@ -package hexgui; - -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.util.Arrays; - -/** Wrapper for starting HexGui. - Loads the main class with the reflection API to set AWT an other - properties before any AWT class is loaded (otherwise they would be - ignored). */ -public final class MainWrapper -{ - public static void main(String[] args) throws Exception - { - // Use GDI rendering on Windows. There are repaint problems using - // DDraw (last tested with Java 1.6 on Windows 7). - System.setProperty("sun.java2d.noddraw", "true"); - - // Invoke hexgui.Main.main() with reflection API - Class mainClass = Class.forName("hexgui.Main"); - Class[] argTypes = new Class[] { String[].class }; - Method mainMethod = mainClass.getMethod("main", argTypes); - mainMethod.invoke(null, (Object)args); - } - - /** Make constructor unavailable; class is for namespace only. */ - private MainWrapper() - { - } -} diff --git a/src/hexgui/game/Clock.java b/src/hexgui/game/Clock.java deleted file mode 100644 index c60b42a..0000000 --- a/src/hexgui/game/Clock.java +++ /dev/null @@ -1,84 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.game; - -import hexgui.hex.*; -import hexgui.util.Pair; -import hexgui.util.StringUtils; - -import java.io.*; -import javax.swing.Timer; -import java.awt.*; -import java.awt.event.*; -import java.net.*; - -public class Clock - implements ActionListener -{ - public interface Listener - { - public void clockChanged(); - } - - /** Initializes a clock. */ - public Clock() - { - m_timer = new Timer(1000, this); - m_elapsed = 0; - m_startTime = -1; - } - - public void addListener(Listener ls) - { - m_listener = ls; - } - - public void start() - { - if (m_startTime != -1) // already started - return; - m_startTime = System.currentTimeMillis(); - m_timer.setInitialDelay((m_elapsed+999)/1000*1000 - m_elapsed); - m_timer.start(); - } - - public void stop() - { - if (m_startTime == -1) // already stopped - return; - m_timer.stop(); - m_elapsed += (int)(System.currentTimeMillis() - m_startTime); - m_startTime = -1; - } - - /** Returns elapsed time in milliseconds. */ - public int elapsed() - { - if (m_startTime == -1) - return m_elapsed; - return m_elapsed + (int)(System.currentTimeMillis() - m_startTime); - } - - /** Sets the time. */ - public void setElapsed(int millis) - { - m_elapsed = millis; - m_listener.clockChanged(); - } - - public void actionPerformed(ActionEvent e) - { - if (m_listener != null) - m_listener.clockChanged(); - } - - private Listener m_listener; - - private Timer m_timer; - - private int m_elapsed; - - private long m_startTime; -}; diff --git a/src/hexgui/game/Game.java b/src/hexgui/game/Game.java deleted file mode 100644 index ce256ba..0000000 --- a/src/hexgui/game/Game.java +++ /dev/null @@ -1,42 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.game; - -//---------------------------------------------------------------------------- - -/** Game state. - - A game can be in one of four states: STARTING, - RUNNING, PAUSED, and - FINISHED. The game is STARTING when the - first move has yet to be played. Once the first move is played - then the game is RUNNING. The clock is ticking only - when the game is running. If a side-to-side connection is - achieved or a player forfeits or resigns then the game is - FINISHED. Moves can only be played in the first - three states. -*/ -public final class Game -{ - - public static final int STARTING = 0; - public static final int RUNNING = 1; - public static final int PAUSED = 2; - public static final int FINISHED = 3; - - public Game() - { - m_state = STARTING; - } - - public int getState() { return m_state; } - - private int m_state; - - private Node m_root; - private Node m_current; -} - -//---------------------------------------------------------------------------- \ No newline at end of file diff --git a/src/hexgui/game/GameInfo.java b/src/hexgui/game/GameInfo.java deleted file mode 100644 index b523a06..0000000 --- a/src/hexgui/game/GameInfo.java +++ /dev/null @@ -1,27 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.game; - -import hexgui.hex.Move; -import java.awt.Dimension; - -//---------------------------------------------------------------------------- - -/** Properties of a game. - Holds properties that appear in a SGF root node. -*/ - -public class GameInfo -{ - - public GameInfo() - { - } - - public void setBoardSize(Dimension dim) { m_boardsize = dim; } - public Dimension getBoardSize() { return m_boardsize; } - - private Dimension m_boardsize; -} diff --git a/src/hexgui/game/Node.java b/src/hexgui/game/Node.java deleted file mode 100644 index f0b333f..0000000 --- a/src/hexgui/game/Node.java +++ /dev/null @@ -1,501 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.game; - -import hexgui.hex.HexColor; -import hexgui.hex.HexPoint; -import hexgui.hex.Move; - -import java.util.Iterator; -import java.util.Map; -import java.util.TreeMap; -import java.util.Vector; - -//---------------------------------------------------------------------------- - -/** Node in a game tree. - Stores moves and other properties. -*/ -public class Node -{ - /** Initializes an empty node with a null move. */ - public Node() - { - this(null); - } - - /** Constructs a new node with the specified move. - @param move move to initialize the node with. - */ - public Node(Move move) - { - // Properties. This can include unstructured properties found - // in SGF files (i.e., properties that HexGUI doesn't know - // about), as well as structured properties such as C - // (comment), PL (player to move), and maybe - // others. Properties that can take multiple values are not - // stored here; e.g., LB is stored in m_label. - m_property = new TreeMap(); - - // For setup moves. - m_setup = new TreeMap(); - - // A list of cell:label pairs can be attached to a node. This - // corresponds to the SGF LB property. - m_label = new Vector(); - - // When navigating the tree, the "recent" child of each parent - // is the one that the "forward" button will navigate to. - m_recent = false; - - // This node's move. - setMove(move); - } - - public void setMove(Move move) { m_move = move; } - public Move getMove() { return m_move; } - public boolean hasMove() { return m_move != null; } - - public void setParent(Node parent) { m_parent = parent; } - public Node getParent() { return m_parent; } - - public void setPrev(Node prev) { m_prev = prev; } - public Node getPrev() { return m_prev; } - - public void setNext(Node next) { m_next = next; } - public Node getNext() { return m_next; } - - /** Sets the first child of this node. - This does not update the sibling pointers of the child. - */ - public void setFirstChild(Node child) { - m_child = child; - } - - /** Removes this node from the gametree. */ - public void removeSelf() - { - Node prev = getPrev(); - Node next = getNext(); - - if (prev == null) { - // need to fix parent since we're first child - if (getParent() != null) { - getParent().setFirstChild(next); - } - } else { - prev.setNext(next); - } - - if (next != null) { - next.setPrev(prev); - } - } - - /** Moves this node to the start of its sibling list. */ - public void moveToFirst() - { - Node parent = getParent(); - if (parent == null) { - return; - } - this.removeSelf(); - parent.addFirstChild(this); - } - - /** Moves this node and all of its parents to the start of their - * sibling lists */ - public void makeMain() - { - Node node = this; - while (node != null) { - node.moveToFirst(); - node = node.getParent(); - } - } - - /** Adds a child to the beginning of the list of children. - @param child Node to be added to start of list. - */ - public void addFirstChild(Node child) - { - Node oldfirst = m_child; - m_child = child; - child.setParent(this); - child.setPrev(null); - child.setNext(oldfirst); - if (oldfirst != null) { - oldfirst.setPrev(child); - } - } - - /** Adds a child to the end of the list of children. - @param child Node to be added to end of list. - */ - public void addChild(Node child) - { - child.setNext(null); - child.setParent(this); - - if (m_child == null) { - m_child = child; - child.setPrev(null); - } else { - Node cur = m_child; - while (cur.getNext() != null) - cur = cur.getNext(); - cur.setNext(child); - child.setPrev(cur); - } - } - - public boolean hasChild() - { - return m_child != null; - } - - /** Returns the number of children of this node. */ - public int numChildren() - { - int num = 0; - Node cur = m_child; - while (cur != null) { - num++; - cur = cur.getNext(); - } - return num; - } - - /** Returns the nth child. - @param n The number of the child to return. - @return The nth child or null that child does not exist. - */ - public Node getChild(int n) - { - Node cur = m_child; - for (int i=0; cur != null; i++) { - if (i == n) return cur; - cur = cur.getNext(); - } - return null; - } - - /** Mark the current node as the most recently used among its - * siblings. This also unmarks the siblings */ - public void markRecent() - { - Node parent = getParent(); - if (parent != null) { - int n = parent.numChildren(); - for (int i=0; inull if no children. - */ - public Node getChild() { return getChild(0); } - - /** Returns the most recent child. - @return most recent child, or first child if no recent one, or - null if no children. - */ - public Node getRecentChild() { - Node cur = m_child; - if (cur == null) { - return null; - } - for (int i=0; cur != null; i++) { - if (cur.isRecent()) { - return cur; - } - cur = cur.getNext(); - } - return m_child; - } - - /** Returns the child that contains node in its subtree. - Currently unused. */ - public Node getChildContainingNode(Node node) - { - for (int i=0; i(key, value) pairs of strings. - These properties will stored if the gametree is saved in SGF format. - @param key name of the property - @param value value of the property - */ - public void setSgfProperty(String key, String value) - { - m_property.put(key, value); - } - - public void unsetSgfProperty(String key) - { - m_property.remove(key); - } - - /** Append the given string to the SGF property */ - public void appendSgfProperty(String key, String toadd) - { - String old = m_property.get(key); - if (old == null) old = ""; - m_property.put(key, old+toadd); - } - - /** Returns the value of a property. - @param key name of property - @return value of key or null if key is - not in the property list. - */ - public String getSgfProperty(String key) - { - return m_property.get(key); - } - - /** Returns a map of the current set of properties. - @return Map containing the properties - */ - public Map getProperties() - { - return m_property; - } - - /** Sets the SGF Comment field of this node. */ - public void setComment(String comment) { setSgfProperty("C", comment); } - - public String getComment() { return getSgfProperty("C"); } - - public boolean hasCount() - { - return (getSgfProperty("CN") != null); - } - - public String getCount() - { - return getSgfProperty("CN"); - } - - /** Adds a stone of specified color to the setup list and the sgf - property string. */ - public void addSetup(HexColor color, HexPoint point) - { - m_setup.put(point, color); - } - - public void removeSetup(HexColor color, HexPoint point) - { - m_setup.remove(point); - } - - /** Returns the set of setup stones of color. */ - public Vector getSetup(HexColor color) - { - Vector points = new Vector(); - HexPoint key; - Iterator i = m_setup.keySet().iterator(); - - while (i.hasNext()) { - key = (HexPoint)i.next(); - if (m_setup.get(key) == color) { - points.add(key); - } - } - - return points; - } - - /** Determine whether the current node has any setup moves */ - public boolean hasSetup() - { - return !m_setup.isEmpty(); - } - - /** Determine whether the current node can accept updates to setup - moves. This happens if the node has no m_move and no children. - In particular, setup is permitted on the root node (which - never has an m_move). */ - public boolean canSetup() - { - if (this.hasMove()) { - return false; - } - if (this.hasChild()) { - return false; - } - return true; - } - - - public boolean hasLabel() - { - return !m_label.isEmpty(); - } - - public Vector getLabels() - { - return m_label; - } - - public void addLabel(String str) - { - m_label.add(str); - } - - /** Return the default player to move for the current node, i.e., - the player who would be moving next if the PL property is not - set. For normal moves, passes, and swap-pieces, this is the - opponent of the player who made the move; for swap-sides, it - is the player who made the move; for the root node, it is - Black; for resign, forfeit, and setup moves, there is no - default, i.e., return null */ - public HexColor defaultPlayerToMove() - { - if (this.hasMove()) { - HexPoint p = m_move.getPoint(); - if (p == HexPoint.RESIGN || p == HexPoint.FORFEIT) { - return null; - } else if (p == HexPoint.SWAP_SIDES) { - return m_move.getColor(); - } else { - return m_move.getColor().otherColor(); - } - } else if (this.m_parent == null) { - // root node - return HexColor.BLACK; - } else { - // non-root setup node - return null; - } - } - - /** Sets the PL property to the given color. For moves that have a - default, unset the property if the requested value is the - default. */ - public void setPlayerToMove(HexColor color) - { - if (color == defaultPlayerToMove()) { - unsetSgfProperty("PL"); - } else { - setSgfProperty("PL", (color == HexColor.BLACK) ? "B" : "W"); - } - } - - /** Compute the player to move, using the "PL" property if it is - set and the standard behavior otherwise. Never return null. */ - public HexColor getPlayerToMove() - { - String cstr = getSgfProperty("PL"); - if (cstr != null) { - if (cstr.equals("B")) { - return HexColor.BLACK; - } else if (cstr.equals("W")) { - return HexColor.WHITE; - } - } - HexColor color = defaultPlayerToMove(); - if (color != null) { - return color; - } - if (this.hasMove()) { - HexPoint p = m_move.getPoint(); - if (p == HexPoint.RESIGN || p == HexPoint.FORFEIT) { - if (m_parent != null) { - return m_parent.getPlayerToMove(); - } - } - } - return HexColor.BLACK; - } - - //---------------------------------------------------------------------- - // Debugging output - - /** Print information about self */ - public void printDebug() - { - System.out.println("("); - System.out.println("move: " + m_move); - System.out.println("setup: " + m_setup); - System.out.println("label: " + m_label); - System.out.println("property: " + m_property); - System.out.println("recent: " + m_recent); - Node cur = m_child; - while (cur != null) { - cur.printDebug(); - cur = cur.getNext(); - } - System.out.println(")"); - } - - //---------------------------------------------------------------------- - - private TreeMap m_property; - - private Map m_setup; - - private Vector m_label; - - private Move m_move; - private Node m_parent, m_prev, m_next, m_child; - private boolean m_recent; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/AboutDialog.java b/src/hexgui/gui/AboutDialog.java deleted file mode 100644 index 0096c0f..0000000 --- a/src/hexgui/gui/AboutDialog.java +++ /dev/null @@ -1,78 +0,0 @@ -package hexgui.gui; - -import hexgui.version.Version; - -import java.awt.*; -import java.awt.event.*; -import java.net.URL; -import javax.swing.*; -import javax.swing.border.EtchedBorder; -import javax.swing.text.html.HTMLEditorKit; - -//---------------------------------------------------------------------------- - -/** Shows info about HexGui. */ -public class AboutDialog - extends JDialog implements ActionListener -{ - /** Display modal about dialog. */ - public AboutDialog(Frame owner) - { - super(owner, true); - setTitle("About HexGui"); - //setPreferredSize(new Dimension(400,300)); - setResizable(false); - - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - JPanel tp = new JPanel(); - tp.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); - - String benzeneUrl = "http://benzene.sf.net"; - String goguiUrl = "http://gogui.sf.net"; - String about = "" - + "

" - + "

HexGui

" - + "

Version " + Version.id + "

" - + "

Graphical user interface for Hex programs
" - + "(C) 2006-2011 Broderick Arneson.
" - + "" + benzeneUrl + "

" - + "

HexGui is based in large part on
" - + "GoGui by Markus Enzenberger
" - + "" + goguiUrl + "

" - + "

HexGui is full of Hexy Goodness!

" - + ""; - - JEditorPane text = new JEditorPane(); - text.setEditable(false); - text.setEditorKit(new HTMLEditorKit()); - text.setText(about); - - tp.add(text); - - JButton button = new JButton("OK"); - button.setActionCommand("OK"); - button.addActionListener(this); - - panel.add(tp); - panel.add(button); - add(panel); - - pack(); - } - - public void actionPerformed(ActionEvent e) - { - setVisible(false); - } - - private URL getImage(String name) - { - ClassLoader loader = getClass().getClassLoader(); - return loader.getResource("hexgui/images/" + name); - } -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/AnalyzeDialog.java b/src/hexgui/gui/AnalyzeDialog.java deleted file mode 100644 index f144c70..0000000 --- a/src/hexgui/gui/AnalyzeDialog.java +++ /dev/null @@ -1,567 +0,0 @@ -// AnalyzeDialog.java - -package hexgui.gui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Frame; -import java.awt.GridLayout; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; -import java.awt.event.FocusEvent; -import java.awt.event.FocusAdapter; -import java.awt.event.ItemEvent; -import java.awt.event.ItemListener; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.io.File; -import java.text.MessageFormat; -import java.util.ArrayList; -import javax.swing.Box; -import javax.swing.ButtonGroup; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JComponent; -import javax.swing.JDialog; -import javax.swing.JList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.ListSelectionModel; -import javax.swing.event.ListSelectionEvent; -import javax.swing.event.ListSelectionListener; -import hexgui.hex.ConstPointList; -import hexgui.hex.HexColor; -import static hexgui.hex.HexColor.BLACK; -import static hexgui.hex.HexColor.WHITE; -import static hexgui.hex.HexColor.EMPTY; -import hexgui.hex.HexPoint; -import hexgui.hex.PointList; -import hexgui.htp.AnalyzeCommand; -import hexgui.htp.AnalyzeDefinition; -import hexgui.htp.AnalyzeType; -import hexgui.htp.HtpError; -//import hexgui.htp.GtpResponseFormatError; -//import hexgui.htp.GtpUtil; -import hexgui.util.Platform; -import hexgui.util.PrefUtil; - -/** Dialog for selecting an AnalyzeCommand. */ -public final class AnalyzeDialog - extends JDialog - implements ActionListener, ListSelectionListener -{ - /** Callback for actions generated by AnalyzeDialog. */ - public interface Listener - { - void actionClearAnalyzeCommand(); - - void actionSetAnalyzeCommand(AnalyzeCommand command, boolean autoRun, - boolean clearBoard, boolean oneRunOnly, - boolean reuseTextWindow); - } - - public AnalyzeDialog(Frame owner, Listener listener, - ArrayList commands, - MessageDialogs messageDialogs) - { - super(owner, "Analyze"); - m_messageDialogs = messageDialogs; - m_commands = commands; - m_listener = listener; - Container contentPane = getContentPane(); - JPanel commandPanel = createCommandPanel(); - contentPane.add(commandPanel, BorderLayout.CENTER); - comboBoxChanged(); - setSelectedColor(BLACK); - int minWidth = commandPanel.getPreferredSize().width; - setMinimumSize(new Dimension(minWidth, 192)); - pack(); - addWindowListener(new WindowAdapter() { - public void windowActivated(WindowEvent e) { - m_comboBoxHistory.requestFocusInWindow(); - } - }); - } - - public void actionPerformed(ActionEvent event) - { - String command = event.getActionCommand(); - if (command.equals("clear")) - clearCommand(); - else if (command.equals("comboBoxChanged")) - comboBoxChanged(); - else if (command.equals("run")) - runCommand(); - else - assert false; - } - - public void dispose() - { - if (! m_autoRun.isSelected()) - clearCommand(); - saveRecent(); - super.dispose(); - } - - public boolean getReuseTextWindow() - { - return m_reuseWindow.isSelected(); - } - - public HexColor getSelectedColor() - { - if (m_black.isSelected()) - return BLACK; - else - return WHITE; - } - - public void saveRecent() - { - ArrayList recent = new ArrayList(MAX_SAVE_RECENT); - int start = (m_firstIsTemp ? 1 : 0); - for (int i = start; i < getComboBoxItemCount(); ++i) - { - String name = getComboBoxItem(i); - if (recent.indexOf(name) < 0) - recent.add(name); - } - for (int i = 0; i < m_fullRecentList.size(); ++i) - { - if (recent.size() == MAX_SAVE_RECENT) - break; - String name = m_fullRecentList.get(i); - if (recent.indexOf(name) < 0) - recent.add(name); - } - PrefUtil.putList("net/sf/hexgui/gui/analyzedialog/recentcommands", - recent); - } - - /** Set board size. - Need for verifying responses to initial value for EPLIST commands. - Default is 19. */ - public void setBoardSize(int boardSize) - { - m_boardSize = boardSize; - } - - public void setReuseTextWindow(boolean enable) - { - m_reuseWindow.setSelected(enable); - } - - public void setSelectedColor(HexColor color) - { - m_selectedColor = color; - selectColor(); - } - - public void valueChanged(ListSelectionEvent e) - { - int index = m_list.getSelectedIndex(); - if (index >= 0) - selectCommand(index); - } - - private static final int MAX_SAVE_RECENT = 100; - - /** Is the first item in the history combo box a temporary item? - Avoids that the first item in the history combo box is treated - as a real history command, if it was not run. */ - private boolean m_firstIsTemp; - - private int m_boardSize = HexPoint.DEFAULT_SIZE; - - private ArrayList m_fullRecentList; - - private HexColor m_selectedColor = EMPTY; - - //private final MessageDialogs m_messageDialogs; - - private JButton m_clearButton; - - private JButton m_runButton; - - private JCheckBox m_autoRun; - - private JCheckBox m_clearBoard; - - private JCheckBox m_reuseWindow; - - private JComboBox m_comboBoxHistory; - - private JList m_list; - - private Box m_colorBox; - - private JRadioButton m_black; - - private JRadioButton m_white; - - private final ArrayList m_commands; - - private final MessageDialogs m_messageDialogs; - - private final Listener m_listener; - - private String m_lastUpdateOptionsCommand; - - private void clearCommand() - { - m_listener.actionClearAnalyzeCommand(); - m_autoRun.setSelected(false); - } - - private void comboBoxChanged() - { - Object item = m_comboBoxHistory.getSelectedItem(); - if (item == null) - { - m_list.clearSelection(); - return; - } - String label = item.toString(); - updateOptions(label); - String selectedValue = (String)m_list.getSelectedValue(); - if (selectedValue != null && ! selectedValue.equals(label)) - m_list.clearSelection(); - } - - private JPanel createButtons() - { - JPanel innerPanel = new JPanel(new GridLayout(1, 0, 5, 0)); - m_runButton = new JButton("Run"); - m_runButton.setToolTipText("Run Command"); - m_runButton.setActionCommand("run"); - m_runButton.addActionListener(this); - m_runButton.setMnemonic(KeyEvent.VK_R); - m_runButton.setEnabled(false); - GuiUtil.setMacBevelButton(m_runButton); - innerPanel.add(m_runButton); - m_clearButton = new JButton("Clear"); - m_clearButton.setToolTipText(("Clear command")); - m_clearButton.setActionCommand("clear"); - m_clearButton.addActionListener(this); - m_clearButton.setMnemonic(KeyEvent.VK_C); - GuiUtil.setMacBevelButton(m_clearButton); - innerPanel.add(m_clearButton); - JPanel outerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - outerPanel.add(innerPanel); - return outerPanel; - } - - private JComponent createColorPanel() - { - m_colorBox = Box.createVerticalBox(); - ButtonGroup group = new ButtonGroup(); - m_black = new JRadioButton("Black"); - m_black.setToolTipText("Black"); - m_black.setEnabled(false); - group.add(m_black); - m_colorBox.add(m_black); - m_white = new JRadioButton("White"); - m_white.setToolTipText("White"); - m_white.setEnabled(false); - group.add(m_white); - m_colorBox.add(m_white); - return m_colorBox; - } - - private JPanel createCommandPanel() - { - JPanel panel = new JPanel(new BorderLayout()); - m_list = new JList(); - m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - m_list.setVisibleRowCount(25); - m_list.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - int modifiers = e.getModifiers(); - int mask = ActionEvent.ALT_MASK; - if (e.getClickCount() == 2 - || ((modifiers & mask) != 0)) - { - //int index = - // m_list.locationToIndex(event.getPoint()); - runCommand(); - } - } - }); - m_list.addFocusListener(new FocusAdapter() { - public void focusGained(FocusEvent e) { - int index = getSelectedCommand(); - if (index >= 0) - m_list.setSelectedIndex(index); - } - }); - m_list.addListSelectionListener(this); - JScrollPane scrollPane = new JScrollPane(m_list); - if (Platform.isMac()) - // Default Apple L&F uses no border, but Quaqua 3.7.4 does - scrollPane.setBorder(null); - panel.add(scrollPane, BorderLayout.CENTER); - panel.add(createLowerPanel(), BorderLayout.SOUTH); - String[] labels = new String[m_commands.size()]; - for (int i = 0; i < m_commands.size(); ++i) - labels[i] = m_commands.get(i).getLabel(); - m_list.setListData(labels); - comboBoxChanged(); - loadRecent(); - return panel; - } - - private JComponent createLowerPanel() - { - Box panel = Box.createVerticalBox(); - panel.add(GuiUtil.createFiller()); - m_comboBoxHistory = new JComboBox(); - panel.add(m_comboBoxHistory); - Box lowerPanel = Box.createVerticalBox(); - lowerPanel.setBorder(GuiUtil.createEmptyBorder()); - panel.add(lowerPanel); - Box optionsPanel = Box.createHorizontalBox(); - lowerPanel.add(optionsPanel); - JPanel leftPanel = new JPanel(); - optionsPanel.add(leftPanel); - Box leftBox = Box.createVerticalBox(); - leftPanel.add(leftBox); - m_autoRun = new JCheckBox("Autorun"); - m_autoRun.addItemListener(new ItemListener() { - public void itemStateChanged(ItemEvent e) { - if (! m_autoRun.isSelected()) - m_listener.actionClearAnalyzeCommand(); - } - }); - m_autoRun.setToolTipText("Autorun"); - m_autoRun.setEnabled(false); - leftBox.add(m_autoRun); - m_clearBoard = new JCheckBox("Clearboard"); - m_clearBoard.setToolTipText("Clearboard"); - m_clearBoard.setEnabled(false); - leftBox.add(m_clearBoard); - m_clearBoard.setSelected(true); - m_reuseWindow = new JCheckBox("Reuse Text"); - m_reuseWindow.setToolTipText("Reuse text window"); - leftBox.add(m_reuseWindow); - JPanel rightPanel = new JPanel(); - rightPanel.add(createColorPanel()); - optionsPanel.add(rightPanel); - - // TODO: The following horizontal glue does not really work as expected - // (tested on Linux/Sun Java 1.6.0_14) and the left two components in - // the box are not aligned to the left. - optionsPanel.add(Box.createHorizontalGlue()); - - // TODO: If GTK Looks L&F is used on Linux/Sun Java 1.6.0_14 or OpenJDK - // 6b14-1.4.1-0ubuntu11, then the text of the checkbox items can be - // truncated a bit on the left (wrong minimum size calculation?). The - // two fillers are a workaround for this. - optionsPanel.add(GuiUtil.createFiller()); - optionsPanel.add(GuiUtil.createFiller()); - - lowerPanel.add(createButtons()); - m_comboBoxHistory.addActionListener(this); - return panel; - } - - private String getComboBoxItem(int i) - { - return m_comboBoxHistory.getItemAt(i).toString(); - } - - private int getComboBoxItemCount() - { - return m_comboBoxHistory.getItemCount(); - } - - private int getCommandIndex(String label) - { - for (int i = 0; i < m_commands.size(); ++i) - if (m_commands.get(i).getLabel().equals(label)) - return i; - return -1; - } - - private int getSelectedCommand() - { - Object item = m_comboBoxHistory.getSelectedItem(); - if (item == null) - return -1; - return getCommandIndex(item.toString()); - } - - private void insertComboBoxItem(String label, int index) - { - m_comboBoxHistory.insertItemAt(GuiUtil.createComboBoxItem(label), - index); - } - - private void loadRecent() - { - m_comboBoxHistory.removeAllItems(); - m_fullRecentList = - PrefUtil.getList("net/sf/hexgui/gui/analyzedialog/recentcommands"); - for (int i = 0; i < m_fullRecentList.size(); ++i) - { - String name = m_fullRecentList.get(i); - if (getCommandIndex(name) >= 0) - m_comboBoxHistory.addItem(GuiUtil.createComboBoxItem(name)); - if (m_comboBoxHistory.getItemCount() > 20) - break; - } - int index = getSelectedCommand(); - if (index >= 0) - selectCommand(index); - m_firstIsTemp = false; - } - - private void runCommand() - { - // if (m_gtp.isCommandInProgress()) - // { - // showError("MSG_ANALYZE_CANNOT_EXECUTE", - // "MSG_ANALYZE_CANNOT_EXECUTE_2", - // false); - // return; - // } - int index = getSelectedCommand(); - if (index < 0) - { - // String name = m_gtp.getName(); - // if (name == null) - // showError("MSG_ANALYZE_NOT_SUPPORTED", - // "MSG_ANALYZE_NOT_SUPPORTED_2", false); - // else - // showError("MSG_ANALYZE_NOT_SUPPORTED", - // "MSG_ANALYZE_NOT_SUPPORTED_3", false, name); - return; - } - updateRecent(index); - AnalyzeCommand command = new AnalyzeCommand(m_commands.get(index)); - if (command.needsColorArg()) - command.setColorArg(getSelectedColor()); - String label = command.getResultTitle(); - if (command.needsStringArg()) - { - String stringArg = - JOptionPane.showInputDialog(this, label, - "TIT_INPUT", - JOptionPane.PLAIN_MESSAGE); - if (stringArg == null) - return; - command.setStringArg(stringArg); - } - if (command.needsOptStringArg()) - { - } - if (command.getType() == AnalyzeType.EPLIST) - { - } - if (command.needsFileArg()) - { - File fileArg = FileDialogs.showSelectFile(this, label); - if (fileArg == null) - return; - command.setFileArg(fileArg); - } - if (command.needsFileOpenArg()) - { - File fileArg = FileDialogs.showOpen(this, label); - if (fileArg == null) - return; - command.setFileOpenArg(fileArg); - } - if (command.needsFileSaveArg()) - { - File fileArg = FileDialogs.showSave(this, label, m_messageDialogs); - if (fileArg == null) - return; - command.setFileSaveArg(fileArg); - } - if (command.needsColorArg()) - command.setColorArg(getSelectedColor()); - boolean autoRun = m_autoRun.isEnabled() && m_autoRun.isSelected(); - boolean clearBoard = - ! m_clearBoard.isEnabled() || m_clearBoard.isSelected(); - boolean reuseWindow = - m_reuseWindow.isEnabled() && m_reuseWindow.isSelected(); - m_listener.actionSetAnalyzeCommand(command, autoRun, clearBoard, - false, reuseWindow); - } - - private void selectCommand(int index) - { - String label = m_commands.get(index).getLabel(); - updateOptions(label); - m_comboBoxHistory.removeActionListener(this); - if (m_firstIsTemp && getComboBoxItemCount() > 0) - m_comboBoxHistory.removeItemAt(0); - if (getComboBoxItemCount() == 0 || ! getComboBoxItem(0).equals(label)) - { - insertComboBoxItem(label, 0); - m_firstIsTemp = true; - m_comboBoxHistory.setSelectedIndex(0); - } - m_comboBoxHistory.addActionListener(this); - } - - private void selectColor() - { - if (m_selectedColor == BLACK) - m_black.setSelected(true); - else if (m_selectedColor == WHITE) - m_white.setSelected(true); - } - - private void showError(String mainMessage, String optionalMessage, - boolean isCritical) - { - ShowError.msg(this, mainMessage); - } - - private void showError(String mainMessage, String optionalMessage, - boolean isCritical, Object... args) - { - ShowError.msg(this, mainMessage); - } - - private void updateOptions(String label) - { - if (label.equals(m_lastUpdateOptionsCommand)) - return; - m_lastUpdateOptionsCommand = label; - int index = getCommandIndex(label); - if (index < 0) - return; - AnalyzeCommand command = - new AnalyzeCommand(m_commands.get(index)); - boolean needsColorArg = command.needsColorArg(); - m_black.setEnabled(needsColorArg); - m_white.setEnabled(needsColorArg); - m_autoRun.setEnabled(command.getType() != AnalyzeType.PARAM); - m_autoRun.setSelected(false); - m_clearBoard.setEnabled(command.getType() != AnalyzeType.PARAM); - m_runButton.setEnabled(true); - } - - private void updateRecent(int index) - { - String label = m_commands.get(index).getLabel(); - insertComboBoxItem(label, 0); - m_comboBoxHistory.setSelectedIndex(0); - for (int i = 1; i < getComboBoxItemCount(); ++i) - if (getComboBoxItem(i).equals(label)) - m_comboBoxHistory.removeItemAt(i); - m_firstIsTemp = false; - } -} diff --git a/src/hexgui/gui/BoardDrawerBase.java b/src/hexgui/gui/BoardDrawerBase.java deleted file mode 100644 index f40f823..0000000 --- a/src/hexgui/gui/BoardDrawerBase.java +++ /dev/null @@ -1,376 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.util.Pair; -import hexgui.util.RadialGradientPaint; -import hexgui.hex.HexColor; -import hexgui.hex.HexPoint; - -import java.util.Vector; -import javax.swing.*; - -import java.awt.Point; -import java.awt.Polygon; -import java.awt.Color; -import java.awt.Graphics2D; -import java.awt.AlphaComposite; -import java.awt.Image; -import java.awt.FontMetrics; -import java.awt.Font; -import java.awt.RenderingHints; -import java.awt.Graphics; - -import java.awt.event.*; -import java.net.URL; - -//---------------------------------------------------------------------------- - -/** Base class for board drawing. - -

Board drawers are responsible for drawing the background, - labels, field outlines, and stone shadows. In addition, they - are also responsible for determining the actual position of each - field in the window. Field contents (i.e. stones, markers, - numerical values, etc) are not drawn, they are drawn with the - GuiField class. - -

Board sizes supported are m x n where - m and n range from 1 to 26. By - default, black connects top and bottom and should be labeled with - letters. White connects left and right and should be labeled with - numbers. -*/ -public abstract class BoardDrawerBase -{ - public BoardDrawerBase() - { - m_background = null; - m_aspect_ratio = 1.0; - } - - /** Loads the image in filename and sets it as the - background. If filename does not exist no - background image is displayed. Image will be scaled to fit - the window. - @param filename filename of the image to use as a background. - */ - public void loadBackground(String filename) - { - ClassLoader classLoader = getClass().getClassLoader(); - URL url = classLoader.getResource(filename); - if (url == null) { - System.out.println("loadBackground: could not load '" + - filename + "'!"); - m_background = null; - } else { - m_background = new ImageIcon(url).getImage(); - } - } - - /** Gets the field containing the specified point. - NOTE: uses the position of fields from the last call to draw(). - Also assumes the set of fields given are the same as those in the - last call to draw(). - @param p the point - @param field the set of fields to search through. - @return the field in the set that p is in or null if - p is not in any field. - */ - public GuiField getFieldContaining(Point p, GuiField field[]) - { - if (m_outline == null) - return null; - for (int x=0; x> arrows) - { - m_width = w; - m_height = h; - - m_bwidth = bw; - m_bheight = bh; - - m_alphaontop = alphaontop; - - computeFieldPlacement(); - m_outline = calcCellOutlines(field); - - setAntiAliasing(g); - drawBackground(g); - drawCells(g, field); - drawLabels(g, alphaontop); - drawShadows(g, field); - drawFields(g, field); - drawAlpha(g, field); - - drawArrows(g, arrows); - } - - //------------------------------------------------------------ - - protected abstract Point getLocation(HexPoint p); - - /** Calculates the width of a field given the dimensions of the - window and board. - @param w width of window - @param h height of window - @param bw width of board - @param bh height of board - */ - protected abstract int calcFieldWidth(int w, int h, int bw, int bh); - - /** Calculates the height of a field given the dimensions of the - window and board. - @see calcFieldWidth - */ - protected abstract int calcFieldHeight(int w, int h, int bw, int bh); - - - protected abstract int calcStepSize(); - - /** Calculates the width of the board in pixels. - @requires calcFieldWidth and calcFieldHeight to have been called. - */ - protected abstract int calcBoardWidth(); - - /** Calculates the height of the board in pixels. - @requires calcFieldWidth and calcFieldHeight to have been called. - */ - protected abstract int calcBoardHeight(); - - /** Performs any necessary initializations for drawing the - outlines of the fields. - @param the fields it will need to draw - */ - protected abstract Polygon[] calcCellOutlines(GuiField field[]); - - /** Draws the outlines of the given fields. - @param g graphics context to draw to. - @param field the list of fields to draw. - */ - protected void drawCells(Graphics g, GuiField field[]) - { - g.setColor(Color.black); - for (int i=0; i= (int)(m_fieldWidth/m_aspect_ratio)) { - m_fieldHeight = (int)(m_fieldWidth/m_aspect_ratio); - } else { - m_fieldWidth = (int)(m_fieldHeight*m_aspect_ratio); - } - - // If field dimensions are not even then the inner cell lines - // on the board can be doubled up. - // FIXME: lines still get doubled up...why? - if ((m_fieldWidth & 1) != 0) m_fieldWidth--; - if ((m_fieldHeight & 1) != 0) m_fieldHeight--; - - m_fieldRadius = (m_fieldWidth < m_fieldHeight) ? - m_fieldWidth : m_fieldHeight; - - m_step = calcStepSize(); - - int bw = calcBoardWidth(); - int bh = calcBoardHeight(); - - // add a half cell's worth of empty space - int extra = (m_width - (bw + 3*m_fieldWidth)); - m_marginX = extra/2 + 3*m_fieldWidth/2; - - m_marginY = (m_height - bh)/2 + m_fieldHeight/2; - } - - //------------------------------------------------------------ - - protected int getShadowOffset() - { - return (m_fieldRadius - 2*GuiField.getStoneMargin(m_fieldRadius)) / 12; - } - - protected void drawBackground(Graphics g) - { - if (m_background != null) - g.drawImage(m_background, 0, 0, m_width, m_height, null); - } - - protected void drawLabel(Graphics g, Point p, String string, int xoff) - { - double size = Math.min(m_fieldWidth, m_fieldHeight) * 0.4; - Font f = g.getFont(); - Font f2 = f.deriveFont((float)size); - - FontMetrics fm = g.getFontMetrics(f2); - int width = fm.stringWidth(string); - int height = fm.getAscent(); - - g.setFont(f2); - int x = width/2; - int y = height/2; - g.drawString(string, p.x + xoff - x, p.y + y); - g.setFont(f); - } - - protected abstract void drawLabels(Graphics g, boolean alphatop); - - protected void drawShadows(Graphics graphics, GuiField[] field) - { - if (m_fieldRadius <= 5) - return; - Graphics2D graphics2D = - graphics instanceof Graphics2D ? (Graphics2D)graphics : null; - if (graphics2D == null) - return; - graphics2D.setComposite(COMPOSITE_3); - int size = m_fieldRadius - 2 * GuiField.getStoneMargin(m_fieldRadius); - int offset = getShadowOffset(); - for (int pos = 0; pos < field.length; pos++) { - if (field[pos].getColor() == HexColor.EMPTY) - continue; - Point location = getLocation(field[pos].getPoint()); - graphics.setColor(Color.black); - graphics.fillOval(location.x - size / 2 + offset, - location.y - size / 2 + offset, - size, size); - } - graphics.setPaintMode(); - } - - protected void drawFields(Graphics g, GuiField field[]) - { - for (int x=0; x> arrows) - { - if (g instanceof Graphics2D) { - Graphics2D g2d = (Graphics2D)g; - g2d.setColor(Color.BLUE); - for (int i=0; i(x,y). Coordinates increase to the - right and down, with the top left of the board having - coordinates (0,0). Negative values are acceptable. - @param x the x coordinate of the field. - @param y the y coordinate of the field. - @return the center of the field at (x,y). - */ - protected Point getLocation(int x, int y) - { - // yoffset will be positive when bwidth > bheight (to push the - // board down) and negative when bwidth < bheight (to lift it - // up) because the a1 square (0,0) will not be - // in the center of the vertical space occupied by the board. - int yoffset = (m_bwidth - m_bheight)*m_fieldHeight/2; - - Point ret = new Point(); - ret.x = m_marginX + (y + x)*m_step; - ret.y = m_marginY + yoffset + (m_bheight/2)*m_fieldHeight - + (y - x)*m_fieldHeight/2; - return ret; - } - - /** Returns the location of the field with HexPoint pos. */ - protected Point getLocation(HexPoint pos) - { - if (pos == HexPoint.EAST) { - return getLocation(m_bwidth+1, m_bheight/2-1); - } else if (pos == HexPoint.WEST) { - return getLocation(-2, m_bheight/2+1); - } else if (pos == HexPoint.SOUTH) { - return getLocation(m_bwidth/2-1, m_bheight+1); - } else if (pos == HexPoint.NORTH) { - return getLocation(m_bwidth/2+1, -2); - } - return getLocation(pos.x, pos.y); - } - - protected int calcFieldWidth(int w, int h, int bw, int bh) - { - return w / (bw + (bh-1)/2 + 2); - } - - protected int calcFieldHeight(int w, int h, int bw, int bh) - { - return h / (bh + 2); - } - - protected int calcStepSize() - { - return m_fieldWidth/4 + m_fieldWidth/2; - } - protected int calcBoardWidth() - { - return (m_bwidth+m_bheight-1)*m_step; - } - - protected int calcBoardHeight() - { - return m_bheight*m_fieldHeight - + (m_bwidth - m_bheight)*m_fieldHeight/2; - } - - protected Polygon[] calcCellOutlines(GuiField field[]) - { - Polygon outline[] = new Polygon[field.length]; - for (int x = 0; x < outline.length; x++) { - Point p = getLocation(field[x].getPoint()); - outline[x] = Hexagon.createHorizontalHexagon(p, - m_fieldWidth, - m_fieldHeight); -// System.out.println("-----"); -// System.out.println(field[x].getPoint().toString()); -// Polygon poly = outline[x]; -// for (int j=0; j<6; j++) { -// System.out.print("(" + poly.xpoints[j] + -// "," + poly.ypoints[j] + -// ") "); -// } -// System.out.println(""); - } - return outline; - } - - protected void drawLabels(Graphics g, boolean alphatop) - { - int xoffset; - String string; - g.setColor(Color.black); - - xoffset = 0; - for (int x=0; x(x,y). Coordinates increase to the - right and down, with the top left of the board having - coordinates (0,0). Negative values are acceptable. - @param x the x coordinate of the field. - @param y the y coordinate of the field. - @return the center of the field at (x,y). - */ - protected Point getLocation(int x, int y) - { - Point ret = new Point(); - ret.x = m_marginX + y*m_fieldWidth/2 + x*m_fieldWidth; - ret.y = m_marginY + y*m_step; - return ret; - } - - /** Returns the location of the field with HexPoint pos. */ - protected Point getLocation(HexPoint pos) - { - if (pos == HexPoint.EAST) { - return getLocation(m_bwidth+1, m_bheight/2-1); - } else if (pos == HexPoint.WEST) { - return getLocation(-2, m_bheight/2+1); - } else if (pos == HexPoint.SOUTH) { - return getLocation(m_bwidth/2-1, m_bheight+1); - } else if (pos == HexPoint.NORTH) { - return getLocation(m_bwidth/2+1, -2); - } - return getLocation(pos.x, pos.y); - } - - protected int calcFieldWidth(int w, int h, int bw, int bh) - { - return w / (bw + (bh-1)/2 + 2); - } - - protected int calcFieldHeight(int w, int h, int bw, int bh) - { - return h / ((bh+1)/2 + (bh/4) + 4); - } - - protected int calcStepSize() - { - return m_fieldHeight/4 + m_fieldHeight/2; - - } - protected int calcBoardWidth() - { - return m_bwidth*m_fieldWidth + (m_bheight-1)*m_fieldWidth/2; - } - - protected int calcBoardHeight() - { - return m_fieldHeight*(m_bheight+1)/2 - + m_fieldHeight*m_bheight/4; - } - - protected Polygon[] calcCellOutlines(GuiField field[]) - { - Polygon outline[] = new Polygon[field.length]; - for (int x = 0; x < outline.length; x++) { - Point p = getLocation(field[x].getPoint()); - outline[x] = Hexagon.createVerticalHexagon(p, - m_fieldWidth, - m_fieldHeight); - } - return outline; - } - - protected void drawLabels(Graphics g, boolean alphatop) - { - String string; - int xoffset,yoffset; - g.setColor(Color.black); - - xoffset = m_fieldWidth/2; - yoffset = 1; - for (int x=0; x(x,y). Coordinates increase to the - right and down, with the top left of the board having - coordinates (0,0). Negative values are acceptable. - @param x the x coordinate of the field. - @param y the y coordinate of the field. - @return the center of the field at (x,y). - */ - protected Point getLocation(int x, int y) - { - Point ret = new Point(); - ret.x = m_marginX + y*m_fieldWidth/2 + x*m_fieldWidth; - ret.y = m_marginY + (m_bheight-1-y)*m_step; - return ret; - } - - /** Returns the location of the field with HexPoint pos. */ - protected Point getLocation(HexPoint pos) - { - if (pos == HexPoint.EAST) { - return getLocation(m_bwidth+1, m_bheight/2-1); - } else if (pos == HexPoint.WEST) { - return getLocation(-2, m_bheight/2+1); - } else if (pos == HexPoint.SOUTH) { - return getLocation(m_bwidth/2-1, m_bheight+1); - } else if (pos == HexPoint.NORTH) { - return getLocation(m_bwidth/2+1, -2); - } - return getLocation(pos.x, pos.y); - } - - protected int calcFieldWidth(int w, int h, int bw, int bh) - { - return w / (bw + (bh-1)/2 + 2); - } - - protected int calcFieldHeight(int w, int h, int bw, int bh) - { - return h / ((bh+1)/2 + (bh/4) + 4); - } - - protected int calcStepSize() - { - return m_fieldHeight/4 + m_fieldHeight/2; - - } - protected int calcBoardWidth() - { - return m_bwidth*m_fieldWidth + (m_bheight-1)*m_fieldWidth/2; - } - - protected int calcBoardHeight() - { - return m_fieldHeight*(m_bheight+1)/2 - + m_fieldHeight*m_bheight/4; - } - - protected Polygon[] calcCellOutlines(GuiField field[]) - { - Polygon outline[] = new Polygon[field.length]; - for (int x = 0; x < outline.length; x++) { - Point p = getLocation(field[x].getPoint()); - outline[x] = Hexagon.createVerticalHexagon(p, - m_fieldWidth, - m_fieldHeight); - } - return outline; - } - - protected void drawLabels(Graphics g, boolean alphatop) - { - String string; - int xoffset,yoffset; - g.setColor(Color.black); - - xoffset = m_fieldWidth/2; - yoffset = 1; - for (int x=0; x(x,y). Coordinates increase to the - right and down, with the top left of the board having - coordinates (0,0). Negative values are acceptable. - @param x the x coordinate of the field. - @param y the y coordinate of the field. - @return the center of the field at (x,y). - */ - protected Point getLocation(int x, int y) - { - Point ret = new Point(); - int center = m_marginX + m_bwidth*m_fieldWidth / 2; - ret.x = center - y*m_fieldWidth/2 + x*m_fieldWidth; - ret.y = m_marginY + y*m_step; - return ret; - } - - /** Returns the location of the field with HexPoint pos. */ - protected Point getLocation(HexPoint pos) - { - if (pos == HexPoint.EAST) { - return getLocation(m_bheight/2+2, m_bheight/2); - } else if (pos == HexPoint.WEST) { - return getLocation(-2, m_bheight/2); - } else if (pos == HexPoint.SOUTH) { - return getLocation(m_bwidth/2+1, m_bheight+1); - } - return getLocation(pos.x, pos.y); - } - - protected int calcFieldWidth(int w, int h, int bw, int bh) - { - return w / (bw + 3); // width + 2 cells for labels + 1 for spacing - } - - protected int calcFieldHeight(int w, int h, int bw, int bh) - { - // each row takes 3/4 of the height of hex - // need 2 extra rows for labels + 1 row of spacing - return h / ( 3*bh/4 + 3); - } - - protected int calcStepSize() - { - return m_fieldHeight/4 + m_fieldHeight/2; - - } - protected int calcBoardWidth() - { - return m_bwidth*m_fieldWidth; - } - - protected int calcBoardHeight() - { - // return m_fieldHeight*(m_bheight+1)/2 - // + m_fieldHeight*m_bheight/4; - return 3*m_fieldHeight/4*(m_bheight+2); - } - - protected Polygon[] calcCellOutlines(GuiField field[]) - { - Polygon outline[] = new Polygon[field.length]; - for (int x = 0; x < outline.length; x++) { - Point p = getLocation(field[x].getPoint()); - outline[x] = Hexagon.createVerticalHexagon(p, - m_fieldWidth, - m_fieldHeight); - } - return outline; - } - - protected void drawLabels(Graphics g, boolean alphatop) - { - String string; - int xoffset,yoffset; - g.setColor(Color.black); - - xoffset = m_fieldWidth/2; - yoffset = 1; - for (int x=0; x programs) - { - super(owner, true); - setTitle(title); - - m_programs = programs; - - // create gui - JEditorPane info = new JEditorPane(); - info.setEditable(false); - info.setEditorKit(new HTMLEditorKit()); - info.setText("Select program from list below."); - add(info, BorderLayout.NORTH); - add(createProgramPanel(), BorderLayout.CENTER); - add(createButtonPanel(), BorderLayout.SOUTH); - - pack(); - - setResizable(false); - } - - public void actionPerformed(ActionEvent e) - { - String cmd = e.getActionCommand(); - if (cmd.equals("OK")) { - - m_program = m_programs.get(m_list.getSelectedIndex()); - - // set the program - setVisible(false); - - } else if (cmd.equals("Cancel")) { - setVisible(false); - } - } - - public Program getProgram() - { - return m_program; - } - - private JPanel createProgramPanel() - { - JPanel panel = new JPanel(); - - m_list = new JList(m_programs); - m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - m_list.setVisibleRowCount(10); - m_list.setSelectedIndex(0); - m_list.setPreferredSize(new Dimension(200, 250)); - - JScrollPane sc = new JScrollPane(m_list); - sc.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - - panel.add(sc); - - return panel; - } - - private JPanel createButtonPanel() - { - JPanel panel = new JPanel(); - - JButton button = new JButton(" OK "); - button.addActionListener(this); - button.setActionCommand("OK"); - panel.add(button); - - button = new JButton("Cancel"); - button.addActionListener(this); - button.setActionCommand("Cancel"); - panel.add(button); - - return panel; - } - - JList m_list; - - Vector m_programs; - Program m_program; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/Comment.java b/src/hexgui/gui/Comment.java deleted file mode 100644 index 3d37449..0000000 --- a/src/hexgui/gui/Comment.java +++ /dev/null @@ -1,73 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import java.io.*; -import java.util.*; -import javax.swing.*; -import javax.swing.event.DocumentListener; -import javax.swing.event.DocumentEvent; -import javax.swing.border.EtchedBorder; -import java.awt.*; -import java.awt.event.*; - -/** Displays comment for current node. */ -public class Comment - extends JScrollPane - implements DocumentListener -{ - - public interface Listener - { - public void commentChanged(String msg); - } - - public Comment(Listener listener) - { - m_listener = listener; - m_textPane = new JTextArea(); - m_textPane.setFont(MONOSPACED_FONT); - m_textPane.setLineWrap(true); - m_textPane.setWrapStyleWord(true); - setViewportView(m_textPane); - setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); - m_textPane.getDocument().addDocumentListener(this); - //setPreferredSize(new Dimension(200, 400)); - } - - public void setText(String text) - { - m_textPane.setText(text); - m_textPane.getCaret().setDot(0); - } - - public void changedUpdate(DocumentEvent e) - { - notifyChanged(); - } - - public void removeUpdate(DocumentEvent e) - { - notifyChanged(); - } - - public void insertUpdate(DocumentEvent e) - { - notifyChanged(); - } - - private void notifyChanged() - { - m_listener.commentChanged(m_textPane.getText()); - } - - JTextArea m_textPane; - - Listener m_listener; - - private static final Font MONOSPACED_FONT = Font.decode("Monospaced"); -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/EditProgramDialog.java b/src/hexgui/gui/EditProgramDialog.java deleted file mode 100644 index 8445969..0000000 --- a/src/hexgui/gui/EditProgramDialog.java +++ /dev/null @@ -1,140 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.util.SpringUtilities; -import hexgui.gui.ShowError; -import hexgui.gui.Program; - -import java.util.Vector; -import javax.swing.*; -import javax.swing.text.html.HTMLEditorKit; -import java.awt.*; -import java.awt.event.*; - -/** Dialog for adding/editing new programs. */ -public final class EditProgramDialog - extends JDialog implements ActionListener -{ - - public EditProgramDialog(Frame owner, Program program, - String title, boolean is_new) - { - super(owner, true); - m_program = program; - - setTitle(title); - init(is_new); - } - - private void init(boolean is_new) - { - JEditorPane info = new JEditorPane(); - info.setEditable(false); - info.setEditorKit(new HTMLEditorKit()); - - if (!is_new) { - info.setText("Edit the program's fields."); - } else { - info.setText("

Enter command for new Hex program

"+ - "

The command can be simply the name of the " + - "executable file, or the name plus any options " + - "you wish to set. The working directory can be left " + - "blank if the program does not need a special " + - "working directory. Enter a simple descriptive name " + - "to refer to this program."); - } - add(info, BorderLayout.NORTH); - add(createProgramPanel(m_program), BorderLayout.CENTER); - add(createButtonPanel(), BorderLayout.SOUTH); - - if (!is_new) { - setPreferredSize(new Dimension(500, 180)); - } else { - setPreferredSize(new Dimension(500, 280)); - } - pack(); - - setResizable(false); - - setVisible(true); - } - - public void actionPerformed(ActionEvent e) - { - String cmd = e.getActionCommand(); - if (cmd.equals("OK")) { - - m_program.m_name = m_name.getText(); - m_program.m_command = m_command.getText(); - m_program.m_working = m_working.getText(); - - dispose(); - - } else if (cmd.equals("Cancel")) { - dispose(); - } - } - - private JPanel createProgramPanel(Program program) - { - JPanel panel = new JPanel(new SpringLayout()); - JLabel l; - - l = new JLabel("Name:", JLabel.TRAILING); - panel.add(l); - - m_name = new JTextField(40); - if (program != null) m_name.setText(program.m_name); - l.setLabelFor(m_name); - panel.add(m_name); - - l = new JLabel("Command:", JLabel.TRAILING); - panel.add(l); - m_command = new JTextField(40); - if (program != null) m_command.setText(program.m_command); - l.setLabelFor(m_command); - panel.add(m_command); - - l = new JLabel("Working Directory:", JLabel.TRAILING); - panel.add(l); - m_working = new JTextField(40); - if (program != null) m_working.setText(program.m_working); - l.setLabelFor(m_working); - panel.add(m_working); - - SpringUtilities.makeCompactGrid(panel, - 3, 2, // rows, cols - 6, 6, // initX, initY - 6, 6); // xPad, yPad - - return panel; - } - - private JPanel createButtonPanel() - { - JPanel panel = new JPanel(); - - JButton button = new JButton(" OK "); - button.addActionListener(this); - button.setActionCommand("OK"); - panel.add(button); - - button = new JButton("Cancel"); - button.addActionListener(this); - button.setActionCommand("Cancel"); - panel.add(button); - - return panel; - } - - JTextField m_name; - JTextField m_command; - JTextField m_working; - - Program m_program; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/FileDialogs.java b/src/hexgui/gui/FileDialogs.java deleted file mode 100644 index f6534e9..0000000 --- a/src/hexgui/gui/FileDialogs.java +++ /dev/null @@ -1,262 +0,0 @@ -// FileDialogs.java - -package hexgui.gui; - -import java.awt.BorderLayout; -import java.awt.Component; -import java.awt.Container; -import java.awt.Dimension; -import java.awt.FileDialog; -import java.awt.Frame; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.MediaTracker; -import java.awt.Toolkit; -import java.awt.event.ActionListener; -import java.awt.event.ActionEvent; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.File; -import java.net.URL; -import java.net.MalformedURLException; -import java.text.MessageFormat; -import java.util.Locale; -import java.util.prefs.Preferences; -import javax.swing.Box; -import javax.swing.BoxLayout; -import javax.swing.JButton; -import javax.swing.JCheckBox; -import javax.swing.JFileChooser; -import javax.swing.JPanel; -import javax.swing.event.ChangeEvent; -import javax.swing.event.ChangeListener; -import hexgui.sgf.GameFileFilter; -import hexgui.util.ErrorMessage; -import hexgui.util.Platform; -import hexgui.util.StringUtils; - -/** File dialogs. */ -public final class FileDialogs -{ - public static File showOpen(Component parent, String title) - { - return showFileChooser(parent, Type.FILE_OPEN, null, false, title); - } - - public static File showOpenSgf(Component parent) - { - return showFileChooser(parent, Type.FILE_OPEN, null, true, null); - } - - public static File showSave(Component parent, String title, - MessageDialogs messageDialogs) - { - return showFileChooserSave(parent, null, false, title, - messageDialogs); - } - - public static File showSaveSgf(Frame parent, - MessageDialogs messageDialogs) - { - return showFileChooserSave(parent, s_lastFile, true, null, - messageDialogs); - } - - /** File selection, unknown whether for load or save. */ - public static File showSelectFile(Component parent, String title) - { - return showFileChooser(parent, Type.FILE_SELECT, s_lastFile, false, - title); - } - - public static void setLastFile(File file) - { - s_lastFile = file; - } - - private enum Type - { - /** Dialog type for opening a file. */ - FILE_OPEN, - - /** Dialog type for saving to a file. */ - FILE_SAVE, - - /** Dialog type for selecting a file. - Use this type, if a file name should be selected, but it is not - known what the file name is used for and if the file already - exists. - @deprecated Not supported by native AWT FileDialog */ - FILE_SELECT - } - - /** Use native AWT-dialogs. - They are used on Mac OS X because JFileChooser looks too different - from the native dialogs (Java 1.5), and on Windows because - JFileChooser is too slow on Windows XP (startup and directory changing - takes up to 10 sec; Java 1.6) */ - private static final boolean NATIVE_DIALOGS = - (Platform.isMac() || Platform.isWindows()); - - private static File s_lastFile; - - /** Make constructor unavailable; class is for namespace only. */ - private FileDialogs() - { - } - - /** Find first parent that is a Frame. - @return null If no such parent. */ - private static Frame findParentFrame(Component component) - { - while (component != null) - if (component instanceof Frame) - return (Frame)component; - else - component = component.getParent(); - return null; - } - - private static File showFileChooser(Component parent, Type type, - File lastFile, boolean setSgfFilter, - String title) - { - // Use native dialogs for some platforms. but not for type select - // There is no native dialog for select - if (NATIVE_DIALOGS && type != Type.FILE_SELECT) - { - Frame frame = findParentFrame(parent); - return showFileChooserAWT(frame, type, title); - } - return showFileChooserSwing(parent, type, lastFile, setSgfFilter, - title); - } - - private static File showFileChooserSave(Component parent, - File lastFile, - boolean setSgfFilter, - String title, - MessageDialogs messageDialogs) - { - File file = showFileChooser(parent, Type.FILE_SAVE, lastFile, - setSgfFilter, title); - if (NATIVE_DIALOGS) - // Overwrite warning is already part of FileDialog - return file; - while (file != null) - { - if (file.exists()) - { - String mainMessage = - MessageFormat.format("Replace file \"{0}\"", - file.getName()); - String optionalMessage = "If you overwrite the file, the previous version will be lost."; - if (! messageDialogs.showQuestion(parent, mainMessage, - optionalMessage, - "Replace", true)) - { - file = showFileChooser(parent, Type.FILE_SAVE, lastFile, - setSgfFilter, title); - continue; - } - } - break; - } - return file; - } - - private static File showFileChooserAWT(Frame parent, Type type, - String title) - { - FileDialog dialog = new FileDialog(parent); - if (title == null) - { - switch (type) - { - case FILE_OPEN: - title = "Open"; - break; - case FILE_SAVE: - title = "Save"; - break; - default: - assert false; - } - } - dialog.setTitle(title); - int mode = FileDialog.LOAD; - if (type == Type.FILE_SAVE) - mode = FileDialog.SAVE; - dialog.setMode(mode); - /* Commented out, because there is no way to change the filter by the - user (at least not on Linux) - if (setSgfFilter) - dialog.setFilenameFilter(new FilenameFilter() { - public boolean accept(File dir, String name) - { - return name.toLowerCase().endsWith("sgf"); - } - }); - */ - //dialog.setLocationRelativeTo(parent); // Java <= 1.4 - dialog.setLocationByPlatform(true); - dialog.setVisible(true); - if (dialog.getFile() == null) - return null; - return new File(dialog.getDirectory(), dialog.getFile()); - } - - private static File showFileChooserSwing(Component parent, Type type, - File lastFile, - boolean setSgfFilter, - String title) - { - JFileChooser chooser; - if (s_lastFile == null) - { - if (Platform.isMac()) - // user.dir is application directory on Mac, which is bad - // I have not found a way to set it to user home in Info.plist - // so I use null here, which sets is to the user home - chooser = new JFileChooser((String)null); - else - chooser = new JFileChooser(System.getProperty("user.dir")); - } - else - chooser = new JFileChooser(s_lastFile); - chooser.setMultiSelectionEnabled(false); - javax.swing.filechooser.FileFilter filter = new GameFileFilter(); - chooser.addChoosableFileFilter(filter); - if (setSgfFilter) - { - chooser.setFileFilter(filter); - } - else - chooser.setFileFilter(chooser.getAcceptAllFileFilter()); - if (type == Type.FILE_SAVE) - { - if (lastFile != null && lastFile.isFile() && lastFile.exists()) - chooser.setSelectedFile(lastFile); - } - if (title != null) - chooser.setDialogTitle(title); - int ret; - switch (type) - { - case FILE_SAVE: - ret = chooser.showSaveDialog(parent); - break; - case FILE_OPEN: - ret = chooser.showOpenDialog(parent); - break; - default: - ret = chooser.showDialog(parent, "Select"); - break; - } - if (ret != JFileChooser.APPROVE_OPTION) - return null; - File file = chooser.getSelectedFile(); - s_lastFile = file; - return file; - } -} diff --git a/src/hexgui/gui/GameInfoPanel.java b/src/hexgui/gui/GameInfoPanel.java deleted file mode 100644 index 994bfdc..0000000 --- a/src/hexgui/gui/GameInfoPanel.java +++ /dev/null @@ -1,120 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.hex.HexColor; -import hexgui.game.Clock; - -import java.io.*; -import java.util.*; -import javax.swing.*; -import javax.swing.event.DocumentListener; -import javax.swing.border.EtchedBorder; -import java.awt.*; -import java.awt.event.*; -import java.net.URL; - -/** Displays info about the current game. */ -public class GameInfoPanel - extends JPanel -{ - public GameInfoPanel(Clock blackClock, Clock whiteClock) - { - JPanel panel = new JPanel(); - add(panel, BorderLayout.CENTER); - - JPanel bpanel = new JPanel(); - bpanel.setLayout(new BoxLayout(bpanel, BoxLayout.Y_AXIS)); - URL bURL = getURL("hexgui/images/black-24x24.png"); - JLabel blab = new JLabel(new ImageIcon(bURL)); - blab.setAlignmentX(Component.CENTER_ALIGNMENT); - bpanel.add(blab); - bpanel.add(new GuiClock(HexColor.BLACK, blackClock)); - - JPanel wpanel = new JPanel(); - wpanel.setLayout(new BoxLayout(wpanel, BoxLayout.Y_AXIS)); - URL wURL = getURL("hexgui/images/white-24x24.png"); - JLabel wlab = new JLabel(new ImageIcon(wURL)); - wlab.setAlignmentX(Component.CENTER_ALIGNMENT); - wpanel.add(wlab); - wpanel.add(new GuiClock(HexColor.WHITE, whiteClock)); - - panel.add(bpanel); - panel.add(wpanel); - - //setPreferredSize(new Dimension(200, 150)); - } - - private URL getURL(String filename) - { - URL url = null; - if (filename != null) { - ClassLoader classLoader = getClass().getClassLoader(); - url = classLoader.getResource(filename); - } - return url; - } - -}; - -class GuiClock - extends JTextField - implements Clock.Listener -{ - public GuiClock(HexColor color, Clock clock) - { - super(COLUMNS); - - m_clock = clock; - m_clock.addListener(this); - - //Monspace font doesn't center correctly on the Mac - //GuiUtil.setMonospacedFont(this); - setEditable(false); - setFocusable(false); - setHorizontalAlignment(SwingConstants.CENTER); - //setMinimumSize(getPreferredSize()); - m_color = color; - setText("00:00"); - } - - public final void setText(String text) - { - super.setText(text); - String toolTip; - if (m_color == HexColor.BLACK) - toolTip = "Time for Black"; - else - toolTip = "Time for White"; - if (text.length() > COLUMNS) - toolTip = toolTip + " (" + text + ")"; - setToolTipText(toolTip); - } - - public void clockChanged() - { - int elapsed = m_clock.elapsed(); - int minutes = elapsed / 60000; - int seconds = (elapsed % 60000) / 1000; - String min, sec; - - min = (minutes < 10) ? "0"+minutes : "" + minutes; - sec = (seconds < 10) ? "0"+seconds : "" + seconds; - setText(min+":"+sec); - } - - /** Serial version to suppress compiler warning. - Contains a marker comment for serialver.sf.net - */ - private static final long serialVersionUID = 0L; // SUID - - private static final int COLUMNS = 5; - - private final HexColor m_color; - - private Clock m_clock; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/GuiBoard.java b/src/hexgui/gui/GuiBoard.java deleted file mode 100644 index e549822..0000000 --- a/src/hexgui/gui/GuiBoard.java +++ /dev/null @@ -1,774 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.hex.*; -import hexgui.util.*; -import hexgui.game.Node; - -import java.util.Vector; -import java.util.Map; -import java.util.TreeMap; -import java.math.BigInteger; -import javax.swing.*; -import javax.swing.border.EtchedBorder; -import java.awt.print.Printable; -import java.awt.print.PageFormat; -import java.awt.print.PrinterException; -import java.awt.*; -import java.awt.event.*; -import java.net.URL; - -//---------------------------------------------------------------------------- - -/** Gui Board. */ -public final class GuiBoard - extends JPanel implements Printable -{ - /** Callback for clicks on a field. */ - public interface Listener - { - void panelClicked(); - void fieldClicked(HexPoint point, boolean ctrl, boolean shift); - void fieldDoubleClicked(HexPoint point, boolean ctrl, boolean shift); - } - - private static final boolean DEFAULT_FLIPPED = true; - - public static final int HEXBOARD = 0; - public static final int YBOARD = 1; - - /** Constructor. */ - public GuiBoard(Listener listener, GuiPreferences preferences) - { - m_image = null; - m_listener = listener; - m_preferences = preferences; - m_arrows = new Vector>(); - - initSize(HEXBOARD, - m_preferences.getInt("gui-board-width"), - m_preferences.getInt("gui-board-height")); - - setDrawType(m_preferences.get("gui-board-type")); - - setPreferredSize(new Dimension - (m_preferences.getInt("gui-board-pixel-width"), - m_preferences.getInt("gui-board-pixel-height"))); - - setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); - setLayout(new BoardLayout()); - m_boardPanel = new BoardPanel(); - add(m_boardPanel); - - MouseAdapter mouseAdapter = new MouseAdapter() - { - public void mouseClicked(MouseEvent e) - { - // First inform the parent that we were clicked, to - // handle things like keyboard focus. - m_listener.panelClicked(); - GuiField f = m_drawer.getFieldContaining(e.getPoint(), m_field); - if (f == null) return; - - int modifiers = e.getModifiers(); - boolean ctrl = (modifiers & ActionEvent.CTRL_MASK) != 0; - boolean shift = (modifiers & ActionEvent.SHIFT_MASK) != 0; - if (e.getClickCount() >= 2) - m_listener.fieldDoubleClicked(f.getPoint(), ctrl, shift); - else - m_listener.fieldClicked(f.getPoint(), ctrl, shift); - } - }; - m_boardPanel.addMouseListener(mouseAdapter); - setCursorType("default"); - setVisible(true); - } - - // Set the cursor to one of: "default", "black", "white", - // "black-setup", "white-setup". - public void setCursorType(String name) - { - String path; - - if (name.equals("white")) { - path = "hexgui/images/cursor-white.png"; - } else if (name.equals("black")) { - path = "hexgui/images/cursor-black.png"; - } else if (name.equals("white-setup")) { - path = "hexgui/images/cursor-white-setup.png"; - } else if (name.equals("black-setup")) { - path = "hexgui/images/cursor-black-setup.png"; - } else { - path = null; - } - - if (path == null) { - setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); - return; - } - - ClassLoader classLoader = getClass().getClassLoader(); - URL url = classLoader.getResource(path); - if (url == null) { - System.out.println("setCursorType: could not load '" + - "hexgui/images/cursor-white.png" + "'!"); - return; - } - Image img = new ImageIcon(url).getImage(); - Point hot = new Point(8, 8); - Toolkit t = getToolkit(); - Cursor c = t.createCustomCursor(img, hot, name); - setCursor(c); - } - - /** Sets the type of board drawer to use. If name is - not one of the known values, "Diamond" is used. - @param name one of ("Diamond", "Flat", "Flat2", "Go"). - */ - public void setDrawType(String name) - { - if (name.equals("Y")) { - m_drawer = new BoardDrawerY(); - initSize(YBOARD, m_width, m_height); - } else if (name.equals("Go")) { - if (m_mode != HEXBOARD) - initSize(HEXBOARD, m_width, m_height); - m_drawer = new BoardDrawerGo(); - m_preferences.put("gui-board-type", "Go"); - } else if (name.equals("Diamond")) { - if (m_mode != HEXBOARD) - initSize(HEXBOARD, m_width, m_height); - m_drawer = new BoardDrawerDiamond(); - m_preferences.put("gui-board-type", "Diamond"); - } else if (name.equals("Flat")) { - if (m_mode != HEXBOARD) - initSize(HEXBOARD, m_width, m_height); - m_drawer = new BoardDrawerFlat(); - m_preferences.put("gui-board-type", "Flat"); - } else if (name.equals("Flat2")) { - if (m_mode != HEXBOARD) - initSize(HEXBOARD, m_width, m_height); - m_drawer = new BoardDrawerFlat2(); - m_preferences.put("gui-board-type", "Flat2"); - } else { - System.out.println("GuiBoard: unknown draw type '" + name + "'."); - m_drawer = new BoardDrawerDiamond(); - } - repaint(); - } - - /** Sets whether black and letters is on top or if white and - numbers is on top. If string is invalid defaults to positive. - @param orient either "Positive" or "Negative". - */ - public void setOrientation(String orient) - { - if (orient.equals("Positive")) - m_preferences.put("gui-board-orientation", "positive"); - else if (orient.equals("Negative")) - m_preferences.put("gui-board-orientation", "negative"); - else { - System.out.println("GuiBoard: unknown orientation '" + - orient + "'."); - } - repaint(); - } - - public void initSize(int w, int h) - { - initSize(m_mode, w, h); - } - - /** Creates a board of the given dimensions. - Dirty flag is set to false. - @param m type of board to create (HEX or Y) - @param w width of the board in cells - @param h height of the board in cells - */ - private void initSize(int m, int w, int h) - { - System.out.println("GuiBoard.initSize: " - + (m == HEXBOARD ? "(HEX) " : "(Y) ") - + w + " " + h); - - m_mode = m; - m_width = w; - m_height = h; - m_size = new Dimension(m_width, m_height); - - m_dirty_stones = false; - clearArrows(); - - if (m_mode == HEXBOARD) - { - m_field = new GuiField[w*h+4]; - for (int x=0; x(from, to)); - repaint(); - } - - public void clearArrows() - { - m_arrows.clear(); - repaint(); - } - - /** Clears dynamic marks, leaving stones intact. If the dirty flag is set, - revert the fields to the saved fields saved in markStonesDirty(). - Dirty stones flag is set to false. See aboutToDirtyStones(). - Empties the list of arrows. - */ - public void clearMarks() - { - if (m_dirty_stones) { - for (int i=0; ipoint - */ - public HexColor getColor(HexPoint point) - { - GuiField f = getField(point); - return f.getColor(); - } - - /** Gets the field at the specified point. - Special points are ignored (SWAP_SIDES, etc). - */ - public GuiField getField(HexPoint point) - { - if (point == HexPoint.SWAP_SIDES - || point == HexPoint.SWAP_PIECES - || point == HexPoint.PASS - || point == HexPoint.RESIGN - || point == HexPoint.FORFEIT) { - return null; - } - - for (int x=0; xpoint is null. */ - - public void markLastPlayed(HexPoint point) - { - assert(point != HexPoint.SWAP_SIDES && point != HexPoint.SWAP_PIECES); - - if (m_last_played != null) { - m_last_played.clearAttributes(GuiField.LAST_PLAYED); - m_last_played = null; - } - if (point != null) { - m_last_played = getField(point); - if (m_last_played != null) { - m_last_played.setAttributes(GuiField.LAST_PLAYED); - } - } - repaint(); - } - - /** Clear swap marks */ - public void clearSwapPlayed() - { - for (int x=0; x colors = new TreeMap(); - for (int x=0; x carrier = vc.getCarrier(); - for (int i=0; i stones = vc.getStones(); - for (int i=0; i key = vc.getKey(); - for (int i=0; i= 1) - { - return Printable.NO_SUCH_PAGE; - } - double width = getWidth(); - double height = getHeight(); - double pageWidth = format.getImageableWidth(); - double pageHeight = format.getImageableHeight(); - double scale = 1; - if (width >= pageWidth) - scale = pageWidth / width; - double xSpace = (pageWidth - width * scale) / 2; - double ySpace = (pageHeight - height * scale) / 2; - Graphics2D g2d = (Graphics2D)g; - g2d.translate(format.getImageableX() + xSpace, - format.getImageableY() + ySpace); - g2d.scale(scale, scale); - print(g2d); - return Printable.PAGE_EXISTS; - } - - //------------------------------------------------------------ - - /** Converts a hex string representing a bitset into a vector of - HexPoints. This relies on m_field being ordered in a particular - fashion. - - NOTE: THIS IS BROKEN SINCE HexPoint was changed in wolve, r182. - USE BASE 64 INSTEAD! - - FIXME: switch carriers to be printed as a list of HexPoints instead of - as hex strings? - */ - private Vector convertHexString(String str) - { - Vector ret = new Vector(); - - for (int i=0; i convertBase64String(String str) - { - - Vector ret = new Vector(); - for (int i=0; i> arrows = m_arrows; - - boolean positive = true; - if (m_preferences.get("gui-board-orientation").equals("negative")) { - positive = false; - } - boolean flip; - if (m_preferences.get("gui-board-type").equals("Flat2")) { - flip = positive; - } else { - flip = !positive; - } - - if (flip) { - bw = m_height; - bh = m_width; - alphaontop = false; - ff = flipFields(m_field); - - arrows = new Vector>(); - for (int i=0; i - (HexPoint.get(p1.y, p1.x), - HexPoint.get(p2.y, p2.x))); - } - } - - m_drawer.draw(m_image.getGraphics(), - w, h, bw, bh, alphaontop, - ff, arrows); - graphics.drawImage(m_image, 0, 0, null); - } - - public void setBounds(int x, int y, int w, int h) - { - super.setBounds(x, y, w, h); - m_image = null; - } - } - - public void mousePressed(MouseEvent e) {} - public void mouseReleased(MouseEvent e) {} - public void mouseEntered(MouseEvent e) {} - public void mouseExited(MouseEvent e) {} - - private int m_width, m_height; - private Dimension m_size; - private int m_mode; - - private Image m_image; - private GuiField m_field[]; - private Vector> m_arrows; - - private boolean m_dirty_stones; - private GuiField m_backup_field[]; - - private GuiField m_last_played; - - private BoardDrawerBase m_drawer; - private BoardPanel m_boardPanel; - - private Listener m_listener; - private GuiPreferences m_preferences; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/GuiField.java b/src/hexgui/gui/GuiField.java deleted file mode 100644 index f673994..0000000 --- a/src/hexgui/gui/GuiField.java +++ /dev/null @@ -1,327 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import javax.swing.*; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.AlphaComposite; -import java.awt.FontMetrics; -import java.awt.Font; - -import java.awt.event.*; -import java.awt.geom.*; - -import hexgui.hex.*; -import hexgui.util.*; - -//---------------------------------------------------------------------------- - -public class GuiField -{ - public static final int DRAW_CELL_OUTLINE = 1; - public static final int LAST_PLAYED = 2; - public static final int SWAP_PLAYED = 4; - public static final int DRAW_TEXT = 8; - public static final int DRAW_ALPHA = 16; - public static final int SELECTED = 32; - - private static final Color COLOR_STONE_BLACK = Color.decode("#030303"); - private static final Color COLOR_STONE_BLACK_BRIGHT = Color.decode("#666666"); - private static final Color COLOR_STONE_WHITE = Color.decode("#d7d0c9"); - private static final Color COLOR_STONE_WHITE_BRIGHT = Color.decode("#ffffff"); - - - public GuiField(HexPoint p) - { - this(p, HexColor.EMPTY, 0, null, null, 0); - } - - public GuiField(HexPoint p, HexColor c, int attributes, - String text, Color alpha, float blend) - { - m_point = p; - m_color = c; - m_text = text; - m_alpha_color = alpha; - m_attributes = attributes; - m_alpha_blend = blend; - } - - /** Creates a copy of the given field. */ - public GuiField(GuiField f) - { - this(f.getPoint(), f.getColor(), f.getAttributes(), - f.getText(), f.getAlphaColor(), f.getAlphaBlend()); - } - - public static int getStoneMargin(int width) - { - return width / 17 + 1; - } - - public void clearAttributes() - { - m_attributes = 0; - } - - public void clearAttributes(int f) - { - m_attributes &= ~f; - } - - public void setAttributes(int f) - { - m_attributes |= f; - } - - public int getAttributes() - { - return m_attributes; - } - - public void setColor(HexColor c) - { - m_color = c; - } - - public HexColor getColor() - { - return m_color; - } - - public void setText(String str) - { - m_text = str; - if (str == null) - clearAttributes(DRAW_TEXT); - else - setAttributes(DRAW_TEXT); - } - - public String getText() { - return m_text; - } - - public void setAlphaColor(Color c) - { - m_alpha_color = c; - m_alpha_blend = 0.3f; - if (c == null) - clearAttributes(DRAW_ALPHA); - else - setAttributes(DRAW_ALPHA); - } - - public void setAlphaColor(Color c, float blend) - { - m_alpha_color = c; - m_alpha_blend = blend; - if (c == null) - clearAttributes(DRAW_ALPHA); - else - setAttributes(DRAW_ALPHA); - } - - public Color getAlphaColor() { - return m_alpha_color; - } - - public float getAlphaBlend() { - return m_alpha_blend; - } - - public void setSelected(boolean f) - { - if (f) { - setAttributes(SELECTED); - } else { - clearAttributes(SELECTED); - } - } - - public void setPoint(HexPoint p) { - m_point = p; - } - public HexPoint getPoint() { - return m_point; - } - - public void clear() - { - setColor(HexColor.EMPTY); - } - - private RadialGradientPaint getPaint(int width, - int height, - Color colorNormal, - Color colorBright) - { - RadialGradientPaint paint; - int paintSize; - int size = (width < height) ? width : height; - int radius = Math.max(size / 3, 1); - Point2D.Double centerPoint = - new Point2D.Double(width/2 - size/6, height/2 - size/6); - Point2D.Double radiusPoint = - new Point2D.Double(radius, radius); - paint = new RadialGradientPaint(centerPoint, colorBright, - radiusPoint, colorNormal); - return paint; - } - - public void draw(Graphics g, int x, int y, int w, int h) - { - if (!g.hitClip(x, y, w, h)) - return; - - m_width = w; - m_height = h; - - m_radius = (h < w) ? h/2 : w/2; - m_margin = getStoneMargin(m_radius*2); - - m_graphics = g.create(x-w/2,y-h/2,w,h); - if (m_graphics instanceof Graphics2D) { - m_graphics2D = (Graphics2D)m_graphics; - } else { - m_graphics2D = null; - } - - if (m_color == HexColor.WHITE) { - drawStone(COLOR_STONE_WHITE, COLOR_STONE_WHITE_BRIGHT); - } else if (m_color == HexColor.BLACK) { - drawStone(COLOR_STONE_BLACK, COLOR_STONE_BLACK_BRIGHT); - } - - if ((m_attributes & LAST_PLAYED) != 0) { - drawLastPlayed(); - } - - if ((m_attributes & SWAP_PLAYED) != 0) { - drawSwapPlayed(); - } - - // FIXME: this is done in BoardDrawer since we don't know - // anything about our shape and size and we want to cover the - // entire field. Should all drawing be done in board drawer? - // if ((m_attributes & DRAW_ALPHA) != 0) drawAlpha(); - - if ((m_attributes & DRAW_TEXT) != 0) - drawText(); - - } - - private void drawStone(Color normal, Color bright) - { - if (m_graphics2D != null) { - RadialGradientPaint paint = getPaint(m_width, m_height, - normal, bright); - m_graphics2D.setPaint(paint); - } else { - m_graphics.setColor(normal); - } - - int size = m_radius - m_margin; - m_graphics.fillOval(m_width/2 - size, m_height/2 - size, - size*2, size*2); - - m_graphics.setPaintMode(); - } - - private void drawLastPlayed() - { - m_graphics.setColor(Color.gray); - int size = (m_radius - m_margin) / 6; - m_graphics.fillOval(m_width/2 - size, m_height/2 - size, 2*size, 2*size); - } - - /** Draw the given string centered at the coordinates (x,y) in the - current font, with the given relative size. */ - private void drawString(String str, double x, double y, double size) - { - double abssize = (m_radius - m_margin) * size; - Font f = m_graphics.getFont(); - Font f2 = f.deriveFont((float)abssize); - FontMetrics m = m_graphics.getFontMetrics(f2); - double width = m.stringWidth(str); - double height = m.getAscent(); - - m_graphics.setFont(f2); - m_graphics.drawString(str, (int)(x - width/2), (int)(y + 0.8*height/2)); - m_graphics.setFont(f); - } - - private void drawSwapPlayed() - { - if (m_color == HexColor.BLACK) { - m_graphics.setColor(Color.white); - } else { - m_graphics.setColor(Color.black); - } - this.drawString("S", m_width/2.0, m_height/2.0, 1); - } - - private void drawAlpha() - { - if (m_alpha_color == null) - return; - if (m_graphics2D == null) - return; - - m_graphics2D.setComposite(AlphaComposite. - getInstance(AlphaComposite.SRC_OVER, - 0.3f)); - m_graphics.setColor(m_alpha_color); - m_graphics.fillRect(m_width/2 - m_width/4, m_height/2 - m_height/4, - m_width/2, m_height/2); - - } - - private void drawText() - { - String[] lines = m_text.split("@"); - int nlines = lines.length; - - double size = m_radius - m_margin; - double relheight = nlines > 1 ? 2.0/nlines : 1.0; - double height = size * relheight; - - double y = m_height/2 + ((nlines-1)*height)/2; - - for (int i=lines.length-1; i>=0; --i) { - String str = lines[i].trim(); - - Color color = Color.black; - if (getColor() == HexColor.BLACK) - color = Color.white; - - m_graphics.setColor(color); - this.drawString(str, m_width/2, y, relheight); - - y -= height; - } - } - - private HexPoint m_point; - private HexColor m_color; - private int m_attributes; - - private Color m_alpha_color; - private float m_alpha_blend; - - private String m_text; - - private int m_width; - private int m_height; - private int m_radius; - private int m_margin; - - private Graphics m_graphics; - private Graphics2D m_graphics2D; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/GuiMenuBar.java b/src/hexgui/gui/GuiMenuBar.java deleted file mode 100644 index 8b2ff16..0000000 --- a/src/hexgui/gui/GuiMenuBar.java +++ /dev/null @@ -1,633 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.game.Node; - -import java.util.*; -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; - -//---------------------------------------------------------------------------- - -/** Menu bar. */ -public final class GuiMenuBar -{ - public GuiMenuBar(ActionListener listener, GuiPreferences preferences) - { - m_preferences = preferences; - - m_menuBar = new JMenuBar(); - - m_listener = listener; - m_menuBar.add(createFileMenu()); - m_menuBar.add(createProgramMenu()); - m_menuBar.add(createGameMenu()); - m_menuBar.add(createEditMenu()); - m_menuBar.add(createViewMenu()); - m_menuBar.add(createHelpMenu()); - - setProgramConnected(false); - } - - public JMenuBar getJMenuBar() - { - return m_menuBar; - } - - public void setProgramConnected(boolean f) - { - //m_connect_remote.setEnabled(!f); - m_connect_local.setEnabled(!f); - m_disconnect.setEnabled(f); - m_reconnect.setEnabled(f); - m_genmove.setEnabled(f); - - if (f == false) { - setShellVisible(false); - m_shell_visible.setEnabled(false); - setAnalyzeVisible(false); - m_analyze_visible.setEnabled(false); - } else { - m_shell_visible.setEnabled(true); - m_analyze_visible.setEnabled(true); - - setShellVisible(m_preferences. - getBoolean("shell-show-on-connect")); - setAnalyzeVisible(m_preferences. - getBoolean("analyze-show-on-connect")); - } - } - - public void updateMenuStates(HexGui current) - { - m_swap_pieces.setEnabled(current.isSwapAllowed()); - m_swap_sides.setEnabled(current.isSwapAllowed()); - } - - //---------------------------------------------------------------------- - - private JMenu createFileMenu() - { - JMenu menu = new JMenu("File"); - menu.setMnemonic(KeyEvent.VK_F); - - JMenuItem item; - item = new JMenuItem("Open..."); - item.setMnemonic(KeyEvent.VK_O); - item.addActionListener(m_listener); - item.setActionCommand("loadgame"); - menu.add(item); - - menu.addSeparator(); - - item = new JMenuItem("Save Game"); - item.setMnemonic(KeyEvent.VK_S); - item.addActionListener(m_listener); - item.setActionCommand("savegame"); - menu.add(item); - - item = new JMenuItem("Save Game As..."); - item.setMnemonic(KeyEvent.VK_A); - item.addActionListener(m_listener); - item.setActionCommand("savegameas"); - menu.add(item); - - item = new JMenuItem("Save Position As..."); - item.addActionListener(m_listener); - item.setActionCommand("save-position-as"); - menu.add(item); - - menu.addSeparator(); - - item = new JMenuItem("Print Preview"); - item.addActionListener(m_listener); - item.setActionCommand("print-preview"); - menu.add(item); - - item = new JMenuItem("Print..."); - item.addActionListener(m_listener); - item.setActionCommand("print"); - menu.add(item); - - menu.addSeparator(); - - item = new JMenuItem("Exit"); - item.setMnemonic(KeyEvent.VK_X); - item.addActionListener(m_listener); - item.setActionCommand("shutdown"); - menu.add(item); - - return menu; - } - - //---------------------------------------------------------------------- - private JMenu createProgramMenu() - { - JMenu menu = new JMenu("Program"); - menu.setMnemonic(KeyEvent.VK_P); - - JMenuItem item; - - item = new JMenuItem("New Program..."); - item.addActionListener(m_listener); - item.setActionCommand("new-program"); - menu.add(item); - - item = new JMenuItem("Edit Program..."); - item.addActionListener(m_listener); - item.setActionCommand("edit-program"); - menu.add(item); - - item = new JMenuItem("Delete Program..."); - item.addActionListener(m_listener); - item.setActionCommand("delete-program"); - menu.add(item); - - menu.addSeparator(); - - item = new JMenuItem("Connect Local Program..."); - item.addActionListener(m_listener); - item.setActionCommand("connect-local-program"); - m_connect_local = item; - menu.add(item); - - // item = new JMenuItem("Connect Remote Program..."); - // item.addActionListener(m_listener); - // item.setActionCommand("connect-program"); - // item.setEnabled(false); - // m_connect_remote = item; - // menu.add(item); - - menu.addSeparator(); - - item = new JMenuItem("Reconnect Program"); - item.addActionListener(m_listener); - item.setActionCommand("reconnect-program"); - m_reconnect = item; - menu.add(item); - - item = new JMenuItem("Disconnect Program"); - item.addActionListener(m_listener); - item.setActionCommand("disconnect-program"); - m_disconnect = item; - menu.add(item); - - return menu; - } - - //---------------------------------------------------------------------- - - private JMenu createGameMenu() - { - JMenu menu = new JMenu("Game"); - menu.setMnemonic(KeyEvent.VK_G); - - JMenuItem item; - item = new JMenuItem("New"); - item.setMnemonic(KeyEvent.VK_N); - item.addActionListener(m_listener); - item.setActionCommand("newgame"); - menu.add(item); - - JMenu submenu; - - menu.addSeparator(); - - submenu = createClockMenu(); - menu.add(submenu); - - menu.addSeparator(); - - submenu = createToMoveMenu(); - menu.add(submenu); - - menu.addSeparator(); - - m_swap_pieces = new JMenuItem("Swap pieces"); - m_swap_pieces.addActionListener(m_listener); - m_swap_pieces.setActionCommand("game_swap_pieces"); - menu.add(m_swap_pieces); - - m_swap_sides = new JMenuItem("Swap sides"); - m_swap_sides.addActionListener(m_listener); - m_swap_sides.setActionCommand("game_swap_sides"); - menu.add(m_swap_sides); - - m_pass = new JMenuItem("Pass"); - m_pass.addActionListener(m_listener); - m_pass.setActionCommand("game_pass"); - menu.add(m_pass); - - m_resign = new JMenuItem("Resign"); - m_resign.addActionListener(m_listener); - m_resign.setActionCommand("game_resign"); - menu.add(m_resign); - - m_forfeit = new JMenuItem("Forfeit"); - m_forfeit.addActionListener(m_listener); - m_forfeit.setActionCommand("game_forfeit"); - menu.add(m_forfeit); - - m_addsetup = new JMenuItem("Add setup node"); - m_addsetup.addActionListener(m_listener); - m_addsetup.setActionCommand("game_addsetup"); - menu.add(m_addsetup); - - m_genmove = new JMenuItem("Generate Computer Move"); - m_genmove.addActionListener(m_listener); - m_genmove.setActionCommand("genmove"); - menu.add(m_genmove); - - menu.addSeparator(); - - item = new JMenuItem("Delete Current Branch"); - item.addActionListener(m_listener); - item.setActionCommand("game_delete_branch"); - menu.add(item); - - item = new JMenuItem("Make Main Branch"); - item.addActionListener(m_listener); - item.setActionCommand("game_make_main_branch"); - menu.add(item); - - return menu; - } - - private JMenu createClockMenu() - { - JMenu menu = new JMenu("Clock"); - JMenuItem item; - - item = new JMenuItem("Start"); - item.addActionListener(m_listener); - item.setActionCommand("game_start_clock"); - menu.add(item); - - item = new JMenuItem("Stop"); - item.addActionListener(m_listener); - item.setActionCommand("game_stop_clock"); - menu.add(item); - - return menu; - } - - private JMenu createToMoveMenu() - { - JMenu menu = new JMenu("Color To Move"); - - m_colorGroup = new ButtonGroup(); - String pref = m_preferences.get("first-move-color"); - - JRadioButtonMenuItem item; - item = new JRadioButtonMenuItem("black"); - item.addActionListener(m_listener); - item.setActionCommand("set_to_move"); - if (pref.equals("black")) item.setSelected(true); - m_colorGroup.add(item); - menu.add(item); - - item = new JRadioButtonMenuItem("white"); - item.addActionListener(m_listener); - item.setActionCommand("set_to_move"); - if (pref.equals("white")) item.setSelected(true); - m_colorGroup.add(item); - menu.add(item); - - return menu; - } - - public String getToMove() - { - Enumeration e = m_colorGroup.getElements(); - AbstractButton b = (AbstractButton)e.nextElement(); - while (!b.isSelected() && e.hasMoreElements()) { - b = (AbstractButton)e.nextElement(); - } - return b.getText(); - } - - public void setToMove(String color) - { - Enumeration e = m_colorGroup.getElements(); - AbstractButton b = (AbstractButton)e.nextElement(); - while (true) { - if (color.equalsIgnoreCase(b.getText())) { - b.setSelected(true); - } else { - b.setSelected(false); - } - if (!e.hasMoreElements()) - break; - b = (AbstractButton)e.nextElement(); - } - } - - //---------------------------------------------------------------------- - - private JMenu createEditMenu() - { - JMenu menu = new JMenu("Edit"); - menu.setMnemonic(KeyEvent.VK_E); - - JMenu size = createBoardSizeMenu(); - menu.add(size); - - menu.addSeparator(); - - JMenuItem item; - item = new JMenuItem("Preferences..."); - item.addActionListener(m_listener); - item.setActionCommand("show-preferences"); - - menu.add(item); - - return menu; - } - - private JMenu createBoardSizeMenu() - { - JMenu menu = new JMenu("Board Size"); - m_bsGroup = new ButtonGroup(); - - String sizes[] = new String[] - { - "19 x 19", - "15 x 15", - "14 x 14", - "13 x 13", - "11 x 11", - "10 x 10", - "9 x 9", - "8 x 8", - "7 x 7", - "6 x 6", - "5 x 5", - "4 x 4", - "3 x 3" - }; - - String preferred = m_preferences.get("gui-board-width") + " x " - + m_preferences.get("gui-board-height"); - - boolean found = false; - JRadioButtonMenuItem item; - for (int i=0; i"; - else - return - ""; - } - - /** Get size of default monspaced font. - Can be used for setting the initial size of some GUI elements. */ - public static int getDefaultMonoFontSize() - { - return MONOSPACED_FONT.getSize(); - } - - public static ImageIcon getIcon(String icon, String name) - { - String resource = "hexgui/images/" + icon + ".png"; - URL url = GuiUtil.class.getClassLoader().getResource(resource); - return new ImageIcon(url, name); - } - - /** Manually break message into multiple lines for multi-line labels. - Needed for multi-line messages in option panes, because pack() on - JOptionPane does not compute the option pane size correctly, if a - maximum width is set and the label text is automatically broken into - multiple lines. The workaround with calling invalidate() and pack() a - second time does not work either in this case. See also Sun Bug ID - 4545951 (still in Linux JDK 1.5.0_04-b05 or Mac 1.4.2_12) */ - public static String insertLineBreaks(String message) - { - final int MAX_CHAR_PER_LINE = 72; - int length = message.length(); - if (length < MAX_CHAR_PER_LINE) - return message; - StringBuilder buffer = new StringBuilder(); - int startLine = 0; - int lastWhiteSpace = -1; - for (int pos = 0; pos < length; ++pos) - { - char c = message.charAt(pos); - if (pos - startLine > 72) - { - int endLine = - (lastWhiteSpace > startLine ? lastWhiteSpace : pos); - if (buffer.length() > 0) - buffer.append("
"); - buffer.append(message.substring(startLine, endLine)); - startLine = endLine; - } - if (Character.isWhitespace(c)) - lastWhiteSpace = pos; - } - if (buffer.length() > 0) - buffer.append("
"); - buffer.append(message.substring(startLine)); - return buffer.toString(); - } - - /** Call SwingUtilities.invokeAndWait. - Ignores possible exceptions (apart from printing a warning to - System.err */ - public static void invokeAndWait(Runnable runnable) - { - try - { - SwingUtilities.invokeAndWait(runnable); - } - catch (InterruptedException e) - { - System.err.println("Thread interrupted"); - } - catch (java.lang.reflect.InvocationTargetException e) - { - System.err.println("InvocationTargetException"); - } - } - - public static boolean isActiveWindow(Window window) - { - KeyboardFocusManager manager = - KeyboardFocusManager.getCurrentKeyboardFocusManager(); - return (manager.getActiveWindow() == window); - } - - /** Check window for normal state. - Checks if window is not maximized (in either or both directions) and - not iconified. */ - public static boolean isNormalSizeMode(JFrame window) - { - int state = window.getExtendedState(); - int mask = Frame.MAXIMIZED_BOTH | Frame.MAXIMIZED_VERT - | Frame.MAXIMIZED_HORIZ | Frame.ICONIFIED; - return ((state & mask) == 0); - } - - public static void paintImmediately(JComponent component) - { - component.paintImmediately(component.getVisibleRect()); - } - - public static void removeKeyBinding(JComponent component, String key) - { - int condition = JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT; - InputMap inputMap = component.getInputMap(condition); - // According to the docs, null should remove the action, but it does - // not seem to work with Sun Java 1.4.2, new Object() works - inputMap.put(KeyStroke.getKeyStroke(key), new Object()); - } - - /** Set antialias rendering hint if graphics is instance of Graphics2D. */ - public static void setAntiAlias(Graphics graphics) - { - if (graphics instanceof Graphics2D) - { - Graphics2D graphics2D = (Graphics2D)graphics; - graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, - RenderingHints.VALUE_ANTIALIAS_ON); - } - } - - /** Set text field non-editable. - Also sets it non-focusable. */ - public static void setEditableFalse(JTextField field) - { - field.setEditable(false); - field.setFocusable(false); - } - - /** Set Go icon on frame. */ - public static void setGoIcon(Frame frame) - { - URL url = s_iconURL; - if (url != null) - frame.setIconImage(new ImageIcon(url).getImage()); - } - - /** Set property to render button in bevel style on the Mac. - Only has an effect if Quaqua Look and Feel is used. */ - public static void setMacBevelButton(JButton button) - { - button.putClientProperty("Quaqua.Button.style", "bevel"); - } - - public static void setMonospacedFont(JComponent component) - { - if (MONOSPACED_FONT != null) - component.setFont(MONOSPACED_FONT); - } - - public static void addStyle(JTextPane textPane, String name, - Color foreground) - { - addStyle(textPane, name, foreground, null, false); - } - - public static void addStyle(JTextPane textPane, String name, - Color foreground, Color background, - boolean bold) - { - StyledDocument doc = textPane.getStyledDocument(); - StyleContext context = StyleContext.getDefaultStyleContext(); - Style def = context.getStyle(StyleContext.DEFAULT_STYLE); - Style style = doc.addStyle(name, def); - if (foreground != null) - StyleConstants.setForeground(style, foreground); - if (background != null) - StyleConstants.setBackground(style, background); - StyleConstants.setBold(style, bold); - } - - public static void setStyle(JTextPane textPane, int start, int length, - String name) - { - StyledDocument doc = textPane.getStyledDocument(); - Style style; - if (name == null) - { - StyleContext context = StyleContext.getDefaultStyleContext(); - style = context.getStyle(StyleContext.DEFAULT_STYLE); - } - else - style = doc.getStyle(name); - doc.setCharacterAttributes(start, length, style, true); - } - - public static void setUnlimitedSize(JComponent component) - { - Dimension size = new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); - component.setMaximumSize(size); - } - - static - { - ClassLoader loader = ClassLoader.getSystemClassLoader(); - // There are problems on some platforms with transparency (e.g. Linux - // Sun Java 1.5.0). Best solution for now is to take an icon without - // transparency - s_iconURL = - loader.getResource("hexgui/images/hexgui-48x48-notrans.png"); - } - - private static final Font MONOSPACED_FONT = Font.decode("Monospaced"); - - private static final Border EMPTY_BORDER = - BorderFactory.createEmptyBorder(PAD, PAD, PAD, PAD); - - private static final Border SMALL_EMPTY_BORDER = - BorderFactory.createEmptyBorder(SMALL_PAD, SMALL_PAD, - SMALL_PAD, SMALL_PAD); - - private static final Dimension FILLER_DIMENSION = - new Dimension(PAD, PAD); - - private static final Dimension SMALL_FILLER_DIMENSION = - new Dimension(SMALL_PAD, SMALL_PAD); - - private static URL s_iconURL; -} diff --git a/src/hexgui/gui/HexGui.java b/src/hexgui/gui/HexGui.java deleted file mode 100644 index b090f85..0000000 --- a/src/hexgui/gui/HexGui.java +++ /dev/null @@ -1,2706 +0,0 @@ -package hexgui.gui; - -import hexgui.hex.*; -import hexgui.util.Pair; -import hexgui.util.StringUtils; -import hexgui.game.Node; -import hexgui.game.GameInfo; -import hexgui.game.Clock; -import hexgui.sgf.SgfWriter; -import hexgui.sgf.SgfReader; -import hexgui.htp.HtpController; -import hexgui.htp.HtpError; -import hexgui.util.StreamCopy; -import hexgui.version.Version; -import hexgui.gui.ParameterDialog; -import hexgui.htp.AnalyzeDefinition; -import hexgui.htp.AnalyzeCommand; -import hexgui.htp.AnalyzeType; -import hexgui.util.ErrorMessage; -import hexgui.gui.ShowAnalyzeText; - -import java.io.*; -import static java.text.MessageFormat.format; -import java.util.*; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.Semaphore; -import javax.swing.*; -import java.awt.*; -import java.awt.image.BufferedImage; -import java.awt.event.*; -import java.net.*; - -//---------------------------------------------------------------------------- - -/** HexGui. */ -public final class HexGui - extends JFrame - implements ActionListener, GuiBoard.Listener, - HtpShell.Callback, HtpController.GuiFxCallback, - AnalyzeDialog.Listener, Comment.Listener -{ - public HexGui(final File file, final String command) - { - super("HexGui"); - setIcon(); - - System.out.println("HexGui v" + Version.id + "; " + Version.date - + "\n"); - - // Catch the close action and shutdown nicely - setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); - addWindowListener(new java.awt.event.WindowAdapter() - { - public void windowClosing(WindowEvent winEvt) { - cmdShutdown(); - } - }); - - m_selected_cells = new Vector(); - - m_about = new AboutDialog(this); - - m_preferences = new GuiPreferences(getClass()); - - m_menubar = new GuiMenuBar(this, m_preferences); - setJMenuBar(m_menubar.getJMenuBar()); - - m_toolbar = new GuiToolBar(this, m_preferences); - getContentPane().add(m_toolbar.getJToolBar(), BorderLayout.NORTH); - - m_statusbar = new StatusBar(); - getContentPane().add(m_statusbar, BorderLayout.SOUTH); - - m_guiboard = new GuiBoard(this, m_preferences); - getContentPane().add(m_guiboard, BorderLayout.CENTER); - - m_showAnalyzeText = new ShowAnalyzeText(this, m_guiboard); - - JPanel panel = new JPanel(new BorderLayout()); - getContentPane().add(panel, BorderLayout.EAST); - - m_blackClock = new Clock(); - m_whiteClock = new Clock(); - m_gameinfopanel = new GameInfoPanel(m_blackClock, m_whiteClock); - m_comment = new Comment(this); - panel.add(m_gameinfopanel, BorderLayout.NORTH); - panel.add(m_comment, BorderLayout.CENTER); - - cmdNewGame(); - - pack(); - - m_locked = false; - - m_semaphore = new Semaphore(1); - m_htp_queue = new ArrayBlockingQueue(256); - new Thread(new CommandHandler(this, m_htp_queue)).start(); - - setVisible(true); - // After frame is visible, further code using Swing functions must - // be run in the Swing event dispatch thread. - SwingUtilities.invokeLater(new Runnable() { - public void run() { - initialize(file, command); - } }); - setCursorType(); - } - - //------------------------------------------------------------------- - - public void actionPerformed(ActionEvent e) - { - String cmd = e.getActionCommand(); - - unFocus(); - - // - // system commands - // - if (cmd.equals("shutdown")) { - cmdShutdown(); - } else if (cmd.equals("new-program")) { - cmdNewProgram(); - } else if (cmd.equals("edit-program")) { - cmdEditProgram(); - } else if (cmd.equals("delete-program")) { - cmdDeleteProgram(); - } else if (cmd.equals("connect-program")) { - cmdConnectRemoteProgram(); - } else if (cmd.equals("connect-local-program")) { - cmdConnectLocalProgram(); - } else if (cmd.equals("disconnect-program")) { - cmdDisconnectProgram(); - } else if (cmd.equals("reconnect-program")) { - cmdReconnectProgram(); - // - // file/help commands - // - } else if (cmd.equals("newgame")) { - end_setup(); - cmdNewGame(); - } else if (cmd.equals("savegame")) { - // We previously only saved when gameChanged() == true, - // but this is dangerous because saving would fail - // silently otherwise. It's better to simply save the game - // when the user asks for it. An alternative use for - // gameChanged() would be to disable the save button when - // we think the game hasn't changed. But we shouldn't just - // offer a button and then do nothing. - cmdSaveGame(); - } else if (cmd.equals("savegameas")) { - cmdSaveGameAs(); - } else if (cmd.equals("loadgame")) { - cmdLoadGame(); - } else if (cmd.equals("save-position-as")) { - cmdSavePositionAs(); - } else if (cmd.equals("print-preview")) { - cmdPrintPreview(); - } else if (cmd.equals("print")) { - cmdPrint(); - } else if (cmd.equals("about")) { - cmdAbout(); - // - // gui commands - // - } else if (cmd.equals("gui_toolbar_visible")) { - cmdGuiToolbarVisible(); - } else if (cmd.equals("gui_shell_visible")) { - cmdGuiShellVisible(); - } else if (cmd.equals("gui_analyze_visible")) { - cmdGuiAnalyzeVisible(); - } else if (cmd.equals("gui_board_draw_type")) { - cmdGuiBoardDrawType(); - } else if (cmd.equals("gui_board_orientation")) { - cmdGuiBoardOrientation(); - } else if (cmd.equals("show-preferences")) { - cmdShowPreferences(); - } else if (cmd.equals("gui-clear-marks")) { - cmdClearMarks(); - // - // game navigation commands - // - } else if (cmd.equals("game_beginning")) { - end_setup(); - backward(-1); - } else if (cmd.equals("game_backward10")) { - end_setup(); - backward(10); - } else if (cmd.equals("game_back")) { - end_setup(); - backward(1); - } else if (cmd.equals("game_forward")) { - end_setup(); - forward(1); - } else if (cmd.equals("game_forward10")) { - end_setup(); - forward(10); - } else if (cmd.equals("game_end")) { - end_setup(); - forward(-1); - } else if (cmd.equals("game_up")) { - end_setup(); - up(); - } else if (cmd.equals("game_down")) { - end_setup(); - down(); - } else if (cmd.equals("game_swap_sides")) { - end_setup(); - humanMove(new Move(HexPoint.get("swap-sides"), m_tomove)); - } else if (cmd.equals("game_swap_pieces")) { - end_setup(); - humanMove(new Move(HexPoint.get("swap-pieces"), m_tomove)); - } else if (cmd.equals("game_pass")) { - end_setup(); - humanMove(new Move(HexPoint.get("pass"), m_tomove)); - } else if (cmd.equals("game_resign")) { - end_setup(); - humanMove(new Move(HexPoint.get("resign"), m_tomove)); - } else if (cmd.equals("game_forfeit")) { - end_setup(); - humanMove(new Move(HexPoint.get("forfeit"), m_tomove)); - } else if (cmd.equals("game_addsetup")) { - end_setup(); - addSetupNode(); - } else if (cmd.equals("genmove")) { - end_setup(); - htpGenMove(m_tomove); - } else if (cmd.equals("game_delete_branch")) { - end_setup(); - cmdDeleteBranch(); - } else if (cmd.equals("game_make_main_branch")) { - end_setup(); - cmdMoveBranchTop(); - } else if (cmd.equals("game_start_clock")) { - startClock(); - } else if (cmd.equals("game_stop_clock")) { - stopClock(); - } else if (cmd.equals("stop")) { - m_white.interrupt(); - } else if (cmd.equals("toggle_tomove")) { - end_setup(); - cmdToggleToMove(); - } else if (cmd.equals("set_to_move")) { - end_setup(); - cmdSetToMove(); - } else if (cmd.equals("setup-black")) { - cmdSetupBlack(); - } else if (cmd.equals("setup-white")) { - cmdSetupWhite(); - } - // - // other - // - else if (cmd.equals("show_consider_set")) - { - Runnable cb = new Runnable() - { public void run() { cbShowInferiorCells(); } }; - Runnable callback = new GuiRunnable(cb); - sendCommand("vc-build " + m_tomove.toString() + "\n", callback); - } - else if (cmd.equals("solve_state")) - { - sendCommand("param_dfpn use_guifx 1\n", null); - Runnable callback = new GuiRunnable(new Runnable() - { - public void run() { cbSolveState(); } - }); - sendCommand("dfpn-solve-state " + m_tomove + "\n", callback); - } - else if (cmd.equals("program_options")) - { - AnalyzeCommand command; - if (m_white_name.equalsIgnoreCase("Mohex") || m_white_name.equalsIgnoreCase("HexHex")) - { - command = new AnalyzeCommand - (new AnalyzeDefinition("param/blah/param_mohex")); - Runnable cb = new Runnable() - { public void run() { cbEditParameters(); } }; - Runnable callback = new GuiRunnable(cb); - m_curAnalyzeCommand = command; - sendCommand(command.getCommand() + "\n", callback); - } - else if (m_white_name.equalsIgnoreCase("Wolve")) - { - command = new AnalyzeCommand - (new AnalyzeDefinition("param/blah/param_wolve")); - Runnable cb = new Runnable() - { public void run() { cbEditParameters(); } }; - Runnable callback = new GuiRunnable(cb); - m_curAnalyzeCommand = command; - sendCommand(command.getCommand() + "\n", callback); - } - else - ShowError.msg(this, "Unknown program!"); - } - // - // unknown command - // - else - { - System.out.println("Unknown command: '" + cmd + "'."); - } - } - - //------------------------------------------------------------ - /** Return true if keyboard shortcuts should be enabled. This - * should be the case unless the user is currently typing in the - * text area. */ - public boolean shortcutsEnabled() - { - return !m_comment.m_textPane.isFocusOwner(); - } - - //------------------------------------------------------------ - private void cmdShutdown() - { - if (gameChanged() && !askSaveGame()) - return; - - System.out.println("Shutting down..."); - - if (m_white_process != null) - { - System.out.println("Stopping [" + m_white_name + " " + - m_white_version + "] process..."); - m_white_process.destroy(); - } - System.exit(0); - } - - private void cmdNewProgram() - { - Program program = new Program(); - new EditProgramDialog(this, program, "Add New Program", true); - - if (program.m_name == null) // user canceled - return; - - // add the program to the list of programs - m_programs.add(program); - Program.save(m_programs); - } - - private void cmdEditProgram() - { - if (m_programs.isEmpty()) - { - ShowError.msg(this, "No programs, add a program first."); - return; - } - - ChooseProgramDialog dialog - = new ChooseProgramDialog(this, "Choose program to edit", m_programs); - dialog.setVisible(true); - Program program = dialog.getProgram(); - dialog.dispose(); - - if (program == null) - return; - - new EditProgramDialog(this, program, "Edit Program", false); - - Program.save(m_programs); - } - - private void cmdDeleteProgram() - { - if (m_programs.isEmpty()) - { - ShowError.msg(this, "No programs, add a program first."); - return; - } - - ChooseProgramDialog dialog - = new ChooseProgramDialog(this, "Choose program to delete", - m_programs); - dialog.setVisible(true); - Program program = dialog.getProgram(); - dialog.dispose(); - - if (program == null) - return; - - if (!m_programs.remove(program)) - System.out.println("cmdDeleteProgram: program was not in list!"); - - Program.save(m_programs); - } - - private void cmdConnectLocalProgram() - { - ChooseProgramDialog dialog - = new ChooseProgramDialog(this, "Choose program to connect", - m_programs); - dialog.setVisible(true); - Program program = dialog.getProgram(); - dialog.dispose(); - - if (program == null) // user aborted - return; - - cmdConnectLocalProgram(program); - } - - - /** @note NOT CURRENTLY USED! */ - private void cmdConnectRemoteProgram() - { - int port = 20000; - String hostname = "localhost"; - - String remote = m_preferences.get("remote-host-name"); - String name = RemoteProgramDialog.show(this, remote); - if (name == null) // user aborted - return; - - hostname = name; - System.out.print("Connecting to HTP program at [" + hostname + - "] on port " + port + "..."); - System.out.flush(); - - try - { - m_white_socket = new Socket(hostname, port); - } - catch (UnknownHostException e) - { - ShowError.msg(this, "Unknown host: '" + e.getMessage() + "'"); - System.out.println("\nconnection attempt aborted."); - return; - } - catch (IOException e) - { - ShowError.msg(this, "Error creating socket: '" - + e.getMessage() + "'"); - System.out.println("\nconnection attempt aborted."); - return; - } - System.out.println("connected."); - - InputStream in; - OutputStream out; - try - { - in = m_white_socket.getInputStream(); - out = m_white_socket.getOutputStream(); - } - catch (IOException e) - { - ShowError.msg(this, "Error obtaining socket stream: " - + e.getMessage()); - m_white = null; - return; - } - m_preferences.put("remote-host-name", hostname); - connectProgram(in, out); - } - - //------------------------------------------------------------ - - private void cmdConnectLocalProgram(Program program) - { - Runtime runtime = Runtime.getRuntime(); - - String cmd = program.m_command; - System.out.println("Executing '" + program.m_name + "':"); - System.out.println("Command = '" + cmd + "'"); - System.out.println("Working directory = '" + program.m_working + "'"); - - File working = null; - if (!program.m_working.trim().equals("")) - { - ShowError.msg(this, - "Working directory not implemented! " + - "Running with no working directory."); - -// working = new File(program.m_working); -// if (!working.isDirectory()) -// { -// ShowError.msg(this, "Invalid working directory: '" -// + working.getName() + "'"); -// } - } - - try - { - // Create command array with StringUtil::splitArguments - // because Runtime.exec(String) uses a default StringTokenizer - // which does not respect ". - String[] cmdArray = StringUtils.splitArguments(cmd); - // Make file name absolute, if working directory is not current - // directory. With Java 1.5, it seems that Runtime.exec succeeds - // if the relative path is valid from the current, but not from - // the given working directory, but the process is not usable - // (reading from its input stream immediately returns - // end-of-stream) - if (cmdArray.length > 0) - { - File file = new File(cmdArray[0]); - // Only replace if executable is a path to a file, not - // an executable in the exec-path - if (file.exists()) - cmdArray[0] = file.getAbsolutePath(); - } - - m_white_process = runtime.exec(cmdArray); - //m_white_process = runtime.exec(cmdArray, null, workingDirectory); - //m_white_process = runtime.exec(cmd); - } - catch (Throwable e) - { - ShowError.msg(this, "Error starting " + program.m_name + ": '" - + e.getMessage() + "'"); - return; - } - - m_program = program; - m_preferences.put("is-program-attached", true); - m_preferences.put("attached-program", program.m_name); - - Process proc = m_white_process; - - /////////////////////////////// - /// FIXME: DEBUGING!!! REMOVE! - Thread blah = new Thread(new StreamCopy(false, proc.getErrorStream(), - System.out, false)); - blah.start(); - /////////////////////////////// - - connectProgram(proc.getInputStream(), proc.getOutputStream()); - } - - private void createAnalyzeDialog() - { - m_analyzeDialog = new AnalyzeDialog(this, this, m_analyzeCommands, - m_messageDialogs); - m_analyzeDialog.addWindowListener(new WindowAdapter() { - public void windowClosing(WindowEvent e) { - m_analyzeDialog.setVisible(false); - m_menubar.setAnalyzeVisible(false); - } - }); - m_analyzeDialog.setBoardSize(m_guiboard.getBoardSize().width); - m_analyzeDialog.setSelectedColor(m_tomove); - } - - public void actionDisposeAnalyzeDialog() - { - if (m_analyzeDialog != null) - { - m_analyzeDialog.dispose(); - m_analyzeDialog = null; - m_menubar.setAnalyzeVisible(false); - } - } - - private void connectProgram(InputStream in, OutputStream out) - { - m_shell = new HtpShell(this, this); - m_shell.addWindowListener(new WindowAdapter() - { - public void windowClosing(WindowEvent winEvt) - { - m_menubar.setShellVisible(false); - } - }); - m_white = new HtpController(in, out, m_shell, this); - - // get name and version information; block until - // version is returned. - - acquireSemaphore(); - htpName(); - htpVersion(); // releases semaphore when finished. - acquireSemaphore(); - releaseSemaphore(); - - m_shell.setTitle("HexGui: [" + m_white_name + " " - + m_white_version + "] Shell"); - - // get list of accepted commands; block until - // this is completed. - acquireSemaphore(); - htpAnalyzeCommands(); // releases semaphore when finished - acquireSemaphore(); - releaseSemaphore(); - - createAnalyzeDialog(); - - m_toolbar.setProgramConnected(true); - m_menubar.setProgramConnected(true); - - m_shell.setVisible(m_preferences.getBoolean("shell-show-on-connect")); - m_analyzeDialog.setVisible(m_preferences.getBoolean("analyze-show-on-connect")); - - htpBoardsize(m_guiboard.getBoardSize()); - - // Replay all moves up to the current node. - replayUpToNode(m_current); - htpShowboard(); - } - - // Replay all moves up to the given node. Do this without changing - // the current node. - private void replayUpToNode(Node node) - { - Vector path = new Vector(); - while (node != null) { - path.add(node); - node = node.getParent(); - } - m_guiboard.clearAll(); - htpClearBoard(); - for (int i = path.size()-1; i>=0; i--) { - node = path.elementAt(i); - if (node.hasMove()) { - Move move = node.getMove(); - guiPlay(move); - htpPlay(move); - } - if (node.hasSetup()) { - playSetup(node); - } - } - } - - /** Run HTP commands to set up the current board position from - scratch. This may be necessary when a setup move removes a - piece, or changes the color of an existing piece, or when - swap-pieces is played, since there is no valid HTP command to - do so. */ - private void htpSetUpCurrentBoard() - { - htpClearBoard(); - Dimension size = m_guiboard.getBoardSize(); - for (int y = 0; y < size.height; y++) { - for (int x = 0; x < size.width; x++) { - HexPoint point = HexPoint.get(x, y); - HexColor c = m_guiboard.getColor(point); - if (c == HexColor.BLACK || c == HexColor.WHITE) { - htpPlay(new Move(point, c)); - } - } - } - } - - private void cmdDisconnectProgram() - { - if (m_white == null) - return; - - htpQuit(); - try - { - if (m_white_process != null) - { - m_white_process.waitFor(); - m_white_process = null; - } - if (m_white_socket != null) - { - m_white_socket.close(); - m_white_socket = null; - } - m_white = null; - m_shell.dispose(); - m_shell = null; - actionDisposeAnalyzeDialog(); - m_program = null; - m_menubar.setProgramConnected(false); - m_toolbar.setProgramConnected(false); - m_preferences.put("is-program-attached", false); - } - catch (Throwable e) - { - ShowError.msg(this, "Error: " + e.getMessage()); - } - } - - private void cmdReconnectProgram() - { - Program prog = m_program; - cmdDisconnectProgram(); - cmdConnectLocalProgram(prog); - } - - //------------------------------------------------------------ - - private void cmdNewGame() - { - if (gameChanged() && !askSaveGame()) - return; - - String size = m_menubar.getSelectedBoardSize(); - Dimension dim = new Dimension(-1,-1); - if (size.equals("Other...")) - { - size = BoardSizeDialog.show(this, m_guiboard.getBoardSize()); - if (size == null) return; - } - - try - { - StringTokenizer st = new StringTokenizer(size); - int w = Integer.parseInt(st.nextToken()); - st.nextToken(); - int h = Integer.parseInt(st.nextToken()); - dim.setSize(w,h); - } - catch (Throwable t) - { - ShowError.msg(this, "Size should be in format 'w x h'."); - return; - } - - if (dim.width < 1 || dim.height < 1) - { - ShowError.msg(this, "Invalid board size."); - } - else - { - m_tomove = HexColor.BLACK; - m_toolbar.setToMove(m_tomove.toString()); - - m_root = new Node(); - m_current = m_root; - m_gameinfo = new GameInfo(); - m_gameinfo.setBoardSize(dim); - stopClock(HexColor.BLACK); - stopClock(HexColor.WHITE); - m_blackClock.setElapsed(0); - m_whiteClock.setElapsed(0); - setComment(m_current); - - m_file = null; - resetGameChanged(); - setFrameTitle(); - - m_guiboard.initSize(dim.width, dim.height); - m_guiboard.repaint(); - - m_preferences.put("gui-board-width", dim.width); - m_preferences.put("gui-board-height", dim.height); - - m_toolbar.updateButtonStates(m_current, this); - m_menubar.updateMenuStates(this); - - htpBoardsize(m_guiboard.getBoardSize()); - htpShowboard(); - - setCursorType(); - } - } - - private boolean cmdSaveGame() - { - if (m_file == null) - m_file = showSaveAsDialog(); - - if (m_file != null) - { - System.out.println("Saving to file: " + m_file.getName()); - if (save(m_file)) - { - resetGameChanged(); - setFrameTitle(); - m_preferences.put("path-save-game", m_file.getPath()); - return true; - } - } - return false; - } - - private boolean cmdSaveGameAs() - { - File file = showSaveAsDialog(); - if (file == null) - return false; - - m_file = file; - return cmdSaveGame(); - } - - private void cmdSavePositionAs() - { - File file = showSaveAsDialog(); - if (file != null) - savePosition(file); - } - - private void cmdLoadGame() - { - if (gameChanged() && !askSaveGame()) - return; - File file = showOpenDialog(); - if (file != null) - loadGame(file); - } - - private void cmdPrintPreview() - { - JFrame frame = new JFrame(); - //frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - Container con = frame.getContentPane(); - - PrintPreview pp = new PrintPreview(m_guiboard); - con.add(pp, BorderLayout.CENTER); - - frame.pack(); - frame.setVisible(true); - frame.toFront(); - } - - private void cmdPrint() - { - Print.run(this, m_guiboard); - } - - private void cmdAbout() - { - m_about.setVisible(true); - } - - //------------------------------------------------------------ - - private void cmdGuiToolbarVisible() - { - boolean visible = m_menubar.getToolbarVisible(); - m_toolbar.setVisible(visible); - } - - private void cmdGuiShellVisible() - { - if (m_shell == null) return; - boolean visible = m_menubar.getShellVisible(); - m_shell.setVisible(visible); - } - - private void cmdGuiAnalyzeVisible() - { - if (m_analyzeDialog == null) return; - boolean visible = m_menubar.getAnalyzeVisible(); - m_analyzeDialog.setVisible(visible); - } - - private void cmdGuiBoardDrawType() - { - String type = m_menubar.getCurrentBoardDrawType(); - System.out.println(type); - m_guiboard.setDrawType(type); - m_guiboard.repaint(); - } - - private void cmdGuiBoardOrientation() - { - String type = m_menubar.getCurrentBoardOrientation(); - System.out.println(type); - m_guiboard.setOrientation(type); - m_guiboard.repaint(); - } - - private void cmdClearMarks() - { - m_guiboard.clearMarks(); - m_guiboard.repaint(); - } - - private void cmdShowPreferences() - { - new PreferencesDialog(this, m_preferences); - } - - /** Toggle the player to move, by explicit user request. This - also updates the PL property in the current node. */ - private void cmdToggleToMove() - { - this.toggleToMove(); - m_current.setPlayerToMove(m_tomove); - } - - /** Toggle the player to move, without setting the PL property */ - private void toggleToMove() - { - m_tomove = m_tomove.otherColor(); - m_toolbar.setToMove(m_tomove.toString()); - m_menubar.setToMove(m_tomove.toString()); - setCursorType(); - } - - /** Set the player to move, by explicit user request. This - also updates the PL property in the current node. */ - private void cmdSetToMove() - { - this.setToMove(); - m_current.setPlayerToMove(m_tomove); - } - - /** Set the player to move, without setting the PL property */ - private void setToMove() - { - m_tomove = HexColor.get(m_menubar.getToMove()); - m_toolbar.setToMove(m_tomove.toString()); - setCursorType(); - } - - private void cmdSetupBlack() - { - setCursorType(); - } - - private void cmdSetupWhite() - { - setCursorType(); - } - - // Update the cursor according to the current move type. - public void setCursorType() - { - String clickContext = m_toolbar.getClickContext(); - if (clickContext == "black") { - m_guiboard.setCursorType("black-setup"); - } else if (clickContext == "white") { - m_guiboard.setCursorType("white-setup"); - } else { - if (m_tomove == HexColor.BLACK) { - m_guiboard.setCursorType("black"); - } else { - m_guiboard.setCursorType("white"); - } - } - } - - //------------------------------------------------------------ - - public void actionClearAnalyzeCommand() - { - - } - - public void actionSetAnalyzeCommand(AnalyzeCommand command) - { - actionSetAnalyzeCommand(command, false, true, true, false); - } - - public void actionSetAnalyzeCommand(AnalyzeCommand command, - boolean autoRun, boolean clearBoard, - boolean oneRunOnly, - boolean reuseTextWindow) - { - AnalyzeType type = command.getType(); - if (command.needsPointArg()) - { - Vector selected = getSelectedCells(); - if (selected.size() < 1) - { - m_statusbar.setMessage("Please select a cell before " + - "running."); - return; - } - command.setPointArg(selected.get(0)); - } - if (command.needsPointListArg()) - { - Vector selected = getSelectedCells(); - if (type == AnalyzeType.VC && selected.size() != 2) - { - m_statusbar.setMessage("Please select a pair of cells before " + - "running."); - return; - } - PointList blah = new PointList(selected); - command.setPointListArg(blah); - } - String cmd = command.replaceWildCards(m_tomove); - String cleaned = StringUtils.cleanWhiteSpace(cmd.trim()); - String args[] = cleaned.split(" "); - String c = args[0]; - m_curAnalyzeCommand = command; - - Runnable cb = null; - switch(type) - { - case GROUP: - cb = new Runnable() { public void run() { cbGroupGet(); } }; - break; - case GFX: - cb = new Runnable() { public void run() { cbGfx(); } }; - break; - case INFERIOR: - cb = new Runnable() { public void run() {cbShowInferiorCells();}}; - break; - case MOVE: - cb = new Runnable() { public void run() { cbGenMove(); } }; - break; - case PLIST: - cb = new Runnable() { public void run() { cbDisplayPointList(); } }; - break; - case PSPAIRS: - cb = new Runnable() { public void run() { cbDisplayPointText(); } }; - break; - case PARAM: - cb = new Runnable() { public void run() { cbEditParameters(); } }; - break; - case VC: - cb = new Runnable() { public void run() { cbVCs(); } }; - break; - case STRING: - cb = new Runnable() { public void run() { cbString(); } }; - break; - case VAR: - cb = new Runnable() { public void run() { cbVar(); } }; - break; - } - // if (c.equals("dfpn-get-bounds")) - // cb = new Runnable() { public void run() { cbDfpnDisplayBounds();} }; - // else if (c.equals("book-scores")) - // cb = new Runnable() { public void run() { cbDisplayBookScores(); } }; - // else if (c.equals("eval-resist")) - // cb = new Runnable() { public void run() { cbEvalResist(); } }; - Runnable callback = null; - if (cb != null) - callback = new GuiRunnable(cb); - sendCommand(cmd + "\n", callback); - } - - /** HtpShell Callback. - By the name of the command it choose the proper callback function. - Arguments are passed as given. - */ - public void commandEntered(String cmd) - { - sendCommand(cmd, null); - } - - //---------------------------------------------------------------------- - - private boolean commandNeedsToLockGUI(String cmd) - { - if ((cmd.length() > 7 && cmd.substring(0, 7).equals("genmove")) || - (cmd.length() > 15 && cmd.substring(0, 15).equals("dfs-solve-state")) || - (cmd.length() > 16 && cmd.substring(0, 16).equals("dfpn-solve-state")) || - (cmd.length() > 23 && cmd.substring(0, 23).equals("dfs-solver-find-winning")) || - (cmd.length() > 24 && cmd.substring(0, 24).equals("dfpn-solver-find-winning"))) - return true; - return false; - } - - private void lockGUI() - { - m_locked = true; - m_toolbar.lockToolbar(); - } - - private void unlockGUI() - { - m_toolbar.unlockToolbar(m_current, this); - m_locked = false; - } - - /** A (command, callback) pair. */ - private class HtpCommand - { - public HtpCommand() - { - } - - public HtpCommand(String cmd, Runnable callback) - { - this.str = cmd; - this.callback = callback; - } - - public String str; - public Runnable callback; - } - - /** Waits for commands to be added to the queue, then processes - each in turn. */ - private class CommandHandler - implements Runnable - { - - public CommandHandler(Component parent, - ArrayBlockingQueue queue) - { - m_parent = parent; - m_queue = queue; - } - - public void run() - { - while (true) - { - HtpCommand cmd = null; - try - { - // block until queue contains an element - cmd = m_queue.take(); - } - catch(InterruptedException e) - { - System.out.println("INTERRUPTED! HUH?"); - } - - if (m_white != null && m_white.connected()) - { - if (commandNeedsToLockGUI(cmd.str)) - lockGUI(); - - try { - m_white.sendCommand(cmd.str); - if (cmd.callback != null) { - cmd.callback.run(); - } - } - catch (HtpError e) { - System.out.println("Caught error '" - + e.getMessage() + "'"); - ShowError.msg(m_parent, e.getMessage()); - } - - if (commandNeedsToLockGUI(cmd.str)) - unlockGUI(); - } - else - { - System.out.println("Not sending to disconnected: '" - + cmd.str.trim() + "'"); - } - } - } - - Component m_parent; - ArrayBlockingQueue m_queue; - } - - private void sendCommand(String cmd, Runnable callback) - { - if (m_white == null) - return; - - try { - System.out.println("sendCommand: '" + cmd.trim() + "'"); - m_htp_queue.put(new HtpCommand(cmd, callback)); - } - catch (InterruptedException e) - { - System.out.println("Interrupted while adding!"); - } - } - - // FIXME: add callback? - private void htpQuit() - { - sendCommand("quit\n", null); - } - - private void htpName() - { - Runnable cb = new Runnable() { public void run() { cbName(); } }; - sendCommand("name\n", cb); - } - - private void htpVersion() - { - Runnable cb = new Runnable() { public void run() { cbVersion(); } }; - sendCommand("version\n", cb); - } - - private void htpAnalyzeCommands() - { - Runnable cb = new Runnable() - { public void run() { cbAnalyzeCommands(); } }; - sendCommand("hexgui-analyze_commands\n", cb); - } - - private void htpClearBoard() - { - sendCommand("clear_board\n", null); - } - - private void htpShowboard() - { - sendCommand("showboard\n", null); - } - - /** Play a move on the attached HTP backend. This only works if - * move is a legal move of color black or white. There is no HTP - * command for setup moves that remove a piece, or that change the - * color of an already existing piece, and swap, pass, resign, and - * forfeit moves are possibly not implemented in HTP, or may not - * be undoable correctly. If the move is swap-pieces, just - * update HTP to the current board position; so gui should always - * be updated before calling this. */ - private void htpPlay(Move move) - { - if (move.getPoint() == HexPoint.RESIGN - || move.getPoint() == HexPoint.FORFEIT - || move.getPoint() == HexPoint.SWAP_SIDES - || move.getPoint() == HexPoint.PASS) { - return; - } - if (move.getPoint() == HexPoint.SWAP_PIECES) { - htpSetUpCurrentBoard(); - return; - } - sendCommand("play " + move.getColor().toString() + - " " + move.getPoint().toString() + "\n", null); - } - - /** GUI must already be updated prior to calling this. */ - private void htpUndo(Move move) - { - if (move.getPoint() == HexPoint.RESIGN - || move.getPoint() == HexPoint.FORFEIT - || move.getPoint() == HexPoint.SWAP_SIDES - || move.getPoint() == HexPoint.PASS) { - return; - } - sendCommand("undo\n", null); - } - - private void htpGenMove(HexColor color) - { - if (! checkBoardSizeSupported()) - return; - m_statusbar.setMessage(format("{0} is thinking...", m_white_name)); - Runnable callback = new GuiRunnable(new Runnable() - { - public void run() { cbGenMove(); } - }); - sendCommand("genmove " + color.toString() + "\n", callback); - } - - private void htpBoardsize(Dimension size) - { - Runnable callback = new Runnable() - { - public void run() { - m_unsupportedBoardSize = ! m_white.wasSuccess(); - checkBoardSizeSupported(); - } - }; - sendCommand("boardsize " + size.width + " " + size.height + "\n", - callback); - m_statusbar.setMessage("New game"); - } - - // - // Callbacks - // - public void cbName() - { - String str = m_white.getResponse(); - // FIXME: handle errors! - m_white_name = str.trim(); - } - - public void cbVersion() - { - String str = m_white.getResponse(); - // FIXME: handle errors! - m_white_version = str.trim(); - releaseSemaphore(); - } - - private void cbAnalyzeCommands() - { - String programAnalyzeCommands = m_white.getResponse(); - try - { - m_analyzeCommands - = AnalyzeDefinition.read(programAnalyzeCommands); - } - catch (ErrorMessage e) - { - ShowError.msg(this, "Could not parse analyze commands!"); - } - releaseSemaphore(); - } - - public void cbGenMove() - { - if (!m_white.wasSuccess()) - return; - m_guiboard.clearMarks(); - String str = m_white.getResponse(); - HexPoint point = HexPoint.get(str.trim()); - if (point == null) - { - System.out.println("Invalid move!!"); - } - else - { - play(new Move(point, m_tomove)); - } - } - - public void cbDisplayPointList() - { - if (!m_white.wasSuccess()) - return; - String str = m_white.getResponse(); - Vector points = StringUtils.parsePointList(str); - m_guiboard.clearMarks(); - for (int i=0; i points = StringUtils.parsePointList(str); - m_guiboard.clearMarks(); - if (points.size() > 0) - { - m_guiboard.setAlphaColor(points.get(0), Color.blue); - for (int i=1; i > pairs = - StringUtils.parseStringPairList(fx.substring(inf + 10, text)); - for (int i=0; i vcs = StringUtils.parseVCList(str); - new VCDisplayDialog(this, m_guiboard, vcs); - } - - public void cbString() - { - if (!m_white.wasSuccess()) - return; - String showText = m_white.getResponse(); - String title = m_curAnalyzeCommand.getResultTitle(); - if (showText != null) - { - if (showText.indexOf("\n") < 0) - { - if (showText.trim().equals("")) - showText = "(empty response)"; - m_statusbar.setMessage(format("{0}: {1}", title, showText)); - } - else - { - HexPoint pointArg = null; - m_showAnalyzeText.show(m_curAnalyzeCommand.getType(), - pointArg, title, showText, false); - } - } - } - - public void cbVar() - { - if (!m_white.wasSuccess()) - return; - String str = m_white.getResponse(); - Vector points = StringUtils.parsePointList(str, " "); - m_guiboard.clearMarks(); - m_guiboard.aboutToDirtyStones(); - HexColor color = m_tomove; - for (int i = 0; i < points.size(); i++) - { - m_guiboard.setColor(points.get(i), color); - m_guiboard.setText(points.get(i), Integer.toString(i + 1)); - color = color.otherColor(); - } - m_guiboard.repaint(); - } - - public void cbDisplayPointText() - { - if (!m_white.wasSuccess()) - return; - String str = m_white.getResponse(); - Vector > pairs = - StringUtils.parseStringPairList(str); - m_guiboard.clearMarks(); - for (int i=0; i > pairs = - StringUtils.parseStringPairList(str); - m_guiboard.clearMarks(); - for (int i=0; i > pairs = - StringUtils.parseStringPairList(str); - String res = ""; - String rew = ""; - String reb = ""; - m_guiboard.clearMarks(); - for (int i=0; i 3 && fx.substring(0, 3).equals("uct")) - guifx_uct(fx.substring(3)); - else if (fx.length() > 2 && fx.substring(0, 2).equals("ab")) - guifx_ab(fx.substring(2)); - else if (fx.length() > 4 && fx.substring(0, 4).equals("dfpn")) - guifx_dfpn(fx.substring(4)); - else if (fx.length() > 6 && fx.substring(0, 6).equals("solver")) - guifx_solver(fx.substring(6)); - } - - private void guifx_uct(String fx) - { - String[] tk = fx.trim().split(" "); - int i=0; - - m_guiboard.clearMarks(); - m_guiboard.aboutToDirtyStones(); - - /** @todo Fix this to parse like guifx_ab() and - guifx_solver(). */ - - ////////////////////////////////////// - // display variation - for (; i < tk.length; ++i) - { - String s = tk[i].trim(); - if (s.equals("VAR")) - break; - } - if (i == tk.length) - return; - ++i; // skip "VAR"; - - Vector var = new Vector(); - Vector col = new Vector(); - for (; i < tk.length; ) - { - String s = tk[i].trim(); - if (s.equals("INFLUENCE")) - break; - ++i; // skip 'B' and 'W' - - col.add((s.charAt(0) == 'B') ? HexColor.BLACK : HexColor.WHITE); - HexPoint point = HexPoint.get(tk[i++].trim()); - var.add(point); - } - - m_guiboard.setColor(var.get(0), col.get(0)); - m_guiboard.setAlphaColor(var.get(0), Color.cyan); - if (var.size() > 1) - { - m_guiboard.setColor(var.get(1), col.get(1)); - m_guiboard.setAlphaColor(var.get(1), Color.blue); - } - - ///////////////////////////////////////// - // display score/search counts - - TreeMap map = new TreeMap(); - - ++i; // skip 'INFLUENCE' - for (; i > it = map.entrySet().iterator(); - while(it.hasNext()) { - Map.Entry e = it.next(); - m_guiboard.setText(e.getKey(), e.getValue()); - } - - m_guiboard.repaint(); - m_statusbar.setMessage(fx.substring(fx.indexOf("TEXT")+5)); - } - - private void guifx_ab(String fx) - { - m_guiboard.clearMarks(); - m_guiboard.aboutToDirtyStones(); - - int var = fx.indexOf("VAR"); - int label = fx.indexOf("LABEL"); - int text = fx.indexOf("TEXT"); - - Vector > vr - = StringUtils.parseVariation(fx.substring(var+3, label)); - if (vr.size() > 0) - { - m_guiboard.setColor(vr.get(0).second, vr.get(0).first); - m_guiboard.setAlphaColor(vr.get(0).second, Color.green); - if (vr.size() >= 2) - { - m_guiboard.setColor(vr.get(1).second, vr.get(1).first); - m_guiboard.setAlphaColor(vr.get(1).second, Color.red); - } - } - String label_str = fx.substring(label+5, text).trim(); - Vector > labels = - StringUtils.parseStringPairList(label_str); - for (int i = 0; i < labels.size(); ++i) - { - HexPoint pt = HexPoint.get(labels.get(i).first); - m_guiboard.setText(pt, labels.get(i).second); - } - m_guiboard.repaint(); - m_statusbar.setMessage(fx.substring(text+5)); - } - - private void guifx_solver(String fx) - { - m_guiboard.clearMarks(); - m_guiboard.aboutToDirtyStones(); - m_statusbar.setProgressVisible(true); - - int var = fx.indexOf("VAR"); - int label = fx.indexOf("LABEL"); - int text = fx.indexOf("TEXT"); - - Vector > vr - = StringUtils.parseVariation(fx.substring(var+3, label)); - for (int i = 0; i < vr.size(); ++i) - { - m_guiboard.setColor(vr.get(i).second, vr.get(i).first); - m_guiboard.setText(vr.get(i).second, Integer.toString(i+1)); - } - - String label_str = fx.substring(label+5, text).trim(); - showInferiorCells(label_str); - - String prog_str = fx.substring(text+4).trim(); - String[] levels = prog_str.split(" "); - - double contribution = 1.0; - double progress = 0.0; - for (int i = 0; i < levels.length; ++i) - { - String[] nums = levels[i].trim().split("/"); - int cur = Integer.decode(nums[0]).intValue(); - int max = Integer.decode(nums[1]).intValue(); - progress += contribution*cur/max; - contribution *= 1.0/max; - } - m_guiboard.repaint(); - m_statusbar.setMessage(fx.substring(text+5)); - m_statusbar.setProgress(progress); - } - - private void guifx_dfpn(String fx) - { - m_guiboard.clearMarks(); - m_guiboard.aboutToDirtyStones(); - - int var = fx.indexOf("VAR"); - int label = fx.indexOf("LABEL"); - int text = fx.indexOf("TEXT"); - - Vector > vr - = StringUtils.parseVariation(fx.substring(var+3, label)); - for (int i = 0; i < vr.size(); ++i) - { - m_guiboard.setColor(vr.get(i).second, vr.get(i).first); - m_guiboard.setText(vr.get(i).second, Integer.toString(i+1)); - m_guiboard.setAlphaColor(vr.get(i).second, Color.blue); - } - String label_str = fx.substring(label+5, text).trim(); - showDfpnBounds(label_str); - - m_guiboard.repaint(); - m_statusbar.setMessage(fx.substring(text+5)); - } - - private void showDfpnBounds(String str) - { - Vector > pairs = - StringUtils.parseStringPairList(str); - for (int i = 0; i < pairs.size(); i++) - { - HexPoint point = HexPoint.get(pairs.get(i).first); - String value = pairs.get(i).second; - m_guiboard.setText(point, value); - if (value.trim().equals("W")) - m_guiboard.setAlphaColor(point, Color.green); - else if (value.trim().equals("L")) - m_guiboard.setAlphaColor(point, Color.red); - } - } - - /** Draws the inferior cells to the gui board. */ - private void showInferiorCells(String str) - { - Vector > pairs = - StringUtils.parseStringPairList(str); - for (int i = 0; i < pairs.size(); i++) - { - HexPoint point = HexPoint.get(pairs.get(i).first); - String value = pairs.get(i).second; - - if (value.charAt(0) == 'f') // fill-in - { - assert(3 == value.length()); - if (value.charAt(1) == 'd') // dead - m_guiboard.setAlphaColor(point, Color.cyan); - else if (value.charAt(1) == 'p') // permanently inferior - m_guiboard.setAlphaColor(point, Color.gray); - else // captured - { - assert(value.charAt(1) == 'c'); - m_guiboard.setAlphaColor(point, Color.red); - } - if (value.charAt(2) == 'b') - m_guiboard.setColor(point, HexColor.BLACK); - else - { - assert(value.charAt(2) == 'w'); - m_guiboard.setColor(point, HexColor.WHITE); - } - } - else if (value.charAt(0) == 'i') // ignorable - { - assert(4 <= value.length()); - if (value.charAt(1) == 'v') // vulnerable - m_guiboard.setAlphaColor(point, Color.green); - else if (value.charAt(1) == 'r') // reversible - m_guiboard.setAlphaColor(point, Color.magenta); - else // dominated - { - assert(value.charAt(1) == 'd'); - m_guiboard.setAlphaColor(point, Color.yellow); - } - assert(value.charAt(2) == '[' && - value.charAt(value.length()-1) == ']'); - String pts = value.substring(3, value.length()-1); - Vector pp = StringUtils.parsePointList(pts,"-"); - for (int j=0; j getSelectedCells() - { - return m_selected_cells; - } - - public HexColor getColorToMove() - { - return m_tomove; - } - - public void humanMove(Move move) - { - play(move); - htpPlay(move); - htpShowboard(); - if (! m_guiboard.isBoardFull() - && m_preferences.getBoolean("auto-respond") - && m_program != null) - htpGenMove(m_tomove); - } - - /** Update the GUI to reflect the given move. Do this without any - * changes to the game tree or the HTP. */ - private void guiPlay(Move move) - { - if (m_guiboard.isYBoard() && move.getPoint() == HexPoint.SWAP_PIECES) { - m_guiboard.swapColors(); - } else if (move.getPoint() == HexPoint.SWAP_PIECES) { - m_guiboard.swapPieces(); - } else { - m_guiboard.setColor(move.getPoint(), - move.getColor()); - } - m_guiboard.clearMarks(); - markLastPlayedStone(); - } - - public boolean isSwapAllowed() - { - // Count the number of pieces on the board. - int count = m_guiboard.numberOfPieces(); - // Check whether the game tree allows swapping. - boolean isswap = m_current.isSwap(); - return count == 1 && !isswap; - } - - private void play(Move move) - { - // see if variation already exists; if so, do not add a duplicate - int variation = -1; - for (int i=0; i labels = node.getLabels(); - for (int i = 0; i < labels.size(); ++i) - { - String lb = labels.get(i); - String[] strs = lb.split(":"); - HexPoint p = HexPoint.get(strs[0].trim()); - m_guiboard.setText(p, strs[1].trim()); - } - } - - // Play the setup moves of the given node in the Gui, not HTP. - private void guiPlaySetup(Node node) - { - Vector black = node.getSetup(HexColor.BLACK); - Vector white = node.getSetup(HexColor.WHITE); - Vector empty = node.getSetup(HexColor.EMPTY); - for (int j=0; j m_analyzeCommands; - - private final MessageDialogs m_messageDialogs = - new MessageDialogs("HexGui"); - - private Vector m_selected_cells; - - private Program m_program; - private Vector m_programs; - - private ShowAnalyzeText m_showAnalyzeText; - - private ArrayBlockingQueue m_htp_queue; - private Semaphore m_semaphore; - private HtpController m_white; - private String m_white_name; - private String m_white_version; - private AnalyzeCommand m_curAnalyzeCommand; - private Process m_white_process; - private Socket m_white_socket; - - private File m_file; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/HtpShell.java b/src/hexgui/gui/HtpShell.java deleted file mode 100644 index 6f2f142..0000000 --- a/src/hexgui/gui/HtpShell.java +++ /dev/null @@ -1,129 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.hex.*; -import hexgui.htp.HtpController; - -import javax.swing.*; -import javax.swing.text.*; -import java.awt.*; -import java.awt.event.*; - -/** Non-modal dialog displaying the communication between HexGui and a - HTP compatible program. */ -public class HtpShell - extends JDialog implements ActionListener, HtpController.IOInterface -{ - public interface Callback - { - void commandEntered(String str); - } - - public HtpShell(JFrame owner, Callback callback) - { - super(owner, "HexGui: Shell"); - m_callback = callback; - - m_editor = new JTextPane(); - m_editor.setEditable(false); - m_document = m_editor.getStyledDocument(); - addStylesToDocument(m_document); - - m_scrollpane = new JScrollPane(m_editor); - m_scrollpane.setVerticalScrollBarPolicy( - JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - - Dimension size = owner.getSize(); - getContentPane().add(m_scrollpane, BorderLayout.CENTER); - - setPreferredSize(new Dimension(400, size.height)); - setMinimumSize(new Dimension(400, 200)); - setLocation(size.width, 0); - - m_field = new JTextField(); - m_field.addActionListener(this); - m_field.setActionCommand("command-entered"); - getContentPane().add(m_field, BorderLayout.SOUTH); - - pack(); - } - - public void appendText(String text) - { - appendText(text, null); - } - - public void appendText(String text, AttributeSet style) - { - try { - m_document.insertString(m_document.getLength(), text, style); - } - catch (BadLocationException e) { - System.out.println("Bad location!"); - } - } - - - /** HtpController.IOInterface */ - public void sentCommand(String str) - { - appendText(str, m_document.getStyle("blue")); - } - - public void receivedResponse(String str) - { - appendText(str, m_document.getStyle("bold")); - } - - public void receivedError(String str) - { - appendText(str, m_document.getStyle("red")); - } - - protected void addStylesToDocument(StyledDocument doc) { - Style def = StyleContext.getDefaultStyleContext(). - getStyle(StyleContext.DEFAULT_STYLE); - - Style regular = doc.addStyle("regular", def); - StyleConstants.setFontFamily(def, "Monospaced"); - StyleConstants.setFontSize(def, 12); - - Style s = doc.addStyle("italic", regular); - StyleConstants.setItalic(s, true); - - s = doc.addStyle("bold", regular); - StyleConstants.setBold(s, true); - - s = doc.addStyle("blue", regular); - StyleConstants.setForeground(s, Color.blue); - - s = doc.addStyle("red", regular); - StyleConstants.setForeground(s, Color.RED); - - s = doc.addStyle("gray", regular); - StyleConstants.setForeground(s, Color.gray); - - s = doc.addStyle("yellow", regular); - StyleConstants.setForeground(s, Color.YELLOW); - } - - public void actionPerformed(ActionEvent e) - { - String cmd = e.getActionCommand(); - if (cmd.equals("command-entered")) { - String text = m_field.getText() + "\n"; - m_callback.commandEntered(text); - m_field.setText(null); - } - } - - JTextPane m_editor; - JTextField m_field; - JScrollPane m_scrollpane; - StyledDocument m_document; - Callback m_callback; -} - diff --git a/src/hexgui/gui/MessageDialogs.java b/src/hexgui/gui/MessageDialogs.java deleted file mode 100644 index f2621b7..0000000 --- a/src/hexgui/gui/MessageDialogs.java +++ /dev/null @@ -1,359 +0,0 @@ -// MessageDialogs.java - -package hexgui.gui; - -import java.awt.Component; -import java.util.TreeSet; -import java.util.Set; -import java.util.prefs.Preferences; -import javax.swing.Box; -import javax.swing.JComponent; -import javax.swing.JCheckBox; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import static hexgui.gui.GuiUtil.insertLineBreaks; -import hexgui.util.Platform; -import hexgui.util.PrefUtil; -import hexgui.util.StringUtils; - -/** Simple message dialogs. */ -public final class MessageDialogs -{ - public MessageDialogs(String applicationName) - { - m_applicationName = applicationName; - } - - public void showError(Component frame, String mainMessage, - String optionalMessage) - { - showError(frame, mainMessage, optionalMessage, true); - } - - public void showError(Component frame, String mainMessage, - String optionalMessage, boolean isCritical) - { - int type; - if (isCritical) - type = JOptionPane.ERROR_MESSAGE; - else - type = JOptionPane.PLAIN_MESSAGE; - Object[] options = { "Close" }; - Object defaultOption = options[0]; - String title = "Error" + " - " + m_applicationName; - show(null, frame, title, mainMessage, optionalMessage, type, - JOptionPane.DEFAULT_OPTION, options, defaultOption, -1); - } - - public void showError(Component frame, String message, Exception e) - { - showError(frame, message, e, true); - } - - public void showError(Component frame, String message, Exception e, - boolean isCritical) - { - showError(frame, message, StringUtils.getErrorMessage(e), isCritical); - } - - public void showInfo(Component frame, String mainMessage, - String optionalMessage, boolean isCritical) - { - showInfo(null, frame, mainMessage, optionalMessage, isCritical); - } - - public void showInfo(String disableKey, Component frame, - String mainMessage, String optionalMessage, - boolean isCritical) - { - if (checkDisabled(disableKey)) - return; - int type; - if (isCritical) - type = JOptionPane.INFORMATION_MESSAGE; - else - type = JOptionPane.PLAIN_MESSAGE; - Object[] options = { "Close" }; - Object defaultOption = options[0]; - String title = "Information" + " - " + m_applicationName; - show(disableKey, frame, title, mainMessage, optionalMessage, - type, JOptionPane.DEFAULT_OPTION, options, defaultOption, -1); - } - - public int showYesNoCancelQuestion(Component parent, String mainMessage, - String optionalMessage, - String destructiveOption, - String nonDestructiveOption) - { - return showYesNoCancelQuestion(null, parent, mainMessage, - optionalMessage, destructiveOption, - nonDestructiveOption); - } - - /** Show a question with two options and cancel. - @return 0 for the destructive option; 1 for the non-destructive - option; 2 for cancel */ - public int showYesNoCancelQuestion(String disableKey, Component parent, - String mainMessage, - String optionalMessage, - String destructiveOption, - String nonDestructiveOption) - { - if (checkDisabled(disableKey)) - return 0; - Object[] options = new Object[3]; - int destructiveIndex; - if (Platform.isMac()) - { - options[0] = nonDestructiveOption; - options[1] = "Cancel"; - options[2] = destructiveOption; - destructiveIndex = 2; - } - else - { - options[0] = nonDestructiveOption; - options[1] = destructiveOption; - options[2] = "Cancel"; - destructiveIndex = -1; - } - Object defaultOption = options[0]; - int type = JOptionPane.QUESTION_MESSAGE; - String title = "Question" + " - " + m_applicationName; - Object value = show(disableKey, parent, title, mainMessage, - optionalMessage, type, - JOptionPane.YES_NO_CANCEL_OPTION, options, - defaultOption, destructiveIndex); - int result; - if (value == destructiveOption) - result = 0; - else if (value == nonDestructiveOption) - result = 1; - else - result = 2; - return result; - } - - public void showWarning(Component parent, String mainMessage, - String optionalMessage, boolean isCritical) - { - showWarning(null, parent, mainMessage, optionalMessage, isCritical); - } - - public void showWarning(String disableKey, Component parent, - String mainMessage, String optionalMessage, - boolean isCritical) - { - if (checkDisabled(disableKey)) - return; - int type; - if (isCritical) - type = JOptionPane.WARNING_MESSAGE; - else - type = JOptionPane.PLAIN_MESSAGE; - Object[] options = { "Close" }; - Object defaultOption = options[0]; - String title = "Warning" + " - " + m_applicationName; - show(disableKey, parent, title, mainMessage, optionalMessage, type, - JOptionPane.DEFAULT_OPTION, options, defaultOption, -1); - } - - public boolean showQuestion(Component parent, String mainMessage, - String optionalMessage, - String destructiveOption, boolean isCritical) - { - return showQuestion(null, parent, mainMessage, optionalMessage, - destructiveOption, isCritical); - } - - public boolean showQuestion(String disableKey, Component parent, - String mainMessage, - String optionalMessage, - String destructiveOption, - boolean isCritical) - { - return showQuestion(disableKey, parent, mainMessage, optionalMessage, - destructiveOption, "Cancel", - isCritical); - } - - /** Show warning message to confirm destructive actions. - @return true, if destructive was chosen; false if cancel was - chosen. */ - public boolean showQuestion(String disableKey, Component parent, - String mainMessage, - String optionalMessage, - String affirmativeOption, - String cancelOption, - boolean isCritical) - { - if (checkDisabled(disableKey)) - return true; - Object[] options = new Object[2]; - if (Platform.isMac()) - { - options[0] = cancelOption; - options[1] = affirmativeOption; - } - else - { - options[0] = affirmativeOption; - options[1] = cancelOption; - } - Object defaultOption = affirmativeOption; - int type; - if (isCritical) - // No reason to show a warning icon for confirmation dialogs - // of frequent actions - type = JOptionPane.QUESTION_MESSAGE; - else - type = JOptionPane.PLAIN_MESSAGE; - String title = "Question" + " - " + m_applicationName; - Object result = show(disableKey, parent, title, mainMessage, - optionalMessage, type, JOptionPane.YES_NO_OPTION, - options, defaultOption, -1); - return (result == affirmativeOption); - } - - public boolean showWarningQuestion(Component parent, String mainMessage, - String optionalMessage, - String destructiveOption, - boolean isCritical) - { - return showWarningQuestion(null, parent, mainMessage, optionalMessage, - destructiveOption, isCritical); - } - - public boolean showWarningQuestion(String disableKey, Component parent, - String mainMessage, - String optionalMessage, - String destructiveOption, - boolean isCritical) - { - return showWarningQuestion(disableKey, parent, mainMessage, - optionalMessage, destructiveOption, - "Cancel", isCritical); - } - - /** Show warning message to confirm destructive actions. - @return true, if destructive was chosen; false if cancel was chosen. */ - public boolean showWarningQuestion(String disableKey, Component parent, - String mainMessage, - String optionalMessage, - String destructiveOption, - String nonDestructiveOption, - boolean isCritical) - { - if (checkDisabled(disableKey)) - return true; - Object[] options = new Object[2]; - if (Platform.isMac()) - { - options[0] = nonDestructiveOption; - options[1] = destructiveOption; - } - else - { - options[0] = destructiveOption; - options[1] = nonDestructiveOption; - } - Object defaultOption = nonDestructiveOption; - int type; - if (isCritical) - type = JOptionPane.WARNING_MESSAGE; - else - type = JOptionPane.PLAIN_MESSAGE; - String title = "Warning" + " - " + m_applicationName; - Object result = show(disableKey, parent, title, mainMessage, - optionalMessage, type, JOptionPane.YES_NO_OPTION, - options, defaultOption, -1); - return (result == destructiveOption); - } - - private final String m_applicationName; - - private final Set m_disabled = new TreeSet(); - - private static void addFiller(JComponent component) - { - Box.Filler filler = GuiUtil.createFiller(); - filler.setAlignmentX(Component.LEFT_ALIGNMENT); - component.add(filler); - } - - private boolean checkDisabled(String disableKey) - { - if (disableKey == null) - return false; - Preferences prefs = - PrefUtil.createNode("net/sf/hexgui/gui/messagedialogs/disabled"); - boolean permanentlyDisabled = prefs.getBoolean(disableKey, false); - if (permanentlyDisabled) - return true; - // Make sure this entry exists (right now these settings can only - // be directly edited in the backing store) - prefs.putBoolean(disableKey, permanentlyDisabled); - return m_disabled.contains(disableKey); - } - - private Object show(String disableKey, Component parent, String title, - String mainMessage, String optionalMessage, - int messageType, int optionType, Object[] options, - Object defaultOption, int destructiveIndex) - { - if (optionalMessage == null) - optionalMessage = ""; - boolean isMac = Platform.isMac(); - Box box = Box.createVerticalBox(); - - String css = GuiUtil.getMessageCss(); - - JLabel label = - new JLabel("" + css + "" + insertLineBreaks(mainMessage) - + "

" - + insertLineBreaks(optionalMessage) + "

"); - label.setAlignmentX(Component.LEFT_ALIGNMENT); - box.add(label); - - addFiller(box); - addFiller(box); - JCheckBox disableCheckBox = null; - if (disableKey != null) - { - if (messageType == JOptionPane.QUESTION_MESSAGE) - disableCheckBox = new JCheckBox("Do not show this message again"); - else if (messageType == JOptionPane.WARNING_MESSAGE) - disableCheckBox = - new JCheckBox("Do not show this warning again"); - else - disableCheckBox = - new JCheckBox("Do not show this message again"); - disableCheckBox.setToolTipText("Do not show this message again"); - disableCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); - box.add(disableCheckBox); - } - if (isMac) - // Don't show icons on Mac, problem with icon generation in - // Quaqua 3.7.2 - messageType = JOptionPane.PLAIN_MESSAGE; - JOptionPane optionPane = - new JOptionPane(box, messageType, optionType, null, options, - defaultOption); - if (destructiveIndex >= 0) - { - String key = "Quaqua.OptionPane.destructiveOption"; - optionPane.putClientProperty(key, - Integer.valueOf(destructiveIndex)); - } - if (isMac && parent.isVisible()) - // Dialogs don't have titles on the Mac - title = null; - JDialog dialog = optionPane.createDialog(parent, title); - dialog.setVisible(true); - dialog.dispose(); - if (disableKey != null && disableCheckBox.isSelected()) - m_disabled.add(disableKey); - return optionPane.getValue(); - } -} diff --git a/src/hexgui/gui/ParameterDialog.java b/src/hexgui/gui/ParameterDialog.java deleted file mode 100644 index b2012e7..0000000 --- a/src/hexgui/gui/ParameterDialog.java +++ /dev/null @@ -1,430 +0,0 @@ -// ParameterDialog.java - -package hexgui.gui; - -import java.awt.Component; -import java.awt.Frame; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Insets; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.awt.Dimension; -import java.beans.PropertyChangeEvent; -import java.beans.PropertyChangeListener; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.StringReader; -import static java.lang.Math.max; -import java.text.MessageFormat; -import java.util.ArrayList; -import javax.swing.Box; -import javax.swing.JDialog; -import javax.swing.JCheckBox; -import javax.swing.JComboBox; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import static javax.swing.JOptionPane.OK_CANCEL_OPTION; -import static javax.swing.JOptionPane.PLAIN_MESSAGE; -import static javax.swing.JOptionPane.UNINITIALIZED_VALUE; -import static javax.swing.JOptionPane.VALUE_PROPERTY; -import javax.swing.JPanel; -import javax.swing.JSeparator; -import javax.swing.JTextField; -import javax.swing.SwingConstants; -import hexgui.htp.AnalyzeUtil; -import hexgui.htp.HtpError; -import hexgui.htp.ParameterType; -import hexgui.util.ObjectUtil; -import hexgui.util.StringUtils; -import hexgui.htp.HtpController; - -/** Dialog for editing parameters in response to an analyze command of type - param. */ -public class ParameterDialog -{ - public static void editParameters(final String paramCommand, Frame owner, - String title, String response, - final HtpController htp, - final MessageDialogs messageDialogs) - { - final ArrayList parameters = parseResponse(response); - Component mainComponent = createMainComponent(parameters); - final Object options[] = { "OK", "Cancel" }; - final JOptionPane optionPane = - new JOptionPane(mainComponent, PLAIN_MESSAGE, OK_CANCEL_OPTION, - null, options, options[0]); - final JDialog dialog = new JDialog(owner, title, true); - dialog.setContentPane(optionPane); - - optionPane.addPropertyChangeListener(new PropertyChangeListener() { - public void propertyChange(PropertyChangeEvent event) { - String prop = event.getPropertyName(); - if (dialog.isVisible() && event.getSource() == optionPane - && prop.equals(VALUE_PROPERTY)) - { - Object value = optionPane.getValue(); - if (ObjectUtil.equals(value, UNINITIALIZED_VALUE)) - return; - if (ObjectUtil.equals(value, options[0])) - { - for (int i = 0; i < parameters.size(); ++i) - { - Parameter parameter = parameters.get(i); - if (! parameter.isChanged()) - continue; - try - { - String command = - getNewValueCommand(paramCommand, - parameter); - htp.sendCommand(command); - } - catch (HtpError e) - { - showError(dialog, messageDialogs, - parameter, e); - optionPane.setValue(UNINITIALIZED_VALUE); - return; - } - } - } - dialog.setVisible(false); - } - } - }); - dialog.pack(); - dialog.setLocationByPlatform(true); - dialog.addWindowListener(new WindowAdapter() { - public void windowOpened(WindowEvent e) { - // JDK 1.5 docs require to invoke selectInitialValue after - // the window is made visible - optionPane.selectInitialValue(); - } }); - dialog.setVisible(true); - } - - /** Length of a textfield for editing string parameters. */ - private static final int TEXTFIELD_LEN = 13; - - private static final int MAX_PARAM_PER_COLUMN = 15; - - private abstract static class Parameter - { - public Parameter(String key, String value) - { - m_key = key; - m_value = value; - m_label = StringUtils.capitalize(key.replace('_', ' ')); - } - - public String getKey() - { - return m_key; - } - - public String getLabel() - { - return m_label; - } - - public String getValue() - { - return m_value; - } - - public abstract String getNewValue(); - - public abstract boolean isChanged(); - - public abstract void createComponents(int gridy, JPanel panel, - GridBagLayout gridbag); - - private final String m_key; - - private final String m_label; - - private final String m_value; - } - - private static class BoolParameter - extends Parameter - { - public BoolParameter(String key, String value) - { - super(key, value); - try - { - m_initialValue = (Integer.parseInt(value) != 0); - } - catch (NumberFormatException e) - { - m_initialValue = false; - } - } - - public String getNewValue() - { - if (m_checkBox.isSelected()) - return "1"; - return "0"; - } - - public boolean isChanged() - { - return (m_checkBox.isSelected() != m_initialValue); - } - - public void createComponents(int gridy, JPanel panel, - GridBagLayout gridbag) - { - m_checkBox = new JCheckBox(getLabel(), m_initialValue); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridx = 0; - constraints.gridy = gridy; - constraints.gridwidth = GridBagConstraints.REMAINDER; - constraints.weightx = 1.0; - constraints.anchor = GridBagConstraints.WEST; - gridbag.setConstraints(m_checkBox, constraints); - panel.add(m_checkBox); - } - - private boolean m_initialValue; - - private JCheckBox m_checkBox; - } - - private static class ListParameter - extends Parameter - { - public ListParameter(String type, String key, String value) - { - super(key, value); - String[] args = type.split("/"); - assert args[0].equals("list"); - m_items = new String[args.length - 1]; - m_labels = new String[args.length - 1]; - int initialIndex = 0; - int maxLength = 0; - for (int i = 1; i < args.length; ++i) - { - String item = args[i]; - if (item.equals(value)) - initialIndex = i - 1; - maxLength = max(item.length(), maxLength); - m_items[i - 1] = item; - m_labels[i - 1] = - StringUtils.capitalize(item.replace('_', ' ')); - } - m_initialIndex = initialIndex; - } - - public String getNewValue() - { - return m_items[m_comboBox.getSelectedIndex()]; - } - - public boolean isChanged() - { - return (m_comboBox.getSelectedIndex() != m_initialIndex); - } - - public void createComponents(int gridy, JPanel panel, - GridBagLayout gridbag) - { - JLabel label = new JLabel(getLabel() + ":"); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridx = 0; - constraints.gridy = gridy; - constraints.weightx = 1.0; - constraints.ipadx = SMALL_PAD; - constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); - constraints.anchor = GridBagConstraints.EAST; - gridbag.setConstraints(label, constraints); - panel.add(label); - - m_comboBox = new JComboBox(m_labels); - m_comboBox.setSelectedIndex(m_initialIndex); - constraints = new GridBagConstraints(); - constraints.gridx = 1; - constraints.gridy = gridy; - constraints.weightx = 1.0; - constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); - constraints.anchor = GridBagConstraints.WEST; - gridbag.setConstraints(m_comboBox, constraints); - panel.add(m_comboBox); - } - - private final int m_initialIndex; - - private final String[] m_items; - - private final String[] m_labels; - - private JComboBox m_comboBox; - } - - private static class StringParameter - extends Parameter - { - public StringParameter(String key, String value) - { - super(key, value); - } - - public String getNewValue() - { - return m_textField.getText().trim(); - } - - public boolean isChanged() - { - return ! getNewValue().equals(getValue()); - } - - public void createComponents(int gridy, JPanel panel, - GridBagLayout gridbag) - { - JLabel label = new JLabel(getLabel() + ":"); - GridBagConstraints constraints = new GridBagConstraints(); - constraints.gridx = 0; - constraints.gridy = gridy; - constraints.weightx = 1.0; - constraints.ipadx = SMALL_PAD; - constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); - constraints.anchor = GridBagConstraints.EAST; - gridbag.setConstraints(label, constraints); - panel.add(label); - - m_textField = new JTextField(TEXTFIELD_LEN); - m_textField.setText(getValue()); - constraints = new GridBagConstraints(); - constraints.gridx = 1; - constraints.gridy = gridy; - constraints.weightx = 1.0; - constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); - constraints.anchor = GridBagConstraints.WEST; - gridbag.setConstraints(m_textField, constraints); - panel.add(m_textField); - } - - private JTextField m_textField; - } - - private static ArrayList parseResponse(String response) - { - ArrayList parameters = new ArrayList(); - BufferedReader reader = - new BufferedReader(new StringReader(response)); - while (true) - { - String line = null; - try - { - line = reader.readLine(); - } - catch (IOException e) - { - } - if (line == null) - break; - AnalyzeUtil.Result result = AnalyzeUtil.parseParameterLine(line); - if (result == null) - continue; - if (result.m_type == ParameterType.BOOL) - parameters.add(new BoolParameter(result.m_key, - result.m_value)); - else if (result.m_type == ParameterType.LIST) - parameters.add(new ListParameter(result.m_typeInfo, - result.m_key, - result.m_value)); - else - // Treat unknown types as string for compatibility with future - // types - parameters.add(new StringParameter(result.m_key, - result.m_value)); - } - return parameters; - } - - private static Component - createMainComponent(ArrayList parameters) - { - int numberParameters = parameters.size(); - Box outerBox = Box.createHorizontalBox(); - int i = 0; - int numberColumns = 0; - JPanel panel = null; - GridBagLayout gridbag = null; - int gridy = 0; - int paramPerColumn = - (numberParameters + 1) - / (numberParameters / MAX_PARAM_PER_COLUMN + 1); - while (i < numberParameters) - { - if (i % paramPerColumn == 0) - { - if (panel != null) - { - if (numberColumns > 0) - { - outerBox.add(createFiller()); - outerBox.add(new JSeparator(SwingConstants.VERTICAL)); - outerBox.add(createFiller()); - } - outerBox.add(panel); - ++numberColumns; - } - gridbag = new GridBagLayout(); - panel = new JPanel(gridbag); - gridy = 0; - } - parameters.get(i).createComponents(gridy, panel, gridbag); - ++gridy; - ++i; - } - if (panel != null) - { - if (numberColumns > 0) - { - outerBox.add(createFiller()); - outerBox.add(new JSeparator(SwingConstants.VERTICAL)); - outerBox.add(createFiller()); - } - outerBox.add(panel); - } - return outerBox; - } - - private static String getNewValueCommand(String paramCommand, - Parameter parameter) - { - String key = parameter.getKey(); - String value = parameter.getNewValue(); - return AnalyzeUtil.getParameterCommand(paramCommand, key, value); - } - - private static void showError(JDialog owner, MessageDialogs messageDialogs, - Parameter parameter, HtpError e) - { - String mainMessage = - MessageFormat.format("Could not change ", - parameter.getLabel()); - String optionalMessage = StringUtils.capitalize(e.getMessage()); - messageDialogs.showError(owner, mainMessage, optionalMessage); - } - - private static final int SMALL_PAD = 2; - - private static final int PAD = 5; - - private static final Dimension FILLER_DIMENSION = - new Dimension(PAD, PAD); - - public static Box.Filler createFiller() - { - return new Box.Filler(FILLER_DIMENSION, FILLER_DIMENSION, - FILLER_DIMENSION); - } - -} diff --git a/src/hexgui/gui/PreferencesDialog.java b/src/hexgui/gui/PreferencesDialog.java deleted file mode 100644 index dc7c375..0000000 --- a/src/hexgui/gui/PreferencesDialog.java +++ /dev/null @@ -1,152 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.hex.*; -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; - -/** Dialog for changes user preferences. - */ -public final class PreferencesDialog - extends JDialog implements ItemListener, ActionListener -{ - public PreferencesDialog(Frame owner, GuiPreferences preferences) - { - super(owner, true); - - m_preferences = preferences; - - JPanel generalPanel = createGeneralPanel(); - JPanel boardPanel = createBoardPanel(); - JPanel drawPanel = createDrawPanel(); - JPanel buttonPane = createButtonPanel(); - - JTabbedPane tabbedPane = new JTabbedPane(); - tabbedPane.addTab("General", null, - generalPanel, - "General preferences"); - tabbedPane.addTab("Board", null, - boardPanel, - "Board preferences"); - tabbedPane.addTab("Draw", null, - drawPanel, - "Drawing preferences"); - - add(tabbedPane, BorderLayout.CENTER); - add(buttonPane, BorderLayout.SOUTH); - pack(); - - setVisible(true); - } - - public void itemStateChanged(ItemEvent e) - { - System.out.println("ItemEvent!"); - Object source = e.getItemSelectable(); - - } - - public void actionPerformed(ActionEvent e) - { - String cmd = e.getActionCommand(); - if (cmd.equals("OK")) { - savePreferences(); - dispose(); - } else if (cmd.equals("Cancel")) { - dispose(); - } - } - - private void savePreferences() - { - System.out.println("Saving preferences..."); - - m_preferences.put("shell-show-on-connect", - (showShellOnConnect.getSelectedObjects() != null)); - m_preferences.put("analyze-show-on-connect", - (showAnalyzeOnConnect.getSelectedObjects() != null)); - m_preferences.put("auto-respond", - (autoRespond.getSelectedObjects() != null)); - - } - - private JPanel createGeneralPanel() - { - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - showShellOnConnect = createCheckBox("Show Shell on Program Connect", - "shell-show-on-connect"); - - showAnalyzeOnConnect = createCheckBox("Show Analyze on Program Connect", - "analyze-show-on-connect"); - - autoRespond = createCheckBox("Auto-respond", "auto-respond"); - - panel.add(showShellOnConnect); - panel.add(showAnalyzeOnConnect); - panel.add(autoRespond); - - return panel; - } - - private JPanel createDrawPanel() - { - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - -// JLabel label; - -// label = new JLabel("Field alpha percentage"); -// SpinnerNumberModel model = -// new SpinnerNumberModel(m_preferences.getDouble(""); -// fieldAlpha = new JSpinner(new ); - - return panel; - } - - - private JPanel createBoardPanel() - { - JPanel panel = new JPanel(new BorderLayout()); - panel.add(new JLabel("Board stuff")); - return panel; - } - - private JPanel createButtonPanel() - { - JPanel panel = new JPanel(); - - JButton button = new JButton(" OK "); - button.addActionListener(this); - button.setActionCommand("OK"); - panel.add(button); - - button = new JButton("Cancel"); - button.addActionListener(this); - button.setActionCommand("Cancel"); - panel.add(button); - - return panel; - } - - private JCheckBox createCheckBox(String name, String prefname) - { - JCheckBox box = new JCheckBox(name); - box.setSelected(m_preferences.getBoolean(prefname)); - box.addItemListener(this); - return box; - } - - JCheckBox showShellOnConnect, showAnalyzeOnConnect; - JCheckBox autoRespond; - - JSpinner fieldAlpha; - - GuiPreferences m_preferences; -} - diff --git a/src/hexgui/gui/Print.java b/src/hexgui/gui/Print.java deleted file mode 100644 index ab9b247..0000000 --- a/src/hexgui/gui/Print.java +++ /dev/null @@ -1,35 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- -package hexgui.gui; - -import java.awt.Component; -import java.awt.print.Printable; -import java.awt.print.PrinterJob; - -/** Print a printable. */ -public final class Print -{ - public static void run(Component parent, Printable printable) - { - PrinterJob job = PrinterJob.getPrinterJob(); - job.setPrintable(printable); - if (! job.printDialog()) - return; - try - { - job.print(); - } - catch (Exception e) - { - System.out.println("Printing failed!"); - } - } - - /** Make constructor unavailable; class is for namespace only. */ - private Print() - { - } -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/Program.java b/src/hexgui/gui/Program.java deleted file mode 100644 index 5101dc9..0000000 --- a/src/hexgui/gui/Program.java +++ /dev/null @@ -1,87 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.util.PrefUtil; - -import java.util.Vector; -import java.util.prefs.Preferences; - -/** Hex playing Program. */ -public class Program -{ - public String m_name; - public String m_command; - public String m_working; - - public Program() - { - } - - public Program(String name, String command, String working) - { - m_name = name; - m_command = command; - m_working = working; - } - - public String toString() - { - return m_name; - } - - //------------------------------------------------------------------------ - - public static Vector load() - { - Vector programs = new Vector(); - Preferences prefs = PrefUtil.getNode("hexgui/gui/program"); - if (prefs == null) - return programs; - int size = prefs.getInt("size", 0); - for (int i = 0; i < size; ++i) - { - prefs = PrefUtil.getNode("hexgui/gui/program/" + i); - if (prefs == null) - break; - String name = prefs.get("name", ""); - String version = prefs.get("version", ""); - String command = prefs.get("command", ""); - String workingDirectory = prefs.get("working-directory", ""); - programs.add(new Program(name, command, workingDirectory)); - } - return programs; - } - - public static void save(Vector programs) - { - Preferences prefs = PrefUtil.createNode("hexgui/gui/program"); - if (prefs == null) - return; - prefs.putInt("size", programs.size()); - for (int i = 0; i < programs.size(); ++i) - { - prefs = PrefUtil.createNode("hexgui/gui/program/" + i); - if (prefs == null) - break; - Program p = programs.get(i); - prefs.put("name", p.m_name); - prefs.put("command", p.m_command); - prefs.put("working-directory", p.m_working); - } - } - - public static Program findWithName(String name, Vector programs) - { - for (int i=0; ihstring. -*/ -public final class TextViewer - extends JDialog -{ - /** Callback for events generated by TextViewer. */ - public interface Listener - { - /** Callback if some text is selected. - If text is unselected again this function will be called - with the complete text content of the window. - */ - void textSelected(String text); - } - - public TextViewer(Frame owner, String title, String text, - boolean highlight, Listener listener) - { - super(owner, title); - initialize(text, highlight, listener); - } - - public TextViewer(Dialog owner, String title, String text, - boolean highlight, Listener listener) - { - super(owner, title); - initialize(text, highlight, listener); - } - - public void setText(String title, String text, boolean highlight) - { - setTitle(title); - insertText(text); - if (highlight) - doSyntaxHighlight(); - } - - private JTextPane m_textPane; - - private Listener m_listener; - - private void insertText(String text) - { - Document doc = m_textPane.getDocument(); - while (text.charAt(text.length() - 1) == '\n') - text = text.substring(0, text.length() - 1); - try - { - doc.remove(0, doc.getLength()); - doc.insertString(0, text, null); - } - catch (BadLocationException e) - { - assert false; - } - } - - private void doSyntaxHighlight() - { - GuiUtil.addStyle(m_textPane, "title", null, null, true); - GuiUtil.addStyle(m_textPane, "point", new Color(0.25f, 0.5f, 0.7f)); - GuiUtil.addStyle(m_textPane, "number", new Color(0f, 0.54f, 0f)); - GuiUtil.addStyle(m_textPane, "const", new Color(0.8f, 0f, 0f)); - GuiUtil.addStyle(m_textPane, "color", new Color(0.54f, 0f, 0.54f)); - m_textPane.setEditable(true); - highlight("number", "\\b-?\\d+\\.?\\d*([Ee][+-]\\d+)?\\b"); - highlight("const", "\\b[A-Z_][A-Z_]+[A-Z]\\b"); - highlight("color", - "\\b([Bb][Ll][Aa][Cc][Kk]|[Ww][Hh][Ii][Tt][Ee])\\b"); - highlight("point", "\\b([Pp][Aa][Ss][Ss]|[A-Ta-t](1\\d|[1-9]))\\b"); - highlight("title", "^\\S+:(\\s|$)"); - m_textPane.setEditable(false); - } - - private void highlight(String styleName, String regex) - { - Document doc = m_textPane.getDocument(); - Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); - try - { - CharSequence text = doc.getText(0, doc.getLength()); - Matcher matcher = pattern.matcher(text); - while (matcher.find()) - { - int start = matcher.start(); - int end = matcher.end(); - GuiUtil.setStyle(m_textPane, start, end - start, styleName); - } - } - catch (BadLocationException e) - { - assert false; - } - } - - private void initialize(String text, boolean highlight, Listener listener) - { - m_listener = listener; - JPanel panel = new JPanel(new BorderLayout()); - Container contentPane = getContentPane(); - contentPane.add(panel, BorderLayout.CENTER); - m_textPane = new JTextPane(); - GuiUtil.setMonospacedFont(m_textPane); - insertText(text); - JScrollPane scrollPane = new JScrollPane(m_textPane); - panel.add(scrollPane, BorderLayout.CENTER); - setDefaultCloseOperation(DISPOSE_ON_CLOSE); - KeyListener keyListener = new KeyAdapter() - { - public void keyReleased(KeyEvent e) - { - int c = e.getKeyCode(); - if (c == KeyEvent.VK_ESCAPE) - dispose(); - } - }; - m_textPane.addKeyListener(keyListener); - CaretListener caretListener = new CaretListener() - { - public void caretUpdate(CaretEvent event) - { - if (m_listener == null) - return; - int start = m_textPane.getSelectionStart(); - int end = m_textPane.getSelectionEnd(); - Document doc = m_textPane.getDocument(); - try - { - if (start == end) - { - String text = doc.getText(0, doc.getLength()); - m_listener.textSelected(text); - return; - } - String text = doc.getText(start, end - start); - m_listener.textSelected(text); - } - catch (BadLocationException e) - { - assert false; - } - } - }; - m_textPane.addCaretListener(caretListener); - if (highlight) - doSyntaxHighlight(); - m_textPane.setCaretPosition(0); - m_textPane.setEditable(false); - packTextViewer(); - } - - private void packTextViewer() - { - pack(); - // Workaround for problems with oversized windows on some platforms - // Also increase width by 10%, because automatic computation of - // JTextPane/JTextArea width according to content text does not - // work (maybe related to Sun Bug 4829215) - Dimension size = getSize(); - size.width *= 1.1; - Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - int maxHeight = (int)(0.9 * screenSize.height); - int maxWidth = (int)(0.9 * screenSize.width); - if (size.height > maxHeight || size.width > maxWidth) - { - size.width = Math.min(size.width, maxWidth); - size.height = Math.min(size.height, maxHeight); - } - setMinimumSize(new Dimension(128, 96)); - size.height = Math.max(size.height, 96); - size.width = Math.max(size.width, 128); - setPreferredSize(size); - setSize(size); - } -} diff --git a/src/hexgui/gui/VCDisplayDialog.java b/src/hexgui/gui/VCDisplayDialog.java deleted file mode 100644 index 2e1119c..0000000 --- a/src/hexgui/gui/VCDisplayDialog.java +++ /dev/null @@ -1,100 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.gui; - -import hexgui.hex.*; -import hexgui.htp.HtpController; - -import javax.swing.*; -import javax.swing.text.*; -import javax.swing.event.ListSelectionListener; -import javax.swing.event.ListSelectionEvent; - -import java.awt.*; -import java.awt.event.*; - -import java.util.*; - -//---------------------------------------------------------------------------- - -/** Non-modal dialog displaying list of VCs. Clicking on a vc displays - it to the given GuiBoard. */ -public class VCDisplayDialog - extends JDialog implements ListSelectionListener, - FocusListener -{ - public VCDisplayDialog(JFrame owner, GuiBoard board, Vector vcs) - { - super(owner, "HexGui: VCs"); - - addWindowListener(new WindowAdapter() - { - public void windowClosing(WindowEvent winEvt) { - dispose(); - } - }); - - m_guiboard = board; - - JPanel panel = new JPanel(); - panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); - - m_list = new JList(); - m_list.addFocusListener(this); - m_list.addListSelectionListener(this); - m_list.setDragEnabled(false); - m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - m_scrollpane = new JScrollPane(m_list); - m_scrollpane.setVerticalScrollBarPolicy( - JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); - - panel.add(m_scrollpane); - add(panel); - - pack(); - - Dimension size = owner.getSize(); - setLocation(0, size.height); - - setVCs(vcs); - setVisible(true); - } - - public void setVCs(Vector vcs) - { - m_vcs = vcs; - m_list.setListData(vcs); - } - - public void valueChanged(ListSelectionEvent e) - { - if (m_list.isSelectionEmpty()) - return; - - VC vc = m_vcs.get(m_list.getSelectedIndex()); - if (vc.getType().equals("softlimit")) // do nothing on this - return; - m_guiboard.clearMarks(); - m_guiboard.displayVC(vc); - m_guiboard.repaint(); - } - - public void focusGained(FocusEvent e) - { - } - - public void focusLost(FocusEvent e) - { - m_list.clearSelection(); - } - - private JList m_list; - private JScrollPane m_scrollpane; - private Vector m_vcs; - private GuiBoard m_guiboard; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/hex/ConstPointList.java b/src/hexgui/hex/ConstPointList.java deleted file mode 100644 index e70b203..0000000 --- a/src/hexgui/hex/ConstPointList.java +++ /dev/null @@ -1,27 +0,0 @@ -// ConstPointList.java - -package hexgui.hex; - -import java.util.Iterator; - -/** Const functions of go.PointList. - @see PointList */ -public interface ConstPointList - extends Iterable -{ - boolean contains(Object elem); - - boolean equals(Object object); - - HexPoint get(int index); - - int hashCode(); - - boolean isEmpty(); - - Iterator iterator(); - - int size(); - - String toString(); -} diff --git a/src/hexgui/hex/HexColor.java b/src/hexgui/hex/HexColor.java deleted file mode 100644 index bd19fe5..0000000 --- a/src/hexgui/hex/HexColor.java +++ /dev/null @@ -1,55 +0,0 @@ -package hexgui.hex; - -/** Possible states of a cell on a Hex board (black, white, or empty). */ -public final class HexColor -{ - public static final HexColor EMPTY; - public static final HexColor WHITE; - public static final HexColor BLACK; - - /** Returns a string representation of this color. - @return "black", "white", or "empty". - */ - public String toString() - { - return m_string; - } - - /** Returns the other opposite color. - @return WHITE for BLACK, BLACK for WHITE, EMPTY otherwise. - */ - public HexColor otherColor() - { - return m_otherColor; - } - - public static HexColor get(String name) - { - if (name.equals("black")) return BLACK; - if (name.equals("white")) return WHITE; - if (name.equals("empty")) return EMPTY; - return null; - } - - private final String m_string; - private HexColor m_otherColor; - - static - { - BLACK = new HexColor("black"); - WHITE = new HexColor("white"); - EMPTY = new HexColor("empty"); - BLACK.setOtherColor(WHITE); - WHITE.setOtherColor(BLACK); - EMPTY.setOtherColor(EMPTY); - } - - private HexColor(String string) - { - m_string = string; - } - private void setOtherColor(HexColor color) - { - m_otherColor = color; - } -} diff --git a/src/hexgui/hex/HexPoint.java b/src/hexgui/hex/HexPoint.java deleted file mode 100644 index 838f75d..0000000 --- a/src/hexgui/hex/HexPoint.java +++ /dev/null @@ -1,185 +0,0 @@ -package hexgui.hex; - -import java.lang.Exception; -import java.lang.NumberFormatException; -import java.awt.Dimension; - -/** A cell on a Hex board. - In addition to each playable cell, HexPoints are created for each edge of - the board and for some special cases like swap moves, resignations, and - forfeitures. */ -public final class HexPoint implements Comparable -{ - /** Exception. */ - public static class InvalidHexPointException - extends Exception - { - public InvalidHexPointException(String message) - { - super("Invalid point: " + message); - } - } - - public static final HexPoint NORTH; - public static final HexPoint SOUTH; - public static final HexPoint WEST; - public static final HexPoint EAST; - public static final HexPoint SWAP_SIDES; - public static final HexPoint SWAP_PIECES; - public static final HexPoint PASS; - public static final HexPoint RESIGN; - public static final HexPoint FORFEIT; - public static final HexPoint INVALID; - - public static final int MAX_WIDTH = 19; - public static final int MAX_HEIGHT = 19; - public static final int MAX_POINTS = MAX_WIDTH*MAX_HEIGHT + 10; - - public static final int DEFAULT_SIZE = 11; - - private static HexPoint s_points[]; - - static - { - s_points = new HexPoint[MAX_POINTS]; - - INVALID = s_points[0] = new HexPoint(0, "invalid"); - RESIGN = s_points[1] = new HexPoint(1, "resign"); - FORFEIT = s_points[2] = new HexPoint(2, "forfeit"); - SWAP_SIDES = s_points[3] = new HexPoint(3, "swap-sides"); - SWAP_PIECES = s_points[4] = new HexPoint(4, "swap-pieces"); - PASS = s_points[5] = new HexPoint(5, "pass"); - - NORTH = s_points[6] = new HexPoint(6, "north"); - EAST = s_points[7] = new HexPoint(7, "east"); - SOUTH = s_points[8] = new HexPoint(8, "south"); - WEST = s_points[9] = new HexPoint(9, "west"); - - for (int y=0; y= 0); - assert(i < MAX_POINTS); - return s_points[i]; - } - - /** Returns the point with the given coordinates. Note that it is - not possible to obtain points for board edges and special - moves with this method. Use the get(String) - method for these types of points. - - @param x x-coordinate of point - @param y y-coordinate of point - @return point with coordinates (x,y). - */ - public static HexPoint get(int x, int y) - { - assert(x >= 0); - assert(y >= 0); - assert(x < MAX_WIDTH); - assert(y < MAX_HEIGHT); - return s_points[10 + y*MAX_WIDTH + x]; - } - - /** Returns the point with the given string representation. - Valid special moves include: "north", "south", "east", "west" - "swap-sides", "swap-pieces", "pass", "resign", and "forfeit". - @param name The name of the point to return - @return the point or null if name is invalid. - */ - public static HexPoint get(String name) - { - if (name.equalsIgnoreCase("swap")) - return SWAP_SIDES; - - for (int x=0; x= 0; - } - - - - public final int x, y; - private final String m_string; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/hex/Move.java b/src/hexgui/hex/Move.java deleted file mode 100644 index a7c4067..0000000 --- a/src/hexgui/hex/Move.java +++ /dev/null @@ -1,63 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.hex; - -//---------------------------------------------------------------------------- - -/** Move. - Contains a HexPoint and a HexColor. To construct swap moves or - other special moves use the appropriate HexPoint. Immutable. -*/ -public final class Move -{ - /** Constructs a move with the given point and color. - @param p location of move - @param c black or white. - */ - public Move(HexPoint p, HexColor c) - { - m_point = p; - m_color = c; - } - - /** Convert to string */ - public String toString() - { - return "Move(" + m_point + ", " + m_color + ")"; - } - - /** Determines whether this move is equal to the given move. - @param other move to compare it to. - @return true if points and colors are equal, false otherwise. - */ - public boolean equals(Move other) - { - if (m_point == other.getPoint() && m_color == other.getColor()) - return true; - return false; - } - - /** Returns the point of this move. - @return HexPoint of the location. - */ - public HexPoint getPoint() - { - return m_point; - } - - /** Returns the color of the move. - @return HexColor of the move (WHITE or BLACK or, in case of a - setup move, sometimes EMPTY). - */ - public HexColor getColor() - { - return m_color; - } - - private final HexPoint m_point; - private final HexColor m_color; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/hex/PointList.java b/src/hexgui/hex/PointList.java deleted file mode 100644 index 6e0f7df..0000000 --- a/src/hexgui/hex/PointList.java +++ /dev/null @@ -1,130 +0,0 @@ -// PointList.java - -package hexgui.hex; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.Vector; - -/** List containing points. */ -public final class PointList - extends ArrayList - implements ConstPointList -{ - public class ConstIterator - implements Iterator - { - public boolean hasNext() - { - return (m_index < size()); - } - - public HexPoint next() - { - return get(m_index++); - } - - public void remove() - { - throw new UnsupportedOperationException(); - } - - private int m_index; - } - - /** Construct empty point list. */ - public PointList() - { - this(0); - } - - /** Construct empty point list with initial capacity. - @param initialCapacity The number of points to reserve memory for. */ - public PointList(int initialCapacity) - { - super(initialCapacity); - } - - /** Construct point list with a single element. - @param p The initial point element. */ - public PointList(HexPoint p) - { - this(1); - add(p); - } - - /** Construct point list as a copy of another point list. - @param list The list to copy the points from. */ - public PointList(ConstPointList list) - { - super((PointList)list); - } - - public PointList(Vector v) - { - for (int i = 0; i < v.size(); i++) - add(v.get(i)); - } - - /** Add points of another list at the end of this list. */ - public void addAllFromConst(ConstPointList list) - { - addAll((PointList)list); - } - - /** Get an empty constant point list. - Can be used at places where an empty temporary point list is needed - that is never modified to avoid memory allocation. */ - public static ConstPointList getEmptyList() - { - return EMPTY_LIST; - } - - /** Returns an iterator over the points elements in this list. - An iterator of type PointList.ConstIterator is returned, which - does not support Iterator.remove(), to allow for-each-loops for - ConstPointList references. */ - public Iterator iterator() - { - return new ConstIterator(); - } - - /** Remove and return last element. - Requires that list is not empty. */ - public HexPoint pop() - { - int index = size() - 1; - if (index < 0) - { - assert false; - return null; - } - HexPoint p = get(index); - remove(index); - return p; - } - - public String toString() - { - StringBuilder buffer = new StringBuilder(); - for (int i = 0; i < size(); ++i) - { - if (i > 0) - buffer.append(' '); - buffer.append(get(i)); - } - return buffer.toString(); - } - - /** Convert point list to string. - Null arguments will be converted to an empty string. */ - public static String toString(ConstPointList list) - { - if (list == null) - return ""; - else - return list.toString(); - } - - private static final ConstPointList EMPTY_LIST = new PointList(); -} diff --git a/src/hexgui/hex/VC.java b/src/hexgui/hex/VC.java deleted file mode 100644 index 067570e..0000000 --- a/src/hexgui/hex/VC.java +++ /dev/null @@ -1,107 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.hex; - -import java.util.Vector; - -//---------------------------------------------------------------------------- - -/** - VC. - A connection between two cells. -*/ -public class VC -{ - /** Constructs a VC. */ - public VC(HexPoint from, HexPoint to, HexColor c, String type) - { - this(from, to, c, type, "unknown", 0, - new Vector(), - new Vector(), - new Vector()); - } - - public VC(HexPoint from, HexPoint to, - HexColor c, String type, - String source, int moves, - Vector carrier, - Vector stones, - Vector key) - { - m_from = from; - m_to = to; - m_color = c; - m_type = type; - m_source = source; - m_moves = moves; - m_carrier = carrier; - m_stones = stones; - m_key = key; - } - - public String toString() - { - StringBuilder ret = new StringBuilder(); - - if (m_type.equals("softlimit")) { - return "---------------- softlimit ----------------"; - } - - ret.append(m_from.toString()); - ret.append(" "); - ret.append(m_to.toString()); - ret.append(" "); - ret.append(m_color.toString()); - ret.append(" "); - ret.append(m_type); - ret.append(" "); - ret.append(m_source); - ret.append(" "); - ret.append(Integer.toString(m_moves)); - ret.append(" "); - - ret.append("["); - for (int i=0; i getCarrier() { return m_carrier; } - public Vector getStones() { return m_stones; } - public Vector getKey() { return m_key; } - public String getSource() { return m_source; } - - private HexPoint m_from; - private HexPoint m_to; - private HexColor m_color; - private String m_type; - private int m_moves; - private Vector m_carrier; - private Vector m_stones; - private Vector m_key; - private String m_source; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/htp/AnalyzeCommand.java b/src/hexgui/htp/AnalyzeCommand.java deleted file mode 100644 index d378c79..0000000 --- a/src/hexgui/htp/AnalyzeCommand.java +++ /dev/null @@ -1,272 +0,0 @@ -// AnalyzeCommand.java - -package hexgui.htp; - -import java.io.File; -import hexgui.hex.ConstPointList; -import hexgui.hex.HexColor; -import static hexgui.hex.HexColor.BLACK; -import static hexgui.hex.HexColor.WHITE; -import hexgui.hex.HexPoint; -import hexgui.hex.PointList; - -/** Concrete analyze command including data for wildcard replacements. - See GoGui documentation, chapter "Analyze Commands" */ -public class AnalyzeCommand -{ - public AnalyzeCommand(AnalyzeDefinition definition) - { - m_definition = definition; - } - - public String getLabel() - { - return m_definition.getLabel(); - } - - public String getCommand() - { - return m_definition.getCommand(); - } - - public HexColor getColorArg() - { - return m_colorArg; - } - - public HexPoint getPointArg() - { - return m_pointArg; - } - - public PointList getPointListArg() - { - return m_pointListArg; - } - - public AnalyzeType getType() - { - return m_definition.getType(); - } - - public String getResultTitle() - { - StringBuilder buffer = new StringBuilder(getLabel()); - if (needsColorArg() && m_colorArg != null) - { - if (m_colorArg == BLACK) - buffer.append(" Black"); - else - { - assert m_colorArg == WHITE; - buffer.append(" White"); - } - } - if (needsPointArg() && m_pointArg != null) - { - buffer.append(' '); - buffer.append(m_pointArg); - } - else if (needsPointListArg()) - { - for (HexPoint p : m_pointListArg) - { - buffer.append(' '); - buffer.append(p); - } - } - if (needsStringArg() && m_stringArg != null) - { - buffer.append(' '); - buffer.append(m_stringArg); - } - return buffer.toString(); - } - - public boolean isPointArgMissing() - { - if (needsPointArg()) - return (m_pointArg == null); - if (needsPointListArg()) - return m_pointListArg.isEmpty(); - return false; - } - - public boolean isTextType() - { - return m_definition.isTextType(); - } - - public boolean needsColorArg() - { - return m_definition.needsColorArg(); - } - - public boolean needsFileArg() - { - return m_definition.needsFileArg(); - } - - public boolean needsFileOpenArg() - { - return m_definition.needsFileOpenArg(); - } - - public boolean needsFileSaveArg() - { - return m_definition.needsFileSaveArg(); - } - - public boolean needsOnlyPointArg() - { - return m_definition.needsOnlyPointArg(); - } - - public boolean needsOnlyPointAndColorArg() - { - return m_definition.needsOnlyPointAndColorArg(); - } - - public boolean needsPointArg() - { - return m_definition.needsPointArg(); - } - - public boolean needsPointListArg() - { - return m_definition.needsPointListArg(); - } - - public boolean needsStringArg() - { - return m_definition.needsStringArg(); - } - - public boolean needsOptStringArg() - { - return m_definition.needsOptStringArg(); - } - - public String replaceWildCards(HexColor toMove) - { - String command = m_definition.getCommand(); - String toMoveString = (toMove == BLACK ? "b" : "w"); - String result = command.replace("%m", toMoveString); - if (needsPointArg() && m_pointArg != null) - result = result.replace("%p", m_pointArg.toString()); - if (needsPointListArg()) - { - String pointList = HexPoint.toString(m_pointListArg); - if (getType() == AnalyzeType.EPLIST - && m_pointListArg.size() > 0) - result = result + ' ' + pointList; - else - result = result.replace("%P", pointList); - } - if (needsFileArg()) - { - String fileArg = m_fileArg.toString(); - if (fileArg.indexOf(' ') >= 0) - fileArg = "\"" + fileArg + "\""; - result = result.replace("%f", fileArg); - } - if (needsFileOpenArg()) - { - String fileOpenArg = m_fileOpenArg.toString(); - if (fileOpenArg.indexOf(' ') >= 0) - fileOpenArg = "\"" + fileOpenArg + "\""; - result = result.replace("%r", fileOpenArg); - } - if (needsFileSaveArg()) - { - String fileSaveArg = m_fileSaveArg.toString(); - if (fileSaveArg.indexOf(' ') >= 0) - fileSaveArg = "\"" + fileSaveArg + "\""; - result = result.replace("%w", fileSaveArg); - } - if (needsStringArg()) - { - assert m_stringArg != null; - result = result.replace("%s", m_stringArg); - } - if (needsOptStringArg()) - { - assert m_optStringArg != null; - result = result.replace("%o", m_optStringArg); - } - if (needsColorArg()) - { - String colorString = "empty"; - if (m_colorArg == BLACK) - colorString = "b"; - else if (m_colorArg == WHITE) - colorString = "w"; - result = result.replace("%c", colorString); - } - return result; - } - - public void setColorArg(HexColor color) - { - assert needsColorArg(); - m_colorArg = color; - } - - public void setFileArg(File file) - { - assert needsFileArg(); - m_fileArg = file; - } - - public void setFileOpenArg(File file) - { - assert needsFileOpenArg(); - m_fileOpenArg = file; - } - - public void setFileSaveArg(File file) - { - assert needsFileSaveArg(); - m_fileSaveArg = file; - } - - public void setPointArg(HexPoint point) - { - m_pointArg = point; - } - - public void setPointListArg(ConstPointList pointList) - { - m_pointListArg = new PointList(pointList); - } - - public void setStringArg(String value) - { - assert needsStringArg(); - m_stringArg = value; - } - - public void setOptStringArg(String value) - { - assert needsOptStringArg(); - m_optStringArg = value; - } - - private final AnalyzeDefinition m_definition; - - private HexColor m_colorArg; - - private File m_fileArg; - - private File m_fileOpenArg; - - private File m_fileSaveArg; - - private String m_optStringArg; - - private String m_stringArg; - - private HexPoint m_pointArg; - - private PointList m_pointListArg = new PointList(); -} diff --git a/src/hexgui/htp/AnalyzeDefinition.java b/src/hexgui/htp/AnalyzeDefinition.java deleted file mode 100644 index dda889e..0000000 --- a/src/hexgui/htp/AnalyzeDefinition.java +++ /dev/null @@ -1,257 +0,0 @@ -// AnalyzeDefinition.java - -package hexgui.htp; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.IOException; -import java.io.Reader; -import java.io.StringReader; -import java.net.URL; -import java.util.ArrayList; -import hexgui.util.ErrorMessage; -import hexgui.util.StringUtils; - -/** Definition of an analyze command. - See GoGui documentation, chapter "Analyze Commands". - This class is immutable. */ -public class AnalyzeDefinition -{ - public AnalyzeDefinition(String line) - { - String array[] = line.split("/"); - String typeStr = array[0]; - if (typeStr.equals("bwboard")) - m_type = AnalyzeType.BWBOARD; - else if (typeStr.equals("cboard")) - m_type = AnalyzeType.CBOARD; - else if (typeStr.equals("dboard")) - m_type = AnalyzeType.DBOARD; - else if (typeStr.equals("eplist")) - m_type = AnalyzeType.EPLIST; - else if (typeStr.equals("gfx")) - m_type = AnalyzeType.GFX; - else if (typeStr.equals("group")) - m_type = AnalyzeType.GROUP; - else if (typeStr.equals("hstring")) - m_type = AnalyzeType.HSTRING; - else if (typeStr.equals("hpstring")) - m_type = AnalyzeType.HPSTRING; - else if (typeStr.equals("inferior")) - m_type = AnalyzeType.INFERIOR; - else if (typeStr.equals("move")) - m_type = AnalyzeType.MOVE; - else if (typeStr.equals("param")) - m_type = AnalyzeType.PARAM; - else if (typeStr.equals("plist")) - m_type = AnalyzeType.PLIST; - else if (typeStr.equals("pspairs")) - m_type = AnalyzeType.PSPAIRS; - else if (typeStr.equals("pstring")) - m_type = AnalyzeType.PSTRING; - else if (typeStr.equals("string")) - m_type = AnalyzeType.STRING; - else if (typeStr.equals("sboard")) - m_type = AnalyzeType.SBOARD; - else if (typeStr.equals("var")) - m_type = AnalyzeType.VAR; - else if (typeStr.equals("varb")) - m_type = AnalyzeType.VARB; - else if (typeStr.equals("varc")) - m_type = AnalyzeType.VARC; - else if (typeStr.equals("varp")) - m_type = AnalyzeType.VARP; - else if (typeStr.equals("varpo")) - m_type = AnalyzeType.VARPO; - else if (typeStr.equals("varw")) - m_type = AnalyzeType.VARW; - else if (typeStr.equals("vc")) - m_type = AnalyzeType.VC; - else - m_type = AnalyzeType.NONE; - m_label = array[1]; - m_command = array[2]; - } - - public AnalyzeDefinition(AnalyzeType type, String label, String command) - { - m_type = type; - m_label = label; - m_command = command; - } - - public String getCommand() - { - return m_command; - } - - public String getLabel() - { - return m_label; - } - - public AnalyzeType getType() - { - return m_type; - } - - /** Should the response be shown as text. - Returns true for types that should be shown (not necessarily only) - as text to the user. - That is string and variation commands. */ - public boolean isTextType() - { - return m_type == AnalyzeType.STRING - || m_type == AnalyzeType.HSTRING - || m_type == AnalyzeType.HPSTRING - || m_type == AnalyzeType.PSTRING - || m_type == AnalyzeType.VAR - || m_type == AnalyzeType.VARC - || m_type == AnalyzeType.VARW - || m_type == AnalyzeType.VARB - || m_type == AnalyzeType.VARP - || m_type == AnalyzeType.VARPO; - } - - public boolean needsColorArg() - { - return (m_command.indexOf("%c") >= 0); - } - - public boolean needsFileArg() - { - return (m_command.indexOf("%f") >= 0); - } - - public boolean needsFileOpenArg() - { - return (m_command.indexOf("%r") >= 0); - } - - public boolean needsFileSaveArg() - { - return (m_command.indexOf("%w") >= 0); - } - - public boolean needsOnlyPointArg() - { - return (needsPointArg() - && ! needsColorArg() - && ! needsFileArg() - && ! needsFileOpenArg() - && ! needsFileSaveArg() - && ! needsPointListArg() - && ! needsStringArg() - && ! needsOptStringArg()); - } - - public boolean needsOnlyPointAndColorArg() - { - return (needsPointArg() && needsColorArg() - && ! needsFileArg() - && ! needsFileOpenArg() - && ! needsFileSaveArg() - && ! needsPointListArg() - && ! needsStringArg() - && ! needsOptStringArg()); - } - - public boolean needsPointArg() - { - return (m_command.indexOf("%p") >= 0); - } - - public boolean needsPointListArg() - { - return (m_command.indexOf("%P") >= 0 || m_type == AnalyzeType.EPLIST); - } - - public boolean needsStringArg() - { - return (m_command.indexOf("%s") >= 0); - } - - public boolean needsOptStringArg() - { - return (m_command.indexOf("%o") >= 0); - } - - public static ArrayList - read(String programAnalyzeCommands) - throws ErrorMessage - { - if (programAnalyzeCommands == null) - throw new ErrorMessage("no analyze commands!"); - - Reader stringReader = new StringReader(programAnalyzeCommands); - BufferedReader reader = new BufferedReader(stringReader); - return readConfig(reader, - "program response to hexgui-analyze_commands", - null); - } - - private final AnalyzeType m_type; - - private final String m_label; - - private final String m_command; - - private static ArrayList - readConfig(BufferedReader reader, String name, - ArrayList supportedCommands) throws ErrorMessage - { - ArrayList result - = new ArrayList(); - ArrayList labels = new ArrayList(); - try - { - String line; - int lineNumber = 0; - while ((line = reader.readLine()) != null) - { - ++lineNumber; - line = line.trim(); - if (line.length() > 0 && line.charAt(0) != '#') - { - String array[] = line.split("/"); - if (array.length < 3 || array.length > 5) - throw new ErrorMessage("Error in " + name + " line " - + lineNumber); - if (supportedCommands != null) - { - String[] cmdArray - = StringUtils.splitArguments(array[2].trim()); - if (cmdArray.length == 0 - || ! supportedCommands.contains(cmdArray[0])) - continue; - } - String label = array[1]; - if (labels.contains(label)) - continue; - labels.add(label); - result.add(new AnalyzeDefinition(line)); - } - } - return result; - } - catch (IOException e) - { - throw new ErrorMessage("Error reading " + name); - } - finally - { - try - { - reader.close(); - } - catch (IOException e) - { - throw new ErrorMessage("Error reading " + name); - } - } - } -} diff --git a/src/hexgui/htp/AnalyzeType.java b/src/hexgui/htp/AnalyzeType.java deleted file mode 100644 index 47680de..0000000 --- a/src/hexgui/htp/AnalyzeType.java +++ /dev/null @@ -1,55 +0,0 @@ -// AnalyzeType.java - -package hexgui.htp; - -/** Type of an analyze command. */ -public enum AnalyzeType -{ - BWBOARD, - - CBOARD, - - DBOARD, - - EPLIST, - - GFX, - - GROUP, - - HSTRING, - - HPSTRING, - - INFERIOR, - - MOVE, - - NONE, - - PARAM, - - PLIST, - - PSTRING, - - PSPAIRS, - - STRING, - - SBOARD, - - VAR, - - VARB, - - VARC, - - VARP, - - VARPO, - - VARW, - - VC -} diff --git a/src/hexgui/htp/AnalyzeUtil.java b/src/hexgui/htp/AnalyzeUtil.java deleted file mode 100644 index 75ec916..0000000 --- a/src/hexgui/htp/AnalyzeUtil.java +++ /dev/null @@ -1,87 +0,0 @@ -// AnalyzeUtil.java - -package hexgui.htp; - -import java.util.ArrayList; -import java.util.Scanner; -import java.util.NoSuchElementException; - -public final class AnalyzeUtil -{ - /** Result of AnalyzeUtil.parseParameterLine(). */ - public static final class Result - { - public ParameterType m_type; - - /** Complete type metainformation. */ - public String m_typeInfo; - - public String m_key; - - public String m_value; - } - - /** Get command for setting a parameter. - See chapter "Analyze Commands" of the GoGui documentation. */ - public static String getParameterCommand(String command, String key, - String value) - { - return command + " " + key + " " + value + "\n"; - } - - /** Parse a line in the response of an analyze command of type "param". - See chapter "Analyze Commands" of the GoGui documentation. - @return The result or null, if line could not be parsed. */ - public static Result parseParameterLine(String line) - { - line = line.trim(); - if (line.startsWith("[") && line.endsWith("]")) - { - // Might be used as label for grouping parameters on tabbing - // panes in a later version of GoGui, so we silently accept it - return null; - } - if (line.length() == 0) - return null; - Scanner scanner = new Scanner(line); - Result result = new Result(); - try - { - result.m_typeInfo = scanner.next("^\\[[^\\]]*\\]"); - line = line.substring(result.m_typeInfo.length()).trim(); - result.m_typeInfo = - result.m_typeInfo.substring(1, result.m_typeInfo.length() - 1); - } - catch (NoSuchElementException e) - { - // Treat unknown types as string for compatibility with future - // types - result.m_typeInfo = "string"; - } - int pos = line.indexOf(' '); - if (pos < 0) - { - result.m_key = line.trim(); - result.m_value = ""; - } - else - { - result.m_key = line.substring(0, pos).trim(); - result.m_value = line.substring(pos + 1).trim(); - } - if (result.m_typeInfo.equals("bool")) - result.m_type = ParameterType.BOOL; - else if (result.m_typeInfo.startsWith("list/")) - result.m_type = ParameterType.LIST; - else - // Treat unknown types as string for compatibility with future - // types - result.m_type = ParameterType.STRING; - return result; - } - - /** Make constructor unavailable; class is for namespace only. */ - private AnalyzeUtil() - { - } -} diff --git a/src/hexgui/htp/HtpController.java b/src/hexgui/htp/HtpController.java deleted file mode 100644 index ff9e9c5..0000000 --- a/src/hexgui/htp/HtpController.java +++ /dev/null @@ -1,212 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.htp; - -import hexgui.hex.HexPoint; -import hexgui.util.Pair; -import hexgui.util.StringUtils; - -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.io.BufferedReader; -import java.io.PrintStream; -import java.io.IOException; -import java.util.Vector; - -//---------------------------------------------------------------------------- - -/** Sends HTP commands and parses the response. */ -public class HtpController -{ - public interface IOInterface - { - void sentCommand(String str); - void receivedResponse(String str); - void receivedError(String str); - } - - //------------------------------------------------------------ - - public interface GuiFxCallback - { - void guifx(String cmd); - } - - //------------------------------------------------------------ - - /** Constructor */ - public HtpController(InputStream in, OutputStream out, - IOInterface io, GuiFxCallback guifx) - { - System.out.println("controller: in constructor."); - m_in = new BufferedReader(new InputStreamReader(in)); - m_out = new PrintStream(out); - m_io = io; - m_guifx = guifx; - m_connected = true; - m_waiting = false; - } - - public void interrupt() - { - System.out.println("Sending interrupt"); - m_out.print("# interrupt\n"); - m_out.flush(); - m_io.sentCommand("# interrupt"); - } - - /** Sends command over the htp channel. - - Note this method is synchronized, and so HtpController will - process only a single command at a time. - */ - public synchronized void sendCommand(String cmd) - throws HtpError - { - if (!m_connected) - return; - - System.out.println("controller: sending '" + cmd.trim() + "'"); - m_out.print(cmd); - m_out.flush(); - m_io.sentCommand(cmd); - handleResponse(); - } - - public boolean cmdInProgress() { return m_waiting; } - - public boolean wasSuccess() { return m_success; } - - public String getResponse() { return m_response; } - - private void handleResponse() throws HtpError - { - m_waiting = true; - - while (m_waiting) { - - String response; - try { - response = waitResponse(); - } - catch (IOException e) { - m_waiting = false; - throw new HtpError("IOException waiting for response!"); - } - - // Since the response must, by definition of the GTP - // protocol, always end with two newline characters, - // remove them. - response = response.replaceAll("[\n\r]$", ""); - - //System.out.println("got: '" + response + "'"); - - if (!m_connected) { - m_success = false; - m_response = ""; - m_waiting = false; - throw new HtpError("Program Disconnected."); - } - else if (response == null) { - m_success = false; - m_response = ""; - m_waiting = false; - throw new HtpError("Null response received!"); - } else if (response.length() < 2) { - m_success = false; - m_response = response; - m_waiting = false; - throw new HtpError("Response length too short! '"+response+"'"); - } else if (response.length() > 10 && - response.substring(0, 10).equals("gogui-gfx:")) - { - - String fx - = StringUtils.cleanWhiteSpace(response.substring(10).trim()); - - m_guifx.guifx(fx); - - } else if (response.substring(0,2).equals("= ")) { - m_success = true; - m_response = response.substring(2); - System.out.print("controller: success: "); - m_io.receivedResponse(response); - m_waiting = false; - } else if (response.substring(0,2).equals("? ")) { - m_success = false; - m_response = response.substring(2); - System.out.print("controller: error: "); - m_io.receivedError(response); - m_waiting = false; - } else { - m_response = response; - m_success = false; - System.out.print("controller: invalid: "); - m_waiting = false; - throw new HtpError("Invalid HTP response:'" + response + "'."); - } - } - - System.out.println("'" + m_response.trim() + "'"); - } - - private String waitResponse() throws IOException - { - StringBuilder ret = new StringBuilder(); - while (true) { - //System.out.println("blocking on response"); - String line = m_in.readLine(); - //System.out.println("readline: '" + line + "'"); - if (line == null) { - System.out.println("controller: Disconnected!"); - m_connected = false; - break; - } - String clean = cleanInput(line); - ret.append(clean); - ret.append('\n'); - - if (clean.equals("")) - break; - } - System.out.println("controller: done waiting on response."); - return ret.toString(); - } - - /** Cleans the input. Removes all occurrences of '\r'. - Converts all '\t' to ' '. - */ - private String cleanInput(String in) - { - StringBuilder out = new StringBuilder(); - for (int i=0; i(); - m_swap_bug = false; - try { - findGameTree(); - m_gametree = parseGameTree(null, true); - m_reader.close(); - } - catch (IOException e) { - throw sgfError("IO error occurred while parsing file."); - } - } - - public Node getGameTree() - { - return m_gametree; - } - - public GameInfo getGameInfo() - { - return m_gameinfo; - } - - public Vector getWarnings() - { - if (m_warnings.size() == 0) - return null; - return m_warnings; - } - - //------------------------------------------------------------ - - /** Fast forward to the first "(". */ - private void findGameTree() throws SgfError, IOException - { - while (true) { - int ttype = m_tokenizer.nextToken(); - if (ttype == StreamTokenizer.TT_EOF) - throw sgfError("No game tree found!"); - - if (ttype == '(') { - m_tokenizer.pushBack(); - break; - } - } - } - - private Node parseGameTree(Node parent, boolean isroot) - throws SgfError, IOException - { - int ttype = m_tokenizer.nextToken(); - if (ttype != '(') - throw sgfError("Missing '(' at head of game tree."); - - Node node = parseNode(parent, isroot); - - ttype = m_tokenizer.nextToken(); - if (ttype != ')') - throw sgfError("Game tree not closed!"); - - return node; - } - - private Node parseNode(Node parent, boolean isroot) - throws SgfError, IOException - { - int ttype = m_tokenizer.nextToken(); - if (ttype != ';') - throw sgfError("Error at head of node!"); - - Node node = new Node(); - node.setParent(parent); - if (parent != null) - parent.addChild(node); - - boolean done = false; - while (!done) { - ttype = m_tokenizer.nextToken(); - switch(ttype) { - case '(': - m_tokenizer.pushBack(); - parseGameTree(node, false); - break; - - case ';': - m_tokenizer.pushBack(); - parseNode(node, false); - done = true; - break; - - case ')': - m_tokenizer.pushBack(); - done = true; - break; - - case StreamTokenizer.TT_WORD: - parseProperty(node, isroot); - break; - - case StreamTokenizer.TT_EOF: - throw sgfError("Unexpected EOF in node!"); - - default: - throw sgfError("Error in SGF file."); - } - } - - return node; - } - - /** Parse a point or move value. - Supports both standard SGF notation for Hex (a1, ...) and Go-like - notation used by Little Golem (aa, ...) */ - private HexPoint parsePoint(String s) throws SgfError - { - s = s.trim().toLowerCase(Locale.ENGLISH); - HexPoint result = null; - if (s.length() >= 2) - { - if (s.length() == 2 && s.charAt(1) >= 'a' && s.charAt(1) <= 'z') - { - // Go-like SGF notation - int x = s.charAt(0) - 'a'; - int y = s.charAt(1) - 'a'; - if (x < HexPoint.MAX_WIDTH && y < HexPoint.MAX_HEIGHT) - result = HexPoint.get(x, y); - } - else - { - // Standard SGF notation for Hex - try - { - int x = s.charAt(0) - 'a'; - int y = Integer.parseInt(s.substring(1)) - 1; - if (y >= 0 && y < HexPoint.MAX_HEIGHT) - result = HexPoint.get(x, y); - } - catch (NumberFormatException e) - { - } - } - } - if (result == null) - throw sgfError(format("Invalid point {0}", s)); - return result; - } - - private HexPoint parseMove(String s) throws SgfError - { - s = s.trim().toLowerCase(Locale.ENGLISH); - - // Special case: some or all versions of HexGui up to 0.9.GIT - // incorrectly used "swap-pieces" instead of "swap-sides". - // The SGF specification states: - // - // * swap-sides - the player elects to swap sides with his - // opponent; if he was playing Black he now plays White, and - // vice versa. - // - // * swap-pieces - the player elects to swap pieces with his - // opponent - all of Black's pieces are colored White, and - // White's pieces are colored Black. Then the entire board - // is reflected in the long diagonal axis. - // - // For backward compatibility, we must compensate for the - // incorrect use of "swap-pieces" when reading SGF files - // written by HexGui 0.9.GIT or earlier. - - if (m_swap_bug && s.equals("swap-pieces")) { - s = "swap-sides"; - } - - // HexPoint.get() handles special move values like "swap" - HexPoint result = HexPoint.get(s); - if (result == null) - // Handles Go-style point notation (aa, ...) - result = parsePoint(s); - return result; - } - - private void parseProperty(Node node, boolean isroot) - throws SgfError, IOException - { - int x,y; - String name = m_tokenizer.sval; - - boolean done = false; - while(!done) { - - int ttype = m_tokenizer.nextToken(); - if (ttype != '[') { - done = true; - } - m_tokenizer.pushBack(); - if (done) { - break; - } - - String val; - if (name.equals("C")) { - val = parseComment(); - } else { - val = parseValue(); - } - //System.out.println(name + "[" + val + "]"); - - if (name.equals("W")) { - HexPoint point = parseMove(val); - node.setMove(new Move(point, HexColor.WHITE)); - } - else if (name.equals("B")) { - HexPoint point = parseMove(val); - node.setMove(new Move(point, HexColor.BLACK)); - } - else if (name.equals("AB")) { - node.addSetup(HexColor.BLACK, parsePoint(val)); - } - else if (name.equals("AW")) { - node.addSetup(HexColor.WHITE, parsePoint(val)); - } - else if (name.equals("AE")) { - node.addSetup(HexColor.EMPTY, parsePoint(val)); - } - else if (name.equals("LB")) { - node.addLabel(val); - } - else if (name.equals("FF")) { - node.setSgfProperty(name, val); - x = parseInt(val); - if (x < 1 || x > 4) - throw sgfError("Invalid SGF Version! (" + x + ")"); - } - else if (name.equals("GM")) { - node.setSgfProperty(name, val); - if (!isroot) sgfWarning("GM property in non-root node!"); - if (parseInt(val) != GM_HEXGAME) throw sgfError("Not a Hex game!"); - } - else if (name.equals("SZ")) { - node.setSgfProperty(name, val); - if (!isroot) sgfWarning("GM property in non-root node!"); - Dimension dim = new Dimension(); - String sp[] = val.split(":"); - if (sp.length == 1) { - x = parseInt(sp[0]); - dim.setSize(x,x); - } else if (sp.length == 2) { - x = parseInt(sp[0]); - y = parseInt(sp[1]); - dim.setSize(x,y); - } else { - throw sgfError("Malformed boardsize!"); - } - m_gameinfo.setBoardSize(dim); - } - else if (name.equals("AP")) { - node.setSgfProperty(name, val); - String regex = "HexGui:0\\.[0-9](\\z|[^0-9].*)"; - if (val.matches(regex)) { - // version HexGui:0.9 or earlier - m_swap_bug = true; - } - } - else { - node.setSgfProperty(name, val); - } - } - } - - // Parse an SGF "SimpleText" property value. - private String parseValue() throws SgfError, IOException - { - int ttype = m_tokenizer.nextToken(); - if (ttype != '[') { - throw sgfError("Property missing opening '['."); - } - - StringBuilder sb = new StringBuilder(256); - boolean quoted = false; - while (true) { - int ch = m_reader.read(); - if (ch < 0) { - throw sgfError("Property runs to EOF."); - } - // Don't rely on the default conversion, because - // sb.append(ch) would convert the integer to a string - // rather than a character. - char c = (char)ch; - - if (!quoted) { - if (c == ']') { - break; - } - if (c == '\\') { - quoted = true; - } else { - if (Character.isWhitespace(c)) { - // The spec says "Whitespaces other than space - // must be converted to space". - sb.append(' '); - } else { - sb.append(c); - } - } - } else { - quoted = false; - if (Character.isWhitespace(c)) { - // The spec says "Any char following "\" is - // inserted verbatim (exception: whitespaces still - // have to be converted to space!)." - sb.append(' '); - } else { - sb.append(c); - } - } - } - - return sb.toString(); - } - - // Parse an SGF "Text" property value. - private String parseComment() throws SgfError, IOException - { - int ttype = m_tokenizer.nextToken(); - if (ttype != '[') { - throw sgfError("Comment missing opening '['."); - } - - StringBuilder sb = new StringBuilder(4096); - boolean quoted = false; - while (true) { - int ch = m_reader.read(); - if (ch < 0) { - throw sgfError("Comment runs to EOF."); - } - char c = (char)ch; - - if (!quoted) { - if (c == ']') { - break; - } - - if (c == '\\') { - quoted = true; - } else { - if (Character.isWhitespace(c) && c != '\n') { - // The spec says "White spaces other than - // linebreaks are converted to space (e.g. no - // tab, vertical tab, ..)." - - // Also note that the LineNumberReader class - // already takes care of newline conversion, - // i.e., "\n", "\r", and "\r\n" are all - // converted to '\n'. - sb.append(' '); - } else { - sb.append(c); - } - } - } else { - quoted = false; - if (c == '\n') { - // Append nothing. The spec says: "Soft line - // break: linebreaks preceded by a "\" (soft - // linebreaks are converted to "", i.e. they are - // removed)". - } else if (Character.isWhitespace(c)) { - // The spec says: "Any char following "\" is - // inserted verbatim (exception: whitespaces still - // have to be converted to space!). - sb.append(' '); - } else { - sb.append(c); - } - } - } - - return sb.toString(); - } - - private int parseInt(String str) throws SgfError - { - int ret; - try { - ret = Integer.parseInt(str); - } - catch (NumberFormatException e) { - throw sgfError("Error parsing integer."); - } - return ret; - } - - //---------------------------------------------------------------------- - - private void verifyGame(Node root) throws SgfError - { - if (m_gameinfo.getBoardSize()==null) { - throw sgfError("Missing SZ property."); - } - } - - private SgfError sgfError(String msg) - { - return new SgfError("Line " + m_reader.getLineNumber() + ": " + msg); - } - - private void sgfWarning(String msg) - { - m_warnings.add("Line " + m_reader.getLineNumber() + ": " + msg); - } - - private StreamTokenizer m_tokenizer; - private LineNumberReader m_reader; - private Node m_gametree; - private GameInfo m_gameinfo; - private Vector m_warnings; - private boolean m_swap_bug; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/sgf/SgfWriter.java b/src/hexgui/sgf/SgfWriter.java deleted file mode 100644 index 05135f3..0000000 --- a/src/hexgui/sgf/SgfWriter.java +++ /dev/null @@ -1,177 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.sgf; - -import hexgui.version.Version; -import hexgui.hex.HexColor; -import hexgui.hex.HexPoint; -import hexgui.hex.Move; -import hexgui.game.Node; -import hexgui.game.GameInfo; - -import java.io.*; -import java.awt.Dimension; -import java.util.Map; -import java.util.Iterator; -import java.util.Vector; - -//---------------------------------------------------------------------------- - -/** SGF Writer. - See https://www.red-bean.com/sgf/ for the SGF definition. -*/ -public final class SgfWriter -{ - - /** Write a game tree. */ - public SgfWriter(OutputStream out, Node root, GameInfo game) - { - m_out = new PrintStream(out); - m_buffer = new StringBuffer(128); - m_gameinfo = game; - - writeTree(root, true); - print("\n"); - flushBuffer(); - m_out.flush(); - m_out.close(); - } - - private void writeTree(Node root, boolean isroot) - { - print("("); - writeNode(root, isroot); - print(")"); - } - - private void writeNode(Node node, boolean isroot) - { - print(";"); - - if (isroot) { - String value; - - node.setSgfProperty("FF", "4"); - node.setSgfProperty("AP", "HexGui:"+Version.id); - node.setSgfProperty("GM", "11"); - - Dimension dim = m_gameinfo.getBoardSize(); - value = Integer.toString(dim.width); - if (dim.width != dim.height) - value += ":" + Integer.toString(dim.height); - node.setSgfProperty("SZ", value); - - } - - if (node.getMove() != null) { - printMove(node.getMove()); - } - - Map map = node.getProperties(); - Iterator > it = map.entrySet().iterator(); - while(it.hasNext()) { - Map.Entry e = it.next(); - if (!(e.getKey().equals("C") && e.getValue().equals(""))) { - String val = e.getValue(); - if (e.getKey().equals("C")) { - // For now we only escape comments, although there - // may be other text values that should be escaped - // too. Avoids escaping the ":" in AP field. - val = escapeString(val); - } - print(e.getKey() + "[" + val + "]"); - } - } - - if (node.hasSetup()) { - Vector list; - list = node.getSetup(HexColor.BLACK); - if (!list.isEmpty()) { - print("AB"); - printPointList(list); - } - list = node.getSetup(HexColor.WHITE); - if (!list.isEmpty()) { - print("AW"); - printPointList(list); - } - list = node.getSetup(HexColor.EMPTY); - if (!list.isEmpty()) { - print("AE"); - printPointList(list); - } - } - - int num = node.numChildren(); - if (num == 0) return; - - if (num == 1) { - writeNode(node.getChild(), false); - return; - } - - for (int i=0; i list) - { - for (int i=0; i 72) { - m_out.append(m_buffer.toString()); - m_out.append("\n"); - m_buffer.setLength(0); - } - m_buffer.append(str); - } - - private void flushBuffer() - { - m_out.append(m_buffer.toString()); - m_buffer.setLength(0); - } - - private PrintStream m_out; - private StringBuffer m_buffer; - private GameInfo m_gameinfo; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/util/BoardLayout.java b/src/hexgui/util/BoardLayout.java deleted file mode 100644 index d3d8995..0000000 --- a/src/hexgui/util/BoardLayout.java +++ /dev/null @@ -1,52 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.util; - -import javax.swing.*; -import java.awt.*; -import java.awt.event.*; - -public class BoardLayout - implements LayoutManager -{ - public void addLayoutComponent(String name, Component comp) - { - - } - - public void layoutContainer(Container parent) - { - if (parent.getComponentCount() != 1) { - System.err.println("BoardLayout: needs exactly one component!"); - } - Dimension size = parent.getSize(); - Insets insets = parent.getInsets(); - size.width -= insets.left + insets.right; - size.height -= insets.top + insets.bottom; - int w = size.width; - int h = size.height; - //if (h*3/2 > w) h = 2*w/3; - //if (h*3/2 < w) w = h*3/2; - - int x = (size.width - w) / 2; - int y = (size.height - h) / 2; - parent.getComponent(0).setBounds(x + insets.left, y + insets.top, w, h); - } - - public Dimension minimumLayoutSize(Container parent) - { - return parent.getComponent(0).getMinimumSize(); - } - public Dimension preferredLayoutSize(Container parent) - { - return parent.getComponent(0).getPreferredSize(); - } - - public void removeLayoutComponent(Component comp) - { - - } - -} diff --git a/src/hexgui/util/ErrorMessage.java b/src/hexgui/util/ErrorMessage.java deleted file mode 100644 index ef2200f..0000000 --- a/src/hexgui/util/ErrorMessage.java +++ /dev/null @@ -1,17 +0,0 @@ -// ErrorMessage.java - -package hexgui.util; - -/** Error with error message. - ErrorMessage are exceptions with a message meaningful for presentation - to the user. */ -public class ErrorMessage - extends Exception -{ - /** Constructor. - @param message The error message text. */ - public ErrorMessage(String message) - { - super(message); - } -} diff --git a/src/hexgui/util/FileUtil.java b/src/hexgui/util/FileUtil.java deleted file mode 100644 index 5b8dc5d..0000000 --- a/src/hexgui/util/FileUtil.java +++ /dev/null @@ -1,175 +0,0 @@ -// FileUtil.java - -package hexgui.util; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; - -/** Static file utility functions. */ -public final class FileUtil -{ - /** Return the file extension of a file name. - @param file The file. - @return File extension or null if file name has no extension. */ - public static String getExtension(File file) - { - String ext = null; - String s = file.getName(); - int i = s.lastIndexOf('.'); - if (i > 0 && i < s.length() - 1) - ext = s.substring(i + 1); - return ext; - } - - /** Returns relative URI between to files. - Can be used instead of URI.relativize(), which does not compute - relative URI's correctly, if toFile is not a subdirectory of fromFile - (Sun's Java 1.5.0). - @todo Handle special characters and file names containing slashes. - @param fromFile File to compute the URI relative to. - @param toFile Target file or directory. - @return Relative URI. */ - public static String getRelativeURI(File fromFile, File toFile) - { - assert ! fromFile.exists() || ! fromFile.isDirectory(); - fromFile = fromFile.getAbsoluteFile().getParentFile(); - assert fromFile != null; - ArrayList fromList = splitFile(fromFile); - ArrayList toList = splitFile(toFile); - int fromSize = fromList.size(); - int toSize = toList.size(); - int i = 0; - while (i < fromSize && i < toSize - && fromList.get(i).equals(toList.get(i))) - ++i; - StringBuilder result = new StringBuilder(); - for (int j = i; j < fromSize; ++j) - result.append("../"); - for (int j = i; j < toSize; ++j) - { - result.append(toList.get(j)); - if (j < toSize - 1) - result.append('/'); - } - return result.toString(); - } - - /** Return URI for file. - Replacement for File.toURI() with defined (empty) authority. */ - public static URI getURI(File file) - { - try - { - return new URI("file", "", file.toURI().getPath(), null, null); - } - catch (URISyntaxException e) - { - return null; - } - } - - /** Check for extension (case-insensitive). */ - public static boolean hasExtension(File f, String extension) - { - String ext = getExtension(f); - if (ext == null) - return false; - return ext.equalsIgnoreCase(extension); - } - - /** Read a list of strings from a file. - The file is expected to contain one string per line; leading and - trailing whitespaces are removed. Empty lines or lines beginning - with the comment character '#' are ignored. */ - public static ArrayList readStringListFromFile(File file) - throws IOException - { - ArrayList result = new ArrayList(); - FileReader reader = new FileReader(file); - BufferedReader in = new BufferedReader(reader); - try - { - while (true) - { - String line = in.readLine(); - if (line == null) - break; - line = line.trim(); - if (! line.equals("") && ! line.startsWith("#")) - result.add(line); - } - return result; - } - finally - { - in.close(); - } - } - - /** Remove extension in file name. - If the file does not have the extension oldExtension, - the extension will not be removed. */ - public static String removeExtension(File file, String oldExtension) - { - String name = file.toString(); - if (hasExtension(file, oldExtension)) - { - int index = name.lastIndexOf('.'); - assert index >= 0; - return name.substring(0, index); - } - return name; - } - - /** Replace extension in file name. - If the file does not have the extension oldExtension, - the extension will not be replaced but the new extension will be - appended. */ - public static String replaceExtension(File file, String oldExtension, - String newExtension) - { - String name = file.toString(); - if (hasExtension(file, oldExtension)) - { - int index = name.lastIndexOf('.'); - assert index >= 0; - return name.substring(0, index) + "." + newExtension; - } - return name + "." + newExtension; - } - - public static String replaceExtension(String file, String oldExtension, - String newExtension) - { - return replaceExtension(new File(file), oldExtension, newExtension); - } - - /** Make constructor unavailable; class is for namespace only. */ - private FileUtil() - { - } - - private static ArrayList splitFile(File file) - { - ArrayList list = new ArrayList(); - file = file.getAbsoluteFile(); - try - { - file = file.getCanonicalFile(); - } - catch (IOException e) - { - } - while (file != null) - { - list.add(0, file.getName()); - file = file.getParentFile(); - } - return list; - } -} diff --git a/src/hexgui/util/Hexagon.java b/src/hexgui/util/Hexagon.java deleted file mode 100644 index b1ad712..0000000 --- a/src/hexgui/util/Hexagon.java +++ /dev/null @@ -1,78 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.util; - -import java.awt.*; - -//---------------------------------------------------------------------------- - -/** Creates hexagons. */ -public final class Hexagon -{ - - /** Creates a hexagon with flat sides centered at the specified point. - @param p center of hexagon. - @param width the width of the hexagon. - @param height the height of the hexagon. - @return an instance of Polygon with the above properties. - */ - public static Polygon - createVerticalHexagon(Point p, int width, int height) - { - int xpoints[] = new int[6]; - int ypoints[] = new int[6]; - - int sx = width/2; - int ly = height/2; - int sy = ly/2; - - xpoints[0] = 0; ypoints[0] = -ly; - xpoints[1] = -sx; ypoints[1] = -sy; - xpoints[2] = -sx; ypoints[2] = +sy; - xpoints[3] = 0; ypoints[3] = +ly; - xpoints[4] = +sx; ypoints[4] = +sy; - xpoints[5] = +sx; ypoints[5] = -sy; - - Polygon ret = new Polygon(xpoints, ypoints, 6); - ret.translate(p.x, p.y); - - return ret; - } - - /** Creates a hexagon with flat top and bottom centered at - the specified point. - @param p center of hexagon. - @param width the width of the hexagon. - @param height the height of the hexagon. - @return an instance of Polygon with the above properties. - */ - public static Polygon - createHorizontalHexagon(Point p, int width, int height) - { - int xpoints[] = new int[6]; - int ypoints[] = new int[6]; - - int sy = height/2; - int lx = width/2; - int sx = lx/2; - - xpoints[0] = -lx; ypoints[0] = 0; - xpoints[1] = -sx; ypoints[1] = +sy; - xpoints[2] = +sx; ypoints[2] = +sy; - xpoints[3] = +lx; ypoints[3] = 0; - xpoints[4] = +sx; ypoints[4] = -sy; - xpoints[5] = -sx; ypoints[5] = -sy; - - Polygon ret = new Polygon(xpoints, ypoints, 6); - ret.translate(p.x, p.y); - - return ret; - } - - /** Private constructor. */ - private Hexagon() {} -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/util/ObjectUtil.java b/src/hexgui/util/ObjectUtil.java deleted file mode 100644 index a6a3ddc..0000000 --- a/src/hexgui/util/ObjectUtil.java +++ /dev/null @@ -1,25 +0,0 @@ -// ObjectUtil.java - -package hexgui.util; - -/** Utils for using class java.lang.Object. */ -public final class ObjectUtil -{ - /** Compare including the case that arguments can be null. */ - public static boolean equals(Object object1, Object object2) - { - if (object1 == null && object2 == null) - return true; - if (object1 == null && object2 != null) - return false; - if (object1 != null && object2 == null) - return false; - assert object1 != null && object2 != null; - return object1.equals(object2); - } - - /** Make constructor unavailable; class is for namespace only. */ - private ObjectUtil() - { - } -} diff --git a/src/hexgui/util/Options.java b/src/hexgui/util/Options.java deleted file mode 100644 index 32d58b3..0000000 --- a/src/hexgui/util/Options.java +++ /dev/null @@ -1,329 +0,0 @@ -package hexgui.util; - -import java.io.*; -import java.util.*; - -/** Parser for command line options. - Options begin with a single '-' character. */ -public class Options -{ - /** Parse options. - @param args Command line args from main method - @param specs Specification of allowed options. Contains option names - (without '-'). Options that need an argument must have a ':' appended. - The special argument '--' stops option parsing, all following - arguments are treated as non-option arguments. - @throws Exception If options are not valid according to specs. */ - public Options(String[] args, String[] specs) throws Exception - { - for (String spec : specs) - { - if (spec.length() > 0) - m_map.put(spec, null); - } - parseArgs(args); - } - - /** Check if option is present. */ - public boolean contains(String option) - { - String value = get(option, null); - return (value != null); - } - - /** Return string option value. - @param option The option key. - @return The option value or an empty string, if option is not - present. */ - public String get(String option) - { - return get(option, ""); - } - - /** Return string option value. - @param option The option key. - @param defaultValue The default value. - @return The option value or defaultValue, if option is not present. */ - public String get(String option, String defaultValue) - { - assert isValidOption(option); - String value = getValue(option); - if (value == null) - return defaultValue; - return value; - } - - /** Get remaining arguments that are not options. - @return The sequence of non-option arguments. */ - public ArrayList getArguments() - { - return m_args; - } - - /** Check that the number of non-option arguments is zero. - @throws Exception If there are any non-option arguments. */ - public void checkNoArguments() throws Exception - { - if (! m_args.isEmpty()) - throw new Exception( - "Command does not allow arguments that are not options"); - } - - /** Parse double option. - @param option The option key. - @return The option value or 0, if option is not present. - @throws Exception If option value is not a double. */ - public double getDouble(String option) throws Exception - { - return getDouble(option, 0); - } - - /** Parse double option. - @param option The option key. - @param defaultValue The default value. - @return The option value or defaultValue, if option is not present. - @throws Exception If option value is not a double. */ - public double getDouble(String option, double defaultValue) - throws Exception - { - String value = get(option, Double.toString(defaultValue)); - if (value == null) - return defaultValue; - try - { - return Double.parseDouble(value); - } - catch (NumberFormatException e) - { - throw new Exception("Option -" + option + " needs float value"); - } - } - - /** Parse integer option. - @param option The option key. - @return The option value or 0, if option is not present. - @throws Exception If option value is not an integer. */ - public int getInteger(String option) throws Exception - { - return getInteger(option, 0); - } - - /** Parse integer option. - @param option The option key. - @param defaultValue The default value. - @return The option value or defaultValue, if option is not present. - @throws Exception If option value is not an integer. */ - public int getInteger(String option, int defaultValue) throws Exception - { - String value = get(option, Integer.toString(defaultValue)); - if (value == null) - return defaultValue; - try - { - return Integer.parseInt(value); - } - catch (NumberFormatException e) - { - throw new Exception("Option -" + option + " needs integer value"); - } - } - - /** Parse integer option with range check. - @param option The option key. - @param defaultValue The default value. - @param min The minimum valid value. - @return The option value or defaultValue, if option is not present. - @throws Exception If option value is less than min. */ - public int getInteger(String option, int defaultValue, int min) - throws Exception - { - int value = getInteger(option, defaultValue); - if (value < min) - throw new Exception("Option -" + option + " must be greater than " - + min); - return value; - } - - /** Parse integer option with range check. - @param option The option key. - @param defaultValue The default value. - @param min The minimum valid value. - @param max The maximum valid value. - @return The option value or defaultValue, if option is not present. - @throws Exception If option value is less than min or greater than - max. */ - public int getInteger(String option, int defaultValue, int min, int max) - throws Exception - { - int value = getInteger(option, defaultValue); - if (value < min || value > max) - throw new Exception("Option -" + option + " must be in [" - + min + ".." + max + "]"); - return value; - } - - /** Parse long integer option. - @param option The option key. - @return The option value or 0, if option is not present. - @throws Exception If option value is not a long integer. */ - public long getLong(String option) throws Exception - { - return getLong(option, 0L); - } - - /** Parse long integer option. - @param option The option key. - @param defaultValue The default value. - @return The option value or defaultValue, if option is not present. - @throws Exception If option value is not a long integer. */ - public long getLong(String option, long defaultValue) throws Exception - { - String value = get(option, Long.toString(defaultValue)); - if (value == null) - return defaultValue; - try - { - return Long.parseLong(value); - } - catch (NumberFormatException e) - { - throw new Exception("Option -" + option - + " needs long integer value"); - } - } - - /** Read options from a file given with the option "config". - Requires that "config" is an allowed option. - @throws Exception If options in file are not valid according to - the specification. */ - public void handleConfigOption() throws Exception - { - if (! contains("config")) - return; - String filename = get("config"); - InputStream inputStream; - try - { - inputStream = new FileInputStream(filename); - } - catch (FileNotFoundException e) - { - throw new Exception("File not found: " + filename); - } - Reader reader = new InputStreamReader(inputStream); - BufferedReader bufferedReader = new BufferedReader(reader); - try - { - StringBuilder buffer = new StringBuilder(256); - String line; - while (true) - { - line = bufferedReader.readLine(); - if (line == null) - break; - buffer.append(line); - buffer.append(' '); - } - parseArgs(StringUtils.splitArguments(buffer.toString())); - } - catch (IOException e) - { - System.err.println(e.getMessage()); - } - finally - { - try - { - bufferedReader.close(); - } - catch (IOException e) - { - System.err.println(e.getMessage()); - } - } - } - - /** Creates a new Options instance from command line. - Automatically calls handleConfigOption. - @param args The command line split into arguments. - @param specs Option specification as in constructor. - @return The new Options instance. - @throws Exception If options are not valid according to specs. */ - public static Options parse(String[] args, String[] specs) - throws Exception - { - Options opt = new Options(args, specs); - opt.handleConfigOption(); - return opt; - } - - private final ArrayList m_args = new ArrayList(); - - private final Map m_map = new TreeMap(); - - private String getSpec(String option) throws Exception - { - if (m_map.containsKey(option)) - return option; - else if (m_map.containsKey(option + ":")) - return option + ":"; - throw new Exception("Unknown option -" + option); - } - - private String getValue(String option) - { - assert isValidOption(option); - if (m_map.containsKey(option)) - return m_map.get(option); - return m_map.get(option + ":"); - } - - private boolean isOptionKey(String s) - { - return (s.length() > 0 && s.charAt(0) == '-'); - } - - private boolean isValidOption(String option) - { - return (m_map.containsKey(option) || m_map.containsKey(option + ":")); - } - - private boolean needsValue(String spec) - { - return (spec.length() > 0 - && spec.substring(spec.length() - 1).equals(":")); - } - - private void parseArgs(String args[]) throws Exception - { - boolean stopParse = false; - int n = 0; - while (n < args.length) - { - String s = args[n]; - ++n; - if (s.equals("--")) - { - stopParse = true; - continue; - } - if (isOptionKey(s) && ! stopParse) - { - String spec = getSpec(s.substring(1)); - if (needsValue(spec)) - { - if (n >= args.length) - throw new Exception("Option " + s + " needs value"); - String value = args[n]; - ++n; - m_map.put(spec, value); - } - else - m_map.put(spec, "1"); - - } - else - m_args.add(s); - } - } -} diff --git a/src/hexgui/util/Pair.java b/src/hexgui/util/Pair.java deleted file mode 100644 index d2e2bf5..0000000 --- a/src/hexgui/util/Pair.java +++ /dev/null @@ -1,22 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.util; - -//---------------------------------------------------------------------------- - -/** A pair of two objects. */ -public class Pair -{ - public E first; - public F second; - - public Pair(E first, F second) - { - this.first = first; - this.second = second; - } -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/util/Platform.java b/src/hexgui/util/Platform.java deleted file mode 100644 index d429ae3..0000000 --- a/src/hexgui/util/Platform.java +++ /dev/null @@ -1,225 +0,0 @@ -// Platform.java - -package hexgui.util; - -import java.io.File; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.net.URL; -import java.util.Locale; - -/** Static utility functions for platform detection and platform-dependent - behavior. */ -public class Platform -{ - /** Handler for events from the Application Menu on MacOS. */ - public interface SpecialMacHandler - { - /** Handle about menu event. - @return true if event was handled successfully. */ - boolean handleAbout(); - - /** Handle open file event. - @param filename name of file. - @return true if event was handled successfully. */ - boolean handleOpenFile(String filename); - - /** Handle quit application event. - @return true if event was handled successfully, false if quit - should be aborted. */ - boolean handleQuit(); - } - - public static String getJavaRuntimeName() - { - // java.runtime.name is not a standard property - String name = System.getProperty("java.runtime.name"); - if (name == null) - name = System.getProperty("java.vm.name"); - return name; - } - - /** Return information on this computer. - Returns host name and cpu information (if /proc/cpuinfo exists). */ - public static String getHostInfo() - { - String info; - try - { - info = InetAddress.getLocalHost().getHostName(); - } - catch (UnknownHostException e) - { - info = "?"; - } - try - { - if (existsProcCpuinfo()) - { - String[] cmdArray = { "/bin/sh", "-c", - "grep '^model name' /proc/cpuinfo" }; - String result = ProcessUtil.runCommand(cmdArray); - int start = result.indexOf(':'); - if (start >= 0) - { - info = info + " ("; - int end = result.indexOf("\n"); - if (end >= 0) - info = info + result.substring(start + 1, end).trim(); - else - info = info + result.substring(start + 1).trim(); - info = info + ")"; - } - } - } - catch (IOException e) - { - } - return info; - } - - /** Check if the platform is Mac OS X. */ - public static boolean isMac() - { - return s_isMac; - } - - /** Check if the platform is Unix. */ - public static boolean isUnix() - { - return s_isUnix; - } - - /** Check if the platform is Windows. */ - public static boolean isWindows() - { - return s_isWindows; - } - - /** Try to open a URL in an external browser. - Tries /usr/bin/open if Platform.isMac(), - rundll32 url.dll,FileProtocolHandler if Platform.isWindows(), - and if isUnix() in this order: - - xdg-open - - kfmclient (if KDE is running) - - firefox - - mozilla - - opera - @param url URL to open. - @return false if everything failed. */ - public static boolean openInExternalBrowser(URL url) - { - if (isMac()) - { - String[] cmd = { "/usr/bin/open", url.toString() }; - if (runProcess(cmd)) - return true; - } - else if (isWindows()) - { - String[] cmd = { "rundll32", "url.dll,FileProtocolHandler", - url.toString() }; - if (runProcess(cmd)) - return true; - } - else if (isUnix()) - { - { - String[] cmd = { "xdg-open", url.toString() }; - if (runProcess(cmd)) - return true; - } - if (checkKDERunning()) - { - String[] cmd = { "kfmclient", "openURL", url.toString() }; - if (runProcess(cmd)) - return true; - } - { - String[] cmd = { "firefox", url.toString() }; - if (runProcess(cmd)) - return true; - } - { - String[] cmd = { "mozilla", url.toString() }; - if (runProcess(cmd)) - return true; - } - { - String[] cmd = { "opera", url.toString() }; - if (runProcess(cmd)) - return true; - } - } - return false; - } - - /** Register handler for events from the Application Menu on MacOS. - @param handler Handler to register. */ - public static void registerSpecialMacHandler(SpecialMacHandler handler) - { - try - { - Object[] args = { handler }; - Class[] arglist = { Platform.SpecialMacHandler.class }; - String name = "net.sf.gogui.specialmac.RegisterSpecialMacHandler"; - Class registerClass = Class.forName(name); - Constructor constructor = registerClass.getConstructor(arglist); - constructor.newInstance(args); - } - catch (Throwable e) - { - System.err.println("Could not register handler for Mac events." + - " (com.apple.eawt classes not found)"); - } - } - - private static boolean s_isMac; - - private static boolean s_isUnix; - - private static boolean s_isWindows; - - static - { - // See http://developer.apple.com/technotes/tn2002/tn2110.html - String name = System.getProperty("os.name"); - s_isMac = name.toLowerCase(Locale.getDefault()).startsWith("mac os x"); - s_isUnix = (name.indexOf("nix") >= 0 || name.indexOf("nux") >= 0); - s_isWindows = name.startsWith("Windows"); - } - - private static boolean checkKDERunning() - { - try - { - String[] cmdArray = { "dcop" }; - String result = ProcessUtil.runCommand(cmdArray); - return (result.indexOf("kicker") >= 0); - } - catch (IOException e) - { - return false; - } - } - - private static boolean existsProcCpuinfo() - { - return new File("/proc/cpuinfo").exists(); - } - - private static boolean runProcess(String[] cmd) - { - try - { - ProcessUtil.runProcess(cmd); - return true; - } - catch (IOException e) - { - return false; - } - } -} diff --git a/src/hexgui/util/PrefUtil.java b/src/hexgui/util/PrefUtil.java deleted file mode 100644 index 4a6b974..0000000 --- a/src/hexgui/util/PrefUtil.java +++ /dev/null @@ -1,85 +0,0 @@ -// PrefUtil.java - -package hexgui.util; - -import java.util.ArrayList; -import java.util.prefs.Preferences; -import java.util.prefs.BackingStoreException; - -/** Utils for using java.util.prefs package. */ -public final class PrefUtil -{ - /** Get node path, create if not already existing. - @param path The absolute path name of the node. - @return The node */ - public static Preferences createNode(String path) - { - assert ! path.startsWith("/"); - return Preferences.userRoot().node(path); - } - - /** Get a list of strings from preferences. - The list is stored as a size property end element_N properties with - N being the element index. - @param path The absolute path name of the node. - @return The list of strings. */ - public static ArrayList getList(String path) - { - Preferences prefs = getNode(path); - if (prefs == null) - return new ArrayList(); - int size = prefs.getInt("size", 0); - if (size <= 0) - return new ArrayList(); - ArrayList result = new ArrayList(size); - for (int i = 0; i < size; ++i) - { - String element = prefs.get("element_" + i, null); - if (element == null) - // Should not happen - break; - result.add(element); - } - return result; - } - - /** Get node for package and path, return null if not already existing. - @param path The absolute path name of the node. - @return The node or null, if node does not exist or failure in the - backing store. */ - public static Preferences getNode(String path) - { - assert ! path.startsWith("/"); - Preferences prefs = Preferences.userRoot(); - try - { - if (! prefs.nodeExists(path)) - return null; - } - catch (BackingStoreException e) - { - return null; - } - return prefs.node(path); - } - - /** Put a list of strings to preferences. - The list is stored as a size property end element_N properties with - N being the element index. - @param path The absolute path name of the node. - @param list The list of strings. */ - public static void putList(String path, ArrayList list) - { - Preferences prefs = createNode(path); - if (prefs == null) - return; - prefs.putInt("size", list.size()); - for (int i = 0; i < list.size(); ++i) - prefs.put("element_" + i, list.get(i)); - } - - /** Make constructor unavailable; class is for namespace only. */ - private PrefUtil() - { - } -} diff --git a/src/hexgui/util/ProcessUtil.java b/src/hexgui/util/ProcessUtil.java deleted file mode 100644 index 23bd3ff..0000000 --- a/src/hexgui/util/ProcessUtil.java +++ /dev/null @@ -1,139 +0,0 @@ -// ProcessUtil.java - -package hexgui.util; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -class ExitWaiter - extends Thread -{ - public ExitWaiter(Object monitor, Process process) - { - m_monitor = monitor; - m_process = process; - } - - public boolean isFinished() - { - synchronized (m_mutex) - { - return m_isFinished; - } - } - - public void run() - { - try - { - m_process.waitFor(); - } - catch (InterruptedException e) - { - } - synchronized (m_mutex) - { - m_isFinished = true; - } - synchronized (m_monitor) - { - m_monitor.notifyAll(); - } - } - - private boolean m_isFinished; - - private final Object m_monitor; - - private final Object m_mutex = new Object(); - - private final Process m_process; -}; - -/** Static utility functions and classes related to processes. */ -public class ProcessUtil -{ - /** Copies standard error of a process to System.err. */ - public static class StdErrThread - extends Thread - { - public StdErrThread(Process process) - { - super(new StreamCopy(false, process.getErrorStream(), System.err, - false)); - } - } - - /** Run a process and return its standard output as a string. */ - public static String runCommand(String[] cmdArray) throws IOException - { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec(cmdArray); - Thread discardErr = new StreamDiscard(process.getErrorStream()); - discardErr.start(); - InputStream in = process.getInputStream(); - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - try - { - StringBuilder result = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) - { - result.append(line); - result.append('\n'); - } - try - { - if (process.waitFor() != 0) - throw new IOException("Process returned error status"); - } - catch (InterruptedException e) - { - throw new IOException("InterruptedException"); - } - return result.toString(); - } - finally - { - reader.close(); - } - } - - /** Run a process. - Forwards the stdout/stderr of the child process to stderr of the - calling process. */ - public static void runProcess(String[] cmdArray) throws IOException - { - Runtime runtime = Runtime.getRuntime(); - Process process = runtime.exec(cmdArray); - Thread copyOut = - new Thread(new StreamCopy(false, process.getInputStream(), - System.err, false)); - copyOut.start(); - Thread copyErr = - new Thread(new StreamCopy(false, process.getErrorStream(), - System.err, false)); - copyErr.start(); - } - - public static boolean waitForExit(Process process, long timeout) - { - Object monitor = new Object(); - ExitWaiter exitWaiter = new ExitWaiter(monitor, process); - synchronized (monitor) - { - exitWaiter.start(); - try - { - monitor.wait(timeout); - return exitWaiter.isFinished(); - } - catch (InterruptedException e) - { - return false; - } - } - } -} diff --git a/src/hexgui/util/RadialGradientContext.java b/src/hexgui/util/RadialGradientContext.java deleted file mode 100644 index b19e922..0000000 --- a/src/hexgui/util/RadialGradientContext.java +++ /dev/null @@ -1,102 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.util; - -import java.awt.Color; -import java.awt.PaintContext; -import java.awt.geom.Point2D; -import java.awt.image.ColorModel; -import java.awt.image.Raster; -import java.awt.image.WritableRaster; - -//---------------------------------------------------------------------------- - -/** Creates a raster with a radial color gradient. */ -public class RadialGradientContext - implements PaintContext -{ - public RadialGradientContext(Point2D point, Color color1, - Point2D radius, Color color2) - { - m_point = point; - m_red1 = color1.getRed(); - m_green1 = color1.getGreen(); - m_blue1 = color1.getBlue(); - m_alpha1 = color1.getAlpha(); - m_radius = radius.distance(0, 0); - m_redDiff = color2.getRed() - m_red1; - m_greenDiff = color2.getGreen() - m_green1; - m_blueDiff = color2.getBlue() - m_blue1; - m_alphaDiff = color2.getAlpha() - m_alpha1; - } - - public void dispose() - { - } - - public ColorModel getColorModel() - { - return ColorModel.getRGBdefault(); - } - - public Raster getRaster(int x, int y, int width, int height) - { - if (m_raster != null && x == m_x && y == m_y && width == m_width - && height == m_height) - return m_raster; - m_x = x; - m_y = y; - m_height = height; - m_width = width; - ColorModel colorModel = getColorModel(); - m_raster = colorModel.createCompatibleWritableRaster(width, height); - int[] data = new int[width * height * 4]; - int index = -1; - for (int j = 0; j < height; ++j) - for (int i = 0; i < width; ++i) - { - double distance = m_point.distance(x + i, y + j); - double ratio = Math.min(distance / m_radius, 1.0); - data[++index] = (int)(m_red1 + ratio * m_redDiff); - data[++index] = (int)(m_green1 + ratio * m_greenDiff); - data[++index] = (int)(m_blue1 + ratio * m_blueDiff); - data[++index] = (int)(m_alpha1 + ratio * m_alphaDiff); - } - m_raster.setPixels(0, 0, width, height, data); - return m_raster; - } - - private final int m_red1; - - private final int m_redDiff; - - private final int m_green1; - - private final int m_greenDiff; - - private final int m_blue1; - - private final int m_blueDiff; - - private final int m_alpha1; - - private final int m_alphaDiff; - - private int m_x; - - private int m_y; - - private int m_height; - - private int m_width; - - private final double m_radius; - - private final Point2D m_point; - - private WritableRaster m_raster; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/util/RadialGradientPaint.java b/src/hexgui/util/RadialGradientPaint.java deleted file mode 100644 index a13d1e0..0000000 --- a/src/hexgui/util/RadialGradientPaint.java +++ /dev/null @@ -1,82 +0,0 @@ -//---------------------------------------------------------------------------- -// $Id$ -//---------------------------------------------------------------------------- - -package hexgui.util; - -import java.awt.Color; -import java.awt.Paint; -import java.awt.PaintContext; -import java.awt.Rectangle; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.awt.image.ColorModel; - - -//---------------------------------------------------------------------------- - -/** Creates a PaintContext for a radial gradient. */ -public class RadialGradientPaint - implements Paint -{ - public RadialGradientPaint(Point2D point, Color pointColor, - Point2D radius, Color backgroundColor) - { - assert(radius.distance(0, 0) > 0); - m_point = point; - m_radius = radius; - m_pointColor = pointColor; - m_backgroundColor = backgroundColor; - int alphaPoint = pointColor.getAlpha(); - int alphaBackground = backgroundColor.getAlpha(); - if ((alphaPoint & alphaBackground) == 0xff) - m_transparency = OPAQUE; - else - m_transparency = TRANSLUCENT; - } - - public PaintContext createContext(ColorModel colorModel, - Rectangle deviceBounds, - Rectangle2D userBounds, - AffineTransform xform, - RenderingHints hints) - { - Point2D transformedPoint = xform.transform(m_point, null); - Point2D transformedRadius = xform.deltaTransform(m_radius, null); - if (m_cachedContext != null - && transformedPoint.equals(m_transformedPoint) - && transformedRadius.equals(m_transformedRadius)) - return m_cachedContext; - m_transformedPoint = (Point2D)transformedPoint.clone(); - m_transformedRadius = (Point2D)transformedRadius.clone(); - m_cachedContext = - new RadialGradientContext(transformedPoint, m_pointColor, - transformedRadius, m_backgroundColor); - return m_cachedContext; - } - - public int getTransparency() - { - return m_transparency; - } - - private final int m_transparency; - - private Point2D m_transformedPoint; - - private Point2D m_transformedRadius; - - private RadialGradientContext m_cachedContext; - - private final Point2D m_point; - - private final Point2D m_radius; - - private final Color m_backgroundColor; - - private final Color m_pointColor; -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/util/SpringUtilities.java b/src/hexgui/util/SpringUtilities.java deleted file mode 100644 index e4fdca9..0000000 --- a/src/hexgui/util/SpringUtilities.java +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Neither the name of Sun Microsystems nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package hexgui.util; - -import javax.swing.*; -import javax.swing.SpringLayout; -import java.awt.*; - -/** - * A 1.4 file that provides utility methods for - * creating form- or grid-style layouts with SpringLayout. - * These utilities are used by several programs, such as - * SpringBox and SpringCompactGrid. - */ -public class SpringUtilities { - /** - * A debugging utility that prints to stdout the component's - * minimum, preferred, and maximum sizes. - */ - public static void printSizes(Component c) { - System.out.println("minimumSize = " + c.getMinimumSize()); - System.out.println("preferredSize = " + c.getPreferredSize()); - System.out.println("maximumSize = " + c.getMaximumSize()); - } - - /** - * Aligns the first rows * cols - * components of parent in - * a grid. Each component is as big as the maximum - * preferred width and height of the components. - * The parent is made just big enough to fit them all. - * - * @param rows number of rows - * @param cols number of columns - * @param initialX x location to start the grid at - * @param initialY y location to start the grid at - * @param xPad x padding between cells - * @param yPad y padding between cells - */ - public static void makeGrid(Container parent, - int rows, int cols, - int initialX, int initialY, - int xPad, int yPad) { - SpringLayout layout; - try { - layout = (SpringLayout)parent.getLayout(); - } catch (ClassCastException exc) { - System.err.println("The first argument to makeGrid must use SpringLayout."); - return; - } - - Spring xPadSpring = Spring.constant(xPad); - Spring yPadSpring = Spring.constant(yPad); - Spring initialXSpring = Spring.constant(initialX); - Spring initialYSpring = Spring.constant(initialY); - int max = rows * cols; - - //Calculate Springs that are the max of the width/height so that all - //cells have the same size. - Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)). - getWidth(); - Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)). - getWidth(); - for (int i = 1; i < max; i++) { - SpringLayout.Constraints cons = layout.getConstraints( - parent.getComponent(i)); - - maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth()); - maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight()); - } - - //Apply the new width/height Spring. This forces all the - //components to have the same size. - for (int i = 0; i < max; i++) { - SpringLayout.Constraints cons = layout.getConstraints( - parent.getComponent(i)); - - cons.setWidth(maxWidthSpring); - cons.setHeight(maxHeightSpring); - } - - //Then adjust the x/y constraints of all the cells so that they - //are aligned in a grid. - SpringLayout.Constraints lastCons = null; - SpringLayout.Constraints lastRowCons = null; - for (int i = 0; i < max; i++) { - SpringLayout.Constraints cons = layout.getConstraints( - parent.getComponent(i)); - if (i % cols == 0) { //start of new row - lastRowCons = lastCons; - cons.setX(initialXSpring); - } else { //x position depends on previous component - cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST), - xPadSpring)); - } - - if (i / cols == 0) { //first row - cons.setY(initialYSpring); - } else { //y position depends on previous row - cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH), - yPadSpring)); - } - lastCons = cons; - } - - //Set the parent's size. - SpringLayout.Constraints pCons = layout.getConstraints(parent); - pCons.setConstraint(SpringLayout.SOUTH, - Spring.sum( - Spring.constant(yPad), - lastCons.getConstraint(SpringLayout.SOUTH))); - pCons.setConstraint(SpringLayout.EAST, - Spring.sum( - Spring.constant(xPad), - lastCons.getConstraint(SpringLayout.EAST))); - } - - /* Used by makeCompactGrid. */ - private static SpringLayout.Constraints getConstraintsForCell( - int row, int col, - Container parent, - int cols) { - SpringLayout layout = (SpringLayout) parent.getLayout(); - Component c = parent.getComponent(row * cols + col); - return layout.getConstraints(c); - } - - /** - * Aligns the first rows * cols - * components of parent in - * a grid. Each component in a column is as wide as the maximum - * preferred width of the components in that column; - * height is similarly determined for each row. - * The parent is made just big enough to fit them all. - * - * @param rows number of rows - * @param cols number of columns - * @param initialX x location to start the grid at - * @param initialY y location to start the grid at - * @param xPad x padding between cells - * @param yPad y padding between cells - */ - public static void makeCompactGrid(Container parent, - int rows, int cols, - int initialX, int initialY, - int xPad, int yPad) { - SpringLayout layout; - try { - layout = (SpringLayout)parent.getLayout(); - } catch (ClassCastException exc) { - System.err.println("The first argument to makeCompactGrid must use SpringLayout."); - return; - } - - //Align all cells in each column and make them the same width. - Spring x = Spring.constant(initialX); - for (int c = 0; c < cols; c++) { - Spring width = Spring.constant(0); - for (int r = 0; r < rows; r++) { - width = Spring.max(width, - getConstraintsForCell(r, c, parent, cols). - getWidth()); - } - for (int r = 0; r < rows; r++) { - SpringLayout.Constraints constraints = - getConstraintsForCell(r, c, parent, cols); - constraints.setX(x); - constraints.setWidth(width); - } - x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad))); - } - - //Align all cells in each row and make them the same height. - Spring y = Spring.constant(initialY); - for (int r = 0; r < rows; r++) { - Spring height = Spring.constant(0); - for (int c = 0; c < cols; c++) { - height = Spring.max(height, - getConstraintsForCell(r, c, parent, cols). - getHeight()); - } - for (int c = 0; c < cols; c++) { - SpringLayout.Constraints constraints = - getConstraintsForCell(r, c, parent, cols); - constraints.setY(y); - constraints.setHeight(height); - } - y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad))); - } - - //Set the parent's size. - SpringLayout.Constraints pCons = layout.getConstraints(parent); - pCons.setConstraint(SpringLayout.SOUTH, y); - pCons.setConstraint(SpringLayout.EAST, x); - } -} diff --git a/src/hexgui/util/StreamCopy.java b/src/hexgui/util/StreamCopy.java deleted file mode 100644 index a457b97..0000000 --- a/src/hexgui/util/StreamCopy.java +++ /dev/null @@ -1,72 +0,0 @@ -// StreamCopy.java - -package hexgui.util; - -import java.io.InputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** Thread copying the output of one stream to another stream. */ -public class StreamCopy - implements Runnable -{ - /** Constructor. - @param verbose Also copy everything to stderr - @param src Source stream - @param dest Destination stream - @param close Close destination after eof in source */ - public StreamCopy(boolean verbose, InputStream src, OutputStream dest, - boolean close) - { - m_verbose = verbose; - m_src = src; - m_dest = dest; - m_close = close; - } - - /** Run method. - Exceptions caught are written to stderr. */ - public void run() - { - try - { - byte buffer[] = new byte[1024]; - while (true) - { - int n = m_src.read(buffer); - if (n < 0) - break; - if (m_verbose) - System.err.write(buffer, 0, n); - m_dest.write(buffer, 0, n); - m_dest.flush(); - } - } - catch (Throwable e) - { - StringUtils.printException(e); - } - finally - { - if (m_close) - { - try - { - m_dest.close(); - } - catch (IOException e) - { - StringUtils.printException(e); - } - } - } - } - - private final boolean m_verbose; - - private final boolean m_close; - - private final InputStream m_src; - - private final OutputStream m_dest; -} diff --git a/src/hexgui/util/StreamDiscard.java b/src/hexgui/util/StreamDiscard.java deleted file mode 100644 index a00fa79..0000000 --- a/src/hexgui/util/StreamDiscard.java +++ /dev/null @@ -1,43 +0,0 @@ -// StreamDiscard.java - -package hexgui.util; - -import java.io.InputStream; - -/** Thread discarding an output stream. */ -public class StreamDiscard - extends Thread -{ - public StreamDiscard(InputStream src) - { - m_src = src; - } - - /** Run method. - Exceptions caught are written to stderr. */ - public void run() - { - try - { - byte buffer[] = new byte[1024]; - while (true) - { - int n = m_src.read(buffer); - if (n < 0) - break; - if (n == 0) - { - // Not sure if this is necessary. - sleep(100); - continue; - } - } - } - catch (Throwable e) - { - StringUtils.printException(e); - } - } - - private final InputStream m_src; -} diff --git a/src/hexgui/util/StringUtils.java b/src/hexgui/util/StringUtils.java deleted file mode 100644 index 1cf965e..0000000 --- a/src/hexgui/util/StringUtils.java +++ /dev/null @@ -1,298 +0,0 @@ -package hexgui.util; - -import hexgui.hex.HexPoint; -import hexgui.hex.HexColor; -import hexgui.hex.VC; -import hexgui.util.Pair; - -import java.io.StringReader; -import java.io.PrintStream; -import java.io.IOException; -import java.util.Vector; -import java.util.ArrayList; - -public final class StringUtils -{ - /** Capitalize the first word and trim whitespaces. */ - public static String capitalize(String message) - { - message = message.trim(); - if (message.equals("")) - return message; - StringBuilder buffer = new StringBuilder(message); - char first = buffer.charAt(0); - if (! Character.isUpperCase(first)) - buffer.setCharAt(0, Character.toUpperCase(first)); - return buffer.toString(); - } - - /** Check if string is null, empty, or contains only whitespaces. */ - public static boolean isEmpty(String s) - { - if (s == null) - return true; - for (int i = 0; i < s.length(); ++i) - if (! Character.isWhitespace(s.charAt(i))) - return false; - return true; - } - - /** Converts all whitespace characters to a single ' '. */ - public static String cleanWhiteSpace(String str) - { - StringReader reader = new StringReader(str); - StringBuilder ret = new StringBuilder(); - - boolean white = false; - while (true) { - int c; - try { - c = reader.read(); - } - catch (Throwable t) { - System.out.println("Something bad happened!"); - break; - } - - if (c == -1) break; - if (c == ' ' || c == '\n' || c == '\t') { - if (!white) ret.append(" "); - white = true; - } else { - white = false; - ret.append((char)c); - } - } - return ret.toString(); - } - - public static Vector parsePointList(String str, String sep) - { - Vector ret = new Vector(); - String cleaned = cleanWhiteSpace(str.trim()); - if (cleaned.length() == 0) - return ret; - - String[] pts = cleaned.split(sep); - for (int i=0; i > parseVariation(String str) - { - Vector > ret - = new Vector >(); - - Vector > pairs - = StringUtils.parseStringPairList(str.trim()); - for (int i=0; i(color, point)); - } - return ret; - } - - public static Vector parsePointList(String str) - { - return parsePointList(str, " "); - } - - public static Vector parseStringList(String str) - { - Vector ret = new Vector(); - String cleaned = cleanWhiteSpace(str.trim()); - if (cleaned.length() == 0) - return ret; - - String[] strs = cleaned.split(" "); - for (int i=0; i > parseStringPairList(String str) - { - Vector > ret = new Vector >(); - String cleaned = cleanWhiteSpace(str.trim()); - if (cleaned.length() == 0) - return ret; - - String[] strs = cleaned.split(" "); - for (int i=0; i(c1, c2)); - } - return ret; - } - - public static Vector parseVCList(String str) - { - Vector ret = new Vector(); - String cleaned = cleanWhiteSpace(str.trim()); - if (cleaned.length() == 0) - return ret; - - String[] vcs = cleaned.split(" "); - - for (int i=0, j=0; i carrier = new Vector(); - Vector stones = new Vector(); - Vector key = new Vector(); - String source = "unknown"; - - try { - color = HexColor.get(vcs[i+0]); - from = HexPoint.get(vcs[i+1]); - to = HexPoint.get(vcs[i+2]); - type = vcs[i+3]; - - j = 5; - if (!type.equals("softlimit")) { - source = vcs[i+4]; - - // read carrier set - if (!vcs[i+5].equals("[")) - throw new Throwable("No carrier!"); - - for (j=6; j < vcs.length; j++) { - if (vcs[i+j].equals("]")) break; - HexPoint p = HexPoint.get(vcs[i+j]); - carrier.add(p); - } - - j++; // skip closing ']' - - // read stone set - if (!vcs[i+j].equals("[")) - throw new Throwable("No stones! Should be '[', got '" + - vcs[j] + "'"); - - for (j++; j < vcs.length; j++) { - if (vcs[i+j].equals("]")) break; - HexPoint p = HexPoint.get(vcs[i+j]); - stones.add(p); - } - - j++; // skip closing ']' - - int blah = 0; - if (type.equals("semi")) blah = 1; - for (int k=0; k=0; i--) { - ret.append(str.charAt(i)); - } - return ret.toString(); - } - - /** Split command line into arguments. - Allows " for words containing whitespaces. - */ - public static String[] splitArguments(String string) - { - assert string != null; - ArrayList result = new ArrayList(); - boolean escape = false; - boolean inString = false; - StringBuilder token = new StringBuilder(); - for (int i = 0; i < string.length(); ++i) - { - char c = string.charAt(i); - if (c == '"' && ! escape) - { - if (inString) - { - result.add(token.toString()); - token.setLength(0); - } - inString = ! inString; - } - else if (Character.isWhitespace(c) && ! inString) - { - if (token.length() > 0) - { - result.add(token.toString()); - token.setLength(0); - } - } - else - token.append(c); - escape = (c == '\\' && ! escape); - } - if (token.length() > 0) - result.add(token.toString()); - return result.toArray(new String[result.size()]); - } - - /** Return a printable error message for an exception. - Returns the error message is for instances of ErrorMessage or - for other exceptions the class name with the exception message - appended, if not empty. */ - public static String getErrorMessage(Throwable e) - { - String message = e.getMessage(); - boolean hasMessage = ! StringUtils.isEmpty(message); - String className = e.getClass().getName(); - String result; - if (e instanceof ErrorMessage) - result = message; - else if (hasMessage) - result = className + ":\n" + message; - else - result = className; - return result; - } - - /** Print exception to standard error. - Prints the class name and message to standard error. - For exceptions of type Error or RuntimeException, a stack trace - is printed in addition. - @return A slightly differently formatted error message - for display in an error dialog. */ - public static String printException(Throwable exception) - { - String result = getErrorMessage(exception); - System.err.println(result); - boolean isSevere = (exception instanceof RuntimeException - || exception instanceof Error); - if (isSevere) - exception.printStackTrace(); - return result; - } - -} - -//---------------------------------------------------------------------------- diff --git a/src/hexgui/version/Version.java b/src/hexgui/version/Version.java deleted file mode 100644 index 56e30be..0000000 --- a/src/hexgui/version/Version.java +++ /dev/null @@ -1,24 +0,0 @@ -//---------------------------------------------------------------------------- -/** */ -//---------------------------------------------------------------------------- - -package hexgui.version; - -//---------------------------------------------------------------------------- - -/** Version information. - Contains the current version number, the svn build number, and - the date of the build. -*/ -public final class Version -{ - - private Version() - { - } - - public static String id = "0.10.GIT"; - public static String date = "2020-05-25 03:42"; -} - -//---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/Main.java b/src/main/java/hexgui/Main.java new file mode 100644 index 0000000..bded38e --- /dev/null +++ b/src/main/java/hexgui/Main.java @@ -0,0 +1,98 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui; + +import hexgui.gui.HexGui; +import hexgui.util.Options; +import hexgui.version.Version; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.*; +import javax.swing.*; + +public final class Main { + static final String LOOKANDFEEL = "System"; + + private static void initLookAndFeel() { + String lookAndFeel = null; + + if (LOOKANDFEEL != null) { + if (LOOKANDFEEL.equals("Metal")) { + lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); + } else if (LOOKANDFEEL.equals("System")) { + lookAndFeel = UIManager.getSystemLookAndFeelClassName(); + } else if (LOOKANDFEEL.equals("Motif")) { + lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; + } else if (LOOKANDFEEL.equals("GTK+")) { // new in 1.4.2 + lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; + } else { + System.err.println("Unexpected value of LOOKANDFEEL specified: " + LOOKANDFEEL); + lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); + } + + try { + UIManager.setLookAndFeel(lookAndFeel); + } catch (ClassNotFoundException e) { + System.err.println("Couldn't find class for specified look and feel:" + lookAndFeel); + System.err.println("Did you include the L&F library in the class path?"); + System.err.println("Using the default look and feel."); + } catch (UnsupportedLookAndFeelException e) { + System.err.println( + "Can't use the specified look and feel (" + lookAndFeel + ") on this platform."); + System.err.println("Using the default look and feel."); + } catch (Exception e) { + System.err.println( + "Couldn't get specified look and feel (" + lookAndFeel + "), for some reason."); + System.err.println("Using the default look and feel."); + e.printStackTrace(); + } + } + } + + private static void createAndShowGUI(File file, String command) { + initLookAndFeel(); + JFrame.setDefaultLookAndFeelDecorated(true); + HexGui app = new HexGui(file, command); + } + + public static void main(String[] args) throws Exception { + try { + String options[] = {"config:", "program:", "help", "version"}; + Options opt = Options.parse(args, options); + if (opt.contains("help")) { + String helpText = + "Usage: hexgui [options] [file]\n" + + "Graphical user interface for Hex programs\n" + + "using the Hex Text Protocol.\n" + + "\n" + + "-config file Read command line arguments from file\n" + + "-help Display this help and exit\n" + + "-program Command for Hex program to attach\n" + + "-version Print version and exit\n"; + System.out.print(helpText); + return; + } + if (opt.contains("version")) { + System.out.println("HexGui " + Version.id + " " + Version.date); + return; + } + final String command = opt.get("program", null); + ArrayList arguments = opt.getArguments(); + final File file; + if (arguments.size() == 0) file = null; + else if (arguments.size() == 1) file = new File(arguments.get(0)); + else throw new Exception("Only one argument allowed"); + javax.swing.SwingUtilities.invokeLater( + new Runnable() { + public void run() { + createAndShowGUI(file, command); + } + }); + } catch (Exception e) { + System.err.println(e.getMessage()); + } + } +} diff --git a/src/main/java/hexgui/MainWrapper.java b/src/main/java/hexgui/MainWrapper.java new file mode 100644 index 0000000..8d0d420 --- /dev/null +++ b/src/main/java/hexgui/MainWrapper.java @@ -0,0 +1,24 @@ +package hexgui; + +import java.lang.reflect.Method; + +/** + * Wrapper for starting HexGui. Loads the main class with the reflection API to set AWT an other + * properties before any AWT class is loaded (otherwise they would be ignored). + */ +public final class MainWrapper { + public static void main(String[] args) throws Exception { + // Use GDI rendering on Windows. There are repaint problems using + // DDraw (last tested with Java 1.6 on Windows 7). + System.setProperty("sun.java2d.noddraw", "true"); + + // Invoke hexgui.Main.main() with reflection API + Class mainClass = Class.forName("hexgui.Main"); + Class[] argTypes = new Class[] {String[].class}; + Method mainMethod = mainClass.getMethod("main", argTypes); + mainMethod.invoke(null, (Object) args); + } + + /** Make constructor unavailable; class is for namespace only. */ + private MainWrapper() {} +} diff --git a/src/main/java/hexgui/game/Clock.java b/src/main/java/hexgui/game/Clock.java new file mode 100644 index 0000000..c72b77f --- /dev/null +++ b/src/main/java/hexgui/game/Clock.java @@ -0,0 +1,70 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.game; + +import hexgui.hex.*; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; +import javax.swing.Timer; + +public class Clock implements ActionListener { + public interface Listener { + public void clockChanged(); + } + + /** Initializes a clock. */ + public Clock() { + m_timer = new Timer(1000, this); + m_elapsed = 0; + m_startTime = -1; + } + + public void addListener(Listener ls) { + m_listener = ls; + } + + public void start() { + if (m_startTime != -1) // already started + return; + m_startTime = System.currentTimeMillis(); + m_timer.setInitialDelay((m_elapsed + 999) / 1000 * 1000 - m_elapsed); + m_timer.start(); + } + + public void stop() { + if (m_startTime == -1) // already stopped + return; + m_timer.stop(); + m_elapsed += (int) (System.currentTimeMillis() - m_startTime); + m_startTime = -1; + } + + /** Returns elapsed time in milliseconds. */ + public int elapsed() { + if (m_startTime == -1) return m_elapsed; + return m_elapsed + (int) (System.currentTimeMillis() - m_startTime); + } + + /** Sets the time. */ + public void setElapsed(int millis) { + m_elapsed = millis; + m_listener.clockChanged(); + } + + public void actionPerformed(ActionEvent e) { + if (m_listener != null) m_listener.clockChanged(); + } + + private Listener m_listener; + + private Timer m_timer; + + private int m_elapsed; + + private long m_startTime; +} +; diff --git a/src/main/java/hexgui/game/Game.java b/src/main/java/hexgui/game/Game.java new file mode 100644 index 0000000..656753c --- /dev/null +++ b/src/main/java/hexgui/game/Game.java @@ -0,0 +1,40 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.game; + +// ---------------------------------------------------------------------------- + +/** + * Game state. + * + *

A game can be in one of four states: STARTING, RUNNING, PAUSED + * , and FINISHED. The game is STARTING when the first move has yet + * to be played. Once the first move is played then the game is RUNNING. The clock is + * ticking only when the game is running. If a side-to-side connection is achieved or a player + * forfeits or resigns then the game is FINISHED. Moves can only be played in the first + * three states. + */ +public final class Game { + + public static final int STARTING = 0; + public static final int RUNNING = 1; + public static final int PAUSED = 2; + public static final int FINISHED = 3; + + public Game() { + m_state = STARTING; + } + + public int getState() { + return m_state; + } + + private int m_state; + + private Node m_root; + private Node m_current; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/game/GameInfo.java b/src/main/java/hexgui/game/GameInfo.java new file mode 100644 index 0000000..ad23d90 --- /dev/null +++ b/src/main/java/hexgui/game/GameInfo.java @@ -0,0 +1,25 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.game; + +import java.awt.Dimension; + +// ---------------------------------------------------------------------------- + +/** Properties of a game. Holds properties that appear in a SGF root node. */ +public class GameInfo { + + public GameInfo() {} + + public void setBoardSize(Dimension dim) { + m_boardsize = dim; + } + + public Dimension getBoardSize() { + return m_boardsize; + } + + private Dimension m_boardsize; +} diff --git a/src/main/java/hexgui/game/Node.java b/src/main/java/hexgui/game/Node.java new file mode 100644 index 0000000..8dd23e6 --- /dev/null +++ b/src/main/java/hexgui/game/Node.java @@ -0,0 +1,505 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.game; + +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.hex.Move; +import java.util.Iterator; +import java.util.Map; +import java.util.TreeMap; +import java.util.Vector; + +// ---------------------------------------------------------------------------- + +/** Node in a game tree. Stores moves and other properties. */ +public class Node { + /** Initializes an empty node with a null move. */ + public Node() { + this(null); + } + + /** + * Constructs a new node with the specified move. + * + * @param move move to initialize the node with. + */ + public Node(Move move) { + // Properties. This can include unstructured properties found + // in SGF files (i.e., properties that HexGUI doesn't know + // about), as well as structured properties such as C + // (comment), PL (player to move), and maybe + // others. Properties that can take multiple values are not + // stored here; e.g., LB is stored in m_label. + m_property = new TreeMap(); + + // For setup moves. + m_setup = new TreeMap(); + + // A list of cell:label pairs can be attached to a node. This + // corresponds to the SGF LB property. + m_label = new Vector(); + + // When navigating the tree, the "recent" child of each parent + // is the one that the "forward" button will navigate to. + m_recent = false; + + // This node's move. + setMove(move); + } + + public void setMove(Move move) { + m_move = move; + } + + public Move getMove() { + return m_move; + } + + public boolean hasMove() { + return m_move != null; + } + + public void setParent(Node parent) { + m_parent = parent; + } + + public Node getParent() { + return m_parent; + } + + public void setPrev(Node prev) { + m_prev = prev; + } + + public Node getPrev() { + return m_prev; + } + + public void setNext(Node next) { + m_next = next; + } + + public Node getNext() { + return m_next; + } + + /** Sets the first child of this node. This does not update the sibling pointers of the child. */ + public void setFirstChild(Node child) { + m_child = child; + } + + /** Removes this node from the gametree. */ + public void removeSelf() { + Node prev = getPrev(); + Node next = getNext(); + + if (prev == null) { + // need to fix parent since we're first child + if (getParent() != null) { + getParent().setFirstChild(next); + } + } else { + prev.setNext(next); + } + + if (next != null) { + next.setPrev(prev); + } + } + + /** Moves this node to the start of its sibling list. */ + public void moveToFirst() { + Node parent = getParent(); + if (parent == null) { + return; + } + this.removeSelf(); + parent.addFirstChild(this); + } + + /** Moves this node and all of its parents to the start of their sibling lists */ + public void makeMain() { + Node node = this; + while (node != null) { + node.moveToFirst(); + node = node.getParent(); + } + } + + /** + * Adds a child to the beginning of the list of children. + * + * @param child Node to be added to start of list. + */ + public void addFirstChild(Node child) { + Node oldfirst = m_child; + m_child = child; + child.setParent(this); + child.setPrev(null); + child.setNext(oldfirst); + if (oldfirst != null) { + oldfirst.setPrev(child); + } + } + + /** + * Adds a child to the end of the list of children. + * + * @param child Node to be added to end of list. + */ + public void addChild(Node child) { + child.setNext(null); + child.setParent(this); + + if (m_child == null) { + m_child = child; + child.setPrev(null); + } else { + Node cur = m_child; + while (cur.getNext() != null) cur = cur.getNext(); + cur.setNext(child); + child.setPrev(cur); + } + } + + public boolean hasChild() { + return m_child != null; + } + + /** Returns the number of children of this node. */ + public int numChildren() { + int num = 0; + Node cur = m_child; + while (cur != null) { + num++; + cur = cur.getNext(); + } + return num; + } + + /** + * Returns the nth child. + * + * @param n The number of the child to return. + * @return The nth child or null that child does not exist. + */ + public Node getChild(int n) { + Node cur = m_child; + for (int i = 0; cur != null; i++) { + if (i == n) return cur; + cur = cur.getNext(); + } + return null; + } + + /** + * Mark the current node as the most recently used among its siblings. This also unmarks the + * siblings + */ + public void markRecent() { + Node parent = getParent(); + if (parent != null) { + int n = parent.numChildren(); + for (int i = 0; i < n; i++) { + parent.getChild(i).setRecent(false); + } + } + this.setRecent(true); + } + + /** + * Set the "recent" property of this node. In a list of variations, the recent one is the most + * recently used. It is the variation that the "forward" button should select. + */ + public void setRecent(boolean b) { + m_recent = b; + } + + /** Get the "recent" property of this node. */ + public boolean isRecent() { + return m_recent; + } + + /** + * Returns the first child. + * + * @return first child or null if no children. + */ + public Node getChild() { + return getChild(0); + } + + /** + * Returns the most recent child. + * + * @return most recent child, or first child if no recent one, or null if no + * children. + */ + public Node getRecentChild() { + Node cur = m_child; + if (cur == null) { + return null; + } + for (int i = 0; cur != null; i++) { + if (cur.isRecent()) { + return cur; + } + cur = cur.getNext(); + } + return m_child; + } + + /** Returns the child that contains node in its subtree. Currently unused. */ + public Node getChildContainingNode(Node node) { + for (int i = 0; i < numChildren(); i++) { + Node c = getChild(i); + if (c == node) return c; + } + for (int i = 0; i < numChildren(); i++) { + Node c = getChild(i); + if (c.getChildContainingNode(node) != null) return c; + } + return null; + } + + /** Returns the depth of this node. */ + public int getDepth() { + Node cur; + int depth = 0; + for (cur = this; ; depth++) { + Node parent = cur.getParent(); + if (parent == null) break; + cur = parent; + } + return depth; + } + + /** Determines if the current node is a swap node */ + public boolean isSwap() { + if (this.hasMove()) { + HexPoint p = m_move.getPoint(); + return p == HexPoint.SWAP_SIDES || p == HexPoint.SWAP_PIECES; + } else { + return false; + } + } + + // ---------------------------------------------------------------------- + + /** + * Adds a property to this node. Node properties are (key, value) pairs of strings. + * These properties will stored if the gametree is saved in SGF format. + * + * @param key name of the property + * @param value value of the property + */ + public void setSgfProperty(String key, String value) { + m_property.put(key, value); + } + + public void unsetSgfProperty(String key) { + m_property.remove(key); + } + + /** Append the given string to the SGF property */ + public void appendSgfProperty(String key, String toadd) { + String old = m_property.get(key); + if (old == null) old = ""; + m_property.put(key, old + toadd); + } + + /** + * Returns the value of a property. + * + * @param key name of property + * @return value of key or null if key is not in the property list. + */ + public String getSgfProperty(String key) { + return m_property.get(key); + } + + /** + * Returns a map of the current set of properties. + * + * @return Map containing the properties + */ + public Map getProperties() { + return m_property; + } + + /** Sets the SGF Comment field of this node. */ + public void setComment(String comment) { + setSgfProperty("C", comment); + } + + public String getComment() { + return getSgfProperty("C"); + } + + public boolean hasCount() { + return (getSgfProperty("CN") != null); + } + + public String getCount() { + return getSgfProperty("CN"); + } + + /** Adds a stone of specified color to the setup list and the sgf property string. */ + public void addSetup(HexColor color, HexPoint point) { + m_setup.put(point, color); + } + + public void removeSetup(HexColor color, HexPoint point) { + m_setup.remove(point); + } + + /** Returns the set of setup stones of color. */ + public Vector getSetup(HexColor color) { + Vector points = new Vector(); + HexPoint key; + Iterator i = m_setup.keySet().iterator(); + + while (i.hasNext()) { + key = (HexPoint) i.next(); + if (m_setup.get(key) == color) { + points.add(key); + } + } + + return points; + } + + /** Determine whether the current node has any setup moves */ + public boolean hasSetup() { + return !m_setup.isEmpty(); + } + + /** + * Determine whether the current node can accept updates to setup moves. This happens if the node + * has no m_move and no children. In particular, setup is permitted on the root node (which never + * has an m_move). + */ + public boolean canSetup() { + if (this.hasMove()) { + return false; + } + if (this.hasChild()) { + return false; + } + return true; + } + + public boolean hasLabel() { + return !m_label.isEmpty(); + } + + public Vector getLabels() { + return m_label; + } + + public void addLabel(String str) { + m_label.add(str); + } + + /** + * Return the default player to move for the current node, i.e., the player who would be moving + * next if the PL property is not set. For normal moves, passes, and swap-pieces, this is the + * opponent of the player who made the move; for swap-sides, it is the player who made the move; + * for the root node, it is Black; for resign, forfeit, and setup moves, there is no default, + * i.e., return null + */ + public HexColor defaultPlayerToMove() { + if (this.hasMove()) { + HexPoint p = m_move.getPoint(); + if (p == HexPoint.RESIGN || p == HexPoint.FORFEIT) { + return null; + } else if (p == HexPoint.SWAP_SIDES) { + return m_move.getColor(); + } else { + return m_move.getColor().otherColor(); + } + } else if (this.m_parent == null) { + // root node + return HexColor.BLACK; + } else { + // non-root setup node + return null; + } + } + + /** + * Sets the PL property to the given color. For moves that have a default, unset the property if + * the requested value is the default. + */ + public void setPlayerToMove(HexColor color) { + if (color == defaultPlayerToMove()) { + unsetSgfProperty("PL"); + } else { + setSgfProperty("PL", (color == HexColor.BLACK) ? "B" : "W"); + } + } + + /** + * Compute the player to move, using the "PL" property if it is set and the standard behavior + * otherwise. Never return null. + */ + public HexColor getPlayerToMove() { + String cstr = getSgfProperty("PL"); + if (cstr != null) { + if (cstr.equals("B")) { + return HexColor.BLACK; + } else if (cstr.equals("W")) { + return HexColor.WHITE; + } + } + HexColor color = defaultPlayerToMove(); + if (color != null) { + return color; + } + if (this.hasMove()) { + HexPoint p = m_move.getPoint(); + if (p == HexPoint.RESIGN || p == HexPoint.FORFEIT) { + if (m_parent != null) { + return m_parent.getPlayerToMove(); + } + } + } + return HexColor.BLACK; + } + + // ---------------------------------------------------------------------- + // Debugging output + + /** Print information about self */ + public void printDebug() { + System.out.println("("); + System.out.println("move: " + m_move); + System.out.println("setup: " + m_setup); + System.out.println("label: " + m_label); + System.out.println("property: " + m_property); + System.out.println("recent: " + m_recent); + Node cur = m_child; + while (cur != null) { + cur.printDebug(); + cur = cur.getNext(); + } + System.out.println(")"); + } + + // ---------------------------------------------------------------------- + + private TreeMap m_property; + + private Map m_setup; + + private Vector m_label; + + private Move m_move; + private Node m_parent, m_prev, m_next, m_child; + private boolean m_recent; +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/game/package.html b/src/main/java/hexgui/game/package.html old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/game/package.html rename to src/main/java/hexgui/game/package.html diff --git a/src/main/java/hexgui/gui/AboutDialog.java b/src/main/java/hexgui/gui/AboutDialog.java new file mode 100644 index 0000000..ca09ea7 --- /dev/null +++ b/src/main/java/hexgui/gui/AboutDialog.java @@ -0,0 +1,84 @@ +package hexgui.gui; + +import hexgui.version.Version; +import java.awt.*; +import java.awt.event.*; +import java.net.URL; +import javax.swing.*; +import javax.swing.border.EtchedBorder; +import javax.swing.text.html.HTMLEditorKit; + +// ---------------------------------------------------------------------------- + +/** Shows info about HexGui. */ +public class AboutDialog extends JDialog implements ActionListener { + /** Display modal about dialog. */ + public AboutDialog(Frame owner) { + super(owner, true); + setTitle("About HexGui"); + // setPreferredSize(new Dimension(400,300)); + setResizable(false); + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + JPanel tp = new JPanel(); + tp.setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); + + String benzeneUrl = "http://benzene.sf.net"; + String goguiUrl = "http://gogui.sf.net"; + String about = + "" + + "

" + + "

HexGui

" + + "

Version " + + Version.id + + "

" + + "

Graphical user interface for Hex programs
" + + "(C) 2006-2011 Broderick Arneson.
" + + "" + + benzeneUrl + + "

" + + "

HexGui is based in large part on
" + + "GoGui by Markus Enzenberger
" + + "" + + goguiUrl + + "

" + + "

HexGui is full of Hexy Goodness!

" + + ""; + + JEditorPane text = new JEditorPane(); + text.setEditable(false); + text.setEditorKit(new HTMLEditorKit()); + text.setText(about); + + tp.add(text); + + JButton button = new JButton("OK"); + button.setActionCommand("OK"); + button.addActionListener(this); + + panel.add(tp); + panel.add(button); + add(panel); + + pack(); + } + + public void actionPerformed(ActionEvent e) { + setVisible(false); + } + + private URL getImage(String name) { + ClassLoader loader = getClass().getClassLoader(); + return loader.getResource("hexgui/images/" + name); + } +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/AnalyzeDialog.java b/src/main/java/hexgui/gui/AnalyzeDialog.java new file mode 100644 index 0000000..ee34d02 --- /dev/null +++ b/src/main/java/hexgui/gui/AnalyzeDialog.java @@ -0,0 +1,483 @@ +// AnalyzeDialog.java + +package hexgui.gui; + +import static hexgui.hex.HexColor.BLACK; +import static hexgui.hex.HexColor.EMPTY; +import static hexgui.hex.HexColor.WHITE; + +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.htp.AnalyzeCommand; +import hexgui.htp.AnalyzeDefinition; +import hexgui.htp.AnalyzeType; +// import hexgui.htp.GtpResponseFormatError; +// import hexgui.htp.GtpUtil; +import hexgui.util.Platform; +import hexgui.util.PrefUtil; +import java.awt.BorderLayout; +import java.awt.Container; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Frame; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.io.File; +import java.util.ArrayList; +import javax.swing.Box; +import javax.swing.ButtonGroup; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRadioButton; +import javax.swing.JScrollPane; +import javax.swing.ListSelectionModel; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; + +/** Dialog for selecting an AnalyzeCommand. */ +public final class AnalyzeDialog extends JDialog implements ActionListener, ListSelectionListener { + /** Callback for actions generated by AnalyzeDialog. */ + public interface Listener { + void actionClearAnalyzeCommand(); + + void actionSetAnalyzeCommand( + AnalyzeCommand command, + boolean autoRun, + boolean clearBoard, + boolean oneRunOnly, + boolean reuseTextWindow); + } + + public AnalyzeDialog( + Frame owner, + Listener listener, + ArrayList commands, + MessageDialogs messageDialogs) { + super(owner, "Analyze"); + m_messageDialogs = messageDialogs; + m_commands = commands; + m_listener = listener; + Container contentPane = getContentPane(); + JPanel commandPanel = createCommandPanel(); + contentPane.add(commandPanel, BorderLayout.CENTER); + comboBoxChanged(); + setSelectedColor(BLACK); + int minWidth = commandPanel.getPreferredSize().width; + setMinimumSize(new Dimension(minWidth, 192)); + pack(); + addWindowListener( + new WindowAdapter() { + public void windowActivated(WindowEvent e) { + m_comboBoxHistory.requestFocusInWindow(); + } + }); + } + + public void actionPerformed(ActionEvent event) { + String command = event.getActionCommand(); + if (command.equals("clear")) clearCommand(); + else if (command.equals("comboBoxChanged")) comboBoxChanged(); + else if (command.equals("run")) runCommand(); + else assert false; + } + + public void dispose() { + if (!m_autoRun.isSelected()) clearCommand(); + saveRecent(); + super.dispose(); + } + + public boolean getReuseTextWindow() { + return m_reuseWindow.isSelected(); + } + + public HexColor getSelectedColor() { + if (m_black.isSelected()) return BLACK; + else return WHITE; + } + + public void saveRecent() { + ArrayList recent = new ArrayList(MAX_SAVE_RECENT); + int start = (m_firstIsTemp ? 1 : 0); + for (int i = start; i < getComboBoxItemCount(); ++i) { + String name = getComboBoxItem(i); + if (recent.indexOf(name) < 0) recent.add(name); + } + for (int i = 0; i < m_fullRecentList.size(); ++i) { + if (recent.size() == MAX_SAVE_RECENT) break; + String name = m_fullRecentList.get(i); + if (recent.indexOf(name) < 0) recent.add(name); + } + PrefUtil.putList("net/sf/hexgui/gui/analyzedialog/recentcommands", recent); + } + + /** + * Set board size. Need for verifying responses to initial value for EPLIST commands. Default is + * 19. + */ + public void setBoardSize(int boardSize) { + m_boardSize = boardSize; + } + + public void setReuseTextWindow(boolean enable) { + m_reuseWindow.setSelected(enable); + } + + public void setSelectedColor(HexColor color) { + m_selectedColor = color; + selectColor(); + } + + public void valueChanged(ListSelectionEvent e) { + int index = m_list.getSelectedIndex(); + if (index >= 0) selectCommand(index); + } + + private static final int MAX_SAVE_RECENT = 100; + + /** + * Is the first item in the history combo box a temporary item? Avoids that the first item in the + * history combo box is treated as a real history command, if it was not run. + */ + private boolean m_firstIsTemp; + + private int m_boardSize = HexPoint.DEFAULT_SIZE; + + private ArrayList m_fullRecentList; + + private HexColor m_selectedColor = EMPTY; + + // private final MessageDialogs m_messageDialogs; + + private JButton m_clearButton; + + private JButton m_runButton; + + private JCheckBox m_autoRun; + + private JCheckBox m_clearBoard; + + private JCheckBox m_reuseWindow; + + private JComboBox m_comboBoxHistory; + + private JList m_list; + + private Box m_colorBox; + + private JRadioButton m_black; + + private JRadioButton m_white; + + private final ArrayList m_commands; + + private final MessageDialogs m_messageDialogs; + + private final Listener m_listener; + + private String m_lastUpdateOptionsCommand; + + private void clearCommand() { + m_listener.actionClearAnalyzeCommand(); + m_autoRun.setSelected(false); + } + + private void comboBoxChanged() { + Object item = m_comboBoxHistory.getSelectedItem(); + if (item == null) { + m_list.clearSelection(); + return; + } + String label = item.toString(); + updateOptions(label); + String selectedValue = (String) m_list.getSelectedValue(); + if (selectedValue != null && !selectedValue.equals(label)) m_list.clearSelection(); + } + + private JPanel createButtons() { + JPanel innerPanel = new JPanel(new GridLayout(1, 0, 5, 0)); + m_runButton = new JButton("Run"); + m_runButton.setToolTipText("Run Command"); + m_runButton.setActionCommand("run"); + m_runButton.addActionListener(this); + m_runButton.setMnemonic(KeyEvent.VK_R); + m_runButton.setEnabled(false); + GuiUtil.setMacBevelButton(m_runButton); + innerPanel.add(m_runButton); + m_clearButton = new JButton("Clear"); + m_clearButton.setToolTipText(("Clear command")); + m_clearButton.setActionCommand("clear"); + m_clearButton.addActionListener(this); + m_clearButton.setMnemonic(KeyEvent.VK_C); + GuiUtil.setMacBevelButton(m_clearButton); + innerPanel.add(m_clearButton); + JPanel outerPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + outerPanel.add(innerPanel); + return outerPanel; + } + + private JComponent createColorPanel() { + m_colorBox = Box.createVerticalBox(); + ButtonGroup group = new ButtonGroup(); + m_black = new JRadioButton("Black"); + m_black.setToolTipText("Black"); + m_black.setEnabled(false); + group.add(m_black); + m_colorBox.add(m_black); + m_white = new JRadioButton("White"); + m_white.setToolTipText("White"); + m_white.setEnabled(false); + group.add(m_white); + m_colorBox.add(m_white); + return m_colorBox; + } + + private JPanel createCommandPanel() { + JPanel panel = new JPanel(new BorderLayout()); + m_list = new JList(); + m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + m_list.setVisibleRowCount(25); + m_list.addMouseListener( + new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + int modifiers = e.getModifiers(); + int mask = ActionEvent.ALT_MASK; + if (e.getClickCount() == 2 || ((modifiers & mask) != 0)) { + // int index = + // m_list.locationToIndex(event.getPoint()); + runCommand(); + } + } + }); + m_list.addFocusListener( + new FocusAdapter() { + public void focusGained(FocusEvent e) { + int index = getSelectedCommand(); + if (index >= 0) m_list.setSelectedIndex(index); + } + }); + m_list.addListSelectionListener(this); + JScrollPane scrollPane = new JScrollPane(m_list); + if (Platform.isMac()) + // Default Apple L&F uses no border, but Quaqua 3.7.4 does + scrollPane.setBorder(null); + panel.add(scrollPane, BorderLayout.CENTER); + panel.add(createLowerPanel(), BorderLayout.SOUTH); + String[] labels = new String[m_commands.size()]; + for (int i = 0; i < m_commands.size(); ++i) labels[i] = m_commands.get(i).getLabel(); + m_list.setListData(labels); + comboBoxChanged(); + loadRecent(); + return panel; + } + + private JComponent createLowerPanel() { + Box panel = Box.createVerticalBox(); + panel.add(GuiUtil.createFiller()); + m_comboBoxHistory = new JComboBox(); + panel.add(m_comboBoxHistory); + Box lowerPanel = Box.createVerticalBox(); + lowerPanel.setBorder(GuiUtil.createEmptyBorder()); + panel.add(lowerPanel); + Box optionsPanel = Box.createHorizontalBox(); + lowerPanel.add(optionsPanel); + JPanel leftPanel = new JPanel(); + optionsPanel.add(leftPanel); + Box leftBox = Box.createVerticalBox(); + leftPanel.add(leftBox); + m_autoRun = new JCheckBox("Autorun"); + m_autoRun.addItemListener( + new ItemListener() { + public void itemStateChanged(ItemEvent e) { + if (!m_autoRun.isSelected()) m_listener.actionClearAnalyzeCommand(); + } + }); + m_autoRun.setToolTipText("Autorun"); + m_autoRun.setEnabled(false); + leftBox.add(m_autoRun); + m_clearBoard = new JCheckBox("Clearboard"); + m_clearBoard.setToolTipText("Clearboard"); + m_clearBoard.setEnabled(false); + leftBox.add(m_clearBoard); + m_clearBoard.setSelected(true); + m_reuseWindow = new JCheckBox("Reuse Text"); + m_reuseWindow.setToolTipText("Reuse text window"); + leftBox.add(m_reuseWindow); + JPanel rightPanel = new JPanel(); + rightPanel.add(createColorPanel()); + optionsPanel.add(rightPanel); + + // TODO: The following horizontal glue does not really work as expected + // (tested on Linux/Sun Java 1.6.0_14) and the left two components in + // the box are not aligned to the left. + optionsPanel.add(Box.createHorizontalGlue()); + + // TODO: If GTK Looks L&F is used on Linux/Sun Java 1.6.0_14 or OpenJDK + // 6b14-1.4.1-0ubuntu11, then the text of the checkbox items can be + // truncated a bit on the left (wrong minimum size calculation?). The + // two fillers are a workaround for this. + optionsPanel.add(GuiUtil.createFiller()); + optionsPanel.add(GuiUtil.createFiller()); + + lowerPanel.add(createButtons()); + m_comboBoxHistory.addActionListener(this); + return panel; + } + + private String getComboBoxItem(int i) { + return m_comboBoxHistory.getItemAt(i).toString(); + } + + private int getComboBoxItemCount() { + return m_comboBoxHistory.getItemCount(); + } + + private int getCommandIndex(String label) { + for (int i = 0; i < m_commands.size(); ++i) + if (m_commands.get(i).getLabel().equals(label)) return i; + return -1; + } + + private int getSelectedCommand() { + Object item = m_comboBoxHistory.getSelectedItem(); + if (item == null) return -1; + return getCommandIndex(item.toString()); + } + + private void insertComboBoxItem(String label, int index) { + m_comboBoxHistory.insertItemAt(GuiUtil.createComboBoxItem(label), index); + } + + private void loadRecent() { + m_comboBoxHistory.removeAllItems(); + m_fullRecentList = PrefUtil.getList("net/sf/hexgui/gui/analyzedialog/recentcommands"); + for (int i = 0; i < m_fullRecentList.size(); ++i) { + String name = m_fullRecentList.get(i); + if (getCommandIndex(name) >= 0) m_comboBoxHistory.addItem(GuiUtil.createComboBoxItem(name)); + if (m_comboBoxHistory.getItemCount() > 20) break; + } + int index = getSelectedCommand(); + if (index >= 0) selectCommand(index); + m_firstIsTemp = false; + } + + private void runCommand() { + // if (m_gtp.isCommandInProgress()) + // { + // showError("MSG_ANALYZE_CANNOT_EXECUTE", + // "MSG_ANALYZE_CANNOT_EXECUTE_2", + // false); + // return; + // } + int index = getSelectedCommand(); + if (index < 0) { + // String name = m_gtp.getName(); + // if (name == null) + // showError("MSG_ANALYZE_NOT_SUPPORTED", + // "MSG_ANALYZE_NOT_SUPPORTED_2", false); + // else + // showError("MSG_ANALYZE_NOT_SUPPORTED", + // "MSG_ANALYZE_NOT_SUPPORTED_3", false, name); + return; + } + updateRecent(index); + AnalyzeCommand command = new AnalyzeCommand(m_commands.get(index)); + if (command.needsColorArg()) command.setColorArg(getSelectedColor()); + String label = command.getResultTitle(); + if (command.needsStringArg()) { + String stringArg = + JOptionPane.showInputDialog(this, label, "TIT_INPUT", JOptionPane.PLAIN_MESSAGE); + if (stringArg == null) return; + command.setStringArg(stringArg); + } + if (command.needsOptStringArg()) {} + if (command.getType() == AnalyzeType.EPLIST) {} + if (command.needsFileArg()) { + File fileArg = FileDialogs.showSelectFile(this, label); + if (fileArg == null) return; + command.setFileArg(fileArg); + } + if (command.needsFileOpenArg()) { + File fileArg = FileDialogs.showOpen(this, label); + if (fileArg == null) return; + command.setFileOpenArg(fileArg); + } + if (command.needsFileSaveArg()) { + File fileArg = FileDialogs.showSave(this, label, m_messageDialogs); + if (fileArg == null) return; + command.setFileSaveArg(fileArg); + } + if (command.needsColorArg()) command.setColorArg(getSelectedColor()); + boolean autoRun = m_autoRun.isEnabled() && m_autoRun.isSelected(); + boolean clearBoard = !m_clearBoard.isEnabled() || m_clearBoard.isSelected(); + boolean reuseWindow = m_reuseWindow.isEnabled() && m_reuseWindow.isSelected(); + m_listener.actionSetAnalyzeCommand(command, autoRun, clearBoard, false, reuseWindow); + } + + private void selectCommand(int index) { + String label = m_commands.get(index).getLabel(); + updateOptions(label); + m_comboBoxHistory.removeActionListener(this); + if (m_firstIsTemp && getComboBoxItemCount() > 0) m_comboBoxHistory.removeItemAt(0); + if (getComboBoxItemCount() == 0 || !getComboBoxItem(0).equals(label)) { + insertComboBoxItem(label, 0); + m_firstIsTemp = true; + m_comboBoxHistory.setSelectedIndex(0); + } + m_comboBoxHistory.addActionListener(this); + } + + private void selectColor() { + if (m_selectedColor == BLACK) m_black.setSelected(true); + else if (m_selectedColor == WHITE) m_white.setSelected(true); + } + + private void showError(String mainMessage, String optionalMessage, boolean isCritical) { + ShowError.msg(this, mainMessage); + } + + private void showError( + String mainMessage, String optionalMessage, boolean isCritical, Object... args) { + ShowError.msg(this, mainMessage); + } + + private void updateOptions(String label) { + if (label.equals(m_lastUpdateOptionsCommand)) return; + m_lastUpdateOptionsCommand = label; + int index = getCommandIndex(label); + if (index < 0) return; + AnalyzeCommand command = new AnalyzeCommand(m_commands.get(index)); + boolean needsColorArg = command.needsColorArg(); + m_black.setEnabled(needsColorArg); + m_white.setEnabled(needsColorArg); + m_autoRun.setEnabled(command.getType() != AnalyzeType.PARAM); + m_autoRun.setSelected(false); + m_clearBoard.setEnabled(command.getType() != AnalyzeType.PARAM); + m_runButton.setEnabled(true); + } + + private void updateRecent(int index) { + String label = m_commands.get(index).getLabel(); + insertComboBoxItem(label, 0); + m_comboBoxHistory.setSelectedIndex(0); + for (int i = 1; i < getComboBoxItemCount(); ++i) + if (getComboBoxItem(i).equals(label)) m_comboBoxHistory.removeItemAt(i); + m_firstIsTemp = false; + } +} diff --git a/src/main/java/hexgui/gui/BoardDrawerBase.java b/src/main/java/hexgui/gui/BoardDrawerBase.java new file mode 100644 index 0000000..3fa4d36 --- /dev/null +++ b/src/main/java/hexgui/gui/BoardDrawerBase.java @@ -0,0 +1,343 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.util.Pair; + +import javax.swing.*; +import java.awt.*; +import java.net.URL; +import java.util.Vector; + +// ---------------------------------------------------------------------------- + +/** + * Base class for board drawing. + * + *

Board drawers are responsible for drawing the background, labels, field outlines, and stone + * shadows. In addition, they are also responsible for determining the actual position of each field + * in the window. Field contents (i.e. stones, markers, numerical values, etc) are not drawn, they + * are drawn with the GuiField class. + * + *

Board sizes supported are m x n where m and n range + * from 1 to 26. By default, black connects top and bottom and should be labeled with letters. White + * connects left and right and should be labeled with numbers. + */ +public abstract class BoardDrawerBase { + public BoardDrawerBase() { + m_background = null; + m_aspect_ratio = 1.0; + } + + /** + * Loads the image in filename and sets it as the background. If filename + * does not exist no background image is displayed. Image will be scaled to fit the + * window. + * + * @param filename filename of the image to use as a background. + */ + public void loadBackground(String filename) { + ClassLoader classLoader = getClass().getClassLoader(); + URL url = classLoader.getResource(filename); + if (url == null) { + System.out.println("loadBackground: could not load '" + filename + "'!"); + m_background = null; + } else { + m_background = new ImageIcon(url).getImage(); + } + } + + /** + * Gets the field containing the specified point. NOTE: uses the position of fields from the last + * call to draw(). Also assumes the set of fields given are the same as those in the last call to + * draw(). + * + * @param p the point + * @param field the set of fields to search through. + * @return the field in the set that p is in or null if p is not in any field. + */ + public GuiField getFieldContaining(Point p, GuiField field[]) { + if (m_outline == null) return null; + for (int x = 0; x < field.length; x++) { + if (m_outline[x].contains(p)) return field[x]; + } + return null; + } + + /** + * Draws the board. The size of the region to draw to, the size of the board, and the field to + * draw must be given. The position of each field is then calculated and the board drawn. + * + * @param g graphics context to draw to + * @param w the width of the region to draw in + * @param h the height of the region to draw in + * @param bw the width of the board (in fields) + * @param bh the height of the board (in fields) + * @param alphaontop true if letters are on top, otherwise numbers + * @param field the fields to draw + * @param arrows the list of arrows to draw + */ + public void draw( + Graphics g, + int w, + int h, + int bw, + int bh, + boolean alphaontop, + GuiField field[], + Vector> arrows) { + m_width = w; + m_height = h; + + m_bwidth = bw; + m_bheight = bh; + + m_alphaontop = alphaontop; + + computeFieldPlacement(); + m_outline = calcCellOutlines(field); + + setAntiAliasing(g); + drawBackground(g); + drawCells(g, field); + drawLabels(g, alphaontop); + drawShadows(g, field); + drawFields(g, field); + drawAlpha(g, field); + + drawArrows(g, arrows); + } + + // ------------------------------------------------------------ + + protected abstract Point getLocation(HexPoint p); + + /** + * Calculates the width of a field given the dimensions of the window and board. + * + * @param w width of window + * @param h height of window + * @param bw width of board + * @param bh height of board + */ + protected abstract int calcFieldWidth(int w, int h, int bw, int bh); + + /** + * Calculates the height of a field given the dimensions of the window and board. + * + * @see calcFieldWidth + */ + protected abstract int calcFieldHeight(int w, int h, int bw, int bh); + + protected abstract int calcStepSize(); + + /** + * Calculates the width of the board in pixels. + * + * @requires calcFieldWidth and calcFieldHeight to have been called. + */ + protected abstract int calcBoardWidth(); + + /** + * Calculates the height of the board in pixels. + * + * @requires calcFieldWidth and calcFieldHeight to have been called. + */ + protected abstract int calcBoardHeight(); + + /** + * Performs any necessary initializations for drawing the outlines of the fields. + * + * @param the fields it will need to draw + */ + protected abstract Polygon[] calcCellOutlines(GuiField field[]); + + /** + * Draws the outlines of the given fields. + * + * @param g graphics context to draw to. + * @param field the list of fields to draw. + */ + protected void drawCells(Graphics g, GuiField field[]) { + g.setColor(Color.black); + for (int i = 0; i < m_outline.length; i++) { + if ((field[i].getAttributes() & GuiField.DRAW_CELL_OUTLINE) != 0) { + g.drawPolygon(m_outline[i]); + } + } + + g.setColor(Color.yellow); + for (int i = 0; i < m_outline.length; i++) { + if ((field[i].getAttributes() & GuiField.SELECTED) != 0) { + g.drawPolygon(m_outline[i]); + } + } + } + + protected void computeFieldPlacement() { + m_fieldWidth = calcFieldWidth(m_width, m_height, m_bwidth, m_bheight); + m_fieldHeight = calcFieldHeight(m_width, m_height, m_bwidth, m_bheight); + + if (m_fieldHeight >= (int) (m_fieldWidth / m_aspect_ratio)) { + m_fieldHeight = (int) (m_fieldWidth / m_aspect_ratio); + } else { + m_fieldWidth = (int) (m_fieldHeight * m_aspect_ratio); + } + + // If field dimensions are not even then the inner cell lines + // on the board can be doubled up. + // FIXME: lines still get doubled up...why? + if ((m_fieldWidth & 1) != 0) m_fieldWidth--; + if ((m_fieldHeight & 1) != 0) m_fieldHeight--; + + m_fieldRadius = (m_fieldWidth < m_fieldHeight) ? m_fieldWidth : m_fieldHeight; + + m_step = calcStepSize(); + + int bw = calcBoardWidth(); + int bh = calcBoardHeight(); + + // add a half cell's worth of empty space + int extra = (m_width - (bw + 3 * m_fieldWidth)); + m_marginX = extra / 2 + 3 * m_fieldWidth / 2; + + m_marginY = (m_height - bh) / 2 + m_fieldHeight / 2; + } + + // ------------------------------------------------------------ + + protected int getShadowOffset() { + return (m_fieldRadius - 2 * GuiField.getStoneMargin(m_fieldRadius)) / 12; + } + + protected void drawBackground(Graphics g) { + if (m_background != null) g.drawImage(m_background, 0, 0, m_width, m_height, null); + } + + protected void drawLabel(Graphics g, Point p, String string, int xoff) { + double size = Math.min(m_fieldWidth, m_fieldHeight) * 0.4; + Font f = g.getFont(); + Font f2 = f.deriveFont((float) size); + + FontMetrics fm = g.getFontMetrics(f2); + int width = fm.stringWidth(string); + int height = fm.getAscent(); + + g.setFont(f2); + int x = width / 2; + int y = height / 2; + g.drawString(string, p.x + xoff - x, p.y + y); + g.setFont(f); + } + + protected abstract void drawLabels(Graphics g, boolean alphatop); + + protected void drawShadows(Graphics graphics, GuiField[] field) { + if (m_fieldRadius <= 5) return; + Graphics2D graphics2D = graphics instanceof Graphics2D ? (Graphics2D) graphics : null; + if (graphics2D == null) return; + graphics2D.setComposite(COMPOSITE_3); + int size = m_fieldRadius - 2 * GuiField.getStoneMargin(m_fieldRadius); + int offset = getShadowOffset(); + for (int pos = 0; pos < field.length; pos++) { + if (field[pos].getColor() == HexColor.EMPTY) continue; + Point location = getLocation(field[pos].getPoint()); + graphics.setColor(Color.black); + graphics.fillOval(location.x - size / 2 + offset, location.y - size / 2 + offset, size, size); + } + graphics.setPaintMode(); + } + + protected void drawFields(Graphics g, GuiField field[]) { + for (int x = 0; x < field.length; x++) { + Point p = getLocation(field[x].getPoint()); + field[x].draw(g, p.x, p.y, m_fieldWidth, m_fieldHeight); + } + } + + protected void drawAlpha(Graphics g, GuiField field[]) { + if (g instanceof Graphics2D) { + Graphics2D g2d = (Graphics2D) g; + + for (int i = 0; i < m_outline.length; i++) { + if ((field[i].getAttributes() & GuiField.DRAW_ALPHA) == 0) continue; + + Color color = field[i].getAlphaColor(); + if (color == null) continue; + + g2d.setComposite( + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, field[i].getAlphaBlend())); + + g2d.setColor(color); + g2d.fillPolygon(m_outline[i]); + } + } + } + + protected void drawArrows(Graphics g, Vector> arrows) { + if (g instanceof Graphics2D) { + Graphics2D g2d = (Graphics2D) g; + g2d.setColor(Color.BLUE); + for (int i = 0; i < arrows.size(); i++) { + Point fm = getLocation(arrows.get(i).first); + Point to = getLocation(arrows.get(i).second); + drawArrow(g2d, fm.x, fm.y, to.x, to.y, 1.5); + } + } + } + + protected void setAntiAliasing(Graphics g) { + if (g instanceof Graphics2D) { + Graphics2D g2d = (Graphics2D) g; + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + } + + public static void drawArrow(Graphics g2d, int x1, int y1, int x2, int y2, double stroke) { + double aDir = Math.atan2(x1 - x2, y1 - y2); + g2d.drawLine(x2, y2, x1, y1); + // make the arrow head solid even if dash pattern has been specified + // g2d.setStroke(new BasicStroke(1f)); + + Polygon tmpPoly = new Polygon(); + int i1 = 12 + (int) (stroke * 2); + // make the arrow head the same size regardless of the length length + int i2 = 6 + (int) stroke; + tmpPoly.addPoint(x2, y2); // arrow tip + tmpPoly.addPoint(x2 + xCor(i1, aDir + .5), y2 + yCor(i1, aDir + .5)); + tmpPoly.addPoint(x2 + xCor(i2, aDir), y2 + yCor(i2, aDir)); + tmpPoly.addPoint(x2 + xCor(i1, aDir - .5), y2 + yCor(i1, aDir - .5)); + tmpPoly.addPoint(x2, y2); // arrow tip + g2d.drawPolygon(tmpPoly); + g2d.fillPolygon(tmpPoly); + } + + private static int yCor(int len, double dir) { + return (int) (len * Math.cos(dir)); + } + + private static int xCor(int len, double dir) { + return (int) (len * Math.sin(dir)); + } + + protected boolean m_alphaontop; + + protected double m_aspect_ratio; + + protected Image m_background; + + protected int m_width, m_height; + protected int m_bwidth, m_bheight; + protected int m_marginX, m_marginY; + protected int m_fieldWidth, m_fieldHeight, m_fieldRadius, m_step; + protected Polygon m_outline[]; + + protected static final AlphaComposite COMPOSITE_3 = + AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f); +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/BoardDrawerDiamond.java b/src/main/java/hexgui/gui/BoardDrawerDiamond.java new file mode 100644 index 0000000..0dcab07 --- /dev/null +++ b/src/main/java/hexgui/gui/BoardDrawerDiamond.java @@ -0,0 +1,114 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.HexPoint; +import hexgui.util.Hexagon; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +// ---------------------------------------------------------------------------- + +public class BoardDrawerDiamond extends BoardDrawerBase { + + protected static final double ASPECT_RATIO = 1.1547; + + public BoardDrawerDiamond() { + super(); + loadBackground("hexgui/images/wood.png"); + m_aspect_ratio = ASPECT_RATIO; + } + + /** + * Returns the location in the window of the field with coordinates (x,y). + * Coordinates increase to the right and down, with the top left of the board having coordinates + * (0,0). Negative values are acceptable. + * + * @param x the x coordinate of the field. + * @param y the y coordinate of the field. + * @return the center of the field at (x,y). + */ + protected Point getLocation(int x, int y) { + // yoffset will be positive when bwidth > bheight (to push the + // board down) and negative when bwidth < bheight (to lift it + // up) because the a1 square (0,0) will not be + // in the center of the vertical space occupied by the board. + int yoffset = (m_bwidth - m_bheight) * m_fieldHeight / 2; + + Point ret = new Point(); + ret.x = m_marginX + (y + x) * m_step; + ret.y = m_marginY + yoffset + (m_bheight / 2) * m_fieldHeight + (y - x) * m_fieldHeight / 2; + return ret; + } + + /** Returns the location of the field with HexPoint pos. */ + protected Point getLocation(HexPoint pos) { + if (pos == HexPoint.EAST) { + return getLocation(m_bwidth + 1, m_bheight / 2 - 1); + } else if (pos == HexPoint.WEST) { + return getLocation(-2, m_bheight / 2 + 1); + } else if (pos == HexPoint.SOUTH) { + return getLocation(m_bwidth / 2 - 1, m_bheight + 1); + } else if (pos == HexPoint.NORTH) { + return getLocation(m_bwidth / 2 + 1, -2); + } + return getLocation(pos.x, pos.y); + } + + protected int calcFieldWidth(int w, int h, int bw, int bh) { + return w / (bw + (bh - 1) / 2 + 2); + } + + protected int calcFieldHeight(int w, int h, int bw, int bh) { + return h / (bh + 2); + } + + protected int calcStepSize() { + return m_fieldWidth / 4 + m_fieldWidth / 2; + } + + protected int calcBoardWidth() { + return (m_bwidth + m_bheight - 1) * m_step; + } + + protected int calcBoardHeight() { + return m_bheight * m_fieldHeight + (m_bwidth - m_bheight) * m_fieldHeight / 2; + } + + protected Polygon[] calcCellOutlines(GuiField field[]) { + Polygon[] outline = new Polygon[field.length]; + for (int x = 0; x < outline.length; x++) { + Point p = getLocation(field[x].getPoint()); + outline[x] = Hexagon.createHorizontalHexagon(p, m_fieldWidth, m_fieldHeight); + } + return outline; + } + + protected void drawLabels(Graphics g, boolean alphatop) { + int xoffset; + String string; + g.setColor(Color.black); + + xoffset = 0; + for (int x = 0; x < m_bwidth; x++) { + if (alphatop) string = Character.toString((char) ((int) 'A' + x)); + else string = Integer.toString(x + 1); + drawLabel(g, getLocation(x, -1), string, xoffset); + drawLabel(g, getLocation(x, m_bheight), string, xoffset); + } + xoffset = 0; + for (int y = 0; y < m_bheight; y++) { + if (!alphatop) string = Character.toString((char) ((int) 'A' + y)); + else string = Integer.toString(y + 1); + drawLabel(g, getLocation(-1, y), string, xoffset); + drawLabel(g, getLocation(m_bwidth, y), string, xoffset); + } + } + + protected Polygon[] m_outline; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/BoardDrawerFlat.java b/src/main/java/hexgui/gui/BoardDrawerFlat.java new file mode 100644 index 0000000..4c637eb --- /dev/null +++ b/src/main/java/hexgui/gui/BoardDrawerFlat.java @@ -0,0 +1,109 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.HexPoint; +import hexgui.util.Hexagon; + +import java.awt.*; + +// ---------------------------------------------------------------------------- + +public class BoardDrawerFlat extends BoardDrawerBase { + + protected static final double ASPECT_RATIO = 1.0 / 1.1547; + + public BoardDrawerFlat() { + super(); + loadBackground("hexgui/images/wood.png"); + m_aspect_ratio = ASPECT_RATIO; + } + + /** + * Returns the location in the window of the field with coordinates (x,y). + * Coordinates increase to the right and down, with the top left of the board having coordinates + * (0,0). Negative values are acceptable. + * + * @param x the x coordinate of the field. + * @param y the y coordinate of the field. + * @return the center of the field at (x,y). + */ + protected Point getLocation(int x, int y) { + Point ret = new Point(); + ret.x = m_marginX + y * m_fieldWidth / 2 + x * m_fieldWidth; + ret.y = m_marginY + y * m_step; + return ret; + } + + /** Returns the location of the field with HexPoint pos. */ + protected Point getLocation(HexPoint pos) { + if (pos == HexPoint.EAST) { + return getLocation(m_bwidth + 1, m_bheight / 2 - 1); + } else if (pos == HexPoint.WEST) { + return getLocation(-2, m_bheight / 2 + 1); + } else if (pos == HexPoint.SOUTH) { + return getLocation(m_bwidth / 2 - 1, m_bheight + 1); + } else if (pos == HexPoint.NORTH) { + return getLocation(m_bwidth / 2 + 1, -2); + } + return getLocation(pos.x, pos.y); + } + + protected int calcFieldWidth(int w, int h, int bw, int bh) { + return w / (bw + (bh - 1) / 2 + 2); + } + + protected int calcFieldHeight(int w, int h, int bw, int bh) { + return h / ((bh + 1) / 2 + (bh / 4) + 4); + } + + protected int calcStepSize() { + return m_fieldHeight / 4 + m_fieldHeight / 2; + } + + protected int calcBoardWidth() { + return m_bwidth * m_fieldWidth + (m_bheight - 1) * m_fieldWidth / 2; + } + + protected int calcBoardHeight() { + return m_fieldHeight * (m_bheight + 1) / 2 + m_fieldHeight * m_bheight / 4; + } + + protected Polygon[] calcCellOutlines(GuiField field[]) { + Polygon outline[] = new Polygon[field.length]; + for (int x = 0; x < outline.length; x++) { + Point p = getLocation(field[x].getPoint()); + outline[x] = Hexagon.createVerticalHexagon(p, m_fieldWidth, m_fieldHeight); + } + return outline; + } + + protected void drawLabels(Graphics g, boolean alphatop) { + String string; + int xoffset, yoffset; + g.setColor(Color.black); + + xoffset = m_fieldWidth / 2; + yoffset = 1; + for (int x = 0; x < m_bwidth; x++) { + if (alphatop) string = Character.toString((char) ((int) 'A' + x)); + else string = Integer.toString(x + 1); + drawLabel(g, getLocation(x, -1), string, xoffset); + drawLabel(g, getLocation(x - yoffset, m_bheight), string, xoffset); + } + xoffset = 0; + yoffset = 0; + for (int y = 0; y < m_bheight; y++) { + if (!alphatop) string = Character.toString((char) ((int) 'A' + y)); + else string = Integer.toString(y + 1); + drawLabel(g, getLocation(-1, y), string, xoffset); + drawLabel(g, getLocation(m_bwidth, y - yoffset), string, xoffset); + } + } + + protected Polygon m_outline[]; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/BoardDrawerFlat2.java b/src/main/java/hexgui/gui/BoardDrawerFlat2.java new file mode 100644 index 0000000..86e9ab9 --- /dev/null +++ b/src/main/java/hexgui/gui/BoardDrawerFlat2.java @@ -0,0 +1,109 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.HexPoint; +import hexgui.util.Hexagon; + +import java.awt.*; + +// ---------------------------------------------------------------------------- + +public class BoardDrawerFlat2 extends BoardDrawerBase { + + protected static final double ASPECT_RATIO = 1.0 / 1.1547; + + public BoardDrawerFlat2() { + super(); + loadBackground("hexgui/images/wood.png"); + m_aspect_ratio = ASPECT_RATIO; + } + + /** + * Returns the location in the window of the field with coordinates (x,y). + * Coordinates increase to the right and down, with the top left of the board having coordinates + * (0,0). Negative values are acceptable. + * + * @param x the x coordinate of the field. + * @param y the y coordinate of the field. + * @return the center of the field at (x,y). + */ + protected Point getLocation(int x, int y) { + Point ret = new Point(); + ret.x = m_marginX + y * m_fieldWidth / 2 + x * m_fieldWidth; + ret.y = m_marginY + (m_bheight - 1 - y) * m_step; + return ret; + } + + /** Returns the location of the field with HexPoint pos. */ + protected Point getLocation(HexPoint pos) { + if (pos == HexPoint.EAST) { + return getLocation(m_bwidth + 1, m_bheight / 2 - 1); + } else if (pos == HexPoint.WEST) { + return getLocation(-2, m_bheight / 2 + 1); + } else if (pos == HexPoint.SOUTH) { + return getLocation(m_bwidth / 2 - 1, m_bheight + 1); + } else if (pos == HexPoint.NORTH) { + return getLocation(m_bwidth / 2 + 1, -2); + } + return getLocation(pos.x, pos.y); + } + + protected int calcFieldWidth(int w, int h, int bw, int bh) { + return w / (bw + (bh - 1) / 2 + 2); + } + + protected int calcFieldHeight(int w, int h, int bw, int bh) { + return h / ((bh + 1) / 2 + (bh / 4) + 4); + } + + protected int calcStepSize() { + return m_fieldHeight / 4 + m_fieldHeight / 2; + } + + protected int calcBoardWidth() { + return m_bwidth * m_fieldWidth + (m_bheight - 1) * m_fieldWidth / 2; + } + + protected int calcBoardHeight() { + return m_fieldHeight * (m_bheight + 1) / 2 + m_fieldHeight * m_bheight / 4; + } + + protected Polygon[] calcCellOutlines(GuiField field[]) { + Polygon outline[] = new Polygon[field.length]; + for (int x = 0; x < outline.length; x++) { + Point p = getLocation(field[x].getPoint()); + outline[x] = Hexagon.createVerticalHexagon(p, m_fieldWidth, m_fieldHeight); + } + return outline; + } + + protected void drawLabels(Graphics g, boolean alphatop) { + String string; + int xoffset, yoffset; + g.setColor(Color.black); + + xoffset = m_fieldWidth / 2; + yoffset = 1; + for (int x = 0; x < m_bwidth; x++) { + if (alphatop) string = Character.toString((char) ((int) 'A' + x)); + else string = Integer.toString(x + 1); + drawLabel(g, getLocation(x, -1), string, xoffset); + drawLabel(g, getLocation(x - yoffset, m_bheight), string, xoffset); + } + xoffset = 0; + yoffset = 0; + for (int y = 0; y < m_bheight; y++) { + if (!alphatop) string = Character.toString((char) ((int) 'A' + y)); + else string = Integer.toString(y + 1); + drawLabel(g, getLocation(-1, y), string, xoffset); + drawLabel(g, getLocation(m_bwidth, y - yoffset), string, xoffset); + } + } + + protected Polygon m_outline[]; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/BoardDrawerGo.java b/src/main/java/hexgui/gui/BoardDrawerGo.java new file mode 100644 index 0000000..068f188 --- /dev/null +++ b/src/main/java/hexgui/gui/BoardDrawerGo.java @@ -0,0 +1,128 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.HexPoint; + +import java.awt.*; + +// ---------------------------------------------------------------------------- + +public class BoardDrawerGo extends BoardDrawerBase { + public BoardDrawerGo() { + super(); + loadBackground("hexgui/images/wood.png"); + } + + protected Point getLocation(int x, int y) { + Point ret = new Point(); + ret.x = m_marginX + (x + 2) * m_fieldWidth; + ret.y = m_marginY + (y + 2) * m_fieldHeight; + return ret; + } + + // FIXME: center stones on even length sides!! + protected Point getLocation(HexPoint pos) { + if (pos == HexPoint.EAST) { + return getLocation(m_bwidth + 1, m_bheight / 2); + } else if (pos == HexPoint.WEST) { + return getLocation(-2, m_bheight / 2); + } else if (pos == HexPoint.SOUTH) { + return getLocation(m_bwidth / 2, m_bheight + 1); + } else if (pos == HexPoint.NORTH) { + return getLocation(m_bwidth / 2, -2); + } + return getLocation(pos.x, pos.y); + } + + protected int calcFieldWidth(int w, int h, int bw, int bh) { + return w / (bw + 4); + } + + protected int calcFieldHeight(int w, int h, int bw, int bh) { + return h / (bh + 4); + } + + // FIXME: not needed... something is wrong with the api? + protected int calcStepSize() { + return 0; + } + + protected int calcBoardWidth() { + return (m_bwidth + 4) * m_fieldWidth; + } + + protected int calcBoardHeight() { + return (m_bheight + 4) * m_fieldHeight; + } + + protected Polygon[] calcCellOutlines(GuiField field[]) { + int w = m_fieldWidth / 2; + int h = m_fieldHeight / 2; + Polygon outline[] = new Polygon[field.length]; + for (int i = 0; i < field.length; i++) { + Point c = getLocation(field[i].getPoint()); + outline[i] = new Polygon(); + outline[i].addPoint(c.x - w, c.y - h); + outline[i].addPoint(c.x + w, c.y - h); + outline[i].addPoint(c.x + w, c.y + h); + outline[i].addPoint(c.x - w, c.y + h); + } + return outline; + } + + protected void drawCells(Graphics g, GuiField field[]) { + g.setColor(Color.black); + Point p = getLocation(0, 0); + int x = p.x; + int y = p.y; + for (int i = 0; i < m_bheight; i++) { + g.drawLine( + x, y + i * m_fieldHeight, x + (m_bwidth - 1) * m_fieldWidth, y + i * m_fieldHeight); + } + for (int i = 0; i < m_bwidth; i++) { + g.drawLine( + x + i * m_fieldWidth, y, x + i * m_fieldWidth, y + (m_bheight - 1) * m_fieldHeight); + } + // diagonal lines from left edge + for (int i = 1; i < m_bheight; i++) { + int j = Math.min(i, m_bwidth - 1); + g.drawLine(x, y + i * m_fieldHeight, x + j * m_fieldWidth, y + (i - j) * m_fieldHeight); + } + // diagonal lines from bottom edge + for (int i = 1; i < m_bwidth; i++) { + int j = Math.min(m_bwidth - i - 1, m_bheight - 1); + int k = Math.max(0, (i + m_bheight - 1) - (m_bwidth - 1)); + g.drawLine( + x + i * m_fieldWidth, + y + (m_bheight - 1) * m_fieldHeight, + x + (i + j) * m_fieldWidth, + y + k * m_fieldHeight); + } + } + + protected void drawLabels(Graphics g, boolean alphatop) { + String string; + int xoffset, yoffset; + g.setColor(Color.black); + + xoffset = yoffset = 0; + for (int x = 0; x < m_bwidth; x++) { + if (alphatop) string = Character.toString((char) ((int) 'A' + x)); + else string = Integer.toString(x + 1); + drawLabel(g, getLocation(x, -1), string, xoffset); + drawLabel(g, getLocation(x, m_bheight), string, xoffset); + } + xoffset = yoffset = 0; + for (int y = 0; y < m_bheight; y++) { + if (!alphatop) string = Character.toString((char) ((int) 'A' + y)); + else string = Integer.toString(y + 1); + drawLabel(g, getLocation(-1, y), string, xoffset); + drawLabel(g, getLocation(m_bwidth, y), string, xoffset); + } + } +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/BoardDrawerY.java b/src/main/java/hexgui/gui/BoardDrawerY.java new file mode 100644 index 0000000..1b805e7 --- /dev/null +++ b/src/main/java/hexgui/gui/BoardDrawerY.java @@ -0,0 +1,107 @@ +// ---------------------------------------------------------------------------- +package hexgui.gui; + +import hexgui.hex.HexPoint; +import hexgui.util.Hexagon; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +// ---------------------------------------------------------------------------- + +public class BoardDrawerY extends BoardDrawerBase { + protected static final double ASPECT_RATIO = 1.0 / 1.1547; + + public BoardDrawerY() { + super(); + loadBackground("hexgui/images/wood.png"); + m_aspect_ratio = ASPECT_RATIO; + } + + /** + * Returns the location in the window of the field with coordinates (x,y). + * Coordinates increase to the right and down, with the top left of the board having coordinates + * (0,0). Negative values are acceptable. + * + * @param x the x coordinate of the field. + * @param y the y coordinate of the field. + * @return the center of the field at (x,y). + */ + protected Point getLocation(int x, int y) { + Point ret = new Point(); + int center = m_marginX + m_bwidth * m_fieldWidth / 2; + ret.x = center - y * m_fieldWidth / 2 + x * m_fieldWidth; + ret.y = m_marginY + y * m_step; + return ret; + } + + /** Returns the location of the field with HexPoint pos. */ + protected Point getLocation(HexPoint pos) { + if (pos == HexPoint.EAST) { + return getLocation(m_bheight / 2 + 2, m_bheight / 2); + } else if (pos == HexPoint.WEST) { + return getLocation(-2, m_bheight / 2); + } else if (pos == HexPoint.SOUTH) { + return getLocation(m_bwidth / 2 + 1, m_bheight + 1); + } + return getLocation(pos.x, pos.y); + } + + protected int calcFieldWidth(int w, int h, int bw, int bh) { + return w / (bw + 3); // width + 2 cells for labels + 1 for spacing + } + + protected int calcFieldHeight(int w, int h, int bw, int bh) { + // each row takes 3/4 of the height of hex + // need 2 extra rows for labels + 1 row of spacing + return h / (3 * bh / 4 + 3); + } + + protected int calcStepSize() { + return m_fieldHeight / 4 + m_fieldHeight / 2; + } + + protected int calcBoardWidth() { + return m_bwidth * m_fieldWidth; + } + + protected int calcBoardHeight() { + // return m_fieldHeight*(m_bheight+1)/2 + // + m_fieldHeight*m_bheight/4; + return 3 * m_fieldHeight / 4 * (m_bheight + 2); + } + + protected Polygon[] calcCellOutlines(GuiField field[]) { + Polygon outline[] = new Polygon[field.length]; + for (int x = 0; x < outline.length; x++) { + Point p = getLocation(field[x].getPoint()); + outline[x] = Hexagon.createVerticalHexagon(p, m_fieldWidth, m_fieldHeight); + } + return outline; + } + + protected void drawLabels(Graphics g, boolean alphatop) { + String string; + int xoffset, yoffset; + g.setColor(Color.black); + + xoffset = m_fieldWidth / 2; + yoffset = 1; + for (int x = 0; x < m_bwidth; x++) { + string = Character.toString((char) ((int) 'A' + x)); + // drawLabel(g, getLocation(x, -1), string, xoffset); + drawLabel(g, getLocation(x, m_bheight), string, xoffset); + } + xoffset = 0; + yoffset = 0; + for (int y = 0; y < m_bheight; y++) { + string = Integer.toString(y + 1); + drawLabel(g, getLocation(-1, y), string, xoffset); + drawLabel(g, getLocation(y + 1, y - yoffset), string, xoffset); + } + } + + protected Polygon m_outline[]; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/BoardSizeDialog.java b/src/main/java/hexgui/gui/BoardSizeDialog.java new file mode 100644 index 0000000..c3db091 --- /dev/null +++ b/src/main/java/hexgui/gui/BoardSizeDialog.java @@ -0,0 +1,28 @@ +// ---------------------------------------------------------------------------- +// $Id: HexGui.java 30 2006-10-27 05:09:12Z broderic $ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** Dialog for entering a board size. */ +public final class BoardSizeDialog { + /** + * Run dialog. + * + * @return Board dimensions as string in format "w x h"; returns "-1 x -1" if aborted. + */ + public static String show(Component parent, Dimension current) { + String ret; + String value = "" + current.width + " x " + current.height; + ret = JOptionPane.showInputDialog(parent, "Board size", value); + return ret; + } + + /** Make constructor unavailable; class is for namespace only. */ + private BoardSizeDialog() {} +} diff --git a/src/main/java/hexgui/gui/ChooseProgramDialog.java b/src/main/java/hexgui/gui/ChooseProgramDialog.java new file mode 100644 index 0000000..620a56e --- /dev/null +++ b/src/main/java/hexgui/gui/ChooseProgramDialog.java @@ -0,0 +1,93 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import java.awt.*; +import java.awt.event.*; +import java.util.Vector; +import javax.swing.*; +import javax.swing.text.html.HTMLEditorKit; + +/** The actual dialog. */ +class ChooseProgramDialog extends JDialog implements ActionListener { + public ChooseProgramDialog(Frame owner, String title, Vector programs) { + super(owner, true); + setTitle(title); + + m_programs = programs; + + // create gui + JEditorPane info = new JEditorPane(); + info.setEditable(false); + info.setEditorKit(new HTMLEditorKit()); + info.setText("Select program from list below."); + add(info, BorderLayout.NORTH); + add(createProgramPanel(), BorderLayout.CENTER); + add(createButtonPanel(), BorderLayout.SOUTH); + + pack(); + + setResizable(false); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("OK")) { + + m_program = m_programs.get(m_list.getSelectedIndex()); + + // set the program + setVisible(false); + + } else if (cmd.equals("Cancel")) { + setVisible(false); + } + } + + public Program getProgram() { + return m_program; + } + + private JPanel createProgramPanel() { + JPanel panel = new JPanel(); + + m_list = new JList(m_programs); + m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + m_list.setVisibleRowCount(10); + m_list.setSelectedIndex(0); + m_list.setPreferredSize(new Dimension(200, 250)); + + JScrollPane sc = new JScrollPane(m_list); + sc.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + + panel.add(sc); + + return panel; + } + + private JPanel createButtonPanel() { + JPanel panel = new JPanel(); + + JButton button = new JButton(" OK "); + button.addActionListener(this); + button.setActionCommand("OK"); + panel.add(button); + + button = new JButton("Cancel"); + button.addActionListener(this); + button.setActionCommand("Cancel"); + panel.add(button); + + return panel; + } + + JList m_list; + + Vector m_programs; + Program m_program; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/Comment.java b/src/main/java/hexgui/gui/Comment.java new file mode 100644 index 0000000..fae2d24 --- /dev/null +++ b/src/main/java/hexgui/gui/Comment.java @@ -0,0 +1,62 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; + +/** Displays comment for current node. */ +public class Comment extends JScrollPane implements DocumentListener { + + public interface Listener { + public void commentChanged(String msg); + } + + public Comment(Listener listener) { + m_listener = listener; + m_textPane = new JTextArea(); + m_textPane.setFont(MONOSPACED_FONT); + m_textPane.setLineWrap(true); + m_textPane.setWrapStyleWord(true); + setViewportView(m_textPane); + setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); + m_textPane.getDocument().addDocumentListener(this); + // setPreferredSize(new Dimension(200, 400)); + } + + public void setText(String text) { + m_textPane.setText(text); + m_textPane.getCaret().setDot(0); + } + + public void changedUpdate(DocumentEvent e) { + notifyChanged(); + } + + public void removeUpdate(DocumentEvent e) { + notifyChanged(); + } + + public void insertUpdate(DocumentEvent e) { + notifyChanged(); + } + + private void notifyChanged() { + m_listener.commentChanged(m_textPane.getText()); + } + + JTextArea m_textPane; + + Listener m_listener; + + private static final Font MONOSPACED_FONT = Font.decode("Monospaced"); +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/EditProgramDialog.java b/src/main/java/hexgui/gui/EditProgramDialog.java new file mode 100644 index 0000000..6080811 --- /dev/null +++ b/src/main/java/hexgui/gui/EditProgramDialog.java @@ -0,0 +1,129 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.util.SpringUtilities; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.text.html.HTMLEditorKit; + +/** Dialog for adding/editing new programs. */ +public final class EditProgramDialog extends JDialog implements ActionListener { + + public EditProgramDialog(Frame owner, Program program, String title, boolean is_new) { + super(owner, true); + m_program = program; + + setTitle(title); + init(is_new); + } + + private void init(boolean is_new) { + JEditorPane info = new JEditorPane(); + info.setEditable(false); + info.setEditorKit(new HTMLEditorKit()); + + if (!is_new) { + info.setText("Edit the program's fields."); + } else { + info.setText( + "

Enter command for new Hex program

" + + "

The command can be simply the name of the " + + "executable file, or the name plus any options " + + "you wish to set. The working directory can be left " + + "blank if the program does not need a special " + + "working directory. Enter a simple descriptive name " + + "to refer to this program."); + } + add(info, BorderLayout.NORTH); + add(createProgramPanel(m_program), BorderLayout.CENTER); + add(createButtonPanel(), BorderLayout.SOUTH); + + if (!is_new) { + setPreferredSize(new Dimension(500, 180)); + } else { + setPreferredSize(new Dimension(500, 280)); + } + pack(); + + setResizable(false); + + setVisible(true); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("OK")) { + + m_program.m_name = m_name.getText(); + m_program.m_command = m_command.getText(); + m_program.m_working = m_working.getText(); + + dispose(); + + } else if (cmd.equals("Cancel")) { + dispose(); + } + } + + private JPanel createProgramPanel(Program program) { + JPanel panel = new JPanel(new SpringLayout()); + JLabel l; + + l = new JLabel("Name:", JLabel.TRAILING); + panel.add(l); + + m_name = new JTextField(40); + if (program != null) m_name.setText(program.m_name); + l.setLabelFor(m_name); + panel.add(m_name); + + l = new JLabel("Command:", JLabel.TRAILING); + panel.add(l); + m_command = new JTextField(40); + if (program != null) m_command.setText(program.m_command); + l.setLabelFor(m_command); + panel.add(m_command); + + l = new JLabel("Working Directory:", JLabel.TRAILING); + panel.add(l); + m_working = new JTextField(40); + if (program != null) m_working.setText(program.m_working); + l.setLabelFor(m_working); + panel.add(m_working); + + SpringUtilities.makeCompactGrid( + panel, 3, 2, // rows, cols + 6, 6, // initX, initY + 6, 6); // xPad, yPad + + return panel; + } + + private JPanel createButtonPanel() { + JPanel panel = new JPanel(); + + JButton button = new JButton(" OK "); + button.addActionListener(this); + button.setActionCommand("OK"); + panel.add(button); + + button = new JButton("Cancel"); + button.addActionListener(this); + button.setActionCommand("Cancel"); + panel.add(button); + + return panel; + } + + JTextField m_name; + JTextField m_command; + JTextField m_working; + + Program m_program; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/FileDialogs.java b/src/main/java/hexgui/gui/FileDialogs.java new file mode 100644 index 0000000..37c4441 --- /dev/null +++ b/src/main/java/hexgui/gui/FileDialogs.java @@ -0,0 +1,190 @@ +// FileDialogs.java + +package hexgui.gui; + +import hexgui.sgf.GameFileFilter; +import hexgui.util.Platform; +import java.awt.Component; +import java.awt.FileDialog; +import java.awt.Frame; +import java.io.File; +import java.text.MessageFormat; +import javax.swing.JFileChooser; + +/** File dialogs. */ +public final class FileDialogs { + public static File showOpen(Component parent, String title) { + return showFileChooser(parent, Type.FILE_OPEN, null, false, title); + } + + public static File showOpenSgf(Component parent) { + return showFileChooser(parent, Type.FILE_OPEN, null, true, null); + } + + public static File showSave(Component parent, String title, MessageDialogs messageDialogs) { + return showFileChooserSave(parent, null, false, title, messageDialogs); + } + + public static File showSaveSgf(Frame parent, MessageDialogs messageDialogs) { + return showFileChooserSave(parent, s_lastFile, true, null, messageDialogs); + } + + /** File selection, unknown whether for load or save. */ + public static File showSelectFile(Component parent, String title) { + return showFileChooser(parent, Type.FILE_SELECT, s_lastFile, false, title); + } + + public static void setLastFile(File file) { + s_lastFile = file; + } + + private enum Type { + /** Dialog type for opening a file. */ + FILE_OPEN, + + /** Dialog type for saving to a file. */ + FILE_SAVE, + + /** + * Dialog type for selecting a file. Use this type, if a file name should be selected, but it is + * not known what the file name is used for and if the file already exists. + * + * @deprecated Not supported by native AWT FileDialog + */ + FILE_SELECT + } + + /** + * Use native AWT-dialogs. They are used on Mac OS X because JFileChooser looks too different from + * the native dialogs (Java 1.5), and on Windows because JFileChooser is too slow on Windows XP + * (startup and directory changing takes up to 10 sec; Java 1.6) + */ + private static final boolean NATIVE_DIALOGS = (Platform.isMac() || Platform.isWindows()); + + private static File s_lastFile; + + /** Make constructor unavailable; class is for namespace only. */ + private FileDialogs() {} + + /** + * Find first parent that is a Frame. + * + * @return null If no such parent. + */ + private static Frame findParentFrame(Component component) { + while (component != null) + if (component instanceof Frame) return (Frame) component; + else component = component.getParent(); + return null; + } + + private static File showFileChooser( + Component parent, Type type, File lastFile, boolean setSgfFilter, String title) { + // Use native dialogs for some platforms. but not for type select + // There is no native dialog for select + if (NATIVE_DIALOGS && type != Type.FILE_SELECT) { + Frame frame = findParentFrame(parent); + return showFileChooserAWT(frame, type, title); + } + return showFileChooserSwing(parent, type, lastFile, setSgfFilter, title); + } + + private static File showFileChooserSave( + Component parent, + File lastFile, + boolean setSgfFilter, + String title, + MessageDialogs messageDialogs) { + File file = showFileChooser(parent, Type.FILE_SAVE, lastFile, setSgfFilter, title); + if (NATIVE_DIALOGS) + // Overwrite warning is already part of FileDialog + return file; + while (file != null) { + if (file.exists()) { + String mainMessage = MessageFormat.format("Replace file \"{0}\"", file.getName()); + String optionalMessage = "If you overwrite the file, the previous version will be lost."; + if (!messageDialogs.showQuestion(parent, mainMessage, optionalMessage, "Replace", true)) { + file = showFileChooser(parent, Type.FILE_SAVE, lastFile, setSgfFilter, title); + continue; + } + } + break; + } + return file; + } + + private static File showFileChooserAWT(Frame parent, Type type, String title) { + FileDialog dialog = new FileDialog(parent); + if (title == null) { + switch (type) { + case FILE_OPEN: + title = "Open"; + break; + case FILE_SAVE: + title = "Save"; + break; + default: + assert false; + } + } + dialog.setTitle(title); + int mode = FileDialog.LOAD; + if (type == Type.FILE_SAVE) mode = FileDialog.SAVE; + dialog.setMode(mode); + /* Commented out, because there is no way to change the filter by the + user (at least not on Linux) + if (setSgfFilter) + dialog.setFilenameFilter(new FilenameFilter() { + public boolean accept(File dir, String name) + { + return name.toLowerCase().endsWith("sgf"); + } + }); + */ + // dialog.setLocationRelativeTo(parent); // Java <= 1.4 + dialog.setLocationByPlatform(true); + dialog.setVisible(true); + if (dialog.getFile() == null) return null; + return new File(dialog.getDirectory(), dialog.getFile()); + } + + private static File showFileChooserSwing( + Component parent, Type type, File lastFile, boolean setSgfFilter, String title) { + JFileChooser chooser; + if (s_lastFile == null) { + if (Platform.isMac()) + // user.dir is application directory on Mac, which is bad + // I have not found a way to set it to user home in Info.plist + // so I use null here, which sets is to the user home + chooser = new JFileChooser((String) null); + else chooser = new JFileChooser(System.getProperty("user.dir")); + } else chooser = new JFileChooser(s_lastFile); + chooser.setMultiSelectionEnabled(false); + javax.swing.filechooser.FileFilter filter = new GameFileFilter(); + chooser.addChoosableFileFilter(filter); + if (setSgfFilter) { + chooser.setFileFilter(filter); + } else chooser.setFileFilter(chooser.getAcceptAllFileFilter()); + if (type == Type.FILE_SAVE) { + if (lastFile != null && lastFile.isFile() && lastFile.exists()) + chooser.setSelectedFile(lastFile); + } + if (title != null) chooser.setDialogTitle(title); + int ret; + switch (type) { + case FILE_SAVE: + ret = chooser.showSaveDialog(parent); + break; + case FILE_OPEN: + ret = chooser.showOpenDialog(parent); + break; + default: + ret = chooser.showDialog(parent, "Select"); + break; + } + if (ret != JFileChooser.APPROVE_OPTION) return null; + File file = chooser.getSelectedFile(); + s_lastFile = file; + return file; + } +} diff --git a/src/main/java/hexgui/gui/GameInfoPanel.java b/src/main/java/hexgui/gui/GameInfoPanel.java new file mode 100644 index 0000000..0dbf3c2 --- /dev/null +++ b/src/main/java/hexgui/gui/GameInfoPanel.java @@ -0,0 +1,102 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.game.Clock; +import hexgui.hex.HexColor; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.URL; +import java.util.*; +import javax.swing.*; + +/** Displays info about the current game. */ +public class GameInfoPanel extends JPanel { + public GameInfoPanel(Clock blackClock, Clock whiteClock) { + JPanel panel = new JPanel(); + add(panel, BorderLayout.CENTER); + + JPanel bpanel = new JPanel(); + bpanel.setLayout(new BoxLayout(bpanel, BoxLayout.Y_AXIS)); + URL bURL = getURL("hexgui/images/black-24x24.png"); + JLabel blab = new JLabel(new ImageIcon(bURL)); + blab.setAlignmentX(Component.CENTER_ALIGNMENT); + bpanel.add(blab); + bpanel.add(new GuiClock(HexColor.BLACK, blackClock)); + + JPanel wpanel = new JPanel(); + wpanel.setLayout(new BoxLayout(wpanel, BoxLayout.Y_AXIS)); + URL wURL = getURL("hexgui/images/white-24x24.png"); + JLabel wlab = new JLabel(new ImageIcon(wURL)); + wlab.setAlignmentX(Component.CENTER_ALIGNMENT); + wpanel.add(wlab); + wpanel.add(new GuiClock(HexColor.WHITE, whiteClock)); + + panel.add(bpanel); + panel.add(wpanel); + + // setPreferredSize(new Dimension(200, 150)); + } + + private URL getURL(String filename) { + URL url = null; + if (filename != null) { + ClassLoader classLoader = getClass().getClassLoader(); + url = classLoader.getResource(filename); + } + return url; + } +} +; + +class GuiClock extends JTextField implements Clock.Listener { + public GuiClock(HexColor color, Clock clock) { + super(COLUMNS); + + m_clock = clock; + m_clock.addListener(this); + + // Monspace font doesn't center correctly on the Mac + // GuiUtil.setMonospacedFont(this); + setEditable(false); + setFocusable(false); + setHorizontalAlignment(SwingConstants.CENTER); + // setMinimumSize(getPreferredSize()); + m_color = color; + setText("00:00"); + } + + public final void setText(String text) { + super.setText(text); + String toolTip; + if (m_color == HexColor.BLACK) toolTip = "Time for Black"; + else toolTip = "Time for White"; + if (text.length() > COLUMNS) toolTip = toolTip + " (" + text + ")"; + setToolTipText(toolTip); + } + + public void clockChanged() { + int elapsed = m_clock.elapsed(); + int minutes = elapsed / 60000; + int seconds = (elapsed % 60000) / 1000; + String min, sec; + + min = (minutes < 10) ? "0" + minutes : "" + minutes; + sec = (seconds < 10) ? "0" + seconds : "" + seconds; + setText(min + ":" + sec); + } + + /** Serial version to suppress compiler warning. Contains a marker comment for serialver.sf.net */ + private static final long serialVersionUID = 0L; // SUID + + private static final int COLUMNS = 5; + + private final HexColor m_color; + + private Clock m_clock; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/GuiBoard.java b/src/main/java/hexgui/gui/GuiBoard.java new file mode 100644 index 0000000..ea0cae4 --- /dev/null +++ b/src/main/java/hexgui/gui/GuiBoard.java @@ -0,0 +1,701 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.game.Node; +import hexgui.hex.*; +import hexgui.util.*; +import java.awt.*; +import java.awt.event.*; +import java.awt.print.PageFormat; +import java.awt.print.Printable; +import java.awt.print.PrinterException; +import java.math.BigInteger; +import java.net.URL; +import java.util.Map; +import java.util.TreeMap; +import java.util.Vector; +import javax.swing.*; +import javax.swing.border.EtchedBorder; + +// ---------------------------------------------------------------------------- + +/** Gui Board. */ +public final class GuiBoard extends JPanel implements Printable { + /** Callback for clicks on a field. */ + public interface Listener { + void panelClicked(); + + void fieldClicked(HexPoint point, boolean ctrl, boolean shift); + + void fieldDoubleClicked(HexPoint point, boolean ctrl, boolean shift); + } + + private static final boolean DEFAULT_FLIPPED = true; + + public static final int HEXBOARD = 0; + public static final int YBOARD = 1; + + /** Constructor. */ + public GuiBoard(Listener listener, GuiPreferences preferences) { + m_image = null; + m_listener = listener; + m_preferences = preferences; + m_arrows = new Vector>(); + + initSize( + HEXBOARD, + m_preferences.getInt("gui-board-width"), + m_preferences.getInt("gui-board-height")); + + setDrawType(m_preferences.get("gui-board-type")); + + setPreferredSize( + new Dimension( + m_preferences.getInt("gui-board-pixel-width"), + m_preferences.getInt("gui-board-pixel-height"))); + + setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); + setLayout(new BoardLayout()); + m_boardPanel = new BoardPanel(); + add(m_boardPanel); + + MouseAdapter mouseAdapter = + new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + // First inform the parent that we were clicked, to + // handle things like keyboard focus. + m_listener.panelClicked(); + GuiField f = m_drawer.getFieldContaining(e.getPoint(), m_field); + if (f == null) return; + + int modifiers = e.getModifiers(); + boolean ctrl = (modifiers & ActionEvent.CTRL_MASK) != 0; + boolean shift = (modifiers & ActionEvent.SHIFT_MASK) != 0; + if (e.getClickCount() >= 2) m_listener.fieldDoubleClicked(f.getPoint(), ctrl, shift); + else m_listener.fieldClicked(f.getPoint(), ctrl, shift); + } + }; + m_boardPanel.addMouseListener(mouseAdapter); + setCursorType("default"); + setVisible(true); + } + + // Set the cursor to one of: "default", "black", "white", + // "black-setup", "white-setup". + public void setCursorType(String name) { + String path; + + if (name.equals("white")) { + path = "hexgui/images/cursor-white.png"; + } else if (name.equals("black")) { + path = "hexgui/images/cursor-black.png"; + } else if (name.equals("white-setup")) { + path = "hexgui/images/cursor-white-setup.png"; + } else if (name.equals("black-setup")) { + path = "hexgui/images/cursor-black-setup.png"; + } else { + path = null; + } + + if (path == null) { + setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)); + return; + } + + ClassLoader classLoader = getClass().getClassLoader(); + URL url = classLoader.getResource(path); + if (url == null) { + System.out.println( + "setCursorType: could not load '" + "hexgui/images/cursor-white.png" + "'!"); + return; + } + Image img = new ImageIcon(url).getImage(); + Point hot = new Point(8, 8); + Toolkit t = getToolkit(); + Cursor c = t.createCustomCursor(img, hot, name); + setCursor(c); + } + + /** + * Sets the type of board drawer to use. If name is not one of the known values, + * "Diamond" is used. + * + * @param name one of ("Diamond", "Flat", "Flat2", "Go"). + */ + public void setDrawType(String name) { + if (name.equals("Y")) { + m_drawer = new BoardDrawerY(); + initSize(YBOARD, m_width, m_height); + } else if (name.equals("Go")) { + if (m_mode != HEXBOARD) initSize(HEXBOARD, m_width, m_height); + m_drawer = new BoardDrawerGo(); + m_preferences.put("gui-board-type", "Go"); + } else if (name.equals("Diamond")) { + if (m_mode != HEXBOARD) initSize(HEXBOARD, m_width, m_height); + m_drawer = new BoardDrawerDiamond(); + m_preferences.put("gui-board-type", "Diamond"); + } else if (name.equals("Flat")) { + if (m_mode != HEXBOARD) initSize(HEXBOARD, m_width, m_height); + m_drawer = new BoardDrawerFlat(); + m_preferences.put("gui-board-type", "Flat"); + } else if (name.equals("Flat2")) { + if (m_mode != HEXBOARD) initSize(HEXBOARD, m_width, m_height); + m_drawer = new BoardDrawerFlat2(); + m_preferences.put("gui-board-type", "Flat2"); + } else { + System.out.println("GuiBoard: unknown draw type '" + name + "'."); + m_drawer = new BoardDrawerDiamond(); + } + repaint(); + } + + /** + * Sets whether black and letters is on top or if white and numbers is on top. If string is + * invalid defaults to positive. + * + * @param orient either "Positive" or "Negative". + */ + public void setOrientation(String orient) { + if (orient.equals("Positive")) m_preferences.put("gui-board-orientation", "positive"); + else if (orient.equals("Negative")) m_preferences.put("gui-board-orientation", "negative"); + else { + System.out.println("GuiBoard: unknown orientation '" + orient + "'."); + } + repaint(); + } + + public void initSize(int w, int h) { + initSize(m_mode, w, h); + } + + /** + * Creates a board of the given dimensions. Dirty flag is set to false. + * + * @param m type of board to create (HEX or Y) + * @param w width of the board in cells + * @param h height of the board in cells + */ + private void initSize(int m, int w, int h) { + System.out.println("GuiBoard.initSize: " + (m == HEXBOARD ? "(HEX) " : "(Y) ") + w + " " + h); + + m_mode = m; + m_width = w; + m_height = h; + m_size = new Dimension(m_width, m_height); + + m_dirty_stones = false; + clearArrows(); + + if (m_mode == HEXBOARD) { + m_field = new GuiField[w * h + 4]; + for (int x = 0; x < w * h; x++) { + m_field[x] = new GuiField(HexPoint.get(x % w, x / w)); + m_field[x].setAttributes(GuiField.DRAW_CELL_OUTLINE); + } + m_field[w * h + 0] = new GuiField(HexPoint.NORTH); + m_field[w * h + 1] = new GuiField(HexPoint.SOUTH); + m_field[w * h + 2] = new GuiField(HexPoint.WEST); + m_field[w * h + 3] = new GuiField(HexPoint.EAST); + } else { + int n = w * (w + 1) / 2; + m_field = new GuiField[n + 3]; + for (int y = 0, i = 0; y < w; y++) { + for (int x = 0; x <= y; x++, i++) { + m_field[i] = new GuiField(HexPoint.get(x, y)); + m_field[i].setAttributes(GuiField.DRAW_CELL_OUTLINE); + } + } + m_field[n + 0] = new GuiField(HexPoint.SOUTH); + m_field[n + 0].setAttributes(GuiField.DRAW_CELL_OUTLINE); + m_field[n + 1] = new GuiField(HexPoint.WEST); + m_field[n + 1].setAttributes(GuiField.DRAW_CELL_OUTLINE); + m_field[n + 2] = new GuiField(HexPoint.EAST); + m_field[n + 2].setAttributes(GuiField.DRAW_CELL_OUTLINE); + } + clearAll(); + repaint(); + } + + /** + * Creates a board with the given dimensions. Convenience function. + * + * @param dim dimension of the board + * @see initSize(int, int) + */ + public void initSize(Dimension dim) { + initSize(m_mode, dim.width, dim.height); + } + + /** + * Gets the size of the board. + * + * @return size of the board as a Dimension. + */ + public Dimension getBoardSize() { + return m_size; + } + + public boolean isHexBoard() { + return m_mode == HEXBOARD; + } + + public boolean isYBoard() { + return m_mode == YBOARD; + } + + /** Clears all marks and stones from the board. */ + public void clearAll() { + for (int x = 0; x < m_field.length; x++) m_field[x].clear(); + if (m_mode == HEXBOARD) { + getField(HexPoint.NORTH).setColor(HexColor.BLACK); + getField(HexPoint.SOUTH).setColor(HexColor.BLACK); + getField(HexPoint.WEST).setColor(HexColor.WHITE); + getField(HexPoint.EAST).setColor(HexColor.WHITE); + } else { + getField(HexPoint.SOUTH).setColor(HexColor.EMPTY); + getField(HexPoint.WEST).setColor(HexColor.EMPTY); + getField(HexPoint.EAST).setColor(HexColor.EMPTY); + } + repaint(); + } + + /** + * Makes a copy of the current fields if the dirty flag is not already set, and then sets the + * dirty flag to true. See clearMarks(). + */ + public void aboutToDirtyStones() { + if (!m_dirty_stones) { + m_backup_field = new GuiField[m_field.length]; + for (int i = 0; i < m_field.length; i++) m_backup_field[i] = new GuiField(m_field[i]); + } + m_dirty_stones = true; + } + + public boolean areStonesDirty() { + return m_dirty_stones; + } + + /** Adds an arrow. */ + public void addArrow(HexPoint from, HexPoint to) { + m_arrows.add(new Pair(from, to)); + repaint(); + } + + public void clearArrows() { + m_arrows.clear(); + repaint(); + } + + /** + * Clears dynamic marks, leaving stones intact. If the dirty flag is set, revert the fields to the + * saved fields saved in markStonesDirty(). Dirty stones flag is set to false. See + * aboutToDirtyStones(). Empties the list of arrows. + */ + public void clearMarks() { + if (m_dirty_stones) { + for (int i = 0; i < m_field.length; i++) { + m_field[i] = new GuiField(m_backup_field[i]); + } + } + m_dirty_stones = false; + + clearArrows(); + + for (int x = 0; x < m_field.length; x++) { + m_field[x].clearAttributes( + GuiField.LAST_PLAYED | GuiField.SWAP_PLAYED | GuiField.DRAW_TEXT | GuiField.DRAW_ALPHA); + } + repaint(); + } + + /** + * Sets the given point to the given color. Special points are ignored (SWAP_SIDES, RESIGN, etc). + * + * @param point the point + * @param color the color to set it to. + */ + public void setColor(HexPoint point, HexColor color) { + GuiField f = getField(point); + if (f != null) { + f.setColor(color); + repaint(); + } + } + + /** + * Gets the color of the specified point. + * + * @param point the point whose color we with to obtain. + * @return the color of point + */ + public HexColor getColor(HexPoint point) { + GuiField f = getField(point); + return f.getColor(); + } + + /** Gets the field at the specified point. Special points are ignored (SWAP_SIDES, etc). */ + public GuiField getField(HexPoint point) { + if (point == HexPoint.SWAP_SIDES + || point == HexPoint.SWAP_PIECES + || point == HexPoint.PASS + || point == HexPoint.RESIGN + || point == HexPoint.FORFEIT) { + return null; + } + + for (int x = 0; x < m_field.length; x++) { + if (m_field[x].getPoint() == point) return m_field[x]; + } + assert (false); + return null; + } + + /** + * Marks the given point to show which move was played last, or clears the mark if point + * is null. + */ + public void markLastPlayed(HexPoint point) { + assert (point != HexPoint.SWAP_SIDES && point != HexPoint.SWAP_PIECES); + + if (m_last_played != null) { + m_last_played.clearAttributes(GuiField.LAST_PLAYED); + m_last_played = null; + } + if (point != null) { + m_last_played = getField(point); + if (m_last_played != null) { + m_last_played.setAttributes(GuiField.LAST_PLAYED); + } + } + repaint(); + } + + /** Clear swap marks */ + public void clearSwapPlayed() { + for (int x = 0; x < m_field.length; x++) { + m_field[x].clearAttributes(GuiField.SWAP_PLAYED); + } + repaint(); + } + + /** Add swap mark to all pieces on the board (hopefully there is exactly one of them */ + public void markSwapPlayed() { + for (int x = 0; x < m_field.length; x++) { + HexPoint p = m_field[x].getPoint(); + if (p.is_cell() && m_field[x].getColor() != HexColor.EMPTY) { + m_field[x].setAttributes(GuiField.SWAP_PLAYED); + } + } + repaint(); + } + + /** Sets the given point's alpha color. */ + public void setAlphaColor(HexPoint point, Color color) { + GuiField f = getField(point); + if (f != null) { + f.setAlphaColor(color); + repaint(); + } + } + + public void setAlphaColor(HexPoint point, Color color, float blend) { + GuiField f = getField(point); + if (f != null) { + f.setAlphaColor(color, blend); + repaint(); + } + } + + /** Returns the point's alpha color; null if it is 'swap-sides' or resign or similar. */ + public Color getAlphaColor(HexPoint point) { + GuiField f = getField(point); + if (f != null) { + return f.getAlphaColor(); + } else { + return null; + } + } + + /** Sets the given point's text. */ + public void setText(HexPoint point, String str) { + getField(point).setText(str); + repaint(); + } + + /** Sets whether this cell is selected. */ + public void setSelected(HexPoint point, boolean selected) { + getField(point).setSelected(selected); + repaint(); + } + + /** Check if the board is full */ + public boolean isBoardFull() { + for (int x = 0; x < m_field.length; x++) { + if (m_field[x].getColor() == HexColor.EMPTY) return false; + } + return true; + } + + /** Count the number of pieces on the board */ + public int numberOfPieces() { + int count = 0; + for (int x = 0; x < m_field.length; x++) { + HexPoint point = m_field[x].getPoint(); + if (point == HexPoint.NORTH + || point == HexPoint.EAST + || point == HexPoint.SOUTH + || point == HexPoint.WEST) { + continue; + } + if (m_field[x].getColor() != HexColor.EMPTY) { + count++; + } + } + return count; + } + + /** Change the pieces' colors without moving them. This is only used in Y. */ + public void swapColors() { + for (int x = 0; x < m_field.length; x++) { + HexPoint point = m_field[x].getPoint(); + if (point == HexPoint.NORTH + || point == HexPoint.EAST + || point == HexPoint.SOUTH + || point == HexPoint.WEST) { + continue; + } + HexColor color = m_field[x].getColor(); + m_field[x].setColor(color.otherColor()); + } + } + + /** Change the pieces' colors and move them. This is only used in Hex. */ + public void swapPieces() { + // Due to the weird way the data structures are set up, it + // is tricky to move pieces to another location on the board. + // In particular, there is no O(1) way to find the HexField + // attached to a given HexPoint. + Map colors = new TreeMap(); + for (int x = 0; x < m_field.length; x++) { + HexPoint point = m_field[x].getPoint(); + if (point == HexPoint.NORTH + || point == HexPoint.EAST + || point == HexPoint.SOUTH + || point == HexPoint.WEST) { + continue; + } + colors.put(point, m_field[x].getColor()); + } + for (int x = 0; x < m_field.length; x++) { + HexPoint point = m_field[x].getPoint(); + if (point == HexPoint.NORTH + || point == HexPoint.EAST + || point == HexPoint.SOUTH + || point == HexPoint.WEST) { + continue; + } + HexPoint otherpoint = point.reflect(); + m_field[x].setColor(colors.get(otherpoint).otherColor()); + } + } + + /** Stores the current state as a setup position in the given sgf node. */ + public void storePosition(Node node) { + for (int x = 0; x < m_field.length; x++) { + HexPoint point = m_field[x].getPoint(); + if (point == HexPoint.NORTH + || point == HexPoint.EAST + || point == HexPoint.SOUTH + || point == HexPoint.WEST) continue; + + HexColor color = m_field[x].getColor(); + if (color == HexColor.EMPTY) continue; + + node.addSetup(color, point); + } + } + + public void paintImmediately() { + assert SwingUtilities.isEventDispatchThread(); + super.paintImmediately(0, 0, getWidth(), getHeight()); + } + + /** Displays this vc on the board. */ + public void displayVC(VC vc) { + getField(vc.getFrom()).setAlphaColor(Color.blue); + getField(vc.getTo()).setAlphaColor(Color.blue); + + Vector carrier = vc.getCarrier(); + for (int i = 0; i < carrier.size(); i++) getField(carrier.get(i)).setAlphaColor(Color.green); + + Vector stones = vc.getStones(); + for (int i = 0; i < stones.size(); i++) getField(stones.get(i)).setAlphaColor(Color.red); + + Vector key = vc.getKey(); + for (int i = 0; i < key.size(); i++) getField(key.get(i)).setAlphaColor(Color.yellow); + } + + // ------------------------------------------------------------ + + public int print(Graphics g, PageFormat format, int page) throws PrinterException { + if (page >= 1) { + return Printable.NO_SUCH_PAGE; + } + double width = getWidth(); + double height = getHeight(); + double pageWidth = format.getImageableWidth(); + double pageHeight = format.getImageableHeight(); + double scale = 1; + if (width >= pageWidth) scale = pageWidth / width; + double xSpace = (pageWidth - width * scale) / 2; + double ySpace = (pageHeight - height * scale) / 2; + Graphics2D g2d = (Graphics2D) g; + g2d.translate(format.getImageableX() + xSpace, format.getImageableY() + ySpace); + g2d.scale(scale, scale); + print(g2d); + return Printable.PAGE_EXISTS; + } + + // ------------------------------------------------------------ + + /** + * Converts a hex string representing a bitset into a vector of HexPoints. This relies on m_field + * being ordered in a particular fashion. + * + *

NOTE: THIS IS BROKEN SINCE HexPoint was changed in wolve, r182. USE BASE 64 INSTEAD! + * + *

FIXME: switch carriers to be printed as a list of HexPoints instead of as hex strings? + */ + private Vector convertHexString(String str) { + Vector ret = new Vector(); + + for (int i = 0; i < str.length(); i++) { + BigInteger big = new BigInteger(StringUtils.reverse(str), 16); + for (int j = 0; j < m_field.length; j++) { + if (big.testBit(j)) ret.add(m_field[j].getPoint()); + } + } + + return ret; + } + + /** Converts a base 64 string representing a bitset into a vector of HexPoints. */ + private static final String m_base64 = + "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"; + + private Vector convertBase64String(String str) { + + Vector ret = new Vector(); + for (int i = 0; i < str.length(); i++) { + int v = m_base64.indexOf(str.charAt(i)); + assert (v != -1); + for (int j = 0; j < 6 && i * 6 + j < HexPoint.MAX_POINTS; j++) { + if ((v & (1 << j)) != 0) ret.add(HexPoint.get(i * 6 + j)); + } + } + return ret; + } + + private GuiField[] flipFields(GuiField field[]) { + GuiField out[] = new GuiField[field.length]; + for (int i = 0; i < field.length; i++) { + HexPoint p = field[i].getPoint(); + out[i] = new GuiField(field[i]); + if (p == HexPoint.NORTH) out[i].setPoint(HexPoint.WEST); + else if (p == HexPoint.WEST) out[i].setPoint(HexPoint.NORTH); + else if (p == HexPoint.EAST) out[i].setPoint(HexPoint.SOUTH); + else if (p == HexPoint.SOUTH) out[i].setPoint(HexPoint.EAST); + else { + out[i].setPoint(HexPoint.get(p.y, p.x)); + } + } + return out; + } + + private class BoardPanel extends JPanel { + public BoardPanel() { + setFocusable(true); + } + + public void paintComponent(Graphics graphics) { + int w = getWidth(); + int h = getHeight(); + + if (m_image == null) { + m_image = createImage(w, h); + } + + int bw = m_width; + int bh = m_height; + GuiField ff[] = m_field; + boolean alphaontop = true; + Vector> arrows = m_arrows; + + boolean positive = true; + if (m_preferences.get("gui-board-orientation").equals("negative")) { + positive = false; + } + boolean flip; + if (m_preferences.get("gui-board-type").equals("Flat2")) { + flip = positive; + } else { + flip = !positive; + } + + if (flip) { + bw = m_height; + bh = m_width; + alphaontop = false; + ff = flipFields(m_field); + + arrows = new Vector>(); + for (int i = 0; i < m_arrows.size(); i++) { + HexPoint p1 = m_arrows.get(i).first; + HexPoint p2 = m_arrows.get(i).second; + arrows.add( + new Pair(HexPoint.get(p1.y, p1.x), HexPoint.get(p2.y, p2.x))); + } + } + + m_drawer.draw(m_image.getGraphics(), w, h, bw, bh, alphaontop, ff, arrows); + graphics.drawImage(m_image, 0, 0, null); + } + + public void setBounds(int x, int y, int w, int h) { + super.setBounds(x, y, w, h); + m_image = null; + } + } + + public void mousePressed(MouseEvent e) {} + + public void mouseReleased(MouseEvent e) {} + + public void mouseEntered(MouseEvent e) {} + + public void mouseExited(MouseEvent e) {} + + private int m_width, m_height; + private Dimension m_size; + private int m_mode; + + private Image m_image; + private GuiField m_field[]; + private Vector> m_arrows; + + private boolean m_dirty_stones; + private GuiField m_backup_field[]; + + private GuiField m_last_played; + + private BoardDrawerBase m_drawer; + private BoardPanel m_boardPanel; + + private Listener m_listener; + private GuiPreferences m_preferences; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/GuiField.java b/src/main/java/hexgui/gui/GuiField.java new file mode 100644 index 0000000..0813301 --- /dev/null +++ b/src/main/java/hexgui/gui/GuiField.java @@ -0,0 +1,288 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import hexgui.util.*; +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.event.*; +import java.awt.geom.*; +import javax.swing.*; + +// ---------------------------------------------------------------------------- + +public class GuiField { + public static final int DRAW_CELL_OUTLINE = 1; + public static final int LAST_PLAYED = 2; + public static final int SWAP_PLAYED = 4; + public static final int DRAW_TEXT = 8; + public static final int DRAW_ALPHA = 16; + public static final int SELECTED = 32; + + private static final Color COLOR_STONE_BLACK = Color.decode("#030303"); + private static final Color COLOR_STONE_BLACK_BRIGHT = Color.decode("#666666"); + private static final Color COLOR_STONE_WHITE = Color.decode("#d7d0c9"); + private static final Color COLOR_STONE_WHITE_BRIGHT = Color.decode("#ffffff"); + + public GuiField(HexPoint p) { + this(p, HexColor.EMPTY, 0, null, null, 0); + } + + public GuiField(HexPoint p, HexColor c, int attributes, String text, Color alpha, float blend) { + m_point = p; + m_color = c; + m_text = text; + m_alpha_color = alpha; + m_attributes = attributes; + m_alpha_blend = blend; + } + + /** Creates a copy of the given field. */ + public GuiField(GuiField f) { + this( + f.getPoint(), + f.getColor(), + f.getAttributes(), + f.getText(), + f.getAlphaColor(), + f.getAlphaBlend()); + } + + public static int getStoneMargin(int width) { + return width / 17 + 1; + } + + public void clearAttributes() { + m_attributes = 0; + } + + public void clearAttributes(int f) { + m_attributes &= ~f; + } + + public void setAttributes(int f) { + m_attributes |= f; + } + + public int getAttributes() { + return m_attributes; + } + + public void setColor(HexColor c) { + m_color = c; + } + + public HexColor getColor() { + return m_color; + } + + public void setText(String str) { + m_text = str; + if (str == null) clearAttributes(DRAW_TEXT); + else setAttributes(DRAW_TEXT); + } + + public String getText() { + return m_text; + } + + public void setAlphaColor(Color c) { + m_alpha_color = c; + m_alpha_blend = 0.3f; + if (c == null) clearAttributes(DRAW_ALPHA); + else setAttributes(DRAW_ALPHA); + } + + public void setAlphaColor(Color c, float blend) { + m_alpha_color = c; + m_alpha_blend = blend; + if (c == null) clearAttributes(DRAW_ALPHA); + else setAttributes(DRAW_ALPHA); + } + + public Color getAlphaColor() { + return m_alpha_color; + } + + public float getAlphaBlend() { + return m_alpha_blend; + } + + public void setSelected(boolean f) { + if (f) { + setAttributes(SELECTED); + } else { + clearAttributes(SELECTED); + } + } + + public void setPoint(HexPoint p) { + m_point = p; + } + + public HexPoint getPoint() { + return m_point; + } + + public void clear() { + setColor(HexColor.EMPTY); + } + + private RadialGradientPaint getPaint( + int width, int height, Color colorNormal, Color colorBright) { + RadialGradientPaint paint; + int paintSize; + int size = (width < height) ? width : height; + int radius = Math.max(size / 3, 1); + Point2D.Double centerPoint = new Point2D.Double(width / 2 - size / 6, height / 2 - size / 6); + Point2D.Double radiusPoint = new Point2D.Double(radius, radius); + paint = + new RadialGradientPaint( + centerPoint, colorBright, + radiusPoint, colorNormal); + return paint; + } + + public void draw(Graphics g, int x, int y, int w, int h) { + if (!g.hitClip(x, y, w, h)) return; + + m_width = w; + m_height = h; + + m_radius = (h < w) ? h / 2 : w / 2; + m_margin = getStoneMargin(m_radius * 2); + + m_graphics = g.create(x - w / 2, y - h / 2, w, h); + if (m_graphics instanceof Graphics2D) { + m_graphics2D = (Graphics2D) m_graphics; + } else { + m_graphics2D = null; + } + + if (m_color == HexColor.WHITE) { + drawStone(COLOR_STONE_WHITE, COLOR_STONE_WHITE_BRIGHT); + } else if (m_color == HexColor.BLACK) { + drawStone(COLOR_STONE_BLACK, COLOR_STONE_BLACK_BRIGHT); + } + + if ((m_attributes & LAST_PLAYED) != 0) { + drawLastPlayed(); + } + + if ((m_attributes & SWAP_PLAYED) != 0) { + drawSwapPlayed(); + } + + // FIXME: this is done in BoardDrawer since we don't know + // anything about our shape and size and we want to cover the + // entire field. Should all drawing be done in board drawer? + // if ((m_attributes & DRAW_ALPHA) != 0) drawAlpha(); + + if ((m_attributes & DRAW_TEXT) != 0) drawText(); + } + + private void drawStone(Color normal, Color bright) { + if (m_graphics2D != null) { + RadialGradientPaint paint = getPaint(m_width, m_height, normal, bright); + m_graphics2D.setPaint(paint); + } else { + m_graphics.setColor(normal); + } + + int size = m_radius - m_margin; + m_graphics.fillOval(m_width / 2 - size, m_height / 2 - size, size * 2, size * 2); + + m_graphics.setPaintMode(); + } + + private void drawLastPlayed() { + m_graphics.setColor(Color.gray); + int size = (m_radius - m_margin) / 6; + m_graphics.fillOval(m_width / 2 - size, m_height / 2 - size, 2 * size, 2 * size); + } + + /** + * Draw the given string centered at the coordinates (x,y) in the current font, with the given + * relative size. + */ + private void drawString(String str, double x, double y, double size) { + double abssize = (m_radius - m_margin) * size; + Font f = m_graphics.getFont(); + Font f2 = f.deriveFont((float) abssize); + FontMetrics m = m_graphics.getFontMetrics(f2); + double width = m.stringWidth(str); + double height = m.getAscent(); + + m_graphics.setFont(f2); + m_graphics.drawString(str, (int) (x - width / 2), (int) (y + 0.8 * height / 2)); + m_graphics.setFont(f); + } + + private void drawSwapPlayed() { + if (m_color == HexColor.BLACK) { + m_graphics.setColor(Color.white); + } else { + m_graphics.setColor(Color.black); + } + this.drawString("S", m_width / 2.0, m_height / 2.0, 1); + } + + private void drawAlpha() { + if (m_alpha_color == null) return; + if (m_graphics2D == null) return; + + m_graphics2D.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); + m_graphics.setColor(m_alpha_color); + m_graphics.fillRect( + m_width / 2 - m_width / 4, m_height / 2 - m_height / 4, m_width / 2, m_height / 2); + } + + private void drawText() { + String[] lines = m_text.split("@"); + int nlines = lines.length; + + double size = m_radius - m_margin; + double relheight = nlines > 1 ? 2.0 / nlines : 1.0; + double height = size * relheight; + + double y = m_height / 2 + ((nlines - 1) * height) / 2; + + for (int i = lines.length - 1; i >= 0; --i) { + String str = lines[i].trim(); + + Color color = Color.black; + if (getColor() == HexColor.BLACK) color = Color.white; + + m_graphics.setColor(color); + this.drawString(str, m_width / 2, y, relheight); + + y -= height; + } + } + + private HexPoint m_point; + private HexColor m_color; + private int m_attributes; + + private Color m_alpha_color; + private float m_alpha_blend; + + private String m_text; + + private int m_width; + private int m_height; + private int m_radius; + private int m_margin; + + private Graphics m_graphics; + private Graphics2D m_graphics2D; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/GuiMenuBar.java b/src/main/java/hexgui/gui/GuiMenuBar.java new file mode 100644 index 0000000..739573a --- /dev/null +++ b/src/main/java/hexgui/gui/GuiMenuBar.java @@ -0,0 +1,582 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + + +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; + +// ---------------------------------------------------------------------------- + +/** Menu bar. */ +public final class GuiMenuBar { + public GuiMenuBar(ActionListener listener, GuiPreferences preferences) { + m_preferences = preferences; + + m_menuBar = new JMenuBar(); + + m_listener = listener; + m_menuBar.add(createFileMenu()); + m_menuBar.add(createProgramMenu()); + m_menuBar.add(createGameMenu()); + m_menuBar.add(createEditMenu()); + m_menuBar.add(createViewMenu()); + m_menuBar.add(createHelpMenu()); + + setProgramConnected(false); + } + + public JMenuBar getJMenuBar() { + return m_menuBar; + } + + public void setProgramConnected(boolean f) { + // m_connect_remote.setEnabled(!f); + m_connect_local.setEnabled(!f); + m_disconnect.setEnabled(f); + m_reconnect.setEnabled(f); + m_genmove.setEnabled(f); + + if (f == false) { + setShellVisible(false); + m_shell_visible.setEnabled(false); + setAnalyzeVisible(false); + m_analyze_visible.setEnabled(false); + } else { + m_shell_visible.setEnabled(true); + m_analyze_visible.setEnabled(true); + + setShellVisible(m_preferences.getBoolean("shell-show-on-connect")); + setAnalyzeVisible(m_preferences.getBoolean("analyze-show-on-connect")); + } + } + + public void updateMenuStates(HexGui current) { + m_swap_pieces.setEnabled(current.isSwapAllowed()); + m_swap_sides.setEnabled(current.isSwapAllowed()); + } + + // ---------------------------------------------------------------------- + + private JMenu createFileMenu() { + JMenu menu = new JMenu("File"); + menu.setMnemonic(KeyEvent.VK_F); + + JMenuItem item; + item = new JMenuItem("Open..."); + item.setMnemonic(KeyEvent.VK_O); + item.addActionListener(m_listener); + item.setActionCommand("loadgame"); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Save Game"); + item.setMnemonic(KeyEvent.VK_S); + item.addActionListener(m_listener); + item.setActionCommand("savegame"); + menu.add(item); + + item = new JMenuItem("Save Game As..."); + item.setMnemonic(KeyEvent.VK_A); + item.addActionListener(m_listener); + item.setActionCommand("savegameas"); + menu.add(item); + + item = new JMenuItem("Save Position As..."); + item.addActionListener(m_listener); + item.setActionCommand("save-position-as"); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Print Preview"); + item.addActionListener(m_listener); + item.setActionCommand("print-preview"); + menu.add(item); + + item = new JMenuItem("Print..."); + item.addActionListener(m_listener); + item.setActionCommand("print"); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Exit"); + item.setMnemonic(KeyEvent.VK_X); + item.addActionListener(m_listener); + item.setActionCommand("shutdown"); + menu.add(item); + + return menu; + } + + // ---------------------------------------------------------------------- + private JMenu createProgramMenu() { + JMenu menu = new JMenu("Program"); + menu.setMnemonic(KeyEvent.VK_P); + + JMenuItem item; + + item = new JMenuItem("New Program..."); + item.addActionListener(m_listener); + item.setActionCommand("new-program"); + menu.add(item); + + item = new JMenuItem("Edit Program..."); + item.addActionListener(m_listener); + item.setActionCommand("edit-program"); + menu.add(item); + + item = new JMenuItem("Delete Program..."); + item.addActionListener(m_listener); + item.setActionCommand("delete-program"); + menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Connect Local Program..."); + item.addActionListener(m_listener); + item.setActionCommand("connect-local-program"); + m_connect_local = item; + menu.add(item); + + // item = new JMenuItem("Connect Remote Program..."); + // item.addActionListener(m_listener); + // item.setActionCommand("connect-program"); + // item.setEnabled(false); + // m_connect_remote = item; + // menu.add(item); + + menu.addSeparator(); + + item = new JMenuItem("Reconnect Program"); + item.addActionListener(m_listener); + item.setActionCommand("reconnect-program"); + m_reconnect = item; + menu.add(item); + + item = new JMenuItem("Disconnect Program"); + item.addActionListener(m_listener); + item.setActionCommand("disconnect-program"); + m_disconnect = item; + menu.add(item); + + return menu; + } + + // ---------------------------------------------------------------------- + + private JMenu createGameMenu() { + JMenu menu = new JMenu("Game"); + menu.setMnemonic(KeyEvent.VK_G); + + JMenuItem item; + item = new JMenuItem("New"); + item.setMnemonic(KeyEvent.VK_N); + item.addActionListener(m_listener); + item.setActionCommand("newgame"); + menu.add(item); + + JMenu submenu; + + menu.addSeparator(); + + submenu = createClockMenu(); + menu.add(submenu); + + menu.addSeparator(); + + submenu = createToMoveMenu(); + menu.add(submenu); + + menu.addSeparator(); + + m_swap_pieces = new JMenuItem("Swap pieces"); + m_swap_pieces.addActionListener(m_listener); + m_swap_pieces.setActionCommand("game_swap_pieces"); + menu.add(m_swap_pieces); + + m_swap_sides = new JMenuItem("Swap sides"); + m_swap_sides.addActionListener(m_listener); + m_swap_sides.setActionCommand("game_swap_sides"); + menu.add(m_swap_sides); + + m_pass = new JMenuItem("Pass"); + m_pass.addActionListener(m_listener); + m_pass.setActionCommand("game_pass"); + menu.add(m_pass); + + m_resign = new JMenuItem("Resign"); + m_resign.addActionListener(m_listener); + m_resign.setActionCommand("game_resign"); + menu.add(m_resign); + + m_forfeit = new JMenuItem("Forfeit"); + m_forfeit.addActionListener(m_listener); + m_forfeit.setActionCommand("game_forfeit"); + menu.add(m_forfeit); + + m_addsetup = new JMenuItem("Add setup node"); + m_addsetup.addActionListener(m_listener); + m_addsetup.setActionCommand("game_addsetup"); + menu.add(m_addsetup); + + m_genmove = new JMenuItem("Generate Computer Move"); + m_genmove.addActionListener(m_listener); + m_genmove.setActionCommand("genmove"); + menu.add(m_genmove); + + menu.addSeparator(); + + item = new JMenuItem("Delete Current Branch"); + item.addActionListener(m_listener); + item.setActionCommand("game_delete_branch"); + menu.add(item); + + item = new JMenuItem("Make Main Branch"); + item.addActionListener(m_listener); + item.setActionCommand("game_make_main_branch"); + menu.add(item); + + return menu; + } + + private JMenu createClockMenu() { + JMenu menu = new JMenu("Clock"); + JMenuItem item; + + item = new JMenuItem("Start"); + item.addActionListener(m_listener); + item.setActionCommand("game_start_clock"); + menu.add(item); + + item = new JMenuItem("Stop"); + item.addActionListener(m_listener); + item.setActionCommand("game_stop_clock"); + menu.add(item); + + return menu; + } + + private JMenu createToMoveMenu() { + JMenu menu = new JMenu("Color To Move"); + + m_colorGroup = new ButtonGroup(); + String pref = m_preferences.get("first-move-color"); + + JRadioButtonMenuItem item; + item = new JRadioButtonMenuItem("black"); + item.addActionListener(m_listener); + item.setActionCommand("set_to_move"); + if (pref.equals("black")) item.setSelected(true); + m_colorGroup.add(item); + menu.add(item); + + item = new JRadioButtonMenuItem("white"); + item.addActionListener(m_listener); + item.setActionCommand("set_to_move"); + if (pref.equals("white")) item.setSelected(true); + m_colorGroup.add(item); + menu.add(item); + + return menu; + } + + public String getToMove() { + Enumeration e = m_colorGroup.getElements(); + AbstractButton b = (AbstractButton) e.nextElement(); + while (!b.isSelected() && e.hasMoreElements()) { + b = (AbstractButton) e.nextElement(); + } + return b.getText(); + } + + public void setToMove(String color) { + Enumeration e = m_colorGroup.getElements(); + AbstractButton b = (AbstractButton) e.nextElement(); + while (true) { + if (color.equalsIgnoreCase(b.getText())) { + b.setSelected(true); + } else { + b.setSelected(false); + } + if (!e.hasMoreElements()) break; + b = (AbstractButton) e.nextElement(); + } + } + + // ---------------------------------------------------------------------- + + private JMenu createEditMenu() { + JMenu menu = new JMenu("Edit"); + menu.setMnemonic(KeyEvent.VK_E); + + JMenu size = createBoardSizeMenu(); + menu.add(size); + + menu.addSeparator(); + + JMenuItem item; + item = new JMenuItem("Preferences..."); + item.addActionListener(m_listener); + item.setActionCommand("show-preferences"); + + menu.add(item); + + return menu; + } + + private JMenu createBoardSizeMenu() { + JMenu menu = new JMenu("Board Size"); + m_bsGroup = new ButtonGroup(); + + String sizes[] = + new String[] { + "19 x 19", "15 x 15", "14 x 14", "13 x 13", "11 x 11", "10 x 10", "9 x 9", "8 x 8", + "7 x 7", "6 x 6", "5 x 5", "4 x 4", "3 x 3" + }; + + String preferred = + m_preferences.get("gui-board-width") + " x " + m_preferences.get("gui-board-height"); + + boolean found = false; + JRadioButtonMenuItem item; + for (int i = 0; i < sizes.length; i++) { + item = new JRadioButtonMenuItem(sizes[i]); + item.addActionListener(m_listener); + item.setActionCommand("newgame"); + if (preferred.equals(sizes[i])) { + item.setSelected(true); + found = true; + } + m_bsGroup.add(item); + menu.add(item); + } + + if (!found) { + item = new JRadioButtonMenuItem(preferred); + item.addActionListener(m_listener); + item.setActionCommand("newgame"); + item.setSelected(true); + m_bsGroup.add(item); + menu.add(item); + } + + menu.addSeparator(); + + item = new JRadioButtonMenuItem("Other..."); + item.addActionListener(m_listener); + item.setActionCommand("newgame"); + item.setSelected(true); + m_bsGroup.add(item); + menu.add(item); + + return menu; + } + + public String getSelectedBoardSize() { + Enumeration e = m_bsGroup.getElements(); + AbstractButton b = (AbstractButton) e.nextElement(); + while (!b.isSelected() && e.hasMoreElements()) { + b = (AbstractButton) e.nextElement(); + } + + return b.getText(); + } + + // ---------------------------------------------------------------------- + + public boolean getToolbarVisible() { + return m_toolbar_visible.getState(); + } + + public boolean getShellVisible() { + return m_shell_visible.getState(); + } + + public void setShellVisible(boolean f) { + m_shell_visible.setState(f); + } + + public boolean getAnalyzeVisible() { + return m_analyze_visible.getState(); + } + + public void setAnalyzeVisible(boolean f) { + m_analyze_visible.setState(f); + } + + public boolean getEvalGraphVisible() { + return m_evalgraph_visible.getState(); + } + + public void setEvalGraphVisible(boolean f) { + m_evalgraph_visible.setState(f); + } + + private JMenu createViewMenu() { + JMenu menu = new JMenu("View"); + menu.setMnemonic(KeyEvent.VK_V); + + m_toolbar_visible = new JCheckBoxMenuItem("Show Toolbar"); + if (m_preferences.getBoolean("gui-toolbar-visible")) m_toolbar_visible.setState(true); + m_toolbar_visible.addActionListener(m_listener); + m_toolbar_visible.setActionCommand("gui_toolbar_visible"); + menu.add(m_toolbar_visible); + + m_shell_visible = new JCheckBoxMenuItem("Show Shell"); + m_shell_visible.addActionListener(m_listener); + m_shell_visible.setActionCommand("gui_shell_visible"); + m_shell_visible.setEnabled(false); + menu.add(m_shell_visible); + + m_analyze_visible = new JCheckBoxMenuItem("Show Analyze"); + m_analyze_visible.addActionListener(m_listener); + m_analyze_visible.setActionCommand("gui_analyze_visible"); + m_analyze_visible.setEnabled(false); + menu.add(m_analyze_visible); + + menu.addSeparator(); + + JMenuItem item = new JMenuItem("Clear Marks"); + item.addActionListener(m_listener); + item.setActionCommand("gui-clear-marks"); + menu.add(item); + + menu.addSeparator(); + + JMenu view; + view = createBoardViewMenu(); + menu.add(view); + + view = createOrientationViewMenu(); + menu.add(view); + + return menu; + } + + private JMenu createBoardViewMenu() { + JMenu menu = new JMenu("Board Type"); + m_btGroup = new ButtonGroup(); + + String defaultType = m_preferences.get("gui-board-type"); + + JRadioButtonMenuItem item; + item = new JRadioButtonMenuItem("Diamond"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_draw_type"); + if (defaultType.equals("Diamond")) item.setSelected(true); + m_btGroup.add(item); + menu.add(item); + + item = new JRadioButtonMenuItem("Flat"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_draw_type"); + if (defaultType.equals("Flat")) item.setSelected(true); + m_btGroup.add(item); + menu.add(item); + + item = new JRadioButtonMenuItem("Flat2"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_draw_type"); + if (defaultType.equals("Flat2")) item.setSelected(true); + m_btGroup.add(item); + menu.add(item); + + item = new JRadioButtonMenuItem("Go"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_draw_type"); + if (defaultType.equals("Go")) item.setSelected(true); + m_btGroup.add(item); + menu.add(item); + + item = new JRadioButtonMenuItem("Y"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_draw_type"); + if (defaultType.equals("Go")) item.setSelected(true); + m_btGroup.add(item); + menu.add(item); + + return menu; + } + + public String getCurrentBoardDrawType() { + Enumeration e = m_btGroup.getElements(); + AbstractButton b = (AbstractButton) e.nextElement(); + while (!b.isSelected() && e.hasMoreElements()) { + b = (AbstractButton) e.nextElement(); + } + return b.getText(); + } + + private JMenu createOrientationViewMenu() { + JMenu menu = new JMenu("Board Orientation"); + m_orGroup = new ButtonGroup(); + + String pref = m_preferences.get("gui-board-orientation"); + + JRadioButtonMenuItem item; + item = new JRadioButtonMenuItem("Positive"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_orientation"); + if (pref.equals("positive")) item.setSelected(true); + m_orGroup.add(item); + menu.add(item); + + item = new JRadioButtonMenuItem("Negative"); + item.addActionListener(m_listener); + item.setActionCommand("gui_board_orientation"); + if (pref.equals("negative")) item.setSelected(true); + m_orGroup.add(item); + menu.add(item); + + return menu; + } + + public String getCurrentBoardOrientation() { + Enumeration e = m_orGroup.getElements(); + AbstractButton b = (AbstractButton) e.nextElement(); + while (!b.isSelected() && e.hasMoreElements()) { + b = (AbstractButton) e.nextElement(); + } + return b.getText(); + } + + // ---------------------------------------------------------------------- + + private JMenu createHelpMenu() { + JMenu menu = new JMenu("Help"); + menu.setMnemonic(KeyEvent.VK_H); + + JMenuItem item; + item = new JMenuItem("About HexGui..."); + item.setMnemonic(KeyEvent.VK_A); + item.addActionListener(m_listener); + item.setActionCommand("about"); + menu.add(item); + + return menu; + } + + private GuiPreferences m_preferences; + private ActionListener m_listener; + private JMenuBar m_menuBar; + + private JCheckBoxMenuItem m_toolbar_visible; + private JCheckBoxMenuItem m_shell_visible; + private JCheckBoxMenuItem m_analyze_visible; + private JCheckBoxMenuItem m_evalgraph_visible; + + private JMenuItem m_connect_local, m_connect_remote, m_disconnect, m_reconnect; + private JMenuItem m_pass, m_resign, m_forfeit, m_swap_pieces, m_swap_sides, m_addsetup, m_genmove; + + private ButtonGroup m_bsGroup; // board sizes + private ButtonGroup m_btGroup; // board view types (diamond, flat, etc) + private ButtonGroup m_orGroup; // orientation: positive or negative? + private ButtonGroup m_colorGroup; // whose turn to move? +} diff --git a/src/main/java/hexgui/gui/GuiPreferences.java b/src/main/java/hexgui/gui/GuiPreferences.java new file mode 100644 index 0000000..31bb079 --- /dev/null +++ b/src/main/java/hexgui/gui/GuiPreferences.java @@ -0,0 +1,78 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import java.io.*; +import java.util.*; +import java.util.prefs.*; + +// ---------------------------------------------------------------------------- + +/** GuiPreferences */ +public final class GuiPreferences { + private static String[][] s_preflist = + new String[][] { + {"gui-board-type", "Flat"}, + {"gui-board-orientation", "positive"}, + {"gui-board-pixel-width", "750"}, + {"gui-board-pixel-height", "500"}, + {"gui-board-width", "11"}, + {"gui-board-height", "11"}, + {"draw-field-alpha", "0.3"}, // FIXME: not used yet! + {"gui-toolbar-visible", "true"}, + {"shell-show-on-connect", "false"}, + {"analyze-show-on-connect", "false"}, + {"auto-respond", "true"}, + {"first-move-color", "black"}, + {"remote-host-name", "localhost"}, + {"is-program-attached", "false"}, + {"attached-program", "dummy-program-name"}, + {"path-load-game", "."}, + {"path-save-game", "."}, + {"dummy-preference", ""} + }; + + public GuiPreferences(Class theClass) { + m_preferences = Preferences.userNodeForPackage(theClass); + } + + public String get(String name) { + for (int i = 0; i < s_preflist.length; i++) { + if (name.equals(s_preflist[i][0])) { + return m_preferences.get(s_preflist[i][0], s_preflist[i][1]); + } + } + System.out.println("Unknown preference: " + name + ", returning 'unknown'."); + return "unknown"; + } + + public int getInt(String name) { + return Integer.parseInt(get(name)); + } + + public boolean getBoolean(String name) { + return Boolean.parseBoolean(get(name)); + } + + public void put(String name, String value) { + for (int i = 0; i < s_preflist.length; i++) { + if (name.equals(s_preflist[i][0])) { + m_preferences.put(s_preflist[i][0], value); + return; + } + } + System.out.println("Unknown preference: " + name + ", nothing stored."); + } + + public void put(String name, int value) { + put(name, Integer.toString(value)); + } + + public void put(String name, boolean value) { + put(name, Boolean.toString(value)); + } + + Preferences m_preferences; +} diff --git a/src/main/java/hexgui/gui/GuiRunnable.java b/src/main/java/hexgui/gui/GuiRunnable.java new file mode 100644 index 0000000..f56cc84 --- /dev/null +++ b/src/main/java/hexgui/gui/GuiRunnable.java @@ -0,0 +1,20 @@ +package hexgui.gui; + +import java.util.*; +import javax.swing.*; + +/** + * Runnable that is guaranteed to be run in the Swing event dispatch thread. Most Swing function may + * only be called in the Swing event dispatch thread. + */ +public class GuiRunnable implements Runnable { + public GuiRunnable(Runnable runnable) { + m_runnable = runnable; + } + + public void run() { + SwingUtilities.invokeLater(m_runnable); + } + + private Runnable m_runnable; +} diff --git a/src/main/java/hexgui/gui/GuiToolBar.java b/src/main/java/hexgui/gui/GuiToolBar.java new file mode 100644 index 0000000..9db9aeb --- /dev/null +++ b/src/main/java/hexgui/gui/GuiToolBar.java @@ -0,0 +1,424 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.game.Node; +import java.awt.*; +import java.awt.event.*; +import java.net.URL; +import java.util.*; +import javax.swing.*; + +// ---------------------------------------------------------------------------- + +public final class GuiToolBar implements ActionListener { + public GuiToolBar(HexGui listener, GuiPreferences preferences) { + m_preferences = preferences; + m_listener = listener; + + m_toolBar = new JToolBar(); + createToolBar(); + + setVisible(m_preferences.getBoolean("gui-toolbar-visible")); + } + + public JToolBar getJToolBar() { + return m_toolBar; + } + + public void setVisible(boolean visible) { + m_toolBar.setVisible(visible); + m_preferences.put("gui-toolbar-visible", visible); + } + + public void enableStopButton() { + m_stop.setEnabled(true); + } + + public void disableStopButton() { + m_stop.setEnabled(false); + } + + public void setToMove(String string) { + if (string.equals("black")) { + if (m_black_to_play != null) m_tomove.setIcon(m_black_to_play); + else m_tomove.setText("black"); + } else if (string.equals("white")) { + if (m_white_to_play != null) m_tomove.setIcon(m_white_to_play); + else m_tomove.setText("white"); + } else System.out.println("Unknown value: '" + string + "'"); + } + + /** + * Returns the click context--what type of move does the user wish to make? + * + * @return "black" if black setup icon is selected. "white" if white setup icon is selected. + * "play" otherwise. + */ + public String getClickContext() { + if (m_setup_black.isSelected()) return "black"; + else if (m_setup_white.isSelected()) return "white"; + return "play"; + } + + public void deselectSetup() { + m_setup_black.setSelected(false); + m_setup_white.setSelected(false); + } + + public void setProgramConnected(boolean f) { + m_play.setEnabled(f); + m_hint.setEnabled(f); + m_solve.setEnabled(f); + m_program_options.setEnabled(f); + } + + public void updateButtonStates(Node node, HexGui gui) { + m_beginning.setEnabled(node.getParent() != null); + m_back10.setEnabled(node.getParent() != null); + m_back.setEnabled(node.getParent() != null); + + m_forward.setEnabled(node.getChild() != null); + m_forward10.setEnabled(node.getChild() != null); + m_end.setEnabled(node.getChild() != null); + + m_up.setEnabled(node.getPrev() != null); + m_down.setEnabled(node.getNext() != null); + + m_swap.setEnabled(gui.isSwapAllowed()); + } + + public void lockToolbar() { + m_new.setEnabled(false); + m_load.setEnabled(false); + m_save.setEnabled(false); + + m_setup_black.setEnabled(false); + m_setup_white.setEnabled(false); + + m_beginning.setEnabled(false); + m_back10.setEnabled(false); + m_back.setEnabled(false); + + m_forward.setEnabled(false); + m_forward10.setEnabled(false); + m_end.setEnabled(false); + + m_up.setEnabled(false); + m_down.setEnabled(false); + + m_swap.setEnabled(false); + + m_play.setEnabled(false); + + m_tomove.setEnabled(false); + + m_hint.setEnabled(false); + m_solve.setEnabled(false); + m_program_options.setEnabled(false); + + enableStopButton(); + } + + public void unlockToolbar(Node node, HexGui gui) { + disableStopButton(); + + m_play.setEnabled(true); + + m_tomove.setEnabled(true); + + m_setup_black.setEnabled(true); + m_setup_white.setEnabled(true); + + m_new.setEnabled(true); + m_load.setEnabled(true); + m_save.setEnabled(true); + + m_hint.setEnabled(true); + m_solve.setEnabled(true); + m_program_options.setEnabled(true); + + updateButtonStates(node, gui); + } + + // ---------------------------------------------------------------------- + + private void createToolBar() { + m_new = makeButton("hexgui/images/filenew.png", "newgame", "New Game", "New"); + m_toolBar.add(m_new); + + m_load = makeButton("hexgui/images/fileopen.png", "loadgame", "Load Game", "Load"); + m_toolBar.add(m_load); + + m_save = makeButton("hexgui/images/filesave2.png", "savegame", "Save Game", "Save"); + m_toolBar.add(m_save); + + m_toolBar.addSeparator(); + + m_setup_black = + makeToggleButton( + "hexgui/images/setup-black.png", + "setup-black", + "Setup Black Stones", + "Setup Black", + KeyEvent.VK_B); + m_toolBar.add(m_setup_black); + + m_setup_white = + makeToggleButton( + "hexgui/images/setup-white.png", + "setup-white", + "Setup White Stones", + "Setup White", + KeyEvent.VK_W); + m_toolBar.add(m_setup_white); + + m_toolBar.addSeparator(); + + m_beginning = + makeButton( + "hexgui/images/beginning.png", + "game_beginning", + "Game Start", + "Start", + KeyEvent.VK_HOME); + m_toolBar.add(m_beginning); + + m_back10 = + makeButton( + "hexgui/images/backward10.png", + "game_backward10", + "Go back ten moves", + "Back10", + KeyEvent.VK_PAGE_UP); + m_toolBar.add(m_back10); + + m_back = + makeButton( + "hexgui/images/back.png", "game_back", "Go back one move", "Back", KeyEvent.VK_LEFT); + m_toolBar.add(m_back); + + m_forward = + makeButton( + "hexgui/images/forward.png", + "game_forward", + "Go forward one move", + "Forward", + KeyEvent.VK_RIGHT); + m_toolBar.add(m_forward); + + m_forward10 = + makeButton( + "hexgui/images/forward10.png", + "game_forward10", + "Go forward ten moves", + "Forward10", + KeyEvent.VK_PAGE_DOWN); + m_toolBar.add(m_forward10); + + m_end = + makeButton( + "hexgui/images/end.png", "game_end", "Go to end of game", "End", KeyEvent.VK_END); + m_toolBar.add(m_end); + + m_toolBar.addSeparator(); + + m_up = + makeButton( + "hexgui/images/up.png", "game_up", "Explore previous variation", "Up", KeyEvent.VK_UP); + m_toolBar.add(m_up); + + m_down = + makeButton( + "hexgui/images/down.png", + "game_down", + "Explore next variation", + "Down", + KeyEvent.VK_DOWN); + m_toolBar.add(m_down); + + m_toolBar.addSeparator(); + + m_swap = makeButton("hexgui/images/swap.png", "game_swap_pieces", "Play swap move", "Swap"); + m_toolBar.add(m_swap); + m_swap.setEnabled(false); + + m_toolBar.addSeparator(); + + m_play = + makeButton( + "hexgui/images/play.png", "genmove", "Generate computer move", "Play", KeyEvent.VK_M); + m_toolBar.add(m_play); + m_play.setEnabled(false); + + m_stop = makeButton("hexgui/images/stop.png", "stop", "Stop Action", "Stop"); + m_toolBar.add(m_stop); + disableStopButton(); + + m_toolBar.addSeparator(); + + String pref = m_preferences.get("first-move-color"); + m_tomove = + makeButton( + "hexgui/images/black-24x24.png", "toggle_tomove", "Color of player to move", pref); + m_tomove.setText(""); + { + URL imageURL = getURL("hexgui/images/black-24x24.png"); + m_black_to_play = new ImageIcon(imageURL, "black"); + } + { + URL imageURL = getURL("hexgui/images/white-24x24.png"); + m_white_to_play = new ImageIcon(imageURL, "white"); + } + setToMove(pref); + + m_toolBar.add(m_tomove); + + m_toolBar.addSeparator(); + + m_hint = + makeButton( + "hexgui/images/hint-24x24.png", + "show_consider_set", + "Show provably inferior cells", + "Hint"); + m_toolBar.add(m_hint); + m_hint.setEnabled(false); + + m_solve = + makeButton( + "hexgui/images/solve.png", + "solve_state", + "Solve State with DFPN", + "Solve", + KeyEvent.VK_S); + m_toolBar.add(m_solve); + m_solve.setEnabled(false); + + m_program_options = + makeButton( + "hexgui/images/program-options.png", "program_options", "Program Options", "Options"); + m_toolBar.add(m_program_options); + m_program_options.setEnabled(false); + } + + private JButton makeButton( + String imageFile, String actionCommand, String tooltip, String altText) { + return makeButton(imageFile, actionCommand, tooltip, altText, -1); + } + + private JButton makeButton( + String imageFile, final String actionCommand, String tooltip, String altText, int key) { + final JButton button = new JButton(); + final ActionListener listener = m_listener; + button.setFocusable(false); + button.addActionListener(m_listener); + button.setActionCommand(actionCommand); + button.setToolTipText(tooltip); + addIconToButton(button, imageFile, altText); + if (key != -1) { + button + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(key, 0), "key"); + Action action = + new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + if (m_listener.shortcutsEnabled()) { + button.doClick(); + } + } + }; + button.getActionMap().put("key", action); + } + return button; + } + + private JToggleButton makeToggleButton( + String imageFile, String actionCommand, String tooltip, String altText, int key) { + final JToggleButton button = new JToggleButton(); + button.setFocusable(false); + button.addActionListener(this); + button.setActionCommand(actionCommand); + button.setToolTipText(tooltip); + addIconToButton(button, imageFile, altText); + if (key != -1) { + button + .getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW) + .put(KeyStroke.getKeyStroke(key, 0), "key"); + Action action = + new AbstractAction() { + public void actionPerformed(ActionEvent actionEvent) { + if (m_listener.shortcutsEnabled()) { + button.doClick(); + } + } + }; + button.getActionMap().put("key", action); + } + return button; + } + + private JToggleButton makeToggleButton( + String imageFile, String actionCommand, String tooltip, String altText) { + return makeToggleButton(imageFile, actionCommand, tooltip, altText, -1); + } + + private void addIconToButton(AbstractButton button, String imageFile, String altText) { + URL imageURL = getURL(imageFile); + if (imageURL != null) { + button.setIcon(new ImageIcon(imageURL, altText)); + } else { + button.setText(altText); + System.out.println("*** Resource not found: " + imageFile); + } + } + + private URL getURL(String filename) { + URL url = null; + if (filename != null) { + ClassLoader classLoader = getClass().getClassLoader(); + url = classLoader.getResource(filename); + } + return url; + } + + // ---------------------------------------------------------------------- + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("setup-black")) { + if (m_setup_white.isSelected()) { + m_setup_white.setSelected(false); + } + m_listener.actionPerformed(e); + } else if (cmd.equals("setup-white")) { + if (m_setup_black.isSelected()) { + m_setup_black.setSelected(false); + } + m_listener.actionPerformed(e); + } else { + System.out.println("GuiToolBar: Unknown action command '" + cmd + "'"); + } + } + + // ---------------------------------------------------------------------- + + private GuiPreferences m_preferences; + private JToolBar m_toolBar; + private HexGui m_listener; + + private JButton m_new, m_load, m_save; + private JToggleButton m_setup_black, m_setup_white; + private JButton m_beginning, m_back10, m_back; + private JButton m_forward, m_forward10, m_end; + private JButton m_up, m_down; + private JButton m_play, m_stop, m_swap; + + private JButton m_tomove; + private ImageIcon m_black_to_play, m_white_to_play; + private JButton m_solve, m_hint, m_program_options; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/GuiUtil.java b/src/main/java/hexgui/gui/GuiUtil.java new file mode 100644 index 0000000..9c7a80d --- /dev/null +++ b/src/main/java/hexgui/gui/GuiUtil.java @@ -0,0 +1,314 @@ +// GuiUtil.java + +package hexgui.gui; + +import hexgui.util.Platform; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Font; +import java.awt.Frame; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.KeyboardFocusManager; +import java.awt.RenderingHints; +import java.awt.Toolkit; +import java.awt.Window; +import java.awt.datatransfer.Clipboard; +import java.awt.datatransfer.ClipboardOwner; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.StringSelection; +import java.awt.datatransfer.Transferable; +import java.awt.datatransfer.UnsupportedFlavorException; +import java.io.IOException; +import java.net.URL; +import javax.swing.BorderFactory; +import javax.swing.Box; +import javax.swing.ImageIcon; +import javax.swing.InputMap; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JFrame; +import javax.swing.JTextField; +import javax.swing.JTextPane; +import javax.swing.KeyStroke; +import javax.swing.SwingUtilities; +import javax.swing.border.Border; +import javax.swing.text.Style; +import javax.swing.text.StyleConstants; +import javax.swing.text.StyleContext; +import javax.swing.text.StyledDocument; + +/** GUI utility classes and static functions. */ +public class GuiUtil { + /** Constant used for padding in dialogs. */ + public static final int PAD = 5; + + /** Constant used for small padding in dialogs. */ + public static final int SMALL_PAD = 2; + + public static void addStyle(JTextPane pane, String name, Color foreground, Color background) { + StyledDocument doc = pane.getStyledDocument(); + StyleContext context = StyleContext.getDefaultStyleContext(); + Style defaultStyle = context.getStyle(StyleContext.DEFAULT_STYLE); + Style style = doc.addStyle(name, defaultStyle); + StyleConstants.setForeground(style, foreground); + StyleConstants.setBackground(style, background); + } + + public static void copyToClipboard(String text) { + StringSelection selection = new StringSelection(text); + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + ClipboardOwner owner = + new ClipboardOwner() { + public void lostOwnership(Clipboard clipboard, Transferable contents) {} + }; + clipboard.setContents(selection, owner); + } + + /** + * Wrapper object for JComboBox items. JComboBox can have focus and keyboard navigation problems + * if duplicate String objects are added. See JDK 1.5 doc for JComboBox.addItem. + */ + public static Object createComboBoxItem(final String item) { + return new Object() { + public String toString() { + return item; + } + }; + } + + /** + * Create empty border with normal padding. + * + * @see #PAD + */ + public static Border createEmptyBorder() { + return EMPTY_BORDER; + } + + /** + * Create empty box with size of normal padding. + * + * @see #PAD + */ + public static Box.Filler createFiller() { + return new Box.Filler(FILLER_DIMENSION, FILLER_DIMENSION, FILLER_DIMENSION); + } + + /** + * Create empty border with small padding. + * + * @see #SMALL_PAD + */ + public static Border createSmallEmptyBorder() { + return SMALL_EMPTY_BORDER; + } + + /** + * Create empty box with size of small padding. + * + * @see #SMALL_PAD + */ + public static Box.Filler createSmallFiller() { + return new Box.Filler(SMALL_FILLER_DIMENSION, SMALL_FILLER_DIMENSION, SMALL_FILLER_DIMENSION); + } + + public static String getClipboardText() { + Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); + Transferable content = clipboard.getContents(null); + if (content == null || !content.isDataFlavorSupported(DataFlavor.stringFlavor)) return null; + try { + return (String) content.getTransferData(DataFlavor.stringFlavor); + } catch (UnsupportedFlavorException e) { + return null; + } catch (IOException e) { + return null; + } + } + + /** + * Return a style sheet for message labels using HTML. + * + * @return A string with a HTML-head tag containing a style tag with formatting options or an + * empty string. + */ + public static String getMessageCss() { + if (Platform.isMac()) + return ""; + else return ""; + } + + /** + * Get size of default monspaced font. Can be used for setting the initial size of some GUI + * elements. + */ + public static int getDefaultMonoFontSize() { + return MONOSPACED_FONT.getSize(); + } + + public static ImageIcon getIcon(String icon, String name) { + String resource = "hexgui/images/" + icon + ".png"; + URL url = GuiUtil.class.getClassLoader().getResource(resource); + return new ImageIcon(url, name); + } + + /** + * Manually break message into multiple lines for multi-line labels. Needed for multi-line + * messages in option panes, because pack() on JOptionPane does not compute the option pane size + * correctly, if a maximum width is set and the label text is automatically broken into multiple + * lines. The workaround with calling invalidate() and pack() a second time does not work either + * in this case. See also Sun Bug ID 4545951 (still in Linux JDK 1.5.0_04-b05 or Mac 1.4.2_12) + */ + public static String insertLineBreaks(String message) { + final int MAX_CHAR_PER_LINE = 72; + int length = message.length(); + if (length < MAX_CHAR_PER_LINE) return message; + StringBuilder buffer = new StringBuilder(); + int startLine = 0; + int lastWhiteSpace = -1; + for (int pos = 0; pos < length; ++pos) { + char c = message.charAt(pos); + if (pos - startLine > 72) { + int endLine = (lastWhiteSpace > startLine ? lastWhiteSpace : pos); + if (buffer.length() > 0) buffer.append("
"); + buffer.append(message.substring(startLine, endLine)); + startLine = endLine; + } + if (Character.isWhitespace(c)) lastWhiteSpace = pos; + } + if (buffer.length() > 0) buffer.append("
"); + buffer.append(message.substring(startLine)); + return buffer.toString(); + } + + /** + * Call SwingUtilities.invokeAndWait. Ignores possible exceptions (apart from printing a warning + * to System.err + */ + public static void invokeAndWait(Runnable runnable) { + try { + SwingUtilities.invokeAndWait(runnable); + } catch (InterruptedException e) { + System.err.println("Thread interrupted"); + } catch (java.lang.reflect.InvocationTargetException e) { + System.err.println("InvocationTargetException"); + } + } + + public static boolean isActiveWindow(Window window) { + KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); + return (manager.getActiveWindow() == window); + } + + /** + * Check window for normal state. Checks if window is not maximized (in either or both directions) + * and not iconified. + */ + public static boolean isNormalSizeMode(JFrame window) { + int state = window.getExtendedState(); + int mask = + Frame.MAXIMIZED_BOTH | Frame.MAXIMIZED_VERT | Frame.MAXIMIZED_HORIZ | Frame.ICONIFIED; + return ((state & mask) == 0); + } + + public static void paintImmediately(JComponent component) { + component.paintImmediately(component.getVisibleRect()); + } + + public static void removeKeyBinding(JComponent component, String key) { + int condition = JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT; + InputMap inputMap = component.getInputMap(condition); + // According to the docs, null should remove the action, but it does + // not seem to work with Sun Java 1.4.2, new Object() works + inputMap.put(KeyStroke.getKeyStroke(key), new Object()); + } + + /** Set antialias rendering hint if graphics is instance of Graphics2D. */ + public static void setAntiAlias(Graphics graphics) { + if (graphics instanceof Graphics2D) { + Graphics2D graphics2D = (Graphics2D) graphics; + graphics2D.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + } + } + + /** Set text field non-editable. Also sets it non-focusable. */ + public static void setEditableFalse(JTextField field) { + field.setEditable(false); + field.setFocusable(false); + } + + /** Set Go icon on frame. */ + public static void setGoIcon(Frame frame) { + URL url = s_iconURL; + if (url != null) frame.setIconImage(new ImageIcon(url).getImage()); + } + + /** + * Set property to render button in bevel style on the Mac. Only has an effect if Quaqua Look and + * Feel is used. + */ + public static void setMacBevelButton(JButton button) { + button.putClientProperty("Quaqua.Button.style", "bevel"); + } + + public static void setMonospacedFont(JComponent component) { + if (MONOSPACED_FONT != null) component.setFont(MONOSPACED_FONT); + } + + public static void addStyle(JTextPane textPane, String name, Color foreground) { + addStyle(textPane, name, foreground, null, false); + } + + public static void addStyle( + JTextPane textPane, String name, Color foreground, Color background, boolean bold) { + StyledDocument doc = textPane.getStyledDocument(); + StyleContext context = StyleContext.getDefaultStyleContext(); + Style def = context.getStyle(StyleContext.DEFAULT_STYLE); + Style style = doc.addStyle(name, def); + if (foreground != null) StyleConstants.setForeground(style, foreground); + if (background != null) StyleConstants.setBackground(style, background); + StyleConstants.setBold(style, bold); + } + + public static void setStyle(JTextPane textPane, int start, int length, String name) { + StyledDocument doc = textPane.getStyledDocument(); + Style style; + if (name == null) { + StyleContext context = StyleContext.getDefaultStyleContext(); + style = context.getStyle(StyleContext.DEFAULT_STYLE); + } else style = doc.getStyle(name); + doc.setCharacterAttributes(start, length, style, true); + } + + public static void setUnlimitedSize(JComponent component) { + Dimension size = new Dimension(Short.MAX_VALUE, Short.MAX_VALUE); + component.setMaximumSize(size); + } + + static { + ClassLoader loader = ClassLoader.getSystemClassLoader(); + // There are problems on some platforms with transparency (e.g. Linux + // Sun Java 1.5.0). Best solution for now is to take an icon without + // transparency + s_iconURL = loader.getResource("hexgui/images/hexgui-48x48-notrans.png"); + } + + private static final Font MONOSPACED_FONT = Font.decode("Monospaced"); + + private static final Border EMPTY_BORDER = BorderFactory.createEmptyBorder(PAD, PAD, PAD, PAD); + + private static final Border SMALL_EMPTY_BORDER = + BorderFactory.createEmptyBorder( + SMALL_PAD, SMALL_PAD, + SMALL_PAD, SMALL_PAD); + + private static final Dimension FILLER_DIMENSION = new Dimension(PAD, PAD); + + private static final Dimension SMALL_FILLER_DIMENSION = new Dimension(SMALL_PAD, SMALL_PAD); + + private static URL s_iconURL; +} diff --git a/src/main/java/hexgui/gui/HexGui.java b/src/main/java/hexgui/gui/HexGui.java new file mode 100644 index 0000000..391a2ac --- /dev/null +++ b/src/main/java/hexgui/gui/HexGui.java @@ -0,0 +1,2393 @@ +package hexgui.gui; + +import static java.text.MessageFormat.format; + +import hexgui.game.Clock; +import hexgui.game.GameInfo; +import hexgui.game.Node; +import hexgui.hex.*; +import hexgui.htp.AnalyzeCommand; +import hexgui.htp.AnalyzeDefinition; +import hexgui.htp.AnalyzeType; +import hexgui.htp.HtpController; +import hexgui.htp.HtpError; +import hexgui.sgf.SgfReader; +import hexgui.sgf.SgfWriter; +import hexgui.util.ErrorMessage; +import hexgui.util.Pair; +import hexgui.util.StreamCopy; +import hexgui.util.StringUtils; +import hexgui.version.Version; +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.net.*; +import java.util.*; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.Semaphore; +import javax.swing.*; + +// ---------------------------------------------------------------------------- + +/** HexGui. */ +public final class HexGui extends JFrame + implements ActionListener, + GuiBoard.Listener, + HtpShell.Callback, + HtpController.GuiFxCallback, + AnalyzeDialog.Listener, + Comment.Listener { + public HexGui(final File file, final String command) { + super("HexGui"); + setIcon(); + + System.out.println("HexGui v" + Version.id + "; " + Version.date + "\n"); + + // Catch the close action and shutdown nicely + setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); + addWindowListener( + new java.awt.event.WindowAdapter() { + public void windowClosing(WindowEvent winEvt) { + cmdShutdown(); + } + }); + + m_selected_cells = new Vector(); + + m_about = new AboutDialog(this); + + m_preferences = new GuiPreferences(getClass()); + + m_menubar = new GuiMenuBar(this, m_preferences); + setJMenuBar(m_menubar.getJMenuBar()); + + m_toolbar = new GuiToolBar(this, m_preferences); + getContentPane().add(m_toolbar.getJToolBar(), BorderLayout.NORTH); + + m_statusbar = new StatusBar(); + getContentPane().add(m_statusbar, BorderLayout.SOUTH); + + m_guiboard = new GuiBoard(this, m_preferences); + getContentPane().add(m_guiboard, BorderLayout.CENTER); + + m_showAnalyzeText = new ShowAnalyzeText(this, m_guiboard); + + JPanel panel = new JPanel(new BorderLayout()); + getContentPane().add(panel, BorderLayout.EAST); + + m_blackClock = new Clock(); + m_whiteClock = new Clock(); + m_gameinfopanel = new GameInfoPanel(m_blackClock, m_whiteClock); + m_comment = new Comment(this); + panel.add(m_gameinfopanel, BorderLayout.NORTH); + panel.add(m_comment, BorderLayout.CENTER); + + cmdNewGame(); + + pack(); + + m_locked = false; + + m_semaphore = new Semaphore(1); + m_htp_queue = new ArrayBlockingQueue(256); + new Thread(new CommandHandler(this, m_htp_queue)).start(); + + setVisible(true); + // After frame is visible, further code using Swing functions must + // be run in the Swing event dispatch thread. + SwingUtilities.invokeLater( + new Runnable() { + public void run() { + initialize(file, command); + } + }); + setCursorType(); + } + + // ------------------------------------------------------------------- + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + + unFocus(); + + // + // system commands + // + if (cmd.equals("shutdown")) { + cmdShutdown(); + } else if (cmd.equals("new-program")) { + cmdNewProgram(); + } else if (cmd.equals("edit-program")) { + cmdEditProgram(); + } else if (cmd.equals("delete-program")) { + cmdDeleteProgram(); + } else if (cmd.equals("connect-program")) { + cmdConnectRemoteProgram(); + } else if (cmd.equals("connect-local-program")) { + cmdConnectLocalProgram(); + } else if (cmd.equals("disconnect-program")) { + cmdDisconnectProgram(); + } else if (cmd.equals("reconnect-program")) { + cmdReconnectProgram(); + // + // file/help commands + // + } else if (cmd.equals("newgame")) { + end_setup(); + cmdNewGame(); + } else if (cmd.equals("savegame")) { + // We previously only saved when gameChanged() == true, + // but this is dangerous because saving would fail + // silently otherwise. It's better to simply save the game + // when the user asks for it. An alternative use for + // gameChanged() would be to disable the save button when + // we think the game hasn't changed. But we shouldn't just + // offer a button and then do nothing. + cmdSaveGame(); + } else if (cmd.equals("savegameas")) { + cmdSaveGameAs(); + } else if (cmd.equals("loadgame")) { + cmdLoadGame(); + } else if (cmd.equals("save-position-as")) { + cmdSavePositionAs(); + } else if (cmd.equals("print-preview")) { + cmdPrintPreview(); + } else if (cmd.equals("print")) { + cmdPrint(); + } else if (cmd.equals("about")) { + cmdAbout(); + // + // gui commands + // + } else if (cmd.equals("gui_toolbar_visible")) { + cmdGuiToolbarVisible(); + } else if (cmd.equals("gui_shell_visible")) { + cmdGuiShellVisible(); + } else if (cmd.equals("gui_analyze_visible")) { + cmdGuiAnalyzeVisible(); + } else if (cmd.equals("gui_board_draw_type")) { + cmdGuiBoardDrawType(); + } else if (cmd.equals("gui_board_orientation")) { + cmdGuiBoardOrientation(); + } else if (cmd.equals("show-preferences")) { + cmdShowPreferences(); + } else if (cmd.equals("gui-clear-marks")) { + cmdClearMarks(); + // + // game navigation commands + // + } else if (cmd.equals("game_beginning")) { + end_setup(); + backward(-1); + } else if (cmd.equals("game_backward10")) { + end_setup(); + backward(10); + } else if (cmd.equals("game_back")) { + end_setup(); + backward(1); + } else if (cmd.equals("game_forward")) { + end_setup(); + forward(1); + } else if (cmd.equals("game_forward10")) { + end_setup(); + forward(10); + } else if (cmd.equals("game_end")) { + end_setup(); + forward(-1); + } else if (cmd.equals("game_up")) { + end_setup(); + up(); + } else if (cmd.equals("game_down")) { + end_setup(); + down(); + } else if (cmd.equals("game_swap_sides")) { + end_setup(); + humanMove(new Move(HexPoint.get("swap-sides"), m_tomove)); + } else if (cmd.equals("game_swap_pieces")) { + end_setup(); + humanMove(new Move(HexPoint.get("swap-pieces"), m_tomove)); + } else if (cmd.equals("game_pass")) { + end_setup(); + humanMove(new Move(HexPoint.get("pass"), m_tomove)); + } else if (cmd.equals("game_resign")) { + end_setup(); + humanMove(new Move(HexPoint.get("resign"), m_tomove)); + } else if (cmd.equals("game_forfeit")) { + end_setup(); + humanMove(new Move(HexPoint.get("forfeit"), m_tomove)); + } else if (cmd.equals("game_addsetup")) { + end_setup(); + addSetupNode(); + } else if (cmd.equals("genmove")) { + end_setup(); + htpGenMove(m_tomove); + } else if (cmd.equals("game_delete_branch")) { + end_setup(); + cmdDeleteBranch(); + } else if (cmd.equals("game_make_main_branch")) { + end_setup(); + cmdMoveBranchTop(); + } else if (cmd.equals("game_start_clock")) { + startClock(); + } else if (cmd.equals("game_stop_clock")) { + stopClock(); + } else if (cmd.equals("stop")) { + m_white.interrupt(); + } else if (cmd.equals("toggle_tomove")) { + end_setup(); + cmdToggleToMove(); + } else if (cmd.equals("set_to_move")) { + end_setup(); + cmdSetToMove(); + } else if (cmd.equals("setup-black")) { + cmdSetupBlack(); + } else if (cmd.equals("setup-white")) { + cmdSetupWhite(); + } + // + // other + // + else if (cmd.equals("show_consider_set")) { + Runnable cb = + new Runnable() { + public void run() { + cbShowInferiorCells(); + } + }; + Runnable callback = new GuiRunnable(cb); + sendCommand("vc-build " + m_tomove.toString() + "\n", callback); + } else if (cmd.equals("solve_state")) { + sendCommand("param_dfpn use_guifx 1\n", null); + Runnable callback = + new GuiRunnable( + new Runnable() { + public void run() { + cbSolveState(); + } + }); + sendCommand("dfpn-solve-state " + m_tomove + "\n", callback); + } else if (cmd.equals("program_options")) { + AnalyzeCommand command; + if (m_white_name.equalsIgnoreCase("Mohex") || m_white_name.equalsIgnoreCase("HexHex")) { + command = new AnalyzeCommand(new AnalyzeDefinition("param/blah/param_mohex")); + Runnable cb = + new Runnable() { + public void run() { + cbEditParameters(); + } + }; + Runnable callback = new GuiRunnable(cb); + m_curAnalyzeCommand = command; + sendCommand(command.getCommand() + "\n", callback); + } else if (m_white_name.equalsIgnoreCase("Wolve")) { + command = new AnalyzeCommand(new AnalyzeDefinition("param/blah/param_wolve")); + Runnable cb = + new Runnable() { + public void run() { + cbEditParameters(); + } + }; + Runnable callback = new GuiRunnable(cb); + m_curAnalyzeCommand = command; + sendCommand(command.getCommand() + "\n", callback); + } else ShowError.msg(this, "Unknown program!"); + } + // + // unknown command + // + else { + System.out.println("Unknown command: '" + cmd + "'."); + } + } + + // ------------------------------------------------------------ + /** + * Return true if keyboard shortcuts should be enabled. This should be the case unless the user is + * currently typing in the text area. + */ + public boolean shortcutsEnabled() { + return !m_comment.m_textPane.isFocusOwner(); + } + + // ------------------------------------------------------------ + private void cmdShutdown() { + if (gameChanged() && !askSaveGame()) return; + + System.out.println("Shutting down..."); + + if (m_white_process != null) { + System.out.println("Stopping [" + m_white_name + " " + m_white_version + "] process..."); + m_white_process.destroy(); + } + System.exit(0); + } + + private void cmdNewProgram() { + Program program = new Program(); + new EditProgramDialog(this, program, "Add New Program", true); + + if (program.m_name == null) // user canceled + return; + + // add the program to the list of programs + m_programs.add(program); + Program.save(m_programs); + } + + private void cmdEditProgram() { + if (m_programs.isEmpty()) { + ShowError.msg(this, "No programs, add a program first."); + return; + } + + ChooseProgramDialog dialog = + new ChooseProgramDialog(this, "Choose program to edit", m_programs); + dialog.setVisible(true); + Program program = dialog.getProgram(); + dialog.dispose(); + + if (program == null) return; + + new EditProgramDialog(this, program, "Edit Program", false); + + Program.save(m_programs); + } + + private void cmdDeleteProgram() { + if (m_programs.isEmpty()) { + ShowError.msg(this, "No programs, add a program first."); + return; + } + + ChooseProgramDialog dialog = + new ChooseProgramDialog(this, "Choose program to delete", m_programs); + dialog.setVisible(true); + Program program = dialog.getProgram(); + dialog.dispose(); + + if (program == null) return; + + if (!m_programs.remove(program)) + System.out.println("cmdDeleteProgram: program was not in list!"); + + Program.save(m_programs); + } + + private void cmdConnectLocalProgram() { + ChooseProgramDialog dialog = + new ChooseProgramDialog(this, "Choose program to connect", m_programs); + dialog.setVisible(true); + Program program = dialog.getProgram(); + dialog.dispose(); + + if (program == null) // user aborted + return; + + cmdConnectLocalProgram(program); + } + + /** + * @note NOT CURRENTLY USED! + */ + private void cmdConnectRemoteProgram() { + int port = 20000; + String hostname = "localhost"; + + String remote = m_preferences.get("remote-host-name"); + String name = RemoteProgramDialog.show(this, remote); + if (name == null) // user aborted + return; + + hostname = name; + System.out.print("Connecting to HTP program at [" + hostname + "] on port " + port + "..."); + System.out.flush(); + + try { + m_white_socket = new Socket(hostname, port); + } catch (UnknownHostException e) { + ShowError.msg(this, "Unknown host: '" + e.getMessage() + "'"); + System.out.println("\nconnection attempt aborted."); + return; + } catch (IOException e) { + ShowError.msg(this, "Error creating socket: '" + e.getMessage() + "'"); + System.out.println("\nconnection attempt aborted."); + return; + } + System.out.println("connected."); + + InputStream in; + OutputStream out; + try { + in = m_white_socket.getInputStream(); + out = m_white_socket.getOutputStream(); + } catch (IOException e) { + ShowError.msg(this, "Error obtaining socket stream: " + e.getMessage()); + m_white = null; + return; + } + m_preferences.put("remote-host-name", hostname); + connectProgram(in, out); + } + + // ------------------------------------------------------------ + + private void cmdConnectLocalProgram(Program program) { + Runtime runtime = Runtime.getRuntime(); + + String cmd = program.m_command; + System.out.println("Executing '" + program.m_name + "':"); + System.out.println("Command = '" + cmd + "'"); + System.out.println("Working directory = '" + program.m_working + "'"); + + File working = null; + if (!program.m_working.trim().equals("")) { + ShowError.msg( + this, "Working directory not implemented! " + "Running with no working directory."); + + // working = new File(program.m_working); + // if (!working.isDirectory()) + // { + // ShowError.msg(this, "Invalid working directory: '" + // + working.getName() + "'"); + // } + } + + try { + // Create command array with StringUtil::splitArguments + // because Runtime.exec(String) uses a default StringTokenizer + // which does not respect ". + String[] cmdArray = StringUtils.splitArguments(cmd); + // Make file name absolute, if working directory is not current + // directory. With Java 1.5, it seems that Runtime.exec succeeds + // if the relative path is valid from the current, but not from + // the given working directory, but the process is not usable + // (reading from its input stream immediately returns + // end-of-stream) + if (cmdArray.length > 0) { + File file = new File(cmdArray[0]); + // Only replace if executable is a path to a file, not + // an executable in the exec-path + if (file.exists()) cmdArray[0] = file.getAbsolutePath(); + } + + m_white_process = runtime.exec(cmdArray); + // m_white_process = runtime.exec(cmdArray, null, workingDirectory); + // m_white_process = runtime.exec(cmd); + } catch (Throwable e) { + ShowError.msg(this, "Error starting " + program.m_name + ": '" + e.getMessage() + "'"); + return; + } + + m_program = program; + m_preferences.put("is-program-attached", true); + m_preferences.put("attached-program", program.m_name); + + Process proc = m_white_process; + + /////////////////////////////// + /// FIXME: DEBUGING!!! REMOVE! + Thread blah = new Thread(new StreamCopy(false, proc.getErrorStream(), System.out, false)); + blah.start(); + /////////////////////////////// + + connectProgram(proc.getInputStream(), proc.getOutputStream()); + } + + private void createAnalyzeDialog() { + m_analyzeDialog = new AnalyzeDialog(this, this, m_analyzeCommands, m_messageDialogs); + m_analyzeDialog.addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + m_analyzeDialog.setVisible(false); + m_menubar.setAnalyzeVisible(false); + } + }); + m_analyzeDialog.setBoardSize(m_guiboard.getBoardSize().width); + m_analyzeDialog.setSelectedColor(m_tomove); + } + + public void actionDisposeAnalyzeDialog() { + if (m_analyzeDialog != null) { + m_analyzeDialog.dispose(); + m_analyzeDialog = null; + m_menubar.setAnalyzeVisible(false); + } + } + + private void connectProgram(InputStream in, OutputStream out) { + m_shell = new HtpShell(this, this); + m_shell.addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent winEvt) { + m_menubar.setShellVisible(false); + } + }); + m_white = new HtpController(in, out, m_shell, this); + + // get name and version information; block until + // version is returned. + + acquireSemaphore(); + htpName(); + htpVersion(); // releases semaphore when finished. + acquireSemaphore(); + releaseSemaphore(); + + m_shell.setTitle("HexGui: [" + m_white_name + " " + m_white_version + "] Shell"); + + // get list of accepted commands; block until + // this is completed. + acquireSemaphore(); + htpAnalyzeCommands(); // releases semaphore when finished + acquireSemaphore(); + releaseSemaphore(); + + createAnalyzeDialog(); + + m_toolbar.setProgramConnected(true); + m_menubar.setProgramConnected(true); + + m_shell.setVisible(m_preferences.getBoolean("shell-show-on-connect")); + m_analyzeDialog.setVisible(m_preferences.getBoolean("analyze-show-on-connect")); + + htpBoardsize(m_guiboard.getBoardSize()); + + // Replay all moves up to the current node. + replayUpToNode(m_current); + htpShowboard(); + } + + // Replay all moves up to the given node. Do this without changing + // the current node. + private void replayUpToNode(Node node) { + Vector path = new Vector(); + while (node != null) { + path.add(node); + node = node.getParent(); + } + m_guiboard.clearAll(); + htpClearBoard(); + for (int i = path.size() - 1; i >= 0; i--) { + node = path.elementAt(i); + if (node.hasMove()) { + Move move = node.getMove(); + guiPlay(move); + htpPlay(move); + } + if (node.hasSetup()) { + playSetup(node); + } + } + } + + /** + * Run HTP commands to set up the current board position from scratch. This may be necessary when + * a setup move removes a piece, or changes the color of an existing piece, or when swap-pieces is + * played, since there is no valid HTP command to do so. + */ + private void htpSetUpCurrentBoard() { + htpClearBoard(); + Dimension size = m_guiboard.getBoardSize(); + for (int y = 0; y < size.height; y++) { + for (int x = 0; x < size.width; x++) { + HexPoint point = HexPoint.get(x, y); + HexColor c = m_guiboard.getColor(point); + if (c == HexColor.BLACK || c == HexColor.WHITE) { + htpPlay(new Move(point, c)); + } + } + } + } + + private void cmdDisconnectProgram() { + if (m_white == null) return; + + htpQuit(); + try { + if (m_white_process != null) { + m_white_process.waitFor(); + m_white_process = null; + } + if (m_white_socket != null) { + m_white_socket.close(); + m_white_socket = null; + } + m_white = null; + m_shell.dispose(); + m_shell = null; + actionDisposeAnalyzeDialog(); + m_program = null; + m_menubar.setProgramConnected(false); + m_toolbar.setProgramConnected(false); + m_preferences.put("is-program-attached", false); + } catch (Throwable e) { + ShowError.msg(this, "Error: " + e.getMessage()); + } + } + + private void cmdReconnectProgram() { + Program prog = m_program; + cmdDisconnectProgram(); + cmdConnectLocalProgram(prog); + } + + // ------------------------------------------------------------ + + private void cmdNewGame() { + if (gameChanged() && !askSaveGame()) return; + + String size = m_menubar.getSelectedBoardSize(); + Dimension dim = new Dimension(-1, -1); + if (size.equals("Other...")) { + size = BoardSizeDialog.show(this, m_guiboard.getBoardSize()); + if (size == null) return; + } + + try { + StringTokenizer st = new StringTokenizer(size); + int w = Integer.parseInt(st.nextToken()); + st.nextToken(); + int h = Integer.parseInt(st.nextToken()); + dim.setSize(w, h); + } catch (Throwable t) { + ShowError.msg(this, "Size should be in format 'w x h'."); + return; + } + + if (dim.width < 1 || dim.height < 1) { + ShowError.msg(this, "Invalid board size."); + } else { + m_tomove = HexColor.BLACK; + m_toolbar.setToMove(m_tomove.toString()); + + m_root = new Node(); + m_current = m_root; + m_gameinfo = new GameInfo(); + m_gameinfo.setBoardSize(dim); + stopClock(HexColor.BLACK); + stopClock(HexColor.WHITE); + m_blackClock.setElapsed(0); + m_whiteClock.setElapsed(0); + setComment(m_current); + + m_file = null; + resetGameChanged(); + setFrameTitle(); + + m_guiboard.initSize(dim.width, dim.height); + m_guiboard.repaint(); + + m_preferences.put("gui-board-width", dim.width); + m_preferences.put("gui-board-height", dim.height); + + m_toolbar.updateButtonStates(m_current, this); + m_menubar.updateMenuStates(this); + + htpBoardsize(m_guiboard.getBoardSize()); + htpShowboard(); + + setCursorType(); + } + } + + private boolean cmdSaveGame() { + if (m_file == null) m_file = showSaveAsDialog(); + + if (m_file != null) { + System.out.println("Saving to file: " + m_file.getName()); + if (save(m_file)) { + resetGameChanged(); + setFrameTitle(); + m_preferences.put("path-save-game", m_file.getPath()); + return true; + } + } + return false; + } + + private boolean cmdSaveGameAs() { + File file = showSaveAsDialog(); + if (file == null) return false; + + m_file = file; + return cmdSaveGame(); + } + + private void cmdSavePositionAs() { + File file = showSaveAsDialog(); + if (file != null) savePosition(file); + } + + private void cmdLoadGame() { + if (gameChanged() && !askSaveGame()) return; + File file = showOpenDialog(); + if (file != null) loadGame(file); + } + + private void cmdPrintPreview() { + JFrame frame = new JFrame(); + // frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + Container con = frame.getContentPane(); + + PrintPreview pp = new PrintPreview(m_guiboard); + con.add(pp, BorderLayout.CENTER); + + frame.pack(); + frame.setVisible(true); + frame.toFront(); + } + + private void cmdPrint() { + Print.run(this, m_guiboard); + } + + private void cmdAbout() { + m_about.setVisible(true); + } + + // ------------------------------------------------------------ + + private void cmdGuiToolbarVisible() { + boolean visible = m_menubar.getToolbarVisible(); + m_toolbar.setVisible(visible); + } + + private void cmdGuiShellVisible() { + if (m_shell == null) return; + boolean visible = m_menubar.getShellVisible(); + m_shell.setVisible(visible); + } + + private void cmdGuiAnalyzeVisible() { + if (m_analyzeDialog == null) return; + boolean visible = m_menubar.getAnalyzeVisible(); + m_analyzeDialog.setVisible(visible); + } + + private void cmdGuiBoardDrawType() { + String type = m_menubar.getCurrentBoardDrawType(); + System.out.println(type); + m_guiboard.setDrawType(type); + m_guiboard.repaint(); + } + + private void cmdGuiBoardOrientation() { + String type = m_menubar.getCurrentBoardOrientation(); + System.out.println(type); + m_guiboard.setOrientation(type); + m_guiboard.repaint(); + } + + private void cmdClearMarks() { + m_guiboard.clearMarks(); + m_guiboard.repaint(); + } + + private void cmdShowPreferences() { + new PreferencesDialog(this, m_preferences); + } + + /** + * Toggle the player to move, by explicit user request. This also updates the PL property in the + * current node. + */ + private void cmdToggleToMove() { + this.toggleToMove(); + m_current.setPlayerToMove(m_tomove); + } + + /** Toggle the player to move, without setting the PL property */ + private void toggleToMove() { + m_tomove = m_tomove.otherColor(); + m_toolbar.setToMove(m_tomove.toString()); + m_menubar.setToMove(m_tomove.toString()); + setCursorType(); + } + + /** + * Set the player to move, by explicit user request. This also updates the PL property in the + * current node. + */ + private void cmdSetToMove() { + this.setToMove(); + m_current.setPlayerToMove(m_tomove); + } + + /** Set the player to move, without setting the PL property */ + private void setToMove() { + m_tomove = HexColor.get(m_menubar.getToMove()); + m_toolbar.setToMove(m_tomove.toString()); + setCursorType(); + } + + private void cmdSetupBlack() { + setCursorType(); + } + + private void cmdSetupWhite() { + setCursorType(); + } + + // Update the cursor according to the current move type. + public void setCursorType() { + String clickContext = m_toolbar.getClickContext(); + if (clickContext == "black") { + m_guiboard.setCursorType("black-setup"); + } else if (clickContext == "white") { + m_guiboard.setCursorType("white-setup"); + } else { + if (m_tomove == HexColor.BLACK) { + m_guiboard.setCursorType("black"); + } else { + m_guiboard.setCursorType("white"); + } + } + } + + // ------------------------------------------------------------ + + public void actionClearAnalyzeCommand() {} + + public void actionSetAnalyzeCommand(AnalyzeCommand command) { + actionSetAnalyzeCommand(command, false, true, true, false); + } + + public void actionSetAnalyzeCommand( + AnalyzeCommand command, + boolean autoRun, + boolean clearBoard, + boolean oneRunOnly, + boolean reuseTextWindow) { + AnalyzeType type = command.getType(); + if (command.needsPointArg()) { + Vector selected = getSelectedCells(); + if (selected.size() < 1) { + m_statusbar.setMessage("Please select a cell before " + "running."); + return; + } + command.setPointArg(selected.get(0)); + } + if (command.needsPointListArg()) { + Vector selected = getSelectedCells(); + if (type == AnalyzeType.VC && selected.size() != 2) { + m_statusbar.setMessage("Please select a pair of cells before " + "running."); + return; + } + PointList blah = new PointList(selected); + command.setPointListArg(blah); + } + String cmd = command.replaceWildCards(m_tomove); + String cleaned = StringUtils.cleanWhiteSpace(cmd.trim()); + String args[] = cleaned.split(" "); + String c = args[0]; + m_curAnalyzeCommand = command; + + Runnable cb = null; + switch (type) { + case GROUP: + cb = + new Runnable() { + public void run() { + cbGroupGet(); + } + }; + break; + case GFX: + cb = + new Runnable() { + public void run() { + cbGfx(); + } + }; + break; + case INFERIOR: + cb = + new Runnable() { + public void run() { + cbShowInferiorCells(); + } + }; + break; + case MOVE: + cb = + new Runnable() { + public void run() { + cbGenMove(); + } + }; + break; + case PLIST: + cb = + new Runnable() { + public void run() { + cbDisplayPointList(); + } + }; + break; + case PSPAIRS: + cb = + new Runnable() { + public void run() { + cbDisplayPointText(); + } + }; + break; + case PARAM: + cb = + new Runnable() { + public void run() { + cbEditParameters(); + } + }; + break; + case VC: + cb = + new Runnable() { + public void run() { + cbVCs(); + } + }; + break; + case STRING: + cb = + new Runnable() { + public void run() { + cbString(); + } + }; + break; + case VAR: + cb = + new Runnable() { + public void run() { + cbVar(); + } + }; + break; + } + // if (c.equals("dfpn-get-bounds")) + // cb = new Runnable() { public void run() { cbDfpnDisplayBounds();} }; + // else if (c.equals("book-scores")) + // cb = new Runnable() { public void run() { cbDisplayBookScores(); } }; + // else if (c.equals("eval-resist")) + // cb = new Runnable() { public void run() { cbEvalResist(); } }; + Runnable callback = null; + if (cb != null) callback = new GuiRunnable(cb); + sendCommand(cmd + "\n", callback); + } + + /** + * HtpShell Callback. By the name of the command it choose the proper callback function. Arguments + * are passed as given. + */ + public void commandEntered(String cmd) { + sendCommand(cmd, null); + } + + // ---------------------------------------------------------------------- + + private boolean commandNeedsToLockGUI(String cmd) { + if ((cmd.length() > 7 && cmd.substring(0, 7).equals("genmove")) + || (cmd.length() > 15 && cmd.substring(0, 15).equals("dfs-solve-state")) + || (cmd.length() > 16 && cmd.substring(0, 16).equals("dfpn-solve-state")) + || (cmd.length() > 23 && cmd.substring(0, 23).equals("dfs-solver-find-winning")) + || (cmd.length() > 24 && cmd.substring(0, 24).equals("dfpn-solver-find-winning"))) + return true; + return false; + } + + private void lockGUI() { + m_locked = true; + m_toolbar.lockToolbar(); + } + + private void unlockGUI() { + m_toolbar.unlockToolbar(m_current, this); + m_locked = false; + } + + /** A (command, callback) pair. */ + private class HtpCommand { + public HtpCommand() {} + + public HtpCommand(String cmd, Runnable callback) { + this.str = cmd; + this.callback = callback; + } + + public String str; + public Runnable callback; + } + + /** Waits for commands to be added to the queue, then processes each in turn. */ + private class CommandHandler implements Runnable { + + public CommandHandler(Component parent, ArrayBlockingQueue queue) { + m_parent = parent; + m_queue = queue; + } + + public void run() { + while (true) { + HtpCommand cmd = null; + try { + // block until queue contains an element + cmd = m_queue.take(); + } catch (InterruptedException e) { + System.out.println("INTERRUPTED! HUH?"); + } + + if (m_white != null && m_white.connected()) { + if (commandNeedsToLockGUI(cmd.str)) lockGUI(); + + try { + m_white.sendCommand(cmd.str); + if (cmd.callback != null) { + cmd.callback.run(); + } + } catch (HtpError e) { + System.out.println("Caught error '" + e.getMessage() + "'"); + ShowError.msg(m_parent, e.getMessage()); + } + + if (commandNeedsToLockGUI(cmd.str)) unlockGUI(); + } else { + System.out.println("Not sending to disconnected: '" + cmd.str.trim() + "'"); + } + } + } + + Component m_parent; + ArrayBlockingQueue m_queue; + } + + private void sendCommand(String cmd, Runnable callback) { + if (m_white == null) return; + + try { + System.out.println("sendCommand: '" + cmd.trim() + "'"); + m_htp_queue.put(new HtpCommand(cmd, callback)); + } catch (InterruptedException e) { + System.out.println("Interrupted while adding!"); + } + } + + // FIXME: add callback? + private void htpQuit() { + sendCommand("quit\n", null); + } + + private void htpName() { + Runnable cb = + new Runnable() { + public void run() { + cbName(); + } + }; + sendCommand("name\n", cb); + } + + private void htpVersion() { + Runnable cb = + new Runnable() { + public void run() { + cbVersion(); + } + }; + sendCommand("version\n", cb); + } + + private void htpAnalyzeCommands() { + Runnable cb = + new Runnable() { + public void run() { + cbAnalyzeCommands(); + } + }; + sendCommand("hexgui-analyze_commands\n", cb); + } + + private void htpClearBoard() { + sendCommand("clear_board\n", null); + } + + private void htpShowboard() { + sendCommand("showboard\n", null); + } + + /** + * Play a move on the attached HTP backend. This only works if move is a legal move of color black + * or white. There is no HTP command for setup moves that remove a piece, or that change the color + * of an already existing piece, and swap, pass, resign, and forfeit moves are possibly not + * implemented in HTP, or may not be undoable correctly. If the move is swap-pieces, just update + * HTP to the current board position; so gui should always be updated before calling this. + */ + private void htpPlay(Move move) { + if (move.getPoint() == HexPoint.RESIGN + || move.getPoint() == HexPoint.FORFEIT + || move.getPoint() == HexPoint.SWAP_SIDES + || move.getPoint() == HexPoint.PASS) { + return; + } + if (move.getPoint() == HexPoint.SWAP_PIECES) { + htpSetUpCurrentBoard(); + return; + } + sendCommand( + "play " + move.getColor().toString() + " " + move.getPoint().toString() + "\n", null); + } + + /** GUI must already be updated prior to calling this. */ + private void htpUndo(Move move) { + if (move.getPoint() == HexPoint.RESIGN + || move.getPoint() == HexPoint.FORFEIT + || move.getPoint() == HexPoint.SWAP_SIDES + || move.getPoint() == HexPoint.PASS) { + return; + } + sendCommand("undo\n", null); + } + + private void htpGenMove(HexColor color) { + if (!checkBoardSizeSupported()) return; + m_statusbar.setMessage(format("{0} is thinking...", m_white_name)); + Runnable callback = + new GuiRunnable( + new Runnable() { + public void run() { + cbGenMove(); + } + }); + sendCommand("genmove " + color.toString() + "\n", callback); + } + + private void htpBoardsize(Dimension size) { + Runnable callback = + new Runnable() { + public void run() { + m_unsupportedBoardSize = !m_white.wasSuccess(); + checkBoardSizeSupported(); + } + }; + sendCommand("boardsize " + size.width + " " + size.height + "\n", callback); + m_statusbar.setMessage("New game"); + } + + // + // Callbacks + // + public void cbName() { + String str = m_white.getResponse(); + // FIXME: handle errors! + m_white_name = str.trim(); + } + + public void cbVersion() { + String str = m_white.getResponse(); + // FIXME: handle errors! + m_white_version = str.trim(); + releaseSemaphore(); + } + + private void cbAnalyzeCommands() { + String programAnalyzeCommands = m_white.getResponse(); + try { + m_analyzeCommands = AnalyzeDefinition.read(programAnalyzeCommands); + } catch (ErrorMessage e) { + ShowError.msg(this, "Could not parse analyze commands!"); + } + releaseSemaphore(); + } + + public void cbGenMove() { + if (!m_white.wasSuccess()) return; + m_guiboard.clearMarks(); + String str = m_white.getResponse(); + HexPoint point = HexPoint.get(str.trim()); + if (point == null) { + System.out.println("Invalid move!!"); + } else { + play(new Move(point, m_tomove)); + } + } + + public void cbDisplayPointList() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector points = StringUtils.parsePointList(str); + m_guiboard.clearMarks(); + for (int i = 0; i < points.size(); i++) { + m_guiboard.setAlphaColor(points.get(i), Color.green); + } + m_guiboard.repaint(); + } + + private void cbDfpnDisplayBounds() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + showDfpnBounds(str); + m_guiboard.repaint(); + } + + public void cbGroupGet() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector points = StringUtils.parsePointList(str); + m_guiboard.clearMarks(); + if (points.size() > 0) { + m_guiboard.setAlphaColor(points.get(0), Color.blue); + for (int i = 1; i < points.size(); i++) { + m_guiboard.setAlphaColor(points.get(i), Color.green); + } + } + m_guiboard.repaint(); + } + + public void cbGfx() { + if (!m_white.wasSuccess()) return; + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + + String fx = m_white.getResponse(); + int inf = fx.indexOf("INFLUENCE"); + if (inf < 0) return; + boolean hasText = false; + int text = fx.indexOf("TEXT"); + if (text < 0) text = fx.length(); + else { + hasText = true; + } + + Vector> pairs = + StringUtils.parseStringPairList(fx.substring(inf + 10, text)); + for (int i = 0; i < pairs.size(); i++) { + HexPoint point = HexPoint.get(pairs.get(i).first); + String value = pairs.get(i).second; + float v = new Float(value).floatValue(); + m_guiboard.setAlphaColor(point, new Color(0, v, 1 - v), 0.7f); + } + if (hasText) m_statusbar.setMessage(fx.substring(text + 5)); + m_guiboard.repaint(); + } + + public void cbShowInferiorCells() { + if (!m_white.wasSuccess()) return; + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + showInferiorCells(m_white.getResponse()); + m_guiboard.repaint(); + } + + public void cbVCs() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector vcs = StringUtils.parseVCList(str); + new VCDisplayDialog(this, m_guiboard, vcs); + } + + public void cbString() { + if (!m_white.wasSuccess()) return; + String showText = m_white.getResponse(); + String title = m_curAnalyzeCommand.getResultTitle(); + if (showText != null) { + if (showText.indexOf("\n") < 0) { + if (showText.trim().equals("")) showText = "(empty response)"; + m_statusbar.setMessage(format("{0}: {1}", title, showText)); + } else { + HexPoint pointArg = null; + m_showAnalyzeText.show(m_curAnalyzeCommand.getType(), pointArg, title, showText, false); + } + } + } + + public void cbVar() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector points = StringUtils.parsePointList(str, " "); + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + HexColor color = m_tomove; + for (int i = 0; i < points.size(); i++) { + m_guiboard.setColor(points.get(i), color); + m_guiboard.setText(points.get(i), Integer.toString(i + 1)); + color = color.otherColor(); + } + m_guiboard.repaint(); + } + + public void cbDisplayPointText() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector> pairs = StringUtils.parseStringPairList(str); + m_guiboard.clearMarks(); + for (int i = 0; i < pairs.size(); i++) { + HexPoint point = HexPoint.get(pairs.get(i).first); + String value = pairs.get(i).second; + m_guiboard.setText(point, value); + } + m_guiboard.repaint(); + } + + public void cbDisplayBookScores() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector> pairs = StringUtils.parseStringPairList(str); + m_guiboard.clearMarks(); + for (int i = 0; i < pairs.size(); i++) { + HexPoint point = HexPoint.get(pairs.get(i).first); + String value = pairs.get(i).second; + m_guiboard.setText(point, value); + if (i == 0) m_guiboard.setAlphaColor(point, Color.red); + else if (1 <= i && i <= 3) m_guiboard.setAlphaColor(point, Color.green); + } + m_guiboard.repaint(); + } + + public void cbEvalResist() { + if (!m_white.wasSuccess()) return; + String str = m_white.getResponse(); + Vector> pairs = StringUtils.parseStringPairList(str); + String res = ""; + String rew = ""; + String reb = ""; + m_guiboard.clearMarks(); + for (int i = 0; i < pairs.size(); i++) { + if (pairs.get(i).first.equals("res")) { + res = pairs.get(i).second; + } else if (pairs.get(i).first.equals("rew")) { + rew = pairs.get(i).second; + } else if (pairs.get(i).first.equals("reb")) { + reb = pairs.get(i).second; + } else { + HexPoint point = HexPoint.get(pairs.get(i).first); + + String value = pairs.get(i).second; + m_guiboard.setText(point, value); + } + } + m_guiboard.repaint(); + m_statusbar.setMessage("Resistance: " + res + " (" + rew + " - " + reb + ")"); + } + + public void cbEditParameters() { + if (!m_white.wasSuccess()) return; + String response = m_white.getResponse(); + ParameterDialog.editParameters( + m_curAnalyzeCommand.getCommand(), + this, + "Edit Parameters", + response, + m_white, + m_messageDialogs); + } + + public void cbSolveState() { + if (!m_white.wasSuccess()) return; + String response = m_white.getResponse(); + m_statusbar.setMessage(format("Winning: {0}", response)); + } + + // ================================================== + // gfx commands + // ================================================== + public void guifx(String fx) { + System.out.println("gogui-gfx:\n'" + fx + "'"); + + if (fx.length() > 3 && fx.substring(0, 3).equals("uct")) guifx_uct(fx.substring(3)); + else if (fx.length() > 2 && fx.substring(0, 2).equals("ab")) guifx_ab(fx.substring(2)); + else if (fx.length() > 4 && fx.substring(0, 4).equals("dfpn")) guifx_dfpn(fx.substring(4)); + else if (fx.length() > 6 && fx.substring(0, 6).equals("solver")) guifx_solver(fx.substring(6)); + } + + private void guifx_uct(String fx) { + String[] tk = fx.trim().split(" "); + int i = 0; + + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + + /** + * @todo Fix this to parse like guifx_ab() and guifx_solver(). + */ + + ////////////////////////////////////// + // display variation + for (; i < tk.length; ++i) { + String s = tk[i].trim(); + if (s.equals("VAR")) break; + } + if (i == tk.length) return; + ++i; // skip "VAR"; + + Vector var = new Vector(); + Vector col = new Vector(); + for (; i < tk.length; ) { + String s = tk[i].trim(); + if (s.equals("INFLUENCE")) break; + ++i; // skip 'B' and 'W' + + col.add((s.charAt(0) == 'B') ? HexColor.BLACK : HexColor.WHITE); + HexPoint point = HexPoint.get(tk[i++].trim()); + var.add(point); + } + + m_guiboard.setColor(var.get(0), col.get(0)); + m_guiboard.setAlphaColor(var.get(0), Color.cyan); + if (var.size() > 1) { + m_guiboard.setColor(var.get(1), col.get(1)); + m_guiboard.setAlphaColor(var.get(1), Color.blue); + } + + ///////////////////////////////////////// + // display score/search counts + + TreeMap map = new TreeMap(); + + ++i; // skip 'INFLUENCE' + for (; i < tk.length; ) { + String s = tk[i].trim(); + if (s.equals("LABEL")) break; + + HexPoint point = HexPoint.get(tk[i++].trim()); + String score = tk[i++].trim(); + map.put(point, score); + if (score.equals("W")) m_guiboard.setAlphaColor(point, Color.green); + else if (score.equals("L")) m_guiboard.setAlphaColor(point, Color.red); + } + + ++i; // skip "LABEL"; + for (; i < tk.length; ) { + String s = tk[i].trim(); + if (s.equals("TEXT")) break; + + HexPoint point = HexPoint.get(tk[i++].trim()); + + String old = map.get(point); + if (old == null) old = ""; + map.put(point, old + "@" + tk[i++].trim()); + } + + Iterator> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + m_guiboard.setText(e.getKey(), e.getValue()); + } + + m_guiboard.repaint(); + m_statusbar.setMessage(fx.substring(fx.indexOf("TEXT") + 5)); + } + + private void guifx_ab(String fx) { + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + + int var = fx.indexOf("VAR"); + int label = fx.indexOf("LABEL"); + int text = fx.indexOf("TEXT"); + + Vector> vr = StringUtils.parseVariation(fx.substring(var + 3, label)); + if (vr.size() > 0) { + m_guiboard.setColor(vr.get(0).second, vr.get(0).first); + m_guiboard.setAlphaColor(vr.get(0).second, Color.green); + if (vr.size() >= 2) { + m_guiboard.setColor(vr.get(1).second, vr.get(1).first); + m_guiboard.setAlphaColor(vr.get(1).second, Color.red); + } + } + String label_str = fx.substring(label + 5, text).trim(); + Vector> labels = StringUtils.parseStringPairList(label_str); + for (int i = 0; i < labels.size(); ++i) { + HexPoint pt = HexPoint.get(labels.get(i).first); + m_guiboard.setText(pt, labels.get(i).second); + } + m_guiboard.repaint(); + m_statusbar.setMessage(fx.substring(text + 5)); + } + + private void guifx_solver(String fx) { + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + m_statusbar.setProgressVisible(true); + + int var = fx.indexOf("VAR"); + int label = fx.indexOf("LABEL"); + int text = fx.indexOf("TEXT"); + + Vector> vr = StringUtils.parseVariation(fx.substring(var + 3, label)); + for (int i = 0; i < vr.size(); ++i) { + m_guiboard.setColor(vr.get(i).second, vr.get(i).first); + m_guiboard.setText(vr.get(i).second, Integer.toString(i + 1)); + } + + String label_str = fx.substring(label + 5, text).trim(); + showInferiorCells(label_str); + + String prog_str = fx.substring(text + 4).trim(); + String[] levels = prog_str.split(" "); + + double contribution = 1.0; + double progress = 0.0; + for (int i = 0; i < levels.length; ++i) { + String[] nums = levels[i].trim().split("/"); + int cur = Integer.decode(nums[0]).intValue(); + int max = Integer.decode(nums[1]).intValue(); + progress += contribution * cur / max; + contribution *= 1.0 / max; + } + m_guiboard.repaint(); + m_statusbar.setMessage(fx.substring(text + 5)); + m_statusbar.setProgress(progress); + } + + private void guifx_dfpn(String fx) { + m_guiboard.clearMarks(); + m_guiboard.aboutToDirtyStones(); + + int var = fx.indexOf("VAR"); + int label = fx.indexOf("LABEL"); + int text = fx.indexOf("TEXT"); + + Vector> vr = StringUtils.parseVariation(fx.substring(var + 3, label)); + for (int i = 0; i < vr.size(); ++i) { + m_guiboard.setColor(vr.get(i).second, vr.get(i).first); + m_guiboard.setText(vr.get(i).second, Integer.toString(i + 1)); + m_guiboard.setAlphaColor(vr.get(i).second, Color.blue); + } + String label_str = fx.substring(label + 5, text).trim(); + showDfpnBounds(label_str); + + m_guiboard.repaint(); + m_statusbar.setMessage(fx.substring(text + 5)); + } + + private void showDfpnBounds(String str) { + Vector> pairs = StringUtils.parseStringPairList(str); + for (int i = 0; i < pairs.size(); i++) { + HexPoint point = HexPoint.get(pairs.get(i).first); + String value = pairs.get(i).second; + m_guiboard.setText(point, value); + if (value.trim().equals("W")) m_guiboard.setAlphaColor(point, Color.green); + else if (value.trim().equals("L")) m_guiboard.setAlphaColor(point, Color.red); + } + } + + /** Draws the inferior cells to the gui board. */ + private void showInferiorCells(String str) { + Vector> pairs = StringUtils.parseStringPairList(str); + for (int i = 0; i < pairs.size(); i++) { + HexPoint point = HexPoint.get(pairs.get(i).first); + String value = pairs.get(i).second; + + if (value.charAt(0) == 'f') // fill-in + { + assert (3 == value.length()); + if (value.charAt(1) == 'd') // dead + m_guiboard.setAlphaColor(point, Color.cyan); + else if (value.charAt(1) == 'p') // permanently inferior + m_guiboard.setAlphaColor(point, Color.gray); + else // captured + { + assert (value.charAt(1) == 'c'); + m_guiboard.setAlphaColor(point, Color.red); + } + if (value.charAt(2) == 'b') m_guiboard.setColor(point, HexColor.BLACK); + else { + assert (value.charAt(2) == 'w'); + m_guiboard.setColor(point, HexColor.WHITE); + } + } else if (value.charAt(0) == 'i') // ignorable + { + assert (4 <= value.length()); + if (value.charAt(1) == 'v') // vulnerable + m_guiboard.setAlphaColor(point, Color.green); + else if (value.charAt(1) == 'r') // reversible + m_guiboard.setAlphaColor(point, Color.magenta); + else // dominated + { + assert (value.charAt(1) == 'd'); + m_guiboard.setAlphaColor(point, Color.yellow); + } + assert (value.charAt(2) == '[' && value.charAt(value.length() - 1) == ']'); + String pts = value.substring(3, value.length() - 1); + Vector pp = StringUtils.parsePointList(pts, "-"); + for (int j = 0; j < pp.size(); ++j) m_guiboard.addArrow(point, pp.get(j)); + } else // not in consider set + { + assert (value.charAt(0) == 'x'); + m_guiboard.setAlphaColor(point, Color.gray); + } + } + } + + // ------------------------------------------------------------ + + // Remove keyboard focus from all text components, so that + // keyboard shortcuts can work. + private void unFocus() { + KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner(); + } + + /** Callback from GuiBoard. Handle a mouse click. */ + public void panelClicked() { + unFocus(); + } + + /** Callback from GuiBoard. Handle a mouse click. */ + public void fieldClicked(HexPoint point, boolean ctrl, boolean shift) { + // do not modify the board in any way if an htp command is in progress! + if (m_locked) { + m_statusbar.setMessage("Board is locked until HTP command is completed."); + return; + } + + if (m_guiboard.areStonesDirty()) { + m_guiboard.clearMarks(); + } + if (ctrl) { + if (!shift) { + for (int i = 0; i < m_selected_cells.size(); i++) { + HexPoint p = m_selected_cells.get(i); + m_guiboard.setSelected(p, false); + } + m_selected_cells.clear(); + m_guiboard.setSelected(point, true); + m_selected_cells.add(point); + } else { + int found_at = -1; + for (int i = 0; i < m_selected_cells.size() && found_at == -1; i++) { + if (m_selected_cells.get(i) == point) found_at = i; + } + + if (found_at != -1) { + m_guiboard.setSelected(point, false); + m_selected_cells.remove(found_at); + } else { + m_guiboard.setSelected(point, true); + m_selected_cells.add(point); + } + } + + m_guiboard.repaint(); + + } else { + String context = m_toolbar.getClickContext(); + if (context.equals("play")) { + if (m_guiboard.getColor(point) == HexColor.EMPTY) { + humanMove(new Move(point, m_tomove)); + } else if (isSwapAllowed()) { + humanMove(new Move(HexPoint.get("swap-pieces"), m_tomove)); + } + } else if (context.equals("black")) { + if (m_guiboard.getColor(point) == HexColor.BLACK) { + addSetupMove(new Move(point, HexColor.EMPTY)); + } else { + addSetupMove(new Move(point, HexColor.BLACK)); + } + } else if (context.equals("white")) { + if (m_guiboard.getColor(point) == HexColor.WHITE) { + addSetupMove(new Move(point, HexColor.EMPTY)); + } else { + addSetupMove(new Move(point, HexColor.WHITE)); + } + } + } + } + + public void fieldDoubleClicked(HexPoint point, boolean ctrl, boolean shift) { + fieldClicked(point, ctrl, shift); + } + + public Vector getSelectedCells() { + return m_selected_cells; + } + + public HexColor getColorToMove() { + return m_tomove; + } + + public void humanMove(Move move) { + play(move); + htpPlay(move); + htpShowboard(); + if (!m_guiboard.isBoardFull() && m_preferences.getBoolean("auto-respond") && m_program != null) + htpGenMove(m_tomove); + } + + /** + * Update the GUI to reflect the given move. Do this without any changes to the game tree or the + * HTP. + */ + private void guiPlay(Move move) { + if (m_guiboard.isYBoard() && move.getPoint() == HexPoint.SWAP_PIECES) { + m_guiboard.swapColors(); + } else if (move.getPoint() == HexPoint.SWAP_PIECES) { + m_guiboard.swapPieces(); + } else { + m_guiboard.setColor(move.getPoint(), move.getColor()); + } + m_guiboard.clearMarks(); + markLastPlayedStone(); + } + + public boolean isSwapAllowed() { + // Count the number of pieces on the board. + int count = m_guiboard.numberOfPieces(); + // Check whether the game tree allows swapping. + boolean isswap = m_current.isSwap(); + return count == 1 && !isswap; + } + + private void play(Move move) { + // see if variation already exists; if so, do not add a duplicate + int variation = -1; + for (int i = 0; i < m_current.numChildren(); i++) { + Node child = m_current.getChild(i); + if (child.hasMove() && move.equals(child.getMove())) { + variation = i; + break; + } + } + + if (variation != -1) { + // variation already exists + m_current = m_current.getChild(variation); + + } else { + if (move.getPoint() == HexPoint.SWAP_SIDES || move.getPoint() == HexPoint.SWAP_PIECES) { + if (!this.isSwapAllowed()) { + ShowError.msg(this, "Swap move not allowed!"); + return; + } + } else if (move.getPoint() == HexPoint.PASS) { + // for simplicity, passing is always possible (even + // twice in a row!) + } else if (move.getPoint() == HexPoint.RESIGN) { + // for simplicity, resigning is always possible (even + // twice in a row!) + } else if (move.getPoint() == HexPoint.FORFEIT) { + // for simplicity, forfeiting is always possible + // (even twice in a row!) + } else { + if (m_guiboard.getColor(move.getPoint()) != HexColor.EMPTY) { + ShowError.msg(this, "Cell '" + move.getPoint().toString() + "' already occupied."); + return; + } + } + + // add new node + Node node = new Node(move); + m_current.addChild(node); + m_current = node; + } + m_current.markRecent(); + + stopClock(m_tomove); + + if (m_guiboard.isYBoard()) { + toggleToMove(); + } else if (m_current.getMove().getPoint() != HexPoint.SWAP_SIDES + && m_current.getMove().getPoint() != HexPoint.RESIGN + && m_current.getMove().getPoint() != HexPoint.FORFEIT) { + toggleToMove(); + } + startClock(m_tomove); + + guiPlay(move); + m_toolbar.updateButtonStates(m_current, this); + m_menubar.updateMenuStates(this); + m_statusbar.setMessage( + m_current.getDepth() + " " + move.getColor().toString() + " " + move.getPoint().toString()); + setComment(m_current); + + setFrameTitle(); + + m_guiboard.paintImmediately(); + if (m_current.hasLabel()) { + displayLabels(m_current); + m_guiboard.paintImmediately(); + } + } + + /** + * Add a new empty setup node as a child of the current node. This allows the creation of + * consecutive setup nodes. + */ + private void addSetupNode() { + Node setup = new Node(); + setup.setPlayerToMove(m_tomove); + m_current.addChild(setup); + m_current = setup; + m_current.markRecent(); + refreshGuiForBoardState(); + m_statusbar.setMessage("Added a new setup node"); + } + + private void addSetupMove(Move move) { + // if current node doesn't permit setup to be edited, create a + // new setup node. + if (!m_current.canSetup()) { + Node setup = new Node(); + setup.setPlayerToMove(m_tomove); + m_current.addChild(setup); + m_current = setup; + } + + m_guiboard.clearMarks(); + + // add the setup stone to the set of setup stones + m_current.addSetup(move.getColor(), move.getPoint()); + + m_guiboard.setColor(move.getPoint(), move.getColor()); + m_guiboard.paintImmediately(); + + htpSetUpCurrentBoard(); + htpShowboard(); + + setFrameTitle(); + m_current.markRecent(); + refreshGuiForBoardState(); + + m_statusbar.setMessage( + "Added setup stone (" + + move.getColor().toString() + + ", " + + move.getPoint().toString() + + ")"); + } + + // ---------------------------------------------------------------------- + + private void displayLabels(Node node) { + Vector labels = node.getLabels(); + for (int i = 0; i < labels.size(); ++i) { + String lb = labels.get(i); + String[] strs = lb.split(":"); + HexPoint p = HexPoint.get(strs[0].trim()); + m_guiboard.setText(p, strs[1].trim()); + } + } + + // Play the setup moves of the given node in the Gui, not HTP. + private void guiPlaySetup(Node node) { + Vector black = node.getSetup(HexColor.BLACK); + Vector white = node.getSetup(HexColor.WHITE); + Vector empty = node.getSetup(HexColor.EMPTY); + for (int j = 0; j < black.size(); j++) { + HexPoint point = black.get(j); + m_guiboard.setColor(point, HexColor.BLACK); + } + for (int j = 0; j < white.size(); j++) { + HexPoint point = white.get(j); + m_guiboard.setColor(point, HexColor.WHITE); + } + for (int j = 0; j < empty.size(); j++) { + HexPoint point = empty.get(j); + m_guiboard.setColor(point, HexColor.EMPTY); + } + } + + private void playSetup(Node node) { + guiPlaySetup(node); + htpSetUpCurrentBoard(); + } + + // Undo the setup moves of the given node. Since the setup moves + // don't contain enough information to know the previous state + // (they can involve deleting pieces or recoloring pieces), we do + // this by replaying all moves up to the node's parent. + private void undoSetup(Node node) { + replayUpToNode(node.getParent()); + } + + // Play the given node in the Gui, not HTP. + private void guiPlayNode(Node node) { + node.markRecent(); + if (node.hasMove()) { + Move move = node.getMove(); + m_guiboard.setColor(move.getPoint(), move.getColor()); + if (move.getPoint() == HexPoint.SWAP_PIECES) { + m_guiboard.swapPieces(); + } + m_statusbar.setMessage( + node.getDepth() + " " + move.getColor().toString() + " " + move.getPoint().toString()); + } + if (node.hasSetup()) { + guiPlaySetup(node); + } + } + + // Play the given node in the Gui and HTP. For HTP, setup moves + // and swap-pieces moves are special cases, as they aren't in + // general supported by HTP. So we rebuild the board from scratch + // in these cases. + private void playNode(Node node) { + guiPlayNode(node); + if (node.hasMove()) { + Move move = node.getMove(); + if (move.getPoint() == HexPoint.SWAP_PIECES) { + htpSetUpCurrentBoard(); + } else { + htpPlay(move); + } + } + if (node.hasSetup()) { + htpSetUpCurrentBoard(); + } + } + + private void undoNode(Node node) { + if (node.hasMove()) { + Move move = node.getMove(); + if (m_guiboard.isYBoard() && move.getPoint() == HexPoint.SWAP_PIECES) { + m_guiboard.swapColors(); + } else if (move.getPoint() == HexPoint.SWAP_PIECES) { + m_guiboard.swapPieces(); + } else { + m_guiboard.setColor(move.getPoint(), HexColor.EMPTY); + } + if (move.getPoint() == HexPoint.SWAP_PIECES) { + replayUpToNode(node.getParent()); + } else { + htpUndo(move); + } + } + if (node.hasSetup()) { + undoSetup(node); + m_statusbar.setMessage("Undo setup stones"); + } + } + + private void refreshGuiForBoardState() { + markLastPlayedStone(); + m_guiboard.repaint(); + m_toolbar.updateButtonStates(m_current, this); + m_menubar.updateMenuStates(this); + setFrameTitle(); + + setComment(m_current); + if (m_current.hasMove()) { + Move move = m_current.getMove(); + m_statusbar.setMessage( + m_current.getDepth() + + " " + + move.getColor().toString() + + " " + + move.getPoint().toString()); + } else if (m_current.hasSetup()) { + m_statusbar.setMessage(m_current.getDepth() + " " + "setup"); + } else { + m_statusbar.setMessage(m_current.getDepth() + ""); + } + if (m_current.hasLabel()) displayLabels(m_current); + if (m_current.hasCount()) System.out.println("Count: " + m_current.getCount()); + determineColorToMove(); + htpShowboard(); + } + + /** Unselect the setup buttons. Most other actions trigger this. */ + private void end_setup() { + m_toolbar.deselectSetup(); + } + + /** Forward by n moves, or to the very end if n == -1 */ + private void forward(int n) { + m_guiboard.clearMarks(); + + for (int i = 0; i < n || n == -1; ++i) { + Node child = m_current.getRecentChild(); + if (child == null) break; + + playNode(child); + m_current = child; + } + stopClock(); + refreshGuiForBoardState(); + } + + /** Rewind by n moves, or to the very start if n == -1 */ + private void backward(int n) { + m_guiboard.clearMarks(); + + for (int i = 0; i < n || n == -1; ++i) { + if (m_current == m_root) break; + + undoNode(m_current); + m_current = m_current.getParent(); + } + stopClock(); + refreshGuiForBoardState(); + } + + private void down() { + if (m_current.getNext() != null) { + m_guiboard.clearMarks(); + undoNode(m_current); + m_current = m_current.getNext(); + playNode(m_current); + + stopClock(); + refreshGuiForBoardState(); + } + } + + private void up() { + if (m_current.getPrev() != null) { + m_guiboard.clearMarks(); + undoNode(m_current); + m_current = m_current.getPrev(); + playNode(m_current); + + stopClock(); + refreshGuiForBoardState(); + } + } + + private void cmdDeleteBranch() { + if (m_current == m_root) { + m_statusbar.setMessage("May not delete root node!"); + System.out.println("May not delete root node!"); + return; + } + + Node to_be_deleted = m_current; + backward(1); + + to_be_deleted.removeSelf(); + m_toolbar.updateButtonStates(m_current, this); + m_menubar.updateMenuStates(this); + setFrameTitle(); + } + + private void cmdMoveBranchTop() { + m_current.makeMain(); + refreshGuiForBoardState(); + } + + private void determineColorToMove() { + // Usually the game tree determines the color to move. + HexColor color = m_current.getPlayerToMove(); + m_tomove = color; + m_toolbar.setToMove(m_tomove.toString()); + setCursorType(); + } + + // ------------------------------------------------------------ + + private void markLastPlayedStone() { + if (m_current == m_root || !m_current.hasMove()) { + m_guiboard.clearSwapPlayed(); + m_guiboard.markLastPlayed(null); + return; + } + + Move move = m_current.getMove(); + + if (move.getPoint() == HexPoint.RESIGN + || move.getPoint() == HexPoint.FORFEIT + || move.getPoint() == HexPoint.PASS) { + m_guiboard.clearSwapPlayed(); + m_guiboard.markLastPlayed(null); + return; + } + + if (move.getPoint() == HexPoint.SWAP_SIDES) { + Node parent = m_current.getParent(); + assert (parent != null); + + m_guiboard.markLastPlayed(null); + m_guiboard.markSwapPlayed(); + } else if (move.getPoint() == HexPoint.SWAP_PIECES) { + Node parent = m_current.getParent(); + assert (parent != null); + + m_guiboard.markLastPlayed(null); + m_guiboard.markSwapPlayed(); + } else { + m_guiboard.markLastPlayed(move.getPoint()); + m_guiboard.clearSwapPlayed(); + } + } + + // Record a snapshot of the current game state. This can later be + // used to check if the game has changed or not. + private void resetGameChanged() { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new SgfWriter(out, m_root, m_gameinfo); + + m_gameSnapshot = out.toString(); + } + + private boolean gameChanged() { + if (m_gameSnapshot == null) { + return false; + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + new SgfWriter(out, m_root, m_gameinfo); + + return !m_gameSnapshot.equals(out.toString()); + } + + private void setFrameTitle() { + String filename = "untitled"; + if (m_file != null) filename = m_file.getName(); + if (gameChanged()) filename = filename + "*"; + String name = "HexGui " + Version.id; + if (m_white != null) name += " - [" + m_white_name + " " + m_white_version + "]"; + setTitle(name + " - " + filename); + } + + /** Returns false if action was aborted. */ + private boolean askSaveGame() { + Object options[] = {"Save", "Discard", "Cancel"}; + int n = + JOptionPane.showOptionDialog( + this, + "Game has changed. Save changes?", + "Save Game?", + JOptionPane.YES_NO_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + if (n == 0) { + if (cmdSaveGame()) return true; + return false; + } else if (n == 1) { + return true; + } + return false; + } + + /** Saves the current game state as a position in the specified sgf file. */ + private boolean savePosition(File file) { + Node root = new Node(); + + GameInfo info = new GameInfo(); + info.setBoardSize(m_guiboard.getBoardSize()); + m_guiboard.storePosition(root); + root.setPlayerToMove(m_tomove); + return save_tree(file, root, info); + } + + /** + * Save game to file. + * + * @return true If successful. + */ + private boolean save(File file) { + return save_tree(file, m_root, m_gameinfo); + } + + private boolean save_tree(File file, Node root, GameInfo gameinfo) { + FileOutputStream out; + try { + out = new FileOutputStream(file); + } catch (FileNotFoundException e) { + ShowError.msg(this, "File not found!"); + return false; + } + + new SgfWriter(out, root, gameinfo); + return true; + } + + /* Load game from file. */ + private SgfReader load(File file) { + FileInputStream in; + try { + in = new FileInputStream(file); + } catch (FileNotFoundException e) { + ShowError.msg(this, "File not found!"); + return null; + } + + SgfReader sgf; + try { + sgf = new SgfReader(in); + } catch (SgfReader.SgfError e) { + ShowError.msg(this, "Error reading SGF file:\n \"" + e.getMessage() + "\""); + return null; + } + return sgf; + } + + // ------------------------------------------------------------ + + /** + * Show save dialog, return File of selected filename. + * + * @return null If aborted. + */ + private File showSaveAsDialog() { + JFileChooser fc = new JFileChooser(m_preferences.get("path-save-game")); + if (m_file != null) fc.setSelectedFile(m_file); + int ret = fc.showSaveDialog(this); + if (ret == JFileChooser.APPROVE_OPTION) return fc.getSelectedFile(); + return null; + } + + /** + * Show open dialog, return File of selected filename. + * + * @return null If aborted. + */ + private File showOpenDialog() { + JFileChooser fc = new JFileChooser(m_preferences.get("path-load-game")); + int ret = fc.showOpenDialog(this); + if (ret == JFileChooser.APPROVE_OPTION) return fc.getSelectedFile(); + return null; + } + + // ------------------------------------------------------------ + + private void acquireSemaphore() { + try { + m_semaphore.acquire(); + } catch (InterruptedException e) { + System.out.println("Acquire interrupted!"); + } + } + + private void releaseSemaphore() { + m_semaphore.release(); + } + + // ------------------------------------------------------------ + + private void stopClock() { + stopClock(HexColor.BLACK); + stopClock(HexColor.WHITE); + } + + private void stopClock(HexColor color) { + if (color == HexColor.BLACK) m_blackClock.stop(); + else m_whiteClock.stop(); + } + + private void startClock() { + startClock(m_tomove); + } + + private void startClock(HexColor color) { + if (color == HexColor.BLACK) m_blackClock.start(); + else m_whiteClock.start(); + } + + private void setComment(Node node) { + String comment = node.getComment(); + m_comment.setText(comment); + } + + public void commentChanged(String string) { + m_current.setComment(string); + } + + private boolean checkBoardSizeSupported() { + if (m_unsupportedBoardSize) { + ShowError.msg(HexGui.this, format("{0} does not support this board size.", m_white_name)); + return false; + } + return true; + } + + private void initialize(File file, String command) { + // attach program from the last run of HexGui + m_program = null; + if (command != null) { + cmdConnectLocalProgram(new Program("", command, "")); + setFrameTitle(); + } + m_programs = Program.load(); + /* + if (m_preferences.getBoolean("is-program-attached")) + { + String name = m_preferences.get("attached-program"); + Program prog = Program.findWithName(name, m_programs); + if (prog != null) + cmdConnectLocalProgram(prog); + } + */ + + if (file != null) loadGame(file); + } + + private void loadGame(File file) { + System.out.println("Loading sgf from file: " + file.getName()); + SgfReader sgf = load(file); + if (sgf != null) { + m_root = sgf.getGameTree(); + m_gameinfo = sgf.getGameInfo(); + m_current = m_root; + + m_guiboard.initSize(m_gameinfo.getBoardSize()); + htpBoardsize(m_guiboard.getBoardSize()); + + // Play the root node, since it may contain setup. + playNode(m_root); + + forward(-1); + + m_file = file; + resetGameChanged(); + setFrameTitle(); + + m_preferences.put("path-load-game", file.getPath()); + end_setup(); + } + } + + private void setIcon() { + ClassLoader loader = ClassLoader.getSystemClassLoader(); + // There are problems on some platforms with transparency (e.g. Linux + // Sun Java 1.5.0). Best solution for now is to take an icon without + // transparency + URL url = loader.getResource("hexgui/images/hexgui-48x48-notrans.png"); + setIconImage(new ImageIcon(url).getImage()); + } + + private AboutDialog m_about; + private GuiPreferences m_preferences; + private GuiBoard m_guiboard; + private GuiToolBar m_toolbar; + private StatusBar m_statusbar; + private GuiMenuBar m_menubar; + private HtpShell m_shell; + private AnalyzeDialog m_analyzeDialog; + private GameInfoPanel m_gameinfopanel; + private Comment m_comment; + private boolean m_locked; + private boolean m_unsupportedBoardSize; + private Node m_root; + private Node m_current; + private GameInfo m_gameinfo; + private HexColor m_tomove; + private Clock m_blackClock; + private Clock m_whiteClock; + private String m_gameSnapshot; + + private ArrayList m_analyzeCommands; + + private final MessageDialogs m_messageDialogs = new MessageDialogs("HexGui"); + + private Vector m_selected_cells; + + private Program m_program; + private Vector m_programs; + + private ShowAnalyzeText m_showAnalyzeText; + + private ArrayBlockingQueue m_htp_queue; + private Semaphore m_semaphore; + private HtpController m_white; + private String m_white_name; + private String m_white_version; + private AnalyzeCommand m_curAnalyzeCommand; + private Process m_white_process; + private Socket m_white_socket; + + private File m_file; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/HtpShell.java b/src/main/java/hexgui/gui/HtpShell.java new file mode 100644 index 0000000..3ddf53f --- /dev/null +++ b/src/main/java/hexgui/gui/HtpShell.java @@ -0,0 +1,112 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import hexgui.htp.HtpController; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; +import javax.swing.text.*; + +/** Non-modal dialog displaying the communication between HexGui and a HTP compatible program. */ +public class HtpShell extends JDialog implements ActionListener, HtpController.IOInterface { + public interface Callback { + void commandEntered(String str); + } + + public HtpShell(JFrame owner, Callback callback) { + super(owner, "HexGui: Shell"); + m_callback = callback; + + m_editor = new JTextPane(); + m_editor.setEditable(false); + m_document = m_editor.getStyledDocument(); + addStylesToDocument(m_document); + + m_scrollpane = new JScrollPane(m_editor); + m_scrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + + Dimension size = owner.getSize(); + getContentPane().add(m_scrollpane, BorderLayout.CENTER); + + setPreferredSize(new Dimension(400, size.height)); + setMinimumSize(new Dimension(400, 200)); + setLocation(size.width, 0); + + m_field = new JTextField(); + m_field.addActionListener(this); + m_field.setActionCommand("command-entered"); + getContentPane().add(m_field, BorderLayout.SOUTH); + + pack(); + } + + public void appendText(String text) { + appendText(text, null); + } + + public void appendText(String text, AttributeSet style) { + try { + m_document.insertString(m_document.getLength(), text, style); + } catch (BadLocationException e) { + System.out.println("Bad location!"); + } + } + + /** HtpController.IOInterface */ + public void sentCommand(String str) { + appendText(str, m_document.getStyle("blue")); + } + + public void receivedResponse(String str) { + appendText(str, m_document.getStyle("bold")); + } + + public void receivedError(String str) { + appendText(str, m_document.getStyle("red")); + } + + protected void addStylesToDocument(StyledDocument doc) { + Style def = StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE); + + Style regular = doc.addStyle("regular", def); + StyleConstants.setFontFamily(def, "Monospaced"); + StyleConstants.setFontSize(def, 12); + + Style s = doc.addStyle("italic", regular); + StyleConstants.setItalic(s, true); + + s = doc.addStyle("bold", regular); + StyleConstants.setBold(s, true); + + s = doc.addStyle("blue", regular); + StyleConstants.setForeground(s, Color.blue); + + s = doc.addStyle("red", regular); + StyleConstants.setForeground(s, Color.RED); + + s = doc.addStyle("gray", regular); + StyleConstants.setForeground(s, Color.gray); + + s = doc.addStyle("yellow", regular); + StyleConstants.setForeground(s, Color.YELLOW); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("command-entered")) { + String text = m_field.getText() + "\n"; + m_callback.commandEntered(text); + m_field.setText(null); + } + } + + JTextPane m_editor; + JTextField m_field; + JScrollPane m_scrollpane; + StyledDocument m_document; + Callback m_callback; +} diff --git a/src/main/java/hexgui/gui/MessageDialogs.java b/src/main/java/hexgui/gui/MessageDialogs.java new file mode 100644 index 0000000..964e53e --- /dev/null +++ b/src/main/java/hexgui/gui/MessageDialogs.java @@ -0,0 +1,390 @@ +// MessageDialogs.java + +package hexgui.gui; + +import static hexgui.gui.GuiUtil.insertLineBreaks; + +import hexgui.util.Platform; +import hexgui.util.PrefUtil; +import hexgui.util.StringUtils; +import java.awt.Component; +import java.util.Set; +import java.util.TreeSet; +import java.util.prefs.Preferences; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; + +/** Simple message dialogs. */ +public final class MessageDialogs { + public MessageDialogs(String applicationName) { + m_applicationName = applicationName; + } + + public void showError(Component frame, String mainMessage, String optionalMessage) { + showError(frame, mainMessage, optionalMessage, true); + } + + public void showError( + Component frame, String mainMessage, String optionalMessage, boolean isCritical) { + int type; + if (isCritical) type = JOptionPane.ERROR_MESSAGE; + else type = JOptionPane.PLAIN_MESSAGE; + Object[] options = {"Close"}; + Object defaultOption = options[0]; + String title = "Error" + " - " + m_applicationName; + show( + null, + frame, + title, + mainMessage, + optionalMessage, + type, + JOptionPane.DEFAULT_OPTION, + options, + defaultOption, + -1); + } + + public void showError(Component frame, String message, Exception e) { + showError(frame, message, e, true); + } + + public void showError(Component frame, String message, Exception e, boolean isCritical) { + showError(frame, message, StringUtils.getErrorMessage(e), isCritical); + } + + public void showInfo( + Component frame, String mainMessage, String optionalMessage, boolean isCritical) { + showInfo(null, frame, mainMessage, optionalMessage, isCritical); + } + + public void showInfo( + String disableKey, + Component frame, + String mainMessage, + String optionalMessage, + boolean isCritical) { + if (checkDisabled(disableKey)) return; + int type; + if (isCritical) type = JOptionPane.INFORMATION_MESSAGE; + else type = JOptionPane.PLAIN_MESSAGE; + Object[] options = {"Close"}; + Object defaultOption = options[0]; + String title = "Information" + " - " + m_applicationName; + show( + disableKey, + frame, + title, + mainMessage, + optionalMessage, + type, + JOptionPane.DEFAULT_OPTION, + options, + defaultOption, + -1); + } + + public int showYesNoCancelQuestion( + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + String nonDestructiveOption) { + return showYesNoCancelQuestion( + null, parent, mainMessage, optionalMessage, destructiveOption, nonDestructiveOption); + } + + /** + * Show a question with two options and cancel. + * + * @return 0 for the destructive option; 1 for the non-destructive option; 2 for cancel + */ + public int showYesNoCancelQuestion( + String disableKey, + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + String nonDestructiveOption) { + if (checkDisabled(disableKey)) return 0; + Object[] options = new Object[3]; + int destructiveIndex; + if (Platform.isMac()) { + options[0] = nonDestructiveOption; + options[1] = "Cancel"; + options[2] = destructiveOption; + destructiveIndex = 2; + } else { + options[0] = nonDestructiveOption; + options[1] = destructiveOption; + options[2] = "Cancel"; + destructiveIndex = -1; + } + Object defaultOption = options[0]; + int type = JOptionPane.QUESTION_MESSAGE; + String title = "Question" + " - " + m_applicationName; + Object value = + show( + disableKey, + parent, + title, + mainMessage, + optionalMessage, + type, + JOptionPane.YES_NO_CANCEL_OPTION, + options, + defaultOption, + destructiveIndex); + int result; + if (value == destructiveOption) result = 0; + else if (value == nonDestructiveOption) result = 1; + else result = 2; + return result; + } + + public void showWarning( + Component parent, String mainMessage, String optionalMessage, boolean isCritical) { + showWarning(null, parent, mainMessage, optionalMessage, isCritical); + } + + public void showWarning( + String disableKey, + Component parent, + String mainMessage, + String optionalMessage, + boolean isCritical) { + if (checkDisabled(disableKey)) return; + int type; + if (isCritical) type = JOptionPane.WARNING_MESSAGE; + else type = JOptionPane.PLAIN_MESSAGE; + Object[] options = {"Close"}; + Object defaultOption = options[0]; + String title = "Warning" + " - " + m_applicationName; + show( + disableKey, + parent, + title, + mainMessage, + optionalMessage, + type, + JOptionPane.DEFAULT_OPTION, + options, + defaultOption, + -1); + } + + public boolean showQuestion( + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + boolean isCritical) { + return showQuestion(null, parent, mainMessage, optionalMessage, destructiveOption, isCritical); + } + + public boolean showQuestion( + String disableKey, + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + boolean isCritical) { + return showQuestion( + disableKey, parent, mainMessage, optionalMessage, destructiveOption, "Cancel", isCritical); + } + + /** + * Show warning message to confirm destructive actions. + * + * @return true, if destructive was chosen; false if cancel was chosen. + */ + public boolean showQuestion( + String disableKey, + Component parent, + String mainMessage, + String optionalMessage, + String affirmativeOption, + String cancelOption, + boolean isCritical) { + if (checkDisabled(disableKey)) return true; + Object[] options = new Object[2]; + if (Platform.isMac()) { + options[0] = cancelOption; + options[1] = affirmativeOption; + } else { + options[0] = affirmativeOption; + options[1] = cancelOption; + } + Object defaultOption = affirmativeOption; + int type; + if (isCritical) + // No reason to show a warning icon for confirmation dialogs + // of frequent actions + type = JOptionPane.QUESTION_MESSAGE; + else type = JOptionPane.PLAIN_MESSAGE; + String title = "Question" + " - " + m_applicationName; + Object result = + show( + disableKey, + parent, + title, + mainMessage, + optionalMessage, + type, + JOptionPane.YES_NO_OPTION, + options, + defaultOption, + -1); + return (result == affirmativeOption); + } + + public boolean showWarningQuestion( + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + boolean isCritical) { + return showWarningQuestion( + null, parent, mainMessage, optionalMessage, destructiveOption, isCritical); + } + + public boolean showWarningQuestion( + String disableKey, + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + boolean isCritical) { + return showWarningQuestion( + disableKey, parent, mainMessage, optionalMessage, destructiveOption, "Cancel", isCritical); + } + + /** + * Show warning message to confirm destructive actions. + * + * @return true, if destructive was chosen; false if cancel was chosen. + */ + public boolean showWarningQuestion( + String disableKey, + Component parent, + String mainMessage, + String optionalMessage, + String destructiveOption, + String nonDestructiveOption, + boolean isCritical) { + if (checkDisabled(disableKey)) return true; + Object[] options = new Object[2]; + if (Platform.isMac()) { + options[0] = nonDestructiveOption; + options[1] = destructiveOption; + } else { + options[0] = destructiveOption; + options[1] = nonDestructiveOption; + } + Object defaultOption = nonDestructiveOption; + int type; + if (isCritical) type = JOptionPane.WARNING_MESSAGE; + else type = JOptionPane.PLAIN_MESSAGE; + String title = "Warning" + " - " + m_applicationName; + Object result = + show( + disableKey, + parent, + title, + mainMessage, + optionalMessage, + type, + JOptionPane.YES_NO_OPTION, + options, + defaultOption, + -1); + return (result == destructiveOption); + } + + private final String m_applicationName; + + private final Set m_disabled = new TreeSet(); + + private static void addFiller(JComponent component) { + Box.Filler filler = GuiUtil.createFiller(); + filler.setAlignmentX(Component.LEFT_ALIGNMENT); + component.add(filler); + } + + private boolean checkDisabled(String disableKey) { + if (disableKey == null) return false; + Preferences prefs = PrefUtil.createNode("net/sf/hexgui/gui/messagedialogs/disabled"); + boolean permanentlyDisabled = prefs.getBoolean(disableKey, false); + if (permanentlyDisabled) return true; + // Make sure this entry exists (right now these settings can only + // be directly edited in the backing store) + prefs.putBoolean(disableKey, permanentlyDisabled); + return m_disabled.contains(disableKey); + } + + private Object show( + String disableKey, + Component parent, + String title, + String mainMessage, + String optionalMessage, + int messageType, + int optionType, + Object[] options, + Object defaultOption, + int destructiveIndex) { + if (optionalMessage == null) optionalMessage = ""; + boolean isMac = Platform.isMac(); + Box box = Box.createVerticalBox(); + + String css = GuiUtil.getMessageCss(); + + JLabel label = + new JLabel( + "" + + css + + "" + + insertLineBreaks(mainMessage) + + "

" + + insertLineBreaks(optionalMessage) + + "

"); + label.setAlignmentX(Component.LEFT_ALIGNMENT); + box.add(label); + + addFiller(box); + addFiller(box); + JCheckBox disableCheckBox = null; + if (disableKey != null) { + if (messageType == JOptionPane.QUESTION_MESSAGE) + disableCheckBox = new JCheckBox("Do not show this message again"); + else if (messageType == JOptionPane.WARNING_MESSAGE) + disableCheckBox = new JCheckBox("Do not show this warning again"); + else disableCheckBox = new JCheckBox("Do not show this message again"); + disableCheckBox.setToolTipText("Do not show this message again"); + disableCheckBox.setAlignmentX(Component.LEFT_ALIGNMENT); + box.add(disableCheckBox); + } + if (isMac) + // Don't show icons on Mac, problem with icon generation in + // Quaqua 3.7.2 + messageType = JOptionPane.PLAIN_MESSAGE; + JOptionPane optionPane = + new JOptionPane(box, messageType, optionType, null, options, defaultOption); + if (destructiveIndex >= 0) { + String key = "Quaqua.OptionPane.destructiveOption"; + optionPane.putClientProperty(key, Integer.valueOf(destructiveIndex)); + } + if (isMac && parent.isVisible()) + // Dialogs don't have titles on the Mac + title = null; + JDialog dialog = optionPane.createDialog(parent, title); + dialog.setVisible(true); + dialog.dispose(); + if (disableKey != null && disableCheckBox.isSelected()) m_disabled.add(disableKey); + return optionPane.getValue(); + } +} diff --git a/src/main/java/hexgui/gui/ParameterDialog.java b/src/main/java/hexgui/gui/ParameterDialog.java new file mode 100644 index 0000000..ce6daf9 --- /dev/null +++ b/src/main/java/hexgui/gui/ParameterDialog.java @@ -0,0 +1,360 @@ +// ParameterDialog.java + +package hexgui.gui; + +import static java.lang.Math.max; +import static javax.swing.JOptionPane.OK_CANCEL_OPTION; +import static javax.swing.JOptionPane.PLAIN_MESSAGE; +import static javax.swing.JOptionPane.UNINITIALIZED_VALUE; +import static javax.swing.JOptionPane.VALUE_PROPERTY; + +import hexgui.htp.AnalyzeUtil; +import hexgui.htp.HtpController; +import hexgui.htp.HtpError; +import hexgui.htp.ParameterType; +import hexgui.util.ObjectUtil; +import hexgui.util.StringUtils; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.text.MessageFormat; +import java.util.ArrayList; +import javax.swing.Box; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JSeparator; +import javax.swing.JTextField; +import javax.swing.SwingConstants; + +/** Dialog for editing parameters in response to an analyze command of type param. */ +public class ParameterDialog { + public static void editParameters( + final String paramCommand, + Frame owner, + String title, + String response, + final HtpController htp, + final MessageDialogs messageDialogs) { + final ArrayList parameters = parseResponse(response); + Component mainComponent = createMainComponent(parameters); + final Object options[] = {"OK", "Cancel"}; + final JOptionPane optionPane = + new JOptionPane(mainComponent, PLAIN_MESSAGE, OK_CANCEL_OPTION, null, options, options[0]); + final JDialog dialog = new JDialog(owner, title, true); + dialog.setContentPane(optionPane); + + optionPane.addPropertyChangeListener( + new PropertyChangeListener() { + public void propertyChange(PropertyChangeEvent event) { + String prop = event.getPropertyName(); + if (dialog.isVisible() + && event.getSource() == optionPane + && prop.equals(VALUE_PROPERTY)) { + Object value = optionPane.getValue(); + if (ObjectUtil.equals(value, UNINITIALIZED_VALUE)) return; + if (ObjectUtil.equals(value, options[0])) { + for (int i = 0; i < parameters.size(); ++i) { + Parameter parameter = parameters.get(i); + if (!parameter.isChanged()) continue; + try { + String command = getNewValueCommand(paramCommand, parameter); + htp.sendCommand(command); + } catch (HtpError e) { + showError( + dialog, messageDialogs, + parameter, e); + optionPane.setValue(UNINITIALIZED_VALUE); + return; + } + } + } + dialog.setVisible(false); + } + } + }); + dialog.pack(); + dialog.setLocationByPlatform(true); + dialog.addWindowListener( + new WindowAdapter() { + public void windowOpened(WindowEvent e) { + // JDK 1.5 docs require to invoke selectInitialValue after + // the window is made visible + optionPane.selectInitialValue(); + } + }); + dialog.setVisible(true); + } + + /** Length of a textfield for editing string parameters. */ + private static final int TEXTFIELD_LEN = 13; + + private static final int MAX_PARAM_PER_COLUMN = 15; + + private abstract static class Parameter { + public Parameter(String key, String value) { + m_key = key; + m_value = value; + m_label = StringUtils.capitalize(key.replace('_', ' ')); + } + + public String getKey() { + return m_key; + } + + public String getLabel() { + return m_label; + } + + public String getValue() { + return m_value; + } + + public abstract String getNewValue(); + + public abstract boolean isChanged(); + + public abstract void createComponents(int gridy, JPanel panel, GridBagLayout gridbag); + + private final String m_key; + + private final String m_label; + + private final String m_value; + } + + private static class BoolParameter extends Parameter { + public BoolParameter(String key, String value) { + super(key, value); + try { + m_initialValue = (Integer.parseInt(value) != 0); + } catch (NumberFormatException e) { + m_initialValue = false; + } + } + + public String getNewValue() { + if (m_checkBox.isSelected()) return "1"; + return "0"; + } + + public boolean isChanged() { + return (m_checkBox.isSelected() != m_initialValue); + } + + public void createComponents(int gridy, JPanel panel, GridBagLayout gridbag) { + m_checkBox = new JCheckBox(getLabel(), m_initialValue); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = gridy; + constraints.gridwidth = GridBagConstraints.REMAINDER; + constraints.weightx = 1.0; + constraints.anchor = GridBagConstraints.WEST; + gridbag.setConstraints(m_checkBox, constraints); + panel.add(m_checkBox); + } + + private boolean m_initialValue; + + private JCheckBox m_checkBox; + } + + private static class ListParameter extends Parameter { + public ListParameter(String type, String key, String value) { + super(key, value); + String[] args = type.split("/"); + assert args[0].equals("list"); + m_items = new String[args.length - 1]; + m_labels = new String[args.length - 1]; + int initialIndex = 0; + int maxLength = 0; + for (int i = 1; i < args.length; ++i) { + String item = args[i]; + if (item.equals(value)) initialIndex = i - 1; + maxLength = max(item.length(), maxLength); + m_items[i - 1] = item; + m_labels[i - 1] = StringUtils.capitalize(item.replace('_', ' ')); + } + m_initialIndex = initialIndex; + } + + public String getNewValue() { + return m_items[m_comboBox.getSelectedIndex()]; + } + + public boolean isChanged() { + return (m_comboBox.getSelectedIndex() != m_initialIndex); + } + + public void createComponents(int gridy, JPanel panel, GridBagLayout gridbag) { + JLabel label = new JLabel(getLabel() + ":"); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = gridy; + constraints.weightx = 1.0; + constraints.ipadx = SMALL_PAD; + constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); + constraints.anchor = GridBagConstraints.EAST; + gridbag.setConstraints(label, constraints); + panel.add(label); + + m_comboBox = new JComboBox(m_labels); + m_comboBox.setSelectedIndex(m_initialIndex); + constraints = new GridBagConstraints(); + constraints.gridx = 1; + constraints.gridy = gridy; + constraints.weightx = 1.0; + constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); + constraints.anchor = GridBagConstraints.WEST; + gridbag.setConstraints(m_comboBox, constraints); + panel.add(m_comboBox); + } + + private final int m_initialIndex; + + private final String[] m_items; + + private final String[] m_labels; + + private JComboBox m_comboBox; + } + + private static class StringParameter extends Parameter { + public StringParameter(String key, String value) { + super(key, value); + } + + public String getNewValue() { + return m_textField.getText().trim(); + } + + public boolean isChanged() { + return !getNewValue().equals(getValue()); + } + + public void createComponents(int gridy, JPanel panel, GridBagLayout gridbag) { + JLabel label = new JLabel(getLabel() + ":"); + GridBagConstraints constraints = new GridBagConstraints(); + constraints.gridx = 0; + constraints.gridy = gridy; + constraints.weightx = 1.0; + constraints.ipadx = SMALL_PAD; + constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); + constraints.anchor = GridBagConstraints.EAST; + gridbag.setConstraints(label, constraints); + panel.add(label); + + m_textField = new JTextField(TEXTFIELD_LEN); + m_textField.setText(getValue()); + constraints = new GridBagConstraints(); + constraints.gridx = 1; + constraints.gridy = gridy; + constraints.weightx = 1.0; + constraints.insets = new Insets(SMALL_PAD, 0, 0, 0); + constraints.anchor = GridBagConstraints.WEST; + gridbag.setConstraints(m_textField, constraints); + panel.add(m_textField); + } + + private JTextField m_textField; + } + + private static ArrayList parseResponse(String response) { + ArrayList parameters = new ArrayList(); + BufferedReader reader = new BufferedReader(new StringReader(response)); + while (true) { + String line = null; + try { + line = reader.readLine(); + } catch (IOException e) { + } + if (line == null) break; + AnalyzeUtil.Result result = AnalyzeUtil.parseParameterLine(line); + if (result == null) continue; + if (result.m_type == ParameterType.BOOL) + parameters.add(new BoolParameter(result.m_key, result.m_value)); + else if (result.m_type == ParameterType.LIST) + parameters.add(new ListParameter(result.m_typeInfo, result.m_key, result.m_value)); + else + // Treat unknown types as string for compatibility with future + // types + parameters.add(new StringParameter(result.m_key, result.m_value)); + } + return parameters; + } + + private static Component createMainComponent(ArrayList parameters) { + int numberParameters = parameters.size(); + Box outerBox = Box.createHorizontalBox(); + int i = 0; + int numberColumns = 0; + JPanel panel = null; + GridBagLayout gridbag = null; + int gridy = 0; + int paramPerColumn = (numberParameters + 1) / (numberParameters / MAX_PARAM_PER_COLUMN + 1); + while (i < numberParameters) { + if (i % paramPerColumn == 0) { + if (panel != null) { + if (numberColumns > 0) { + outerBox.add(createFiller()); + outerBox.add(new JSeparator(SwingConstants.VERTICAL)); + outerBox.add(createFiller()); + } + outerBox.add(panel); + ++numberColumns; + } + gridbag = new GridBagLayout(); + panel = new JPanel(gridbag); + gridy = 0; + } + parameters.get(i).createComponents(gridy, panel, gridbag); + ++gridy; + ++i; + } + if (panel != null) { + if (numberColumns > 0) { + outerBox.add(createFiller()); + outerBox.add(new JSeparator(SwingConstants.VERTICAL)); + outerBox.add(createFiller()); + } + outerBox.add(panel); + } + return outerBox; + } + + private static String getNewValueCommand(String paramCommand, Parameter parameter) { + String key = parameter.getKey(); + String value = parameter.getNewValue(); + return AnalyzeUtil.getParameterCommand(paramCommand, key, value); + } + + private static void showError( + JDialog owner, MessageDialogs messageDialogs, Parameter parameter, HtpError e) { + String mainMessage = MessageFormat.format("Could not change ", parameter.getLabel()); + String optionalMessage = StringUtils.capitalize(e.getMessage()); + messageDialogs.showError(owner, mainMessage, optionalMessage); + } + + private static final int SMALL_PAD = 2; + + private static final int PAD = 5; + + private static final Dimension FILLER_DIMENSION = new Dimension(PAD, PAD); + + public static Box.Filler createFiller() { + return new Box.Filler(FILLER_DIMENSION, FILLER_DIMENSION, FILLER_DIMENSION); + } +} diff --git a/src/main/java/hexgui/gui/PreferencesDialog.java b/src/main/java/hexgui/gui/PreferencesDialog.java new file mode 100644 index 0000000..9fbd960 --- /dev/null +++ b/src/main/java/hexgui/gui/PreferencesDialog.java @@ -0,0 +1,127 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** Dialog for changes user preferences. */ +public final class PreferencesDialog extends JDialog implements ItemListener, ActionListener { + public PreferencesDialog(Frame owner, GuiPreferences preferences) { + super(owner, true); + + m_preferences = preferences; + + JPanel generalPanel = createGeneralPanel(); + JPanel boardPanel = createBoardPanel(); + JPanel drawPanel = createDrawPanel(); + JPanel buttonPane = createButtonPanel(); + + JTabbedPane tabbedPane = new JTabbedPane(); + tabbedPane.addTab("General", null, generalPanel, "General preferences"); + tabbedPane.addTab("Board", null, boardPanel, "Board preferences"); + tabbedPane.addTab("Draw", null, drawPanel, "Drawing preferences"); + + add(tabbedPane, BorderLayout.CENTER); + add(buttonPane, BorderLayout.SOUTH); + pack(); + + setVisible(true); + } + + public void itemStateChanged(ItemEvent e) { + System.out.println("ItemEvent!"); + Object source = e.getItemSelectable(); + } + + public void actionPerformed(ActionEvent e) { + String cmd = e.getActionCommand(); + if (cmd.equals("OK")) { + savePreferences(); + dispose(); + } else if (cmd.equals("Cancel")) { + dispose(); + } + } + + private void savePreferences() { + System.out.println("Saving preferences..."); + + m_preferences.put("shell-show-on-connect", (showShellOnConnect.getSelectedObjects() != null)); + m_preferences.put( + "analyze-show-on-connect", (showAnalyzeOnConnect.getSelectedObjects() != null)); + m_preferences.put("auto-respond", (autoRespond.getSelectedObjects() != null)); + } + + private JPanel createGeneralPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + showShellOnConnect = createCheckBox("Show Shell on Program Connect", "shell-show-on-connect"); + + showAnalyzeOnConnect = + createCheckBox("Show Analyze on Program Connect", "analyze-show-on-connect"); + + autoRespond = createCheckBox("Auto-respond", "auto-respond"); + + panel.add(showShellOnConnect); + panel.add(showAnalyzeOnConnect); + panel.add(autoRespond); + + return panel; + } + + private JPanel createDrawPanel() { + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + // JLabel label; + + // label = new JLabel("Field alpha percentage"); + // SpinnerNumberModel model = + // new SpinnerNumberModel(m_preferences.getDouble(""); + // fieldAlpha = new JSpinner(new ); + + return panel; + } + + private JPanel createBoardPanel() { + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JLabel("Board stuff")); + return panel; + } + + private JPanel createButtonPanel() { + JPanel panel = new JPanel(); + + JButton button = new JButton(" OK "); + button.addActionListener(this); + button.setActionCommand("OK"); + panel.add(button); + + button = new JButton("Cancel"); + button.addActionListener(this); + button.setActionCommand("Cancel"); + panel.add(button); + + return panel; + } + + private JCheckBox createCheckBox(String name, String prefname) { + JCheckBox box = new JCheckBox(name); + box.setSelected(m_preferences.getBoolean(prefname)); + box.addItemListener(this); + return box; + } + + JCheckBox showShellOnConnect, showAnalyzeOnConnect; + JCheckBox autoRespond; + + JSpinner fieldAlpha; + + GuiPreferences m_preferences; +} diff --git a/src/main/java/hexgui/gui/Print.java b/src/main/java/hexgui/gui/Print.java new file mode 100644 index 0000000..d0dd0e9 --- /dev/null +++ b/src/main/java/hexgui/gui/Print.java @@ -0,0 +1,27 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- +package hexgui.gui; + +import java.awt.Component; +import java.awt.print.Printable; +import java.awt.print.PrinterJob; + +/** Print a printable. */ +public final class Print { + public static void run(Component parent, Printable printable) { + PrinterJob job = PrinterJob.getPrinterJob(); + job.setPrintable(printable); + if (!job.printDialog()) return; + try { + job.print(); + } catch (Exception e) { + System.out.println("Printing failed!"); + } + } + + /** Make constructor unavailable; class is for namespace only. */ + private Print() {} +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/PrintPreview.java b/src/main/java/hexgui/gui/PrintPreview.java similarity index 66% rename from src/hexgui/gui/PrintPreview.java rename to src/main/java/hexgui/gui/PrintPreview.java index 3a64fe9..75816d2 100644 --- a/src/hexgui/gui/PrintPreview.java +++ b/src/main/java/hexgui/gui/PrintPreview.java @@ -1,86 +1,87 @@ -//---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- // $Id$ -//---------------------------------------------------------------------------- +// ---------------------------------------------------------------------------- -// +// // The basis of this was stolen from the thread: // http://forum.java.sun.com/thread.jspa?threadID=601884&messageID=4215335 // package hexgui.gui; -import javax.swing.*; import java.awt.*; -import javax.swing.event.*; -import javax.swing.table.*; -import javax.swing.border.*; import java.awt.event.*; -import java.awt.print.*; import java.awt.image.*; - -public class PrintPreview - extends JPanel implements ActionListener -{ +import java.awt.print.*; +import javax.swing.*; +import javax.swing.border.*; +import javax.swing.event.*; +import javax.swing.table.*; + +public class PrintPreview extends JPanel implements ActionListener { static final double INITIAL_SCALE_DIVISOR = 1.0; // scale factor - + Component targetComponent; PageFormat pageFormat = new PageFormat(); double scaleDivisor = INITIAL_SCALE_DIVISOR; BufferedImage pcImage; - + JPanel hold = new JPanel(); PreviewPage prp; - + ButtonGroup pf = new ButtonGroup(); JRadioButton pf1; JRadioButton pf2; JLabel xsl = new JLabel("Scale:", JLabel.LEFT); JButton preview = new JButton("PREVIEW"); JButton print = new JButton("PRINT"); - + JSpinner xsp; SpinnerNumberModel snmx; - + JFrame workFrame; - + Color bgColor = Color.white; - + int pcw, pch; double wh, hw; - - public PrintPreview(Component pc){ + + public PrintPreview(Component pc) { setBackground(bgColor); - + targetComponent = pc; - + // for a JTable, we can't use simple component.paint(g) call // because it doesn't paint table header !! - if (pc instanceof JTable){ - TableModel tm = ((JTable)pc).getModel(); + if (pc instanceof JTable) { + TableModel tm = ((JTable) pc).getModel(); JTable workTable = new JTable(tm); // make pure clone targetComponent = getTableComponent(workTable); } - - pcImage = new BufferedImage(pcw = targetComponent.getWidth(), - pch = targetComponent.getHeight(), BufferedImage.TYPE_INT_ARGB); + + pcImage = + new BufferedImage( + pcw = targetComponent.getWidth(), + pch = targetComponent.getHeight(), + BufferedImage.TYPE_INT_ARGB); Graphics g = pcImage.createGraphics(); targetComponent.paint(g); g.dispose(); - wh = (double)pcw / (double)pch; - hw = (double)pch / (double)pcw; + wh = (double) pcw / (double) pch; + hw = (double) pch / (double) pcw; /* workFrame is used in getTableComponent() method * only for visualizing the table component and its header */ - if (workFrame != null){ // if you don't use table clone here, - workFrame.dispose(); // calling dispose() delete the table - } // from original app window - + if (workFrame != null) { // if you don't use table clone here, + workFrame.dispose(); // calling dispose() delete the table + } // from original app window + pageFormat.setOrientation(PageFormat.LANDSCAPE); prp = new PreviewPage(); - + snmx = new SpinnerNumberModel(INITIAL_SCALE_DIVISOR, 1.0, 10.0, 0.1); xsp = new JSpinner(snmx); - + pf2 = new JRadioButton("Landscape"); pf2.setActionCommand("2"); pf2.setSelected(true); @@ -91,76 +92,77 @@ public PrintPreview(Component pc){ pf.add(pf1); pf2.setBackground(bgColor); pf1.setBackground(bgColor); - + preview.addActionListener(this); print.addActionListener(this); - + prp.setBackground(bgColor); hold.setBorder(BorderFactory.createLineBorder(Color.black, 2)); hold.setBackground(bgColor); hold.setLayout(new GridBagLayout()); - + GridBagConstraints c1 = new GridBagConstraints(); - + c1.insets = new Insets(15, 45, 0, 5); c1 = buildConstraints(c1, 0, 0, 2, 1, 0.0, 0.0); hold.add(pf1, c1); - + c1.insets = new Insets(2, 45, 0, 5); c1 = buildConstraints(c1, 0, 1, 2, 1, 0.0, 0.0); hold.add(pf2, c1); - + c1.insets = new Insets(25, 5, 0, 5); c1 = buildConstraints(c1, 0, 2, 1, 1, 0.0, 0.0); hold.add(xsl, c1); - + c1.insets = new Insets(25, 5, 0, 35); c1 = buildConstraints(c1, 1, 2, 1, 1, 0.0, 0.0); hold.add(xsp, c1); - + c1.insets = new Insets(25, 35, 0, 35); c1 = buildConstraints(c1, 0, 6, 2, 1, 0.0, 0.0); hold.add(preview, c1); - + c1.insets = new Insets(5, 35, 25, 35); c1 = buildConstraints(c1, 0, 7, 2, 1, 0.0, 0.0); hold.add(print, c1); - + add(hold); add(prp); } - - Component getTableComponent(JTable table){ + + Component getTableComponent(JTable table) { Box box = new Box(BoxLayout.Y_AXIS); JTableHeader jth = table.getTableHeader(); - + Dimension dh = jth.getPreferredSize(); Dimension dt = table.getPreferredSize(); Dimension db = new Dimension(dh.width, dh.height + dt.height); box.setPreferredSize(db); - - jth.setBorder(new LineBorder(Color.black, 1){ - public Insets getBorderInsets(Component c){ - return new Insets(2, 2, 2, 2); - } - }); - + + jth.setBorder( + new LineBorder(Color.black, 1) { + public Insets getBorderInsets(Component c) { + return new Insets(2, 2, 2, 2); + } + }); + table.setBorder(new PartialLineBorder(false, true, false, false)); - + box.add(jth); box.add(table); - + // visualize table for getting non-zero sizes(width, height) workFrame = new JFrame(); workFrame.getContentPane().add(box); workFrame.pack(); workFrame.setVisible(true); - + return box; } - - GridBagConstraints buildConstraints(GridBagConstraints gbc, int gx, int gy, - int gw, int gh, double wx, double wy){ + + GridBagConstraints buildConstraints( + GridBagConstraints gbc, int gx, int gy, int gw, int gh, double wx, double wy) { gbc.gridx = gx; gbc.gridy = gy; gbc.gridwidth = gw; @@ -170,34 +172,32 @@ GridBagConstraints buildConstraints(GridBagConstraints gbc, int gx, int gy, gbc.fill = GridBagConstraints.BOTH; return gbc; } - - public class PreviewPage extends JPanel{ + + public class PreviewPage extends JPanel { int x1, y1, l1, h1, x2, y2; Image image; - - public PreviewPage(){ + + public PreviewPage() { setPreferredSize(new Dimension(460, 460)); setBorder(BorderFactory.createLineBorder(Color.black, 2)); } - - public void paintComponent(Graphics g){ + + public void paintComponent(Graphics g) { super.paintComponent(g); - + // PORTRAIT - if(pageFormat.getOrientation() == PageFormat.PORTRAIT){ + if (pageFormat.getOrientation() == PageFormat.PORTRAIT) { g.setColor(Color.black); g.drawRect(60, 10, 340, 440); - x1 = (int)Math.rint(((double)pageFormat.getImageableX() / 72) * 40); - y1 = (int)Math.rint(((double)pageFormat.getImageableY() / 72) * 40); - l1 - = (int)Math.rint(((double)pageFormat.getImageableWidth() / 72) * 40); - h1 - = (int)Math.rint(((double)pageFormat.getImageableHeight() / 72) * 40); + x1 = (int) Math.rint(((double) pageFormat.getImageableX() / 72) * 40); + y1 = (int) Math.rint(((double) pageFormat.getImageableY() / 72) * 40); + l1 = (int) Math.rint(((double) pageFormat.getImageableWidth() / 72) * 40); + h1 = (int) Math.rint(((double) pageFormat.getImageableHeight() / 72) * 40); g.setColor(Color.red); g.drawRect(x1 + 60, y1 + 10, l1, h1); // setScales(); // commenting-out suppresses too frequent paint updates - x2 = (int)Math.rint((double)l1 / scaleDivisor); - y2 = (int)Math.rint(((double)l1 * hw) / scaleDivisor); + x2 = (int) Math.rint((double) l1 / scaleDivisor); + y2 = (int) Math.rint(((double) l1 * hw) / scaleDivisor); image = pcImage.getScaledInstance(x2, y2, Image.SCALE_AREA_AVERAGING); int xspace = (l1 - x2) / 2; @@ -205,62 +205,58 @@ public void paintComponent(Graphics g){ g.drawImage(image, x1 + 60 + xspace, y1 + 10 + yspace, this); } // LANDSCAPE - else{ + else { g.setColor(Color.black); g.drawRect(10, 60, 440, 340); - x1 = (int)Math.rint(((double)pageFormat.getImageableX() / 72) * 40); - y1 = (int)Math.rint(((double)pageFormat.getImageableY() / 72) * 40); - l1 - = (int)Math.rint(((double)pageFormat.getImageableWidth() / 72) * 40); - h1 - = (int)Math.rint(((double)pageFormat.getImageableHeight() / 72) * 40); + x1 = (int) Math.rint(((double) pageFormat.getImageableX() / 72) * 40); + y1 = (int) Math.rint(((double) pageFormat.getImageableY() / 72) * 40); + l1 = (int) Math.rint(((double) pageFormat.getImageableWidth() / 72) * 40); + h1 = (int) Math.rint(((double) pageFormat.getImageableHeight() / 72) * 40); g.setColor(Color.red); g.drawRect(x1 + 10, y1 + 60, l1, h1); // setScales(); - x2 = (int)Math.rint((double)l1 / scaleDivisor); - y2 = (int)Math.rint(((double)l1 * hw) / scaleDivisor); + x2 = (int) Math.rint((double) l1 / scaleDivisor); + y2 = (int) Math.rint(((double) l1 * hw) / scaleDivisor); image = pcImage.getScaledInstance(x2, y2, Image.SCALE_AREA_AVERAGING); int xspace = (l1 - x2) / 2; int yspace = (h1 - y2) / 2; g.drawImage(image, x1 + 10 + xspace, y1 + 60 + yspace, this); } - } + } } - - public void actionPerformed(ActionEvent e){ - if(e.getSource() == preview){ + + public void actionPerformed(ActionEvent e) { + if (e.getSource() == preview) { setProperties(); } - if(e.getSource() == print){ + if (e.getSource() == print) { doPrint(); } } - - public void setProperties(){ - if(pf1.isSelected()){ + + public void setProperties() { + if (pf1.isSelected()) { pageFormat.setOrientation(PageFormat.PORTRAIT); - } - else if(pf2.isSelected()){ + } else if (pf2.isSelected()) { pageFormat.setOrientation(PageFormat.LANDSCAPE); } setScales(); prp.repaint(); } - - public void setScales(){ - try{ - scaleDivisor = ((Double)xsp.getValue()).doubleValue(); - } - catch (NumberFormatException e) { + + public void setScales() { + try { + scaleDivisor = ((Double) xsp.getValue()).doubleValue(); + } catch (NumberFormatException e) { } } - - public void doPrint(){ + + public void doPrint() { PrintThis(); } - - public void PrintThis(){ + + public void PrintThis() { PrinterJob printerJob = PrinterJob.getPrinterJob(); Book book = new Book(); book.append(new PrintPage(), pageFormat); @@ -269,24 +265,23 @@ public void PrintThis(){ if (doPrint) { try { printerJob.print(); - } - catch (PrinterException exception) { + } catch (PrinterException exception) { System.err.println("Printing error: " + exception); } } } - - //public class PrintPage implements Printable{ - class PrintPage implements Printable{ - + + // public class PrintPage implements Printable{ + class PrintPage implements Printable { + public int print(Graphics g, PageFormat format, int pageIndex) { Graphics2D g2D = (Graphics2D) g; - g2D.translate(format.getImageableX (), format.getImageableY ()); -// disableDoubleBuffering(mp); - System.out.println("get i x " + format.getImageableX ()); - System.out.println("get i x " + format.getImageableY ()); - System.out.println("getx: " + format.getImageableWidth() ); - System.out.println("getx: " + format.getImageableHeight() ); + g2D.translate(format.getImageableX(), format.getImageableY()); + // disableDoubleBuffering(mp); + System.out.println("get i x " + format.getImageableX()); + System.out.println("get i x " + format.getImageableY()); + System.out.println("getx: " + format.getImageableWidth()); + System.out.println("getx: " + format.getImageableHeight()); // scale to fill the page double dw = format.getImageableWidth(); double dh = format.getImageableHeight(); @@ -296,59 +291,57 @@ public int print(Graphics g, PageFormat format, int pageIndex) { double yScale = dh / (768 / scaleDivisor); double scale = Math.min(xScale, yScale); System.out.println("" + scale); - g2D.scale( xScale, yScale); + g2D.scale(xScale, yScale); targetComponent.paint(g); -// enableDoubleBuffering(mp); + // enableDoubleBuffering(mp); return Printable.PAGE_EXISTS; } - + public void disableDoubleBuffering(Component c) { RepaintManager currentManager = RepaintManager.currentManager(c); currentManager.setDoubleBufferingEnabled(false); } - + public void enableDoubleBuffering(Component c) { RepaintManager currentManager = RepaintManager.currentManager(c); currentManager.setDoubleBufferingEnabled(true); } } - } - -class PartialLineBorder extends AbstractBorder{ + +class PartialLineBorder extends AbstractBorder { boolean top, left, bottom, right; - - public PartialLineBorder(boolean t, boolean l, boolean b, boolean r){ + + public PartialLineBorder(boolean t, boolean l, boolean b, boolean r) { top = t; left = l; bottom = b; right = r; } - - public boolean isBorderOpaque(){ + + public boolean isBorderOpaque() { return true; } - - public Insets getBorderInsets(Component c){ + + public Insets getBorderInsets(Component c) { return new Insets(2, 2, 2, 2); } - - public void paintBorder - (Component c, Graphics g, int x, int y, int width, int height){ - - Graphics2D g2 = (Graphics2D)g; + + public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + + Graphics2D g2 = (Graphics2D) g; g2.setStroke(new BasicStroke(1.0f)); - - if (top){ + + if (top) { g2.drawLine(x, y, x + width, y); } - if (left){ + if (left) { g2.drawLine(x, y, x, y + height); } - if (bottom){ + if (bottom) { g2.drawLine(x, y + height, x + width, y + height); } - if (right){ + if (right) { g2.drawLine(x + width, y, x + width, y + height); } } diff --git a/src/main/java/hexgui/gui/Program.java b/src/main/java/hexgui/gui/Program.java new file mode 100644 index 0000000..c37f3e2 --- /dev/null +++ b/src/main/java/hexgui/gui/Program.java @@ -0,0 +1,71 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.util.PrefUtil; +import java.util.Vector; +import java.util.prefs.Preferences; + +/** Hex playing Program. */ +public class Program { + public String m_name; + public String m_command; + public String m_working; + + public Program() {} + + public Program(String name, String command, String working) { + m_name = name; + m_command = command; + m_working = working; + } + + public String toString() { + return m_name; + } + + // ------------------------------------------------------------------------ + + public static Vector load() { + Vector programs = new Vector(); + Preferences prefs = PrefUtil.getNode("hexgui/gui/program"); + if (prefs == null) return programs; + int size = prefs.getInt("size", 0); + for (int i = 0; i < size; ++i) { + prefs = PrefUtil.getNode("hexgui/gui/program/" + i); + if (prefs == null) break; + String name = prefs.get("name", ""); + String version = prefs.get("version", ""); + String command = prefs.get("command", ""); + String workingDirectory = prefs.get("working-directory", ""); + programs.add(new Program(name, command, workingDirectory)); + } + return programs; + } + + public static void save(Vector programs) { + Preferences prefs = PrefUtil.createNode("hexgui/gui/program"); + if (prefs == null) return; + prefs.putInt("size", programs.size()); + for (int i = 0; i < programs.size(); ++i) { + prefs = PrefUtil.createNode("hexgui/gui/program/" + i); + if (prefs == null) break; + Program p = programs.get(i); + prefs.put("name", p.m_name); + prefs.put("command", p.m_command); + prefs.put("working-directory", p.m_working); + } + } + + public static Program findWithName(String name, Vector programs) { + for (int i = 0; i < programs.size(); ++i) { + Program p = programs.get(i); + if (p.m_name.equals(name)) return p; + } + return null; + } +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/RemoteProgramDialog.java b/src/main/java/hexgui/gui/RemoteProgramDialog.java new file mode 100644 index 0000000..b48abdc --- /dev/null +++ b/src/main/java/hexgui/gui/RemoteProgramDialog.java @@ -0,0 +1,26 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +/** Dialog for entering a remote program hostname. FIXME: allow port to be set. */ +public final class RemoteProgramDialog { + /** + * Run dialog. + * + * @return command to run. if user aborted. + */ + public static String show(Component parent, String value) { + String ret = JOptionPane.showInputDialog(parent, "Remote Host", value); + return ret; + } + + /** Make constructor unavailable; class is for namespace only. */ + private RemoteProgramDialog() {} +} diff --git a/src/main/java/hexgui/gui/ShowAnalyzeText.java b/src/main/java/hexgui/gui/ShowAnalyzeText.java new file mode 100644 index 0000000..6b148a8 --- /dev/null +++ b/src/main/java/hexgui/gui/ShowAnalyzeText.java @@ -0,0 +1,67 @@ +// ShowAnalyzeText.java + +package hexgui.gui; + +import hexgui.hex.HexPoint; +import hexgui.htp.AnalyzeType; +// import hexgui.htp.GtpUtil; +// import hexgui.gui.GuiBoardUtil; +import java.awt.Frame; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; + +/** Show multi-line text output from analyze command. Optionally can reuse window of last output. */ +public final class ShowAnalyzeText { + public ShowAnalyzeText(Frame owner, GuiBoard guiBoard) { + m_owner = owner; + m_guiBoard = guiBoard; + } + + public void show( + AnalyzeType type, HexPoint pointArg, String title, String response, boolean reuseWindow) { + boolean highlight = (type == AnalyzeType.HSTRING || type == AnalyzeType.HPSTRING); + TextViewer.Listener listener = null; + if (type == AnalyzeType.PSTRING || type == AnalyzeType.HPSTRING) + listener = new PointSelectionMarker(m_guiBoard); + // Remove first line, if empty (formatted responses frequently start + // with an empty line to avoid text on the line with the status + // character) + response = response.replaceAll("\\A *\n", ""); + if (reuseWindow && m_textViewer != null) m_textViewer.setText(title, response, highlight); + else { + m_textViewer = new TextViewer(m_owner, title, response, highlight, listener); + m_textViewer.addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent e) { + m_textViewer = null; + } + }); + if (pointArg == null) m_textViewer.setLocationByPlatform(true); + else { + // Point location = m_guiBoard.getLocationOnScreen(pointArg); + // m_textViewer.setLocation(location); + } + m_textViewer.setVisible(true); + } + } + + private static class PointSelectionMarker implements TextViewer.Listener { + public PointSelectionMarker(GuiBoard guiBoard) { + m_guiBoard = guiBoard; + } + + public void textSelected(String text) { + if (!m_guiBoard.isShowing()) return; + // PointList points = GtpUtil.parsePointString(text); + // GuiBoardUtil.showPointList(m_guiBoard, points); + } + + private final GuiBoard m_guiBoard; + } + + private Frame m_owner; + + private GuiBoard m_guiBoard; + + private TextViewer m_textViewer; +} diff --git a/src/main/java/hexgui/gui/ShowError.java b/src/main/java/hexgui/gui/ShowError.java new file mode 100644 index 0000000..7fba3e0 --- /dev/null +++ b/src/main/java/hexgui/gui/ShowError.java @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import java.awt.Component; +import javax.swing.JOptionPane; + +/** Displays a simple error message dialog. */ +public class ShowError { + public static void msg(Component parent, String msg) { + JOptionPane.showMessageDialog(parent, msg, "Error", JOptionPane.ERROR_MESSAGE); + } + + /** No constructor; class is for namespace only. */ + private ShowError() {} +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/StatusBar.java b/src/main/java/hexgui/gui/StatusBar.java new file mode 100644 index 0000000..45c4061 --- /dev/null +++ b/src/main/java/hexgui/gui/StatusBar.java @@ -0,0 +1,67 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import java.awt.*; +import java.awt.event.*; +import java.io.*; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.*; +import javax.swing.*; +import javax.swing.border.EtchedBorder; + +// ---------------------------------------------------------------------------- + +public class StatusBar extends JPanel { + public StatusBar() { + super(); + setLayout(new BorderLayout()); + setMinimumSize(new Dimension(0, 25)); + setBorder(BorderFactory.createEtchedBorder(EtchedBorder.LOWERED)); + // setLayout(new FlowLayout(FlowLayout.RIGHT)); + + m_message = new JLabel(); + m_message.setHorizontalTextPosition(JLabel.LEFT); + add(m_message, BorderLayout.WEST); + + m_progress = new JProgressBar(0, 1000000); + m_progress.setValue(0); + m_progress.setMinimumSize(new Dimension(100, 25)); + m_progress.setStringPainted(true); + m_progress.setString(""); + m_progress.setVisible(false); + add(m_progress, BorderLayout.EAST); + + setMessage("Ready"); + setVisible(true); + } + + public void setMessage(String msg) { + assert SwingUtilities.isEventDispatchThread(); + m_message.setText(msg); + } + + public void setProgressVisible(boolean visible) { + assert SwingUtilities.isEventDispatchThread(); + m_progress.setVisible(visible); + } + + public void setProgress(double pct) { + assert SwingUtilities.isEventDispatchThread(); + m_progress.setValue((int) (pct * 1000000.0)); + + // show 4 decimal places of accuracy + NumberFormat nf = NumberFormat.getInstance(); + if (nf instanceof DecimalFormat) ((DecimalFormat) nf).applyPattern("##0.0000"); + + m_progress.setString(nf.format(pct * 100.0) + "%"); + } + + JLabel m_message; + JProgressBar m_progress; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/gui/TextViewer.java b/src/main/java/hexgui/gui/TextViewer.java new file mode 100644 index 0000000..72d11b0 --- /dev/null +++ b/src/main/java/hexgui/gui/TextViewer.java @@ -0,0 +1,169 @@ +// TextViewer.java + +package hexgui.gui; + +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Container; +import java.awt.Dialog; +import java.awt.Dimension; +import java.awt.Frame; +import java.awt.Toolkit; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.KeyListener; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.swing.JDialog; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTextPane; +import javax.swing.event.CaretEvent; +import javax.swing.event.CaretListener; +import javax.swing.text.BadLocationException; +import javax.swing.text.Document; + +/** + * Dialog for displaying text. Allows syntax highlighting for analyze commands of type + * hstring. + */ +public final class TextViewer extends JDialog { + /** Callback for events generated by TextViewer. */ + public interface Listener { + /** + * Callback if some text is selected. If text is unselected again this function will be called + * with the complete text content of the window. + */ + void textSelected(String text); + } + + public TextViewer(Frame owner, String title, String text, boolean highlight, Listener listener) { + super(owner, title); + initialize(text, highlight, listener); + } + + public TextViewer(Dialog owner, String title, String text, boolean highlight, Listener listener) { + super(owner, title); + initialize(text, highlight, listener); + } + + public void setText(String title, String text, boolean highlight) { + setTitle(title); + insertText(text); + if (highlight) doSyntaxHighlight(); + } + + private JTextPane m_textPane; + + private Listener m_listener; + + private void insertText(String text) { + Document doc = m_textPane.getDocument(); + while (text.charAt(text.length() - 1) == '\n') text = text.substring(0, text.length() - 1); + try { + doc.remove(0, doc.getLength()); + doc.insertString(0, text, null); + } catch (BadLocationException e) { + assert false; + } + } + + private void doSyntaxHighlight() { + GuiUtil.addStyle(m_textPane, "title", null, null, true); + GuiUtil.addStyle(m_textPane, "point", new Color(0.25f, 0.5f, 0.7f)); + GuiUtil.addStyle(m_textPane, "number", new Color(0f, 0.54f, 0f)); + GuiUtil.addStyle(m_textPane, "const", new Color(0.8f, 0f, 0f)); + GuiUtil.addStyle(m_textPane, "color", new Color(0.54f, 0f, 0.54f)); + m_textPane.setEditable(true); + highlight("number", "\\b-?\\d+\\.?\\d*([Ee][+-]\\d+)?\\b"); + highlight("const", "\\b[A-Z_][A-Z_]+[A-Z]\\b"); + highlight("color", "\\b([Bb][Ll][Aa][Cc][Kk]|[Ww][Hh][Ii][Tt][Ee])\\b"); + highlight("point", "\\b([Pp][Aa][Ss][Ss]|[A-Ta-t](1\\d|[1-9]))\\b"); + highlight("title", "^\\S+:(\\s|$)"); + m_textPane.setEditable(false); + } + + private void highlight(String styleName, String regex) { + Document doc = m_textPane.getDocument(); + Pattern pattern = Pattern.compile(regex, Pattern.MULTILINE); + try { + CharSequence text = doc.getText(0, doc.getLength()); + Matcher matcher = pattern.matcher(text); + while (matcher.find()) { + int start = matcher.start(); + int end = matcher.end(); + GuiUtil.setStyle(m_textPane, start, end - start, styleName); + } + } catch (BadLocationException e) { + assert false; + } + } + + private void initialize(String text, boolean highlight, Listener listener) { + m_listener = listener; + JPanel panel = new JPanel(new BorderLayout()); + Container contentPane = getContentPane(); + contentPane.add(panel, BorderLayout.CENTER); + m_textPane = new JTextPane(); + GuiUtil.setMonospacedFont(m_textPane); + insertText(text); + JScrollPane scrollPane = new JScrollPane(m_textPane); + panel.add(scrollPane, BorderLayout.CENTER); + setDefaultCloseOperation(DISPOSE_ON_CLOSE); + KeyListener keyListener = + new KeyAdapter() { + public void keyReleased(KeyEvent e) { + int c = e.getKeyCode(); + if (c == KeyEvent.VK_ESCAPE) dispose(); + } + }; + m_textPane.addKeyListener(keyListener); + CaretListener caretListener = + new CaretListener() { + public void caretUpdate(CaretEvent event) { + if (m_listener == null) return; + int start = m_textPane.getSelectionStart(); + int end = m_textPane.getSelectionEnd(); + Document doc = m_textPane.getDocument(); + try { + if (start == end) { + String text = doc.getText(0, doc.getLength()); + m_listener.textSelected(text); + return; + } + String text = doc.getText(start, end - start); + m_listener.textSelected(text); + } catch (BadLocationException e) { + assert false; + } + } + }; + m_textPane.addCaretListener(caretListener); + if (highlight) doSyntaxHighlight(); + m_textPane.setCaretPosition(0); + m_textPane.setEditable(false); + packTextViewer(); + } + + private void packTextViewer() { + pack(); + // Workaround for problems with oversized windows on some platforms + // Also increase width by 10%, because automatic computation of + // JTextPane/JTextArea width according to content text does not + // work (maybe related to Sun Bug 4829215) + Dimension size = getSize(); + size.width *= 1.1; + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + int maxHeight = (int) (0.9 * screenSize.height); + int maxWidth = (int) (0.9 * screenSize.width); + if (size.height > maxHeight || size.width > maxWidth) { + size.width = Math.min(size.width, maxWidth); + size.height = Math.min(size.height, maxHeight); + } + setMinimumSize(new Dimension(128, 96)); + size.height = Math.max(size.height, 96); + size.width = Math.max(size.width, 128); + setPreferredSize(size); + setSize(size); + } +} diff --git a/src/main/java/hexgui/gui/VCDisplayDialog.java b/src/main/java/hexgui/gui/VCDisplayDialog.java new file mode 100644 index 0000000..7eae615 --- /dev/null +++ b/src/main/java/hexgui/gui/VCDisplayDialog.java @@ -0,0 +1,84 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.gui; + +import hexgui.hex.*; +import java.awt.*; +import java.awt.event.*; +import java.util.*; +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.text.*; + +// ---------------------------------------------------------------------------- + +/** Non-modal dialog displaying list of VCs. Clicking on a vc displays it to the given GuiBoard. */ +public class VCDisplayDialog extends JDialog implements ListSelectionListener, FocusListener { + public VCDisplayDialog(JFrame owner, GuiBoard board, Vector vcs) { + super(owner, "HexGui: VCs"); + + addWindowListener( + new WindowAdapter() { + public void windowClosing(WindowEvent winEvt) { + dispose(); + } + }); + + m_guiboard = board; + + JPanel panel = new JPanel(); + panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS)); + + m_list = new JList(); + m_list.addFocusListener(this); + m_list.addListSelectionListener(this); + m_list.setDragEnabled(false); + m_list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + + m_scrollpane = new JScrollPane(m_list); + m_scrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); + + panel.add(m_scrollpane); + add(panel); + + pack(); + + Dimension size = owner.getSize(); + setLocation(0, size.height); + + setVCs(vcs); + setVisible(true); + } + + public void setVCs(Vector vcs) { + m_vcs = vcs; + m_list.setListData(vcs); + } + + public void valueChanged(ListSelectionEvent e) { + if (m_list.isSelectionEmpty()) return; + + VC vc = m_vcs.get(m_list.getSelectedIndex()); + if (vc.getType().equals("softlimit")) // do nothing on this + return; + m_guiboard.clearMarks(); + m_guiboard.displayVC(vc); + m_guiboard.repaint(); + } + + public void focusGained(FocusEvent e) {} + + public void focusLost(FocusEvent e) { + m_list.clearSelection(); + } + + private JList m_list; + private JScrollPane m_scrollpane; + private Vector m_vcs; + private GuiBoard m_guiboard; +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/gui/package.html b/src/main/java/hexgui/gui/package.html old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/gui/package.html rename to src/main/java/hexgui/gui/package.html diff --git a/src/main/java/hexgui/hex/ConstPointList.java b/src/main/java/hexgui/hex/ConstPointList.java new file mode 100644 index 0000000..2dc0ada --- /dev/null +++ b/src/main/java/hexgui/hex/ConstPointList.java @@ -0,0 +1,28 @@ +// ConstPointList.java + +package hexgui.hex; + +import java.util.Iterator; + +/** + * Const functions of go.PointList. + * + * @see PointList + */ +public interface ConstPointList extends Iterable { + boolean contains(Object elem); + + boolean equals(Object object); + + HexPoint get(int index); + + int hashCode(); + + boolean isEmpty(); + + Iterator iterator(); + + int size(); + + String toString(); +} diff --git a/src/main/java/hexgui/hex/HexColor.java b/src/main/java/hexgui/hex/HexColor.java new file mode 100644 index 0000000..bb15f3a --- /dev/null +++ b/src/main/java/hexgui/hex/HexColor.java @@ -0,0 +1,53 @@ +package hexgui.hex; + +/** Possible states of a cell on a Hex board (black, white, or empty). */ +public final class HexColor { + public static final HexColor EMPTY; + public static final HexColor WHITE; + public static final HexColor BLACK; + + /** + * Returns a string representation of this color. + * + * @return "black", "white", or "empty". + */ + public String toString() { + return m_string; + } + + /** + * Returns the other opposite color. + * + * @return WHITE for BLACK, BLACK for WHITE, EMPTY otherwise. + */ + public HexColor otherColor() { + return m_otherColor; + } + + public static HexColor get(String name) { + if (name.equals("black")) return BLACK; + if (name.equals("white")) return WHITE; + if (name.equals("empty")) return EMPTY; + return null; + } + + private final String m_string; + private HexColor m_otherColor; + + static { + BLACK = new HexColor("black"); + WHITE = new HexColor("white"); + EMPTY = new HexColor("empty"); + BLACK.setOtherColor(WHITE); + WHITE.setOtherColor(BLACK); + EMPTY.setOtherColor(EMPTY); + } + + private HexColor(String string) { + m_string = string; + } + + private void setOtherColor(HexColor color) { + m_otherColor = color; + } +} diff --git a/src/main/java/hexgui/hex/HexPoint.java b/src/main/java/hexgui/hex/HexPoint.java new file mode 100644 index 0000000..542895c --- /dev/null +++ b/src/main/java/hexgui/hex/HexPoint.java @@ -0,0 +1,165 @@ +package hexgui.hex; + + +/** + * A cell on a Hex board. In addition to each playable cell, HexPoints are created for each edge of + * the board and for some special cases like swap moves, resignations, and forfeitures. + */ +public final class HexPoint implements Comparable { + /** Exception. */ + public static class InvalidHexPointException extends Exception { + public InvalidHexPointException(String message) { + super("Invalid point: " + message); + } + } + + public static final HexPoint NORTH; + public static final HexPoint SOUTH; + public static final HexPoint WEST; + public static final HexPoint EAST; + public static final HexPoint SWAP_SIDES; + public static final HexPoint SWAP_PIECES; + public static final HexPoint PASS; + public static final HexPoint RESIGN; + public static final HexPoint FORFEIT; + public static final HexPoint INVALID; + + public static final int MAX_WIDTH = 19; + public static final int MAX_HEIGHT = 19; + public static final int MAX_POINTS = MAX_WIDTH * MAX_HEIGHT + 10; + + public static final int DEFAULT_SIZE = 11; + + private static HexPoint s_points[]; + + static { + s_points = new HexPoint[MAX_POINTS]; + + INVALID = s_points[0] = new HexPoint(0, "invalid"); + RESIGN = s_points[1] = new HexPoint(1, "resign"); + FORFEIT = s_points[2] = new HexPoint(2, "forfeit"); + SWAP_SIDES = s_points[3] = new HexPoint(3, "swap-sides"); + SWAP_PIECES = s_points[4] = new HexPoint(4, "swap-pieces"); + PASS = s_points[5] = new HexPoint(5, "pass"); + + NORTH = s_points[6] = new HexPoint(6, "north"); + EAST = s_points[7] = new HexPoint(7, "east"); + SOUTH = s_points[8] = new HexPoint(8, "south"); + WEST = s_points[9] = new HexPoint(9, "west"); + + for (int y = 0; y < MAX_HEIGHT; y++) { + for (int x = 0; x < MAX_WIDTH; x++) { + String name = "" + (char) ('a' + x) + (y + 1); + s_points[10 + y * MAX_WIDTH + x] = new HexPoint(x, y, name); + } + } + } + + /** + * Returns the point with the given index. + * + * @param i index of the point. + * @return point with index i. + */ + public static HexPoint get(int i) { + assert (i >= 0); + assert (i < MAX_POINTS); + return s_points[i]; + } + + /** + * Returns the point with the given coordinates. Note that it is not possible to obtain points for + * board edges and special moves with this method. Use the get(String) method for + * these types of points. + * + * @param x x-coordinate of point + * @param y y-coordinate of point + * @return point with coordinates (x,y). + */ + public static HexPoint get(int x, int y) { + assert (x >= 0); + assert (y >= 0); + assert (x < MAX_WIDTH); + assert (y < MAX_HEIGHT); + return s_points[10 + y * MAX_WIDTH + x]; + } + + /** + * Returns the point with the given string representation. Valid special moves include: "north", + * "south", "east", "west" "swap-sides", "swap-pieces", "pass", "resign", and "forfeit". + * + * @param name The name of the point to return + * @return the point or null if name is invalid. + */ + public static HexPoint get(String name) { + if (name.equalsIgnoreCase("swap")) return SWAP_SIDES; + + for (int x = 0; x < MAX_POINTS; x++) { + if (name.equalsIgnoreCase(s_points[x].toString())) { + return s_points[x]; + } + } + assert (false); + return null; + } + + /** Returns the string representation of the point. */ + public String toString() { + return m_string; + } + + /** + * Convert a list of points to a string. Points are separated by a single space. If pointList is + * null, "(null)" is returned. + */ + public static String toString(ConstPointList pointList) { + if (pointList == null) return "(null)"; + int length = pointList.size(); + StringBuilder buffer = new StringBuilder(length * 4); + for (int i = 0; i < length; ++i) { + buffer.append(pointList.get(i)); + if (i < length - 1) buffer.append(' '); + } + return buffer.toString(); + } + + public int compareTo(Object other) { + if (other instanceof HexPoint) { + HexPoint o = (HexPoint) other; + if (this.x == o.x && this.y == o.y) return 0; + if (this.x != o.x) return this.x - o.x; + return this.y - o.y; + } + return -1; + } + + private HexPoint(int p, String name) { + this.x = -1; + this.y = p - 10; + m_string = name; + } + + private HexPoint(int x, int y, String name) { + this.x = x; + this.y = y; + m_string = name; + } + + /** return the HexPoint that is opposite to this one, across the long diagonal. */ + public HexPoint reflect() { + return HexPoint.get(this.y, this.x); + } + + /** + * return true is the point is a ordinary cell on the board (i.e., not something like NORTH, + * SWAP_SIDES, PASS, RESIGN, etc + */ + public boolean is_cell() { + return this.x >= 0; + } + + public final int x, y; + private final String m_string; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/hex/Move.java b/src/main/java/hexgui/hex/Move.java new file mode 100644 index 0000000..2fa0f5b --- /dev/null +++ b/src/main/java/hexgui/hex/Move.java @@ -0,0 +1,63 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.hex; + +// ---------------------------------------------------------------------------- + +/** + * Move. Contains a HexPoint and a HexColor. To construct swap moves or other special moves use the + * appropriate HexPoint. Immutable. + */ +public final class Move { + /** + * Constructs a move with the given point and color. + * + * @param p location of move + * @param c black or white. + */ + public Move(HexPoint p, HexColor c) { + m_point = p; + m_color = c; + } + + /** Convert to string */ + public String toString() { + return "Move(" + m_point + ", " + m_color + ")"; + } + + /** + * Determines whether this move is equal to the given move. + * + * @param other move to compare it to. + * @return true if points and colors are equal, false otherwise. + */ + public boolean equals(Move other) { + if (m_point == other.getPoint() && m_color == other.getColor()) return true; + return false; + } + + /** + * Returns the point of this move. + * + * @return HexPoint of the location. + */ + public HexPoint getPoint() { + return m_point; + } + + /** + * Returns the color of the move. + * + * @return HexColor of the move (WHITE or BLACK or, in case of a setup move, sometimes EMPTY). + */ + public HexColor getColor() { + return m_color; + } + + private final HexPoint m_point; + private final HexColor m_color; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/hex/PointList.java b/src/main/java/hexgui/hex/PointList.java new file mode 100644 index 0000000..aea45d5 --- /dev/null +++ b/src/main/java/hexgui/hex/PointList.java @@ -0,0 +1,114 @@ +// PointList.java + +package hexgui.hex; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.Vector; + +/** List containing points. */ +public final class PointList extends ArrayList implements ConstPointList { + public class ConstIterator implements Iterator { + public boolean hasNext() { + return (m_index < size()); + } + + public HexPoint next() { + return get(m_index++); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private int m_index; + } + + /** Construct empty point list. */ + public PointList() { + this(0); + } + + /** + * Construct empty point list with initial capacity. + * + * @param initialCapacity The number of points to reserve memory for. + */ + public PointList(int initialCapacity) { + super(initialCapacity); + } + + /** + * Construct point list with a single element. + * + * @param p The initial point element. + */ + public PointList(HexPoint p) { + this(1); + add(p); + } + + /** + * Construct point list as a copy of another point list. + * + * @param list The list to copy the points from. + */ + public PointList(ConstPointList list) { + super((PointList) list); + } + + public PointList(Vector v) { + for (int i = 0; i < v.size(); i++) add(v.get(i)); + } + + /** Add points of another list at the end of this list. */ + public void addAllFromConst(ConstPointList list) { + addAll((PointList) list); + } + + /** + * Get an empty constant point list. Can be used at places where an empty temporary point list is + * needed that is never modified to avoid memory allocation. + */ + public static ConstPointList getEmptyList() { + return EMPTY_LIST; + } + + /** + * Returns an iterator over the points elements in this list. An iterator of type + * PointList.ConstIterator is returned, which does not support Iterator.remove(), to allow + * for-each-loops for ConstPointList references. + */ + public Iterator iterator() { + return new ConstIterator(); + } + + /** Remove and return last element. Requires that list is not empty. */ + public HexPoint pop() { + int index = size() - 1; + if (index < 0) { + assert false; + return null; + } + HexPoint p = get(index); + remove(index); + return p; + } + + public String toString() { + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < size(); ++i) { + if (i > 0) buffer.append(' '); + buffer.append(get(i)); + } + return buffer.toString(); + } + + /** Convert point list to string. Null arguments will be converted to an empty string. */ + public static String toString(ConstPointList list) { + if (list == null) return ""; + else return list.toString(); + } + + private static final ConstPointList EMPTY_LIST = new PointList(); +} diff --git a/src/main/java/hexgui/hex/VC.java b/src/main/java/hexgui/hex/VC.java new file mode 100644 index 0000000..fa4784c --- /dev/null +++ b/src/main/java/hexgui/hex/VC.java @@ -0,0 +1,133 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.hex; + +import java.util.Vector; + +// ---------------------------------------------------------------------------- + +/** VC. A connection between two cells. */ +public class VC { + /** Constructs a VC. */ + public VC(HexPoint from, HexPoint to, HexColor c, String type) { + this( + from, + to, + c, + type, + "unknown", + 0, + new Vector(), + new Vector(), + new Vector()); + } + + public VC( + HexPoint from, + HexPoint to, + HexColor c, + String type, + String source, + int moves, + Vector carrier, + Vector stones, + Vector key) { + m_from = from; + m_to = to; + m_color = c; + m_type = type; + m_source = source; + m_moves = moves; + m_carrier = carrier; + m_stones = stones; + m_key = key; + } + + public String toString() { + StringBuilder ret = new StringBuilder(); + + if (m_type.equals("softlimit")) { + return "---------------- softlimit ----------------"; + } + + ret.append(m_from.toString()); + ret.append(" "); + ret.append(m_to.toString()); + ret.append(" "); + ret.append(m_color.toString()); + ret.append(" "); + ret.append(m_type); + ret.append(" "); + ret.append(m_source); + ret.append(" "); + ret.append(Integer.toString(m_moves)); + ret.append(" "); + + ret.append("["); + for (int i = 0; i < m_carrier.size(); i++) { + ret.append(" "); + ret.append(m_carrier.get(i).toString()); + } + ret.append(" ] "); + + ret.append("["); + for (int i = 0; i < m_stones.size(); i++) { + ret.append(" "); + ret.append(m_stones.get(i).toString()); + } + ret.append(" ] "); + + for (int i = 0; i < m_key.size(); i++) { + ret.append(" "); + ret.append(m_key.get(i).toString()); + } + + return ret.toString(); + } + + public HexPoint getFrom() { + return m_from; + } + + public HexPoint getTo() { + return m_to; + } + + public HexColor getColor() { + return m_color; + } + + public String getType() { + return m_type; + } + + public Vector getCarrier() { + return m_carrier; + } + + public Vector getStones() { + return m_stones; + } + + public Vector getKey() { + return m_key; + } + + public String getSource() { + return m_source; + } + + private HexPoint m_from; + private HexPoint m_to; + private HexColor m_color; + private String m_type; + private int m_moves; + private Vector m_carrier; + private Vector m_stones; + private Vector m_key; + private String m_source; +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/hex/package.html b/src/main/java/hexgui/hex/package.html old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/hex/package.html rename to src/main/java/hexgui/hex/package.html diff --git a/src/main/java/hexgui/htp/AnalyzeCommand.java b/src/main/java/hexgui/htp/AnalyzeCommand.java new file mode 100644 index 0000000..c0eca69 --- /dev/null +++ b/src/main/java/hexgui/htp/AnalyzeCommand.java @@ -0,0 +1,220 @@ +// AnalyzeCommand.java + +package hexgui.htp; + +import static hexgui.hex.HexColor.BLACK; +import static hexgui.hex.HexColor.WHITE; + +import hexgui.hex.ConstPointList; +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.hex.PointList; +import java.io.File; + +/** + * Concrete analyze command including data for wildcard replacements. See GoGui documentation, + * chapter "Analyze Commands" + */ +public class AnalyzeCommand { + public AnalyzeCommand(AnalyzeDefinition definition) { + m_definition = definition; + } + + public String getLabel() { + return m_definition.getLabel(); + } + + public String getCommand() { + return m_definition.getCommand(); + } + + public HexColor getColorArg() { + return m_colorArg; + } + + public HexPoint getPointArg() { + return m_pointArg; + } + + public PointList getPointListArg() { + return m_pointListArg; + } + + public AnalyzeType getType() { + return m_definition.getType(); + } + + public String getResultTitle() { + StringBuilder buffer = new StringBuilder(getLabel()); + if (needsColorArg() && m_colorArg != null) { + if (m_colorArg == BLACK) buffer.append(" Black"); + else { + assert m_colorArg == WHITE; + buffer.append(" White"); + } + } + if (needsPointArg() && m_pointArg != null) { + buffer.append(' '); + buffer.append(m_pointArg); + } else if (needsPointListArg()) { + for (HexPoint p : m_pointListArg) { + buffer.append(' '); + buffer.append(p); + } + } + if (needsStringArg() && m_stringArg != null) { + buffer.append(' '); + buffer.append(m_stringArg); + } + return buffer.toString(); + } + + public boolean isPointArgMissing() { + if (needsPointArg()) return (m_pointArg == null); + if (needsPointListArg()) return m_pointListArg.isEmpty(); + return false; + } + + public boolean isTextType() { + return m_definition.isTextType(); + } + + public boolean needsColorArg() { + return m_definition.needsColorArg(); + } + + public boolean needsFileArg() { + return m_definition.needsFileArg(); + } + + public boolean needsFileOpenArg() { + return m_definition.needsFileOpenArg(); + } + + public boolean needsFileSaveArg() { + return m_definition.needsFileSaveArg(); + } + + public boolean needsOnlyPointArg() { + return m_definition.needsOnlyPointArg(); + } + + public boolean needsOnlyPointAndColorArg() { + return m_definition.needsOnlyPointAndColorArg(); + } + + public boolean needsPointArg() { + return m_definition.needsPointArg(); + } + + public boolean needsPointListArg() { + return m_definition.needsPointListArg(); + } + + public boolean needsStringArg() { + return m_definition.needsStringArg(); + } + + public boolean needsOptStringArg() { + return m_definition.needsOptStringArg(); + } + + public String replaceWildCards(HexColor toMove) { + String command = m_definition.getCommand(); + String toMoveString = (toMove == BLACK ? "b" : "w"); + String result = command.replace("%m", toMoveString); + if (needsPointArg() && m_pointArg != null) result = result.replace("%p", m_pointArg.toString()); + if (needsPointListArg()) { + String pointList = HexPoint.toString(m_pointListArg); + if (getType() == AnalyzeType.EPLIST && m_pointListArg.size() > 0) + result = result + ' ' + pointList; + else result = result.replace("%P", pointList); + } + if (needsFileArg()) { + String fileArg = m_fileArg.toString(); + if (fileArg.indexOf(' ') >= 0) fileArg = "\"" + fileArg + "\""; + result = result.replace("%f", fileArg); + } + if (needsFileOpenArg()) { + String fileOpenArg = m_fileOpenArg.toString(); + if (fileOpenArg.indexOf(' ') >= 0) fileOpenArg = "\"" + fileOpenArg + "\""; + result = result.replace("%r", fileOpenArg); + } + if (needsFileSaveArg()) { + String fileSaveArg = m_fileSaveArg.toString(); + if (fileSaveArg.indexOf(' ') >= 0) fileSaveArg = "\"" + fileSaveArg + "\""; + result = result.replace("%w", fileSaveArg); + } + if (needsStringArg()) { + assert m_stringArg != null; + result = result.replace("%s", m_stringArg); + } + if (needsOptStringArg()) { + assert m_optStringArg != null; + result = result.replace("%o", m_optStringArg); + } + if (needsColorArg()) { + String colorString = "empty"; + if (m_colorArg == BLACK) colorString = "b"; + else if (m_colorArg == WHITE) colorString = "w"; + result = result.replace("%c", colorString); + } + return result; + } + + public void setColorArg(HexColor color) { + assert needsColorArg(); + m_colorArg = color; + } + + public void setFileArg(File file) { + assert needsFileArg(); + m_fileArg = file; + } + + public void setFileOpenArg(File file) { + assert needsFileOpenArg(); + m_fileOpenArg = file; + } + + public void setFileSaveArg(File file) { + assert needsFileSaveArg(); + m_fileSaveArg = file; + } + + public void setPointArg(HexPoint point) { + m_pointArg = point; + } + + public void setPointListArg(ConstPointList pointList) { + m_pointListArg = new PointList(pointList); + } + + public void setStringArg(String value) { + assert needsStringArg(); + m_stringArg = value; + } + + public void setOptStringArg(String value) { + assert needsOptStringArg(); + m_optStringArg = value; + } + + private final AnalyzeDefinition m_definition; + + private HexColor m_colorArg; + + private File m_fileArg; + + private File m_fileOpenArg; + + private File m_fileSaveArg; + + private String m_optStringArg; + + private String m_stringArg; + + private HexPoint m_pointArg; + + private PointList m_pointListArg = new PointList(); +} diff --git a/src/main/java/hexgui/htp/AnalyzeDefinition.java b/src/main/java/hexgui/htp/AnalyzeDefinition.java new file mode 100644 index 0000000..a16758b --- /dev/null +++ b/src/main/java/hexgui/htp/AnalyzeDefinition.java @@ -0,0 +1,188 @@ +// AnalyzeDefinition.java + +package hexgui.htp; + +import hexgui.util.ErrorMessage; +import hexgui.util.StringUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.util.ArrayList; + +/** + * Definition of an analyze command. See GoGui documentation, chapter "Analyze Commands". This class + * is immutable. + */ +public class AnalyzeDefinition { + public AnalyzeDefinition(String line) { + String array[] = line.split("/"); + String typeStr = array[0]; + if (typeStr.equals("bwboard")) m_type = AnalyzeType.BWBOARD; + else if (typeStr.equals("cboard")) m_type = AnalyzeType.CBOARD; + else if (typeStr.equals("dboard")) m_type = AnalyzeType.DBOARD; + else if (typeStr.equals("eplist")) m_type = AnalyzeType.EPLIST; + else if (typeStr.equals("gfx")) m_type = AnalyzeType.GFX; + else if (typeStr.equals("group")) m_type = AnalyzeType.GROUP; + else if (typeStr.equals("hstring")) m_type = AnalyzeType.HSTRING; + else if (typeStr.equals("hpstring")) m_type = AnalyzeType.HPSTRING; + else if (typeStr.equals("inferior")) m_type = AnalyzeType.INFERIOR; + else if (typeStr.equals("move")) m_type = AnalyzeType.MOVE; + else if (typeStr.equals("param")) m_type = AnalyzeType.PARAM; + else if (typeStr.equals("plist")) m_type = AnalyzeType.PLIST; + else if (typeStr.equals("pspairs")) m_type = AnalyzeType.PSPAIRS; + else if (typeStr.equals("pstring")) m_type = AnalyzeType.PSTRING; + else if (typeStr.equals("string")) m_type = AnalyzeType.STRING; + else if (typeStr.equals("sboard")) m_type = AnalyzeType.SBOARD; + else if (typeStr.equals("var")) m_type = AnalyzeType.VAR; + else if (typeStr.equals("varb")) m_type = AnalyzeType.VARB; + else if (typeStr.equals("varc")) m_type = AnalyzeType.VARC; + else if (typeStr.equals("varp")) m_type = AnalyzeType.VARP; + else if (typeStr.equals("varpo")) m_type = AnalyzeType.VARPO; + else if (typeStr.equals("varw")) m_type = AnalyzeType.VARW; + else if (typeStr.equals("vc")) m_type = AnalyzeType.VC; + else m_type = AnalyzeType.NONE; + m_label = array[1]; + m_command = array[2]; + } + + public AnalyzeDefinition(AnalyzeType type, String label, String command) { + m_type = type; + m_label = label; + m_command = command; + } + + public String getCommand() { + return m_command; + } + + public String getLabel() { + return m_label; + } + + public AnalyzeType getType() { + return m_type; + } + + /** + * Should the response be shown as text. Returns true for types that should be shown (not + * necessarily only) as text to the user. That is string and variation commands. + */ + public boolean isTextType() { + return m_type == AnalyzeType.STRING + || m_type == AnalyzeType.HSTRING + || m_type == AnalyzeType.HPSTRING + || m_type == AnalyzeType.PSTRING + || m_type == AnalyzeType.VAR + || m_type == AnalyzeType.VARC + || m_type == AnalyzeType.VARW + || m_type == AnalyzeType.VARB + || m_type == AnalyzeType.VARP + || m_type == AnalyzeType.VARPO; + } + + public boolean needsColorArg() { + return (m_command.indexOf("%c") >= 0); + } + + public boolean needsFileArg() { + return (m_command.indexOf("%f") >= 0); + } + + public boolean needsFileOpenArg() { + return (m_command.indexOf("%r") >= 0); + } + + public boolean needsFileSaveArg() { + return (m_command.indexOf("%w") >= 0); + } + + public boolean needsOnlyPointArg() { + return (needsPointArg() + && !needsColorArg() + && !needsFileArg() + && !needsFileOpenArg() + && !needsFileSaveArg() + && !needsPointListArg() + && !needsStringArg() + && !needsOptStringArg()); + } + + public boolean needsOnlyPointAndColorArg() { + return (needsPointArg() + && needsColorArg() + && !needsFileArg() + && !needsFileOpenArg() + && !needsFileSaveArg() + && !needsPointListArg() + && !needsStringArg() + && !needsOptStringArg()); + } + + public boolean needsPointArg() { + return (m_command.indexOf("%p") >= 0); + } + + public boolean needsPointListArg() { + return (m_command.indexOf("%P") >= 0 || m_type == AnalyzeType.EPLIST); + } + + public boolean needsStringArg() { + return (m_command.indexOf("%s") >= 0); + } + + public boolean needsOptStringArg() { + return (m_command.indexOf("%o") >= 0); + } + + public static ArrayList read(String programAnalyzeCommands) + throws ErrorMessage { + if (programAnalyzeCommands == null) throw new ErrorMessage("no analyze commands!"); + + Reader stringReader = new StringReader(programAnalyzeCommands); + BufferedReader reader = new BufferedReader(stringReader); + return readConfig(reader, "program response to hexgui-analyze_commands", null); + } + + private final AnalyzeType m_type; + + private final String m_label; + + private final String m_command; + + private static ArrayList readConfig( + BufferedReader reader, String name, ArrayList supportedCommands) throws ErrorMessage { + ArrayList result = new ArrayList(); + ArrayList labels = new ArrayList(); + try { + String line; + int lineNumber = 0; + while ((line = reader.readLine()) != null) { + ++lineNumber; + line = line.trim(); + if (line.length() > 0 && line.charAt(0) != '#') { + String array[] = line.split("/"); + if (array.length < 3 || array.length > 5) + throw new ErrorMessage("Error in " + name + " line " + lineNumber); + if (supportedCommands != null) { + String[] cmdArray = StringUtils.splitArguments(array[2].trim()); + if (cmdArray.length == 0 || !supportedCommands.contains(cmdArray[0])) continue; + } + String label = array[1]; + if (labels.contains(label)) continue; + labels.add(label); + result.add(new AnalyzeDefinition(line)); + } + } + return result; + } catch (IOException e) { + throw new ErrorMessage("Error reading " + name); + } finally { + try { + reader.close(); + } catch (IOException e) { + throw new ErrorMessage("Error reading " + name); + } + } + } +} diff --git a/src/main/java/hexgui/htp/AnalyzeType.java b/src/main/java/hexgui/htp/AnalyzeType.java new file mode 100644 index 0000000..4952df2 --- /dev/null +++ b/src/main/java/hexgui/htp/AnalyzeType.java @@ -0,0 +1,54 @@ +// AnalyzeType.java + +package hexgui.htp; + +/** Type of an analyze command. */ +public enum AnalyzeType { + BWBOARD, + + CBOARD, + + DBOARD, + + EPLIST, + + GFX, + + GROUP, + + HSTRING, + + HPSTRING, + + INFERIOR, + + MOVE, + + NONE, + + PARAM, + + PLIST, + + PSTRING, + + PSPAIRS, + + STRING, + + SBOARD, + + VAR, + + VARB, + + VARC, + + VARP, + + VARPO, + + VARW, + + VC +} diff --git a/src/main/java/hexgui/htp/AnalyzeUtil.java b/src/main/java/hexgui/htp/AnalyzeUtil.java new file mode 100644 index 0000000..bd85007 --- /dev/null +++ b/src/main/java/hexgui/htp/AnalyzeUtil.java @@ -0,0 +1,72 @@ +// AnalyzeUtil.java + +package hexgui.htp; + +import java.util.NoSuchElementException; +import java.util.Scanner; + +public final class AnalyzeUtil { + /** Result of AnalyzeUtil.parseParameterLine(). */ + public static final class Result { + public ParameterType m_type; + + /** Complete type metainformation. */ + public String m_typeInfo; + + public String m_key; + + public String m_value; + } + + /** + * Get command for setting a parameter. See chapter "Analyze Commands" of the GoGui documentation. + */ + public static String getParameterCommand(String command, String key, String value) { + return command + " " + key + " " + value + "\n"; + } + + /** + * Parse a line in the response of an analyze command of type "param". See chapter "Analyze + * Commands" of the GoGui documentation. + * + * @return The result or null, if line could not be parsed. + */ + public static Result parseParameterLine(String line) { + line = line.trim(); + if (line.startsWith("[") && line.endsWith("]")) { + // Might be used as label for grouping parameters on tabbing + // panes in a later version of GoGui, so we silently accept it + return null; + } + if (line.length() == 0) return null; + Scanner scanner = new Scanner(line); + Result result = new Result(); + try { + result.m_typeInfo = scanner.next("^\\[[^\\]]*\\]"); + line = line.substring(result.m_typeInfo.length()).trim(); + result.m_typeInfo = result.m_typeInfo.substring(1, result.m_typeInfo.length() - 1); + } catch (NoSuchElementException e) { + // Treat unknown types as string for compatibility with future + // types + result.m_typeInfo = "string"; + } + int pos = line.indexOf(' '); + if (pos < 0) { + result.m_key = line.trim(); + result.m_value = ""; + } else { + result.m_key = line.substring(0, pos).trim(); + result.m_value = line.substring(pos + 1).trim(); + } + if (result.m_typeInfo.equals("bool")) result.m_type = ParameterType.BOOL; + else if (result.m_typeInfo.startsWith("list/")) result.m_type = ParameterType.LIST; + else + // Treat unknown types as string for compatibility with future + // types + result.m_type = ParameterType.STRING; + return result; + } + + /** Make constructor unavailable; class is for namespace only. */ + private AnalyzeUtil() {} +} diff --git a/src/main/java/hexgui/htp/HtpController.java b/src/main/java/hexgui/htp/HtpController.java new file mode 100644 index 0000000..bd53676 --- /dev/null +++ b/src/main/java/hexgui/htp/HtpController.java @@ -0,0 +1,195 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.htp; + +import hexgui.util.StringUtils; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintStream; + +// ---------------------------------------------------------------------------- + +/** Sends HTP commands and parses the response. */ +public class HtpController { + public interface IOInterface { + void sentCommand(String str); + + void receivedResponse(String str); + + void receivedError(String str); + } + + // ------------------------------------------------------------ + + public interface GuiFxCallback { + void guifx(String cmd); + } + + // ------------------------------------------------------------ + + /** Constructor */ + public HtpController(InputStream in, OutputStream out, IOInterface io, GuiFxCallback guifx) { + System.out.println("controller: in constructor."); + m_in = new BufferedReader(new InputStreamReader(in)); + m_out = new PrintStream(out); + m_io = io; + m_guifx = guifx; + m_connected = true; + m_waiting = false; + } + + public void interrupt() { + System.out.println("Sending interrupt"); + m_out.print("# interrupt\n"); + m_out.flush(); + m_io.sentCommand("# interrupt"); + } + + /** + * Sends command over the htp channel. + * + *

Note this method is synchronized, and so HtpController will process only a single command at + * a time. + */ + public synchronized void sendCommand(String cmd) throws HtpError { + if (!m_connected) return; + + System.out.println("controller: sending '" + cmd.trim() + "'"); + m_out.print(cmd); + m_out.flush(); + m_io.sentCommand(cmd); + handleResponse(); + } + + public boolean cmdInProgress() { + return m_waiting; + } + + public boolean wasSuccess() { + return m_success; + } + + public String getResponse() { + return m_response; + } + + private void handleResponse() throws HtpError { + m_waiting = true; + + while (m_waiting) { + + String response; + try { + response = waitResponse(); + } catch (IOException e) { + m_waiting = false; + throw new HtpError("IOException waiting for response!"); + } + + // Since the response must, by definition of the GTP + // protocol, always end with two newline characters, + // remove them. + response = response.replaceAll("[\n\r]$", ""); + + // System.out.println("got: '" + response + "'"); + + if (!m_connected) { + m_success = false; + m_response = ""; + m_waiting = false; + throw new HtpError("Program Disconnected."); + } else if (response == null) { + m_success = false; + m_response = ""; + m_waiting = false; + throw new HtpError("Null response received!"); + } else if (response.length() < 2) { + m_success = false; + m_response = response; + m_waiting = false; + throw new HtpError("Response length too short! '" + response + "'"); + } else if (response.length() > 10 && response.substring(0, 10).equals("gogui-gfx:")) { + + String fx = StringUtils.cleanWhiteSpace(response.substring(10).trim()); + + m_guifx.guifx(fx); + + } else if (response.substring(0, 2).equals("= ")) { + m_success = true; + m_response = response.substring(2); + System.out.print("controller: success: "); + m_io.receivedResponse(response); + m_waiting = false; + } else if (response.substring(0, 2).equals("? ")) { + m_success = false; + m_response = response.substring(2); + System.out.print("controller: error: "); + m_io.receivedError(response); + m_waiting = false; + } else { + m_response = response; + m_success = false; + System.out.print("controller: invalid: "); + m_waiting = false; + throw new HtpError("Invalid HTP response:'" + response + "'."); + } + } + + System.out.println("'" + m_response.trim() + "'"); + } + + private String waitResponse() throws IOException { + StringBuilder ret = new StringBuilder(); + while (true) { + // System.out.println("blocking on response"); + String line = m_in.readLine(); + // System.out.println("readline: '" + line + "'"); + if (line == null) { + System.out.println("controller: Disconnected!"); + m_connected = false; + break; + } + String clean = cleanInput(line); + ret.append(clean); + ret.append('\n'); + + if (clean.equals("")) break; + } + System.out.println("controller: done waiting on response."); + return ret.toString(); + } + + /** Cleans the input. Removes all occurrences of '\r'. Converts all '\t' to ' '. */ + private String cleanInput(String in) { + StringBuilder out = new StringBuilder(); + for (int i = 0; i < in.length(); i++) { + if (in.charAt(i) == '\t') out.append(' '); + else if (in.charAt(i) != '\r') { + out.append(in.charAt(i)); + } + } + return out.toString(); + } + + public boolean connected() { + return m_connected; + } + + private boolean m_connected; + private BufferedReader m_in; + private PrintStream m_out; + private IOInterface m_io; + private GuiFxCallback m_guifx; + + private boolean m_waiting; + + private String m_response; + private boolean m_success; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/htp/HtpError.java b/src/main/java/hexgui/htp/HtpError.java new file mode 100644 index 0000000..61dce47 --- /dev/null +++ b/src/main/java/hexgui/htp/HtpError.java @@ -0,0 +1,14 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.htp; + +// ---------------------------------------------------------------------------- + +/** Htp exception. */ +public class HtpError extends Exception { + public HtpError(String message) { + super(message); + } +} diff --git a/src/hexgui/htp/ParameterType.java b/src/main/java/hexgui/htp/ParameterType.java similarity index 64% rename from src/hexgui/htp/ParameterType.java rename to src/main/java/hexgui/htp/ParameterType.java index 40191a2..b0cc141 100644 --- a/src/hexgui/htp/ParameterType.java +++ b/src/main/java/hexgui/htp/ParameterType.java @@ -3,11 +3,10 @@ package hexgui.htp; /** Parameter types in analyze commands of type "param". */ -public enum ParameterType -{ - STRING, +public enum ParameterType { + STRING, - BOOL, + BOOL, - LIST + LIST } diff --git a/src/hexgui/package.html b/src/main/java/hexgui/package.html old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/package.html rename to src/main/java/hexgui/package.html diff --git a/src/main/java/hexgui/sgf/GameFileFilter.java b/src/main/java/hexgui/sgf/GameFileFilter.java new file mode 100644 index 0000000..8cc4455 --- /dev/null +++ b/src/main/java/hexgui/sgf/GameFileFilter.java @@ -0,0 +1,28 @@ +// GameFileFilter.java + +package hexgui.sgf; + +import hexgui.util.FileUtil; +import java.io.File; +import javax.swing.filechooser.FileFilter; + +/** Swing file filter for SGF or Jago XML files. */ +public class GameFileFilter extends FileFilter { + /** + * Accept function. + * + * @param file The file to check. + * @return true if file has extension .sgf or .SGF or is a directory + */ + public boolean accept(File file) { + if (file.isDirectory()) return true; + return (FileUtil.hasExtension(file, "sgf") + || FileUtil.hasExtension(file, "SGF") + || FileUtil.hasExtension(file, "xml") + || FileUtil.hasExtension(file, "XML")); + } + + public String getDescription() { + return "Hex Game"; + } +} diff --git a/src/main/java/hexgui/sgf/SgfReader.java b/src/main/java/hexgui/sgf/SgfReader.java new file mode 100644 index 0000000..d16ca11 --- /dev/null +++ b/src/main/java/hexgui/sgf/SgfReader.java @@ -0,0 +1,411 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.sgf; + +import static java.text.MessageFormat.format; + +import hexgui.game.GameInfo; +import hexgui.game.Node; +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.hex.Move; +import java.awt.Dimension; +import java.io.*; +import java.util.*; + +// ---------------------------------------------------------------------------- + +/** + * SGF reader. See https://www.red-bean.com/sgf/ for the SGF definition. NOTE: Uses StringBuilder + * which requires Java 1.5. + */ +public final class SgfReader { + /** Sgf exception. */ + public static class SgfError extends Exception { + public SgfError(String message) { + super(message); + } + } + + private static final int GM_HEXGAME = 11; + + /** Constructor. Parse the input stream in sgf format. */ + public SgfReader(InputStream in) throws SgfError { + InputStreamReader reader = new InputStreamReader(in); + m_reader = new LineNumberReader(reader); + m_tokenizer = new StreamTokenizer(m_reader); + m_gameinfo = new GameInfo(); + m_warnings = new Vector(); + m_swap_bug = false; + try { + findGameTree(); + m_gametree = parseGameTree(null, true); + m_reader.close(); + } catch (IOException e) { + throw sgfError("IO error occurred while parsing file."); + } + } + + public Node getGameTree() { + return m_gametree; + } + + public GameInfo getGameInfo() { + return m_gameinfo; + } + + public Vector getWarnings() { + if (m_warnings.size() == 0) return null; + return m_warnings; + } + + // ------------------------------------------------------------ + + /** Fast forward to the first "(". */ + private void findGameTree() throws SgfError, IOException { + while (true) { + int ttype = m_tokenizer.nextToken(); + if (ttype == StreamTokenizer.TT_EOF) throw sgfError("No game tree found!"); + + if (ttype == '(') { + m_tokenizer.pushBack(); + break; + } + } + } + + private Node parseGameTree(Node parent, boolean isroot) throws SgfError, IOException { + int ttype = m_tokenizer.nextToken(); + if (ttype != '(') throw sgfError("Missing '(' at head of game tree."); + + Node node = parseNode(parent, isroot); + + ttype = m_tokenizer.nextToken(); + if (ttype != ')') throw sgfError("Game tree not closed!"); + + return node; + } + + private Node parseNode(Node parent, boolean isroot) throws SgfError, IOException { + int ttype = m_tokenizer.nextToken(); + if (ttype != ';') throw sgfError("Error at head of node!"); + + Node node = new Node(); + node.setParent(parent); + if (parent != null) parent.addChild(node); + + boolean done = false; + while (!done) { + ttype = m_tokenizer.nextToken(); + switch (ttype) { + case '(': + m_tokenizer.pushBack(); + parseGameTree(node, false); + break; + + case ';': + m_tokenizer.pushBack(); + parseNode(node, false); + done = true; + break; + + case ')': + m_tokenizer.pushBack(); + done = true; + break; + + case StreamTokenizer.TT_WORD: + parseProperty(node, isroot); + break; + + case StreamTokenizer.TT_EOF: + throw sgfError("Unexpected EOF in node!"); + + default: + throw sgfError("Error in SGF file."); + } + } + + return node; + } + + /** + * Parse a point or move value. Supports both standard SGF notation for Hex (a1, ...) and Go-like + * notation used by Little Golem (aa, ...) + */ + private HexPoint parsePoint(String s) throws SgfError { + s = s.trim().toLowerCase(Locale.ENGLISH); + HexPoint result = null; + if (s.length() >= 2) { + if (s.length() == 2 && s.charAt(1) >= 'a' && s.charAt(1) <= 'z') { + // Go-like SGF notation + int x = s.charAt(0) - 'a'; + int y = s.charAt(1) - 'a'; + if (x < HexPoint.MAX_WIDTH && y < HexPoint.MAX_HEIGHT) result = HexPoint.get(x, y); + } else { + // Standard SGF notation for Hex + try { + int x = s.charAt(0) - 'a'; + int y = Integer.parseInt(s.substring(1)) - 1; + if (y >= 0 && y < HexPoint.MAX_HEIGHT) result = HexPoint.get(x, y); + } catch (NumberFormatException e) { + } + } + } + if (result == null) throw sgfError(format("Invalid point {0}", s)); + return result; + } + + private HexPoint parseMove(String s) throws SgfError { + s = s.trim().toLowerCase(Locale.ENGLISH); + + // Special case: some or all versions of HexGui up to 0.9.GIT + // incorrectly used "swap-pieces" instead of "swap-sides". + // The SGF specification states: + // + // * swap-sides - the player elects to swap sides with his + // opponent; if he was playing Black he now plays White, and + // vice versa. + // + // * swap-pieces - the player elects to swap pieces with his + // opponent - all of Black's pieces are colored White, and + // White's pieces are colored Black. Then the entire board + // is reflected in the long diagonal axis. + // + // For backward compatibility, we must compensate for the + // incorrect use of "swap-pieces" when reading SGF files + // written by HexGui 0.9.GIT or earlier. + + if (m_swap_bug && s.equals("swap-pieces")) { + s = "swap-sides"; + } + + // HexPoint.get() handles special move values like "swap" + HexPoint result = HexPoint.get(s); + if (result == null) + // Handles Go-style point notation (aa, ...) + result = parsePoint(s); + return result; + } + + private void parseProperty(Node node, boolean isroot) throws SgfError, IOException { + int x, y; + String name = m_tokenizer.sval; + + boolean done = false; + while (!done) { + + int ttype = m_tokenizer.nextToken(); + if (ttype != '[') { + done = true; + } + m_tokenizer.pushBack(); + if (done) { + break; + } + + String val; + if (name.equals("C")) { + val = parseComment(); + } else { + val = parseValue(); + } + // System.out.println(name + "[" + val + "]"); + + if (name.equals("W")) { + HexPoint point = parseMove(val); + node.setMove(new Move(point, HexColor.WHITE)); + } else if (name.equals("B")) { + HexPoint point = parseMove(val); + node.setMove(new Move(point, HexColor.BLACK)); + } else if (name.equals("AB")) { + node.addSetup(HexColor.BLACK, parsePoint(val)); + } else if (name.equals("AW")) { + node.addSetup(HexColor.WHITE, parsePoint(val)); + } else if (name.equals("AE")) { + node.addSetup(HexColor.EMPTY, parsePoint(val)); + } else if (name.equals("LB")) { + node.addLabel(val); + } else if (name.equals("FF")) { + node.setSgfProperty(name, val); + x = parseInt(val); + if (x < 1 || x > 4) throw sgfError("Invalid SGF Version! (" + x + ")"); + } else if (name.equals("GM")) { + node.setSgfProperty(name, val); + if (!isroot) sgfWarning("GM property in non-root node!"); + if (parseInt(val) != GM_HEXGAME) throw sgfError("Not a Hex game!"); + } else if (name.equals("SZ")) { + node.setSgfProperty(name, val); + if (!isroot) sgfWarning("GM property in non-root node!"); + Dimension dim = new Dimension(); + String sp[] = val.split(":"); + if (sp.length == 1) { + x = parseInt(sp[0]); + dim.setSize(x, x); + } else if (sp.length == 2) { + x = parseInt(sp[0]); + y = parseInt(sp[1]); + dim.setSize(x, y); + } else { + throw sgfError("Malformed boardsize!"); + } + m_gameinfo.setBoardSize(dim); + } else if (name.equals("AP")) { + node.setSgfProperty(name, val); + String regex = "HexGui:0\\.[0-9](\\z|[^0-9].*)"; + if (val.matches(regex)) { + // version HexGui:0.9 or earlier + m_swap_bug = true; + } + } else { + node.setSgfProperty(name, val); + } + } + } + + // Parse an SGF "SimpleText" property value. + private String parseValue() throws SgfError, IOException { + int ttype = m_tokenizer.nextToken(); + if (ttype != '[') { + throw sgfError("Property missing opening '['."); + } + + StringBuilder sb = new StringBuilder(256); + boolean quoted = false; + while (true) { + int ch = m_reader.read(); + if (ch < 0) { + throw sgfError("Property runs to EOF."); + } + // Don't rely on the default conversion, because + // sb.append(ch) would convert the integer to a string + // rather than a character. + char c = (char) ch; + + if (!quoted) { + if (c == ']') { + break; + } + if (c == '\\') { + quoted = true; + } else { + if (Character.isWhitespace(c)) { + // The spec says "Whitespaces other than space + // must be converted to space". + sb.append(' '); + } else { + sb.append(c); + } + } + } else { + quoted = false; + if (Character.isWhitespace(c)) { + // The spec says "Any char following "\" is + // inserted verbatim (exception: whitespaces still + // have to be converted to space!)." + sb.append(' '); + } else { + sb.append(c); + } + } + } + + return sb.toString(); + } + + // Parse an SGF "Text" property value. + private String parseComment() throws SgfError, IOException { + int ttype = m_tokenizer.nextToken(); + if (ttype != '[') { + throw sgfError("Comment missing opening '['."); + } + + StringBuilder sb = new StringBuilder(4096); + boolean quoted = false; + while (true) { + int ch = m_reader.read(); + if (ch < 0) { + throw sgfError("Comment runs to EOF."); + } + char c = (char) ch; + + if (!quoted) { + if (c == ']') { + break; + } + + if (c == '\\') { + quoted = true; + } else { + if (Character.isWhitespace(c) && c != '\n') { + // The spec says "White spaces other than + // linebreaks are converted to space (e.g. no + // tab, vertical tab, ..)." + + // Also note that the LineNumberReader class + // already takes care of newline conversion, + // i.e., "\n", "\r", and "\r\n" are all + // converted to '\n'. + sb.append(' '); + } else { + sb.append(c); + } + } + } else { + quoted = false; + if (c == '\n') { + // Append nothing. The spec says: "Soft line + // break: linebreaks preceded by a "\" (soft + // linebreaks are converted to "", i.e. they are + // removed)". + } else if (Character.isWhitespace(c)) { + // The spec says: "Any char following "\" is + // inserted verbatim (exception: whitespaces still + // have to be converted to space!). + sb.append(' '); + } else { + sb.append(c); + } + } + } + + return sb.toString(); + } + + private int parseInt(String str) throws SgfError { + int ret; + try { + ret = Integer.parseInt(str); + } catch (NumberFormatException e) { + throw sgfError("Error parsing integer."); + } + return ret; + } + + // ---------------------------------------------------------------------- + + private void verifyGame(Node root) throws SgfError { + if (m_gameinfo.getBoardSize() == null) { + throw sgfError("Missing SZ property."); + } + } + + private SgfError sgfError(String msg) { + return new SgfError("Line " + m_reader.getLineNumber() + ": " + msg); + } + + private void sgfWarning(String msg) { + m_warnings.add("Line " + m_reader.getLineNumber() + ": " + msg); + } + + private StreamTokenizer m_tokenizer; + private LineNumberReader m_reader; + private Node m_gametree; + private GameInfo m_gameinfo; + private Vector m_warnings; + private boolean m_swap_bug; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/sgf/SgfWriter.java b/src/main/java/hexgui/sgf/SgfWriter.java new file mode 100644 index 0000000..974ae71 --- /dev/null +++ b/src/main/java/hexgui/sgf/SgfWriter.java @@ -0,0 +1,161 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.sgf; + +import hexgui.game.GameInfo; +import hexgui.game.Node; +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.hex.Move; +import hexgui.version.Version; +import java.awt.Dimension; +import java.io.*; +import java.util.Iterator; +import java.util.Map; +import java.util.Vector; + +// ---------------------------------------------------------------------------- + +/** SGF Writer. See https://www.red-bean.com/sgf/ for the SGF definition. */ +public final class SgfWriter { + + /** Write a game tree. */ + public SgfWriter(OutputStream out, Node root, GameInfo game) { + m_out = new PrintStream(out); + m_buffer = new StringBuffer(128); + m_gameinfo = game; + + writeTree(root, true); + print("\n"); + flushBuffer(); + m_out.flush(); + m_out.close(); + } + + private void writeTree(Node root, boolean isroot) { + print("("); + writeNode(root, isroot); + print(")"); + } + + private void writeNode(Node node, boolean isroot) { + print(";"); + + if (isroot) { + String value; + + node.setSgfProperty("FF", "4"); + node.setSgfProperty("AP", "HexGui:" + Version.id); + node.setSgfProperty("GM", "11"); + + Dimension dim = m_gameinfo.getBoardSize(); + value = Integer.toString(dim.width); + if (dim.width != dim.height) value += ":" + Integer.toString(dim.height); + node.setSgfProperty("SZ", value); + } + + if (node.getMove() != null) { + printMove(node.getMove()); + } + + Map map = node.getProperties(); + Iterator> it = map.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry e = it.next(); + if (!(e.getKey().equals("C") && e.getValue().equals(""))) { + String val = e.getValue(); + if (e.getKey().equals("C")) { + // For now we only escape comments, although there + // may be other text values that should be escaped + // too. Avoids escaping the ":" in AP field. + val = escapeString(val); + } + print(e.getKey() + "[" + val + "]"); + } + } + + if (node.hasSetup()) { + Vector list; + list = node.getSetup(HexColor.BLACK); + if (!list.isEmpty()) { + print("AB"); + printPointList(list); + } + list = node.getSetup(HexColor.WHITE); + if (!list.isEmpty()) { + print("AW"); + printPointList(list); + } + list = node.getSetup(HexColor.EMPTY); + if (!list.isEmpty()) { + print("AE"); + printPointList(list); + } + } + + int num = node.numChildren(); + if (num == 0) return; + + if (num == 1) { + writeNode(node.getChild(), false); + return; + } + + for (int i = 0; i < num; i++) { + writeTree(node.getChild(i), false); + } + } + + private String escapeString(String s) { + StringBuilder out = new StringBuilder(256); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + switch (c) { + case '\\': + case '[': + case ']': + case ':': + out.append('\\'); + out.append(c); + break; + default: + out.append(c); + } + } + return out.toString(); + } + + private void printMove(Move move) { + String color = "B"; + if (move.getColor() == HexColor.WHITE) color = "W"; + print(color + "[" + move.getPoint().toString() + "]"); + } + + private void printPointList(Vector list) { + for (int i = 0; i < list.size(); ++i) { + print("[" + list.get(i).toString() + "]"); + } + } + + private void print(String str) { + if (m_buffer.length() + str.length() > 72) { + m_out.append(m_buffer.toString()); + m_out.append("\n"); + m_buffer.setLength(0); + } + m_buffer.append(str); + } + + private void flushBuffer() { + m_out.append(m_buffer.toString()); + m_buffer.setLength(0); + } + + private PrintStream m_out; + private StringBuffer m_buffer; + private GameInfo m_gameinfo; +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/sgf/package.html b/src/main/java/hexgui/sgf/package.html old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/sgf/package.html rename to src/main/java/hexgui/sgf/package.html diff --git a/src/main/java/hexgui/util/BoardLayout.java b/src/main/java/hexgui/util/BoardLayout.java new file mode 100644 index 0000000..2f83437 --- /dev/null +++ b/src/main/java/hexgui/util/BoardLayout.java @@ -0,0 +1,41 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.util; + +import java.awt.*; +import java.awt.event.*; +import javax.swing.*; + +public class BoardLayout implements LayoutManager { + public void addLayoutComponent(String name, Component comp) {} + + public void layoutContainer(Container parent) { + if (parent.getComponentCount() != 1) { + System.err.println("BoardLayout: needs exactly one component!"); + } + Dimension size = parent.getSize(); + Insets insets = parent.getInsets(); + size.width -= insets.left + insets.right; + size.height -= insets.top + insets.bottom; + int w = size.width; + int h = size.height; + // if (h*3/2 > w) h = 2*w/3; + // if (h*3/2 < w) w = h*3/2; + + int x = (size.width - w) / 2; + int y = (size.height - h) / 2; + parent.getComponent(0).setBounds(x + insets.left, y + insets.top, w, h); + } + + public Dimension minimumLayoutSize(Container parent) { + return parent.getComponent(0).getMinimumSize(); + } + + public Dimension preferredLayoutSize(Container parent) { + return parent.getComponent(0).getPreferredSize(); + } + + public void removeLayoutComponent(Component comp) {} +} diff --git a/src/main/java/hexgui/util/ErrorMessage.java b/src/main/java/hexgui/util/ErrorMessage.java new file mode 100644 index 0000000..2c3d2fe --- /dev/null +++ b/src/main/java/hexgui/util/ErrorMessage.java @@ -0,0 +1,18 @@ +// ErrorMessage.java + +package hexgui.util; + +/** + * Error with error message. ErrorMessage are exceptions with a message meaningful for presentation + * to the user. + */ +public class ErrorMessage extends Exception { + /** + * Constructor. + * + * @param message The error message text. + */ + public ErrorMessage(String message) { + super(message); + } +} diff --git a/src/main/java/hexgui/util/FileUtil.java b/src/main/java/hexgui/util/FileUtil.java new file mode 100644 index 0000000..a11c497 --- /dev/null +++ b/src/main/java/hexgui/util/FileUtil.java @@ -0,0 +1,144 @@ +// FileUtil.java + +package hexgui.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; + +/** Static file utility functions. */ +public final class FileUtil { + /** + * Return the file extension of a file name. + * + * @param file The file. + * @return File extension or null if file name has no extension. + */ + public static String getExtension(File file) { + String ext = null; + String s = file.getName(); + int i = s.lastIndexOf('.'); + if (i > 0 && i < s.length() - 1) ext = s.substring(i + 1); + return ext; + } + + /** + * Returns relative URI between to files. Can be used instead of URI.relativize(), which does not + * compute relative URI's correctly, if toFile is not a subdirectory of fromFile (Sun's Java + * 1.5.0). + * + * @todo Handle special characters and file names containing slashes. + * @param fromFile File to compute the URI relative to. + * @param toFile Target file or directory. + * @return Relative URI. + */ + public static String getRelativeURI(File fromFile, File toFile) { + assert !fromFile.exists() || !fromFile.isDirectory(); + fromFile = fromFile.getAbsoluteFile().getParentFile(); + assert fromFile != null; + ArrayList fromList = splitFile(fromFile); + ArrayList toList = splitFile(toFile); + int fromSize = fromList.size(); + int toSize = toList.size(); + int i = 0; + while (i < fromSize && i < toSize && fromList.get(i).equals(toList.get(i))) ++i; + StringBuilder result = new StringBuilder(); + for (int j = i; j < fromSize; ++j) result.append("../"); + for (int j = i; j < toSize; ++j) { + result.append(toList.get(j)); + if (j < toSize - 1) result.append('/'); + } + return result.toString(); + } + + /** Return URI for file. Replacement for File.toURI() with defined (empty) authority. */ + public static URI getURI(File file) { + try { + return new URI("file", "", file.toURI().getPath(), null, null); + } catch (URISyntaxException e) { + return null; + } + } + + /** Check for extension (case-insensitive). */ + public static boolean hasExtension(File f, String extension) { + String ext = getExtension(f); + if (ext == null) return false; + return ext.equalsIgnoreCase(extension); + } + + /** + * Read a list of strings from a file. The file is expected to contain one string per line; + * leading and trailing whitespaces are removed. Empty lines or lines beginning with the comment + * character '#' are ignored. + */ + public static ArrayList readStringListFromFile(File file) throws IOException { + ArrayList result = new ArrayList(); + FileReader reader = new FileReader(file); + BufferedReader in = new BufferedReader(reader); + try { + while (true) { + String line = in.readLine(); + if (line == null) break; + line = line.trim(); + if (!line.equals("") && !line.startsWith("#")) result.add(line); + } + return result; + } finally { + in.close(); + } + } + + /** + * Remove extension in file name. If the file does not have the extension oldExtension, the + * extension will not be removed. + */ + public static String removeExtension(File file, String oldExtension) { + String name = file.toString(); + if (hasExtension(file, oldExtension)) { + int index = name.lastIndexOf('.'); + assert index >= 0; + return name.substring(0, index); + } + return name; + } + + /** + * Replace extension in file name. If the file does not have the extension oldExtension, the + * extension will not be replaced but the new extension will be appended. + */ + public static String replaceExtension(File file, String oldExtension, String newExtension) { + String name = file.toString(); + if (hasExtension(file, oldExtension)) { + int index = name.lastIndexOf('.'); + assert index >= 0; + return name.substring(0, index) + "." + newExtension; + } + return name + "." + newExtension; + } + + public static String replaceExtension(String file, String oldExtension, String newExtension) { + return replaceExtension(new File(file), oldExtension, newExtension); + } + + /** Make constructor unavailable; class is for namespace only. */ + private FileUtil() {} + + private static ArrayList splitFile(File file) { + ArrayList list = new ArrayList(); + file = file.getAbsoluteFile(); + try { + file = file.getCanonicalFile(); + } catch (IOException e) { + } + while (file != null) { + list.add(0, file.getName()); + file = file.getParentFile(); + } + return list; + } +} diff --git a/src/main/java/hexgui/util/Hexagon.java b/src/main/java/hexgui/util/Hexagon.java new file mode 100644 index 0000000..179f059 --- /dev/null +++ b/src/main/java/hexgui/util/Hexagon.java @@ -0,0 +1,88 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.util; + +import java.awt.*; + +// ---------------------------------------------------------------------------- + +/** Creates hexagons. */ +public final class Hexagon { + + /** + * Creates a hexagon with flat sides centered at the specified point. + * + * @param p center of hexagon. + * @param width the width of the hexagon. + * @param height the height of the hexagon. + * @return an instance of Polygon with the above properties. + */ + public static Polygon createVerticalHexagon(Point p, int width, int height) { + int xpoints[] = new int[6]; + int ypoints[] = new int[6]; + + int sx = width / 2; + int ly = height / 2; + int sy = ly / 2; + + xpoints[0] = 0; + ypoints[0] = -ly; + xpoints[1] = -sx; + ypoints[1] = -sy; + xpoints[2] = -sx; + ypoints[2] = +sy; + xpoints[3] = 0; + ypoints[3] = +ly; + xpoints[4] = +sx; + ypoints[4] = +sy; + xpoints[5] = +sx; + ypoints[5] = -sy; + + Polygon ret = new Polygon(xpoints, ypoints, 6); + ret.translate(p.x, p.y); + + return ret; + } + + /** + * Creates a hexagon with flat top and bottom centered at the specified point. + * + * @param p center of hexagon. + * @param width the width of the hexagon. + * @param height the height of the hexagon. + * @return an instance of Polygon with the above properties. + */ + public static Polygon createHorizontalHexagon(Point p, int width, int height) { + int xpoints[] = new int[6]; + int ypoints[] = new int[6]; + + int sy = height / 2; + int lx = width / 2; + int sx = lx / 2; + + xpoints[0] = -lx; + ypoints[0] = 0; + xpoints[1] = -sx; + ypoints[1] = +sy; + xpoints[2] = +sx; + ypoints[2] = +sy; + xpoints[3] = +lx; + ypoints[3] = 0; + xpoints[4] = +sx; + ypoints[4] = -sy; + xpoints[5] = -sx; + ypoints[5] = -sy; + + Polygon ret = new Polygon(xpoints, ypoints, 6); + ret.translate(p.x, p.y); + + return ret; + } + + /** Private constructor. */ + private Hexagon() {} +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/util/ObjectUtil.java b/src/main/java/hexgui/util/ObjectUtil.java new file mode 100644 index 0000000..ba9d1f3 --- /dev/null +++ b/src/main/java/hexgui/util/ObjectUtil.java @@ -0,0 +1,18 @@ +// ObjectUtil.java + +package hexgui.util; + +/** Utils for using class java.lang.Object. */ +public final class ObjectUtil { + /** Compare including the case that arguments can be null. */ + public static boolean equals(Object object1, Object object2) { + if (object1 == null && object2 == null) return true; + if (object1 == null && object2 != null) return false; + if (object1 != null && object2 == null) return false; + assert object1 != null && object2 != null; + return object1.equals(object2); + } + + /** Make constructor unavailable; class is for namespace only. */ + private ObjectUtil() {} +} diff --git a/src/main/java/hexgui/util/Options.java b/src/main/java/hexgui/util/Options.java new file mode 100644 index 0000000..ce12373 --- /dev/null +++ b/src/main/java/hexgui/util/Options.java @@ -0,0 +1,294 @@ +package hexgui.util; + +import java.io.*; +import java.util.*; + +/** Parser for command line options. Options begin with a single '-' character. */ +public class Options { + /** + * Parse options. + * + * @param args Command line args from main method + * @param specs Specification of allowed options. Contains option names (without '-'). Options + * that need an argument must have a ':' appended. The special argument '--' stops option + * parsing, all following arguments are treated as non-option arguments. + * @throws Exception If options are not valid according to specs. + */ + public Options(String[] args, String[] specs) throws Exception { + for (String spec : specs) { + if (spec.length() > 0) m_map.put(spec, null); + } + parseArgs(args); + } + + /** Check if option is present. */ + public boolean contains(String option) { + String value = get(option, null); + return (value != null); + } + + /** + * Return string option value. + * + * @param option The option key. + * @return The option value or an empty string, if option is not present. + */ + public String get(String option) { + return get(option, ""); + } + + /** + * Return string option value. + * + * @param option The option key. + * @param defaultValue The default value. + * @return The option value or defaultValue, if option is not present. + */ + public String get(String option, String defaultValue) { + assert isValidOption(option); + String value = getValue(option); + if (value == null) return defaultValue; + return value; + } + + /** + * Get remaining arguments that are not options. + * + * @return The sequence of non-option arguments. + */ + public ArrayList getArguments() { + return m_args; + } + + /** + * Check that the number of non-option arguments is zero. + * + * @throws Exception If there are any non-option arguments. + */ + public void checkNoArguments() throws Exception { + if (!m_args.isEmpty()) + throw new Exception("Command does not allow arguments that are not options"); + } + + /** + * Parse double option. + * + * @param option The option key. + * @return The option value or 0, if option is not present. + * @throws Exception If option value is not a double. + */ + public double getDouble(String option) throws Exception { + return getDouble(option, 0); + } + + /** + * Parse double option. + * + * @param option The option key. + * @param defaultValue The default value. + * @return The option value or defaultValue, if option is not present. + * @throws Exception If option value is not a double. + */ + public double getDouble(String option, double defaultValue) throws Exception { + String value = get(option, Double.toString(defaultValue)); + if (value == null) return defaultValue; + try { + return Double.parseDouble(value); + } catch (NumberFormatException e) { + throw new Exception("Option -" + option + " needs float value"); + } + } + + /** + * Parse integer option. + * + * @param option The option key. + * @return The option value or 0, if option is not present. + * @throws Exception If option value is not an integer. + */ + public int getInteger(String option) throws Exception { + return getInteger(option, 0); + } + + /** + * Parse integer option. + * + * @param option The option key. + * @param defaultValue The default value. + * @return The option value or defaultValue, if option is not present. + * @throws Exception If option value is not an integer. + */ + public int getInteger(String option, int defaultValue) throws Exception { + String value = get(option, Integer.toString(defaultValue)); + if (value == null) return defaultValue; + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + throw new Exception("Option -" + option + " needs integer value"); + } + } + + /** + * Parse integer option with range check. + * + * @param option The option key. + * @param defaultValue The default value. + * @param min The minimum valid value. + * @return The option value or defaultValue, if option is not present. + * @throws Exception If option value is less than min. + */ + public int getInteger(String option, int defaultValue, int min) throws Exception { + int value = getInteger(option, defaultValue); + if (value < min) throw new Exception("Option -" + option + " must be greater than " + min); + return value; + } + + /** + * Parse integer option with range check. + * + * @param option The option key. + * @param defaultValue The default value. + * @param min The minimum valid value. + * @param max The maximum valid value. + * @return The option value or defaultValue, if option is not present. + * @throws Exception If option value is less than min or greater than max. + */ + public int getInteger(String option, int defaultValue, int min, int max) throws Exception { + int value = getInteger(option, defaultValue); + if (value < min || value > max) + throw new Exception("Option -" + option + " must be in [" + min + ".." + max + "]"); + return value; + } + + /** + * Parse long integer option. + * + * @param option The option key. + * @return The option value or 0, if option is not present. + * @throws Exception If option value is not a long integer. + */ + public long getLong(String option) throws Exception { + return getLong(option, 0L); + } + + /** + * Parse long integer option. + * + * @param option The option key. + * @param defaultValue The default value. + * @return The option value or defaultValue, if option is not present. + * @throws Exception If option value is not a long integer. + */ + public long getLong(String option, long defaultValue) throws Exception { + String value = get(option, Long.toString(defaultValue)); + if (value == null) return defaultValue; + try { + return Long.parseLong(value); + } catch (NumberFormatException e) { + throw new Exception("Option -" + option + " needs long integer value"); + } + } + + /** + * Read options from a file given with the option "config". Requires that "config" is an allowed + * option. + * + * @throws Exception If options in file are not valid according to the specification. + */ + public void handleConfigOption() throws Exception { + if (!contains("config")) return; + String filename = get("config"); + InputStream inputStream; + try { + inputStream = new FileInputStream(filename); + } catch (FileNotFoundException e) { + throw new Exception("File not found: " + filename); + } + Reader reader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(reader); + try { + StringBuilder buffer = new StringBuilder(256); + String line; + while (true) { + line = bufferedReader.readLine(); + if (line == null) break; + buffer.append(line); + buffer.append(' '); + } + parseArgs(StringUtils.splitArguments(buffer.toString())); + } catch (IOException e) { + System.err.println(e.getMessage()); + } finally { + try { + bufferedReader.close(); + } catch (IOException e) { + System.err.println(e.getMessage()); + } + } + } + + /** + * Creates a new Options instance from command line. Automatically calls handleConfigOption. + * + * @param args The command line split into arguments. + * @param specs Option specification as in constructor. + * @return The new Options instance. + * @throws Exception If options are not valid according to specs. + */ + public static Options parse(String[] args, String[] specs) throws Exception { + Options opt = new Options(args, specs); + opt.handleConfigOption(); + return opt; + } + + private final ArrayList m_args = new ArrayList(); + + private final Map m_map = new TreeMap(); + + private String getSpec(String option) throws Exception { + if (m_map.containsKey(option)) return option; + else if (m_map.containsKey(option + ":")) return option + ":"; + throw new Exception("Unknown option -" + option); + } + + private String getValue(String option) { + assert isValidOption(option); + if (m_map.containsKey(option)) return m_map.get(option); + return m_map.get(option + ":"); + } + + private boolean isOptionKey(String s) { + return (s.length() > 0 && s.charAt(0) == '-'); + } + + private boolean isValidOption(String option) { + return (m_map.containsKey(option) || m_map.containsKey(option + ":")); + } + + private boolean needsValue(String spec) { + return (spec.length() > 0 && spec.substring(spec.length() - 1).equals(":")); + } + + private void parseArgs(String args[]) throws Exception { + boolean stopParse = false; + int n = 0; + while (n < args.length) { + String s = args[n]; + ++n; + if (s.equals("--")) { + stopParse = true; + continue; + } + if (isOptionKey(s) && !stopParse) { + String spec = getSpec(s.substring(1)); + if (needsValue(spec)) { + if (n >= args.length) throw new Exception("Option " + s + " needs value"); + String value = args[n]; + ++n; + m_map.put(spec, value); + } else m_map.put(spec, "1"); + + } else m_args.add(s); + } + } +} diff --git a/src/main/java/hexgui/util/Pair.java b/src/main/java/hexgui/util/Pair.java new file mode 100644 index 0000000..858451c --- /dev/null +++ b/src/main/java/hexgui/util/Pair.java @@ -0,0 +1,20 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.util; + +// ---------------------------------------------------------------------------- + +/** A pair of two objects. */ +public class Pair { + public E first; + public F second; + + public Pair(E first, F second) { + this.first = first; + this.second = second; + } +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/util/Platform.java b/src/main/java/hexgui/util/Platform.java new file mode 100644 index 0000000..ad0608c --- /dev/null +++ b/src/main/java/hexgui/util/Platform.java @@ -0,0 +1,186 @@ +// Platform.java + +package hexgui.util; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.net.InetAddress; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.Locale; + +/** Static utility functions for platform detection and platform-dependent behavior. */ +public class Platform { + /** Handler for events from the Application Menu on MacOS. */ + public interface SpecialMacHandler { + /** + * Handle about menu event. + * + * @return true if event was handled successfully. + */ + boolean handleAbout(); + + /** + * Handle open file event. + * + * @param filename name of file. + * @return true if event was handled successfully. + */ + boolean handleOpenFile(String filename); + + /** + * Handle quit application event. + * + * @return true if event was handled successfully, false if quit should be aborted. + */ + boolean handleQuit(); + } + + public static String getJavaRuntimeName() { + // java.runtime.name is not a standard property + String name = System.getProperty("java.runtime.name"); + if (name == null) name = System.getProperty("java.vm.name"); + return name; + } + + /** + * Return information on this computer. Returns host name and cpu information (if /proc/cpuinfo + * exists). + */ + public static String getHostInfo() { + String info; + try { + info = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + info = "?"; + } + try { + if (existsProcCpuinfo()) { + String[] cmdArray = {"/bin/sh", "-c", "grep '^model name' /proc/cpuinfo"}; + String result = ProcessUtil.runCommand(cmdArray); + int start = result.indexOf(':'); + if (start >= 0) { + info = info + " ("; + int end = result.indexOf("\n"); + if (end >= 0) info = info + result.substring(start + 1, end).trim(); + else info = info + result.substring(start + 1).trim(); + info = info + ")"; + } + } + } catch (IOException e) { + } + return info; + } + + /** Check if the platform is Mac OS X. */ + public static boolean isMac() { + return s_isMac; + } + + /** Check if the platform is Unix. */ + public static boolean isUnix() { + return s_isUnix; + } + + /** Check if the platform is Windows. */ + public static boolean isWindows() { + return s_isWindows; + } + + /** + * Try to open a URL in an external browser. Tries /usr/bin/open if Platform.isMac(), rundll32 + * url.dll,FileProtocolHandler if Platform.isWindows(), and if isUnix() in this order: - xdg-open + * - kfmclient (if KDE is running) - firefox - mozilla - opera + * + * @param url URL to open. + * @return false if everything failed. + */ + public static boolean openInExternalBrowser(URL url) { + if (isMac()) { + String[] cmd = {"/usr/bin/open", url.toString()}; + if (runProcess(cmd)) return true; + } else if (isWindows()) { + String[] cmd = {"rundll32", "url.dll,FileProtocolHandler", url.toString()}; + if (runProcess(cmd)) return true; + } else if (isUnix()) { + { + String[] cmd = {"xdg-open", url.toString()}; + if (runProcess(cmd)) return true; + } + if (checkKDERunning()) { + String[] cmd = {"kfmclient", "openURL", url.toString()}; + if (runProcess(cmd)) return true; + } + { + String[] cmd = {"firefox", url.toString()}; + if (runProcess(cmd)) return true; + } + { + String[] cmd = {"mozilla", url.toString()}; + if (runProcess(cmd)) return true; + } + { + String[] cmd = {"opera", url.toString()}; + if (runProcess(cmd)) return true; + } + } + return false; + } + + /** + * Register handler for events from the Application Menu on MacOS. + * + * @param handler Handler to register. + */ + public static void registerSpecialMacHandler(SpecialMacHandler handler) { + try { + Object[] args = {handler}; + Class[] arglist = {Platform.SpecialMacHandler.class}; + String name = "net.sf.gogui.specialmac.RegisterSpecialMacHandler"; + Class registerClass = Class.forName(name); + Constructor constructor = registerClass.getConstructor(arglist); + constructor.newInstance(args); + } catch (Throwable e) { + System.err.println( + "Could not register handler for Mac events." + " (com.apple.eawt classes not found)"); + } + } + + private static boolean s_isMac; + + private static boolean s_isUnix; + + private static boolean s_isWindows; + + static { + // See http://developer.apple.com/technotes/tn2002/tn2110.html + String name = System.getProperty("os.name"); + s_isMac = name.toLowerCase(Locale.getDefault()).startsWith("mac os x"); + s_isUnix = (name.indexOf("nix") >= 0 || name.indexOf("nux") >= 0); + s_isWindows = name.startsWith("Windows"); + } + + private static boolean checkKDERunning() { + try { + String[] cmdArray = {"dcop"}; + String result = ProcessUtil.runCommand(cmdArray); + return (result.indexOf("kicker") >= 0); + } catch (IOException e) { + return false; + } + } + + private static boolean existsProcCpuinfo() { + return new File("/proc/cpuinfo").exists(); + } + + private static boolean runProcess(String[] cmd) { + try { + ProcessUtil.runProcess(cmd); + return true; + } catch (IOException e) { + return false; + } + } +} diff --git a/src/main/java/hexgui/util/PrefUtil.java b/src/main/java/hexgui/util/PrefUtil.java new file mode 100644 index 0000000..97a6826 --- /dev/null +++ b/src/main/java/hexgui/util/PrefUtil.java @@ -0,0 +1,78 @@ +// PrefUtil.java + +package hexgui.util; + +import java.util.ArrayList; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +/** Utils for using java.util.prefs package. */ +public final class PrefUtil { + /** + * Get node path, create if not already existing. + * + * @param path The absolute path name of the node. + * @return The node + */ + public static Preferences createNode(String path) { + assert !path.startsWith("/"); + return Preferences.userRoot().node(path); + } + + /** + * Get a list of strings from preferences. The list is stored as a size property end element_N + * properties with N being the element index. + * + * @param path The absolute path name of the node. + * @return The list of strings. + */ + public static ArrayList getList(String path) { + Preferences prefs = getNode(path); + if (prefs == null) return new ArrayList(); + int size = prefs.getInt("size", 0); + if (size <= 0) return new ArrayList(); + ArrayList result = new ArrayList(size); + for (int i = 0; i < size; ++i) { + String element = prefs.get("element_" + i, null); + if (element == null) + // Should not happen + break; + result.add(element); + } + return result; + } + + /** + * Get node for package and path, return null if not already existing. + * + * @param path The absolute path name of the node. + * @return The node or null, if node does not exist or failure in the backing store. + */ + public static Preferences getNode(String path) { + assert !path.startsWith("/"); + Preferences prefs = Preferences.userRoot(); + try { + if (!prefs.nodeExists(path)) return null; + } catch (BackingStoreException e) { + return null; + } + return prefs.node(path); + } + + /** + * Put a list of strings to preferences. The list is stored as a size property end element_N + * properties with N being the element index. + * + * @param path The absolute path name of the node. + * @param list The list of strings. + */ + public static void putList(String path, ArrayList list) { + Preferences prefs = createNode(path); + if (prefs == null) return; + prefs.putInt("size", list.size()); + for (int i = 0; i < list.size(); ++i) prefs.put("element_" + i, list.get(i)); + } + + /** Make constructor unavailable; class is for namespace only. */ + private PrefUtil() {} +} diff --git a/src/main/java/hexgui/util/ProcessUtil.java b/src/main/java/hexgui/util/ProcessUtil.java new file mode 100644 index 0000000..0fbb39f --- /dev/null +++ b/src/main/java/hexgui/util/ProcessUtil.java @@ -0,0 +1,106 @@ +// ProcessUtil.java + +package hexgui.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +class ExitWaiter extends Thread { + public ExitWaiter(Object monitor, Process process) { + m_monitor = monitor; + m_process = process; + } + + public boolean isFinished() { + synchronized (m_mutex) { + return m_isFinished; + } + } + + public void run() { + try { + m_process.waitFor(); + } catch (InterruptedException e) { + } + synchronized (m_mutex) { + m_isFinished = true; + } + synchronized (m_monitor) { + m_monitor.notifyAll(); + } + } + + private boolean m_isFinished; + + private final Object m_monitor; + + private final Object m_mutex = new Object(); + + private final Process m_process; +} +; + +/** Static utility functions and classes related to processes. */ +public class ProcessUtil { + /** Copies standard error of a process to System.err. */ + public static class StdErrThread extends Thread { + public StdErrThread(Process process) { + super(new StreamCopy(false, process.getErrorStream(), System.err, false)); + } + } + + /** Run a process and return its standard output as a string. */ + public static String runCommand(String[] cmdArray) throws IOException { + Runtime runtime = Runtime.getRuntime(); + Process process = runtime.exec(cmdArray); + Thread discardErr = new StreamDiscard(process.getErrorStream()); + discardErr.start(); + InputStream in = process.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + try { + StringBuilder result = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + result.append(line); + result.append('\n'); + } + try { + if (process.waitFor() != 0) throw new IOException("Process returned error status"); + } catch (InterruptedException e) { + throw new IOException("InterruptedException"); + } + return result.toString(); + } finally { + reader.close(); + } + } + + /** + * Run a process. Forwards the stdout/stderr of the child process to stderr of the calling + * process. + */ + public static void runProcess(String[] cmdArray) throws IOException { + Runtime runtime = Runtime.getRuntime(); + Process process = runtime.exec(cmdArray); + Thread copyOut = new Thread(new StreamCopy(false, process.getInputStream(), System.err, false)); + copyOut.start(); + Thread copyErr = new Thread(new StreamCopy(false, process.getErrorStream(), System.err, false)); + copyErr.start(); + } + + public static boolean waitForExit(Process process, long timeout) { + Object monitor = new Object(); + ExitWaiter exitWaiter = new ExitWaiter(monitor, process); + synchronized (monitor) { + exitWaiter.start(); + try { + monitor.wait(timeout); + return exitWaiter.isFinished(); + } catch (InterruptedException e) { + return false; + } + } + } +} diff --git a/src/main/java/hexgui/util/RadialGradientContext.java b/src/main/java/hexgui/util/RadialGradientContext.java new file mode 100644 index 0000000..b7eddd8 --- /dev/null +++ b/src/main/java/hexgui/util/RadialGradientContext.java @@ -0,0 +1,92 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.util; + +import java.awt.Color; +import java.awt.PaintContext; +import java.awt.geom.Point2D; +import java.awt.image.ColorModel; +import java.awt.image.Raster; +import java.awt.image.WritableRaster; + +// ---------------------------------------------------------------------------- + +/** Creates a raster with a radial color gradient. */ +public class RadialGradientContext implements PaintContext { + public RadialGradientContext(Point2D point, Color color1, Point2D radius, Color color2) { + m_point = point; + m_red1 = color1.getRed(); + m_green1 = color1.getGreen(); + m_blue1 = color1.getBlue(); + m_alpha1 = color1.getAlpha(); + m_radius = radius.distance(0, 0); + m_redDiff = color2.getRed() - m_red1; + m_greenDiff = color2.getGreen() - m_green1; + m_blueDiff = color2.getBlue() - m_blue1; + m_alphaDiff = color2.getAlpha() - m_alpha1; + } + + public void dispose() {} + + public ColorModel getColorModel() { + return ColorModel.getRGBdefault(); + } + + public Raster getRaster(int x, int y, int width, int height) { + if (m_raster != null && x == m_x && y == m_y && width == m_width && height == m_height) + return m_raster; + m_x = x; + m_y = y; + m_height = height; + m_width = width; + ColorModel colorModel = getColorModel(); + m_raster = colorModel.createCompatibleWritableRaster(width, height); + int[] data = new int[width * height * 4]; + int index = -1; + for (int j = 0; j < height; ++j) + for (int i = 0; i < width; ++i) { + double distance = m_point.distance(x + i, y + j); + double ratio = Math.min(distance / m_radius, 1.0); + data[++index] = (int) (m_red1 + ratio * m_redDiff); + data[++index] = (int) (m_green1 + ratio * m_greenDiff); + data[++index] = (int) (m_blue1 + ratio * m_blueDiff); + data[++index] = (int) (m_alpha1 + ratio * m_alphaDiff); + } + m_raster.setPixels(0, 0, width, height, data); + return m_raster; + } + + private final int m_red1; + + private final int m_redDiff; + + private final int m_green1; + + private final int m_greenDiff; + + private final int m_blue1; + + private final int m_blueDiff; + + private final int m_alpha1; + + private final int m_alphaDiff; + + private int m_x; + + private int m_y; + + private int m_height; + + private int m_width; + + private final double m_radius; + + private final Point2D m_point; + + private WritableRaster m_raster; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/util/RadialGradientPaint.java b/src/main/java/hexgui/util/RadialGradientPaint.java new file mode 100644 index 0000000..48e0322 --- /dev/null +++ b/src/main/java/hexgui/util/RadialGradientPaint.java @@ -0,0 +1,75 @@ +// ---------------------------------------------------------------------------- +// $Id$ +// ---------------------------------------------------------------------------- + +package hexgui.util; + +import java.awt.Color; +import java.awt.Paint; +import java.awt.PaintContext; +import java.awt.Rectangle; +import java.awt.RenderingHints; +import java.awt.geom.AffineTransform; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.awt.image.ColorModel; + +// ---------------------------------------------------------------------------- + +/** Creates a PaintContext for a radial gradient. */ +public class RadialGradientPaint implements Paint { + public RadialGradientPaint( + Point2D point, Color pointColor, Point2D radius, Color backgroundColor) { + assert (radius.distance(0, 0) > 0); + m_point = point; + m_radius = radius; + m_pointColor = pointColor; + m_backgroundColor = backgroundColor; + int alphaPoint = pointColor.getAlpha(); + int alphaBackground = backgroundColor.getAlpha(); + if ((alphaPoint & alphaBackground) == 0xff) m_transparency = OPAQUE; + else m_transparency = TRANSLUCENT; + } + + public PaintContext createContext( + ColorModel colorModel, + Rectangle deviceBounds, + Rectangle2D userBounds, + AffineTransform xform, + RenderingHints hints) { + Point2D transformedPoint = xform.transform(m_point, null); + Point2D transformedRadius = xform.deltaTransform(m_radius, null); + if (m_cachedContext != null + && transformedPoint.equals(m_transformedPoint) + && transformedRadius.equals(m_transformedRadius)) return m_cachedContext; + m_transformedPoint = (Point2D) transformedPoint.clone(); + m_transformedRadius = (Point2D) transformedRadius.clone(); + m_cachedContext = + new RadialGradientContext( + transformedPoint, m_pointColor, + transformedRadius, m_backgroundColor); + return m_cachedContext; + } + + public int getTransparency() { + return m_transparency; + } + + private final int m_transparency; + + private Point2D m_transformedPoint; + + private Point2D m_transformedRadius; + + private RadialGradientContext m_cachedContext; + + private final Point2D m_point; + + private final Point2D m_radius; + + private final Color m_backgroundColor; + + private final Color m_pointColor; +} + +// ---------------------------------------------------------------------------- diff --git a/src/main/java/hexgui/util/SpringUtilities.java b/src/main/java/hexgui/util/SpringUtilities.java new file mode 100644 index 0000000..e515b48 --- /dev/null +++ b/src/main/java/hexgui/util/SpringUtilities.java @@ -0,0 +1,199 @@ +/* + * Copyright (c) 1995 - 2008 Sun Microsystems, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of Sun Microsystems nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package hexgui.util; + +import java.awt.*; +import javax.swing.*; +import javax.swing.SpringLayout; + +/** + * A 1.4 file that provides utility methods for creating form- or grid-style layouts with + * SpringLayout. These utilities are used by several programs, such as SpringBox and + * SpringCompactGrid. + */ +public class SpringUtilities { + /** + * A debugging utility that prints to stdout the component's minimum, preferred, and maximum + * sizes. + */ + public static void printSizes(Component c) { + System.out.println("minimumSize = " + c.getMinimumSize()); + System.out.println("preferredSize = " + c.getPreferredSize()); + System.out.println("maximumSize = " + c.getMaximumSize()); + } + + /** + * Aligns the first rows * cols components of parent in a + * grid. Each component is as big as the maximum preferred width and height of the components. The + * parent is made just big enough to fit them all. + * + * @param rows number of rows + * @param cols number of columns + * @param initialX x location to start the grid at + * @param initialY y location to start the grid at + * @param xPad x padding between cells + * @param yPad y padding between cells + */ + public static void makeGrid( + Container parent, int rows, int cols, int initialX, int initialY, int xPad, int yPad) { + SpringLayout layout; + try { + layout = (SpringLayout) parent.getLayout(); + } catch (ClassCastException exc) { + System.err.println("The first argument to makeGrid must use SpringLayout."); + return; + } + + Spring xPadSpring = Spring.constant(xPad); + Spring yPadSpring = Spring.constant(yPad); + Spring initialXSpring = Spring.constant(initialX); + Spring initialYSpring = Spring.constant(initialY); + int max = rows * cols; + + // Calculate Springs that are the max of the width/height so that all + // cells have the same size. + Spring maxWidthSpring = layout.getConstraints(parent.getComponent(0)).getWidth(); + Spring maxHeightSpring = layout.getConstraints(parent.getComponent(0)).getWidth(); + for (int i = 1; i < max; i++) { + SpringLayout.Constraints cons = layout.getConstraints(parent.getComponent(i)); + + maxWidthSpring = Spring.max(maxWidthSpring, cons.getWidth()); + maxHeightSpring = Spring.max(maxHeightSpring, cons.getHeight()); + } + + // Apply the new width/height Spring. This forces all the + // components to have the same size. + for (int i = 0; i < max; i++) { + SpringLayout.Constraints cons = layout.getConstraints(parent.getComponent(i)); + + cons.setWidth(maxWidthSpring); + cons.setHeight(maxHeightSpring); + } + + // Then adjust the x/y constraints of all the cells so that they + // are aligned in a grid. + SpringLayout.Constraints lastCons = null; + SpringLayout.Constraints lastRowCons = null; + for (int i = 0; i < max; i++) { + SpringLayout.Constraints cons = layout.getConstraints(parent.getComponent(i)); + if (i % cols == 0) { // start of new row + lastRowCons = lastCons; + cons.setX(initialXSpring); + } else { // x position depends on previous component + cons.setX(Spring.sum(lastCons.getConstraint(SpringLayout.EAST), xPadSpring)); + } + + if (i / cols == 0) { // first row + cons.setY(initialYSpring); + } else { // y position depends on previous row + cons.setY(Spring.sum(lastRowCons.getConstraint(SpringLayout.SOUTH), yPadSpring)); + } + lastCons = cons; + } + + // Set the parent's size. + SpringLayout.Constraints pCons = layout.getConstraints(parent); + pCons.setConstraint( + SpringLayout.SOUTH, + Spring.sum(Spring.constant(yPad), lastCons.getConstraint(SpringLayout.SOUTH))); + pCons.setConstraint( + SpringLayout.EAST, + Spring.sum(Spring.constant(xPad), lastCons.getConstraint(SpringLayout.EAST))); + } + + /* Used by makeCompactGrid. */ + private static SpringLayout.Constraints getConstraintsForCell( + int row, int col, Container parent, int cols) { + SpringLayout layout = (SpringLayout) parent.getLayout(); + Component c = parent.getComponent(row * cols + col); + return layout.getConstraints(c); + } + + /** + * Aligns the first rows * cols components of parent in a + * grid. Each component in a column is as wide as the maximum preferred width of the components in + * that column; height is similarly determined for each row. The parent is made just big enough to + * fit them all. + * + * @param rows number of rows + * @param cols number of columns + * @param initialX x location to start the grid at + * @param initialY y location to start the grid at + * @param xPad x padding between cells + * @param yPad y padding between cells + */ + public static void makeCompactGrid( + Container parent, int rows, int cols, int initialX, int initialY, int xPad, int yPad) { + SpringLayout layout; + try { + layout = (SpringLayout) parent.getLayout(); + } catch (ClassCastException exc) { + System.err.println("The first argument to makeCompactGrid must use SpringLayout."); + return; + } + + // Align all cells in each column and make them the same width. + Spring x = Spring.constant(initialX); + for (int c = 0; c < cols; c++) { + Spring width = Spring.constant(0); + for (int r = 0; r < rows; r++) { + width = Spring.max(width, getConstraintsForCell(r, c, parent, cols).getWidth()); + } + for (int r = 0; r < rows; r++) { + SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, cols); + constraints.setX(x); + constraints.setWidth(width); + } + x = Spring.sum(x, Spring.sum(width, Spring.constant(xPad))); + } + + // Align all cells in each row and make them the same height. + Spring y = Spring.constant(initialY); + for (int r = 0; r < rows; r++) { + Spring height = Spring.constant(0); + for (int c = 0; c < cols; c++) { + height = Spring.max(height, getConstraintsForCell(r, c, parent, cols).getHeight()); + } + for (int c = 0; c < cols; c++) { + SpringLayout.Constraints constraints = getConstraintsForCell(r, c, parent, cols); + constraints.setY(y); + constraints.setHeight(height); + } + y = Spring.sum(y, Spring.sum(height, Spring.constant(yPad))); + } + + // Set the parent's size. + SpringLayout.Constraints pCons = layout.getConstraints(parent); + pCons.setConstraint(SpringLayout.SOUTH, y); + pCons.setConstraint(SpringLayout.EAST, x); + } +} diff --git a/src/main/java/hexgui/util/StreamCopy.java b/src/main/java/hexgui/util/StreamCopy.java new file mode 100644 index 0000000..c2ec0fa --- /dev/null +++ b/src/main/java/hexgui/util/StreamCopy.java @@ -0,0 +1,57 @@ +// StreamCopy.java + +package hexgui.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +/** Thread copying the output of one stream to another stream. */ +public class StreamCopy implements Runnable { + /** + * Constructor. + * + * @param verbose Also copy everything to stderr + * @param src Source stream + * @param dest Destination stream + * @param close Close destination after eof in source + */ + public StreamCopy(boolean verbose, InputStream src, OutputStream dest, boolean close) { + m_verbose = verbose; + m_src = src; + m_dest = dest; + m_close = close; + } + + /** Run method. Exceptions caught are written to stderr. */ + public void run() { + try { + byte buffer[] = new byte[1024]; + while (true) { + int n = m_src.read(buffer); + if (n < 0) break; + if (m_verbose) System.err.write(buffer, 0, n); + m_dest.write(buffer, 0, n); + m_dest.flush(); + } + } catch (Throwable e) { + StringUtils.printException(e); + } finally { + if (m_close) { + try { + m_dest.close(); + } catch (IOException e) { + StringUtils.printException(e); + } + } + } + } + + private final boolean m_verbose; + + private final boolean m_close; + + private final InputStream m_src; + + private final OutputStream m_dest; +} diff --git a/src/main/java/hexgui/util/StreamDiscard.java b/src/main/java/hexgui/util/StreamDiscard.java new file mode 100644 index 0000000..23919b4 --- /dev/null +++ b/src/main/java/hexgui/util/StreamDiscard.java @@ -0,0 +1,32 @@ +// StreamDiscard.java + +package hexgui.util; + +import java.io.InputStream; + +/** Thread discarding an output stream. */ +public class StreamDiscard extends Thread { + public StreamDiscard(InputStream src) { + m_src = src; + } + + /** Run method. Exceptions caught are written to stderr. */ + public void run() { + try { + byte buffer[] = new byte[1024]; + while (true) { + int n = m_src.read(buffer); + if (n < 0) break; + if (n == 0) { + // Not sure if this is necessary. + sleep(100); + continue; + } + } + } catch (Throwable e) { + StringUtils.printException(e); + } + } + + private final InputStream m_src; +} diff --git a/src/main/java/hexgui/util/StringUtils.java b/src/main/java/hexgui/util/StringUtils.java new file mode 100644 index 0000000..7204f90 --- /dev/null +++ b/src/main/java/hexgui/util/StringUtils.java @@ -0,0 +1,245 @@ +package hexgui.util; + +import hexgui.hex.HexColor; +import hexgui.hex.HexPoint; +import hexgui.hex.VC; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Vector; + +public final class StringUtils { + /** Capitalize the first word and trim whitespaces. */ + public static String capitalize(String message) { + message = message.trim(); + if (message.equals("")) return message; + StringBuilder buffer = new StringBuilder(message); + char first = buffer.charAt(0); + if (!Character.isUpperCase(first)) buffer.setCharAt(0, Character.toUpperCase(first)); + return buffer.toString(); + } + + /** Check if string is null, empty, or contains only whitespaces. */ + public static boolean isEmpty(String s) { + if (s == null) return true; + for (int i = 0; i < s.length(); ++i) if (!Character.isWhitespace(s.charAt(i))) return false; + return true; + } + + /** Converts all whitespace characters to a single ' '. */ + public static String cleanWhiteSpace(String str) { + StringReader reader = new StringReader(str); + StringBuilder ret = new StringBuilder(); + + boolean white = false; + while (true) { + int c; + try { + c = reader.read(); + } catch (Throwable t) { + System.out.println("Something bad happened!"); + break; + } + + if (c == -1) break; + if (c == ' ' || c == '\n' || c == '\t') { + if (!white) ret.append(" "); + white = true; + } else { + white = false; + ret.append((char) c); + } + } + return ret.toString(); + } + + public static Vector parsePointList(String str, String sep) { + Vector ret = new Vector(); + String cleaned = cleanWhiteSpace(str.trim()); + if (cleaned.length() == 0) return ret; + + String[] pts = cleaned.split(sep); + for (int i = 0; i < pts.length; i++) { + HexPoint p = HexPoint.get(pts[i].trim()); + ret.add(p); + } + return ret; + } + + public static Vector> parseVariation(String str) { + Vector> ret = new Vector>(); + + Vector> pairs = StringUtils.parseStringPairList(str.trim()); + for (int i = 0; i < pairs.size(); ++i) { + HexColor color = (pairs.get(i).first.charAt(0) == 'B') ? HexColor.BLACK : HexColor.WHITE; + HexPoint point = HexPoint.get(pairs.get(i).second); + ret.add(new Pair(color, point)); + } + return ret; + } + + public static Vector parsePointList(String str) { + return parsePointList(str, " "); + } + + public static Vector parseStringList(String str) { + Vector ret = new Vector(); + String cleaned = cleanWhiteSpace(str.trim()); + if (cleaned.length() == 0) return ret; + + String[] strs = cleaned.split(" "); + for (int i = 0; i < strs.length; i++) { + String cur = strs[i].trim(); + ret.add(cur); + } + return ret; + } + + public static Vector> parseStringPairList(String str) { + Vector> ret = new Vector>(); + String cleaned = cleanWhiteSpace(str.trim()); + if (cleaned.length() == 0) return ret; + + String[] strs = cleaned.split(" "); + for (int i = 0; i < strs.length; i += 2) { + String c1 = strs[i].trim(); + String c2 = strs[i + 1].trim(); + ret.add(new Pair(c1, c2)); + } + return ret; + } + + public static Vector parseVCList(String str) { + Vector ret = new Vector(); + String cleaned = cleanWhiteSpace(str.trim()); + if (cleaned.length() == 0) return ret; + + String[] vcs = cleaned.split(" "); + + for (int i = 0, j = 0; i < vcs.length; i += j) { + HexPoint from, to; + HexColor color; + String type = "unknown"; + int moves = 0; + Vector carrier = new Vector(); + Vector stones = new Vector(); + Vector key = new Vector(); + String source = "unknown"; + + try { + color = HexColor.get(vcs[i + 0]); + from = HexPoint.get(vcs[i + 1]); + to = HexPoint.get(vcs[i + 2]); + type = vcs[i + 3]; + + j = 5; + if (!type.equals("softlimit")) { + source = vcs[i + 4]; + + // read carrier set + if (!vcs[i + 5].equals("[")) throw new Throwable("No carrier!"); + + for (j = 6; j < vcs.length; j++) { + if (vcs[i + j].equals("]")) break; + HexPoint p = HexPoint.get(vcs[i + j]); + carrier.add(p); + } + + j++; // skip closing ']' + + // read stone set + if (!vcs[i + j].equals("[")) + throw new Throwable("No stones! Should be '[', got '" + vcs[j] + "'"); + + for (j++; j < vcs.length; j++) { + if (vcs[i + j].equals("]")) break; + HexPoint p = HexPoint.get(vcs[i + j]); + stones.add(p); + } + + j++; // skip closing ']' + + int blah = 0; + if (type.equals("semi")) blah = 1; + for (int k = 0; k < blah; k++, j++) { + HexPoint p = HexPoint.get(vcs[i + j]); + key.add(p); + } + } + + } catch (Throwable t) { + System.out.println("Exception occurred while parsing VC: '" + t.getMessage() + "'"); + return ret; + } + + ret.add(new VC(from, to, color, type, source, moves, carrier, stones, key)); + } + return ret; + } + + public static String reverse(String str) { + StringBuilder ret = new StringBuilder(); + for (int i = str.length() - 1; i >= 0; i--) { + ret.append(str.charAt(i)); + } + return ret.toString(); + } + + /** Split command line into arguments. Allows " for words containing whitespaces. */ + public static String[] splitArguments(String string) { + assert string != null; + ArrayList result = new ArrayList(); + boolean escape = false; + boolean inString = false; + StringBuilder token = new StringBuilder(); + for (int i = 0; i < string.length(); ++i) { + char c = string.charAt(i); + if (c == '"' && !escape) { + if (inString) { + result.add(token.toString()); + token.setLength(0); + } + inString = !inString; + } else if (Character.isWhitespace(c) && !inString) { + if (token.length() > 0) { + result.add(token.toString()); + token.setLength(0); + } + } else token.append(c); + escape = (c == '\\' && !escape); + } + if (token.length() > 0) result.add(token.toString()); + return result.toArray(new String[result.size()]); + } + + /** + * Return a printable error message for an exception. Returns the error message is for instances + * of ErrorMessage or for other exceptions the class name with the exception message appended, if + * not empty. + */ + public static String getErrorMessage(Throwable e) { + String message = e.getMessage(); + boolean hasMessage = !StringUtils.isEmpty(message); + String className = e.getClass().getName(); + String result; + if (e instanceof ErrorMessage) result = message; + else if (hasMessage) result = className + ":\n" + message; + else result = className; + return result; + } + + /** + * Print exception to standard error. Prints the class name and message to standard error. For + * exceptions of type Error or RuntimeException, a stack trace is printed in addition. + * + * @return A slightly differently formatted error message for display in an error dialog. + */ + public static String printException(Throwable exception) { + String result = getErrorMessage(exception); + System.err.println(result); + boolean isSevere = (exception instanceof RuntimeException || exception instanceof Error); + if (isSevere) exception.printStackTrace(); + return result; + } +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/util/package.html b/src/main/java/hexgui/util/package.html old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/util/package.html rename to src/main/java/hexgui/util/package.html diff --git a/src/main/java/hexgui/version/Version.java b/src/main/java/hexgui/version/Version.java new file mode 100644 index 0000000..77c27bf --- /dev/null +++ b/src/main/java/hexgui/version/Version.java @@ -0,0 +1,21 @@ +// ---------------------------------------------------------------------------- +/** */ +// ---------------------------------------------------------------------------- + +package hexgui.version; + +// ---------------------------------------------------------------------------- + +/** + * Version information. Contains the current version number, the svn build number, and the date of + * the build. + */ +public final class Version { + + private Version() {} + + public static String id = "0.10.GIT"; + public static String date = "2020-05-25 03:42"; +} + +// ---------------------------------------------------------------------------- diff --git a/src/hexgui/version/Version.java.in b/src/main/java/hexgui/version/Version.java.in similarity index 100% rename from src/hexgui/version/Version.java.in rename to src/main/java/hexgui/version/Version.java.in diff --git a/src/hexgui/images/back.png b/src/main/resources/hexgui/images/back.png similarity index 100% rename from src/hexgui/images/back.png rename to src/main/resources/hexgui/images/back.png diff --git a/src/hexgui/images/backward10.png b/src/main/resources/hexgui/images/backward10.png similarity index 100% rename from src/hexgui/images/backward10.png rename to src/main/resources/hexgui/images/backward10.png diff --git a/src/hexgui/images/beginning.png b/src/main/resources/hexgui/images/beginning.png similarity index 100% rename from src/hexgui/images/beginning.png rename to src/main/resources/hexgui/images/beginning.png diff --git a/src/hexgui/images/black-24x24.png b/src/main/resources/hexgui/images/black-24x24.png similarity index 100% rename from src/hexgui/images/black-24x24.png rename to src/main/resources/hexgui/images/black-24x24.png diff --git a/src/hexgui/images/cursor-black-setup.png b/src/main/resources/hexgui/images/cursor-black-setup.png similarity index 100% rename from src/hexgui/images/cursor-black-setup.png rename to src/main/resources/hexgui/images/cursor-black-setup.png diff --git a/src/hexgui/images/cursor-black.png b/src/main/resources/hexgui/images/cursor-black.png similarity index 100% rename from src/hexgui/images/cursor-black.png rename to src/main/resources/hexgui/images/cursor-black.png diff --git a/src/hexgui/images/cursor-white-setup.png b/src/main/resources/hexgui/images/cursor-white-setup.png similarity index 100% rename from src/hexgui/images/cursor-white-setup.png rename to src/main/resources/hexgui/images/cursor-white-setup.png diff --git a/src/hexgui/images/cursor-white.png b/src/main/resources/hexgui/images/cursor-white.png similarity index 100% rename from src/hexgui/images/cursor-white.png rename to src/main/resources/hexgui/images/cursor-white.png diff --git a/src/hexgui/images/down.png b/src/main/resources/hexgui/images/down.png similarity index 100% rename from src/hexgui/images/down.png rename to src/main/resources/hexgui/images/down.png diff --git a/src/hexgui/images/end.png b/src/main/resources/hexgui/images/end.png similarity index 100% rename from src/hexgui/images/end.png rename to src/main/resources/hexgui/images/end.png diff --git a/src/hexgui/images/filenew.png b/src/main/resources/hexgui/images/filenew.png similarity index 100% rename from src/hexgui/images/filenew.png rename to src/main/resources/hexgui/images/filenew.png diff --git a/src/hexgui/images/fileopen.png b/src/main/resources/hexgui/images/fileopen.png similarity index 100% rename from src/hexgui/images/fileopen.png rename to src/main/resources/hexgui/images/fileopen.png diff --git a/src/hexgui/images/filesave2.png b/src/main/resources/hexgui/images/filesave2.png similarity index 100% rename from src/hexgui/images/filesave2.png rename to src/main/resources/hexgui/images/filesave2.png diff --git a/src/hexgui/images/forward.png b/src/main/resources/hexgui/images/forward.png similarity index 100% rename from src/hexgui/images/forward.png rename to src/main/resources/hexgui/images/forward.png diff --git a/src/hexgui/images/forward10.png b/src/main/resources/hexgui/images/forward10.png similarity index 100% rename from src/hexgui/images/forward10.png rename to src/main/resources/hexgui/images/forward10.png diff --git a/src/hexgui/images/hexgui-48x48-notrans.png b/src/main/resources/hexgui/images/hexgui-48x48-notrans.png similarity index 100% rename from src/hexgui/images/hexgui-48x48-notrans.png rename to src/main/resources/hexgui/images/hexgui-48x48-notrans.png diff --git a/src/hexgui/images/hexgui.svg b/src/main/resources/hexgui/images/hexgui.svg similarity index 100% rename from src/hexgui/images/hexgui.svg rename to src/main/resources/hexgui/images/hexgui.svg diff --git a/src/hexgui/images/hint-24x24.png b/src/main/resources/hexgui/images/hint-24x24.png similarity index 100% rename from src/hexgui/images/hint-24x24.png rename to src/main/resources/hexgui/images/hint-24x24.png diff --git a/src/hexgui/images/hint.svg b/src/main/resources/hexgui/images/hint.svg similarity index 100% rename from src/hexgui/images/hint.svg rename to src/main/resources/hexgui/images/hint.svg diff --git a/src/hexgui/images/next.png b/src/main/resources/hexgui/images/next.png similarity index 100% rename from src/hexgui/images/next.png rename to src/main/resources/hexgui/images/next.png diff --git a/src/hexgui/images/play.png b/src/main/resources/hexgui/images/play.png old mode 100755 new mode 100644 similarity index 100% rename from src/hexgui/images/play.png rename to src/main/resources/hexgui/images/play.png diff --git a/src/hexgui/images/program-options.png b/src/main/resources/hexgui/images/program-options.png similarity index 100% rename from src/hexgui/images/program-options.png rename to src/main/resources/hexgui/images/program-options.png diff --git a/src/hexgui/images/setup-black.png b/src/main/resources/hexgui/images/setup-black.png similarity index 100% rename from src/hexgui/images/setup-black.png rename to src/main/resources/hexgui/images/setup-black.png diff --git a/src/hexgui/images/setup-white.png b/src/main/resources/hexgui/images/setup-white.png similarity index 100% rename from src/hexgui/images/setup-white.png rename to src/main/resources/hexgui/images/setup-white.png diff --git a/src/hexgui/images/solve.png b/src/main/resources/hexgui/images/solve.png similarity index 100% rename from src/hexgui/images/solve.png rename to src/main/resources/hexgui/images/solve.png diff --git a/src/hexgui/images/stop.png b/src/main/resources/hexgui/images/stop.png similarity index 100% rename from src/hexgui/images/stop.png rename to src/main/resources/hexgui/images/stop.png diff --git a/src/hexgui/images/swap.png b/src/main/resources/hexgui/images/swap.png similarity index 100% rename from src/hexgui/images/swap.png rename to src/main/resources/hexgui/images/swap.png diff --git a/src/hexgui/images/up.png b/src/main/resources/hexgui/images/up.png similarity index 100% rename from src/hexgui/images/up.png rename to src/main/resources/hexgui/images/up.png diff --git a/src/hexgui/images/white-24x24.png b/src/main/resources/hexgui/images/white-24x24.png similarity index 100% rename from src/hexgui/images/white-24x24.png rename to src/main/resources/hexgui/images/white-24x24.png diff --git a/src/hexgui/images/wood.png b/src/main/resources/hexgui/images/wood.png similarity index 100% rename from src/hexgui/images/wood.png rename to src/main/resources/hexgui/images/wood.png