package hirondelle.stocks.util.ui; import hirondelle.stocks.util.Args; import hirondelle.stocks.util.Consts; import hirondelle.stocks.util.Util; import java.util.*; import java.text.*; import java.net.URL; import javax.swing.*; import javax.swing.border.Border; import java.awt.*; import javax.swing.plaf.metal.MetalLookAndFeel; import hirondelle.stocks.preferences.GeneralLookPreferencesEditor; /** Static convenience methods for GUIs which eliminate code duplication.*/ public final class UiUtil { /** * <tt>pack</tt>, center, and <tt>show</tt> a window on the screen. * * <P>If the size of <tt>aWindow</tt> exceeds that of the screen, * then the size of <tt>aWindow</tt> is reset to the size of the screen. */ public static void centerAndShow(Window aWindow){ //note that the order here is important aWindow.pack(); /* * If called from outside the event dispatch thread (as is * the case upon startup, in the launch thread), then * in principle this code is not thread-safe: once pack has * been called, the component is realized, and (most) further * work on the component should take place in the event-dispatch * thread. * * In practice, it is exceedingly unlikely that this will lead * to an error, since invisible components cannot receive events. */ Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); Dimension window = aWindow.getSize(); //ensure that no parts of aWindow will be off-screen if (window.height > screen.height) { window.height = screen.height; } if (window.width > screen.width) { window.width = screen.width; } int xCoord = (screen.width/2 - window.width/2); int yCoord = (screen.height/2 - window.height/2); aWindow.setLocation( xCoord, yCoord ); aWindow.setVisible(true); } /** * A window is packed, centered with respect to a parent, and then shown. * * <P>This method is intended for dialogs only. * * <P>If centering with respect to a parent causes any part of the dialog * to be off screen, then the centering is overidden, such that all of the * dialog will always appear fully on screen, but it will still appear * near the parent. * * @param aWindow must have non-null result for <tt>aWindow.getParent</tt>. */ public static void centerOnParentAndShow(Window aWindow){ aWindow.pack(); Dimension parent = aWindow.getParent().getSize(); Dimension window = aWindow.getSize(); int xCoord = aWindow.getParent().getLocationOnScreen().x + (parent.width/2 - window.width/2) ; int yCoord = aWindow.getParent().getLocationOnScreen().y + (parent.height/2 - window.height/2) ; //Ensure that no part of aWindow will be off-screen Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); int xOffScreenExcess = xCoord + window.width - screen.width; if ( xOffScreenExcess > 0 ) { xCoord = xCoord - xOffScreenExcess; } if (xCoord < 0 ) { xCoord = 0; } int yOffScreenExcess = yCoord + window.height - screen.height; if ( yOffScreenExcess > 0 ) { yCoord = yCoord - yOffScreenExcess; } if (yCoord < 0) { yCoord = 0; } aWindow.setLocation( xCoord, yCoord ); aWindow.setVisible(true); } /** * Return a border of dimensions recommended by the Java Look and Feel * Design Guidelines, suitable for many common cases. * *<P>Each side of the border has size {@link UiConsts#STANDARD_BORDER}. */ public static Border getStandardBorder(){ return BorderFactory.createEmptyBorder( UiConsts.STANDARD_BORDER, UiConsts.STANDARD_BORDER, UiConsts.STANDARD_BORDER, UiConsts.STANDARD_BORDER ); } /** * Return text which conforms to the Look and Feel Design Guidelines * for the title of a dialog : the application name, a colon, then * the name of the specific dialog. * *<P>Example return value: <tt>StocksMonitor: Preferences</tt> * * @param aSpecificDialogName must have visible content */ public static String getDialogTitle(String aSpecificDialogName){ Args.checkForContent(aSpecificDialogName); StringBuilder result = new StringBuilder(Consts.APP_NAME); result.append(": "); result.append(aSpecificDialogName); return result.toString(); } /** * Make a horizontal row of buttons of equal size, whch are equally spaced, * and aligned on the right. * * <P>The returned component has border spacing only on the top (of the size * recommended by the Look and Feel Design Guidelines). * All other spacing must be applied elsewhere ; usually, this will only mean * that the dialog's top-level panel should use {@link #getStandardBorder}. * * @param aButtons contains the buttons to be placed in a row. */ public static JComponent getCommandRow(java.util.List<JComponent> aButtons){ equalizeSizes( aButtons ); JPanel panel = new JPanel(); LayoutManager layout = new BoxLayout(panel, BoxLayout.X_AXIS); panel.setLayout(layout); panel.setBorder(BorderFactory.createEmptyBorder(UiConsts.THREE_SPACES, 0, 0, 0)); panel.add(Box.createHorizontalGlue()); Iterator<JComponent> buttonsIter = aButtons.iterator(); while (buttonsIter.hasNext()) { panel.add(buttonsIter.next()); if (buttonsIter.hasNext()) { panel.add(Box.createHorizontalStrut(UiConsts.ONE_SPACE)); } } return panel; } /** * Make a vertical row of buttons of equal size, whch are equally spaced, * and aligned on the right. * * <P>The returned component has border spacing only on the left (of the size * recommended by the Look and Feel Design Guidelines). * All other spacing must be applied elsewhere ; usually, this will only mean * that the dialog's top-level panel should use {@link #getStandardBorder}. * * @param aButtons contains the buttons to be placed in a column */ public static JComponent getCommandColumn(java.util.List<JComponent> aButtons){ equalizeSizes(aButtons); JPanel panel = new JPanel(); LayoutManager layout = new BoxLayout(panel, BoxLayout.Y_AXIS); panel.setLayout(layout); panel.setBorder( BorderFactory.createEmptyBorder(0,UiConsts.THREE_SPACES, 0,0) ); //(no for-each is used here, because of the 'not-yet-last' check) Iterator<JComponent> buttonsIter = aButtons.iterator(); while (buttonsIter.hasNext()) { panel.add(buttonsIter.next()); if (buttonsIter.hasNext()) { panel.add( Box.createVerticalStrut(UiConsts.ONE_SPACE) ); } } panel.add(Box.createVerticalGlue()); return panel; } /** * Return an <tt>ImageIcon</tt> using its <tt>String</tt> identifier. * * @param aImageId starts with '/', and refers to an image resource * which is accessible through {@link Class#getResource}. */ public static ImageIcon getImageIcon(String aImageId){ if( ! aImageId.startsWith(BACK_SLASH) ){ throw new IllegalArgumentException( "Image identifier does not start with backslash: " + aImageId ); } return fetchImageIcon(aImageId, UiUtil.class); } /** * Return an <tt>ImageIcon</tt> using its <tt>String</tt> identifier, relative to * a given class. * * @param aImageId does NOT start with '/', and must refer to an image resource which is * accessible through {@link Class#getResource}. * @param aClass the class relative to which the image is located. */ public static ImageIcon getImageIcon(String aImageId, Class<?> aClass){ if( aImageId.startsWith(BACK_SLASH) ){ throw new IllegalArgumentException( "Image identifier starts with a backslash: " + aImageId ); } return fetchImageIcon(aImageId, aClass); } /** * Return a square icon which paints nothing, and whose dimensions correspond * to the user preference for icon size. * * <P>A common problem occurs with text alignment in menus, where there is * a mixture of menu items with and without an icon. Adding an empty icon * to menu items which do not have one will adjust its alignment to match * that of the others which do have an icon. */ public static Icon getEmptyIcon(){ GeneralLookPreferencesEditor prefs = new GeneralLookPreferencesEditor(); return prefs.hasLargeIcons() ? EmptyIcon.SIZE_24 : EmptyIcon.SIZE_16; } /** * Return a <tt>Dimension</tt> whose size is defined not in terms of pixels, * but in terms of a given percent of the screen's width and height. * *<P> Use to set the preferred size of a component to a certain * percentage of the screen. * * @param aPercentWidth percentage width of the screen, in range <tt>1..100</tt>. * @param aPercentHeight percentage height of the screen, in range <tt>1..100</tt>. */ public static final Dimension getDimensionFromPercent( int aPercentWidth, int aPercentHeight ){ int low = 1; int high = 100; Args.checkForRange(aPercentWidth, low, high); Args.checkForRange(aPercentHeight, low, high); Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); return calcDimensionFromPercent(screenSize, aPercentWidth, aPercentHeight); } /** * Sets the items in <tt>aComponents</tt> to the same size. * * <P>Sets each component's preferred and maximum sizes. * The actual size is determined by the layout manager, whcih adjusts * for locale-specific strings and customized fonts. (See this * <a href="http://java.sun.com/products/jlf/ed2/samcode/prefere.html">Sun doc</a> * for more information.) * * @param aComponents items whose sizes are to be equalized */ public static void equalizeSizes(java.util.List<JComponent> aComponents) { Dimension targetSize = new Dimension(0,0); for(JComponent comp: aComponents ) { Dimension compSize = comp.getPreferredSize(); double width = Math.max(targetSize.getWidth(), compSize.getWidth()); double height = Math.max(targetSize.getHeight(), compSize.getHeight()); targetSize.setSize(width, height); } setSizes(aComponents, targetSize); } /** * Create a pair of components, a <tt>JLabel</tt> and an associated * <tt>JTextField</tt>, as is typically used for user input. * *<P>The <tt>JLabel</tt> appears on the left, and the <tt>JTextField</tt> * appears on the same row, just to the right of the <tt>JLabel</tt>. * The <tt>JLabel</tt> has a mnemonic which forwards focus to the * <tt>JTextField</tt> when activated. * * @param aContainer holds the pair of components. * @param aName text of the <tt>JLabel</tt> component. * @param aInitialValue possibly-null initial value to appear * in the <tt>JTextField</tt>; if <tt>null</tt>, then * <tt>JTextField</tt> will be blank. * @param aMnemonic <tt>KeyEvent</tt> field, used as the mnemonic for * the <tt>JLabel</tt>. * @param aConstraints applied to the <tt>JLabel</tt>; the corresponding * constraints for the <tt>JTextField</tt> are the same as * <tt>aConstraints</tt>, except for <tt>gridx</tt> being incremented by one; * in addition, if <tt>aConstraints</tt> has <tt>weightx=0</tt> (the default), * then the entry field will receive <tt>weightx=1.0</tt> (entry field gets more * horizontal space upon resize). * @param aTooltip possibly-null text displayed as tool tip for the * <tt>JTextField</tt> ; if <tt>null</tt>, the tool tip is turned off. * @return the user input <tt>JTextField</tt>. */ public static JTextField addSimpleEntryField( Container aContainer, String aName, String aInitialValue, int aMnemonic, GridBagConstraints aConstraints, String aTooltip ){ Args.checkForNull(aName); JLabel label = new JLabel(aName); label.setDisplayedMnemonic(aMnemonic); aContainer.add( label, aConstraints ); JTextField result = new JTextField(UiConsts.SIMPLE_FIELD_WIDTH); label.setLabelFor(result); result.setToolTipText(aTooltip); if (aInitialValue != null) { result.setText(aInitialValue); } aConstraints.gridx = ++aConstraints.gridx; if (aConstraints.weightx == 0.0){ aConstraints.weightx = 1.0; } aContainer.add(result, aConstraints); return result; } /** * Return a set of constraints with convenient default values. * *<P>Return constraints with these values : *<ul> * <li> <tt>gridx, gridy</tt> - set to <tt>aX, aY</tt> * <li> <tt>anchor - GridBagConstraints.WEST</tt> * <li> <tt>insets - Insets(0,0,0, UiConsts.ONE_SPACE)</tt> *</ul> * *<P> All other items simply take their default values : *<ul> * <li> <tt>fill - GridBagConstraints.NONE</tt> * <li> <tt>gridwidth, gridheight - 0, 0</tt> * <li> <tt>weightx , weighty - 0, 0</tt> * <li> <tt>ipadx, ipady - 0, 0</tt> *</ul> * * <P>The caller is free to change the returned constraints, to customize for * their particular needs. * * @param aY in range <tt>0..10</tt>. * @param aX in range <tt>0..10</tt>. */ public static GridBagConstraints getConstraints(int aY, int aX){ int low = 0; int high = 10; Args.checkForRange(aY, low, high); Args.checkForRange(aX, low, high); GridBagConstraints result = new GridBagConstraints(); result.gridy = aY; result.gridx = aX; result.anchor = GridBagConstraints.WEST; result.insets = new Insets(0,0,0,UiConsts.ONE_SPACE); return result; } /** * Return {@link #getConstraints(int, int)}, with the addition of setting * <tt>gridwidth</tt> to <tt>aWidth</tt>, and setting * <tt>gridheight</tt> to <tt>aHeight</tt>. * * <P>The caller is free to change the returned constraints, to customize for * their particular needs. * * @param aY in range <tt>0..10</tt>. * @param aX in range <tt>0..10</tt>. * @param aWidth in range <tt>1..10</tt>. * @param aHeight in range <tt>1..10</tt>. */ public static GridBagConstraints getConstraints(int aY, int aX, int aWidth, int aHeight){ int low = 0; int high = 10; Args.checkForRange(aHeight, low, high); Args.checkForRange(aWidth, low, high); GridBagConstraints result = getConstraints(aY, aX); result.gridheight = aHeight; result.gridwidth = aWidth; return result; } /** * Create a pair of <tt>JLabel</tt> components, as is typically needed * for display of a name-value pair. * * <P>The name appears on the left, and the value appears on the right, * all on the same row. A colon and an empty space are appended to the name. * * <P> If the the length of "value" label is greater than * {@link UiConsts#MAX_LABEL_LENGTH}, then the text is truncated, an ellipsis * is placed at its end, and the full text is placed in a tooltip. * * @param aContainer holds the pair of components. * @param aName text of the name <tt>JLabel</tt>. * @param aValue possibly-null ; if null, then an empty <tt>String</tt> * is used for the value; otherwise <tt>Object.toString</tt> is used. * @param aConstraints for the name <tt>JLabel</tt>; the corresponding * constraints for the value <tt>JLabel</tt> are mostly taken from * <tt>aConstraints</tt>, except for <tt>gridx</tt> being incremented by one * (<tt>weightx</tt> may differ as well - see <tt>aWeightOnDisplay</tt>.) * @param aWeightOnDisplay if true, then set <tt>weightx</tt> for the value * field to 1.0 (to give it more horizontal space upon resize). * @return the <tt>JLabel</tt> for the value (which is usually variable). */ public static JLabel addSimpleDisplayField( Container aContainer, String aName, Object aValue, GridBagConstraints aConstraints, boolean aWeightOnDisplay ){ StringBuilder formattedName = new StringBuilder(aName); formattedName.append(": "); JLabel name = new JLabel( formattedName.toString() ); aContainer.add( name, aConstraints ); String valueText = (aValue != null? aValue.toString() : Consts.EMPTY_STRING); JLabel value = new JLabel(valueText); truncateLabelIfLong(value); aConstraints.gridx = ++aConstraints.gridx; if (aWeightOnDisplay){ aConstraints.weightx = 1.0; } aContainer.add( value, aConstraints ); return value; } /** * Present a number of read-only items to the user as a vertical listing * of <tt>JLabel</tt> name-value pairs. * * <P>Each pair is added in the style of * {@link #addSimpleDisplayField} (its <tt>aConstraints</tt> param are those * returned by {@link #getConstraints(int, int)}, and its <tt>aWeightOnDisplay</tt> * param is set to <tt>true</tt>). * * <P>The order of presentation is determined by the iteration order of * <tt>aNameValuePairs</tt>. * *<P>The number of items which should be presented using this method is limited, since * no scrolling mechanism is given to the user. * * @param aContainer holds the display fields. * @param aNameValuePairs has <tt>String</tt> keys for the names, * and values are possibly null <tt>Object</tt>s; * if null, then an empty <tt>String</tt> is displayed, otherwise * <tt>Object.toString</tt> is called on the value and displayed. */ public static void addSimpleDisplayFields( Container aContainer, Map<String, String> aNameValuePairs ) { Set<String> keys = aNameValuePairs.keySet(); int rowIdx = 0; for(String name: keys) { String value = aNameValuePairs.get(name); if(value == null){ value = Consts.EMPTY_STRING; } UiUtil.addSimpleDisplayField( aContainer, name, value, UiUtil.getConstraints(rowIdx,0), true ); ++rowIdx; } } /** * Adds "glue" (an empty component with desired resizing behavior) to the bottom * row of a <tt>GridBagLayout</tt> of components. When resized, this glue will * take up extra vertical space. * * <P>This method is especially useful for text data presented in a listing or * tabular form. Such components naturally resize horizontally, while their vertical * resizing should often be absent. If such a listing is resized vertically, then this * glue can take up the remaining vertical space, keeping the text at the top. * * @param aPanel uses <tt>GridBagLayout</tt>, and contains components whose * <tt>weighty</tt> values are all 0.0 (the default). * @param aLastRowIdx index of the last row of components, in which the glue will be * placed. */ public static void addVerticalGridGlue(JPanel aPanel, int aLastRowIdx) { GridBagConstraints glueConstraints = UiUtil.getConstraints(aLastRowIdx,0); glueConstraints.weighty = 1.0; glueConstraints.fill = GridBagConstraints.VERTICAL; aPanel.add(new JLabel(), glueConstraints); } /** * Return a <tt>String</tt>, suitable for presentation to the end user, * representing a percentage having two decimal places, using the default locale. * * <P>An example return value is "5.15%". The intent of this method is to * provide a standard representation and number of decimals for the entire * application. If a different number of decimal places is required, then * the caller should use <tt>NumberFormat</tt> instead. */ public static String getLocalizedPercent( Number aNumber ){ NumberFormat localFormatter = NumberFormat.getPercentInstance(); localFormatter.setMinimumFractionDigits(2); return localFormatter.format(aNumber.doubleValue()); } /** * Return a <tt>String</tt>, suitable for presentation to the end user, * representing an integral number with no decimal places, using the default * locale. * * <P>An example return value is "8,000". The intent of this method is to * provide a standard representation of integers for the entire * application. */ public static String getLocalizedInteger( Number aNumber ) { NumberFormat localFormatter = NumberFormat.getNumberInstance(); return localFormatter.format(aNumber.intValue()); } /** * Return a <tt>String</tt>, suitable for presentation to the end user, * representing a date in <tt>DateFormat.SHORT</tt> and the default locale. */ public static String getLocalizedTime(Date aDate){ DateFormat dateFormat = DateFormat.getTimeInstance(DateFormat.SHORT); return dateFormat.format(aDate); } /** * Make the sytem emit a beep. * * <P>May not beep unless the speakers are turned on, so this cannot * be guaranteed to work. */ public static void beep(){ Toolkit.getDefaultToolkit().beep(); } /** * An alternative to multi-line labels, for the presentation of * several lines of text, and for which the line breaks are determined * solely by the widget. * * @param aText must have visible content, doesn't contain newline characters or html. * @return <tt>JTextArea</tt> which is not editable, has improved spacing over the * supplied default (placing {@link UiConsts#ONE_SPACE} on the left and right), * which wraps lines on word boundaries, and whose background color is the * same as {@link javax.swing.plaf.metal.MetalLookAndFeel#getMenuBackground}. */ public static JTextArea getStandardTextArea(String aText){ Args.checkForContent(aText); if ( aText.indexOf(Consts.NEW_LINE) != -1 ){ throw new IllegalArgumentException("Must not contain new line characters: " + aText); } JTextArea result = new JTextArea(aText); result.setEditable(false); result.setWrapStyleWord(true); result.setLineWrap(true); result.setMargin( new Insets(0,UiConsts.ONE_SPACE,0,UiConsts.ONE_SPACE) ); //this is a bit hacky: the desired color is "secondary3", but cannot see how //to reference it directly; hence, an element which uses secondary3 is used instead. result.setBackground( MetalLookAndFeel.getMenuBackground() ); return result; } /** * An alternative to multi-line labels, for the presentation of * several lines of text, and for which line breaks are determined * solely by <tt>aText</tt>, and not by the widget. * * @param aText has visible content * @return <tt>JTextArea</tt> which is not editable, has improved spacing over the * supplied default (placing {@link UiConsts#ONE_SPACE} on the left and right), * and whose background color is the same as * {@link javax.swing.plaf.metal.MetalLookAndFeel#getMenuBackground}. */ public static JTextArea getStandardTextAreaHardNewLines(String aText){ Args.checkForContent(aText); JTextArea result = new JTextArea(aText); result.setEditable(false); result.setMargin(new Insets(0,UiConsts.ONE_SPACE,0,UiConsts.ONE_SPACE)); result.setBackground( MetalLookAndFeel.getMenuBackground() ); return result; } /** * Imposes a uniform horizontal alignment on all items in a container. * *<P> Intended especially for <tt>BoxLayout</tt>, where all components need * to share the same alignment in order for display to be reasonable. * (Indeed, this method may only work for <tt>BoxLayout</tt>, since apparently * it is the only layout to use <tt>setAlignmentX, setAlignmentY</tt>.) * * @param aContainer contains only <tt>JComponent</tt> objects. */ public static void alignAllX(Container aContainer, UiUtil.AlignX aAlignment){ java.util.List<Component> components = Arrays.asList( aContainer.getComponents() ); for(Component comp: components){ JComponent jcomp = (JComponent)comp; jcomp.setAlignmentX(aAlignment.getValue()); } } /** Enumeration for horizontal alignment. */ public enum AlignX { LEFT(Component.LEFT_ALIGNMENT), CENTER(Component.CENTER_ALIGNMENT), RIGHT(Component.RIGHT_ALIGNMENT); public float getValue(){ return fValue; } private final float fValue; private AlignX(float aValue){ fValue = aValue; } } /** * Imposes a uniform vertical alignment on all items in a container. * *<P> Intended especially for <tt>BoxLayout</tt>, where all components need * to share the same alignment in order for display to be reasonable. * (Indeed, this method may only work for <tt>BoxLayout</tt>, since apparently * it is the only layout to use <tt>setAlignmentX, setAlignmentY</tt>.) * * @param aContainer contains only <tt>JComponent</tt> objects. */ public static void alignAllY(Container aContainer, UiUtil.AlignY aAlignment){ java.util.List components = Arrays.asList( aContainer.getComponents() ); Iterator compsIter = components.iterator(); while ( compsIter.hasNext() ) { JComponent comp = (JComponent)compsIter.next(); comp.setAlignmentY( aAlignment.getValue() ); } } /** Type-safe enumeration vertical alignment. */ public enum AlignY { TOP(Component.TOP_ALIGNMENT), CENTER(Component.CENTER_ALIGNMENT), BOTTOM(Component.BOTTOM_ALIGNMENT); float getValue(){ return fValue; } private final float fValue; private AlignY( float aValue){ fValue = aValue; } } /** * Ensure that <tt>aRootPane</tt> has no default button associated with it. * * <P>Intended mainly for dialogs where the user is confirming a delete action. * In this case, an explicit Yes or No is preferred, with no default action being * taken when the user hits the Enter key. */ public static void noDefaultButton(JRootPane aRootPane){ aRootPane.setDefaultButton(null); } // PRIVATE private static final String BACK_SLASH = "/"; /** * If <tt>aIconName</tt> indicates that the icon is part of the standard graphic * repository (by starting with "/toolbar"), then append either "16.gif" or * "24.gif" to the name, according to the user's current preference for icon size. */ private static String addSizeToStandardIcon(String aIconName){ assert( Util.textHasContent(aIconName) ); StringBuilder result = new StringBuilder(aIconName); if ( aIconName.startsWith("/toolbar") ) { GeneralLookPreferencesEditor prefs = new GeneralLookPreferencesEditor(); if ( prefs.hasLargeIcons() ) { result.append("24.gif"); } else { result.append("16.gif"); } } return result.toString(); } private static void setSizes(java.util.List aComponents, Dimension aDimension){ Iterator compsIter = aComponents.iterator(); while ( compsIter.hasNext() ) { JComponent comp = (JComponent) compsIter.next(); comp.setPreferredSize((Dimension)aDimension.clone()); comp.setMaximumSize((Dimension)aDimension.clone()); } } private static Dimension calcDimensionFromPercent( Dimension aSourceDimension, int aPercentWidth, int aPercentHeight ){ int width = aSourceDimension.width * aPercentWidth/100; int height = aSourceDimension.height * aPercentHeight/100; return new Dimension(width, height); } /** * If aLabel has text which is longer than MAX_LABEL_LENGTH, then truncate * the label text and place an ellipsis at the end; the original text is placed * in a tooltip. * * This is particularly useful for displaying file names, whose length * can vary widely between deployments. */ private static void truncateLabelIfLong(JLabel aLabel){ String originalText = aLabel.getText(); if (originalText.length() > UiConsts.MAX_LABEL_LENGTH){ aLabel.setToolTipText( originalText ); String truncatedText = originalText.substring(0, UiConsts.MAX_LABEL_LENGTH) + Consts.ELLIPSIS ; aLabel.setText(truncatedText); } } private static ImageIcon fetchImageIcon(String aImageId, Class<?> aClass){ String imgLocation = addSizeToStandardIcon(aImageId); URL imageURL = aClass.getResource(imgLocation); if ( imageURL != null ) { return new ImageIcon(imageURL); } else { throw new IllegalArgumentException("Cannot retrieve image using id: " + aImageId); } } }