1 /* 2 * Copyright 2006, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 */ 8 9 #include "StyleListView.h" 10 11 #include <stdio.h> 12 13 #include <Application.h> 14 #include <ListItem.h> 15 #include <Menu.h> 16 #include <MenuItem.h> 17 #include <Message.h> 18 #include <Mime.h> 19 #include <Window.h> 20 21 #include "AddStylesCommand.h" 22 #include "AssignStyleCommand.h" 23 #include "CurrentColor.h" 24 #include "CommandStack.h" 25 #include "Gradient.h" 26 #include "MoveStylesCommand.h" 27 #include "RemoveStylesCommand.h" 28 #include "Style.h" 29 #include "Observer.h" 30 #include "ResetTransformationCommand.h" 31 #include "Shape.h" 32 #include "ShapeContainer.h" 33 #include "Selection.h" 34 #include "Util.h" 35 36 using std::nothrow; 37 38 static const float kMarkWidth = 14.0; 39 static const float kBorderOffset = 3.0; 40 static const float kTextOffset = 4.0; 41 42 enum { 43 MSG_ADD = 'adst', 44 MSG_REMOVE = 'rmst', 45 MSG_DUPLICATE = 'dpst', 46 MSG_RESET_TRANSFORMATION = 'rstr', 47 }; 48 49 class StyleListItem : public SimpleItem, 50 public Observer { 51 public: 52 StyleListItem(Style* s, 53 StyleListView* listView, 54 bool markEnabled) 55 : SimpleItem(""), 56 style(NULL), 57 fListView(listView), 58 fMarkEnabled(markEnabled), 59 fMarked(false) 60 { 61 SetStyle(s); 62 } 63 64 virtual ~StyleListItem() 65 { 66 SetStyle(NULL); 67 } 68 69 // SimpleItem interface 70 virtual void Draw(BView* owner, BRect itemFrame, uint32 flags) 71 { 72 SimpleItem::DrawBackground(owner, itemFrame, flags); 73 74 // text 75 owner->SetHighColor(0, 0, 0, 255); 76 font_height fh; 77 owner->GetFontHeight(&fh); 78 BString truncatedString(Text()); 79 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 80 itemFrame.Width() 81 - kBorderOffset 82 - kMarkWidth 83 - kTextOffset 84 - kBorderOffset); 85 float height = itemFrame.Height(); 86 float textHeight = fh.ascent + fh.descent; 87 BPoint pos; 88 pos.x = itemFrame.left 89 + kBorderOffset + kMarkWidth + kTextOffset; 90 pos.y = itemFrame.top 91 + 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 void UpdateText() 145 { 146 SetText(style->Name()); 147 Invalidate(); 148 } 149 150 void SetMarkEnabled(bool enabled) 151 { 152 if (fMarkEnabled == enabled) 153 return; 154 fMarkEnabled = enabled; 155 Invalidate(); 156 } 157 void SetMarked(bool marked) 158 { 159 if (fMarked == marked) 160 return; 161 fMarked = marked; 162 Invalidate(); 163 } 164 165 void Invalidate() 166 { 167 // :-/ 168 if (fListView->LockLooper()) { 169 fListView->InvalidateItem( 170 fListView->IndexOf(this)); 171 fListView->UnlockLooper(); 172 } 173 } 174 175 Style* style; 176 private: 177 StyleListView* fListView; 178 bool fMarkEnabled; 179 bool fMarked; 180 }; 181 182 183 class ShapeStyleListener : public ShapeListener, 184 public ShapeContainerListener { 185 public: 186 ShapeStyleListener(StyleListView* listView) 187 : fListView(listView), 188 fShape(NULL) 189 { 190 } 191 virtual ~ShapeStyleListener() 192 { 193 SetShape(NULL); 194 } 195 196 // ShapeListener interface 197 virtual void TransformerAdded(Transformer* t, int32 index) {} 198 virtual void TransformerRemoved(Transformer* t) {} 199 200 virtual void StyleChanged(Style* oldStyle, Style* newStyle) 201 { 202 fListView->_SetStyleMarked(oldStyle, false); 203 fListView->_SetStyleMarked(newStyle, true); 204 } 205 206 // ShapeContainerListener interface 207 virtual void ShapeAdded(Shape* shape, int32 index) {} 208 virtual void ShapeRemoved(Shape* shape) 209 { 210 fListView->SetCurrentShape(NULL); 211 } 212 213 // ShapeStyleListener 214 void SetShape(Shape* shape) 215 { 216 if (fShape == shape) 217 return; 218 219 if (fShape) 220 fShape->RemoveListener(this); 221 222 fShape = shape; 223 224 if (fShape) 225 fShape->AddListener(this); 226 } 227 228 Shape* CurrentShape() const 229 { 230 return fShape; 231 } 232 233 private: 234 StyleListView* fListView; 235 Shape* fShape; 236 }; 237 238 // #pragma mark - 239 240 // constructor 241 StyleListView::StyleListView(BRect frame, 242 const char* name, 243 BMessage* message, BHandler* target) 244 : SimpleListView(frame, name, 245 NULL, B_SINGLE_SELECTION_LIST), 246 fMessage(message), 247 fStyleContainer(NULL), 248 fShapeContainer(NULL), 249 fCommandStack(NULL), 250 251 fCurrentShape(NULL), 252 fShapeListener(new ShapeStyleListener(this)), 253 254 fMenu(NULL) 255 { 256 SetTarget(target); 257 } 258 259 // destructor 260 StyleListView::~StyleListView() 261 { 262 _MakeEmpty(); 263 delete fMessage; 264 265 if (fStyleContainer) 266 fStyleContainer->RemoveListener(this); 267 268 if (fShapeContainer) 269 fShapeContainer->RemoveListener(fShapeListener); 270 271 delete fShapeListener; 272 } 273 274 // #pragma mark - 275 276 // MessageReceived 277 void 278 StyleListView::MessageReceived(BMessage* message) 279 { 280 switch (message->what) { 281 case MSG_ADD: { 282 Style* style; 283 AddStylesCommand* command; 284 new_style(CurrentColor::Default()->Color(), 285 fStyleContainer, &style, &command); 286 fCommandStack->Perform(command); 287 break; 288 } 289 case MSG_REMOVE: 290 RemoveSelected(); 291 break; 292 case MSG_DUPLICATE: { 293 int32 count = CountSelectedItems(); 294 int32 index = 0; 295 BList items; 296 for (int32 i = 0; i < count; i++) { 297 index = CurrentSelection(i); 298 BListItem* item = ItemAt(index); 299 if (item) 300 items.AddItem((void*)item); 301 } 302 CopyItems(items, index + 1); 303 break; 304 } 305 case MSG_RESET_TRANSFORMATION: { 306 int32 count = CountSelectedItems(); 307 BList gradients; 308 for (int32 i = 0; i < count; i++) { 309 StyleListItem* item = dynamic_cast<StyleListItem*>( 310 ItemAt(CurrentSelection(i))); 311 if (item && item->style && item->style->Gradient()) 312 if (!gradients.AddItem( 313 (void*)item->style->Gradient())) 314 break; 315 } 316 count = gradients.CountItems(); 317 if (count < 0) 318 break; 319 320 Transformable* transformables[count]; 321 for (int32 i = 0; i < count; i++) { 322 Gradient* gradient = (Gradient*)gradients.ItemAtFast(i); 323 transformables[i] = gradient; 324 } 325 326 ResetTransformationCommand* command = 327 new ResetTransformationCommand(transformables, count); 328 329 fCommandStack->Perform(command); 330 break; 331 } 332 default: 333 SimpleListView::MessageReceived(message); 334 break; 335 } 336 } 337 338 // SelectionChanged 339 void 340 StyleListView::SelectionChanged() 341 { 342 SimpleListView::SelectionChanged(); 343 344 if (!fSyncingToSelection) { 345 // NOTE: single selection list 346 StyleListItem* item 347 = dynamic_cast<StyleListItem*>(ItemAt(CurrentSelection(0))); 348 if (fMessage) { 349 BMessage message(*fMessage); 350 message.AddPointer("style", item ? (void*)item->style : NULL); 351 Invoke(&message); 352 } 353 } 354 355 _UpdateMenu(); 356 } 357 358 // MouseDown 359 void 360 StyleListView::MouseDown(BPoint where) 361 { 362 if (!fCurrentShape) { 363 SimpleListView::MouseDown(where); 364 return; 365 } 366 367 bool handled = false; 368 int32 index = IndexOf(where); 369 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index)); 370 if (item) { 371 BRect itemFrame(ItemFrame(index)); 372 itemFrame.right = itemFrame.left 373 + kBorderOffset + kMarkWidth 374 + kTextOffset / 2.0; 375 Style* style = item->style; 376 if (itemFrame.Contains(where)) { 377 // set the style on the shape 378 if (fCommandStack) { 379 ::Command* command = new AssignStyleCommand( 380 fCurrentShape, style); 381 fCommandStack->Perform(command); 382 } else { 383 fCurrentShape->SetStyle(style); 384 } 385 handled = true; 386 } 387 } 388 389 if (!handled) 390 SimpleListView::MouseDown(where); 391 } 392 393 // MakeDragMessage 394 void 395 StyleListView::MakeDragMessage(BMessage* message) const 396 { 397 SimpleListView::MakeDragMessage(message); 398 message->AddPointer("container", fStyleContainer); 399 int32 count = CountSelectedItems(); 400 for (int32 i = 0; i < count; i++) { 401 StyleListItem* item = dynamic_cast<StyleListItem*>( 402 ItemAt(CurrentSelection(i))); 403 if (item) 404 message->AddPointer("style", (void*)item->style); 405 else 406 break; 407 } 408 } 409 410 // AcceptDragMessage 411 bool 412 StyleListView::AcceptDragMessage(const BMessage* message) const 413 { 414 return SimpleListView::AcceptDragMessage(message); 415 } 416 417 // SetDropTargetRect 418 void 419 StyleListView::SetDropTargetRect(const BMessage* message, BPoint where) 420 { 421 SimpleListView::SetDropTargetRect(message, where); 422 } 423 424 // MoveItems 425 void 426 StyleListView::MoveItems(BList& items, int32 toIndex) 427 { 428 if (!fCommandStack || !fStyleContainer) 429 return; 430 431 int32 count = items.CountItems(); 432 Style** styles = new (nothrow) Style*[count]; 433 if (!styles) 434 return; 435 436 for (int32 i = 0; i < count; i++) { 437 StyleListItem* item 438 = dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i)); 439 styles[i] = item ? item->style : NULL; 440 } 441 442 MoveStylesCommand* command 443 = new (nothrow) MoveStylesCommand(fStyleContainer, 444 styles, count, toIndex); 445 if (!command) { 446 delete[] styles; 447 return; 448 } 449 450 fCommandStack->Perform(command); 451 } 452 453 // CopyItems 454 void 455 StyleListView::CopyItems(BList& items, int32 toIndex) 456 { 457 if (!fCommandStack || !fStyleContainer) 458 return; 459 460 int32 count = items.CountItems(); 461 Style* styles[count]; 462 463 for (int32 i = 0; i < count; i++) { 464 StyleListItem* item 465 = dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i)); 466 styles[i] = item ? new (nothrow) Style(*item->style) : NULL; 467 } 468 469 AddStylesCommand* command 470 = new (nothrow) AddStylesCommand(fStyleContainer, 471 styles, count, toIndex); 472 if (!command) { 473 for (int32 i = 0; i < count; i++) 474 delete styles[i]; 475 return; 476 } 477 478 fCommandStack->Perform(command); 479 } 480 481 // RemoveItemList 482 void 483 StyleListView::RemoveItemList(BList& items) 484 { 485 if (!fCommandStack || !fStyleContainer) 486 return; 487 488 int32 count = items.CountItems(); 489 Style* styles[count]; 490 for (int32 i = 0; i < count; i++) { 491 StyleListItem* item = dynamic_cast<StyleListItem*>( 492 (BListItem*)items.ItemAtFast(i)); 493 if (item) 494 styles[i] = item->style; 495 else 496 styles[i] = NULL; 497 } 498 499 RemoveStylesCommand* command 500 = new (nothrow) RemoveStylesCommand(fStyleContainer, 501 styles, count); 502 fCommandStack->Perform(command); 503 } 504 505 // CloneItem 506 BListItem* 507 StyleListView::CloneItem(int32 index) const 508 { 509 if (StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index))) { 510 return new StyleListItem(item->style, 511 const_cast<StyleListView*>(this), 512 fCurrentShape != NULL); 513 } 514 return NULL; 515 } 516 517 // IndexOfSelectable 518 int32 519 StyleListView::IndexOfSelectable(Selectable* selectable) const 520 { 521 Style* style = dynamic_cast<Style*>(selectable); 522 if (!style) 523 return -1; 524 525 for (int32 i = 0; 526 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 527 i++) { 528 if (item->style == style) 529 return i; 530 } 531 532 return -1; 533 } 534 535 // SelectableFor 536 Selectable* 537 StyleListView::SelectableFor(BListItem* item) const 538 { 539 StyleListItem* styleItem = dynamic_cast<StyleListItem*>(item); 540 if (styleItem) 541 return styleItem->style; 542 return NULL; 543 } 544 545 // #pragma mark - 546 547 // StyleAdded 548 void 549 StyleListView::StyleAdded(Style* style, int32 index) 550 { 551 // NOTE: we are in the thread that messed with the 552 // StyleContainer, so no need to lock the 553 // container, when this is changed to asynchronous 554 // notifications, then it would need to be read-locked! 555 if (!LockLooper()) 556 return; 557 558 // NOTE: shapes are always added at the end 559 // of the list, so the sorting is synced... 560 if (_AddStyle(style, index)) 561 Select(CountItems() - 1); 562 563 UnlockLooper(); 564 } 565 566 // StyleRemoved 567 void 568 StyleListView::StyleRemoved(Style* style) 569 { 570 // NOTE: we are in the thread that messed with the 571 // StyleContainer, so no need to lock the 572 // container, when this is changed to asynchronous 573 // notifications, then it would need to be read-locked! 574 if (!LockLooper()) 575 return; 576 577 // NOTE: we're only interested in Style objects 578 _RemoveStyle(style); 579 580 UnlockLooper(); 581 } 582 583 // #pragma mark - 584 585 // SetMenu 586 void 587 StyleListView::SetMenu(BMenu* menu) 588 { 589 if (fMenu == menu) 590 return; 591 592 fMenu = menu; 593 if (fMenu == NULL) 594 return; 595 596 fAddMI = new BMenuItem("Add", new BMessage(MSG_ADD)); 597 fMenu->AddItem(fAddMI); 598 599 fMenu->AddSeparatorItem(); 600 601 fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE)); 602 fMenu->AddItem(fDuplicateMI); 603 604 fResetTransformationMI = new BMenuItem("Reset Transformation", 605 new BMessage(MSG_RESET_TRANSFORMATION)); 606 fMenu->AddItem(fResetTransformationMI); 607 608 fMenu->AddSeparatorItem(); 609 610 fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE)); 611 fMenu->AddItem(fRemoveMI); 612 613 fMenu->SetTargetForItems(this); 614 615 _UpdateMenu(); 616 } 617 618 // SetStyleContainer 619 void 620 StyleListView::SetStyleContainer(StyleContainer* container) 621 { 622 if (fStyleContainer == container) 623 return; 624 625 // detach from old container 626 if (fStyleContainer) 627 fStyleContainer->RemoveListener(this); 628 629 _MakeEmpty(); 630 631 fStyleContainer = container; 632 633 if (!fStyleContainer) 634 return; 635 636 fStyleContainer->AddListener(this); 637 638 // sync 639 int32 count = fStyleContainer->CountStyles(); 640 for (int32 i = 0; i < count; i++) 641 _AddStyle(fStyleContainer->StyleAtFast(i), i); 642 } 643 644 // SetShapeContainer 645 void 646 StyleListView::SetShapeContainer(ShapeContainer* container) 647 { 648 if (fShapeContainer == container) 649 return; 650 651 // detach from old container 652 if (fShapeContainer) 653 fShapeContainer->RemoveListener(fShapeListener); 654 655 fShapeContainer = container; 656 657 if (fShapeContainer) 658 fShapeContainer->AddListener(fShapeListener); 659 } 660 661 // SetCommandStack 662 void 663 StyleListView::SetCommandStack(CommandStack* stack) 664 { 665 fCommandStack = stack; 666 } 667 668 // SetCurrentShape 669 void 670 StyleListView::SetCurrentShape(Shape* shape) 671 { 672 if (fCurrentShape == shape) 673 return; 674 675 fCurrentShape = shape; 676 fShapeListener->SetShape(shape); 677 678 _UpdateMarks(); 679 } 680 681 // #pragma mark - 682 683 // _AddStyle 684 bool 685 StyleListView::_AddStyle(Style* style, int32 index) 686 { 687 if (style) { 688 return AddItem(new StyleListItem( 689 style, this, fCurrentShape != NULL), index); 690 } 691 return false; 692 } 693 694 // _RemoveStyle 695 bool 696 StyleListView::_RemoveStyle(Style* style) 697 { 698 StyleListItem* item = _ItemForStyle(style); 699 if (item && RemoveItem(item)) { 700 delete item; 701 return true; 702 } 703 return false; 704 } 705 706 // _ItemForStyle 707 StyleListItem* 708 StyleListView::_ItemForStyle(Style* style) const 709 { 710 for (int32 i = 0; 711 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 712 i++) { 713 if (item->style == style) 714 return item; 715 } 716 return NULL; 717 } 718 719 // #pragma mark - 720 721 // _UpdateMarks 722 void 723 StyleListView::_UpdateMarks() 724 { 725 int32 count = CountItems(); 726 if (fCurrentShape) { 727 // enable display of marks and mark items whoes 728 // style is contained in fCurrentShape 729 for (int32 i = 0; i < count; i++) { 730 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 731 if (!item) 732 continue; 733 item->SetMarkEnabled(true); 734 item->SetMarked(fCurrentShape->Style() == item->style); 735 } 736 } else { 737 // disable display of marks 738 for (int32 i = 0; i < count; i++) { 739 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 740 if (!item) 741 continue; 742 item->SetMarkEnabled(false); 743 } 744 } 745 746 Invalidate(); 747 } 748 749 // _SetStyleMarked 750 void 751 StyleListView::_SetStyleMarked(Style* style, bool marked) 752 { 753 if (StyleListItem* item = _ItemForStyle(style)) { 754 item->SetMarked(marked); 755 } 756 } 757 758 // _UpdateMenu 759 void 760 StyleListView::_UpdateMenu() 761 { 762 if (!fMenu) 763 return; 764 765 bool gotSelection = CurrentSelection(0) >= 0; 766 767 fDuplicateMI->SetEnabled(gotSelection); 768 // TODO: only enable fResetTransformationMI if styles 769 // with gradients are selected! 770 fResetTransformationMI->SetEnabled(gotSelection); 771 fRemoveMI->SetEnabled(gotSelection); 772 } 773 774