Ignore:
Timestamp:
2014-10-12T12:10:09+02:00 (10 years ago)
Author:
donvip
Message:

[josm_mege_overlap] move duplicated code to "hack" package and move inner classes into separate files + code alignment of MyTagConflictResolver* to latest version of JOSM classes

Location:
applications/editors/josm/plugins/merge-overlap/src/mergeoverlap
Files:
7 added
1 edited
1 moved

Legend:

Unmodified
Added
Removed
  • applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/MergeOverlapAction.java

    r29854 r30712  
    2222
    2323import javax.swing.JOptionPane;
     24
     25import mergeoverlap.hack.MyCombinePrimitiveResolverDialog;
    2426
    2527import org.openstreetmap.josm.Main;
  • applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/hack/MyCombinePrimitiveResolverDialog.java

    r30709 r30712  
    11// License: GPL. For details, see LICENSE file.
    2 package mergeoverlap;
     2package mergeoverlap.hack;
    33
    44import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
    55import static org.openstreetmap.josm.tools.I18n.tr;
    6 import static org.openstreetmap.josm.tools.I18n.trc;
    76
    87import java.awt.BorderLayout;
     
    109import java.awt.Dimension;
    1110import java.awt.FlowLayout;
    12 import java.awt.GridBagConstraints;
    13 import java.awt.GridBagLayout;
    14 import java.awt.Insets;
    1511import java.awt.event.ActionEvent;
    16 import java.awt.event.FocusAdapter;
    17 import java.awt.event.FocusEvent;
    1812import java.awt.event.HierarchyBoundsListener;
    1913import java.awt.event.HierarchyEvent;
    20 import java.awt.event.KeyEvent;
    2114import java.awt.event.WindowAdapter;
    2215import java.awt.event.WindowEvent;
    2316import java.beans.PropertyChangeEvent;
    2417import java.beans.PropertyChangeListener;
    25 import java.beans.PropertyChangeSupport;
    26 import java.util.ArrayList;
    27 import java.util.Collection;
    28 import java.util.Collections;
    29 import java.util.Comparator;
    30 import java.util.HashMap;
    3118import java.util.HashSet;
    3219import java.util.LinkedList;
     
    3623
    3724import javax.swing.AbstractAction;
    38 import javax.swing.AbstractButton;
    3925import javax.swing.Action;
    40 import javax.swing.BorderFactory;
    41 import javax.swing.BoxLayout;
    42 import javax.swing.ButtonModel;
    43 import javax.swing.JButton;
    44 import javax.swing.JCheckBox;
    45 import javax.swing.JComboBox;
    46 import javax.swing.JComponent;
    4726import javax.swing.JDialog;
    4827import javax.swing.JLabel;
    4928import javax.swing.JOptionPane;
    5029import javax.swing.JPanel;
    51 import javax.swing.JScrollPane;
    5230import javax.swing.JSplitPane;
    53 import javax.swing.JTable;
    54 import javax.swing.KeyStroke;
    55 import javax.swing.ListSelectionModel;
    56 import javax.swing.UIManager;
    57 import javax.swing.event.ChangeEvent;
    58 import javax.swing.event.ChangeListener;
    59 import javax.swing.table.DefaultTableModel;
    6031
    6132import org.openstreetmap.josm.Main;
     
    6536import org.openstreetmap.josm.data.osm.OsmPrimitive;
    6637import org.openstreetmap.josm.data.osm.Relation;
    67 import org.openstreetmap.josm.data.osm.RelationMember;
    68 import org.openstreetmap.josm.data.osm.RelationToChildReference;
    6938import org.openstreetmap.josm.data.osm.TagCollection;
    7039import org.openstreetmap.josm.data.osm.Way;
    7140import org.openstreetmap.josm.gui.DefaultNameFormatter;
    7241import org.openstreetmap.josm.gui.SideButton;
    73 import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor;
    74 import org.openstreetmap.josm.gui.conflict.tags.MultiValueDecisionType;
    7542import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision;
    7643import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision;
    7744import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecisionType;
    78 import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictResolverColumnModel;
    79 import org.openstreetmap.josm.gui.conflict.tags.TagConflictResolverColumnModel;
    8045import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction;
    8146import org.openstreetmap.josm.gui.help.HelpUtil;
    82 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletingTextField;
    83 import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionList;
    84 import org.openstreetmap.josm.gui.widgets.JMultilineLabel;
    85 import org.openstreetmap.josm.tools.CheckParameterUtil;
    8647import org.openstreetmap.josm.tools.ImageProvider;
    8748import org.openstreetmap.josm.tools.WindowGeometry;
     
    470431        }
    471432    }
    472 
    473     /**
    474      * This model manages a list of conflicting relation members.
    475      *
    476      * It can be used as {@see TableModel}.
    477      *
    478      *
    479      */
    480     public static class MyRelationMemberConflictResolverModel extends DefaultTableModel {
    481         /** the property name for the number conflicts managed by this model */
    482         static public final String NUM_CONFLICTS_PROP = MyRelationMemberConflictResolverModel.class.getName() + ".numConflicts";
    483 
    484         /** the list of conflict decisions */
    485         private List<RelationMemberConflictDecision> decisions;
    486         /** the collection of relations for which we manage conflicts */
    487         private Collection<Relation> relations;
    488         /** the number of conflicts */
    489         private int numConflicts;
    490         private PropertyChangeSupport support;
    491 
    492         /**
    493          * Replies the current number of conflicts
    494          *
    495          * @return the current number of conflicts
    496          */
    497         public int getNumConflicts() {
    498             return numConflicts;
    499         }
    500 
    501         /**
    502          * Updates the current number of conflicts from list of decisions and emits
    503          * a property change event if necessary.
    504          *
    505          */
    506         protected void updateNumConflicts() {
    507             int count = 0;
    508             for (RelationMemberConflictDecision decision: decisions) {
    509                 if (!decision.isDecided()) {
    510                     count++;
    511                 }
    512             }
    513             int oldValue = numConflicts;
    514             numConflicts = count;
    515             if (numConflicts != oldValue) {
    516                 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, numConflicts);
    517             }
    518         }
    519 
    520         public void addPropertyChangeListener(PropertyChangeListener l) {
    521             support.addPropertyChangeListener(l);
    522         }
    523 
    524         public void removePropertyChangeListener(PropertyChangeListener l) {
    525             support.removePropertyChangeListener(l);
    526         }
    527 
    528         public MyRelationMemberConflictResolverModel() {
    529             decisions = new ArrayList<RelationMemberConflictDecision>();
    530             support = new PropertyChangeSupport(this);
    531         }
    532 
    533         @Override
    534         public int getRowCount() {
    535             if (decisions == null) return 0;
    536             return decisions.size();
    537         }
    538 
    539         @Override
    540         public Object getValueAt(int row, int column) {
    541             if (decisions == null) return null;
    542 
    543             RelationMemberConflictDecision d = decisions.get(row);
    544             switch(column) {
    545             case 0: /* relation */ return d.getRelation();
    546             case 1: /* pos */ return Integer.toString(d.getPos() + 1); // position in "user space" starting at 1
    547             case 2: /* role */ return d.getRole();
    548             case 3: /* original */ return d.getOriginalPrimitive();
    549             case 4: /* decision */ return d.getDecision();
    550             }
    551             return null;
    552         }
    553 
    554         @Override
    555         public void setValueAt(Object value, int row, int column) {
    556             RelationMemberConflictDecision d = decisions.get(row);
    557             switch(column) {
    558             case 2: /* role */
    559                 d.setRole((String)value);
    560                 break;
    561             case 4: /* decision */
    562                 d.decide((RelationMemberConflictDecisionType)value);
    563                 refresh();
    564                 break;
    565             }
    566             fireTableDataChanged();
    567         }
    568 
    569         /**
    570          * Populates the model with the members of the relation <code>relation</code>
    571          * referring to <code>primitive</code>.
    572          *
    573          * @param relation the parent relation
    574          * @param primitive the child primitive
    575          */
    576         protected void populate(Relation relation, OsmPrimitive primitive, Map<Way, Way> oldWays) {
    577             for (int i = 0; i<relation.getMembersCount(); i++) {
    578                 if (MergeOverlapAction.getOld(relation.getMember(i).getWay(), oldWays) == MergeOverlapAction.getOld((Way)primitive, oldWays)) {
    579                     decisions.add(new RelationMemberConflictDecision(relation, i));
    580                 }
    581             }
    582         }
    583 
    584         /**
    585          * Populates the model with the relation members belonging to one of the relations in <code>relations</code>
    586          * and referring to one of the primitives in <code>memberPrimitives</code>.
    587          *
    588          * @param relations  the parent relations. Empty list assumed if null.
    589          * @param memberPrimitives the child primitives. Empty list assumed if null.
    590          */
    591         public void populate(Collection<Relation> relations, Collection<? extends OsmPrimitive> memberPrimitives, Map<Way, Way> oldWays) {
    592             decisions.clear();
    593                
    594             relations = relations == null ? new LinkedList<Relation>() : relations;
    595             memberPrimitives = memberPrimitives == null ? new LinkedList<OsmPrimitive>() : memberPrimitives;
    596             for (Relation r : relations) {
    597                 for (OsmPrimitive p: memberPrimitives) {
    598                     populate(r, p, oldWays);
    599                 }
    600             }
    601             this.relations = relations;
    602             refresh();
    603         }
    604 
    605         /**
    606          * Populates the model with the relation members represented as a collection of
    607          * {@see RelationToChildReference}s.
    608          *
    609          * @param references the references. Empty list assumed if null.
    610          */
    611         public void populate(Collection<RelationToChildReference> references) {
    612             references = references == null ? new LinkedList<RelationToChildReference>() : references;
    613             decisions.clear();
    614             this.relations = new HashSet<Relation>(references.size());
    615             for (RelationToChildReference reference: references) {
    616                 decisions.add(new RelationMemberConflictDecision(reference.getParent(), reference.getPosition()));
    617                 relations.add(reference.getParent());
    618             }
    619             refresh();
    620         }
    621 
    622         /**
    623          * Replies the decision at position <code>row</code>
    624          *
    625          * @param row
    626          * @return the decision at position <code>row</code>
    627          */
    628         public RelationMemberConflictDecision getDecision(int row) {
    629             return decisions.get(row);
    630         }
    631 
    632         /**
    633          * Replies the number of decisions managed by this model
    634          *
    635          * @return the number of decisions managed by this model
    636          */
    637         public int getNumDecisions() {
    638             return  getRowCount();
    639         }
    640 
    641         /**
    642          * Refreshes the model state. Invoke this method to trigger necessary change
    643          * events after an update of the model data.
    644          *
    645          */
    646         public void refresh() {
    647             updateNumConflicts();
    648             fireTableDataChanged();
    649         }
    650 
    651         /**
    652          * Apply a role to all member managed by this model.
    653          *
    654          * @param role the role. Empty string assumed if null.
    655          */
    656         public void applyRole(String role) {
    657             role = role == null ? "" : role;
    658             for (RelationMemberConflictDecision decision : decisions) {
    659                 decision.setRole(role);
    660             }
    661             refresh();
    662         }
    663 
    664         protected RelationMemberConflictDecision getDecision(Relation relation, int pos) {
    665             for(RelationMemberConflictDecision decision: decisions) {
    666                 if (decision.matches(relation, pos)) return decision;
    667             }
    668             return null;
    669         }
    670 
    671         protected void buildResolveCorrespondance(Relation relation, OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
    672 
    673                 List<RelationMember> relationsMembers = relation.getMembers();
    674                 Relation modifiedRelation = MergeOverlapAction.getNew(relation, newRelations);
    675             modifiedRelation.setMembers(null);
    676 //            boolean isChanged = false;
    677             for (int i=0; i < relationsMembers.size(); i++) {
    678                 RelationMember rm = relationsMembers.get(i);
    679 //                RelationMember rm = relation.getMember(i);
    680 //                RelationMember rmNew;
    681                 RelationMemberConflictDecision decision = getDecision(relation, i);
    682                 if (decision == null) {
    683                     modifiedRelation.addMember(rm);
    684                 } else {
    685                         System.out.println(modifiedRelation);
    686                         System.out.println(111);
    687                     switch(decision.getDecision()) {
    688                     case KEEP:
    689 //                      modifiedRelation.removeMembersFor(newPrimitive);
    690                         System.out.println(222);
    691                         if (newPrimitive instanceof Way) {
    692                                 modifiedRelation.addMember(new RelationMember(decision.getRole(), MergeOverlapAction.getOld((Way)newPrimitive, oldWays)));
    693                         }
    694                         else {
    695                                 modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
    696                         }
    697 //                      modifiedRelation.addMember(new RelationMember(decision.getRole(), newPrimitive));
    698                         break;
    699                     case REMOVE:
    700                         System.out.println(333);
    701 //                      modifiedRelation.removeMembersFor(rm.getMember());
    702 //                        isChanged = true;
    703                         // do nothing
    704                         break;
    705                     case UNDECIDED:
    706                         // FIXME: this is an error
    707                         break;
    708                     }
    709                 }
    710             }
    711         }
    712 
    713         /**
    714          * Builds a collection of commands executing the decisions made in this model.
    715          *
    716          * @param newPrimitive the primitive which members shall refer to if the
    717          * decision is {@see RelationMemberConflictDecisionType#REPLACE}
    718          * @return a list of commands
    719          */
    720         public void buildRelationCorrespondance(OsmPrimitive newPrimitive, Map<Relation, Relation> newRelations, Map<Way, Way> oldWays) {
    721             for (Relation relation : relations) {
    722                 buildResolveCorrespondance(relation, newPrimitive, newRelations, oldWays);
    723             }
    724         }
    725 
    726         protected boolean isChanged(Relation relation, OsmPrimitive newPrimitive) {
    727             for (int i=0; i < relation.getMembersCount(); i++) {
    728                 RelationMemberConflictDecision decision = getDecision(relation, i);
    729                 if (decision == null) {
    730                     continue;
    731                 }
    732                 switch(decision.getDecision()) {
    733                 case REMOVE: return true;
    734                 case KEEP:
    735                     if (!relation.getMember(i).getRole().equals(decision.getRole()))
    736                         return true;
    737                     if (relation.getMember(i).getMember() != newPrimitive)
    738                         return true;
    739                 case UNDECIDED:
    740                     // FIXME: handle error
    741                 }
    742             }
    743             return false;
    744         }
    745 
    746         /**
    747          * Replies the set of relations which have to be modified according
    748          * to the decisions managed by this model.
    749          *
    750          * @param newPrimitive the primitive which members shall refer to if the
    751          * decision is {@see RelationMemberConflictDecisionType#REPLACE}
    752          *
    753          * @return the set of relations which have to be modified according
    754          * to the decisions managed by this model
    755          */
    756         public Set<Relation> getModifiedRelations(OsmPrimitive newPrimitive) {
    757             HashSet<Relation> ret = new HashSet<Relation>();
    758             for (Relation relation: relations) {
    759                 if (isChanged(relation, newPrimitive)) {
    760                     ret.add(relation);
    761                 }
    762             }
    763             return ret;
    764         }
    765     }
    766    
    767     public class MyRelationMemberConflictResolver extends JPanel {
    768 
    769         private AutoCompletingTextField tfRole;
    770         private AutoCompletingTextField tfKey;
    771         private AutoCompletingTextField tfValue;
    772         private JCheckBox cbTagRelations;
    773         private MyRelationMemberConflictResolverModel model;
    774         private MyRelationMemberConflictResolverTable tblResolver;
    775         private JMultilineLabel lblHeader;
    776 
    777         protected void build() {
    778             setLayout(new GridBagLayout());
    779             JPanel pnl = new JPanel();
    780             pnl.setLayout(new BorderLayout());
    781             pnl.add(lblHeader = new JMultilineLabel(""));
    782             GridBagConstraints gc = new GridBagConstraints();
    783             gc.fill = GridBagConstraints.HORIZONTAL;
    784             gc.weighty = 0.0;
    785             gc.weightx = 1.0;
    786             gc.insets = new Insets(5,5,5,5);
    787             add(pnl, gc);
    788             model = new MyRelationMemberConflictResolverModel();
    789 
    790             gc.gridy = 1;
    791             gc.weighty = 1.0;
    792             gc.fill = GridBagConstraints.BOTH;
    793             gc.insets = new Insets(0,0,0,0);
    794             add(new JScrollPane(tblResolver = new MyRelationMemberConflictResolverTable(model)), gc);
    795             pnl = new JPanel();
    796             pnl.setLayout(new BoxLayout(pnl, BoxLayout.Y_AXIS));
    797             pnl.add(buildRoleEditingPanel());
    798             pnl.add(buildTagRelationsPanel());
    799             gc.gridy = 2;
    800             gc.weighty = 0.0;
    801             gc.fill = GridBagConstraints.HORIZONTAL;
    802             add(pnl,gc);
    803         }
    804 
    805         protected JPanel buildRoleEditingPanel() {
    806             JPanel pnl = new JPanel();
    807             pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
    808             pnl.add(new JLabel(tr("Role:")));
    809             pnl.add(tfRole = new AutoCompletingTextField(10));
    810             tfRole.setToolTipText(tr("Enter a role for all relation memberships"));
    811             pnl.add(new JButton(new ApplyRoleAction()));
    812             tfRole.addActionListener(new ApplyRoleAction());
    813             tfRole.addFocusListener(
    814                     new FocusAdapter() {
    815                         @Override
    816                         public void focusGained(FocusEvent e) {
    817                             tfRole.selectAll();
    818                         }
    819                     }
    820             );
    821             return pnl;
    822         }
    823 
    824         protected JPanel buildTagRelationsPanel() {
    825             JPanel pnl = new JPanel();
    826             pnl.setLayout(new FlowLayout(FlowLayout.LEFT));
    827             cbTagRelations = new JCheckBox(tr("Tag modified relations with "));
    828             cbTagRelations.addChangeListener(new ToggleTagRelationsAction());
    829             cbTagRelations.setToolTipText(
    830                     tr("<html>Select to enable entering a tag which will be applied<br>"
    831                             + "to all modified relations.</html>"));
    832             pnl.add(cbTagRelations);
    833             pnl.add(new JLabel(trc("tag", "Key:")));
    834             pnl.add(tfKey = new AutoCompletingTextField(10));
    835             tfKey.setToolTipText(tr("<html>Enter a tag key, i.e. <strong><tt>fixme</tt></strong></html>"));
    836             pnl.add(new JLabel(tr("Value:")));
    837             pnl.add(tfValue = new AutoCompletingTextField(10));
    838             tfValue.setToolTipText(tr("<html>Enter a tag value, i.e. <strong><tt>check members</tt></strong></html>"));
    839             cbTagRelations.setSelected(false);
    840             tfKey.setEnabled(false);
    841             tfValue.setEnabled(false);
    842             return pnl;
    843         }
    844 
    845         public MyRelationMemberConflictResolver() {
    846             build();
    847         }
    848 
    849         public void initForWayCombining() {
    850             lblHeader.setText(tr("<html>The combined ways are members in one ore more relations. "
    851                     + "Please decide whether you want to <strong>keep</strong> these memberships "
    852                     + "for the combined way or whether you want to <strong>remove</strong> them.<br>"
    853                     + "The default is to <strong>keep</strong> the first way and <strong>remove</strong> "
    854                     + "the other ways that are members of the same relation: the combined way will "
    855                     + "take the place of the original way in the relation."
    856                     + "</html>"));
    857             invalidate();
    858         }
    859 
    860         public void initForNodeMerging() {
    861             lblHeader.setText(tr("<html>The merged nodes are members in one ore more relations. "
    862                     + "Please decide whether you want to <strong>keep</strong> these memberships "
    863                     + "for the target node or whether you want to <strong>remove</strong> them.<br>"
    864                     + "The default is to <strong>keep</strong> the first node and <strong>remove</strong> "
    865                     + "the other nodes that are members of the same relation: the target node will "
    866                     + "take the place of the original node in the relation."
    867                     + "</html>"));
    868             invalidate();
    869         }
    870 
    871         class ApplyRoleAction extends AbstractAction {
    872             public ApplyRoleAction() {
    873                 putValue(NAME, tr("Apply"));
    874                 putValue(SMALL_ICON, ImageProvider.get("ok"));
    875                 putValue(SHORT_DESCRIPTION, tr("Apply this role to all members"));
    876             }
    877 
    878             @Override
    879             public void actionPerformed(ActionEvent e) {
    880                 model.applyRole(tfRole.getText());
    881             }
    882         }
    883 
    884         class ToggleTagRelationsAction implements ChangeListener {
    885             @Override
    886             public void stateChanged(ChangeEvent e) {
    887                 ButtonModel buttonModel = ((AbstractButton) e.getSource()).getModel();
    888                 tfKey.setEnabled(buttonModel.isSelected());
    889                 tfValue.setEnabled(buttonModel.isSelected());
    890                 tfKey.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
    891                         .getColor("Panel.background"));
    892                 tfValue.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager
    893                         .getColor("Panel.background"));
    894             }
    895         }
    896 
    897         public MyRelationMemberConflictResolverModel getModel() {
    898             return model;
    899         }
    900 
    901         public Command buildTagApplyCommands(Collection<? extends OsmPrimitive> primitives) {
    902             if (!cbTagRelations.isSelected())
    903                 return null;
    904             if (tfKey.getText().trim().equals(""))
    905                 return null;
    906             if (tfValue.getText().trim().equals(""))
    907                 return null;
    908             if (primitives == null || primitives.isEmpty())
    909                 return null;
    910             return new ChangePropertyCommand(primitives, tfKey.getText(), tfValue.getText());
    911         }
    912 
    913         public void prepareForEditing() {
    914             AutoCompletionList acList = new AutoCompletionList();
    915             Main.main.getEditLayer().data.getAutoCompletionManager().populateWithMemberRoles(acList);
    916             tfRole.setAutoCompletionList(acList);
    917             AutoCompletingTextField editor = (AutoCompletingTextField) tblResolver.getColumnModel().getColumn(2).getCellEditor();
    918             if (editor != null) {
    919                 editor.setAutoCompletionList(acList);
    920             }
    921             AutoCompletionList acList2 = new AutoCompletionList();
    922             Main.main.getEditLayer().data.getAutoCompletionManager().populateWithKeys(acList2);
    923             tfKey.setAutoCompletionList(acList2);
    924         }
    925     }
    926 
    927 
    928     public class MyRelationMemberConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
    929 
    930         private SelectNextColumnCellAction selectNextColumnCellAction;
    931         private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
    932 
    933         public MyRelationMemberConflictResolverTable(MyRelationMemberConflictResolverModel model) {
    934             super(model, new RelationMemberConflictResolverColumnModel());
    935             build();
    936         }
    937 
    938         protected void build() {
    939             setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    940             setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    941             putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    942 
    943             // make ENTER behave like TAB
    944             //
    945             getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
    946                     KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
    947 
    948             // install custom navigation actions
    949             //
    950             selectNextColumnCellAction = new SelectNextColumnCellAction();
    951             selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
    952             getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
    953             getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
    954 
    955             setRowHeight((int)new JComboBox<Object>().getPreferredSize().getHeight());
    956         }
    957 
    958         /**
    959          * Action to be run when the user navigates to the next cell in the table, for instance by
    960          * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
    961          * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
    962          * when the user leaves the last cell in the table</li> <ul>
    963          *
    964          *
    965          */
    966         class SelectNextColumnCellAction extends AbstractAction {
    967             @Override
    968             public void actionPerformed(ActionEvent e) {
    969                 run();
    970             }
    971 
    972             public void run() {
    973                 int col = getSelectedColumn();
    974                 int row = getSelectedRow();
    975                 if (getCellEditor() != null) {
    976                     getCellEditor().stopCellEditing();
    977                 }
    978 
    979                 if (col == 2 && row < getRowCount() - 1) {
    980                     row++;
    981                 } else if (row < getRowCount() - 1) {
    982                     col = 2;
    983                     row++;
    984                 }
    985                 changeSelection(row, col, false, false);
    986                 editCellAt(getSelectedRow(), getSelectedColumn());
    987                 getEditorComponent().requestFocusInWindow();
    988             }
    989         }
    990 
    991         /**
    992          * Action to be run when the user navigates to the previous cell in the table, for instance by
    993          * pressing Shift-TAB
    994          *
    995          */
    996         class SelectPreviousColumnCellAction extends AbstractAction {
    997 
    998             @Override
    999             public void actionPerformed(ActionEvent e) {
    1000                 run();
    1001             }
    1002 
    1003             public void run() {
    1004                 int col = getSelectedColumn();
    1005                 int row = getSelectedRow();
    1006                 if (getCellEditor() != null) {
    1007                     getCellEditor().stopCellEditing();
    1008                 }
    1009 
    1010                 if (col <= 0 && row <= 0) {
    1011                     // change nothing
    1012                 } else if (row > 0) {
    1013                     col = 2;
    1014                     row--;
    1015                 }
    1016                 changeSelection(row, col, false, false);
    1017                 editCellAt(getSelectedRow(), getSelectedColumn());
    1018                 getEditorComponent().requestFocusInWindow();
    1019             }
    1020         }
    1021 
    1022         @Override
    1023         public void gotoNextDecision() {
    1024             selectNextColumnCellAction.run();
    1025         }
    1026 
    1027         @Override
    1028         public void gotoPreviousDecision() {
    1029             selectPreviousColumnCellAction.run();
    1030         }
    1031     }
    1032 
    1033 
    1034     public static class MyTagConflictResolverModel extends DefaultTableModel {
    1035         static public final String NUM_CONFLICTS_PROP = MyTagConflictResolverModel.class.getName() + ".numConflicts";
    1036 
    1037         private TagCollection tags;
    1038         private List<String> displayedKeys;
    1039         private Set<String> keysWithConflicts;
    1040         private HashMap<String, MultiValueResolutionDecision> decisions;
    1041         private int numConflicts;
    1042         private PropertyChangeSupport support;
    1043         private boolean showTagsWithConflictsOnly = false;
    1044         private boolean showTagsWithMultiValuesOnly = false;
    1045 
    1046         public MyTagConflictResolverModel() {
    1047             numConflicts = 0;
    1048             support = new PropertyChangeSupport(this);
    1049         }
    1050 
    1051         public void addPropertyChangeListener(PropertyChangeListener listener) {
    1052             support.addPropertyChangeListener(listener);
    1053         }
    1054 
    1055         public void removePropertyChangeListener(PropertyChangeListener listener) {
    1056             support.removePropertyChangeListener(listener);
    1057         }
    1058 
    1059         protected void setNumConflicts(int numConflicts) {
    1060             int oldValue = this.numConflicts;
    1061             this.numConflicts = numConflicts;
    1062             if (oldValue != this.numConflicts) {
    1063                 support.firePropertyChange(NUM_CONFLICTS_PROP, oldValue, this.numConflicts);
    1064             }
    1065         }
    1066 
    1067         protected void refreshNumConflicts() {
    1068             int count = 0;
    1069             for (MultiValueResolutionDecision d : decisions.values()) {
    1070                 if (!d.isDecided()) {
    1071                     count++;
    1072                 }
    1073             }
    1074             setNumConflicts(count);
    1075         }
    1076 
    1077         protected void sort() {
    1078             Collections.sort(
    1079                     displayedKeys,
    1080                     new Comparator<String>() {
    1081                         @Override
    1082                         public int compare(String key1, String key2) {
    1083                             if (decisions.get(key1).isDecided() && ! decisions.get(key2).isDecided())
    1084                                 return 1;
    1085                             else if (!decisions.get(key1).isDecided() && decisions.get(key2).isDecided())
    1086                                 return -1;
    1087                             return key1.compareTo(key2);
    1088                         }
    1089                     }
    1090             );
    1091         }
    1092 
    1093         /**
    1094          * initializes the model from the current tags
    1095          *
    1096          */
    1097         protected void rebuild() {
    1098             if (tags == null) return;
    1099             for(String key: tags.getKeys()) {
    1100                 MultiValueResolutionDecision decision = new MultiValueResolutionDecision(tags.getTagsFor(key));
    1101                 if (decisions.get(key) == null) {
    1102                     decisions.put(key,decision);
    1103                 }
    1104             }
    1105             displayedKeys.clear();
    1106             Set<String> keys = tags.getKeys();
    1107             if (showTagsWithConflictsOnly) {
    1108                 keys.retainAll(keysWithConflicts);
    1109                 if (showTagsWithMultiValuesOnly) {
    1110                     Set<String> keysWithMultiValues = new HashSet<String>();
    1111                     for (String key: keys) {
    1112                         if (decisions.get(key).canKeepAll()) {
    1113                             keysWithMultiValues.add(key);
    1114                         }
    1115                     }
    1116                     keys.retainAll(keysWithMultiValues);
    1117                 }
    1118                 for (String key: tags.getKeys()) {
    1119                     if (!decisions.get(key).isDecided() && !keys.contains(key)) {
    1120                         keys.add(key);
    1121                     }
    1122                 }
    1123             }
    1124             displayedKeys.addAll(keys);
    1125             refreshNumConflicts();
    1126             sort();
    1127             fireTableDataChanged();
    1128         }
    1129 
    1130         /**
    1131          * Populates the model with the tags for which conflicts are to be resolved.
    1132          *
    1133          * @param tags  the tag collection with the tags. Must not be null.
    1134          * @param keysWithConflicts the set of tag keys with conflicts
    1135          * @throws IllegalArgumentException thrown if tags is null
    1136          */
    1137         public void populate(TagCollection tags, Set<String> keysWithConflicts) {
    1138             CheckParameterUtil.ensureParameterNotNull(tags, "tags");
    1139             this.tags = tags;
    1140             displayedKeys = new ArrayList<String>();
    1141             this.keysWithConflicts = keysWithConflicts == null ? new HashSet<String>() : keysWithConflicts;
    1142             decisions = new HashMap<String, MultiValueResolutionDecision>();
    1143             rebuild();
    1144         }
    1145 
    1146         @Override
    1147         public int getRowCount() {
    1148             if (displayedKeys == null) return 0;
    1149             return displayedKeys.size();
    1150         }
    1151 
    1152         @Override
    1153         public Object getValueAt(int row, int column) {
    1154             return decisions.get(displayedKeys.get(row));
    1155         }
    1156 
    1157         @Override
    1158         public boolean isCellEditable(int row, int column) {
    1159             return column == 2;
    1160         }
    1161 
    1162         @Override
    1163         public void setValueAt(Object value, int row, int column) {
    1164             MultiValueResolutionDecision decision = decisions.get(displayedKeys.get(row));
    1165             if (value instanceof String) {
    1166                 decision.keepOne((String)value);
    1167             } else if (value instanceof MultiValueDecisionType) {
    1168                 MultiValueDecisionType type = (MultiValueDecisionType)value;
    1169                 switch(type) {
    1170                 case KEEP_NONE:
    1171                     decision.keepNone();
    1172                     break;
    1173                 case KEEP_ALL:
    1174                     decision.keepAll();
    1175                     break;
    1176                 }
    1177             }
    1178             fireTableDataChanged();
    1179             refreshNumConflicts();
    1180         }
    1181 
    1182         /**
    1183          * Replies true if each {@see MultiValueResolutionDecision} is decided.
    1184          *
    1185          * @return true if each {@see MultiValueResolutionDecision} is decided; false
    1186          * otherwise
    1187          */
    1188         public boolean isResolvedCompletely() {
    1189             return numConflicts == 0;
    1190         }
    1191 
    1192         public int getNumConflicts() {
    1193             return numConflicts;
    1194         }
    1195 
    1196         public int getNumDecisions() {
    1197             return getRowCount();
    1198         }
    1199 
    1200         //TODO Should this method work with all decisions or only with displayed decisions? For MergeNodes it should be
    1201         //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes
    1202         public TagCollection getResolution() {
    1203             TagCollection tc = new TagCollection();
    1204             for (String key: displayedKeys) {
    1205                 tc.add(decisions.get(key).getResolution());
    1206             }
    1207             return tc;
    1208         }
    1209 
    1210         public TagCollection getAllResolutions() {
    1211             TagCollection tc = new TagCollection();
    1212             for (MultiValueResolutionDecision value: decisions.values()) {
    1213                 tc.add(value.getResolution());
    1214             }
    1215             return tc;
    1216         }
    1217 
    1218         public MultiValueResolutionDecision getDecision(int row) {
    1219             return decisions.get(displayedKeys.get(row));
    1220         }
    1221 
    1222         /**
    1223          * Sets whether all tags or only tags with conflicts are displayed
    1224          *
    1225          * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed
    1226          */
    1227         public void setShowTagsWithConflictsOnly(boolean showTagsWithConflictsOnly) {
    1228             this.showTagsWithConflictsOnly = showTagsWithConflictsOnly;
    1229             rebuild();
    1230         }
    1231 
    1232         /**
    1233          * Sets whether all conflicts or only conflicts with multiple values are displayed
    1234          *
    1235          * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed
    1236          */
    1237         public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {
    1238             this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;
    1239             rebuild();
    1240         }
    1241 
    1242         /**
    1243          * Prepare the default decisions for the current model
    1244          *
    1245          */
    1246         public void prepareDefaultTagDecisions() {
    1247             for (MultiValueResolutionDecision decision: decisions.values()) {
    1248                 List<String> values = decision.getValues();
    1249                 values.remove("");
    1250                 if (values.size() == 1) {
    1251                     decision.keepOne(values.get(0));
    1252                 } else {
    1253                     decision.keepAll();
    1254                 }
    1255             }
    1256             rebuild();
    1257         }
    1258 
    1259     }
    1260 
    1261 
    1262     /**
    1263      * This is a UI widget for resolving tag conflicts, i.e. differences of the tag values
    1264      * of multiple {@see OsmPrimitive}s.
    1265      *
    1266      *
    1267      */
    1268     public class MyTagConflictResolver extends JPanel {
    1269 
    1270         /** the model for the tag conflict resolver */
    1271         private MyTagConflictResolverModel model;
    1272         /** selects wheter only tags with conflicts are displayed */
    1273         private JCheckBox cbShowTagsWithConflictsOnly;
    1274         private JCheckBox cbShowTagsWithMultiValuesOnly;
    1275 
    1276         protected JPanel buildInfoPanel() {
    1277             JPanel pnl = new JPanel();
    1278             pnl.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
    1279             pnl.setLayout(new GridBagLayout());
    1280             GridBagConstraints gc = new GridBagConstraints();
    1281             gc.fill = GridBagConstraints.BOTH;
    1282             gc.weighty = 1.0;
    1283             gc.weightx = 1.0;
    1284             gc.anchor = GridBagConstraints.LINE_START;
    1285             pnl.add(new JLabel(tr("<html>Please select the values to keep for the following tags.</html>")), gc);
    1286 
    1287             gc.gridy = 1;
    1288             gc.fill = GridBagConstraints.HORIZONTAL;
    1289             gc.weighty = 0.0;
    1290             pnl.add(cbShowTagsWithConflictsOnly = new JCheckBox(tr("Show tags with conflicts only")), gc);
    1291             pnl.add(cbShowTagsWithMultiValuesOnly = new JCheckBox(tr("Show tags with multiple values only")), gc);
    1292             cbShowTagsWithConflictsOnly.addChangeListener(
    1293                     new ChangeListener() {
    1294                         @Override
    1295                         public void stateChanged(ChangeEvent e) {
    1296                             model.setShowTagsWithConflictsOnly(cbShowTagsWithConflictsOnly.isSelected());
    1297                             cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
    1298                         }
    1299                     }
    1300             );
    1301             cbShowTagsWithConflictsOnly.setSelected(
    1302                     Main.pref.getBoolean(getClass().getName() + ".showTagsWithConflictsOnly", false)
    1303             );
    1304             cbShowTagsWithMultiValuesOnly.addChangeListener(
    1305                     new ChangeListener() {
    1306                         @Override
    1307                         public void stateChanged(ChangeEvent e) {
    1308                             model.setShowTagsWithMultiValuesOnly(cbShowTagsWithMultiValuesOnly.isSelected());
    1309                         }
    1310                     }
    1311             );
    1312             cbShowTagsWithMultiValuesOnly.setSelected(
    1313                     Main.pref.getBoolean(getClass().getName() + ".showTagsWithMultiValuesOnly", false)
    1314             );
    1315             cbShowTagsWithMultiValuesOnly.setEnabled(cbShowTagsWithConflictsOnly.isSelected());
    1316             return pnl;
    1317         }
    1318 
    1319         /**
    1320          * Remembers the current settings in the global preferences
    1321          *
    1322          */
    1323         public void rememberPreferences() {
    1324             Main.pref.put(getClass().getName() + ".showTagsWithConflictsOnly", cbShowTagsWithConflictsOnly.isSelected());
    1325             Main.pref.put(getClass().getName() + ".showTagsWithMultiValuesOnly", cbShowTagsWithMultiValuesOnly.isSelected());
    1326         }
    1327 
    1328         protected void build() {
    1329             setLayout(new BorderLayout());
    1330             add(buildInfoPanel(), BorderLayout.NORTH);
    1331             add(new JScrollPane(new MyTagConflictResolverTable(model)), BorderLayout.CENTER);
    1332         }
    1333 
    1334         public MyTagConflictResolver() {
    1335             this.model = new MyTagConflictResolverModel();
    1336             build();
    1337         }
    1338 
    1339         /**
    1340          * Replies the model used by this dialog
    1341          *
    1342          * @return the model
    1343          */
    1344         public MyTagConflictResolverModel getModel() {
    1345             return model;
    1346         }
    1347     }
    1348 
    1349     public class MyTagConflictResolverTable extends JTable implements MultiValueCellEditor.NavigationListener {
    1350 
    1351         private SelectNextColumnCellAction selectNextColumnCellAction;
    1352         private SelectPreviousColumnCellAction selectPreviousColumnCellAction;
    1353 
    1354         public MyTagConflictResolverTable(MyTagConflictResolverModel model) {
    1355             super(model, new TagConflictResolverColumnModel());
    1356             build();
    1357         }
    1358 
    1359         protected void build() {
    1360             setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
    1361             setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    1362             putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
    1363 
    1364             // make ENTER behave like TAB
    1365             //
    1366             getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(
    1367                     KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");
    1368 
    1369             // install custom navigation actions
    1370             //
    1371             selectNextColumnCellAction = new SelectNextColumnCellAction();
    1372             selectPreviousColumnCellAction = new SelectPreviousColumnCellAction();
    1373             getActionMap().put("selectNextColumnCell", selectNextColumnCellAction);
    1374             getActionMap().put("selectPreviousColumnCell", selectPreviousColumnCellAction);
    1375 
    1376             ((MultiValueCellEditor)getColumnModel().getColumn(2).getCellEditor()).addNavigationListeners(this);
    1377 
    1378             setRowHeight((int)new JComboBox<Object>().getPreferredSize().getHeight());
    1379         }
    1380 
    1381         /**
    1382          * Action to be run when the user navigates to the next cell in the table, for instance by
    1383          * pressing TAB or ENTER. The action alters the standard navigation path from cell to cell: <ul>
    1384          * <li>it jumps over cells in the first column</li> <li>it automatically add a new empty row
    1385          * when the user leaves the last cell in the table</li> <ul>
    1386          *
    1387          *
    1388          */
    1389         class SelectNextColumnCellAction extends AbstractAction {
    1390             @Override
    1391             public void actionPerformed(ActionEvent e) {
    1392                 run();
    1393             }
    1394 
    1395             public void run() {
    1396                 int col = getSelectedColumn();
    1397                 int row = getSelectedRow();
    1398                 if (getCellEditor() != null) {
    1399                     getCellEditor().stopCellEditing();
    1400                 }
    1401 
    1402                 if (col == 2 && row < getRowCount() - 1) {
    1403                     row++;
    1404                 } else if (row < getRowCount() - 1) {
    1405                     col = 2;
    1406                     row++;
    1407                 }
    1408                 changeSelection(row, col, false, false);
    1409                 editCellAt(getSelectedRow(), getSelectedColumn());
    1410                 getEditorComponent().requestFocusInWindow();
    1411             }
    1412         }
    1413 
    1414         /**
    1415          * Action to be run when the user navigates to the previous cell in the table, for instance by
    1416          * pressing Shift-TAB
    1417          *
    1418          */
    1419         class SelectPreviousColumnCellAction extends AbstractAction {
    1420 
    1421             @Override
    1422             public void actionPerformed(ActionEvent e) {
    1423                 run();
    1424             }
    1425 
    1426             public void run() {
    1427                 int col = getSelectedColumn();
    1428                 int row = getSelectedRow();
    1429                 if (getCellEditor() != null) {
    1430                     getCellEditor().stopCellEditing();
    1431                 }
    1432 
    1433                 if (col <= 0 && row <= 0) {
    1434                     // change nothing
    1435                 } else if (row > 0) {
    1436                     col = 2;
    1437                     row--;
    1438                 }
    1439                 changeSelection(row, col, false, false);
    1440                 editCellAt(getSelectedRow(), getSelectedColumn());
    1441                 getEditorComponent().requestFocusInWindow();
    1442             }
    1443         }
    1444 
    1445         @Override
    1446         public void gotoNextDecision() {
    1447             selectNextColumnCellAction.run();
    1448         }
    1449 
    1450         @Override
    1451         public void gotoPreviousDecision() {
    1452             selectPreviousColumnCellAction.run();
    1453         }
    1454     }
    1455433}
Note: See TracChangeset for help on using the changeset viewer.