/* * GUIUtilities.java - Various GUI utility functions * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 1999, 2004 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit; //{{{ Imports import org.gjt.sp.jedit.browser.VFSFileChooserDialog; import org.gjt.sp.jedit.gui.DynamicContextMenuService; import org.gjt.sp.jedit.gui.EnhancedButton; import org.gjt.sp.jedit.gui.FloatingWindowContainer; import org.gjt.sp.jedit.gui.SplashScreen; import org.gjt.sp.jedit.gui.VariableGridLayout; import org.jedit.keymap.Keymap; import org.gjt.sp.jedit.menu.EnhancedCheckBoxMenuItem; import org.gjt.sp.jedit.menu.EnhancedMenu; import org.gjt.sp.jedit.menu.EnhancedMenuItem; import org.gjt.sp.jedit.syntax.SyntaxStyle; import org.gjt.sp.jedit.textarea.JEditTextArea; import org.gjt.sp.jedit.textarea.TextAreaMouseHandler; import org.gjt.sp.util.Log; import org.gjt.sp.util.SyntaxUtilities; import java.net.URL; import java.util.*; import java.util.List; import java.lang.ref.SoftReference; import javax.swing.AbstractButton; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; 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.JTextPane; import javax.swing.JToolBar; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.UIDefaults; import java.awt.*; import java.awt.event.*; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; //}}} /** Various GUI utility functions related to icons, menus, toolbars, keyboard shortcuts, etc. * * The most frequently used members of this class are: * * <ul> * <li>{@link #loadIcon(String)}</li> * <li>{@link #confirm(Component,String,Object[],int,int)}</li> * <li>{@link #error(Component,String,Object[])}</li> * <li>{@link #message(Component,String,Object[])}</li> * <li>{@link #showVFSFileDialog(View,String,int,boolean)}</li> * <li>{@link #loadGeometry(Window,String)}</li> * <li>{@link #saveGeometry(Window,String)}</li> * <li>{@link #showPopupMenu(JPopupMenu,Component,int,int)}</li> * </ul> * * @author Slava Pestov * @version $Id: GUIUtilities.java 23857 2015-02-07 23:18:09Z ezust $ */ public class GUIUtilities { //{{{ Icon methods //{{{ setIconPath() method /** * Sets the path where jEdit looks for icons. * @since jEdit 4.2pre5 */ public static void setIconPath(String iconPath) { GUIUtilities.iconPath = iconPath; iconCache = null; } //}}} //{{{ loadIcon() method /** * Loads an icon. * @param iconName The icon name * @since jEdit 2.6pre7 */ public static Icon loadIcon(String iconName) { if(iconName == null) return null; // * Enable old icon naming scheme support if(deprecatedIcons != null && deprecatedIcons.containsKey(iconName)) iconName = deprecatedIcons.get(iconName); // check if there is a cached version first Map<String, Icon> cache = null; if(iconCache != null) { cache = iconCache.get(); } if(cache == null) { cache = new Hashtable<String, Icon>(); iconCache = new SoftReference<Map<String, Icon>>(cache); } Icon icon = cache.get(iconName); if(icon != null) return icon; URL url; try { // get the icon if(MiscUtilities.isURL(iconName)) url = new URL(iconName); else url = new URL(iconPath + iconName); } catch(Exception e) { try { url = new URL(defaultIconPath + iconName); } catch(Exception ex) { Log.log(Log.ERROR,GUIUtilities.class, "Icon not found: " + iconName); Log.log(Log.ERROR,GUIUtilities.class,ex); return null; } } icon = new ImageIcon(url); cache.put(iconName,icon); return icon; } //}}} //{{{ getEditorIcon() method /** * Returns the default editor window image. */ public static Image getEditorIcon() { return ((ImageIcon)loadIcon(jEdit.getProperty("logo.icon.medium"))).getImage(); } //}}} //{{{ getPluginIcon() method /** * Returns the default plugin window image. */ public static Image getPluginIcon() { return getEditorIcon(); } //}}} //}}} //{{{ Menus, tool bars //{{{ loadMenuBar() method /** * Creates a menubar. Plugins should not need to call this method. * @param name The menu bar name * @since jEdit 3.2pre5 */ public static JMenuBar loadMenuBar(String name) { return loadMenuBar(jEdit.getActionContext(),name); } //}}} //{{{ loadMenuBar() method /** * Creates a menubar. Plugins should not need to call this method. * @param context An action context * @param name The menu bar name * @since jEdit 4.2pre1 */ public static JMenuBar loadMenuBar(ActionContext context, String name) { String menus = jEdit.getProperty(name); StringTokenizer st = new StringTokenizer(menus); JMenuBar mbar = new JMenuBar(); while(st.hasMoreTokens()) { String menuName = st.nextToken(); mbar.add(loadMenu(context, menuName)); } return mbar; } //}}} //{{{ loadMenu() method /** * Creates a menu. The menu label is set from the * <code><i>name</i>.label</code> property. The menu contents is taken * from the <code><i>name</i></code> property, which is a whitespace * separated list of action names. An action name of <code>-</code> * inserts a separator in the menu. * @param name The menu name * @see #loadMenuItem(String) * @since jEdit 2.6pre2 */ public static JMenu loadMenu(String name) { return loadMenu(jEdit.getActionContext(),name); } //}}} //{{{ loadMenu() method /** * Creates a menu. The menu label is set from the * <code><i>name</i>.label</code> property. The menu contents is taken * from the <code><i>name</i></code> property, which is a whitespace * separated list of action names. An action name of <code>-</code> * inserts a separator in the menu. * @param context An action context; either * <code>jEdit.getActionContext()</code> or * <code>VFSBrowser.getActionContext()</code>. * @param name The menu name * @see #loadMenuItem(String) * @since jEdit 4.2pre1 */ public static JMenu loadMenu(ActionContext context, String name) { return new EnhancedMenu(name, jEdit.getProperty(name.concat(".label")), context); } //}}} //{{{ loadPopupMenu() method /** * Creates a popup menu. * @param name The menu name * @since jEdit 2.6pre2 */ public static JPopupMenu loadPopupMenu(String name, JEditTextArea textArea, MouseEvent evt) { return loadPopupMenu(jEdit.getActionContext(), name, textArea, evt); } //}}} //{{{ loadPopupMenu() method /** * Creates a popup menu. * @param name The menu name * @since jEdit 2.6pre2 */ public static JPopupMenu loadPopupMenu(String name) { return loadPopupMenu(jEdit.getActionContext(),name); } //}}} //{{{ loadPopupMenu() method /** * Creates a popup menu. * @param context An action context; either * <code>jEdit.getActionContext()</code> or * <code>VFSBrowser.getActionContext()</code>. * @param name The menu name * @since jEdit 4.2pre1 */ public static JPopupMenu loadPopupMenu(ActionContext context, String name) { return loadPopupMenu(context, name, null, null); } //}}} //{{{ loadPopupMenu() method /** * Creates a popup menu. * @param context An action context; either * <code>jEdit.getActionContext()</code> or * <code>VFSBrowser.getActionContext()</code>. * @param name The menu name * @param textArea the textArea wanting to show the popup. * If not null, include context menu items defined by services. * @param evt additional context info about where the mouse was when menu was requested * @since jEdit 4.3pre15 */ public static JPopupMenu loadPopupMenu(ActionContext context, String name, JEditTextArea textArea, MouseEvent evt) { JPopupMenu menu = new JPopupMenu(); String menuItems = jEdit.getProperty(name); if(menuItems != null) { StringTokenizer st = new StringTokenizer(menuItems); while(st.hasMoreTokens()) { String menuItemName = st.nextToken(); if("-".equals(menuItemName)) menu.addSeparator(); else menu.add(loadMenuItem(context,menuItemName,false)); } } // load menu items defined by services if (textArea != null) { List<JMenuItem> list = GUIUtilities.getServiceContextMenuItems(textArea, evt); if (!list.isEmpty()) { menu.addSeparator(); } for (JMenuItem mi : list) { menu.add(mi); } } return menu; } //}}} //{{{ addServiceContextMenuItems() method /** * @return a list of menu items defined by services. * * @param textArea the TextArea desiring to display these menu items * @since jEdit 4.3pre15 */ public static List<JMenuItem> getServiceContextMenuItems(JEditTextArea textArea, MouseEvent evt) { List<JMenuItem> list = new ArrayList<JMenuItem>(); String serviceClassName = DynamicContextMenuService.class.getName(); String[] menuServiceList = ServiceManager.getServiceNames(serviceClassName); for (String menuServiceName : menuServiceList) { if (menuServiceName != null && menuServiceName.trim().length() > 0) { DynamicContextMenuService dcms = (DynamicContextMenuService) ServiceManager.getService(serviceClassName, menuServiceName); if (dcms != null) { JMenuItem[] items = dcms.createMenu(textArea, evt); if (items != null) { list.addAll(Arrays.asList(items)); } } } } return list; } //}}} //{{{ loadMenuItem() method /** * Creates a menu item. The menu item is bound to the action named by * <code>name</code> with label taken from the return value of the * {@link EditAction#getLabel()} method. * * @param name The menu item name * @see #loadMenu(String) * @since jEdit 2.6pre1 */ public static JMenuItem loadMenuItem(String name) { return loadMenuItem(jEdit.getActionContext(),name,true); } //}}} //{{{ loadMenuItem() method /** * Creates a menu item. * @param name The menu item name * @param setMnemonic True if the menu item should have a mnemonic * @since jEdit 3.1pre1 */ public static JMenuItem loadMenuItem(String name, boolean setMnemonic) { return loadMenuItem(jEdit.getActionContext(),name,setMnemonic); } //}}} //{{{ loadMenuItem() method /** * Creates a menu item. * @param context An action context; either * <code>jEdit.getActionContext()</code> or * <code>VFSBrowser.getActionContext()</code>. * @param name The menu item name * @param setMnemonic True if the menu item should have a mnemonic * @since jEdit 4.2pre1 */ public static JMenuItem loadMenuItem(ActionContext context, String name, boolean setMnemonic) { if(name.charAt(0) == '%') return loadMenu(context,name.substring(1)); return _loadMenuItem(name, context, setMnemonic); } //}}} //{{{ loadMenuItem(EditAction, boolean) public static JMenuItem loadMenuItem(EditAction editAction, boolean setMnemonic) { String name = editAction.getName(); ActionContext context = jEdit.getActionContext(); return _loadMenuItem(name, context, setMnemonic); } //}}} //{{{ loadToolBar() method /** * Creates a toolbar. * @param name The toolbar name * @since jEdit 4.2pre2 */ public static Container loadToolBar(String name) { return loadToolBar(jEdit.getActionContext(),name); } //}}} //{{{ loadToolBar() method /** * Creates a toolbar. * @param context An action context; either * <code>jEdit.getActionContext()</code> or * <code>VFSBrowser.getActionContext()</code>. * @param name The toolbar name * @since jEdit 4.2pre2 */ public static Container loadToolBar(ActionContext context, String name) { JToolBar toolB = new JToolBar(); toolB.setName(name); toolB.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); toolB.setFloatable(jEdit.getBooleanProperty("view.toolbar.floatable")); String buttons = jEdit.getProperty(name); if(buttons != null) { StringTokenizer st = new StringTokenizer(buttons); while(st.hasMoreTokens()) { String button = st.nextToken(); if("-".equals(button)) { toolB.addSeparator(new Dimension(12,12)); } else { JButton b = loadToolButton(context,button); if(b != null) toolB.add(b); } } } toolB.addSeparator(new Dimension(12,12)); return toolB; } //}}} //{{{ loadToolButton() method /** * Loads a tool bar button. The tooltip is constructed from * the <code><i>name</i>.label</code> and * <code><i>name</i>.shortcut</code> properties and the icon is loaded * from the resource named '/org/gjt/sp/jedit/icons/' suffixed * with the value of the <code><i>name</i>.icon</code> property. * @param name The name of the button */ public static EnhancedButton loadToolButton(String name) { return loadToolButton(jEdit.getActionContext(),name); } //}}} //{{{ loadToolButton() method /** * Loads a tool bar button. The tooltip is constructed from * the <code><i>name</i>.label</code> and * <code><i>name</i>.shortcut</code> properties and the icon is loaded * from the resource named '/org/gjt/sp/jedit/icons/' suffixed * with the value of the <code><i>name</i>.icon</code> property. * @param context An action context; either * <code>jEdit.getActionContext()</code> or * <code>VFSBrowser.getActionContext()</code>. * @param name The name of the button * @since jEdit 4.2pre1 */ public static EnhancedButton loadToolButton(ActionContext context, String name) { String label = jEdit.getProperty(name + ".label"); if(label == null) label = name; Icon icon; String iconName = jEdit.getProperty(name + ".icon"); if(iconName == null) icon = loadIcon(jEdit.getProperty("broken-image.icon")); else { icon = loadIcon(iconName); if(icon == null) icon = loadIcon(jEdit.getProperty("broken-image.icon")); } String toolTip = prettifyMenuLabel(label); String shortcutLabel = getShortcutLabel(name, true); if(shortcutLabel != null) { toolTip = toolTip + " (" + shortcutLabel + ')'; } EnhancedButton b = new EnhancedButton(icon,toolTip,name,context); b.setPreferredSize(new Dimension(32,32)); return b; } //}}} //{{{ prettifyMenuLabel() method /** * `Prettifies' a menu item label by removing the `$' sign. This * can be used to process the contents of an <i>action</i>.label * property. */ public static String prettifyMenuLabel(String label) { int index = label.indexOf('$'); if(index != -1) { label = label.substring(0,index) .concat(label.substring(index + 1)); } return label; } //}}} //{{{ getPlatformShortcutLabel() method /** * Translates a shortcut description string (e.g. "CS+SEMICOLON") to * a platform-localized description. On OS X this puts in the pretty * unicode characters for Shift, Cmd, etc. */ public static String getPlatformShortcutLabel(String label) { if( !OperatingSystem.isMacOSLF() || label == null || label.length() == 0) return label; String[] strokes = label.split(" +"); StringBuilder out = new StringBuilder(); for (int i = 0; i < strokes.length; i++) { if (i > 0) out.append(' '); out.append(getMacShortcutLabel(strokes[i])); } return out.toString(); } //}}} //{{{ getShortcutLabel() method /** * Returns a label string to show users what shortcut are * assigned to the action. * @param platform if true, show fancy platform-specific label for the modifiers. */ public static String getShortcutLabel(String action, Boolean platform) { if(action == null) return null; else { Keymap keymap = jEdit.getKeymapManager().getKeymap(); String shortcut1 = keymap.getShortcut(action + ".shortcut"); String shortcut2 = keymap.getShortcut(action + ".shortcut2"); shortcut1 = platform ? getPlatformShortcutLabel(shortcut1) : shortcut1; shortcut2 = platform ? getPlatformShortcutLabel(shortcut2) : shortcut2; if(shortcut1 == null || shortcut1.length() == 0) { if(shortcut2 == null || shortcut2.length() == 0) return null; else return shortcut2; } else { if(shortcut2 == null || shortcut2.length() == 0) return shortcut1; else return shortcut1 + " or " + shortcut2; } } } //}}} //{{{ setAutoMnemonic() method /** * Sets the mnemonic for the given button using jEdit convention, * taking the letter after the dollar. * @param button The button to set the mnemonic for. * @since jEdit 5.1 */ public static void setAutoMnemonic(AbstractButton button) { String label = button.getText(); char mnemonic; int index = label.indexOf('$'); if (index != -1 && label.length() - index > 1) { mnemonic = Character.toLowerCase(label.charAt(index + 1)); label = label.substring(0, index).concat(label.substring(++index)); } else { mnemonic = '\0'; } if (mnemonic != '\0') { button.setMnemonic(mnemonic); button.setText(label); } } //}}} //}}} //{{{ Canned dialog boxes //{{{ message() method /** * Displays a dialog box. * The title of the dialog is fetched from * the <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. The message * is formatted by the property manager with <code>args</code> as * positional parameters. * @param comp The component to display the dialog for * @param name The name of the dialog * @param args Positional parameters to be substituted into the * message text */ public static void message(final Component comp, final String name, final Object[] args) { if (EventQueue.isDispatchThread()) { hideSplashScreen(); JOptionPane.showMessageDialog(comp, jEdit.getProperty(name.concat(".message"),args), jEdit.getProperty(name.concat(".title"),args), JOptionPane.INFORMATION_MESSAGE); } else { try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { message(comp, name, args); } }); } catch (Exception e) { // ignored } } } //}}} //{{{ error() method /** * Displays an error dialog box. * The title of the dialog is fetched from * the <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. The message * is formatted by the property manager with <code>args</code> as * positional parameters. * @param comp The component to display the dialog for * @param name The name of the dialog * @param args Positional parameters to be substituted into the * message text */ public static void error(final Component comp, final String name, final Object[] args) { if (EventQueue.isDispatchThread()) { hideSplashScreen(); JOptionPane.showMessageDialog(comp, jEdit.getProperty(name.concat(".message"),args), jEdit.getProperty(name.concat(".title"),args), JOptionPane.ERROR_MESSAGE); } else { try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { error(comp, name, args); } }); } catch (Exception e) { // ignored } } } //}}} //{{{ input() method /** * Displays an input dialog box and returns any text the user entered. * The title of the dialog is fetched from * the <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. * @param comp The component to display the dialog for * @param name The name of the dialog * @param def The text to display by default in the input field */ public static String input(Component comp, String name, Object def) { return input(comp,name,null,def); } //}}} //{{{ inputProperty() method /** * Displays an input dialog box and returns any text the user entered. * The title of the dialog is fetched from * the <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. * @param comp The component to display the dialog for * @param name The name of the dialog * @param def The property whose text to display in the input field */ public static String inputProperty(Component comp, String name, String def) { return inputProperty(comp,name,null,def); } //}}} //{{{ input() method /** * Displays an input dialog box and returns any text the user entered. * The title of the dialog is fetched from * the <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. * @param comp The component to display the dialog for * @param name The name of the dialog * @param def The text to display by default in the input field * @param args Positional parameters to be substituted into the * message text * @since jEdit 3.1pre3 */ public static String input(final Component comp, final String name, final Object[] args, final Object def) { if (EventQueue.isDispatchThread()) { hideSplashScreen(); return (String)JOptionPane.showInputDialog(comp, jEdit.getProperty(name.concat(".message"),args), jEdit.getProperty(name.concat(".title")), JOptionPane.QUESTION_MESSAGE,null,null,def); } final String[] retValue = new String[1]; try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { retValue[0] = input(comp, name, args, def); } }); } catch (Exception e) { return null; } return retValue[0]; } //}}} //{{{ inputProperty() method /** * Displays an input dialog box and returns any text the user entered. * The title of the dialog is fetched from * the <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. * @param comp The component to display the dialog for * @param name The name of the dialog * @param args Positional parameters to be substituted into the * message text * @param def The property whose text to display in the input field * @since jEdit 3.1pre3 */ public static String inputProperty(final Component comp, final String name, final Object[] args, final String def) { if (EventQueue.isDispatchThread()) { hideSplashScreen(); String retVal = (String)JOptionPane.showInputDialog(comp, jEdit.getProperty(name.concat(".message"),args), jEdit.getProperty(name.concat(".title")), JOptionPane.QUESTION_MESSAGE, null,null,jEdit.getProperty(def)); if(retVal != null) jEdit.setProperty(def,retVal); return retVal; } final String[] retValue = new String[1]; try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { retValue[0] = inputProperty(comp, name, args, def); } }); } catch (Exception e) { return null; } return retValue[0]; } //}}} //{{{ confirm() method /** * Displays a confirm dialog box and returns the button pushed by the * user. The title of the dialog is fetched from the * <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. * @param comp The component to display the dialog for * @param name The name of the dialog * @param args Positional parameters to be substituted into the * message text * @param buttons The buttons to display - for example, * JOptionPane.YES_NO_CANCEL_OPTION * @param type The dialog type - for example, * JOptionPane.WARNING_MESSAGE * @since jEdit 3.1pre3 */ public static int confirm(final Component comp, final String name, final Object[] args, final int buttons, final int type) { if (EventQueue.isDispatchThread()) { hideSplashScreen(); return JOptionPane.showConfirmDialog(comp, jEdit.getProperty(name + ".message",args), jEdit.getProperty(name + ".title"),buttons,type); } final int [] retValue = new int[1]; try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { retValue[0] = confirm(comp, name, args, buttons, type); } }); } catch (Exception e) { return JOptionPane.CANCEL_OPTION; } return retValue[0]; } //}}} //{{{ option() method /** * Displays an option dialog dialog box and returns the button pushed by the * user. The title of the dialog is fetched from the * <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. * @param comp The component to display the dialog for * @param name The name of the dialog * @param args Positional parameters to be substituted into the * message text * @param type The dialog type - for example, * JOptionPane.WARNING_MESSAGE * @param options the buttons * @param initialValue the initial value * @since jEdit 4.5pre1 */ public static int option(final Component comp, final String name, final Object[] args, final int type, final Object[] options, final Object initialValue) { if (EventQueue.isDispatchThread()) { hideSplashScreen(); return JOptionPane.showOptionDialog(comp, jEdit.getProperty(name + ".message",args), jEdit.getProperty(name + ".title"),JOptionPane.DEFAULT_OPTION, type, null, options, initialValue); } final int[] retValue = new int[1]; try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { retValue[0] = option(comp, name, args, type, options, initialValue); } }); } catch (Exception e) { return 0; } return retValue[0]; } //}}} //{{{ listConfirm() method /** * Displays a confirm dialog box and returns the button pushed by the * user. The title of the dialog is fetched from the * <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. The dialog * also shows a list of entries given by the <code>listModel</code> * parameter. * @param comp the parent component * @param name the name of the confirm dialog * @param args the for the message * @param listModel the items in the list * @return an integer indicating the option selected by the user * @since jEdit 4.3pre1 */ public static int listConfirm(final Component comp, final String name, final String[] args, final Object[] listModel) { if (EventQueue.isDispatchThread()) { JList list = new JList(listModel); list.setVisibleRowCount(8); Object[] message = { jEdit.getProperty(name + ".message",args), new JScrollPane(list) }; return JOptionPane.showConfirmDialog(comp, message, jEdit.getProperty(name + ".title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); } final int [] retValue = new int[1]; try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { retValue[0] = listConfirm(comp, name, args, listModel); } }); } catch (Exception e) { return JOptionPane.CANCEL_OPTION; } return retValue[0]; } //}}} //{{{ listConfirm() method /** * Displays a confirm dialog box and returns the button pushed by the * user. The title of the dialog is fetched from the * <code><i>name</i>.title</code> property. The message is fetched * from the <code><i>name</i>.message</code> property. The dialog * also shows a list of entries given by the <code>listModel</code> * parameter. * @param comp the parent component * @param name the name of the confirm dialog * @param args the for the message * @param listModel the items in the list * @param selectedItems give an empty list, it will contains in return the selected items * @return an integer indicating the option selected by the user * @since jEdit 4.3pre12 */ public static int listConfirm(final Component comp, final String name, final String[] args, final Object[] listModel, final List selectedItems) { if (EventQueue.isDispatchThread()) { JList list = new JList(listModel); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); list.setVisibleRowCount(8); list.addSelectionInterval(0,listModel.length - 1); Object[] message = { jEdit.getProperty(name + ".message",args), new JScrollPane(list) }; int ret = JOptionPane.showConfirmDialog(comp, message, jEdit.getProperty(name + ".title"), JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); Object[] selectedValues = list.getSelectedValues(); selectedItems.addAll(Arrays.asList(selectedValues)); return ret; } final int [] retValue = new int[1]; try { EventQueue.invokeAndWait(new Runnable() { @Override public void run() { retValue[0] = listConfirm(comp, name, args, listModel, selectedItems); } }); } catch (Exception e) { return JOptionPane.CANCEL_OPTION; } return retValue[0]; } //}}} //{{{ showVFSFileDialog() methods /** * Displays a VFS file selection dialog box. * @param view The view, should be non-null * @param path The initial directory to display. May be null * @param type The dialog type. One of * {@link org.gjt.sp.jedit.browser.VFSBrowser#OPEN_DIALOG}, * {@link org.gjt.sp.jedit.browser.VFSBrowser#SAVE_DIALOG}, or * {@link org.gjt.sp.jedit.browser.VFSBrowser#CHOOSE_DIRECTORY_DIALOG}. * @param multipleSelection True if multiple selection should be allowed * @return The selected file(s) * @since jEdit 2.6pre2 */ public static String[] showVFSFileDialog(View view, String path, int type, boolean multipleSelection) { // the view should not be null, but some plugins might do this if(view == null) { Log.log(Log.WARNING,GUIUtilities.class, "showVFSFileDialog(): given null view, assuming jEdit.getActiveView()"); view = jEdit.getActiveView(); } hideSplashScreen(); VFSFileChooserDialog fileChooser = new VFSFileChooserDialog( view,path,type,multipleSelection); return fileChooser.getSelectedFiles(); } /** * Displays a VFS file selection dialog box. * This version can specify a dialog as the parent instead * of the view. * @param view The view, should be non-null * @param path The initial directory to display. May be null * @param type The dialog type. One of * {@link org.gjt.sp.jedit.browser.VFSBrowser#OPEN_DIALOG}, * {@link org.gjt.sp.jedit.browser.VFSBrowser#SAVE_DIALOG}, or * {@link org.gjt.sp.jedit.browser.VFSBrowser#CHOOSE_DIRECTORY_DIALOG}. * @param multipleSelection True if multiple selection should be allowed * @return The selected file(s) * @since jEdit 4.3pre10 */ public static String[] showVFSFileDialog(Dialog parent, View view, String path, int type, boolean multipleSelection) { hideSplashScreen(); VFSFileChooserDialog fileChooser = new VFSFileChooserDialog( parent, view, path, type, multipleSelection, true); return fileChooser.getSelectedFiles(); } /** * Displays a VFS file selection dialog box. * This version can specify a frame as the parent instead * of the view. * @param parent The parent frame * @param view The view, should be non-null * @param path The initial directory to display. May be null * @param type The dialog type. One of * {@link org.gjt.sp.jedit.browser.VFSBrowser#OPEN_DIALOG}, * {@link org.gjt.sp.jedit.browser.VFSBrowser#SAVE_DIALOG}, or * {@link org.gjt.sp.jedit.browser.VFSBrowser#CHOOSE_DIRECTORY_DIALOG}. * @param multipleSelection True if multiple selection should be allowed * @return The selected file(s) * @since jEdit 4.3pre10 */ public static String[] showVFSFileDialog(Frame parent, View view, String path, int type, boolean multipleSelection) { hideSplashScreen(); VFSFileChooserDialog fileChooser = new VFSFileChooserDialog( parent, view, path, type, multipleSelection, true); return fileChooser.getSelectedFiles(); } //}}} //}}} //{{{ Colors and styles //{{{ parseColor() method /** * Converts a color name to a color object. The name must either be * a known string, such as `red', `green', etc (complete list is in * the <code>java.awt.Color</code> class) or a hex color value * prefixed with `#', for example `#ff0088'. * @param name The color name */ public static Color parseColor(String name) { return SyntaxUtilities.parseColor(name, Color.black); } //}}} //{{{ parseStyle() method /** * Converts a style string to a style object. * @param str The style string * @param family Style strings only specify font style, not font family * @param size Style strings only specify font style, not font family * @exception IllegalArgumentException if the style is invalid * @since jEdit 3.2pre6 */ public static SyntaxStyle parseStyle(String str, String family, int size) throws IllegalArgumentException { return SyntaxUtilities.parseStyle(str,family,size,true); } //}}} //{{{ getStyleString() method /** * Converts a style into it's string representation. * @param style The style */ public static String getStyleString(SyntaxStyle style) { StringBuilder buf = new StringBuilder(); if (style.getForegroundColor() != null) { buf.append("color:").append(SyntaxUtilities.getColorHexString(style.getForegroundColor())); } if (style.getBackgroundColor() != null) { buf.append(" bgColor:").append(SyntaxUtilities.getColorHexString(style.getBackgroundColor())); } Font font = style.getFont(); if (!font.isPlain()) { buf.append(" style:"); if (font.isItalic()) buf.append('i'); if (font.isBold()) buf.append('b'); } return buf.toString(); } //}}} //}}} //{{{ Loading, saving window geometry //{{{ loadGeometry() method /** * Loads a windows's geometry from the properties. * The geometry is loaded from the <code><i>name</i>.x</code>, * <code><i>name</i>.y</code>, <code><i>name</i>.width</code> and * <code><i>name</i>.height</code> properties. * * @param win The window to load geometry from * @param parent The parent frame to be relative to. * @param name The name of the window */ public static void loadGeometry(Window win, Container parent, String name ) { Dimension size = win.getSize(); int width = jEdit.getIntegerProperty(name + ".width", size.width); int height = jEdit.getIntegerProperty(name + ".height", size.height); int x = jEdit.getIntegerProperty(name + ".x",50); int y = jEdit.getIntegerProperty(name + ".y",50); if(parent != null) { Point location = parent.getLocation(); x = location.x + x; y = location.y + y; } int extState = jEdit.getIntegerProperty(name + ".extendedState", Frame.NORMAL); Rectangle desired = new Rectangle(x,y,width,height); try { if(!Debug.DISABLE_MULTIHEAD) adjustForScreenBounds(desired); } catch(Exception e) { /* Workaround for OS X bug. */ Log.log(Log.ERROR,GUIUtilities.class,e); } if(OperatingSystem.isX11() && Debug.GEOMETRY_WORKAROUND) new UnixWorkaround(win,name,desired,extState); else { win.setBounds(desired); if(win instanceof Frame) ((Frame)win).setExtendedState(extState); } } //}}} //{{{ loadGeometry() method /** * Loads a windows's geometry from the properties. * The geometry is loaded from the <code><i>name</i>.x</code>, * <code><i>name</i>.y</code>, <code><i>name</i>.width</code> and * <code><i>name</i>.height</code> properties. * * @param win The window to load geometry from * @param name The name of the window */ public static void loadGeometry(Window win, String name) { loadGeometry(win, win.getParent(), name); } //}}} //{{{ adjustForScreenBounds() method /** * Gives a rectangle the specified bounds, ensuring it is within the * screen bounds. * @since jEdit 4.2pre3 */ public static void adjustForScreenBounds(Rectangle desired) { // Make sure the window is displayed in visible region Rectangle osbounds = OperatingSystem.getScreenBounds(desired); if (desired.width > osbounds.width) { desired.width = osbounds.width; } if (desired.x < osbounds.x) { desired.x = osbounds.x; } if (desired.x + desired.width > osbounds.x + osbounds.width) { desired.x = osbounds.x + osbounds.width - desired.width; } if (desired.height > osbounds.height) { desired.height = osbounds.height; } if (desired.y < osbounds.y) { desired.y = osbounds.y; } if (desired.y + desired.height > osbounds.y + osbounds.height) { desired.y = osbounds.y + osbounds.height - desired.height; } } //}}} //{{{ UnixWorkaround class public static class UnixWorkaround { Window win; String name; Rectangle desired; Rectangle required; long start; boolean windowOpened; //{{{ UnixWorkaround constructor public UnixWorkaround(Window win, String name, Rectangle desired, int extState) { this.win = win; this.name = name; this.desired = desired; int adjust_x = jEdit.getIntegerProperty(name + ".dx",0); int adjust_y = jEdit.getIntegerProperty(name + ".dy",0); int adjust_width = jEdit.getIntegerProperty(name + ".d-width",0); int adjust_height = jEdit.getIntegerProperty(name + ".d-height",0); required = new Rectangle( desired.x - adjust_x, desired.y - adjust_y, desired.width - adjust_width, desired.height - adjust_height); Log.log(Log.DEBUG,GUIUtilities.class,"Window " + name + ": desired geometry is " + desired); Log.log(Log.DEBUG,GUIUtilities.class,"Window " + name + ": setting geometry to " + required); start = System.currentTimeMillis(); win.setBounds(required); if(win instanceof Frame) ((Frame)win).setExtendedState(extState); win.addComponentListener(new ComponentHandler()); win.addWindowListener(new WindowHandler()); } //}}} //{{{ ComponentHandler class private class ComponentHandler extends ComponentAdapter { //{{{ componentMoved() method @Override public void componentMoved(ComponentEvent evt) { if(System.currentTimeMillis() - start < 1000L) { Rectangle r = win.getBounds(); if(!windowOpened && r.equals(required)) return; if(!r.equals(desired)) { Log.log(Log.DEBUG,GUIUtilities.class, "Window resize blocked: " + win.getBounds()); win.setBounds(desired); } } win.removeComponentListener(this); } //}}} //{{{ componentResized() method @Override public void componentResized(ComponentEvent evt) { if(System.currentTimeMillis() - start < 1000L) { Rectangle r = win.getBounds(); if(!windowOpened && r.equals(required)) return; if(!r.equals(desired)) { Log.log(Log.DEBUG,GUIUtilities.class, "Window resize blocked: " + win.getBounds()); win.setBounds(desired); } } win.removeComponentListener(this); } //}}} } //}}} //{{{ WindowHandler class private class WindowHandler extends WindowAdapter { //{{{ windowOpened() method @Override public void windowOpened(WindowEvent evt) { windowOpened = true; Rectangle r = win.getBounds(); Log.log(Log.DEBUG,GUIUtilities.class,"Window " + name + ": bounds after opening: " + r); jEdit.setIntegerProperty(name + ".dx", r.x - required.x); jEdit.setIntegerProperty(name + ".dy", r.y - required.y); jEdit.setIntegerProperty(name + ".d-width", r.width - required.width); jEdit.setIntegerProperty(name + ".d-height", r.height - required.height); win.removeWindowListener(this); } //}}} } //}}} } //}}} //{{{ saveGeometry() method /** * Saves a window's geometry to the properties. * The geometry is saved to the <code><i>name</i>.x</code>, * <code><i>name</i>.y</code>, <code><i>name</i>.width</code> and * <code><i>name</i>.height</code> properties.<br /> * For Frame's and descendents use {@link #addSizeSaver(Frame,String)} to save the sizes * correct even if the Frame is in maximized or iconified state. * @param win The window to load geometry from * @param name The name of the window * @see #addSizeSaver(Frame,String) */ public static void saveGeometry(Window win, String name) { saveGeometry (win, win.getParent(), name); } //}}} //{{{ saveGeometry() method /** * Saves a window's geometry to the properties. * The geometry is saved to the <code><i>name</i>.x</code>, * <code><i>name</i>.y</code>, <code><i>name</i>.width</code> and * <code><i>name</i>.height</code> properties.<br /> * For Frame's and descendents use {@link #addSizeSaver(Frame,Container,String)} to save the sizes * correct even if the Frame is in maximized or iconified state. * @param win The window to load geometry from * @param parent The parent frame to be relative to. * @param name The name of the window * @see #addSizeSaver(Frame,Container,String) */ public static void saveGeometry(Window win, Container parent, String name) { if(win instanceof Frame) { jEdit.setIntegerProperty(name + ".extendedState", ((Frame)win).getExtendedState()); } Rectangle bounds = win.getBounds(); int x = bounds.x; int y = bounds.y; if (parent != null) { Rectangle parentBounds = parent.getBounds(); x -= parentBounds.x; y -= parentBounds.y; } jEdit.setIntegerProperty(name + ".x",x); jEdit.setIntegerProperty(name + ".y",y); jEdit.setIntegerProperty(name + ".width", bounds.width); jEdit.setIntegerProperty(name + ".height", bounds.height); } //}}} //}}} //{{{ hideSplashScreen() method /** * Ensures that the splash screen is not visible. This should be * called before displaying any dialog boxes or windows at * startup. */ public static void hideSplashScreen() { if(splash != null) { splash.dispose(); splash = null; } } //}}} //{{{ applyTextAreaColors() method /** * experimental - applies the text area colors on a Component * (such as a dockable window) and its children. * @since jEdit 5.0pre1 * @author ezust * */ public static void applyTextAreaColors(Container win) { for (Component child: win.getComponents()) { child.setBackground(jEdit.getColorProperty("view.bgColor", Color.WHITE)); child.setForeground(jEdit.getColorProperty("view.fgColor", Color.BLACK)); if (child instanceof JTextPane) ((JTextPane)child).setUI(new javax.swing.plaf.basic.BasicEditorPaneUI()); if (child instanceof Container) applyTextAreaColors((Container)child); } } //}}} //{{{ createMultilineLabel() method /** * Creates a component that displays a multiple line message. This * is implemented by assembling a number of <code>JLabels</code> in * a <code>JPanel</code>. * @param str The string, with lines delimited by newline * (<code>\n</code>) characters. * @since jEdit 4.1pre3 */ public static JComponent createMultilineLabel(String str) { JPanel panel = new JPanel(new VariableGridLayout( VariableGridLayout.FIXED_NUM_COLUMNS,1,1,1)); int lastOffset = 0; while(true) { int index = str.indexOf('\n',lastOffset); if(index == -1) break; else { panel.add(new JLabel(str.substring(lastOffset,index))); lastOffset = index + 1; } } if(lastOffset != str.length()) panel.add(new JLabel(str.substring(lastOffset))); return panel; } //}}} //{{{ requestFocus() method /** * Focuses on the specified component as soon as the window becomes * active. * @param win The window * @param comp The component */ public static void requestFocus(final Window win, final Component comp) { win.addWindowFocusListener(new WindowAdapter() { @Override public void windowGainedFocus(WindowEvent evt) { EventQueue.invokeLater(new Runnable() { @Override public void run() { comp.requestFocusInWindow(); } }); win.removeWindowFocusListener(this); } }); } //}}} //{{{ isPopupTrigger() method /** * Returns if the specified event is the popup trigger event. * This implements precisely defined behavior, as opposed to * MouseEvent.isPopupTrigger(). * @param evt The event * @since jEdit 3.2pre8 */ public static boolean isPopupTrigger(MouseEvent evt) { return TextAreaMouseHandler.isRightButton(evt.getModifiers()); } //}}} //{{{ isMiddleButton() method /** * @param modifiers The modifiers flag from a mouse event * @since jEdit 4.1pre9 */ public static boolean isMiddleButton(int modifiers) { return TextAreaMouseHandler.isMiddleButton(modifiers); } //}}} //{{{ isRightButton() method /** * @param modifiers The modifiers flag from a mouse event * @since jEdit 4.1pre9 */ public static boolean isRightButton(int modifiers) { return TextAreaMouseHandler.isRightButton(modifiers); } //}}} //{{{ getScreenBounds() method /** * Returns the screen bounds, taking into account multi-screen * environments. * @since jEdit 4.3pre18 */ public static Rectangle getScreenBounds() { Rectangle bounds = GraphicsEnvironment.getLocalGraphicsEnvironment(). getMaximumWindowBounds(); GraphicsDevice [] devices = GraphicsEnvironment. getLocalGraphicsEnvironment().getScreenDevices(); if (devices.length > 1) { for (GraphicsDevice device: devices) { for (GraphicsConfiguration config: device.getConfigurations()) bounds = bounds.union(config.getBounds()); } } return bounds; } //}}} //{{{ showPopupMenu() method /** * Shows the specified popup menu, ensuring it is displayed within * the bounds of the screen. * @param popup The popup menu * @param comp The component to show it for * @param x The x co-ordinate * @param y The y co-ordinate * @since jEdit 4.0pre1 * @see javax.swing.JComponent#setComponentPopupMenu(javax.swing.JPopupMenu) setComponentPopupMenu * which works better and is simpler to use: you don't have to write the code to * show/hide popups in response to mouse events anymore. */ public static void showPopupMenu(JPopupMenu popup, Component comp, int x, int y) { showPopupMenu(popup,comp,x,y,true); } //}}} //{{{ showPopupMenu() method /** * Shows the specified popup menu, ensuring it is displayed within * the bounds of the screen. * @param popup The popup menu * @param comp The component to show it for * @param x The x co-ordinate * @param y The y co-ordinate * @param point If true, then the popup originates from a single point; * otherwise it will originate from the component itself. This affects * positioning in the case where the popup does not fit onscreen. * * @since jEdit 4.1pre1 */ public static void showPopupMenu(JPopupMenu popup, Component comp, int x, int y, boolean point) { int offsetX = 0; int offsetY = 0; int extraOffset = point ? 1 : 0; Component win = comp; while(!(win instanceof Window || win == null)) { offsetX += win.getX(); offsetY += win.getY(); win = win.getParent(); } if(win != null) { Dimension size = popup.getPreferredSize(); Rectangle screenSize = getScreenBounds(); if(x + offsetX + size.width + win.getX() > screenSize.width && x + offsetX + win.getX() >= size.width) { //System.err.println("x overflow"); if(point) x -= size.width + extraOffset; else x = win.getWidth() - size.width - offsetX + extraOffset; } else { x += extraOffset; } //System.err.println("y=" + y + ",offsetY=" + offsetY // + ",size.height=" + size.height // + ",win.height=" + win.getHeight()); if(y + offsetY + size.height + win.getY() > screenSize.height && y + offsetY + win.getY() >= size.height) { if(point) y = win.getHeight() - size.height - offsetY + extraOffset; else y = -size.height - 1; } else { y += extraOffset; } popup.show(comp,x,y); } else popup.show(comp,x + extraOffset,y + extraOffset); } //}}} //{{{ isAncestorOf() method /** * Returns if the first component is an ancestor of the * second by traversing up the component hierarchy. * * @param comp1 The ancestor * @param comp2 The component to check * @since jEdit 4.1pre5 */ public static boolean isAncestorOf(Component comp1, Component comp2) { while(comp2 != null) { if(comp1 == comp2) return true; else comp2 = comp2.getParent(); } return false; } //}}} //{{{ getParentDialog() method /** * Traverses the given component's parent tree looking for an * instance of JDialog, and return it. If not found, return null. * @param c The component */ public static JDialog getParentDialog(Component c) { return (JDialog) SwingUtilities.getAncestorOfClass(JDialog.class, c); } //}}} //{{{ getComponentParent() method /** * Finds a parent of the specified component. * @param comp The component * @param clazz Looks for a parent with this class (exact match, not * derived). * @since jEdit 4.2pre1 */ public static Component getComponentParent(Component comp, Class clazz) { while(true) { if(comp == null) break; if(comp instanceof JComponent) { Component real = (Component)((JComponent)comp) .getClientProperty("KORTE_REAL_FRAME"); if(real != null) comp = real; } if(comp.getClass().equals(clazz)) return comp; else if(comp instanceof JPopupMenu) comp = ((JPopupMenu)comp).getInvoker(); else if(comp instanceof FloatingWindowContainer) { comp = ((FloatingWindowContainer)comp) .getDockableWindowManager(); } else comp = comp.getParent(); } return null; } //}}} //{{{ setEnabledRecursively() method /** * Call setEnabled() recursively on the container and its descendants. * @param c The container * @param enabled The enabled state to set * @since jEdit 4.3pre17 */ public static void setEnabledRecursively(Container c, boolean enabled) { for (Component child: c.getComponents()) { if (child instanceof Container) setEnabledRecursively((Container)child, enabled); else child.setEnabled(enabled); } c.setEnabled(enabled); } //}}} //{{{ getView() method /** * Finds the view parent of the specified component. * @param comp the component from which you want to get the parent view * @return the parent view, or null if the component was not in a View. * @since jEdit 4.0pre2 */ public static View getView(Component comp) { return (View)getComponentParent(comp,View.class); } //}}} //{{{ setButtonContentMargin() method /** * Sets the content margin of a button (for Nimbus L&F). * @param button the button to modify * @param margin the new margin * @since jEdit 5.3 */ public static void setButtonContentMargin(AbstractButton button, Insets margin) { UIDefaults defaults = new UIDefaults(); defaults.put("Button.contentMargins", margin); defaults.put("ToggleButton.contentMargins", margin); button.putClientProperty("Nimbus.Overrides", defaults); } //}}} //{{{ addSizeSaver() method /** * Adds a SizeSaver to the specified Frame. For non-Frame's use {@link #saveGeometry(Window,String)} * * @param frame The Frame for which to save the size * @param name The prefix for the settings * @since jEdit 4.3pre6 * @see #saveGeometry(Window,String) */ public static void addSizeSaver(Frame frame, String name) { addSizeSaver(frame,frame.getParent(),name); } //}}} //{{{ addSizeSaver() method /** * Adds a SizeSaver to the specified Frame. For non-Frame's use {@link #saveGeometry(Window,Container,String)} * * @param frame The Frame for which to save the size * @param parent The parent to be relative to * @param name The prefix for the settings * @since jEdit 4.3pre7 * @see #saveGeometry(Window,Container,String) */ public static void addSizeSaver(Frame frame, Container parent, String name) { SizeSaver ss = new SizeSaver(frame,parent,name); frame.addWindowStateListener(ss); frame.addComponentListener(ss); } //}}} //{{{ Package-private members //{{{ initializeDeprecatedIcons() method /** * Initializes a list of mappings between old icon names and new names */ private static void initializeDeprecatedIcons() { deprecatedIcons.put("File.png", "16x16/mimetypes/text-x-generic.png"); deprecatedIcons.put("Folder.png", "16x16/places/folder.png"); deprecatedIcons.put("OpenFolder.png", "16x16/status/folder-open.png"); deprecatedIcons.put("OpenFile.png", "16x16/actions/edit-select-all.png"); deprecatedIcons.put("ReloadSmall.png","16x16/actions/view-refresh.png"); deprecatedIcons.put("DriveSmall.png", "16x16/devices/drive-harddisk.png"); deprecatedIcons.put("New.png", "22x22/actions/document-new.png"); deprecatedIcons.put("NewDir.png", "22x22/actions/folder-new.png"); deprecatedIcons.put("Reload.png", "22x22/actions/view-refresh.png"); deprecatedIcons.put("Load.png", "22x22/places/plugins.png"); deprecatedIcons.put("Save.png", "22x22/actions/document-save.png"); deprecatedIcons.put("SaveAs.png", "22x22/actions/document-save-as.png"); deprecatedIcons.put("SaveAll.png", "22x22/actions/document-save-all.png"); deprecatedIcons.put("Open.png", "22x22/actions/document-open.png"); deprecatedIcons.put("Print.png", "22x22/actions/document-print.png"); deprecatedIcons.put("Drive.png", "22x22/devices/drive-harddisk.png"); deprecatedIcons.put("Clear.png", "22x22/actions/edit-clear.png"); deprecatedIcons.put("Run.png", "22x22/actions/application-run.png"); deprecatedIcons.put("RunAgain.png", "22x22/actions/application-run-again.png"); deprecatedIcons.put("RunToBuffer.png", "22x22/actions/run-to-buffer.png"); deprecatedIcons.put("CopyToBuffer.png", "22x22/actions/copy-to-buffer.png"); deprecatedIcons.put("Plus.png", "22x22/actions/list-add.png"); deprecatedIcons.put("Minus.png", "22x22/actions/list-remove.png"); deprecatedIcons.put("Find.png", "22x22/actions/edit-find.png"); deprecatedIcons.put("FindAgain.png", "22x22/actions/edit-find-next.png"); deprecatedIcons.put("FindInDir.png", "22x22/actions/edit-find-in-folder.png"); deprecatedIcons.put("Parse.png", "22x22/actions/document-reload2.png"); deprecatedIcons.put("Delete.png", "22x22/actions/edit-delete.png"); deprecatedIcons.put("Paste.png", "22x22/actions/edit-paste.png"); deprecatedIcons.put("Cut.png", "22x22/actions/edit-cut.png"); deprecatedIcons.put("Copy.png", "22x22/actions/edit-copy.png"); deprecatedIcons.put("Undo.png", "22x22/actions/edit-undo.png"); deprecatedIcons.put("Redo.png", "22x22/actions/edit-redo.png"); deprecatedIcons.put("CurrentDir.png", "22x22/status/folder-visiting.png"); deprecatedIcons.put("ParentDir.png", "22x22/actions/go-parent.png"); deprecatedIcons.put("PageSetup.png", "22x22/actions/printer-setup.png"); deprecatedIcons.put("Plugins.png", "22x22/apps/system-installer.png"); deprecatedIcons.put("Floppy.png", "22x22/devices/media-floppy.png"); deprecatedIcons.put("Stop.png", "22x22/actions/process-stop.png"); deprecatedIcons.put("Cancel.png", "22x22/actions/process-stop.png"); deprecatedIcons.put("Home.png", "22x22/actions/go-home.png"); deprecatedIcons.put("Help.png", "22x22/apps/help-browser.png"); deprecatedIcons.put("Properties.png", "22x22/actions/document-properties.png"); deprecatedIcons.put("Preferences.png","22x22/categories/preferences-system.png"); deprecatedIcons.put("ZoomIn.png", "22x22/actions/zoom-in.png"); deprecatedIcons.put("ZoomOut.png", "22x22/actions/zoom-out.png"); deprecatedIcons.put("BrokenImage.png","22x22/status/image-missing.png"); deprecatedIcons.put("AdjustWidth.png","22x22/actions/resize-horisontal.png"); deprecatedIcons.put("ToolbarMenu.gif","ToolbarMenu.gif"); deprecatedIcons.put("Play.png","22x22/actions/media-playback-start.png"); deprecatedIcons.put("Pause.png","22x22/actions/media-playback-pause.png"); deprecatedIcons.put("MultipleResults.png", "22x22/actions/edit-find-multiple.png"); deprecatedIcons.put("SingleResult.png", "22x22/actions/edit-find-single.png"); deprecatedIcons.put("NextFile.png", "22x22/go-last.png"); deprecatedIcons.put("PreviousFile.png","22x22/go-first.png"); deprecatedIcons.put("closebox.gif", "10x10/actions/close.png"); deprecatedIcons.put("normal.gif", "10x10/status/document-unmodified.png"); deprecatedIcons.put("readonly.gif", "10x10/emblem/emblem-readonly.png"); deprecatedIcons.put("dirty.gif", "10x10/status/document-modified.png"); deprecatedIcons.put("new.gif", "10x10/status/document-new.png"); deprecatedIcons.put("ArrowU.png", "22x22/actions/go-up.png"); deprecatedIcons.put("ArrowR.png", "22x22/actions/go-next.png"); deprecatedIcons.put("ArrowD.png", "22x22/actions/go-down.png"); deprecatedIcons.put("ArrowL.png", "22x22/actions/go-previous.png"); deprecatedIcons.put("arrow1.png", "16x16/actions/group-expand.png"); deprecatedIcons.put("arrow2.png", "16x16/actions/group-collapse.png"); deprecatedIcons.put("NewView.png", "22x22/actions/window-new.png"); deprecatedIcons.put("UnSplit.png", "22x22/actions/window-unsplit.png"); deprecatedIcons.put("SplitVertical.png", "22x22/actions/window-split-vertical.png"); deprecatedIcons.put("SplitHorizontal.png", "22x22/actions/window-split-horizontal.png"); deprecatedIcons.put("ButtonProperties.png", "22x22/actions/document-properties.png"); } //}}} //{{{ init() method static void init() { initializeDeprecatedIcons(); // Load the icon theme but fallback on the old icons String theme = jEdit.getProperty("icon-theme", "tango"); Log.log(Log.DEBUG, GUIUtilities.class, "Icon theme set to: "+theme); setIconPath("jeditresource:/org/gjt/sp/jedit/icons/themes/" + theme + '/'); Log.log(Log.DEBUG, GUIUtilities.class, "Loading icon theme from: "+iconPath); } //}}} //{{{ showSplashScreen() method static void showSplashScreen() { // Have to do it in the EDT, since it creates gui components try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { splash = new SplashScreen(); } }); } catch (Exception e) { Log.log(Log.ERROR, GUIUtilities.class, "error displaying splash screen !",e); } } //}}} //{{{ advanceSplashProgress() method static void advanceSplashProgress() { if(splash != null) splash.advance(); } //}}} //{{{ advanceSplashProgress() method static void advanceSplashProgress(String label) { if(splash != null) splash.advance(label); } //}}} //}}} //{{{ Private members private static SplashScreen splash; private static SoftReference<Map<String, Icon>> iconCache; private static String iconPath = "jeditresource:/org/gjt/sp/jedit/icons/themes/"; private static final String defaultIconPath = "jeditresource:/org/gjt/sp/jedit/icons/themes/"; private static final HashMap<String, String> deprecatedIcons = new HashMap<String, String>(); //{{{ _loadMenuItem() method private static JMenuItem _loadMenuItem(String name, ActionContext context, boolean setMnemonic) { String label = jEdit.getProperty(name + ".label", name); char mnemonic; int index = label.indexOf('$'); if (index != -1 && label.length() - index > 1) { mnemonic = Character.toLowerCase(label.charAt(index + 1)); label = label.substring(0, index).concat(label.substring(++index)); } else { mnemonic = '\0'; } JMenuItem mi; if (jEdit.getBooleanProperty(name + ".toggle")) { mi = new EnhancedCheckBoxMenuItem(label, name, context); } else { mi = new EnhancedMenuItem(label, name, context); } if (!OperatingSystem.isMacOS() && setMnemonic && mnemonic != '\0') { mi.setMnemonic(mnemonic); } Icon itemIcon = loadIcon(jEdit.getProperty(name + ".icon.small")); if(itemIcon != null) { mi.setIcon(itemIcon); } return mi; } //}}} private static HashMap<String, String> macKeySymbols = null; /* * Create a list of unicode characters to be used in displaying keyboard shortcuts * on Mac OS X. */ static { macKeySymbols = new HashMap<String, String>(); // These are the unicode code points used in cocoa apps for displaying // shortcuts. macKeySymbols.put("ENTER", "\u21A9"); macKeySymbols.put("HOME", "\u2196"); macKeySymbols.put("END", "\u2198"); macKeySymbols.put("BACK_SPACE", "\u232B"); macKeySymbols.put("DELETE", "\u2326"); macKeySymbols.put("PAGE_UP", "\u21DE"); macKeySymbols.put("PAGE_DOWN", "\u21DF"); macKeySymbols.put("LEFT", "\u2190"); macKeySymbols.put("UP", "\u2191"); macKeySymbols.put("RIGHT", "\u2192"); macKeySymbols.put("DOWN", "\u2193"); macKeySymbols.put("ESCAPE", "\u238B"); macKeySymbols.put("TAB", "\u21E5"); macKeySymbols.put("SPACE", "\u2423"); } //{{{ getMacShortcutLabel() method /** * Convert a shortcut label to a Mac-friendly version by changing written-out * names and modifiers (e.g. C+PERIOD) to symbols. */ private static String getMacShortcutLabel(String label) { StringBuilder out = new StringBuilder(); // Show the list of modifiers in standard order int endOfModifiers = label.indexOf('+'); if (endOfModifiers != -1) { String modifiers = label.substring(0, endOfModifiers).toUpperCase(); if (modifiers.indexOf('A') != -1) { out.append('\u2303'); // ctrl } if (modifiers.indexOf('M') != -1) { out.append('\u2325'); // alt } if (modifiers.indexOf('S') != -1) { out.append('\u21E7'); // shift } if (modifiers.indexOf('C') != -1) { out.append('\u2318'); // cmd } } // We've done the modifiers, now do the key String key = label.substring(endOfModifiers + 1); // Some keys have Mac-specific symbols String text = macKeySymbols.get(key); // Others don't if (text == null) { // Everything else: periods, commas, etc. should be shown as the actual // character try { // e.g., convert the string "PERIOD" to the int KeyEvent.VK_PERIOD int keyCode = KeyEvent.class.getField("VK_".concat(key)).getInt(null); // And then convert KeyEvent.VK_PERIOD to the string "." text = KeyEvent.getKeyText(keyCode).toUpperCase(); } catch(Exception e) { // This is probably an error, but it will be logged in // KeyEventTranslator.parseKey anyway, so just ignore it here. text = key.toUpperCase(); } } out.append(text); return out.toString(); } //}}} private GUIUtilities() {} //}}} //{{{ Inner classes private static final AtomicLong executorThreadsCounter = new AtomicLong(); private static final ScheduledExecutorService schedExecutor = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() { @Override public Thread newThread(Runnable r) { return new Thread(r, "SizeSaver-" + executorThreadsCounter.incrementAndGet()); } }); //{{{ SizeSaver class /** * A combined ComponentListener and WindowStateListener to continually save a Frames size.<br /> * For non-Frame's use {@link GUIUtilities#saveGeometry(Window,String)} * * @author Björn Kautler * @version $Id: GUIUtilities.java 23857 2015-02-07 23:18:09Z ezust $ * @since jEdit 4.3pre6 * @see GUIUtilities#saveGeometry(Window,Container,String) */ private static class SizeSaver extends ComponentAdapter implements WindowStateListener { private final Frame frame; private final Container parent; private final String name; private Future<?> resizeDelayFuture; //{{{ SizeSaver constructors /** * Constructs a new SizeSaver. * * @param frame The Frame for which to save the size * @param parent The parent to be relative to. * @param name The prefix for the settings */ SizeSaver(Frame frame, Container parent, String name) { if (frame == null || name == null) { throw new NullPointerException(); } this.frame = frame; this.parent = parent; this.name = name; } //}}} //{{{ windowStateChanged() method @Override public void windowStateChanged(WindowEvent wse) { int extendedState = wse.getNewState(); jEdit.setIntegerProperty(name + ".extendedState",extendedState); Rectangle bounds = frame.getBounds(); save(extendedState, bounds); } //}}} //{{{ save() method private void save(int extendedState, Rectangle bounds) { cancelResizeSave(); switch (extendedState) { case Frame.MAXIMIZED_VERT: jEdit.setIntegerProperty(name + ".x",bounds.x); jEdit.setIntegerProperty(name + ".width",bounds.width); break; case Frame.MAXIMIZED_HORIZ: jEdit.setIntegerProperty(name + ".y",bounds.y); jEdit.setIntegerProperty(name + ".height",bounds.height); break; case Frame.NORMAL: saveGeometry(frame,parent,name ); break; } } //}}} //{{{ cancelResizeSave() method private void cancelResizeSave() { if (resizeDelayFuture != null) { resizeDelayFuture.cancel(false); resizeDelayFuture = null; } } //}}} //{{{ componentResized() method @Override public void componentResized(ComponentEvent ce) { componentMoved(ce); } //}}} //{{{ componentMoved() method @Override public void componentMoved(ComponentEvent ce) { final Rectangle bounds = frame.getBounds(); final Runnable sizeSaver = new Runnable() { @Override public void run() { EventQueue.invokeLater(new Runnable() { @Override public void run() { save(frame.getExtendedState(), bounds); } }); } }; cancelResizeSave(); resizeDelayFuture = schedExecutor.schedule(sizeSaver, 500, TimeUnit.MILLISECONDS); } //}}} } //}}} //}}} }