1/* 2* Copyright (c) 2009-2013 Yorba Foundation 3* 4* This program is free software; you can redistribute it and/or 5* modify it under the terms of the GNU Lesser General Public 6* License as published by the Free Software Foundation; either 7* version 2.1 of the License, or (at your option) any later version. 8* 9* This program is distributed in the hope that it will be useful, 10* but WITHOUT ANY WARRANTY; without even the implied warranty of 11* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12* General Public License for more details. 13* 14* You should have received a copy of the GNU General Public 15* License along with this program; if not, write to the 16* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 17* Boston, MA 02110-1301 USA 18*/ 19 20public class DataCollection { 21 public const int64 INVALID_OBJECT_ORDINAL = -1; 22 23 private class MarkerImpl : Object, Marker { 24 public DataCollection owner; 25 public Gee.HashSet<DataObject> marked = new Gee.HashSet<DataObject> (); 26 public int freeze_count = 0; 27 28 public MarkerImpl (DataCollection owner) { 29 this.owner = owner; 30 31 // if items are removed from main collection, they're removed from the marked list 32 // as well 33 owner.items_removed.connect (on_items_removed); 34 } 35 36 ~MarkerImpl () { 37 owner.items_removed.disconnect (on_items_removed); 38 } 39 40 public void mark (DataObject object) { 41 assert (owner.internal_contains (object)); 42 43 marked.add (object); 44 } 45 46 public void unmark (DataObject object) { 47 assert (owner.internal_contains (object)); 48 49 marked.remove (object); 50 } 51 52 public bool toggle (DataObject object) { 53 assert (owner.internal_contains (object)); 54 55 if (marked.contains (object)) { 56 marked.remove (object); 57 } else { 58 marked.add (object); 59 } 60 61 return marked.contains (object); 62 } 63 64 public void mark_many (Gee.Collection<DataObject> list) { 65 foreach (DataObject object in list) { 66 assert (owner.internal_contains (object)); 67 68 marked.add (object); 69 } 70 } 71 72 public void unmark_many (Gee.Collection<DataObject> list) { 73 foreach (DataObject object in list) { 74 assert (owner.internal_contains (object)); 75 76 marked.remove (object); 77 } 78 } 79 80 public void mark_all () { 81 foreach (DataObject object in owner.get_all ()) 82 marked.add (object); 83 } 84 85 public int get_count () { 86 return (marked != null) ? marked.size : freeze_count; 87 } 88 89 public Gee.Collection<DataObject> get_all () { 90 Gee.ArrayList<DataObject> copy = new Gee.ArrayList<DataObject> (); 91 copy.add_all (marked); 92 93 return copy; 94 } 95 96 private void on_items_removed (Gee.Iterable<DataObject> removed) { 97 foreach (DataObject object in removed) 98 marked.remove (object); 99 } 100 101 // This method is called by DataCollection when it starts iterating over the marked list ... 102 // the marker at this point stops monitoring the collection, preventing a possible 103 // removal during an iteration, which is bad. 104 public void freeze () { 105 owner.items_removed.disconnect (on_items_removed); 106 } 107 108 public void finished () { 109 if (marked != null) 110 freeze_count = marked.size; 111 112 marked = null; 113 } 114 115 public bool is_valid (DataCollection collection) { 116 return (collection == owner) && (marked != null); 117 } 118 } 119 120 private string name; 121 private DataSet dataset = new DataSet (); 122 private Gee.HashMap < string, Value?> properties = new Gee.HashMap < string, Value?> (); 123 private int64 object_ordinal_generator = 0; 124 private int notifies_frozen = 0; 125 private Gee.HashMap<DataObject, Alteration> frozen_items_altered = null; 126 private bool fire_ordering_changed = false; 127 128 // When this signal has been fired, the added items are part of the collection 129 public virtual signal void items_added (Gee.Iterable<DataObject> added) { 130 } 131 132 // When this signal is fired, the removed items are no longer part of the collection 133 public virtual signal void items_removed (Gee.Iterable<DataObject> removed) { 134 } 135 136 // When this signal is fired, the removed items are no longer part of the collection 137 public virtual signal void contents_altered (Gee.Iterable<DataObject>? added, 138 Gee.Iterable<DataObject>? removed) { 139 } 140 141 // This signal fires whenever any (or multiple) items in the collection signal they've been 142 // altered. 143 public virtual signal void items_altered (Gee.Map<DataObject, Alteration> items) { 144 } 145 146 // Fired when a new sort comparator is registered or an item has moved in the ordering due to 147 // an alteration. 148 public virtual signal void ordering_changed () { 149 } 150 151 // Fired when a collection property is set. The old value is passed as well, null if not set 152 // previously. 153 public virtual signal void property_set (string name, Value? old, Value val) { 154 } 155 156 // Fired when a collection property is cleared. 157 public virtual signal void property_cleared (string name) { 158 } 159 160 // Fired when "altered" signal (and possibly other related signals, depending on the subclass) 161 // is frozen. 162 public virtual signal void frozen () { 163 } 164 165 // Fired when "altered" signal (and other related signals, depending on the subclass) is 166 // restored (thawed). 167 public virtual signal void thawed () { 168 } 169 170 public DataCollection (string name) { 171 this.name = name; 172 } 173 174 ~DataCollection () { 175#if TRACE_DTORS 176 debug ("DTOR: DataCollection %s", name); 177#endif 178 } 179 180 public virtual string to_string () { 181 return "%s (%d)".printf (name, get_count ()); 182 } 183 184 // use notifies to ensure proper chronology of signal handling 185 protected virtual void notify_items_added (Gee.Iterable<DataObject> added) { 186 items_added (added); 187 } 188 189 protected virtual void notify_items_removed (Gee.Iterable<DataObject> removed) { 190 items_removed (removed); 191 } 192 193 protected virtual void notify_contents_altered (Gee.Iterable<DataObject>? added, 194 Gee.Iterable<DataObject>? removed) { 195 contents_altered (added, removed); 196 } 197 198 protected virtual void notify_items_altered (Gee.Map<DataObject, Alteration> items) { 199 items_altered (items); 200 } 201 202 protected virtual void notify_ordering_changed () { 203 ordering_changed (); 204 } 205 206 protected virtual void notify_property_set (string name, Value? old, Value val) { 207 property_set (name, old, val); 208 } 209 210 protected virtual void notify_property_cleared (string name) { 211 property_cleared (name); 212 } 213 214 // A singleton list is used when a single item has been added/remove/selected/unselected 215 // and needs to be reported via a signal, which uses a list as a parameter ... although this 216 // seems wasteful, can't reuse a single singleton list because it's possible for a method 217 // that needs it to be called from within a signal handler for another method, corrupting the 218 // shared list's contents mid-signal 219 protected static Gee.Collection<DataObject> get_singleton (DataObject object) { 220 return new SingletonCollection<DataObject> (object); 221 } 222 223 protected static Gee.Map<DataObject, Alteration> get_alteration_singleton (DataObject object, 224 Alteration alteration) { 225 Gee.Map<DataObject, Alteration> map = new Gee.HashMap<DataObject, Alteration> (); 226 map.set (object, alteration); 227 228 return map; 229 } 230 231 public virtual bool valid_type (DataObject object) { 232 return true; 233 } 234 235 public unowned Comparator get_comparator () { 236 return dataset.get_comparator (); 237 } 238 239 public unowned ComparatorPredicate get_comparator_predicate () { 240 return dataset.get_comparator_predicate (); 241 } 242 243 public virtual void set_comparator (Comparator comparator, ComparatorPredicate? predicate) { 244 dataset.set_comparator (comparator, predicate); 245 notify_ordering_changed (); 246 } 247 248 // Return to natural ordering of DataObjects, which is order-added 249 public virtual void reset_comparator () { 250 dataset.reset_comparator (); 251 notify_ordering_changed (); 252 } 253 254 public virtual Gee.Collection<DataObject> get_all () { 255 return dataset.get_all (); 256 } 257 258 protected DataSet get_dataset_copy () { 259 return dataset.copy (); 260 } 261 262 public virtual int get_count () { 263 return dataset.get_count (); 264 } 265 266 public virtual DataObject? get_at (int index) { 267 return dataset.get_at (index); 268 } 269 270 public virtual int index_of (DataObject object) { 271 return dataset.index_of (object); 272 } 273 274 public virtual bool contains (DataObject object) { 275 return internal_contains (object); 276 } 277 278 // Because subclasses may filter out objects (by overriding key methods here), need an 279 // internal_contains for consistency checking. 280 private bool internal_contains (DataObject object) { 281 if (!dataset.contains (object)) 282 return false; 283 284 assert (object.get_membership () == this); 285 286 return true; 287 } 288 289 private void internal_add (DataObject object) { 290 assert (valid_type (object)); 291 292 object.internal_set_membership (this, object_ordinal_generator++); 293 294 bool added = dataset.add (object); 295 assert (added); 296 } 297 298 private void internal_add_many (Gee.List<DataObject> objects, ProgressMonitor? monitor) { 299 int count = objects.size; 300 for (int ctr = 0; ctr < count; ctr++) { 301 DataObject object = objects.get (ctr); 302 assert (valid_type (object)); 303 304 object.internal_set_membership (this, object_ordinal_generator++); 305 306 if (monitor != null) 307 monitor (ctr, count); 308 } 309 310 bool added = dataset.add_many (objects); 311 assert (added); 312 } 313 314 private void internal_remove (DataObject object) { 315 bool removed = dataset.remove (object); 316 assert (removed); 317 318 object.internal_clear_membership (); 319 } 320 321 // Returns false if item is already part of the collection. 322 public virtual bool add (DataObject object) { 323 if (internal_contains (object)) { 324 debug ("%s cannot add %s: already present", to_string (), object.to_string ()); 325 326 return false; 327 } 328 329 internal_add (object); 330 331 // fire signal after added using singleton list 332 Gee.Collection<DataObject> added = get_singleton (object); 333 notify_items_added (added); 334 notify_contents_altered (added, null); 335 336 // This must be called *after* the DataCollection has signalled. 337 object.notify_membership_changed (this); 338 339 return true; 340 } 341 342 // Returns the items added to the collection. 343 public virtual Gee.Collection<DataObject> add_many (Gee.Collection<DataObject> objects, 344 ProgressMonitor? monitor = null) { 345 Gee.ArrayList<DataObject> added = new Gee.ArrayList<DataObject> (); 346 foreach (DataObject object in objects) { 347 if (internal_contains (object)) { 348 debug ("%s cannot add %s: already present", to_string (), object.to_string ()); 349 350 continue; 351 } 352 353 added.add (object); 354 } 355 356 int count = added.size; 357 if (count == 0) 358 return added; 359 360 internal_add_many (added, monitor); 361 362 // signal once all have been added 363 notify_items_added (added); 364 notify_contents_altered (added, null); 365 366 // This must be called *after* the DataCollection signals have fired. 367 for (int ctr = 0; ctr < count; ctr++) 368 added.get (ctr).notify_membership_changed (this); 369 370 return added; 371 } 372 373 // Obtain a marker to build a list of objects to perform an action upon. 374 public Marker start_marking () { 375 return new MarkerImpl (this); 376 } 377 378 // Obtain a marker with a single item marked. More can be added. 379 public Marker mark (DataObject object) { 380 Marker marker = new MarkerImpl (this); 381 marker.mark (object); 382 383 return marker; 384 } 385 386 // Obtain a marker for all items in a collection. More can be added. 387 public Marker mark_many (Gee.Collection<DataObject> objects) { 388 Marker marker = new MarkerImpl (this); 389 marker.mark_many (objects); 390 391 return marker; 392 } 393 394 // Iterate over all the marked objects performing a user-supplied action on each one. The 395 // marker is invalid after calling this method. 396 public void act_on_marked (Marker m, MarkedAction action, ProgressMonitor? monitor = null, 397 Object? user = null) { 398 MarkerImpl marker = (MarkerImpl) m; 399 400 assert (marker.is_valid (this)); 401 402 // freeze the marker to prepare it for iteration 403 marker.freeze (); 404 405 uint64 count = 0; 406 uint64 total = marker.marked.size; 407 408 // iterate, breaking if the callback asks to stop 409 foreach (DataObject object in marker.marked) { 410 // although marker tracks when items are removed, catch it here as well 411 if (!internal_contains (object)) { 412 warning ("act_on_marked: marker holding ref to unknown %s", object.to_string ()); 413 414 continue; 415 } 416 417 if (!action (object, user)) 418 break; 419 420 if (monitor != null) { 421 if (!monitor (++count, total)) 422 break; 423 } 424 } 425 426 // invalidate the marker 427 marker.finished (); 428 } 429 430 // Remove marked items from collection. This two-step process allows for iterating in a foreach 431 // loop and removing without creating a separate list. The marker is invalid after this call. 432 public virtual void remove_marked (Marker m) { 433 MarkerImpl marker = (MarkerImpl) m; 434 435 assert (marker.is_valid (this)); 436 437 // freeze the marker before signalling, so it doesn't remove all its items 438 marker.freeze (); 439 440 // remove everything in the marked list 441 Gee.ArrayList<DataObject> skipped = null; 442 foreach (DataObject object in marker.marked) { 443 // although marker should track items already removed, catch it here as well 444 if (!internal_contains (object)) { 445 warning ("remove_marked: marker holding ref to unknown %s", object.to_string ()); 446 447 if (skipped == null) 448 skipped = new Gee.ArrayList<DataObject> (); 449 450 skipped.add (object); 451 452 continue; 453 } 454 455 internal_remove (object); 456 } 457 458 if (skipped != null) 459 marker.marked.remove_all (skipped); 460 461 // signal after removing 462 if (marker.marked.size > 0) { 463 notify_items_removed (marker.marked); 464 notify_contents_altered (null, marker.marked); 465 466 // this must be called after the DataCollection has signalled. 467 foreach (DataObject object in marker.marked) 468 object.notify_membership_changed (null); 469 } 470 471 // invalidate the marker 472 marker.finished (); 473 } 474 475 public virtual void clear () { 476 if (dataset.get_count () == 0) 477 return; 478 479 // remove everything in the list, but have to maintain a new list for reporting the signal. 480 // Don't use an iterator, as list is modified in internal_remove (). 481 Gee.ArrayList<DataObject> removed = new Gee.ArrayList<DataObject> (); 482 do { 483 DataObject? object = dataset.get_at (0); 484 assert (object != null); 485 486 removed.add (object); 487 internal_remove (object); 488 } while (dataset.get_count () > 0); 489 490 // report after removal 491 notify_items_removed (removed); 492 notify_contents_altered (null, removed); 493 494 // This must be called after the DataCollection has signalled. 495 foreach (DataObject object in removed) 496 object.notify_membership_changed (null); 497 } 498 499 // close () must be called before disposing of the DataCollection, so all signals may be 500 // disconnected and all internal references to the collection can be dropped. In the bare 501 // minimum, all items will be removed from the collection (and the appropriate signals and 502 // notify calls will be made). Subclasses may fire other signals while disposing of their 503 // references. However, if they are entirely synchronized on DataCollection's signals, that 504 // may be enough for them to clean up. 505 public virtual void close () { 506 clear (); 507 } 508 509 // This method is only called by DataObject to report when it has been altered, so observers of 510 // this collection may be notified as well. 511 public void internal_notify_altered (DataObject object, Alteration alteration) { 512 assert (internal_contains (object)); 513 514 bool resort_occurred = dataset.resort_object (object, alteration); 515 516 if (are_notifications_frozen ()) { 517 if (frozen_items_altered == null) 518 frozen_items_altered = new Gee.HashMap<DataObject, Alteration> (); 519 520 // if an alteration for the object is already in place, compress the two and add the 521 // new one, otherwise set the supplied one 522 Alteration? current = frozen_items_altered.get (object); 523 if (current != null) 524 current = current.compress (alteration); 525 else 526 current = alteration; 527 528 frozen_items_altered.set (object, current); 529 530 fire_ordering_changed = fire_ordering_changed || resort_occurred; 531 532 return; 533 } 534 535 if (resort_occurred) 536 notify_ordering_changed (); 537 538 notify_items_altered (get_alteration_singleton (object, alteration)); 539 } 540 541 public Value? get_property (string name) { 542 return properties.get (name); 543 } 544 545 public void set_property (string name, Value val, ValueEqualFunc? value_equals = null) { 546 if (value_equals == null) { 547 if (val.holds (typeof (bool))) 548 value_equals = bool_value_equals; 549 else if (val.holds (typeof (int))) 550 value_equals = int_value_equals; 551 else 552 error ("value_equals must be specified for this type"); 553 } 554 555 Value? old = properties.get (name); 556 if (old != null) { 557 if (value_equals (old, val)) 558 return; 559 } 560 561 properties.set (name, val); 562 563 notify_property_set (name, old, val); 564 565 // notify all items in the collection of the change 566 int count = dataset.get_count (); 567 for (int ctr = 0; ctr < count; ctr++) 568 dataset.get_at (ctr).notify_collection_property_set (name, old, val); 569 } 570 571 public void clear_property (string name) { 572 if (!properties.unset (name)) 573 return; 574 575 // only notify if the propery was unset (that is, was set to begin with) 576 notify_property_cleared (name); 577 578 // notify all items 579 int count = dataset.get_count (); 580 for (int ctr = 0; ctr < count; ctr++) 581 dataset.get_at (ctr).notify_collection_property_cleared (name); 582 } 583 584 // This is only guaranteed to freeze notifications that come in from contained objects and 585 // need to be propagated with collection signals. Thus, the caller can freeze notifications, 586 // make modifications to many or all member objects, then unthaw and have the aggregated signals 587 // fired at once. 588 // 589 // DataObject/DataSource/DataView should also "eat" their signals as well, to prevent observers 590 // from being notified while their collection is frozen, and only fire them when 591 // internal_collection_thawed is called. 592 // 593 // For DataCollection, the signals affected are items_altered and ordering_changed. 594 public void freeze_notifications () { 595 if (notifies_frozen++ == 0) 596 notify_frozen (); 597 } 598 599 public void thaw_notifications () { 600 if (notifies_frozen == 0) 601 return; 602 603 if (--notifies_frozen == 0) 604 notify_thawed (); 605 } 606 607 public bool are_notifications_frozen () { 608 return notifies_frozen > 0; 609 } 610 611 // This is called when notifications have frozen. Child collections should halt notifications 612 // until thawed () is called. 613 protected virtual void notify_frozen () { 614 frozen (); 615 } 616 617 // This is called when enough thaw_notifications () calls have been made. Child collections 618 // should issue caught notifications. 619 protected virtual void notify_thawed () { 620 if (frozen_items_altered != null) { 621 // refs are swapped around due to reentrancy 622 Gee.Map<DataObject, Alteration> copy = frozen_items_altered; 623 frozen_items_altered = null; 624 625 notify_items_altered (copy); 626 } 627 628 if (fire_ordering_changed) { 629 fire_ordering_changed = false; 630 notify_ordering_changed (); 631 } 632 633 thawed (); 634 } 635} 636