/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander 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 3 of the License, or * (at your option) any later version. * * muCommander 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, see <http://www.gnu.org/licenses/>. */ package com.mucommander.ui.main; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.io.IOException; import javax.swing.Box; import javax.swing.BoxLayout; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.SwingConstants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mucommander.cache.FastLRUCache; import com.mucommander.cache.LRUCache; import com.mucommander.commons.conf.ConfigurationEvent; import com.mucommander.commons.conf.ConfigurationListener; import com.mucommander.commons.file.AbstractFile; import com.mucommander.commons.file.protocol.local.LocalFile; import com.mucommander.commons.runtime.JavaVersion; import com.mucommander.conf.MuConfigurations; import com.mucommander.conf.MuPreference; import com.mucommander.conf.MuPreferences; import com.mucommander.desktop.DesktopManager; import com.mucommander.text.SizeFormat; import com.mucommander.text.Translator; import com.mucommander.ui.action.ActionManager; import com.mucommander.ui.border.MutableLineBorder; import com.mucommander.ui.event.ActivePanelListener; import com.mucommander.ui.event.LocationEvent; import com.mucommander.ui.event.LocationListener; import com.mucommander.ui.event.TableSelectionListener; import com.mucommander.ui.icon.SpinningDial; import com.mucommander.ui.main.table.FileTable; import com.mucommander.ui.main.table.FileTableModel; import com.mucommander.ui.theme.ColorChangedEvent; import com.mucommander.ui.theme.FontChangedEvent; import com.mucommander.ui.theme.Theme; import com.mucommander.ui.theme.ThemeListener; import com.mucommander.ui.theme.ThemeManager; /** * StatusBar is the component that sits at the bottom of each MainFrame, between the folder panels and command bar. * There is one and only one StatusBar per MainFrame, created by the associated MainFrame. It can be hidden, * but the instance will always remain, until the MainFrame is disposed. * * <p>StatusBar is used to display info about the total/selected number of files in the current folder and current volume's * free/total space. When a folder is being changed, a waiting message is displayed. When quick search is being used, * the current quick search string is displayed. * * <p>StatusBar receives LocationListener events when the folder has or is being changed, and automatically updates * selected files and volume info, and display the waiting message when the folder is changing. Quick search info * is set by FileTable.QuickSearch. * * <p>When StatusBar is visible, a Thread runs in the background to periodically update free/total space volume info. * This thread stops when the StatusBar is hidden. * * @author Maxence Bernard */ public class StatusBar extends JPanel implements Runnable, MouseListener, ActivePanelListener, TableSelectionListener, LocationListener, ComponentListener, ThemeListener { private static final Logger LOGGER = LoggerFactory.getLogger(StatusBar.class); private MainFrame mainFrame; /** Label that displays info about current selected file(s) */ private JLabel selectedFilesLabel; /** Icon used while loading is in progress. */ private SpinningDial dial; /** Label that displays info about current volume (free/total space) */ private VolumeSpaceLabel volumeSpaceLabel; /** Thread which auto updates volume info */ private Thread autoUpdateThread; /** Number of volume info strings that can be temporarily cached */ private final static int VOLUME_INFO_CACHE_CAPACITY = 50; /** Number of milliseconds before cached volume info strings expire */ private final static int VOLUME_INFO_TIME_TO_LIVE = 60000; /** Number of milliseconds between each volume info update by auto-update thread */ private final static int AUTO_UPDATE_PERIOD = 6000; /** Caches volume info strings (free/total space) for a while, since this information is expensive to retrieve * (I/O bound). This map uses folders' volume path as its key. */ private static LRUCache<String, Long[]> volumeInfoCache = new FastLRUCache<String, Long[]>(VOLUME_INFO_CACHE_CAPACITY); /** Icon that is displayed when folder is changing */ public final static String WAITING_ICON = "waiting.png"; /** SizeFormat's format used to display volume info in status bar */ private final static int VOLUME_INFO_SIZE_FORMAT = SizeFormat.DIGITS_MEDIUM| SizeFormat.UNIT_SHORT| SizeFormat.INCLUDE_SPACE| SizeFormat.ROUND_TO_KB; /** Listens to configuration changes and updates static fields accordingly */ public final static ConfigurationListener CONFIGURATION_ADAPTER; /** SizeFormat format used to create the selected file(s) size string */ private static int selectedFileSizeFormat; static { // Initialize the size column format based on the configuration setSelectedFileSizeFormat(MuConfigurations.getPreferences().getVariable(MuPreference.DISPLAY_COMPACT_FILE_SIZE, MuPreferences.DEFAULT_DISPLAY_COMPACT_FILE_SIZE)); // Listens to configuration changes and updates static fields accordingly. // Note: a reference to the listener must be kept to prevent it from being garbage-collected. CONFIGURATION_ADAPTER = new ConfigurationListener() { public synchronized void configurationChanged(ConfigurationEvent event) { String var = event.getVariable(); if (var.equals(MuPreferences.DISPLAY_COMPACT_FILE_SIZE)) setSelectedFileSizeFormat(event.getBooleanValue()); } }; MuConfigurations.addPreferencesListener(CONFIGURATION_ADAPTER); } /** * Sets the SizeFormat format used to create the selected file(s) size string. * * @param compactSize true to use a compact size format, false for full size in bytes */ private static void setSelectedFileSizeFormat(boolean compactSize) { if(compactSize) selectedFileSizeFormat = SizeFormat.DIGITS_MEDIUM | SizeFormat.UNIT_SHORT | SizeFormat.ROUND_TO_KB; else selectedFileSizeFormat = SizeFormat.DIGITS_FULL | SizeFormat.UNIT_LONG; selectedFileSizeFormat |= SizeFormat.INCLUDE_SPACE; } /** * Creates a new StatusBar instance. */ public StatusBar(MainFrame mainFrame) { // Create and add status bar setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); this.mainFrame = mainFrame; selectedFilesLabel = new JLabel(""); dial = new SpinningDial(); add(selectedFilesLabel); add(Box.createHorizontalGlue()); JobsPopupButton jobsButton = new JobsPopupButton(); jobsButton.setPopupMenuLocation(SwingConstants.TOP); add(jobsButton); add(Box.createRigidArea(new Dimension(2, 0))); // Add a button for interacting with the trash, only if the current platform has a trash implementation if(DesktopManager.getTrash()!=null) { TrashPopupButton trashButton = new TrashPopupButton(mainFrame); trashButton.setPopupMenuLocation(SwingConstants.TOP); add(trashButton); add(Box.createRigidArea(new Dimension(2, 0))); } volumeSpaceLabel = new VolumeSpaceLabel(); add(volumeSpaceLabel); // Show/hide this status bar based on user preferences // Note: setVisible has to be called even with true for the auto-update thread to be initialized setVisible(MuConfigurations.getPreferences().getVariable(MuPreference.STATUS_BAR_VISIBLE, MuPreferences.DEFAULT_STATUS_BAR_VISIBLE)); // Catch location events to update status bar info when folder is changed FolderPanel leftPanel = mainFrame.getLeftPanel(); leftPanel.getLocationManager().addLocationListener(this); FolderPanel rightPanel = mainFrame.getRightPanel(); rightPanel.getLocationManager().addLocationListener(this); // Catch table selection change events to update the selected files info when the selected files have changed on // one of the file tables leftPanel.getFileTable().addTableSelectionListener(this); rightPanel.getFileTable().addTableSelectionListener(this); // Catch active panel change events to update status bar info when current table has changed mainFrame.addActivePanelListener(this); // Catch mouse events to pop up a menu on right-click selectedFilesLabel.addMouseListener(this); volumeSpaceLabel.addMouseListener(this); addMouseListener(this); // Catch component events to be notified when this component is made visible // and update status info addComponentListener(this); // Initialises theme. selectedFilesLabel.setFont(ThemeManager.getCurrentFont(Theme.STATUS_BAR_FONT)); selectedFilesLabel.setForeground(ThemeManager.getCurrentColor(Theme.STATUS_BAR_FOREGROUND_COLOR)); volumeSpaceLabel.setFont(ThemeManager.getCurrentFont(Theme.STATUS_BAR_FONT)); volumeSpaceLabel.setForeground(ThemeManager.getCurrentColor(Theme.STATUS_BAR_FOREGROUND_COLOR)); ThemeManager.addCurrentThemeListener(this); } /** * Updates info displayed on the status bar: currently selected files and volume info. */ private void updateStatusInfo() { // No need to waste precious cycles if status bar is not visible if(!isVisible()) return; updateSelectedFilesInfo(); updateVolumeInfo(); } /** * Updates info about currently selected files ((nb of selected files, combined size), displayed on the left-side of this status bar. */ // Making this method synchronized creates a deadlock with FileTable // public synchronized void updateSelectedFilesInfo() { public void updateSelectedFilesInfo() { // No need to waste precious cycles if status bar is not visible if(!isVisible()) return; FileTable currentFileTable = mainFrame.getActiveTable(); // Currently select file, can be null AbstractFile selectedFile = currentFileTable.getSelectedFile(false, true); FileTableModel tableModel = currentFileTable.getFileTableModel(); // Number of marked files, can be 0 int nbMarkedFiles = tableModel.getNbMarkedFiles(); // Combined size of marked files, 0 if no file has been marked long markedTotalSize = tableModel.getTotalMarkedSize(); // number of files in folder int fileCount = tableModel.getFileCount(); // Update files info based on marked files if there are some, or currently selected file otherwise int nbSelectedFiles; if(nbMarkedFiles==0 && selectedFile!=null) nbSelectedFiles = 1; else nbSelectedFiles = nbMarkedFiles; String filesInfo; if(fileCount==0) { // Set status bar to a space character, not an empty string // otherwise it will disappear filesInfo = " "; } else { filesInfo = Translator.get("status_bar.selected_files", ""+nbSelectedFiles, ""+fileCount); if(nbMarkedFiles>0) filesInfo += " - "+ SizeFormat.format(markedTotalSize, selectedFileSizeFormat); if(selectedFile!=null) filesInfo += " - "+selectedFile.getName(); } // Update label setStatusInfo(filesInfo); } /** * Updates info about current volume (free space, total space), displayed on the right-side of this status bar. */ private synchronized void updateVolumeInfo() { // No need to waste precious cycles if status bar is not visible if(!isVisible()) return; final AbstractFile currentFolder = mainFrame.getActivePanel().getCurrentFolder(); // Resolve the current folder's volume and use its path as a key for the volume info cache final String volumePath = currentFolder.exists() ? currentFolder.getVolume().getAbsolutePath(true) : ""; Long cachedVolumeInfo[] = volumeInfoCache.get(volumePath); if(cachedVolumeInfo!=null) { LOGGER.debug("Cache hit!"); volumeSpaceLabel.setVolumeSpace(cachedVolumeInfo[0], cachedVolumeInfo[1]); } else { // Retrieves free and total volume space. // Perform volume info retrieval in a separate thread as this method may be called // by the event thread and it can take a while, we want to return as soon as possible new Thread("StatusBar.updateVolumeInfo") { @Override public void run() { // Free space on current volume, -1 if this information is not available long volumeFree; // Total space on current volume, -1 if this information is not available long volumeTotal; // Folder is a local file and Java version is 1.5: call getVolumeInfo() instead of // separate calls to getFreeSpace() and getTotalSpace() as it is twice as fast. if(currentFolder instanceof LocalFile && JavaVersion.JAVA_1_5.isCurrentOrLower()) { try { long volumeInfo[] = ((LocalFile)currentFolder).getVolumeInfo(); volumeTotal = volumeInfo[0]; volumeFree = volumeInfo[1]; } catch(IOException e) { volumeTotal = -1; volumeFree = -1; } } // Java 1.6 and up or any other file type else { try { volumeFree = currentFolder.getFreeSpace(); } catch(IOException e) { volumeFree = -1; } try { volumeTotal = currentFolder.getTotalSpace(); } catch(IOException e) { volumeTotal = -1; } } // For testing the free space indicator //volumeFree = (long)(volumeTotal * Math.random()); volumeSpaceLabel.setVolumeSpace(volumeTotal, volumeFree); LOGGER.debug("Adding to cache"); volumeInfoCache.add(volumePath, new Long[]{volumeTotal, volumeFree}, VOLUME_INFO_TIME_TO_LIVE); } }.start(); } } /** * Displays the specified text and icon on the left-side of the status bar, * replacing any previous information. * * @param text the piece of text to display * @param icon the icon to display next to the text * @param iconBeforeText if true, icon will be placed on the left side of the text, if not on the right side */ public void setStatusInfo(String text, Icon icon, boolean iconBeforeText) { selectedFilesLabel.setText(text); if(icon==null) { // What we don't want here is the label's height to change depending on whether it has an icon or not. // This would result in having to revalidate the status bar and in turn the whole MainFrame. // A label's height is roughly the max of the text's font height and the icon (if any). So if there is no // icon for the label, we use a transparent image for padding in case the text's font height is smaller // than a 'standard' (16x16) icon. This ensures that the label's height remains constant. BufferedImage bi = new BufferedImage(1, 16, BufferedImage.TYPE_INT_ARGB); icon = new ImageIcon(bi); } selectedFilesLabel.setIcon(icon); selectedFilesLabel.setHorizontalTextPosition(iconBeforeText?JLabel.TRAILING:JLabel.LEADING); } /** * Displays the specified text on the left-side of the status bar, * replacing any previous text and icon. * * @param infoMessage the piece of text to display */ public void setStatusInfo(String infoMessage) { setStatusInfo(infoMessage, null, false); } /** * Starts a volume info auto-update thread, only if there isn't already one running. */ private synchronized void startAutoUpdate() { if(autoUpdateThread==null) { // Start volume info auto-update thread autoUpdateThread = new Thread(this, "StatusBar autoUpdateThread"); // Set the thread as a daemon thread autoUpdateThread.setDaemon(true); autoUpdateThread.start(); } } /** * Overrides JComponent.setVisible(boolean) to start/stop volume info auto-update thread. */ @Override public void setVisible(boolean visible) { if(visible) { // Start auto-update thread startAutoUpdate(); super.setVisible(true); // Update status bar info updateStatusInfo(); } else { // Stop auto-update thread this.autoUpdateThread = null; super.setVisible(false); } } ////////////////////// // Runnable methods // ////////////////////// /** * Periodically updates volume info (free / total space). */ public void run() { do { // Sleep for a while try { Thread.sleep(AUTO_UPDATE_PERIOD); } catch (InterruptedException e) {} // Update volume info if: // - status bar is visible // - MainFrame isn't changing folders // - MainFrame is active and in the foreground // Volume info update will potentially hit the LRU cache and not actually update volume info if(isVisible() && !mainFrame.getNoEventsMode() && mainFrame.isForegroundActive()) updateVolumeInfo(); } while(autoUpdateThread!=null && mainFrame.isVisible()); // Stop when MainFrame is disposed } //////////////////////////////////////// // ActivePanelListener implementation // //////////////////////////////////////// public void activePanelChanged(FolderPanel folderPanel) { updateStatusInfo(); } /////////////////////////////////////////// // TableSelectionListener implementation // /////////////////////////////////////////// public void selectedFileChanged(FileTable source) { // No need to update if the originating FileTable is not the currently active one if(source==mainFrame.getActiveTable() && mainFrame.isForegroundActive()) updateSelectedFilesInfo(); } public void markedFilesChanged(FileTable source) { // No need to update if the originating FileTable is not the currently active one if(source==mainFrame.getActiveTable() && mainFrame.isForegroundActive()) updateSelectedFilesInfo(); } ///////////////////////////////////// // LocationListener implementation // ///////////////////////////////////// public void locationChanged(LocationEvent e) { dial.setAnimated(false); updateStatusInfo(); } public void locationChanging(LocationEvent e) { // Show a message in the status bar saying that folder is being changed setStatusInfo(Translator.get("status_bar.connecting_to_folder"), dial, true); dial.setAnimated(true); } public void locationCancelled(LocationEvent e) { dial.setAnimated(false); updateStatusInfo(); } public void locationFailed(LocationEvent e) { dial.setAnimated(false); updateStatusInfo(); } ////////////////////////////////// // MouseListener implementation // ////////////////////////////////// public void mouseClicked(MouseEvent e) { // Discard mouse events while in 'no events mode' if(mainFrame.getNoEventsMode()) return; // Right clicking on the toolbar brings up a popup menu that allows the user to hide this status bar if (DesktopManager.isRightMouseButton(e)) { // if (e.isPopupTrigger()) { // Doesn't work under Mac OS X (CTRL+click doesn't return true) JPopupMenu popupMenu = new JPopupMenu(); popupMenu.add(ActionManager.getActionInstance(com.mucommander.ui.action.impl.ToggleStatusBarAction.Descriptor.ACTION_ID, mainFrame)); popupMenu.show(this, e.getX(), e.getY()); popupMenu.setVisible(true); } } public void mouseReleased(MouseEvent e) { } public void mousePressed(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } ////////////////////////////////////// // ComponentListener implementation // ////////////////////////////////////// public void componentShown(ComponentEvent e) { // Invoked when the component has been made visible (apparently not called when just created) // Status bar needs to be updated sihce it is not updated when not visible updateStatusInfo(); } public void componentHidden(ComponentEvent e) { } public void componentMoved(ComponentEvent e) { } public void componentResized(ComponentEvent e) { } public void fontChanged(FontChangedEvent event) { if(event.getFontId() == Theme.STATUS_BAR_FONT) { selectedFilesLabel.setFont(event.getFont()); volumeSpaceLabel.setFont(event.getFont()); repaint(); } } public void colorChanged(ColorChangedEvent event) { if(event.getColorId() == Theme.STATUS_BAR_FOREGROUND_COLOR) { selectedFilesLabel.setForeground(event.getColor()); volumeSpaceLabel.setForeground(event.getColor()); repaint(); } } /////////////////// // Inner classes // /////////////////// /** * This label displays the amount of free and/or total space on a volume. */ private static class VolumeSpaceLabel extends JLabel implements ThemeListener { private long freeSpace; private long totalSpace; private Color backgroundColor; private Color okColor; private Color warningColor; private Color criticalColor; private final static float SPACE_WARNING_THRESHOLD = 0.1f; private final static float SPACE_CRITICAL_THRESHOLD = 0.05f; private VolumeSpaceLabel() { super(""); setHorizontalAlignment(CENTER); backgroundColor = ThemeManager.getCurrentColor(Theme.STATUS_BAR_BACKGROUND_COLOR); // borderColor = ThemeManager.getCurrentColor(Theme.STATUS_BAR_BORDER_COLOR); okColor = ThemeManager.getCurrentColor(Theme.STATUS_BAR_OK_COLOR); warningColor = ThemeManager.getCurrentColor(Theme.STATUS_BAR_WARNING_COLOR); criticalColor = ThemeManager.getCurrentColor(Theme.STATUS_BAR_CRITICAL_COLOR); setBorder(new MutableLineBorder(ThemeManager.getCurrentColor(Theme.STATUS_BAR_BORDER_COLOR))); ThemeManager.addCurrentThemeListener(this); } /** * Sets the new volume total and free space, and updates the label's text to show the new values and, * only if both total and free space are available (different from -1), paint a graphical representation * of the amount of free space available and set a tooltip showing the percentage of free space on the volume. * * @param totalSpace total volume space, -1 if not available * @param freeSpace free volume space, -1 if not available */ private void setVolumeSpace(long totalSpace, long freeSpace) { this.freeSpace = freeSpace; this.totalSpace = totalSpace; // Set new label's text String volumeInfo; if(freeSpace!=-1) { volumeInfo = SizeFormat.format(freeSpace, VOLUME_INFO_SIZE_FORMAT); if(totalSpace!=-1) volumeInfo += " / "+ SizeFormat.format(totalSpace, VOLUME_INFO_SIZE_FORMAT); volumeInfo = Translator.get("status_bar.volume_free", volumeInfo); } else if(totalSpace!=-1) { volumeInfo = SizeFormat.format(totalSpace, VOLUME_INFO_SIZE_FORMAT); volumeInfo = Translator.get("status_bar.volume_capacity", volumeInfo); } else { volumeInfo = ""; } setText(volumeInfo); // Set tooltip if(freeSpace==-1 || totalSpace==-1) setToolTipText(null); // Removes any previous tooltip else setToolTipText(""+(int)(100*freeSpace/(float)totalSpace)+"%"); repaint(); } /** * Adds some empty space around the label. */ @Override public Dimension getPreferredSize() { Dimension d = super.getPreferredSize(); return new Dimension(d.width+4, d.height+2); } /** * Returns an interpolated color value, located at percent between c1 and c2 in the RGB space. * * @param c1 first color * @param c2 end color * @param percent distance between c1 and c2, comprised between 0 and 1. * @return an interpolated color value, located at percent between c1 and c2 in the RGB space. */ private Color interpolateColor(Color c1, Color c2, float percent) { return new Color( (int)(c1.getRed()+(c2.getRed()-c1.getRed())*percent), (int)(c1.getGreen()+(c2.getGreen()-c1.getGreen())*percent), (int)(c1.getBlue()+(c2.getBlue()-c1.getBlue())*percent) ); } @Override public void paint(Graphics g) { // If free or total space is not available, this label will just be painted as a normal JLabel if(freeSpace!=-1 && totalSpace!=-1) { int width = getWidth(); int height = getHeight(); // Paint amount of free volume space if both free and total space are available float freeSpacePercentage = freeSpace/(float)totalSpace; Color c; if(freeSpacePercentage<=SPACE_CRITICAL_THRESHOLD) { c = criticalColor; } else if(freeSpacePercentage<=SPACE_WARNING_THRESHOLD) { c = interpolateColor(warningColor, criticalColor, (SPACE_WARNING_THRESHOLD-freeSpacePercentage)/SPACE_WARNING_THRESHOLD); } else { c = interpolateColor(okColor, warningColor, (1-freeSpacePercentage)/(1-SPACE_WARNING_THRESHOLD)); } g.setColor(c); int freeSpaceWidth = Math.max(Math.round(freeSpacePercentage*(float)(width-2)), 1); g.fillRect(1, 1, freeSpaceWidth + 1, height - 2); // Fill background g.setColor(backgroundColor); g.fillRect(freeSpaceWidth + 1, 1, width - freeSpaceWidth - 1, height - 2); } super.paint(g); } // Total/Free space reversed, doesn't look quite right // @Override // public void paint(Graphics g) { // // If free or total space is not available, this label will just be painted as a normal JLabel // if(freeSpace!=-1 && totalSpace!=-1) { // int width = getWidth(); // int height = getHeight(); // // // Paint amount of free volume space if both free and total space are available // float freeSpacePercentage = freeSpace/(float)totalSpace; // float usedSpacePercentage = (totalSpace-freeSpace)/(float)totalSpace; // // Color c; // if(freeSpacePercentage<=SPACE_CRITICAL_THRESHOLD) { // c = criticalColor; // } // else if(freeSpacePercentage<=SPACE_WARNING_THRESHOLD) { // c = interpolateColor(warningColor, criticalColor, (SPACE_WARNING_THRESHOLD-freeSpacePercentage)/SPACE_WARNING_THRESHOLD); // } // else { // c = interpolateColor(okColor, warningColor, (1-freeSpacePercentage)/(1-SPACE_WARNING_THRESHOLD)); // } // // g.setColor(c); // // int usedSpaceWidth = Math.max(Math.round(usedSpacePercentage*(float)(width-2)), 1); // g.fillRect(1, 1, usedSpaceWidth + 1, height - 2); // // // Fill background // g.setColor(backgroundColor); // g.fillRect(usedSpaceWidth + 1, 1, width - usedSpaceWidth - 1, height - 2); // } // // super.paint(g); // } public void fontChanged(FontChangedEvent event) {} public void colorChanged(ColorChangedEvent event) { switch(event.getColorId()) { case Theme.STATUS_BAR_BACKGROUND_COLOR: backgroundColor = event.getColor(); break; case Theme.STATUS_BAR_BORDER_COLOR: // Some (rather evil) look and feels will change borders outside of muCommander's control, // this check is necessary to ensure no exception is thrown. if(getBorder() instanceof MutableLineBorder) ((MutableLineBorder)getBorder()).setLineColor(event.getColor()); break; case Theme.STATUS_BAR_OK_COLOR: okColor = event.getColor(); break; case Theme.STATUS_BAR_WARNING_COLOR: warningColor = event.getColor(); break; case Theme.STATUS_BAR_CRITICAL_COLOR: criticalColor = event.getColor(); break; default: return; } repaint(); } } }