xref: /haiku/src/apps/icon-o-matic/gui/PathListView.cpp (revision dd2a1e350b303b855a50fd64e6cb55618be1ae6a)
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 "PathListView.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 "CleanUpPathCommand.h"
27 #include "CommandStack.h"
28 #include "MovePathsCommand.h"
29 #include "Observer.h"
30 #include "PathSourceShape.h"
31 #include "RemovePathsCommand.h"
32 #include "ReversePathCommand.h"
33 #include "RotatePathIndicesCommand.h"
34 #include "Shape.h"
35 #include "Selection.h"
36 #include "UnassignPathCommand.h"
37 #include "Util.h"
38 #include "VectorPath.h"
39 
40 
41 #undef B_TRANSLATION_CONTEXT
42 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-PathsList"
43 
44 
45 using std::nothrow;
46 
47 static const float kMarkWidth		= 14.0;
48 static const float kBorderOffset	= 3.0;
49 static const float kTextOffset		= 4.0;
50 
51 
52 class PathListItem : public SimpleItem, public Observer {
53 public:
54 	PathListItem(VectorPath* p, PathListView* listView, bool markEnabled)
55 		:
56 		SimpleItem(""),
57 		path(NULL),
58 		fListView(listView),
59 		fMarkEnabled(markEnabled),
60 		fMarked(false)
61 	{
62 		SetPath(p);
63 	}
64 
65 
66 	virtual ~PathListItem()
67 	{
68 		SetPath(NULL);
69 	}
70 
71 
72 	// SimpleItem interface
73 	virtual	void DrawItem(BView* owner, BRect itemFrame, bool even)
74 	{
75 		SimpleItem::DrawBackground(owner, itemFrame, even);
76 
77 		float offset = kBorderOffset + kMarkWidth + kTextOffset;
78 		SimpleItem::DrawItem(owner, itemFrame.OffsetByCopy(offset, 0), even);
79 
80 		if (!fMarkEnabled)
81 			return;
82 
83 		// mark
84 		BRect markRect = itemFrame;
85 		float markRectBorderTint = B_DARKEN_1_TINT;
86 		float markRectFillTint = 1.04;
87 		float markTint = B_DARKEN_4_TINT;
88 					// Dark Themes
89 		rgb_color lowColor = owner->LowColor();
90 		if (lowColor.red + lowColor.green + lowColor.blue < 128 * 3) {
91 			markRectBorderTint = B_LIGHTEN_2_TINT;
92 			markRectFillTint = 0.85;
93 			markTint = 0.1;
94 		}
95 		markRect.left += kBorderOffset;
96 		markRect.right = markRect.left + kMarkWidth;
97 		markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0;
98 		markRect.bottom = markRect.top + kMarkWidth;
99 		owner->SetHighColor(tint_color(owner->LowColor(), markRectBorderTint));
100 		owner->StrokeRect(markRect);
101 		markRect.InsetBy(1, 1);
102 		owner->SetHighColor(tint_color(owner->LowColor(), markRectFillTint));
103 		owner->FillRect(markRect);
104 		if (fMarked) {
105 			markRect.InsetBy(2, 2);
106 			owner->SetHighColor(tint_color(owner->LowColor(),
107 				markTint));
108 			owner->SetPenSize(2);
109 			owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom());
110 			owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop());
111 			owner->SetPenSize(1);
112 		}
113 	}
114 
115 
116 	// Observer interface
117 	virtual	void ObjectChanged(const Observable* object)
118 	{
119 		UpdateText();
120 	}
121 
122 
123 	// PathListItem
124 	void SetPath(VectorPath* p)
125 	{
126 		if (p == path)
127 			return;
128 
129 		if (path) {
130 			path->RemoveObserver(this);
131 			path->ReleaseReference();
132 		}
133 
134 		path = p;
135 
136 		if (path) {
137 			path->AcquireReference();
138 			path->AddObserver(this);
139 			UpdateText();
140 		}
141 	}
142 
143 
144 	void UpdateText()
145 	{
146 		SetText(path->Name());
147 		Invalidate();
148 	}
149 
150 
151 	void SetMarkEnabled(bool enabled)
152 	{
153 		if (fMarkEnabled == enabled)
154 			return;
155 		fMarkEnabled = enabled;
156 		Invalidate();
157 	}
158 
159 
160 	void SetMarked(bool marked)
161 	{
162 		if (fMarked == marked)
163 			return;
164 		fMarked = marked;
165 		Invalidate();
166 	}
167 
168 
169 	void Invalidate()
170 	{
171 		if (fListView->LockLooper()) {
172 			fListView->InvalidateItem(
173 				fListView->IndexOf(this));
174 			fListView->UnlockLooper();
175 		}
176 	}
177 
178 public:
179 	VectorPath* 	path;
180 
181 private:
182 	PathListView*	fListView;
183 	bool			fMarkEnabled;
184 	bool			fMarked;
185 };
186 
187 
188 class ShapePathListener : public ContainerListener<VectorPath>,
189 	public ContainerListener<Shape> {
190 public:
191 	ShapePathListener(PathListView* listView)
192 		:
193 		fListView(listView),
194 		fShape(NULL)
195 	{
196 	}
197 
198 
199 	virtual ~ShapePathListener()
200 	{
201 		SetShape(NULL);
202 	}
203 
204 
205 	// ContainerListener<VectorPath> interface
206 	virtual void ItemAdded(VectorPath* path, int32 index)
207 	{
208 		fListView->_SetPathMarked(path, true);
209 	}
210 
211 
212 	virtual void ItemRemoved(VectorPath* path)
213 	{
214 		fListView->_SetPathMarked(path, false);
215 	}
216 
217 
218 	// ContainerListener<Shape> interface
219 	virtual void ItemAdded(Shape* shape, int32 index)
220 	{
221 	}
222 
223 
224 	virtual void ItemRemoved(Shape* shape)
225 	{
226 		fListView->SetCurrentShape(NULL);
227 	}
228 
229 
230 	// ShapePathListener
231 	void SetShape(PathSourceShape* shape)
232 	{
233 		if (fShape == shape)
234 			return;
235 
236 		if (fShape)
237 			fShape->Paths()->RemoveListener(this);
238 
239 		fShape = shape;
240 
241 		if (fShape)
242 			fShape->Paths()->AddListener(this);
243 	}
244 
245 
246 	Shape* CurrentShape() const
247 	{
248 		return fShape;
249 	}
250 
251 private:
252 	PathListView*		fListView;
253 	PathSourceShape*	fShape;
254 };
255 
256 
257 // #pragma mark -
258 
259 
260 enum {
261 	MSG_ADD					= 'addp',
262 
263 	MSG_ADD_RECT			= 'addr',
264 	MSG_ADD_CIRCLE			= 'addc',
265 	MSG_ADD_ARC				= 'adda',
266 
267 	MSG_DUPLICATE			= 'dupp',
268 
269 	MSG_REVERSE				= 'rvrs',
270 	MSG_CLEAN_UP			= 'clup',
271 	MSG_ROTATE_INDICES_CW	= 'ricw',
272 	MSG_ROTATE_INDICES_CCW	= 'ricc',
273 
274 	MSG_REMOVE				= 'remp',
275 };
276 
277 
278 PathListView::PathListView(BRect frame, const char* name, BMessage* message,
279 	BHandler* target)
280 	:
281 	SimpleListView(frame, name, NULL, B_SINGLE_SELECTION_LIST),
282 	fMessage(message),
283 	fMenu(NULL),
284 
285 	fPathContainer(NULL),
286 	fShapeContainer(NULL),
287 	fCommandStack(NULL),
288 
289 	fCurrentShape(NULL),
290 	fShapePathListener(new ShapePathListener(this))
291 {
292 	SetTarget(target);
293 }
294 
295 
296 PathListView::~PathListView()
297 {
298 	_MakeEmpty();
299 	delete fMessage;
300 
301 	if (fPathContainer != NULL)
302 		fPathContainer->RemoveListener(this);
303 
304 	if (fShapeContainer != NULL)
305 		fShapeContainer->RemoveListener(fShapePathListener);
306 
307 	delete fShapePathListener;
308 }
309 
310 
311 void
312 PathListView::SelectionChanged()
313 {
314 	SimpleListView::SelectionChanged();
315 
316 	if (!fSyncingToSelection) {
317 		// NOTE: single selection list
318 		PathListItem* item
319 			= dynamic_cast<PathListItem*>(ItemAt(CurrentSelection(0)));
320 		if (fMessage != NULL) {
321 			BMessage message(*fMessage);
322 			message.AddPointer("path", item ? (void*)item->path : NULL);
323 			Invoke(&message);
324 		}
325 	}
326 
327 	_UpdateMenu();
328 }
329 
330 
331 void
332 PathListView::MouseDown(BPoint where)
333 {
334 	if (fCurrentShape == NULL) {
335 		SimpleListView::MouseDown(where);
336 		return;
337 	}
338 
339 	bool handled = false;
340 	int32 index = IndexOf(where);
341 	PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index));
342 	if (item != NULL) {
343 		BRect itemFrame(ItemFrame(index));
344 		itemFrame.right = itemFrame.left + kBorderOffset + kMarkWidth
345 			+ kTextOffset / 2.0;
346 
347 		VectorPath* path = item->path;
348 		if (itemFrame.Contains(where) && fCommandStack) {
349 			// add or remove the path to the shape
350 			::Command* command;
351 			if (fCurrentShape->Paths()->HasItem(path)) {
352 				command = new UnassignPathCommand(fCurrentShape, path);
353 			} else {
354 				VectorPath* paths[1];
355 				paths[0] = path;
356 				command = new AddPathsCommand(fCurrentShape->Paths(),
357 					paths, 1, false, fCurrentShape->Paths()->CountItems());
358 			}
359 			fCommandStack->Perform(command);
360 			handled = true;
361 		}
362 	}
363 
364 	if (!handled)
365 		SimpleListView::MouseDown(where);
366 }
367 
368 
369 void
370 PathListView::MessageReceived(BMessage* message)
371 {
372 	switch (message->what) {
373 		case MSG_ADD:
374 			if (fCommandStack != NULL) {
375 				VectorPath* path;
376 				AddPathsCommand* command;
377 				new_path(fPathContainer, &path, &command);
378 				fCommandStack->Perform(command);
379 			}
380 			break;
381 
382 		case MSG_ADD_RECT:
383 			if (fCommandStack != NULL) {
384 				VectorPath* path;
385 				AddPathsCommand* command;
386 				new_path(fPathContainer, &path, &command);
387 				if (path != NULL) {
388 					path->AddPoint(BPoint(16, 16));
389 					path->AddPoint(BPoint(16, 48));
390 					path->AddPoint(BPoint(48, 48));
391 					path->AddPoint(BPoint(48, 16));
392 					path->SetClosed(true);
393 				}
394 				fCommandStack->Perform(command);
395 			}
396 			break;
397 
398 		case MSG_ADD_CIRCLE:
399 			// TODO: ask for number of secions
400 			if (fCommandStack != NULL) {
401 				VectorPath* path;
402 				AddPathsCommand* command;
403 				new_path(fPathContainer, &path, &command);
404 				if (path != NULL) {
405 					// add four control points defining a circle:
406 					//   a
407 					// b   d
408 					//   c
409 					BPoint a(32, 16);
410 					BPoint b(16, 32);
411 					BPoint c(32, 48);
412 					BPoint d(48, 32);
413 
414 					path->AddPoint(a);
415 					path->AddPoint(b);
416 					path->AddPoint(c);
417 					path->AddPoint(d);
418 
419 					path->SetClosed(true);
420 
421 					float controlDist = 0.552284 * 16;
422 					path->SetPoint(0, a, a + BPoint(controlDist, 0.0),
423 										 a + BPoint(-controlDist, 0.0), true);
424 					path->SetPoint(1, b, b + BPoint(0.0, -controlDist),
425 										 b + BPoint(0.0, controlDist), true);
426 					path->SetPoint(2, c, c + BPoint(-controlDist, 0.0),
427 										 c + BPoint(controlDist, 0.0), true);
428 					path->SetPoint(3, d, d + BPoint(0.0, controlDist),
429 										 d + BPoint(0.0, -controlDist), true);
430 				}
431 				fCommandStack->Perform(command);
432 			}
433 			break;
434 
435 		case MSG_DUPLICATE:
436 			if (fCommandStack != NULL) {
437 				PathListItem* item = dynamic_cast<PathListItem*>(
438 					ItemAt(CurrentSelection(0)));
439 				if (item == NULL)
440 					break;
441 
442 				VectorPath* path;
443 				AddPathsCommand* command;
444 				new_path(fPathContainer, &path, &command, item->path);
445 				fCommandStack->Perform(command);
446 			}
447 			break;
448 
449 		case MSG_REVERSE:
450 			if (fCommandStack != NULL) {
451 				PathListItem* item = dynamic_cast<PathListItem*>(
452 					ItemAt(CurrentSelection(0)));
453 				if (item == NULL)
454 					break;
455 
456 				ReversePathCommand* command
457 					= new (nothrow) ReversePathCommand(item->path);
458 				fCommandStack->Perform(command);
459 			}
460 			break;
461 
462 		case MSG_CLEAN_UP:
463 			if (fCommandStack != NULL) {
464 				PathListItem* item = dynamic_cast<PathListItem*>(
465 					ItemAt(CurrentSelection(0)));
466 				if (item == NULL)
467 					break;
468 
469 				CleanUpPathCommand* command
470 					= new (nothrow) CleanUpPathCommand(item->path);
471 				fCommandStack->Perform(command);
472 			}
473 			break;
474 
475 		case MSG_ROTATE_INDICES_CW:
476 		case MSG_ROTATE_INDICES_CCW:
477 			if (fCommandStack != NULL) {
478 				PathListItem* item = dynamic_cast<PathListItem*>(
479 					ItemAt(CurrentSelection(0)));
480 				if (item == NULL)
481 					break;
482 
483 				RotatePathIndicesCommand* command
484 					= new (nothrow) RotatePathIndicesCommand(item->path,
485 					message->what == MSG_ROTATE_INDICES_CW);
486 				fCommandStack->Perform(command);
487 			}
488 			break;
489 
490 		case MSG_REMOVE:
491 			RemoveSelected();
492 			break;
493 
494 		default:
495 			SimpleListView::MessageReceived(message);
496 			break;
497 	}
498 }
499 
500 
501 status_t
502 PathListView::ArchiveSelection(BMessage* into, bool deep) const
503 {
504 	into->what = PathListView::kSelectionArchiveCode;
505 
506 	int32 count = CountSelectedItems();
507 	for (int32 i = 0; i < count; i++) {
508 		PathListItem* item = dynamic_cast<PathListItem*>(
509 			ItemAt(CurrentSelection(i)));
510 		if (item != NULL) {
511 			BMessage archive;
512 			if (item->path->Archive(&archive, deep) == B_OK)
513 				into->AddMessage("path", &archive);
514 		} else
515 			return B_ERROR;
516 	}
517 
518 	return B_OK;
519 }
520 
521 
522 bool
523 PathListView::InstantiateSelection(const BMessage* archive, int32 dropIndex)
524 {
525 	if (archive->what != PathListView::kSelectionArchiveCode
526 		|| fCommandStack == NULL || fPathContainer == NULL)
527 		return false;
528 
529 	// Drag may have come from another instance, like in another window.
530 	// Reconstruct the Styles from the archive and add them at the drop
531 	// index.
532 	int index = 0;
533 	BList paths;
534 	while (true) {
535 		BMessage pathArchive;
536 		if (archive->FindMessage("path", index, &pathArchive) != B_OK)
537 			break;
538 
539 		VectorPath* path = new(std::nothrow) VectorPath(&pathArchive);
540 		if (path == NULL)
541 			break;
542 
543 		if (!paths.AddItem(path)) {
544 			delete path;
545 			break;
546 		}
547 
548 		index++;
549 	}
550 
551 	int32 count = paths.CountItems();
552 	if (count == 0)
553 		return false;
554 
555 	AddPathsCommand* command = new(nothrow) AddPathsCommand(fPathContainer,
556 		(VectorPath**)paths.Items(), count, true, dropIndex);
557 	if (command == NULL) {
558 		for (int32 i = 0; i < count; i++)
559 			delete (VectorPath*)paths.ItemAtFast(i);
560 		return false;
561 	}
562 
563 	fCommandStack->Perform(command);
564 
565 	return true;
566 }
567 
568 
569 void
570 PathListView::MoveItems(BList& items, int32 toIndex)
571 {
572 	if (fCommandStack == NULL || fPathContainer == NULL)
573 		return;
574 
575 	int32 count = items.CountItems();
576 	VectorPath** paths = new (nothrow) VectorPath*[count];
577 	if (paths == NULL)
578 		return;
579 
580 	for (int32 i = 0; i < count; i++) {
581 		PathListItem* item
582 			= dynamic_cast<PathListItem*>((BListItem*)items.ItemAtFast(i));
583 		paths[i] = item ? item->path : NULL;
584 	}
585 
586 	MovePathsCommand* command = new (nothrow) MovePathsCommand(fPathContainer,
587 		paths, count, toIndex);
588 	if (command == NULL) {
589 		delete[] paths;
590 		return;
591 	}
592 
593 	fCommandStack->Perform(command);
594 }
595 
596 
597 void
598 PathListView::CopyItems(BList& items, int32 toIndex)
599 {
600 	if (fCommandStack == NULL || fPathContainer == NULL)
601 		return;
602 
603 	int32 count = items.CountItems();
604 	VectorPath* paths[count];
605 
606 	for (int32 i = 0; i < count; i++) {
607 		PathListItem* item
608 			= dynamic_cast<PathListItem*>((BListItem*)items.ItemAtFast(i));
609 		paths[i] = item ? new (nothrow) VectorPath(*item->path) : NULL;
610 	}
611 
612 	AddPathsCommand* command = new(nothrow) AddPathsCommand(fPathContainer,
613 		paths, count, true, toIndex);
614 	if (command == NULL) {
615 		for (int32 i = 0; i < count; i++)
616 			delete paths[i];
617 		return;
618 	}
619 
620 	fCommandStack->Perform(command);
621 }
622 
623 
624 void
625 PathListView::RemoveItemList(BList& items)
626 {
627 	if (fCommandStack == NULL || fPathContainer == NULL)
628 		return;
629 
630 	int32 count = items.CountItems();
631 	int32 indices[count];
632 	for (int32 i = 0; i < count; i++)
633 		indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
634 
635 	RemovePathsCommand* command = new (nothrow) RemovePathsCommand(
636 		fPathContainer, indices, count);
637 
638 	fCommandStack->Perform(command);
639 }
640 
641 
642 BListItem*
643 PathListView::CloneItem(int32 index) const
644 {
645 	if (PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index))) {
646 		return new(nothrow) PathListItem(item->path,
647 			const_cast<PathListView*>(this), fCurrentShape != NULL);
648 	}
649 	return NULL;
650 }
651 
652 
653 int32
654 PathListView::IndexOfSelectable(Selectable* selectable) const
655 {
656 	VectorPath* path = dynamic_cast<VectorPath*>(selectable);
657 	if (path == NULL)
658 		return -1;
659 
660 	int32 count = CountItems();
661 	for (int32 i = 0; i < count; i++) {
662 		if (SelectableFor(ItemAt(i)) == path)
663 			return i;
664 	}
665 
666 	return -1;
667 }
668 
669 
670 Selectable*
671 PathListView::SelectableFor(BListItem* item) const
672 {
673 	PathListItem* pathItem = dynamic_cast<PathListItem*>(item);
674 	if (pathItem != NULL)
675 		return pathItem->path;
676 	return NULL;
677 }
678 
679 
680 // #pragma mark -
681 
682 
683 void
684 PathListView::ItemAdded(VectorPath* path, int32 index)
685 {
686 	// NOTE: we are in the thread that messed with the
687 	// ShapeContainer, so no need to lock the
688 	// container, when this is changed to asynchronous
689 	// notifications, then it would need to be read-locked!
690 	if (!LockLooper())
691 		return;
692 
693 	if (_AddPath(path, index))
694 		Select(index);
695 
696 	UnlockLooper();
697 }
698 
699 
700 void
701 PathListView::ItemRemoved(VectorPath* path)
702 {
703 	// NOTE: we are in the thread that messed with the
704 	// ShapeContainer, so no need to lock the
705 	// container, when this is changed to asynchronous
706 	// notifications, then it would need to be read-locked!
707 	if (!LockLooper())
708 		return;
709 
710 	// NOTE: we're only interested in VectorPath objects
711 	_RemovePath(path);
712 
713 	UnlockLooper();
714 }
715 
716 
717 // #pragma mark -
718 
719 
720 void
721 PathListView::SetPathContainer(Container<VectorPath>* container)
722 {
723 	if (fPathContainer == container)
724 		return;
725 
726 	// detach from old container
727 	if (fPathContainer != NULL)
728 		fPathContainer->RemoveListener(this);
729 
730 	_MakeEmpty();
731 
732 	fPathContainer = container;
733 
734 	if (fPathContainer == NULL)
735 		return;
736 
737 	fPathContainer->AddListener(this);
738 
739 	// sync
740 //	if (!fPathContainer->ReadLock())
741 //		return;
742 
743 	int32 count = fPathContainer->CountItems();
744 	for (int32 i = 0; i < count; i++)
745 		_AddPath(fPathContainer->ItemAtFast(i), i);
746 
747 //	fPathContainer->ReadUnlock();
748 }
749 
750 
751 void
752 PathListView::SetShapeContainer(Container<Shape>* container)
753 {
754 	if (fShapeContainer == container)
755 		return;
756 
757 	// detach from old container
758 	if (fShapeContainer != NULL)
759 		fShapeContainer->RemoveListener(fShapePathListener);
760 
761 	fShapeContainer = container;
762 
763 	if (fShapeContainer != NULL)
764 		fShapeContainer->AddListener(fShapePathListener);
765 }
766 
767 
768 void
769 PathListView::SetCommandStack(CommandStack* stack)
770 {
771 	fCommandStack = stack;
772 }
773 
774 
775 void
776 PathListView::SetMenu(BMenu* menu)
777 {
778 	fMenu = menu;
779 	if (fMenu == NULL)
780 		return;
781 
782 	fAddMI = new BMenuItem(B_TRANSLATE("Add"),
783 		new BMessage(MSG_ADD));
784 	fAddRectMI = new BMenuItem(B_TRANSLATE("Add rect"),
785 		new BMessage(MSG_ADD_RECT));
786 	fAddCircleMI = new BMenuItem(B_TRANSLATE("Add circle"/*B_UTF8_ELLIPSIS*/),
787 		new BMessage(MSG_ADD_CIRCLE));
788 //	fAddArcMI = new BMenuItem("Add arc" B_UTF8_ELLIPSIS,
789 //		new BMessage(MSG_ADD_ARC));
790 	fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"),
791 		new BMessage(MSG_DUPLICATE));
792 	fReverseMI = new BMenuItem(B_TRANSLATE("Reverse"),
793 		new BMessage(MSG_REVERSE));
794 	fCleanUpMI = new BMenuItem(B_TRANSLATE("Clean up"),
795 		new BMessage(MSG_CLEAN_UP));
796 	fRotateIndicesRightMI = new BMenuItem(B_TRANSLATE("Rotate indices forwards"),
797 		new BMessage(MSG_ROTATE_INDICES_CCW), 'R');
798 	fRotateIndicesLeftMI = new BMenuItem(B_TRANSLATE("Rotate indices backwards"),
799 		new BMessage(MSG_ROTATE_INDICES_CW), 'R', B_SHIFT_KEY);
800 	fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"),
801 		new BMessage(MSG_REMOVE));
802 
803 	fMenu->AddItem(fAddMI);
804 	fMenu->AddItem(fAddRectMI);
805 	fMenu->AddItem(fAddCircleMI);
806 //	fMenu->AddItem(fAddArcMI);
807 
808 	fMenu->AddSeparatorItem();
809 
810 	fMenu->AddItem(fDuplicateMI);
811 	fMenu->AddItem(fReverseMI);
812 	fMenu->AddItem(fCleanUpMI);
813 
814 	fMenu->AddSeparatorItem();
815 
816 	fMenu->AddItem(fRotateIndicesRightMI);
817 	fMenu->AddItem(fRotateIndicesLeftMI);
818 
819 	fMenu->AddSeparatorItem();
820 
821 	fMenu->AddItem(fRemoveMI);
822 
823 	fMenu->SetTargetForItems(this);
824 
825 	_UpdateMenu();
826 }
827 
828 
829 void
830 PathListView::SetCurrentShape(Shape* shape)
831 {
832 	if (fCurrentShape == shape)
833 		return;
834 
835 	fCurrentShape = dynamic_cast<PathSourceShape*>(shape);
836 	fShapePathListener->SetShape(fCurrentShape);
837 
838 	_UpdateMarks();
839 }
840 
841 
842 // #pragma mark -
843 
844 
845 bool
846 PathListView::_AddPath(VectorPath* path, int32 index)
847 {
848 	if (path == NULL)
849 		return false;
850 
851 	PathListItem* item = new(nothrow) PathListItem(path, this,
852 		fCurrentShape != NULL);
853 	if (item == NULL)
854 		return false;
855 
856 	if (!AddItem(item, index)) {
857 		delete item;
858 		return false;
859 	}
860 
861 	return true;
862 }
863 
864 
865 bool
866 PathListView::_RemovePath(VectorPath* path)
867 {
868 	PathListItem* item = _ItemForPath(path);
869 	if (item != NULL && RemoveItem(item)) {
870 		delete item;
871 		return true;
872 	}
873 	return false;
874 }
875 
876 
877 PathListItem*
878 PathListView::_ItemForPath(VectorPath* path) const
879 {
880 	int32 count = CountItems();
881 	for (int32 i = 0; i < count; i++) {
882 		 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
883 		if (item == NULL)
884 			continue;
885 		if (item->path == path)
886 			return item;
887 	}
888 	return NULL;
889 }
890 
891 
892 // #pragma mark -
893 
894 
895 void
896 PathListView::_UpdateMarks()
897 {
898 	int32 count = CountItems();
899 	if (fCurrentShape != NULL) {
900 		// enable display of marks and mark items whoes
901 		// path is contained in fCurrentShape
902 		for (int32 i = 0; i < count; i++) {
903 			PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
904 			if (item == NULL)
905 				continue;
906 			item->SetMarkEnabled(true);
907 			item->SetMarked(fCurrentShape->Paths()->HasItem(item->path));
908 		}
909 	} else {
910 		// disable display of marks
911 		for (int32 i = 0; i < count; i++) {
912 			PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
913 			if (item == NULL)
914 				continue;
915 			item->SetMarkEnabled(false);
916 		}
917 	}
918 
919 	Invalidate();
920 }
921 
922 
923 void
924 PathListView::_SetPathMarked(VectorPath* path, bool marked)
925 {
926 	PathListItem* item = _ItemForPath(path);
927 	if (item != NULL)
928 		item->SetMarked(marked);
929 }
930 
931 
932 void
933 PathListView::_UpdateMenu()
934 {
935 	if (fMenu == NULL)
936 		return;
937 
938 	bool gotSelection = CurrentSelection(0) >= 0;
939 
940 	fDuplicateMI->SetEnabled(gotSelection);
941 	fReverseMI->SetEnabled(gotSelection);
942 	fCleanUpMI->SetEnabled(gotSelection);
943 	fRotateIndicesLeftMI->SetEnabled(gotSelection);
944 	fRotateIndicesRightMI->SetEnabled(gotSelection);
945 	fRemoveMI->SetEnabled(gotSelection);
946 }
947 
948 
949