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