xref: /haiku/src/apps/icon-o-matic/gui/StyleListView.cpp (revision 3cb015b1ee509d69c643506e8ff573808c86dcfc)
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 "StyleListView.h"
10 
11 #include <stdio.h>
12 
13 #include <Application.h>
14 #include <ListItem.h>
15 #include <Menu.h>
16 #include <MenuItem.h>
17 #include <Message.h>
18 #include <Mime.h>
19 #include <Window.h>
20 
21 #include "AddStylesCommand.h"
22 #include "AssignStyleCommand.h"
23 #include "CurrentColor.h"
24 #include "CommandStack.h"
25 #include "Gradient.h"
26 #include "MoveStylesCommand.h"
27 #include "RemoveStylesCommand.h"
28 #include "Style.h"
29 #include "Observer.h"
30 #include "ResetTransformationCommand.h"
31 #include "Shape.h"
32 #include "ShapeContainer.h"
33 #include "Selection.h"
34 #include "Util.h"
35 
36 using std::nothrow;
37 
38 static const float kMarkWidth		= 14.0;
39 static const float kBorderOffset	= 3.0;
40 static const float kTextOffset		= 4.0;
41 
42 enum {
43 	MSG_ADD							= 'adst',
44 	MSG_REMOVE						= 'rmst',
45 	MSG_DUPLICATE					= 'dpst',
46 	MSG_RESET_TRANSFORMATION		= 'rstr',
47 };
48 
49 class StyleListItem : public SimpleItem,
50 					 public Observer {
51  public:
52 					StyleListItem(Style* s,
53 								  StyleListView* listView,
54 								  bool markEnabled)
55 						: SimpleItem(""),
56 						  style(NULL),
57 						  fListView(listView),
58 						  fMarkEnabled(markEnabled),
59 						  fMarked(false)
60 					{
61 						SetStyle(s);
62 					}
63 
64 	virtual			~StyleListItem()
65 					{
66 						SetStyle(NULL);
67 					}
68 
69 	// SimpleItem interface
70 	virtual	void	Draw(BView* owner, BRect itemFrame, uint32 flags)
71 	{
72 		SimpleItem::DrawBackground(owner, itemFrame, flags);
73 
74 		// text
75 		owner->SetHighColor(0, 0, 0, 255);
76 		font_height fh;
77 		owner->GetFontHeight(&fh);
78 		BString truncatedString(Text());
79 		owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE,
80 							  itemFrame.Width()
81 							  - kBorderOffset
82 							  - kMarkWidth
83 							  - kTextOffset
84 							  - kBorderOffset);
85 		float height = itemFrame.Height();
86 		float textHeight = fh.ascent + fh.descent;
87 		BPoint pos;
88 		pos.x = itemFrame.left
89 					+ kBorderOffset + kMarkWidth + kTextOffset;
90 		pos.y = itemFrame.top
91 					 + ceilf((height - textHeight) / 2.0 + fh.ascent);
92 		owner->DrawString(truncatedString.String(), pos);
93 
94 		if (!fMarkEnabled)
95 			return;
96 
97 		// mark
98 		BRect markRect = itemFrame;
99 		markRect.left += kBorderOffset;
100 		markRect.right = markRect.left + kMarkWidth;
101 		markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0;
102 		markRect.bottom = markRect.top + kMarkWidth;
103 		owner->SetHighColor(tint_color(owner->LowColor(), B_DARKEN_1_TINT));
104 		owner->StrokeRect(markRect);
105 		markRect.InsetBy(1, 1);
106 		owner->SetHighColor(tint_color(owner->LowColor(), 1.04));
107 		owner->FillRect(markRect);
108 		if (fMarked) {
109 			markRect.InsetBy(2, 2);
110 			owner->SetHighColor(tint_color(owner->LowColor(),
111 								B_DARKEN_4_TINT));
112 			owner->SetPenSize(2);
113 			owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom());
114 			owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop());
115 			owner->SetPenSize(1);
116 		}
117 	}
118 
119 	// Observer interface
120 	virtual	void	ObjectChanged(const Observable* object)
121 					{
122 						UpdateText();
123 					}
124 
125 	// StyleListItem
126 			void	SetStyle(Style* s)
127 					{
128 						if (s == style)
129 							return;
130 
131 						if (style) {
132 							style->RemoveObserver(this);
133 							style->Release();
134 						}
135 
136 						style = s;
137 
138 						if (style) {
139 							style->Acquire();
140 							style->AddObserver(this);
141 							UpdateText();
142 						}
143 					}
144 			void	UpdateText()
145 					{
146 						SetText(style->Name());
147 						Invalidate();
148 					}
149 
150 			void	SetMarkEnabled(bool enabled)
151 					{
152 						if (fMarkEnabled == enabled)
153 							return;
154 						fMarkEnabled = enabled;
155 						Invalidate();
156 					}
157 			void	SetMarked(bool marked)
158 					{
159 						if (fMarked == marked)
160 							return;
161 						fMarked = marked;
162 						Invalidate();
163 					}
164 
165 			void Invalidate()
166 					{
167 						// :-/
168 						if (fListView->LockLooper()) {
169 							fListView->InvalidateItem(
170 								fListView->IndexOf(this));
171 							fListView->UnlockLooper();
172 						}
173 					}
174 
175 	Style* 	style;
176  private:
177 	StyleListView*	fListView;
178 	bool			fMarkEnabled;
179 	bool			fMarked;
180 };
181 
182 
183 class ShapeStyleListener : public ShapeListener,
184 						   public ShapeContainerListener {
185  public:
186 	ShapeStyleListener(StyleListView* listView)
187 		: fListView(listView),
188 		  fShape(NULL)
189 	{
190 	}
191 	virtual ~ShapeStyleListener()
192 	{
193 		SetShape(NULL);
194 	}
195 
196 	// ShapeListener interface
197 	virtual	void TransformerAdded(Transformer* t, int32 index) {}
198 	virtual	void TransformerRemoved(Transformer* t) {}
199 
200 	virtual void StyleChanged(Style* oldStyle, Style* newStyle)
201 	{
202 		fListView->_SetStyleMarked(oldStyle, false);
203 		fListView->_SetStyleMarked(newStyle, true);
204 	}
205 
206 	// ShapeContainerListener interface
207 	virtual void ShapeAdded(Shape* shape, int32 index) {}
208 	virtual void ShapeRemoved(Shape* shape)
209 	{
210 		fListView->SetCurrentShape(NULL);
211 	}
212 
213 	// ShapeStyleListener
214 	void SetShape(Shape* shape)
215 	{
216 		if (fShape == shape)
217 			return;
218 
219 		if (fShape)
220 			fShape->RemoveListener(this);
221 
222 		fShape = shape;
223 
224 		if (fShape)
225 			fShape->AddListener(this);
226 	}
227 
228 	Shape* CurrentShape() const
229 	{
230 		return fShape;
231 	}
232 
233  private:
234 	StyleListView*	fListView;
235 	Shape*			fShape;
236 };
237 
238 // #pragma mark -
239 
240 // constructor
241 StyleListView::StyleListView(BRect frame,
242 						   const char* name,
243 						   BMessage* message, BHandler* target)
244 	: SimpleListView(frame, name,
245 					 NULL, B_SINGLE_SELECTION_LIST),
246 	  fMessage(message),
247 	  fStyleContainer(NULL),
248 	  fShapeContainer(NULL),
249 	  fCommandStack(NULL),
250 
251 	  fCurrentShape(NULL),
252 	  fShapeListener(new ShapeStyleListener(this)),
253 
254 	  fMenu(NULL)
255 {
256 	SetTarget(target);
257 }
258 
259 // destructor
260 StyleListView::~StyleListView()
261 {
262 	_MakeEmpty();
263 	delete fMessage;
264 
265 	if (fStyleContainer)
266 		fStyleContainer->RemoveListener(this);
267 
268 	if (fShapeContainer)
269 		fShapeContainer->RemoveListener(fShapeListener);
270 
271 	delete fShapeListener;
272 }
273 
274 // #pragma mark -
275 
276 // MessageReceived
277 void
278 StyleListView::MessageReceived(BMessage* message)
279 {
280 	switch (message->what) {
281 		case MSG_ADD: {
282 			Style* style;
283 			AddStylesCommand* command;
284 			new_style(CurrentColor::Default()->Color(),
285 					  fStyleContainer, &style, &command);
286 			fCommandStack->Perform(command);
287 			break;
288 		}
289 		case MSG_REMOVE:
290 			RemoveSelected();
291 			break;
292 		case MSG_DUPLICATE: {
293 			int32 count = CountSelectedItems();
294 			int32 index = 0;
295 			BList items;
296 			for (int32 i = 0; i < count; i++) {
297 				index = CurrentSelection(i);
298 				BListItem* item = ItemAt(index);
299 				if (item)
300 					items.AddItem((void*)item);
301 			}
302 			CopyItems(items, index + 1);
303 			break;
304 		}
305 		case MSG_RESET_TRANSFORMATION: {
306 			int32 count = CountSelectedItems();
307 			BList gradients;
308 			for (int32 i = 0; i < count; i++) {
309 				StyleListItem* item = dynamic_cast<StyleListItem*>(
310 					ItemAt(CurrentSelection(i)));
311 				if (item && item->style && item->style->Gradient())
312 					if (!gradients.AddItem(
313 							(void*)item->style->Gradient()))
314 						break;
315 			}
316 			count = gradients.CountItems();
317 			if (count < 0)
318 				break;
319 
320 			Transformable* transformables[count];
321 			for (int32 i = 0; i < count; i++) {
322 				Gradient* gradient = (Gradient*)gradients.ItemAtFast(i);
323 				transformables[i] = gradient;
324 			}
325 
326 			ResetTransformationCommand* command =
327 				new ResetTransformationCommand(transformables, count);
328 
329 			fCommandStack->Perform(command);
330 			break;
331 		}
332 		default:
333 			SimpleListView::MessageReceived(message);
334 			break;
335 	}
336 }
337 
338 // SelectionChanged
339 void
340 StyleListView::SelectionChanged()
341 {
342 	SimpleListView::SelectionChanged();
343 
344 	if (!fSyncingToSelection) {
345 		// NOTE: single selection list
346 		StyleListItem* item
347 			= dynamic_cast<StyleListItem*>(ItemAt(CurrentSelection(0)));
348 		if (fMessage) {
349 			BMessage message(*fMessage);
350 			message.AddPointer("style", item ? (void*)item->style : NULL);
351 			Invoke(&message);
352 		}
353 	}
354 
355 	_UpdateMenu();
356 }
357 
358 // MouseDown
359 void
360 StyleListView::MouseDown(BPoint where)
361 {
362 	if (!fCurrentShape) {
363 		SimpleListView::MouseDown(where);
364 		return;
365 	}
366 
367 	bool handled = false;
368 	int32 index = IndexOf(where);
369 	StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index));
370 	if (item) {
371 		BRect itemFrame(ItemFrame(index));
372 		itemFrame.right = itemFrame.left
373 							+ kBorderOffset + kMarkWidth
374 							+ kTextOffset / 2.0;
375 		Style* style = item->style;
376 		if (itemFrame.Contains(where)) {
377 			// set the style on the shape
378 			if (fCommandStack) {
379 				::Command* command = new AssignStyleCommand(
380 											fCurrentShape, style);
381 				fCommandStack->Perform(command);
382 			} else {
383 				fCurrentShape->SetStyle(style);
384 			}
385 			handled = true;
386 		}
387 	}
388 
389 	if (!handled)
390 		SimpleListView::MouseDown(where);
391 }
392 
393 // MakeDragMessage
394 void
395 StyleListView::MakeDragMessage(BMessage* message) const
396 {
397 	SimpleListView::MakeDragMessage(message);
398 	message->AddPointer("container", fStyleContainer);
399 	int32 count = CountSelectedItems();
400 	for (int32 i = 0; i < count; i++) {
401 		StyleListItem* item = dynamic_cast<StyleListItem*>(
402 			ItemAt(CurrentSelection(i)));
403 		if (item)
404 			message->AddPointer("style", (void*)item->style);
405 		else
406 			break;
407 	}
408 }
409 
410 // AcceptDragMessage
411 bool
412 StyleListView::AcceptDragMessage(const BMessage* message) const
413 {
414 	return SimpleListView::AcceptDragMessage(message);
415 }
416 
417 // SetDropTargetRect
418 void
419 StyleListView::SetDropTargetRect(const BMessage* message, BPoint where)
420 {
421 	SimpleListView::SetDropTargetRect(message, where);
422 }
423 
424 // MoveItems
425 void
426 StyleListView::MoveItems(BList& items, int32 toIndex)
427 {
428 	if (!fCommandStack || !fStyleContainer)
429 		return;
430 
431 	int32 count = items.CountItems();
432 	Style** styles = new (nothrow) Style*[count];
433 	if (!styles)
434 		return;
435 
436 	for (int32 i = 0; i < count; i++) {
437 		StyleListItem* item
438 			= dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i));
439 		styles[i] = item ? item->style : NULL;
440 	}
441 
442 	MoveStylesCommand* command
443 		= new (nothrow) MoveStylesCommand(fStyleContainer,
444 										  styles, count, toIndex);
445 	if (!command) {
446 		delete[] styles;
447 		return;
448 	}
449 
450 	fCommandStack->Perform(command);
451 }
452 
453 // CopyItems
454 void
455 StyleListView::CopyItems(BList& items, int32 toIndex)
456 {
457 	if (!fCommandStack || !fStyleContainer)
458 		return;
459 
460 	int32 count = items.CountItems();
461 	Style* styles[count];
462 
463 	for (int32 i = 0; i < count; i++) {
464 		StyleListItem* item
465 			= dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i));
466 		styles[i] = item ? new (nothrow) Style(*item->style) : NULL;
467 	}
468 
469 	AddStylesCommand* command
470 		= new (nothrow) AddStylesCommand(fStyleContainer,
471 										 styles, count, toIndex);
472 	if (!command) {
473 		for (int32 i = 0; i < count; i++)
474 			delete styles[i];
475 		return;
476 	}
477 
478 	fCommandStack->Perform(command);
479 }
480 
481 // RemoveItemList
482 void
483 StyleListView::RemoveItemList(BList& items)
484 {
485 	if (!fCommandStack || !fStyleContainer)
486 		return;
487 
488 	int32 count = items.CountItems();
489 	Style* styles[count];
490 	for (int32 i = 0; i < count; i++) {
491 		StyleListItem* item = dynamic_cast<StyleListItem*>(
492 			(BListItem*)items.ItemAtFast(i));
493 		if (item)
494 			styles[i] = item->style;
495 		else
496 			styles[i] = NULL;
497 	}
498 
499 	RemoveStylesCommand* command
500 		= new (nothrow) RemoveStylesCommand(fStyleContainer,
501 											styles, count);
502 	fCommandStack->Perform(command);
503 }
504 
505 // CloneItem
506 BListItem*
507 StyleListView::CloneItem(int32 index) const
508 {
509 	if (StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index))) {
510 		return new StyleListItem(item->style,
511 								 const_cast<StyleListView*>(this),
512 								 fCurrentShape != NULL);
513 	}
514 	return NULL;
515 }
516 
517 // IndexOfSelectable
518 int32
519 StyleListView::IndexOfSelectable(Selectable* selectable) const
520 {
521 	Style* style = dynamic_cast<Style*>(selectable);
522 	if (!style)
523 		return -1;
524 
525 	for (int32 i = 0;
526 		 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
527 		 i++) {
528 		if (item->style == style)
529 			return i;
530 	}
531 
532 	return -1;
533 }
534 
535 // SelectableFor
536 Selectable*
537 StyleListView::SelectableFor(BListItem* item) const
538 {
539 	StyleListItem* styleItem = dynamic_cast<StyleListItem*>(item);
540 	if (styleItem)
541 		return styleItem->style;
542 	return NULL;
543 }
544 
545 // #pragma mark -
546 
547 // StyleAdded
548 void
549 StyleListView::StyleAdded(Style* style, int32 index)
550 {
551 	// NOTE: we are in the thread that messed with the
552 	// StyleContainer, so no need to lock the
553 	// container, when this is changed to asynchronous
554 	// notifications, then it would need to be read-locked!
555 	if (!LockLooper())
556 		return;
557 
558 	// NOTE: shapes are always added at the end
559 	// of the list, so the sorting is synced...
560 	if (_AddStyle(style, index))
561 		Select(CountItems() - 1);
562 
563 	UnlockLooper();
564 }
565 
566 // StyleRemoved
567 void
568 StyleListView::StyleRemoved(Style* style)
569 {
570 	// NOTE: we are in the thread that messed with the
571 	// StyleContainer, so no need to lock the
572 	// container, when this is changed to asynchronous
573 	// notifications, then it would need to be read-locked!
574 	if (!LockLooper())
575 		return;
576 
577 	// NOTE: we're only interested in Style objects
578 	_RemoveStyle(style);
579 
580 	UnlockLooper();
581 }
582 
583 // #pragma mark -
584 
585 // SetMenu
586 void
587 StyleListView::SetMenu(BMenu* menu)
588 {
589 	if (fMenu == menu)
590 		return;
591 
592 	fMenu = menu;
593 	if (fMenu == NULL)
594 		return;
595 
596 	fAddMI = new BMenuItem("Add", new BMessage(MSG_ADD));
597 	fMenu->AddItem(fAddMI);
598 
599 	fMenu->AddSeparatorItem();
600 
601 	fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE));
602 	fMenu->AddItem(fDuplicateMI);
603 
604 	fResetTransformationMI = new BMenuItem("Reset Transformation",
605 		new BMessage(MSG_RESET_TRANSFORMATION));
606 	fMenu->AddItem(fResetTransformationMI);
607 
608 	fMenu->AddSeparatorItem();
609 
610 	fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE));
611 	fMenu->AddItem(fRemoveMI);
612 
613 	fMenu->SetTargetForItems(this);
614 
615 	_UpdateMenu();
616 }
617 
618 // SetStyleContainer
619 void
620 StyleListView::SetStyleContainer(StyleContainer* container)
621 {
622 	if (fStyleContainer == container)
623 		return;
624 
625 	// detach from old container
626 	if (fStyleContainer)
627 		fStyleContainer->RemoveListener(this);
628 
629 	_MakeEmpty();
630 
631 	fStyleContainer = container;
632 
633 	if (!fStyleContainer)
634 		return;
635 
636 	fStyleContainer->AddListener(this);
637 
638 	// sync
639 	int32 count = fStyleContainer->CountStyles();
640 	for (int32 i = 0; i < count; i++)
641 		_AddStyle(fStyleContainer->StyleAtFast(i), i);
642 }
643 
644 // SetShapeContainer
645 void
646 StyleListView::SetShapeContainer(ShapeContainer* container)
647 {
648 	if (fShapeContainer == container)
649 		return;
650 
651 	// detach from old container
652 	if (fShapeContainer)
653 		fShapeContainer->RemoveListener(fShapeListener);
654 
655 	fShapeContainer = container;
656 
657 	if (fShapeContainer)
658 		fShapeContainer->AddListener(fShapeListener);
659 }
660 
661 // SetCommandStack
662 void
663 StyleListView::SetCommandStack(CommandStack* stack)
664 {
665 	fCommandStack = stack;
666 }
667 
668 // SetCurrentShape
669 void
670 StyleListView::SetCurrentShape(Shape* shape)
671 {
672 	if (fCurrentShape == shape)
673 		return;
674 
675 	fCurrentShape = shape;
676 	fShapeListener->SetShape(shape);
677 
678 	_UpdateMarks();
679 }
680 
681 // #pragma mark -
682 
683 // _AddStyle
684 bool
685 StyleListView::_AddStyle(Style* style, int32 index)
686 {
687 	if (style) {
688 		 return AddItem(new StyleListItem(
689 		 	style, this, fCurrentShape != NULL), index);
690 	}
691 	return false;
692 }
693 
694 // _RemoveStyle
695 bool
696 StyleListView::_RemoveStyle(Style* style)
697 {
698 	StyleListItem* item = _ItemForStyle(style);
699 	if (item && RemoveItem(item)) {
700 		delete item;
701 		return true;
702 	}
703 	return false;
704 }
705 
706 // _ItemForStyle
707 StyleListItem*
708 StyleListView::_ItemForStyle(Style* style) const
709 {
710 	for (int32 i = 0;
711 		 StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
712 		 i++) {
713 		if (item->style == style)
714 			return item;
715 	}
716 	return NULL;
717 }
718 
719 // #pragma mark -
720 
721 // _UpdateMarks
722 void
723 StyleListView::_UpdateMarks()
724 {
725 	int32 count = CountItems();
726 	if (fCurrentShape) {
727 		// enable display of marks and mark items whoes
728 		// style is contained in fCurrentShape
729 		for (int32 i = 0; i < count; i++) {
730 			StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
731 			if (!item)
732 				continue;
733 			item->SetMarkEnabled(true);
734 			item->SetMarked(fCurrentShape->Style() == item->style);
735 		}
736 	} else {
737 		// disable display of marks
738 		for (int32 i = 0; i < count; i++) {
739 			StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
740 			if (!item)
741 				continue;
742 			item->SetMarkEnabled(false);
743 		}
744 	}
745 
746 	Invalidate();
747 }
748 
749 // _SetStyleMarked
750 void
751 StyleListView::_SetStyleMarked(Style* style, bool marked)
752 {
753 	if (StyleListItem* item = _ItemForStyle(style)) {
754 		item->SetMarked(marked);
755 	}
756 }
757 
758 // _UpdateMenu
759 void
760 StyleListView::_UpdateMenu()
761 {
762 	if (!fMenu)
763 		return;
764 
765 	bool gotSelection = CurrentSelection(0) >= 0;
766 
767 	fDuplicateMI->SetEnabled(gotSelection);
768 	// TODO: only enable fResetTransformationMI if styles
769 	// with gradients are selected!
770 	fResetTransformationMI->SetEnabled(gotSelection);
771 	fRemoveMI->SetEnabled(gotSelection);
772 }
773 
774