xref: /haiku/src/apps/icon-o-matic/gui/ShapeListView.cpp (revision 97901ec593ec4dd50ac115c1c35a6d72f6e489a5)
1 /*
2  * Copyright 2006-2009, 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 "ShapeListView.h"
10 
11 #include <new>
12 #include <stdio.h>
13 
14 #include <Application.h>
15 #include <ListItem.h>
16 #include <Menu.h>
17 #include <MenuItem.h>
18 #include <Message.h>
19 #include <Mime.h>
20 #include <Window.h>
21 
22 #include "AddPathsCommand.h"
23 #include "AddShapesCommand.h"
24 #include "AddStylesCommand.h"
25 #include "CommandStack.h"
26 #include "FreezeTransformationCommand.h"
27 #include "MoveShapesCommand.h"
28 #include "Observer.h"
29 #include "RemoveShapesCommand.h"
30 #include "ResetTransformationCommand.h"
31 #include "Selection.h"
32 #include "Shape.h"
33 #include "Util.h"
34 
35 using std::nothrow;
36 
37 class ShapeListItem : public SimpleItem,
38 					  public Observer {
39  public:
40 					ShapeListItem(Shape* s,
41 								  ShapeListView* listView)
42 						: SimpleItem(""),
43 						  shape(NULL),
44 						  fListView(listView)
45 					{
46 						SetClip(s);
47 					}
48 
49 	virtual			~ShapeListItem()
50 					{
51 						SetClip(NULL);
52 					}
53 
54 	virtual	void	ObjectChanged(const Observable* object)
55 					{
56 						UpdateText();
57 					}
58 
59 			void	SetClip(Shape* s)
60 					{
61 						if (s == shape)
62 							return;
63 
64 						if (shape) {
65 							shape->RemoveObserver(this);
66 							shape->Release();
67 						}
68 
69 						shape = s;
70 
71 						if (shape) {
72 							shape->Acquire();
73 							shape->AddObserver(this);
74 							UpdateText();
75 						}
76 					}
77 			void	UpdateText()
78 					{
79 						SetText(shape->Name());
80 						// :-/
81 						if (fListView->LockLooper()) {
82 							fListView->InvalidateItem(
83 								fListView->IndexOf(this));
84 							fListView->UnlockLooper();
85 						}
86 					}
87 
88 	Shape* 			shape;
89  private:
90 	ShapeListView*	fListView;
91 };
92 
93 // #pragma mark -
94 
95 enum {
96 	MSG_REMOVE						= 'rmsh',
97 	MSG_DUPLICATE					= 'dpsh',
98 	MSG_RESET_TRANSFORMATION		= 'rstr',
99 	MSG_FREEZE_TRANSFORMATION		= 'frzt',
100 
101 	MSG_DRAG_SHAPE					= 'drgs',
102 };
103 
104 // constructor
105 ShapeListView::ShapeListView(BRect frame,
106 							 const char* name,
107 							 BMessage* message, BHandler* target)
108 	: SimpleListView(frame, name,
109 					 NULL, B_MULTIPLE_SELECTION_LIST),
110 	  fMessage(message),
111 	  fShapeContainer(NULL),
112 	  fCommandStack(NULL)
113 {
114 	SetDragCommand(MSG_DRAG_SHAPE);
115 	SetTarget(target);
116 }
117 
118 // destructor
119 ShapeListView::~ShapeListView()
120 {
121 	_MakeEmpty();
122 	delete fMessage;
123 
124 	if (fShapeContainer)
125 		fShapeContainer->RemoveListener(this);
126 }
127 
128 // SelectionChanged
129 void
130 ShapeListView::SelectionChanged()
131 {
132 	SimpleListView::SelectionChanged();
133 
134 	if (!fSyncingToSelection) {
135 		ShapeListItem* item
136 			= dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(0)));
137 		if (fMessage) {
138 			BMessage message(*fMessage);
139 			message.AddPointer("shape", item ? (void*)item->shape : NULL);
140 			Invoke(&message);
141 		}
142 	}
143 
144 	_UpdateMenu();
145 }
146 
147 // MessageReceived
148 void
149 ShapeListView::MessageReceived(BMessage* message)
150 {
151 	switch (message->what) {
152 		case MSG_REMOVE:
153 			RemoveSelected();
154 			break;
155 		case MSG_DUPLICATE: {
156 			int32 count = CountSelectedItems();
157 			int32 index = 0;
158 			BList items;
159 			for (int32 i = 0; i < count; i++) {
160 				index = CurrentSelection(i);
161 				BListItem* item = ItemAt(index);
162 				if (item)
163 					items.AddItem((void*)item);
164 			}
165 			CopyItems(items, index + 1);
166 			break;
167 		}
168 		case MSG_RESET_TRANSFORMATION: {
169 			BList shapes;
170 			_GetSelectedShapes(shapes);
171 			int32 count = shapes.CountItems();
172 			if (count < 0)
173 				break;
174 
175 			Transformable* transformables[count];
176 			for (int32 i = 0; i < count; i++) {
177 				Shape* shape = (Shape*)shapes.ItemAtFast(i);
178 				transformables[i] = shape;
179 			}
180 
181 			ResetTransformationCommand* command =
182 				new ResetTransformationCommand(transformables, count);
183 
184 			fCommandStack->Perform(command);
185 			break;
186 		}
187 		case MSG_FREEZE_TRANSFORMATION: {
188 			BList shapes;
189 			_GetSelectedShapes(shapes);
190 			int32 count = shapes.CountItems();
191 			if (count < 0)
192 				break;
193 
194 			FreezeTransformationCommand* command =
195 				new FreezeTransformationCommand((Shape**)shapes.Items(),
196 					count);
197 
198 			fCommandStack->Perform(command);
199 			break;
200 		}
201 		default:
202 			SimpleListView::MessageReceived(message);
203 			break;
204 	}
205 }
206 
207 // MakeDragMessage
208 void
209 ShapeListView::MakeDragMessage(BMessage* message) const
210 {
211 	SimpleListView::MakeDragMessage(message);
212 	message->AddPointer("container", fShapeContainer);
213 	int32 count = CountSelectedItems();
214 	for (int32 i = 0; i < count; i++) {
215 		ShapeListItem* item = dynamic_cast<ShapeListItem*>(
216 			ItemAt(CurrentSelection(i)));
217 		if (item)
218 			message->AddPointer("shape", (void*)item->shape);
219 		else
220 			break;
221 	}
222 
223 //		message->AddInt32("be:actions", B_COPY_TARGET);
224 //		message->AddInt32("be:actions", B_TRASH_TARGET);
225 //
226 //		message->AddString("be:types", B_FILE_MIME_TYPE);
227 ////		message->AddString("be:filetypes", "");
228 ////		message->AddString("be:type_descriptions", "");
229 //
230 //		message->AddString("be:clip_name", item->shape->Name());
231 //
232 //		message->AddString("be:originator", "Icon-O-Matic");
233 //		message->AddPointer("be:originator_data", (void*)item->shape);
234 }
235 
236 // AcceptDragMessage
237 bool
238 ShapeListView::AcceptDragMessage(const BMessage* message) const
239 {
240 	return SimpleListView::AcceptDragMessage(message);
241 }
242 
243 // SetDropTargetRect
244 void
245 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where)
246 {
247 	SimpleListView::SetDropTargetRect(message, where);
248 }
249 
250 // #pragma mark -
251 
252 // MoveItems
253 void
254 ShapeListView::MoveItems(BList& items, int32 toIndex)
255 {
256 	if (!fCommandStack || !fShapeContainer)
257 		return;
258 
259 	int32 count = items.CountItems();
260 	Shape** shapes = new (nothrow) Shape*[count];
261 	if (!shapes)
262 		return;
263 
264 	for (int32 i = 0; i < count; i++) {
265 		ShapeListItem* item
266 			= dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i));
267 		shapes[i] = item ? item->shape : NULL;
268 	}
269 
270 	MoveShapesCommand* command
271 		= new (nothrow) MoveShapesCommand(fShapeContainer,
272 										  shapes, count, toIndex);
273 	if (!command) {
274 		delete[] shapes;
275 		return;
276 	}
277 
278 	fCommandStack->Perform(command);
279 }
280 
281 // CopyItems
282 void
283 ShapeListView::CopyItems(BList& items, int32 toIndex)
284 {
285 	if (!fCommandStack || !fShapeContainer)
286 		return;
287 
288 	int32 count = items.CountItems();
289 	Shape* shapes[count];
290 
291 	for (int32 i = 0; i < count; i++) {
292 		ShapeListItem* item
293 			= dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i));
294 		shapes[i] = item ? new (nothrow) Shape(*item->shape) : NULL;
295 	}
296 
297 	AddShapesCommand* command
298 		= new (nothrow) AddShapesCommand(fShapeContainer,
299 										 shapes, count, toIndex,
300 										 fSelection);
301 	if (!command) {
302 		for (int32 i = 0; i < count; i++)
303 			delete shapes[i];
304 		return;
305 	}
306 
307 	fCommandStack->Perform(command);
308 }
309 
310 // RemoveItemList
311 void
312 ShapeListView::RemoveItemList(BList& items)
313 {
314 	if (!fCommandStack || !fShapeContainer)
315 		return;
316 
317 	int32 count = items.CountItems();
318 	int32 indices[count];
319 	for (int32 i = 0; i < count; i++)
320 	 	indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
321 
322 	RemoveShapesCommand* command
323 		= new (nothrow) RemoveShapesCommand(fShapeContainer,
324 											indices, count);
325 	fCommandStack->Perform(command);
326 }
327 
328 // CloneItem
329 BListItem*
330 ShapeListView::CloneItem(int32 index) const
331 {
332 	if (ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index))) {
333 		return new ShapeListItem(item->shape,
334 								 const_cast<ShapeListView*>(this));
335 	}
336 	return NULL;
337 }
338 
339 // IndexOfSelectable
340 int32
341 ShapeListView::IndexOfSelectable(Selectable* selectable) const
342 {
343 	Shape* shape = dynamic_cast<Shape*>(selectable);
344 	if (!shape) {
345 		Transformer* transformer = dynamic_cast<Transformer*>(selectable);
346 		if (!transformer)
347 			return -1;
348 		for (int32 i = 0;
349 			 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
350 			 i++) {
351 			if (item->shape->HasTransformer(transformer))
352 				return i;
353 		}
354 	} else {
355 		for (int32 i = 0;
356 			 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
357 			 i++) {
358 			if (item->shape == shape)
359 				return i;
360 		}
361 	}
362 
363 	return -1;
364 }
365 
366 // SelectableFor
367 Selectable*
368 ShapeListView::SelectableFor(BListItem* item) const
369 {
370 	ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item);
371 	if (shapeItem)
372 		return shapeItem->shape;
373 	return NULL;
374 }
375 
376 // #pragma mark -
377 
378 // ShapeAdded
379 void
380 ShapeListView::ShapeAdded(Shape* shape, int32 index)
381 {
382 	// NOTE: we are in the thread that messed with the
383 	// ShapeContainer, so no need to lock the
384 	// container, when this is changed to asynchronous
385 	// notifications, then it would need to be read-locked!
386 	if (!LockLooper())
387 		return;
388 
389 	if (_AddShape(shape, index))
390 		Select(index);
391 
392 	UnlockLooper();
393 }
394 
395 // ShapeRemoved
396 void
397 ShapeListView::ShapeRemoved(Shape* shape)
398 {
399 	// NOTE: we are in the thread that messed with the
400 	// ShapeContainer, so no need to lock the
401 	// container, when this is changed to asynchronous
402 	// notifications, then it would need to be read-locked!
403 	if (!LockLooper())
404 		return;
405 
406 	// NOTE: we're only interested in Shape objects
407 	_RemoveShape(shape);
408 
409 	UnlockLooper();
410 }
411 
412 // #pragma mark -
413 
414 // SetMenu
415 void
416 ShapeListView::SetMenu(BMenu* menu)
417 {
418 	if (fMenu == menu)
419 		return;
420 
421 	fMenu = menu;
422 	if (fMenu == NULL)
423 		return;
424 
425 	BMessage* message = new BMessage(MSG_ADD_SHAPE);
426 	fAddEmptyMI = new BMenuItem("Add empty", message);
427 
428 	message = new BMessage(MSG_ADD_SHAPE);
429 	message->AddBool("path", true);
430 	fAddWidthPathMI = new BMenuItem("Add with path", message);
431 
432 	message = new BMessage(MSG_ADD_SHAPE);
433 	message->AddBool("style", true);
434 	fAddWidthStyleMI = new BMenuItem("Add with style", message);
435 
436 	message = new BMessage(MSG_ADD_SHAPE);
437 	message->AddBool("path", true);
438 	message->AddBool("style", true);
439 	fAddWidthPathAndStyleMI = new BMenuItem("Add with path & style", message);
440 
441 	fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE));
442 	fResetTransformationMI = new BMenuItem("Reset transformation",
443 		new BMessage(MSG_RESET_TRANSFORMATION));
444 	fFreezeTransformationMI = new BMenuItem("Freeze transformation",
445 		new BMessage(MSG_FREEZE_TRANSFORMATION));
446 
447 	fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE));
448 
449 
450 	fMenu->AddItem(fAddEmptyMI);
451 	fMenu->AddItem(fAddWidthPathMI);
452 	fMenu->AddItem(fAddWidthStyleMI);
453 	fMenu->AddItem(fAddWidthPathAndStyleMI);
454 
455 	fMenu->AddSeparatorItem();
456 
457 	fMenu->AddItem(fDuplicateMI);
458 	fMenu->AddItem(fResetTransformationMI);
459 	fMenu->AddItem(fFreezeTransformationMI);
460 
461 	fMenu->AddSeparatorItem();
462 
463 	fMenu->AddItem(fRemoveMI);
464 
465 	fDuplicateMI->SetTarget(this);
466 	fResetTransformationMI->SetTarget(this);
467 	fFreezeTransformationMI->SetTarget(this);
468 	fRemoveMI->SetTarget(this);
469 
470 	_UpdateMenu();
471 }
472 
473 // SetShapeContainer
474 void
475 ShapeListView::SetShapeContainer(ShapeContainer* container)
476 {
477 	if (fShapeContainer == container)
478 		return;
479 
480 	// detach from old container
481 	if (fShapeContainer)
482 		fShapeContainer->RemoveListener(this);
483 
484 	_MakeEmpty();
485 
486 	fShapeContainer = container;
487 
488 	if (!fShapeContainer)
489 		return;
490 
491 	fShapeContainer->AddListener(this);
492 
493 	// sync
494 	int32 count = fShapeContainer->CountShapes();
495 	for (int32 i = 0; i < count; i++)
496 		_AddShape(fShapeContainer->ShapeAtFast(i), i);
497 }
498 
499 // SetCommandStack
500 void
501 ShapeListView::SetCommandStack(CommandStack* stack)
502 {
503 	fCommandStack = stack;
504 }
505 
506 // #pragma mark -
507 
508 // _AddShape
509 bool
510 ShapeListView::_AddShape(Shape* shape, int32 index)
511 {
512 	if (shape)
513 		 return AddItem(new ShapeListItem(shape, this), index);
514 	return false;
515 }
516 
517 // _RemoveShape
518 bool
519 ShapeListView::_RemoveShape(Shape* shape)
520 {
521 	ShapeListItem* item = _ItemForShape(shape);
522 	if (item && RemoveItem(item)) {
523 		delete item;
524 		return true;
525 	}
526 	return false;
527 }
528 
529 // _ItemForShape
530 ShapeListItem*
531 ShapeListView::_ItemForShape(Shape* shape) const
532 {
533 	for (int32 i = 0;
534 		 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
535 		 i++) {
536 		if (item->shape == shape)
537 			return item;
538 	}
539 	return NULL;
540 }
541 
542 // _UpdateMenu
543 void
544 ShapeListView::_UpdateMenu()
545 {
546 	if (!fMenu)
547 		return;
548 
549 	bool gotSelection = CurrentSelection(0) >= 0;
550 
551 	fDuplicateMI->SetEnabled(gotSelection);
552 	fResetTransformationMI->SetEnabled(gotSelection);
553 	fFreezeTransformationMI->SetEnabled(gotSelection);
554 	fRemoveMI->SetEnabled(gotSelection);
555 }
556 
557 // _GetSelectedShapes
558 void
559 ShapeListView::_GetSelectedShapes(BList& shapes) const
560 {
561 	int32 count = CountSelectedItems();
562 	for (int32 i = 0; i < count; i++) {
563 		ShapeListItem* item = dynamic_cast<ShapeListItem*>(
564 			ItemAt(CurrentSelection(i)));
565 		if (item && item->shape) {
566 			if (!shapes.AddItem((void*)item->shape))
567 				break;
568 		}
569 	}
570 }
571