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