/* * Copyright 2006-2009, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus */ #include "PropertyListView.h" #include #include #include #include #ifdef __HAIKU__ # include #endif #include #include #include #include #include #include "CommonPropertyIDs.h" //#include "LanguageManager.h" #include "Property.h" #include "PropertyItemView.h" #include "PropertyObject.h" #include "Scrollable.h" #include "Scroller.h" #include "ScrollView.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Icon-O-Matic-Properties" enum { MSG_COPY_PROPERTIES = 'cppr', MSG_PASTE_PROPERTIES = 'pspr', MSG_ADD_KEYFRAME = 'adkf', MSG_SELECT_ALL = B_SELECT_ALL, MSG_SELECT_NONE = 'slnn', MSG_INVERT_SELECTION = 'invs', }; // TabFilter class class TabFilter : public BMessageFilter { public: TabFilter(PropertyListView* target) : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), fTarget(target) { } virtual ~TabFilter() { } virtual filter_result Filter(BMessage* message, BHandler** target) { filter_result result = B_DISPATCH_MESSAGE; switch (message->what) { case B_UNMAPPED_KEY_DOWN: case B_KEY_DOWN: { uint32 key; uint32 modifiers; if (message->FindInt32("raw_char", (int32*)&key) >= B_OK && message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK) if (key == B_TAB && fTarget->TabFocus(modifiers & B_SHIFT_KEY)) result = B_SKIP_MESSAGE; break; } default: break; } return result; } private: PropertyListView* fTarget; }; // constructor PropertyListView::PropertyListView() : BView(BRect(0.0, 0.0, 100.0, 100.0), NULL, B_FOLLOW_NONE, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE), Scrollable(), BList(20), fClipboard(new BClipboard("icon-o-matic properties")), fPropertyM(NULL), fPropertyObject(NULL), fSavedProperties(new PropertyObject()), fLastClickedItem(NULL), fSuspendUpdates(false), fMouseWheelFilter(new MouseWheelFilter(this)), fTabFilter(new TabFilter(this)) { SetLowColor(ui_color(B_LIST_BACKGROUND_COLOR)); SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); SetViewColor(B_TRANSPARENT_32_BIT); } // destructor PropertyListView::~PropertyListView() { delete fClipboard; delete fPropertyObject; delete fSavedProperties; delete fMouseWheelFilter; delete fTabFilter; } // AttachedToWindow void PropertyListView::AttachedToWindow() { Window()->AddCommonFilter(fMouseWheelFilter); Window()->AddCommonFilter(fTabFilter); } // DetachedFromWindow void PropertyListView::DetachedFromWindow() { Window()->RemoveCommonFilter(fTabFilter); Window()->RemoveCommonFilter(fMouseWheelFilter); } // FrameResized void PropertyListView::FrameResized(float width, float height) { SetVisibleSize(width, height); Invalidate(); } // Draw void PropertyListView::Draw(BRect updateRect) { if (!fSuspendUpdates) FillRect(updateRect, B_SOLID_LOW); } // MakeFocus void PropertyListView::MakeFocus(bool focus) { if (focus == IsFocus()) return; BView::MakeFocus(focus); if (::ScrollView* scrollView = dynamic_cast< ::ScrollView*>(Parent())) scrollView->ChildFocusChanged(focus); } // MouseDown void PropertyListView::MouseDown(BPoint where) { if (!(modifiers() & B_SHIFT_KEY)) { DeselectAll(); } MakeFocus(true); } // MessageReceived void PropertyListView::MessageReceived(BMessage* message) { switch (message->what) { case MSG_PASTE_PROPERTIES: { if (!fPropertyObject || !fClipboard->Lock()) break; BMessage* data = fClipboard->Data(); if (!data) { fClipboard->Unlock(); break; } PropertyObject propertyObject; BMessage archive; for (int32 i = 0; data->FindMessage("property", i, &archive) >= B_OK; i++) { BArchivable* archivable = instantiate_object(&archive); if (!archivable) continue; // see if this is actually a property Property* property = dynamic_cast(archivable); if (property == NULL || !propertyObject.AddProperty(property)) delete archivable; } if (propertyObject.CountProperties() > 0) PasteProperties(&propertyObject); fClipboard->Unlock(); break; } case MSG_COPY_PROPERTIES: { if (!fPropertyObject || !fClipboard->Lock()) break; BMessage* data = fClipboard->Data(); if (!data) { fClipboard->Unlock(); break; } fClipboard->Clear(); for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { if (!item->IsSelected()) continue; const Property* property = item->GetProperty(); if (property) { BMessage archive; if (property->Archive(&archive) >= B_OK) { data->AddMessage("property", &archive); } } } fClipboard->Commit(); fClipboard->Unlock(); _CheckMenuStatus(); break; } // property selection case MSG_SELECT_ALL: for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { item->SetSelected(true); } _CheckMenuStatus(); break; case MSG_SELECT_NONE: for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { item->SetSelected(false); } _CheckMenuStatus(); break; case MSG_INVERT_SELECTION: for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { item->SetSelected(!item->IsSelected()); } _CheckMenuStatus(); break; default: BView::MessageReceived(message); } } #ifdef __HAIKU__ BSize PropertyListView::MinSize() { // We need a stable min size: the BView implementation uses // GetPreferredSize(), which by default just returns the current size. return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10)); } BSize PropertyListView::MaxSize() { return BView::MaxSize(); } BSize PropertyListView::PreferredSize() { // We need a stable preferred size: the BView implementation uses // GetPreferredSize(), which by default just returns the current size. return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50)); } #endif // __HAIKU__ // #pragma mark - // TabFocus bool PropertyListView::TabFocus(bool shift) { bool result = false; PropertyItemView* item = NULL; if (IsFocus() && !shift) { item = _ItemAt(0); } else { int32 focussedIndex = -1; for (int32 i = 0; PropertyItemView* oldItem = _ItemAt(i); i++) { if (oldItem->IsFocused()) { focussedIndex = shift ? i - 1 : i + 1; break; } } item = _ItemAt(focussedIndex); } if (item) { item->MakeFocus(true); result = true; } return result; } // SetMenu void PropertyListView::SetMenu(BMenu* menu) { fPropertyM = menu; if (!fPropertyM) return; fSelectM = new BMenu(B_TRANSLATE("Select")); fSelectAllMI = new BMenuItem(B_TRANSLATE("All"), new BMessage(MSG_SELECT_ALL)); fSelectM->AddItem(fSelectAllMI); fSelectNoneMI = new BMenuItem(B_TRANSLATE("None"), new BMessage(MSG_SELECT_NONE)); fSelectM->AddItem(fSelectNoneMI); fInvertSelectionMI = new BMenuItem(B_TRANSLATE("Invert selection"), new BMessage(MSG_INVERT_SELECTION)); fSelectM->AddItem(fInvertSelectionMI); fSelectM->SetTargetForItems(this); fPropertyM->AddItem(fSelectM); fPropertyM->AddSeparatorItem(); fCopyMI = new BMenuItem(B_TRANSLATE("Copy"), new BMessage(MSG_COPY_PROPERTIES)); fPropertyM->AddItem(fCopyMI); fPasteMI = new BMenuItem(B_TRANSLATE("Paste"), new BMessage(MSG_PASTE_PROPERTIES)); fPropertyM->AddItem(fPasteMI); fPropertyM->SetTargetForItems(this); // disable menus _CheckMenuStatus(); } // ScrollView ::ScrollView* PropertyListView::ScrollView() const { return dynamic_cast< ::ScrollView*>(ScrollSource()); } // #pragma mark - // SetTo void PropertyListView::SetTo(PropertyObject* object) { // try to do without rebuilding the list // it should in fact be pretty unlikely that this does not // work, but we keep being defensive if (fPropertyObject && object && fPropertyObject->ContainsSameProperties(*object)) { // iterate over view items and update their value views bool error = false; for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { Property* property = object->PropertyAt(i); if (!item->AdoptProperty(property)) { // the reason for this can be that the property is // unkown to the PropertyEditorFactory and therefor // there is no editor view at this item fprintf(stderr, "PropertyListView::_SetTo() - " "property mismatch at %" B_PRId32 "\n", i); error = true; break; } if (property) item->SetEnabled(property->IsEditable()); } // we didn't need to make empty, but transfer ownership // of the object if (!error) { // if the "adopt" process went only halfway, // some properties of the original object // are still referenced, so we can only // delete the original object if the process // was successful and leak Properties otherwise, // but this case is only theoretical anyways... delete fPropertyObject; } fPropertyObject = object; } else { // remember scroll pos, selection and focused item BPoint scrollOffset = ScrollOffset(); BList selection(20); int32 focused = -1; for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { if (item->IsSelected()) selection.AddItem((void*)(long)i); if (item->IsFocused()) focused = i; } if (Window()) Window()->BeginViewTransaction(); fSuspendUpdates = true; // rebuild list _MakeEmpty(); fPropertyObject = object; if (fPropertyObject) { // fill with content for (int32 i = 0; Property* property = fPropertyObject->PropertyAt(i); i++) { PropertyItemView* item = new PropertyItemView(property); item->SetEnabled(property->IsEditable()); _AddItem(item); } _LayoutItems(); // restore scroll pos, selection and focus SetScrollOffset(scrollOffset); for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { if (selection.HasItem((void*)(long)i)) item->SetSelected(true); if (i == focused) item->MakeFocus(true); } } if (Window()) Window()->EndViewTransaction(); fSuspendUpdates = false; SetDataRect(_ItemsRect()); } _UpdateSavedProperties(); _CheckMenuStatus(); Invalidate(); } // PropertyChanged void PropertyListView::PropertyChanged(const Property* previous, const Property* current) { printf("PropertyListView::PropertyChanged(%s)\n", name_for_id(current->Identifier())); } // PasteProperties void PropertyListView::PasteProperties(const PropertyObject* object) { if (!fPropertyObject) return; // default implementation is to adopt the pasted properties int32 count = object->CountProperties(); for (int32 i = 0; i < count; i++) { Property* p = object->PropertyAtFast(i); Property* local = fPropertyObject->FindProperty(p->Identifier()); if (local) local->SetValue(p); } } // IsEditingMultipleObjects bool PropertyListView::IsEditingMultipleObjects() { return false; } // #pragma mark - // UpdateObject void PropertyListView::UpdateObject(uint32 propertyID) { Property* previous = fSavedProperties->FindProperty(propertyID); Property* current = fPropertyObject->FindProperty(propertyID); if (previous && current) { // call hook function PropertyChanged(previous, current); // update saved property if it is still contained // in the saved properties (if not, the notification // mechanism has caused to update the properties // and "previous" and "current" are toast) if (fSavedProperties->HasProperty(previous) && fPropertyObject->HasProperty(current)) previous->SetValue(current); } } // ScrollOffsetChanged void PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset) { ScrollBy(newOffset.x - oldOffset.x, newOffset.y - oldOffset.y); } // Select void PropertyListView::Select(PropertyItemView* item) { if (item) { if (modifiers() & B_SHIFT_KEY) { item->SetSelected(!item->IsSelected()); } else if (modifiers() & B_OPTION_KEY) { item->SetSelected(true); int32 firstSelected = _CountItems(); int32 lastSelected = -1; for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) { if (otherItem->IsSelected()) { if (i < firstSelected) firstSelected = i; if (i > lastSelected) lastSelected = i; } } if (lastSelected > firstSelected) { for (int32 i = firstSelected; PropertyItemView* otherItem = _ItemAt(i); i++) { if (i > lastSelected) break; otherItem->SetSelected(true); } } } else { for (int32 i = 0; PropertyItemView* otherItem = _ItemAt(i); i++) { otherItem->SetSelected(otherItem == item); } } } _CheckMenuStatus(); } // DeselectAll void PropertyListView::DeselectAll() { for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { item->SetSelected(false); } _CheckMenuStatus(); } // Clicked void PropertyListView::Clicked(PropertyItemView* item) { fLastClickedItem = item; } // DoubleClicked void PropertyListView::DoubleClicked(PropertyItemView* item) { if (fLastClickedItem == item) { printf("implement PropertyListView::DoubleClicked()\n"); } fLastClickedItem = NULL; } // #pragma mark - // _UpdateSavedProperties void PropertyListView::_UpdateSavedProperties() { fSavedProperties->DeleteProperties(); if (!fPropertyObject) return; int32 count = fPropertyObject->CountProperties(); for (int32 i = 0; i < count; i++) { const Property* p = fPropertyObject->PropertyAtFast(i); fSavedProperties->AddProperty(p->Clone()); } } // _AddItem bool PropertyListView::_AddItem(PropertyItemView* item) { if (item && BList::AddItem((void*)item)) { // AddChild(item); // NOTE: for now added in _LayoutItems() item->SetListView(this); return true; } return false; } // _RemoveItem PropertyItemView* PropertyListView::_RemoveItem(int32 index) { PropertyItemView* item = (PropertyItemView*)BList::RemoveItem(index); if (item) { item->SetListView(NULL); if (!RemoveChild(item)) fprintf(stderr, "failed to remove view in PropertyListView::_RemoveItem()\n"); } return item; } // _ItemAt PropertyItemView* PropertyListView::_ItemAt(int32 index) const { return (PropertyItemView*)BList::ItemAt(index); } // _CountItems int32 PropertyListView::_CountItems() const { return BList::CountItems(); } // _MakeEmpty void PropertyListView::_MakeEmpty() { int32 count = _CountItems(); while (PropertyItemView* item = _RemoveItem(count - 1)) { delete item; count--; } delete fPropertyObject; fPropertyObject = NULL; SetScrollOffset(BPoint(0.0, 0.0)); } // _ItemsRect BRect PropertyListView::_ItemsRect() const { float width = Bounds().Width(); float height = -1.0; for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { height += item->PreferredHeight() + 1.0; } if (height < 0.0) height = 0.0; return BRect(0.0, 0.0, width, height); } // _LayoutItems void PropertyListView::_LayoutItems() { // figure out maximum label width float labelWidth = 0.0; for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { if (item->PreferredLabelWidth() > labelWidth) labelWidth = item->PreferredLabelWidth(); } labelWidth = ceilf(labelWidth); // layout items float top = 0.0; float width = Bounds().Width(); for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { item->MoveTo(BPoint(0.0, top)); float height = item->PreferredHeight(); item->SetLabelWidth(labelWidth); item->ResizeTo(width, height); item->FrameResized(item->Bounds().Width(), item->Bounds().Height()); top += height + 1.0; AddChild(item); } } // _CheckMenuStatus void PropertyListView::_CheckMenuStatus() { if (!fPropertyM || fSuspendUpdates) return; if (!fPropertyObject) { fPropertyM->SetEnabled(false); return; } else fPropertyM->SetEnabled(false); bool gotSelection = false; for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) { if (item->IsSelected()) { gotSelection = true; break; } } fCopyMI->SetEnabled(gotSelection); bool clipboardHasData = false; if (fClipboard->Lock()) { if (BMessage* data = fClipboard->Data()) { clipboardHasData = data->HasMessage("property"); } fClipboard->Unlock(); } fPasteMI->SetEnabled(clipboardHasData); if (IsEditingMultipleObjects()) fPasteMI->SetLabel(B_TRANSLATE("Multi-paste")); else fPasteMI->SetLabel(B_TRANSLATE("Paste")); bool enableMenu = fPropertyObject; if (fPropertyM->IsEnabled() != enableMenu) fPropertyM->SetEnabled(enableMenu); bool gotItems = _CountItems() > 0; fSelectM->SetEnabled(gotItems); }