001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.util; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.BasicStroke; 007import java.awt.Component; 008import java.awt.Container; 009import java.awt.Dialog; 010import java.awt.Dimension; 011import java.awt.Font; 012import java.awt.GraphicsEnvironment; 013import java.awt.Image; 014import java.awt.Stroke; 015import java.awt.Toolkit; 016import java.awt.Window; 017import java.awt.event.ActionListener; 018import java.awt.event.HierarchyEvent; 019import java.awt.event.HierarchyListener; 020import java.awt.image.FilteredImageSource; 021import java.lang.reflect.InvocationTargetException; 022import java.util.Arrays; 023import java.util.List; 024 025import javax.swing.GrayFilter; 026import javax.swing.Icon; 027import javax.swing.ImageIcon; 028import javax.swing.JOptionPane; 029import javax.swing.SwingUtilities; 030import javax.swing.Timer; 031 032import org.openstreetmap.josm.Main; 033import org.openstreetmap.josm.gui.ExtendedDialog; 034import org.openstreetmap.josm.tools.ImageProvider; 035 036/** 037 * basic gui utils 038 */ 039public final class GuiHelper { 040 041 private GuiHelper() { 042 // Hide default constructor for utils classes 043 } 044 045 /** 046 * disable / enable a component and all its child components 047 */ 048 public static void setEnabledRec(Container root, boolean enabled) { 049 root.setEnabled(enabled); 050 Component[] children = root.getComponents(); 051 for (Component child : children) { 052 if(child instanceof Container) { 053 setEnabledRec((Container) child, enabled); 054 } else { 055 child.setEnabled(enabled); 056 } 057 } 058 } 059 060 public static void executeByMainWorkerInEDT(final Runnable task) { 061 Main.worker.submit(new Runnable() { 062 @Override 063 public void run() { 064 runInEDTAndWait(task); 065 } 066 }); 067 } 068 069 public static void runInEDT(Runnable task) { 070 if (SwingUtilities.isEventDispatchThread()) { 071 task.run(); 072 } else { 073 SwingUtilities.invokeLater(task); 074 } 075 } 076 077 public static void runInEDTAndWait(Runnable task) { 078 if (SwingUtilities.isEventDispatchThread()) { 079 task.run(); 080 } else { 081 try { 082 SwingUtilities.invokeAndWait(task); 083 } catch (InterruptedException e) { 084 e.printStackTrace(); 085 } catch (InvocationTargetException e) { 086 e.printStackTrace(); 087 } 088 } 089 } 090 091 /** 092 * returns true if the user wants to cancel, false if they 093 * want to continue 094 */ 095 public static final boolean warnUser(String title, String content, ImageIcon baseActionIcon, String continueToolTip) { 096 ExtendedDialog dlg = new ExtendedDialog(Main.parent, 097 title, new String[] {tr("Cancel"), tr("Continue")}); 098 dlg.setContent(content); 099 dlg.setButtonIcons(new Icon[] { 100 ImageProvider.get("cancel"), 101 ImageProvider.overlay( 102 ImageProvider.get("upload"), 103 new ImageIcon(ImageProvider.get("warning-small").getImage().getScaledInstance(10 , 10, Image.SCALE_SMOOTH)), 104 ImageProvider.OverlayPosition.SOUTHEAST)}); 105 dlg.setToolTipTexts(new String[] { 106 tr("Cancel"), 107 continueToolTip}); 108 dlg.setIcon(JOptionPane.WARNING_MESSAGE); 109 dlg.setCancelButton(1); 110 return dlg.showDialog().getValue() != 2; 111 } 112 113 /** 114 * Replies the disabled (grayed) version of the specified image. 115 * @param image The image to disable 116 * @return The disabled (grayed) version of the specified image, brightened by 20%. 117 * @since 5484 118 */ 119 public static final Image getDisabledImage(Image image) { 120 return Toolkit.getDefaultToolkit().createImage( 121 new FilteredImageSource(image.getSource(), new GrayFilter(true, 20))); 122 } 123 124 /** 125 * Replies the disabled (grayed) version of the specified icon. 126 * @param icon The icon to disable 127 * @return The disabled (grayed) version of the specified icon, brightened by 20%. 128 * @since 5484 129 */ 130 public static final ImageIcon getDisabledIcon(ImageIcon icon) { 131 return new ImageIcon(getDisabledImage(icon.getImage())); 132 } 133 134 /** 135 * Attaches a {@code HierarchyListener} to the specified {@code Component} that 136 * will set its parent dialog resizeable. Use it before a call to JOptionPane#showXXXXDialog 137 * to make it resizeable. 138 * @param pane The component that will be displayed 139 * @param minDimension The minimum dimension that will be set for the dialog. Ignored if null 140 * @return {@code pane} 141 * @since 5493 142 */ 143 public static final Component prepareResizeableOptionPane(final Component pane, final Dimension minDimension) { 144 if (pane != null) { 145 pane.addHierarchyListener(new HierarchyListener() { 146 @Override 147 public void hierarchyChanged(HierarchyEvent e) { 148 Window window = SwingUtilities.getWindowAncestor(pane); 149 if (window instanceof Dialog) { 150 Dialog dialog = (Dialog)window; 151 if (!dialog.isResizable()) { 152 dialog.setResizable(true); 153 if (minDimension != null) { 154 dialog.setMinimumSize(minDimension); 155 } 156 } 157 } 158 } 159 }); 160 } 161 return pane; 162 } 163 164 /** 165 * Schedules a new Timer to be run in the future (once or several times). 166 * @param initialDelay milliseconds for the initial and between-event delay if repeatable 167 * @param actionListener an initial listener; can be null 168 * @param repeats specify false to make the timer stop after sending its first action event 169 * @return The (started) timer. 170 * @since 5735 171 */ 172 public static final Timer scheduleTimer(int initialDelay, ActionListener actionListener, boolean repeats) { 173 Timer timer = new Timer(initialDelay, actionListener); 174 timer.setRepeats(repeats); 175 timer.start(); 176 return timer; 177 } 178 179 /** 180 * Return s new BasicStroke object with given thickness and style 181 * @param code = 3.5 -> thickness=3.5px; 3.5 10 5 -> thickness=3.5px, dashed: 10px filled + 5px empty 182 * @return stroke for drawing 183 */ 184 public static Stroke getCustomizedStroke(String code) { 185 String[] s = code.trim().split("[^\\.0-9]+"); 186 187 if (s.length==0) return new BasicStroke(); 188 float w; 189 try { 190 w = Float.parseFloat(s[0]); 191 } catch (NumberFormatException ex) { 192 w = 1.0f; 193 } 194 if (s.length>1) { 195 float[] dash= new float[s.length-1]; 196 float sumAbs = 0; 197 try { 198 for (int i=0; i<s.length-1; i++) { 199 dash[i] = Float.parseFloat(s[i+1]); 200 sumAbs += Math.abs(dash[i]); 201 } 202 } catch (NumberFormatException ex) { 203 Main.error("Error in stroke preference format: "+code); 204 dash = new float[]{5.0f}; 205 } 206 if (sumAbs < 1e-1) { 207 Main.error("Error in stroke dash fomat (all zeros): "+code); 208 return new BasicStroke(w); 209 } 210 // dashed stroke 211 return new BasicStroke(w, BasicStroke.CAP_BUTT, 212 BasicStroke.JOIN_MITER, 10.0f, dash, 0.0f); 213 } else { 214 if (w>1) { 215 // thick stroke 216 return new BasicStroke(w, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND); 217 } else { 218 // thin stroke 219 return new BasicStroke(w); 220 } 221 } 222 } 223 224 /** 225 * Gets the font used to display JOSM title in about dialog and splash screen. 226 * @return By order or priority, the first font available in local fonts: 227 * 1. Helvetica Bold 20 228 * 2. Calibri Bold 23 229 * 3. Arial Bold 20 230 * 4. SansSerif Bold 20 231 * @since 5797 232 */ 233 public static Font getTitleFont() { 234 List<String> fonts = Arrays.asList(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames()); 235 // Helvetica is the preferred choice but is not available by default on Windows 236 // (http://www.microsoft.com/typography/fonts/product.aspx?pid=161) 237 if (fonts.contains("Helvetica")) { 238 return new Font("Helvetica", Font.BOLD, 20); 239 // Calibri is the default Windows font since Windows Vista but is not available on older versions of Windows, where Arial is preferred 240 } else if (fonts.contains("Calibri")) { 241 return new Font("Calibri", Font.BOLD, 23); 242 } else if (fonts.contains("Arial")) { 243 return new Font("Arial", Font.BOLD, 20); 244 // No luck, nothing found, fallback to one of the 5 fonts provided with Java (Serif, SansSerif, Monospaced, Dialog, and DialogInput) 245 } else { 246 return new Font("SansSerif", Font.BOLD, 20); 247 } 248 } 249}