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