001: /*
002: * ProfileTree.java
003: *
004: * This file is part of SQL Workbench/J, http://www.sql-workbench.net
005: *
006: * Copyright 2002-2008, Thomas Kellerer
007: * No part of this code maybe reused without the permission of the author
008: *
009: * To contact the author please send an email to: support@sql-workbench.net
010: *
011: */
012: package workbench.gui.profiles;
013:
014: import java.awt.Insets;
015: import java.awt.Point;
016: import java.awt.Rectangle;
017: import java.awt.dnd.DnDConstants;
018: import java.awt.event.ActionEvent;
019: import java.awt.event.ActionListener;
020: import java.awt.event.MouseEvent;
021: import java.awt.event.MouseListener;
022: import java.util.LinkedList;
023: import java.util.List;
024: import javax.swing.ActionMap;
025: import javax.swing.InputMap;
026: import javax.swing.JTree;
027: import javax.swing.SwingUtilities;
028: import javax.swing.UIManager;
029: import javax.swing.event.TreeModelEvent;
030: import javax.swing.event.TreeModelListener;
031: import javax.swing.event.TreeSelectionEvent;
032: import javax.swing.event.TreeSelectionListener;
033: import javax.swing.tree.DefaultMutableTreeNode;
034: import javax.swing.tree.TreeModel;
035: import javax.swing.tree.TreeNode;
036: import javax.swing.tree.TreePath;
037: import javax.swing.tree.TreeSelectionModel;
038: import workbench.db.ConnectionProfile;
039: import workbench.gui.WbSwingUtilities;
040: import workbench.gui.actions.DeleteListEntryAction;
041: import workbench.gui.actions.WbAction;
042: import workbench.gui.menu.CutCopyPastePopup;
043: import workbench.interfaces.ClipboardSupport;
044: import workbench.resource.ResourceMgr;
045: import workbench.util.StringUtil;
046:
047: /**
048: * A Tree to display connection profiles
049: * It supports drag & drop from profiles into different groups
050: *
051: * @author support@sql-workbench.net
052: */
053: public class ProfileTree extends JTree implements TreeModelListener,
054: MouseListener, ClipboardSupport, ActionListener,
055: TreeSelectionListener {
056: private ProfileListModel profileModel;
057: private DefaultMutableTreeNode[] clipboardNodes;
058: private static final int CLIP_COPY = 1;
059: private static final int CLIP_CUT = 2;
060: private int clipboardType = 0;
061: private CutCopyPastePopup popup;
062: private WbAction pasteToFolderAction;
063: private RenameGroupAction renameGroup;
064: private ProfileTreeDragHandler ds;
065: private Insets autoscrollInsets = new Insets(20, 20, 20, 20);
066:
067: public ProfileTree() {
068: super ();
069: setRootVisible(false);
070: putClientProperty("JTree.lineStyle", "Angled");
071: setShowsRootHandles(true);
072: setEditable(true);
073: setExpandsSelectedPaths(true);
074: addMouseListener(this );
075: getSelectionModel().setSelectionMode(
076: TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
077: addTreeSelectionListener(this );
078: UIManager.put("Tree.openIcon", ResourceMgr.getImage("Tree"));
079: UIManager.put("Tree.closedIcon", ResourceMgr.getImage("Tree"));
080: UIManager.put("Tree.leafIcon", ResourceMgr.getImage("profile"));
081: InputMap im = this .getInputMap(WHEN_FOCUSED);
082: ActionMap am = this .getActionMap();
083:
084: this .popup = new CutCopyPastePopup(this );
085:
086: WbAction a = popup.getPasteAction();
087: a.addToInputMap(im, am);
088:
089: a = popup.getCopyAction();
090: a.addToInputMap(im, am);
091:
092: a = popup.getCutAction();
093: a.addToInputMap(im, am);
094:
095: pasteToFolderAction = new WbAction(this , "pasteToFolder");
096: pasteToFolderAction.removeIcon();
097: pasteToFolderAction.initMenuDefinition("MnuTxtPasteNewFolder");
098: popup.addAction(pasteToFolderAction, true);
099:
100: renameGroup = new RenameGroupAction(this );
101: popup.addAction(renameGroup, false);
102:
103: setCellRenderer(new ProfileTreeCellRenderer());
104: ds = new ProfileTreeDragHandler(this ,
105: DnDConstants.ACTION_COPY_OR_MOVE);
106: setAutoscrolls(true);
107: }
108:
109: public void setDeleteAction(DeleteListEntryAction delete) {
110: this .popup.addSeparator();
111: this .popup.add(delete);
112: InputMap im = this .getInputMap(WHEN_FOCUSED);
113: ActionMap am = this .getActionMap();
114: delete.addToInputMap(im, am);
115: }
116:
117: public void setModel(TreeModel model) {
118: super .setModel(model);
119: if (model instanceof ProfileListModel) {
120: this .profileModel = (ProfileListModel) model;
121: }
122: model.addTreeModelListener(this );
123: }
124:
125: public boolean isPathEditable(TreePath path) {
126: if (path == null)
127: return false;
128: // Only allow editing of groups
129: if (path.getPathCount() != 2)
130: return false;
131:
132: DefaultMutableTreeNode node = (DefaultMutableTreeNode) path
133: .getLastPathComponent();
134:
135: return node.getAllowsChildren();
136: }
137:
138: public void treeNodesChanged(TreeModelEvent e) {
139: Object[] changed = e.getChildren();
140: DefaultMutableTreeNode group = (DefaultMutableTreeNode) changed[0];
141: Object data = group.getUserObject();
142:
143: if (group.getAllowsChildren()) {
144: String newGroupName = (String) data;
145: renameGroup(group, newGroupName);
146: } else if (data instanceof ConnectionProfile) {
147: // If the connection profile has changed, the title
148: // of the profile possibly changed as well, so we need to
149: // trigger a repaint to display the correct title
150: // in the tree
151: this .repaint();
152: }
153: }
154:
155: public void expandAll() {
156: TreePath[] groups = this .profileModel.getGroupNodes();
157: for (int i = 0; i < groups.length; i++) {
158: if (groups[i] != null)
159: expandPath(groups[i]);
160: }
161: }
162:
163: public void collapseAll() {
164: TreePath[] groups = this .profileModel.getGroupNodes();
165: for (int i = 0; i < groups.length; i++) {
166: if (groups[i] != null)
167: collapsePath(groups[i]);
168: }
169: }
170:
171: /**
172: * Expand the groups that are contained in th list.
173: * The list is expected to contain Sting objects that identify
174: * the names of the groups.
175: */
176: public void expandGroups(List groupList) {
177: if (groupList == null)
178: return;
179: TreePath[] groupNodes = this .profileModel.getGroupNodes();
180: if (groupNodes == null)
181: return;
182: for (int i = 0; i < groupNodes.length; i++) {
183: DefaultMutableTreeNode node = (DefaultMutableTreeNode) groupNodes[i]
184: .getLastPathComponent();
185: String g = (String) node.getUserObject();
186: if (groupList.contains(g)) {
187: if (!isExpanded(groupNodes[i]))
188: expandPath(groupNodes[i]);
189: }
190: }
191: }
192:
193: /**
194: * Return the names of the expaned groups.
195: */
196: public List getExpandedGroupNames() {
197: LinkedList result = new LinkedList();
198: TreePath[] groupNodes = this .profileModel.getGroupNodes();
199: for (int i = 0; i < groupNodes.length; i++) {
200: if (isExpanded(groupNodes[i])) {
201: DefaultMutableTreeNode node = (DefaultMutableTreeNode) groupNodes[i]
202: .getLastPathComponent();
203: String g = (String) node.getUserObject();
204: result.add(g);
205: }
206: }
207: return result;
208: }
209:
210: public void treeNodesInserted(TreeModelEvent e) {
211: }
212:
213: public void treeNodesRemoved(TreeModelEvent e) {
214: }
215:
216: public void treeStructureChanged(TreeModelEvent e) {
217: }
218:
219: public boolean isGroup(TreePath p) {
220: if (p == null)
221: return false;
222: TreeNode n = (TreeNode) p.getLastPathComponent();
223: return n.getAllowsChildren();
224: }
225:
226: /**
227: * Enable/disable the cut/copy/paste actions
228: * according to the current selection and the content
229: * of the "clipboard"
230: */
231: private void checkActions() {
232: boolean groupSelected = onlyGroupSelected();
233: boolean canPaste = this .clipboardNodes != null && groupSelected;
234: boolean canCopy = onlyProfilesSelected();
235:
236: pasteToFolderAction.setEnabled(canPaste);
237:
238: WbAction a = popup.getPasteAction();
239: a.setEnabled(canPaste);
240:
241: a = popup.getCopyAction();
242: a.setEnabled(canCopy);
243:
244: a = popup.getCutAction();
245: a.setEnabled(canCopy);
246:
247: }
248:
249: public void mouseClicked(MouseEvent e) {
250: if (e.getButton() == MouseEvent.BUTTON3
251: && e.getClickCount() == 1) {
252: TreePath p = this .getClosestPathForLocation(e.getX(), e
253: .getY());
254: if (p == null)
255: return;
256:
257: if (this .getSelectionCount() == 1 || isGroup(p)) {
258: setSelectionPath(p);
259: }
260: checkActions();
261: popup.show(this , e.getX(), e.getY());
262: }
263: }
264:
265: /**
266: * Finds and selects the connection profile with the given
267: * name. If the profile is not found, the first profile
268: * will be selected (and expanded)
269: */
270: public void selectProfile(ProfileKey def) {
271: if (profileModel == null)
272: return;
273: TreePath path = this .profileModel.getPath(def);
274: if (path == null) {
275: path = this .profileModel.getFirstProfile();
276: }
277: selectPath(path);
278: }
279:
280: /**
281: * Checks if the current selection contains only profiles
282: */
283: public boolean onlyProfilesSelected() {
284: TreePath[] selection = getSelectionPaths();
285: if (selection == null)
286: return false;
287: for (int i = 0; i < selection.length; i++) {
288: TreeNode n = (TreeNode) selection[i].getLastPathComponent();
289: if (n.getAllowsChildren())
290: return false;
291: }
292: return true;
293: }
294:
295: /**
296: * Checks if the current selection contains only groups
297: */
298: public boolean onlyGroupSelected() {
299: if (getSelectionCount() > 1)
300: return false;
301: TreePath[] selection = getSelectionPaths();
302: if (selection == null)
303: return false;
304: for (int i = 0; i < selection.length; i++) {
305: TreeNode n = (TreeNode) selection[i].getLastPathComponent();
306: if (!n.getAllowsChildren())
307: return false;
308: }
309: return true;
310: }
311:
312: protected DefaultMutableTreeNode getSelectedGroupNode() {
313: TreePath[] selection = getSelectionPaths();
314: if (selection == null)
315: return null;
316: if (selection.length != 1)
317: return null;
318:
319: DefaultMutableTreeNode node = (DefaultMutableTreeNode) getLastSelectedPathComponent();
320: if (node != null && node.getAllowsChildren())
321: return node;
322: return null;
323: }
324:
325: /**
326: * Returns the currently selected Profile. If either more then one
327: * entry is selected or a group is selected, null is returned
328: *
329: * @return the selected profile if any
330: */
331: public ConnectionProfile getSelectedProfile() {
332: TreePath[] selection = getSelectionPaths();
333: if (selection == null)
334: return null;
335: if (selection.length != 1)
336: return null;
337:
338: DefaultMutableTreeNode node = (DefaultMutableTreeNode) getLastSelectedPathComponent();
339: if (node == null)
340: return null;
341:
342: Object o = node.getUserObject();
343: if (o instanceof ConnectionProfile) {
344: ConnectionProfile prof = (ConnectionProfile) o;
345: return prof;
346: }
347: return null;
348: }
349:
350: public void mousePressed(MouseEvent e) {
351: }
352:
353: public void mouseReleased(MouseEvent e) {
354: }
355:
356: public void mouseEntered(MouseEvent e) {
357: }
358:
359: public void mouseExited(MouseEvent e) {
360: }
361:
362: /**
363: * Stores the selected nodes in the internal "clipboard"
364: */
365: private void storeSelectedNodes() {
366: TreePath[] p = getSelectionPaths();
367:
368: this .clipboardNodes = new DefaultMutableTreeNode[p.length];
369: for (int i = 0; i < p.length; i++) {
370: this .clipboardNodes[i] = (DefaultMutableTreeNode) p[i]
371: .getLastPathComponent();
372: }
373: }
374:
375: public void copy() {
376: storeSelectedNodes();
377: this .clipboardType = CLIP_COPY;
378: }
379:
380: public void selectAll() {
381: }
382:
383: public void clear() {
384: }
385:
386: public void cut() {
387: storeSelectedNodes();
388: this .clipboardType = CLIP_CUT;
389: }
390:
391: public void paste() {
392: if (clipboardNodes == null)
393: return;
394: if (clipboardNodes.length == 0)
395: return;
396:
397: DefaultMutableTreeNode group = (DefaultMutableTreeNode) getLastSelectedPathComponent();
398: if (group == null)
399: return;
400: if (!group.getAllowsChildren())
401: return;
402:
403: try {
404: if (clipboardType == CLIP_CUT) {
405: profileModel.moveProfilesToGroup(clipboardNodes, group);
406: } else if (clipboardType == CLIP_COPY) {
407: profileModel.copyProfilesToGroup(clipboardNodes, group);
408: }
409: } finally {
410: this .clipboardType = 0;
411: this .clipboardNodes = null;
412: }
413: }
414:
415: public void handleDroppedNodes(DefaultMutableTreeNode[] nodes,
416: DefaultMutableTreeNode newParent, int action) {
417: if (nodes == null || nodes.length < 1)
418: return;
419: if (newParent == null)
420: return;
421:
422: if (action == DnDConstants.ACTION_MOVE) {
423: profileModel.moveProfilesToGroup(nodes, newParent);
424: } else if (action == DnDConstants.ACTION_COPY) {
425: profileModel.copyProfilesToGroup(nodes, newParent);
426: }
427: selectNode(nodes[0]);
428: }
429:
430: public void actionPerformed(ActionEvent e) {
431: // invoked from the "paste into new folder" action
432: String group = addProfileGroup();
433: if (group != null) {
434: paste();
435: }
436: }
437:
438: /**
439: * Prompts the user for a new group name and renames the currently selected group
440: * to the supplied name.
441: */
442: public void renameGroup() {
443: DefaultMutableTreeNode group = this .getSelectedGroupNode();
444: if (group == null)
445: return;
446: String oldName = (String) group.getUserObject();
447: String newName = WbSwingUtilities.getUserInput(SwingUtilities
448: .getWindowAncestor(this ), ResourceMgr
449: .getString("LblNewProfileGroup"), oldName);
450: if (StringUtil.isEmptyString(newName))
451: return;
452: group.setUserObject(newName);
453: renameGroup(group, newName);
454: }
455:
456: private void renameGroup(DefaultMutableTreeNode group,
457: String newGroupName) {
458: if (StringUtil.isEmptyString(newGroupName))
459: return;
460: int count = profileModel.getChildCount(group);
461: for (int i = 0; i < count; i++) {
462: DefaultMutableTreeNode node = (DefaultMutableTreeNode) profileModel
463: .getChild(group, i);
464: ConnectionProfile prof = (ConnectionProfile) node
465: .getUserObject();
466: prof.setGroup(newGroupName);
467: }
468: }
469:
470: /**
471: * Prompts the user for a group name and creates a new group
472: * with the provided name. The new group node is automatically
473: * after creation.
474: * @return the name of the new group or null if the user cancelled the name input
475: */
476: public String addProfileGroup() {
477: String group = WbSwingUtilities.getUserInput(SwingUtilities
478: .getWindowAncestor(this ), ResourceMgr
479: .getString("LblNewProfileGroup"), "");
480: if (StringUtil.isEmptyString(group))
481: return null;
482: List groups = this .profileModel.getGroups();
483: if (groups.contains(group)) {
484: WbSwingUtilities.showErrorMessageKey(SwingUtilities
485: .getWindowAncestor(this ), "ErrGroupNotUnique");
486: return null;
487: }
488: TreePath path = this .profileModel.addGroup(group);
489: selectPath(path);
490: return group;
491: }
492:
493: public void selectPath(TreePath path) {
494: if (path == null)
495: return;
496: expandPath(path);
497: setSelectionPath(path);
498: scrollPathToVisible(path);
499: }
500:
501: private void selectNode(DefaultMutableTreeNode node) {
502: TreeNode[] nodes = this .profileModel.getPathToRoot(node);
503: TreePath path = new TreePath(nodes);
504: this .selectPath(path);
505: }
506:
507: public void valueChanged(TreeSelectionEvent e) {
508: checkActions();
509: }
510:
511: public Insets getAutoscrollInsets() {
512: return this .autoscrollInsets;
513: }
514:
515: public void autoscroll(Point cursorLocation) {
516: Insets insets = getAutoscrollInsets();
517: Rectangle outer = getVisibleRect();
518: Rectangle inner = new Rectangle(outer.x + insets.left, outer.y
519: + insets.top, outer.width
520: - (insets.left + insets.right), outer.height
521: - (insets.top + insets.bottom));
522: if (!inner.contains(cursorLocation)) {
523: Rectangle scrollRect = new Rectangle(cursorLocation.x
524: - insets.left, cursorLocation.y - insets.top,
525: insets.left + insets.right, insets.top
526: + insets.bottom);
527: scrollRectToVisible(scrollRect);
528: }
529: }
530:
531: }
|