package org.korsakow.ide.util; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.GraphicsConfiguration; import java.awt.Insets; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; 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.awt.event.WindowEvent; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Timer; import java.util.TimerTask; import javax.swing.JCheckBoxMenuItem; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JList; import javax.swing.JMenuItem; import javax.swing.JTable; import javax.swing.SwingUtilities; import javax.swing.ToolTipManager; import javax.swing.TransferHandler; import javax.swing.UIManager; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.event.EventListenerList; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import javax.swing.tree.TreeNode; import org.apache.log4j.Logger; import org.korsakow.ide.Application; import org.korsakow.ide.ui.components.KTreeTable; import org.korsakow.ide.ui.components.code.CodeTable; import org.korsakow.ide.ui.components.code.CodeTableModel; import org.korsakow.ide.ui.components.tree.FolderNode; import org.korsakow.ide.ui.components.tree.KNode; import org.korsakow.ide.ui.components.tree.KTreeTableModel; import org.korsakow.ide.ui.laf.KorsakowLookAndFeel; import org.w3c.dom.Document; import org.w3c.dom.Element; import sun.awt.SunToolkit; public class UIUtil { /** * Sets the Korsakow Look and Feel (LAF) while conserving the required Mac look and feel if applicable */ public static void setUpLAF () { // // Mac-specific behaviour removed per #840 // HashMap<String, Object> platformUI = new HashMap<String, Object>(); final Collection<String> uiNames = new ArrayList<String>(); // on OSX restoring FileChooserUI causes a NullPointerException (also we use FileDialog) if (Platform.isWindowsOS()) { uiNames.add("FileChooserUI"); } try{ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); for (String uiName : uiNames) platformUI.put(uiName, UIManager.get(uiName)); } catch (Exception e) { Logger.getLogger(Application.class).error("", e); } // // properties required to ensure the Menu Bar appears properly in Mac // String[] macUIProperties = {"MenuBarUI"}; // // Boolean isMac = Platform.getOSFamily() == Platform.OSFamily.MAC; // // // // // First, set the Mac LAF to grab the properties we need // // // if ( isMac ) { // try{ // UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); // } catch (Exception e) { // Logger.getLogger(Application.class).error("", e); // } // // for (int i = 0; i < macUIProperties.length; i++) { // String property = macUIProperties[i]; // macUICollection.put(property, UIManager.get(property)); // } // } // // Second, set the Korsakow LAF, that overrides everything. // try { UIManager.setLookAndFeel(new KorsakowLookAndFeel()); } catch (UnsupportedLookAndFeelException e) { Logger.getLogger(Application.class).error("", e); } for (String uiName : platformUI.keySet()) UIManager.put(uiName, platformUI.get(uiName)); // // Third, restore the necessary elements of the Mac LAF // // if ( isMac ) { // for (int i = 0; i < macUIProperties.length; i++) { // String property = macUIProperties[i]; // UIManager.put(property, macUICollection.get(property)); // } // } ToolTipManager.sharedInstance().setDismissDelay(Integer.MAX_VALUE); } /** * TODO: determine if this is actually different from PlatformCommandKeyMask * @return */ public static int getPlatformShortcutKeyMask() { return Toolkit.getDefaultToolkit( ).getMenuShortcutKeyMask(); } public static int getPlatformCommandKeyMask() { switch (Platform.getOS()) { case WIN: case NIX: return InputEvent.CTRL_DOWN_MASK; case MAC: return InputEvent.META_DOWN_MASK; } return 0; } public static boolean isPlatformMultipleSectionKeyDown(InputEvent event) { switch (Platform.getOS()) { case WIN: case NIX: return event.isControlDown(); case MAC: return event.isMetaDown(); } return false; } public static boolean isPlatformCommandKeyDown(InputEvent event) { switch (Platform.getOS()) { case WIN: case NIX: return event.isControlDown(); case MAC: return event.isMetaDown(); } return false; } /** * At least once difference between this and MouseEvent.isPopupTrigger is that this function also * accounts for the fact that on Mac, ctl+left-click = popuptrigger, notably on Macbooks. * * OTOH this method is useful, OTOH its TOO useful in that it will introduce itself(UIUtil) as a dependency(read: coupling) in too many places, but such is life. If it ever comes to it we can introduce more localized(up to some reasonable package-level) versions of this method. * * @param event * @return */ public static boolean isPopupTrigger(MouseEvent event) { switch (Platform.getOS()) { case WIN: case NIX: return event.isPopupTrigger(); case MAC: return event.isPopupTrigger();// || (event.getButton()==MouseEvent.BUTTON1&&event.isControlDown()); } return false; } public static void setChildrenEnabled(Component comp, boolean enabled) { if (comp instanceof Container) { Container parent = (Container)comp; for (Component child: parent.getComponents()) if (child instanceof Component) setChildrenEnabled(child, enabled); } } public static int getComponentIndex(Container parent, Component child) { Component[] comps = parent.getComponents(); for (int i = 0; i < comps.length; ++i) if (comps[i] == child) return i; return -1; } public static Timer runUITaskLater(final Runnable runnable, final long delay) { Timer timer = new Timer(true); timer.schedule(new TimerTask() { @Override public void run() { runUITaskLater(runnable); } }, delay); return timer; } public static void runUITaskLater(Runnable runnable) { SwingUtilities.invokeLater(runnable); } public static void runUITaskNow(Runnable runnable) { if (SwingUtilities.isEventDispatchThread()) runnable.run(); else try { SwingUtilities.invokeAndWait(runnable); } catch (InterruptedException e) { Logger.getLogger(UIUtil.class).error("", e); } catch (InvocationTargetException e) { Logger.getLogger(UIUtil.class).error("", e); } } public static interface RunnableThrow { public void run() throws Throwable; } public static void runUITaskNowThrow(final RunnableThrow runnable) throws Exception { final StrongReference<Exception> ref = new StrongReference<Exception>(); UIUtil.runUITaskNow(new Runnable() { public void run() { try { runnable.run(); } catch (Exception e) { ref.set(e); } catch (Throwable e) { ref.set(new Exception(e)); } } }); if (!ref.isNull()) throw ref.get(); } public static void dispatchEvent(EventListenerList listenerList, ActionEvent event) { ActionListener[] listeners = listenerList.getListeners(ActionListener.class); for (ActionListener listener : listeners) { listener.actionPerformed(event); } } public static ActionListener createActionEventRedispatcher(final Component redispatcher, boolean changeSource) { ActionListener listener = new ActionListener() { public void actionPerformed(ActionEvent event) { event = new ActionEvent(redispatcher, event.getID(), event.getActionCommand()); redispatcher.dispatchEvent(event); } }; return listener; } public static ActionListener createActionEventRedispatcher(final Component redispatcher) { return createActionEventRedispatcher(redispatcher, false); } public static void addColumn(CodeTable table, String name, String identifier) { TableColumnModel tableColumnModel = table.getColumnModel(); CodeTableModel tableModel = table.getModel(); TableColumn column = new TableColumn(tableColumnModel.getColumnCount()); column.setHeaderValue(name); column.setIdentifier(identifier); tableColumnModel.addColumn(column); tableModel.setColumnName(tableColumnModel.getColumnCount()-1, identifier); } public static void addColumn(JTable table, String name, Object identifier) { TableColumnModel tableColumnModel = table.getColumnModel(); DefaultTableModel tableModel = (DefaultTableModel)table.getModel(); TableColumn column = new TableColumn(tableColumnModel.getColumnCount()); column.setHeaderValue(name); column.setIdentifier(identifier); tableColumnModel.addColumn(column); tableModel.addColumn(identifier); } public static void addColumn(KTreeTable table, String name, Object identifier) { TableColumnModel tableColumnModel = table.getColumnModel(); KTreeTableModel treeTableModel = table.getTreeTableModel(); TableColumn column = new TableColumn(tableColumnModel.getColumnCount()); column.setHeaderValue(name); column.setIdentifier(identifier); tableColumnModel.addColumn(column); treeTableModel.addColumn(identifier); } public static void setColumnFixedSize(JTable table, Object identifier, int width) { table.getColumn(identifier).setMinWidth(width); table.getColumn(identifier).setMaxWidth(width); table.getColumn(identifier).setResizable(false); } private static MouseMotionListener JListDragOnPressMotionListener = new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { JList list = (JList)e.getSource(); int index = list.locationToIndex(e.getPoint()); if (index != -1) { // if pressed on part of current selection, maintain the selection // otherwise create a new selection boolean isSelected = false; for (int i : list.getSelectedIndices()) if (i == index) isSelected = true; if (!isSelected) list.setSelectedIndex(index); } TransferHandler handler = list.getTransferHandler(); handler.exportAsDrag(list, e, TransferHandler.COPY); } }; private static MouseListener JListDragOnPressListener = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { e.getComponent().removeMouseMotionListener(JListDragOnPressMotionListener); e.getComponent().addMouseMotionListener(JListDragOnPressMotionListener); } @Override public void mouseReleased(MouseEvent e) { e.getComponent().removeMouseMotionListener(JListDragOnPressMotionListener); } }; private static MouseMotionListener JTableDragOnPressMotionListener = new MouseMotionAdapter() { @Override public void mouseDragged(MouseEvent e) { if (e.getButton() != MouseEvent.BUTTON1) return; JTable table = (JTable)e.getSource(); int index = table.rowAtPoint(e.getPoint()); if (index != -1) { // if pressed on part of current selection, maintain the selection boolean isSelected = false; int[] selectedRows = table.getSelectedRows(); for (int i : selectedRows) if (i == index) isSelected = true; if (!isSelected) table.getSelectionModel().setSelectionInterval(index, index); } TransferHandler handler = table.getTransferHandler(); handler.exportAsDrag(table, e, TransferHandler.MOVE); } }; private static MouseListener JTableDragOnPressListener = new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { e.getComponent().removeMouseMotionListener(JTableDragOnPressMotionListener); e.getComponent().addMouseMotionListener(JTableDragOnPressMotionListener); } @Override public void mouseReleased(MouseEvent e) { e.getComponent().removeMouseMotionListener(JTableDragOnPressMotionListener); } }; public static void setDragOnPress(JTable table) { table.removeMouseListener(JTableDragOnPressListener); table.addMouseListener(JTableDragOnPressListener); } public static void setDragOnPress(JList list) { list.removeMouseListener(JListDragOnPressListener); list.addMouseListener(JListDragOnPressListener); } public static MouseListener forwardMouseEvents(Component origin, final Component target) { MouseListener listener = new MouseListener() { private MouseEvent fixMouseEvent(MouseEvent e) { return new MouseEvent(target, e.getID(), e.getWhen(), e.getModifiers(), e.getX(), e.getY(), e.getClickCount(), e.isPopupTrigger(), e.getButton()); } public void mouseClicked(MouseEvent e) { target.dispatchEvent(fixMouseEvent(e)); } public void mouseEntered(MouseEvent e) { target.dispatchEvent(fixMouseEvent(e)); } public void mouseExited(MouseEvent e) { target.dispatchEvent(fixMouseEvent(e)); } public void mousePressed(MouseEvent e) { target.dispatchEvent(fixMouseEvent(e)); } public void mouseReleased(MouseEvent e) { target.dispatchEvent(fixMouseEvent(e)); } }; origin.addMouseListener(listener); return listener; } public static String createUniqueName(KNode parent, String base, String prefix) { if (prefix.length() == 0) //otherwise we'd loop indefinite throw new IllegalArgumentException("empty prefix"); String name = base; while (true) { int length = parent.getChildCount(); for (int i = 0; i < length; ++i) { KNode child = parent.getChildAt(i); if (child.getName().equals(name)) { name = prefix + name; i = -1; // -1 cause of subsequent ++, java has no unsigned =P must start over since name has changed continue; } } break; } return name; } public static String createUniqueName(KNode parent, String base) { String name = base; List<Integer> numbers = new ArrayList<Integer>(); for (int i = 0; i < parent.getChildCount(); ++i) numbers.add(i); List<Integer> toRemove = new ArrayList<Integer>(); for (int i = 0; i < parent.getChildCount(); ++i) { KNode child = parent.getChildAt(i); if (child.getName() == null) continue; if (child.getName().equals(name)) { toRemove.add(0); continue; } if (!child.getName().startsWith(name + " ")) continue; try { int num = Integer.parseInt(child.getName().substring(name.length() + 1)); toRemove.add(num); } catch (NumberFormatException e) { // nothing } } numbers.removeAll(toRemove); int num = 0; if (numbers.isEmpty()) { num = parent.getChildCount(); } else { num = numbers.get(0); } if (num > 0) name = name + " " + num; return name; } public static Element resourceTreeToDom(Document doc, FolderNode treeRoot) { Element docRoot = doc.createElement("resources"); docRoot.appendChild(treeRoot.toDomElement(doc)); return docRoot; } public static boolean isRegularDoubleClick(MouseEvent event) { return event.getButton() == 1 && event.getClickCount() == 2; } public static boolean isRegularSingleClick(MouseEvent event) { return !event.isPopupTrigger() && event.getButton() == 1 && event.getClickCount() == 1; } public static boolean isRooted(TreeNode root, TreeNode node) { while (node != null) { if (node == root) return true; node = node.getParent(); } return false; } public static JMenuItem createMenuItem(final EventListenerList listenerList, String label, Integer mnemonic, final String actionCommand) { final JMenuItem menuItem = new JMenuItem(label); if (mnemonic != null) menuItem.setMnemonic(mnemonic); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { UIUtil.dispatchEvent(listenerList, new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, actionCommand)); } }); return menuItem; } public static JCheckBoxMenuItem createCheckboxMenuItem(final EventListenerList listenerList, String label, final String actionCommand) { final JCheckBoxMenuItem menuItem = new JCheckBoxMenuItem(label); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { UIUtil.dispatchEvent(listenerList, new ActionEvent(menuItem, ActionEvent.ACTION_PERFORMED, actionCommand)); } }); return menuItem; } private static JFrame _graphicsConfigurator = new JFrame(); private static GraphicsConfiguration getGraphicsConfiguration() { if (_graphicsConfigurator == null) _graphicsConfigurator = new JFrame(); return _graphicsConfigurator.getGraphicsConfiguration(); } public static Dimension getAvailableScreenSize() { Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration()); screenSize.width -= screenInsets.left + screenInsets.right; screenSize.height -= screenInsets.top + screenInsets.bottom; return screenSize; } public static Dimension constrainSizeToScreen(Component comp) { Dimension screenSize = getAvailableScreenSize(); Dimension size = comp.getSize(); size.width = Math.min(size.width, screenSize.width); size.height = Math.min(size.height, screenSize.height); comp.setSize(size); return size; } public static void centerOnScreen(Window dialog) { Dimension screenSize = getAvailableScreenSize(); Dimension size = dialog.getSize(); dialog.setLocation((screenSize.width-size.width)/2, (screenSize.height-size.height)/2); } public static void centerOnScreen(JDialog dialog) { Dimension screenSize = getAvailableScreenSize(); Dimension size = dialog.getSize(); dialog.setLocation((screenSize.width-size.width)/2, (screenSize.height-size.height)/2); } public static void centerOnFrame(JDialog dialog, Window frame) { dialog.setLocation(frame.getX() + (frame.getWidth() - dialog.getWidth())/2, frame.getY() + (frame.getHeight() - dialog.getHeight())/2); } public static void centerOnFrame(JDialog dialog, JFrame frame) { dialog.setLocation(frame.getX() + (frame.getWidth() - dialog.getWidth())/2, frame.getY() + (frame.getHeight() - dialog.getHeight())/2); } public static void centerOnFrame(JFrame dialog, JFrame frame) { dialog.setLocation(frame.getX() + (frame.getWidth() - dialog.getWidth())/2, frame.getY() + (frame.getHeight() - dialog.getHeight())/2); } /** * Simulates the WINDOW_CLOSING event. * This is pertinant since it allows JFrame/JDialog defaultCloseOperation and the event hooks to operate as usual. * @author d * */ public static void closeWindow(Window window) { WindowEvent event = new WindowEvent(window, WindowEvent.WINDOW_CLOSING); Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); event = new WindowEvent(window, WindowEvent.WINDOW_CLOSED); Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(event); SunToolkit.flushPendingEvents(); // force them to happen synchronously window.dispose(); // for some reason the above doesn't always work } }