xref: /haiku/src/apps/icon-o-matic/generic/property/view/PropertyListView.cpp (revision e2a31283dde5ae5dfc14c3d2075e0d17cf392892)
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
472 		previous->SetValue(current);
473 	}
474 }
475 
476 // ScrollOffsetChanged
477 void
478 PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset)
479 {
480 	ScrollBy(newOffset.x - oldOffset.x,
481 			 newOffset.y - oldOffset.y);
482 }
483 
484 // Select
485 void
486 PropertyListView::Select(PropertyItemView* item)
487 {
488 	if (item) {
489 		if (modifiers() & B_SHIFT_KEY) {
490 			item->SetSelected(!item->IsSelected());
491 		} else if (modifiers() & B_OPTION_KEY) {
492 			item->SetSelected(true);
493 			int32 firstSelected = _CountItems();
494 			int32 lastSelected = -1;
495 			for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) {
496 				if (otherItem->IsSelected()) {
497 					 if (i < firstSelected)
498 					 	firstSelected = i;
499 					 if (i > lastSelected)
500 					 	lastSelected = i;
501 				}
502 			}
503 			if (lastSelected > firstSelected) {
504 				for (int32 i = firstSelected; PropertyItemView* otherItem = _ItemAt(i); i++) {
505 					if (i > lastSelected)
506 						break;
507 					otherItem->SetSelected(true);
508 				}
509 			}
510 		} else {
511 			for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) {
512 				otherItem->SetSelected(otherItem == item);
513 			}
514 		}
515 	}
516 	_CheckMenuStatus();
517 }
518 
519 // DeselectAll
520 void
521 PropertyListView::DeselectAll()
522 {
523 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
524 		item->SetSelected(false);
525 	}
526 	_CheckMenuStatus();
527 }
528 
529 // Clicked
530 void
531 PropertyListView::Clicked(PropertyItemView* item)
532 {
533 	fLastClickedItem = item;
534 }
535 
536 // DoubleClicked
537 void
538 PropertyListView::DoubleClicked(PropertyItemView* item)
539 {
540 	if (fLastClickedItem == item) {
541 		printf("implement PropertyListView::DoubleClicked()\n");
542 	}
543 	fLastClickedItem = NULL;
544 }
545 
546 // #pragma mark -
547 
548 // _UpdateSavedProperties
549 void
550 PropertyListView::_UpdateSavedProperties()
551 {
552 	fSavedProperties->DeleteProperties();
553 
554 	if (!fPropertyObject)
555 		return;
556 
557 	int32 count = fPropertyObject->CountProperties();
558 	for (int32 i = 0; i < count; i++) {
559 		const Property* p = fPropertyObject->PropertyAtFast(i);
560 		fSavedProperties->AddProperty(p->Clone());
561 	}
562 }
563 
564 // _AddItem
565 bool
566 PropertyListView::_AddItem(PropertyItemView* item)
567 {
568 	if (item && BList::AddItem((void*)item)) {
569 //		AddChild(item);
570 // NOTE: for now added in _LayoutItems()
571 		item->SetListView(this);
572 		return true;
573 	}
574 	return false;
575 }
576 
577 // _RemoveItem
578 PropertyItemView*
579 PropertyListView::_RemoveItem(int32 index)
580 {
581 	PropertyItemView* item = (PropertyItemView*)BList::RemoveItem(index);
582 	if (item) {
583 		item->SetListView(NULL);
584 		if (!RemoveChild(item))
585 			fprintf(stderr, "failed to remove view in PropertyListView::_RemoveItem()\n");
586 	}
587 	return item;
588 }
589 
590 // _ItemAt
591 PropertyItemView*
592 PropertyListView::_ItemAt(int32 index) const
593 {
594 	return (PropertyItemView*)BList::ItemAt(index);
595 }
596 
597 // _CountItems
598 int32
599 PropertyListView::_CountItems() const
600 {
601 	return BList::CountItems();
602 }
603 
604 // _MakeEmpty
605 void
606 PropertyListView::_MakeEmpty()
607 {
608 	int32 count = _CountItems();
609 	while (PropertyItemView* item = _RemoveItem(count - 1)) {
610 		delete item;
611 		count--;
612 	}
613 	delete fPropertyObject;
614 	fPropertyObject = NULL;
615 
616 	SetScrollOffset(BPoint(0.0, 0.0));
617 }
618 
619 // _ItemsRect
620 BRect
621 PropertyListView::_ItemsRect() const
622 {
623 	float width = Bounds().Width();
624 	float height = -1.0;
625 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
626 		height += item->PreferredHeight() + 1.0;
627 	}
628 	if (height < 0.0)
629 		height = 0.0;
630 	return BRect(0.0, 0.0, width, height);
631 }
632 
633 // _LayoutItems
634 void
635 PropertyListView::_LayoutItems()
636 {
637 	// figure out maximum label width
638 	float labelWidth = Bounds().Width() * 0.5;
639 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
640 		if (item->PreferredLabelWidth() > labelWidth)
641 			labelWidth = item->PreferredLabelWidth();
642 	}
643 	labelWidth = ceilf(labelWidth);
644 	// layout items
645 	float top = 0.0;
646 	float width = Bounds().Width();
647 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
648 		item->MoveTo(BPoint(0.0, top));
649 		float height = item->PreferredHeight();
650 		item->SetLabelWidth(labelWidth);
651 		item->ResizeTo(width, height);
652 		item->FrameResized(item->Bounds().Width(),
653 						   item->Bounds().Height());
654 		top += height + 1.0;
655 
656 		AddChild(item);
657 	}
658 }
659 
660 // _CheckMenuStatus
661 void
662 PropertyListView::_CheckMenuStatus()
663 {
664 	if (!fPropertyM || !fSuspendUpdates)
665 		return;
666 
667 	bool gotSelection = false;
668 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
669 		if (item->IsSelected()) {
670 			gotSelection = true;
671 			break;
672 		}
673 	}
674 	fCopyMI->SetEnabled(gotSelection);
675 
676 	bool clipboardHasData = false;
677 	if (fClipboard->Lock()) {
678 		if (BMessage* data = fClipboard->Data()) {
679 			clipboardHasData = data->HasMessage("property");
680 		}
681 		fClipboard->Unlock();
682 	}
683 
684 	fPasteMI->SetEnabled(clipboardHasData);
685 //	LanguageManager* m = LanguageManager::Default();
686 	if (IsEditingMultipleObjects())
687 //		fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi Paste"));
688 		fPasteMI->SetLabel("Multi Paste");
689 	else
690 //		fPasteMI->SetLabel(m->GetString(PASTE, "Paste"));
691 		fPasteMI->SetLabel("Paste");
692 
693 	bool enableMenu = fPropertyObject;
694 	if (fPropertyM->IsEnabled() != enableMenu)
695 		fPropertyM->SetEnabled(enableMenu);
696 
697 	bool gotItems = _CountItems() > 0;
698 	fSelectM->SetEnabled(gotItems);
699 }
700 
701 
702