Java tutorial
package org.arcanist.client; import java.awt.Dimension; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.AdjustmentEvent; import java.awt.event.AdjustmentListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionAdapter; import java.awt.event.MouseMotionListener; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStreamWriter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.List; import java.util.TreeMap; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JCheckBoxMenuItem; import javax.swing.JComponent; import javax.swing.JDesktopPane; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JSeparator; import javax.swing.JTextArea; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.event.InternalFrameAdapter; import javax.swing.event.InternalFrameEvent; import org.jdom2.Document; import org.jdom2.Element; import org.jdom2.output.Format; import org.jdom2.output.XMLOutputter; import org.arcanist.client.*; import org.arcanist.server.*; import org.arcanist.util.*; public class ArcanistCCGFrame extends JFrame implements ActionListener, Nerfable { private ArcanistCCGFrame pronoun = this; /** The main game window. */ private JDesktopPane desktop = new JDesktopPane(); /** * Available to Cards and Decks for a dragging glitch workaround. * Table components must activate the table frame on mouseOver. * It has something to do with InternalFrames and confused mouse coordinate systems. */ private JInternalFrame tableFrame = new JInternalFrame("Table", true, //resizable false, //closable true, //maximizable false); //iconifiable /** The table in which everything is draggable. */ private TableComponent tablePane = new TableComponent(); /** The JScrollPane that contains tablePane. */ private JScrollPane myScrollPane; /** Allows other objects to reach the JumboView. */ private JumboView jumboFrame = null; /** Allows other objects to reach the ChatPanel. */ private ChatPanel ChatPanel = new ChatPanel(); /** Allows other objects to reach the ChatFrame. */ private ChatFrame chatFrame = null; /** Suppresses JumboView update when dragging. * A Card changes this value while glued to mouse, * so other Cards know not to react to mouseEntered events. */ private volatile boolean dragging = false; /** Stand-in graphic while moving */ private volatile DragGhost dragGhost = null; /** Master list of cards on table. */ private List<Card> cardArray = new ArrayList<Card>(0); /** Master list of decks on table. */ private List<Deck> deckArray = new ArrayList<Deck>(0); /** Master list of notes on table. */ private List<FloatingNote> noteArray = new ArrayList<FloatingNote>(0); /** Master list of tokens on table. */ private List<Token> tokenArray = new ArrayList<Token>(0); /** Master list of hands on table. */ private List<Hand> handArray = new ArrayList<Hand>(0); /** Master list of card groups on table. */ private List<MassDragger> groupArray = new ArrayList<MassDragger>(0); /** List of objects to notify on scroll events */ private List<HoverListener> hoverListeners = new ArrayList<HoverListener>(); private JMenuItem fileOpenDeckMenuItem = new JMenuItem("Open Deck..."); private JMenuItem fileDeckBuilderMenuItem = new JMenuItem("Build Deck..."); private JMenuItem fileClearMenuItem = new JMenuItem("Clear Table"); private JMenuItem fileConnectMenuItem = new JMenuItem("NetPlay..."); private JMenuItem fileLobbyMenuItem = new JMenuItem("Lobby..."); private JMenuItem fileDisconnectMenuItem = new JMenuItem("Disconnect"); private JMenuItem fileSendStateMenuItem = new JMenuItem("Send State"); private JMenuItem fileLoadStateMenuItem = new JMenuItem("Load State..."); private JMenuItem fileSaveStateMenuItem = new JMenuItem("Save State..."); private JMenuItem fileExitMenuItem = new JMenuItem("Exit"); private JMenuItem settingsAppearanceMenuItem = new JMenuItem("Appearance"); private JMenuItem settingsKeyboardMenuItem = new JMenuItem("Keyboard"); private JMenuItem settingsPathsMenuItem = new JMenuItem("Paths"); private JMenuItem settingsTextOnImagesMenuItem = new JCheckBoxMenuItem("TextOnCards"); private JMenuItem settingsTableSizeMenuItem = new JMenuItem("Table Size..."); private JMenuItem addCounterMenuItem = new JMenuItem("Counter"); private JMenuItem addRollerMenuItem = new JMenuItem("Die Roller"); private JMenuItem addTimerMenuItem = new JMenuItem("Timer"); private JMenuItem addTokenMenuItem = new JMenuItem("Tokens..."); private JMenuItem addNoteMenuItem = new JMenuItem("Floating Note"); private JMenuItem addDiscardMenuItem = new JMenuItem("Discard Pile"); private JMenuItem addHandMenuItem = new JMenuItem("Hand"); private JMenuItem addJumboMenuItem = new JMenuItem("Jumbo Card View"); private JMenuItem helpHelpMenuItem = new JMenuItem("Help"); private JMenuItem helpAboutMenuItem = new JMenuItem("About"); private MouseListener focusOnEnterListener = null; AdjustmentListener hoverwatchListener = null; public ArcanistCCGFrame(String title) { super(title); DatParser newDat = new DatParser(); newDat.setup(this, Prefs.gameloc, Prefs.gamedat, Prefs.gameset); Prefs.textDat = newDat; //Declare menus JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu("File"); // fileOpenDeckMenuItem // fileDeckBuilderMenuItem // fileClearMenuItem // fileConnectMenuItem // fileLobbyMenuItem // fileDisconnectMenuItem // fileSendStateMenuItem // fileLoadStateMenuItem // fileSaveStateMenuItem // fileExitMenuItem JMenu settingsMenu = new JMenu("Settings"); // settingsAppearanceMenuItem // settingsKeyboardMenuItem // settingsPathsMenuItem // settingsTextOnImagesMenuItem // settingsTableSizeMenuItem JMenu addMenu = new JMenu("Add"); // addCounterMenuItem // addRollerMenuItem // addTimerMenuItem // addTokenMenuItem // addNoteMenuItem // addDiscardMenuItem // addHandMenuItem // addJumboMenuItem JMenu helpMenu = new JMenu("Help"); // helpHelpMenuItem // helpAboutMenuItem //Build menus menuBar.add(fileMenu); fileMenu.add(fileOpenDeckMenuItem); fileMenu.add(fileDeckBuilderMenuItem); fileMenu.addSeparator(); fileMenu.add(fileClearMenuItem); fileMenu.add(fileConnectMenuItem); fileMenu.add(fileLobbyMenuItem); fileMenu.add(fileDisconnectMenuItem); fileMenu.addSeparator(); fileMenu.add(fileSendStateMenuItem); fileMenu.add(fileLoadStateMenuItem); fileMenu.add(fileSaveStateMenuItem); fileMenu.addSeparator(); fileMenu.add(fileExitMenuItem); menuBar.add(settingsMenu); settingsMenu.add(settingsAppearanceMenuItem); settingsMenu.add(settingsKeyboardMenuItem); settingsMenu.add(settingsPathsMenuItem); settingsMenu.addSeparator(); settingsMenu.add(settingsTextOnImagesMenuItem); settingsMenu.add(settingsTableSizeMenuItem); menuBar.add(addMenu); addMenu.add(addCounterMenuItem); addMenu.add(addRollerMenuItem); addMenu.add(addTimerMenuItem); addMenu.add(addTokenMenuItem); addMenu.add(new JSeparator()); addMenu.add(addNoteMenuItem); addMenu.add(addDiscardMenuItem); addMenu.add(addHandMenuItem); addMenu.add(new JSeparator()); addMenu.add(addJumboMenuItem); menuBar.add(helpMenu); helpMenu.add(helpHelpMenuItem); helpMenu.add(helpAboutMenuItem); this.setJMenuBar(menuBar); //Declare the table tablePane.setPreferredSize(new Dimension(3000, 3000)); //Put the table inside a scrollable pane myScrollPane = new JScrollPane(tablePane, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); //The default scroll speed is too slow myScrollPane.getHorizontalScrollBar().setUnitIncrement(25); myScrollPane.getVerticalScrollBar().setUnitIncrement(25); desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE); //slow painting otherwise tableFrame.setFrameIcon(null); tableFrame.getContentPane().add(myScrollPane); desktop.add(tableFrame); tableFrame.show(); this.setContentPane(desktop); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); if (Prefs.appIconPositive != null) this.setIconImage(new ImageIcon(Prefs.appIconPositive).getImage()); dragGhost = new DragGhost(this); setConnectedState(false); // Set up listeners Action keyFocusChatAction = new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { if (ChatPanel != null) { if (jumboFrame != null && jumboFrame.hasDockableChild(ChatPanel)) { jumboFrame.switchToChatTab(); } else if (ChatPanel.getParent() != null) { ChatPanel.getInputField().requestFocusInWindow(); } } } }; tableFrame.getActionMap().put(Prefs.ACTION_TABLE_FOCUS_CHAT, keyFocusChatAction); //Begin MultiSelect tablePane.addMouseListener(new MouseAdapter() { MouseMotionListener multiselect; @Override public void mousePressed(MouseEvent e) { if (e.isShiftDown() == true) { tablePane.setSelectionCorner(e.getX(), e.getY()); dragging = true; multiselect = new MouseMotionAdapter() { public void mouseDragged(MouseEvent e) { tablePane.setSelectionCorner(e.getX(), e.getY()); } }; tablePane.addMouseMotionListener(multiselect); } } @Override public void mouseReleased(MouseEvent e) { tablePane.removeMouseMotionListener(multiselect); Rectangle selection = tablePane.getSelection(); if (selection != null && !selection.isEmpty()) { MassDragger newMD = new MassDragger(tablePane.getSelection(), pronoun); newMD.addToTable(); } tablePane.clearSelection(); dragging = false; } }); //End MultiSelect tableFrame.addInternalFrameListener(new InternalFrameAdapter() { @Override public void internalFrameActivated(InternalFrameEvent e) { tableFrame.moveToBack(); } }); fileOpenDeckMenuItem.addActionListener(this); fileDeckBuilderMenuItem.addActionListener(this); fileClearMenuItem.addActionListener(this); fileConnectMenuItem.addActionListener(this); fileLobbyMenuItem.addActionListener(this); fileDisconnectMenuItem.addActionListener(this); fileSendStateMenuItem.addActionListener(this); fileLoadStateMenuItem.addActionListener(this); fileSaveStateMenuItem.addActionListener(this); fileExitMenuItem.addActionListener(this); settingsAppearanceMenuItem.addActionListener(this); settingsKeyboardMenuItem.addActionListener(this); settingsPathsMenuItem.addActionListener(this); settingsTextOnImagesMenuItem.addActionListener(this); settingsTableSizeMenuItem.addActionListener(this); addCounterMenuItem.addActionListener(this); addRollerMenuItem.addActionListener(this); addTimerMenuItem.addActionListener(this); addTokenMenuItem.addActionListener(this); addNoteMenuItem.addActionListener(this); addDiscardMenuItem.addActionListener(this); addHandMenuItem.addActionListener(this); addJumboMenuItem.addActionListener(this); helpHelpMenuItem.addActionListener(this); helpAboutMenuItem.addActionListener(this); //Dragging freaks out if frame isn't selected focusOnEnterListener = new MouseAdapter() { @Override public void mouseEntered(MouseEvent e) { if (getTableFrame().isSelected() == false) { try { getTableFrame().setSelected(true); getTableFrame().moveToBack(); } catch (java.beans.PropertyVetoException exception) { ArcanistCCG.LogManager.write(exception, "Couldn't move frame to back for drag safety."); } } } }; hoverwatchListener = new AdjustmentListener() { @Override public void adjustmentValueChanged(AdjustmentEvent e) { if (hoverListeners.size() == 0) return; JScrollBar src = (JScrollBar) e.getSource(); if (src.getValueIsAdjusting()) return; Rectangle tableView = getTableView(); int hoverCount = hoverListeners.size(); for (int i = 0; i < hoverCount; i++) { HoverListener h = (HoverListener) hoverListeners.get(i); Point offset = h.getHoverOffset(); Rectangle oldBounds = h.getBounds(); Rectangle newBounds = new Rectangle(tableView.x + offset.x, tableView.y + offset.y, oldBounds.width, oldBounds.height); if (tableView.contains(newBounds)) { h.hoverTo(tableView.x + offset.x, tableView.y + offset.y); } else { h.setHoverOffset(oldBounds.x - tableView.x, oldBounds.y - tableView.y); } } } }; myScrollPane.getHorizontalScrollBar().addAdjustmentListener(hoverwatchListener); myScrollPane.getVerticalScrollBar().addAdjustmentListener(hoverwatchListener); updateHotkeys(); loadKeyboard(); } @Override public void actionPerformed(ActionEvent e) { Object source = e.getSource(); if (source == fileOpenDeckMenuItem) { ExtensionFileFilter filter = new ExtensionFileFilter("Text Files (*.txt)", new String[] { ".txt" }); File file = fileChooser(JFileChooser.OPEN_DIALOG, Prefs.homePath + "decks", filter); if (file != null) { Rectangle tableView = getTableView(); String deckString = DeckParser.readDeckStringFromFile(pronoun, file); if (deckString != null) { ArcanistCCG.NetManager.deckAdd(tableView.x, tableView.y, deckString); } // //DeckParser newDeckParser = new DeckParser(pronoun, true, file, tableView.x, tableView.y); // //newDeckParser.startThread(); } } else if (source == fileDeckBuilderMenuItem) { new DeckBuilder(this); } else if (source == fileClearMenuItem) { if (JOptionPane.showInternalConfirmDialog(desktop, "Are you SURE you want to remove EVERYTHING?", "Remove?", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) return; ArcanistCCG.NetManager.tableClear(); } else if (source == fileConnectMenuItem) { new NetPlayFrame(this); } else if (source == fileLobbyMenuItem) { ServerThread st = ArcanistCCG.getServerThread(); if (st == null) { return; } new LobbyFrame(this, st); } else if (source == fileDisconnectMenuItem) { if (JOptionPane.showInternalConfirmDialog(desktop, "Are you SURE you want to disconnect?", "Disconnect?", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) return; ArcanistCCG.NetManager.setClientThread(null); } else if (source == fileSendStateMenuItem) { String state = XmlUtils.saveState(pronoun); ArcanistCCG.NetManager.tableStateLoad(state, true); } else if (source == fileLoadStateMenuItem) { ExtensionFileFilter filter = new ExtensionFileFilter("XML Files (*.xml)", new String[] { ".xml" }); File file = fileChooser(JFileChooser.OPEN_DIALOG, Prefs.homePath + "states", filter); if (file != null) { Document doc = XmlUtils.readXML(file); if (doc == null) { ArcanistCCG.NetManager.notify(ChatPanel.STYLE_ERROR, "--Error loading state. Check the log for details.--"); } else { boolean neutralView = false; int userinput = JOptionPane.showInternalConfirmDialog(desktop, "Should locally-visible things (cards, hands, etc) in this saved state be hidden?\nOtherwise everyone will see whatever was visible at the time it was saved.", "Neutral View?", JOptionPane.YES_NO_OPTION); if (userinput == JOptionPane.YES_OPTION) neutralView = true; XMLOutputter outputter = new XMLOutputter(Format.getRawFormat()); String stateString = outputter.outputString(doc); ArcanistCCG.NetManager.tableStateLoad(stateString, neutralView); } } } else if (source == fileSaveStateMenuItem) { ExtensionFileFilter filter = new ExtensionFileFilter("XML Files (*.xml)", new String[] { ".xml" }); File file = fileChooser(JFileChooser.SAVE_DIALOG, Prefs.homePath + "states", filter); if (file != null) { XmlUtils.saveState(pronoun, file.getAbsolutePath()); } } else if (source == fileExitMenuItem) { System.exit(0); } else if (source == settingsAppearanceMenuItem) { new PrefsAppearanceFrame(this); } else if (source == settingsKeyboardMenuItem) { new PrefsKeyboardFrame(this); } else if (source == settingsPathsMenuItem) { new PrefsPathsFrame(this); } else if (source == settingsTextOnImagesMenuItem) { Prefs.textOnImages = ((JCheckBoxMenuItem) settingsTextOnImagesMenuItem).getState(); if (Prefs.textOnImages == true) updateOverlayText(); tablePane.repaint(); } else if (source == settingsTableSizeMenuItem) { Dimension prevSize = tablePane.getPreferredSize(); int userinput; int tableWidth = prevSize.width, tableHeight = prevSize.height; JPanel sizePanel = new JPanel(); JTextField widthField = new JTextField(new NumberDocument(), tableWidth + "", 3); //widthField.setPreferredSize(new Dimension(40,20)); sizePanel.add(widthField); sizePanel.add(new JLabel("x")); JTextField heightField = new JTextField(new NumberDocument(), tableHeight + "", 3); //heightField.setPreferredSize(new Dimension(40,20)); sizePanel.add(heightField); Object[] options = { "OK", "Cancel" }; do { userinput = JOptionPane.showInternalOptionDialog(desktop, sizePanel, "Table Size (" + tableWidth + "x" + tableHeight + ")", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]); if (userinput == 1) return; //User hit 'cancel' else { try { tableWidth = Integer.parseInt(widthField.getText()); tableHeight = Integer.parseInt(heightField.getText()); } catch (NumberFormatException exception) { return; } } } while (tableWidth <= 0 || tableHeight <= 0); ArcanistCCG.NetManager.tableResize(tableWidth, tableHeight); // //resizeTable(tableWidth, tableHeight); } else if (source == addCounterMenuItem) { new CounterFrame(pronoun); } else if (source == addRollerMenuItem) { new DieRollerFrame(pronoun); } else if (source == addTimerMenuItem) { new TimerFrame(pronoun); } else if (source == addTokenMenuItem) { new TokensFrame(pronoun); } else if (source == addNoteMenuItem) { Rectangle tableView = getTableView(); ArcanistCCG.NetManager.noteAdd(tableView.x, tableView.y, " Note "); } else if (source == addDiscardMenuItem) { Rectangle tableView = getTableView(); ArcanistCCG.NetManager.deckAdd(tableView.x, tableView.y, false, true, true, false, 'D', ""); } else if (source == addHandMenuItem) { Rectangle tableView = getTableView(); ArcanistCCG.NetManager.handAdd(tableView.x, tableView.y, "Hand", Hand.DEFAULT_WIDTH, Hand.DEFAULT_HEIGHT); } else if (source == addJumboMenuItem) { if (addJumboMenuItem.isEnabled()) { //It's still clickable when disabled! createJumboFrame(); } } else if (source == helpHelpMenuItem) { new HelpWindow(this); } else if (source == helpAboutMenuItem) { String message = "ArcanistCCG " + ArcanistCCG.VERSION + "\n"; message += "Copyright (C) 2011 David Millis\n"; message += ArcanistCCG.WEBSITE + "\n\n"; message += "ArcanistCCG comes with ABSOLUTELY NO WARRANTY.\n"; message += "Distributed under the terms of the GNU General Public License."; message += "\n\nIncludes rotation code based on Image Processing Filters\n"; message += "Copyright (C) Jerry Huxtable 1998\n"; message += "http://www.lhlabs.com"; JTextArea aboutTxt = new JTextArea(message); aboutTxt.setEditable(false); JScrollPane aboutScrollPane = new JScrollPane(aboutTxt); aboutScrollPane.setPreferredSize(new Dimension(350, 175)); JOptionPane.showInternalMessageDialog(desktop, aboutScrollPane, "About", JOptionPane.PLAIN_MESSAGE); } } public void guiInterrupted() { for (int i = getCardCount() - 1; i >= 0; i--) { getCard(i).guiInterrupted(); } for (int i = getDeckCount() - 1; i >= 0; i--) { getDeck(i).guiInterrupted(); } for (int i = getNoteCount() - 1; i >= 0; i--) { getNote(i).guiInterrupted(); } for (int i = getTokenCount() - 1; i >= 0; i--) { getToken(i).guiInterrupted(); } for (int i = getHandCount() - 1; i >= 0; i--) { getHand(i).guiInterrupted(); } for (int i = getGroupCount() - 1; i >= 0; i--) { getGroup(i).guiInterrupted(); } } /** * Toggles user interaction within this window. * Various menu items and all nerfable jinternalframes are affected. */ @Override public void setNerfed(boolean b) { //button mnemonics will still work tablePane.setNerfed(b); fileOpenDeckMenuItem.setEnabled(!b); fileClearMenuItem.setEnabled(!b); fileLoadStateMenuItem.setEnabled(!b); settingsTableSizeMenuItem.setEnabled(!b); addTokenMenuItem.setEnabled(!b); addNoteMenuItem.setEnabled(!b); addDiscardMenuItem.setEnabled(!b); addHandMenuItem.setEnabled(!b); JInternalFrame[] frames = desktop.getAllFrames(); for (int i = 0; i < frames.length; i++) { if (frames[i] instanceof Nerfable) ((Nerfable) frames[i]).setNerfed(b); } guiInterrupted(); } public void setDragging(boolean b) { dragging = b; } public boolean isDragging() { return dragging; } public DragGhost getDragGhost() { return dragGhost; } public DragGhost addDragGhost() { if (dragGhost.getParent() == null) tablePane.add(dragGhost, Prefs.ghostLayer, 0); return dragGhost; } public void removeDragGhost() { tablePane.remove(dragGhost); } public void setJumboFrame(JumboView f) { jumboFrame = f; } public JumboView getJumboFrame() { return jumboFrame; } public MouseListener getFocusOnEnterListener() { return focusOnEnterListener; } public Card getCard(int index) { return cardArray.get(index); } public Deck getDeck(int index) { return deckArray.get(index); } public FloatingNote getNote(int index) { return noteArray.get(index); } public Token getToken(int index) { return tokenArray.get(index); } public Hand getHand(int index) { return handArray.get(index); } public MassDragger getGroup(int index) { return groupArray.get(index); } public void addCard(Card c) { cardArray.add(c); } public void addDeck(Deck d) { deckArray.add(d); } public void addNote(FloatingNote n) { noteArray.add(n); } public void addToken(Token t) { tokenArray.add(t); } public void addHand(Hand h) { handArray.add(h); } public void addGroup(MassDragger g) { groupArray.add(g); } public boolean hasCard(Card c) { return cardArray.contains(c); } public boolean hasDeck(Deck d) { return deckArray.contains(d); } public boolean hasNote(FloatingNote n) { return noteArray.contains(n); } public boolean hasToken(Token t) { return tokenArray.contains(t); } public boolean hasHand(Hand h) { return handArray.contains(h); } public boolean hasGroup(MassDragger g) { return groupArray.contains(g); } public void removeCard(int index) { cardArray.remove(index); } public void removeDeck(int index) { deckArray.remove(index); } public void removeNote(int index) { noteArray.remove(index); } public void removeToken(int index) { tokenArray.remove(index); } public void removeHand(int index) { handArray.remove(index); } public void removeGroup(int index) { groupArray.remove(index); } public int getCardCount() { return cardArray.size(); } public int getDeckCount() { return deckArray.size(); } public int getNoteCount() { return noteArray.size(); } public int getTokenCount() { return tokenArray.size(); } public int getHandCount() { return handArray.size(); } public int getGroupCount() { return groupArray.size(); } public int getCardIndex(Card c) { int id = c.getId(); for (int i = getCardCount() - 1; i >= 0; i--) { if (getCard(i).getId() == id) return i; } return -1; } public int getDeckIndex(Deck d) { int id = d.getId(); for (int i = getDeckCount() - 1; i >= 0; i--) { if (getDeck(i).getId() == id) return i; } return -1; } public int getNoteIndex(FloatingNote n) { int id = n.getId(); for (int i = getNoteCount() - 1; i >= 0; i--) { if (getNote(i).getId() == id) return i; } return -1; } public int getTokenIndex(Token t) { int id = t.getId(); for (int i = getTokenCount() - 1; i >= 0; i--) { if (getToken(i).getId() == id) return i; } return -1; } public int getHandIndex(Hand h) { int id = h.getId(); for (int i = getHandCount() - 1; i >= 0; i--) { if (getHand(i).getId() == id) return i; } return -1; } public int getGroupIndex(MassDragger g) { for (int i = getGroupCount() - 1; i >= 0; i--) { if (getGroup(i) == g) return i; } return -1; } public JDesktopPane getDesktop() { return desktop; } public JInternalFrame getTableFrame() { return tableFrame; } public TableComponent getTablePane() { return tablePane; } public JMenuItem getAddJumboMenuItem() { return addJumboMenuItem; } public ChatPanel getChatPanel() { return ChatPanel; } public void setChatPanel(ChatPanel c) { ChatPanel = c; } public void addHoverListener(HoverListener h) { if (!hoverListeners.contains(h)) hoverListeners.add(h); } public void removeHoverListener(HoverListener h) { hoverListeners.remove(h); } /** * Sets a new size for the table. * Existing objects and view will be moved centerward * from the top-left, so added space appears at the border. */ public void resizeTable(int tableWidth, int tableHeight) { guiInterrupted(); Dimension prevSize = tablePane.getPreferredSize(); tablePane.setPreferredSize(new Dimension(tableWidth, tableHeight)); int fudgeX = tableWidth / 2 - prevSize.width / 2; int fudgeY = tableHeight / 2 - prevSize.height / 2; int i = 0; for (i = getCardCount() - 1; i >= 0; i--) { Card tmpCard = getCard(i); tmpCard.setLocation(tmpCard.getX() + fudgeX, tmpCard.getY() + fudgeY); } for (i = getDeckCount() - 1; i >= 0; i--) { Deck tmpDeck = getDeck(i); tmpDeck.setLocation(tmpDeck.getX() + fudgeX, tmpDeck.getY() + fudgeY); } for (i = getNoteCount() - 1; i >= 0; i--) { FloatingNote tmpNote = getNote(i); tmpNote.setLocation(tmpNote.getX() + fudgeX, tmpNote.getY() + fudgeY); } for (i = getTokenCount() - 1; i >= 0; i--) { Token tmpToken = getToken(i); tmpToken.setLocation(tmpToken.getX() + fudgeX, tmpToken.getY() + fudgeY); } for (i = getHandCount() - 1; i >= 0; i--) { getHand(i).setLocation((getHand(i).getX() + fudgeX), (getHand(i).getY() + fudgeY)); } for (i = getGroupCount() - 1; i >= 0; i--) { getGroup(i).setLocation((getGroup(i).getX() + fudgeX), (getGroup(i).getY() + fudgeY)); } tablePane.revalidate(); //myScrollPane.revalidate(); Rectangle newView = myScrollPane.getViewport().getViewRect(); newView.x += fudgeX; newView.y += fudgeY; tablePane.scrollRectToVisible(newView); tablePane.repaint(); } /** * Returns visible region in table coords. */ public Rectangle getTableView() { return myScrollPane.getViewport().getViewRect(); } /** * Scrolls the table view to the center. */ public void centerTableView() { Rectangle newView = myScrollPane.getViewport().getViewRect(); newView.x += (tablePane.getSize().width - newView.width) / 2; newView.y += (tablePane.getSize().height - newView.height) / 2; tablePane.scrollRectToVisible(newView); } /** * Removes all objects from table. * Also clears the image cache, and empties the used ids list. */ public void clearTable() { for (int i = getCardCount() - 1; i >= 0; i--) { getCard(i).removeFromTable(); } for (int i = getDeckCount() - 1; i >= 0; i--) { getDeck(i).removeFromTable(); } for (int i = getTokenCount() - 1; i >= 0; i--) { getToken(i).removeFromTable(); } for (int i = getNoteCount() - 1; i >= 0; i--) { getNote(i).removeFromTable(); } for (int i = getHandCount() - 1; i >= 0; i--) { getHand(i).removeFromTable(); } for (int i = getGroupCount() - 1; i >= 0; i--) { getGroup(i).removeFromTable(); } tablePane.repaint(); ArcanistCCG.setUsedIds(null); Prefs.Cache.setup(); } /** * Unuses all objects on the table. */ public void unuseEverything() { for (int i = getCardCount() - 1; i >= 0; i--) { getCard(i).setInUse(false); } for (int i = getDeckCount() - 1; i >= 0; i--) { getDeck(i).setInUse(false); } for (int i = getTokenCount() - 1; i >= 0; i--) { getToken(i).setInUse(false); } for (int i = getNoteCount() - 1; i >= 0; i--) { getNote(i).setInUse(false); } for (int i = getHandCount() - 1; i >= 0; i--) { getHand(i).setInUse(false); } for (int i = getGroupCount() - 1; i >= 0; i--) { getGroup(i).setInUse(false); } } /** * Toggles visibility of menu items depending on connected state. * NetPlay/Disconnect/Lobby/SendState * * @param state true if connected, false if not connected */ public void setConnectedState(boolean state) { if (state == false) { fileConnectMenuItem.setVisible(true); fileConnectMenuItem.setEnabled(true); fileDisconnectMenuItem.setVisible(false); fileDisconnectMenuItem.setEnabled(false); fileSendStateMenuItem.setVisible(false); fileSendStateMenuItem.setEnabled(false); fileLobbyMenuItem.setVisible(false); fileLobbyMenuItem.setEnabled(false); } else { fileConnectMenuItem.setVisible(false); fileConnectMenuItem.setEnabled(false); fileDisconnectMenuItem.setVisible(true); fileDisconnectMenuItem.setEnabled(true); fileSendStateMenuItem.setVisible(true); fileSendStateMenuItem.setEnabled(true); ServerThread st = ArcanistCCG.getServerThread(); fileLobbyMenuItem.setVisible(st != null); fileLobbyMenuItem.setEnabled(st != null); } } /** * Clears objects' hotkeys and reapplies current global ones. */ public void updateHotkeys() { InputMap tableInputMap = tableFrame.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); KeyStroke[] tableInputKeys = tableInputMap.keys(); if (tableInputKeys != null) { for (int i = 0; i < tableInputKeys.length; i++) { Object o = tableInputMap.get(tableInputKeys[i]); if (o == Prefs.ACTION_TABLE_FOCUS_CHAT) tableInputMap.remove(tableInputKeys[i]); } } synchronized (Prefs.hotkeyLock) { Object tmpStroke = Prefs.hotkeyStrokeMap.get(Prefs.ACTION_TABLE_FOCUS_CHAT); if (tmpStroke != null) tableInputMap.put((KeyStroke) tmpStroke, Prefs.ACTION_TABLE_FOCUS_CHAT); } ChatPanel chatPanel = getChatPanel(); if (chatPanel != null) chatPanel.updateHotkeys(); for (int i = getCardCount() - 1; i >= 0; i--) { getCard(i).updateHotkeys(); } for (int i = getDeckCount() - 1; i >= 0; i--) { getDeck(i).updateHotkeys(); } for (int i = getHandCount() - 1; i >= 0; i--) { getHand(i).updateHotkeys(); } } /** * Applies overlay text to cards in play. */ public void updateOverlayText() { for (int i = getCardCount() - 1; i >= 0; i--) { getCard(i).updateOverlayText(); } } /** * Creates and displays a JumboView. */ public JumboView createJumboFrame() { if (jumboFrame != null) return jumboFrame; jumboFrame = new JumboView(this); return jumboFrame; } /** * Displays a file chooser dialog. * When saving, a file filter was active, the chosen file * doesn't exist, and the filter doesn't accept the proposed * file, the filter's primary suffix will be appended to the * filename. * * @param chooserType JFileChooser.OPEN_DIALOG or JFileChooser.SAVE_DIALOG * @param path initial directory * @param filter a file filter, or null * @return the chosen file, or null */ public File fileChooser(int chooserType, String path, ExtensionFileFilter filter) { if (chooserType != JFileChooser.OPEN_DIALOG && chooserType != JFileChooser.SAVE_DIALOG) return null; JFileChooser chooser = new JFileChooser(path); chooser.setDialogType(chooserType); if (filter != null) chooser.addChoosableFileFilter(filter); //int status = chooser.showOpenDialog(desktop); int status = chooser.showDialog(desktop, null); if (status != JFileChooser.APPROVE_OPTION) return null; File result = chooser.getSelectedFile(); if (chooserType == JFileChooser.SAVE_DIALOG) { if (filter != null) { if (!result.exists() && chooser.getFileFilter() == filter && !filter.accept(result)) { result = new File(result.getAbsolutePath() + filter.getPrimarySuffix()); } } if (result.exists()) { if (JOptionPane.showInternalConfirmDialog(desktop, result.getName() + " already exists. Overwrite?", "Overwrite?", JOptionPane.YES_NO_OPTION) != JOptionPane.YES_OPTION) { return null; } } } return result; } /** * Loads appearance settings. */ public boolean loadAppearance() { File appearanceFile = new File(Prefs.appearanceFilePath); if (!appearanceFile.exists()) return false; ArcanistCCG.LogManager.write(LogManager.INFO_LEVEL, "Loading appearance settings (" + Prefs.appearanceFilePath + ")."); Document doc = XmlUtils.readXML(appearanceFile); if (doc == null) return false; Element rootNode = doc.getRootElement(); Element tableElement = rootNode.getChild("table"); if (tableElement != null) { Element mainBgColorElement = tableElement.getChild("main-bgcolor"); Prefs.mainBgColor = XmlUtils.parseRGBAttribs(mainBgColorElement, null); if (Prefs.mainBgColor != null) pronoun.getContentPane().setBackground(Prefs.mainBgColor); Element tableBgColorElement = tableElement.getChild("table-bgcolor"); Prefs.tableBgColor = XmlUtils.parseRGBAttribs(tableBgColorElement, null); if (Prefs.tableBgColor != null) tablePane.getParent().setBackground(Prefs.tableBgColor); Element tableBgImageElement = tableElement.getChild("table-bgimage"); if (tableBgImageElement != null) { try { Prefs.tableBgTiled = XmlUtils .parseString(tableBgImageElement.getAttributeValue("tiled"), "false").equals("true"); Prefs.tableBgImage = tableBgImageElement.getText(); // Defaults to "" if (Prefs.tableBgImage.length() > 0) { tablePane.setBackgroundImage(Prefs.tableBgImage, Prefs.tableBgTiled); } else { tablePane.setBackgroundImage(null, Prefs.tableBgTiled); } } catch (IOException f) { JOptionPane.showInternalMessageDialog(desktop, "Couldn't load background image.", "Error", JOptionPane.PLAIN_MESSAGE); Prefs.tableBgImage = ""; Prefs.tableBgTiled = false; } } Element gridElement = tableElement.getChild("table-grid"); tablePane.setGridSnap(Math.max(1, XmlUtils.parseInt(gridElement.getAttributeValue("value"), 1))); } Element playerElement = rootNode.getChild("player"); if (playerElement != null) { Element chatAliasElement = playerElement.getChild("chat-alias"); if (chatAliasElement != null) { String savedAlias = chatAliasElement.getText(); String prevAlias = ArcanistCCG.NetManager.getPlayerAlias(); Prefs.setPlayerAlias(savedAlias); String newAlias = ArcanistCCG.NetManager.getPlayerAlias(); if (prevAlias.equals(newAlias) == false) { ArcanistCCG.NetManager.chatNotice(ChatPanel.STYLE_NOTICE2, "--" + prevAlias + " set alias to " + newAlias + "--"); } } Element chatTimestmpsElement = playerElement.getChild("chat-timestamps"); Prefs.chatTimestamps = XmlUtils.parseString(chatTimestmpsElement.getAttributeValue("value"), "false") .equals("true"); } boolean tableSized = false; Element windowsElement = rootNode.getChild("windows"); if (windowsElement != null) { List<Element> windowList = windowsElement.getChildren("window"); Iterator<Element> it = windowList.iterator(); while (it.hasNext()) { Element windowElement = it.next(); JInternalFrame newFrame = null; String windowType = windowElement.getAttributeValue("type"); if (windowType == null) continue; else if (windowType.equals("table")) { newFrame = tableFrame; tableSized = true; } else if (windowType.equals("jumbo")) { jumboFrame = createJumboFrame(); newFrame = jumboFrame; } else if (windowType.equals("chat")) { if (chatFrame == null) chatFrame = new ChatFrame(pronoun); newFrame = chatFrame; } else if (windowType.equals("tokens")) { newFrame = new TokensFrame(pronoun); } else if (windowType.equals("counter")) { newFrame = new CounterFrame(pronoun); String counterName = XmlUtils.parseString(windowElement.getChildText("name"), "Amount"); String counterValue = XmlUtils.parseString(windowElement.getAttributeValue("value"), "0"); ((CounterFrame) newFrame).setName(counterName); ((CounterFrame) newFrame).setValue(counterValue); } else if (windowType.equals("dieroller")) { newFrame = new DieRollerFrame(pronoun); String dieCount = XmlUtils.parseString(windowElement.getAttributeValue("count"), "1"); String dieSides = XmlUtils.parseString(windowElement.getAttributeValue("sides"), "20"); ((DieRollerFrame) newFrame).setDice(dieCount, dieSides); } else if (windowType.equals("timer")) { newFrame = new TimerFrame(pronoun); ((TimerFrame) newFrame) .setCountdown(XmlUtils.parseInt(windowElement.getAttributeValue("countdown"), 0)); } else continue; if (newFrame != null) { Rectangle newBounds = new Rectangle(); newBounds.x = XmlUtils.parseInt(windowElement.getAttributeValue("x"), 0); newBounds.y = XmlUtils.parseInt(windowElement.getAttributeValue("y"), 0); newBounds.width = XmlUtils.parseInt(windowElement.getAttributeValue("w"), 100); newBounds.height = XmlUtils.parseInt(windowElement.getAttributeValue("h"), 100); newFrame.reshape(newBounds.x, newBounds.y, newBounds.width, newBounds.height); } } } return tableSized; } /** * Saves appearance settings. */ public void saveAppearance(boolean saveWindowLayout) { ArcanistCCG.LogManager.write(LogManager.INFO_LEVEL, "Saving appearance settings."); StringBuffer buf = new StringBuffer(); buf.append("<?xml version=\"1.0\"?>\n"); buf.append("<appearance version=\"1\">\n"); buf.append(" <table>\n"); if (Prefs.mainBgColor != null) { buf.append(" <main-bgcolor"); buf.append(" r=\"").append(Prefs.mainBgColor.getRed()).append("\""); buf.append(" g=\"").append(Prefs.mainBgColor.getGreen()).append("\""); buf.append(" b=\"").append(Prefs.mainBgColor.getBlue()).append("\""); buf.append(" />\n"); } if (Prefs.tableBgColor != null) { buf.append(" <table-bgcolor"); buf.append(" r=\"").append(Prefs.tableBgColor.getRed()).append("\""); buf.append(" g=\"").append(Prefs.tableBgColor.getGreen()).append("\""); buf.append(" b=\"").append(Prefs.tableBgColor.getBlue()).append("\""); buf.append(" />\n"); } if (Prefs.tableBgImage.length() > 0) { buf.append(" <table-bgimage"); buf.append(" tiled=\"").append(Prefs.tableBgTiled).append("\""); buf.append(">"); buf.append("<![CDATA[").append(Prefs.tableBgImage).append("]]>"); buf.append("</table-bgimage>\n"); } if (tablePane.getGridSnap() > 1) { buf.append(" <table-grid"); buf.append(" value=\"").append(tablePane.getGridSnap()).append("\""); buf.append(" />\n"); } buf.append(" </table>\n"); buf.append(" <player>\n"); if (Prefs.savedAlias.length() > 0) { buf.append(" <chat-alias>"); buf.append("<![CDATA[").append(Prefs.savedAlias).append("]]>"); buf.append("</chat-alias>\n"); } if (Prefs.chatTimestamps != false) { buf.append(" <chat-timestamps"); buf.append(" value=\"").append(Prefs.chatTimestamps).append("\""); buf.append(" />\n"); } buf.append(" </player>\n"); if (saveWindowLayout) { JInternalFrame[] windows = desktop.getAllFrames(); buf.append(" <windows>\n"); for (int i = 0; i < windows.length; i++) { Rectangle bounds = windows[i].getNormalBounds(); StringBuffer boundsBuf = new StringBuffer(); boundsBuf.append(" x=\"").append(bounds.x).append("\""); boundsBuf.append(" y=\"").append(bounds.y).append("\""); boundsBuf.append(" w=\"").append(bounds.width).append("\""); boundsBuf.append(" h=\"").append(bounds.height).append("\""); if (windows[i].getTitle().equals("Table")) { buf.append(" <window type=\"table\"").append(boundsBuf).append(" />\n"); } else if (windows[i].getClass() == JumboView.class) { buf.append(" <window type=\"jumbo\"").append(boundsBuf).append(" />\n"); } else if (windows[i].getClass() == ChatFrame.class) { buf.append(" <window type=\"chat\"").append(boundsBuf).append(" />\n"); } else if (windows[i].getClass() == TokensFrame.class) { buf.append(" <window type=\"tokens\"").append(boundsBuf).append(" />\n"); } else if (windows[i].getClass() == CounterFrame.class) { buf.append(" <window type=\"counter\"").append(boundsBuf); buf.append(" value=\"").append(((CounterFrame) windows[i]).getValue()).append("\""); buf.append(">"); buf.append("<name><![CDATA[").append(((CounterFrame) windows[i]).getName()) .append("]]></name>"); buf.append("</window>\n"); } else if (windows[i].getClass() == DieRollerFrame.class) { buf.append(" <window type=\"dieroller\"").append(boundsBuf); buf.append(" count=\"").append(((DieRollerFrame) windows[i]).getDice()).append("\""); buf.append(" sides=\"").append(((DieRollerFrame) windows[i]).getDie()).append("\""); buf.append(" />\n"); } else if (windows[i].getClass() == TimerFrame.class) { buf.append(" <window type=\"timer\"").append(boundsBuf); buf.append(" countdown=\"").append(((TimerFrame) windows[i]).getCountdown()).append("\""); buf.append(" />\n"); } } buf.append(" </windows>\n"); } buf.append("</appearance>\n"); BufferedWriter outFile = null; try { OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(Prefs.appearanceFilePath), "UTF-8"); outFile = new BufferedWriter(fw); outFile.write(buf.toString()); } catch (IOException e) { ArcanistCCG.LogManager.write(e, "Couldn't save appearance settings."); } finally { try { if (outFile != null) outFile.close(); } catch (IOException e) { } } } /** * Loads keyboard settings. */ public void loadKeyboard() { File keyboardFile = new File(Prefs.keyboardFilePath); if (!keyboardFile.exists()) return; ArcanistCCG.LogManager.write(LogManager.INFO_LEVEL, "Loading keyboard settings (" + Prefs.keyboardFilePath + ")."); Map<String, KeyStroke> newStrokeMap = new HashMap<String, KeyStroke>(); Map<String, String> newArgMap = new HashMap<String, String>(); Iterator<Element> it = null; Document doc = XmlUtils.readXML(keyboardFile); if (doc == null) return; Element rootNode = doc.getRootElement(); Element hotkeysElement = rootNode.getChild("hotkeys"); if (hotkeysElement == null) return; List<Element> hotkeyList = hotkeysElement.getChildren("hotkey"); it = hotkeyList.iterator(); while (it.hasNext()) { Element hotkeyElement = it.next(); String hotkeyAction = hotkeyElement.getChildText("action"); String hotkeyString = hotkeyElement.getChildText("keystroke"); if (hotkeyAction == null || hotkeyAction.length() == 0 || hotkeyString == null) continue; KeyStroke hotkeyStroke = null; // Blank hotkeyString means null if (hotkeyString.length() > 0) { hotkeyStroke = KeyStroke.getKeyStroke(hotkeyString); if (hotkeyStroke == null) { ArcanistCCG.LogManager.write(LogManager.ERROR_LEVEL, "Invalid keystroke for keyboard setting: '" + hotkeyAction + "'."); continue; } } newStrokeMap.put(hotkeyAction, hotkeyStroke); String hotkeyArg = hotkeyElement.getChildText("arg"); if (hotkeyArg == null || hotkeyArg.length() == 0) continue; newArgMap.put(hotkeyAction, hotkeyArg); } if (newStrokeMap.size() == 0) return; synchronized (Prefs.hotkeyLock) { for (String hotkeyAction : newStrokeMap.keySet()) { KeyStroke hotkeyStroke = newStrokeMap.get(hotkeyAction); if (Prefs.hotkeyStrokeMap.containsKey(hotkeyAction)) { Prefs.hotkeyStrokeMap.put(hotkeyAction, hotkeyStroke); } else { ArcanistCCG.LogManager.write(LogManager.ERROR_LEVEL, "Unrecognied keyboard setting action: '" + hotkeyAction + "'."); continue; } if (newArgMap.containsKey(hotkeyAction)) { String hotkeyArg = newArgMap.get(hotkeyAction); if (Prefs.hotkeyArgMap.containsKey(hotkeyAction)) { Prefs.hotkeyArgMap.put(hotkeyAction, hotkeyArg); } else { ArcanistCCG.LogManager.write(LogManager.ERROR_LEVEL, "Unexpected keyboard setting arg for action: '" + hotkeyAction + "'."); continue; } } } } updateHotkeys(); } /** * Saves keyboard settings. */ public void saveKeyboard() { ArcanistCCG.LogManager.write(LogManager.INFO_LEVEL, "Saving keyboard settings."); StringBuffer buf = new StringBuffer(); synchronized (Prefs.hotkeyLock) { Iterator<Map.Entry<String, KeyStroke>> it = new TreeMap<String, KeyStroke>(Prefs.hotkeyStrokeMap) .entrySet().iterator(); buf.append("<?xml version=\"1.0\"?>\n"); buf.append("<keyboard version=\"1\">\n"); buf.append(" <hotkeys>\n"); while (it.hasNext()) { Map.Entry<String, KeyStroke> entry = it.next(); String hotkeyAction = entry.getKey(); String hotkeyString = ""; String hotkeyArg = Prefs.hotkeyArgMap.get(hotkeyAction); if (hotkeyArg == null) hotkeyArg = ""; if (entry.getValue() != null) hotkeyString = XmlUtils.keyStroke2String((KeyStroke) entry.getValue()); if (hotkeyString == null || ((String) hotkeyString).indexOf("unknown") != -1) hotkeyString = ""; buf.append(" <hotkey>\n"); buf.append(" <action>").append(hotkeyAction).append("</action>\n"); buf.append(" <keystroke><![CDATA[").append(hotkeyString).append("]]></keystroke>\n"); if (hotkeyArg != null) { buf.append(" <arg><![CDATA[").append(hotkeyArg).append("]]></arg>\n"); } buf.append(" </hotkey>\n"); } buf.append(" </hotkeys>\n"); buf.append("</keyboard>\n"); } BufferedWriter outFile = null; try { OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(Prefs.keyboardFilePath), "UTF-8"); outFile = new BufferedWriter(fw); outFile.write(buf.toString()); outFile.close(); } catch (IOException e) { ArcanistCCG.LogManager.write(e, "Couldn't save keyboard settings."); } finally { try { if (outFile != null) outFile.close(); } catch (IOException e) { } } } }