001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.mercurial.ui.status;
043:
044: import org.netbeans.modules.versioning.spi.VCSContext;
045: import org.netbeans.modules.mercurial.HgModuleConfig;
046: import org.netbeans.modules.mercurial.Mercurial;
047: import org.netbeans.modules.mercurial.FileStatusCache;
048: import org.netbeans.modules.mercurial.FileInformation;
049: import org.netbeans.modules.mercurial.util.HgUtils;
050: import org.netbeans.modules.mercurial.MercurialAnnotator;
051: import org.netbeans.modules.mercurial.ui.diff.DiffAction;
052: import org.netbeans.modules.mercurial.ui.update.RevertModificationsAction;
053: import org.netbeans.modules.mercurial.ui.commit.CommitAction;
054: import org.netbeans.modules.mercurial.ui.commit.ExcludeFromCommitAction;
055: import org.netbeans.modules.mercurial.ui.annotate.AnnotateAction;
056: import org.openide.explorer.view.NodeTableModel;
057: import org.openide.nodes.*;
058: import org.openide.nodes.PropertySupport.ReadOnly;
059: import org.openide.windows.TopComponent;
060: import org.openide.util.NbBundle;
061: import org.openide.awt.MouseUtils;
062: import org.openide.awt.Mnemonics;
063: import org.netbeans.modules.versioning.util.FilePathCellRenderer;
064: import org.netbeans.modules.versioning.util.TableSorter;
065:
066: import javax.swing.*;
067: import javax.swing.table.DefaultTableCellRenderer;
068: import javax.swing.event.ListSelectionListener;
069: import javax.swing.event.ListSelectionEvent;
070: import javax.swing.event.AncestorListener;
071: import javax.swing.event.AncestorEvent;
072: import java.lang.reflect.InvocationTargetException;
073: import java.awt.event.MouseListener;
074: import java.awt.event.MouseEvent;
075: import java.awt.event.ActionEvent;
076: import java.awt.event.KeyEvent;
077: import java.awt.Component;
078: import java.awt.Color;
079: import java.awt.Point;
080: import java.io.File;
081: import java.util.*;
082: import java.util.logging.Level;
083: import org.netbeans.modules.mercurial.ui.update.ConflictResolvedAction;
084:
085: /**
086: * Controls the {@link #getComponent() tsble} that displays nodes
087: * in the Versioning view. The table is {@link #setTableModel populated)
088: * from VersioningPanel.
089: *
090: * @author Maros Sandor
091: */
092: class SyncTable implements MouseListener, ListSelectionListener,
093: AncestorListener {
094:
095: private NodeTableModel tableModel;
096: private JTable table;
097: private JScrollPane component;
098: private SyncFileNode[] nodes = new SyncFileNode[0];
099:
100: private String[] tableColumns;
101: private TableSorter sorter;
102:
103: /**
104: * Defines labels for Versioning view table columns.
105: */
106: private static final Map<String, String[]> columnLabels = new HashMap<String, String[]>(
107: 4);
108: {
109: ResourceBundle loc = NbBundle.getBundle(SyncTable.class);
110: columnLabels
111: .put(
112: SyncFileNode.COLUMN_NAME_BRANCH,
113: new String[] {
114: loc
115: .getString("CTL_VersioningView_Column_Branch_Title"), // NOI18N
116: loc
117: .getString("CTL_VersioningView_Column_Branch_Desc") }); // NOI18N
118: columnLabels.put(SyncFileNode.COLUMN_NAME_NAME, new String[] {
119: loc.getString("CTL_VersioningView_Column_File_Title"), // NOI18N
120: loc.getString("CTL_VersioningView_Column_File_Desc") }); // NOI18N
121: columnLabels
122: .put(
123: SyncFileNode.COLUMN_NAME_STATUS,
124: new String[] {
125: loc
126: .getString("CTL_VersioningView_Column_Status_Title"), // NOI18N
127: loc
128: .getString("CTL_VersioningView_Column_Status_Desc") }); // NOI18N
129: columnLabels.put(SyncFileNode.COLUMN_NAME_PATH, new String[] {
130: loc.getString("CTL_VersioningView_Column_Path_Title"), // NOI18N
131: loc.getString("CTL_VersioningView_Column_Path_Desc") }); // NOI18N
132: }
133:
134: private static final Comparator NodeComparator = new Comparator() {
135: public int compare(Object o1, Object o2) {
136: Node.Property p1 = (Node.Property) o1;
137: Node.Property p2 = (Node.Property) o2;
138: String sk1 = (String) p1.getValue("sortkey"); // NOI18N
139: if (sk1 != null) {
140: String sk2 = (String) p2.getValue("sortkey"); // NOI18N
141: return sk1.compareToIgnoreCase(sk2);
142: } else {
143: try {
144: String s1 = (String) p1.getValue();
145: String s2 = (String) p2.getValue();
146: return s1.compareToIgnoreCase(s2);
147: } catch (Exception e) {
148: Mercurial.LOG.log(Level.INFO, null, e);
149: return 0;
150: }
151: }
152: }
153: };
154:
155: public SyncTable() {
156: tableModel = new NodeTableModel();
157: sorter = new TableSorter(tableModel);
158: sorter.setColumnComparator(Node.Property.class, NodeComparator);
159: table = new JTable(sorter);
160: sorter.setTableHeader(table.getTableHeader());
161: table.setRowHeight(table.getRowHeight() * 6 / 5);
162: component = new JScrollPane(table,
163: JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
164: JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
165: component.getViewport().setBackground(table.getBackground());
166: Color borderColor = UIManager.getColor("scrollpane_border"); // NOI18N
167: if (borderColor == null)
168: borderColor = UIManager.getColor("controlShadow"); // NOI18N
169: component.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0,
170: borderColor));
171: table.addMouseListener(this );
172: table.setDefaultRenderer(Node.Property.class,
173: new SyncTableCellRenderer());
174: table.getSelectionModel().addListSelectionListener(this );
175: table.addAncestorListener(this );
176: table.getAccessibleContext().setAccessibleName(
177: NbBundle.getMessage(SyncTable.class,
178: "ACSN_VersioningTable")); // NOI18N
179: table.getAccessibleContext().setAccessibleDescription(
180: NbBundle.getMessage(SyncTable.class,
181: "ACSD_VersioningTable")); // NOI18N
182: setColumns(new String[] { SyncFileNode.COLUMN_NAME_NAME,
183: SyncFileNode.COLUMN_NAME_STATUS,
184: SyncFileNode.COLUMN_NAME_PATH });
185: table
186: .getInputMap(
187: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
188: .put(
189: KeyStroke.getKeyStroke(KeyEvent.VK_F10,
190: KeyEvent.SHIFT_DOWN_MASK),
191: "org.openide.actions.PopupAction"); // NOI18N
192: table.getActionMap().put("org.openide.actions.PopupAction",
193: new AbstractAction() { // NOI18N
194: public void actionPerformed(ActionEvent e) {
195: showPopup(org.netbeans.modules.versioning.util.Utils
196: .getPositionForPopup(table));
197: }
198: });
199: }
200:
201: void setDefaultColumnSizes() {
202: SwingUtilities.invokeLater(new Runnable() {
203: public void run() {
204: int width = table.getWidth();
205: if (tableColumns.length == 3) {
206: for (int i = 0; i < tableColumns.length; i++) {
207: if (SyncFileNode.COLUMN_NAME_PATH
208: .equals(tableColumns[i])) {
209: table
210: .getColumnModel()
211: .getColumn(i)
212: .setPreferredWidth(width * 60 / 100);
213: } else {
214: table
215: .getColumnModel()
216: .getColumn(i)
217: .setPreferredWidth(width * 20 / 100);
218: }
219: }
220: } else if (tableColumns.length == 4) {
221: for (int i = 0; i < tableColumns.length; i++) {
222: if (SyncFileNode.COLUMN_NAME_PATH
223: .equals(tableColumns[i])) {
224: table
225: .getColumnModel()
226: .getColumn(i)
227: .setPreferredWidth(width * 55 / 100);
228: } else if (SyncFileNode.COLUMN_NAME_BRANCH
229: .equals(tableColumns[i])) {
230: table
231: .getColumnModel()
232: .getColumn(i)
233: .setPreferredWidth(width * 20 / 100);
234: } else {
235: table
236: .getColumnModel()
237: .getColumn(i)
238: .setPreferredWidth(width * 15 / 100);
239: }
240: }
241: }
242: }
243: });
244: }
245:
246: public void ancestorAdded(AncestorEvent event) {
247: setDefaultColumnSizes();
248: }
249:
250: public void ancestorMoved(AncestorEvent event) {
251: }
252:
253: public void ancestorRemoved(AncestorEvent event) {
254: }
255:
256: public SyncFileNode[] getDisplayedNodes() {
257: int n = sorter.getRowCount();
258: SyncFileNode[] ret = new SyncFileNode[n];
259: for (int i = 0; i < n; i++) {
260: ret[i] = nodes[sorter.modelIndex(i)];
261: }
262: return ret;
263: }
264:
265: public JComponent getComponent() {
266: return component;
267: }
268:
269: /**
270: * Sets visible columns in the Versioning table.
271: *
272: * @param columns array of column names, they must be one of SyncFileNode.COLUMN_NAME_XXXXX constants.
273: */
274: final void setColumns(String[] columns) {
275: if (Arrays.equals(columns, tableColumns))
276: return;
277: setModelProperties(columns);
278: tableColumns = columns;
279: for (int i = 0; i < tableColumns.length; i++) {
280: sorter.setColumnComparator(i, null);
281: sorter.setSortingStatus(i, TableSorter.NOT_SORTED);
282: if (SyncFileNode.COLUMN_NAME_STATUS.equals(tableColumns[i])) {
283: sorter.setSortingStatus(i, TableSorter.ASCENDING);
284: break;
285: }
286: }
287: setDefaultColumnSizes();
288: }
289:
290: private void setModelProperties(String[] columns) {
291: Node.Property[] properties = new Node.Property[columns.length];
292: for (int i = 0; i < columns.length; i++) {
293: String column = columns[i];
294: String[] labels = (String[]) columnLabels.get(column);
295: properties[i] = new ColumnDescriptor(column, String.class,
296: labels[0], labels[1]);
297: }
298: tableModel.setProperties(properties);
299: }
300:
301: void setTableModel(SyncFileNode[] nodes) {
302: this .nodes = nodes;
303: tableModel.setNodes(nodes);
304: }
305:
306: void focus() {
307: table.requestFocus();
308: }
309:
310: private static class ColumnDescriptor extends ReadOnly {
311:
312: @SuppressWarnings("unchecked")
313: public ColumnDescriptor(String name, Class type,
314: String displayName, String shortDescription) {
315: super (name, type, displayName, shortDescription);
316: }
317:
318: public Object getValue() throws IllegalAccessException,
319: InvocationTargetException {
320: return null;
321: }
322: }
323:
324: private void showPopup(final MouseEvent e) {
325: int row = table.rowAtPoint(e.getPoint());
326: if (row != -1) {
327: boolean makeRowSelected = true;
328: int[] selectedrows = table.getSelectedRows();
329:
330: for (int i = 0; i < selectedrows.length; i++) {
331: if (row == selectedrows[i]) {
332: makeRowSelected = false;
333: break;
334: }
335: }
336: if (makeRowSelected) {
337: table.getSelectionModel()
338: .setSelectionInterval(row, row);
339: }
340: }
341: SwingUtilities.invokeLater(new Runnable() {
342: public void run() {
343: // invoke later so the selection on the table will be set first
344: JPopupMenu menu = getPopup();
345: menu.show(table, e.getX(), e.getY());
346: }
347: });
348: }
349:
350: private void showPopup(Point p) {
351: JPopupMenu menu = getPopup();
352: menu.show(table, p.x, p.y);
353: }
354:
355: /**
356: * Constructs contextual Menu: File Node
357: <pre>
358: Open
359: -------------------
360: Diff (default action)
361: Update
362: Commit...
363: --------------------
364: Conflict Resolved (on conflicting file)
365: --------------------
366: Blame
367: Show History...
368: --------------------
369: Revert Modifications (Revert Delete)(Delete)
370: Exclude from Commit (Include in Commit)
371: Ignore (Unignore)
372: </pre>
373: */
374: private JPopupMenu getPopup() {
375:
376: JPopupMenu menu = new JPopupMenu();
377: JMenuItem item;
378: VCSContext context = HgUtils.getCurrentContext(null);
379: ResourceBundle loc = NbBundle.getBundle(Mercurial.class);
380:
381: item = menu.add(new OpenInEditorAction());
382: Mnemonics.setLocalizedText(item, item.getText());
383: menu.add(new JSeparator());
384: item = menu.add(new DiffAction(loc
385: .getString("CTL_PopupMenuItem_Diff"), context)); // NOI18N
386: Mnemonics.setLocalizedText(item, item.getText());
387: item = menu.add(new CommitAction(loc
388: .getString("CTL_PopupMenuItem_Commit"), context)); // NOI18N
389: Mnemonics.setLocalizedText(item, item.getText());
390:
391: menu.add(new JSeparator());
392:
393: item = menu.add(new ConflictResolvedAction(loc
394: .getString("CTL_PopupMenuItem_MarkResolved"), context)); // NOI18N
395: Mnemonics.setLocalizedText(item, item.getText());
396:
397: menu.add(new JSeparator());
398:
399: AnnotateAction tempA = new AnnotateAction(loc
400: .getString("CTL_PopupMenuItem_ShowAnnotations"),
401: context); // NOI18N
402: if (tempA.visible(null)) {
403: tempA = new AnnotateAction(loc
404: .getString("CTL_PopupMenuItem_HideAnnotations"),
405: context); // NOI18N
406: }
407: item = menu.add(tempA);
408: Mnemonics.setLocalizedText(item, item.getText());
409: menu.add(new JSeparator());
410:
411: boolean allLocallyDeleted = true;
412: FileStatusCache cache = Mercurial.getInstance()
413: .getFileStatusCache();
414: Set<File> files = HgUtils.getCurrentContext(null)
415: .getRootFiles();
416:
417: for (File file : files) {
418: FileInformation info = cache.getStatus(file);
419: if (info.getStatus() != FileInformation.STATUS_VERSIONED_DELETEDLOCALLY
420: && info.getStatus() != FileInformation.STATUS_VERSIONED_REMOVEDLOCALLY) {
421: allLocallyDeleted = false;
422: }
423: }
424: if (allLocallyDeleted) {
425: item = menu.add(new RevertModificationsAction(loc
426: .getString("CTL_PopupMenuItem_RevertDelete"),
427: context)); // NOI18N
428: } else {
429: item = menu.add(new RevertModificationsAction(loc
430: .getString("CTL_PopupMenuItem_GetClean"), context)); // NOI18N
431: }
432: Mnemonics.setLocalizedText(item, item.getText());
433:
434: ExcludeFromCommitAction exclude = new ExcludeFromCommitAction(
435: loc.getString("CTL_PopupMenuItem_IncludeInCommit"),
436: context); // NOI18N
437: if (exclude.getActionStatus(null) != exclude.INCLUDING)
438: exclude = new ExcludeFromCommitAction(loc
439: .getString("CTL_PopupMenuItem_ExcludeFromCommit"),
440: context); // NOI18N
441: item = menu.add(exclude);
442: Mnemonics.setLocalizedText(item, item.getText());
443: /*
444: item = menu.add(new SystemActionBridge(SystemAction.get(SearchHistoryAction.class), actionString("CTL_PopupMenuItem_SearchHistory"))); // NOI18N
445: Mnemonics.setLocalizedText(item, item.getText());
446:
447: menu.add(new JSeparator());
448:
449: // item = menu.add(new SystemActionBridge(SystemAction.get(ResolveConflictsAction.class), actionString("CTL_PopupMenuItem_ResolveConflicts"))); // NOI18N
450: // Mnemonics.setLocalizedText(item, item.getText());
451:
452: Action ignoreAction = new SystemActionBridge(SystemAction.get(IgnoreAction.class),
453: ((IgnoreAction)SystemAction.get(IgnoreAction.class)).getActionStatus(files) == IgnoreAction.UNIGNORING ?
454: actionString("CTL_PopupMenuItem_Unignore") : // NOI18N
455: actionString("CTL_PopupMenuItem_Ignore")); // NOI18N
456: item = menu.add(ignoreAction);
457: Mnemonics.setLocalizedText(item, item.getText());
458: */
459:
460: return menu;
461: }
462:
463: /**
464: * Workaround.
465: * I18N Test Wizard searches for keys in syncview package Bundle.properties
466: */
467: private String actionString(String key) {
468: ResourceBundle actionsLoc = NbBundle
469: .getBundle(MercurialAnnotator.class);
470: return actionsLoc.getString(key);
471: }
472:
473: public void mouseEntered(MouseEvent e) {
474: }
475:
476: public void mouseExited(MouseEvent e) {
477: }
478:
479: public void mousePressed(MouseEvent e) {
480: if (e.isPopupTrigger()) {
481: showPopup(e);
482: }
483: }
484:
485: public void mouseReleased(MouseEvent e) {
486: if (e.isPopupTrigger()) {
487: showPopup(e);
488: }
489: }
490:
491: public void mouseClicked(MouseEvent e) {
492: if (SwingUtilities.isLeftMouseButton(e)
493: && MouseUtils.isDoubleClick(e)) {
494: int row = table.rowAtPoint(e.getPoint());
495: if (row == -1)
496: return;
497: row = sorter.modelIndex(row);
498: Action action = nodes[row].getPreferredAction();
499: if (action == null || !action.isEnabled())
500: action = new OpenInEditorAction();
501: if (action.isEnabled()) {
502: action.actionPerformed(new ActionEvent(this , 0, "")); // NOI18N
503: }
504: }
505: }
506:
507: public void valueChanged(ListSelectionEvent e) {
508: List<SyncFileNode> selectedNodes = new ArrayList<SyncFileNode>();
509: ListSelectionModel selectionModel = table.getSelectionModel();
510: final TopComponent tc = (TopComponent) SwingUtilities
511: .getAncestorOfClass(TopComponent.class, table);
512: if (tc == null)
513: return; // table is no longer in component hierarchy
514:
515: int min = selectionModel.getMinSelectionIndex();
516: if (min != -1) {
517: int max = selectionModel.getMaxSelectionIndex();
518: for (int i = min; i <= max; i++) {
519: if (selectionModel.isSelectedIndex(i)) {
520: int idx = sorter.modelIndex(i);
521: selectedNodes.add(nodes[idx]);
522: }
523: }
524: }
525: // this method may be called outside of AWT if a node fires change events from some other thread, see #79174
526: final Node[] nodes = selectedNodes
527: .toArray(new Node[selectedNodes.size()]);
528: if (SwingUtilities.isEventDispatchThread()) {
529: tc.setActivatedNodes(nodes);
530: } else {
531: SwingUtilities.invokeLater(new Runnable() {
532: public void run() {
533: tc.setActivatedNodes(nodes);
534: }
535: });
536: }
537: }
538:
539: private class SyncTableCellRenderer extends
540: DefaultTableCellRenderer {
541:
542: private FilePathCellRenderer pathRenderer = new FilePathCellRenderer();
543:
544: public Component getTableCellRendererComponent(JTable table,
545: Object value, boolean isSelected, boolean hasFocus,
546: int row, int column) {
547: Component renderer;
548: int modelColumnIndex = table
549: .convertColumnIndexToModel(column);
550: if (modelColumnIndex == 0) {
551: SyncFileNode node = nodes[sorter.modelIndex(row)];
552: if (!isSelected) {
553: value = "<html>" + node.getHtmlDisplayName(); // NOI18N
554: }
555: if (HgModuleConfig.getDefault().isExcludedFromCommit(
556: node.getFile().getAbsolutePath())) {
557: String nodeName = node.getDisplayName();
558: if (isSelected) {
559: value = "<html><s>" + nodeName + "</s></html>"; // NOI18N
560: } else {
561: value = "<html><s>"
562: + Mercurial
563: .getInstance()
564: .getMercurialAnnotator()
565: .annotateNameHtml(
566: nodeName,
567: node
568: .getFileInformation(),
569: null) + "</s>"; // NOI18N
570: }
571: }
572: }
573: if (modelColumnIndex == 2) {
574: renderer = pathRenderer
575: .getTableCellRendererComponent(table, value,
576: isSelected, hasFocus, row, column);
577: } else {
578: renderer = super .getTableCellRendererComponent(table,
579: value, isSelected, hasFocus, row, column);
580: }
581: if (renderer instanceof JComponent) {
582: String path = nodes[sorter.modelIndex(row)].getFile()
583: .getAbsolutePath();
584: ((JComponent) renderer).setToolTipText(path);
585: }
586: return renderer;
587: }
588: }
589: }
|