root/src/apps/icon-o-matic/gui/StyleView.cpp
/*
 * Copyright 2006-2011, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 */

#include "StyleView.h"

#include <new>

#include <Catalog.h>
#include <GridLayout.h>
#include <GroupLayout.h>
#include <Locale.h>
#include <Menu.h>
#include <MenuBar.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Message.h>
#include <PopUpMenu.h>
#include <SpaceLayoutItem.h>
#include <Window.h>

#include "CommandStack.h"
#include "CurrentColor.h"
#include "GradientTransformable.h"
#include "GradientControl.h"
#include "SetColorCommand.h"
#include "SetGradientCommand.h"
#include "Style.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Icon-O-Matic-StyleTypes"


using std::nothrow;

enum {
        MSG_SET_COLOR                   = 'stcl',

        MSG_SET_STYLE_TYPE              = 'stst',
        MSG_SET_GRADIENT_TYPE   = 'stgt',
};

enum {
        STYLE_TYPE_COLOR                = 0,
        STYLE_TYPE_GRADIENT,
};


StyleView::StyleView(BRect frame)
        :
        BView("style view", 0),
        fCommandStack(NULL),
        fCurrentColor(NULL),
        fStyle(NULL),
        fGradient(NULL),
        fIgnoreCurrentColorNotifications(false),
        fIgnoreControlGradientNotifications(false),
        fPreviousBounds(frame.OffsetToCopy(B_ORIGIN))
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

        // style type
        BMenu* menu = new BPopUpMenu(B_TRANSLATE("<unavailable>"));
        BMessage* message = new BMessage(MSG_SET_STYLE_TYPE);
        message->AddInt32("type", STYLE_TYPE_COLOR);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Color"), message));
        message = new BMessage(MSG_SET_STYLE_TYPE);
        message->AddInt32("type", STYLE_TYPE_GRADIENT);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Gradient"), message));

        BGridLayout* layout = new BGridLayout(5, 5);
        SetLayout(layout);

        fStyleType = new BMenuField(B_TRANSLATE("Style type"), menu);

        // gradient type
        menu = new BPopUpMenu(B_TRANSLATE("<unavailable>"));
        message = new BMessage(MSG_SET_GRADIENT_TYPE);
        message->AddInt32("type", GRADIENT_LINEAR);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Linear"), message));
        message = new BMessage(MSG_SET_GRADIENT_TYPE);
        message->AddInt32("type", GRADIENT_CIRCULAR);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Radial"), message));
        message = new BMessage(MSG_SET_GRADIENT_TYPE);
        message->AddInt32("type", GRADIENT_DIAMOND);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Diamond"), message));
        message = new BMessage(MSG_SET_GRADIENT_TYPE);
        message->AddInt32("type", GRADIENT_CONIC);
        menu->AddItem(new BMenuItem(B_TRANSLATE("Conic"), message));

        fGradientType = new BMenuField(B_TRANSLATE("Gradient type"), menu);
        fGradientControl = new GradientControl(new BMessage(MSG_SET_COLOR), this);

        layout->AddItem(BSpaceLayoutItem::CreateVerticalStrut(3), 0, 0, 4);
        layout->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(3), 0, 1, 1, 3);

        layout->AddItem(fStyleType->CreateLabelLayoutItem(), 1, 1);
        layout->AddItem(fStyleType->CreateMenuBarLayoutItem(), 2, 1);

        layout->AddItem(fGradientType->CreateLabelLayoutItem(), 1, 2);
        layout->AddItem(fGradientType->CreateMenuBarLayoutItem(), 2, 2);

        layout->AddView(fGradientControl, 1, 3, 2);

        layout->AddItem(BSpaceLayoutItem::CreateHorizontalStrut(3), 3, 1, 1, 3);
        layout->AddItem(BSpaceLayoutItem::CreateVerticalStrut(3), 0, 4, 4);

        fStyleType->SetEnabled(false);
        fGradientType->SetEnabled(false);
        fGradientControl->SetEnabled(false);
        fGradientControl->Gradient()->AddObserver(this);
}


