| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans.modules.bugtracking.issuetable; |
| |
| import javax.swing.JButton; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ListSelectionEvent; |
| import javax.swing.event.TableColumnModelEvent; |
| import javax.swing.table.TableColumn; |
| import org.openide.util.NbBundle; |
| import javax.swing.event.AncestorListener; |
| import javax.swing.event.AncestorEvent; |
| import java.awt.event.MouseListener; |
| import java.awt.event.MouseEvent; |
| import java.awt.event.ActionEvent; |
| import java.awt.event.KeyEvent; |
| import java.awt.Color; |
| import java.awt.Component; |
| import java.awt.EventQueue; |
| import java.awt.Rectangle; |
| import java.awt.event.ActionListener; |
| import java.awt.event.KeyListener; |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import java.util.prefs.BackingStoreException; |
| import java.util.prefs.Preferences; |
| import javax.swing.Action; |
| import javax.swing.BorderFactory; |
| import javax.swing.GroupLayout; |
| import javax.swing.ImageIcon; |
| import javax.swing.JComponent; |
| import javax.swing.JLabel; |
| import javax.swing.JPanel; |
| import javax.swing.JScrollPane; |
| import javax.swing.JTable; |
| import javax.swing.KeyStroke; |
| import javax.swing.SwingUtilities; |
| import javax.swing.UIManager; |
| import javax.swing.event.TableColumnModelListener; |
| import javax.swing.table.TableCellRenderer; |
| import javax.swing.table.TableColumnModel; |
| import org.netbeans.modules.bugtracking.commons.UIUtils; |
| import org.netbeans.modules.bugtracking.spi.BugtrackingSupport; |
| import org.netbeans.modules.bugtracking.spi.IssueStatusProvider; |
| import org.netbeans.modules.bugtracking.spi.QueryController; |
| import org.netbeans.modules.bugtracking.spi.QueryProvider; |
| import org.openide.awt.MouseUtils; |
| import org.openide.explorer.view.TreeTableView; |
| import org.openide.nodes.Node; |
| import org.openide.util.ImageUtilities; |
| import org.openide.util.NbPreferences; |
| import org.openide.util.RequestProcessor; |
| import org.openide.util.RequestProcessor.Task; |
| |
| /** |
| * @author Tomas Stupka |
| */ |
| public class IssueTable implements MouseListener, AncestorListener, KeyListener, PropertyChangeListener { |
| |
| static final Logger LOG = Logger.getLogger(IssueTable.class.getName()); |
| |
| private NodeTableModel tableModel; |
| private JTable table; |
| private final JPanel component; |
| |
| private final TableSorter sorter; |
| |
| private ColumnDescriptor[] descriptors; |
| |
| private Filter allFilter; |
| private Filter newOrChangedFilter; |
| private Filter filter; |
| private Filter[] filters; |
| private Set<IssueNode> nodes = new HashSet<>(); |
| |
| private final QueryTableHeaderRenderer queryTableHeaderRenderer; |
| |
| private Task storeColumnsTask; |
| private final StoreColumnsHandler storeColumnsWidthHandler; |
| private final JButton colsButton; |
| private boolean savedQueryInitialized; |
| private SummaryTextFilter textFilter; |
| |
| private static final String CONFIG_DELIMITER = "<=>"; // NOI18N |
| private final FindInQuerySupport findInQuerySupport; |
| private boolean isSaved; |
| |
| private static final Comparator<IssueNode<Object>.IssueProperty<Object>> nodeComparator = new Comparator<IssueNode<Object>.IssueProperty<Object>>() { |
| @Override |
| public int compare(IssueNode<Object>.IssueProperty<Object> p1, IssueNode<Object>.IssueProperty<Object> p2) { |
| Integer sk1 = (Integer) p1.getValue("sortkey"); // NOI18N |
| if (sk1 != null) { |
| Integer sk2 = (Integer) p2.getValue("sortkey"); // NOI18N |
| return sk2 != null ? sk1.compareTo(sk2) : 1; |
| } else { |
| try { |
| return p1.compareTo(p2); |
| } catch (Exception e) { |
| LOG.log(Level.SEVERE, null, e); |
| return 0; |
| } |
| } |
| } |
| }; |
| private RequestProcessor rp; |
| private final String repositoryId; |
| |
| public IssueTable(String repositoryId, String queryName, QueryController controller, ColumnDescriptor[] descriptors, final boolean isSaved) { |
| this(repositoryId, queryName, controller, descriptors, isSaved, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); |
| } |
| |
| public IssueTable(String repositoryId, String queryName, QueryController controller, ColumnDescriptor[] descriptors, final boolean isSaved, int vsbPolicy, int hsbPolicy) { |
| assert descriptors != null; |
| assert descriptors.length > 0; |
| |
| |
| if(queryName == null) { |
| queryName = "#find#issues#hitlist#table#"; // NOI18N |
| } |
| this.repositoryId = repositoryId + ":" + queryName; // NOI18N |
| |
| controller.addPropertyChangeListener(this); |
| |
| this.descriptors = descriptors; |
| this.component = new JPanel() { |
| @Override |
| public void requestFocus() { |
| table.requestFocus(); |
| } |
| }; |
| this.isSaved = isSaved; |
| |
| initFilters(); |
| |
| /* table */ |
| tableModel = new NodeTableModel(); |
| sorter = new TableSorter(tableModel, this); |
| sorter.setColumnComparator(Node.Property.class, nodeComparator); |
| table = new JTable(sorter); |
| sorter.setTableHeader(table.getTableHeader()); |
| table.setRowHeight(table.getRowHeight() * 6 / 5); |
| |
| JScrollPane tableScrollPane = new JScrollPane(table, vsbPolicy, hsbPolicy); |
| tableScrollPane.getViewport().setBackground(table.getBackground()); |
| Color borderColor = UIManager.getColor("scrollpane_border"); // NOI18N |
| if (borderColor == null) borderColor = UIManager.getColor("controlShadow"); // NOI18N |
| tableScrollPane.setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0, borderColor)); |
| |
| ImageIcon ic = new ImageIcon(ImageUtilities.loadImage("org/netbeans/modules/bugtracking/commons/resources/columns_16.png", true)); // NOI18N |
| colsButton = new javax.swing.JButton(ic); |
| colsButton.getAccessibleContext().setAccessibleName(NbBundle.getMessage(TreeTableView.class, "ACN_ColumnsSelector")); //NOI18N |
| colsButton.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(TreeTableView.class, "ACD_ColumnsSelector")); //NOI18N |
| colsButton.addActionListener( |
| new ActionListener() { |
| @Override |
| public void actionPerformed(ActionEvent evt) { |
| if(tableModel.selectVisibleColumns()) { |
| setDefaultColumnSizes(); |
| storeColumnsTask.schedule(1000); |
| } |
| } |
| } |
| ); |
| tableScrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, colsButton); |
| |
| /* find bar */ |
| findInQuerySupport = FindInQuerySupport.create(this); |
| FindInQueryBar findBar = findInQuerySupport.getFindBar(); |
| |
| initComponents(tableScrollPane, findBar); |
| |
| table.addMouseListener(this); |
| table.addKeyListener(this); |
| table.addAncestorListener(findInQuerySupport.getAncestorListener()); |
| cellRenderer = new QueryTableCellRenderer(this, isSaved); |
| table.setDefaultRenderer(Node.Property.class, cellRenderer); |
| queryTableHeaderRenderer = new QueryTableHeaderRenderer(table.getTableHeader().getDefaultRenderer(), this); |
| queryTableHeaderRenderer.setSaved(isSaved); |
| table.getTableHeader().setDefaultRenderer(queryTableHeaderRenderer); |
| table.addAncestorListener(this); |
| table.getAccessibleContext().setAccessibleName(NbBundle.getMessage(IssueTable.class, "ACSN_IssueTable")); // NOI18N |
| table.getAccessibleContext().setAccessibleDescription(NbBundle.getMessage(IssueTable.class, "ACSD_IssueTable")); // NOI18N |
| |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| initColumns(); |
| } |
| }); |
| table.getTableHeader().addMouseListener(new MouseListener() { |
| @Override |
| public void mouseClicked(MouseEvent e) {} |
| @Override |
| public void mousePressed(MouseEvent e) { |
| table.getColumnModel().addColumnModelListener(tcml); |
| } |
| @Override |
| public void mouseReleased(MouseEvent e) { |
| table.getColumnModel().removeColumnModelListener(tcml); |
| } |
| @Override |
| public void mouseEntered(MouseEvent e) {} |
| @Override |
| public void mouseExited(MouseEvent e) {} |
| }); |
| |
| table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( |
| KeyStroke.getKeyStroke(KeyEvent.VK_F10, KeyEvent.SHIFT_DOWN_MASK ), "org.openide.actions.PopupAction"); // NOI18N |
| UIUtils.fixFocusTraversalKeys(table); |
| |
| storeColumnsWidthHandler = new StoreColumnsHandler(); |
| storeColumnsTask = getRequestProcessor().create(storeColumnsWidthHandler); |
| } |
| private final QueryTableCellRenderer cellRenderer; |
| |
| private void initComponents(JScrollPane tablePane, FindInQueryBar findBar) { |
| GroupLayout layout = new GroupLayout(component); |
| component.setLayout(layout); |
| layout.setHorizontalGroup( |
| layout.createParallelGroup(GroupLayout.Alignment.LEADING) |
| .addComponent(findBar, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) |
| .addComponent(tablePane, GroupLayout.DEFAULT_SIZE, 549, Short.MAX_VALUE) |
| ); |
| layout.setVerticalGroup( |
| layout.createParallelGroup(GroupLayout.Alignment.LEADING) |
| .addGroup(GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() |
| .addComponent(tablePane, GroupLayout.DEFAULT_SIZE, 379, Short.MAX_VALUE) |
| .addGap(0, 0, 0) |
| .addComponent(findBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)) |
| ); |
| } |
| |
| /** |
| * Returns the issue table filters |
| * @return |
| */ |
| public Filter[] getDefinedFilters() { |
| return filters; |
| } |
| |
| public Filter getAllFilter() { |
| return allFilter; |
| } |
| |
| public Filter getNewOrChangedFilter() { |
| return newOrChangedFilter; |
| } |
| |
| /** |
| * Reset the filter criteria set in |
| * {@link #setFilterBySummary(java.lang.String, boolean, boolean, boolean) } |
| */ |
| public void resetFilterBySummary() { |
| setFilterIntern(filter); |
| } |
| |
| /** |
| * Switch highlighting in rows matching the filter criteria set in |
| * {@link #setFilterBySummary(java.lang.String, boolean, boolean, boolean) } |
| * @param on |
| */ |
| public void switchFilterBySummaryHighlight(boolean on) { |
| assert textFilter != null; |
| if(textFilter == null) { |
| return; |
| } |
| textFilter.setHighlighting(on); |
| table.repaint(); |
| } |
| |
| /** |
| * Given values are used to filter the current issue hitlist |
| * |
| * @param searchText |
| * @param regular |
| * @param wholeWords |
| * @param matchCase |
| */ |
| public void setFilterBySummary(String searchText, boolean regular, boolean wholeWords, boolean matchCase) { |
| if(textFilter == null) { |
| textFilter = new SummaryTextFilter(); |
| } |
| textFilter.setText(searchText, regular, wholeWords, matchCase); |
| setFilterIntern(textFilter); |
| } |
| |
| /** |
| * Sets the renderer in the underlying JTable |
| * @param renderer |
| */ |
| public void setRenderer(TableCellRenderer renderer) { |
| table.setDefaultRenderer(Node.Property.class, renderer); |
| } |
| |
| /** |
| * Gets the renderer from the underlying JTable |
| * @return |
| */ |
| public TableCellRenderer getRenderer() { |
| return table.getDefaultRenderer(Node.Property.class); |
| } |
| |
| /** |
| * Sets a filter on the current issue hitlist |
| * @param filter |
| */ |
| public void setFilter(Filter filter) { |
| this.filter = filter; |
| setFilterIntern(filter); |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| if(evt.getPropertyName().equals(QueryController.PROP_CHANGED)) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| isSaved = true; |
| cellRenderer.setSaved(true); |
| queryTableHeaderRenderer.setSaved(true); |
| initColumns(); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Returns a UI component holding this tables visual representation |
| * @return |
| */ |
| public JComponent getComponent() { |
| return component; |
| } |
| |
| /** |
| * Sets visible columns in the Versioning table. |
| */ |
| public final void initColumns() { |
| if(savedQueryInitialized) { |
| return; |
| } |
| setModelProperties(); |
| if(descriptors.length > 0) { |
| Map<Integer, Integer> sorting = getColumnSorting(); |
| if(descriptors.length > 1) { |
| for (int i = 0; i < descriptors.length; i++) { |
| int visibleIdx = tableModel.getVisibleIndex(i); |
| Integer order = sorting.get(visibleIdx); |
| if(order != null) { |
| sorter.setSortingStatus(visibleIdx, order); |
| } else { |
| if(i == 0) { |
| sorter.setSortingStatus(0, TableSorter.ASCENDING); // default sorting by first column |
| } else { |
| sorter.setColumnComparator(i, null); |
| sorter.setSortingStatus(i, TableSorter.NOT_SORTED); |
| } |
| } |
| } |
| } |
| } |
| setDefaultColumnSizes(); |
| if(isSaved) { |
| savedQueryInitialized = true; |
| } |
| } |
| |
| /** |
| * Callback from sorter. It also throws an event when the order is changed, unfortunately |
| * that also applies for changes caused by refreshing a query and there is no way to |
| * distinguish between those events. |
| */ |
| void sortOrderChanged() { |
| // sorting changed |
| StringBuilder sb = new StringBuilder(); |
| for(int i = 0; i < sorter.getColumnCount(); i++) { |
| if(i > 0) { |
| sb.append(CONFIG_DELIMITER); |
| } |
| sb.append(i).append(CONFIG_DELIMITER).append(sorter.getSortingStatus(i)); |
| } |
| storeColumnSorting(repositoryId, sb.toString()); |
| } |
| |
| private Map<Integer, Integer> getColumnSorting() { |
| String sortingString = getColumnSorting(repositoryId); |
| if(sortingString == null || sortingString.equals("")) { |
| return Collections.EMPTY_MAP; |
| } |
| Map<Integer, Integer> map = new HashMap<>(); |
| String[] sortingArray = sortingString.split(CONFIG_DELIMITER); |
| for (int i = 0; i < sortingArray.length; i+=2) { |
| try { |
| map.put(Integer.parseInt(sortingArray[i]), |
| Integer.parseInt(sortingArray[i + 1])); |
| } catch ( NumberFormatException | ArrayIndexOutOfBoundsException e) { |
| LOG.log(Level.FINE, null, e); |
| } |
| } |
| return map; |
| } |
| |
| private void initFilters() { |
| allFilter = Filter.getAllFilter(); |
| newOrChangedFilter = Filter.getNotSeenFilter(); |
| filters = new Filter[]{allFilter, newOrChangedFilter}; |
| filter = allFilter; |
| } |
| |
| int getSeenColumnIdx() { |
| return tableModel.getIndexForPropertyName(IssueNode.LABEL_NAME_SEEN); |
| } |
| |
| int getRecentChangesColumnIdx() { |
| return tableModel.getIndexForPropertyName(IssueNode.LABEL_RECENT_CHANGES); |
| } |
| |
| private void setFilterIntern(Filter filter) { |
| final List<IssueNode> filteredNodes = new ArrayList<IssueNode>(nodes.size()); |
| for (IssueNode node : nodes) { |
| if (filter == null || filter.accept(node)) { |
| filteredNodes.add(node); |
| } |
| } |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| setTableModel(filteredNodes.toArray(new IssueNode[filteredNodes.size()])); |
| } |
| }); |
| } |
| |
| SummaryTextFilter getSummaryFilter() { |
| return textFilter; |
| } |
| |
| private RequestProcessor getRequestProcessor() { |
| if(rp == null) { |
| rp = new RequestProcessor("Issue table", 5); |
| } |
| return rp; |
| } |
| |
| private static class CellAction implements ActionListener { |
| private final Rectangle bounds; |
| private final ActionListener listener; |
| public CellAction(Rectangle bounds, ActionListener listener) { |
| this.bounds = bounds; |
| this.listener = listener; |
| } |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| final CellAction other = (CellAction) obj; |
| if (this.bounds != other.bounds && (this.bounds == null || !this.bounds.equals(other.bounds))) { |
| return false; |
| } |
| return true; |
| } |
| @Override |
| public int hashCode() { |
| return toString().hashCode(); |
| } |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("["); // NOI18N |
| sb.append("bounds="); // NOI18N |
| sb.append(bounds); |
| sb.append("]"); // NOI18N |
| return sb.toString(); |
| } |
| public void actionPerformed(ActionEvent e) { |
| listener.actionPerformed(e); |
| } |
| } |
| |
| private class Cell { |
| private final int row; |
| private final int column; |
| public Cell(int row, int column) { |
| this.row = row; |
| this.column = column; |
| } |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| final Cell other = (Cell) obj; |
| if (this.row != other.row) { |
| return false; |
| } |
| if (this.column != other.column) { |
| return false; |
| } |
| return true; |
| } |
| @Override |
| public int hashCode() { |
| return toString().hashCode(); |
| } |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| sb.append("["); // NOI18N |
| sb.append("row="); // NOI18N |
| sb.append(row); |
| sb.append(",column="); // NOI18N |
| sb.append(column); |
| sb.append("]"); // NOI18N |
| return sb.toString(); |
| } |
| } |
| private final Map<Cell, Set<CellAction>> cellActions = new HashMap<Cell, Set<CellAction>>(); |
| |
| public void addCellAction(int row, int column, Rectangle bounds, ActionListener l) { |
| synchronized(cellActions) { |
| Cell cell = new Cell(row, column); |
| Set<CellAction> actions = cellActions.get(cell); |
| if(actions == null) { |
| actions = new HashSet<CellAction>(1); |
| cellActions.put(cell, actions); |
| } |
| actions.add(new CellAction(bounds, l)); |
| } |
| } |
| |
| public void removeCellActions(int row, int column) { |
| Cell cell = new Cell(row, column); |
| synchronized(cellActions) { |
| cellActions.remove(cell); |
| } |
| } |
| |
| void setDefaultColumnSizes() { |
| Runnable r = new Runnable() { |
| @Override |
| public void run() { |
| int[] widths = getColumnWidths(repositoryId); |
| Map<String, Integer> persistedColumnsMap = getPersistedColumnValues(); |
| if(persistedColumnsMap.size() > 0) { |
| final TableColumnModel columnModel = table.getColumnModel(); |
| int columnCount = columnModel.getColumnCount(); |
| for (int i = 0; i < columnCount; i++) { |
| String id = tableModel.getColumnId(i); |
| Integer w = persistedColumnsMap.get(id); |
| if(w != null && w > 0) { |
| setColumnWidth(i, w); |
| } |
| } |
| } else if(widths != null && widths.length > 0) { |
| // XXX for backward comp. remove together with BugtrackingConfig.getInstance().getColumnWidths |
| int columnCount = table.getColumnModel().getColumnCount(); |
| for (int i = 0; i < widths.length && i < columnCount; i++) { |
| int w = widths[i]; |
| if(w > 0) { |
| setColumnWidth(i, w); |
| } |
| } |
| } else { |
| ColumnDescriptor[] visibleDescriptors = getVisibleDescriptors(); |
| for (int i = 0; i < visibleDescriptors.length; i++) { |
| ColumnDescriptor desc = visibleDescriptors[i]; |
| int w = desc.getWidth(); |
| if(w > 0) { |
| setColumnWidth(i, w); |
| } else if(w == 0) { |
| setWidthForFit(i); |
| } |
| } |
| if(isSaved) { |
| int idx = getRecentChangesColumnIdx(); |
| if(idx > -1) { |
| int w = UIUtils.getColumnWidthInPixels(25, table); |
| setColumnWidth(getRecentChangesColumnIdx(), w); |
| } |
| } |
| } |
| |
| if(isSaved) { |
| int seenIdx = getSeenColumnIdx(); |
| table.getColumnModel().getColumn(seenIdx).setMaxWidth(28); |
| table.getColumnModel().getColumn(seenIdx).setPreferredWidth(28); |
| } |
| } |
| |
| private void setColumnWidth(int i, int w) { |
| table.getColumnModel().getColumn(i).setMinWidth(10); |
| table.getColumnModel().getColumn(i).setMaxWidth(10000); |
| table.getColumnModel().getColumn(i).setPreferredWidth(w); |
| } |
| |
| private void setWidthForFit(int i) { |
| TableColumn c = table.getColumnModel().getColumn(i); |
| Component comp = queryTableHeaderRenderer.getTableCellRendererComponent(table, c.getHeaderValue(), false, false, 0, i); |
| if(comp instanceof JLabel) { |
| JLabel label = (JLabel) comp; |
| int w = label.getPreferredSize().width; |
| if(w > -1) { |
| setColumnWidth(i, w); |
| } |
| } |
| } |
| |
| }; |
| if(EventQueue.isDispatchThread()) { |
| r.run(); |
| } else { |
| SwingUtilities.invokeLater(r); |
| } |
| } |
| |
| private ColumnDescriptor[] getVisibleDescriptors() { |
| List<ColumnDescriptor> visible = new LinkedList<ColumnDescriptor>(); |
| for (ColumnDescriptor d : descriptors) { |
| if(d.isVisible()) { |
| visible.add(d); |
| } |
| } |
| return visible.toArray(new ColumnDescriptor[visible.size()]); |
| } |
| |
| @Override |
| public void ancestorAdded(AncestorEvent event) { |
| setDefaultColumnSizes(); |
| } |
| |
| @Override |
| public void ancestorMoved(AncestorEvent event) { } |
| |
| @Override |
| public void ancestorRemoved(AncestorEvent event) { } |
| |
| private void setModelProperties() { |
| List<ColumnDescriptor> properties = new ArrayList<ColumnDescriptor>(descriptors.length + (isSaved ? 2 : 0)); |
| int i = 0; |
| for (; i < descriptors.length; i++) { |
| ColumnDescriptor desc = descriptors[i]; |
| properties.add(desc); |
| } |
| if(isSaved) { |
| properties.add(new RecentChangesDescriptor()); |
| properties.add(new SeenDescriptor()); |
| } |
| |
| // set visibility dependeing on persisted values |
| Map<String, Integer> persistedColumnsMap = getPersistedColumnValues(); |
| if(persistedColumnsMap.size() > 0) { |
| for (ColumnDescriptor cd : properties) { |
| if(!cd.getName().equals(IssueNode.LABEL_NAME_SEEN)) { // always show seen, no matter if persisted or not |
| cd.setVisible(persistedColumnsMap.containsKey(cd.getName())); |
| } |
| } |
| } |
| descriptors = properties.toArray(new ColumnDescriptor[properties.size()]); |
| tableModel.setProperties(descriptors); |
| } |
| |
| private Map<String, Integer> getPersistedColumnValues() { |
| String columns = getColumns(repositoryId); |
| String[] visibleColumns = columns.split(CONFIG_DELIMITER); // NOI18N |
| if(visibleColumns.length <= 1) { |
| return Collections.EMPTY_MAP; |
| } |
| Map<String, Integer> ret = new HashMap<String, Integer>(); |
| for (int i = 0; i < visibleColumns.length; i=i+2) { |
| try { |
| ret.put(visibleColumns[i], Integer.parseInt(visibleColumns[i + 1])); |
| } catch (NumberFormatException nfe) { |
| ret.put(visibleColumns[i], -1); |
| LOG.log(Level.WARNING, visibleColumns[i], nfe); |
| } |
| } |
| return ret; |
| } |
| |
| private void setTableModel(IssueNode[] nodes) { |
| tableModel.setNodes(nodes); |
| } |
| |
| void focus() { |
| table.requestFocus(); |
| } |
| |
| @Override |
| public void mouseEntered(MouseEvent e) {} |
| @Override |
| public void mouseExited(MouseEvent e) {} |
| @Override |
| public void mousePressed(MouseEvent e) {} |
| @Override |
| public void mouseReleased(MouseEvent e) {} |
| |
| @Override |
| public void mouseClicked(MouseEvent e) { |
| int row = table.rowAtPoint(e.getPoint()); |
| int column = table.columnAtPoint(e.getPoint()); |
| if (SwingUtilities.isLeftMouseButton(e)) { |
| if (row == -1) return; |
| row = sorter.modelIndex(row); |
| if(MouseUtils.isDoubleClick(e)) { |
| Action action = tableModel.getNodes()[row].getPreferredAction(); |
| if (action.isEnabled()) { |
| action.actionPerformed(new ActionEvent(this, 0, "")); // NOI18N |
| } |
| } else { |
| |
| // seen column |
| if(column == getSeenColumnIdx()) { |
| final IssueNode in = (IssueNode) tableModel.getNodes()[row]; |
| getRequestProcessor().post(new Runnable() { |
| @Override |
| public void run() { |
| IssueStatusProvider.Status status = in.getStatus(); |
| in.setSeen(status != IssueStatusProvider.Status.SEEN); |
| } |
| }); |
| } |
| // check for action |
| CellAction[] actions = null; |
| synchronized(cellActions) { |
| Cell cell = new Cell(row, column); |
| Set<CellAction> set = cellActions.get(cell); |
| actions = set != null ? set.toArray(new CellAction[set.size()]) : null; |
| } |
| if(actions != null) { |
| for (CellAction cellAction : actions) { |
| cellAction.actionPerformed(null); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void keyTyped(KeyEvent e) { |
| if (e.getKeyChar() == '\n') { // NOI18N |
| int row = table.getSelectedRow(); |
| if (row != -1) { |
| row = sorter.modelIndex(row); |
| Action action = tableModel.getNodes()[row].getPreferredAction(); |
| if (action.isEnabled()) { |
| action.actionPerformed(new ActionEvent(this, 0, "")); // NOI18N |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void keyPressed(KeyEvent e) { |
| if (e.getKeyChar() == '\n') { // NOI18N |
| int row = table.getSelectedRow(); |
| if (row != -1) { |
| // Hack for bug 4486444 |
| e.consume(); |
| } |
| } |
| } |
| |
| @Override |
| public void keyReleased(KeyEvent e) { } |
| |
| public void addNode(final IssueNode node) { |
| nodes.add(node); |
| if(filter == null || filter.accept(node)) { |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| tableModel.insertNode(node); |
| } |
| }); |
| } |
| } |
| public void started() { |
| nodes.clear(); |
| SwingUtilities.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| IssueTable.this.setTableModel(new IssueNode[0]); |
| } |
| }); |
| } |
| |
| private static class SeenDescriptor extends ColumnDescriptor<Boolean> { |
| public SeenDescriptor() { |
| super(IssueNode.LABEL_NAME_SEEN, Boolean.class, "", NbBundle.getBundle(IssueTable.class).getString("CTL_Issue_Seen_Desc"), -1, true, true); // NOI18N |
| } |
| } |
| |
| private static class RecentChangesDescriptor extends ColumnDescriptor<String> { |
| public RecentChangesDescriptor() { |
| super(IssueNode.LABEL_RECENT_CHANGES, String.class, NbBundle.getBundle(IssueTable.class).getString("CTL_Issue_Recent"), NbBundle.getBundle(IssueTable.class).getString("CTL_Issue_Recent_Desc"), -1, true, true); // NOI18N |
| } |
| } |
| |
| private class StoreColumnsHandler implements Runnable { |
| @Override |
| public void run() { |
| TableColumnModel cm = table.getColumnModel(); |
| int count = cm.getColumnCount(); |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < count; i++) { |
| if(!tableModel.getColumnId(i).equals(IssueNode.LABEL_NAME_SEEN)) { |
| sb.append(tableModel.getColumnId(i)); |
| sb.append(CONFIG_DELIMITER); |
| sb.append(cm.getColumn(i).getWidth()); |
| if(i < count - 1) { |
| sb.append(CONFIG_DELIMITER); |
| } |
| } |
| } |
| storeColumns(repositoryId, sb.toString()); |
| } |
| } |
| |
| private TableColumnModelListener tcml = new TableColumnModelListener() { |
| @Override |
| public void columnAdded(TableColumnModelEvent e) {} |
| @Override |
| public void columnRemoved(TableColumnModelEvent e) {} |
| @Override |
| public void columnMoved(TableColumnModelEvent e) { |
| int from = e.getFromIndex(); |
| int to = e.getToIndex(); |
| if(from == to) { |
| return; |
| } |
| table.getTableHeader().getColumnModel().getColumn(from).setModelIndex(from); |
| table.getTableHeader().getColumnModel().getColumn(to).setModelIndex(to); |
| tableModel.moveColumn(from, to); |
| } |
| @Override |
| public void columnSelectionChanged(ListSelectionEvent e) {} |
| @Override |
| public void columnMarginChanged(ChangeEvent e) { |
| storeColumnsTask.schedule(1000); |
| } |
| }; |
| |
| private static final String COLUMN_WIDTH_PREFIX = "bugtracking.issuetable.columnwidth"; // NOI18N |
| private static final String COLUMN_SORTING_PREFIX = "bugtracking.issuetable.columnsorting"; // NOI18N |
| |
| public Preferences getPreferences() { |
| // legacy - use some public bugtracking type, |
| // to acces the previously (before 8.0) used preferences location |
| return NbPreferences.forModule(BugtrackingSupport.class); |
| } |
| |
| public void storeColumns(String key, String columns) { |
| getPreferences().put(COLUMN_WIDTH_PREFIX + "." + key, columns); // NOI18N |
| } |
| |
| public String getColumns(String key) { |
| return getPreferences().get(COLUMN_WIDTH_PREFIX + "." + key, ""); // NOI18N |
| } |
| |
| @Deprecated |
| public int[] getColumnWidths(String key) { |
| List<Integer> retval = new ArrayList<>(); |
| try { |
| String[] keys = getPreferences().keys(); |
| for (int i = 0; i < keys.length; i++) { |
| String k = keys[i]; |
| if (k != null && k.startsWith(COLUMN_WIDTH_PREFIX + "." + key + ".")) { // NOI18N |
| int idx = Integer.parseInt(k.substring(k.lastIndexOf('.') + 1)); // NOI18N |
| int value = getPreferences().getInt(k, -1); |
| retval.add(idx, value); |
| getPreferences().remove(k); |
| } |
| } |
| int[] ret = new int[retval.size()]; |
| for (int i = 0; i < ret.length; i++) { |
| ret[i] = retval.get(i); |
| } |
| return ret; |
| } catch (NumberFormatException | BackingStoreException ex) { |
| LOG.log(Level.INFO, null, ex); |
| return new int[0]; |
| } |
| } |
| |
| public void storeColumnSorting(String columnsKey, String sorting) { |
| getPreferences().put(COLUMN_SORTING_PREFIX + "." + columnsKey, sorting); // NOI18N |
| } |
| |
| public String getColumnSorting(String key) { |
| return getPreferences().get(COLUMN_SORTING_PREFIX + "." + key, ""); // NOI18N |
| } |
| } |
| |