/*
* 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.bookmark;
import com.intellij.icons.AllIcons;
import com.intellij.ide.bookmarks.Bookmark;
import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ProjectComponent;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.components.StoragePathMacros;
import com.intellij.ui.LightColors;
import jetbrains.mps.ide.bookmark.BookmarkManager.MyState;
import jetbrains.mps.nodeEditor.Highlighter;
import jetbrains.mps.openapi.navigation.EditorNavigator;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.util.Pair;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import javax.swing.Icon;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@State(
name = "MPSBookmarkManager",
storages = @Storage(StoragePathMacros.WORKSPACE_FILE)
)
public class BookmarkManager implements ProjectComponent, PersistentStateComponent<MyState> {
private static final Logger LOG = LogManager.getLogger(BookmarkManager.class);
private static Icon myUnnumberedBookmarkIcon = AllIcons.Actions.Checked;
private List<BookmarkListener> myBookmarkListeners = new ArrayList<BookmarkListener>();
private SNodeReference[] myBookmarks = new SNodeReference[10];
private List<SNodeReference> myUnnumberedBookmarks = new ArrayList<SNodeReference>();
private final MPSProject myProject;
private Highlighter myHighlighter;
private BookmarksHighlighter myChecker;
public BookmarkManager(MPSProject project, Highlighter highlighter) {
myProject = project;
myHighlighter = highlighter;
}
@Override
public void projectOpened() {
}
@Override
public void projectClosed() {
}
@Override
@NonNls
@NotNull
public String getComponentName() {
return getClass().getName();
}
@Override
public void initComponent() {
myChecker = new BookmarksHighlighter(this);
myHighlighter.addChecker(myChecker);
}
@Override
public void disposeComponent() {
myHighlighter.removeChecker(myChecker);
myChecker.dispose();
}
public List<Pair<SNode, Integer>> getBookmarks(SNode root) {
if (root == null) return Collections.emptyList();
List<Pair<SNode, Integer>> result = new ArrayList<Pair<SNode, Integer>>();
for (int i = 0; i <= 9; i++) {
SNodeReference nodePointer = myBookmarks[i];
if (nodePointer != null) {
SNode node = nodePointer.resolve(myProject.getRepository());
if (node != null && node.getContainingRoot() == root) {
result.add(new Pair<SNode, Integer>(node, i));
}
}
}
for (SNodeReference nodePointer : myUnnumberedBookmarks) {
if (nodePointer != null) {
SNode node = nodePointer.resolve(myProject.getRepository());
if (node != null && node.getContainingRoot() == root) {
result.add(new Pair<SNode, Integer>(node, -1));
}
}
}
return result;
}
public void setUnnumberedBookmark(SNode node) {
if (node == null) {
LOG.error("node to bookmark is null");
return;
}
SNodeReference newBookmark = node.getReference();
boolean bookmarkRemoved = false;
for (int i = 0; i < 10; i++) {
if (myBookmarks[i] != null && myBookmarks[i].resolve(myProject.getRepository()) == node) {
myBookmarks[i] = null;
bookmarkRemoved = true;
fireBookmarkRemoved(i, node);
}
}
if (myUnnumberedBookmarks.contains(newBookmark)) {
myUnnumberedBookmarks.remove(newBookmark);
bookmarkRemoved = true;
fireBookmarkRemoved(-1, newBookmark.resolve(myProject.getRepository()));
}
if (!bookmarkRemoved) {
myUnnumberedBookmarks.add(newBookmark);
fireBookmarkAdded(-1, newBookmark.resolve(myProject.getRepository()));
}
}
public void setBookmark(SNode node, int number) {
if (node == null) {
LOG.error("node to bookmark is null");
return;
}
if (number == -1) {
setUnnumberedBookmark(node);
return;
}
SNodeReference newBookmark = new jetbrains.mps.smodel.SNodePointer(node);
for (int i = 0; i < 10; i++) {
SNodeReference bookmark = myBookmarks[i];
if (i != number && bookmark != null && bookmark.resolve(myProject.getRepository()) == node) {
return;
}
}
if (getAllUnnumberedBookmarks().contains(newBookmark)) {
return;
}
SNodeReference oldBookmark = myBookmarks[number];
SNode oldNode = null;
myBookmarks[number] = null;
if (oldBookmark != null) {
oldNode = oldBookmark.resolve(myProject.getRepository());
fireBookmarkRemoved(number, oldNode);
}
if (!node.equals(oldNode)) {
myBookmarks[number] = newBookmark;
fireBookmarkAdded(number, node);
}
}
public void clearBookmarks() {
for (int i = 0; i < myBookmarks.length; i++) {
SNodeReference pointer = myBookmarks[i];
if (pointer != null) {
myBookmarks[i] = null;
fireBookmarkRemoved(i, pointer.resolve(myProject.getRepository()));
}
}
ArrayList<SNodeReference> nodePointers = new ArrayList<SNodeReference>(myUnnumberedBookmarks);
myUnnumberedBookmarks.clear();
for (SNodeReference pointer : nodePointers) {
if (pointer != null) {
fireBookmarkRemoved(-1, pointer.resolve(myProject.getRepository()));
}
}
}
public void removeBookmark(int i) {
if (i > 9) return;
SNodeReference pointer = myBookmarks[i];
if (pointer != null) {
myBookmarks[i] = null;
fireBookmarkRemoved(i, pointer.resolve(myProject.getRepository()));
}
}
public void removeUnnumberedBookmark(SNodeReference nodePointer) {
if (myUnnumberedBookmarks.contains(nodePointer)) {
myUnnumberedBookmarks.remove(nodePointer);
fireBookmarkRemoved(-1, nodePointer.resolve(myProject.getRepository()));
}
}
public List<SNodeReference> getAllBookmarks() {
List<SNodeReference> nodePointers = getAllNumberedBookmarks();
nodePointers.addAll(getAllUnnumberedBookmarks());
return nodePointers;
}
public List<SNodeReference> getAllNumberedBookmarks() {
return Arrays.asList(myBookmarks);
}
public List<SNodeReference> getAllUnnumberedBookmarks() {
return new ArrayList<SNodeReference>(myUnnumberedBookmarks);
}
public static Icon getIcon(int bookmarkNumber) {
if (bookmarkNumber == -1) {
return myUnnumberedBookmarkIcon;
}
return new MnemonicIcon(Character.forDigit(bookmarkNumber, 10));
}
public SNodeReference getBookmark(int number) {
return myBookmarks[number];
}
public void navigateToBookmark(int number) {
if (number < 0 || number > 9) return;
SNodeReference pointer = myBookmarks[number];
if (pointer == null) {
return;
}
new EditorNavigator(myProject).shallFocus(true).shallSelect(true).open(pointer);
}
public void addBookmarkListener(BookmarkListener listener) {
myBookmarkListeners.add(listener);
}
public void removeBookmarkListener(BookmarkListener listener) {
myBookmarkListeners.remove(listener);
}
public boolean hasBookmarkListener(BookmarkListener listener) {
return myBookmarkListeners.contains(listener);
}
private void fireBookmarkAdded(int number, SNode node) {
for (BookmarkListener listener : myBookmarkListeners) {
listener.bookmarkAdded(number, node);
}
}
private void fireBookmarkRemoved(int number, SNode node) {
for (BookmarkListener listener : myBookmarkListeners) {
listener.bookmarkRemoved(number, node);
}
}
@Override
public MyState getState() {
MyState state = new MyState();
for (int i = 0; i < myBookmarks.length; i++) {
SNodeReference pointer = myBookmarks[i];
if (pointer != null) {
state.myBookmarkInfos[i] = new BookmarkInfo(pointer, i);
} else {
state.myBookmarkInfos[i] = new BookmarkInfo();
}
}
state.myUnnumberedBookmarkInfos = new BookmarkInfo[myUnnumberedBookmarks.size()];
for (int i = 0; i < myUnnumberedBookmarks.size(); i++) {
SNodeReference pointer = myUnnumberedBookmarks.get(i);
if (pointer != null) {
state.myUnnumberedBookmarkInfos[i] = new BookmarkInfo(pointer, -1);
} else {
state.myUnnumberedBookmarkInfos[i] = new BookmarkInfo();
}
}
return state;
}
@Override
public void loadState(MyState state) {
for (int i = 0; i < state.myBookmarkInfos.length; i++) {
BookmarkInfo bookmarkInfo = state.myBookmarkInfos[i];
if (!bookmarkInfo.myIsNull) {
assert i == bookmarkInfo.myNumber;
myBookmarks[i] = bookmarkInfo.myNodeRef;
} else {
myBookmarks[i] = null;
}
}
myUnnumberedBookmarks.clear();
for (BookmarkInfo bookmarkInfo : state.myUnnumberedBookmarkInfos) {
if (bookmarkInfo != null) {
myUnnumberedBookmarks.add(bookmarkInfo.myNodeRef);
}
}
}
public interface BookmarkListener {
public void bookmarkAdded(int number, SNode node);
public void bookmarkRemoved(int number, SNode node);
}
public static class MyState {
public BookmarkInfo[] myBookmarkInfos = new BookmarkInfo[10];
public BookmarkInfo[] myUnnumberedBookmarkInfos = new BookmarkInfo[0];
}
public static class BookmarkInfo {
private SNodeReference myNodeRef;
public int myNumber;
public boolean myIsNull = true;
public BookmarkInfo() {
myIsNull = true;
}
public BookmarkInfo(SNodeReference nodeRef, int number) {
myNodeRef = nodeRef;
myNumber = number;
myIsNull = false;
}
//for serialization/deserialization
@SuppressWarnings("UnusedDeclaration")
public String getNodeRef() {
if (myNodeRef == null) return "";
return jetbrains.mps.smodel.SNodePointer.serialize(myNodeRef);
}
//for serialization/deserialization
@SuppressWarnings("UnusedDeclaration")
public void setNodeRef(String nodeRef) {
if (nodeRef.equals("")) return;
myNodeRef = jetbrains.mps.smodel.SNodePointer.deserialize(nodeRef);
}
}
private static class MnemonicIcon implements Icon {
private final char myMnemonic;
private MnemonicIcon(char mnemonic) {
myMnemonic = mnemonic;
}
@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(LightColors.YELLOW);
g.fillRect(x, y, getIconWidth(), getIconHeight());
g.setColor(Color.gray);
g.drawRect(x, y, getIconWidth(), getIconHeight());
g.setColor(Color.black);
final Font oldFont = g.getFont();
g.setFont(Bookmark.getBookmarkFont());
g.drawString(Character.toString(myMnemonic), x + 2, y + getIconHeight() - 2);
g.setFont(oldFont);
}
@Override
public int getIconWidth() {
return 10;
}
@Override
public int getIconHeight() {
return 12;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MnemonicIcon that = (MnemonicIcon) o;
return myMnemonic == that.myMnemonic;
}
}
}