Changeset 1709 in josm for trunk/src/org


Ignore:
Timestamp:
2009-06-28T11:37:21+02:00 (15 years ago)
Author:
Gubaer
Message:

new: history feature implemented

Location:
trunk/src/org/openstreetmap/josm
Files:
20 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/osm/history/HistoryDataSet.java

    r1670 r1709  
    99
    1010public class HistoryDataSet {
     11
     12    private static HistoryDataSet historyDataSet;
     13
     14    public static HistoryDataSet getInstance() {
     15        if (historyDataSet == null) {
     16            historyDataSet = new HistoryDataSet();
     17        }
     18        return  historyDataSet;
     19    }
    1120
    1221    private HashMap<Long, ArrayList<HistoryOsmPrimitive>> data;
     
    4453        ArrayList<HistoryOsmPrimitive> versions = data.get(id);
    4554        if (versions == null)
    46             throw new NoSuchElementException(tr("Didn't find an historized primitive with id {0} in this dataset", id));
     55            return null;
    4756        return new History(id, versions);
    4857    }
     58
     59    /**
     60     * merges the histories from the {@see HistoryDataSet} other in this history data set
     61     *
     62     * @param other the other history data set. Ignored if null.
     63     */
     64    public void mergeInto(HistoryDataSet other) {
     65        if (other == null)
     66            return;
     67        for (Long id : other.data.keySet()) {
     68            if (!this.data.keySet().contains(id)) {
     69                this.data.put(id, other.data.get(id));
     70            }
     71        }
     72    }
    4973}
  • trunk/src/org/openstreetmap/josm/data/osm/history/HistoryOsmPrimitive.java

    r1670 r1709  
    22package org.openstreetmap.josm.data.osm.history;
    33
     4import java.util.Collections;
    45import java.util.Date;
    56import java.util.HashMap;
     7import java.util.Map;
    68
    79import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     
    116118    }
    117119
     120    public Map<String,String> getTags() {
     121        return Collections.unmodifiableMap(tags);
     122    }
     123
    118124    @Override
    119125    public int hashCode() {
  • trunk/src/org/openstreetmap/josm/gui/dialogs/HistoryDialog.java

    r1499 r1709  
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    5 import static org.openstreetmap.josm.tools.I18n.marktr;
    65
    76import java.awt.BorderLayout;
     7import java.awt.Color;
    88import java.awt.Component;
    9 import java.awt.GridBagLayout;
    109import java.awt.GridLayout;
    1110import java.awt.event.ActionEvent;
    12 import java.awt.event.ActionListener;
    1311import java.awt.event.KeyEvent;
    14 import java.util.Calendar;
     12import java.awt.event.MouseAdapter;
     13import java.awt.event.MouseEvent;
     14import java.io.IOException;
     15import java.util.ArrayList;
    1516import java.util.Collection;
    16 import java.util.Date;
    1717import java.util.HashMap;
    18 import java.util.List;
    19 import java.util.Map;
    20 import java.util.SortedSet;
    21 import java.util.TreeSet;
    22 
     18import java.util.Iterator;
     19
     20import javax.swing.AbstractAction;
     21import javax.swing.Action;
     22import javax.swing.ImageIcon;
     23import javax.swing.JButton;
    2324import javax.swing.JComponent;
    2425import javax.swing.JLabel;
     
    2728import javax.swing.JScrollPane;
    2829import javax.swing.JTable;
     30import javax.swing.ListSelectionModel;
     31import javax.swing.SwingUtilities;
     32import javax.swing.event.ListSelectionEvent;
     33import javax.swing.event.ListSelectionListener;
    2934import javax.swing.table.DefaultTableCellRenderer;
     35import javax.swing.table.DefaultTableColumnModel;
    3036import javax.swing.table.DefaultTableModel;
    3137import javax.swing.table.TableCellRenderer;
     38import javax.swing.table.TableColumn;
    3239
    3340import org.openstreetmap.josm.Main;
     
    3542import org.openstreetmap.josm.data.osm.DataSet;
    3643import org.openstreetmap.josm.data.osm.OsmPrimitive;
    37 import org.openstreetmap.josm.gui.SideButton;
    38 import org.openstreetmap.josm.tools.DateUtils;
    39 import org.openstreetmap.josm.tools.GBC;
     44import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     45import org.openstreetmap.josm.data.osm.history.History;
     46import org.openstreetmap.josm.data.osm.history.HistoryDataSet;
     47import org.openstreetmap.josm.gui.PleaseWaitRunnable;
     48import org.openstreetmap.josm.gui.history.HistoryBrowserDialog;
     49import org.openstreetmap.josm.io.OsmApi;
     50import org.openstreetmap.josm.io.OsmServerHistoryReader;
     51import org.openstreetmap.josm.io.OsmTransferException;
    4052import org.openstreetmap.josm.tools.ImageProvider;
    4153import org.openstreetmap.josm.tools.Shortcut;
     54import org.xml.sax.SAXException;
    4255
    4356/**
     
    5366 * @author imi
    5467 */
    55 public class HistoryDialog extends ToggleDialog implements SelectionChangedListener {
    56 
    57     public static final Date unifyDate(Date d) {
    58         Calendar c = Calendar.getInstance();
    59         c.setTime(d);
    60         c.set(Calendar.MINUTE, 0);
    61         c.set(Calendar.SECOND, 0);
    62         return c.getTime();
    63     }
    64 
    65     private static class HistoryItem implements Comparable<HistoryItem> {
    66         OsmPrimitive osm;
    67         boolean visible;
    68 
    69         public int compareTo(HistoryItem o) {
    70             return unifyDate(osm.getTimestamp()).compareTo(unifyDate(o.osm.getTimestamp()));
    71         }
    72     }
    73 
    74     private final DefaultTableModel data = new DefaultTableModel(){
    75         @Override public boolean isCellEditable(int row, int column) {
    76             return false;
    77         }
    78     };
    79 
    80     /**
    81      * Main table. 3 columns:
    82      * Object | Date | visible (icon, no text)
    83      */
    84     private JTable history = new JTable(data);
    85     private JScrollPane historyPane = new JScrollPane(history);
    86 
    87     private Map<OsmPrimitive, List<HistoryItem>> cache = new HashMap<OsmPrimitive, List<HistoryItem>>();
    88     private JLabel notLoaded = new JLabel("<html><i>"+tr("Click Reload to refresh list")+"</i></html>");
    89 
    90     public HistoryDialog() {
    91         super(tr("History"), "history", tr("Display the history of all selected items."),
    92         Shortcut.registerShortcut("subwindow:history", tr("Toggle: {0}", tr("History")), KeyEvent.VK_H,
    93         Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
    94         historyPane.setVisible(false);
    95         notLoaded.setVisible(true);
    96         notLoaded.setHorizontalAlignment(JLabel.CENTER);
    97 
    98         history.setDefaultRenderer(Object.class, new DefaultTableCellRenderer(){
    99             @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    100                 return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    101             }
    102         });
    103         data.setColumnIdentifiers(new Object[]{tr("Object"),tr("Date"),""});
    104         history.getColumnModel().getColumn(0).setPreferredWidth(200);
    105         history.getColumnModel().getColumn(1).setPreferredWidth(200);
    106         history.getColumnModel().getColumn(2).setPreferredWidth(20);
    107         final TableCellRenderer oldRenderer = history.getTableHeader().getDefaultRenderer();
    108         history.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer(){
     68public class HistoryDialog extends ToggleDialog {
     69
     70    /** the registry of history browser dialogs which are currently displaying */
     71    static private HashMap<Long, HistoryBrowserDialog> historyBrowserDialogs;
     72
     73    /**
     74     * registers a {@see HistoryBrowserDialog}
     75     * @param id the id of the primitive dialog shows the history for
     76     * @param dialog the dialog
     77     */
     78    public static void registerHistoryBrowserDialog(long id, HistoryBrowserDialog dialog) {
     79        if (historyBrowserDialogs == null) {
     80            historyBrowserDialogs = new HashMap<Long, HistoryBrowserDialog>();
     81        }
     82        historyBrowserDialogs.put(id, dialog);
     83    }
     84
     85    /**
     86     * unregisters a {@see HistoryBrowserDialog}
     87     * @param id the id of the primitive whose history dialog is to be unregistered
     88     *
     89     */
     90    public static void unregisterHistoryBrowserDialog(long id) {
     91        if (historyBrowserDialogs == null)
     92            return;
     93        historyBrowserDialogs.remove(id);
     94    }
     95
     96    /**
     97     * replies the history dialog for the primitive with id <code>id</code>; null, if
     98     * no such {@see HistoryBrowserDialog} is currently showing
     99     *
     100     * @param id the id of the primitive
     101     * @return the dialog; null, if no such dialog is showing
     102     */
     103    public static HistoryBrowserDialog getHistoryBrowserDialog(long id) {
     104        if (historyBrowserDialogs == null)
     105            return null;
     106        return historyBrowserDialogs.get(id);
     107    }
     108
     109
     110    /** the table model */
     111    protected HistoryItemDataModel model;
     112    /** the table with the history items */
     113    protected JTable historyTable;
     114
     115    protected ShowHistoryAction showHistoryAction;
     116    protected ReloadAction reloadAction;
     117
     118    /**
     119     * builds the row with the command buttons
     120     *
     121     * @return the rows with the command buttons
     122     */
     123    protected JPanel buildButtonRow() {
     124        JPanel buttons = new JPanel(new GridLayout(1,2));
     125
     126        JButton btn = new JButton(reloadAction = new ReloadAction());
     127        btn.setName("btn.reload");
     128        buttons.add(btn);
     129
     130        btn = new JButton(showHistoryAction = new ShowHistoryAction());
     131        btn.setName("btn.showhistory");
     132        buttons.add(btn);
     133
     134        return buttons;
     135    }
     136
     137    /**
     138     * builds the GUI
     139     */
     140    protected void build() {
     141        model = new HistoryItemDataModel();
     142        //setLayout(new BorderLayout());
     143        historyTable = new JTable(
     144                model,
     145                new HistoryTableColumnModel()
     146        );
     147        historyTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
     148        historyTable.setName("table.historyitems");
     149        final TableCellRenderer oldRenderer = historyTable.getTableHeader().getDefaultRenderer();
     150        historyTable.getTableHeader().setDefaultRenderer(new DefaultTableCellRenderer(){
    109151            @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    110152                JComponent c = (JComponent)oldRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
     
    120162            }
    121163        });
    122 
    123         JPanel centerPanel = new JPanel(new GridBagLayout());
    124         centerPanel.add(notLoaded, GBC.eol().fill(GBC.BOTH));
    125         centerPanel.add(historyPane, GBC.eol().fill(GBC.BOTH));
    126         add(centerPanel, BorderLayout.CENTER);
    127 
    128         JPanel buttons = new JPanel(new GridLayout(1,2));
    129         buttons.add(new SideButton(marktr("Reload"), "refresh", "History", tr("Reload all currently selected objects and refresh the list."),
    130         new ActionListener(){
    131             public void actionPerformed(ActionEvent e) {
    132                 reload();
     164        historyTable.addMouseListener(
     165                new MouseAdapter() {
     166                    @Override
     167                    public void mouseClicked(MouseEvent e) {
     168                        if (e.getClickCount() == 2) {
     169                            int row = historyTable.rowAtPoint(e.getPoint());
     170                            History h = model.get(row);
     171                            showHistory(h);
     172                        }
     173                    }
     174                }
     175        );
     176
     177        JScrollPane pane = new JScrollPane(historyTable);
     178        pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
     179        pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
     180        historyTable.setTableHeader(null);
     181        pane.setColumnHeaderView(null);
     182        add(pane, BorderLayout.CENTER);
     183
     184        add(buildButtonRow(), BorderLayout.SOUTH);
     185
     186        // wire actions
     187        //
     188        historyTable.getSelectionModel().addListSelectionListener(showHistoryAction);
     189        DataSet.selListeners.add(reloadAction);
     190    }
     191
     192    public HistoryDialog() {
     193        super(tr("History"), "history", tr("Display the history of all selected items."),
     194                Shortcut.registerShortcut("subwindow:history", tr("Toggle: {0}", tr("History")), KeyEvent.VK_H,
     195                        Shortcut.GROUP_LAYER, Shortcut.SHIFT_DEFAULT), 150);
     196        build();
     197        DataSet.selListeners.add(model);
     198    }
     199
     200    /**
     201     * refreshes the current list of history items; reloads history information from the server
     202     */
     203    protected void refresh() {
     204        HistoryLoadTask task = new HistoryLoadTask();
     205        Main.worker.execute(task);
     206    }
     207
     208    /**
     209     * shows the {@see HistoryBrowserDialog} for a given {@see History}
     210     *
     211     * @param h the history. Must not be null.
     212     * @exception IllegalArgumentException thrown, if h is null
     213     */
     214    protected void showHistory(History h) throws IllegalArgumentException {
     215        if (h == null)
     216            throw new IllegalArgumentException(tr("parameter ''{0}'' must not be null", "h"));
     217        HistoryBrowserDialog dialog = getHistoryBrowserDialog(h.getId());
     218        if (dialog == null) {
     219            dialog = new HistoryBrowserDialog(h);
     220        }
     221        dialog.setVisible(true);
     222    }
     223
     224    /**
     225     * invoked after the asynchronous {@see HistoryLoadTask} is finished.
     226     *
     227     * @param task the task which is calling back.
     228     */
     229    protected void postRefresh(HistoryLoadTask task) {
     230        model.refresh();
     231        if (task.isCancelled())
     232            return;
     233        if (task.getLastException() != null) {
     234            task.getLastException().printStackTrace();
     235            String msg = task.getLastException().getMessage();
     236            if (msg == null) {
     237                msg = task.getLastException().toString();
    133238            }
    134         }));
    135         buttons.add(new SideButton(marktr("Revert"), "revert", "History",
    136         tr("Revert the state of all currently selected objects to the version selected in the history list."), new ActionListener(){
    137             public void actionPerformed(ActionEvent e) {
    138                 JOptionPane.showMessageDialog(Main.parent, tr("Not implemented yet."));
     239            JOptionPane.showMessageDialog(
     240                    Main.parent,
     241                    tr(
     242                            "<html>Failed to load history from the server. Details:<br>{0}</html>",
     243                            msg.replaceAll("&", "&amp;").replaceAll(">", "&gt;").replaceAll("<", "&lt;")
     244                    ),
     245                    tr("Error"),
     246                    JOptionPane.ERROR_MESSAGE
     247            );
     248        }
     249    }
     250
     251    /**
     252     * The table model with the history items
     253     *
     254     */
     255    class HistoryItemDataModel extends DefaultTableModel implements SelectionChangedListener{
     256        private ArrayList<History> data;
     257
     258        public HistoryItemDataModel() {
     259            data = new ArrayList<History>();
     260        }
     261
     262        @Override
     263        public int getRowCount() {
     264            if (data == null)
     265                return 0;
     266            return data.size();
     267        }
     268
     269        @Override
     270        public Object getValueAt(int row, int column) {
     271            return data.get(row);
     272        }
     273
     274        @Override
     275        public boolean isCellEditable(int row, int column) {
     276            return false;
     277        }
     278
     279        public void refresh() {
     280            data.clear();
     281            for (OsmPrimitive primitive: Main.ds.getSelected()) {
     282                if (primitive.id == 0) {
     283                    continue;
     284                }
     285                History h = HistoryDataSet.getInstance().getHistory(primitive.id);
     286                if (h !=null) {
     287                    data.add(h);
     288                }
    139289            }
    140         }));
    141         add(buttons, BorderLayout.SOUTH);
    142 
    143         DataSet.selListeners.add(this);
    144     }
    145 
    146 
    147     @Override public void setVisible(boolean b) {
    148         super.setVisible(b);
    149         if (b)
    150             update();
    151     }
    152 
    153 
    154     public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
    155         if (isVisible())
    156             update();
    157     }
    158 
    159     /**
    160      * Identify all new objects in the selection and if any, hide the list.
    161      * Else, update the list with the selected items shown.
    162      */
    163     private void update() {
    164         Collection<OsmPrimitive> sel = Main.ds.getSelected();
    165         if (!cache.keySet().containsAll(sel)) {
    166             historyPane.setVisible(false);
    167             notLoaded.setVisible(true);
    168         } else {
    169             SortedSet<HistoryItem> orderedHistory = new TreeSet<HistoryItem>();
    170             for (OsmPrimitive osm : sel)
    171                 orderedHistory.addAll(cache.get(osm));
    172             data.setRowCount(0);
    173             for (HistoryItem i : orderedHistory)
    174                 data.addRow(new Object[]{i.osm, DateUtils.fromDate(i.osm.getTimestamp()), i.visible});
    175             historyPane.setVisible(true);
    176             notLoaded.setVisible(false);
    177         }
    178     }
    179 
    180     void reload() {
    181         JOptionPane.showMessageDialog(Main.parent, tr("Not implemented yet."));
     290            fireTableDataChanged();
     291        }
     292
     293        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     294            refresh();
     295        }
     296
     297        public History get(int idx) throws IndexOutOfBoundsException {
     298            if (idx < 0 || idx >= data.size())
     299                throw new IndexOutOfBoundsException(tr("index out of bounds Got {0}", idx));
     300            return data.get(idx);
     301        }
     302    }
     303
     304
     305    /**
     306     * The table cell renderer for the history items.
     307     *
     308     */
     309    class HistoryTableCellRenderer extends JLabel implements TableCellRenderer {
     310
     311        public final Color BGCOLOR_SELECTED = new Color(143,170,255);
     312
     313        private HashMap<OsmPrimitiveType, ImageIcon> icons;
     314
     315        public HistoryTableCellRenderer() {
     316            setOpaque(true);
     317            icons = new HashMap<OsmPrimitiveType, ImageIcon>();
     318            icons.put(OsmPrimitiveType.NODE, ImageProvider.get("data", "node"));
     319            icons.put(OsmPrimitiveType.WAY, ImageProvider.get("data", "way"));
     320            icons.put(OsmPrimitiveType.RELATION, ImageProvider.get("data", "relation"));
     321        }
     322
     323        protected void renderIcon(History history) {
     324            setIcon(icons.get(history.getEarliest().getType()));
     325        }
     326
     327        protected void renderText(History h) {
     328            setText(h.getEarliest().getType().getLocalizedDisplayNameSingular() + " " + h.getId());
     329        }
     330
     331        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
     332                boolean hasFocus, int row, int column) {
     333            History h = (History)value;
     334            renderIcon(h);
     335            renderText(h);
     336            if (isSelected) {
     337                setBackground(BGCOLOR_SELECTED);
     338            } else {
     339                setBackground(Color.WHITE);
     340            }
     341            return this;
     342        }
     343    }
     344
     345    /**
     346     * The column model
     347     */
     348    class HistoryTableColumnModel extends DefaultTableColumnModel {
     349        protected void createColumns() {
     350            TableColumn col = null;
     351            HistoryTableCellRenderer renderer = new HistoryTableCellRenderer();
     352            // column 0 - History item
     353            col = new TableColumn(0);
     354            col.setHeaderValue(tr("History item"));
     355            col.setCellRenderer(renderer);
     356            addColumn(col);
     357        }
     358
     359        public HistoryTableColumnModel() {
     360            createColumns();
     361        }
     362    }
     363
     364    /**
     365     * The asynchronous task which loads history information for  the currently selected
     366     * primitives from the server.
     367     *
     368     */
     369    class HistoryLoadTask extends PleaseWaitRunnable {
     370
     371        private boolean cancelled = false;
     372        private Exception lastException  = null;
     373
     374        public HistoryLoadTask() {
     375            super(tr("Load history"), true);
     376        }
     377
     378        @Override
     379        protected void cancel() {
     380            OsmApi.getOsmApi().cancel();
     381            cancelled = true;
     382        }
     383
     384        @Override
     385        protected void finish() {
     386            postRefresh(this);
     387        }
     388
     389        /**
     390         * update the title of the {@see PleaseWaitDialog} with information about
     391         * which primitive is currently loaded
     392         *
     393         * @param primitive the primitive to be loaded
     394         */
     395        protected void notifyStartLoadingHistory(final OsmPrimitive primitive) {
     396            SwingUtilities.invokeLater(
     397                    new Runnable() {
     398                        public void run() {
     399                            Main.pleaseWaitDlg.setTitle(
     400                                    tr("Loading history for {0} with id {1}",
     401                                            OsmPrimitiveType.from(primitive).getLocalizedDisplayNameSingular(),
     402                                            Long.toString(primitive.id)
     403                                    )
     404                            );
     405                        }
     406                    }
     407            );
     408        }
     409
     410        /**
     411         * enables/disables interminate progress indication in the {@see PleaseWaitDialog}
     412         *
     413         * @param enabled true, if interminate progress indication is to enabled; false, otherwise
     414         */
     415        protected void setInterminateEnabled(final boolean enabled) {
     416            SwingUtilities.invokeLater(
     417                    new Runnable() {
     418                        public void run() {
     419                            Main.pleaseWaitDlg.setIndeterminate(enabled);
     420                        }
     421                    }
     422            );
     423        }
     424
     425        @Override
     426        protected void realRun() throws SAXException, IOException, OsmTransferException {
     427            Collection<OsmPrimitive> selection = Main.ds.getSelected();
     428            Iterator<OsmPrimitive> it = selection.iterator();
     429            setInterminateEnabled(true);
     430            try {
     431                while(it.hasNext()) {
     432                    OsmPrimitive primitive = it.next();
     433                    if (cancelled) {
     434                        break;
     435                    }
     436                    if (primitive.id == 0) {
     437                        continue;
     438                    }
     439                    notifyStartLoadingHistory(primitive);
     440                    OsmServerHistoryReader reader = null;
     441                    HistoryDataSet ds = null;
     442                    try {
     443                        reader = new OsmServerHistoryReader(OsmPrimitiveType.from(primitive), primitive.id);
     444                        ds = reader.parseHistory();
     445                    } catch(OsmTransferException e) {
     446                        if (cancelled)
     447                            return;
     448                        throw e;
     449                    }
     450                    HistoryDataSet.getInstance().mergeInto(ds);
     451                }
     452            } catch(OsmTransferException e) {
     453                lastException = e;
     454                throw e;
     455            } finally {
     456                setInterminateEnabled(false);
     457            }
     458        }
     459
     460        public boolean isCancelled() {
     461            return cancelled;
     462        }
     463
     464        public Exception getLastException() {
     465            return lastException;
     466        }
     467    }
     468
     469    /**
     470     * The action for reloading history information of the currently selected primitives.
     471     *
     472     */
     473    class ReloadAction extends AbstractAction implements SelectionChangedListener {
     474        public ReloadAction() {
     475            putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
     476            putValue(Action.SHORT_DESCRIPTION, tr("Reload all currently selected objects and refresh the list."));
     477        }
     478
     479        public void actionPerformed(ActionEvent e) {
     480            refresh();
     481        }
     482
     483        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
     484            setEnabled(Main.ds.getSelected().size() > 0);
     485
     486        }
     487    }
     488
     489    /**
     490     * The action for showing history information of the current history item.
     491     */
     492    class ShowHistoryAction extends AbstractAction implements ListSelectionListener {
     493        public ShowHistoryAction() {
     494            //putValue(Action.SMALL_ICON, ImageProvider.get("dialogs","refresh"));
     495            putValue(Action.NAME, tr("Show"));
     496            putValue(Action.SHORT_DESCRIPTION, tr("Display the history of the selected primitive"));
     497        }
     498
     499        public void actionPerformed(ActionEvent e) {
     500            int row = historyTable.getSelectionModel().getMinSelectionIndex();
     501            if (row < 0) return;
     502            History h = model.get(row);
     503            showHistory(h);
     504        }
     505
     506        public void valueChanged(ListSelectionEvent e) {
     507            setEnabled(historyTable.getSelectionModel().getMinSelectionIndex() >= 0);
     508        }
    182509    }
    183510}
Note: See TracChangeset for help on using the changeset viewer.