* 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 "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',
};
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;
};
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);
}
PropertyListView::~PropertyListView()
{
delete fClipboard;
delete fPropertyObject;
delete fSavedProperties;
delete fMouseWheelFilter;
delete fTabFilter;
}
void
PropertyListView::AttachedToWindow()
{
Window()->AddCommonFilter(fMouseWheelFilter);
Window()->AddCommonFilter(fTabFilter);
}
void
PropertyListView::DetachedFromWindow()
{
Window()->RemoveCommonFilter(fTabFilter);
Window()->RemoveCommonFilter(fMouseWheelFilter);
}
void
PropertyListView::FrameResized(float width, float height)
{
SetVisibleSize(width, height);
Invalidate();
}
void
PropertyListView::Draw(BRect updateRect)
{
if (!fSuspendUpdates)
FillRect(updateRect, B_SOLID_LOW);
}
void
PropertyListView::MakeFocus(bool focus)
{
if (focus == IsFocus())
return;
BView::MakeFocus(focus);
if (::ScrollView* scrollView = dynamic_cast< ::ScrollView*>(Parent()))
scrollView->ChildFocusChanged(focus);
}
void
PropertyListView::MouseDown(BPoint where)
{
if (!(modifiers() & B_SHIFT_KEY)) {
DeselectAll();
}
MakeFocus(true);
}
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;
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;
}
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()
{
return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(10, 10));
}
BSize
PropertyListView::MaxSize()
{
return BView::MaxSize();
}
BSize
PropertyListView::PreferredSize()
{
return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), BSize(100, 50));
}
#endif
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;
}
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);
_CheckMenuStatus();
}
::ScrollView*
PropertyListView::ScrollView() const
{
return dynamic_cast< ::ScrollView*>(ScrollSource());
}
void
PropertyListView::SetTo(PropertyObject* object)
{
if (fPropertyObject && object &&
fPropertyObject->ContainsSameProperties(*object)) {
bool error = false;
for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
Property* property = object->PropertyAt(i);
if (!item->AdoptProperty(property)) {
fprintf(stderr, "PropertyListView::_SetTo() - "
"property mismatch at %" B_PRId32 "\n", i);
error = true;
break;
}
if (property)
item->SetEnabled(property->IsEditable());
}
if (!error) {
delete fPropertyObject;
}
fPropertyObject = object;
} else {
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;
_MakeEmpty();
fPropertyObject = object;
if (fPropertyObject) {
for (int32 i = 0; Property* property = fPropertyObject->PropertyAt(i); i++) {
PropertyItemView* item = new PropertyItemView(property);
item->SetEnabled(property->IsEditable());
_AddItem(item);
}
_LayoutItems();
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();
}
void
PropertyListView::PropertyChanged(const Property* previous,
const Property* current)
{
printf("PropertyListView::PropertyChanged(%s)\n",
name_for_id(current->Identifier()));
}
void
PropertyListView::PasteProperties(const PropertyObject* object)
{
if (!fPropertyObject)
return;
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);
}
}
bool
PropertyListView::IsEditingMultipleObjects()
{
return false;
}
void
PropertyListView::UpdateObject(uint32 propertyID)
{
Property* previous = fSavedProperties->FindProperty(propertyID);
Property* current = fPropertyObject->FindProperty(propertyID);
if (previous && current) {
PropertyChanged(previous, current);
if (fSavedProperties->HasProperty(previous)
&& fPropertyObject->HasProperty(current))
previous->SetValue(current);
}
}
void
PropertyListView::ScrollOffsetChanged(BPoint oldOffset, BPoint newOffset)
{
ScrollBy(newOffset.x - oldOffset.x,
newOffset.y - oldOffset.y);
}
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();
}
void
PropertyListView::DeselectAll()
{
for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
item->SetSelected(false);
}
_CheckMenuStatus();
}
void
PropertyListView::Clicked(PropertyItemView* item)
{
fLastClickedItem = item;
}
void
PropertyListView::DoubleClicked(PropertyItemView* item)
{
if (fLastClickedItem == item) {
printf("implement PropertyListView::DoubleClicked()\n");
}
fLastClickedItem = NULL;
}
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());
}
}
bool
PropertyListView::_AddItem(PropertyItemView* item)
{
if (item && BList::AddItem((void*)item)) {
item->SetListView(this);
return true;
}
return false;
}
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;
}
PropertyItemView*
PropertyListView::_ItemAt(int32 index) const
{
return (PropertyItemView*)BList::ItemAt(index);
}
int32
PropertyListView::_CountItems() const
{
return BList::CountItems();
}
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));
}
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);
}
void
PropertyListView::_LayoutItems()
{
float labelWidth = 0.0;
for (int32 i = 0; PropertyItemView* item = _ItemAt(i); i++) {
if (item->PreferredLabelWidth() > labelWidth)
labelWidth = item->PreferredLabelWidth();
}
labelWidth = ceilf(labelWidth);
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);
}
}
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);
}