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