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