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