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