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