You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Tree.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.addons.ui_swing.framemanager.tree;
  18. import com.dmdirc.addons.ui_swing.EDTInvocation;
  19. import com.dmdirc.addons.ui_swing.UIUtilities;
  20. import com.dmdirc.addons.ui_swing.actions.CloseWindowAction;
  21. import com.dmdirc.addons.ui_swing.components.frames.TextFrame;
  22. import com.dmdirc.addons.ui_swing.events.SwingActiveWindowChangeRequestEvent;
  23. import com.dmdirc.addons.ui_swing.events.SwingEventBus;
  24. import com.dmdirc.config.binding.ConfigBinding;
  25. import com.dmdirc.config.provider.AggregateConfigProvider;
  26. import java.awt.Rectangle;
  27. import java.awt.event.ActionEvent;
  28. import java.awt.event.ActionListener;
  29. import java.awt.event.MouseEvent;
  30. import java.awt.event.MouseListener;
  31. import java.awt.event.MouseMotionListener;
  32. import java.util.Optional;
  33. import javax.swing.BorderFactory;
  34. import javax.swing.JComponent;
  35. import javax.swing.JMenuItem;
  36. import javax.swing.JPopupMenu;
  37. import javax.swing.JTree;
  38. import javax.swing.UIManager;
  39. import javax.swing.tree.DefaultTreeModel;
  40. import javax.swing.tree.MutableTreeNode;
  41. import javax.swing.tree.TreeModel;
  42. import javax.swing.tree.TreePath;
  43. import javax.swing.tree.TreeSelectionModel;
  44. import net.miginfocom.layout.PlatformDefaults;
  45. /**
  46. * Specialised JTree for the frame manager.
  47. */
  48. public class Tree extends JTree implements MouseMotionListener, MouseListener, ActionListener {
  49. /** A version number for this class. */
  50. private static final long serialVersionUID = 1;
  51. /** Tree frame manager. */
  52. private final TreeFrameManager manager;
  53. /** The swing event bus to post events to. */
  54. private final SwingEventBus eventBus;
  55. /** Config manager. */
  56. private final AggregateConfigProvider config;
  57. /** Drag selection enabled? */
  58. private boolean dragSelect;
  59. /** Drag button 1? */
  60. private boolean dragButton;
  61. /** Show handles. */
  62. private boolean showHandles;
  63. /**
  64. * Specialised JTree for frame manager.
  65. *
  66. * @param manager Frame manager
  67. * @param model tree model.
  68. * @param eventBus The swing event bus to post events to.
  69. * @param globalConfig The config to read settings from.
  70. * @param domain The domain to read settings from.
  71. */
  72. public Tree(
  73. final TreeFrameManager manager,
  74. final TreeModel model,
  75. final SwingEventBus eventBus,
  76. final AggregateConfigProvider globalConfig,
  77. final String domain) {
  78. super(model);
  79. this.manager = manager;
  80. this.config = globalConfig;
  81. this.eventBus = eventBus;
  82. putClientProperty("JTree.lineStyle", "Angled");
  83. getInputMap().setParent(null);
  84. getInputMap(JComponent.WHEN_FOCUSED).clear();
  85. getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).clear();
  86. getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).clear();
  87. getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
  88. setRootVisible(false);
  89. setRowHeight(getFontMetrics(UIManager.getFont("Tree.font")).getHeight() + 2);
  90. setOpaque(true);
  91. setBorder(BorderFactory.createEmptyBorder(
  92. (int) PlatformDefaults.getUnitValueX("related").getValue(),
  93. (int) PlatformDefaults.getUnitValueX("related").getValue(),
  94. (int) PlatformDefaults.getUnitValueX("related").getValue(),
  95. (int) PlatformDefaults.getUnitValueX("related").getValue()));
  96. setFocusable(false);
  97. config.getBinder().withDefaultDomain(domain).bind(this, Tree.class);
  98. // TODO: Create a nicer lifecycle and unbind
  99. addMouseListener(this);
  100. addMouseMotionListener(this);
  101. }
  102. @Override
  103. public void scrollRectToVisible(final Rectangle aRect) {
  104. final Rectangle rect = new Rectangle(0, aRect.y, aRect.width, aRect.height);
  105. super.scrollRectToVisible(rect);
  106. }
  107. /**
  108. * Set path.
  109. *
  110. * @param path Path
  111. */
  112. public void setTreePath(final TreePath path) {
  113. UIUtilities.invokeLater(() -> setSelectionPath(path));
  114. }
  115. /**
  116. * Returns the node for the specified location, returning null if rollover is disabled or there
  117. * is no node at the specified location.
  118. *
  119. * @param x x coordiantes
  120. * @param y y coordiantes
  121. *
  122. * @return node or null
  123. */
  124. public TreeViewNode getNodeForLocation(final int x,
  125. final int y) {
  126. TreeViewNode node = null;
  127. final TreePath selectedPath = getPathForLocation(x, y);
  128. if (selectedPath != null) {
  129. node = (TreeViewNode) selectedPath.getLastPathComponent();
  130. }
  131. return node;
  132. }
  133. @ConfigBinding(key = "dragSelection", invocation = EDTInvocation.class)
  134. public void handleDragSelection(final String value) {
  135. dragSelect = config.getOptionBool("treeview", "dragSelection");
  136. }
  137. @ConfigBinding(key="showtreeexpands")
  138. public void handleTreeExpands(final String value) {
  139. showHandles = Boolean.valueOf(value);
  140. setShowsRootHandles(showHandles);
  141. putClientProperty("showHandles", showHandles);
  142. }
  143. @Override
  144. public void mouseDragged(final MouseEvent e) {
  145. if (dragSelect && dragButton) {
  146. final TreeViewNode node = getNodeForLocation(e.getX(), e.getY());
  147. if (node != null) {
  148. eventBus.publishAsync(new SwingActiveWindowChangeRequestEvent(
  149. Optional.ofNullable(((TreeViewNode) new TreePath(node.getPath())
  150. .getLastPathComponent()).getWindow())));
  151. }
  152. }
  153. manager.checkRollover(e);
  154. }
  155. @Override
  156. public void mouseMoved(final MouseEvent e) {
  157. manager.checkRollover(e);
  158. }
  159. @Override
  160. public void mouseClicked(final MouseEvent e) {
  161. processMouseEvents(e);
  162. }
  163. @Override
  164. public void mousePressed(final MouseEvent e) {
  165. if (e.getButton() == MouseEvent.BUTTON1) {
  166. dragButton = true;
  167. final TreePath selectedPath = getPathForLocation(e.getX(),
  168. e.getY());
  169. if (selectedPath != null) {
  170. eventBus.publishAsync(new SwingActiveWindowChangeRequestEvent(
  171. Optional.ofNullable(((TreeViewNode) selectedPath.getLastPathComponent())
  172. .getWindow())));
  173. }
  174. }
  175. processMouseEvents(e);
  176. }
  177. @Override
  178. public void mouseReleased(final MouseEvent e) {
  179. dragButton = false;
  180. processMouseEvents(e);
  181. }
  182. @Override
  183. public void mouseEntered(final MouseEvent e) {
  184. //Ignore
  185. }
  186. @Override
  187. public void mouseExited(final MouseEvent e) {
  188. manager.checkRollover(null);
  189. }
  190. /**
  191. * Processes every mouse button event to check for a popup trigger.
  192. *
  193. * @param e mouse event
  194. */
  195. void processMouseEvents(final MouseEvent e) {
  196. final TreePath localPath = getPathForLocation(e.getX(), e.getY());
  197. if (localPath != null && e.isPopupTrigger()) {
  198. final TextFrame frame = ((TreeViewNode) localPath.getLastPathComponent()).getWindow();
  199. if (frame == null) {
  200. return;
  201. }
  202. final JPopupMenu popupMenu = frame.getPopupMenu(null,
  203. new Object[][]{new Object[]{""}});
  204. frame.addCustomPopupItems(popupMenu);
  205. if (popupMenu.getComponentCount() > 0) {
  206. popupMenu.addSeparator();
  207. }
  208. final TreeViewNodeMenuItem popoutMenuItem;
  209. if (frame.getPopoutFrame() == null) {
  210. popoutMenuItem = new TreeViewNodeMenuItem("Pop Out", "popout",
  211. (TreeViewNode) localPath.getLastPathComponent());
  212. } else {
  213. popoutMenuItem = new TreeViewNodeMenuItem("Pop In", "popin",
  214. (TreeViewNode) localPath.getLastPathComponent());
  215. }
  216. popupMenu.add(popoutMenuItem);
  217. popupMenu.addSeparator();
  218. popoutMenuItem.addActionListener(this);
  219. final TreeViewNodeMenuItem moveUp = new TreeViewNodeMenuItem("Move Up", "Up",
  220. (TreeViewNode) localPath.getLastPathComponent());
  221. final TreeViewNodeMenuItem moveDown = new TreeViewNodeMenuItem("Move Down", "Down",
  222. (TreeViewNode) localPath.getLastPathComponent());
  223. moveUp.addActionListener(this);
  224. moveDown.addActionListener(this);
  225. popupMenu.add(moveUp);
  226. popupMenu.add(moveDown);
  227. popupMenu.add(new JMenuItem(new CloseWindowAction(frame.getContainer())));
  228. popupMenu.show(this, e.getX(), e.getY());
  229. }
  230. }
  231. @Override
  232. public void actionPerformed(final ActionEvent e) {
  233. final TreeViewNode node = ((TreeViewNodeMenuItem) e.getSource()).
  234. getTreeNode();
  235. int index = getModel().getIndexOfChild(node.getParent(), node);
  236. switch (e.getActionCommand()) {
  237. case "Up":
  238. if (index == 0) {
  239. index = node.getSiblingCount() - 1;
  240. } else {
  241. index--;
  242. }
  243. break;
  244. case "Down":
  245. if (index == node.getSiblingCount() - 1) {
  246. index = 0;
  247. } else {
  248. index++;
  249. }
  250. break;
  251. case "popout":
  252. node.getWindow().setPopout(true);
  253. break;
  254. case "popin":
  255. node.getWindow().setPopout(false);
  256. break;
  257. }
  258. final MutableTreeNode parentNode = (MutableTreeNode) node.getParent();
  259. final TreePath nodePath = new TreePath(node.getPath());
  260. final boolean isExpanded = isExpanded(nodePath);
  261. ((DefaultTreeModel) getModel()).removeNodeFromParent(node);
  262. ((DefaultTreeModel) getModel()).insertNodeInto(node, parentNode, index);
  263. setExpandedState(nodePath, isExpanded);
  264. }
  265. }