1 /* 2 * Copyright (c) 1999-2000, Eric Moon. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions, and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions, and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * 3. The name of the author may not be used to endorse or promote products 17 * derived from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR 20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 32 // DiagramItemGroup.cpp 33 34 /*! \class DiagramItemGroup. 35 \brief Basic class for managing and accessing DiagramItem objects. 36 37 Objects of this class can manage one or more of the DiagramItem 38 type M_BOX, M_WIRE and M_ENDPOINT. Many methods let you specify 39 which type of item you want to deal with. 40 */ 41 42 #include "DiagramItemGroup.h" 43 #include "DiagramItem.h" 44 45 #include <Region.h> 46 47 __USE_CORTEX_NAMESPACE 48 49 #include <Debug.h> 50 #define D_METHOD(x) //PRINT (x) 51 52 53 DiagramItemGroup::DiagramItemGroup(uint32 acceptedTypes, bool multiSelection) 54 :fBoxes(0), 55 fWires(0), 56 fEndPoints(0), 57 fSelection(0), 58 fTypes(acceptedTypes), 59 fItemAlignment(1.0, 1.0), 60 fMultiSelection(multiSelection), 61 fLastItemUnder(0) 62 { 63 D_METHOD(("DiagramItemGroup::DiagramItemGroup()\n")); 64 fSelection = new BList(1); 65 } 66 67 68 DiagramItemGroup::~DiagramItemGroup() 69 { 70 D_METHOD(("DiagramItemGroup::~DiagramItemGroup()\n")); 71 72 int32 count = 0; 73 if (fWires && (fTypes & DiagramItem::M_WIRE)) { 74 count = fWires->CountItems(); 75 for (int32 i = 0; i < count; ++i) 76 delete static_cast<DiagramItem*>(fWires->ItemAt(i)); 77 delete fWires; 78 } 79 80 if (fBoxes && (fTypes & DiagramItem::M_BOX)) { 81 count = fBoxes->CountItems(); 82 for (int32 i = 0; i < count; ++i) 83 delete static_cast<DiagramItem*>(fBoxes->ItemAt(i)); 84 delete fBoxes; 85 } 86 87 if (fEndPoints && (fTypes & DiagramItem::M_ENDPOINT)) { 88 count = fEndPoints->CountItems(); 89 for (int32 i = 0; i < count; ++i) 90 delete static_cast<DiagramItem*>(fEndPoints->ItemAt(i)); 91 delete fEndPoints; 92 } 93 94 if (fSelection) 95 delete fSelection; 96 } 97 98 99 // #pragma mark - item accessors 100 101 102 /*! Returns the number of items in the group (optionally only those 103 of the given type \param whichType) 104 */ 105 uint32 106 DiagramItemGroup::CountItems(uint32 whichType) const 107 { 108 D_METHOD(("DiagramItemGroup::CountItems()\n")); 109 uint32 count = 0; 110 if (whichType & fTypes) { 111 if (whichType & DiagramItem::M_BOX) { 112 if (fBoxes) 113 count += fBoxes->CountItems(); 114 } 115 116 if (whichType & DiagramItem::M_WIRE) { 117 if (fWires) 118 count += fWires->CountItems(); 119 } 120 121 if (whichType & DiagramItem::M_ENDPOINT) { 122 if (fEndPoints) 123 count += fEndPoints->CountItems(); 124 } 125 } 126 127 return count; 128 } 129 130 131 /*! Returns a pointer to the item in the lists which is 132 at the given index; if none is found, this function 133 returns 0 134 */ 135 DiagramItem* 136 DiagramItemGroup::ItemAt(uint32 index, uint32 whichType) const 137 { 138 D_METHOD(("DiagramItemGroup::ItemAt()\n")); 139 if (fTypes & whichType) { 140 if (whichType & DiagramItem::M_BOX) { 141 if (fBoxes && (index < CountItems(DiagramItem::M_BOX))) 142 return static_cast<DiagramItem *>(fBoxes->ItemAt(index)); 143 else 144 index -= CountItems(DiagramItem::M_BOX); 145 } 146 147 if (whichType & DiagramItem::M_WIRE) { 148 if (fWires && (index < CountItems(DiagramItem::M_WIRE))) 149 return static_cast<DiagramItem *>(fWires->ItemAt(index)); 150 else 151 index -= CountItems(DiagramItem::M_WIRE); 152 } 153 154 if (whichType & DiagramItem::M_ENDPOINT) { 155 if (fEndPoints && (index < CountItems(DiagramItem::M_ENDPOINT))) 156 return static_cast<DiagramItem *>(fEndPoints->ItemAt(index)); 157 } 158 } 159 160 return 0; 161 } 162 163 164 /*! This function returns the first box or endpoint found that 165 contains the given \param point. For connections it looks at all 166 wires that 'might' contain the point and calls their method 167 howCloseTo() to find the one closest to the point. 168 The lists should be sorted by selection time for proper results! 169 */ 170 DiagramItem* 171 DiagramItemGroup::ItemUnder(BPoint point) 172 { 173 D_METHOD(("DiagramItemGroup::ItemUnder()\n")); 174 if (fTypes & DiagramItem::M_BOX) { 175 for (uint32 i = 0; i < CountItems(DiagramItem::M_BOX); i++) { 176 DiagramItem *item = ItemAt(i, DiagramItem::M_BOX); 177 if (item->Frame().Contains(point) && (item->howCloseTo(point) == 1.0)) { 178 // DiagramItemGroup *group = dynamic_cast<DiagramItemGroup *>(item); 179 return (fLastItemUnder = item); 180 } 181 } 182 } 183 184 if (fTypes & DiagramItem::M_WIRE) { 185 float closest = 0.0; 186 DiagramItem *closestItem = 0; 187 for (uint32 i = 0; i < CountItems(DiagramItem::M_WIRE); i++) { 188 DiagramItem *item = ItemAt(i, DiagramItem::M_WIRE); 189 if (item->Frame().Contains(point)) { 190 float howClose = item->howCloseTo(point); 191 if (howClose > closest) { 192 closestItem = item; 193 if (howClose == 1.0) 194 return (fLastItemUnder = item); 195 closest = howClose; 196 } 197 } 198 } 199 200 if (closest > 0.5) 201 return (fLastItemUnder = closestItem); 202 } 203 204 if (fTypes & DiagramItem::M_ENDPOINT) { 205 for (uint32 i = 0; i < CountItems(DiagramItem::M_ENDPOINT); i++) { 206 DiagramItem *item = ItemAt(i, DiagramItem::M_ENDPOINT); 207 if (item->Frame().Contains(point) && (item->howCloseTo(point) == 1.0)) 208 return (fLastItemUnder = item); 209 } 210 } 211 212 return (fLastItemUnder = 0); // no item was found! 213 } 214 215 216 // #pragma mark - item operations 217 218 219 //! Adds an \param item to the group; returns true on success. 220 bool 221 DiagramItemGroup::AddItem(DiagramItem *item) 222 { 223 D_METHOD(("DiagramItemGroup::AddItem()\n")); 224 if (item && (fTypes & item->type())) { 225 if (item->m_group) 226 item->m_group->RemoveItem(item); 227 228 switch (item->type()) { 229 case DiagramItem::M_BOX: 230 if (!fBoxes) 231 fBoxes = new BList(); 232 item->m_group = this; 233 return fBoxes->AddItem(static_cast<void *>(item)); 234 235 case DiagramItem::M_WIRE: 236 if (!fWires) 237 fWires = new BList(); 238 item->m_group = this; 239 return fWires->AddItem(static_cast<void *>(item)); 240 241 case DiagramItem::M_ENDPOINT: 242 if (!fEndPoints) 243 fEndPoints = new BList(); 244 item->m_group = this; 245 return fEndPoints->AddItem(static_cast<void *>(item)); 246 } 247 } 248 249 return false; 250 } 251 252 253 //! Removes an \param item from the group; returns true on success. 254 bool 255 DiagramItemGroup::RemoveItem(DiagramItem* item) 256 { 257 D_METHOD(("DiagramItemGroup::RemoveItem()\n")); 258 if (item && (fTypes & item->type())) { 259 // reset the lastItemUnder-pointer if it pointed to this item 260 if (fLastItemUnder == item) 261 fLastItemUnder = 0; 262 263 // remove it from the selection list if it was selected 264 if (item->isSelected()) 265 fSelection->RemoveItem(static_cast<void *>(item)); 266 267 // try to remove the item from its list 268 switch (item->type()) { 269 case DiagramItem::M_BOX: 270 if (fBoxes) { 271 item->m_group = 0; 272 return fBoxes->RemoveItem(static_cast<void *>(item)); 273 } 274 break; 275 276 case DiagramItem::M_WIRE: 277 if (fWires) { 278 item->m_group = 0; 279 return fWires->RemoveItem(static_cast<void *>(item)); 280 } 281 break; 282 283 case DiagramItem::M_ENDPOINT: 284 if (fEndPoints) { 285 item->m_group = 0; 286 return fEndPoints->RemoveItem(static_cast<void *>(item)); 287 } 288 } 289 } 290 291 return false; 292 } 293 294 295 /*! Performs a quicksort on a list of items with the provided 296 compare function (one is already defined in the DiagramItem 297 implementation); can't handle more than one item type at a 298 time! 299 */ 300 void 301 DiagramItemGroup::SortItems(uint32 whichType, 302 int (*compareFunc)(const void *, const void *)) 303 { 304 D_METHOD(("DiagramItemGroup::SortItems()\n")); 305 if ((whichType != DiagramItem::M_ANY) && (fTypes & whichType)) { 306 switch (whichType) { 307 case DiagramItem::M_BOX: 308 if (fBoxes) 309 fBoxes->SortItems(compareFunc); 310 break; 311 312 case DiagramItem::M_WIRE: 313 if (fWires) 314 fWires->SortItems(compareFunc); 315 break; 316 317 case DiagramItem::M_ENDPOINT: 318 if (fEndPoints) 319 fEndPoints->SortItems(compareFunc); 320 break; 321 } 322 } 323 } 324 325 326 /*! Fires a Draw() command at all items of a specific type that 327 intersect with the \param updateRect; 328 items are drawn in reverse order; they should be sorted by 329 selection time before this function gets called, so that 330 the more recently selected item are drawn above others. 331 */ 332 void 333 DiagramItemGroup::DrawItems(BRect updateRect, uint32 whichType, BRegion* updateRegion) 334 { 335 D_METHOD(("DiagramItemGroup::DrawItems()\n")); 336 if (whichType & DiagramItem::M_WIRE) { 337 for (int32 i = CountItems(DiagramItem::M_WIRE) - 1; i >= 0; i--) { 338 DiagramItem *item = ItemAt(i, DiagramItem::M_WIRE); 339 if (item->Frame().Intersects(updateRect)) 340 item->Draw(updateRect); 341 } 342 } 343 344 if (whichType & DiagramItem::M_BOX) { 345 for (int32 i = CountItems(DiagramItem::M_BOX) - 1; i >= 0; i--) { 346 DiagramItem *item = ItemAt(i, DiagramItem::M_BOX); 347 if (item && item->Frame().Intersects(updateRect)) { 348 item->Draw(updateRect); 349 if (updateRegion) 350 updateRegion->Exclude(item->Frame()); 351 } 352 } 353 } 354 355 if (whichType & DiagramItem::M_ENDPOINT) { 356 for (int32 i = CountItems(DiagramItem::M_ENDPOINT) - 1; i >= 0; i--) { 357 DiagramItem *item = ItemAt(i, DiagramItem::M_ENDPOINT); 358 if (item && item->Frame().Intersects(updateRect)) 359 item->Draw(updateRect); 360 } 361 } 362 } 363 364 365 /*! Returns in outRegion the \param region of items that lay "over" the given 366 DiagramItem in \param which; returns false if no items are above or the item 367 doesn't exist. 368 */ 369 bool 370 DiagramItemGroup::GetClippingAbove(DiagramItem *which, BRegion *region) 371 { 372 D_METHOD(("DiagramItemGroup::GetClippingAbove()\n")); 373 bool found = false; 374 if (which && region) { 375 switch (which->type()) { 376 case DiagramItem::M_BOX: 377 { 378 int32 index = fBoxes->IndexOf(which); 379 if (index >= 0) { // the item was found 380 BRect r = which->Frame(); 381 for (int32 i = 0; i < index; i++) { 382 DiagramItem *item = ItemAt(i, DiagramItem::M_BOX); 383 if (item && item->Frame().Intersects(r)) { 384 region->Include(item->Frame() & r); 385 found = true; 386 } 387 } 388 } 389 break; 390 } 391 392 case DiagramItem::M_WIRE: 393 { 394 BRect r = which->Frame(); 395 for (uint32 i = 0; i < CountItems(DiagramItem::M_BOX); i++) { 396 DiagramItem *item = ItemAt(i, DiagramItem::M_BOX); 397 if (item && item->Frame().Intersects(r)) { 398 region->Include(item->Frame() & r); 399 found = true; 400 } 401 } 402 break; 403 } 404 } 405 } 406 407 return found; 408 } 409 410 411 // #pragma mark - selection accessors 412 413 414 /*! Returns the type of DiagramItems in the current selection 415 (currently only one type at a time is supported!) 416 */ 417 uint32 418 DiagramItemGroup::SelectedType() const 419 { 420 D_METHOD(("DiagramItemGroup::SelectedType()\n")); 421 if (CountSelectedItems() > 0) 422 return SelectedItemAt(0)->type(); 423 424 return 0; 425 } 426 427 428 //! Returns the number of items in the current selection 429 uint32 430 DiagramItemGroup::CountSelectedItems() const 431 { 432 D_METHOD(("DiagramItemGroup::CountSelectedItems()\n")); 433 if (fSelection) 434 return fSelection->CountItems(); 435 436 return 0; 437 } 438 439 440 /*! Returns a pointer to the item in the list which is 441 at the given \param index; if none is found, this function 442 returns 0 443 */ 444 DiagramItem* 445 DiagramItemGroup::SelectedItemAt(uint32 index) const 446 { 447 D_METHOD(("DiagramItemGroup::SelectedItemAt()\n")); 448 if (fSelection) 449 return static_cast<DiagramItem *>(fSelection->ItemAt(index)); 450 451 return 0; 452 } 453 454 455 // #pragma mark - selection related operations 456 457 458 /*! Selects an item, optionally replacing the complete former 459 selection. If the type of the item to be selected differs 460 from the type of items currently selected, this methods 461 automatically replaces the former selection 462 */ 463 bool 464 DiagramItemGroup::SelectItem(DiagramItem* which, bool deselectOthers) 465 { 466 D_METHOD(("DiagramItemGroup::SelectItem()\n")); 467 bool selectionChanged = false; 468 if (which && !which->isSelected() && which->isSelectable()) { 469 // check if the item's type is the same as of the other 470 // selected items 471 if (fMultiSelection) { 472 if (which->type() != SelectedType()) 473 deselectOthers = true; 474 } 475 476 // check if the former selection has to be deselected 477 if (deselectOthers || !fMultiSelection) { 478 while (CountSelectedItems() > 0) 479 DeselectItem(SelectedItemAt(0)); 480 } 481 482 // select the item 483 if (deselectOthers || CountSelectedItems() == 0) 484 which->select(); 485 else 486 which->selectAdding(); 487 488 fSelection->AddItem(which); 489 selectionChanged = true; 490 } 491 492 // resort the lists if necessary 493 if (selectionChanged) { 494 SortItems(which->type(), compareSelectionTime); 495 SortSelectedItems(compareSelectionTime); 496 return true; 497 } 498 499 return false; 500 } 501 502 503 //! Simply deselects one item 504 bool 505 DiagramItemGroup::DeselectItem(DiagramItem* which) 506 { 507 D_METHOD(("DiagramItemGroup::DeselectItem()\n")); 508 if (which && which->isSelected()) { 509 fSelection->RemoveItem(which); 510 which->deselect(); 511 SortItems(which->type(), compareSelectionTime); 512 SortSelectedItems(compareSelectionTime); 513 return true; 514 } 515 516 return false; 517 } 518 519 520 //! Selects all items of the given \param itemType 521 bool 522 DiagramItemGroup::SelectAll(uint32 itemType) 523 { 524 D_METHOD(("DiagramItemGroup::SelectAll()\n")); 525 bool selectionChanged = false; 526 if (fTypes & itemType) { 527 for (uint32 i = 0; i < CountItems(itemType); i++) { 528 if (SelectItem(ItemAt(i, itemType), false)) 529 selectionChanged = true; 530 } 531 } 532 533 return selectionChanged; 534 } 535 536 537 //! Deselects all items of the given \param itemType 538 bool 539 DiagramItemGroup::DeselectAll(uint32 itemType) 540 { 541 D_METHOD(("DiagramItemGroup::DeselectAll()\n")); 542 bool selectionChanged = false; 543 if (fTypes & itemType) { 544 for (uint32 i = 0; i < CountItems(itemType); i++) { 545 if (DeselectItem(ItemAt(i, itemType))) 546 selectionChanged = true; 547 } 548 } 549 550 return selectionChanged; 551 } 552 553 554 /*! Performs a quicksort on the list of selected items with the 555 provided compare function (one is already defined in the DiagramItem 556 implementation) 557 */ 558 void 559 DiagramItemGroup::SortSelectedItems(int (*compareFunc)(const void *, const void *)) 560 { 561 D_METHOD(("DiagramItemGroup::SortSelectedItems()\n")); 562 fSelection->SortItems(compareFunc); 563 } 564 565 566 /*! Moves all selected items by a given amount, taking 567 item alignment into account; in updateRegion the areas 568 that still require updating by the caller are returned 569 */ 570 void 571 DiagramItemGroup::DragSelectionBy(float x, float y, BRegion* updateRegion) 572 { 573 D_METHOD(("DiagramItemGroup::DragSelectionBy()\n")); 574 if (SelectedType() == DiagramItem::M_BOX) { 575 Align(&x, &y); 576 if ((x != 0) || (y != 0)) { 577 for (int32 i = CountSelectedItems() - 1; i >= 0; i--) { 578 DiagramItem *item = dynamic_cast<DiagramItem *>(SelectedItemAt(i)); 579 if (item->isDraggable()) 580 item->MoveBy(x, y, updateRegion); 581 } 582 } 583 } 584 } 585 586 587 //! Removes all selected items from the group 588 void 589 DiagramItemGroup::RemoveSelection() 590 { 591 D_METHOD(("DiagramItemGroup::RemoveSelection()\n")); 592 for (uint32 i = 0; i < CountSelectedItems(); i++) 593 RemoveItem(SelectedItemAt(i)); 594 } 595 596 597 // #pragma mark - alignment related accessors & operations 598 599 600 void 601 DiagramItemGroup::GetItemAlignment(float *horizontal, float *vertical) 602 { 603 D_METHOD(("DiagramItemGroup::GetItemAlignment()\n")); 604 if (horizontal) 605 *horizontal = fItemAlignment.x; 606 if (vertical) 607 *vertical = fItemAlignment.y; 608 } 609 610 611 //! Align a given point(\param x, \param y) to the current grid 612 void 613 DiagramItemGroup::Align(float *x, float *y) const 614 { 615 D_METHOD(("DiagramItemGroup::Align()\n")); 616 *x = ((int)*x / (int)fItemAlignment.x) * fItemAlignment.x; 617 *y = ((int)*y / (int)fItemAlignment.y) * fItemAlignment.y; 618 } 619 620 621 //! Align a given \param point to the current grid 622 BPoint 623 DiagramItemGroup::Align(BPoint point) const 624 { 625 D_METHOD(("DiagramItemGroup::Align()\n")); 626 float x = point.x, y = point.y; 627 Align(&x, &y); 628 return BPoint(x, y); 629 } 630