1 /*
2 * Copyright 2006-2012, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2023, Haiku.
4 * All rights reserved. Distributed under the terms of the MIT License.
5 *
6 * Authors:
7 * Zardshard
8 */
9
10
11 #include "StyleListView.h"
12
13 #include <new>
14 #include <stdio.h>
15
16 #include <Application.h>
17 #include <Catalog.h>
18 #include <ListItem.h>
19 #include <Locale.h>
20 #include <Menu.h>
21 #include <MenuItem.h>
22 #include <Message.h>
23 #include <Mime.h>
24 #include <Window.h>
25
26 #include "AddStylesCommand.h"
27 #include "AssignStyleCommand.h"
28 #include "CurrentColor.h"
29 #include "CommandStack.h"
30 #include "GradientTransformable.h"
31 #include "MoveStylesCommand.h"
32 #include "PathSourceShape.h"
33 #include "RemoveStylesCommand.h"
34 #include "Style.h"
35 #include "Observer.h"
36 #include "ResetTransformationCommand.h"
37 #include "Shape.h"
38 #include "Selection.h"
39 #include "Util.h"
40
41
42 #undef B_TRANSLATION_CONTEXT
43 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-StylesList"
44
45
46 using std::nothrow;
47
48 static const float kMarkWidth = 14.0;
49 static const float kBorderOffset = 3.0;
50 static const float kTextOffset = 4.0;
51
52 enum {
53 MSG_ADD = 'adst',
54 MSG_REMOVE = 'rmst',
55 MSG_DUPLICATE = 'dpst',
56 MSG_RESET_TRANSFORMATION = 'rstr',
57 };
58
59 class StyleListItem : public SimpleItem,
60 public Observer {
61 public:
StyleListItem(Style * s,StyleListView * listView,bool markEnabled)62 StyleListItem(Style* s, StyleListView* listView, bool markEnabled)
63 :
64 SimpleItem(""),
65 style(NULL),
66 fListView(listView),
67 fMarkEnabled(markEnabled),
68 fMarked(false)
69 {
70 SetStyle(s);
71 }
72
~StyleListItem()73 virtual ~StyleListItem()
74 {
75 SetStyle(NULL);
76 }
77
78 // SimpleItem interface
DrawItem(BView * owner,BRect itemFrame,bool even)79 virtual void DrawItem(BView* owner, BRect itemFrame, bool even)
80 {
81 SimpleItem::DrawBackground(owner, itemFrame, even);
82
83 float offset = kBorderOffset + kMarkWidth + kTextOffset;
84 SimpleItem::DrawItem(owner, itemFrame.OffsetByCopy(offset, 0), even);
85
86 if (!fMarkEnabled)
87 return;
88
89 // mark
90 BRect markRect = itemFrame;
91 float markRectBorderTint = B_DARKEN_1_TINT;
92 float markRectFillTint = 1.04;
93 float markTint = B_DARKEN_4_TINT;
94 // Dark Themes
95 rgb_color lowColor = owner->LowColor();
96 if (lowColor.red + lowColor.green + lowColor.blue < 128 * 3) {
97 markRectBorderTint = B_LIGHTEN_2_TINT;
98 markRectFillTint = 0.85;
99 markTint = 0.1;
100 }
101 markRect.left += kBorderOffset;
102 markRect.right = markRect.left + kMarkWidth;
103 markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0;
104 markRect.bottom = markRect.top + kMarkWidth;
105 owner->SetHighColor(tint_color(owner->LowColor(), markRectBorderTint));
106 owner->StrokeRect(markRect);
107 markRect.InsetBy(1, 1);
108 owner->SetHighColor(tint_color(owner->LowColor(), markRectFillTint));
109 owner->FillRect(markRect);
110 if (fMarked) {
111 markRect.InsetBy(2, 2);
112 owner->SetHighColor(tint_color(owner->LowColor(),
113 markTint));
114 owner->SetPenSize(2);
115 owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom());
116 owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop());
117 owner->SetPenSize(1);
118 }
119 }
120
121 // Observer interface
ObjectChanged(const Observable * object)122 virtual void ObjectChanged(const Observable* object)
123 {
124 UpdateText();
125 }
126
127 // StyleListItem
SetStyle(Style * s)128 void SetStyle(Style* s)
129 {
130 if (s == style)
131 return;
132
133 if (style) {
134 style->RemoveObserver(this);
135 style->ReleaseReference();
136 }
137
138 style = s;
139
140 if (style) {
141 style->AcquireReference();
142 style->AddObserver(this);
143 UpdateText();
144 }
145 }
146
UpdateText()147 void UpdateText()
148 {
149 SetText(style->Name());
150 Invalidate();
151 }
152
SetMarkEnabled(bool enabled)153 void SetMarkEnabled(bool enabled)
154 {
155 if (fMarkEnabled == enabled)
156 return;
157 fMarkEnabled = enabled;
158 Invalidate();
159 }
160
SetMarked(bool marked)161 void SetMarked(bool marked)
162 {
163 if (fMarked == marked)
164 return;
165 fMarked = marked;
166 Invalidate();
167 }
168
Invalidate()169 void Invalidate()
170 {
171 if (fListView->LockLooper()) {
172 fListView->InvalidateItem(fListView->IndexOf(this));
173 fListView->UnlockLooper();
174 }
175 }
176
177 public:
178 Style* style;
179
180 private:
181 StyleListView* fListView;
182 bool fMarkEnabled;
183 bool fMarked;
184 };
185
186
187 class ShapeStyleListener : public ShapeListener,
188 public ContainerListener<Shape> {
189 public:
ShapeStyleListener(StyleListView * listView)190 ShapeStyleListener(StyleListView* listView)
191 :
192 fListView(listView),
193 fShape(NULL)
194 {
195 }
196
~ShapeStyleListener()197 virtual ~ShapeStyleListener()
198 {
199 SetShape(NULL);
200 }
201
202 // ShapeListener interface
TransformerAdded(Transformer * t,int32 index)203 virtual void TransformerAdded(Transformer* t, int32 index)
204 {
205 }
206
TransformerRemoved(Transformer * t)207 virtual void TransformerRemoved(Transformer* t)
208 {
209 }
210
StyleChanged(Style * oldStyle,Style * newStyle)211 virtual void StyleChanged(Style* oldStyle, Style* newStyle)
212 {
213 fListView->_SetStyleMarked(oldStyle, false);
214 fListView->_SetStyleMarked(newStyle, true);
215 }
216
217 // ContainerListener<Shape> interface
ItemAdded(Shape * shape,int32 index)218 virtual void ItemAdded(Shape* shape, int32 index)
219 {
220 }
221
ItemRemoved(Shape * shape)222 virtual void ItemRemoved(Shape* shape)
223 {
224 fListView->SetCurrentShape(NULL);
225 }
226
227 // ShapeStyleListener
SetShape(PathSourceShape * shape)228 void SetShape(PathSourceShape* shape)
229 {
230 if (fShape == shape)
231 return;
232
233 if (fShape)
234 fShape->RemoveListener(this);
235
236 fShape = shape;
237
238 if (fShape)
239 fShape->AddListener(this);
240 }
241
CurrentShape() const242 PathSourceShape* CurrentShape() const
243 {
244 return fShape;
245 }
246
247 private:
248 StyleListView* fListView;
249 PathSourceShape* fShape;
250 };
251
252
253 // #pragma mark -
254
255
StyleListView(BRect frame,const char * name,BMessage * message,BHandler * target)256 StyleListView::StyleListView(BRect frame, const char* name, BMessage* message,
257 BHandler* target)
258 :
259 SimpleListView(frame, name, NULL, B_SINGLE_SELECTION_LIST),
260 fMessage(message),
261 fStyleContainer(NULL),
262 fShapeContainer(NULL),
263 fCommandStack(NULL),
264 fCurrentColor(NULL),
265
266 fCurrentShape(NULL),
267 fShapeListener(new ShapeStyleListener(this)),
268
269 fMenu(NULL)
270 {
271 SetTarget(target);
272 }
273
274
~StyleListView()275 StyleListView::~StyleListView()
276 {
277 _MakeEmpty();
278 delete fMessage;
279
280 if (fStyleContainer != NULL)
281 fStyleContainer->RemoveListener(this);
282
283 if (fShapeContainer != NULL)
284 fShapeContainer->RemoveListener(fShapeListener);
285
286 delete fShapeListener;
287 }
288
289
290 // #pragma mark -
291
292
293 void
MessageReceived(BMessage * message)294 StyleListView::MessageReceived(BMessage* message)
295 {
296 switch (message->what) {
297 case MSG_ADD:
298 {
299 Style* style;
300 AddStylesCommand* command;
301 rgb_color color;
302 if (fCurrentColor != NULL)
303 color = fCurrentColor->Color();
304 else {
305 color.red = 0;
306 color.green = 0;
307 color.blue = 0;
308 color.alpha = 255;
309 }
310 new_style(color, fStyleContainer, &style, &command);
311 fCommandStack->Perform(command);
312 break;
313 }
314
315 case MSG_REMOVE:
316 RemoveSelected();
317 break;
318
319 case MSG_DUPLICATE:
320 {
321 int32 count = CountSelectedItems();
322 int32 index = 0;
323 BList items;
324 for (int32 i = 0; i < count; i++) {
325 index = CurrentSelection(i);
326 BListItem* item = ItemAt(index);
327 if (item)
328 items.AddItem((void*)item);
329 }
330 CopyItems(items, index + 1);
331 break;
332 }
333
334 case MSG_RESET_TRANSFORMATION:
335 {
336 int32 count = CountSelectedItems();
337 BList gradients;
338 for (int32 i = 0; i < count; i++) {
339 StyleListItem* item = dynamic_cast<StyleListItem*>(
340 ItemAt(CurrentSelection(i)));
341 if (item && item->style && item->style->Gradient()) {
342 if (!gradients.AddItem((void*)item->style->Gradient()))
343 break;
344 }
345 }
346 count = gradients.CountItems();
347 if (count <= 0)
348 break;
349
350 Transformable* transformables[count];
351 for (int32 i = 0; i < count; i++) {
352 Gradient* gradient = (Gradient*)gradients.ItemAtFast(i);
353 transformables[i] = gradient;
354 }
355
356 ResetTransformationCommand* command
357 = new ResetTransformationCommand(transformables, count);
358
359 fCommandStack->Perform(command);
360 break;
361 }
362
363 default:
364 SimpleListView::MessageReceived(message);
365 break;
366 }
367 }
368
369
370 void
SelectionChanged()371 StyleListView::SelectionChanged()
372 {
373 SimpleListView::SelectionChanged();
374
375 if (!fSyncingToSelection) {
376 // NOTE: single selection list
377 StyleListItem* item
378 = dynamic_cast<StyleListItem*>(ItemAt(CurrentSelection(0)));
379 if (fMessage) {
380 BMessage message(*fMessage);
381 message.AddPointer("style", item ? (void*)item->style : NULL);
382 Invoke(&message);
383 }
384 }
385
386 _UpdateMenu();
387 }
388
389
390 void
MouseDown(BPoint where)391 StyleListView::MouseDown(BPoint where)
392 {
393 if (fCurrentShape == NULL) {
394 SimpleListView::MouseDown(where);
395 return;
396 }
397
398 bool handled = false;
399 int32 index = IndexOf(where);
400 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index));
401 if (item != NULL) {
402 BRect itemFrame(ItemFrame(index));
403 itemFrame.right = itemFrame.left + kBorderOffset + kMarkWidth
404 + kTextOffset / 2.0;
405 Style* style = item->style;
406 if (itemFrame.Contains(where)) {
407 // set the style on the shape
408 if (fCommandStack) {
409 ::Command* command = new AssignStyleCommand(
410 fCurrentShape, style);
411 fCommandStack->Perform(command);
412 } else {
413 fCurrentShape->SetStyle(style);
414 }
415 handled = true;
416 }
417 }
418
419 if (!handled)
420 SimpleListView::MouseDown(where);
421 }
422
423
424 status_t
ArchiveSelection(BMessage * into,bool deep) const425 StyleListView::ArchiveSelection(BMessage* into, bool deep) const
426 {
427 into->what = StyleListView::kSelectionArchiveCode;
428
429 int32 count = CountSelectedItems();
430 for (int32 i = 0; i < count; i++) {
431 StyleListItem* item = dynamic_cast<StyleListItem*>(
432 ItemAt(CurrentSelection(i)));
433 if (item != NULL) {
434 BMessage archive;
435 if (item->style->Archive(&archive, true) == B_OK)
436 into->AddMessage("style", &archive);
437 } else
438 return B_ERROR;
439 }
440 return B_OK;
441 }
442
443
444 bool
InstantiateSelection(const BMessage * archive,int32 dropIndex)445 StyleListView::InstantiateSelection(const BMessage* archive, int32 dropIndex)
446 {
447 if (archive->what != StyleListView::kSelectionArchiveCode
448 || fCommandStack == NULL || fStyleContainer == NULL)
449 return false;
450
451 // Drag may have come from another instance, like in another window.
452 // Reconstruct the Styles from the archive and add them at the drop
453 // index.
454 int index = 0;
455 BList styles;
456 while (true) {
457 BMessage styleArchive;
458 if (archive->FindMessage("style", index, &styleArchive) != B_OK)
459 break;
460 Style* style = new(std::nothrow) Style(&styleArchive);
461 if (style == NULL)
462 break;
463
464 if (!styles.AddItem(style)) {
465 delete style;
466 break;
467 }
468
469 index++;
470 }
471
472 int32 count = styles.CountItems();
473 if (count == 0)
474 return false;
475
476 AddCommand<Style>* command = new(std::nothrow) AddCommand<Style>(
477 fStyleContainer, (Style**)styles.Items(), count, true, dropIndex);
478
479 if (command == NULL) {
480 for (int32 i = 0; i < count; i++)
481 delete (Style*)styles.ItemAtFast(i);
482 return false;
483 }
484
485 fCommandStack->Perform(command);
486
487 return true;
488 }
489
490
491 void
MoveItems(BList & items,int32 toIndex)492 StyleListView::MoveItems(BList& items, int32 toIndex)
493 {
494 if (fCommandStack == NULL || fStyleContainer == NULL)
495 return;
496
497 int32 count = items.CountItems();
498 Style** styles = new (nothrow) Style*[count];
499 if (styles == NULL)
500 return;
501
502 for (int32 i = 0; i < count; i++) {
503 StyleListItem* item
504 = dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i));
505 styles[i] = item ? item->style : NULL;
506 }
507
508 MoveStylesCommand* command = new (nothrow) MoveStylesCommand(
509 fStyleContainer, styles, count, toIndex);
510 if (command == NULL) {
511 delete[] styles;
512 return;
513 }
514
515 fCommandStack->Perform(command);
516 }
517
518
519 void
CopyItems(BList & items,int32 toIndex)520 StyleListView::CopyItems(BList& items, int32 toIndex)
521 {
522 if (fCommandStack == NULL || fStyleContainer == NULL)
523 return;
524
525 int32 count = items.CountItems();
526 Style* styles[count];
527
528 for (int32 i = 0; i < count; i++) {
529 StyleListItem* item
530 = dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i));
531 styles[i] = item ? new (nothrow) Style(*item->style) : NULL;
532 }
533
534 AddCommand<Style>* command
535 = new (nothrow) AddCommand<Style>(fStyleContainer, styles, count, true, toIndex);
536 if (!command) {
537 for (int32 i = 0; i < count; i++)
538 delete styles[i];
539 return;
540 }
541
542 fCommandStack->Perform(command);
543 }
544
545
546 void
RemoveItemList(BList & items)547 StyleListView::RemoveItemList(BList& items)
548 {
549 if (!fCommandStack || !fStyleContainer)
550 return;
551
552 int32 count = items.CountItems();
553 int32 indices[count];
554 for (int32 i = 0; i < count; i++)
555 indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
556
557 RemoveStylesCommand* command
558 = new (nothrow) RemoveStylesCommand(fStyleContainer, indices, count);
559 fCommandStack->Perform(command);
560 }
561
562
563 BListItem*
CloneItem(int32 index) const564 StyleListView::CloneItem(int32 index) const
565 {
566 if (StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index))) {
567 return new StyleListItem(item->style,
568 const_cast<StyleListView*>(this),
569 fCurrentShape != NULL);
570 }
571 return NULL;
572 }
573
574
575 int32
IndexOfSelectable(Selectable * selectable) const576 StyleListView::IndexOfSelectable(Selectable* selectable) const
577 {
578 Style* style = dynamic_cast<Style*>(selectable);
579 if (style == NULL)
580 return -1;
581
582 int count = CountItems();
583 for (int32 i = 0; i < count; i++) {
584 if (SelectableFor(ItemAt(i)) == style)
585 return i;
586 }
587
588 return -1;
589 }
590
591
592 Selectable*
SelectableFor(BListItem * item) const593 StyleListView::SelectableFor(BListItem* item) const
594 {
595 StyleListItem* styleItem = dynamic_cast<StyleListItem*>(item);
596 if (styleItem != NULL)
597 return styleItem->style;
598 return NULL;
599 }
600
601
602 // #pragma mark -
603
604
605 void
ItemAdded(Style * style,int32 index)606 StyleListView::ItemAdded(Style* style, int32 index)
607 {
608 // NOTE: we are in the thread that messed with the
609 // StyleContainer, so no need to lock the
610 // container, when this is changed to asynchronous
611 // notifications, then it would need to be read-locked!
612 if (!LockLooper())
613 return;
614
615 if (_AddStyle(style, index))
616 Select(index);
617
618 UnlockLooper();
619 }
620
621
622 void
ItemRemoved(Style * style)623 StyleListView::ItemRemoved(Style* style)
624 {
625 // NOTE: we are in the thread that messed with the
626 // StyleContainer, so no need to lock the
627 // container, when this is changed to asynchronous
628 // notifications, then it would need to be read-locked!
629 if (!LockLooper())
630 return;
631
632 // NOTE: we're only interested in Style objects
633 _RemoveStyle(style);
634
635 UnlockLooper();
636 }
637
638
639 // #pragma mark -
640
641
642 void
SetMenu(BMenu * menu)643 StyleListView::SetMenu(BMenu* menu)
644 {
645 if (fMenu == menu)
646 return;
647
648 fMenu = menu;
649 if (fMenu == NULL)
650 return;
651
652 fAddMI = new BMenuItem(B_TRANSLATE("Add"), new BMessage(MSG_ADD));
653 fMenu->AddItem(fAddMI);
654
655 fMenu->AddSeparatorItem();
656
657 fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"),
658 new BMessage(MSG_DUPLICATE));
659 fMenu->AddItem(fDuplicateMI);
660
661 fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"),
662 new BMessage(MSG_RESET_TRANSFORMATION));
663 fMenu->AddItem(fResetTransformationMI);
664
665 fMenu->AddSeparatorItem();
666
667 fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE));
668 fMenu->AddItem(fRemoveMI);
669
670 fMenu->SetTargetForItems(this);
671
672 _UpdateMenu();
673 }
674
675
676 void
SetStyleContainer(Container<Style> * container)677 StyleListView::SetStyleContainer(Container<Style>* container)
678 {
679 if (fStyleContainer == container)
680 return;
681
682 // detach from old container
683 if (fStyleContainer != NULL)
684 fStyleContainer->RemoveListener(this);
685
686 _MakeEmpty();
687
688 fStyleContainer = container;
689
690 if (fStyleContainer == NULL)
691 return;
692
693 fStyleContainer->AddListener(this);
694
695 // sync
696 int32 count = fStyleContainer->CountItems();
697 for (int32 i = 0; i < count; i++)
698 _AddStyle(fStyleContainer->ItemAtFast(i), i);
699 }
700
701
702 void
SetShapeContainer(Container<Shape> * container)703 StyleListView::SetShapeContainer(Container<Shape>* container)
704 {
705 if (fShapeContainer == container)
706 return;
707
708 // detach from old container
709 if (fShapeContainer)
710 fShapeContainer->RemoveListener(fShapeListener);
711
712 fShapeContainer = container;
713
714 if (fShapeContainer)
715 fShapeContainer->AddListener(fShapeListener);
716 }
717
718
719 void
SetCommandStack(CommandStack * stack)720 StyleListView::SetCommandStack(CommandStack* stack)
721 {
722 fCommandStack = stack;
723 }
724
725
726 void
SetCurrentColor(CurrentColor * color)727 StyleListView::SetCurrentColor(CurrentColor* color)
728 {
729 fCurrentColor = color;
730 }
731
732
733 void
SetCurrentShape(Shape * shape)734 StyleListView::SetCurrentShape(Shape* shape)
735 {
736 PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(shape);
737
738 if (fCurrentShape == pathSourceShape)
739 return;
740
741 fCurrentShape = pathSourceShape;
742 fShapeListener->SetShape(pathSourceShape);
743
744 _UpdateMarks();
745 }
746
747
748 // #pragma mark -
749
750
751 bool
_AddStyle(Style * style,int32 index)752 StyleListView::_AddStyle(Style* style, int32 index)
753 {
754 if (style != NULL) {
755 return AddItem(new StyleListItem(
756 style, this, fCurrentShape != NULL), index);
757 }
758 return false;
759 }
760
761
762 bool
_RemoveStyle(Style * style)763 StyleListView::_RemoveStyle(Style* style)
764 {
765 StyleListItem* item = _ItemForStyle(style);
766 if (item != NULL && RemoveItem(item)) {
767 delete item;
768 return true;
769 }
770 return false;
771 }
772
773
774 StyleListItem*
_ItemForStyle(Style * style) const775 StyleListView::_ItemForStyle(Style* style) const
776 {
777 int count = CountItems();
778 for (int32 i = 0; i < count; i++) {
779 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
780 if (item == NULL)
781 continue;
782 if (item->style == style)
783 return item;
784 }
785 return NULL;
786 }
787
788
789 // #pragma mark -
790
791
792 void
_UpdateMarks()793 StyleListView::_UpdateMarks()
794 {
795 int32 count = CountItems();
796 if (fCurrentShape) {
797 // enable display of marks and mark items whoes
798 // style is contained in fCurrentShape
799 for (int32 i = 0; i < count; i++) {
800 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
801 if (item == NULL)
802 continue;
803 item->SetMarkEnabled(true);
804 item->SetMarked(fCurrentShape->Style() == item->style);
805 }
806 } else {
807 // disable display of marks
808 for (int32 i = 0; i < count; i++) {
809 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
810 if (item == NULL)
811 continue;
812 item->SetMarkEnabled(false);
813 }
814 }
815
816 Invalidate();
817 }
818
819
820 void
_SetStyleMarked(Style * style,bool marked)821 StyleListView::_SetStyleMarked(Style* style, bool marked)
822 {
823 StyleListItem* item = _ItemForStyle(style);
824 if (item != NULL)
825 item->SetMarked(marked);
826 }
827
828
829 void
_UpdateMenu()830 StyleListView::_UpdateMenu()
831 {
832 if (fMenu == NULL)
833 return;
834
835 bool gotSelection = CurrentSelection(0) >= 0;
836
837 fDuplicateMI->SetEnabled(gotSelection);
838 // TODO: only enable fResetTransformationMI if styles
839 // with gradients are selected!
840 fResetTransformationMI->SetEnabled(gotSelection);
841 fRemoveMI->SetEnabled(gotSelection);
842 }
843
844