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:
TabFilter(PropertyListView * target)54 TabFilter(PropertyListView* target)
55 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
56 fTarget(target)
57 {
58 }
~TabFilter()59 virtual ~TabFilter()
60 {
61 }
Filter(BMessage * message,BHandler ** target)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
PropertyListView()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
~PropertyListView()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
AttachedToWindow()124 PropertyListView::AttachedToWindow()
125 {
126 Window()->AddCommonFilter(fMouseWheelFilter);
127 Window()->AddCommonFilter(fTabFilter);
128 }
129
130 // DetachedFromWindow
131 void
DetachedFromWindow()132 PropertyListView::DetachedFromWindow()
133 {
134 Window()->RemoveCommonFilter(fTabFilter);
135 Window()->RemoveCommonFilter(fMouseWheelFilter);
136 }
137
138 // FrameResized
139 void
FrameResized(float width,float height)140 PropertyListView::FrameResized(float width, float height)
141 {
142 SetVisibleSize(width, height);
143 Invalidate();
144 }
145
146 // Draw
147 void
Draw(BRect updateRect)148 PropertyListView::Draw(BRect updateRect)
149 {
150 if (!fSuspendUpdates)
151 FillRect(updateRect, B_SOLID_LOW);
152 }
153
154 // MakeFocus
155 void
MakeFocus(bool focus)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
MouseDown(BPoint where)168 PropertyListView::MouseDown(BPoint where)
169 {
170 if (!(modifiers() & B_SHIFT_KEY)) {
171 DeselectAll();
172 }
173 MakeFocus(true);
174 }
175
176 // MessageReceived
177 void
MessageReceived(BMessage * message)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
MinSize()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
MaxSize()274 PropertyListView::MaxSize()
275 {
276 return BView::MaxSize();
277 }
278
279
280 BSize
PreferredSize()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
TabFocus(bool shift)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
SetMenu(BMenu * menu)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*
ScrollView() const357 PropertyListView::ScrollView() const
358 {
359 return dynamic_cast< ::ScrollView*>(ScrollSource());
360 }
361
362 // #pragma mark -
363
364 // SetTo
365 void
SetTo(PropertyObject * object)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
PropertyChanged(const Property * previous,const Property * current)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
PasteProperties(const PropertyObject * object)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
IsEditingMultipleObjects()479 PropertyListView::IsEditingMultipleObjects()
480 {
481 return false;
482 }
483
484 // #pragma mark -
485
486 // UpdateObject
487 void
UpdateObject(uint32 propertyID)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
ScrollOffsetChanged(BPoint oldOffset,BPoint newOffset)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
Select(PropertyItemView * item)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
DeselectAll()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
Clicked(PropertyItemView * item)560 PropertyListView::Clicked(PropertyItemView* item)
561 {
562 fLastClickedItem = item;
563 }
564
565 // DoubleClicked
566 void
DoubleClicked(PropertyItemView * item)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
_UpdateSavedProperties()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
_AddItem(PropertyItemView * item)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*
_RemoveItem(int32 index)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*
_ItemAt(int32 index) const621 PropertyListView::_ItemAt(int32 index) const
622 {
623 return (PropertyItemView*)BList::ItemAt(index);
624 }
625
626 // _CountItems
627 int32
_CountItems() const628 PropertyListView::_CountItems() const
629 {
630 return BList::CountItems();
631 }
632
633 // _MakeEmpty
634 void
_MakeEmpty()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
_ItemsRect() const650 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
_LayoutItems()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
_CheckMenuStatus()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