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-2007 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: package org.netbeans.modules.subversion.ui.update;
042:
043: import org.netbeans.modules.versioning.util.VersioningEvent;
044: import org.openide.explorer.view.NodeTableModel;
045: import org.openide.util.NbBundle;
046: import org.openide.nodes.Node;
047: import org.openide.nodes.PropertySupport;
048: import org.openide.windows.TopComponent;
049: import org.openide.awt.MouseUtils;
050: import org.netbeans.modules.versioning.util.TableSorter;
051: import org.netbeans.modules.versioning.util.FilePathCellRenderer;
052:
053: import javax.swing.event.ListSelectionListener;
054: import javax.swing.event.AncestorListener;
055: import javax.swing.event.AncestorEvent;
056: import javax.swing.event.ListSelectionEvent;
057: import javax.swing.*;
058: import javax.swing.table.DefaultTableCellRenderer;
059: import java.awt.event.MouseListener;
060: import java.awt.event.ActionEvent;
061: import java.awt.event.MouseEvent;
062: import java.awt.Color;
063: import java.awt.Component;
064: import java.io.File;
065: import java.util.*;
066: import java.lang.reflect.InvocationTargetException;
067: import java.util.logging.Level;
068: import org.netbeans.modules.subversion.FileInformation;
069: import org.netbeans.modules.subversion.FileStatusCache;
070: import org.netbeans.modules.subversion.Subversion;
071: import org.netbeans.modules.subversion.ui.properties.SvnPropertiesAction;
072: import org.netbeans.modules.subversion.ui.status.OpenInEditorAction;
073: import org.netbeans.modules.versioning.util.VersioningListener;
074:
075: /**
076: * Table that displays nodes in the Update Results view.
077: *
078: * @author Maros Sandor
079: */
080: class UpdateResultsTable implements MouseListener,
081: ListSelectionListener, AncestorListener, VersioningListener {
082:
083: private NodeTableModel tableModel;
084: private JTable table;
085: private JScrollPane component;
086: private UpdateResultNode[] nodes = new UpdateResultNode[0];
087:
088: private String[] tableColumns;
089: private TableSorter sorter;
090:
091: /**
092: * Defines labels for Versioning view table columns.
093: */
094: private final Map<String, String[]> columnLabels = new HashMap<String, String[]>(
095: 4);
096: {
097: ResourceBundle loc = NbBundle
098: .getBundle(UpdateResultsTable.class);
099: columnLabels
100: .put(
101: UpdateResultNode.COLUMN_NAME_NAME,
102: new String[] {
103: loc
104: .getString("CTL_UpdateResults_Column_File_Title"), // NOI18N
105: loc
106: .getString("CTL_UpdateResults_Column_File_Desc") }); // NOI18N
107: columnLabels
108: .put(
109: UpdateResultNode.COLUMN_NAME_STATUS,
110: new String[] {
111: loc
112: .getString("CTL_UpdateResults_Column_Status_Title"), // NOI18N
113: loc
114: .getString("CTL_UpdateResults_Column_Status_Desc") }); // NOI18N
115: columnLabels
116: .put(
117: UpdateResultNode.COLUMN_NAME_PATH,
118: new String[] {
119: loc
120: .getString("CTL_UpdateResults_Column_Path_Title"), // NOI18N
121: loc
122: .getString("CTL_UpdateResults_Column_Path_Desc") }); // NOI18N
123: }
124:
125: private static final Comparator NodeComparator = new Comparator() {
126: public int compare(Object o1, Object o2) {
127: Node.Property p1 = (Node.Property) o1;
128: Node.Property p2 = (Node.Property) o2;
129: String sk1 = (String) p1.getValue("sortkey"); // NOI18N
130: if (sk1 != null) {
131: String sk2 = (String) p2.getValue("sortkey"); // NOI18N
132: return sk1.compareToIgnoreCase(sk2);
133: } else {
134: try {
135: String s1 = (String) p1.getValue();
136: String s2 = (String) p2.getValue();
137: return s1.compareToIgnoreCase(s2);
138: } catch (Exception e) {
139: Subversion.LOG.log(Level.SEVERE, null, e);
140: return 0;
141: }
142: }
143: }
144: };
145:
146: public UpdateResultsTable() {
147: tableModel = new NodeTableModel();
148: Subversion.getInstance().getStatusCache()
149: .addVersioningListener(this );
150: sorter = new TableSorter(tableModel);
151: sorter.setColumnComparator(Node.Property.class, NodeComparator);
152: table = new JTable(sorter);
153: sorter.setTableHeader(table.getTableHeader());
154: int height = new JLabel("FONTSIZE").getPreferredSize().height * 6 / 5; // NOI18N
155: table.setRowHeight(height);
156: component = new JScrollPane(table,
157: JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
158: JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
159: component.getViewport().setBackground(table.getBackground());
160: Color borderColor = UIManager.getColor("scrollpane_border"); // NOI18N
161: if (borderColor == null)
162: borderColor = UIManager.getColor("controlShadow"); // NOI18N
163: component.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0,
164: borderColor));
165: table.addMouseListener(this );
166: table.setDefaultRenderer(Node.Property.class,
167: new UpdateResultsTable.SyncTableCellRenderer());
168: table.getSelectionModel().addListSelectionListener(this );
169: table.addAncestorListener(this );
170: table.getAccessibleContext().setAccessibleName(
171: NbBundle.getMessage(UpdateResultsTable.class,
172: "ACSN_UpdateResults")); // NOI18N
173: table.getAccessibleContext().setAccessibleDescription(
174: NbBundle.getMessage(UpdateResultsTable.class,
175: "ACSD_UpdateResults")); // NOI18N
176: setColumns(new String[] { UpdateResultNode.COLUMN_NAME_NAME,
177: UpdateResultNode.COLUMN_NAME_STATUS,
178: UpdateResultNode.COLUMN_NAME_PATH });
179: }
180:
181: void setDefaultColumnSizes() {
182: SwingUtilities.invokeLater(new Runnable() {
183: public void run() {
184: int width = table.getWidth();
185: for (int i = 0; i < tableColumns.length; i++) {
186: if (UpdateResultNode.COLUMN_NAME_PATH
187: .equals(tableColumns[i])) {
188: table.getColumnModel().getColumn(i)
189: .setPreferredWidth(width * 60 / 100);
190: } else {
191: table.getColumnModel().getColumn(i)
192: .setPreferredWidth(width * 20 / 100);
193: }
194: }
195: }
196: });
197: }
198:
199: public void ancestorAdded(AncestorEvent event) {
200: setDefaultColumnSizes();
201: }
202:
203: public void ancestorMoved(AncestorEvent event) {
204: }
205:
206: public void ancestorRemoved(AncestorEvent event) {
207: }
208:
209: public UpdateResultNode[] getDisplayedNodes() {
210: int n = sorter.getRowCount();
211: UpdateResultNode[] ret = new UpdateResultNode[n];
212: for (int i = 0; i < n; i++) {
213: ret[i] = nodes[sorter.modelIndex(i)];
214: }
215: return ret;
216: }
217:
218: public JComponent getComponent() {
219: return component;
220: }
221:
222: /**
223: * Sets visible columns in the Versioning table.
224: *
225: * @param columns array of column names, they must be one of SyncFileNode.COLUMN_NAME_XXXXX constants.
226: */
227: final void setColumns(String[] columns) {
228: if (Arrays.equals(columns, tableColumns))
229: return;
230: setDefaultColumnSizes();
231: setModelProperties(columns);
232: tableColumns = columns;
233: for (int i = 0; i < tableColumns.length; i++) {
234: sorter.setColumnComparator(i, null);
235: sorter.setSortingStatus(i, TableSorter.NOT_SORTED);
236: if (UpdateResultNode.COLUMN_NAME_STATUS
237: .equals(tableColumns[i])) {
238: sorter.setSortingStatus(i, TableSorter.ASCENDING);
239: break;
240: }
241: }
242: }
243:
244: private void setModelProperties(String[] columns) {
245: Node.Property[] properties = new Node.Property[columns.length];
246: for (int i = 0; i < columns.length; i++) {
247: String column = columns[i];
248: String[] labels = (String[]) columnLabels.get(column);
249: properties[i] = new UpdateResultsTable.ColumnDescriptor(
250: column, String.class, labels[0], labels[1]);
251: }
252: tableModel.setProperties(properties);
253: }
254:
255: void setTableModel(UpdateResultNode[] nodes) {
256: this .nodes = nodes;
257: tableModel.setNodes(nodes);
258: }
259:
260: void focus() {
261: table.requestFocus();
262: }
263:
264: private static class ColumnDescriptor extends
265: PropertySupport.ReadOnly {
266:
267: public ColumnDescriptor(String name, Class type,
268: String displayName, String shortDescription) {
269: super (name, type, displayName, shortDescription);
270: }
271:
272: public Object getValue() throws IllegalAccessException,
273: InvocationTargetException {
274: return null;
275: }
276: }
277:
278: public void mouseEntered(MouseEvent e) {
279: }
280:
281: public void mouseExited(MouseEvent e) {
282: }
283:
284: public void mousePressed(MouseEvent e) {
285: if (e.isPopupTrigger()) {
286: onPopup(e);
287: }
288: }
289:
290: public void mouseReleased(MouseEvent e) {
291: if (e.isPopupTrigger()) {
292: onPopup(e);
293: }
294: }
295:
296: public void mouseClicked(MouseEvent e) {
297: if (SwingUtilities.isLeftMouseButton(e)
298: && MouseUtils.isDoubleClick(e)) {
299: int row = table.rowAtPoint(e.getPoint());
300: if (row == -1)
301: return;
302: row = sorter.modelIndex(row);
303: performOpen(row);
304: }
305: }
306:
307: private interface ActionEvaluator {
308: boolean isAction(FileUpdateInfo info);
309: }
310:
311: private static ActionEvaluator conflictEvaluator = new ActionEvaluator() {
312: public boolean isAction(FileUpdateInfo info) {
313: return (info.getAction() & FileUpdateInfo.ACTION_CONFLICTED & FileUpdateInfo.ACTION_TYPE_FILE) != 0;
314: }
315: };
316: private static ActionEvaluator notDeletedEvaluator = new ActionEvaluator() {
317: public boolean isAction(FileUpdateInfo info) {
318: return (info.getAction() & ~FileUpdateInfo.ACTION_DELETED) != 0;
319: }
320: };
321:
322: private void onPopup(MouseEvent e) {
323: final int[] selection = table.getSelectedRows();
324: JPopupMenu menu = new JPopupMenu();
325: menu.add(new JMenuItem(new AbstractAction(NbBundle.getMessage(
326: UpdateResultsTable.class, "CTL_MenuItem_Open")) { // NOI18N
327: {
328: setEnabled(selection.length == 1
329: && hasAction(selection,
330: notDeletedEvaluator));
331: }
332:
333: public void actionPerformed(ActionEvent e) {
334: performOpen(selection[0]);
335: }
336: }));
337: menu.add(new JMenuItem(new AbstractAction(NbBundle.getMessage(
338: UpdateResultsTable.class,
339: "CTL_MenuItem_ResolveConflicts")) { // NOI18N
340: {
341: setEnabled(selection.length > -1
342: && hasAction(selection,
343: conflictEvaluator));
344: }
345:
346: public void actionPerformed(ActionEvent e) {
347: ResolveConflictsAction
348: .resolveConflicts(getSelectedFiles(selection));
349: }
350: }));
351: menu.show(e.getComponent(), e.getX(), e.getY());
352: }
353:
354: private boolean hasAction(int[] selection, ActionEvaluator ae) {
355: for (int idx : selection) {
356: int nodesIdx = sorter.modelIndex(idx);
357: Node node = nodes[nodesIdx];
358: FileUpdateInfo fui = (FileUpdateInfo) node.getLookup()
359: .lookup(FileUpdateInfo.class);
360: if (fui != null && ae.isAction(fui)) {
361: continue;
362: }
363: return false;
364: }
365: return true;
366: }
367:
368: private void performOpen(int idx) {
369: FileUpdateInfo fui = (FileUpdateInfo) nodes[idx].getLookup()
370: .lookup(FileUpdateInfo.class);
371: if (fui == null) {
372: return;
373: }
374: if ((fui.getAction() & FileUpdateInfo.ACTION_TYPE_FILE) != 0) {
375: performOpenInEditorAction(nodes[idx]);
376: } else {
377: performOpenSvnProperties(fui.getFile());
378: }
379: }
380:
381: private void performOpenInEditorAction(Node node) {
382: // XXX how is this supposed to work ???
383: Action action = node.getPreferredAction();
384: if (action == null || !action.isEnabled())
385: action = new OpenInEditorAction();
386: if (action.isEnabled()) {
387: action.actionPerformed(new ActionEvent(this , 0, "")); // NOI18N
388: }
389: }
390:
391: private void performOpenSvnProperties(File file) {
392: SvnPropertiesAction.openProperties(new File[] { file }, file
393: .getName());
394: }
395:
396: private File[] getSelectedFiles(int[] selection) {
397: List<File> files = new ArrayList<File>();
398: for (int idx : selection) {
399: int nodesIdx = sorter.modelIndex(idx);
400: Node node = nodes[nodesIdx];
401: FileUpdateInfo fui = (FileUpdateInfo) node.getLookup()
402: .lookup(FileUpdateInfo.class);
403: if (fui == null) {
404: continue;
405: }
406: files.add(fui.getFile());
407: }
408: return files.toArray(new File[files.size()]);
409: }
410:
411: public void valueChanged(ListSelectionEvent e) {
412: final List<Node> selectedNodes = new ArrayList<Node>();
413: ListSelectionModel selectionModel = table.getSelectionModel();
414: final TopComponent tc = (TopComponent) SwingUtilities
415: .getAncestorOfClass(TopComponent.class, table);
416: if (tc == null)
417: return; // table is no longer in component hierarchy
418:
419: int min = selectionModel.getMinSelectionIndex();
420: if (min > -1) {
421: int max = selectionModel.getMaxSelectionIndex();
422: for (int i = min; i <= max; i++) {
423: if (selectionModel.isSelectedIndex(i)) {
424: int idx = sorter.modelIndex(i);
425: selectedNodes.add(nodes[idx]);
426: }
427: }
428: }
429: SwingUtilities.invokeLater(new Runnable() {
430: public void run() {
431: tc.setActivatedNodes((Node[]) selectedNodes
432: .toArray(new Node[selectedNodes.size()]));
433: }
434: });
435: }
436:
437: private class SyncTableCellRenderer extends
438: DefaultTableCellRenderer {
439:
440: private FilePathCellRenderer pathRenderer = new FilePathCellRenderer();
441:
442: public Component getTableCellRendererComponent(JTable table,
443: Object value, boolean isSelected, boolean hasFocus,
444: int row, int column) {
445: Component renderer;
446: int modelColumnIndex = table
447: .convertColumnIndexToModel(column);
448: if (modelColumnIndex == 0) {
449: UpdateResultNode node = nodes[sorter.modelIndex(row)];
450: if (!isSelected) {
451: value = "<html>" + node.getHtmlDisplayName(); // NOI18N
452: }
453: }
454: if (modelColumnIndex == 2) {
455: renderer = pathRenderer
456: .getTableCellRendererComponent(table, value,
457: isSelected, hasFocus, row, column);
458: } else {
459: renderer = super .getTableCellRendererComponent(table,
460: value, isSelected, hasFocus, row, column);
461: }
462: if (renderer instanceof JComponent) {
463: String path = nodes[sorter.modelIndex(row)].getInfo()
464: .getFile().getAbsolutePath();
465: ((JComponent) renderer).setToolTipText(path);
466: }
467: return renderer;
468: }
469: }
470:
471: public void versioningEvent(VersioningEvent event) {
472: if (nodes.length == 0) {
473: return;
474: }
475:
476: if (event.getId() == FileStatusCache.EVENT_FILE_STATUS_CHANGED) {
477: File changedFile = (File) event.getParams()[0];
478: FileInformation newFileInfo = (FileInformation) event
479: .getParams()[2];
480:
481: List<UpdateResultNode> nodesList = new ArrayList<UpdateResultNode>();
482: boolean touched = false;
483: for (UpdateResultNode node : nodes) {
484: FileUpdateInfo fui = (FileUpdateInfo) node.getLookup()
485: .lookup(FileUpdateInfo.class);
486: if (fui != null) {
487: int action = fui.getAction();
488: if ((action & FileUpdateInfo.ACTION_CONFLICTED & FileUpdateInfo.ACTION_TYPE_FILE) != 0
489: && fui.getFile().equals(changedFile)
490: && ((newFileInfo.getStatus() & newFileInfo.STATUS_VERSIONED_CONFLICT) == 0)) {
491: action &= ~FileUpdateInfo.ACTION_CONFLICTED;
492: action |= FileUpdateInfo.ACTION_CONFLICTED_RESOLVED;
493:
494: FileUpdateInfo newFui = new FileUpdateInfo(fui
495: .getFile(), action);
496: nodesList.add(new UpdateResultNode(newFui));
497: touched = true;
498: } else {
499: nodesList.add(node);
500: }
501: } else {
502: nodesList.add(node);
503: }
504: }
505:
506: // XXX reschedule !!!
507: if (touched) {
508: setTableModel(nodesList
509: .toArray(new UpdateResultNode[nodesList.size()]));
510: }
511: }
512: }
513: }
|