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