* Copyright 2006-2012, Stephan Aßmus <superstippi@gmx.de>.
* Copyright 2023, Haiku.
* All rights reserved. Distributed under the terms of the MIT License.
*
* Authors:
* Zardshard
*/
#include "StyleListView.h"
#include <new>
#include <stdio.h>
#include <Application.h>
#include <Catalog.h>
#include <ListItem.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuItem.h>
#include <Message.h>
#include <Mime.h>
#include <Window.h>
#include "AddStylesCommand.h"
#include "AssignStyleCommand.h"
#include "CurrentColor.h"
#include "CommandStack.h"
#include "GradientTransformable.h"
#include "MoveStylesCommand.h"
#include "PathSourceShape.h"
#include "RemoveStylesCommand.h"
#include "Style.h"
#include "Observer.h"
#include "ResetTransformationCommand.h"
#include "Shape.h"
#include "Selection.h"
#include "Util.h"
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-StylesList"
using std::nothrow;
static const float kMarkWidth = 14.0;
static const float kBorderOffset = 3.0;
static const float kTextOffset = 4.0;
enum {
MSG_ADD = 'adst',
MSG_REMOVE = 'rmst',
MSG_DUPLICATE = 'dpst',
MSG_RESET_TRANSFORMATION = 'rstr',
};
class StyleListItem : public SimpleItem,
public Observer {
public:
StyleListItem(Style* s, StyleListView* listView, bool markEnabled)
:
SimpleItem(""),
style(NULL),
fListView(listView),
fMarkEnabled(markEnabled),
fMarked(false)
{
SetStyle(s);
}
virtual ~StyleListItem()
{
SetStyle(NULL);
}
virtual void DrawItem(BView* owner, BRect itemFrame, bool even)
{
SimpleItem::DrawBackground(owner, itemFrame, even);
float offset = kBorderOffset + kMarkWidth + kTextOffset;
SimpleItem::DrawItem(owner, itemFrame.OffsetByCopy(offset, 0), even);
if (!fMarkEnabled)
return;
BRect markRect = itemFrame;
float markRectBorderTint = B_DARKEN_1_TINT;
float markRectFillTint = 1.04;
float markTint = B_DARKEN_4_TINT;
rgb_color lowColor = owner->LowColor();
if (lowColor.red + lowColor.green + lowColor.blue < 128 * 3) {
markRectBorderTint = B_LIGHTEN_2_TINT;
markRectFillTint = 0.85;
markTint = 0.1;
}
markRect.left += kBorderOffset;
markRect.right = markRect.left + kMarkWidth;
markRect.top = (markRect.top + markRect.bottom - kMarkWidth) / 2.0;
markRect.bottom = markRect.top + kMarkWidth;
owner->SetHighColor(tint_color(owner->LowColor(), markRectBorderTint));
owner->StrokeRect(markRect);
markRect.InsetBy(1, 1);
owner->SetHighColor(tint_color(owner->LowColor(), markRectFillTint));
owner->FillRect(markRect);
if (fMarked) {
markRect.InsetBy(2, 2);
owner->SetHighColor(tint_color(owner->LowColor(),
markTint));
owner->SetPenSize(2);
owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom());
owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop());
owner->SetPenSize(1);
}
}
virtual void ObjectChanged(const Observable* object)
{
UpdateText();
}
void SetStyle(Style* s)
{
if (s == style)
return;
if (style) {
style->RemoveObserver(this);
style->ReleaseReference();
}
style = s;
if (style) {
style->AcquireReference();
style->AddObserver(this);
UpdateText();
}
}
void UpdateText()
{
SetText(style->Name());
Invalidate();
}
void SetMarkEnabled(bool enabled)
{
if (fMarkEnabled == enabled)
return;
fMarkEnabled = enabled;
Invalidate();
}
void SetMarked(bool marked)
{
if (fMarked == marked)
return;
fMarked = marked;
Invalidate();
}
void Invalidate()
{
if (fListView->LockLooper()) {
fListView->InvalidateItem(fListView->IndexOf(this));
fListView->UnlockLooper();
}
}
public:
Style* style;
private:
StyleListView* fListView;
bool fMarkEnabled;
bool fMarked;
};
class ShapeStyleListener : public ShapeListener,
public ContainerListener<Shape> {
public:
ShapeStyleListener(StyleListView* listView)
:
fListView(listView),
fShape(NULL)
{
}
virtual ~ShapeStyleListener()
{
SetShape(NULL);
}
virtual void TransformerAdded(Transformer* t, int32 index)
{
}
virtual void TransformerRemoved(Transformer* t)
{
}
virtual void StyleChanged(Style* oldStyle, Style* newStyle)
{
fListView->_SetStyleMarked(oldStyle, false);
fListView->_SetStyleMarked(newStyle, true);
}
virtual void ItemAdded(Shape* shape, int32 index)
{
}
virtual void ItemRemoved(Shape* shape)
{
fListView->SetCurrentShape(NULL);
}
void SetShape(PathSourceShape* shape)
{
if (fShape == shape)
return;
if (fShape)
fShape->RemoveListener(this);
fShape = shape;
if (fShape)
fShape->AddListener(this);
}
PathSourceShape* CurrentShape() const
{
return fShape;
}
private:
StyleListView* fListView;
PathSourceShape* fShape;
};
StyleListView::StyleListView(BRect frame, const char* name, BMessage* message,
BHandler* target)
:
SimpleListView(frame, name, NULL, B_SINGLE_SELECTION_LIST),
fMessage(message),
fStyleContainer(NULL),
fShapeContainer(NULL),
fCommandStack(NULL),
fCurrentColor(NULL),
fCurrentShape(NULL),
fShapeListener(new ShapeStyleListener(this)),
fMenu(NULL)
{
SetTarget(target);
}
StyleListView::~StyleListView()
{
_MakeEmpty();
delete fMessage;
if (fStyleContainer != NULL)
fStyleContainer->RemoveListener(this);
if (fShapeContainer != NULL)
fShapeContainer->RemoveListener(fShapeListener);
delete fShapeListener;
}
void
StyleListView::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_ADD:
{
Style* style;
AddStylesCommand* command;
rgb_color color;
if (fCurrentColor != NULL)
color = fCurrentColor->Color();
else {
color.red = 0;
color.green = 0;
color.blue = 0;
color.alpha = 255;
}
new_style(color, fStyleContainer, &style, &command);
fCommandStack->Perform(command);
break;
}
case MSG_REMOVE:
RemoveSelected();
break;
case MSG_DUPLICATE:
{
int32 count = CountSelectedItems();
int32 index = 0;
BList items;
for (int32 i = 0; i < count; i++) {
index = CurrentSelection(i);
BListItem* item = ItemAt(index);
if (item)
items.AddItem((void*)item);
}
CopyItems(items, index + 1);
break;
}
case MSG_RESET_TRANSFORMATION:
{
int32 count = CountSelectedItems();
BList gradients;
for (int32 i = 0; i < count; i++) {
StyleListItem* item = dynamic_cast<StyleListItem*>(
ItemAt(CurrentSelection(i)));
if (item && item->style && item->style->Gradient()) {
if (!gradients.AddItem((void*)item->style->Gradient()))
break;
}
}
count = gradients.CountItems();
if (count <= 0)
break;
Transformable* transformables[count];
for (int32 i = 0; i < count; i++) {
Gradient* gradient = (Gradient*)gradients.ItemAtFast(i);
transformables[i] = gradient;
}
ResetTransformationCommand* command
= new ResetTransformationCommand(transformables, count);
fCommandStack->Perform(command);
break;
}
default:
SimpleListView::MessageReceived(message);
break;
}
}
void
StyleListView::SelectionChanged()
{
SimpleListView::SelectionChanged();
if (!fSyncingToSelection) {
StyleListItem* item
= dynamic_cast<StyleListItem*>(ItemAt(CurrentSelection(0)));
if (fMessage) {
BMessage message(*fMessage);
message.AddPointer("style", item ? (void*)item->style : NULL);
Invoke(&message);
}
}
_UpdateMenu();
}
void
StyleListView::MouseDown(BPoint where)
{
if (fCurrentShape == NULL) {
SimpleListView::MouseDown(where);
return;
}
bool handled = false;
int32 index = IndexOf(where);
StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index));
if (item != NULL) {
BRect itemFrame(ItemFrame(index));
itemFrame.right = itemFrame.left + kBorderOffset + kMarkWidth
+ kTextOffset / 2.0;
Style* style = item->style;
if (itemFrame.Contains(where)) {
if (fCommandStack) {
::Command* command = new AssignStyleCommand(
fCurrentShape, style);
fCommandStack->Perform(command);
} else {
fCurrentShape->SetStyle(style);
}
handled = true;
}
}
if (!handled)
SimpleListView::MouseDown(where);
}
status_t
StyleListView::ArchiveSelection(BMessage* into, bool deep) const
{
into->what = StyleListView::kSelectionArchiveCode;
int32 count = CountSelectedItems();
for (int32 i = 0; i < count; i++) {
StyleListItem* item = dynamic_cast<StyleListItem*>(
ItemAt(CurrentSelection(i)));
if (item != NULL) {
BMessage archive;
if (item->style->Archive(&archive, true) == B_OK)
into->AddMessage("style", &archive);
} else
return B_ERROR;
}
return B_OK;
}
bool
StyleListView::InstantiateSelection(const BMessage* archive, int32 dropIndex)
{
if (archive->what != StyleListView::kSelectionArchiveCode
|| fCommandStack == NULL || fStyleContainer == NULL)
return false;
int index = 0;
BList styles;
while (true) {
BMessage styleArchive;
if (archive->FindMessage("style", index, &styleArchive) != B_OK)
break;
Style* style = new(std::nothrow) Style(&styleArchive);
if (style == NULL)
break;
if (!styles.AddItem(style)) {
delete style;
break;
}
index++;
}
int32 count = styles.CountItems();
if (count == 0)
return false;
AddCommand<Style>* command = new(std::nothrow) AddCommand<Style>(
fStyleContainer, (Style**)styles.Items(), count, true, dropIndex);
if (command == NULL) {
for (int32 i = 0; i < count; i++)
delete (Style*)styles.ItemAtFast(i);
return false;
}
fCommandStack->Perform(command);
return true;
}
void
StyleListView::MoveItems(BList& items, int32 toIndex)
{
if (fCommandStack == NULL || fStyleContainer == NULL)
return;
int32 count = items.CountItems();
Style** styles = new (nothrow) Style*[count];
if (styles == NULL)
return;
for (int32 i = 0; i < count; i++) {
StyleListItem* item
= dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i));
styles[i] = item ? item->style : NULL;
}
MoveStylesCommand* command = new (nothrow) MoveStylesCommand(
fStyleContainer, styles, count, toIndex);
if (command == NULL) {
delete[] styles;
return;
}
fCommandStack->Perform(command);
}
void
StyleListView::CopyItems(BList& items, int32 toIndex)
{
if (fCommandStack == NULL || fStyleContainer == NULL)
return;
int32 count = items.CountItems();
Style* styles[count];
for (int32 i = 0; i < count; i++) {
StyleListItem* item
= dynamic_cast<StyleListItem*>((BListItem*)items.ItemAtFast(i));
styles[i] = item ? new (nothrow) Style(*item->style) : NULL;
}
AddCommand<Style>* command
= new (nothrow) AddCommand<Style>(fStyleContainer, styles, count, true, toIndex);
if (!command) {
for (int32 i = 0; i < count; i++)
delete styles[i];
return;
}
fCommandStack->Perform(command);
}
void
StyleListView::RemoveItemList(BList& items)
{
if (!fCommandStack || !fStyleContainer)
return;
int32 count = items.CountItems();
int32 indices[count];
for (int32 i = 0; i < count; i++)
indices[i] = IndexOf((BListItem*)items.ItemAtFast(i));
RemoveStylesCommand* command
= new (nothrow) RemoveStylesCommand(fStyleContainer, indices, count);
fCommandStack->Perform(command);
}
BListItem*
StyleListView::CloneItem(int32 index) const
{
if (StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(index))) {
return new StyleListItem(item->style,
const_cast<StyleListView*>(this),
fCurrentShape != NULL);
}
return NULL;
}
int32
StyleListView::IndexOfSelectable(Selectable* selectable) const
{
Style* style = dynamic_cast<Style*>(selectable);
if (style == NULL)
return -1;
int count = CountItems();
for (int32 i = 0; i < count; i++) {
if (SelectableFor(ItemAt(i)) == style)
return i;
}
return -1;
}
Selectable*
StyleListView::SelectableFor(BListItem* item) const
{
StyleListItem* styleItem = dynamic_cast<StyleListItem*>(item);
if (styleItem != NULL)
return styleItem->style;
return NULL;
}
void
StyleListView::ItemAdded(Style* style, int32 index)
{
if (!LockLooper())
return;
if (_AddStyle(style, index))
Select(index);
UnlockLooper();
}
void
StyleListView::ItemRemoved(Style* style)
{
if (!LockLooper())
return;
_RemoveStyle(style);
UnlockLooper();
}
void
StyleListView::SetMenu(BMenu* menu)
{
if (fMenu == menu)
return;
fMenu = menu;
if (fMenu == NULL)
return;
fAddItem = new BMenuItem(B_TRANSLATE("Add"), new BMessage(MSG_ADD));
fMenu->AddItem(fAddItem);
fMenu->AddSeparatorItem();
fDuplicateItem = new BMenuItem(B_TRANSLATE("Duplicate"),
new BMessage(MSG_DUPLICATE));
fMenu->AddItem(fDuplicateItem);
fResetTransformationItem = new BMenuItem(B_TRANSLATE("Reset transformation"),
new BMessage(MSG_RESET_TRANSFORMATION));
fMenu->AddItem(fResetTransformationItem);
fMenu->AddSeparatorItem();
fRemoveItem = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE));
fMenu->AddItem(fRemoveItem);
fMenu->SetTargetForItems(this);
_UpdateMenu();
}
void
StyleListView::SetStyleContainer(Container<Style>* container)
{
if (fStyleContainer == container)
return;
if (fStyleContainer != NULL)
fStyleContainer->RemoveListener(this);
_MakeEmpty();
fStyleContainer = container;
if (fStyleContainer == NULL)
return;
fStyleContainer->AddListener(this);
int32 count = fStyleContainer->CountItems();
for (int32 i = 0; i < count; i++)
_AddStyle(fStyleContainer->ItemAtFast(i), i);
}
void
StyleListView::SetShapeContainer(Container<Shape>* container)
{
if (fShapeContainer == container)
return;
if (fShapeContainer)
fShapeContainer->RemoveListener(fShapeListener);
fShapeContainer = container;
if (fShapeContainer)
fShapeContainer->AddListener(fShapeListener);
}
void
StyleListView::SetCommandStack(CommandStack* stack)
{
fCommandStack = stack;
}
void
StyleListView::SetCurrentColor(CurrentColor* color)
{
fCurrentColor = color;
}
void
StyleListView::SetCurrentShape(Shape* shape)
{
PathSourceShape* pathSourceShape = dynamic_cast<PathSourceShape*>(shape);
if (fCurrentShape == pathSourceShape)
return;
fCurrentShape = pathSourceShape;
fShapeListener->SetShape(pathSourceShape);
_UpdateMarks();
}
bool
StyleListView::_AddStyle(Style* style, int32 index)
{
if (style != NULL) {
return AddItem(new StyleListItem(
style, this, fCurrentShape != NULL), index);
}
return false;
}
bool
StyleListView::_RemoveStyle(Style* style)
{
StyleListItem* item = _ItemForStyle(style);
if (item != NULL && RemoveItem(item)) {
delete item;
return true;
}
return false;
}
StyleListItem*
StyleListView::_ItemForStyle(Style* style) const
{
int count = CountItems();
for (int32 i = 0; i < count; i++) {
StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
if (item == NULL)
continue;
if (item->style == style)
return item;
}
return NULL;
}
void
StyleListView::_UpdateMarks()
{
int32 count = CountItems();
if (fCurrentShape) {
for (int32 i = 0; i < count; i++) {
StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
if (item == NULL)
continue;
item->SetMarkEnabled(true);
item->SetMarked(fCurrentShape->Style() == item->style);
}
} else {
for (int32 i = 0; i < count; i++) {
StyleListItem* item = dynamic_cast<StyleListItem*>(ItemAt(i));
if (item == NULL)
continue;
item->SetMarkEnabled(false);
}
}
Invalidate();
}
void
StyleListView::_SetStyleMarked(Style* style, bool marked)
{
StyleListItem* item = _ItemForStyle(style);
if (item != NULL)
item->SetMarked(marked);
}
void
StyleListView::_UpdateMenu()
{
if (fMenu == NULL)
return;
bool hasSelection = CurrentSelection(0) >= 0;
fDuplicateItem->SetEnabled(hasSelection);
fResetTransformationItem->SetEnabled(hasSelection);
fRemoveItem->SetEnabled(hasSelection);
}