xref: /haiku/src/apps/icon-o-matic/generic/property/view/PropertyListView.cpp (revision 14f0e6d73d4ee697753149a03ebb0dff13bc0cc5)
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 // UpdateStrings
355 void
356 PropertyListView::UpdateStrings()
357 {
358 //	if (fSelectM) {
359 //		LanguageManager* m = LanguageManager::Default();
360 //
361 //		fSelectM->Superitem()->SetLabel(m->GetString(PROPERTY_SELECTION, "Select"));
362 //		fSelectAllMI->SetLabel(m->GetString(SELECT_ALL_PROPERTIES, "All"));
363 //		fSelectNoneMI->SetLabel(m->GetString(SELECT_NO_PROPERTIES, "None"));
364 //		fInvertSelectionMI->SetLabel(m->GetString(INVERT_SELECTION, "Invert Selection"));
365 //
366 //		fPropertyM->Superitem()->SetLabel(m->GetString(PROPERTY, "Property"));
367 //		fCopyMI->SetLabel(m->GetString(COPY, "Copy"));
368 //		if (IsEditingMultipleObjects())
369 //			fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi Paste"));
370 //		else
371 //			fPasteMI->SetLabel(m->GetString(PASTE, "Paste"));
372 //	}
373 }
374 
375 // ScrollView
376 ::ScrollView*
377 PropertyListView::ScrollView() const
378 {
379 	return dynamic_cast< ::ScrollView*>(ScrollSource());
380 }
381 
382 // #pragma mark -
383 
384 // SetTo
385 void
386 PropertyListView::SetTo(PropertyObject* object)
387 {
388 	// try to do without rebuilding the list
389 	// it should in fact be pretty unlikely that this does not
390 	// work, but we keep being defensive
391 	if (fPropertyObject && object &&
392 		fPropertyObject->ContainsSameProperties(*object)) {
393 		// iterate over view items and update their value views
394 		bool error = false;
395 		for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
396 			Property* property = object->PropertyAt(i);
397 			if (!item->AdoptProperty(property)) {
398 				// the reason for this can be that the property is
399 				// unkown to the PropertyEditorFactory and therefor
400 				// there is no editor view at this item
401 				fprintf(stderr, "PropertyListView::_SetTo() - "
402 								"property mismatch at %" B_PRId32 "\n", i);
403 				error = true;
404 				break;
405 			}
406 			if (property)
407 				item->SetEnabled(property->IsEditable());
408 		}
409 		// we didn't need to make empty, but transfer ownership
410 		// of the object
411 		if (!error) {
412 			// if the "adopt" process went only halfway,
413 			// some properties of the original object
414 			// are still referenced, so we can only
415 			// delete the original object if the process
416 			// was successful and leak Properties otherwise,
417 			// but this case is only theoretical anyways...
418 			delete fPropertyObject;
419 		}
420 		fPropertyObject = object;
421 	} else {
422 		// remember scroll pos, selection and focused item
423 		BPoint scrollOffset = ScrollOffset();
424 		BList selection(20);
425 		int32 focused = -1;
426 		for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
427 			if (item->IsSelected())
428 				selection.AddItem((void*)(long)i);
429 			if (item->IsFocused())
430 				focused = i;
431 		}
432 		if (Window())
433 			Window()->BeginViewTransaction();
434 		fSuspendUpdates = true;
435 
436 		// rebuild list
437 		_MakeEmpty();
438 		fPropertyObject = object;
439 
440 		if (fPropertyObject) {
441 			// fill with content
442 			for (int32 i = 0; Property* property = fPropertyObject->PropertyAt(i); i++) {
443 				PropertyItemView* item = new PropertyItemView(property);
444 				item->SetEnabled(property->IsEditable());
445 				_AddItem(item);
446 			}
447 			_LayoutItems();
448 
449 			// restore scroll pos, selection and focus
450 			SetScrollOffset(scrollOffset);
451 			for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
452 				if (selection.HasItem((void*)(long)i))
453 					item->SetSelected(true);
454 				if (i == focused)
455 					item->MakeFocus(true);
456 			}
457 		}
458 
459 		if (Window())
460 			Window()->EndViewTransaction();
461 		fSuspendUpdates = false;
462 
463 		SetDataRect(_ItemsRect());
464 	}
465 
466 	_UpdateSavedProperties();
467 	_CheckMenuStatus();
468 	Invalidate();
469 }
470 
471 // PropertyChanged
472 void
473 PropertyListView::PropertyChanged(const Property* previous,
474 								  const Property* current)
475 {
476 	printf("PropertyListView::PropertyChanged(%s)\n",
477 		name_for_id(current->Identifier()));
478 }
479 
480 // PasteProperties
481 void
482 PropertyListView::PasteProperties(const PropertyObject* object)
483 {
484 	if (!fPropertyObject)
485 		return;
486 
487 	// default implementation is to adopt the pasted properties
488 	int32 count = object->CountProperties();
489 	for (int32 i = 0; i < count; i++) {
490 		Property* p = object->PropertyAtFast(i);
491 		Property* local = fPropertyObject->FindProperty(p->Identifier());
492 		if (local)
493 			local->SetValue(p);
494 	}
495 }
496 
497 // IsEditingMultipleObjects
498 bool
499 PropertyListView::IsEditingMultipleObjects()
500 {
501 	return false;
502 }
503 
504 // #pragma mark -
505 
506 // UpdateObject
507 void
508 PropertyListView::UpdateObject(uint32 propertyID)
509 {
510 	Property* previous = fSavedProperties->FindProperty(propertyID);
511 	Property* current = fPropertyObject->FindProperty(propertyID);
512 	if (previous && current) {
513 		// call hook function
514 		PropertyChanged(previous, current);
515 		// update saved property if it is still contained
516 		// in the saved properties (if not, the notification
517 		// mechanism has caused to update the properties
518 		// and "previous" and "current" are toast)
519 		if (fSavedProperties->HasProperty(previous)
520 			&& fPropertyObject->HasProperty(current))
521 			previous->SetValue(current);
522 	}
523 }
524 
525 // ScrollOffsetChanged
526 void
527 PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset)
528 {
529 	ScrollBy(newOffset.x - oldOffset.x,
530 			 newOffset.y - oldOffset.y);
531 }
532 
533 // Select
534 void
535 PropertyListView::Select(PropertyItemView* item)
536 {
537 	if (item) {
538 		if (modifiers() & B_SHIFT_KEY) {
539 			item->SetSelected(!item->IsSelected());
540 		} else if (modifiers() & B_OPTION_KEY) {
541 			item->SetSelected(true);
542 			int32 firstSelected = _CountItems();
543 			int32 lastSelected = -1;
544 			for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) {
545 				if (otherItem->IsSelected()) {
546 					 if (i < firstSelected)
547 					 	firstSelected = i;
548 					 if (i > lastSelected)
549 					 	lastSelected = i;
550 				}
551 			}
552 			if (lastSelected > firstSelected) {
553 				for (int32 i = firstSelected; PropertyItemView* otherItem = _ItemAt(i); i++) {
554 					if (i > lastSelected)
555 						break;
556 					otherItem->SetSelected(true);
557 				}
558 			}
559 		} else {
560 			for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) {
561 				otherItem->SetSelected(otherItem == item);
562 			}
563 		}
564 	}
565 	_CheckMenuStatus();
566 }
567 
568 // DeselectAll
569 void
570 PropertyListView::DeselectAll()
571 {
572 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
573 		item->SetSelected(false);
574 	}
575 	_CheckMenuStatus();
576 }
577 
578 // Clicked
579 void
580 PropertyListView::Clicked(PropertyItemView* item)
581 {
582 	fLastClickedItem = item;
583 }
584 
585 // DoubleClicked
586 void
587 PropertyListView::DoubleClicked(PropertyItemView* item)
588 {
589 	if (fLastClickedItem == item) {
590 		printf("implement PropertyListView::DoubleClicked()\n");
591 	}
592 	fLastClickedItem = NULL;
593 }
594 
595 // #pragma mark -
596 
597 // _UpdateSavedProperties
598 void
599 PropertyListView::_UpdateSavedProperties()
600 {
601 	fSavedProperties->DeleteProperties();
602 
603 	if (!fPropertyObject)
604 		return;
605 
606 	int32 count = fPropertyObject->CountProperties();
607 	for (int32 i = 0; i < count; i++) {
608 		const Property* p = fPropertyObject->PropertyAtFast(i);
609 		fSavedProperties->AddProperty(p->Clone());
610 	}
611 }
612 
613 // _AddItem
614 bool
615 PropertyListView::_AddItem(PropertyItemView* item)
616 {
617 	if (item && BList::AddItem((void*)item)) {
618 //		AddChild(item);
619 // NOTE: for now added in _LayoutItems()
620 		item->SetListView(this);
621 		return true;
622 	}
623 	return false;
624 }
625 
626 // _RemoveItem
627 PropertyItemView*
628 PropertyListView::_RemoveItem(int32 index)
629 {
630 	PropertyItemView* item = (PropertyItemView*)BList::RemoveItem(index);
631 	if (item) {
632 		item->SetListView(NULL);
633 		if (!RemoveChild(item))
634 			fprintf(stderr, "failed to remove view in PropertyListView::_RemoveItem()\n");
635 	}
636 	return item;
637 }
638 
639 // _ItemAt
640 PropertyItemView*
641 PropertyListView::_ItemAt(int32 index) const
642 {
643 	return (PropertyItemView*)BList::ItemAt(index);
644 }
645 
646 // _CountItems
647 int32
648 PropertyListView::_CountItems() const
649 {
650 	return BList::CountItems();
651 }
652 
653 // _MakeEmpty
654 void
655 PropertyListView::_MakeEmpty()
656 {
657 	int32 count = _CountItems();
658 	while (PropertyItemView* item = _RemoveItem(count - 1)) {
659 		delete item;
660 		count--;
661 	}
662 	delete fPropertyObject;
663 	fPropertyObject = NULL;
664 
665 	SetScrollOffset(BPoint(0.0, 0.0));
666 }
667 
668 // _ItemsRect
669 BRect
670 PropertyListView::_ItemsRect() const
671 {
672 	float width = Bounds().Width();
673 	float height = -1.0;
674 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
675 		height += item->PreferredHeight() + 1.0;
676 	}
677 	if (height < 0.0)
678 		height = 0.0;
679 	return BRect(0.0, 0.0, width, height);
680 }
681 
682 // _LayoutItems
683 void
684 PropertyListView::_LayoutItems()
685 {
686 	// figure out maximum label width
687 	float labelWidth = 0.0;
688 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
689 		if (item->PreferredLabelWidth() > labelWidth)
690 			labelWidth = item->PreferredLabelWidth();
691 	}
692 	labelWidth = ceilf(labelWidth);
693 	// layout items
694 	float top = 0.0;
695 	float width = Bounds().Width();
696 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
697 		item->MoveTo(BPoint(0.0, top));
698 		float height = item->PreferredHeight();
699 		item->SetLabelWidth(labelWidth);
700 		item->ResizeTo(width, height);
701 		item->FrameResized(item->Bounds().Width(),
702 						   item->Bounds().Height());
703 		top += height + 1.0;
704 
705 		AddChild(item);
706 	}
707 }
708 
709 // _CheckMenuStatus
710 void
711 PropertyListView::_CheckMenuStatus()
712 {
713 	if (!fPropertyM || fSuspendUpdates)
714 		return;
715 
716 	if (!fPropertyObject) {
717 		fPropertyM->SetEnabled(false);
718 		return;
719 	} else
720 		fPropertyM->SetEnabled(false);
721 
722 	bool gotSelection = false;
723 	for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
724 		if (item->IsSelected()) {
725 			gotSelection = true;
726 			break;
727 		}
728 	}
729 	fCopyMI->SetEnabled(gotSelection);
730 
731 	bool clipboardHasData = false;
732 	if (fClipboard->Lock()) {
733 		if (BMessage* data = fClipboard->Data()) {
734 			clipboardHasData = data->HasMessage("property");
735 		}
736 		fClipboard->Unlock();
737 	}
738 
739 	fPasteMI->SetEnabled(clipboardHasData);
740 //	LanguageManager* m = LanguageManager::Default();
741 	if (IsEditingMultipleObjects())
742 //		fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi paste"));
743 		fPasteMI->SetLabel(B_TRANSLATE("Multi paste"));
744 	else
745 //		fPasteMI->SetLabel(m->GetString(PASTE, "Paste"));
746 		fPasteMI->SetLabel(B_TRANSLATE("Paste"));
747 
748 	bool enableMenu = fPropertyObject;
749 	if (fPropertyM->IsEnabled() != enableMenu)
750 		fPropertyM->SetEnabled(enableMenu);
751 
752 	bool gotItems = _CountItems() > 0;
753 	fSelectM->SetEnabled(gotItems);
754 }
755 
756 
757