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