/* * Copyright 2006-2012, 2023, Haiku, Inc. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus * Zardshard */ #include "PathListView.h" #include #include #include #include #include #include #include #include #include #include #include #include "AddPathsCommand.h" #include "CleanUpPathCommand.h" #include "CommandStack.h" #include "MovePathsCommand.h" #include "Observer.h" #include "PathSourceShape.h" #include "RemovePathsCommand.h" #include "ReversePathCommand.h" #include "RotatePathIndicesCommand.h" #include "Shape.h" #include "Selection.h" #include "UnassignPathCommand.h" #include "Util.h" #include "VectorPath.h" #undef B_TRANSLATION_CONTEXT #define B_TRANSLATION_CONTEXT "Icon-O-Matic-PathsList" using std::nothrow; 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 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; // mark BRect markRect = itemFrame; float markRectBorderTint = B_DARKEN_1_TINT; float markRectFillTint = 1.04; float markTint = B_DARKEN_4_TINT; // Dark Themes 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); } } // Observer interface virtual void ObjectChanged(const Observable* object) { UpdateText(); } // PathListItem void SetPath(VectorPath* p) { if (p == path) return; if (path) { path->RemoveObserver(this); path->ReleaseReference(); } path = p; if (path) { path->AcquireReference(); 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(); } } public: VectorPath* path; private: PathListView* fListView; bool fMarkEnabled; bool fMarked; }; class ShapePathListener : public ContainerListener, public ContainerListener { public: ShapePathListener(PathListView* listView) : fListView(listView), fShape(NULL) { } virtual ~ShapePathListener() { SetShape(NULL); } // ContainerListener interface virtual void ItemAdded(VectorPath* path, int32 index) { fListView->_SetPathMarked(path, true); } virtual void ItemRemoved(VectorPath* path) { fListView->_SetPathMarked(path, false); } // ContainerListener interface virtual void ItemAdded(Shape* shape, int32 index) { } virtual void ItemRemoved(Shape* shape) { fListView->SetCurrentShape(NULL); } // ShapePathListener void SetShape(PathSourceShape* 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; PathSourceShape* 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_CW = 'ricw', MSG_ROTATE_INDICES_CCW = 'ricc', MSG_REMOVE = 'remp', }; 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); } PathListView::~PathListView() { _MakeEmpty(); delete fMessage; if (fPathContainer != NULL) fPathContainer->RemoveListener(this); if (fShapeContainer != NULL) fShapeContainer->RemoveListener(fShapePathListener); delete fShapePathListener; } void PathListView::SelectionChanged() { SimpleListView::SelectionChanged(); if (!fSyncingToSelection) { // NOTE: single selection list PathListItem* item = dynamic_cast(ItemAt(CurrentSelection(0))); if (fMessage != NULL) { BMessage message(*fMessage); message.AddPointer("path", item ? (void*)item->path : NULL); Invoke(&message); } } _UpdateMenu(); } void PathListView::MouseDown(BPoint where) { if (fCurrentShape == NULL) { SimpleListView::MouseDown(where); return; } bool handled = false; int32 index = IndexOf(where); PathListItem* item = dynamic_cast(ItemAt(index)); if (item != NULL) { 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()->HasItem(path)) { command = new UnassignPathCommand(fCurrentShape, path); } else { VectorPath* paths[1]; paths[0] = path; command = new AddPathsCommand(fCurrentShape->Paths(), paths, 1, false, fCurrentShape->Paths()->CountItems()); } fCommandStack->Perform(command); handled = true; } } if (!handled) SimpleListView::MouseDown(where); } void PathListView::MessageReceived(BMessage* message) { switch (message->what) { case MSG_ADD: if (fCommandStack != NULL) { VectorPath* path; AddPathsCommand* command; new_path(fPathContainer, &path, &command); fCommandStack->Perform(command); } break; case MSG_ADD_RECT: if (fCommandStack != NULL) { VectorPath* path; AddPathsCommand* command; new_path(fPathContainer, &path, &command); if (path != NULL) { 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 != NULL) { VectorPath* path; AddPathsCommand* command; new_path(fPathContainer, &path, &command); if (path != NULL) { // 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 != NULL) { PathListItem* item = dynamic_cast( ItemAt(CurrentSelection(0))); if (item == NULL) break; VectorPath* path; AddPathsCommand* command; new_path(fPathContainer, &path, &command, item->path); fCommandStack->Perform(command); } break; case MSG_REVERSE: if (fCommandStack != NULL) { PathListItem* item = dynamic_cast( ItemAt(CurrentSelection(0))); if (item == NULL) break; ReversePathCommand* command = new (nothrow) ReversePathCommand(item->path); fCommandStack->Perform(command); } break; case MSG_CLEAN_UP: if (fCommandStack != NULL) { PathListItem* item = dynamic_cast( ItemAt(CurrentSelection(0))); if (item == NULL) break; CleanUpPathCommand* command = new (nothrow) CleanUpPathCommand(item->path); fCommandStack->Perform(command); } break; case MSG_ROTATE_INDICES_CW: case MSG_ROTATE_INDICES_CCW: if (fCommandStack != NULL) { PathListItem* item = dynamic_cast( ItemAt(CurrentSelection(0))); if (item == NULL) break; RotatePathIndicesCommand* command = new (nothrow) RotatePathIndicesCommand(item->path, message->what == MSG_ROTATE_INDICES_CW); fCommandStack->Perform(command); } break; case MSG_REMOVE: RemoveSelected(); break; default: SimpleListView::MessageReceived(message); break; } } status_t PathListView::ArchiveSelection(BMessage* into, bool deep) const { into->what = PathListView::kSelectionArchiveCode; int32 count = CountSelectedItems(); for (int32 i = 0; i < count; i++) { PathListItem* item = dynamic_cast( ItemAt(CurrentSelection(i))); if (item != NULL) { BMessage archive; if (item->path->Archive(&archive, deep) == B_OK) into->AddMessage("path", &archive); } else return B_ERROR; } return B_OK; } bool PathListView::InstantiateSelection(const BMessage* archive, int32 dropIndex) { if (archive->what != PathListView::kSelectionArchiveCode || fCommandStack == NULL || fPathContainer == NULL) return false; // Drag may have come from another instance, like in another window. // Reconstruct the Styles from the archive and add them at the drop // index. int index = 0; BList paths; while (true) { BMessage pathArchive; if (archive->FindMessage("path", index, &pathArchive) != B_OK) break; VectorPath* path = new(std::nothrow) VectorPath(&pathArchive); if (path == NULL) break; if (!paths.AddItem(path)) { delete path; break; } index++; } int32 count = paths.CountItems(); if (count == 0) return false; AddPathsCommand* command = new(nothrow) AddPathsCommand(fPathContainer, (VectorPath**)paths.Items(), count, true, dropIndex); if (command == NULL) { for (int32 i = 0; i < count; i++) delete (VectorPath*)paths.ItemAtFast(i); return false; } fCommandStack->Perform(command); return true; } void PathListView::MoveItems(BList& items, int32 toIndex) { if (fCommandStack == NULL || fPathContainer == NULL) return; int32 count = items.CountItems(); VectorPath** paths = new (nothrow) VectorPath*[count]; if (paths == NULL) return; for (int32 i = 0; i < count; i++) { PathListItem* item = dynamic_cast((BListItem*)items.ItemAtFast(i)); paths[i] = item ? item->path : NULL; } MovePathsCommand* command = new (nothrow) MovePathsCommand(fPathContainer, paths, count, toIndex); if (command == NULL) { delete[] paths; return; } fCommandStack->Perform(command); } void PathListView::CopyItems(BList& items, int32 toIndex) { if (fCommandStack == NULL || fPathContainer == NULL) return; int32 count = items.CountItems(); VectorPath* paths[count]; for (int32 i = 0; i < count; i++) { PathListItem* item = dynamic_cast((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 == NULL) { for (int32 i = 0; i < count; i++) delete paths[i]; return; } fCommandStack->Perform(command); } void PathListView::RemoveItemList(BList& items) { if (fCommandStack == NULL || fPathContainer == NULL) return; int32 count = items.CountItems(); int32 indices[count]; for (int32 i = 0; i < count; i++) indices[i] = IndexOf((BListItem*)items.ItemAtFast(i)); RemovePathsCommand* command = new (nothrow) RemovePathsCommand( fPathContainer, indices, count); fCommandStack->Perform(command); } BListItem* PathListView::CloneItem(int32 index) const { if (PathListItem* item = dynamic_cast(ItemAt(index))) { return new(nothrow) PathListItem(item->path, const_cast(this), fCurrentShape != NULL); } return NULL; } int32 PathListView::IndexOfSelectable(Selectable* selectable) const { VectorPath* path = dynamic_cast(selectable); if (path == NULL) return -1; int32 count = CountItems(); for (int32 i = 0; i < count; i++) { if (SelectableFor(ItemAt(i)) == path) return i; } return -1; } Selectable* PathListView::SelectableFor(BListItem* item) const { PathListItem* pathItem = dynamic_cast(item); if (pathItem != NULL) return pathItem->path; return NULL; } // #pragma mark - void PathListView::ItemAdded(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(index); UnlockLooper(); } void PathListView::ItemRemoved(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 - void PathListView::SetPathContainer(Container* container) { if (fPathContainer == container) return; // detach from old container if (fPathContainer != NULL) fPathContainer->RemoveListener(this); _MakeEmpty(); fPathContainer = container; if (fPathContainer == NULL) return; fPathContainer->AddListener(this); // sync // if (!fPathContainer->ReadLock()) // return; int32 count = fPathContainer->CountItems(); for (int32 i = 0; i < count; i++) _AddPath(fPathContainer->ItemAtFast(i), i); // fPathContainer->ReadUnlock(); } void PathListView::SetShapeContainer(Container* container) { if (fShapeContainer == container) return; // detach from old container if (fShapeContainer != NULL) fShapeContainer->RemoveListener(fShapePathListener); fShapeContainer = container; if (fShapeContainer != NULL) fShapeContainer->AddListener(fShapePathListener); } void PathListView::SetCommandStack(CommandStack* stack) { fCommandStack = stack; } void PathListView::SetMenu(BMenu* menu) { fMenu = menu; if (fMenu == NULL) return; fAddMI = new BMenuItem(B_TRANSLATE("Add"), new BMessage(MSG_ADD)); fAddRectMI = new BMenuItem(B_TRANSLATE("Add rect"), new BMessage(MSG_ADD_RECT)); fAddCircleMI = new BMenuItem(B_TRANSLATE("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(B_TRANSLATE("Duplicate"), new BMessage(MSG_DUPLICATE)); fReverseMI = new BMenuItem(B_TRANSLATE("Reverse"), new BMessage(MSG_REVERSE)); fCleanUpMI = new BMenuItem(B_TRANSLATE("Clean up"), new BMessage(MSG_CLEAN_UP)); fRotateIndicesRightMI = new BMenuItem(B_TRANSLATE("Rotate indices forwards"), new BMessage(MSG_ROTATE_INDICES_CCW), 'R'); fRotateIndicesLeftMI = new BMenuItem(B_TRANSLATE("Rotate indices backwards"), new BMessage(MSG_ROTATE_INDICES_CW), 'R', B_SHIFT_KEY); fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE)); fMenu->AddItem(fAddMI); fMenu->AddItem(fAddRectMI); fMenu->AddItem(fAddCircleMI); // fMenu->AddItem(fAddArcMI); fMenu->AddSeparatorItem(); fMenu->AddItem(fDuplicateMI); fMenu->AddItem(fReverseMI); fMenu->AddItem(fCleanUpMI); fMenu->AddSeparatorItem(); fMenu->AddItem(fRotateIndicesRightMI); fMenu->AddItem(fRotateIndicesLeftMI); fMenu->AddSeparatorItem(); fMenu->AddItem(fRemoveMI); fMenu->SetTargetForItems(this); _UpdateMenu(); } void PathListView::SetCurrentShape(Shape* shape) { if (fCurrentShape == shape) return; fCurrentShape = dynamic_cast(shape); fShapePathListener->SetShape(fCurrentShape); _UpdateMarks(); } // #pragma mark - bool PathListView::_AddPath(VectorPath* path, int32 index) { if (path == NULL) return false; PathListItem* item = new(nothrow) PathListItem(path, this, fCurrentShape != NULL); if (item == NULL) return false; if (!AddItem(item, index)) { delete item; return false; } return true; } bool PathListView::_RemovePath(VectorPath* path) { PathListItem* item = _ItemForPath(path); if (item != NULL && RemoveItem(item)) { delete item; return true; } return false; } PathListItem* PathListView::_ItemForPath(VectorPath* path) const { int32 count = CountItems(); for (int32 i = 0; i < count; i++) { PathListItem* item = dynamic_cast(ItemAt(i)); if (item == NULL) continue; if (item->path == path) return item; } return NULL; } // #pragma mark - void PathListView::_UpdateMarks() { int32 count = CountItems(); if (fCurrentShape != NULL) { // enable display of marks and mark items whoes // path is contained in fCurrentShape for (int32 i = 0; i < count; i++) { PathListItem* item = dynamic_cast(ItemAt(i)); if (item == NULL) continue; item->SetMarkEnabled(true); item->SetMarked(fCurrentShape->Paths()->HasItem(item->path)); } } else { // disable display of marks for (int32 i = 0; i < count; i++) { PathListItem* item = dynamic_cast(ItemAt(i)); if (item == NULL) continue; item->SetMarkEnabled(false); } } Invalidate(); } void PathListView::_SetPathMarked(VectorPath* path, bool marked) { PathListItem* item = _ItemForPath(path); if (item != NULL) item->SetMarked(marked); } void PathListView::_UpdateMenu() { if (fMenu == NULL) return; bool gotSelection = CurrentSelection(0) >= 0; fDuplicateMI->SetEnabled(gotSelection); fReverseMI->SetEnabled(gotSelection); fCleanUpMI->SetEnabled(gotSelection); fRotateIndicesLeftMI->SetEnabled(gotSelection); fRotateIndicesRightMI->SetEnabled(gotSelection); fRemoveMI->SetEnabled(gotSelection); }