/* * Jajuk * Copyright (C) The Jajuk Team * http://jajuk.info * * This program 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 2 * of the License, or any later version. * * This program 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ package org.jajuk.ui.perspectives; import com.vlsolutions.swing.docking.AutoHideButton; import com.vlsolutions.swing.docking.AutoHideExpandPanel; import com.vlsolutions.swing.docking.DockKey; import com.vlsolutions.swing.docking.Dockable; import com.vlsolutions.swing.docking.DockableResolver; import com.vlsolutions.swing.docking.DockableState; import com.vlsolutions.swing.docking.DockingContext; import com.vlsolutions.swing.docking.DockingDesktop; import com.vlsolutions.swing.docking.event.DockingActionCloseEvent; import com.vlsolutions.swing.docking.event.DockingActionDockableEvent; import com.vlsolutions.swing.docking.event.DockingActionEvent; import com.vlsolutions.swing.docking.event.DockingActionListener; import java.awt.Component; import java.awt.Container; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; import javax.xml.parsers.ParserConfigurationException; import org.jajuk.events.ObservationManager; import org.jajuk.events.Observer; import org.jajuk.services.core.PersistenceService; import org.jajuk.services.core.SessionService; import org.jajuk.ui.views.IView; import org.jajuk.ui.views.ViewAdapter; import org.jajuk.ui.views.ViewFactory; import org.jajuk.util.Const; import org.jajuk.util.Messages; import org.jajuk.util.UtilSystem; import org.jajuk.util.log.Log; import org.xml.sax.SAXException; /** * Perspective adapter, provide default implementation for perspectives. */ @SuppressWarnings("serial") public abstract class PerspectiveAdapter extends DockingDesktop implements IPerspective, Const { /** Perspective id (class). */ private final String sID; private long dateFirstDisplay; private boolean alreadySelected = false; private static final int RESIZE_EVENTS_DISABLING_DELAY_MS = 5000; /** * Constructor. */ public PerspectiveAdapter() { super(); this.sID = getClass().getName(); } @Override public void selected() { if (!alreadySelected) { this.dateFirstDisplay = System.currentTimeMillis(); } alreadySelected = true; } @Override public synchronized void commit() throws IOException { try { // The writeXML method must be called in the EDT to avoid freezing, it // requires a lock some UI components File saveFile = SessionService.getConfFileByPath(PerspectiveAdapter.this.getClass() .getSimpleName() + Const.FILE_XML_EXT); // No need for recovery-enable commit here as the perspective can be restored to default // any time in case of write failure BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(saveFile)); try { writeXML(out); out.flush(); } finally { out.close(); } Log.debug("Perspective " + getID() + " commited"); } catch (Exception e) { Log.error(e); throw new IOException(e); } } @Override public void load() throws IOException, ParserConfigurationException, SAXException { // Try to read XML conf file from home directory File loadFile = SessionService.getConfFileByPath(getClass().getSimpleName() + Const.FILE_XML_EXT); // If file doesn't exist (normally only at first install), read // perspective conf from the jar URL url = loadFile.toURI().toURL(); if (!loadFile.exists()) { url = UtilSystem.getResource(FILE_DEFAULT_PERSPECTIVES_PATH + '/' + getClass().getSimpleName() + Const.FILE_XML_EXT); } BufferedInputStream in = new BufferedInputStream(url.openStream()); // then, load the workspace DockingContext ctx = new DockingContext(); DockableResolver resolver = new DockableResolver() { @Override public Dockable resolveDockable(String keyName) { Dockable view = null; try { StringTokenizer st = new StringTokenizer(keyName, "/"); String className = st.nextToken(); int id = Integer.parseInt(st.nextToken()); view = ViewFactory.createView(Class.forName(className), PerspectiveAdapter.this, id); // save disposition upon resize view.getComponent().addComponentListener(new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { // Avoid persisting the perspective for nothing at first display display. // We disable the resize events during a small period of time to make sure events are done. if (System.currentTimeMillis() - dateFirstDisplay > RESIZE_EVENTS_DISABLING_DELAY_MS) { PersistenceService.getInstance().setPerspectiveChanged(PerspectiveAdapter.this); } } }); } catch (Exception e) { Log.error(e); } return view; } }; addDockableListener(ctx); ctx.setDockableResolver(resolver); setContext(ctx); ctx.addDesktop(this); try { ctx.readXML(in); in.close(); } catch (Exception e) { // error parsing the file, user can't be blocked, use // default conf Log.error(e); Log.debug("Error parsing conf file, use defaults - " + getID()); url = UtilSystem.getResource(FILE_DEFAULT_PERSPECTIVES_PATH + '/' + getClass().getSimpleName() + Const.FILE_XML_EXT); BufferedInputStream defaultConf = new BufferedInputStream(url.openStream()); ctx.readXML(defaultConf); // Delete the corrupted file if (in != null) { in.close(); } loadFile.delete(); } } private void addDockableListener(DockingContext ctx) { // Listen action on the perspective itself, used to track events like drag over // another view of the same size that doesn't throw a view event addDockingActionListener(new DockingActionListener() { @Override public void dockingActionPerformed(DockingActionEvent dockingactionevent) { PersistenceService.getInstance().setPerspectiveChanged(PerspectiveAdapter.this); } @Override public boolean acceptDockingAction(DockingActionEvent arg0) { return true; } }); // Actions on views themselves ctx.addDockingActionListener(new DockingActionListener() { @Override public void dockingActionPerformed(DockingActionEvent dockingactionevent) { // on closing/removing of a view try to unregister it at the // ObservationManager if (dockingactionevent instanceof DockingActionCloseEvent) { Dockable obj = ((DockingActionDockableEvent) dockingactionevent).getDockable(); if (obj instanceof Observer) { ObservationManager.unregister((Observer) obj); } // Seems that the Docking-library does not unregister these things by itself // so we need to do it on our own here as well. We create the Dockable (i.e. // the View) from scratch every time (see constructor of JajukJMenuBar where we create // the menu entries to add new views and ViewFactory) unregisterDockable(obj); // workaround for DockingDesktop-leaks, we need to remove the Dockable from the // "TitleBar" // if it is one of those that are hidden on the left side. removeFromDockingDesktop(PerspectiveAdapter.this, obj); // do some additional cleanup on the View itself if necessary if (obj instanceof ViewAdapter) { ((ViewAdapter) obj).cleanup(); } } PersistenceService.getInstance().setPerspectiveChanged(PerspectiveAdapter.this); } @Override public boolean acceptDockingAction(DockingActionEvent dockingactionevent) { // always accept here return true; } }); } /** * Helper method that performs some additional cleanup for the Dockable. * * @param c The Container to look at, usually the DockingDesktop, i.e. * the PerspectiveAdapter in this case. * @param dockable The Dockable to remove/replace. */ private static void removeFromDockingDesktop(Container c, Dockable dockable) { /** * walk through the list of components and replace the Dockable with * an empty new one whereever necessary to free all references */ for (int i = 0; i < c.getComponentCount(); i++) { Component comp = c.getComponent(i); // on the AutoHideExpandPanel, we need to set a new Dockable on the TitleBar // as it otherwise keeps the Dockable as "target" if (comp instanceof AutoHideExpandPanel) { AutoHideExpandPanel panel = (AutoHideExpandPanel) comp; panel.getTitleBar().setDockable(new Dockable() { @Override public DockKey getDockKey() { return new DockKey(); } @Override public Component getComponent() { return null; } }); } // the AutoHideButton points at the dockable, replace it with a new one here as well if (comp instanceof AutoHideButton) { AutoHideButton button = (AutoHideButton) comp; if (button.getDockable() == dockable) { // set an empty dockable to free up this one... button.init(new Dockable() { @Override public DockKey getDockKey() { return new DockKey(); } @Override public Component getComponent() { return null; } }, button.getZone()); } } // recursively call the Container to also look at it's components if (comp instanceof Container) { removeFromDockingDesktop((Container) comp, dockable); } } } @Override public Container getContentPane() { return this; } @Override public void restoreDefaults() { // SHOULD BE CALLED ONLY FOR THE CURRENT PERSPECTIVE // to ensure views are not invisible try { // Remove current conf file to force using default file from the jar File loadFile = SessionService.getConfFileByPath(getClass().getSimpleName() + Const.FILE_XML_EXT); // lazy deletion, the file can be already removed by a previous reset loadFile.delete(); // Remove all registered dockables DockableState[] ds = getDockables(); for (DockableState element : ds) { close(element.getDockable()); } // force reload load(); // set perspective again to force UI refresh PerspectiveManager.setCurrentPerspective(this); } catch (Exception e) { // display an error message Log.error(e); Messages.showErrorMessage(163); } } @Override public Set<IView> getViews() { Set<IView> views = new HashSet<IView>(); DockableState[] dockables = getDockables(); for (DockableState element : dockables) { views.add((IView) element.getDockable()); } return views; } @Override public String getID() { return sID; } @Override public String toString() { return "Perspective[name=" + getID() + " description='" + getDesc() + "]"; } }