xref: /haiku/src/apps/icon-o-matic/generic/property/view/PropertyListView.cpp (revision e1c4049fed1047bdb957b0529e1921e97ef94770)
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 "PropertyListView.h"
10 
11 #include <stdio.h>
12 #include <string.h>
13 
14 #include <Catalog.h>
15 #include <Clipboard.h>
16 #ifdef __HAIKU__
17 #  include <LayoutUtils.h>
18 #endif
19 #include <Locale.h>
20 #include <Menu.h>
21 #include <MenuItem.h>
22 #include <Message.h>
23 #include <Window.h>
24 
25 #include "CommonPropertyIDs.h"
26 //#include "LanguageManager.h"
27 #include "Property.h"
28 #include "PropertyItemView.h"
29 #include "PropertyObject.h"
30 #include "Scrollable.h"
31 #include "Scroller.h"
32 #include "ScrollView.h"
33 
34 
35 #undef B_TRANSLATION_CONTEXT
36 #define B_TRANSLATION_CONTEXT "Icon-O-Matic-Properties"
37 
38 
39 enum {
40 	MSG_COPY_PROPERTIES		= 'cppr',
41 	MSG_PASTE_PROPERTIES	= 'pspr',
42 
43 	MSG_ADD_KEYFRAME		= 'adkf',
44 
45 	MSG_SELECT_ALL			= B_SELECT_ALL,
46 	MSG_SELECT_NONE			= 'slnn',
47 	MSG_INVERT_SELECTION	= 'invs',
48 };
49 
50 // TabFilter class
51 
52 class TabFilter : public BMessageFilter {
53  public:
54 	TabFilter(PropertyListView* target)
55 		: BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
56 		  fTarget(target)
57 		{
58 		}
59 	virtual	~TabFilter()
60 		{
61 		}
62 	virtual	filter_result	Filter(BMessage* message, BHandler** target)
63 		{
64 			filter_result result = B_DISPATCH_MESSAGE;
65 			switch (message->what) {
66 				case B_UNMAPPED_KEY_DOWN:
67 				case B_KEY_DOWN: {
68 					uint32 key;
69 					uint32 modifiers;
70 					if (message->FindInt32("raw_char", (int32*)&key) >= B_OK
71 						&& message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK)
72 						if (key == B_TAB && fTarget->TabFocus(modifiers & B_SHIFT_KEY))
73 							result = B_SKIP_MESSAGE;
74 					break;
75 				}
76 				default:
77 					break;
78 			}
79 			return result;
80 		}
81  private:
82  	PropertyListView*		fTarget;
83 };
84 
85 
86 // constructor
87 PropertyListView::PropertyListView()
88 	: BView(BRect(0.0, 0.0, 100.0, 100.0), NULL, B_FOLLOW_NONE,
89 			B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE),
90 	  Scrollable(),
91 	  BList(20),
92 	  fClipboard(new BClipboard("icon-o-matic properties")),
93 
94 	  fPropertyM(NULL),
95 
96 	  fPropertyObject(NULL),
97 	  fSavedProperties(new PropertyObject()),
98 
99 	  fLastClickedItem(NULL),
100 	  fSuspendUpdates(false),
101 
102 	  fMouseWheelFilter(new MouseWheelFilter(this)),
103 	  fTabFilter(new TabFilter(this))
104 {
105 	SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR));
106 	SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR));
107 	SetViewColor(B_TRANSPARENT_32_BIT);
108 }
109 
110 // destructor
111 PropertyListView::~PropertyListView()
112 {
113 	delete fClipboard;
114 
115 	delete fPropertyObject;
116 	delete fSavedProperties;
117 
118 	delete fMouseWheelFilter;
119 	delete fTabFilter;
120 }
121 
122 // AttachedToWindow
123 void
124 PropertyListView::AttachedToWindow()
125 {
126 	Window()->AddCommonFilter(fMouseWheelFilter);
127 	Window()->AddCommonFilter(fTabFilter);
128 }
129 
130 // DetachedFromWindow
131 void
132 PropertyListView::DetachedFromWindow()
133 {
134 	Window()->RemoveCommonFilter(fTabFilter);
135 	Window()->RemoveCommonFilter(fMouseWheelFilter);
136 }
137 
138 // FrameResized
139 void
140 PropertyListView::FrameResized(float width, float height)
141 {
142 	SetVisibleSize(width, height);
143 	Invalidate();
144 }
145 
146 // Draw
147 void
148 PropertyListView::Draw(BRect updateRect)
149 {
150 	if (!fSuspendUpdates)
151 		FillRect(updateRect, B_SOLID_LOW);
152 }
153 
154 // MakeFocus
155 void
156 PropertyListView::MakeFocus(bool focus)
157 {
158 	if (focus == IsFocus())
159 		return;
160 
161 	BView::MakeFocus(focus);
162 	if (::ScrollView* scrollView = dynamic_cast< ::ScrollView*>(Parent()))
163 		scrollView->ChildFocusChanged(focus);
164 }
165 
166 // MouseDown
167 void
168 PropertyListView::MouseDown(BPoint where)
169 {
170 	if (!(modifiers() & B_SHIFT_KEY)) {
171 		DeselectAll();
172 	}
173 	MakeFocus(true);
174 }
175 
176 // MessageReceived
177 void
178 PropertyListView::MessageReceived(BMessage* message)
179 {
180 	switch (message->what) {
181 		case MSG_PASTE_PROPERTIES: {
182 			if (!fPropertyObject || !fClipboard->Lock())
183 				break;
184 
185 			BMessage* data = fClipboard->Data();
186 			if (!data) {
187 				fClipboard->Unlock();
188 				break;
189 			}
190 
191 			PropertyObject propertyObject;
192 			BMessage archive;
193 			for (int32 i = 0;
194 				 data->FindMessage("property", i, &archive) >= B_OK;
195 				 i++) {
196 				BArchivable* archivable = instantiate_object(&archive);
197 				if (!archivable)
198 					continue;
199 				// see if this is actually a property
200 				Property* property = dynamic_cast<Property*>(archivable);
201 				if (property == NULL || !propertyObject.AddProperty(property))
202 					delete archivable;
203 			}
204 			if (propertyObject.CountProperties() > 0)
205 				PasteProperties(&propertyObject);
206 			fClipboard->Unlock();
207 			break;
208 		}
209 		case MSG_COPY_PROPERTIES: {
210 			if (!fPropertyObject || !fClipboard->Lock())
211 				break;
212 
213 			BMessage* data = fClipboard->Data();
214 			if (!data) {
215 				fClipboard->Unlock();
216 				break;
217 			}
218 
219 			fClipboard->Clear();
220 			for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
221 				if (!item->IsSelected())
222 					continue;
223 				const Property* property = item->GetProperty();
224 				if (property) {
225 					BMessage archive;
226 					if (property->Archive(&archive) >= B_OK) {
227 						data->AddMessage("property", &archive);
228 					}
229 				}
230 			}
231 			fClipboard->Commit();
232 			fClipboard->Unlock();
233 			_CheckMenuStatus();
234 			break;
235 		}
236 
237 		// property selection
238 		case MSG_SELECT_ALL:
239 			for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
240 				item->SetSelected(true);
241 			}
242 			_CheckMenuStatus();
243 			break;
244 		case MSG_SELECT_NONE:
245 			for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
246 				item->SetSelected(false);
247 			}
248 			_CheckMenuStatus();
249 			break;
250 		case MSG_INVERT_SELECTION:
251 			for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
252 				item->SetSelected(!item->IsSelected());
253 			}
254 			_CheckMenuStatus();
255 			break;
256 
257 		default:
258 			BView::MessageReceived(message);
259 	}
260 }
261 
262 #ifdef __HAIKU__
263 
264 BSize
265 PropertyListView::MinSize()
266 {
267 	// We need a stable min size: the BView implementation uses
268 	// GetPreferredSize(), which by default just returns the current size.
269 	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
270 }
271 
272 
273 BSize
274 PropertyListView::MaxSize()
275 {
276 	return BView::MaxSize();
277 }
278 
279 
280 BSize
281 PropertyListView::PreferredSize()
282 {
283 	// We need a stable preferred size: the BView implementation uses
284 	// GetPreferredSize(), which by default just returns the current size.
285 	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
286 }
287 
288 #endif // __HAIKU__
289 
290 // #pragma mark -
291 
292 // TabFocus
293 bool
294 PropertyListView::TabFocus(bool shift)
295 {
296 	bool result = false;
297 	PropertyItemView* item = NULL;
298 	if (IsFocus() && !shift) {
299 		item = _ItemAt(0);
300 	} else {
301 		int32 focussedIndex = -1;
302 		for (int32 i = 0; PropertyItemView* oldItem = _ItemAt(i); i++) {
303 			if (oldItem->IsFocused()) {
304 				focussedIndex = shift ? i - 1 : i + 1;
305 				break;
306 			}
307 		}
308 		item = _ItemAt(focussedIndex);
309 	}
310 	if (item) {
311 		item->MakeFocus(true);
312 		result = true;
313 	}
314 	return result;
315 }
316 
317 // SetMenu
318 void
319 PropertyListView::SetMenu(BMenu* menu)
320 {
321 	fPropertyM = menu;
322 	if (!fPropertyM)
323 		return;
324 
325 	fSelectM = new BMenu(B_TRANSLATE("Select"));
326 	fSelectAllMI = new BMenuItem(B_TRANSLATE("All"),
327 		new BMessage(MSG_SELECT_ALL));
328 	fSelectM->AddItem(fSelectAllMI);
329 	fSelectNoneMI = new BMenuItem(B_TRANSLATE("None"),
330 		new BMessage(MSG_SELECT_NONE));
331 	fSelectM->AddItem(fSelectNoneMI);
332 	fInvertSelectionMI = new BMenuItem(B_TRANSLATE("Invert selection"),
333 		new BMessage(MSG_INVERT_SELECTION));
334 	fSelectM->AddItem(fInvertSelectionMI);
335 	fSelectM->SetTargetForItems(this);
336 
337 	fPropertyM->AddItem(fSelectM);
338 
339 	fPropertyM->AddSeparatorItem();
340 
341 	fCopyMI = new BMenuItem(B_TRANSLATE("Copy"),
342 		new BMessage(MSG_COPY_PROPERTIES));
343 	fPropertyM->AddItem(fCopyMI);
344 	fPasteMI = new BMenuItem(B_TRANSLATE("Paste"),
345 		new BMessage(MSG_PASTE_PROPERTIES));
346 	fPropertyM->AddItem(fPasteMI);
347 
348 	fPropertyM->SetTargetForItems(this);
349 
350 	// disable menus
351 	_CheckMenuStatus();
352 }
353 
354 
355 // ScrollView
356 ::ScrollView*
357 PropertyListView::ScrollView() const
358 {
359 	return dynamic_cast< ::ScrollView*>(ScrollSource());
360 }
361 
362 // #pragma mark -
363 
364 // SetTo
365 void
366 PropertyListView::SetTo(PropertyObject* object)
367 {
368 	// try to do without rebuilding the list
369 	// it should in fact be pretty unlikely that this does not
370 	// work, but we keep being defensive
371 	if (fPropertyObject && object &&
372 		fPropertyObject->ContainsSameProperties(*object)) {
373 		// iterate over view items and update their value views
374 		bool error = false;
375 		for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
376 			Property* property = object->PropertyAt(i);
377 			if (!item->AdoptProperty(property)) {
378 				// the reason for this can be that the property is
379 				// unkown to the PropertyEditorFactory and therefor
380 				// there is no editor view at this item
381 				fprintf(stderr, "PropertyListView::_SetTo() - "
382 								"property mismatch at %" B_PRId32 "\n", i);
383 				error = true;
384 				break;
385 			}
386 			if (property)
387 				item->SetEnabled(property->IsEditable());
388 		}
389 		// we didn't need to make empty, but transfer ownership
390 		// of the object
391 		if (!error) {
392 			// if the "adopt" process went only halfway,
393 			// some properties of the original object
394 			// are still referenced, so we can only
395 			// delete the original object if the process
396 			// was successful and leak Properties otherwise,
397 			// but this case is only theoretical anyways...
398 			delete fPropertyObject;
399 		}
400 		fPropertyObject = object;
401 	} else {
402 		// remember scroll pos, selection and focused item
403 		BPoint scrollOffset = ScrollOffset();
404 		BList selection(20);
405 		int32 focused = -1;
406 		for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
407 			if (item->IsSelected())
408 				selection.AddItem((void*)(long)i);
409 			if (item->IsFocused())
410 				focused = i;
411 		}
412 		if (Window())
413 			Window()->BeginViewTransaction();
414 		fSuspendUpdates = true;
415 
416 		// rebuild list
417 		_MakeEmpty();
418 		fPropertyObject = object;
419 
420 		if (fPropertyObject) {
421 			// fill with content
422 			for (int32 i = 0; Property* property = fPropertyObject->PropertyAt(i); i++) {
423 				PropertyItemView* item = new PropertyItemView(property);
424 				item->SetEnabled(property->IsEditable());
425 				_AddItem(item);
426 			}
427 			_LayoutItems();
428 
429 			// restore scroll pos, selection and focus
430 			SetScrollOffset(scrollOffset);
431 			for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
432 				if (selection.HasItem((void*)(long)i))
433 					item->SetSelected(true);
434 				if (i == focused)
435 					item->MakeFocus(true);
436 			}
437 		}
438 
439 		if (Window())
440 			Window()->EndViewTransaction();
441 		fSuspendUpdates = false;
442 
443 		SetDataRect(_ItemsRect());
444 	}
445 
446 	_UpdateSavedProperties();
447 	_CheckMenuStatus();
448 	Invalidate();
449 }
450 
451 // PropertyChanged
452 void
453 PropertyListView::PropertyChanged(const Property* previous,
454 								  const Property* current)
455 {
456 	printf("PropertyListView::PropertyChanged(%s)\n",
457 		name_for_id(current->Identifier()));
458 }
459 
460 // PasteProperties
461 void
462 PropertyListView::PasteProperties(const PropertyObject* object)
463 {
464 	if (!fPropertyObject)
465 		return;
466 
467 	// default implementation is to adopt the pasted properties
468 	int32 count = object->CountProperties();
469 	for (int32 i = 0; i < count; i++) {
470 		Property* p = object->PropertyAtFast(i);
471 		Property* local = fPropertyObject->FindProperty(p->Identifier());
472 		if (local)
473 			local->SetValue(p);
474 	}
475 }
476 
477 // IsEditingMultipleObjects
478 bool
479 PropertyListView::IsEditingMultipleObjects()
480 {
481 	return false;
482 }
483 
484 // #pragma mark -
485 
486 // UpdateObject
487 void
488 PropertyListView::UpdateObject(uint32 propertyID)
489 {
490 	Property* previous = fSavedProperties->FindProperty(propertyID);
491 	Property* current = fPropertyObject->FindProperty(propertyID);
492 	if (previous && current) {
493 		// call hook function
494 		PropertyChanged(previous, current);
495 		// update saved property if it is still contained
496 		// in the saved properties (if not, the notification
497 		// mechanism has caused to update the properties
498 		// and "previous" and "current" are toast)
499 		if (fSavedProperties->HasProperty(previous)
500 			&& fPropertyObject->HasProperty(current))
501 			previous->SetValue(current);
502 	}
503 }
504 
505 // ScrollOffsetChanged
506 void
507 PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset)
508 {
509 	ScrollBy(newOffset.x - oldOffset.x,
510 			 newOffset.y - oldOffset.y);
511 }
512 
513 // Select
514 void
515 PropertyListView::Select(PropertyItemView* item)
516 {
517 	if (item) {
518 		if (modifiers() & B_SHIFT_KEY) {
519 			item->SetSelected(!item->IsSelected());
520 		} else if (modifiers() & B_OPTION_KEY) {
521 			item->SetSelected(true);
522 			int32 firstSelected = _CountItems();
523 			int32 lastSelected = -1;
524 			for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) {
525 				if (otherItem->IsSelected()) {
526 					 if (i < firstSelected)
527 					 	firstSelected = i;
528 					 if (i > lastSelected)
529 					 	lastSelected = i;
530 				}
531 			}
532 			if (lastSelected > firstSelected) {
533 				for (int32 i = firstSelected; PropertyItemView* otherItem = _ItemAt(i); i++) {
534 					if (i > lastSelected)
535 						break;
536 					otherItem->SetSelected(true);
537 				}
538 			}
539 		} else {
540 			for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) {
541 				otherItem->SetSelected(otherItem == item);
542 			}
543 		}
544 	}
545 	_CheckMenuStatus();
546 }
547 
548 // DeselectAll
549 void
550 PropertyListView::DeselectAll()
551 {
552 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
553 		item->SetSelected(false);
554 	}
555 	_CheckMenuStatus();
556 }
557 
558 // Clicked
559 void
560 PropertyListView::Clicked(PropertyItemView* item)
561 {
562 	fLastClickedItem = item;
563 }
564 
565 // DoubleClicked
566 void
567 PropertyListView::DoubleClicked(PropertyItemView* item)
568 {
569 	if (fLastClickedItem == item) {
570 		printf("implement PropertyListView::DoubleClicked()\n");
571 	}
572 	fLastClickedItem = NULL;
573 }
574 
575 // #pragma mark -
576 
577 // _UpdateSavedProperties
578 void
579 PropertyListView::_UpdateSavedProperties()
580 {
581 	fSavedProperties->DeleteProperties();
582 
583 	if (!fPropertyObject)
584 		return;
585 
586 	int32 count = fPropertyObject->CountProperties();
587 	for (int32 i = 0; i < count; i++) {
588 		const Property* p = fPropertyObject->PropertyAtFast(i);
589 		fSavedProperties->AddProperty(p->Clone());
590 	}
591 }
592 
593 // _AddItem
594 bool
595 PropertyListView::_AddItem(PropertyItemView* item)
596 {
597 	if (item && BList::AddItem((void*)item)) {
598 //		AddChild(item);
599 // NOTE: for now added in _LayoutItems()
600 		item->SetListView(this);
601 		return true;
602 	}
603 	return false;
604 }
605 
606 // _RemoveItem
607 PropertyItemView*
608 PropertyListView::_RemoveItem(int32 index)
609 {
610 	PropertyItemView* item = (PropertyItemView*)BList::RemoveItem(index);
611 	if (item) {
612 		item->SetListView(NULL);
613 		if (!RemoveChild(item))
614 			fprintf(stderr, "failed to remove view in PropertyListView::_RemoveItem()\n");
615 	}
616 	return item;
617 }
618 
619 // _ItemAt
620 PropertyItemView*
621 PropertyListView::_ItemAt(int32 index) const
622 {
623 	return (PropertyItemView*)BList::ItemAt(index);
624 }
625 
626 // _CountItems
627 int32
628 PropertyListView::_CountItems() const
629 {
630 	return BList::CountItems();
631 }
632 
633 // _MakeEmpty
634 void
635 PropertyListView::_MakeEmpty()
636 {
637 	int32 count = _CountItems();
638 	while (PropertyItemView* item = _RemoveItem(count - 1)) {
639 		delete item;
640 		count--;
641 	}
642 	delete fPropertyObject;
643 	fPropertyObject = NULL;
644 
645 	SetScrollOffset(BPoint(0.0, 0.0));
646 }
647 
648 // _ItemsRect
649 BRect
650 PropertyListView::_ItemsRect() const
651 {
652 	float width = Bounds().Width();
653 	float height = -1.0;
654 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
655 		height += item->PreferredHeight() + 1.0;
656 	}
657 	if (height < 0.0)
658 		height = 0.0;
659 	return BRect(0.0, 0.0, width, height);
660 }
661 
662 // _LayoutItems
663 void
664 PropertyListView::_LayoutItems()
665 {
666 	// figure out maximum label width
667 	float labelWidth = 0.0;
668 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
669 		if (item->PreferredLabelWidth() > labelWidth)
670 			labelWidth = item->PreferredLabelWidth();
671 	}
672 	labelWidth = ceilf(labelWidth);
673 	// layout items
674 	float top = 0.0;
675 	float width = Bounds().Width();
676 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
677 		item->MoveTo(BPoint(0.0, top));
678 		float height = item->PreferredHeight();
679 		item->SetLabelWidth(labelWidth);
680 		item->ResizeTo(width, height);
681 		item->FrameResized(item->Bounds().Width(),
682 						   item->Bounds().Height());
683 		top += height + 1.0;
684 
685 		AddChild(item);
686 	}
687 }
688 
689 // _CheckMenuStatus
690 void
691 PropertyListView::_CheckMenuStatus()
692 {
693 	if (!fPropertyM || fSuspendUpdates)
694 		return;
695 
696 	if (!fPropertyObject) {
697 		fPropertyM->SetEnabled(false);
698 		return;
699 	} else
700 		fPropertyM->SetEnabled(false);
701 
702 	bool gotSelection = false;
703 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
704 		if (item->IsSelected()) {
705 			gotSelection = true;
706 			break;
707 		}
708 	}
709 	fCopyMI->SetEnabled(gotSelection);
710 
711 	bool clipboardHasData = false;
712 	if (fClipboard->Lock()) {
713 		if (BMessage* data = fClipboard->Data()) {
714 			clipboardHasData = data->HasMessage("property");
715 		}
716 		fClipboard->Unlock();
717 	}
718 
719 	fPasteMI->SetEnabled(clipboardHasData);
720 	if (IsEditingMultipleObjects())
721 		fPasteMI->SetLabel(B_TRANSLATE("Multi-paste"));
722 	else
723 		fPasteMI->SetLabel(B_TRANSLATE("Paste"));
724 
725 	bool enableMenu = fPropertyObject;
726 	if (fPropertyM->IsEnabled() != enableMenu)
727 		fPropertyM->SetEnabled(enableMenu);
728 
729 	bool gotItems = _CountItems() > 0;
730 	fSelectM->SetEnabled(gotItems);
731 }
732 
733 
734