1 package org
.nbgit
.ui
.status
;
4 import java
.awt
.Component
;
6 import java
.awt
.event
.ActionEvent
;
7 import java
.awt
.event
.KeyEvent
;
8 import java
.awt
.event
.MouseEvent
;
9 import java
.awt
.event
.MouseListener
;
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
;
18 import java
.util
.ResourceBundle
;
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
;
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
;
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
;
73 private JScrollPane component
;
74 private SyncFileNode
[] nodes
= new SyncFileNode
[0];
75 private String
[] tableColumns
;
76 private TableSorter sorter
;
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")
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")
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")
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")
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
109 String sk2
= (String
) p2
.getValue("sortkey"); // NOI18N
110 return sk1
.compareToIgnoreCase(sk2
);
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
);
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() {
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);
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);
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
)];
206 public JComponent
getComponent() {
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
)) {
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
);
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
) {
244 tableModel
.setNodes(nodes
);
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
{
263 private void showPopup(final MouseEvent e
) {
264 int row
= table
.rowAtPoint(e
.getPoint());
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;
275 if (makeRowSelected
) {
276 table
.getSelectionModel().setSelectionInterval(row
, row
);
279 SwingUtilities
.invokeLater(new Runnable() {
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
299 Diff (default action)
303 Conflict Resolved (on conflicting file)
308 Revert Modifications (Revert Delete)(Delete)
309 Exclude from Commit (Include in Commit)
313 private JPopupMenu
getPopup() {
315 JPopupMenu menu
= new JPopupMenu();
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
));
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());
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()) {
411 public void mouseReleased(MouseEvent e
) {
412 if (e
.isPopupTrigger()) {
417 public void mouseClicked(MouseEvent e
) {
418 if (SwingUtilities
.isLeftMouseButton(e
) && MouseUtils
.isDoubleClick(e
)) {
419 int row
= table
.rowAtPoint(e
.getPoint());
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
);
439 return; // table is no longer in component hierarchy
441 int min
= selectionModel
.getMinSelectionIndex();
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
);
456 SwingUtilities
.invokeLater(new Runnable() {
459 tc
.setActivatedNodes(nodes
);
465 private class SyncTableCellRenderer
extends DefaultTableCellRenderer
{
467 private FilePathCellRenderer pathRenderer
= new FilePathCellRenderer();
470 public Component
getTableCellRendererComponent(JTable table
, Object value
, boolean isSelected
, boolean hasFocus
, int row
, int column
) {
472 int modelColumnIndex
= table
.convertColumnIndexToModel(column
);
473 if (modelColumnIndex
== 0) {
474 SyncFileNode node
= nodes
[sorter
.modelIndex(row
)];
476 value
= "<html>" + node
.getHtmlDisplayName();
478 if (GitModuleConfig
.getDefault().isExcludedFromCommit(node
.getFile().getAbsolutePath())) {
479 String nodeName
= node
.getDisplayName();
481 value
= "<html><s>" + nodeName
+ "</s></html>";
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
);
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
);