| /* |
| * 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()); |
| } |
| } |
| |
| } |