/*
* Copyright 2006-2009, Haiku, Inc. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#include "PropertyListView.h"
#include <stdio.h>
#include <string.h>
#include <Catalog.h>
#include <Clipboard.h>
#ifdef __HAIKU__
# include <LayoutUtils.h>
#endif
#include <Locale.h>
#include <Menu.h>
#include <MenuItem.h>
#include <Message.h>
#include <Window.h>
#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<Property*>(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();
}
// UpdateStrings
void
PropertyListView::UpdateStrings()
{
// if (fSelectM) {
// LanguageManager* m = LanguageManager::Default();
//
// fSelectM->Superitem()->SetLabel(m->GetString(PROPERTY_SELECTION, "Select"));
// fSelectAllMI->SetLabel(m->GetString(SELECT_ALL_PROPERTIES, "All"));
// fSelectNoneMI->SetLabel(m->GetString(SELECT_NO_PROPERTIES, "None"));
// fInvertSelectionMI->SetLabel(m->GetString(INVERT_SELECTION, "Invert Selection"));
//
// fPropertyM->Superitem()->SetLabel(m->GetString(PROPERTY, "Property"));
// fCopyMI->SetLabel(m->GetString(COPY, "Copy"));
// if (IsEditingMultipleObjects())
// fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi Paste"));
// else
// fPasteMI->SetLabel(m->GetString(PASTE, "Paste"));
// }
}
// 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);
// LanguageManager* m = LanguageManager::Default();
if (IsEditingMultipleObjects())
// fPasteMI->SetLabel(m->GetString(MULTI_PASTE, "Multi paste"));
fPasteMI->SetLabel(B_TRANSLATE("Multi paste"));
else
// fPasteMI->SetLabel(m->GetString(PASTE, "Paste"));
fPasteMI->SetLabel(B_TRANSLATE("Paste"));
bool enableMenu = fPropertyObject;
if (fPropertyM->IsEnabled() != enableMenu)
fPropertyM->SetEnabled(enableMenu);
bool gotItems = _CountItems() > 0;
fSelectM->SetEnabled(gotItems);
}
↑ V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fSelectM, fSelectAllMI, fSelectNoneMI, fInvertSelectionMI, fCopyMI, fPasteMI.