xref: /haiku/src/apps/icon-o-matic/gui/ShapeListView.cpp (revision 0d07b1d98a7c4595b39324f0603d3b3005ad38d9)
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 
49 using std::nothrow;
50 
51 class ShapeListItem : public SimpleItem, public Observer {
52 public:
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 
102 public:
103 	Shape* 			shape;
104 
105 private:
106 	ShapeListView*	fListView;
107 };
108 
109 
110 // #pragma mark -
111 
112 
113 enum {
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 
123 ShapeListView::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 
138 ShapeListView::~ShapeListView()
139 {
140 	_MakeEmpty();
141 	delete fMessage;
142 
143 	if (fShapeContainer != NULL)
144 		fShapeContainer->RemoveListener(this);
145 }
146 
147 
148 void
149 ShapeListView::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 
167 void
168 ShapeListView::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 
245 void
246 ShapeListView::MakeDragMessage(BMessage* message) const
247 {
248 	SimpleListView::MakeDragMessage(message);
249 	message->AddPointer("container", fShapeContainer);
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 && item->shape != NULL) {
255 			PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(item->shape);
256 			if (pathSourceShape != NULL) {
257 				message->AddInt32("type", PathSourceShape::archive_code);
258 
259 				PathSourceShape* shape = pathSourceShape;
260 				message->AddPointer("shape", (void*)shape);
261 
262 				// Add archives of everything this Shape uses
263 				BMessage archive;
264 
265 				BMessage styleArchive;
266 				shape->Style()->Archive(&styleArchive, true);
267 				archive.AddMessage("style", &styleArchive);
268 
269 				Container<VectorPath>* paths = shape->Paths();
270 				for (int32 j = 0; j < paths->CountItems(); j++) {
271 					BMessage pathArchive;
272 					paths->ItemAt(j)->Archive(&pathArchive, true);
273 					archive.AddMessage("path", &pathArchive);
274 				}
275 
276 				BMessage shapeArchive;
277 				shape->Archive(&shapeArchive, true);
278 				archive.AddMessage("shape", &shapeArchive);
279 
280 				message->AddMessage("shape archive", &archive);
281 				continue;
282 			}
283 
284 			ReferenceImage* referenceImage = dynamic_cast<ReferenceImage*>(item->shape);
285 			if (referenceImage != NULL) {
286 				message->AddInt32("type", ReferenceImage::archive_code);
287 
288 				message->AddPointer("shape", (void*)referenceImage);
289 
290 				// Add archives of everything this Shape uses
291 				BMessage archive;
292 
293 				BMessage shapeArchive;
294 				referenceImage->Archive(&shapeArchive, true);
295 				archive.AddMessage("shape", &shapeArchive);
296 
297 				message->AddMessage("shape archive", &archive);
298 				continue;
299 			}
300 		} else
301 			break;
302 	}
303 }
304 
305 
306 bool
307 ShapeListView::AcceptDragMessage(const BMessage* message) const
308 {
309 	return SimpleListView::AcceptDragMessage(message);
310 }
311 
312 
313 void
314 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where)
315 {
316 	SimpleListView::SetDropTargetRect(message, where);
317 }
318 
319 
320 bool
321 ShapeListView::HandleDropMessage(const BMessage* message, int32 dropIndex)
322 {
323 	// Let SimpleListView handle drag-sorting (when drag came from ourself)
324 	if (SimpleListView::HandleDropMessage(message, dropIndex))
325 		return true;
326 
327 	if (fCommandStack == NULL || fShapeContainer == NULL
328 		|| fStyleContainer == NULL || fPathContainer == NULL) {
329 		return false;
330 	}
331 
332 	// Drag may have come from another instance, like in another window.
333 	// Reconstruct the Shapes from the archive and add them at the drop
334 	// index.
335 	int index = 0;
336 	BList styles;
337 	BList paths;
338 	BList shapes;
339 	while (true) {
340 		int32 type;
341 		if (message->FindInt32("type", index, &type) != B_OK)
342 			break;
343 
344 		if (type == PathSourceShape::archive_code) {
345 			BMessage archive;
346 			if (message->FindMessage("shape archive", index, &archive) != B_OK)
347 				break;
348 
349 			// Extract the shape archive
350 			BMessage shapeArchive;
351 			if (archive.FindMessage("shape", &shapeArchive) != B_OK)
352 				break;
353 
354 			// Extract the style
355 			BMessage styleArchive;
356 			if (archive.FindMessage("style", &styleArchive) != B_OK)
357 				break;
358 
359 			Style* style = new Style(&styleArchive);
360 			if (style == NULL)
361 				break;
362 
363 			Style* styleToAssign = style;
364 			// Try to find an existing style that is the same as the extracted
365 			// style and use that one instead.
366 			for (int32 i = 0; i < fStyleContainer->CountItems(); i++) {
367 				Style* other = fStyleContainer->ItemAtFast(i);
368 				if (*other == *style) {
369 					styleToAssign = other;
370 					delete style;
371 					style = NULL;
372 					break;
373 				}
374 			}
375 
376 			if (style != NULL && !styles.AddItem(style)) {
377 				delete style;
378 				break;
379 			}
380 
381 			// Create the shape using the given style
382 			PathSourceShape* shape = new(std::nothrow) PathSourceShape(styleToAssign);
383 			if (shape == NULL)
384 				break;
385 
386 			if (shape->Unarchive(&shapeArchive) != B_OK
387 				|| !shapes.AddItem(shape)) {
388 				delete shape;
389 				if (style != NULL) {
390 					styles.RemoveItem(style);
391 					delete style;
392 				}
393 				break;
394 			}
395 
396 			// Extract the paths
397 			int pathIndex = 0;
398 			while (true) {
399 				BMessage pathArchive;
400 				if (archive.FindMessage("path", pathIndex, &pathArchive) != B_OK)
401 					break;
402 
403 				VectorPath* path = new(nothrow) VectorPath(&pathArchive);
404 				if (path == NULL)
405 					break;
406 
407 				VectorPath* pathToInclude = path;
408 				for (int32 i = 0; i < fPathContainer->CountItems(); i++) {
409 					VectorPath* other = fPathContainer->ItemAtFast(i);
410 					if (*other == *path) {
411 						pathToInclude = other;
412 						delete path;
413 						path = NULL;
414 						break;
415 					}
416 				}
417 
418 				if (path != NULL && !paths.AddItem(path)) {
419 					delete path;
420 					break;
421 				}
422 
423 				shape->Paths()->AddItem(pathToInclude);
424 
425 				pathIndex++;
426 			}
427 		} else if (type == ReferenceImage::archive_code) {
428 			BMessage archive;
429 			if (message->FindMessage("shape archive", index, &archive) != B_OK)
430 				break;
431 
432 			BMessage shapeArchive;
433 			if (archive.FindMessage("shape", &shapeArchive) != B_OK)
434 				break;
435 
436 			ReferenceImage* shape = new (std::nothrow) ReferenceImage(&shapeArchive);
437 			if (shape == NULL)
438 				break;
439 
440 			if (shapes.AddItem(shape) != B_OK)
441 				break;
442 		}
443 
444 		index++;
445 	}
446 
447 	int32 shapeCount = shapes.CountItems();
448 	if (shapeCount == 0)
449 		return false;
450 
451 	// TODO: Add allocation checks beyond this point.
452 
453 	AddStylesCommand* stylesCommand = new(std::nothrow) AddStylesCommand(
454 		fStyleContainer, (Style**)styles.Items(), styles.CountItems(),
455 		fStyleContainer->CountItems());
456 
457 	AddPathsCommand* pathsCommand = new(std::nothrow) AddPathsCommand(
458 		fPathContainer, (VectorPath**)paths.Items(), paths.CountItems(),
459 		true, fPathContainer->CountItems());
460 
461 	AddShapesCommand* shapesCommand = new(std::nothrow) AddShapesCommand(
462 		fShapeContainer, (Shape**)shapes.Items(), shapeCount, dropIndex);
463 
464 	::Command** commands = new(std::nothrow) ::Command*[3];
465 
466 	commands[0] = stylesCommand;
467 	commands[1] = pathsCommand;
468 	commands[2] = shapesCommand;
469 
470 	CompoundCommand* command = new CompoundCommand(commands, 3,
471 		B_TRANSLATE("Drop shapes"), -1);
472 
473 	fCommandStack->Perform(command);
474 
475 	return true;
476 }
477 
478 
479 // #pragma mark -
480 
481 
482 void
483 ShapeListView::MoveItems(BList& items, int32 toIndex)
484 {
485 	if (fCommandStack == NULL || fShapeContainer == NULL)
486 		return;
487 
488 	int32 count = items.CountItems();
489 	Shape** shapes = new(nothrow) Shape*[count];
490 	if (shapes == NULL)
491 		return;
492 
493 	for (int32 i = 0; i < count; i++) {
494 		ShapeListItem* item
495 			= dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i));
496 		shapes[i] = item ? item->shape : NULL;
497 	}
498 
499 	MoveShapesCommand* command = new (nothrow) MoveShapesCommand(
500 		fShapeContainer, shapes, count, toIndex);
501 	if (command == NULL) {
502 		delete[] shapes;
503 		return;
504 	}
505 
506 	fCommandStack->Perform(command);
507 }
508 
509 // CopyItems
510 void
511 ShapeListView::CopyItems(BList& items, int32 toIndex)
512 {
513 	if (fCommandStack == NULL || fShapeContainer == NULL)
514 		return;
515 
516 	int32 count = items.CountItems();
517 	Shape* shapes[count];
518 
519 	for (int32 i = 0; i < count; i++) {
520 		ShapeListItem* item
521 			= dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i));
522 		shapes[i] = item ? item->shape->Clone() : NULL;
523 	}
524 
525 	AddShapesCommand* command = new(nothrow) AddShapesCommand(fShapeContainer,
526 		shapes, count, toIndex);
527 	if (command == NULL) {
528 		for (int32 i = 0; i < count; i++)
529 			delete shapes[i];
530 		return;
531 	}
532 
533 	fCommandStack->Perform(command);
534 }
535 
536 
537 void
538 ShapeListView::RemoveItemList(BList& items)
539 {
540 	if (fCommandStack == NULL || fShapeContainer == NULL)
541 		return;
542 
543 	int32 count = items.CountItems();
544 	int32 indices[count];
545 	for (int32 i = 0; i < count; i++)
546 	 	indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
547 
548 	RemoveShapesCommand* command = new(nothrow) RemoveShapesCommand(
549 		fShapeContainer, indices, count);
550 
551 	fCommandStack->Perform(command);
552 }
553 
554 
555 BListItem*
556 ShapeListView::CloneItem(int32 index) const
557 {
558 	ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index));
559 	if (item != NULL) {
560 		return new ShapeListItem(item->shape,
561 			const_cast<ShapeListView*>(this));
562 	}
563 	return NULL;
564 }
565 
566 
567 int32
568 ShapeListView::IndexOfSelectable(Selectable* selectable) const
569 {
570 	Shape* shape = dynamic_cast<Shape*>(selectable);
571 	if (shape == NULL) {
572 		Transformer* transformer = dynamic_cast<Transformer*>(selectable);
573 		if (transformer == NULL)
574 			return -1;
575 		int32 count = CountItems();
576 		for (int32 i = 0; i < count; i++) {
577 			ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
578 			if (item != NULL && item->shape->Transformers()->HasItem(transformer))
579 				return i;
580 		}
581 	} else {
582 		int32 count = CountItems();
583 		for (int32 i = 0; i < count; i++) {
584 			ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
585 			if (item != NULL && item->shape == shape)
586 				return i;
587 		}
588 	}
589 
590 	return -1;
591 }
592 
593 
594 Selectable*
595 ShapeListView::SelectableFor(BListItem* item) const
596 {
597 	ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item);
598 	if (shapeItem != NULL)
599 		return shapeItem->shape;
600 	return NULL;
601 }
602 
603 
604 // #pragma mark -
605 
606 
607 void
608 ShapeListView::ItemAdded(Shape* shape, int32 index)
609 {
610 	// NOTE: we are in the thread that messed with the
611 	// ShapeContainer, so no need to lock the
612 	// container, when this is changed to asynchronous
613 	// notifications, then it would need to be read-locked!
614 	if (!LockLooper())
615 		return;
616 
617 	if (_AddShape(shape, index))
618 		Select(index);
619 
620 	UnlockLooper();
621 }
622 
623 
624 void
625 ShapeListView::ItemRemoved(Shape* shape)
626 {
627 	// NOTE: we are in the thread that messed with the
628 	// ShapeContainer, so no need to lock the
629 	// container, when this is changed to asynchronous
630 	// notifications, then it would need to be read-locked!
631 	if (!LockLooper())
632 		return;
633 
634 	// NOTE: we're only interested in Shape objects
635 	_RemoveShape(shape);
636 
637 	UnlockLooper();
638 }
639 
640 
641 // #pragma mark -
642 
643 
644 void
645 ShapeListView::SetMenu(BMenu* menu)
646 {
647 	if (fMenu == menu)
648 		return;
649 
650 	fMenu = menu;
651 
652 	if (fMenu == NULL)
653 		return;
654 
655 	BMessage* message = new BMessage(MSG_ADD_SHAPE);
656 	fAddEmptyMI = new BMenuItem(B_TRANSLATE("Add empty"), message);
657 
658 	message = new BMessage(MSG_ADD_SHAPE);
659 	message->AddBool("path", true);
660 	fAddWidthPathMI = new BMenuItem(B_TRANSLATE("Add with path"), message);
661 
662 	message = new BMessage(MSG_ADD_SHAPE);
663 	message->AddBool("style", true);
664 	fAddWidthStyleMI = new BMenuItem(B_TRANSLATE("Add with style"), message);
665 
666 	message = new BMessage(MSG_ADD_SHAPE);
667 	message->AddBool("path", true);
668 	message->AddBool("style", true);
669 	fAddWidthPathAndStyleMI = new BMenuItem(
670 		B_TRANSLATE("Add with path & style"), message);
671 
672 	message = new BMessage(MSG_OPEN);
673 	message->AddBool("reference image", true);
674 	fAddReferenceImageMI = new BMenuItem(B_TRANSLATE("Add reference image"), message);
675 
676 	fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"),
677 		new BMessage(MSG_DUPLICATE));
678 	fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"),
679 		new BMessage(MSG_RESET_TRANSFORMATION));
680 	fFreezeTransformationMI = new BMenuItem(
681 		B_TRANSLATE("Freeze transformation"),
682 		new BMessage(MSG_FREEZE_TRANSFORMATION));
683 
684 	fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE));
685 
686 
687 	fMenu->AddItem(fAddEmptyMI);
688 	fMenu->AddItem(fAddWidthPathMI);
689 	fMenu->AddItem(fAddWidthStyleMI);
690 	fMenu->AddItem(fAddWidthPathAndStyleMI);
691 
692 	fMenu->AddSeparatorItem();
693 
694 	fMenu->AddItem(fAddReferenceImageMI);
695 
696 	fMenu->AddSeparatorItem();
697 
698 	fMenu->AddItem(fDuplicateMI);
699 	fMenu->AddItem(fResetTransformationMI);
700 	fMenu->AddItem(fFreezeTransformationMI);
701 	fMenu->AddSeparatorItem();
702 
703 	fMenu->AddItem(fRemoveMI);
704 
705 	fDuplicateMI->SetTarget(this);
706 	fResetTransformationMI->SetTarget(this);
707 	fFreezeTransformationMI->SetTarget(this);
708 	fRemoveMI->SetTarget(this);
709 
710 	_UpdateMenu();
711 }
712 
713 
714 void
715 ShapeListView::SetShapeContainer(Container<Shape>* container)
716 {
717 	if (fShapeContainer == container)
718 		return;
719 
720 	// detach from old container
721 	if (fShapeContainer != NULL)
722 		fShapeContainer->RemoveListener(this);
723 
724 	_MakeEmpty();
725 
726 	fShapeContainer = container;
727 
728 	if (fShapeContainer == NULL)
729 		return;
730 
731 	fShapeContainer->AddListener(this);
732 
733 	// sync
734 	int32 count = fShapeContainer->CountItems();
735 	for (int32 i = 0; i < count; i++)
736 		_AddShape(fShapeContainer->ItemAtFast(i), i);
737 }
738 
739 
740 void
741 ShapeListView::SetStyleContainer(Container<Style>* container)
742 {
743 	fStyleContainer = container;
744 }
745 
746 
747 void
748 ShapeListView::SetPathContainer(Container<VectorPath>* container)
749 {
750 	fPathContainer = container;
751 }
752 
753 
754 void
755 ShapeListView::SetCommandStack(CommandStack* stack)
756 {
757 	fCommandStack = stack;
758 }
759 
760 
761 // #pragma mark -
762 
763 
764 bool
765 ShapeListView::_AddShape(Shape* shape, int32 index)
766 {
767 	if (shape == NULL)
768 		return false;
769 
770 	ShapeListItem* item = new(std::nothrow) ShapeListItem(shape, this);
771 	if (item == NULL)
772 		return false;
773 
774 	if (!AddItem(item, index)) {
775 		delete item;
776 		return false;
777 	}
778 
779 	return true;
780 }
781 
782 
783 bool
784 ShapeListView::_RemoveShape(Shape* shape)
785 {
786 	ShapeListItem* item = _ItemForShape(shape);
787 	if (item != NULL && RemoveItem(item)) {
788 		delete item;
789 		return true;
790 	}
791 	return false;
792 }
793 
794 
795 ShapeListItem*
796 ShapeListView::_ItemForShape(Shape* shape) const
797 {
798 	int32 count = CountItems();
799 	for (int32 i = 0; i < count; i++) {
800 		ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
801 		if (item != NULL && item->shape == shape)
802 			return item;
803 	}
804 	return NULL;
805 }
806 
807 
808 void
809 ShapeListView::_UpdateMenu()
810 {
811 	if (fMenu == NULL)
812 		return;
813 
814 	bool gotSelection = CurrentSelection(0) >= 0;
815 
816 	fDuplicateMI->SetEnabled(gotSelection);
817 	fResetTransformationMI->SetEnabled(gotSelection);
818 	fFreezeTransformationMI->SetEnabled(gotSelection);
819 	fRemoveMI->SetEnabled(gotSelection);
820 
821 	if (gotSelection) {
822 		bool hasPathSourceShape = false;
823 
824 		int32 count = CountSelectedItems();
825 		for (int32 i = 0; i < count; i++) {
826 			ShapeListItem* item
827 				= dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(i)));
828 			bool isPathSourceShape
829 				= item ? dynamic_cast<PathSourceShape*>(item->shape) != NULL : false;
830 			hasPathSourceShape |= isPathSourceShape;
831 		}
832 
833 		fFreezeTransformationMI->SetEnabled(hasPathSourceShape);
834 	}
835 }
836 
837 
838 void
839 ShapeListView::_GetSelectedShapes(BList& shapes) const
840 {
841 	int32 count = CountSelectedItems();
842 	for (int32 i = 0; i < count; i++) {
843 		ShapeListItem* item = dynamic_cast<ShapeListItem*>(
844 			ItemAt(CurrentSelection(i)));
845 		if (item != NULL && item->shape != NULL) {
846 			if (!shapes.AddItem((void*)item->shape))
847 				break;
848 		}
849 	}
850 }
851