/* * 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.tabs; import java.awt.BorderLayout; import java.awt.Component; import java.util.Iterator; import java.util.WeakHashMap; import javax.swing.JComponent; import javax.swing.SwingUtilities; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.mucommander.commons.conf.ConfigurationEvent; import com.mucommander.commons.conf.ConfigurationListener; import com.mucommander.conf.MuConfigurations; import com.mucommander.conf.MuPreference; import com.mucommander.conf.MuPreferences; /** * This component acts like a tabbedpane in which multiple tabs are presented in a JTabbedPane layout * and single tab is presented without the JTabbedPane layout, only the tab's data. * * When a single tab is presented and new tab is added this component makes a switch to JTabbedPane layout, * and when two tabs are presented and there is a removal of one of the tabs this component makes a switch * to JPanel layout that contains the data of the tab that is left. * * This component also provides an interface for the other parts of the application to make operations * that influence the tabs layout. * * @author Arik Hadas */ public class HideableTabbedPane<T extends Tab> extends JComponent implements TabsEventListener, ConfigurationListener, ChangeListener { private static final Logger LOGGER = LoggerFactory.getLogger(HideableTabbedPane.class); /* The tabs which are being displayed */ private TabsCollection<T> tabsCollection; /* The tabs display type (with/without tabs headers) * It is initialize as nullable so that it can be destroyed when it's replaced for the first time (see @{link tabAdded()})*/ private TabsViewer<T> tabsViewer = new NullableTabsViewer<T>(); /* The factory that will be used to create the viewers for tabs with no headers */ private TabsViewerFactory<T> tabsWithoutHeadersViewerFactory; /* The factory that will be used to create the viewers for tabs with headers */ private TabsViewerFactory<T> tabsWithHeadersViewerFactory; /* Contains all registered active tab change listeners, stored as weak references */ private WeakHashMap<ActiveTabListener, ?> activeTabChangedListener = new WeakHashMap<ActiveTabListener, Object>(); /** * Constructor * * @param tabsDisplayFactory - factory of tabs-display */ public HideableTabbedPane(TabsViewerFactory<T> tabsWithoutHeadersViewerFactory, TabsViewerFactory<T> tabsWithHeadersViewerFactory) { setLayout(new BorderLayout()); this.tabsWithoutHeadersViewerFactory = tabsWithoutHeadersViewerFactory; this.tabsWithHeadersViewerFactory = tabsWithHeadersViewerFactory; // Initialize the tabs collection tabsCollection = new TabsCollection<T>(); // Register for tabs changes tabsCollection.addTabsListener(this); MuConfigurations.addPreferencesListener(this); } /** */ public synchronized void addActiveTabListener(ActiveTabListener listener) { activeTabChangedListener.put(listener, null); } /** */ public synchronized void removeActiveTabChangedListener(ActiveTabListener listener) { activeTabChangedListener.remove(listener); } /** */ protected synchronized void fireActiveTabChanged() { for(ActiveTabListener listener : activeTabChangedListener.keySet()) listener.activeTabChanged(); } /** * This function returns an iterator that points to the current Tabs contained in the TabbedPane * * @return Iterator that points to current Tabs */ public Iterator<T> iterator() { return tabsCollection.iterator(); } /** * Select the given tab * * @param tab the tab to be selected */ public void selectTab(T tab) { int index = tabsCollection.indexOf(tab); if (index != -1) selectTab(index); else LOGGER.error("Was requested to change to non-existing tab, ignoring"); } /** * Select the tab at the given index * An exception will be thrown if no tab exists in the given index * * @param index of the tab to be selected */ public void selectTab(int index) { tabsViewer.setSelectedTabIndex(index); } /** * Return the index of the selected tab * * @return index of the selected tab */ public int getSelectedIndex() { return tabsViewer.getSelectedTabIndex(); } /** * Return how many tabs the panel contains * * @return number of tabs contained in the panel */ public int getTabsCount() { return tabsCollection.count(); } protected TabsCollection<T> getTabs() { return tabsCollection; } /*********************** * Tabs Actions Support ***********************/ /* Actions which are not depended on the display type (single/multiple tabs) */ /** * Add new tab * * @param tab - new tab's data */ protected void addTab(T tab) { tabsCollection.add(tab); } /** * Add new tab and select it * * @param tab - new tab's data */ protected void addAndSelectTab(T tab) { addTab(tab); tabsViewer.setSelectedTabIndex(tabsCollection.count()-1); } /** * Update the current displayed tab's data with the given {@link TabUpdater} * * @param updater - object that will be used to update the tab */ protected void updateCurrentTab(TabUpdater<T> updater) { tabsCollection.updateTab(getSelectedIndex(), updater); } /* Actions that depended on the display type (single/multiple tabs) */ /** * Remove tab with the given header */ protected void removeTab(Component header) { tabsViewer.removeTab(header); } /** * Remove current displayed tab */ protected T removeTab() { return tabsCollection.remove(getSelectedIndex()); } /** * Remove duplicate tabs */ protected void removeDuplicateTabs() { tabsViewer.removeDuplicateTabs(); } /** * Remove all tabs except the current displayed tab */ protected void removeOtherTabs() { tabsViewer.removeOtherTabs(); } /** * Change the current displayed tab to the tab which is located to the right of the * current displayed tab. * If the current displayed tab is the rightmost tab, the leftmost tab will be displayed. */ public void nextTab() { tabsViewer.nextTab(); } /** * Change the current displayed tab to the tab which is located to the left of the * current displayed tab. * If the current displayed tab is the leftmost tab, the rightmost tab will be displayed. */ public void previousTab() { tabsViewer.previousTab(); } /****************** * Private Methods ******************/ private void switchToTabsWithHeaders() { setTabsViewer(tabsWithHeadersViewerFactory); } private void switchToTabWithoutHeader() { setTabsViewer(tabsWithoutHeadersViewerFactory); } private void setTabsViewer(TabsViewerFactory<T> tabsViewerFactory) { tabsViewer.removeChangeListener(this); tabsViewer = tabsViewerFactory.create(tabsCollection); tabsViewer.addChangeListener(this); removeAll(); add(tabsViewer); validate(); tabsViewer.requestFocus(); } /******************** * Protected Methods ********************/ /** * Returns the tab at the given index * An exception will be thrown if no tab exists in the given index * * @param index of the requested tab * @return tab in the given index */ protected T getTab(int index) { return tabsCollection.get(index); } protected boolean refreshViewer() { int nbTabs = tabsCollection.count(); switch (nbTabs) { case 2: switchToTabsWithHeaders(); return true; case 1: if (showSingleTabHeader()) switchToTabsWithHeaders(); else switchToTabWithoutHeader(); return true; default: return false; } } protected boolean showSingleTabHeader() { return MuConfigurations.getPreferences().getVariable(MuPreference.SHOW_TAB_HEADER, MuPreferences.DEFAULT_SHOW_TAB_HEADER); } protected void show(int tabIndex) { } /************************************ * TabsChangeListener Implementation ************************************/ public void tabAdded(int index) { if (!refreshViewer()) tabsViewer.add(tabsCollection.get(index), index); if(isDisplayable()) tabsViewer.setSelectedTabIndex(index); } public void tabRemoved(int index) { int previouslySelectedIndex = tabsViewer.getSelectedTabIndex(); if (!refreshViewer()) tabsViewer.removeTab(index); else selectTab(Math.max(previouslySelectedIndex-1, 0)); } public void tabUpdated(int index) { tabsViewer.update(tabsCollection.get(index), index); fireActiveTabChanged(); } /*************************************** * ConfigurationListener Implementation ***************************************/ public void configurationChanged(ConfigurationEvent event) { String var = event.getVariable(); // Update the button's icon if the system file icons policy has changed if (var.equals(MuPreferences.SHOW_SINGLE_TAB_HEADER)) refreshViewer(); } public void stateChanged(ChangeEvent e) { final int selectedIndex = tabsViewer.getSelectedTabIndex(); if (selectedIndex != -1) show(selectedIndex); SwingUtilities.invokeLater(new Runnable() { public void run() { tabsViewer.requestFocus(); } }); } }