1 /* 2 * Copyright 2006-2012, Stephan Aßmus <superstippi@gmx.de>. 3 * Copyright 2023, Haiku. 4 * All rights reserved. Distributed under the terms of the MIT License. 5 * 6 * Authors: 7 * Zardshard 8 */ 9 10 11 #include "StyleListView.h" 12 13 #include <new> 14 #include <stdio.h> 15 16 #include <Application.h> 17 #include <Catalog.h> 18 #include <ListItem.h> 19 #include <Locale.h> 20 #include <Menu.h> 21 #include <MenuItem.h> 22 #include <Message.h> 23 #include <Mime.h> 24 #include <Window.h> 25 26 #include "AddStylesCommand.h" 27 #include "AssignStyleCommand.h" 28 #include "CurrentColor.h" 29 #include "CommandStack.h" 30 #include "GradientTransformable.h" 31 #include "MoveStylesCommand.h" 32 #include "PathSourceShape.h" 33 #include "RemoveStylesCommand.h" 34 #include "Style.h" 35 #include "Observer.h" 36 #include "ResetTransformationCommand.h" 37 #include "Shape.h" 38 #include "Selection.h" 39 #include "Util.h" 40 41 42 #undef B_TRANSLATION_CONTEXT 43 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-StylesList" 44 45 46 using std::nothrow; 47 48 static const float kMarkWidth = 14.0; 49 static const float kBorderOffset = 3.0; 50 static const float kTextOffset = 4.0; 51 52 enum { 53 MSG_ADD = 'adst', 54 MSG_REMOVE = 'rmst', 55 MSG_DUPLICATE = 'dpst', 56 MSG_RESET_TRANSFORMATION = 'rstr', 57 }; 58 59 class StyleListItem : public SimpleItem, 60 public Observer { 61 public: 62 StyleListItem(Style* s, StyleListView* listView, bool markEnabled) 63 : 64 SimpleItem(""), 65 style(NULL), 66 fListView(listView), 67 fMarkEnabled(markEnabled), 68 fMarked(false) 69 { 70 SetStyle(s); 71 } 72 73 virtual ~StyleListItem() 74 { 75 SetStyle(NULL); 76 } 77 78 // SimpleItem interface 79 virtual void DrawItem(BView* owner, BRect itemFrame, bool even) 80 { 81 SimpleItem::DrawBackground(owner, itemFrame, even); 82 83 float offset = kBorderOffset + kMarkWidth + kTextOffset; 84 SimpleItem::DrawItem(owner, itemFrame.OffsetByCopy(offset, 0), even); 85 86 if (!fMarkEnabled) 87 return; 88 89 // mark 90 BRect markRect = itemFrame; 91 float markRectBorderTint = B_DARKEN_1_TINT; 92 float markRectFillTint = 1.04; 93 float markTint = B_DARKEN_4_TINT; 94 // Dark Themes 95 rgb_color lowColor = owner->LowColor(); 96 if (lowColor.red + lowColor.green + lowColor.blue < 128 * 3) { 97 markRectBorderTint = B_LIGHTEN_2_TINT; 98 markRectFillTint = 0.85; 99 markTint = 0.1; 100 } 101 markRect.left += kBorderOffset; 102 markRect.right = markRect.left + kMarkWidth; 103 markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0; 104 markRect.bottom = markRect.top + kMarkWidth; 105 owner->SetHighColor(tint_color(owner->LowColor(), markRectBorderTint)); 106 owner->StrokeRect(markRect); 107 markRect.InsetBy(1, 1); 108 owner->SetHighColor(tint_color(owner->LowColor(), markRectFillTint)); 109 owner->FillRect(markRect); 110 if (fMarked) { 111 markRect.InsetBy(2, 2); 112 owner->SetHighColor(tint_color(owner->LowColor(), 113 markTint)); 114 owner->SetPenSize(2); 115 owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom()); 116 owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop()); 117 owner->SetPenSize(1); 118 } 119 } 120 121 // Observer interface 122 virtual void ObjectChanged(const Observable* object) 123 { 124 UpdateText(); 125 } 126 127 // StyleListItem 128 void SetStyle(Style* s) 129 { 130 if (s == style) 131 return; 132 133 if (style) { 134 style->RemoveObserver(this); 135 style->ReleaseReference(); 136 } 137 138 style = s; 139 140 if (style) { 141 style->AcquireReference(); 142 style->AddObserver(this); 143 UpdateText(); 144 } 145 } 146 147 void UpdateText() 148 { 149 SetText(style->Name()); 150 Invalidate(); 151 } 152 153 void SetMarkEnabled(bool enabled) 154 { 155 if (fMarkEnabled == enabled) 156 return; 157 fMarkEnabled = enabled; 158 Invalidate(); 159 } 160 161 void SetMarked(bool marked) 162 { 163 if (fMarked == marked) 164 return; 165 fMarked = marked; 166 Invalidate(); 167 } 168 169 void Invalidate() 170 { 171 if (fListView->LockLooper()) { 172 fListView->InvalidateItem(fListView->IndexOf(this)); 173 fListView->UnlockLooper(); 174 } 175 } 176 177 public: 178 Style* style; 179 180 private: 181 StyleListView* fListView; 182 bool fMarkEnabled; 183 bool fMarked; 184 }; 185 186 187 class ShapeStyleListener : public ShapeListener, 188 public ContainerListener<Shape> { 189 public: 190 ShapeStyleListener(StyleListView* listView) 191 : 192 fListView(listView), 193 fShape(NULL) 194 { 195 } 196 197 virtual ~ShapeStyleListener() 198 { 199 SetShape(NULL); 200 } 201 202 // ShapeListener interface 203 virtual void TransformerAdded(Transformer* t, int32 index) 204 { 205 } 206 207 virtual void TransformerRemoved(Transformer* t) 208 { 209 } 210 211 virtual void StyleChanged(Style* oldStyle, Style* newStyle) 212 { 213 fListView->_SetStyleMarked(oldStyle, false); 214 fListView->_SetStyleMarked(newStyle, true); 215 } 216 217 // ContainerListener<Shape> interface 218 virtual void ItemAdded(Shape* shape, int32 index) 219 { 220 } 221 222 virtual void ItemRemoved(Shape* shape) 223 { 224 fListView->SetCurrentShape(NULL); 225 } 226 227 // ShapeStyleListener 228 void SetShape(PathSourceShape* shape) 229 { 230 if (fShape == shape) 231 return; 232 233 if (fShape) 234 fShape->RemoveListener(this); 235 236 fShape = shape; 237 238 if (fShape) 239 fShape->AddListener(this); 240 } 241 242 PathSourceShape* CurrentShape() const 243 { 244 return fShape; 245 } 246 247 private: 248 StyleListView* fListView; 249 PathSourceShape* fShape; 250 }; 251 252 253 // #pragma mark - 254 255 256 StyleListView::StyleListView(BRect frame, const char* name, BMessage* message, 257 BHandler* target) 258 : 259 SimpleListView(frame, name, NULL, B_SINGLE_SELECTION_LIST), 260 fMessage(message), 261 fStyleContainer(NULL), 262 fShapeContainer(NULL), 263 fCommandStack(NULL), 264 fCurrentColor(NULL), 265 266 fCurrentShape(NULL), 267 fShapeListener(new ShapeStyleListener(this)), 268 269 fMenu(NULL) 270 { 271 SetTarget(target); 272 } 273 274 275 StyleListView::~StyleListView() 276 { 277 _MakeEmpty(); 278 delete fMessage; 279 280 if (fStyleContainer != NULL) 281 fStyleContainer->RemoveListener(this); 282 283 if (fShapeContainer != NULL) 284 fShapeContainer->RemoveListener(fShapeListener); 285 286 delete fShapeListener; 287 } 288 289 290 // #pragma mark - 291 292 293 void 294 StyleListView::MessageReceived(BMessage* message) 295 { 296 switch (message->what) { 297 case MSG_ADD: 298 { 299 Style* style; 300 AddStylesCommand* command; 301 rgb_color color; 302 if (fCurrentColor != NULL) 303 color = fCurrentColor->Color(); 304 else { 305 color.red = 0; 306 color.green = 0; 307 color.blue = 0; 308 color.alpha = 255; 309 } 310 new_style(color, fStyleContainer, &style, &command); 311 fCommandStack->Perform(command); 312 break; 313 } 314 315 case MSG_REMOVE: 316 RemoveSelected(); 317 break; 318 319 case MSG_DUPLICATE: 320 { 321 int32 count = CountSelectedItems(); 322 int32 index = 0; 323 BList items; 324 for (int32 i = 0; i < count; i++) { 325 index = CurrentSelection(i); 326 BListItem* item = ItemAt(index); 327 if (item) 328 items.AddItem((void*)item); 329 } 330 CopyItems(items, index + 1); 331 break; 332 } 333 334 case MSG_RESET_TRANSFORMATION: 335 { 336 int32 count = CountSelectedItems(); 337 BList gradients; 338 for (int32 i = 0; i < count; i++) { 339 StyleListItem* item = dynamic_cast<StyleListItem*>( 340 ItemAt(CurrentSelection(i))); 341 if (item && item->style && item->style->Gradient()) { 342 if (!gradients.AddItem((void*)item->style->Gradient())) 343 break; 344 } 345 } 346 count = gradients.CountItems(); 347 if (count <= 0) 348 break; 349 350 Transformable* transformables[count]; 351 for (int32 i = 0; i < count; i++) { 352 Gradient* gradient = (Gradient*)gradients.ItemAtFast(i); 353 transformables[i] = gradient; 354 } 355 356 ResetTransformationCommand* command 357 = new ResetTransformationCommand(transformables, count); 358 359 fCommandStack->Perform(command); 360 break; 361 } 362 363 default: 364 SimpleListView::MessageReceived(message); 365 break; 366 } 367 } 368 369 370 void 371 StyleListView::SelectionChanged() 372 { 373 SimpleListView::SelectionChanged(); 374 375 if (!fSyncingToSelection) { 376 // NOTE: single selection list 377 StyleListItem* item 378 = dynamic_cast<StyleListItem*>(ItemAt(CurrentSelection(0))); 379 if (fMessage) { 380 BMessage message(*fMessage); 381 message.AddPointer("style", item ? (void*)item->style : NULL); 382 Invoke(&message); 383 } 384 } 385 386 _UpdateMenu(); 387 } 388 389 390 void 391 StyleListView::MouseDown(BPoint where) 392 { 393 if (fCurrentShape == NULL) { 394 SimpleListView::MouseDown(where); 395 return; 396 } 397 398 bool handled = false; 399 int32 index = IndexOf(where); 400 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index)); 401 if (item != NULL) { 402 BRect itemFrame(ItemFrame(index)); 403 itemFrame.right = itemFrame.left + kBorderOffset + kMarkWidth 404 + kTextOffset / 2.0; 405 Style* style = item->style; 406 if (itemFrame.Contains(where)) { 407 // set the style on the shape 408 if (fCommandStack) { 409 ::Command* command = new AssignStyleCommand( 410 fCurrentShape, style); 411 fCommandStack->Perform(command); 412 } else { 413 fCurrentShape->SetStyle(style); 414 } 415 handled = true; 416 } 417 } 418 419 if (!handled) 420 SimpleListView::MouseDown(where); 421 } 422 423 424 status_t 425 StyleListView::ArchiveSelection(BMessage* into, bool deep) const 426 { 427 into->what = StyleListView::kSelectionArchiveCode; 428 429 int32 count = CountSelectedItems(); 430 for (int32 i = 0; i < count; i++) { 431 StyleListItem* item = dynamic_cast<StyleListItem*>( 432 ItemAt(CurrentSelection(i))); 433 if (item != NULL) { 434 BMessage archive; 435 if (item->style->Archive(&archive, true) == B_OK) 436 into->AddMessage("style", &archive); 437 } else 438 return B_ERROR; 439 } 440 return B_OK; 441 } 442 443 444 bool 445 StyleListView::InstantiateSelection(const BMessage* archive, int32 dropIndex) 446 { 447 if (archive->what != StyleListView::kSelectionArchiveCode 448 || fCommandStack == NULL || fStyleContainer == NULL) 449 return false; 450 451 // Drag may have come from another instance, like in another window. 452 // Reconstruct the Styles from the archive and add them at the drop 453 // index. 454 int index = 0; 455 BList styles; 456 while (true) { 457 BMessage styleArchive; 458 if (archive->FindMessage("style", index, &styleArchive) != B_OK) 459 break; 460 Style* style = new(std::nothrow) Style(&styleArchive); 461 if (style == NULL) 462 break; 463 464 if (!styles.AddItem(style)) { 465 delete style; 466 break; 467 } 468 469 index++; 470 } 471 472 int32 count = styles.CountItems(); 473 if (count == 0) 474 return false; 475 476 AddCommand<Style>* command = new(std::nothrow) AddCommand<Style>( 477 fStyleContainer, (Style**)styles.Items(), count, true, dropIndex); 478 479 if (command == NULL) { 480 for (int32 i = 0; i < count; i++) 481 delete (Style*)styles.ItemAtFast(i); 482 return false; 483 } 484 485 fCommandStack->Perform(command); 486 487 return true; 488 } 489 490 491 void 492 StyleListView::MoveItems(BList& items, int32 toIndex) 493 { 494 if (fCommandStack == NULL || fStyleContainer == NULL) 495 return; 496 497 int32 count = items.CountItems(); 498 Style** styles = new (nothrow) Style*[count]; 499 if (styles == NULL) 500 return; 501 502 for (int32 i = 0; i < count; i++) { 503 StyleListItem* item 504 = dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i)); 505 styles[i] = item ? item->style : NULL; 506 } 507 508 MoveStylesCommand* command = new (nothrow) MoveStylesCommand( 509 fStyleContainer, styles, count, toIndex); 510 if (command == NULL) { 511 delete[] styles; 512 return; 513 } 514 515 fCommandStack->Perform(command); 516 } 517 518 519 void 520 StyleListView::CopyItems(BList& items, int32 toIndex) 521 { 522 if (fCommandStack == NULL || fStyleContainer == NULL) 523 return; 524 525 int32 count = items.CountItems(); 526 Style* styles[count]; 527 528 for (int32 i = 0; i < count; i++) { 529 StyleListItem* item 530 = dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i)); 531 styles[i] = item ? new (nothrow) Style(*item->style) : NULL; 532 } 533 534 AddCommand<Style>* command 535 = new (nothrow) AddCommand<Style>(fStyleContainer, styles, count, true, toIndex); 536 if (!command) { 537 for (int32 i = 0; i < count; i++) 538 delete styles[i]; 539 return; 540 } 541 542 fCommandStack->Perform(command); 543 } 544 545 546 void 547 StyleListView::RemoveItemList(BList& items) 548 { 549 if (!fCommandStack || !fStyleContainer) 550 return; 551 552 int32 count = items.CountItems(); 553 int32 indices[count]; 554 for (int32 i = 0; i < count; i++) 555 indices[i] = IndexOf((BListItem*)items.ItemAtFast(i)); 556 557 RemoveStylesCommand* command 558 = new (nothrow) RemoveStylesCommand(fStyleContainer, indices, count); 559 fCommandStack->Perform(command); 560 } 561 562 563 BListItem* 564 StyleListView::CloneItem(int32 index) const 565 { 566 if (StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index))) { 567 return new StyleListItem(item->style, 568 const_cast<StyleListView*>(this), 569 fCurrentShape != NULL); 570 } 571 return NULL; 572 } 573 574 575 int32 576 StyleListView::IndexOfSelectable(Selectable* selectable) const 577 { 578 Style* style = dynamic_cast<Style*>(selectable); 579 if (style == NULL) 580 return -1; 581 582 int count = CountItems(); 583 for (int32 i = 0; i < count; i++) { 584 if (SelectableFor(ItemAt(i)) == style) 585 return i; 586 } 587 588 return -1; 589 } 590 591 592 Selectable* 593 StyleListView::SelectableFor(BListItem* item) const 594 { 595 StyleListItem* styleItem = dynamic_cast<StyleListItem*>(item); 596 if (styleItem != NULL) 597 return styleItem->style; 598 return NULL; 599 } 600 601 602 // #pragma mark - 603 604 605 void 606 StyleListView::ItemAdded(Style* style, int32 index) 607 { 608 // NOTE: we are in the thread that messed with the 609 // StyleContainer, so no need to lock the 610 // container, when this is changed to asynchronous 611 // notifications, then it would need to be read-locked! 612 if (!LockLooper()) 613 return; 614 615 if (_AddStyle(style, index)) 616 Select(index); 617 618 UnlockLooper(); 619 } 620 621 622 void 623 StyleListView::ItemRemoved(Style* style) 624 { 625 // NOTE: we are in the thread that messed with the 626 // StyleContainer, so no need to lock the 627 // container, when this is changed to asynchronous 628 // notifications, then it would need to be read-locked! 629 if (!LockLooper()) 630 return; 631 632 // NOTE: we're only interested in Style objects 633 _RemoveStyle(style); 634 635 UnlockLooper(); 636 } 637 638 639 // #pragma mark - 640 641 642 void 643 StyleListView::SetMenu(BMenu* menu) 644 { 645 if (fMenu == menu) 646 return; 647 648 fMenu = menu; 649 if (fMenu == NULL) 650 return; 651 652 fAddMI = new BMenuItem(B_TRANSLATE("Add"), new BMessage(MSG_ADD)); 653 fMenu->AddItem(fAddMI); 654 655 fMenu->AddSeparatorItem(); 656 657 fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"), 658 new BMessage(MSG_DUPLICATE)); 659 fMenu->AddItem(fDuplicateMI); 660 661 fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"), 662 new BMessage(MSG_RESET_TRANSFORMATION)); 663 fMenu->AddItem(fResetTransformationMI); 664 665 fMenu->AddSeparatorItem(); 666 667 fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE)); 668 fMenu->AddItem(fRemoveMI); 669 670 fMenu->SetTargetForItems(this); 671 672 _UpdateMenu(); 673 } 674 675 676 void 677 StyleListView::SetStyleContainer(Container<Style>* container) 678 { 679 if (fStyleContainer == container) 680 return; 681 682 // detach from old container 683 if (fStyleContainer != NULL) 684 fStyleContainer->RemoveListener(this); 685 686 _MakeEmpty(); 687 688 fStyleContainer = container; 689 690 if (fStyleContainer == NULL) 691 return; 692 693 fStyleContainer->AddListener(this); 694 695 // sync 696 int32 count = fStyleContainer->CountItems(); 697 for (int32 i = 0; i < count; i++) 698 _AddStyle(fStyleContainer->ItemAtFast(i), i); 699 } 700 701 702 void 703 StyleListView::SetShapeContainer(Container<Shape>* container) 704 { 705 if (fShapeContainer == container) 706 return; 707 708 // detach from old container 709 if (fShapeContainer) 710 fShapeContainer->RemoveListener(fShapeListener); 711 712 fShapeContainer = container; 713 714 if (fShapeContainer) 715 fShapeContainer->AddListener(fShapeListener); 716 } 717 718 719 void 720 StyleListView::SetCommandStack(CommandStack* stack) 721 { 722 fCommandStack = stack; 723 } 724 725 726 void 727 StyleListView::SetCurrentColor(CurrentColor* color) 728 { 729 fCurrentColor = color; 730 } 731 732 733 void 734 StyleListView::SetCurrentShape(Shape* shape) 735 { 736 PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(shape); 737 738 if (fCurrentShape == pathSourceShape) 739 return; 740 741 fCurrentShape = pathSourceShape; 742 fShapeListener->SetShape(pathSourceShape); 743 744 _UpdateMarks(); 745 } 746 747 748 // #pragma mark - 749 750 751 bool 752 StyleListView::_AddStyle(Style* style, int32 index) 753 { 754 if (style != NULL) { 755 return AddItem(new StyleListItem( 756 style, this, fCurrentShape != NULL), index); 757 } 758 return false; 759 } 760 761 762 bool 763 StyleListView::_RemoveStyle(Style* style) 764 { 765 StyleListItem* item = _ItemForStyle(style); 766 if (item != NULL && RemoveItem(item)) { 767 delete item; 768 return true; 769 } 770 return false; 771 } 772 773 774 StyleListItem* 775 StyleListView::_ItemForStyle(Style* style) const 776 { 777 int count = CountItems(); 778 for (int32 i = 0; i < count; i++) { 779 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 780 if (item == NULL) 781 continue; 782 if (item->style == style) 783 return item; 784 } 785 return NULL; 786 } 787 788 789 // #pragma mark - 790 791 792 void 793 StyleListView::_UpdateMarks() 794 { 795 int32 count = CountItems(); 796 if (fCurrentShape) { 797 // enable display of marks and mark items whoes 798 // style is contained in fCurrentShape 799 for (int32 i = 0; i < count; i++) { 800 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 801 if (item == NULL) 802 continue; 803 item->SetMarkEnabled(true); 804 item->SetMarked(fCurrentShape->Style() == item->style); 805 } 806 } else { 807 // disable display of marks 808 for (int32 i = 0; i < count; i++) { 809 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 810 if (item == NULL) 811 continue; 812 item->SetMarkEnabled(false); 813 } 814 } 815 816 Invalidate(); 817 } 818 819 820 void 821 StyleListView::_SetStyleMarked(Style* style, bool marked) 822 { 823 StyleListItem* item = _ItemForStyle(style); 824 if (item != NULL) 825 item->SetMarked(marked); 826 } 827 828 829 void 830 StyleListView::_UpdateMenu() 831 { 832 if (fMenu == NULL) 833 return; 834 835 bool gotSelection = CurrentSelection(0) >= 0; 836 837 fDuplicateMI->SetEnabled(gotSelection); 838 // TODO: only enable fResetTransformationMI if styles 839 // with gradients are selected! 840 fResetTransformationMI->SetEnabled(gotSelection); 841 fRemoveMI->SetEnabled(gotSelection); 842 } 843 844