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                    adjustColumnWidth(VersionTable.this, 5, 0);
061                }
062            });
063        }
064    
065        public VersionTable(HistoryBrowserModel model) {
066            super(model.getVersionTableModel(), new VersionTableColumnModel());
067            model.addObserver(this);
068            build();
069        }
070    
071        // some kind of hack to prevent the table from scrolling to the
072        // right when clicking on the cells
073        @Override
074        public void scrollRectToVisible(Rectangle aRect) {
075            super.scrollRectToVisible(new Rectangle(0, aRect.y, aRect.width, aRect.height));
076        }
077    
078        protected HistoryBrowserModel.VersionTableModel getVersionTableModel() {
079            return (HistoryBrowserModel.VersionTableModel) getModel();
080        }
081    
082        public void update(Observable o, Object arg) {
083            repaint();
084        }
085    
086        protected void showPopupMenu(MouseEvent evt) {
087            HistoryBrowserModel.VersionTableModel model = getVersionTableModel();
088            int row = rowAtPoint(evt.getPoint());
089            if (row > -1 && !model.isLatest(row)) {
090                HistoryOsmPrimitive primitive = model.getPrimitive(row);
091                popupMenu.prepare(primitive);
092                popupMenu.show(evt.getComponent(), evt.getX(), evt.getY());
093            }
094        }
095    
096        class PopupMenuTrigger extends MouseAdapter {
097            @Override
098            public void mousePressed(MouseEvent e) {
099                showPopup(e);
100            }
101            @Override
102            public void mouseReleased(MouseEvent e) {
103                showPopup(e);
104            }
105            private void showPopup(MouseEvent e) {
106                if (e.isPopupTrigger()) {
107                    showPopupMenu(e);
108                }
109            }
110        }
111    
112        static class ChangesetInfoAction extends AbstractInfoAction {
113            private HistoryOsmPrimitive primitive;
114    
115            public ChangesetInfoAction() {
116                super(true);
117                putValue(NAME, tr("Changeset info"));
118                putValue(SHORT_DESCRIPTION, tr("Launch browser with information about the changeset"));
119                putValue(SMALL_ICON, ImageProvider.get("about"));
120            }
121    
122            @Override
123            protected String createInfoUrl(Object infoObject) {
124                HistoryOsmPrimitive primitive = (HistoryOsmPrimitive) infoObject;
125                return getBaseBrowseUrl() + "/changeset/" + primitive.getChangesetId();
126            }
127    
128            @Override
129            public void actionPerformed(ActionEvent e) {
130                if (!isEnabled())
131                    return;
132                String url = createInfoUrl(primitive);
133                launchBrowser(url);
134            }
135    
136            public void prepare(HistoryOsmPrimitive primitive) {
137                putValue(NAME, tr("Show changeset {0}", primitive.getChangesetId()));
138                this.primitive = primitive;
139            }
140        }
141    
142        static class VersionTablePopupMenu extends JPopupMenu {
143    
144            private ChangesetInfoAction changesetInfoAction;
145    
146            protected void build() {
147                changesetInfoAction = new ChangesetInfoAction();
148                add(changesetInfoAction);
149            }
150            public VersionTablePopupMenu() {
151                super();
152                build();
153            }
154    
155            public void prepare(HistoryOsmPrimitive primitive) {
156                changesetInfoAction.prepare(primitive);
157                invalidate();
158            }
159        }
160    
161        public static class RadioButtonRenderer extends JRadioButton implements TableCellRenderer {
162    
163            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
164                setSelected(value != null && (Boolean)value);
165                setHorizontalAlignment(SwingConstants.CENTER);
166                return this;
167            }
168        }
169    
170        public static class RadioButtonEditor extends DefaultCellEditor implements ItemListener {
171    
172            private JRadioButton btn;
173    
174            public RadioButtonEditor() {
175                super(new JCheckBox());
176                btn = new JRadioButton();
177                btn.setHorizontalAlignment(SwingConstants.CENTER);
178            }
179    
180            public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
181                if (value == null) return null;
182                boolean val = (Boolean) value;
183                btn.setSelected(val);
184                btn.addItemListener(this);
185                return btn;
186            }
187    
188            public Object getCellEditorValue() {
189                btn.removeItemListener(this);
190                return btn.isSelected();
191            }
192    
193            public void itemStateChanged(ItemEvent e) {
194                fireEditingStopped();
195            }
196        }
197    
198        public static class LabelRenderer implements TableCellRenderer {
199            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
200                return (Component) value;
201            }
202        }
203    
204        public static class AlignedRenderer extends JLabel implements TableCellRenderer {
205            public AlignedRenderer(int hAlignment) {
206                setHorizontalAlignment(hAlignment);
207            }
208            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,int row,int column) {
209                String v = value.toString();
210                setText(v);
211                return this;
212            }
213        }
214    
215        private static void adjustColumnWidth(JTable tbl, int col, int cellInset) {
216            int maxwidth = 0;
217    
218            for (int row=0; row<tbl.getRowCount(); row++) {
219                TableCellRenderer tcr = tbl.getCellRenderer(row, col);
220                Object val = tbl.getValueAt(row, col);
221                Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, row, col);
222                maxwidth = Math.max(comp.getPreferredSize().width + cellInset, maxwidth);
223            }
224            TableCellRenderer tcr = tbl.getTableHeader().getDefaultRenderer();
225            Object val = tbl.getColumnModel().getColumn(col).getHeaderValue();
226            Component comp = tcr.getTableCellRendererComponent(tbl, val, false, false, -1, col);
227            maxwidth = Math.max(comp.getPreferredSize().width + Main.pref.getInteger("table.header-inset", 0), maxwidth);
228    
229            int spacing = tbl.getIntercellSpacing().width;
230            tbl.getColumnModel().getColumn(col).setPreferredWidth(maxwidth + spacing);
231        }
232    
233    }