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