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