/* * Copyright 2006-2012, Stephan Aßmus . * All rights reserved. Distributed under the terms of the MIT License. */ #include "StyleListView.h" #include #include #include #include #include #include #include #include #include #include #include #include "AddStylesCommand.h" #include "AssignStyleCommand.h" #include "CurrentColor.h" #include "CommandStack.h" #include "GradientTransformable.h" #include "MoveStylesCommand.h" #include "RemoveStylesCommand.h" #include "Style.h" #include "Observer.h" #include "ResetTransformationCommand.h" #include "Shape.h" #include "ShapeContainer.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); } // SimpleItem interface virtual void Draw(BView* owner, BRect itemFrame, uint32 flags) { SimpleItem::DrawBackground(owner, itemFrame, flags); // text if (IsSelected()) owner->SetHighColor(ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR)); else owner->SetHighColor(ui_color(B_LIST_ITEM_TEXT_COLOR)); 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; 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(); } // StyleListItem 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 ShapeContainerListener { public: ShapeStyleListener(StyleListView* listView) : fListView(listView), fShape(NULL) { } virtual ~ShapeStyleListener() { SetShape(NULL); } // ShapeListener interface 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); } // ShapeContainerListener interface virtual void ShapeAdded(Shape* shape, int32 index) { } virtual void ShapeRemoved(Shape* shape) { fListView->SetCurrentShape(NULL); } // ShapeStyleListener void SetShape(Shape* shape) { if (fShape == shape) return; if (fShape) fShape->RemoveListener(this); fShape = shape; if (fShape) fShape->AddListener(this); } Shape* CurrentShape() const { return fShape; } private: StyleListView* fListView; Shape* fShape; }; // #pragma mark - 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; } // #pragma mark - 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( 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) { // NOTE: single selection list StyleListItem* item = dynamic_cast(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(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)) { // set the style on the shape if (fCommandStack) { ::Command* command = new AssignStyleCommand( fCurrentShape, style); fCommandStack->Perform(command); } else { fCurrentShape->SetStyle(style); } handled = true; } } if (!handled) SimpleListView::MouseDown(where); } void StyleListView::MakeDragMessage(BMessage* message) const { SimpleListView::MakeDragMessage(message); message->AddPointer("container", fStyleContainer); int32 count = CountSelectedItems(); for (int32 i = 0; i < count; i++) { StyleListItem* item = dynamic_cast( ItemAt(CurrentSelection(i))); if (item != NULL) { message->AddPointer("style", (void*)item->style); BMessage archive; if (item->style->Archive(&archive, true) == B_OK) message->AddMessage("style archive", &archive); } else break; } } bool StyleListView::AcceptDragMessage(const BMessage* message) const { return SimpleListView::AcceptDragMessage(message); } void StyleListView::SetDropTargetRect(const BMessage* message, BPoint where) { SimpleListView::SetDropTargetRect(message, where); } bool StyleListView::HandleDropMessage(const BMessage* message, int32 dropIndex) { // Let SimpleListView handle drag-sorting (when drag came from ourself) if (SimpleListView::HandleDropMessage(message, dropIndex)) return true; if (fCommandStack == NULL || fStyleContainer == 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 styles; while (true) { BMessage archive; if (message->FindMessage("style archive", index, &archive) != B_OK) break; Style* style = new(std::nothrow) Style(&archive); if (style == NULL) break; if (!styles.AddItem(style)) { delete style; break; } index++; } int32 count = styles.CountItems(); if (count == 0) return false; AddStylesCommand* command = new(std::nothrow) AddStylesCommand( fStyleContainer, (Style**)styles.Items(), count, 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((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((BListItem*)items.ItemAtFast(i)); styles[i] = item ? new (nothrow) Style(*item->style) : NULL; } AddStylesCommand* command = new (nothrow) AddStylesCommand(fStyleContainer, styles, count, 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(); Style* styles[count]; for (int32 i = 0; i < count; i++) { StyleListItem* item = dynamic_cast( (BListItem*)items.ItemAtFast(i)); if (item) styles[i] = item->style; else styles[i] = NULL; } RemoveStylesCommand* command = new (nothrow) RemoveStylesCommand(fStyleContainer, styles, count); fCommandStack->Perform(command); } BListItem* StyleListView::CloneItem(int32 index) const { if (StyleListItem* item = dynamic_cast(ItemAt(index))) { return new StyleListItem(item->style, const_cast(this), fCurrentShape != NULL); } return NULL; } int32 StyleListView::IndexOfSelectable(Selectable* selectable) const { Style* style = dynamic_cast(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(item); if (styleItem != NULL) return styleItem->style; return NULL; } // #pragma mark - void StyleListView::StyleAdded(Style* style, int32 index) { // NOTE: we are in the thread that messed with the // StyleContainer, 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 (_AddStyle(style, index)) Select(index); UnlockLooper(); } void StyleListView::StyleRemoved(Style* style) { // NOTE: we are in the thread that messed with the // StyleContainer, 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 Style objects _RemoveStyle(style); UnlockLooper(); } // #pragma mark - void StyleListView::SetMenu(BMenu* menu) { if (fMenu == menu) return; fMenu = menu; if (fMenu == NULL) return; fAddMI = new BMenuItem(B_TRANSLATE("Add"), new BMessage(MSG_ADD)); fMenu->AddItem(fAddMI); fMenu->AddSeparatorItem(); fDuplicateMI = new BMenuItem(B_TRANSLATE("Duplicate"), new BMessage(MSG_DUPLICATE)); fMenu->AddItem(fDuplicateMI); fResetTransformationMI = new BMenuItem(B_TRANSLATE("Reset transformation"), new BMessage(MSG_RESET_TRANSFORMATION)); fMenu->AddItem(fResetTransformationMI); fMenu->AddSeparatorItem(); fRemoveMI = new BMenuItem(B_TRANSLATE("Remove"), new BMessage(MSG_REMOVE)); fMenu->AddItem(fRemoveMI); fMenu->SetTargetForItems(this); _UpdateMenu(); } void StyleListView::SetStyleContainer(StyleContainer* container) { if (fStyleContainer == container) return; // detach from old container if (fStyleContainer != NULL) fStyleContainer->RemoveListener(this); _MakeEmpty(); fStyleContainer = container; if (fStyleContainer == NULL) return; fStyleContainer->AddListener(this); // sync int32 count = fStyleContainer->CountStyles(); for (int32 i = 0; i < count; i++) _AddStyle(fStyleContainer->StyleAtFast(i), i); } void StyleListView::SetShapeContainer(ShapeContainer* container) { if (fShapeContainer == container) return; // detach from old container 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) { if (fCurrentShape == shape) return; fCurrentShape = shape; fShapeListener->SetShape(shape); _UpdateMarks(); } // #pragma mark - 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(ItemAt(i)); if (item == NULL) continue; if (item->style == style) return item; } return NULL; } // #pragma mark - void StyleListView::_UpdateMarks() { int32 count = CountItems(); if (fCurrentShape) { // enable display of marks and mark items whoes // style is contained in fCurrentShape for (int32 i = 0; i < count; i++) { StyleListItem* item = dynamic_cast(ItemAt(i)); if (item == NULL) continue; item->SetMarkEnabled(true); item->SetMarked(fCurrentShape->Style() == item->style); } } else { // disable display of marks for (int32 i = 0; i < count; i++) { StyleListItem* item = dynamic_cast(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 gotSelection = CurrentSelection(0) >= 0; fDuplicateMI->SetEnabled(gotSelection); // TODO: only enable fResetTransformationMI if styles // with gradients are selected! fResetTransformationMI->SetEnabled(gotSelection); fRemoveMI->SetEnabled(gotSelection); }