/* * Copyright 2003-2015 JetBrains s.r.o. * * 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 jetbrains.mps.ide.editorTabs.tabfactory.tabs.plaintabs; import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Disposer; import com.intellij.ui.tabs.JBTabsPosition; import com.intellij.ui.tabs.TabInfo; import com.intellij.ui.tabs.TabsListener; import com.intellij.ui.tabs.UiDecorator; import com.intellij.ui.tabs.impl.JBTabsImpl; import jetbrains.mps.ide.editorTabs.TabColorProvider; import jetbrains.mps.ide.editorTabs.tabfactory.NodeChangeCallback; import jetbrains.mps.ide.editorTabs.tabfactory.tabs.BaseTabsComponent; import jetbrains.mps.ide.editorTabs.tabfactory.tabs.CreateModeCallback; import jetbrains.mps.ide.editorTabs.tabfactory.tabs.TabEditorLayout; import jetbrains.mps.ide.editorTabs.tabfactory.tabs.TabEditorLayout.Entry; import jetbrains.mps.ide.icons.IconManager; import jetbrains.mps.plugins.relations.RelationDescriptor; import jetbrains.mps.util.EqualUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.mps.openapi.model.SNode; import org.jetbrains.mps.openapi.model.SNodeReference; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.border.EmptyBorder; import java.awt.Color; import java.awt.Insets; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; public class PlainTabsComponent extends BaseTabsComponent { private final List<PlainEditorTab> myRealTabs = new ArrayList<PlainEditorTab>(); private final JBTabsImpl myTabs; private RelationDescriptor myLastEmptyTab = null; private volatile boolean myRebuilding = false; private final Disposable myJbTabsDisposable = new Disposable() { @Override public void dispose() { } }; public PlainTabsComponent(SNodeReference baseNode, Set<RelationDescriptor> possibleTabs, JComponent editor, NodeChangeCallback callback, boolean showGrayed, CreateModeCallback createModeCallback, Project project) { super(baseNode, possibleTabs, editor, callback, showGrayed, createModeCallback, project); myTabs = new JBTabsImpl(project, null, myJbTabsDisposable); myTabs.setTabsPosition(JBTabsPosition.bottom) .setPaintBorder(0, 0, 0, 0) .setTabSidePaintBorder(1) .setGhostsAlwaysVisible(false) .setUiDecorator(new UiDecorator() { @NotNull @Override public UiDecoration getDecoration() { return new UiDecoration(null, new Insets(0, 8, 0, 8)); } }); myTabs.setBorder(new EmptyBorder(0, 0, 1, 0)); setContent(myTabs); myTabs.addListener(new TabsListener.Adapter() { @Override public void selectionChanged(TabInfo oldSelection, TabInfo newSelection) { if (isDisposed() || myRebuilding) { return; } getProject().getModelAccess().runReadAction(new Runnable() { @Override public void run() { onTabIndexChange(); } }); } }); } private synchronized void onTabIndexChange() { if (isDisposed()) { return; } if (myTabs.getTabCount() == 0) { return; } int index = myTabs.getIndexOf(myTabs.getSelectedInfo()); PlainEditorTab tab = myRealTabs.get(index); SNodeReference np = tab.getNode(); if (np != null && EqualUtil.equals(np, getEditedNode())) { return; } if (np != null) { myLastEmptyTab = null; editNode(np); } else { myLastEmptyTab = tab.getTab(); enterCreateMode(myLastEmptyTab); } } @Override public synchronized RelationDescriptor getCurrentTabAspect() { if (isDisposed()) { return null; } if (myLastEmptyTab != null) { return myLastEmptyTab; } final int i = myTabs.getIndexOf(myTabs.getSelectedInfo()); return i == -1 ? null : myRealTabs.get(i).getTab(); } @NotNull @Override public Collection<SNodeReference> getSelectionFor(RelationDescriptor tabDescriptor, SNodeReference editedNode) { for (PlainEditorTab t : myRealTabs) { if (t.getTab() == tabDescriptor && editedNode.equals(t.getNode())) { return t.getLayoutEntry().getSelection(); } } return Collections.emptyList(); } @Override public void editNode(SNodeReference node) { if (isDisposed()) { return; } //not to make infinite recursion when tab is clicked if (EqualUtil.equals(node, getEditedNode())) { return; } super.editNode(node); selectNodeTab(); } //this is synchronized because we change myJbTabs here (while disposing) @Override public synchronized void dispose() { Disposer.dispose(myJbTabsDisposable); super.dispose(); } @Override public synchronized void updateTabColors() { if (isDisposed()) { return; } for (int i = 0; i < myRealTabs.size(); i++) { SNodeReference nodePtr = myRealTabs.get(i).getNode(); TabColorProvider colorProvider = getColorProvider(); SNode node = nodePtr != null ? nodePtr.resolve(getProject().getRepository()) : null; if (node != null && colorProvider != null) { Color color = colorProvider.getNodeColor(node); if (color != null) { myTabs.getTabAt(i).setDefaultForeground(color); continue; } } myTabs.getTabAt(i).setDefaultForeground(null); } } @Override public synchronized void updateTabs() { if (isDisposed()) { return; } SNodeReference selectedNode = null; RelationDescriptor selectedAspect = null; int selected = myTabs.getTabCount() > 0 ? myTabs.getIndexOf(myTabs.getSelectedInfo()) : -1; if (selected != -1) { selectedNode = myRealTabs.get(selected).getNode(); selectedAspect = myRealTabs.get(selected).getTab(); } boolean oldRebuilding = myRebuilding; myRebuilding = true; try { myTabs.removeAllTabs(); myRealTabs.clear(); TabEditorLayout newContent = updateDocumentsAndNodes(); //todo sort nodes inside aspect for (RelationDescriptor tab : myPossibleTabs) { if (newContent.covers(tab)) { for (Entry tabDescriptor : newContent.get(tab)) { final PlainEditorTab pet = new PlainEditorTab(tabDescriptor); myRealTabs.add(pet); SNode node = pet.getNode().resolve(getProject().getRepository()); TabInfo info = new TabInfo(new JLabel("")) .setIcon(IconManager.getIconFor(node)) .setText(node.getPresentation()) .setPreferredFocusableComponent(myEditor); myTabs.addTab(info); } } else if (myShowGrayed) { myRealTabs.add(new PlainEditorTab(tab)); TabInfo info = new TabInfo(new JLabel("")) .setText(tab.getTitle()).setDefaultForeground(Color.GRAY) .setPreferredFocusableComponent(myEditor); myTabs.addTab(info); } } updateTabColors(); } finally { myRebuilding = oldRebuilding; } boolean selectionRestored = false; // selectedNode.resolve() != null even for removed roots because at the moment we get #updateTabs() from commandFinish if (selectedNode != null && selectedNode.resolve(getProject().getRepository()) != null) { for (PlainEditorTab tab : myRealTabs) { if (EqualUtil.equals(tab.getNode(), selectedNode)) { myTabs.select(myTabs.getTabAt(myRealTabs.indexOf(tab)), true); selectionRestored = true; break; } } } if (!selectionRestored && myTabs.getTabCount() > 0) { myTabs.select(myTabs.getTabAt(0), true); selectionRestored = true; } if (selectionRestored) { //this is needed as Idea component sends no events if we've just removed all tabs and added one new and then are trying to select it //see http://youtrack.jetbrains.com/issue/MPS-17943 onTabIndexChange(); } } private synchronized void selectNodeTab() { if (isDisposed()) return; for (PlainEditorTab t : myRealTabs) { if (t.getNode() != null && t.getNode().equals(getEditedNode())) { myTabs.select(myTabs.getTabAt(myRealTabs.indexOf(t)), true); return; } } for (PlainEditorTab t : myRealTabs) { if (t.getNode() == null && t.getTab().equals(myLastEmptyTab)) { myTabs.select(myTabs.getTabAt(myRealTabs.indexOf(t)), true); return; } } } @Override public synchronized void nextTab() { if (isDisposed()) { return; } int i = myTabs.getIndexOf(myTabs.getSelectedInfo()); if (i < myTabs.getTabCount() - 1) { myTabs.select(myTabs.getTabAt(i + 1), true); } } @Override public synchronized void prevTab() { if (isDisposed()) { return; } int i = myTabs.getIndexOf(myTabs.getSelectedInfo()); if (i > 0) { myTabs.select(myTabs.getTabAt(i - 1), true); } } }