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
46using std::nothrow;
47
48static const float kMarkWidth		= 14.0;
49static const float kBorderOffset	= 3.0;
50static const float kTextOffset		= 4.0;
51
52enum {
53	MSG_ADD							= 'adst',
54	MSG_REMOVE						= 'rmst',
55	MSG_DUPLICATE					= 'dpst',
56	MSG_RESET_TRANSFORMATION		= 'rstr',
57};
58
59class StyleListItem : public SimpleItem,
60					 public Observer {
61public:
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
177public:
178	Style*			style;
179
180private:
181	StyleListView*	fListView;
182	bool			fMarkEnabled;
183	bool			fMarked;
184};
185
186
187class ShapeStyleListener : public ShapeListener,
188	public ContainerListener<Shape> {
189public:
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
247private:
248	StyleListView*		fListView;
249	PathSourceShape*	fShape;
250};
251
252
253// #pragma mark -
254
255
256StyleListView::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
275StyleListView::~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
293void
294StyleListView::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
370void
371StyleListView::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
390void
391StyleListView::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
424status_t
425StyleListView::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
444bool
445StyleListView::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
491void
492StyleListView::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
519void
520StyleListView::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
546void
547StyleListView::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
563BListItem*
564StyleListView::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
575int32
576StyleListView::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
592Selectable*
593StyleListView::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
605void
606StyleListView::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
622void
623StyleListView::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
642void
643StyleListView::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
676void
677StyleListView::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
702void
703StyleListView::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
719void
720StyleListView::SetCommandStack(CommandStack* stack)
721{
722	fCommandStack = stack;
723}
724
725
726void
727StyleListView::SetCurrentColor(CurrentColor* color)
728{
729	fCurrentColor = color;
730}
731
732
733void
734StyleListView::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
751bool
752StyleListView::_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
762bool
763StyleListView::_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
774StyleListItem*
775StyleListView::_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
792void
793StyleListView::_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
820void
821StyleListView::_SetStyleMarked(Style* style, bool marked)
822{
823	StyleListItem* item = _ItemForStyle(style);
824	if (item != NULL)
825		item->SetMarked(marked);
826}
827
828
829void
830StyleListView::_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