/* * Copyright (c) 2014 tabletoptool.com team. * All rights reserved. This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0 * which accompanies this distribution, and is available at * http://www.gnu.org/licenses/gpl.html * * Contributors: * rptools.com team - initial implementation * tabletoptool.com team - further development */ package com.t3.client.ui; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.EventQueue; import java.awt.GraphicsConfiguration; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.IllegalComponentStateException; import java.awt.Image; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.awt.event.WindowListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Observable; import java.util.Observer; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.BorderFactory; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.border.BevelBorder; import javax.swing.filechooser.FileFilter; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.collections4.map.LinkedMap; import org.apache.log4j.Logger; import org.xml.sax.SAXException; import com.jidesoft.docking.DefaultDockableHolder; import com.jidesoft.docking.DockableFrame; import com.t3.AppEvent; import com.t3.AppEventListener; import com.t3.MD5Key; import com.t3.client.AppActions; import com.t3.client.AppActions.ClientAction; import com.t3.client.AppConstants; import com.t3.client.AppPreferences; import com.t3.client.AppState; import com.t3.client.AppStyle; import com.t3.client.AppUtil; import com.t3.client.TabletopTool; import com.t3.client.swing.CoordinateStatusBar; import com.t3.client.swing.DragImageGlassPane; import com.t3.client.swing.GlassPane; import com.t3.client.swing.ImageChooserDialog; import com.t3.client.swing.MemoryStatusBar; import com.t3.client.swing.ProgressStatusBar; import com.t3.client.swing.SpacerStatusBar; import com.t3.client.swing.StatusPanel; import com.t3.client.swing.ZoomStatusBar; import com.t3.client.tool.PointerTool; import com.t3.client.tool.StampTool; import com.t3.client.ui.assetpanel.AssetDirectory; import com.t3.client.ui.assetpanel.AssetPanel; import com.t3.client.ui.commandpanel.CommandPanel; import com.t3.client.ui.lookuptable.LookupTablePanel; import com.t3.client.ui.macrobuttons.buttons.MacroButton; import com.t3.client.ui.macrobuttons.panels.CampaignPanel; import com.t3.client.ui.macrobuttons.panels.GlobalPanel; import com.t3.client.ui.macrobuttons.panels.ImpersonatePanel; import com.t3.client.ui.macrobuttons.panels.SelectionPanel; import com.t3.client.ui.token.EditTokenDialog; import com.t3.client.ui.tokenpanel.InitiativePanel; import com.t3.client.ui.tokenpanel.TokenPanelTreeCellRenderer; import com.t3.client.ui.tokenpanel.TokenPanelTreeModel; import com.t3.client.ui.zone.PointerOverlay; import com.t3.client.ui.zone.ZoneRenderer; import com.t3.guid.GUID; import com.t3.image.ImageUtil; import com.t3.language.I18N; import com.t3.model.Asset; import com.t3.model.Token; import com.t3.model.Zone; import com.t3.model.ZoneFactory; import com.t3.model.ZonePoint; import com.t3.model.drawing.DrawableColorPaint; import com.t3.model.drawing.DrawablePaint; import com.t3.model.drawing.DrawableTexturePaint; import com.t3.model.drawing.Pen; import com.t3.networking.ServerDisconnectHandler; import com.t3.persistence.FileUtil; import com.t3.swing.AboutDialog; import com.t3.swing.ColorPicker; import com.t3.swing.PositionalLayout; import com.t3.swing.SwingUtil; import com.t3.swing.preference.WindowPreferences; import com.t3.util.ImageManager; /** */ public class T3Frame extends DefaultDockableHolder implements WindowListener, AppEventListener { private static final Logger log = Logger.getLogger(T3Frame.class); private static final String INITIAL_LAYOUT_XML = "com/t3/client/ui/ilayout.xml"; private static final String T3_LOGO_IMAGE = "com/t3/client/image/tabletoptool-logo.png"; private static final String CREDITS_HTML = "com/t3/client/credits.html"; private static final String MINILOGO_IMAGE = "com/t3/client/image/minilogo.png"; private static final long serialVersionUID = 3905523813025329458L; private static final String DOCKING_PROFILE_NAME = "tabletoptoolDocking"; private static final int WINDOW_WIDTH = 800; private static final int WINDOW_HEIGHT = 600; private final Pen pen = new Pen(Pen.DEFAULT); private final Map<MTFrame, DockableFrame> frameMap = new HashMap<MTFrame, DockableFrame>(); /** * Are the drawing measurements being painted? */ private boolean paintDrawingMeasurement = true; private ImageChooserDialog imageChooserDialog; private ZoneRenderer currentRenderer; // Components private final AssetPanel assetPanel; private final ClientConnectionPanel connectionPanel; private final InitiativePanel initiativePanel; private final PointerOverlay pointerOverlay; private final CommandPanel commandPanel; private final AboutDialog aboutDialog; private final ColorPicker colorPicker; private final Toolbox toolbox; private final JPanel zoneRendererPanel; private JPanel visibleControlPanel; private FullScreenFrame fullScreenFrame; private final JPanel rendererBorderPanel; private final List<ZoneRenderer> zoneRendererList; private final JMenuBar menuBar; private final StatusPanel statusPanel; private String statusMessage = ""; private final ActivityMonitorPanel activityMonitor = new ActivityMonitorPanel(); private final ProgressStatusBar progressBar = new ProgressStatusBar(); private final ConnectionStatusPanel connectionStatusPanel = new ConnectionStatusPanel(); private CoordinateStatusBar coordinateStatusBar; private ZoomStatusBar zoomStatusBar; private JLabel chatActionLabel; private Color chatTypingLabelColor; private ChatTypingNotification chatTypingPanel; private Timer chatTimer; private long chatNotifyDuration; private final ChatNotificationTimers chatTyperTimers; private final ChatTyperObserver chatTyperObserver; private final GlassPane glassPane; private TokenPanelTreeModel tokenPanelTreeModel; private final TextureChooserPanel textureChooserPanel; private LookupTablePanel lookupTablePanel; // External filename support private JFileChooser loadPropsFileChooser; private JFileChooser loadFileChooser; private JFileChooser saveCmpgnFileChooser; private JFileChooser savePropsFileChooser; private JFileChooser saveFileChooser; private final FileFilter campaignFilter = new MTFileFilter("cmpgn", I18N.getText("file.ext.cmpgn")); private final FileFilter mapFilter = new MTFileFilter("rpmap", I18N.getText("file.ext.rpmap")); private final FileFilter propertiesFilter = new MTFileFilter("mtprops", I18N.getText("file.ext.mtprops")); private final FileFilter macroFilter = new MTFileFilter("mtmacro", I18N.getText("file.ext.mtmacro")); private final FileFilter macroSetFilter = new MTFileFilter("mtmacset", I18N.getText("file.ext.mtmacset")); private final FileFilter tableFilter = new MTFileFilter("mttable", I18N.getText("file.ext.mttable")); private EditTokenDialog tokenPropertiesDialog; private final CampaignPanel campaignPanel = new CampaignPanel(); private final GlobalPanel globalPanel = new GlobalPanel(); private final SelectionPanel selectionPanel = new SelectionPanel(); private final ImpersonatePanel impersonatePanel = new ImpersonatePanel(); private final DragImageGlassPane dragImageGlassPane = new DragImageGlassPane(); private class ChatTyperObserver implements Observer { @Override public void update(Observable o, Object arg) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { chatTypingPanel.invalidate(); chatTypingPanel.repaint(); } }); } } public class ChatNotificationTimers extends Observable { private final LinkedMap<String, Long> chatTypingNotificationTimers; public synchronized void setChatTyper(final String playerName) { if (AppPreferences.getTypingNotificationDuration() == 0) { turnOffUpdates(); chatTypingNotificationTimers.clear(); } else { TabletopTool.getFrame().getChatTimer().start(); TabletopTool.getFrame().getChatTypingPanel().setVisible(true); chatTypingNotificationTimers.put(playerName, System.currentTimeMillis()); setChanged(); notifyObservers(); } } private void turnOffUpdates() { TabletopTool.getFrame().getChatTimer().stop(); TabletopTool.getFrame().getChatTypingPanel().setVisible(false); } public synchronized void removeChatTyper(final String playerName) { chatTypingNotificationTimers.remove(playerName); if (chatTypingNotificationTimers.isEmpty()) turnOffUpdates(); setChanged(); notifyObservers(); } public synchronized LinkedMap<String, Long> getChatTypers() { return new LinkedMap<String, Long>(chatTypingNotificationTimers); } public ChatNotificationTimers() { chatTypingNotificationTimers = new LinkedMap<String, Long>(); } } public T3Frame(JMenuBar menuBar) { // Set up the frame super(AppConstants.APP_NAME); this.menuBar = menuBar; setDefaultCloseOperation(DO_NOTHING_ON_CLOSE); addWindowListener(this); setSize(WINDOW_WIDTH, WINDOW_HEIGHT); SwingUtil.centerOnScreen(this); setFocusTraversalPolicy(new T3FocusTraversalPolicy()); try { setIconImage(ImageUtil.getImage(MINILOGO_IMAGE)); } catch (IOException ioe) { String msg = I18N.getText("msg.error.loadingIconImage"); log.error(msg, ioe); System.err.println(msg); } // Notify duration initializeNotifyDuration(); // Components glassPane = new GlassPane(); assetPanel = createAssetPanel(); connectionPanel = createConnectionPanel(); toolbox = new Toolbox(); initiativePanel = createInitiativePanel(); zoneRendererList = new CopyOnWriteArrayList<ZoneRenderer>(); pointerOverlay = new PointerOverlay(); colorPicker = new ColorPicker(this); textureChooserPanel = new TextureChooserPanel(colorPicker.getPaintChooser(), assetPanel.getModel(), "imageExplorerTextureChooser"); colorPicker.getPaintChooser().addPaintChooser(textureChooserPanel); String credits = ""; String version = ""; Image logo = null; try { credits = new String(FileUtil.loadResource(CREDITS_HTML), "UTF-8"); // 2nd param of type Charset is Java6+ version = TabletopTool.getVersion(); credits = credits.replace("%VERSION%", version); logo = ImageUtil.getImage(T3_LOGO_IMAGE); } catch (Exception ioe) { log.error(I18N.getText("msg.error.credits"), ioe); ioe.printStackTrace(); } aboutDialog = new AboutDialog(this, logo, credits); aboutDialog.setSize(354, 400); statusPanel = new StatusPanel(); statusPanel.addPanel(getCoordinateStatusBar()); statusPanel.addPanel(getZoomStatusBar()); statusPanel.addPanel(MemoryStatusBar.getInstance()); // statusPanel.addPanel(progressBar); statusPanel.addPanel(connectionStatusPanel); statusPanel.addPanel(activityMonitor); statusPanel.addPanel(new SpacerStatusBar(25)); zoneRendererPanel = new JPanel(new PositionalLayout(5)); zoneRendererPanel.setBackground(Color.black); // zoneRendererPanel.add(zoneMiniMapPanel, PositionalLayout.Position.SE); // zoneRendererPanel.add(getChatTypingLabel(), PositionalLayout.Position.NW); zoneRendererPanel.add(getChatTypingPanel(), PositionalLayout.Position.NW); zoneRendererPanel.add(getChatActionLabel(), PositionalLayout.Position.SW); commandPanel = new CommandPanel(); TabletopTool.getMessageList().addObserver(commandPanel); rendererBorderPanel = new JPanel(new GridLayout()); rendererBorderPanel.setBorder(BorderFactory.createLineBorder(Color.darkGray)); rendererBorderPanel.add(zoneRendererPanel); // Put it all together setJMenuBar(menuBar); add(BorderLayout.NORTH, new ToolbarPanel(toolbox)); add(BorderLayout.SOUTH, statusPanel); JLayeredPane glassPaneComposite = new JLayeredPane(); glassPaneComposite.setLayout(new GridBagLayout()); GridBagConstraints constraints = new GridBagConstraints(); constraints.gridx = 1; constraints.gridy = 1; constraints.fill = GridBagConstraints.BOTH; constraints.weightx = 1; constraints.weighty = 1; glassPaneComposite.add(glassPane, constraints); glassPaneComposite.add(dragImageGlassPane, constraints); setGlassPane(glassPane); // setGlassPane(glassPaneComposite); glassPaneComposite.setVisible(true); if (!TabletopTool.MAC_OS_X) removeWindowsF10(); else registerForMacOSXEvents(); TabletopTool.getEventDispatcher().addListener(this, TabletopTool.ZoneEvent.Activated); restorePreferences(); updateKeyStrokes(); // This will cause the frame to be set to visible (BAD jide, BAD! No cookie for you!) configureDocking(); new WindowPreferences(AppConstants.APP_NAME, "mainFrame", this); chatTyperObserver = new ChatTyperObserver(); chatTyperTimers = new ChatNotificationTimers(); chatTyperTimers.addObserver(chatTyperObserver); chatTimer = getChatTimer(); setChatTypingLabelColor(AppPreferences.getChatNotificationColor()); } public ChatNotificationTimers getChatNotificationTimers() { return chatTyperTimers; } public void registerForMacOSXEvents() { try { OSXAdapter.setQuitHandler(this, getClass().getDeclaredMethod("macOSXExit", (Class[]) null)); OSXAdapter.setAboutHandler(this, getClass().getDeclaredMethod("macOSXAbout", (Class[]) null)); OSXAdapter.setPreferencesHandler(this, getClass().getDeclaredMethod("macOSXPreferences", (Class[]) null)); } catch (Exception e) { String msg = "Error while loading the OSXAdapter"; log.error(msg, e); System.err.println(msg); } } public void macOSXAbout() { ((ClientAction) AppActions.SHOW_ABOUT).execute(null); } public boolean macOSXExit() { ((ClientAction) AppActions.EXIT).execute(null); // Always return false to abort exit from os. Above call will close app normally if user accepts return false; } public void macOSXPreferences() { ((ClientAction) AppActions.SHOW_PREFERENCES).execute(null); } public DragImageGlassPane getDragImageGlassPane() { return dragImageGlassPane; } public ImageChooserDialog getImageChooserDialog() { if (imageChooserDialog == null) { imageChooserDialog = new ImageChooserDialog(this); } return imageChooserDialog; } public enum MTFrame { /* * These enums should be specified using references to the properties * file. However, a simple toString() method is used later to determine * what to display on the various panels. So if I convert the propName * into the value from the properties file and return it, parts of the * code later on use that string to do a properties file lookup! That * means that any code using MTFrame enums that are converted to Strings * need to be checked so that when the return value is used as the NAME * of an Action, the property name is retrieved instead. Ugh. :( * * We'll need two additional methods: getPropName() and * getDisplayName(). Perhaps toString() could call getDisplayName(), but * it might be much simpler to debug if toString() weren't used. In that * case, there's no reason to use an enum either ... may as well use a * class with static final objects in it. Sigh. */ // @formatter:off CONNECTIONS("Connections"), TOKEN_TREE("MapExplorer"), INITIATIVE("Initiative"), IMAGE_EXPLORER("Library"), CHAT("Chat"), LOOKUP_TABLES("Tables"), GLOBAL("Global"), CAMPAIGN("Campaign"), SELECTION("Selected"), IMPERSONATED("Impersonate"); // @formatter:on private String displayName; private MTFrame(String dispName) { displayName = dispName; } @Override public String toString() { return displayName; } public String getPropertyName() { return "panel." + displayName; } } private void configureDocking() { initializeFrames(); getDockingManager().setProfileKey(DOCKING_PROFILE_NAME); getDockingManager().setUsePref(false); getDockingManager().getWorkspace().setAcceptDockableFrame(false); // Main panel getDockingManager().getWorkspace().add(rendererBorderPanel); // Docked frames getDockingManager().addFrame(getFrame(MTFrame.CONNECTIONS)); getDockingManager().addFrame(getFrame(MTFrame.TOKEN_TREE)); getDockingManager().addFrame(getFrame(MTFrame.INITIATIVE)); getDockingManager().addFrame(getFrame(MTFrame.IMAGE_EXPLORER)); getDockingManager().addFrame(getFrame(MTFrame.CHAT)); getDockingManager().addFrame(getFrame(MTFrame.LOOKUP_TABLES)); getDockingManager().addFrame(getFrame(MTFrame.GLOBAL)); getDockingManager().addFrame(getFrame(MTFrame.CAMPAIGN)); getDockingManager().addFrame(getFrame(MTFrame.SELECTION)); getDockingManager().addFrame(getFrame(MTFrame.IMPERSONATED)); try { getDockingManager().loadInitialLayout(T3Frame.class.getClassLoader().getResourceAsStream(INITIAL_LAYOUT_XML)); } catch (ParserConfigurationException e) { TabletopTool.showError("msg.error.layoutParse", e); } catch (SAXException s) { TabletopTool.showError("msg.error.layoutParse", s); } catch (IOException e) { TabletopTool.showError("msg.error.layoutParse", e); } getDockingManager().loadLayoutDataFromFile(AppUtil.getAppHome("config").getAbsolutePath() + "/layout.dat"); getDockingManager().setOutlineMode(com.jidesoft.docking.DockingManager.FULL_OUTLINE_MODE); getDockingManager().setContinuousLayout(true); } public DockableFrame getFrame(MTFrame frame) { return frameMap.get(frame); } private void initializeFrames() { frameMap.put(MTFrame.CONNECTIONS, createDockingFrame(MTFrame.CONNECTIONS, new JScrollPane(connectionPanel), new ImageIcon(AppStyle.connectionsImage))); frameMap.put(MTFrame.TOKEN_TREE, createDockingFrame(MTFrame.TOKEN_TREE, new JScrollPane(createTokenTreePanel()), new ImageIcon(AppStyle.mapExplorerImage))); frameMap.put(MTFrame.IMAGE_EXPLORER, createDockingFrame(MTFrame.IMAGE_EXPLORER, assetPanel, new ImageIcon(AppStyle.resourceLibraryImage))); frameMap.put(MTFrame.CHAT, createDockingFrame(MTFrame.CHAT, commandPanel, new ImageIcon(AppStyle.chatPanelImage))); frameMap.put(MTFrame.LOOKUP_TABLES, createDockingFrame(MTFrame.LOOKUP_TABLES, getLookupTablePanel(), new ImageIcon(AppStyle.tablesPanelImage))); frameMap.put(MTFrame.INITIATIVE, createDockingFrame(MTFrame.INITIATIVE, initiativePanel, new ImageIcon(AppStyle.initiativePanelImage))); JScrollPane campaign = scrollPaneFactory(campaignPanel); JScrollPane global = scrollPaneFactory(globalPanel); JScrollPane selection = scrollPaneFactory(selectionPanel); JScrollPane impersonate = scrollPaneFactory(impersonatePanel); frameMap.put(MTFrame.GLOBAL, createDockingFrame(MTFrame.GLOBAL, global, new ImageIcon(AppStyle.globalPanelImage))); frameMap.put(MTFrame.CAMPAIGN, createDockingFrame(MTFrame.CAMPAIGN, campaign, new ImageIcon(AppStyle.campaignPanelImage))); frameMap.put(MTFrame.SELECTION, createDockingFrame(MTFrame.SELECTION, selection, new ImageIcon(AppStyle.selectionPanelImage))); frameMap.put(MTFrame.IMPERSONATED, createDockingFrame(MTFrame.IMPERSONATED, impersonate, new ImageIcon(AppStyle.impersonatePanelImage))); } private JScrollPane scrollPaneFactory(JPanel panel) { JScrollPane pane = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); pane.getViewport().setBorder(null); return pane; } private static DockableFrame createDockingFrame(MTFrame mtFrame, Component component, Icon icon) { DockableFrame frame = new DockableFrame(mtFrame.name(), icon); frame.add(component); frame.addDockableFrameListener(new T3DockListener()); return frame; } public LookupTablePanel getLookupTablePanel() { if (lookupTablePanel == null) { lookupTablePanel = new LookupTablePanel(); } return lookupTablePanel; } public EditTokenDialog getTokenPropertiesDialog() { if (tokenPropertiesDialog == null) { tokenPropertiesDialog = new EditTokenDialog(); } return tokenPropertiesDialog; } public void refresh() { if (getCurrentZoneRenderer() != null) { getCurrentZoneRenderer().repaint(); } } private class MTFileFilter extends FileFilter { private final String extension; private final String description; MTFileFilter(String exten, String desc) { super(); extension = exten; description = desc; } // Accept directories and files matching extension @Override public boolean accept(File f) { if (f.isDirectory()) { return true; } String ext = getExtension(f); if (ext != null) { if (ext.equals(extension)) { return true; } else { return false; } } return false; } @Override public String getDescription() { return description; } public String getExtension(File f) { String ext = null; String s = f.getName(); int i = s.lastIndexOf('.'); if (i > 0 && i < s.length() - 1) { ext = s.substring(i + 1).toLowerCase(); } return ext; } } public FileFilter getCmpgnFileFilter() { return campaignFilter; } public FileFilter getMapFileFilter() { return mapFilter; } public JFileChooser getLoadPropsFileChooser() { if (loadPropsFileChooser == null) { loadPropsFileChooser = new JFileChooser(); loadPropsFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); loadPropsFileChooser.addChoosableFileFilter(propertiesFilter); loadPropsFileChooser.setDialogTitle(I18N.getText("msg.title.importProperties")); } loadPropsFileChooser.setFileFilter(propertiesFilter); return loadPropsFileChooser; } public JFileChooser getLoadFileChooser() { if (loadFileChooser == null) { loadFileChooser = new JFileChooser(); loadFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); } return loadFileChooser; } public JFileChooser getSaveCmpgnFileChooser() { if (saveCmpgnFileChooser == null) { saveCmpgnFileChooser = new JFileChooser(); saveCmpgnFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); saveCmpgnFileChooser.addChoosableFileFilter(campaignFilter); saveCmpgnFileChooser.setDialogTitle(I18N.getText("msg.title.saveCampaign")); } saveCmpgnFileChooser.setAcceptAllFileFilterUsed(true); return saveCmpgnFileChooser; } public JFileChooser getSavePropsFileChooser() { if (savePropsFileChooser == null) { savePropsFileChooser = new JFileChooser(); savePropsFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); savePropsFileChooser.addChoosableFileFilter(propertiesFilter); savePropsFileChooser.setDialogTitle(I18N.getText("msg.title.exportProperties")); } savePropsFileChooser.setAcceptAllFileFilterUsed(true); return savePropsFileChooser; } public JFileChooser getSaveFileChooser() { if (saveFileChooser == null) { saveFileChooser = new JFileChooser(); saveFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); } return saveFileChooser; } public void showControlPanel(JPanel... panels) { JPanel layoutPanel = new JPanel(new GridBagLayout()); layoutPanel.setBorder(BorderFactory.createBevelBorder(BevelBorder.RAISED)); int i = 0; for (JPanel panel : panels) { GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 1; gbc.gridy = i; gbc.weightx = 1; gbc.fill = GridBagConstraints.BOTH; layoutPanel.add(panel, gbc); i++; } layoutPanel.setSize(layoutPanel.getPreferredSize()); zoneRendererPanel.add(layoutPanel, PositionalLayout.Position.NE); zoneRendererPanel.setComponentZOrder(layoutPanel, 0); zoneRendererPanel.revalidate(); zoneRendererPanel.repaint(); visibleControlPanel = layoutPanel; } public ZoomStatusBar getZoomStatusBar() { if (zoomStatusBar == null) { zoomStatusBar = new ZoomStatusBar(); } return zoomStatusBar; } public CoordinateStatusBar getCoordinateStatusBar() { if (coordinateStatusBar == null) { coordinateStatusBar = new CoordinateStatusBar(); } return coordinateStatusBar; } public void hideControlPanel() { if (visibleControlPanel != null) { if (zoneRendererPanel != null) { zoneRendererPanel.remove(visibleControlPanel); } visibleControlPanel = null; refresh(); } } public void showNonModalGlassPane(JComponent component, int x, int y) { showGlassPane(component, x, y, false); } public void showModalGlassPane(JComponent component, int x, int y) { showGlassPane(component, x, y, true); } private void showGlassPane(JComponent component, int x, int y, boolean modal) { component.setSize(component.getPreferredSize()); component.setLocation(x, y); glassPane.setLayout(null); glassPane.add(component); glassPane.setModel(modal); glassPane.setVisible(true); } public void showFilledGlassPane(JComponent component) { glassPane.setLayout(new GridLayout()); glassPane.add(component); // glassPane.setActionMap(null); // glassPane.setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); glassPane.setVisible(true); } public void hideGlassPane() { glassPane.removeAll(); glassPane.setVisible(false); } public ChatTypingNotification getChatTypingPanel() { if (chatTypingPanel == null) { chatTypingPanel = new ChatTypingNotification(); chatTypingPanel.setOpaque(false); chatTypingPanel.setSize(220, 100); // FIXME change to variable width chatTypingPanel.setEnabled(true); chatTypingPanel.setVisible(false); // Only visible when there are notifications to display } return chatTypingPanel; } public Color getChatTypingLabelColor() { if (chatTypingLabelColor == null) { chatTypingLabelColor = Color.BLACK; } return chatTypingLabelColor; } public void setChatTypingLabelColor(Color color) { if (color != null) { chatTypingLabelColor = color; } } public void setChatNotifyDuration(int duration) { chatNotifyDuration = duration; } private void initializeNotifyDuration() { chatNotifyDuration = AppPreferences.getTypingNotificationDuration(); } public JLabel getChatActionLabel() { if (chatActionLabel == null) { chatActionLabel = new JLabel(new ImageIcon(AppStyle.chatImage)); chatActionLabel.setSize(chatActionLabel.getPreferredSize()); chatActionLabel.setVisible(false); chatActionLabel.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { showCommandPanel(); } }); } return chatActionLabel; } public boolean isCommandPanelVisible() { return getFrame(MTFrame.CHAT).isShowing(); } public void showCommandPanel() { chatActionLabel.setVisible(false); getDockingManager().showFrame(MTFrame.CHAT.name()); commandPanel.requestFocus(); } public void hideCommandPanel() { getDockingManager().hideFrame(MTFrame.CHAT.name()); } public ColorPicker getColorPicker() { return colorPicker; } public void showAboutDialog() { aboutDialog.setVisible(true); } public ConnectionStatusPanel getConnectionStatusPanel() { return connectionStatusPanel; } private void restorePreferences() { Set<File> assetRootList = AppPreferences.getAssetRoots(); for (File file : assetRootList) { addAssetRoot(file); } } private JComponent createTokenTreePanel() { final JTree tree = new JTree(); tokenPanelTreeModel = new TokenPanelTreeModel(tree); tree.setModel(tokenPanelTreeModel); tree.setCellRenderer(new TokenPanelTreeCellRenderer()); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); tree.addMouseListener(new MouseAdapter() { // TODO: Make this a handler class, not an aic @Override public void mousePressed(MouseEvent e) { // tree.setSelectionPath(tree.getPathForLocation(e.getX(), e.getY())); TreePath path = tree.getPathForLocation(e.getX(), e.getY()); if (path == null) { return; } Object row = path.getLastPathComponent(); int rowIndex = tree.getRowForLocation(e.getX(), e.getY()); if (SwingUtilities.isLeftMouseButton(e)) { if (!SwingUtil.isShiftDown(e)) { tree.clearSelection(); } tree.addSelectionInterval(rowIndex, rowIndex); if (row instanceof Token) { if (e.getClickCount() == 2) { Token token = (Token) row; getCurrentZoneRenderer().clearSelectedTokens(); getCurrentZoneRenderer().centerOn(new ZonePoint(token.getX(), token.getY())); // Pick an appropriate tool getToolbox().setSelectedTool(token.isToken() ? PointerTool.class : StampTool.class); getCurrentZoneRenderer().setActiveLayer(token.getLayer()); getCurrentZoneRenderer().selectToken(token.getId()); getCurrentZoneRenderer().requestFocusInWindow(); } } } if (SwingUtilities.isRightMouseButton(e)) { if (!isRowSelected(tree.getSelectionRows(), rowIndex) && !SwingUtil.isShiftDown(e)) { tree.clearSelection(); tree.addSelectionInterval(rowIndex, rowIndex); } final int x = e.getX(); final int y = e.getY(); EventQueue.invokeLater(new Runnable() { @Override public void run() { Token firstToken = null; Set<GUID> selectedTokenSet = new HashSet<GUID>(); for (TreePath path : tree.getSelectionPaths()) { if (path.getLastPathComponent() instanceof Token) { Token token = (Token) path.getLastPathComponent(); if (firstToken == null) { firstToken = token; } if (AppUtil.playerOwns(token)) { selectedTokenSet.add(token.getId()); } } } if (!selectedTokenSet.isEmpty()) { try { if (firstToken.isStamp()) { new StampPopupMenu(selectedTokenSet, x, y, getCurrentZoneRenderer(), firstToken).showPopup(tree); } else { new TokenPopupMenu(selectedTokenSet, x, y, getCurrentZoneRenderer(), firstToken).showPopup(tree); } } catch (IllegalComponentStateException icse) { log.info(tree.toString(), icse); } } } }); } } }); TabletopTool.getEventDispatcher().addListener(new AppEventListener() { @Override public void handleAppEvent(AppEvent event) { tokenPanelTreeModel.setZone((Zone) event.getNewValue()); } }, TabletopTool.ZoneEvent.Activated); return tree; } public void clearTokenTree() { if (tokenPanelTreeModel != null) { tokenPanelTreeModel.setZone(null); } } public void updateTokenTree() { if (tokenPanelTreeModel != null) { tokenPanelTreeModel.update(); } if (initiativePanel != null) { initiativePanel.update(); } } private boolean isRowSelected(int[] selectedRows, int row) { if (selectedRows == null) { return false; } for (int selectedRow : selectedRows) { if (row == selectedRow) { return true; } } return false; } private ClientConnectionPanel createConnectionPanel() { ClientConnectionPanel panel = new ClientConnectionPanel(); return panel; } private InitiativePanel createInitiativePanel() { TabletopTool.getEventDispatcher().addListener(new AppEventListener() { @Override public void handleAppEvent(AppEvent event) { initiativePanel.setZone((Zone) event.getNewValue()); } }, TabletopTool.ZoneEvent.Activated); return new InitiativePanel(); } private AssetPanel createAssetPanel() { final AssetPanel panel = new AssetPanel("mainAssetPanel"); panel.addImagePanelMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { // TODO use for real popup logic // if (SwingUtilities.isLeftMouseButton(e)) { // if (e.getClickCount() == 2) { // // List<Object> idList = panel.getSelectedIds(); // if (idList == null || idList.size() == 0) { // return; // } // // final int index = (Integer) idList.get(0); // createZone(panel.getAsset(index)); // } // } if (SwingUtilities.isRightMouseButton(e) && TabletopTool.getPlayer().isGM()) { List<Object> idList = panel.getSelectedIds(); if (idList == null || idList.size() == 0) { return; } final int index = (Integer) idList.get(0); JPopupMenu menu = new JPopupMenu(); menu.add(new JMenuItem(new AbstractAction() { { putValue(NAME, I18N.getText("action.newMap")); } @Override public void actionPerformed(ActionEvent e) { createZone(panel.getAsset(index)); } })); panel.showImagePanelPopup(menu, e.getX(), e.getY()); } } private void createZone(Asset asset) { Zone zone = ZoneFactory.createZone(); zone.setName(asset.getName()); BufferedImage image = ImageManager.getImageAndWait(asset.getId()); if (image.getWidth() < 200 || image.getHeight() < 200) { zone.setBackgroundPaint(new DrawableTexturePaint(asset)); } else { zone.setMapAsset(asset.getId()); zone.setBackgroundPaint(new DrawableColorPaint(Color.black)); } MapPropertiesDialog newMapDialog = new MapPropertiesDialog(TabletopTool.getFrame()); newMapDialog.setZone(zone); newMapDialog.setVisible(true); if (newMapDialog.getStatus() == MapPropertiesDialog.Status.OK) { TabletopTool.addZone(zone); } } }); return panel; } public PointerOverlay getPointerOverlay() { return pointerOverlay; } public void setStatusMessage(final String message) { statusMessage = message; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { statusPanel.setStatus(" " + message); } }); } public String getStatusMessage() { return statusMessage; } public ActivityMonitorPanel getActivityMonitor() { return activityMonitor; } public void startIndeterminateAction() { progressBar.startIndeterminate(); } public void endIndeterminateAction() { progressBar.endIndeterminate(); } public void startDeterminateAction(int totalWork) { progressBar.startDeterminate(totalWork); } public void updateDeterminateActionProgress(int additionalWorkCompleted) { progressBar.updateDeterminateProgress(additionalWorkCompleted); } public void endDeterminateAction() { progressBar.endDeterminate(); } // ///////////////////////////////////////////////////////////////////////// // static methods // ///////////////////////////////////////////////////////////////////////// public CommandPanel getCommandPanel() { return commandPanel; } public ClientConnectionPanel getConnectionPanel() { return connectionPanel; } public AssetPanel getAssetPanel() { return assetPanel; } public void addAssetRoot(File rootDir) { assetPanel.addAssetRoot(new AssetDirectory(rootDir, AppConstants.IMAGE_FILE_FILTER)); } public Pen getPen() { pen.setPaint(DrawablePaint.convertPaint(colorPicker.getForegroundPaint())); pen.setBackgroundPaint(DrawablePaint.convertPaint(colorPicker.getBackgroundPaint())); pen.setThickness(colorPicker.getStrokeWidth()); pen.setOpacity(colorPicker.getOpacity()); pen.setThickness(colorPicker.getStrokeWidth()); return pen; } public List<ZoneRenderer> getZoneRenderers() { // TODO: This should prob be immutable return zoneRendererList; } public ZoneRenderer getZoneRenderer(String name) { for (ZoneRenderer zr : zoneRendererList) { if (zr.getZone().getName().equals(name.toString())) { return zr; } } return null; } public ZoneRenderer getCurrentZoneRenderer() { return currentRenderer; } public void addZoneRenderer(ZoneRenderer renderer) { zoneRendererList.add(renderer); } public void removeZoneRenderer(ZoneRenderer renderer) { boolean isCurrent = renderer == getCurrentZoneRenderer(); zoneRendererList.remove(renderer); if (isCurrent) { boolean rendererSet = false; for (ZoneRenderer currRenderer : zoneRendererList) { if (TabletopTool.getPlayer().isGM() || currRenderer.getZone().isVisible()) { setCurrentZoneRenderer(currRenderer); rendererSet = true; break; } } if (!rendererSet) { setCurrentZoneRenderer(null); } } } public void clearZoneRendererList() { zoneRendererList.clear(); } public void setCurrentZoneRenderer(ZoneRenderer renderer) { // Flush first so that the new zone renderer can inject the newly needed images if (renderer != null) { ImageManager.flush(renderer.getZone().getAllAssetIds()); } else { ImageManager.flush(); // zoneRendererList.remove(currentRenderer); } // Handle new renderers // TODO: should this be here ? if (renderer != null && !zoneRendererList.contains(renderer)) { zoneRendererList.add(renderer); } if (currentRenderer != null) { currentRenderer.flush(); zoneRendererPanel.remove(currentRenderer); } if (renderer != null) { zoneRendererPanel.add(renderer, PositionalLayout.Position.CENTER); zoneRendererPanel.doLayout(); } currentRenderer = renderer; initiativePanel.update(); toolbox.setTargetRenderer(renderer); if (renderer != null) { TabletopTool.getEventDispatcher().fireEvent(TabletopTool.ZoneEvent.Activated, this, null, renderer.getZone()); renderer.requestFocusInWindow(); } AppActions.updateActions(); repaint(); setTitleViaRenderer(renderer); getZoomStatusBar().update(); } public void setTitleViaRenderer(ZoneRenderer renderer) { String campaignName = " - [Default]"; if (AppState.getCampaignFile() != null) { String s = AppState.getCampaignFile().getName(); // remove the file extension of the campaign file name s = s.substring(0, s.length() - AppConstants.CAMPAIGN_FILE_EXTENSION.length()); campaignName = " - [" + s + "]"; } setTitle(AppConstants.APP_NAME + " - " + TabletopTool.getPlayer() + campaignName + (renderer != null ? " - " + renderer.getZone().getName() : "")); } public Toolbox getToolbox() { return toolbox; } public ZoneRenderer getZoneRenderer(Zone zone) { for (ZoneRenderer renderer : zoneRendererList) { if (zone == renderer.getZone()) { return renderer; } } return null; } public ZoneRenderer getZoneRenderer(GUID zoneGUID) { for (ZoneRenderer renderer : zoneRendererList) { if (zoneGUID.equals(renderer.getZone().getId())) { return renderer; } } return null; } /** * Get the paintDrawingMeasurements for this TabletopToolClient. * * @return Returns the current value of paintDrawingMeasurements. */ public boolean isPaintDrawingMeasurement() { return paintDrawingMeasurement; } /** * Set the value of paintDrawingMeasurements for this TabletopToolClient. * * @param aPaintDrawingMeasurements * The paintDrawingMeasurements to set. */ public void setPaintDrawingMeasurement(boolean aPaintDrawingMeasurements) { paintDrawingMeasurement = aPaintDrawingMeasurements; } public void showFullScreen() { GraphicsConfiguration graphicsConfig = getGraphicsConfiguration(); Rectangle bounds = graphicsConfig.getBounds(); fullScreenFrame = new FullScreenFrame(); fullScreenFrame.add(zoneRendererPanel); // Under mac os x this does not properly hide the menu bar so adjust top and height // so menu bar does not overlay screen. if (TabletopTool.MAC_OS_X) { fullScreenFrame.setBounds(bounds.x, bounds.y + 21, bounds.width, bounds.height - 21); } else { fullScreenFrame.setBounds(bounds.x, bounds.y, bounds.width, bounds.height); } fullScreenFrame.setJMenuBar(menuBar); // Menu bar is visible anyways on MAC so leave menu items on it if (!TabletopTool.MAC_OS_X) menuBar.setVisible(false); fullScreenFrame.setVisible(true); this.setVisible(false); } public boolean isFullScreen() { return fullScreenFrame != null; } public void showWindowed() { if (fullScreenFrame == null) { return; } rendererBorderPanel.add(zoneRendererPanel); setJMenuBar(menuBar); menuBar.setVisible(true); this.setVisible(true); fullScreenFrame.dispose(); fullScreenFrame = null; } public class FullScreenFrame extends JFrame { public FullScreenFrame() { setUndecorated(true); } } // APP EVENT LISTENER @Override public void handleAppEvent(AppEvent evt) { if (evt.getId() != TabletopTool.ZoneEvent.Activated) { return; } final Zone zone = (Zone) evt.getNewValue(); // AssetAvailableListener listener = new AssetAvailableListener() { // public void assetAvailable(com.t3.MD5Key key) { // ZoneRenderer renderer = getCurrentZoneRenderer(); // if (renderer.getZone() == zone) { // ImageManager.getImage(key, renderer); // } // } // }; // Let's add all the assets, starting with the backgrounds for (Token token : zone.getBackgroundStamps()) { MD5Key key = token.getImageAssetId(); ImageManager.getImage(key); } // Now the stamps for (Token token : zone.getStampTokens()) { MD5Key key = token.getImageAssetId(); ImageManager.getImage(key); } // Now add the rest for (Token token : zone.getAllTokens()) { MD5Key key = token.getImageAssetId(); ImageManager.getImage(key); } } // WINDOW LISTENER @Override public void windowOpened(WindowEvent e) { } @Override public void windowClosing(WindowEvent e) { if (!confirmClose()) { return; } closingMaintenance(); } public boolean confirmClose() { if (TabletopTool.isHostingServer()) { if (!TabletopTool.confirm("msg.confirm.hostingDisconnect")) { return false; } } return true; } public void closingMaintenance() { if (AppPreferences.getSaveReminder()) { if (TabletopTool.getPlayer().isGM()) { int result = TabletopTool.confirmImpl(I18N.getText("msg.title.saveCampaign"), JOptionPane.YES_NO_CANCEL_OPTION, "msg.confirm.saveCampaign", (Object[]) null); // int result = JOptionPane.showConfirmDialog(TabletopTool.getFrame(), I18N.getText("msg.confirm.saveCampaign"), I18N.getText("msg.title.saveCampaign"), JOptionPane.YES_NO_CANCEL_OPTION); if (result == JOptionPane.CANCEL_OPTION || result == JOptionPane.CLOSED_OPTION) { return; } if (result == JOptionPane.YES_OPTION) { final Observer callback = new Observer() { @Override public void update(java.util.Observable o, Object arg) { if (arg instanceof String) { // There was an error during the save -- don't terminate TabletopTool! } else { TabletopTool.getFrame().close(); } } }; ActionEvent ae = new ActionEvent(callback, 0, "close"); AppActions.SAVE_CAMPAIGN.actionPerformed(ae); return; } } else { if (!TabletopTool.confirm("msg.confirm.disconnecting")) { return; } } } close(); } public void close() { ServerDisconnectHandler.disconnectExpected = true; TabletopTool.disconnect(); getDockingManager().saveLayoutDataToFile(AppUtil.getAppHome("config").getAbsolutePath() + "/layout.dat"); // If closing cleanly, remove the autosave file TabletopTool.getAutoSaveManager().purge(); setVisible(false); EventQueue.invokeLater(new Runnable() { @Override public void run() { dispose(); } }); } @Override public void windowClosed(WindowEvent e) { System.exit(0); } @Override public void windowIconified(WindowEvent e) { } @Override public void windowDeiconified(WindowEvent e) { } @Override public void windowActivated(WindowEvent e) { } @Override public void windowDeactivated(WindowEvent e) { } // Windows OS defaults F10 to the menu bar, noooooo!! We want for macro buttons. // XXX Shouldn't this keystroke be configurable via the properties file anyway? // XXX Doesn't work for Mac OSX and isn't called in that case. private void removeWindowsF10() { InputMap imap = menuBar.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW); Object action = imap.get(KeyStroke.getKeyStroke("F10")); if (log.isInfoEnabled()) log.info("Removing the F10 key from the menuBar's InputMap; it did " + (action == null ? "not" : "") + " exist"); ActionMap amap = menuBar.getActionMap(); amap.getParent().remove(action); } public void updateKeyStrokes() { /* * Lee: This causes input map conflicts in Java 7. Going over the code, * this line does nothing as key mapping here does not conflict with * hotkeys set aside for macros; unless someone modifies the * accelerators in the i18n file. Commenting it out. */ // updateKeyStrokes(menuBar); for (MTFrame frame : frameMap.keySet()) { updateKeyStrokes(frameMap.get(frame)); } } public Timer getChatTimer() { if (chatTimer == null) { chatTimer = newChatTimer(); } return chatTimer; } public void setChatTimer(Timer timer) { chatTimer = timer; } private Timer newChatTimer() { // Set up the Chat timer to listen for changes Timer tm = new Timer(500, new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { long currentTime = System.currentTimeMillis(); LinkedMap<String,Long> chatTimers = chatTyperTimers.getChatTypers(); List<String> removeThese = new ArrayList<String>(chatTimers.size()); Set<String> playerTimers = chatTimers.keySet(); for (String player : playerTimers) { if (currentTime - chatTimers.get(player) >= (chatNotifyDuration * 1000)) { // set up a temp place and remove them after the loop removeThese.add(player); } } for (String remove : removeThese) { chatTyperTimers.removeChatTyper(remove); } } }); tm.start(); return tm; } private void updateKeyStrokes(JComponent c) { c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).clear(); Map<KeyStroke, MacroButton> keyStrokeMap = MacroButtonHotKeyManager.getKeyStrokeMap(); if (c.getActionMap().keys() != null) { for (Object o : c.getActionMap().keys()) { // We're looking for MacroButton here, but we're adding AbstractActions below... Is this right? XXX if (o instanceof MacroButton) { if (log.isInfoEnabled()) log.info("Removing MacroButton " + ((MacroButton) o).getButtonText()); c.getActionMap().remove(o); } } } for (KeyStroke keyStroke : keyStrokeMap.keySet()) { final MacroButton button = keyStrokeMap.get(keyStroke); if (button != null) { c.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(keyStroke, button); c.getActionMap().put(button, new MTButtonHotKeyAction(button)); } else { // This shouldn't be possible... log.error("No MacroButton found for keyStroke " + keyStroke.toString()); } } } public CampaignPanel getCampaignPanel() { return campaignPanel; } public GlobalPanel getGlobalPanel() { return globalPanel; } public ImpersonatePanel getImpersonatePanel() { return impersonatePanel; } public SelectionPanel getSelectionPanel() { return selectionPanel; } public void resetTokenPanels() { impersonatePanel.reset(); selectionPanel.reset(); } // currently only used after loading a campaign public void resetPanels() { MacroButtonHotKeyManager.clearKeyStrokes(); campaignPanel.reset(); globalPanel.reset(); impersonatePanel.reset(); selectionPanel.reset(); updateKeyStrokes(); } /** * @return Getter for initiativePanel */ public InitiativePanel getInitiativePanel() { return initiativePanel; } private JFileChooser saveMacroFileChooser; private JFileChooser saveMacroSetFileChooser; public JFileChooser getSaveMacroFileChooser() { if (saveMacroFileChooser == null) { saveMacroFileChooser = new JFileChooser(); saveMacroFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); saveMacroFileChooser.addChoosableFileFilter(macroFilter); saveMacroFileChooser.setDialogTitle(I18N.getText("msg.title.exportMacro")); } saveMacroFileChooser.setAcceptAllFileFilterUsed(true); return saveMacroFileChooser; } public JFileChooser getSaveMacroSetFileChooser() { if (saveMacroSetFileChooser == null) { saveMacroSetFileChooser = new JFileChooser(); saveMacroSetFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); saveMacroSetFileChooser.addChoosableFileFilter(macroSetFilter); saveMacroSetFileChooser.setDialogTitle(I18N.getText("msg.title.exportMacroSet")); } saveMacroSetFileChooser.setAcceptAllFileFilterUsed(true); return saveMacroSetFileChooser; } private JFileChooser loadMacroFileChooser; private JFileChooser loadMacroSetFileChooser; public JFileChooser getLoadMacroFileChooser() { if (loadMacroFileChooser == null) { loadMacroFileChooser = new JFileChooser(); loadMacroFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); loadMacroFileChooser.addChoosableFileFilter(macroFilter); loadMacroFileChooser.setDialogTitle(I18N.getText("msg.title.importMacro")); } loadMacroFileChooser.setFileFilter(macroFilter); return loadMacroFileChooser; } public JFileChooser getLoadMacroSetFileChooser() { if (loadMacroSetFileChooser == null) { loadMacroSetFileChooser = new JFileChooser(); loadMacroSetFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); loadMacroSetFileChooser.addChoosableFileFilter(macroSetFilter); loadMacroSetFileChooser.setDialogTitle(I18N.getText("msg.title.importMacroSet")); } loadMacroSetFileChooser.setFileFilter(macroSetFilter); return loadMacroSetFileChooser; } // end of Macro import/export support private JFileChooser saveTableFileChooser; private JFileChooser loadTableFileChooser; public JFileChooser getSaveTableFileChooser() { if (saveTableFileChooser == null) { saveTableFileChooser = new JFileChooser(); saveTableFileChooser.setCurrentDirectory(AppPreferences.getSaveDir()); saveTableFileChooser.addChoosableFileFilter(tableFilter); saveTableFileChooser.setDialogTitle("Export Table"); } saveTableFileChooser.setAcceptAllFileFilterUsed(true); return saveTableFileChooser; } // Should the load FileChooser really be different from the save? That means recording two separate default directories // and when a user loads a file, don't they expect the save dialog to start at the same place?? public JFileChooser getLoadTableFileChooser() { if (loadTableFileChooser == null) { loadTableFileChooser = new JFileChooser(); loadTableFileChooser.setCurrentDirectory(AppPreferences.getLoadDir()); loadTableFileChooser.addChoosableFileFilter(tableFilter); loadTableFileChooser.setDialogTitle("Import Table"); } loadTableFileChooser.setFileFilter(tableFilter); return loadTableFileChooser; } // end of Table import/export support @SuppressWarnings("serial") private static class MTButtonHotKeyAction extends AbstractAction { private final MacroButton macroButton; public MTButtonHotKeyAction(MacroButton button) { macroButton = button; } @Override public void actionPerformed(ActionEvent e) { macroButton.getProperties().executeMacro(); } } public Token findToken(GUID guid) { for(ZoneRenderer zr:getZoneRenderers()) { Token t=zr.getZone().getToken(guid); if(t!=null) return t; } return null; } }