/**
* Copyright (C) 2015 Valkyrie RCP
*
* 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.
*/
package org.valkyriercp.application.docking.editor;
import java.awt.Component;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import com.jidesoft.docking.DockingManager;
import com.jidesoft.document.DocumentComponent;
import com.jidesoft.document.DocumentComponentAdapter;
import com.jidesoft.document.DocumentComponentEvent;
import com.jidesoft.document.DocumentComponentListener;
import com.jidesoft.document.DocumentPane;
import com.jidesoft.document.IDocumentGroup;
import com.jidesoft.document.PopupMenuCustomizer;
import com.jidesoft.swing.JideTabbedPane;
import com.jidesoft.swing.StringConverter;
import org.valkyriercp.application.ApplicationWindow;
import org.valkyriercp.application.PageComponent;
import org.valkyriercp.application.docking.JideApplicationPage;
import org.valkyriercp.application.docking.JideApplicationWindow;
import org.valkyriercp.application.support.AbstractView;
import org.valkyriercp.util.ValkyrieRepository;
/**
* Encapsulates the Jide mechanism for opening editors in their
* so called workspace. Basically adapts the Jide workspace
* concept (ie document pane and DocumentComponent) to the
* Spring RCP architecture.
*
* @author Jonny Wray
*
*/
public class WorkspaceView extends AbstractView {
private static final String PROFILE_KEY = "WorkspaceView";
private Map pageComponentMap = new HashMap();
private MouseListener tabDoubleClickListener = new DoubleClickListener();
private boolean autohideAll = false;
private byte[] fullScreenLayout = null;
private DocumentPane contentPane;
private DropTargetListener dropTargetListener;
private boolean closeAndReopenEditor = true;
private boolean groupsAllowed = true;
private boolean heavyweightComponentEnabled = false;
private boolean doubleClickMaximizeEnabled = true;
private boolean reorderAllowed = true;
private boolean showContextMenu = true;
private int maxGroupCount = 0;
private int tabPlacement = SwingConstants.TOP;
private StringConverter titleConverter = null;
private boolean updateTitle = true;
private DocumentPane.TabbedPaneCustomizer tabbedPaneCustomizer = new DefaultCustomizer();
private PopupMenuCustomizer popupMenuCustomizer = null;
protected WorkspaceView(String id) {
super(id);
}
public void setCloseAndReopenEditor(boolean closeAndReopenEditor){
this.closeAndReopenEditor = closeAndReopenEditor;
}
public void setTabDoubleClickMouseListener(MouseListener tabDoubleClickListener){
this.tabDoubleClickListener = tabDoubleClickListener;
}
/**
* Configures the maximum group count for the document pane. Default is zero which
* implies unlimited.
*
* @param maxGroupCount
*/
public void setMaximumGroupCount(int maxGroupCount){
this.maxGroupCount = maxGroupCount;
}
/**
* Configure the groupsAllowed property of the document pane;
* @param groupsAllowed
*/
public void setGroupsAllowed(boolean groupsAllowed){
this.groupsAllowed = groupsAllowed;
}
/**
* Configure the updateTitle property of the document pane.
*
* @param updateTitle
*/
public void setUpdateTitle(boolean updateTitle){
this.updateTitle = updateTitle;
}
/**
* Configure the title converter of the document pane
* @param titleConverter
*/
public void setTitleConverter(StringConverter titleConverter){
this.titleConverter = titleConverter;
}
/**
* Configures the tab placement of the underlying document pane
*
* @param tabPlacement
*/
public void setTabPlacement(int tabPlacement){
this.tabPlacement = tabPlacement;
}
/**
* Specifies a PopumMenuCustomizer to be used by the document pane.
*
* @param customizer
*/
public void setPopupMenuCustomizer(PopupMenuCustomizer customizer){
this.popupMenuCustomizer = customizer;
}
/**
* Specifies a TabbedPaneCustomizer that allows the appearence of the tabbed pane used
* in the workspace to be customized. Note, the method call setRequestFocusEnabled(true);
* should be made to ensure correct JIDE to Spring RCP event translation. If not
* customizer is specified then this requestFocusEnabled is set to true by default.
*
* @param tabbedPaneCustomizer the specific customizer
*/
public void setTabbedPaneCustomizer(DocumentPane.TabbedPaneCustomizer tabbedPaneCustomizer){
this.tabbedPaneCustomizer = tabbedPaneCustomizer;
}
/**
* Specifies if the show context menu flag is set on the underlying document pane, default if
* not set is true.
*
* @param showContextMenu
*/
public void setShowContextMenu(boolean showContextMenu){
this.showContextMenu = showContextMenu;
}
/**
* Specifies if reordering is allowed in the underlying document pane. If not
* set then default is true.
*
* @param reorderAllowed
*/
public void setReorderAllowed(boolean reorderAllowed){
this.reorderAllowed = reorderAllowed;
}
/**
* Specified whether the documents should maximize on double click, and
* minimize again on next double click. Default is true.
*
* @param doubleClickMaximizedEnabled maximize on double click. Default is true.
*/
public void setDoubleClickMaximizeEnabled(boolean doubleClickMaximizedEnabled){
this.doubleClickMaximizeEnabled = doubleClickMaximizedEnabled;
}
/**
* Specifies if heavyweight components are enabled for the workspace. Default is
* false.
*
* @param heavyweightComponentEnabled enable heavyweight components.
*/
public void setHeavyweightComponentEnabled(boolean heavyweightComponentEnabled){
this.heavyweightComponentEnabled = heavyweightComponentEnabled;
}
public void setDropTargetListener(DropTargetListener dropTargetListener){
this.dropTargetListener = dropTargetListener;
}
/**
* Overridden close method to avoid memory leaks by Mikael Valot
*/
public void dispose(){
Collection pageComponents = new ArrayList(pageComponentMap.values());
Iterator it = pageComponents.iterator();
while(it.hasNext()){
PageComponent pageComponent = (PageComponent)it.next();
remove(pageComponent);
}
contentPane.dispose();
contentPane = null;
super.dispose();
}
/**
* Returns a map of document names to the page component, which will
* be concrete editors.
* @return
*/
public Map getPageComponentMap(){
return Collections.unmodifiableMap(pageComponentMap);
}
public PageComponent getActiveComponent(){
String documentName = contentPane.getActiveDocumentName();
PageComponent component = (PageComponent)pageComponentMap.get(documentName);
return component;
}
/**
* This is a bit of a hack to get over a limitation in the JIDE
* docking framework. When focus is regained to the workspace by
* activation the currently activated document the
* documentComponentActivated is not fired. This needs to be fired
* when we know the workspace has become active. For some reason
* it dosen't work when using the componentFocusGained method
*/
public void fireFocusGainedOnActiveComponent(){
String documentName = contentPane.getActiveDocumentName();
if(documentName != null){
PageComponent component = (PageComponent)pageComponentMap.get(documentName);
JideApplicationPage page = (JideApplicationPage)getActiveWindow().getPage();
page.fireFocusGained(component);
}
}
private ApplicationWindow getActiveWindow() {
return ValkyrieRepository.getInstance().getApplicationConfig().windowManager().getActiveWindow();
}
private void fireFocusGainedOnWorkspace(){
JideApplicationPage page = (JideApplicationPage)getActiveWindow().getPage();
page.fireFocusGained(this);
}
private void registerDropTargetListeners(Component component){
if(dropTargetListener != null){
new DropTarget(component, dropTargetListener);
}
}
protected JComponent createControl() {
if(contentPane == null){
contentPane = constructDocumentPane();
registerDropTargetListeners(contentPane);
contentPane.getLayoutPersistence().setProfileKey(PROFILE_KEY);
}
return contentPane;
}
private DocumentPane constructDocumentPane(){
DocumentPane documentPane;
if(doubleClickMaximizeEnabled){
documentPane = new DocumentPane(){
// add function to maximize (autohideAll) the document pane when mouse
// double clicks on the tabs of DocumentPane. This comes from the JIDE
// demos
protected IDocumentGroup createDocumentGroup() {
IDocumentGroup group = super.createDocumentGroup();
if (group instanceof JideTabbedPane) {
JideTabbedPane tabbedPane = (JideTabbedPane)group;
tabbedPane.addMouseListener(tabDoubleClickListener);
//((JideTabbedPaneUI) ((JideTabbedPane) group).getUI()).getTabPanel().
// addMouseListener(tabDoubleClickListener);
}
return group;
}
};
}
else{
documentPane = new DocumentPane();
}
documentPane.setHeavyweightComponentEnabled(heavyweightComponentEnabled);
documentPane.setTabbedPaneCustomizer(tabbedPaneCustomizer);
documentPane.setReorderAllowed(reorderAllowed);
documentPane.setShowContextMenu(showContextMenu);
documentPane.setTabPlacement(tabPlacement);
documentPane.setUpdateTitle(updateTitle);
documentPane.setGroupsAllowed(groupsAllowed);
documentPane.setMaximumGroupCount(maxGroupCount);
if(titleConverter != null){
documentPane.setTitleConverter(titleConverter);
}
if(popupMenuCustomizer != null){
documentPane.setPopupMenuCustomizer(popupMenuCustomizer);
}
return documentPane;
}
/**
* By default constructs an EditorLifecycleListener, override to provide an application
* specific document component listener factory method.
*
* @param pageComponent
* @return
*/
protected DocumentComponentListener constructLifecycleListener(PageComponent pageComponent){
return new EditorLifecycleListener(this, pageComponent);
}
/**
* Calls addDocumentComponent with activateAfterOpen as true
*
*/
public void addDocumentComponent(final PageComponent pageComponent){
addDocumentComponent(pageComponent, true);
}
/**
* Adds a document to the editor workspace. The behaviour when an
* editor is already open, with editor identity defined by id property,
* is determined by the closeAndReopenEditor property. If this property
* is true, the default, the editor is closed and reopened. If false, the
* existing editor becomes the active one.
*
* @param pageComponent The page component to be added as a document
* @param activateAfterOpen specifies if the component should be activated after opening
*/
public void addDocumentComponent(final PageComponent pageComponent, boolean activateAfterOpen){
String id = pageComponent.getId();
if(!closeAndReopenEditor && contentPane.getDocument(id) != null){
contentPane.setActiveDocument(id);
}
else{
if(contentPane.getDocument(id) != null){
contentPane.closeDocument(id);
}
DocumentComponent document = constructDocumentComponent(pageComponent);
DocumentComponentListener lifecycleListener = constructLifecycleListener(pageComponent);
document.addDocumentComponentListener(lifecycleListener);
if(contentPane.getDocument(id) == null){
contentPane.openDocument(document);
}
if(activateAfterOpen){
contentPane.setActiveDocument(id);
}
registerDropTargetListeners(pageComponent.getControl());
// This listener ensures that the focus is transfered to the workspace
// itself if the number of documents becomes zero.
document.addDocumentComponentListener(new DocumentComponentAdapter(){
public void documentComponentClosed(DocumentComponentEvent event) {
int count = contentPane.getDocumentCount();
if(count == 0){
fireFocusGainedOnWorkspace();
}
}
});
pageComponentMap.put(id, pageComponent);
}
}
public void remove(PageComponent pageComponent){
contentPane.closeDocument(pageComponent.getId());
pageComponentMap.remove(pageComponent.getId());
pageComponent.dispose();
}
/*
* Actually constructs a Jide document component from the page component
*/
private DocumentComponent constructDocumentComponent(final PageComponent pageComponent) {
String id = pageComponent.getId();
String title = pageComponent.getDisplayName();
Icon icon = pageComponent.getIcon();
DocumentComponent document = new DocumentComponent(
pageComponent.getContext().getPane().getControl(),
id, title, icon);
document.setTooltip(pageComponent.getDescription());
return document;
}
private class DoubleClickListener extends MouseAdapter{
public void mouseClicked(MouseEvent e) {
DockingManager manager = ((JideApplicationWindow)getActiveWindow()).getDockingManager();
if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2) {
if (!autohideAll) {
fullScreenLayout = manager.getLayoutRawData();
manager.autohideAll();
autohideAll = true;
}
else {
// call next two methods so that the farme bounds and state will not change.
manager.setUseFrameBounds(false);
manager.setUseFrameState(false);
if (fullScreenLayout != null) {
manager.setLayoutRawData(fullScreenLayout);
}
autohideAll = false;
}
}
}
}
private static class DefaultCustomizer implements DocumentPane.TabbedPaneCustomizer{
// This is needed for correct JIDE to Spring RCP focus/activation
// event translation
public void customize(JideTabbedPane tabbedPane) {
tabbedPane.setRequestFocusEnabled(true);
}
}
}