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 if (_AddStyle(style, index)) 559 Select(index); 560 561 UnlockLooper(); 562 } 563 564 // StyleRemoved 565 void 566 StyleListView::StyleRemoved(Style* style) 567 { 568 // NOTE: we are in the thread that messed with the 569 // StyleContainer, so no need to lock the 570 // container, when this is changed to asynchronous 571 // notifications, then it would need to be read-locked! 572 if (!LockLooper()) 573 return; 574 575 // NOTE: we're only interested in Style objects 576 _RemoveStyle(style); 577 578 UnlockLooper(); 579 } 580 581 // #pragma mark - 582 583 // SetMenu 584 void 585 StyleListView::SetMenu(BMenu* menu) 586 { 587 if (fMenu == menu) 588 return; 589 590 fMenu = menu; 591 if (fMenu == NULL) 592 return; 593 594 fAddMI = new BMenuItem("Add", new BMessage(MSG_ADD)); 595 fMenu->AddItem(fAddMI); 596 597 fMenu->AddSeparatorItem(); 598 599 fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE)); 600 fMenu->AddItem(fDuplicateMI); 601 602 fResetTransformationMI = new BMenuItem("Reset Transformation", 603 new BMessage(MSG_RESET_TRANSFORMATION)); 604 fMenu->AddItem(fResetTransformationMI); 605 606 fMenu->AddSeparatorItem(); 607 608 fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE)); 609 fMenu->AddItem(fRemoveMI); 610 611 fMenu->SetTargetForItems(this); 612 613 _UpdateMenu(); 614 } 615 616 // SetStyleContainer 617 void 618 StyleListView::SetStyleContainer(StyleContainer* container) 619 { 620 if (fStyleContainer == container) 621 return; 622 623 // detach from old container 624 if (fStyleContainer) 625 fStyleContainer->RemoveListener(this); 626 627 _MakeEmpty(); 628 629 fStyleContainer = container; 630 631 if (!fStyleContainer) 632 return; 633 634 fStyleContainer->AddListener(this); 635 636 // sync 637 int32 count = fStyleContainer->CountStyles(); 638 for (int32 i = 0; i < count; i++) 639 _AddStyle(fStyleContainer->StyleAtFast(i), i); 640 } 641 642 // SetShapeContainer 643 void 644 StyleListView::SetShapeContainer(ShapeContainer* container) 645 { 646 if (fShapeContainer == container) 647 return; 648 649 // detach from old container 650 if (fShapeContainer) 651 fShapeContainer->RemoveListener(fShapeListener); 652 653 fShapeContainer = container; 654 655 if (fShapeContainer) 656 fShapeContainer->AddListener(fShapeListener); 657 } 658 659 // SetCommandStack 660 void 661 StyleListView::SetCommandStack(CommandStack* stack) 662 { 663 fCommandStack = stack; 664 } 665 666 // SetCurrentShape 667 void 668 StyleListView::SetCurrentShape(Shape* shape) 669 { 670 if (fCurrentShape == shape) 671 return; 672 673 fCurrentShape = shape; 674 fShapeListener->SetShape(shape); 675 676 _UpdateMarks(); 677 } 678 679 // #pragma mark - 680 681 // _AddStyle 682 bool 683 StyleListView::_AddStyle(Style* style, int32 index) 684 { 685 if (style) { 686 return AddItem(new StyleListItem( 687 style, this, fCurrentShape != NULL), index); 688 } 689 return false; 690 } 691 692 // _RemoveStyle 693 bool 694 StyleListView::_RemoveStyle(Style* style) 695 { 696 StyleListItem* item = _ItemForStyle(style); 697 if (item && RemoveItem(item)) { 698 delete item; 699 return true; 700 } 701 return false; 702 } 703 704 // _ItemForStyle 705 StyleListItem* 706 StyleListView::_ItemForStyle(Style* style) const 707 { 708 for (int32 i = 0; 709 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 710 i++) { 711 if (item->style == style) 712 return item; 713 } 714 return NULL; 715 } 716 717 // #pragma mark - 718 719 // _UpdateMarks 720 void 721 StyleListView::_UpdateMarks() 722 { 723 int32 count = CountItems(); 724 if (fCurrentShape) { 725 // enable display of marks and mark items whoes 726 // style is contained in fCurrentShape 727 for (int32 i = 0; i < count; i++) { 728 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 729 if (!item) 730 continue; 731 item->SetMarkEnabled(true); 732 item->SetMarked(fCurrentShape->Style() == item->style); 733 } 734 } else { 735 // disable display of marks 736 for (int32 i = 0; i < count; i++) { 737 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i)); 738 if (!item) 739 continue; 740 item->SetMarkEnabled(false); 741 } 742 } 743 744 Invalidate(); 745 } 746 747 // _SetStyleMarked 748 void 749 StyleListView::_SetStyleMarked(Style* style, bool marked) 750 { 751 if (StyleListItem* item = _ItemForStyle(style)) { 752 item->SetMarked(marked); 753 } 754 } 755 756 // _UpdateMenu 757 void 758 StyleListView::_UpdateMenu() 759 { 760 if (!fMenu) 761 return; 762 763 bool gotSelection = CurrentSelection(0) >= 0; 764 765 fDuplicateMI->SetEnabled(gotSelection); 766 // TODO: only enable fResetTransformationMI if styles 767 // with gradients are selected! 768 fResetTransformationMI->SetEnabled(gotSelection); 769 fRemoveMI->SetEnabled(gotSelection); 770 } 771 772