001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.history;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.Component;
007    import java.awt.Dimension;
008    import java.awt.Rectangle;
009    import java.awt.event.ActionEvent;
010    import java.awt.event.ItemEvent;
011    import java.awt.event.ItemListener;
012    import java.awt.event.MouseAdapter;
013    import java.awt.event.MouseEvent;
014    import java.util.Observable;
015    import java.util.Observer;
016    
017    import javax.swing.DefaultCellEditor;
018    import javax.swing.JCheckBox;
019    import javax.swing.JLabel;
020    import javax.swing.JPopupMenu;
021    import javax.swing.JRadioButton;
022    import javax.swing.JTable;
023    import javax.swing.SwingConstants;
024    import javax.swing.UIManager;
025    import javax.swing.event.TableModelEvent;
026    import javax.swing.event.TableModelListener;
027    import javax.swing.table.TableCellRenderer;
028    
029    import org.openstreetmap.josm.Main;
030    import org.openstreetmap.josm.actions.AbstractInfoAction;
031    import org.openstreetmap.josm.data.osm.history.HistoryOsmPrimitive;
032    import org.openstreetmap.josm.tools.ImageProvider;
033    
034    /**
035     * VersionTable shows a list of version in a {@link org.openstreetmap.josm.data.osm.history.History}
036     * of an {@link org.openstreetmap.josm.data.osm.OsmPrimitive}.
037     *
038     */
039    public class VersionTable extends JTable implements Observer{
040        private VersionTablePopupMenu popupMenu;
041    
042        protected void build() {
043            getTableHeader().setFont(getTableHeader().getFont().deriveFont(9f));
044            setRowSelectionAllowed(false);
045            setShowGrid(false);
046            setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
047            setBackground(UIManager.getColor("Button.background"));
048            setIntercellSpacing(new Dimension(6, 0));
049            putClientProperty("terminateEditOnFocusLost", true);
050            popupMenu = new VersionTablePopupMenu();
051            addMouseListener(new PopupMenuTrigger());
052            getModel().addTableModelListener(new TableModelListener() {
053                @Override
054                public void tableChanged(TableModelEvent e) {
055                    adjustColumnWidth(VersionTable.this, 0, 0);
056                    adjustColumnWidth(VersionTable.this, 1, -8);
057                    adjustColumnWidth(VersionTable.this, 2, -8);
058                    adjustColumnWidth(VersionTable.this, 3, 0);
059                    adjustColumnWidth(VersionTable.this, 4, 0);
060                }
061            });
062        }
063    
064        public VersionTable(HistoryBrowserModel model) {
065            super(model.getVersionTableModel(), new VersionTableColumnModel());
066            model.addObserver(this);
067            build();
068        }
069    
070        // some kind of hack to prevent the table from scrolling to the
071        // right when clicking on the cells
072        @Override
073        public void scrollRectToVisible(Rectangle aRect) {
074            super.scrollRectToVisible(new Rectangle(0, aRect.y, aRect.width, aRect.height));
075        }
076    
077        protected HistoryBrowserModel.VersionTableModel getVersionTableModel() {
078            return (HistoryBrowserModel.VersionTableModel) getModel();
079        }
080    
081        public void update(Observable o, Object arg) {
082            repaint();
083        }
084    
085        protected void showPopupMenu(MouseEvent evt) {
086            HistoryBrowserModel.VersionTableModel model = getVersionTableModel();
087            int row = rowAtPoint(evt.getPoint());
088            if (row > -1 && !model.isLatest(row)) {
089                HistoryOsmPrimitive primitive = model.getPrimitive(row);
090                popupMenu.prepare(primitive);
091                popupMenu.show(evt.getComponent(), evt.getX(), evt.getY());
092            }
093        }
094    
095        class PopupMenuTrigger extends MouseAdapter {
096            @Override
097            public void mousePressed(MouseEvent e) {
098                showPopup(e);
099            }
100            @Override
101            public void mouseReleased(MouseEvent e) {
102                showPopup(e);
103            }
104            private void showPopup(MouseEvent e) {
105                if (e.isPopupTrigger()) {
106                    showPopupMenu(e);
107                }
108            }
109        }
110    
111        static class ChangesetInfoAction extends AbstractInfoAction {
112            private HistoryOsmPrimitive primitive;
113    
114            public ChangesetInfoAction() {
115                super(true);
116                putValue(NAME, tr("Changeset info"));
117                putValue(SHORT_DESCRIPTION, tr("Launch browser with information about the changeset"));
118                putValue(SMALL_ICON, ImageProvider.get("about"));
119            }
120    
121            @Override
122            protected String createInfoUrl(Object infoObject) {
123                HistoryOsmPrimitive primitive = (HistoryOsmPrimitive) infoObject;
124                return getBaseBrowseUrl() + "/changeset/" + primitive.getChangesetId();
125            }
126    
127            @Override
128            public void actionPerformed(ActionEvent e) {
129                if (!isEnabled())
130                    return;
131                String url = createInfoUrl(primitive);
132                launchBrowser(url);
133            }
134    
135            public void prepare(HistoryOsmPrimitive primitive) {
136                putValue(NAME, tr("Show changeset {0}", primitive.getChangesetId()));
137                this.primitive = primitive;
138            }
139        }
140    
141        static class VersionTablePopupMenu extends JPopupMenu {
142    
143            private ChangesetInfoAction changesetInfoAction;
144    
145            protected void build() {
146                changesetInfoAction = new ChangesetInfoAction();
147                add(changesetInfoAction);
148            }
149            public VersionTablePopupMenu() {
150                super();
151                build();
152            }
153    
154            public void prepare(HistoryOsmPrimitive primitive) {
155                changesetInfoAction.prepare(primitive);
156                invalidate();
157            }
158        }
159    
160        public static class RadioButtonRenderer extends JRadioButton implements TableCellRenderer {
161    
162            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
163                setSelected(value != null && (Boolean)value);
164                setHorizontalAlignment(SwingConstants.CENTER);
165                return this;
166            }
167        }
168    
169        public static class RadioButtonEditor extends DefaultCellEditor implements ItemListener {
170    
171            private JRadioButton btn;
172    
173            public RadioButtonEditor() {
174                super(new JCheckBox());
175                btn = new JRadioButton();
176                btn.setHorizontalAlignment(SwingConstants.CENTER);
177            }
178    
179            public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
180                if (value == null) return null;
181                boolean val = (Boolean) value;
182                btn.setSelected(val);
183                btn.addItemListener(this);
184                return btn;
185            }
186    
187            public Object getCellEditorValue() {
188                btn.removeItemListener(this);
189                return btn.isSelected();
190            }
191    
192            public void itemStateChanged(ItemEvent e) {
193                fireEditingStopped();
194            }
195        }
196    
197        public static class LabelRenderer implements TableCellRenderer {
198            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
199                return (Component) value;
200            }
201        }
202    
203        public static class AlignedRenderer extends JLabel implements TableCellRenderer {
204            public AlignedRenderer(int hAlignment) {
205                setHorizontalAlignment(hAlignment);
206            }
207            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
208                String v = value.toString();
209                setText(v);
210                return this;
211            }
212        }
213    
214        private static void adjustColumnWidth(JTable tbl, int col, int cellInset) {
215            int maxwidth = 0;
216    
217            for (int row=0; row<tbl.getRowCount(); row++) {
218                TableCellRenderer tcr = tbl.getCellRenderer(row, col);
219                Object val = tbl.getValueAt(row, col);
220                Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col);
221                maxwidth = Math.max(comp.getPreferredSize().width + cellInset, maxwidth);
222            }
223            TableCellRenderer tcr = tbl.getTableHeader().getDefaultRenderer();
224            Object val = tbl.getColumnModel().getColumn(col).getHeaderValue();
225            Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, -1, col);
226            maxwidth = Math.max(comp.getPreferredSize().width + Main.pref.getInteger("table.header-inset", 0), maxwidth);
227    
228            int spacing = tbl.getIntercellSpacing().width;
229            tbl.getColumnModel().getColumn(col).setPreferredWidth(maxwidth + spacing);
230        }
231    
232    }