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