xref: /haiku/src/apps/icon-o-matic/gui/StyleListView.cpp (revision 1322e37a03386ffe6321ca3fbb03bd3c4e443074)
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:
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 
73 	virtual ~StyleListItem()
74 	{
75 		SetStyle(NULL);
76 	}
77 
78 	// SimpleItem interface
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
122 	virtual	void	ObjectChanged(const Observable* object)
123 	{
124 		UpdateText();
125 	}
126 
127 	// StyleListItem
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 
147 	void UpdateText()
148 	{
149 		SetText(style->Name());
150 		Invalidate();
151 	}
152 
153 	void SetMarkEnabled(bool enabled)
154 	{
155 		if (fMarkEnabled == enabled)
156 			return;
157 		fMarkEnabled = enabled;
158 		Invalidate();
159 	}
160 
161 	void SetMarked(bool marked)
162 	{
163 		if (fMarked == marked)
164 			return;
165 		fMarked = marked;
166 		Invalidate();
167 	}
168 
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:
190 	ShapeStyleListener(StyleListView* listView)
191 		:
192 		fListView(listView),
193 		fShape(NULL)
194 	{
195 	}
196 
197 	virtual ~ShapeStyleListener()
198 	{
199 		SetShape(NULL);
200 	}
201 
202 	// ShapeListener interface
203 	virtual	void TransformerAdded(Transformer* t, int32 index)
204 	{
205 	}
206 
207 	virtual	void TransformerRemoved(Transformer* t)
208 	{
209 	}
210 
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
218 	virtual void ItemAdded(Shape* shape, int32 index)
219 	{
220 	}
221 
222 	virtual void ItemRemoved(Shape* shape)
223 	{
224 		fListView->SetCurrentShape(NULL);
225 	}
226 
227 	// ShapeStyleListener
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 
242 	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 
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 
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
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
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
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
425 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
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
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
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
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*
564 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
576 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*
593 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
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
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
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
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
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
720 StyleListView::SetCommandStack(CommandStack* stack)
721 {
722 	fCommandStack = stack;
723 }
724 
725 
726 void
727 StyleListView::SetCurrentColor(CurrentColor* color)
728 {
729 	fCurrentColor = color;
730 }
731 
732 
733 void
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
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
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*
775 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
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
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
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