xref: /haiku/src/apps/icon-o-matic/gui/ShapeListView.cpp (revision 820dca4df6c7bf955c46e8f6521b9408f50b2900)
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 
47 using std::nothrow;
48 
49 class ShapeListItem : public SimpleItem, public Observer {
50 public:
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 
100 public:
101 	Shape* 			shape;
102 
103 private:
104 	ShapeListView*	fListView;
105 };
106 
107 
108 // #pragma mark -
109 
110 
111 enum {
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 
121 ShapeListView::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 
136 ShapeListView::~ShapeListView()
137 {
138 	_MakeEmpty();
139 	delete fMessage;
140 
141 	if (fShapeContainer != NULL)
142 		fShapeContainer->RemoveListener(this);
143 }
144 
145 
146 void
147 ShapeListView::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 
165 void
166 ShapeListView::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 
232 void
233 ShapeListView::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 
269 bool
270 ShapeListView::AcceptDragMessage(const BMessage* message) const
271 {
272 	return SimpleListView::AcceptDragMessage(message);
273 }
274 
275 
276 void
277 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where)
278 {
279 	SimpleListView::SetDropTargetRect(message, where);
280 }
281 
282 
283 bool
284 ShapeListView::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 
425 void
426 ShapeListView::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
453 void
454 ShapeListView::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 
480 void
481 ShapeListView::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 
498 BListItem*
499 ShapeListView::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 
510 int32
511 ShapeListView::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 
537 Selectable*
538 ShapeListView::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 
550 void
551 ShapeListView::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 
567 void
568 ShapeListView::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 
587 void
588 ShapeListView::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 
650 void
651 ShapeListView::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 
676 void
677 ShapeListView::SetStyleContainer(StyleContainer* container)
678 {
679 	fStyleContainer = container;
680 }
681 
682 
683 void
684 ShapeListView::SetPathContainer(PathContainer* container)
685 {
686 	fPathContainer = container;
687 }
688 
689 
690 void
691 ShapeListView::SetCommandStack(CommandStack* stack)
692 {
693 	fCommandStack = stack;
694 }
695 
696 
697 // #pragma mark -
698 
699 
700 bool
701 ShapeListView::_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 
719 bool
720 ShapeListView::_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 
731 ShapeListItem*
732 ShapeListView::_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 
744 void
745 ShapeListView::_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 
759 void
760 ShapeListView::_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