/*
* Copyright 2010 Ronnie Kolehmainen
*
* 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 com.googlecode.cssxfire.ui;
import com.googlecode.cssxfire.IncomingChangesComponent;
import com.googlecode.cssxfire.action.ApplyAll;
import com.googlecode.cssxfire.action.ClearAll;
import com.googlecode.cssxfire.tree.*;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.ui.popup.ListPopup;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.util.Collection;
/**
* Created by IntelliJ IDEA.
* User: Ronnie
*/
public class CssToolWindow extends JPanel implements TreeModelListener, TreeViewModel
{
private final CssChangesTreeModel treeModel;
private final JTree tree;
private JButton clearButton, applyButton;
private final Project project;
public CssToolWindow(final Project project)
{
this.project = project;
this.treeModel = new CssChangesTreeModel(project);
setLayout(new BorderLayout());
ActionGroup toolbarGroup = (ActionGroup) ActionManager.getInstance().getAction("IncomingChanges.ToolBar");
ActionToolbar actionToolbar = ActionManager.getInstance().createActionToolbar(IncomingChangesComponent.TOOLWINDOW_ID, toolbarGroup, false);
JToolBar toolBar = new JToolBar(SwingConstants.VERTICAL);
toolBar.add(actionToolbar.getComponent());
JPanel incomingChangesPanel = new JPanel(new BorderLayout(5, 5));
tree = new JTree(treeModel);
tree.setBorder(new EmptyBorder(3, 3, 3, 3));
// tree.setRootVisible(false);
tree.setCellRenderer(treeModel.getTreeCellRenderer());
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.addMouseListener(new MouseAdapter()
{
@Override
public void mouseClicked(MouseEvent e)
{
if ((e.getClickCount() == 2 && e.getButton() == MouseEvent.BUTTON1)
|| (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON2))
{
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
navigateTo(selPath);
}
else if (e.getClickCount() == 1 && e.getButton() == MouseEvent.BUTTON3)
{
TreePath selPath = tree.getPathForLocation(e.getX(), e.getY());
Point point = new Point(e.getXOnScreen(), e.getYOnScreen());
showMenu(selPath, point);
}
}
});
tree.addKeyListener(new KeyAdapter()
{
@Override
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_ENTER)
{
TreePath path = tree.getSelectionPath();
navigateTo(path);
}
}
});
JScrollPane scrollPane = new JScrollPane(tree);
incomingChangesPanel.add(scrollPane, BorderLayout.CENTER);
Presentation clearAllPresentation = ActionManager.getInstance().getAction(ClearAll.ID).getTemplatePresentation();
clearButton = new JButton(clearAllPresentation.getText(), clearAllPresentation.getIcon());
clearButton.setEnabled(false);
clearButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
ActionManager.getInstance().getAction(ClearAll.ID).actionPerformed(createAnActionEvent(ClearAll.ID));
}
});
Presentation applyAllPresentation = ActionManager.getInstance().getAction(ApplyAll.ID).getTemplatePresentation();
applyButton = new JButton(applyAllPresentation.getText(), applyAllPresentation.getIcon());
applyButton.setEnabled(false);
applyButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
ActionManager.getInstance().getAction(ApplyAll.ID).actionPerformed(createAnActionEvent(ApplyAll.ID));
}
});
JPanel southPanel = new JPanel(new BorderLayout());
JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
buttonsPanel.add(clearButton);
buttonsPanel.add(applyButton);
southPanel.add(buttonsPanel, BorderLayout.WEST);
southPanel.add(new LegendDescriptorPanel(), BorderLayout.CENTER);
add(toolBar, BorderLayout.WEST);
add(incomingChangesPanel, BorderLayout.CENTER);
add(southPanel, BorderLayout.SOUTH);
treeModel.addTreeModelListener(this);
}
public CssChangesTreeModel getTreeModel()
{
return treeModel;
}
private void navigateTo(@Nullable TreePath path)
{
if (path != null)
{
Object source = path.getLastPathComponent();
if (source instanceof Navigatable)
{
((Navigatable) source).navigate();
}
}
}
private void showMenu(@Nullable TreePath path, @NotNull Point point)
{
if (path != null)
{
tree.setSelectionPath(path);
Object source = path.getLastPathComponent();
ActionGroup actionGroup = source instanceof CssTreeNode ? ((CssTreeNode) source).getActionGroup() : null;
if (actionGroup != null)
{
ListPopup listPopup = JBPopupFactory.getInstance().createActionGroupPopup(null,
actionGroup,
createDataContext(),
JBPopupFactory.ActionSelectionAid.MNEMONICS,
true);
listPopup.showInScreenCoordinates(tree, point);
}
}
}
private AnActionEvent createAnActionEvent(String actionId)
{
return new AnActionEvent(null,
createDataContext(),
ActionPlaces.UNKNOWN,
ActionManager.getInstance().getAction(actionId).getTemplatePresentation(),
ActionManager.getInstance(),
0);
}
private DataContext createDataContext()
{
return DataManager.getInstance().getDataContext(tree);
}
private void updateButtons()
{
boolean hasChildren = ((CssTreeNode) treeModel.getRoot()).getChildCount() > 0;
clearButton.setEnabled(hasChildren);
applyButton.setEnabled(hasChildren);
}
public void clearTree()
{
CssTreeNode root = (CssTreeNode) treeModel.getRoot();
root.removeAllChildren();
treeModel.nodeStructureChanged(root);
}
private void deleteNode(CssTreeNode node)
{
CssTreeNode parent = (CssTreeNode) node.getParent();
if (parent != null)
{
int index = parent.getIndex(node);
parent.remove(node);
treeModel.nodesWereRemoved(parent, new int[] {index}, new CssTreeNode[] {node});
if (node instanceof CssDeclarationNode)
{
// notify that file node is changed (update the number of changes in file)
treeModel.nodeChanged(parent.getParent());
}
if (parent.getChildCount() == 0)
{
deleteNode(parent);
}
}
}
/**
* Executes a runnable in a write action. The command may be undo'ed. After
* the command has been executed all documents in project will be saved, which will
* trigger other actions which listen for file changes such as "Transfer files"
* @param command the command
*/
private void executeCommand(final Runnable command)
{
CommandProcessor.getInstance().executeCommand(project, new Runnable()
{
public void run()
{
ApplicationManager.getApplication().runWriteAction(command);
}
}, "Apply CSS", "CSS");
FileDocumentManager.getInstance().saveAllDocuments();
}
//
// TreeViewModel
//
public void applyPending()
{
executeCommand(new Runnable()
{
public void run()
{
CssTreeNode root = (CssTreeNode) treeModel.getRoot();
CssTreeNode leaf;
while ((leaf = (CssTreeNode) root.getFirstLeaf()) != null)
{
if (leaf.isRoot())
{
// eventually getFirstLeaf() will return the root itself
break;
}
if (leaf instanceof CssDeclarationNode)
{
CssDeclarationNode declarationNode = (CssDeclarationNode) leaf;
declarationNode.applyToCode();
}
treeModel.removeNodeFromParent(leaf);
}
treeModel.nodeStructureChanged(root);
}
});
}
public void applySelectedNode()
{
TreePath selectedPath = tree.getSelectionPath();
if (selectedPath == null)
{
return;
}
Object source = selectedPath.getLastPathComponent();
if (source instanceof CssFileNode || source instanceof CssSelectorNode)
{
final Collection<CssDeclarationNode> declarations = new ArrayList<CssDeclarationNode>();
for (CssTreeNode leaf : TreeUtils.iterateLeafs((CssTreeNode) source))
{
if (leaf instanceof CssDeclarationNode)
{
declarations.add((CssDeclarationNode) leaf);
}
}
executeCommand(new Runnable()
{
public void run()
{
for (CssDeclarationNode declarationNode : declarations)
{
declarationNode.applyToCode();
deleteNode(declarationNode);
}
}
});
}
else if (source instanceof CssDeclarationNode)
{
final CssDeclarationNode declarationNode = (CssDeclarationNode) source;
executeCommand(new Runnable()
{
public void run()
{
declarationNode.applyToCode();
deleteSelectedNode();
}
});
}
}
public void deleteSelectedNode()
{
TreePath selectedPath = tree.getSelectionPath();
if (selectedPath == null)
{
return;
}
Object source = selectedPath.getLastPathComponent();
if (source instanceof CssFileNode || source instanceof CssSelectorNode)
{
final Collection<CssDeclarationNode> declarations = new ArrayList<CssDeclarationNode>();
for (CssTreeNode leaf : TreeUtils.iterateLeafs((CssTreeNode) source))
{
if (leaf instanceof CssDeclarationNode)
{
declarations.add((CssDeclarationNode) leaf);
}
}
for (CssDeclarationNode declarationNode : declarations)
{
deleteNode(declarationNode);
}
}
else if (source instanceof CssDeclarationNode)
{
deleteNode((CssDeclarationNode) source);
}
}
public void collapseAll()
{
for (CssTreeNode node : TreeUtils.iterateLeafs((CssTreeNode) treeModel.getRoot()))
{
CssTreeNode parent = node;
while ((parent = (CssTreeNode) parent.getParent()) != null)
{
if (parent.isRoot())
{
break;
}
tree.collapsePath(new TreePath(parent.getPath()));
}
}
}
public void expandAll()
{
for (CssTreeNode node : TreeUtils.iterateLeafs((CssTreeNode) treeModel.getRoot()))
{
tree.expandPath(new TreePath(((CssTreeNode) node.getParent()).getPath()));
}
}
public void refreshLeafs()
{
for (CssTreeNode node : TreeUtils.iterateLeafs((CssTreeNode) treeModel.getRoot()))
{
treeModel.nodeChanged(node);
}
}
public boolean canSelect(int direction)
{
int leafCount = TreeUtils.countLeafs((CssTreeNode) treeModel.getRoot());
switch (leafCount)
{
case 0:
return false;
case 1:
TreePath selectionPath = tree.getSelectionPath();
if (direction < 0)
{
// When there's only one declaration node, only forward navigation is possible
return false;
}
return selectionPath == null || !(selectionPath.getLastPathComponent() instanceof CssDeclarationNode);
default:
return true;
}
}
public void select(int direction)
{
CssTreeNode root = (CssTreeNode) treeModel.getRoot();
TreePath selectionPath = tree.getSelectionPath();
CssTreeNode anchor = selectionPath == null ? null : (CssTreeNode) selectionPath.getLastPathComponent();
CssDeclarationNode declarationNode = TreeUtils.seek(root, anchor, direction);
if (declarationNode != null)
{
TreePath path = new TreePath(declarationNode.getPath());
tree.getSelectionModel().setSelectionPath(path);
navigateTo(path);
}
}
//
// TreeModelListener
//
public void treeNodesChanged(TreeModelEvent e)
{
updateButtons();
}
public void treeNodesInserted(TreeModelEvent e)
{
updateButtons();
}
public void treeNodesRemoved(TreeModelEvent e)
{
updateButtons();
}
public void treeStructureChanged(TreeModelEvent e)
{
updateButtons();
}
}