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