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