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