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