1/*
2 * Copyright 2006-2009, 2023, 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 *		Zardshard
8 */
9
10#include "TransformerListView.h"
11
12#include <new>
13#include <stdio.h>
14
15#include <Application.h>
16#include <Catalog.h>
17#include <ListItem.h>
18#include <Locale.h>
19#include <Menu.h>
20#include <MenuItem.h>
21#include <Mime.h>
22#include <Message.h>
23#include <Window.h>
24
25#include "AddTransformersCommand.h"
26#include "CommandStack.h"
27#include "MoveTransformersCommand.h"
28#include "ReferenceImage.h"
29#include "RemoveTransformersCommand.h"
30#include "Transformer.h"
31#include "TransformerFactory.h"
32#include "Observer.h"
33#include "Selection.h"
34
35
36#undef B_TRANSLATION_CONTEXT
37#define B_TRANSLATION_CONTEXT "Icon-O-Matic-TransformersList"
38
39
40using std::nothrow;
41
42
43class TransformerItem : public SimpleItem,
44						public Observer {
45 public:
46					TransformerItem(Transformer* t, TransformerListView* listView)
47						: SimpleItem(t->Name()),
48						  transformer(NULL),
49						  fListView(listView)
50					{
51						SetTransformer(t);
52					}
53
54	virtual			~TransformerItem()
55					{
56						SetTransformer(NULL);
57					}
58
59	// Observer interface
60	virtual	void	ObjectChanged(const Observable* object)
61					{
62						UpdateText();
63					}
64
65	// TransformerItem
66			void	SetTransformer(Transformer* t)
67					{
68						if (t == transformer)
69							return;
70
71						if (transformer) {
72							transformer->RemoveObserver(this);
73							transformer->ReleaseReference();
74						}
75
76						transformer = t;
77
78						if (transformer) {
79							transformer->AcquireReference();
80							transformer->AddObserver(this);
81							UpdateText();
82						}
83					}
84			void	UpdateText()
85					{
86						SetText(transformer->Name());
87						// :-/
88						if (fListView->LockLooper()) {
89							fListView->InvalidateItem(
90								fListView->IndexOf(this));
91							fListView->UnlockLooper();
92						}
93					}
94
95	Transformer* 	transformer;
96 private:
97	TransformerListView* fListView;
98};
99
100
101// #pragma mark -
102
103
104enum {
105	MSG_DRAG_TRANSFORMER			= 'drgt',
106	MSG_ADD_TRANSFORMER				= 'adtr',
107};
108
109
110TransformerListView::TransformerListView(BRect frame, const char* name,
111										 BMessage* message, BHandler* target)
112	: SimpleListView(frame, name,
113					 NULL, B_MULTIPLE_SELECTION_LIST),
114	  fMessage(message),
115	  fShape(NULL),
116	  fCommandStack(NULL)
117{
118	SetDragCommand(MSG_DRAG_TRANSFORMER);
119	SetTarget(target);
120}
121
122
123TransformerListView::~TransformerListView()
124{
125	_MakeEmpty();
126	delete fMessage;
127
128	if (fShape)
129		fShape->Transformers()->RemoveListener(this);
130}
131
132
133void
134TransformerListView::Draw(BRect updateRect)
135{
136	SimpleListView::Draw(updateRect);
137
138	if (fShape)
139		return;
140
141	// display helpful messages
142	const char* message1 = B_TRANSLATE_COMMENT("Click on a shape above",
143 		"Empty transformers list - 1st line");
144	const char* message2 = B_TRANSLATE_COMMENT("to attach transformers.",
145 		"Empty transformers list - 2nd line");
146
147	// Dark Themes
148	rgb_color lowColor = LowColor();
149	if (lowColor.red + lowColor.green + lowColor.blue > 128 * 3)
150		SetHighColor(tint_color(LowColor(), B_DARKEN_2_TINT));
151	else
152		SetHighColor(tint_color(LowColor(), B_LIGHTEN_2_TINT));
153
154	font_height fh;
155	GetFontHeight(&fh);
156	BRect b(Bounds());
157
158	BPoint middle;
159	float textHeight = (fh.ascent + fh.descent) * 1.5;
160	middle.y = (b.top + b.bottom - textHeight) / 2.0;
161	middle.x = (b.left + b.right - StringWidth(message1)) / 2.0;
162	DrawString(message1, middle);
163
164	middle.y += textHeight;
165	middle.x = (b.left + b.right - StringWidth(message2)) / 2.0;
166	DrawString(message2, middle);
167}
168
169
170void
171TransformerListView::SelectionChanged()
172{
173	if (CountSelectedItems() > 0)
174		SimpleListView::SelectionChanged();
175	// else
176	// TODO: any selected transformer will still be visible in the
177	// PropertyListView
178
179	if (!fSyncingToSelection) {
180		TransformerItem* item
181			= dynamic_cast<TransformerItem*>(ItemAt(CurrentSelection(0)));
182		if (fMessage) {
183			BMessage message(*fMessage);
184			message.AddPointer("transformer", item ? (void*)item->transformer : NULL);
185			Invoke(&message);
186		}
187	}
188}
189
190
191void
192TransformerListView::MessageReceived(BMessage* message)
193{
194	switch (message->what) {
195		case MSG_ADD_TRANSFORMER: {
196			if (!fShape || !fCommandStack)
197				break;
198
199			uint32 type;
200			if (message->FindInt32("type", (int32*)&type) < B_OK)
201				break;
202
203			Transformer* transformer
204				= TransformerFactory::TransformerFor(type, fShape->VertexSource(), fShape);
205			if (!transformer)
206				break;
207
208			Transformer* transformers[1];
209			transformers[0] = transformer;
210			::Command* command = new (nothrow) AddTransformersCommand(
211				fShape->Transformers(), transformers, 1, fShape->Transformers()->CountItems());
212
213			if (!command)
214				delete transformer;
215
216			fCommandStack->Perform(command);
217			break;
218		}
219		default:
220			SimpleListView::MessageReceived(message);
221			break;
222	}
223}
224
225
226status_t
227TransformerListView::ArchiveSelection(BMessage* into, bool deep) const
228{
229	into->what = TransformerListView::kSelectionArchiveCode;
230
231	int32 count = CountSelectedItems();
232	for (int32 i = 0; i < count; i++) {
233		TransformerItem* item = dynamic_cast<TransformerItem*>(
234			ItemAt(CurrentSelection(i)));
235		if (item != NULL) {
236			BMessage archive;
237			if (item->transformer->Archive(&archive, deep) == B_OK)
238				into->AddMessage("transformer", &archive);
239		} else
240			return B_ERROR;
241	}
242
243	return B_OK;
244}
245
246
247bool
248TransformerListView::InstantiateSelection(const BMessage* archive, int32 dropIndex)
249{
250	if (archive->what != TransformerListView::kSelectionArchiveCode
251		|| fCommandStack == NULL || fShape == NULL)
252		return false;
253
254	// Drag may have come from another instance, like in another window.
255	// Reconstruct the Styles from the archive and add them at the drop
256	// index.
257	int index = 0;
258	BList transformers;
259	while (true) {
260		BMessage transformerArchive;
261		if (archive->FindMessage("transformer", index, &transformerArchive) != B_OK)
262			break;
263
264		Transformer* transformer = TransformerFactory::TransformerFor(
265			&transformerArchive, fShape->VertexSource(), fShape);
266		if (transformer == NULL)
267			break;
268
269		if (!transformers.AddItem(transformer)) {
270			delete transformer;
271			break;
272		}
273
274		index++;
275	}
276
277	int32 count = transformers.CountItems();
278	if (count == 0)
279		return false;
280
281	AddTransformersCommand* command = new(nothrow) AddTransformersCommand(
282		fShape->Transformers(), (Transformer**)transformers.Items(), count, dropIndex);
283	if (command == NULL) {
284		for (int32 i = 0; i < count; i++)
285			delete (Transformer*)transformers.ItemAtFast(i);
286		return false;
287	}
288
289	fCommandStack->Perform(command);
290
291	return true;
292}
293
294
295// #pragma mark -
296
297
298void
299TransformerListView::MoveItems(BList& items, int32 toIndex)
300{
301	if (!fCommandStack || !fShape)
302		return;
303
304	int32 count = items.CountItems();
305	Transformer** transformers = new (nothrow) Transformer*[count];
306	if (!transformers)
307		return;
308
309	for (int32 i = 0; i < count; i++) {
310		TransformerItem* item
311			= dynamic_cast<TransformerItem*>((BListItem*)items.ItemAtFast(i));
312		transformers[i] = item ? item->transformer : NULL;
313	}
314
315	MoveTransformersCommand* command
316		= new (nothrow) MoveTransformersCommand(
317			fShape->Transformers(), transformers, count, toIndex);
318	if (!command) {
319		delete[] transformers;
320		return;
321	}
322
323	fCommandStack->Perform(command);
324}
325
326
327void
328TransformerListView::CopyItems(BList& items, int32 toIndex)
329{
330	MoveItems(items, toIndex);
331	// TODO: allow copying items
332}
333
334
335void
336TransformerListView::RemoveItemList(BList& items)
337{
338	if (!fCommandStack || !fShape)
339		return;
340
341	int32 count = items.CountItems();
342	int32 indices[count];
343	for (int32 i = 0; i < count; i++)
344		indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
345
346	RemoveTransformersCommand* command
347		= new (nothrow) RemoveTransformersCommand(fShape->Transformers(), indices, count);
348	fCommandStack->Perform(command);
349}
350
351
352BListItem*
353TransformerListView::CloneItem(int32 index) const
354{
355	if (TransformerItem* item = dynamic_cast<TransformerItem*>(ItemAt(index))) {
356		return new TransformerItem(item->transformer,
357								   const_cast<TransformerListView*>(this));
358	}
359	return NULL;
360}
361
362
363int32
364TransformerListView::IndexOfSelectable(Selectable* selectable) const
365{
366	Transformer* transformer = dynamic_cast<Transformer*>(selectable);
367	if (!transformer)
368		return -1;
369
370	for (int32 i = 0;
371		 TransformerItem* item = dynamic_cast<TransformerItem*>(ItemAt(i));
372		 i++) {
373		if (item->transformer == transformer)
374			return i;
375	}
376
377	return -1;
378}
379
380
381Selectable*
382TransformerListView::SelectableFor(BListItem* item) const
383{
384	TransformerItem* transformerItem = dynamic_cast<TransformerItem*>(item);
385	if (transformerItem)
386		return transformerItem->transformer;
387	return NULL;
388}
389
390// #pragma mark -
391
392
393void
394TransformerListView::ItemAdded(Transformer* transformer, int32 index)
395{
396	// NOTE: we are in the thread that messed with the
397	// Shape, so no need to lock the document, when this is
398	// changed to asynchronous notifications, then it would
399	// need to be read-locked!
400	if (!LockLooper())
401		return;
402
403	_AddTransformer(transformer, index);
404
405	UnlockLooper();
406}
407
408
409void
410TransformerListView::ItemRemoved(Transformer* transformer)
411{
412	// NOTE: we are in the thread that messed with the
413	// Shape, so no need to lock the document, when this is
414	// changed to asynchronous notifications, then it would
415	// need to be read-locked!
416	if (!LockLooper())
417		return;
418
419	_RemoveTransformer(transformer);
420
421	UnlockLooper();
422}
423
424
425// #pragma mark -
426
427
428void
429TransformerListView::SetMenu(BMenu* menu)
430{
431	if (fMenu == menu)
432		return;
433
434	fMenu = menu;
435	if (fMenu == NULL)
436		return;
437
438	BMenu* addMenu = new BMenu(B_TRANSLATE("Add"));
439
440	// Keep translated strings that were brought in from another file
441#undef B_TRANSLATION_CONTEXT
442#define B_TRANSLATION_CONTEXT "Transformation"
443	BMessage* message = new BMessage(MSG_ADD_TRANSFORMER);
444	message->AddInt32("type", CONTOUR_TRANSFORMER);
445	fContourMI = new BMenuItem(B_TRANSLATE("Contour"), message);
446
447	message = new BMessage(MSG_ADD_TRANSFORMER);
448	message->AddInt32("type", STROKE_TRANSFORMER);
449	fStrokeMI = new BMenuItem(B_TRANSLATE("Stroke"), message);
450
451	message = new BMessage(MSG_ADD_TRANSFORMER);
452	message->AddInt32("type", PERSPECTIVE_TRANSFORMER);
453	fPerspectiveMI = new BMenuItem(B_TRANSLATE("Perspective"), message);
454
455	// message = new BMessage(MSG_ADD_TRANSFORMER);
456	// message->AddInt32("type", AFFINE_TRANSFORMER);
457	// fTransformationMI = new BMenuItem(B_TRANSLATE("Transformation"), message);
458#undef B_TRANSLATION_CONTEXT
459#define B_TRANSLATION_CONTEXT "Icon-O-Matic-TransformersList"
460
461	addMenu->AddItem(fContourMI);
462	addMenu->AddItem(fStrokeMI);
463	addMenu->AddItem(fPerspectiveMI);
464
465	addMenu->SetTargetForItems(this);
466	fMenu->AddItem(addMenu);
467
468	_UpdateMenu();
469}
470
471
472void
473TransformerListView::SetShape(Shape* shape)
474{
475	if (fShape == shape)
476		return;
477
478	// detach from old container
479	if (fShape)
480		fShape->Transformers()->RemoveListener(this);
481
482	_MakeEmpty();
483
484	fShape = shape;
485
486	if (fShape) {
487		fShape->Transformers()->AddListener(this);
488
489		int32 count = fShape->Transformers()->CountItems();
490		for (int32 i = 0; i < count; i++)
491			_AddTransformer(fShape->Transformers()->ItemAtFast(i), i);
492	}
493
494	_UpdateMenu();
495}
496
497
498void
499TransformerListView::SetCommandStack(CommandStack* stack)
500{
501	fCommandStack = stack;
502}
503
504
505// #pragma mark -
506
507
508bool
509TransformerListView::_AddTransformer(Transformer* transformer, int32 index)
510{
511	if (transformer)
512		 return AddItem(new TransformerItem(transformer, this), index);
513	return false;
514}
515
516
517bool
518TransformerListView::_RemoveTransformer(Transformer* transformer)
519{
520	TransformerItem* item = _ItemForTransformer(transformer);
521	if (item && RemoveItem(item)) {
522		delete item;
523		return true;
524	}
525	return false;
526}
527
528
529TransformerItem*
530TransformerListView::_ItemForTransformer(Transformer* transformer) const
531{
532	for (int32 i = 0;
533		 TransformerItem* item = dynamic_cast<TransformerItem*>(ItemAt(i));
534		 i++) {
535		if (item->transformer == transformer)
536			return item;
537	}
538	return NULL;
539}
540
541
542void
543TransformerListView::_UpdateMenu()
544{
545	fMenu->SetEnabled(fShape != NULL);
546
547	bool isReferenceImage = dynamic_cast<ReferenceImage*>(fShape) != NULL;
548	fContourMI->SetEnabled(!isReferenceImage);
549	fStrokeMI->SetEnabled(!isReferenceImage);
550}
551