Files
haiku-beta6/src/apps/icon-o-matic/gui/PathListView.cpp
T
Stephan Aßmus f4bd80a2dc * when loading an icon via drag&drop, the file will be remembered
for saving later
* added "Clean Up" feature to "Path" menu
* added "Freeze Transformation" to "Shape" menu (will apply the
  current shape transformation onto the path and reset the shape
  transformation to identity)
* small cleanup in ShapeListView
* implemented zooming in CanvasView
* added context menu while editing a path
* implemented undo/redo for transforming points
  (press T or use context menu)
* added "Split Points" feature (path editing context menu), it will
  make two points from one control point
* improved selecting path points with selection rect
* improved SVG import for zuMis BeOS icons (more precise scale)



git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@18800 a95241bf-73f2-0310-859d-f6bbb57e9c96
2006-09-10 22:31:09 +00:00

851 lines
18 KiB
C++

/*
* Copyright 2006, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <[email protected]>
*/
#include "PathListView.h"
#include <stdio.h>
#include <Application.h>
#include <ListItem.h>
#include <Menu.h>
#include <MenuItem.h>
#include <Message.h>
#include <Mime.h>
#include <Window.h>
#include "AddPathsCommand.h"
#include "CleanUpPathCommand.h"
#include "CommandStack.h"
#include "MovePathsCommand.h"
#include "Observer.h"
#include "RemovePathsCommand.h"
#include "ReversePathCommand.h"
#include "Shape.h"
#include "ShapeContainer.h"
#include "Selection.h"
#include "UnassignPathCommand.h"
#include "Util.h"
#include "VectorPath.h"
static const float kMarkWidth = 14.0;
static const float kBorderOffset = 3.0;
static const float kTextOffset = 4.0;
class PathListItem : public SimpleItem,
public Observer {
public:
PathListItem(VectorPath* p,
PathListView* listView,
bool markEnabled)
: SimpleItem(""),
path(NULL),
fListView(listView),
fMarkEnabled(markEnabled),
fMarked(false)
{
SetPath(p);
}
virtual ~PathListItem()
{
SetPath(NULL);
}
// SimpleItem interface
virtual void Draw(BView* owner, BRect itemFrame, uint32 flags)
{
SimpleItem::DrawBackground(owner, itemFrame, flags);
// text
owner->SetHighColor(0, 0, 0, 255);
font_height fh;
owner->GetFontHeight(&fh);
BString truncatedString(Text());
owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE,
itemFrame.Width()
- kBorderOffset
- kMarkWidth
- kTextOffset
- kBorderOffset);
float height = itemFrame.Height();
float textHeight = fh.ascent + fh.descent;
BPoint pos;
pos.x = itemFrame.left
+ kBorderOffset + kMarkWidth + kTextOffset;
pos.y = itemFrame.top
+ ceilf((height - textHeight) / 2.0 + fh.ascent);
owner->DrawString(truncatedString.String(), pos);
if (!fMarkEnabled)
return;
// mark
BRect markRect = itemFrame;
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(), B_DARKEN_1_TINT));
owner->StrokeRect(markRect);
markRect.InsetBy(1, 1);
owner->SetHighColor(tint_color(owner->LowColor(), 1.04));
owner->FillRect(markRect);
if (fMarked) {
markRect.InsetBy(2, 2);
owner->SetHighColor(tint_color(owner->LowColor(),
B_DARKEN_4_TINT));
owner->SetPenSize(2);
owner->StrokeLine(markRect.LeftTop(), markRect.RightBottom());
owner->StrokeLine(markRect.LeftBottom(), markRect.RightTop());
owner->SetPenSize(1);
}
}
// Observer interface
virtual void ObjectChanged(const Observable* object)
{
UpdateText();
}
// PathListItem
void SetPath(VectorPath* p)
{
if (p == path)
return;
if (path) {
path->RemoveObserver(this);
path->Release();
}
path = p;
if (path) {
path->Acquire();
path->AddObserver(this);
UpdateText();
}
}
void UpdateText()
{
SetText(path->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();
}
}
VectorPath* path;
private:
PathListView* fListView;
bool fMarkEnabled;
bool fMarked;
};
class ShapePathListener : public PathContainerListener,
public ShapeContainerListener {
public:
ShapePathListener(PathListView* listView)
: fListView(listView),
fShape(NULL)
{
}
virtual ~ShapePathListener()
{
SetShape(NULL);
}
// PathContainerListener interface
virtual void PathAdded(VectorPath* path, int32 index)
{
fListView->_SetPathMarked(path, true);
}
virtual void PathRemoved(VectorPath* path)
{
fListView->_SetPathMarked(path, false);
}
// ShapeContainerListener interface
virtual void ShapeAdded(Shape* shape, int32 index) {}
virtual void ShapeRemoved(Shape* shape)
{
fListView->SetCurrentShape(NULL);
}
// ShapePathListener
void SetShape(Shape* shape)
{
if (fShape == shape)
return;
if (fShape)
fShape->Paths()->RemoveListener(this);
fShape = shape;
if (fShape)
fShape->Paths()->AddListener(this);
}
Shape* CurrentShape() const
{
return fShape;
}
private:
PathListView* fListView;
Shape* fShape;
};
// #pragma mark -
enum {
MSG_ADD = 'addp',
MSG_ADD_RECT = 'addr',
MSG_ADD_CIRCLE = 'addc',
MSG_ADD_ARC = 'adda',
MSG_DUPLICATE = 'dupp',
MSG_REVERSE = 'rvrs',
MSG_CLEAN_UP = 'clup',
MSG_ROTATE_INDICES = 'roti',
MSG_REMOVE = 'remp',
};
// constructor
PathListView::PathListView(BRect frame,
const char* name,
BMessage* message, BHandler* target)
: SimpleListView(frame, name,
NULL, B_SINGLE_SELECTION_LIST),
fMessage(message),
fMenu(NULL),
fPathContainer(NULL),
fShapeContainer(NULL),
fCommandStack(NULL),
fCurrentShape(NULL),
fShapePathListener(new ShapePathListener(this))
{
SetTarget(target);
}
// destructor
PathListView::~PathListView()
{
_MakeEmpty();
delete fMessage;
if (fPathContainer)
fPathContainer->RemoveListener(this);
if (fShapeContainer)
fShapeContainer->RemoveListener(fShapePathListener);
delete fShapePathListener;
}
// SelectionChanged
void
PathListView::SelectionChanged()
{
SimpleListView::SelectionChanged();
if (!fSyncingToSelection) {
// NOTE: single selection list
PathListItem* item
= dynamic_cast<PathListItem*>(ItemAt(CurrentSelection(0)));
if (fMessage) {
BMessage message(*fMessage);
message.AddPointer("path", item ? (void*)item->path : NULL);
Invoke(&message);
}
}
_UpdateMenu();
}
// MouseDown
void
PathListView::MouseDown(BPoint where)
{
if (!fCurrentShape) {
SimpleListView::MouseDown(where);
return;
}
bool handled = false;
int32 index = IndexOf(where);
PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index));
if (item) {
BRect itemFrame(ItemFrame(index));
itemFrame.right = itemFrame.left
+ kBorderOffset + kMarkWidth
+ kTextOffset / 2.0;
VectorPath* path = item->path;
if (itemFrame.Contains(where) && fCommandStack) {
// add or remove the path to the shape
::Command* command;
if (fCurrentShape->Paths()->HasPath(path)) {
command = new UnassignPathCommand(
fCurrentShape, path);
} else {
VectorPath* paths[1];
paths[0] = path;
command = new AddPathsCommand(
fCurrentShape->Paths(),
paths, 1, false,
fCurrentShape->Paths()->CountPaths());
}
fCommandStack->Perform(command);
handled = true;
}
}
if (!handled)
SimpleListView::MouseDown(where);
}
// MessageReceived
void
PathListView::MessageReceived(BMessage* message)
{
switch (message->what) {
case MSG_ADD:
if (fCommandStack) {
VectorPath* path;
AddPathsCommand* command;
new_path(fPathContainer, &path, &command);
fCommandStack->Perform(command);
}
break;
case MSG_ADD_RECT:
if (fCommandStack) {
VectorPath* path;
AddPathsCommand* command;
new_path(fPathContainer, &path, &command);
if (path) {
path->AddPoint(BPoint(16, 16));
path->AddPoint(BPoint(16, 48));
path->AddPoint(BPoint(48, 48));
path->AddPoint(BPoint(48, 16));
path->SetClosed(true);
}
fCommandStack->Perform(command);
}
break;
case MSG_ADD_CIRCLE:
// TODO: ask for number of secions
if (fCommandStack) {
VectorPath* path;
AddPathsCommand* command;
new_path(fPathContainer, &path, &command);
if (path) {
// add four control points defining a circle:
// a
// b d
// c
BPoint a(32, 16);
BPoint b(16, 32);
BPoint c(32, 48);
BPoint d(48, 32);
path->AddPoint(a);
path->AddPoint(b);
path->AddPoint(c);
path->AddPoint(d);
path->SetClosed(true);
float controlDist = 0.552284 * 16;
path->SetPoint(0, a, a + BPoint(controlDist, 0.0),
a + BPoint(-controlDist, 0.0), true);
path->SetPoint(1, b, b + BPoint(0.0, -controlDist),
b + BPoint(0.0, controlDist), true);
path->SetPoint(2, c, c + BPoint(-controlDist, 0.0),
c + BPoint(controlDist, 0.0), true);
path->SetPoint(3, d, d + BPoint(0.0, controlDist),
d + BPoint(0.0, -controlDist), true);
}
fCommandStack->Perform(command);
}
break;
case MSG_DUPLICATE:
if (fCommandStack) {
PathListItem* item = dynamic_cast<PathListItem*>(
ItemAt(CurrentSelection(0)));
if (!item)
break;
VectorPath* path;
AddPathsCommand* command;
new_path(fPathContainer, &path, &command, item->path);
fCommandStack->Perform(command);
}
break;
case MSG_REVERSE:
if (fCommandStack) {
PathListItem* item = dynamic_cast<PathListItem*>(
ItemAt(CurrentSelection(0)));
if (!item)
break;
ReversePathCommand* command
= new (nothrow) ReversePathCommand(item->path);
fCommandStack->Perform(command);
}
break;
case MSG_CLEAN_UP:
if (fCommandStack) {
PathListItem* item = dynamic_cast<PathListItem*>(
ItemAt(CurrentSelection(0)));
if (!item)
break;
CleanUpPathCommand* command
= new (nothrow) CleanUpPathCommand(item->path);
fCommandStack->Perform(command);
}
break;
case MSG_REMOVE:
RemoveSelected();
break;
default:
SimpleListView::MessageReceived(message);
break;
}
}
// MakeDragMessage
void
PathListView::MakeDragMessage(BMessage* message) const
{
SimpleListView::MakeDragMessage(message);
message->AddPointer("container", fPathContainer);
int32 count = CountSelectedItems();
for (int32 i = 0; i < count; i++) {
PathListItem* item = dynamic_cast<PathListItem*>(
ItemAt(CurrentSelection(i)));
if (item)
message->AddPointer("path", (void*)item->path);
else
break;
}
}
// AcceptDragMessage
bool
PathListView::AcceptDragMessage(const BMessage* message) const
{
return SimpleListView::AcceptDragMessage(message);
}
// SetDropTargetRect
void
PathListView::SetDropTargetRect(const BMessage* message, BPoint where)
{
SimpleListView::SetDropTargetRect(message, where);
}
// MoveItems
void
PathListView::MoveItems(BList& items, int32 toIndex)
{
if (!fCommandStack || !fPathContainer)
return;
int32 count = items.CountItems();
VectorPath** paths = new (nothrow) VectorPath*[count];
if (!paths)
return;
for (int32 i = 0; i < count; i++) {
PathListItem* item
= dynamic_cast<PathListItem*>((BListItem*)items.ItemAtFast(i));
paths[i] = item ? item->path : NULL;
}
MovePathsCommand* command
= new (nothrow) MovePathsCommand(fPathContainer,
paths, count, toIndex);
if (!command) {
delete[] paths;
return;
}
fCommandStack->Perform(command);
}
// CopyItems
void
PathListView::CopyItems(BList& items, int32 toIndex)
{
if (!fCommandStack || !fPathContainer)
return;
int32 count = items.CountItems();
VectorPath* paths[count];
for (int32 i = 0; i < count; i++) {
PathListItem* item
= dynamic_cast<PathListItem*>((BListItem*)items.ItemAtFast(i));
paths[i] = item ? new (nothrow) VectorPath(*item->path) : NULL;
}
AddPathsCommand* command
= new (nothrow) AddPathsCommand(fPathContainer,
paths, count, true, toIndex);
if (!command) {
for (int32 i = 0; i < count; i++)
delete paths[i];
return;
}
fCommandStack->Perform(command);
}
// RemoveItemList
void
PathListView::RemoveItemList(BList& items)
{
if (!fCommandStack || !fPathContainer)
return;
int32 count = items.CountItems();
VectorPath* paths[count];
for (int32 i = 0; i < count; i++) {
PathListItem* item = dynamic_cast<PathListItem*>(
(BListItem*)items.ItemAtFast(i));
if (item)
paths[i] = item->path;
else
paths[i] = NULL;
}
RemovePathsCommand* command
= new (nothrow) RemovePathsCommand(fPathContainer,
paths, count);
fCommandStack->Perform(command);
}
// CloneItem
BListItem*
PathListView::CloneItem(int32 index) const
{
if (PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(index))) {
return new PathListItem(item->path,
const_cast<PathListView*>(this),
fCurrentShape != NULL);
}
return NULL;
}
// IndexOfSelectable
int32
PathListView::IndexOfSelectable(Selectable* selectable) const
{
VectorPath* path = dynamic_cast<VectorPath*>(selectable);
if (!path)
return -1;
for (int32 i = 0;
PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
i++) {
if (item->path == path)
return i;
}
return -1;
}
// SelectableFor
Selectable*
PathListView::SelectableFor(BListItem* item) const
{
PathListItem* pathItem = dynamic_cast<PathListItem*>(item);
if (pathItem)
return pathItem->path;
return NULL;
}
// #pragma mark -
// PathAdded
void
PathListView::PathAdded(VectorPath* path, int32 index)
{
// NOTE: we are in the thread that messed with the
// ShapeContainer, so no need to lock the
// container, when this is changed to asynchronous
// notifications, then it would need to be read-locked!
if (!LockLooper())
return;
if (_AddPath(path, index))
Select(CountItems() - 1);
UnlockLooper();
}
// PathRemoved
void
PathListView::PathRemoved(VectorPath* path)
{
// NOTE: we are in the thread that messed with the
// ShapeContainer, so no need to lock the
// container, when this is changed to asynchronous
// notifications, then it would need to be read-locked!
if (!LockLooper())
return;
// NOTE: we're only interested in VectorPath objects
_RemovePath(path);
UnlockLooper();
}
// #pragma mark -
// SetPathContainer
void
PathListView::SetPathContainer(PathContainer* container)
{
if (fPathContainer == container)
return;
// detach from old container
if (fPathContainer)
fPathContainer->RemoveListener(this);
_MakeEmpty();
fPathContainer = container;
if (!fPathContainer)
return;
fPathContainer->AddListener(this);
// sync
// if (!fPathContainer->ReadLock())
// return;
int32 count = fPathContainer->CountPaths();
for (int32 i = 0; i < count; i++)
_AddPath(fPathContainer->PathAtFast(i), i);
// fPathContainer->ReadUnlock();
}
// SetShapeContainer
void
PathListView::SetShapeContainer(ShapeContainer* container)
{
if (fShapeContainer == container)
return;
// detach from old container
if (fShapeContainer)
fShapeContainer->RemoveListener(fShapePathListener);
fShapeContainer = container;
if (fShapeContainer)
fShapeContainer->AddListener(fShapePathListener);
}
// SetCommandStack
void
PathListView::SetCommandStack(CommandStack* stack)
{
fCommandStack = stack;
}
// SetMenu
void
PathListView::SetMenu(BMenu* menu)
{
fMenu = menu;
if (!fMenu)
return;
fAddMI = new BMenuItem("Add", new BMessage(MSG_ADD));
fAddRectMI = new BMenuItem("Add Rect", new BMessage(MSG_ADD_RECT));
fAddCircleMI = new BMenuItem("Add Circle"B_UTF8_ELLIPSIS,
new BMessage(MSG_ADD_CIRCLE));
fAddArcMI = new BMenuItem("Add Arc"B_UTF8_ELLIPSIS,
new BMessage(MSG_ADD_ARC));
fDuplicateMI = new BMenuItem("Duplicate", new BMessage(MSG_DUPLICATE));
fReverseMI = new BMenuItem("Reverse", new BMessage(MSG_REVERSE));
fCleanUpMI = new BMenuItem("Clean Up", new BMessage(MSG_CLEAN_UP));
fRotateIndicesMI = new BMenuItem("Rotate Indices",
new BMessage(MSG_ROTATE_INDICES));
fRemoveMI = new BMenuItem("Remove", new BMessage(MSG_REMOVE));
fMenu->AddItem(fAddMI);
fMenu->AddItem(fAddRectMI);
fMenu->AddItem(fAddCircleMI);
fMenu->AddItem(fAddArcMI);
fAddArcMI->SetEnabled(false);
fMenu->AddSeparatorItem();
fMenu->AddItem(fDuplicateMI);
fMenu->AddItem(fReverseMI);
fMenu->AddItem(fCleanUpMI);
fMenu->AddItem(fRotateIndicesMI);
fMenu->AddSeparatorItem();
fMenu->AddItem(fRemoveMI);
fMenu->SetTargetForItems(this);
_UpdateMenu();
}
// SetCurrentShape
void
PathListView::SetCurrentShape(Shape* shape)
{
if (fCurrentShape == shape)
return;
fCurrentShape = shape;
fShapePathListener->SetShape(shape);
_UpdateMarks();
}
// #pragma mark -
// _AddPath
bool
PathListView::_AddPath(VectorPath* path, int32 index)
{
if (path) {
return AddItem(
new PathListItem(path, this, fCurrentShape != NULL), index);
}
return false;
}
// _RemovePath
bool
PathListView::_RemovePath(VectorPath* path)
{
PathListItem* item = _ItemForPath(path);
if (item && RemoveItem(item)) {
delete item;
return true;
}
return false;
}
// _ItemForPath
PathListItem*
PathListView::_ItemForPath(VectorPath* path) const
{
for (int32 i = 0;
PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
i++) {
if (item->path == path)
return item;
}
return NULL;
}
// #pragma mark -
// _UpdateMarks
void
PathListView::_UpdateMarks()
{
int32 count = CountItems();
if (fCurrentShape) {
// enable display of marks and mark items whoes
// path is contained in fCurrentShape
for (int32 i = 0; i < count; i++) {
PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
if (!item)
continue;
item->SetMarkEnabled(true);
item->SetMarked(fCurrentShape->Paths()->HasPath(item->path));
}
} else {
// disable display of marks
for (int32 i = 0; i < count; i++) {
PathListItem* item = dynamic_cast<PathListItem*>(ItemAt(i));
if (!item)
continue;
item->SetMarkEnabled(false);
}
}
Invalidate();
}
// _SetPathMarked
void
PathListView::_SetPathMarked(VectorPath* path, bool marked)
{
if (PathListItem* item = _ItemForPath(path)) {
item->SetMarked(marked);
}
}
// _UpdateMenu
void
PathListView::_UpdateMenu()
{
if (!fMenu)
return;
bool gotSelection = CurrentSelection(0) >= 0;
fDuplicateMI->SetEnabled(gotSelection);
fReverseMI->SetEnabled(gotSelection);
fRemoveMI->SetEnabled(gotSelection);
}