/*
* This file is part of muCommander, http://www.mucommander.com
* Copyright (C) 2002-2012 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.layout;
import com.mucommander.text.Translator;
import com.mucommander.ui.icon.SpinningDial;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.AncestorEvent;
import javax.swing.event.AncestorListener;
import java.awt.*;
/**
* <code>AsyncPanel</code> is a <code>JPanel</code> aimed at components that potentially take a long time to
* initialize. It allows to deport their initialization in a separate thread as to not lock the event thread.
* It works as follows:
* <ol>
* <li>Initially, AsyncPanel displays a 'please wait component' that symbolizes the fact that the contents of the
* panel is being loaded.</li>
* <li>When AsyncPanel becomes visible on screen, the {@link #getTargetComponent()} method is called to trigger the
* initialization of the real component to display.</li>
* <li>As soon as the method returns, the wait component is removed and the target component added to AsyncPanel.
* If AsyncPanel is the child of a <code>java.awt.Window</code>, the window is repacked to take into account the
* new size of this panel.</li>
* </ol>
*
* <p>This panel tries to be as 'transparent' as possible for the target component: the borders of this panel are empty
* and its layout is a <code>BorderLayout</code> where the target component is added to the center.</p>
*
* @author Maxence Bernard
*/
public abstract class AsyncPanel extends JPanel {
/** The component displayed while the target component is being loaded */
private JComponent waitComponent;
/** This field becomes true when this panel has become visible on screen. */
private boolean visibleOnScreen;
/**
* Creates a new <code>AsyncPanel</code> with the default wait component.
*/
public AsyncPanel() {
this(getDefaultWaitComponent());
}
/**
* Creates a new <code>AsyncPanel</code> that displays the given component while the target component is being
* loaded.
*
* @param waitComponent the component to display while the target component is being loaded
*/
public AsyncPanel(JComponent waitComponent) {
super(new BorderLayout());
this.waitComponent = waitComponent;
add(waitComponent, BorderLayout.CENTER);
// Starts loading the component when this panel has become visible on screen
addAncestorListener(new AncestorListener() {
public void ancestorAdded(AncestorEvent e) {
if(visibleOnScreen)
return;
visibleOnScreen = true;
removeAncestorListener(this);
loadTargetComponent();
}
public void ancestorRemoved(AncestorEvent event) {}
public void ancestorMoved(AncestorEvent event) {}
});
}
/**
* Loads the target component by calling {@link #getTargetComponent()} and replace the wait component by it.
*/
private void loadTargetComponent() {
new Thread() {
@Override
public void run() {
JComponent targetComponent = getTargetComponent();
remove(waitComponent);
setBorder(new EmptyBorder(0, 0, 0, 0));
add(targetComponent, BorderLayout.CENTER);
updateLayout();
}
}.start();
}
/**
* Returns the default component to be displayed while the target component is being loaded.
*
* @return the default component to be displayed while the target component is being loaded
*/
private static JComponent getDefaultWaitComponent() {
JLabel label = new JLabel(Translator.get("loading"));
label.setIcon(new SpinningDial(24, 24, true));
// Center the label both horizontally and vertically
JPanel tempPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.fill = GridBagConstraints.BOTH;
tempPanel.add(label, gbc);
return tempPanel;
}
/////////////////////////
// Overridable methods //
/////////////////////////
/**
* Packs the parent Window that contains this component, if any. This method is called once the target component
* has been made initialized and added to this panel. This method can be overridden by subclasses if additional work
* needs to be done to update the layout.
*/
protected void updateLayout() {
Container tla = getTopLevelAncestor();
if(tla instanceof Window)
((Window)tla).pack();
}
//////////////////////
// Abstract methods //
//////////////////////
public abstract JComponent getTargetComponent();
}