StyleView::~StyleView()
{
        SetStyle(NULL);
        SetCurrentColor(NULL);
        fGradientControl->Gradient()->RemoveObserver(this);

        if (fGradient.IsSet()) {
                fGradient->RemoveObserver(this);
        }
}


void
StyleView::AttachedToWindow()
{
        fStyleType->Menu()->SetTargetForItems(this);
        fGradientType->Menu()->SetTargetForItems(this);
}


void
StyleView::FrameResized(float width, float height)
{
        fPreviousBounds = Bounds();
}


void
StyleView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_SET_STYLE_TYPE:
                {
                        int32 type;
                        if (message->FindInt32("type", &type) == B_OK)
                                _SetStyleType(type);
                        break;
                }
                case MSG_SET_GRADIENT_TYPE:
                {
                        int32 type;
                        if (message->FindInt32("type", &type) == B_OK)
                                _SetGradientType(type);
                        break;
                }
                case MSG_SET_COLOR:
                case MSG_GRADIENT_CONTROL_FOCUS_CHANGED:
                        _TransferGradientStopColor();
                        break;

                default:
                        BView::MessageReceived(message);
                        break;
        }
}


BSize
StyleView::MinSize()
{
        BSize minSize = BView::MinSize();
        minSize.width = fGradientControl->MinSize().width + 10;
        return minSize;
}


// #pragma mark -


void
StyleView::ObjectChanged(const Observable* object)
{
        if (!fStyle)
                return;

        Gradient* controlGradient = fGradientControl->Gradient();

        // NOTE: it is important to compare the gradients
        // before assignment, or we will get into an endless loop
        if (object == controlGradient) {
                if (!fGradient.IsSet())
                        return;
                if (fIgnoreControlGradientNotifications)
                        return;
                fIgnoreControlGradientNotifications = true;

                if (!fGradient->ColorStepsAreEqual(*controlGradient)) {
                        // Make sure we never apply the transformation from the control
                        // gradient to the style gradient. Setting this here would cause to
                        // re-enter ObjectChanged(), which is prevented to cause harm via
                        // fIgnoreControlGradientNotifications.
                        controlGradient->SetTransform(*fGradient);
                        if (fCommandStack) {
                                fCommandStack->Perform(
                                        new (nothrow) SetGradientCommand(
                                                fStyle, controlGradient));
                        } else {
                                *fGradient = *controlGradient;
                        }
                        // transfer the current gradient color to the current color
                        _TransferGradientStopColor();
                }

                fIgnoreControlGradientNotifications = false;
        } else if (object == fGradient) {
                if (!fGradient->ColorStepsAreEqual(*controlGradient)) {
                        fGradientControl->SetGradient(fGradient);
                        _MarkType(fGradientType->Menu(), fGradient->Type());
                        // transfer the current gradient color to the current color
                        _TransferGradientStopColor();
                }
        } else if (object == fStyle) {
                // maybe the gradient was added or removed
                // or the color changed
                _SetGradient(fStyle->Gradient(), false, true);
                if (fCurrentColor && !fStyle->Gradient())
                        fCurrentColor->SetColor(fStyle->Color());
        } else if (object == fCurrentColor) {
                // NOTE: because of imprecisions in converting
                // RGB<->HSV, the current color can be different
                // even if we just set it to the current
                // gradient stop color, that's why we ignore
                // notifications caused by this situation
                if (!fIgnoreCurrentColorNotifications)
                        _AdoptCurrentColor(fCurrentColor->Color());
        }
}


// #pragma mark -


void
StyleView::SetStyle(Style* style)
{
        if (fStyle == style)
                return;

        if (fStyle) {
                fStyle->RemoveObserver(this);
                fStyle->ReleaseReference();
        }

        fStyle = style;

        Gradient* gradient = NULL;
        if (fStyle) {
                fStyle->AcquireReference();
                fStyle->AddObserver(this);
                gradient = fStyle->Gradient();

                if (fCurrentColor && !gradient)
                        fCurrentColor->SetColor(fStyle->Color());

                fStyleType->SetEnabled(true);
        } else
                fStyleType->SetEnabled(false);

        _SetGradient(gradient, true);
}


