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