xref: /haiku/src/apps/icon-o-matic/gui/PathListView.cpp (revision 9ecf9d1c1d4888d341a6eac72112c72d1ae3a4cb)
1 /*
2  * Copyright 2006, Haiku.
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 <stdio.h>
12 
13 #include <Application.h>
14 #include <ListItem.h>
15 #include <Message.h>
16 #include <Mime.h>
17 #include <Window.h>
18 
19 #include "AddPathsCommand.h"
20 #include "CommandStack.h"
21 #include "Observer.h"
22 #include "RemovePathsCommand.h"
23 #include "Shape.h"
24 #include "ShapeContainer.h"
25 #include "Selection.h"
26 #include "UnassignPathCommand.h"
27 #include "VectorPath.h"
28 
29 static const float kMarkWidth		= 14.0;
30 static const float kBorderOffset	= 3.0;
31 static const float kTextOffset		= 4.0;
32 
33 class PathListItem : public SimpleItem,
34 					 public Observer {
35  public:
36 					PathListItem(VectorPath* p,
37 								 PathListView* listView,
38 								 bool markEnabled)
39 						: SimpleItem(""),
40 						  path(NULL),
41 						  fListView(listView),
42 						  fMarkEnabled(markEnabled),
43 						  fMarked(false)
44 					{
45 						SetPath(p);
46 					}
47 
48 	virtual			~PathListItem()
49 					{
50 						SetPath(NULL);
51 					}
52 
53 	// SimpleItem interface
54 	virtual	void	Draw(BView* owner, BRect itemFrame, uint32 flags)
55 	{
56 		SimpleItem::DrawBackground(owner, itemFrame, flags);
57 
58 		// text
59 		owner->SetHighColor(0, 0, 0, 255);
60 		font_height fh;
61 		owner->GetFontHeight(&fh);
62 		BString truncatedString(Text());
63 		owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE,
64 							  itemFrame.Width()
65 							  - kBorderOffset
66 							  - kMarkWidth
67 							  - kTextOffset
68 							  - kBorderOffset);
69 		float height = itemFrame.Height();
70 		float textHeight = fh.ascent + fh.descent;
71 		BPoint pos;
72 		pos.x = itemFrame.left
73 					+ kBorderOffset + kMarkWidth + kTextOffset;
74 		pos.y = itemFrame.top
75 					 + ceilf((height - textHeight) / 2.0 + fh.ascent);
76 		owner->DrawString(truncatedString.String(), pos);
77 
78 		if (!fMarkEnabled)
79 			return;
80 
81 		// mark
82 		BRect markRect = itemFrame;
83 		markRect.left += kBorderOffset;
84 		markRect.right = markRect.left + kMarkWidth;
85 		markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0;
86 		markRect.bottom = markRect.top + kMarkWidth;
87 		owner->SetHighColor(tint_color(owner->LowColor(), B_DARKEN_1_TINT));
88 		owner->StrokeRect(markRect);
89 		markRect.InsetBy(1, 1);
90 		owner->SetHighColor(tint_color(owner->LowColor(), 1.04));
91 		owner->FillRect(markRect);
92 		if (fMarked) {
93 			markRect.InsetBy(2, 2);
94 			owner->SetHighColor(tint_color(owner->LowColor(),
95 								B_DARKEN_4_TINT));
96 			owner->SetPenSize(2);
97 			owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom());
98 			owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop());
99 			owner->SetPenSize(1);
100 		}
101 	}
102 
103 	// Observer interface
104 	virtual	void	ObjectChanged(const Observable* object)
105 					{
106 						UpdateText();
107 					}
108 
109 	// PathListItem
110 			void	SetPath(VectorPath* p)
111 					{
112 						if (p == path)
113 							return;
114 
115 						if (path) {
116 							path->RemoveObserver(this);
117 							path->Release();
118 						}
119 
120 						path = p;
121 
122 						if (path) {
123 							path->Acquire();
124 							path->AddObserver(this);
125 							UpdateText();
126 						}
127 					}
128 			void	UpdateText()
129 					{
130 						SetText(path->Name());
131 						Invalidate();
132 					}
133 
134 			void	SetMarkEnabled(bool enabled)
135 					{
136 						if (fMarkEnabled == enabled)
137 							return;
138 						fMarkEnabled = enabled;
139 						Invalidate();
140 					}
141 			void	SetMarked(bool marked)
142 					{
143 						if (fMarked == marked)
144 							return;
145 						fMarked = marked;
146 						Invalidate();
147 					}
148 
149 			void Invalidate()
150 					{
151 						// :-/
152 						if (fListView->LockLooper()) {
153 							fListView->InvalidateItem(
154 								fListView->IndexOf(this));
155 							fListView->UnlockLooper();
156 						}
157 					}
158 
159 	VectorPath* 	path;
160  private:
161 	PathListView*	fListView;
162 	bool			fMarkEnabled;
163 	bool			fMarked;
164 };
165 
166 
167 class ShapePathListener : public PathContainerListener,
168 						  public ShapeContainerListener {
169  public:
170 	ShapePathListener(PathListView* listView)
171 		: fListView(listView),
172 		  fShape(NULL)
173 	{
174 	}
175 	virtual ~ShapePathListener()
176 	{
177 		SetShape(NULL);
178 	}
179 
180 	// PathContainerListener interface
181 	virtual void PathAdded(VectorPath* path)
182 	{
183 		fListView->_SetPathMarked(path, true);
184 	}
185 	virtual void PathRemoved(VectorPath* path)
186 	{
187 		fListView->_SetPathMarked(path, false);
188 	}
189 
190 	// ShapeContainerListener interface
191 	virtual void ShapeAdded(Shape* shape, int32 index) {}
192 	virtual void ShapeRemoved(Shape* shape)
193 	{
194 		fListView->SetCurrentShape(NULL);
195 	}
196 
197 	// ShapePathListener
198 	void SetShape(Shape* shape)
199 	{
200 		if (fShape == shape)
201 			return;
202 
203 		if (fShape)
204 			fShape->Paths()->RemoveListener(this);
205 
206 		fShape = shape;
207 
208 		if (fShape)
209 			fShape->Paths()->AddListener(this);
210 	}
211 
212 	Shape* CurrentShape() const
213 	{
214 		return fShape;
215 	}
216 
217  private:
218 	PathListView*	fListView;
219 	Shape*			fShape;
220 };
221 
222 // #pragma mark -
223 
224 // constructor
225 PathListView::PathListView(BRect frame,
226 						   const char* name,
227 						   BMessage* message, BHandler* target)
228 	: SimpleListView(frame, name,
229 					 NULL, B_SINGLE_SELECTION_LIST),
230 	  fMessage(message),
231 	  fPathContainer(NULL),
232 	  fShapeContainer(NULL),
233 	  fSelection(NULL),
234 	  fCommandStack(NULL),
235 
236 	  fCurrentShape(NULL),
237 	  fShapePathListener(new ShapePathListener(this))
238 {
239 	SetTarget(target);
240 }
241 
242 // destructor
243 PathListView::~PathListView()
244 {
245 	_MakeEmpty();
246 	delete fMessage;
247 
248 	if (fPathContainer)
249 		fPathContainer->RemoveListener(this);
250 
251 	if (fShapeContainer)
252 		fShapeContainer->RemoveListener(fShapePathListener);
253 
254 	delete fShapePathListener;
255 }
256 
257 // SelectionChanged
258 void
259 PathListView::SelectionChanged()
260 {
261 	// NOTE: single selection list
262 
263 	PathListItem* item
264 		= dynamic_cast<PathListItem*>(ItemAt(CurrentSelection(0)));
265 	if (fMessage) {
266 		BMessage message(*fMessage);
267 		message.AddPointer("path", item ? (void*)item->path : NULL);
268 		Invoke(&message);
269 	}
270 
271 	// modify global Selection
272 	if (!fSelection)
273 		return;
274 
275 	if (item)
276 		fSelection->Select(item->path);
277 	else
278 		fSelection->DeselectAll();
279 }
280 
281 // MouseDown
282 void
283 PathListView::MouseDown(BPoint where)
284 {
285 	if (!fCurrentShape) {
286 		SimpleListView::MouseDown(where);
287 		return;
288 	}
289 
290 	bool handled = false;
291 	int32 index = IndexOf(where);
292 	PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index));
293 	if (item) {
294 		BRect itemFrame(ItemFrame(index));
295 		itemFrame.right = itemFrame.left
296 							+ kBorderOffset + kMarkWidth
297 							+ kTextOffset / 2.0;
298 		VectorPath* path = item->path;
299 		if (itemFrame.Contains(where) && fCommandStack) {
300 			// add or remove the path to the shape
301 			::Command* command;
302 			if (fCurrentShape->Paths()->HasPath(path)) {
303 				command = new UnassignPathCommand(
304 								fCurrentShape, path);
305 			} else {
306 				VectorPath* paths[1];
307 				paths[0] = path;
308 				command = new AddPathsCommand(
309 								fCurrentShape->Paths(),
310 								paths, 1, false,
311 								fCurrentShape->Paths()->CountPaths());
312 			}
313 			fCommandStack->Perform(command);
314 			handled = true;
315 		}
316 	}
317 
318 	if (!handled)
319 		SimpleListView::MouseDown(where);
320 }
321 
322 // MessageReceived
323 void
324 PathListView::MessageReceived(BMessage* message)
325 {
326 	SimpleListView::MessageReceived(message);
327 }
328 
329 // MakeDragMessage
330 void
331 PathListView::MakeDragMessage(BMessage* message) const
332 {
333 }
334 
335 // AcceptDragMessage
336 bool
337 PathListView::AcceptDragMessage(const BMessage* message) const
338 {
339 	return false;
340 }
341 
342 // SetDropTargetRect
343 void
344 PathListView::SetDropTargetRect(const BMessage* message, BPoint where)
345 {
346 }
347 
348 // MoveItems
349 void
350 PathListView::MoveItems(BList& items, int32 toIndex)
351 {
352 }
353 
354 // CopyItems
355 void
356 PathListView::CopyItems(BList& items, int32 toIndex)
357 {
358 	// TODO: allow to copy path
359 }
360 
361 // RemoveItemList
362 void
363 PathListView::RemoveItemList(BList& items)
364 {
365 	if (!fCommandStack || !fPathContainer)
366 		return;
367 
368 	int32 count = items.CountItems();
369 	VectorPath* paths[count];
370 	for (int32 i = 0; i < count; i++) {
371 		PathListItem* item = dynamic_cast<PathListItem*>(
372 			(SimpleItem*)items.ItemAtFast(i));
373 		if (item)
374 			paths[i] = item->path;
375 		else
376 			paths[i] = NULL;
377 	}
378 
379 	RemovePathsCommand* command
380 		= new (nothrow) RemovePathsCommand(fPathContainer,
381 										   paths, count);
382 	fCommandStack->Perform(command);
383 }
384 
385 // CloneItem
386 BListItem*
387 PathListView::CloneItem(int32 index) const
388 {
389 	if (PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index))) {
390 		return new PathListItem(item->path,
391 								const_cast<PathListView*>(this),
392 								fCurrentShape != NULL);
393 	}
394 	return NULL;
395 }
396 
397 // #pragma mark -
398 
399 // PathAdded
400 void
401 PathListView::PathAdded(VectorPath* path)
402 {
403 	// NOTE: we are in the thread that messed with the
404 	// ShapeContainer, so no need to lock the
405 	// container, when this is changed to asynchronous
406 	// notifications, then it would need to be read-locked!
407 	if (!LockLooper())
408 		return;
409 
410 	// NOTE: shapes are always added at the end
411 	// of the list, so the sorting is synced...
412 	if (_AddPath(path))
413 		Select(CountItems() - 1);
414 
415 	UnlockLooper();
416 }
417 
418 // PathRemoved
419 void
420 PathListView::PathRemoved(VectorPath* path)
421 {
422 	// NOTE: we are in the thread that messed with the
423 	// ShapeContainer, so no need to lock the
424 	// container, when this is changed to asynchronous
425 	// notifications, then it would need to be read-locked!
426 	if (!LockLooper())
427 		return;
428 
429 	// NOTE: we're only interested in VectorPath objects
430 	_RemovePath(path);
431 
432 	UnlockLooper();
433 }
434 
435 // #pragma mark -
436 
437 // SetPathContainer
438 void
439 PathListView::SetPathContainer(PathContainer* container)
440 {
441 	if (fPathContainer == container)
442 		return;
443 
444 	// detach from old container
445 	if (fPathContainer)
446 		fPathContainer->RemoveListener(this);
447 
448 	_MakeEmpty();
449 
450 	fPathContainer = container;
451 
452 	if (!fPathContainer)
453 		return;
454 
455 	fPathContainer->AddListener(this);
456 
457 	// sync
458 //	if (!fPathContainer->ReadLock())
459 //		return;
460 
461 	int32 count = fPathContainer->CountPaths();
462 	for (int32 i = 0; i < count; i++)
463 		_AddPath(fPathContainer->PathAtFast(i));
464 
465 //	fPathContainer->ReadUnlock();
466 }
467 
468 // SetShapeContainer
469 void
470 PathListView::SetShapeContainer(ShapeContainer* container)
471 {
472 	if (fShapeContainer == container)
473 		return;
474 
475 	// detach from old container
476 	if (fShapeContainer)
477 		fShapeContainer->RemoveListener(fShapePathListener);
478 
479 	fShapeContainer = container;
480 
481 	if (fShapeContainer)
482 		fShapeContainer->AddListener(fShapePathListener);
483 }
484 
485 // SetSelection
486 void
487 PathListView::SetSelection(Selection* selection)
488 {
489 	fSelection = selection;
490 }
491 
492 // SetCommandStack
493 void
494 PathListView::SetCommandStack(CommandStack* stack)
495 {
496 	fCommandStack = stack;
497 }
498 
499 // SetCurrentShape
500 void
501 PathListView::SetCurrentShape(Shape* shape)
502 {
503 	if (fCurrentShape == shape)
504 		return;
505 
506 	fCurrentShape = shape;
507 	fShapePathListener->SetShape(shape);
508 
509 	_UpdateMarks();
510 }
511 
512 // #pragma mark -
513 
514 // _AddPath
515 bool
516 PathListView::_AddPath(VectorPath* path)
517 {
518 	if (path)
519 		 return AddItem(new PathListItem(path, this, fCurrentShape != NULL));
520 	return false;
521 }
522 
523 // _RemovePath
524 bool
525 PathListView::_RemovePath(VectorPath* path)
526 {
527 	PathListItem* item = _ItemForPath(path);
528 	if (item && RemoveItem(item)) {
529 		delete item;
530 		return true;
531 	}
532 	return false;
533 }
534 
535 // _ItemForPath
536 PathListItem*
537 PathListView::_ItemForPath(VectorPath* path) const
538 {
539 	for (int32 i = 0;
540 		 PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
541 		 i++) {
542 		if (item->path == path)
543 			return item;
544 	}
545 	return NULL;
546 }
547 
548 // #pragma mark -
549 
550 // _UpdateMarks
551 void
552 PathListView::_UpdateMarks()
553 {
554 	int32 count = CountItems();
555 	if (fCurrentShape) {
556 		// enable display of marks and mark items whoes
557 		// path is contained in fCurrentShape
558 		for (int32 i = 0; i < count; i++) {
559 			PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
560 			if (!item)
561 				continue;
562 			item->SetMarkEnabled(true);
563 			item->SetMarked(fCurrentShape->Paths()->HasPath(item->path));
564 		}
565 	} else {
566 		// disable display of marks
567 		for (int32 i = 0; i < count; i++) {
568 			PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
569 			if (!item)
570 				continue;
571 			item->SetMarkEnabled(false);
572 		}
573 	}
574 
575 	Invalidate();
576 }
577 
578 // _SetPathMarked
579 void
580 PathListView::_SetPathMarked(VectorPath* path, bool marked)
581 {
582 	if (PathListItem* item = _ItemForPath(path)) {
583 		item->SetMarked(marked);
584 	}
585 }
586