xref: /haiku/src/apps/icon-o-matic/gui/ShapeListView.cpp (revision 2f470aec1c92ce6917b8a903e343795dc77af41f)
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 "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 
133 	SimpleListView::SelectionChanged();
134 
135 	if (!fSyncingToSelection) {
136 		ShapeListItem* item
137 			= dynamic_cast<ShapeListItem*>(ItemAt(CurrentSelection(0)));
138 		if (fMessage) {
139 			BMessage message(*fMessage);
140 			message.AddPointer("shape", item ? (void*)item->shape : NULL);
141 			Invoke(&message);
142 		}
143 	}
144 
145 	_UpdateMenu();
146 }
147 
148 // MessageReceived
149 void
150 ShapeListView::MessageReceived(BMessage* message)
151 {
152 	switch (message->what) {
153 		case MSG_REMOVE:
154 			RemoveSelected();
155 			break;
156 		case MSG_DUPLICATE: {
157 			int32 count = CountSelectedItems();
158 			int32 index = 0;
159 			BList items;
160 			for (int32 i = 0; i < count; i++) {
161 				index = CurrentSelection(i);
162 				BListItem* item = ItemAt(index);
163 				if (item)
164 					items.AddItem((void*)item);
165 			}
166 			CopyItems(items, index + 1);
167 			break;
168 		}
169 		case MSG_RESET_TRANSFORMATION: {
170 			BList shapes;
171 			_GetSelectedShapes(shapes);
172 			int32 count = shapes.CountItems();
173 			if (count < 0)
174 				break;
175 
176 			Transformable* transformables[count];
177 			for (int32 i = 0; i < count; i++) {
178 				Shape* shape = (Shape*)shapes.ItemAtFast(i);
179 				transformables[i] = shape;
180 			}
181 
182 			ResetTransformationCommand* command =
183 				new ResetTransformationCommand(transformables, count);
184 
185 			fCommandStack->Perform(command);
186 			break;
187 		}
188 		case MSG_FREEZE_TRANSFORMATION: {
189 			BList shapes;
190 			_GetSelectedShapes(shapes);
191 			int32 count = shapes.CountItems();
192 			if (count < 0)
193 				break;
194 
195 			FreezeTransformationCommand* command =
196 				new FreezeTransformationCommand((Shape**)shapes.Items(),
197 					count);
198 
199 			fCommandStack->Perform(command);
200 			break;
201 		}
202 		default:
203 			SimpleListView::MessageReceived(message);
204 			break;
205 	}
206 }
207 
208 // MakeDragMessage
209 void
210 ShapeListView::MakeDragMessage(BMessage* message) const
211 {
212 	SimpleListView::MakeDragMessage(message);
213 	message->AddPointer("container", fShapeContainer);
214 	int32 count = CountSelectedItems();
215 	for (int32 i = 0; i < count; i++) {
216 		ShapeListItem* item = dynamic_cast<ShapeListItem*>(
217 			ItemAt(CurrentSelection(i)));
218 		if (item)
219 			message->AddPointer("shape", (void*)item->shape);
220 		else
221 			break;
222 	}
223 
224 //		message->AddInt32("be:actions", B_COPY_TARGET);
225 //		message->AddInt32("be:actions", B_TRASH_TARGET);
226 //
227 //		message->AddString("be:types", B_FILE_MIME_TYPE);
228 ////		message->AddString("be:filetypes", "");
229 ////		message->AddString("be:type_descriptions", "");
230 //
231 //		message->AddString("be:clip_name", item->shape->Name());
232 //
233 //		message->AddString("be:originator", "Icon-O-Matic");
234 //		message->AddPointer("be:originator_data", (void*)item->shape);
235 }
236 
237 // AcceptDragMessage
238 bool
239 ShapeListView::AcceptDragMessage(const BMessage* message) const
240 {
241 	return SimpleListView::AcceptDragMessage(message);
242 }
243 
244 // SetDropTargetRect
245 void
246 ShapeListView::SetDropTargetRect(const BMessage* message, BPoint where)
247 {
248 	SimpleListView::SetDropTargetRect(message, where);
249 }
250 
251 // #pragma mark -
252 
253 // MoveItems
254 void
255 ShapeListView::MoveItems(BList& items, int32 toIndex)
256 {
257 	if (!fCommandStack || !fShapeContainer)
258 		return;
259 
260 	int32 count = items.CountItems();
261 	Shape** shapes = new (nothrow) Shape*[count];
262 	if (!shapes)
263 		return;
264 
265 	for (int32 i = 0; i < count; i++) {
266 		ShapeListItem* item
267 			= dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i));
268 		shapes[i] = item ? item->shape : NULL;
269 	}
270 
271 	MoveShapesCommand* command
272 		= new (nothrow) MoveShapesCommand(fShapeContainer,
273 										  shapes, count, toIndex);
274 	if (!command) {
275 		delete[] shapes;
276 		return;
277 	}
278 
279 	fCommandStack->Perform(command);
280 }
281 
282 // CopyItems
283 void
284 ShapeListView::CopyItems(BList& items, int32 toIndex)
285 {
286 	if (!fCommandStack || !fShapeContainer)
287 		return;
288 
289 	int32 count = items.CountItems();
290 	Shape* shapes[count];
291 
292 	for (int32 i = 0; i < count; i++) {
293 		ShapeListItem* item
294 			= dynamic_cast<ShapeListItem*>((BListItem*)items.ItemAtFast(i));
295 		shapes[i] = item ? new (nothrow) Shape(*item->shape) : NULL;
296 	}
297 
298 	AddShapesCommand* command
299 		= new (nothrow) AddShapesCommand(fShapeContainer,
300 										 shapes, count, toIndex,
301 										 fSelection);
302 	if (!command) {
303 		for (int32 i = 0; i < count; i++)
304 			delete shapes[i];
305 		return;
306 	}
307 
308 	fCommandStack->Perform(command);
309 }
310 
311 // RemoveItemList
312 void
313 ShapeListView::RemoveItemList(BList& items)
314 {
315 	if (!fCommandStack || !fShapeContainer)
316 		return;
317 
318 	int32 count = items.CountItems();
319 	int32 indices[count];
320 	for (int32 i = 0; i < count; i++)
321 	 	indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
322 
323 	RemoveShapesCommand* command
324 		= new (nothrow) RemoveShapesCommand(fShapeContainer,
325 											indices, count);
326 	fCommandStack->Perform(command);
327 }
328 
329 // CloneItem
330 BListItem*
331 ShapeListView::CloneItem(int32 index) const
332 {
333 	if (ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(index))) {
334 		return new ShapeListItem(item->shape,
335 								 const_cast<ShapeListView*>(this));
336 	}
337 	return NULL;
338 }
339 
340 // IndexOfSelectable
341 int32
342 ShapeListView::IndexOfSelectable(Selectable* selectable) const
343 {
344 	Shape* shape = dynamic_cast<Shape*>(selectable);
345 	if (!shape) {
346 		Transformer* transformer = dynamic_cast<Transformer*>(selectable);
347 		if (!transformer)
348 			return -1;
349 		for (int32 i = 0;
350 			 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
351 			 i++) {
352 			if (item->shape->HasTransformer(transformer))
353 				return i;
354 		}
355 	} else {
356 		for (int32 i = 0;
357 			 ShapeListItem* item = dynamic_cast<ShapeListItem*>(ItemAt(i));
358 			 i++) {
359 			if (item->shape == shape)
360 				return i;
361 		}
362 	}
363 
364 	return -1;
365 }
366 
367 // SelectableFor
368 Selectable*
369 ShapeListView::SelectableFor(BListItem* item) const
370 {
371 	ShapeListItem* shapeItem = dynamic_cast<ShapeListItem*>(item);
372 	if (shapeItem)
373 		return shapeItem->shape;
374 	return NULL;
375 }
376 
377 // #pragma mark -
378 
379 // ShapeAdded
380 void
381 ShapeListView::ShapeAdded(Shape* shape, int32 index)
382 {
383 	// NOTE: we are in the thread that messed with the
384 	// ShapeContainer, so no need to lock the
385 	// container, when this is changed to asynchronous
386 	// notifications, then it would need to be read-locked!
387 	if (!LockLooper())
388 		return;
389 
390 	_AddShape(shape, 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