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