Apply code conventions for project
[nbgit.git] / src / org / nbgit / ui / status / SyncTable.java
blob348cb43061615c36a2e8c032568a4ca0b42cfa79
1 package org.nbgit.ui.status;
3 import java.awt.Color;
4 import java.awt.Component;
5 import java.awt.Point;
6 import java.awt.event.ActionEvent;
7 import java.awt.event.KeyEvent;
8 import java.awt.event.MouseEvent;
9 import java.awt.event.MouseListener;
10 import java.io.File;
11 import java.lang.reflect.InvocationTargetException;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Comparator;
15 import java.util.HashMap;
16 import java.util.List;
17 import java.util.Map;
18 import java.util.ResourceBundle;
19 import java.util.Set;
20 import java.util.logging.Level;
21 import javax.swing.AbstractAction;
22 import javax.swing.Action;
23 import javax.swing.BorderFactory;
24 import javax.swing.JComponent;
25 import javax.swing.JMenuItem;
26 import javax.swing.JPopupMenu;
27 import javax.swing.JScrollPane;
28 import javax.swing.JSeparator;
29 import javax.swing.JTable;
30 import javax.swing.KeyStroke;
31 import javax.swing.ListSelectionModel;
32 import javax.swing.SwingUtilities;
33 import javax.swing.UIManager;
34 import javax.swing.event.AncestorEvent;
35 import javax.swing.event.AncestorListener;
36 import javax.swing.event.ListSelectionEvent;
37 import javax.swing.event.ListSelectionListener;
38 import javax.swing.table.DefaultTableCellRenderer;
39 import org.nbgit.StatusInfo;
40 import org.nbgit.StatusCache;
41 import org.nbgit.Git;
42 import org.nbgit.GitAnnotator;
43 import org.nbgit.GitModuleConfig;
44 import org.nbgit.util.GitUtils;
45 import org.nbgit.ui.commit.CommitAction;
46 import org.nbgit.ui.commit.ExcludeFromCommitAction;
47 import org.nbgit.ui.diff.DiffAction;
48 import org.nbgit.ui.log.SearchHistoryAction;
49 import org.nbgit.ui.update.RevertModificationsAction;
50 import org.nbgit.util.HtmlFormatter;
51 import org.netbeans.modules.versioning.spi.VCSContext;
52 import org.netbeans.modules.versioning.util.FilePathCellRenderer;
53 import org.netbeans.modules.versioning.util.TableSorter;
54 import org.openide.awt.Mnemonics;
55 import org.openide.awt.MouseUtils;
56 import org.openide.explorer.view.NodeTableModel;
57 import org.openide.nodes.Node;
58 import org.openide.nodes.PropertySupport.ReadOnly;
59 import org.openide.util.NbBundle;
60 import org.openide.windows.TopComponent;
62 /**
63 * Controls the {@link #getComponent() table} that displays nodes
64 * in the Versioning view. The table is {@link #setTableModel populated)
65 * from VersioningPanel.
67 * @author Maros Sandor
69 class SyncTable implements MouseListener, ListSelectionListener, AncestorListener {
71 private NodeTableModel tableModel;
72 private JTable table;
73 private JScrollPane component;
74 private SyncFileNode[] nodes = new SyncFileNode[0];
75 private String[] tableColumns;
76 private TableSorter sorter;
77 /**
78 * Defines labels for Versioning view table columns.
80 private static final Map<String, String[]> columnLabels = new HashMap<String, String[]>(4);
84 ResourceBundle loc = NbBundle.getBundle(SyncTable.class);
85 columnLabels.put(SyncFileNode.COLUMN_NAME_BRANCH, new String[]{
86 loc.getString("CTL_VersioningView_Column_Branch_Title"), // NOI18N
87 loc.getString("CTL_VersioningView_Column_Branch_Desc")
88 }); // NOI18N
89 columnLabels.put(SyncFileNode.COLUMN_NAME_NAME, new String[]{
90 loc.getString("CTL_VersioningView_Column_File_Title"), // NOI18N
91 loc.getString("CTL_VersioningView_Column_File_Desc")
92 }); // NOI18N
93 columnLabels.put(SyncFileNode.COLUMN_NAME_STATUS, new String[]{
94 loc.getString("CTL_VersioningView_Column_Status_Title"), // NOI18N
95 loc.getString("CTL_VersioningView_Column_Status_Desc")
96 }); // NOI18N
97 columnLabels.put(SyncFileNode.COLUMN_NAME_PATH, new String[]{
98 loc.getString("CTL_VersioningView_Column_Path_Title"), // NOI18N
99 loc.getString("CTL_VersioningView_Column_Path_Desc")
100 }); // NOI18N
102 private static final Comparator NodeComparator = new Comparator() {
104 public int compare(Object o1, Object o2) {
105 Node.Property p1 = (Node.Property) o1;
106 Node.Property p2 = (Node.Property) o2;
107 String sk1 = (String) p1.getValue("sortkey"); // NOI18N
108 if (sk1 != null) {
109 String sk2 = (String) p2.getValue("sortkey"); // NOI18N
110 return sk1.compareToIgnoreCase(sk2);
111 } else {
112 try {
113 String s1 = (String) p1.getValue();
114 String s2 = (String) p2.getValue();
115 return s1.compareToIgnoreCase(s2);
116 } catch (Exception e) {
117 Git.LOG.log(Level.INFO, null, e);
118 return 0;
124 public SyncTable() {
125 tableModel = new NodeTableModel();
126 sorter = new TableSorter(tableModel);
127 sorter.setColumnComparator(Node.Property.class, NodeComparator);
128 table = new JTable(sorter);
129 sorter.setTableHeader(table.getTableHeader());
130 table.setRowHeight(table.getRowHeight() * 6 / 5);
131 component = new JScrollPane(table, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
132 component.getViewport().setBackground(table.getBackground());
133 Color borderColor = UIManager.getColor("scrollpane_border"); // NOI18N
134 if (borderColor == null) {
135 borderColor = UIManager.getColor("controlShadow"); // NOI18N
137 component.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, borderColor));
138 table.addMouseListener(this);
139 table.setDefaultRenderer(Node.Property.class, new SyncTableCellRenderer());
140 table.getSelectionModel().addListSelectionListener(this);
141 table.addAncestorListener(this);
142 table.getAccessibleContext().setAccessibleName(NbBundle.getMessage(SyncTable.class, "ACSN_VersioningTable")); // NOI18N
143 table.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(SyncTable.class, "ACSD_VersioningTable")); // NOI18N
144 setColumns(new String[]{
145 SyncFileNode.COLUMN_NAME_NAME,
146 SyncFileNode.COLUMN_NAME_STATUS,
147 SyncFileNode.COLUMN_NAME_PATH
149 table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
150 KeyStroke.getKeyStroke(KeyEvent.VK_F10, KeyEvent.SHIFT_DOWN_MASK), "org.openide.actions.PopupAction"); // NOI18N
151 table.getActionMap().put("org.openide.actions.PopupAction", new AbstractAction() { // NOI18N
153 public void actionPerformed(ActionEvent e) {
154 showPopup(org.netbeans.modules.versioning.util.Utils.getPositionForPopup(table));
159 void setDefaultColumnSizes() {
160 SwingUtilities.invokeLater(new Runnable() {
162 public void run() {
163 int width = table.getWidth();
164 if (tableColumns.length == 3) {
165 for (int i = 0; i < tableColumns.length; i++) {
166 if (SyncFileNode.COLUMN_NAME_PATH.equals(tableColumns[i])) {
167 table.getColumnModel().getColumn(i).setPreferredWidth(width * 60 / 100);
168 } else {
169 table.getColumnModel().getColumn(i).setPreferredWidth(width * 20 / 100);
172 } else if (tableColumns.length == 4) {
173 for (int i = 0; i < tableColumns.length; i++) {
174 if (SyncFileNode.COLUMN_NAME_PATH.equals(tableColumns[i])) {
175 table.getColumnModel().getColumn(i).setPreferredWidth(width * 55 / 100);
176 } else if (SyncFileNode.COLUMN_NAME_BRANCH.equals(tableColumns[i])) {
177 table.getColumnModel().getColumn(i).setPreferredWidth(width * 20 / 100);
178 } else {
179 table.getColumnModel().getColumn(i).setPreferredWidth(width * 15 / 100);
187 public void ancestorAdded(AncestorEvent event) {
188 setDefaultColumnSizes();
191 public void ancestorMoved(AncestorEvent event) {
194 public void ancestorRemoved(AncestorEvent event) {
197 public SyncFileNode[] getDisplayedNodes() {
198 int n = sorter.getRowCount();
199 SyncFileNode[] ret = new SyncFileNode[n];
200 for (int i = 0; i < n; i++) {
201 ret[i] = nodes[sorter.modelIndex(i)];
203 return ret;
206 public JComponent getComponent() {
207 return component;
211 * Sets visible columns in the Versioning table.
213 * @param columns array of column names, they must be one of SyncFileNode.COLUMN_NAME_XXXXX constants.
215 final void setColumns(String[] columns) {
216 if (Arrays.equals(columns, tableColumns)) {
217 return;
219 setModelProperties(columns);
220 tableColumns = columns;
221 for (int i = 0; i < tableColumns.length; i++) {
222 sorter.setColumnComparator(i, null);
223 sorter.setSortingStatus(i, TableSorter.NOT_SORTED);
224 if (SyncFileNode.COLUMN_NAME_STATUS.equals(tableColumns[i])) {
225 sorter.setSortingStatus(i, TableSorter.ASCENDING);
226 break;
229 setDefaultColumnSizes();
232 private void setModelProperties(String[] columns) {
233 Node.Property[] properties = new Node.Property[columns.length];
234 for (int i = 0; i < columns.length; i++) {
235 String column = columns[i];
236 String[] labels = columnLabels.get(column);
237 properties[i] = new ColumnDescriptor(column, String.class, labels[0], labels[1]);
239 tableModel.setProperties(properties);
242 void setTableModel(SyncFileNode[] nodes) {
243 this.nodes = nodes;
244 tableModel.setNodes(nodes);
247 void focus() {
248 table.requestFocus();
251 private static class ColumnDescriptor extends ReadOnly {
253 @SuppressWarnings("unchecked")
254 public ColumnDescriptor(String name, Class type, String displayName, String shortDescription) {
255 super(name, type, displayName, shortDescription);
258 public Object getValue() throws IllegalAccessException, InvocationTargetException {
259 return null;
263 private void showPopup(final MouseEvent e) {
264 int row = table.rowAtPoint(e.getPoint());
265 if (row != -1) {
266 boolean makeRowSelected = true;
267 int[] selectedrows = table.getSelectedRows();
269 for (int i = 0; i < selectedrows.length; i++) {
270 if (row == selectedrows[i]) {
271 makeRowSelected = false;
272 break;
275 if (makeRowSelected) {
276 table.getSelectionModel().setSelectionInterval(row, row);
279 SwingUtilities.invokeLater(new Runnable() {
281 public void run() {
282 // invoke later so the selection on the table will be set first
283 JPopupMenu menu = getPopup();
284 menu.show(table, e.getX(), e.getY());
289 private void showPopup(Point p) {
290 JPopupMenu menu = getPopup();
291 menu.show(table, p.x, p.y);
295 * Constructs contextual Menu: File Node
296 <pre>
297 Open
298 -------------------
299 Diff (default action)
300 Update
301 Commit...
302 --------------------
303 Conflict Resolved (on conflicting file)
304 --------------------
305 Blame
306 Show History...
307 --------------------
308 Revert Modifications (Revert Delete)(Delete)
309 Exclude from Commit (Include in Commit)
310 Ignore (Unignore)
311 </pre>
313 private JPopupMenu getPopup() {
315 JPopupMenu menu = new JPopupMenu();
316 JMenuItem item;
317 VCSContext context = GitUtils.getCurrentContext(null);
318 ResourceBundle loc = NbBundle.getBundle(Git.class);
320 item = menu.add(new OpenInEditorAction());
321 Mnemonics.setLocalizedText(item, item.getText());
322 menu.add(new JSeparator());
323 item = menu.add(new DiffAction(loc.getString("CTL_PopupMenuItem_Diff"), context)); // NOI18N
324 Mnemonics.setLocalizedText(item, item.getText());
325 item = menu.add(new CommitAction(loc.getString("CTL_PopupMenuItem_Commit"), context)); // NOI18N
326 Mnemonics.setLocalizedText(item, item.getText());
329 menu.add(new JSeparator());
331 item = menu.add(new ConflictResolvedAction(loc.getString("CTL_PopupMenuItem_MarkResolved"), context)); // NOI18N
332 Mnemonics.setLocalizedText(item, item.getText());
333 menu.add(new JSeparator());
337 AnnotateAction tempA = new AnnotateAction(loc.getString("CTL_PopupMenuItem_ShowAnnotations"), context); // NOI18N
338 if (tempA.visible(null)) {
339 tempA = new AnnotateAction(loc.getString("CTL_PopupMenuItem_HideAnnotations"), context); // NOI18N
341 item = menu.add(tempA);
342 Mnemonics.setLocalizedText(item, item.getText());
344 menu.add(new JSeparator());
346 boolean allLocallyDeleted = true;
347 StatusCache cache = Git.getInstance().getStatusCache();
348 Set<File> files = GitUtils.getCurrentContext(null).getRootFiles();
350 for (File file : files) {
351 StatusInfo info = cache.getStatus(file);
352 if (info.getStatus() != StatusInfo.STATUS_VERSIONED_DELETEDLOCALLY && info.getStatus() != StatusInfo.STATUS_VERSIONED_REMOVEDLOCALLY) {
353 allLocallyDeleted = false;
356 if (allLocallyDeleted) {
357 item = menu.add(new RevertModificationsAction(loc.getString("CTL_PopupMenuItem_RevertDelete"), context));
358 } else {
359 item = menu.add(new RevertModificationsAction(loc.getString("CTL_PopupMenuItem_GetClean"), context));
361 Mnemonics.setLocalizedText(item, item.getText());
363 ExcludeFromCommitAction exclude = new ExcludeFromCommitAction(loc.getString("CTL_PopupMenuItem_IncludeInCommit"), context); // NOI18N
364 if (exclude.getActionStatus(null) != ExcludeFromCommitAction.INCLUDING) {
365 exclude = new ExcludeFromCommitAction(loc.getString("CTL_PopupMenuItem_ExcludeFromCommit"), context); // NOI18N
367 item = menu.add(exclude);
368 Mnemonics.setLocalizedText(item, item.getText());
371 item = menu.add(new SystemActionBridge(SystemAction.get(SearchHistoryAction.class), actionString("CTL_PopupMenuItem_SearchHistory"))); // NOI18N
372 Mnemonics.setLocalizedText(item, item.getText());
374 menu.add(new JSeparator());
376 // item = menu.add(new SystemActionBridge(SystemAction.get(ResolveConflictsAction.class), actionString("CTL_PopupMenuItem_ResolveConflicts"))); // NOI18N
377 // Mnemonics.setLocalizedText(item, item.getText());
379 Action ignoreAction = new SystemActionBridge(SystemAction.get(IgnoreAction.class),
380 ((IgnoreAction)SystemAction.get(IgnoreAction.class)).getActionStatus(files) == IgnoreAction.UNIGNORING ?
381 actionString("CTL_PopupMenuItem_Unignore") : // NOI18N
382 actionString("CTL_PopupMenuItem_Ignore")); // NOI18N
383 item = menu.add(ignoreAction);
384 Mnemonics.setLocalizedText(item, item.getText());
387 return menu;
391 * Workaround.
392 * I18N Test Wizard searches for keys in syncview package Bundle.properties
394 private String actionString(String key) {
395 ResourceBundle actionsLoc = NbBundle.getBundle(GitAnnotator.class);
396 return actionsLoc.getString(key);
399 public void mouseEntered(MouseEvent e) {
402 public void mouseExited(MouseEvent e) {
405 public void mousePressed(MouseEvent e) {
406 if (e.isPopupTrigger()) {
407 showPopup(e);
411 public void mouseReleased(MouseEvent e) {
412 if (e.isPopupTrigger()) {
413 showPopup(e);
417 public void mouseClicked(MouseEvent e) {
418 if (SwingUtilities.isLeftMouseButton(e) && MouseUtils.isDoubleClick(e)) {
419 int row = table.rowAtPoint(e.getPoint());
420 if (row == -1) {
421 return;
423 row = sorter.modelIndex(row);
424 Action action = nodes[row].getPreferredAction();
425 if (action == null || !action.isEnabled()) {
426 action = new OpenInEditorAction();
428 if (action.isEnabled()) {
429 action.actionPerformed(new ActionEvent(this, 0, ""));
434 public void valueChanged(ListSelectionEvent e) {
435 List<SyncFileNode> selectedNodes = new ArrayList<SyncFileNode>();
436 ListSelectionModel selectionModel = table.getSelectionModel();
437 final TopComponent tc = (TopComponent) SwingUtilities.getAncestorOfClass(TopComponent.class, table);
438 if (tc == null) {
439 return; // table is no longer in component hierarchy
441 int min = selectionModel.getMinSelectionIndex();
442 if (min != -1) {
443 int max = selectionModel.getMaxSelectionIndex();
444 for (int i = min; i <= max; i++) {
445 if (selectionModel.isSelectedIndex(i)) {
446 int idx = sorter.modelIndex(i);
447 selectedNodes.add(nodes[idx]);
451 // this method may be called outside of AWT if a node fires change events from some other thread, see #79174
452 final Node[] nodes = selectedNodes.toArray(new Node[selectedNodes.size()]);
453 if (SwingUtilities.isEventDispatchThread()) {
454 tc.setActivatedNodes(nodes);
455 } else {
456 SwingUtilities.invokeLater(new Runnable() {
458 public void run() {
459 tc.setActivatedNodes(nodes);
465 private class SyncTableCellRenderer extends DefaultTableCellRenderer {
467 private FilePathCellRenderer pathRenderer = new FilePathCellRenderer();
469 @Override
470 public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
471 Component renderer;
472 int modelColumnIndex = table.convertColumnIndexToModel(column);
473 if (modelColumnIndex == 0) {
474 SyncFileNode node = nodes[sorter.modelIndex(row)];
475 if (!isSelected) {
476 value = "<html>" + node.getHtmlDisplayName();
478 if (GitModuleConfig.getDefault().isExcludedFromCommit(node.getFile().getAbsolutePath())) {
479 String nodeName = node.getDisplayName();
480 if (isSelected) {
481 value = "<html><s>" + nodeName + "</s></html>";
482 } else {
483 value = "<html><s>" + HtmlFormatter.getInstance().annotateNameHtml(nodeName, node.getFileInformation(), null) + "</s>";
487 if (modelColumnIndex == 2) {
488 renderer = pathRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
489 } else {
490 renderer = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
492 if (renderer instanceof JComponent) {
493 String path = nodes[sorter.modelIndex(row)].getFile().getAbsolutePath();
494 ((JComponent) renderer).setToolTipText(path);
496 return renderer;