/** * Copyright 2011 multibit.org * * Licensed under the MIT license (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://opensource.org/licenses/mit-license.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.multibit.viewsystem.swing; /** * L2FProd.com Common Components 7.3 License. * * Copyright 2005-2007 L2FProd.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import com.google.bitcoin.core.Block; import org.multibit.controller.Controller; import org.multibit.controller.bitcoin.BitcoinController; import org.multibit.message.Message; import org.multibit.message.MessageListener; import org.multibit.model.core.StatusEnum; import org.multibit.viewsystem.swing.action.MultiBitAction; import org.multibit.viewsystem.swing.view.components.BlinkLabel; import org.multibit.viewsystem.swing.view.components.FontSizer; import org.multibit.viewsystem.swing.view.components.MultiBitButton; import org.multibit.viewsystem.swing.view.panels.HelpContentsPanel; import org.multibit.viewsystem.swing.view.panels.SendBitcoinPanel; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.swing.*; import javax.swing.border.Border; import javax.swing.border.CompoundBorder; import javax.swing.border.EmptyBorder; import javax.swing.plaf.UIResource; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.text.SimpleDateFormat; import java.util.*; import java.util.Timer; /** * StatusBar. <BR> * A status bar is made of multiple zones. A zone can be any JComponent. * <p/> * In MultiBit the StatusBar is responsible for : * <p/> * 1) Online/ Connecting/ Error status label * 2) Status messages - these are cleared after a period of time * 3) Synchronisation progress bar */ public class StatusBar extends JPanel implements MessageListener { private static final Logger log = LoggerFactory.getLogger(StatusBar.class); private static final long serialVersionUID = 7824115980324911080L; private static final int A_SMALL_NUMBER_OF_PIXELS = 100; private static final int A_LARGE_NUMBER_OF_PIXELS = 1000000; private static final int STATUSBAR_HEIGHT = 30; private static final double TOLERANCE = 0.0000001; public static final int ONLINE_LABEL_WIDTH_DELTA = 12; public static final int ONLINE_LABEL_HEIGHT_DELTA = 8; private JLabel onlineLabel; final private MultiBitButton statusLabel; private StatusEnum statusEnum; public static final long TIMER_REPEAT_TIME = 5000; // millisecond public static final int NUMBER_OF_REPEATS = 12; private Timer statusClearTimer; static boolean clearAutomatically = true; private HashMap<String, Component> idToZones; private Border zoneBorder; private final Controller controller; private final BitcoinController bitcoinController; private MultiBitFrame mainFrame; private JProgressBar syncProgressBar; private SimpleDateFormat dateFormatter; /** * Construct a new StatusBar. */ public StatusBar(BitcoinController bitcoinController, MultiBitFrame mainFrame) { this.bitcoinController = bitcoinController; this.controller = this.bitcoinController; this.mainFrame = mainFrame; setLayout(LookAndFeelTweaks.createHorizontalPercentLayout(controller.getLocaliser().getLocale())); idToZones = new HashMap<String, Component>(); setZoneBorder(BorderFactory.createEmptyBorder()); setBackground(ColorAndFontConstants.MID_BACKGROUND_COLOR); setOpaque(true); setBorder(BorderFactory.createMatteBorder(2, 0, 2, 0, ColorAndFontConstants.MID_BACKGROUND_COLOR)); applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale())); final BitcoinController finalController = this.bitcoinController; dateFormatter = new SimpleDateFormat("dd MMM yyyy HH:mm", controller.getLocaliser().getLocale()); onlineLabel = new JLabel(""); onlineLabel.setFont(FontSizer.INSTANCE.getAdjustedDefaultFont()); onlineLabel.setBackground(ColorAndFontConstants.VERY_LIGHT_BACKGROUND_COLOR); onlineLabel.setOpaque(true); onlineLabel.setHorizontalAlignment(SwingConstants.CENTER); onlineLabel.applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale())); onlineLabel.addMouseListener(new MouseAdapter() { @Override public void mouseEntered(MouseEvent arg0) { int blockHeight = -1; if (finalController.getMultiBitService() != null) { if (finalController.getMultiBitService().getChain() != null) { if (finalController.getMultiBitService().getChain().getChainHead() != null) { blockHeight = finalController.getMultiBitService().getChain().getChainHead().getHeight(); Block blockHeader = finalController.getMultiBitService().getChain().getChainHead().getHeader(); Date blockTime = blockHeader.getTime(); int numberOfPeers = 0; if (finalController.getMultiBitService().getPeerGroup() != null && finalController.getMultiBitService().getPeerGroup().getConnectedPeers() != null) { numberOfPeers = finalController.getMultiBitService().getPeerGroup().getConnectedPeers().size(); } onlineLabel.setToolTipText(HelpContentsPanel.createMultilineTooltipText(new String[]{ finalController.getLocaliser().getString("multiBitFrame.numberOfBlocks", new Object[]{blockHeight}), finalController.getLocaliser().getString("multiBitFrame.blockDate", new Object[]{dateFormatter.format(blockTime)}), finalController.getLocaliser().getString("multiBitFrame.connectedTo", new Object[]{numberOfPeers})})); } } } } }); statusLabel = new MultiBitButton(""); statusLabel.setBackground(ColorAndFontConstants.MID_BACKGROUND_COLOR); statusLabel.setOpaque(true); statusLabel.setBorderPainted(false); statusLabel.setForeground(Color.BLACK); //statusLabel.setBorder(BorderFactory.createLineBorder(Color.CYAN)); statusLabel.setFocusPainted(false); statusLabel.setContentAreaFilled(false); statusLabel.applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale())); // show messages action MultiBitAction showMessagesAction = new MultiBitAction(controller, null, null, "messagesPanel.title", "messagesPanel.mnemonic", org.multibit.viewsystem.View.MESSAGES_VIEW); statusLabel.setAction(showMessagesAction); statusLabel.setHorizontalAlignment(JButton.LEADING); String tooltipText = HelpContentsPanel.createMultilineTooltipText(new String[]{ controller.getLocaliser().getString("multiBitFrame.statusBar.tooltip1"), "\n", controller.getLocaliser().getString("multiBitFrame.statusBar.tooltip2")}); statusLabel.setToolTipText(tooltipText); int onlineWidth = Math.max(Math.max( getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont()).stringWidth( controller.getLocaliser().getString("multiBitFrame.onlineText")), getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont()).stringWidth( controller.getLocaliser().getString("multiBitFrame.offlineText"))), getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont()).stringWidth( controller.getLocaliser().getString("multiBitFrame.errorText")) ) + ONLINE_LABEL_WIDTH_DELTA; int onlineHeight = getFontMetrics(FontSizer.INSTANCE.getAdjustedDefaultFont()).getHeight() + ONLINE_LABEL_HEIGHT_DELTA; onlineLabel.setPreferredSize(new Dimension(onlineWidth, onlineHeight)); int statusBarHeight = Math.max(STATUSBAR_HEIGHT, onlineHeight); setMaximumSize(new Dimension(A_LARGE_NUMBER_OF_PIXELS, statusBarHeight)); setMaximumSize(new Dimension(A_SMALL_NUMBER_OF_PIXELS, statusBarHeight)); syncProgressBar = new JProgressBar(0, 100); syncProgressBar.setValue(0); syncProgressBar.setStringPainted(false); syncProgressBar.setVisible(false); syncProgressBar.setBackground(ColorAndFontConstants.MID_BACKGROUND_COLOR); syncProgressBar.setOpaque(true); syncProgressBar.applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale())); JPanel filler = new JPanel(); filler.setOpaque(true); filler.setBackground(ColorAndFontConstants.MID_BACKGROUND_COLOR); addZone("online", onlineLabel, "" + onlineWidth, "left"); addZone("progressBar", syncProgressBar, "" + 200, "left"); addZone("network", statusLabel, "*", ""); addZone("filler2", filler, "0", "right"); statusClearTimer = new java.util.Timer(); statusClearTimer.schedule(new StatusClearTask(statusLabel), TIMER_REPEAT_TIME, TIMER_REPEAT_TIME); } /** * initialise the statusbar; */ public void initialise() { updateOnlineStatusText(StatusEnum.CONNECTING); updateStatusLabel("", true); } /** * refresh online status text with existing value */ public void refreshOnlineStatusText() { updateOnlineStatusText(statusEnum); } /** * Update online status text with new value. * * @param finalStatusEnum */ public void updateOnlineStatusText(final StatusEnum finalStatusEnum) { if (EventQueue.isDispatchThread()) { updateOnlineStatusTextOnSwingThread(finalStatusEnum); } else { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateOnlineStatusTextOnSwingThread(finalStatusEnum); } }); } } /** * Update online status text with new value. * * @param finalStatusEnum */ public void updateOnlineStatusTextOnSwingThread(final StatusEnum finalStatusEnum) { this.statusEnum = finalStatusEnum; String onlineStatus = controller.getLocaliser().getString(finalStatusEnum.getLocalisationKey()); if (finalStatusEnum == StatusEnum.ONLINE) { onlineLabel.setForeground(new Color(0, 100, 0)); if (mainFrame != null) { BlinkLabel estimatedBalanceBTCLabel = mainFrame.getEstimatedBalanceBTCLabel(); if (estimatedBalanceBTCLabel != null) { estimatedBalanceBTCLabel.setBlinkEnabled(true); } BlinkLabel estimatedBalanceFiatLabel = mainFrame.getEstimatedBalanceFiatLabel(); if (estimatedBalanceFiatLabel != null) { estimatedBalanceFiatLabel.setBlinkEnabled(true); } } } else { onlineLabel.setForeground(new Color(180, 0, 0)); } onlineLabel.setText(onlineStatus); if (finalStatusEnum == StatusEnum.ERROR) { // Set tooltip to look at Messages view String toolTip = HelpContentsPanel.createMultilineTooltipText(new String[]{ controller.getLocaliser().getString("multiBitFrame.statusBar.error1"), controller.getLocaliser().getString("multiBitFrame.statusBar.error2")}); onlineLabel.setToolTipText(toolTip); } } synchronized private void startSync() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // Switch off the Send button SendBitcoinPanel.setEnableSendButton(false); syncProgressBar.applyComponentOrientation(ComponentOrientation.getOrientation(controller.getLocaliser().getLocale())); syncProgressBar.setValue(0); syncProgressBar.setVisible(true); } }); } synchronized private void finishSync() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // Switch on the Send button SendBitcoinPanel.setEnableSendButton(true); syncProgressBar.setValue(100); syncProgressBar.setVisible(false); } }); } synchronized private void updateSync(final int percent, final String syncMessage) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { syncProgressBar.setValue(percent); syncProgressBar.setToolTipText(HelpContentsPanel.createTooltipText(syncMessage)); // when a language changes the progress bar needs to be made visible syncProgressBar.setVisible(true); } }); } @Override public void newMessageReceived(final Message newMessage) { if (newMessage == null || !newMessage.isShowInStatusBar()) { return; } if (newMessage.getPercentComplete() == Message.NOT_RELEVANT_PERCENTAGE_COMPLETE) { updateStatusLabel(newMessage.getText(), newMessage.isClearAutomatically()); } else { if (Math.abs(newMessage.getPercentComplete() - 0) < TOLERANCE) { startSync(); } updateSync((int) newMessage.getPercentComplete(), newMessage.getText()); if (Math.abs(newMessage.getPercentComplete() - 100) < TOLERANCE) { finishSync(); } } } private void updateStatusLabel(final String newStatusLabel, Boolean clearAutomatically) { StatusBar.clearAutomatically = clearAutomatically; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { if (statusLabel != null) { log.debug("StatusBar label = '" + newStatusLabel + "'"); statusLabel.setText(newStatusLabel); } } }); } private void setZoneBorder(Border border) { zoneBorder = border; } /** * Adds a new zone in the StatusBar * * @param id * @param zone * @param constraints one of the constraint support by the * com.l2fprod.common.swing.PercentLayout */ private void addZone(String id, Component zone, String constraints, String tweak) { // is there already a zone with this id? Component previousZone = getZone(id); if (previousZone != null) { remove(previousZone); idToZones.remove(id); } if (zone instanceof JComponent) { JComponent jc = (JComponent) zone; jc.setOpaque(true); //jc.setBackground(ColorAndFontConstants.BACKGROUND_COLOR); if (jc.getBorder() == null || jc.getBorder() instanceof UIResource) { if (jc instanceof JLabel) { if ("left".equals(tweak)) { Border border = new CompoundBorder(BorderFactory.createMatteBorder(0, 3, 0, 2, ColorAndFontConstants.BACKGROUND_COLOR), BorderFactory .createLineBorder(Color.lightGray)); jc.setBorder(border); } else { if ("right".equals(tweak)) { jc.setBorder(new CompoundBorder(zoneBorder, new EmptyBorder(0, 2, 0, 1))); } else { jc.setBorder(new CompoundBorder(zoneBorder, new EmptyBorder(0, 2, 0, 2))); } } ((JLabel) jc).setText(" "); } else { if (!(jc instanceof JPanel)) { jc.setBorder(zoneBorder); } } } } add(zone, constraints); idToZones.put(id, zone); } public Component getZone(String id) { return idToZones.get(id); } } /** * PercentLayout. <BR> * Constraint based layout which allow the space to be splitted using * percentages. The following are allowed when adding components to container: * <ul> * <li>container.add(component); <br> * in this case, the component will be sized to its preferred size * <li>container.add(component, "100"); <br> * in this case, the component will have a width (or height) of 100 * <li>container.add(component, "25%"); <br> * in this case, the component will have a width (or height) of 25 % of the * container width (or height) <br> * <li>container.add(component, "*"); <br> * in this case, the component will take the remaining space. if several * components use the "*" constraint the space will be divided among the * components. * </ul> * * @javabean.class name="PercentLayout" * shortDescription="A layout supports constraints expressed in percent." */ class PercentLayout implements LayoutManager2 { /** * Useful constant to layout the components horizontally (from top to * bottom). */ public final static int HORIZONTAL = 0; /** * Useful constant to layout the components vertically (from left to right). */ public final static int VERTICAL = 1; static class Constraint { protected Object value; private Constraint(Object value) { this.value = value; } } static class NumberConstraint extends Constraint { public NumberConstraint(int d) { this(Integer.valueOf(d)); } public NumberConstraint(Integer d) { super(d); } public int intValue() { return (Integer) value; } } static class PercentConstraint extends Constraint { public PercentConstraint(float d) { super(d); } public float floatValue() { return (Float) value; } } private final static Constraint REMAINING_SPACE = new Constraint("*"); private final static Constraint PREFERRED_SIZE = new Constraint(""); private int orientation; private int gap; private Locale locale; // Consider using HashMap private Hashtable<Component, Constraint> m_ComponentToConstraint; public PercentLayout(int orientation, int gap, Locale locale) { setOrientation(orientation); this.gap = gap; this.locale = locale; m_ComponentToConstraint = new Hashtable<Component, Constraint>(); } public void setGap(int gap) { this.gap = gap; } /** * @javabean.property bound="true" preferred="true" */ public int getGap() { return gap; } public void setOrientation(int orientation) { if (orientation != HORIZONTAL && orientation != VERTICAL) { throw new IllegalArgumentException("Orientation must be one of HORIZONTAL or VERTICAL"); } this.orientation = orientation; } /** * @javabean.property bound="true" preferred="true" */ public int getOrientation() { return orientation; } public void setConstraint(Component component, Object constraints) { if (constraints instanceof Constraint) { m_ComponentToConstraint.put(component, (Constraint) constraints); } else if (constraints instanceof Number) { setConstraint(component, new NumberConstraint(((Number) constraints).intValue())); } else if ("*".equals(constraints)) { setConstraint(component, REMAINING_SPACE); } else if ("".equals(constraints)) { setConstraint(component, PREFERRED_SIZE); } else if (constraints instanceof String) { String s = (String) constraints; if (s.endsWith("%")) { float value = Float.valueOf(s.substring(0, s.length() - 1)) / 100; if (value > 1 || value < 0) { throw new IllegalArgumentException("percent value must be >= 0 and <= 100"); } setConstraint(component, new PercentConstraint(value)); } else { setConstraint(component, new NumberConstraint(Integer.valueOf(s))); } } else if (constraints == null) { // null constraint means preferred size setConstraint(component, PREFERRED_SIZE); } else { throw new IllegalArgumentException("Invalid Constraint"); } } @Override public void addLayoutComponent(Component component, Object constraints) { setConstraint(component, constraints); } /** * Returns the alignment along the x axis. This specifies how the component * would like to be aligned relative to other components. The value should * be a number between 0 and 1 where 0 represents alignment along the * origin, 1 is aligned the furthest away from the origin, 0.5 is centered, * etc. */ @Override public float getLayoutAlignmentX(Container target) { return 1.0f / 2.0f; } /** * Returns the alignment along the y axis. This specifies how the component * would like to be aligned relative to other components. The value should * be a number between 0 and 1 where 0 represents alignment along the * origin, 1 is aligned the furthest away from the origin, 0.5 is centered, * etc. */ @Override public float getLayoutAlignmentY(Container target) { return 1.0f / 2.0f; } /** * Invalidates the layout, indicating that if the layout manager has cached * information it should be discarded. */ @Override public void invalidateLayout(Container target) { } /** * Adds the specified component with the specified name to the layout. * * @param name the component name * @param comp the component to be added */ @Override public void addLayoutComponent(String name, Component comp) { } /** * Removes the specified component from the layout. * * @param comp the component ot be removed */ @Override public void removeLayoutComponent(Component comp) { m_ComponentToConstraint.remove(comp); } /** * Calculates the minimum size dimensions for the specified panel given the * components in the specified parent container. * * @param parent the component to be laid out * @see #preferredLayoutSize */ @Override public Dimension minimumLayoutSize(Container parent) { return preferredLayoutSize(parent); } /** * Returns the maximum size of this component. * * @see java.awt.Component#getMinimumSize() * @see java.awt.Component#getPreferredSize() * @see java.awt.LayoutManager */ @Override public Dimension maximumLayoutSize(Container parent) { return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); } @Override public Dimension preferredLayoutSize(Container parent) { Component[] components = parent.getComponents(); Insets insets = parent.getInsets(); int width = 0; int height = 0; Dimension componentPreferredSize; boolean firstVisibleComponent = true; for (int i = 0, c = components.length; i < c; i++) { if (components[i].isVisible()) { componentPreferredSize = components[i].getPreferredSize(); if (orientation == HORIZONTAL) { height = Math.max(height, componentPreferredSize.height); width += componentPreferredSize.width; if (firstVisibleComponent) { firstVisibleComponent = false; } else { width += gap; } } else { height += componentPreferredSize.height; width = Math.max(width, componentPreferredSize.width); if (firstVisibleComponent) { firstVisibleComponent = false; } else { height += gap; } } } } return new Dimension(width + insets.right + insets.left, height + insets.top + insets.bottom); } @Override public void layoutContainer(Container parent) { Insets insets = parent.getInsets(); Dimension d = parent.getSize(); // calculate the available sizes d.width = d.width - insets.left - insets.right; d.height = d.height - insets.top - insets.bottom; // pre-calculate the size of each components Component[] components = parent.getComponents(); int[] sizes = new int[components.length]; // calculate the available size int availableSize = (HORIZONTAL == orientation ? d.width : d.height) - (components.length - 1) * gap; // PENDING(fred): the following code iterates 4 times on the component // array, need to find something more efficient! // give priority to components who want to use their preferred size or // who // have a predefined size for (int i = 0, c = components.length; i < c; i++) { if (components[i].isVisible()) { Constraint constraint = m_ComponentToConstraint.get(components[i]); if (constraint == null || constraint == PREFERRED_SIZE) { sizes[i] = (HORIZONTAL == orientation ? components[i].getPreferredSize().width : components[i] .getPreferredSize().height); availableSize -= sizes[i]; } else if (constraint instanceof NumberConstraint) { sizes[i] = ((NumberConstraint) constraint).intValue(); availableSize -= sizes[i]; } } } // then work with the components who want a percentage of the remaining // space int remainingSize = availableSize; for (int i = 0, c = components.length; i < c; i++) { if (components[i].isVisible()) { Constraint constraint = m_ComponentToConstraint.get(components[i]); if (constraint instanceof PercentConstraint) { sizes[i] = (int) (remainingSize * ((PercentConstraint) constraint).floatValue()); availableSize -= sizes[i]; } } } // finally share the remaining space between the other components ArrayList<Integer> remaining = new ArrayList<Integer>(); for (int i = 0, c = components.length; i < c; i++) { if (components[i].isVisible()) { Constraint constraint = m_ComponentToConstraint.get(components[i]); if (constraint == REMAINING_SPACE) { remaining.add(i); sizes[i] = 0; } } } if (remaining.size() > 0) { int rest = availableSize / remaining.size(); for (Integer aRemaining : remaining) { sizes[aRemaining] = rest; } } // all calculations are done, apply the sizes int currentOffset = (HORIZONTAL == orientation ? insets.left : insets.top); if (!ComponentOrientation.getOrientation(locale).isLeftToRight()) { currentOffset = (HORIZONTAL == orientation ? d.width - insets.right : insets.top); } for (int i = 0, c = components.length; i < c; i++) { if (components[i].isVisible()) { if (HORIZONTAL == orientation) { if (ComponentOrientation.getOrientation(locale).isLeftToRight()) { components[i].setBounds(currentOffset, insets.top, sizes[i], d.height); } else { components[i].setBounds(currentOffset - sizes[i], insets.top, sizes[i], d.height); } } else { components[i].setBounds(insets.left, currentOffset, d.width, sizes[i]); } if (ComponentOrientation.getOrientation(locale).isLeftToRight()) { currentOffset += gap + sizes[i]; } else { currentOffset = currentOffset - gap - sizes[i]; } } } } } class StatusClearTask extends TimerTask { JButton statusLabel; private String previousStatusLabelText = null; private int previousLabelRepeats = 0; StatusClearTask(JButton statusLabel) { this.statusLabel = statusLabel; } @Override public void run() { String currentStatusLabelText = statusLabel.getText(); boolean hasReset = false; if (previousLabelRepeats > StatusBar.NUMBER_OF_REPEATS) { if (currentStatusLabelText != null && !"".equals(currentStatusLabelText) && currentStatusLabelText.equals(previousStatusLabelText)) { if (StatusBar.clearAutomatically) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // clear label statusLabel.setText(""); } }); previousStatusLabelText = ""; previousLabelRepeats = 0; hasReset = true; } } } if (currentStatusLabelText != null && !currentStatusLabelText.equals(previousStatusLabelText)) { // different - reset previousStatusLabelText = currentStatusLabelText; previousLabelRepeats = 0; } else { if (currentStatusLabelText != null && currentStatusLabelText.equals(previousStatusLabelText)) { if (!hasReset) { // label is the same as before previousLabelRepeats++; } } } } } /** * LookAndFeelTweaks */ final class LookAndFeelTweaks { public final static Border PANEL_BORDER = BorderFactory.createMatteBorder(3, 3, 3, 3, ColorAndFontConstants.MID_BACKGROUND_COLOR); /** * Utility class should not have a public constructor */ private LookAndFeelTweaks() { } public static PercentLayout createHorizontalPercentLayout(Locale locale) { return new PercentLayout(PercentLayout.HORIZONTAL, 4, locale); } public static void setBorder(JComponent component) { if (component instanceof JPanel) { component.setBorder(PANEL_BORDER); } } }