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 "RemoveStylesCommand.h" 29 #include "Style.h" 30 #include "Observer.h" 31 #include "ResetTransformationCommand.h" 32 #include "Shape.h" 33 #include "ShapeContainer.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 ShapeContainerListener { 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 // ShapeContainerListener interface 229 virtual void ShapeAdded(Shape* shape, int32 index) 230 { 231 } 232 233 virtual void ShapeRemoved(Shape* shape) 234 { 235 fListView->SetCurrentShape(NULL); 236 } 237 238 // ShapeStyleListener 239 void SetShape(Shape* 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 Shape* CurrentShape() const 254 { 255 return fShape; 256 } 257 258 private: 259 StyleListView* fListView; 260 Shape* 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 AddStylesCommand* command = new(std::nothrow) AddStylesCommand( 505 fStyleContainer, (Style**)styles.Items(), count, 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 AddStylesCommand* command 563 = new (nothrow) AddStylesCommand(fStyleContainer, 564 styles, count, toIndex); 565 if (!command) { 566 for (int32 i = 0; i < count; i++) 567 delete styles[i]; 568 return; 569 } 570 571 fCommandStack->Perform(command); 572 } 573 574 575 void 576 StyleListView::RemoveItemList(BList& items) 577 { 578 if (!fCommandStack || !fStyleContainer) 579 return; 580 581 int32 count = items.CountItems(); 582 Style* styles[count]; 583 for (int32 i = 0; i < count; i++) { 584 StyleListItem* item = dynamic_cast<StyleListItem*>( 585 (BListItem*)items.ItemAtFast(i)); 586 if (item) 587 styles[i] = item->style; 588 else 589 styles[i] = NULL; 590 } 591 592 RemoveStylesCommand* command 593 = new (nothrow) RemoveStylesCommand(fStyleContainer, 594 styles, count); 595 fCommandStack->Perform(command); 596 } 597 598 599 BListItem* 600 StyleListView::CloneItem(int32 index) const 601 { 602 if (StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index))) { 603 return new StyleListItem(item->style, 604 const_cast<StyleListView*>(this), 605 fCurrentShape != NULL); 606 } 607 return NULL; 608 } 609 610 611 int32 612 StyleListView::IndexOfSelectable(Selectable* selectable) const 613 { 614 Style* style = dynamic_cast<Style*>(selectable); 615 if (style == NULL) 616 return -1; 617 618 int count = CountItems(); 619 for (int32 i = 0; i < count; i++) { 620 if (SelectableFor(ItemAt(i)) == style) 621 return i; 622 } 623 624 return -1; 625 } 626 627 628 Selectable* 629 StyleListView::SelectableFor(BListItem* item) const 630 { 631 StyleListItem* styleItem = dynamic_cast<StyleListItem*>(item); 632 if (styleItem != NULL) 633 return styleItem->style; 634 return NULL; 635 } 636 637 638 // #pragma mark - 639 640 641 void 642 StyleListView::StyleAdded(Style* style, int32 index) 643 { 644 // NOTE: we are in the thread that messed with the 645 // StyleContainer, so no need to lock the 646 // container, when this is changed to asynchronous 647 // notifications, then it would need to be read-locked! 648 if (!LockLooper()) 649 return; 650 651 if (_AddStyle(style, index)) 652 Select(index); 653 654 UnlockLooper(); 655 } 656 657 658 void 659 StyleListView::StyleRemoved(Style* style) 660 { 661 // NOTE: we are in the thread that messed with the 662 // StyleContainer, so no need to lock the 663 // container, when this is changed to asynchronous 664 // notifications, then it would need to be read-locked! 665 if (!LockLooper()) 666 return; 667 668 // NOTE: we're only interested in Style objects 669 _RemoveStyle(style); 670 671 UnlockLooper(); 672 } 673 674 675 // #pragma mark - 676 677 678 void 679 StyleListView::SetMenu(BMenu* menu) 680 { 681 if (fMenu == menu) 682 return; 683 684 fMenu = menu; 685 if (fMenu == NULL) 686 return; 687 688 fAddMI = new BMenuItem(B_TRANSLATE("Add"), new BMessage(MSG_ADD)); 689 fMenu->AddItem(fAddMI); 690 691 fMenu->AddSeparatorItem(); 692 693 fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"), 694 new BMessage(MSG_DUPLICATE)); 695 fMenu->AddItem(fDuplicateMI); 696 697 fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"), 698 new BMessage(MSG_RESET_TRANSFORMATION)); 699 fMenu->AddItem(fResetTransformationMI); 700 701 fMenu->AddSeparatorItem(); 702 703 fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE)); 704 fMenu->AddItem(fRemoveMI); 705 706 fMenu->SetTargetForItems(this); 707 708 _UpdateMenu(); 709 } 710 711 712 void 713 StyleListView::SetStyleContainer(StyleContainer* container) 714 { 715 if (fStyleContainer == container) 716 return; 717 718 // detach from old container 719 if (fStyleContainer != NULL) 720 fStyleContainer->RemoveListener(this); 721 722 _MakeEmpty(); 723 724 fStyleContainer = container; 725 726 if (fStyleContainer == NULL) 727 return; 728 729 fStyleContainer->AddListener(this); 730 731 // sync 732 int32 count = fStyleContainer->CountStyles(); 733 for (int32 i = 0; i < count; i++) 734 _AddStyle(fStyleContainer->StyleAtFast(i), i); 735 } 736 737 738 void 739 StyleListView::SetShapeContainer(ShapeContainer* container) 740 { 741 if (fShapeContainer == container) 742 return; 743 744 // detach from old container 745 if (fShapeContainer) 746 fShapeContainer->RemoveListener(fShapeListener); 747 748 fShapeContainer = container; 749 750 if (fShapeContainer) 751 fShapeContainer->AddListener(fShapeListener); 752 } 753 754 755 void 756 StyleListView::SetCommandStack(CommandStack* stack) 757 { 758 fCommandStack = stack; 759 } 760 761 762 void 763 StyleListView::SetCurrentColor(CurrentColor* color) 764 { 765 fCurrentColor = color; 766 } 767 768 769 void 770 StyleListView::SetCurrentShape(Shape* shape) 771 { 772 if (fCurrentShape == shape) 773 return; 774 775 fCurrentShape = shape; 776 fShapeListener->SetShape(shape); 777 778 _UpdateMarks(); 779 } 780 781 782 // #pragma mark - 783 784 785 bool 786 StyleListView::_AddStyle(Style* style, int32 index) 787 { 788 if (style != NULL) { 789 return AddItem(new StyleListItem( 790 style, this, fCurrentShape != NULL), index); 791 } 792 return false; 793 } 794 795 796 bool 797 StyleListView::_RemoveStyle(Style* style) 798 { 799 StyleListItem* item = _ItemForStyle(style); 800 if (item != NULL && RemoveItem(item)) { 801 delete item; 802 return true; 803 } 804 return false; 805 } 806 807 808 StyleListItem* 809 StyleListView::_ItemForStyle(Style* style) const 810 { 811 int count = CountItems(); 812 for (int32 i = 0; i < count; i++) { 813 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 814 if (item == NULL) 815 continue; 816 if (item->style == style) 817 return item; 818 } 819 return NULL; 820 } 821 822 823 // #pragma mark - 824 825 826 void 827 StyleListView::_UpdateMarks() 828 { 829 int32 count = CountItems(); 830 if (fCurrentShape) { 831 // enable display of marks and mark items whoes 832 // style is contained in fCurrentShape 833 for (int32 i = 0; i < count; i++) { 834 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 835 if (item == NULL) 836 continue; 837 item->SetMarkEnabled(true); 838 item->SetMarked(fCurrentShape->Style() == item->style); 839 } 840 } else { 841 // disable display of marks 842 for (int32 i = 0; i < count; i++) { 843 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 844 if (item == NULL) 845 continue; 846 item->SetMarkEnabled(false); 847 } 848 } 849 850 Invalidate(); 851 } 852 853 854 void 855 StyleListView::_SetStyleMarked(Style* style, bool marked) 856 { 857 StyleListItem* item = _ItemForStyle(style); 858 if (item != NULL) 859 item->SetMarked(marked); 860 } 861 862 863 void 864 StyleListView::_UpdateMenu() 865 { 866 if (fMenu == NULL) 867 return; 868 869 bool gotSelection = CurrentSelection(0) >= 0; 870 871 fDuplicateMI->SetEnabled(gotSelection); 872 // TODO: only enable fResetTransformationMI if styles 873 // with gradients are selected! 874 fResetTransformationMI->SetEnabled(gotSelection); 875 fRemoveMI->SetEnabled(gotSelection); 876 } 877 878