void
StyleView::SetCommandStack(CommandStack* stack)
{
        fCommandStack = stack;
}


void
StyleView::SetCurrentColor(CurrentColor* color)
{
        if (fCurrentColor == color)
                return;

        if (fCurrentColor)
                fCurrentColor->RemoveObserver(this);

        fCurrentColor = color;

        if (fCurrentColor)
                fCurrentColor->AddObserver(this);
}


// #pragma mark -


void
StyleView::_SetGradient(Gradient* gradient, bool forceControlUpdate,
        bool sendMessage)
{
        if (!forceControlUpdate && gradient == fGradient)
                return;

        if (fGradient.IsSet())
                fGradient->RemoveObserver(this);

        fGradient.SetTo(gradient);

        if (fGradient.IsSet()) {
                fGradient->AddObserver(this);
        }

        if (fGradient.IsSet()) {
                fGradientControl->SetEnabled(true);
                fGradientControl->SetGradient(fGradient);
                fGradientType->SetEnabled(true);
                _MarkType(fStyleType->Menu(), STYLE_TYPE_GRADIENT);
                _MarkType(fGradientType->Menu(), fGradient->Type());
        } else {
                fGradientControl->SetEnabled(false);
                fGradientType->SetEnabled(false);
                _MarkType(fStyleType->Menu(), STYLE_TYPE_COLOR);
                _MarkType(fGradientType->Menu(), -1);
        }

        if (sendMessage) {
                BMessage message(MSG_STYLE_TYPE_CHANGED);
                message.AddPointer("style", fStyle);
                Window()->PostMessage(&message);
        }
}


void
StyleView::_MarkType(BMenu* menu, int32 type) const
{
        for (int32 i = 0; BMenuItem* item = menu->ItemAt(i); i++) {
                BMessage* message = item->Message();
                int32 t;
                if (message->FindInt32("type", &t) == B_OK && t == type) {
                        if (!item->IsMarked())
                                item->SetMarked(true);
                        return;
                }
        }
}


void
StyleView::_SetStyleType(int32 type)
{
        if (!fStyle)
                return;

        if (type == STYLE_TYPE_COLOR) {
                if (fCommandStack) {
                        fCommandStack->Perform(
                                new (nothrow) SetGradientCommand(fStyle, NULL));
                } else {
                        fStyle->SetGradient(NULL);
                }
        } else if (type == STYLE_TYPE_GRADIENT) {
                if (fCommandStack) {
                        Gradient gradient(true);
                        gradient.AddColor(fStyle->Color(), 0);
                        gradient.AddColor(fStyle->Color(), 1);
                        fCommandStack->Perform(
                                new (nothrow) SetGradientCommand(fStyle, &gradient));
                } else {
                        fStyle->SetGradient(fGradientControl->Gradient());
                }
        }
}


void
StyleView::_SetGradientType(int32 type)
{
        fGradientControl->Gradient()->SetType((gradients_type)type);
}


void
StyleView::_AdoptCurrentColor(rgb_color color)
{
        if (!fStyle)
                return;

        if (fGradient.IsSet()) {
                // set the focused gradient color stop
                if (fGradientControl->IsFocus()) {
                        fGradientControl->SetCurrentStop(color);
                }
        } else {
                if (fCommandStack) {
                        fCommandStack->Perform(
                                new (nothrow) SetColorCommand(fStyle, color));
                } else {
                        fStyle->SetColor(color);
                }
        }
}


void
StyleView::_TransferGradientStopColor()
{
        if (fCurrentColor && fGradientControl->IsFocus()) {
                rgb_color color;
                if (fGradientControl->GetCurrentStop(&color)) {
                        fIgnoreCurrentColorNotifications = true;
                        fCurrentColor->SetColor(color);
                        fIgnoreCurrentColorNotifications = false;
                }
        }
}