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