Changeset 30712 in osm for applications/editors/josm/plugins/merge-overlap/src
- Timestamp:
- 2014-10-12T12:10:09+02:00 (10 years ago)
- 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 22 22 23 23 import javax.swing.JOptionPane; 24 25 import mergeoverlap.hack.MyCombinePrimitiveResolverDialog; 24 26 25 27 import org.openstreetmap.josm.Main; -
applications/editors/josm/plugins/merge-overlap/src/mergeoverlap/hack/MyCombinePrimitiveResolverDialog.java
r30709 r30712 1 1 // License: GPL. For details, see LICENSE file. 2 package mergeoverlap ;2 package mergeoverlap.hack; 3 3 4 4 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 5 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 import static org.openstreetmap.josm.tools.I18n.trc;7 6 8 7 import java.awt.BorderLayout; … … 10 9 import java.awt.Dimension; 11 10 import java.awt.FlowLayout; 12 import java.awt.GridBagConstraints;13 import java.awt.GridBagLayout;14 import java.awt.Insets;15 11 import java.awt.event.ActionEvent; 16 import java.awt.event.FocusAdapter;17 import java.awt.event.FocusEvent;18 12 import java.awt.event.HierarchyBoundsListener; 19 13 import java.awt.event.HierarchyEvent; 20 import java.awt.event.KeyEvent;21 14 import java.awt.event.WindowAdapter; 22 15 import java.awt.event.WindowEvent; 23 16 import java.beans.PropertyChangeEvent; 24 17 import 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;31 18 import java.util.HashSet; 32 19 import java.util.LinkedList; … … 36 23 37 24 import javax.swing.AbstractAction; 38 import javax.swing.AbstractButton;39 25 import 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;47 26 import javax.swing.JDialog; 48 27 import javax.swing.JLabel; 49 28 import javax.swing.JOptionPane; 50 29 import javax.swing.JPanel; 51 import javax.swing.JScrollPane;52 30 import 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;60 31 61 32 import org.openstreetmap.josm.Main; … … 65 36 import org.openstreetmap.josm.data.osm.OsmPrimitive; 66 37 import org.openstreetmap.josm.data.osm.Relation; 67 import org.openstreetmap.josm.data.osm.RelationMember;68 import org.openstreetmap.josm.data.osm.RelationToChildReference;69 38 import org.openstreetmap.josm.data.osm.TagCollection; 70 39 import org.openstreetmap.josm.data.osm.Way; 71 40 import org.openstreetmap.josm.gui.DefaultNameFormatter; 72 41 import org.openstreetmap.josm.gui.SideButton; 73 import org.openstreetmap.josm.gui.conflict.tags.MultiValueCellEditor;74 import org.openstreetmap.josm.gui.conflict.tags.MultiValueDecisionType;75 42 import org.openstreetmap.josm.gui.conflict.tags.MultiValueResolutionDecision; 76 43 import org.openstreetmap.josm.gui.conflict.tags.RelationMemberConflictDecision; 77 44 import 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;80 45 import org.openstreetmap.josm.gui.help.ContextSensitiveHelpAction; 81 46 import 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;86 47 import org.openstreetmap.josm.tools.ImageProvider; 87 48 import org.openstreetmap.josm.tools.WindowGeometry; … … 470 431 } 471 432 } 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 conflicts494 *495 * @return the current number of conflicts496 */497 public int getNumConflicts() {498 return numConflicts;499 }500 501 /**502 * Updates the current number of conflicts from list of decisions and emits503 * 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 @Override534 public int getRowCount() {535 if (decisions == null) return 0;536 return decisions.size();537 }538 539 @Override540 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 1547 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 @Override555 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 relation574 * @param primitive the child primitive575 */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 of607 * {@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 row626 * @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 model634 *635 * @return the number of decisions managed by this model636 */637 public int getNumDecisions() {638 return getRowCount();639 }640 641 /**642 * Refreshes the model state. Invoke this method to trigger necessary change643 * 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 nothing704 break;705 case UNDECIDED:706 // FIXME: this is an error707 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 the717 * decision is {@see RelationMemberConflictDecisionType#REPLACE}718 * @return a list of commands719 */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 error741 }742 }743 return false;744 }745 746 /**747 * Replies the set of relations which have to be modified according748 * to the decisions managed by this model.749 *750 * @param newPrimitive the primitive which members shall refer to if the751 * decision is {@see RelationMemberConflictDecisionType#REPLACE}752 *753 * @return the set of relations which have to be modified according754 * to the decisions managed by this model755 */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 @Override816 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 @Override879 public void actionPerformed(ActionEvent e) {880 model.applyRole(tfRole.getText());881 }882 }883 884 class ToggleTagRelationsAction implements ChangeListener {885 @Override886 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") : UIManager891 .getColor("Panel.background"));892 tfValue.setBackground(buttonModel.isSelected() ? UIManager.getColor("TextField.background") : UIManager893 .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 TAB944 //945 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(946 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");947 948 // install custom navigation actions949 //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 by960 * 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 row962 * when the user leaves the last cell in the table</li> <ul>963 *964 *965 */966 class SelectNextColumnCellAction extends AbstractAction {967 @Override968 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 by993 * pressing Shift-TAB994 *995 */996 class SelectPreviousColumnCellAction extends AbstractAction {997 998 @Override999 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 nothing1012 } 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 @Override1023 public void gotoNextDecision() {1024 selectNextColumnCellAction.run();1025 }1026 1027 @Override1028 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 @Override1082 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 tags1095 *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 conflicts1135 * @throws IllegalArgumentException thrown if tags is null1136 */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 @Override1147 public int getRowCount() {1148 if (displayedKeys == null) return 0;1149 return displayedKeys.size();1150 }1151 1152 @Override1153 public Object getValueAt(int row, int column) {1154 return decisions.get(displayedKeys.get(row));1155 }1156 1157 @Override1158 public boolean isCellEditable(int row, int column) {1159 return column == 2;1160 }1161 1162 @Override1163 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; false1186 * otherwise1187 */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 be1201 //all decisions, but this method is also used on other places, so I've made new method just for MergeNodes1202 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 displayed1224 *1225 * @param showTagsWithConflictsOnly if true, only tags with conflicts are displayed1226 */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 displayed1234 *1235 * @param showTagsWithMultiValuesOnly if true, only tags with multiple values are displayed1236 */1237 public void setShowTagsWithMultiValuesOnly(boolean showTagsWithMultiValuesOnly) {1238 this.showTagsWithMultiValuesOnly = showTagsWithMultiValuesOnly;1239 rebuild();1240 }1241 1242 /**1243 * Prepare the default decisions for the current model1244 *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 values1264 * 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 @Override1295 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 @Override1307 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 preferences1321 *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 dialog1341 *1342 * @return the model1343 */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 TAB1365 //1366 getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(1367 KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), "selectNextColumnCell");1368 1369 // install custom navigation actions1370 //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 by1383 * 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 row1385 * when the user leaves the last cell in the table</li> <ul>1386 *1387 *1388 */1389 class SelectNextColumnCellAction extends AbstractAction {1390 @Override1391 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 by1416 * pressing Shift-TAB1417 *1418 */1419 class SelectPreviousColumnCellAction extends AbstractAction {1420 1421 @Override1422 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 nothing1435 } 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 @Override1446 public void gotoNextDecision() {1447 selectNextColumnCellAction.run();1448 }1449 1450 @Override1451 public void gotoPreviousDecision() {1452 selectPreviousColumnCellAction.run();1453 }1454 }1455 433 }
Note:
See TracChangeset
for help on using the changeset viewer.