source: josm/trunk/src/org/openstreetmap/josm/gui/dialogs/relation/MemberTableModel.java@ 1922

Last change on this file since 1922 was 1922, checked in by Gubaer, 15 years ago

fixed #3207 again: Highlighting members in relation editor is broken

File size: 24.6 KB
Line 
1package org.openstreetmap.josm.gui.dialogs.relation;
2
3import java.util.ArrayList;
4import java.util.Arrays;
5import java.util.Collection;
6import java.util.Collections;
7import java.util.HashSet;
8import java.util.Iterator;
9import java.util.LinkedList;
10import java.util.List;
11import java.util.Set;
12import java.util.Vector;
13import java.util.concurrent.CopyOnWriteArrayList;
14
15import javax.swing.DefaultListSelectionModel;
16import javax.swing.ListSelectionModel;
17import javax.swing.table.AbstractTableModel;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.data.SelectionChangedListener;
21import org.openstreetmap.josm.data.osm.DataSet;
22import org.openstreetmap.josm.data.osm.Node;
23import org.openstreetmap.josm.data.osm.OsmPrimitive;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.RelationMember;
26import org.openstreetmap.josm.data.osm.Way;
27import org.openstreetmap.josm.gui.layer.OsmDataLayer;
28
29public class MemberTableModel extends AbstractTableModel {
30
31 private ArrayList<RelationMember> members;
32 private DefaultListSelectionModel listSelectionModel;
33 private CopyOnWriteArrayList<IMemberModelListener> listeners;
34 private OsmDataLayer layer;
35
36 /**
37 * constructor
38 */
39 public MemberTableModel(OsmDataLayer layer) {
40 members = new ArrayList<RelationMember>();
41 listeners = new CopyOnWriteArrayList<IMemberModelListener>();
42 this.layer = layer;
43 }
44
45 public void addMemberModelListener(IMemberModelListener listener) {
46 synchronized (listeners) {
47 if (listener != null && !listeners.contains(listener)) {
48 listeners.add(listener);
49 }
50 }
51 }
52
53 public void removeMemberModelListener(IMemberModelListener listener) {
54 synchronized (listeners) {
55 if (listener != null && listeners.contains(listener)) {
56 listeners.remove(listener);
57 }
58 }
59 }
60
61 protected void fireMakeMemberVisible(int index) {
62 synchronized (listeners) {
63 for (IMemberModelListener listener : listeners) {
64 listener.makeMemberVisible(index);
65 }
66 }
67 }
68
69 public void populate(Relation relation) {
70 members.clear();
71 if (relation != null && relation.members != null) {
72 // make sure we work with clones of the relation members
73 // in the model.
74 members.addAll(new Relation(relation).members);
75 }
76 fireTableDataChanged();
77 }
78
79 public int getColumnCount() {
80 return 3;
81 }
82
83 public int getRowCount() {
84 return members.size();
85 }
86
87 public Object getValueAt(int rowIndex, int columnIndex) {
88 switch (columnIndex) {
89 case 0:
90 return members.get(rowIndex).role;
91 case 1:
92 return members.get(rowIndex).member;
93 case 2:
94 return linked(rowIndex);
95 }
96 // should not happen
97 return null;
98 }
99
100 @Override
101 public boolean isCellEditable(int rowIndex, int columnIndex) {
102 return columnIndex == 0;
103 }
104
105 @Override
106 public void setValueAt(Object value, int rowIndex, int columnIndex) {
107 RelationMember member = members.get(rowIndex);
108 member.role = value.toString();
109 }
110
111 public OsmPrimitive getReferredPrimitive(int idx) {
112 return members.get(idx).member;
113 }
114
115 public void moveUp(int[] selectedRows) {
116 if (!canMoveUp(selectedRows))
117 return;
118
119 for (int row : selectedRows) {
120 RelationMember member1 = members.get(row);
121 RelationMember member2 = members.get(row - 1);
122 members.set(row, member2);
123 members.set(row - 1, member1);
124 }
125 fireTableDataChanged();
126 getSelectionModel().setValueIsAdjusting(true);
127 getSelectionModel().clearSelection();
128 for (int row : selectedRows) {
129 row--;
130 getSelectionModel().addSelectionInterval(row, row);
131 }
132 getSelectionModel().setValueIsAdjusting(false);
133 fireMakeMemberVisible(selectedRows[0] - 1);
134 }
135
136 public void moveDown(int[] selectedRows) {
137 if (!canMoveDown(selectedRows))
138 return;
139
140 for (int i = selectedRows.length - 1; i >= 0; i--) {
141 int row = selectedRows[i];
142 RelationMember member1 = members.get(row);
143 RelationMember member2 = members.get(row + 1);
144 members.set(row, member2);
145 members.set(row + 1, member1);
146 }
147 fireTableDataChanged();
148 getSelectionModel();
149 getSelectionModel().setValueIsAdjusting(true);
150 getSelectionModel().clearSelection();
151 for (int row : selectedRows) {
152 row++;
153 getSelectionModel().addSelectionInterval(row, row);
154 }
155 getSelectionModel().setValueIsAdjusting(false);
156 fireMakeMemberVisible(selectedRows[0] + 1);
157 }
158
159 public void remove(int[] selectedRows) {
160 if (!canRemove(selectedRows))
161 return;
162 int offset = 0;
163 for (int row : selectedRows) {
164 row -= offset;
165 members.remove(row);
166 offset++;
167 }
168 fireTableDataChanged();
169 }
170
171 public boolean canMoveUp(int[] rows) {
172 if (rows == null || rows.length == 0)
173 return false;
174 Arrays.sort(rows);
175 return rows[0] > 0 && members.size() > 0;
176 }
177
178 public boolean canMoveDown(int[] rows) {
179 if (rows == null || rows.length == 0)
180 return false;
181 Arrays.sort(rows);
182 return members.size() > 0 && rows[rows.length - 1] < members.size() - 1;
183 }
184
185 public boolean canRemove(int[] rows) {
186 if (rows == null || rows.length == 0)
187 return false;
188 return true;
189 }
190
191 public DefaultListSelectionModel getSelectionModel() {
192 if (listSelectionModel == null) {
193 listSelectionModel = new DefaultListSelectionModel();
194 listSelectionModel.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
195 }
196 return listSelectionModel;
197 }
198
199 public void updateMemberReferences(DataSet ds) {
200 for (RelationMember member : members) {
201 if (member.member.id == 0) {
202 continue;
203 }
204 OsmPrimitive primitive = ds.getPrimitiveById(member.member.id);
205 if (primitive != null) {
206 member.member = primitive;
207 }
208 }
209 fireTableDataChanged();
210 }
211
212 public void removeMembersReferringTo(List<? extends OsmPrimitive> primitives) {
213 if (primitives == null)
214 return;
215 Iterator<RelationMember> it = members.iterator();
216 while (it.hasNext()) {
217 RelationMember member = it.next();
218 if (primitives.contains(member.member)) {
219 it.remove();
220 }
221 }
222 fireTableDataChanged();
223 }
224
225 public void applyToRelation(Relation relation) {
226 relation.members.clear();
227 relation.members.addAll(members);
228 }
229
230 public boolean hasSameMembersAs(Relation relation) {
231 if (relation == null)
232 return false;
233 if (relation.members.size() != members.size())
234 return false;
235 for (int i = 0; i < relation.members.size(); i++) {
236 if (!relation.members.get(i).equals(members.get(i)))
237 return false;
238 }
239 return true;
240 }
241
242 public boolean hasIncompleteMembers() {
243 for (RelationMember member : members) {
244 if (member.member.incomplete)
245 return true;
246 }
247 return false;
248 }
249
250 protected List<Integer> getSelectedIndices() {
251 ArrayList<Integer> selectedIndices = new ArrayList<Integer>();
252 for (int i = 0; i < members.size(); i++) {
253 if (getSelectionModel().isSelectedIndex(i)) {
254 selectedIndices.add(i);
255 }
256 }
257 return selectedIndices;
258 }
259
260 public void addMembersAtBeginning(List<OsmPrimitive> primitives) {
261 if (primitives == null)
262 return;
263 for (OsmPrimitive primitive : primitives) {
264 RelationMember member = new RelationMember("", primitive);
265 members.add(0, member);
266 }
267 fireTableDataChanged();
268 getSelectionModel().clearSelection();
269 for (int i = 0; i < primitives.size(); i++) {
270 getSelectionModel().addSelectionInterval(i, i);
271 }
272 fireMakeMemberVisible(0);
273 }
274
275 public void addMembersAtEnd(List<? extends OsmPrimitive> primitives) {
276 if (primitives == null)
277 return;
278
279 for (OsmPrimitive primitive : primitives) {
280 RelationMember member = new RelationMember("", primitive);
281 members.add(member);
282 }
283 fireTableDataChanged();
284 getSelectionModel().clearSelection();
285 for (int i = 0; i < primitives.size(); i++) {
286 getSelectionModel().addSelectionInterval(members.size() - 1 - i, members.size() - 1 - i);
287 }
288 fireMakeMemberVisible(members.size() - 1);
289 }
290
291 public void addMembersBeforeIdx(List<? extends OsmPrimitive> primitives, int idx) {
292 if (primitives == null)
293 return;
294
295 for (OsmPrimitive primitive : primitives) {
296 RelationMember member = new RelationMember("", primitive);
297 members.add(idx, member);
298 }
299 fireTableDataChanged();
300 getSelectionModel().setValueIsAdjusting(true);
301 getSelectionModel().clearSelection();
302 for (int i = 0; i < primitives.size(); i++) {
303 getSelectionModel().addSelectionInterval(idx + i, idx + i);
304 }
305 getSelectionModel().setValueIsAdjusting(false);
306 fireMakeMemberVisible(idx);
307 }
308
309 public void addMembersAfterIdx(List<? extends OsmPrimitive> primitives, int idx) {
310 if (primitives == null)
311 return;
312 int j = 1;
313 for (OsmPrimitive primitive : primitives) {
314 RelationMember member = new RelationMember("", primitive);
315 members.add(idx + j, member);
316 j++;
317 }
318 fireTableDataChanged();
319 getSelectionModel().setValueIsAdjusting(true);
320 getSelectionModel().clearSelection();
321 for (int i = 0; i < primitives.size(); i++) {
322 getSelectionModel().addSelectionInterval(idx + 1 + i, idx + 1 + i);
323 }
324 getSelectionModel().setValueIsAdjusting(false);
325 fireMakeMemberVisible(idx + 1);
326 }
327
328 /**
329 * Replies the number of members which refer to a particular primitive
330 *
331 * @param primitive the primitive
332 * @return the number of members which refer to a particular primitive
333 */
334 public int getNumMembersWithPrimitive(OsmPrimitive primitive) {
335 int count = 0;
336 for (RelationMember member : members) {
337 if (member.member.equals(primitive)) {
338 count++;
339 }
340 }
341 return count;
342 }
343
344 /**
345 * updates the role of the members given by the indices in <code>idx</code>
346 *
347 * @param idx the array of indices
348 * @param role the new role
349 */
350 public void updateRole(int[] idx, String role) {
351 if (idx == null || idx.length == 0)
352 return;
353 for (int row : idx) {
354 members.get(row).role = role;
355 }
356 fireTableDataChanged();
357 for (int row : idx) {
358 getSelectionModel().addSelectionInterval(row, row);
359 }
360 }
361
362 /**
363 * Get the currently selected relation members
364 *
365 * @return a collection with the currently selected relation members
366 */
367 public Collection<RelationMember> getSelectedMembers() {
368 ArrayList<RelationMember> selectedMembers = new ArrayList<RelationMember>();
369 for (int i : getSelectedIndices()) {
370 selectedMembers.add(members.get(i));
371 }
372 return selectedMembers;
373 }
374
375 /**
376 * Replies the set of selected referers. Never null, but may be empty.
377 *
378 * @return the set of selected referers
379 */
380 public Set<OsmPrimitive> getSelectedReferers() {
381 HashSet<OsmPrimitive> ret = new HashSet<OsmPrimitive>();
382 for (RelationMember m: getSelectedMembers()) {
383 ret.add(m.member);
384 }
385 return ret;
386 }
387
388 /**
389 * Replies true, if the selected {@see OsmPrimitive}s in the layer belonging
390 * to this model are in sync with the selected referers in this model.
391 *
392 * @return
393 */
394 public boolean selectionsAreInSync() {
395 HashSet<OsmPrimitive> s1 = new HashSet<OsmPrimitive>(getSelectedReferers());
396 if (s1.size() != layer.data.getSelected().size()) return false;
397 s1.removeAll(layer.data.getSelected());
398 return s1.isEmpty();
399 }
400 /**
401 * Selects the members in the collection selectedMembers
402 *
403 * @param selectedMembers the collection of selected members
404 */
405 public void setSelectedMembers(Collection<RelationMember> selectedMembers) {
406 if (selectedMembers == null || selectedMembers.isEmpty())
407 return;
408
409 // lookup the indices for the respective members
410 //
411 ArrayList<Integer> selectedIndices = new ArrayList<Integer>();
412 for (RelationMember member : selectedMembers) {
413 for (int idx = 0; idx < members.size(); idx ++) {
414 if (members.get(idx).equals(member)) {
415 if (!selectedIndices.contains(idx)) {
416 selectedIndices.add(idx);
417 }
418 }
419 }
420 }
421
422 // select the members
423 //
424 Collections.sort(selectedIndices);
425 getSelectionModel().setValueIsAdjusting(true);
426 getSelectionModel().clearSelection();
427 for (int row : selectedIndices) {
428 getSelectionModel().addSelectionInterval(row, row);
429 }
430 getSelectionModel().setValueIsAdjusting(false);
431
432 // make the first selected member visible
433 //
434 if (selectedIndices.size() > 0) {
435 fireMakeMemberVisible(selectedIndices.get(0));
436 }
437 }
438
439 /**
440 * Replies true if the index-th relation members referrs
441 * to an editable relation, i.e. a relation which is not
442 * incomplete.
443 *
444 * @param index the index
445 * @return true, if the index-th relation members referrs
446 * to an editable relation, i.e. a relation which is not
447 * incomplete
448 */
449 public boolean isEditableRelation(int index) {
450 if (index < 0 || index >= members.size())
451 return false;
452 RelationMember member = members.get(index);
453 if (!(member.member instanceof Relation))
454 return false;
455 Relation r = (Relation) member.member;
456 return !r.incomplete;
457 }
458
459 /**
460 * Replies true if there is at least one relation member in this model
461 * which refers to at least on the primitives in <code>primitives</code>.
462 *
463 * @param primitives the collection of primitives
464 * @return true if there is at least one relation member in this model
465 * which refers to at least on the primitives in <code>primitives</code>; false
466 * otherwise
467 */
468 public boolean hasMembersReferringTo(Collection<OsmPrimitive> primitives) {
469 if (primitives == null || primitives.isEmpty())
470 return false;
471 HashSet<OsmPrimitive> referrers = new HashSet<OsmPrimitive>();
472 for(RelationMember member : members) {
473 referrers.add(member.member);
474 }
475 Iterator<OsmPrimitive> it = primitives.iterator();
476 while(it.hasNext()) {
477 OsmPrimitive referred = it.next();
478 if (referrers.contains(referred))
479 return true;
480 }
481 return false;
482 }
483
484 /**
485 * Selects all mebers which refer to {@see OsmPrimitive}s in the collections
486 * <code>primitmives</code>. Does nothing is primitives is null.
487 *
488 * @param primitives the collection of primitives
489 */
490 public void selectMembersReferringTo(Collection<? extends OsmPrimitive> primitives) {
491 if (primitives == null) return;
492 getSelectionModel().setValueIsAdjusting(true);
493 getSelectionModel().clearSelection();
494 for (int i=0; i< members.size();i++) {
495 RelationMember m = members.get(i);
496 if (primitives.contains(m.member)) {
497 this.getSelectionModel().addSelectionInterval(i,i);
498 }
499 }
500 getSelectionModel().setValueIsAdjusting(false);
501 if (getSelectedIndices().size() > 0) {
502 fireMakeMemberVisible(getSelectedIndices().get(0));
503 }
504 }
505
506 /**
507 * Replies true if the layer this model belongs to is equal to the active
508 * layer
509 *
510 * @return true if the layer this model belongs to is equal to the active
511 * layer
512 */
513 protected boolean isActiveLayer() {
514 if (Main.map == null || Main.map.mapView == null) return false;
515 return Main.map.mapView.getActiveLayer() == layer;
516 }
517
518 /**
519 * get a node we can link against when sorting members
520 * @param element the element we want to link against
521 * @param linked_element already linked against element
522 * @return the unlinked node if element is a way, the node itself if element is a node, null otherwise
523 */
524 private static Node getUnusedNode(RelationMember element, RelationMember linked_element)
525 {
526 Node result = null;
527
528 if (element.member instanceof Way) {
529 Way w = (Way) element.member;
530 if (linked_element.member instanceof Way) {
531 Way x = (Way) linked_element.member;
532 if ((w.firstNode() == x.firstNode()) || (w.firstNode() == x.lastNode())) {
533 result = w.lastNode();
534 } else {
535 result = w.firstNode();
536 }
537 } else if (linked_element.member instanceof Node) {
538 Node m = (Node) linked_element.member;
539 if (w.firstNode() == m) {
540 result = w.lastNode();
541 } else {
542 result = w.firstNode();
543 }
544 }
545 } else if (element.member instanceof Node) {
546 Node n = (Node) element.member;
547 result = n;
548 }
549
550 return result;
551 }
552
553 void sort() {
554 RelationNodeMap map = new RelationNodeMap(members);
555 Vector<LinkedList<Integer>> segments;
556 LinkedList<Integer> segment;
557 Node startSearchNode;
558 Node endSearchNode;
559 boolean something_done;
560
561 /*
562 * sort any 2 or more connected elements together may be slow with many unconnected members
563 */
564
565 if (map.isEmpty())
566 // empty relation or incomplete members
567 return;
568 segments = new Vector<LinkedList<Integer>>();
569
570 while (!map.isEmpty()) {
571 // find an element for the next segment
572 // try first element in relation if we just started
573 // otherwise, or if first element is another relation, just fetch some element from the
574 // map
575 Integer next;
576 if ((segments.size() == 0) && map.remove(0, members.get(0))) {
577 next = 0;
578 } else {
579 next = map.pop();
580 if (next == null) {
581 break;
582 }
583 }
584
585 segment = new LinkedList<Integer>();
586 segment.add(next);
587 segments.add(segment);
588
589 do {
590 something_done = false;
591 startSearchNode = null;
592 endSearchNode = null;
593 if (segment.size() == 1) {
594 // only one element in segment, so try to link against each linkable node of element
595 RelationMember m = members.get(segment.getFirst());
596 if (m.member instanceof Way) {
597 Way w = (Way) m.member;
598 endSearchNode = w.lastNode();
599 if (w.lastNode() != w.firstNode())
600 {
601 startSearchNode = w.firstNode();
602 }
603 } else if (m.member instanceof Node) {
604 Node n = (Node) m.member;
605 endSearchNode = n;
606 }
607 } else {
608 // add unused node of first element and unused node of last element
609 // start with the first element (compared to next element)
610 startSearchNode = getUnusedNode(members.get(segment.getFirst()), members.get(segment.get(1)));
611
612 // now the same for the last element (compared to previous element)
613 endSearchNode = getUnusedNode(members.get(segment.getLast()), members.get(segment.get(segment.size() - 2)));
614 }
615
616 // let's see if we can find connected elements for endSearchNode and startSearchNode
617 if (startSearchNode != null) {
618 Integer m2 = map.find(startSearchNode, segment.getFirst());
619 if (m2 != null) {
620 segment.add(0, m2);
621 map.remove(m2, members.get(m2));
622 something_done = true;
623 }
624 }
625 if (endSearchNode != null) {
626 Integer m2 = map.find(endSearchNode, segment.getLast());
627 if (m2 != null) {
628 segment.add(segment.size(), m2);
629 map.remove(m2, members.get(m2));
630 something_done = true;
631 }
632 }
633 } while (something_done);
634
635 }
636
637 if (segments.size() > 0) {
638 // append map.remaining() to segments list (as a single segment)
639 segment = new LinkedList<Integer>();
640 segment.addAll(map.getRemaining());
641 segments.add(segment);
642
643 // now we need to actually re-order the relation members
644 ArrayList<RelationMember> newmembers = new ArrayList<RelationMember>();
645 for (LinkedList<Integer> segment2 : segments) {
646 for (Integer p : segment2) {
647 newmembers.add(members.get(p));
648 }
649 }
650 members.clear();
651 members.addAll(newmembers);
652
653 fireTableDataChanged();
654 }
655 }
656
657 // simple version of code that was removed from GenericReleationEditor
658 // no recursion and no forward/backward support
659 // TODO: add back the number of linked elements
660 private WayConnectionType linked(int i) {
661 // this method is aimed at finding out whether the
662 // relation member is "linked" with the next, i.e. whether
663 // (if both are ways) these ways are connected. It should
664 // really produce a much more beautiful output (with a linkage
665 // symbol somehow placed between the two member lines!),
666 // so... FIXME ;-)
667
668 WayConnectionType link = WayConnectionType.none;
669 RelationMember m1 = members.get(i);
670 RelationMember m2 = members.get((i + 1) % members.size());
671 Way way1 = null;
672 Way way2 = null;
673
674 if (m1.member instanceof Way) {
675 way1 = (Way) m1.member;
676 }
677 if (m2.member instanceof Way) {
678 way2 = (Way) m2.member;
679 }
680 if ((way1 != null) && (way2 != null)) {
681 Node way1first = way1.firstNode();
682 Node way1last = way1.lastNode();
683 Node way2first = way2.firstNode();
684 Node way2last = way2.lastNode();
685 if (way1first != null && way2first != null && (way1first == way2first)) {
686 link = WayConnectionType.tail_to_tail;
687 } else if (way1first != null && way2last != null && (way1first == way2last)) {
688 link = WayConnectionType.tail_to_head;
689 } else if (way1last != null && way2first != null && (way1last == way2first)) {
690 link = WayConnectionType.head_to_tail;
691 } else if (way1last != null && way2last != null && (way1last == way2last)) {
692 link = WayConnectionType.head_to_head;
693 }
694 }
695
696 return link;
697 }
698}
Note: See TracBrowser for help on using the repository browser.