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