blob: 7e4f9e69adb293d0375c2a63208b9266a4e42681 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.core.multiview;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OptionalDataException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.core.api.multiview.MultiViewPerspective;
import org.netbeans.core.multiview.MultiViewModel.ActionRequestObserverFactory;
import org.netbeans.core.multiview.MultiViewModel.ElementSelectionListener;
import org.netbeans.core.spi.multiview.CloseOperationHandler;
import org.netbeans.core.spi.multiview.CloseOperationState;
import org.netbeans.core.spi.multiview.MultiViewDescription;
import org.netbeans.core.spi.multiview.MultiViewElement;
import org.openide.awt.UndoRedo;
import org.openide.text.CloneableEditorSupport.Pane;
import org.openide.util.HelpCtx;
import org.openide.util.Lookup;
import org.openide.util.NbPreferences;
import org.openide.util.WeakListeners;
import org.openide.windows.CloneableTopComponent;
import org.openide.windows.TopComponent;
/** Special subclass of TopComponent which shows and handles set of
* MultiViewElements, shows them in switchable toggle buttons style, along
* with toolbars of actions associated with individual view elements.
*
*
* @author Dafe Simonek, Milos Kleint
*/
public final class MultiViewPeer implements PropertyChangeListener {
static final String MULTIVIEW_ID = "MultiView-"; //NOI18N
private static final String TOOLBAR_VISIBLE_PROP = /* org.netbeans.api.editor.settings.SimpleValueNames.TOOLBAR_VISIBLE_PROP */ "toolbarVisible"; // NOI18N
private static final Preferences editorSettingsPreferences;
static {
Preferences p = MimeLookup.getLookup(MimePath.EMPTY).lookup(Preferences.class);
String n;
if (p == null && (n = System.getProperty("test.multiview.toolbar.settings")) != null) { // NOI18N
p = NbPreferences.root().node(n);
}
editorSettingsPreferences = p;
}
private Lookup.Provider context;
private String mimeType;
MultiViewModel model;
TabsComponent tabs;
SelectionListener selListener;
CloseOperationHandler closeHandler;
transient MultiViewTopComponentLookup lookup;
TopComponent peer;
private ActionRequestObserverFactory factory;
private MultiViewActionMap delegatingMap;
private boolean activated = false;
private final PreferenceChangeListener editorSettingsListener = new PreferenceChangeListenerImpl();
private final PropertyChangeListener propListener;
private DelegateUndoRedo delegateUndoRedo;
private int splitOrientation = -1;
private int initialSplitOrientation = -1;
private MultiViewDescription initialSplitDescription;
MultiViewPeer(TopComponent pr, ActionRequestObserverFactory fact) {
selListener = new SelectionListener();
peer = pr;
factory = fact;
delegateUndoRedo = new DelegateUndoRedo();
propListener = WeakListeners.propertyChange(this, null);
}
void copyMimeContext(MultiViewPeer other) {
this.context = other.context;
this.mimeType = other.mimeType;
}
/** @param context context needs to be also serializable */
public void setMimeLookup(String mimeType, Lookup.Provider context) {
this.context = context;
this.mimeType = mimeType;
List<MultiViewDescription> arr = new ArrayList<MultiViewDescription>();
final Lookup lkp = MimeLookup.getLookup(mimeType);
for (ContextAwareDescription d : lkp.lookupAll(ContextAwareDescription.class)) {
d = d.createContextAwareDescription(context.getLookup(), false);
arr.add(d);
d = d.createContextAwareDescription(context.getLookup(), true);
arr.add(d);
}
if (arr.isEmpty()) {
arr.add(new EmptyViewDescription(mimeType));
}
if (model != null) {
model.removeElementSelectionListener(selListener);
}
model = new MultiViewModel(arr.toArray(new MultiViewDescription[0]), arr.get(0), factory);
model.addElementSelectionListener(selListener);
tabs.setModel(model);
CloseOperationHandler h = lkp.lookup(CloseOperationHandler.class);
if (h == null) {
h = SpiAccessor.DEFAULT.createDefaultCloseHandler();
}
closeHandler = h;
}
public void setMultiViewDescriptions(MultiViewDescription[] descriptions, MultiViewDescription defaultDesc) {
assert context == null;
_setMultiViewDescriptions(descriptions, defaultDesc);
}
private void _setMultiViewDescriptions(MultiViewDescription[] descriptions, MultiViewDescription defaultDesc) {
Map<MultiViewDescription, MultiViewElement> createdElements = Collections.emptyMap();
if (model != null) {
model.removeElementSelectionListener(selListener);
createdElements = model.getCreatedElementsMap();
}
model = new MultiViewModel(descriptions, defaultDesc, factory, createdElements);
model.addElementSelectionListener(selListener);
tabs.setModel(model);
}
public void setCloseOperationHandler(CloseOperationHandler handler) {
assert context == null;
closeHandler = handler;
}
void setDeserializedMultiViewDescriptions(int splitOrientation, MultiViewDescription[] descriptions,
MultiViewDescription defaultDesc, MultiViewDescription defaultDescSplit, Map<MultiViewDescription, MultiViewElement> existingElements) {
if (model != null) {
model.removeElementSelectionListener(selListener);
}
// if Design view was active before closing, set default to Source view
if( splitOrientation != -1 )
defaultDescSplit = defaultDescSplit.getDisplayName().startsWith("&Design") ? descriptions[1] : defaultDescSplit; //NOI18N
model = new MultiViewModel(descriptions, defaultDesc, factory, existingElements);
model.addElementSelectionListener(selListener);
tabs.setModel(model);
this.initialSplitOrientation = splitOrientation;
this.initialSplitDescription = defaultDescSplit;
}
/**
* for use in tests only!!!!!
*/
MultiViewModel getModel() {
return model;
}
void initComponents() {
initActionMap();
peer.setLayout(new BorderLayout());
tabs = new TabsComponent(isToolbarVisible());
peer.add(tabs);
ActionMap map = peer.getActionMap();
Action act = new AccessTogglesAction();
map.put("NextViewAction", new GetRightEditorAction()); //NOI18N
map.put("PreviousViewAction", new GetLeftEditorAction()); //NOI18N
map.put("accesstoggles", act); //NOI18N
InputMap input = peer.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke stroke = KeyStroke.getKeyStroke("control F10"); //NOI18N
input.put(stroke, "accesstoggles"); //NOI18N
// stroke = (KeyStroke)new GetLeftEditorAction().getValue(Action.ACCELERATOR_KEY);
// input.put(stroke, "getLeftEditor");
input = peer.getInputMap(JComponent.WHEN_FOCUSED);
input.put(stroke, "accesstoggles"); //NOI18N
peer.putClientProperty("MultiViewBorderHack.topOffset", new Integer(tabs.getPreferredSize().height - 1));
}
private void assignLookup(MultiViewElement el, MultiViewTopComponentLookup lkp) {
Lookup elementLookup = el.getLookup();
assert null != elementLookup : "Null lookup from " + el;
lkp.setElementLookup(elementLookup);
}
private void assignLookup(MultiViewElement el) {
assignLookup(el, (MultiViewTopComponentLookup)peer.getLookup());
}
final void assignLookup(MultiViewTopComponentLookup lkp) {
if (lkp.isInitialized()) {
return;
}
final MultiViewElement el = getModel().getActiveElement();
if (el != null) {
assignLookup(el, lkp);
}
}
// It is necessary so the old actions (clone and close from org.openide.actions package) remain working.
// cannot use the
private void initActionMap() {
delegatingMap = new MultiViewActionMap(peer, new ActionMap ());
if(peer instanceof TopComponent.Cloneable) {
delegatingMap.put("cloneWindow", new javax.swing.AbstractAction() { // NOI18N
public void actionPerformed(ActionEvent evt) {
TopComponent cloned = ((TopComponent.Cloneable)
peer).cloneComponent();
cloned.open();
cloned.requestActive();
}
});
}
if(peer instanceof MultiViewTopComponent || peer instanceof MultiViewCloneableTopComponent) {
delegatingMap.put("splitWindowHorizantally", new javax.swing.AbstractAction() { // NOI18N
@Override
public void actionPerformed(ActionEvent evt) {
TopComponent split;
if(peer instanceof Splitable) {
split = ((Splitable) peer).splitComponent(JSplitPane.HORIZONTAL_SPLIT, -1);
split.open();
split.requestActive();
}
}
});
delegatingMap.put("splitWindowVertically", new javax.swing.AbstractAction() { // NOI18N
@Override
public void actionPerformed(ActionEvent evt) {
TopComponent split;
if(peer instanceof Splitable) {
split = ((Splitable) peer).splitComponent(JSplitPane.VERTICAL_SPLIT, -1);
split.open();
split.requestActive();
}
}
});
delegatingMap.put("clearSplit", new javax.swing.AbstractAction() { // NOI18N
@Override
public void actionPerformed(ActionEvent evt) {
TopComponent original;
if(peer instanceof Splitable) {
original = ((Splitable) peer).clearSplit(-1);
original.open();
original.requestActive();
}
}
});
}
delegatingMap.put("closeWindow", new javax.swing.AbstractAction() { // NOI18N
public void actionPerformed(ActionEvent evt) {
peer.close();
}
});
peer.setActionMap(delegatingMap);
}
void peerComponentClosed() {
Iterator<MultiViewElement> it = model.getCreatedElements().iterator();
while (it.hasNext()) {
MultiViewElement el = it.next();
model.markAsHidden(el);
el.componentClosed();
}
tabs.peerComponentClosed();
}
void peerComponentShowing() {
MultiViewElement el = model.getActiveElement();
el.componentShowing();
delegatingMap.setDelegateMap(el.getVisualRepresentation().getActionMap());
assignLookup(el);
JComponent jc = el.getToolbarRepresentation();
assert jc != null : "MultiViewElement " + el.getClass() + " returns null as toolbar component."; //NOI18N
jc.setOpaque(false);
boolean isSplitDescription = false;
MultiViewDescription desc = model.getActiveDescription();
if(desc instanceof ContextAwareDescription) {
isSplitDescription = ((ContextAwareDescription)desc).isSplitDescription();
}
tabs.setInnerToolBar(jc, isSplitDescription);
tabs.setToolbarBarVisible(isToolbarVisible());
if (editorSettingsPreferences != null) {
editorSettingsPreferences.addPreferenceChangeListener(editorSettingsListener);
}
if( initialSplitOrientation != -1 ) {
splitOrientation = initialSplitOrientation;
tabs.peerSplitComponent(splitOrientation, MultiViewPeer.this, getModel().getActiveDescription(), initialSplitDescription, -1);
initialSplitDescription = null;
initialSplitOrientation = -1;
}
}
void peerComponentHidden() {
model.getActiveElement().componentHidden();
if (editorSettingsPreferences != null) {
editorSettingsPreferences.removePreferenceChangeListener(editorSettingsListener);
}
}
void peerComponentDeactivated() {
activated = false;
model.getActiveElement().componentDeactivated();
}
boolean isActivated() {
return activated;
}
void peerComponentActivated() {
activated = true;
model.getActiveElement().componentActivated();
}
void peerComponentOpened() {
showCurrentElement(true);
tabs.setToolbarBarVisible(isToolbarVisible());
addPropertyChangeListeners();
}
private void addPropertyChangeListeners() {
if( null != model ) {
for (MultiViewDescription mvd : model.getDescriptions()) {
if( mvd instanceof ContextAwareDescription && ((ContextAwareDescription)mvd).isSplitDescription() )
continue; //#240371 - don't update name from spit elements
MultiViewElement el = model.getElementForDescription( mvd, false );
if (el == null) {
continue;
}
if (el.getVisualRepresentation() instanceof Pane) {
Pane pane = (Pane)el.getVisualRepresentation();
final CloneableTopComponent tc = pane.getComponent();
if (!Arrays.asList(tc.getPropertyChangeListeners()).contains(propListener)) {
tc.addPropertyChangeListener(propListener);
}
}
}
}
}
void peerSplitComponent(int orientation, int splitPosition) {
splitOrientation = orientation;
tabs.peerSplitComponent(orientation, this, null, null, splitPosition);
}
void peerClearSplit( int elementToActivate ) {
tabs.peerClearSplit( elementToActivate );
showCurrentElement();
model.fireActivateCurrent();
splitOrientation = -1;
}
int getSplitOrientation() {
return splitOrientation;
}
boolean requestFocusInWindow() {
// somehow this may be called when model is null
if (model == null) {
return false;
}
return model.getActiveElement().getVisualRepresentation().requestFocusInWindow();
}
void requestFocus() {
// somehow this may be called when model is null
if (model != null) {
model.getActiveElement().getVisualRepresentation().requestFocus();
}
}
/**
* hides the old element when switching elements.
*/
void hideElement(MultiViewDescription desc) {
if (desc != null && splitOrientation != -1) {
MultiViewDescription topDesc = tabs.getTopComponentDescription();
MultiViewDescription bottomDesc = tabs.getBottomComponentDescription();
if (tabs.isHiddenTriggeredByMultiViewButton()
&& (!topDesc.getDisplayName().equals(desc.getDisplayName())
|| !bottomDesc.getDisplayName().equals(desc.getDisplayName()))) {
MultiViewElement el = model.getElementForDescription(desc);
el.componentHidden();
}
return;
}
if (desc != null) {
MultiViewElement el = model.getElementForDescription(desc);
el.componentHidden();
}
}
void showCurrentElement() {
showCurrentElement(false);
}
/**
* shows the new element after switching elements.
*/
private void showCurrentElement(boolean calledFromComponentOpened) {
MultiViewElement el = model.getActiveElement();
MultiViewDescription desc = model.getActiveDescription();
// TODO display name is not a good unique id..
// also consider a usecase where multiple elements point to a single visual component.
//. eg. property sheet uses same component and only changes model.
// in this case we probably should not remove and add the component from awt hierarchy
boolean isSplitDescription = false;
if(desc instanceof ContextAwareDescription) {
isSplitDescription = ((ContextAwareDescription)desc).isSplitDescription();
}
tabs.switchToCard(el, desc.getDisplayName(), isSplitDescription);
if( null == peer.getIcon() ) {
Image icon = desc.getIcon();
if( null == icon ) {
//#204072
MultiViewDescription[] descriptions = model.getDescriptions();
if( null != descriptions && descriptions.length > 0 )
icon = descriptions[0].getIcon();
}
peer.setIcon(icon);
}
// the first time the component is shown, we need to call componentOpened() on it to be in synch with current
// TopComponent behaviour?
if (peer.isOpened() || calledFromComponentOpened) {
if (!model.wasShownBefore(el)) {
el.componentOpened();
model.markAsShown(el);
}
}
if (!calledFromComponentOpened) {
//#68199
// replace isOpened() with isVisible() because some multiview don't have to be directly referenced form the
// winsys codebase
if (peer.isVisible()) {
el.componentShowing();
}
// should we really set the stuff only when not called from componentOpened()? maybe it's better to call it twice sometimes.
// if we don't call it here for opened but not showing component, then the map, lookup and nodes will not be initialized properly.
// is it a problem?
delegatingMap.setDelegateMap(el.getVisualRepresentation().getActionMap());
assignLookup(el);
if (peer.isVisible()) {
tabs.setInnerToolBar(el.getToolbarRepresentation(), isSplitDescription);
tabs.setToolbarBarVisible(isToolbarVisible());
}
}
}
/**
* merge action for the topcomponent and the enclosed MultiViewElement..
*
*/
Action[] peerGetActions(Action[] superActs) {
//TEMP don't delegate to element's actions..
Action[] acts = model.getActiveElement().getActions();
// copy super actions as we will possibly null it in cycle later
Action[] superActions = new Action[superActs.length];
System.arraycopy(superActs, 0, superActions, 0, superActs.length);
for (int i = 0; i < acts.length; i++) {
Action act = acts[i];
for (int j = 0 ; j < superActions.length; j++) {
Action superact = superActions[j];
if (act == null && superact == null){ // just to same some time.
break;
}
if (superact != null && act != null && superact.getClass().equals(act.getClass())) {
// these are the default topcomponent actions.. we need to replace them
// in order to have the correct context.
acts[i] = superActions[j];
// to keep superact.getClass().equals(act.getClass()) from filtering out
// different instances of the same action, null out the superActions
// array as you go.
superActions[j] = null;
break;
}
}
}
return acts;
}
MultiViewHandlerDelegate getMultiViewHandlerDelegate() {
// TODO have one handler only or create a new one each time?
return new MVTCHandler();
}
/**
* Delegates the value to the element descriptions.
*/
int getPersistenceType() {
// should also take the opened/created elements into account.
// no need to serialize the tc when the element that want to be serialized, was not
// even opened?!? but maybe handle this during the serialization proceess, avoid creating
// the element when serializing.
int type = TopComponent.PERSISTENCE_NEVER;
if( null != model ) {
MultiViewDescription[] descs = model.getDescriptions();
for (int i = 0; i < descs.length; i++) {
if (context == null && !(descs[i] instanceof Serializable)) {
Logger.getLogger(MultiViewTopComponent.class.getName()).warning(
"The MultiviewDescription instance " + descs[i].getClass() + " is not serializable. Cannot persist TopComponent.");
type = TopComponent.PERSISTENCE_NEVER;
break;
}
if (descs[i].getPersistenceType() == TopComponent.PERSISTENCE_ALWAYS) {
type = descs[i].getPersistenceType();
// cannot ge any better than that.
}
if (descs[i].getPersistenceType() == TopComponent.PERSISTENCE_ONLY_OPENED &&
type != TopComponent.PERSISTENCE_ALWAYS) {
type = descs[i].getPersistenceType();
// go on searching..
}
}
}
return type;
}
String preferredID() {
StringBuffer retValue = new StringBuffer(MULTIVIEW_ID);
assert model != null : "Multiview Model not set, error on deserialization of client code. " + //NOI18N
"Please add comment to issue #121119 at netbeans.org and attach the ~/.netbeans/var/log/messages.log file"; //NOI18N
MultiViewDescription[] descs = model.getDescriptions();
for (int i = 0; i < descs.length; i++) {
retValue.append(descs[i].preferredID());
retValue.append("|"); //NOI18N
}
return retValue.toString();
}
/** Serialize this top component.
* Subclasses wishing to store state must call the super method, then write to the stream.
* @param out the stream to serialize to
*/
void peerWriteExternal (ObjectOutput out) throws IOException {
boolean fromMime;
if (context != null) {
out.writeObject(mimeType);
out.writeObject(context);
fromMime = true;
} else {
if (closeHandler != null) {
if (closeHandler instanceof Serializable) {
out.writeObject(closeHandler);
} else {
//TODO some warning to the SPI programmer
Logger.getAnonymousLogger().info(
"The CloseOperationHandler isn not serializable. MultiView component id=" + preferredID());
}
}
fromMime = false;
}
MultiViewDescription[] descs = model.getDescriptions();
MultiViewDescription curr = tabs.getTopComponentDescription();
MultiViewDescription currSplit = tabs.getBottomComponentDescription();
int currIndex = 0;
int currIndexSplit = 0;
for (int i = 0; i < descs.length; i++) {
if( descs[i] instanceof RuntimeMultiViewDescription ) {
continue; //don't store multiview elements added at runtime
}
if (!fromMime) {
out.writeObject(descs[i]);
} else {
out.writeObject(descs[i].preferredID());
}
if (descs[i].getPersistenceType() != TopComponent.PERSISTENCE_NEVER) {
// only those requeTopsted and previously created elements are serialized.
MultiViewElement elem = model.getElementForDescription(descs[i], false);
if (elem != null && elem instanceof Serializable) {
out.writeObject(elem);
}
}
if (descs[i] == curr) {
currIndex = i;
}
if (descs[i] == currSplit) {
currIndexSplit = i;
}
}
out.writeObject(new Integer(currIndex));
out.writeObject(new Integer(currIndexSplit));
out.writeObject(new Integer(splitOrientation));
String htmlDisplayName = peer.getHtmlDisplayName();
if( null != htmlDisplayName )
out.writeObject(htmlDisplayName);
}
/** Deserialize this top component.
* Subclasses wishing to store state must call the super method, then read from the stream.
* @param in the stream to deserialize from
*/
void peerReadExternal (ObjectInput in) throws IOException, ClassNotFoundException {
ArrayList<MultiViewDescription> descList = new ArrayList<MultiViewDescription>();
HashMap<MultiViewDescription, MultiViewElement> map = new HashMap<MultiViewDescription, MultiViewElement>();
int current = 0;
int currentSplit = 0;
int splitOrient = -1;
CloseOperationHandler close = null;
try {
int counting = 0;
int intCounting = 0;
MultiViewDescription lastDescription = null;
while (true) {
Object obj = in.readObject();
if ((obj instanceof String) && counting++ == 0) {
Lookup.Provider lp = (Lookup.Provider)in.readObject();
setMimeLookup((String)obj, lp);
descList.addAll(Arrays.asList(model.getDescriptions()));
continue;
}
if (obj instanceof MultiViewDescription) {
lastDescription = (MultiViewDescription)obj;
descList.add(lastDescription);
}
else if (obj instanceof String) {
boolean match = false;
for (MultiViewDescription md : descList) {
if (md.preferredID().equals(obj)) {
lastDescription = md;
match = true;
break;
}
}
if( !match ) {
throw new IOException( "Cannot find multiview description for id \"" + obj
+ "\". Maybe some module(s) is not installed or activated." );
}
}
else if (obj instanceof MultiViewElement) {
assert lastDescription != null;
map.put(lastDescription, (MultiViewElement)obj);
lastDescription = null;
}
else if (obj instanceof Integer) {
Integer integ = (Integer)obj;
if(intCounting == 0) {
intCounting++;
current = integ.intValue();
} else if (intCounting == 1) {
intCounting++;
currentSplit = integ.intValue();
} else if (intCounting == 2) {
splitOrient = integ.intValue();
break;
}
}
if (obj instanceof CloseOperationHandler) {
close = (CloseOperationHandler)obj;
}
}
try {
Object htmlDisplayName = in.readObject();
if( htmlDisplayName instanceof String ) {
peer.setHtmlDisplayName( (String)htmlDisplayName );
}
} catch( OptionalDataException odE ) {
if( odE.eof ) {
//end of file, HTML description field is not present
} else {
throw odE;
}
}
} catch (IOException exc) {
//#121119 try preventing model corruption when deserialization of client code fails.
if (context == null) {
if (close == null) {
//TODO some warning to the SPI programmer
close = SpiAccessor.DEFAULT.createDefaultCloseHandler();
}
setCloseOperationHandler(close);
}
if (descList.size() > 0) {
MultiViewDescription[] descs = new MultiViewDescription[descList.size()];
descs = descList.toArray(descs);
//the integer with current element was not read yet, fallback to zero.
MultiViewDescription currDesc = descs[0];
MultiViewDescription currDescSplit = descs[1];
//when error, ignore any deserialized elements..
map.clear();
setDeserializedMultiViewDescriptions(1, descs, currDesc, currDescSplit, map);
}
throw exc;
}
if (context == null) {
if (close == null) {
//TODO some warning to the SPI programmer
close = SpiAccessor.DEFAULT.createDefaultCloseHandler();
}
setCloseOperationHandler(close);
}
// now that we've read everything, we should set it correctly.
MultiViewDescription[] descs = new MultiViewDescription[descList.size()];
descs = descList.toArray(descs);
MultiViewDescription currDesc = descs[current];
MultiViewDescription currDescSplit = descs[currentSplit];
setDeserializedMultiViewDescriptions(splitOrient, descs, currDesc, currDescSplit, map);
}
private Action[] getDefaultTCActions() {
//TODO for each suppoerted peer have one entry..
if (peer instanceof MultiViewTopComponent) {
return ((MultiViewTopComponent)peer).getDefaultTCActions();
}
return new Action[0];
}
JEditorPane getEditorPane() {
if (model != null) {
MultiViewElement el = model.getActiveElement();
if (el != null && el.getVisualRepresentation() instanceof Pane) {
Pane pane = (Pane)el.getVisualRepresentation();
return pane.getEditorPane();
}
}
return null;
}
HelpCtx getHelpCtx() {
return model.getActiveDescription().getHelpCtx();
}
/**
* Get the undo/redo support for this component.
* The default implementation returns a dummy support that cannot
* undo anything.
*
* @return undoable edit for this component
*/
UndoRedo peerGetUndoRedo() {
return delegateUndoRedo;
}
private UndoRedo privateGetUndoRedo() {
return model.getActiveElement().getUndoRedo() != null ? model.getActiveElement().getUndoRedo() : UndoRedo.NONE;
}
/**
* This method is called when this <code>TopComponent</code> is about to close.
* Delegates to CloseOperationHandler.
*/
boolean canClose() {
Collection<MultiViewElement> col = model.getCreatedElements();
Iterator<MultiViewElement> it = col.iterator();
Collection<CloseOperationState> badOnes = new ArrayList<>();
while (it.hasNext()) {
MultiViewElement el = it.next();
CloseOperationState state = el.canCloseElement();
if (!state.canClose()) {
badOnes.add(state);
}
}
if (badOnes.size() > 0) {
CloseOperationState[] states = new CloseOperationState[badOnes.size()];
states = badOnes.toArray(states);
boolean res = closeHandler.resolveCloseOperation(states);
if( res && SpiAccessor.DEFAULT.shouldCheckCanCloseAgain(closeHandler) ) {
//#236369 - check if everything saved ok
col = model.getCreatedElements();
it = col.iterator();
while (it.hasNext()) {
MultiViewElement el = it.next();
CloseOperationState state = el.canCloseElement();
if (!state.canClose()) {
res = false;
break;
}
}
}
return res;
}
return true;
}
// from CloneableEditor.Pane
public void updateName() {
// is called before setMultiViewDescriptions() need to check for null.
if (model != null) {
for (MultiViewDescription mvd : model.getDescriptions()) {
if( mvd instanceof ContextAwareDescription && ((ContextAwareDescription)mvd).isSplitDescription() )
continue; //#240371 - don't update name from spit elements
MultiViewElement el = model.getElementForDescription(
mvd, MultiViewCloneableTopComponent.isSourceView(mvd)
);
if (el == null) {
continue;
}
if (el.getVisualRepresentation() instanceof Pane) {
Pane pane = (Pane)el.getVisualRepresentation();
pane.updateName();
final CloneableTopComponent tc = pane.getComponent();
peer.setDisplayName(tc.getDisplayName());
peer.setIcon(tc.getIcon());
if (!Arrays.asList(tc.getPropertyChangeListeners()).contains(propListener)) {
tc.addPropertyChangeListener(propListener);
}
}
}
}
}
public Lookup getLookup() {
if (lookup == null) {
lookup = new MultiViewTopComponentLookup(delegatingMap);
}
return lookup;
}
private boolean isToolbarVisible() {
return editorSettingsPreferences == null || editorSettingsPreferences.getBoolean(TOOLBAR_VISIBLE_PROP, true);
}
@Override
public String toString() {
return "[model=" + model + "]"; // NOI18N
}
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (
"icon".equals(evt.getPropertyName()) || // NOI18N
"name".equals(evt.getPropertyName()) || // NOI18N
"displayName".equals(evt.getPropertyName()) || // NOI18N
"htmlDisplayName".equals(evt.getPropertyName()) // NOI18N
) {
updateName();
}
}
/**
* notification from the model that the selection changed.
*/
private class SelectionListener implements ElementSelectionListener {
public void selectionChanged(MultiViewDescription oldOne, MultiViewDescription newOne) {
if (isActivated()) {
MultiViewElement el = model.getElementForDescription(oldOne);
el.componentDeactivated();
}
hideElement(oldOne);
showCurrentElement();
delegateUndoRedo.updateListeners(model.getElementForDescription(oldOne),
model.getElementForDescription(newOne));
}
public void selectionActivatedByButton() {
MultiViewElement elem = model.getActiveElement();
elem.getVisualRepresentation().requestFocus();
elem.componentActivated();
}
}
private class MVTCHandler implements MultiViewHandlerDelegate {
private MultiViewPerspective[] perspectives = null;
public MultiViewPerspective[] getDescriptions() {
return model.getPerspectives();
}
public MultiViewPerspective getSelectedDescription() {
return model.getSelectedPerspective();
}
public void requestActive(MultiViewPerspective pers) {
MultiViewDescription desc = Accessor.DEFAULT.extractDescription(pers);
if (model.getActiveDescription() != desc) {
tabs.changeActiveManually(desc);
model.getActiveElement().componentActivated();
}
}
public void requestVisible(MultiViewPerspective pers) {
MultiViewDescription desc = Accessor.DEFAULT.extractDescription(pers);
tabs.changeVisibleManually(desc);
}
// public MultiViewPerspectiveComponent getElementForDescription(MultiViewPerspective pers) {
// MultiViewDescription desc = Accessor.DEFAULT.extractDescription(pers);
// return model.getMVComponentForDescription(desc);
// }
@Override
public void addMultiViewDescription(MultiViewDescription descr, int position) {
if( -1 != splitOrientation )
peerClearSplit(0);
MultiViewDescription[] oldDesc = model.getDescriptions();
if( position < 0 || position >= oldDesc.length/2 )
position = oldDesc.length/2;
RuntimeMultiViewDescription wrapper = new RuntimeMultiViewDescription(descr, false);
RuntimeMultiViewDescription splitWrapper = new RuntimeMultiViewDescription(descr, true);
MultiViewDescription[] newDesc = new MultiViewDescription[oldDesc.length+2];
int index = 0;
for( int i=0; i<newDesc.length/2; i++ ) {
if( i == position ) {
newDesc[2*i] = wrapper;
newDesc[2*i+1] = splitWrapper;
} else {
newDesc[2*i] = oldDesc[index++];
newDesc[2*i+1] = oldDesc[index++];
}
}
_setMultiViewDescriptions(newDesc, null);
tabs.changeActiveManually(wrapper);
}
@Override
public void removeMultiViewDescription(MultiViewDescription descr) {
MultiViewDescription[] oldDesc = model.getDescriptions();
int position = -1;
for( int i=0; i<oldDesc.length/2; i++ ) {
if( oldDesc[2*i] instanceof RuntimeMultiViewDescription ) {
RuntimeMultiViewDescription runtimeDesc = (RuntimeMultiViewDescription) oldDesc[2*i];
if( runtimeDesc.delegate.equals(descr) ) {
position = i;
break;
}
}
}
if( position < 0 )
return; //trying to remove multiview description that isn't in our model
if( -1 != splitOrientation )
peerClearSplit(0);
MultiViewDescription[] newDesc = new MultiViewDescription[oldDesc.length-2];
int index = 0;
for( int i=0; i<oldDesc.length/2; i++ ) {
if( i == position ) {
continue;
}
newDesc[index++] = oldDesc[2*i];
newDesc[index++] = oldDesc[2*i+1];
}
_setMultiViewDescriptions(newDesc, null);
tabs.changeActiveManually(newDesc[0]);
model.setActiveDescription(newDesc[0]);
showCurrentElement();
}
}
private static class RuntimeMultiViewDescription implements ContextAwareDescription {
private final MultiViewDescription delegate;
private final boolean split;
public RuntimeMultiViewDescription( MultiViewDescription delegate, boolean split ) {
this.delegate = delegate;
this.split = split;
}
@Override
public ContextAwareDescription createContextAwareDescription(Lookup context, boolean isSplitDescription) {
return new RuntimeMultiViewDescription(delegate, isSplitDescription);
}
@Override
public boolean isSplitDescription() {
return split;
}
@Override
public int getPersistenceType() {
return delegate.getPersistenceType();
}
@Override
public String getDisplayName() {
return delegate.getDisplayName();
}
@Override
public Image getIcon() {
return delegate.getIcon();
}
@Override
public HelpCtx getHelpCtx() {
return delegate.getHelpCtx();
}
@Override
public String preferredID() {
return delegate.preferredID();
}
@Override
public MultiViewElement createElement() {
return delegate.createElement();
}
}
private class AccessTogglesAction extends AbstractAction {
AccessTogglesAction() {
// putValue(Action.NAME, "AccessToggleMenu");
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("control F10")); //NOI18N
}
public void actionPerformed(ActionEvent e) {
tabs.requestFocusForSelectedButton();
}
}
private class DelegateUndoRedo implements UndoRedo {
private List<ChangeListener> listeners = new ArrayList<>();
public boolean canUndo() {
return privateGetUndoRedo().canUndo();
}
public boolean canRedo() {
return privateGetUndoRedo().canRedo();
}
public void undo() throws CannotUndoException {
privateGetUndoRedo().undo();
}
public void redo() throws CannotRedoException {
privateGetUndoRedo().redo();
}
public void addChangeListener(ChangeListener l) {
listeners.add(l);
privateGetUndoRedo().addChangeListener(l);
}
public void removeChangeListener(ChangeListener l) {
listeners.remove(l);
privateGetUndoRedo().removeChangeListener(l);
}
public String getUndoPresentationName() {
return privateGetUndoRedo().getUndoPresentationName();
}
public String getRedoPresentationName() {
return privateGetUndoRedo().getRedoPresentationName();
}
private void fireElementChange() {
Iterator<ChangeListener> it = new ArrayList<ChangeListener>(listeners).iterator();
while (it.hasNext()) {
ChangeListener elem = it.next();
ChangeEvent event = new ChangeEvent(this);
elem.stateChanged(event);
}
}
void updateListeners(MultiViewElement old, MultiViewElement fresh) {
Iterator<ChangeListener> it = listeners.iterator();
while (it.hasNext()) {
ChangeListener elem = it.next();
if (old.getUndoRedo() != null) {
old.getUndoRedo().removeChangeListener(elem);
}
if (fresh.getUndoRedo() != null) {
fresh.getUndoRedo().addChangeListener(elem);
}
}
fireElementChange();
}
}
private class PreferenceChangeListenerImpl
implements PreferenceChangeListener, Runnable {
public PreferenceChangeListenerImpl() {
}
public @Override void preferenceChange(PreferenceChangeEvent evt) {
if (TOOLBAR_VISIBLE_PROP.equals(evt.getKey())) {
EventQueue.invokeLater(this);
}
}
public @Override void run() {
tabs.setToolbarBarVisible(isToolbarVisible());
}
}
}