root/src/apps/icon-o-matic/generic/gui/stateview/StateView.cpp
/*
 * Copyright 2006-2007, 2023, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Zardshard
 */

#include "StateView.h"

#include <new>

#include <Message.h>
#include <MessageFilter.h>
#include <TextView.h>
#include <Window.h>

#include "Command.h"
#include "CommandStack.h"
// TODO: hack - somehow figure out of catching
// key events for a given control is ok
#include "GradientControl.h"
#include "ListViews.h"
//
#include "RWLocker.h"

using std::nothrow;

class EventFilter : public BMessageFilter {
 public:
        EventFilter(StateView* target)
                : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
                  fTarget(target)
                {
                }
        virtual ~EventFilter()
                {
                }
        virtual filter_result   Filter(BMessage* message, BHandler** target)
                {
                        filter_result result = B_DISPATCH_MESSAGE;
                        switch (message->what) {
                                case B_KEY_DOWN: {
if (dynamic_cast<BTextView*>(*target))
        break;
if (dynamic_cast<SimpleListView*>(*target))
        break;
if (dynamic_cast<GradientControl*>(*target))
        break;
                                        uint32 key;
                                        uint32 modifiers;
                                        if (message->FindInt32("raw_char", (int32*)&key) >= B_OK
                                                && message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK)
                                                if (fTarget->HandleKeyDown(key, modifiers))
                                                        result = B_SKIP_MESSAGE;
                                        break;
                                }
                                case B_KEY_UP: {
if (dynamic_cast<BTextView*>(*target))
        break;
if (dynamic_cast<SimpleListView*>(*target))
        break;
if (dynamic_cast<GradientControl*>(*target))
        break;
                                        uint32 key;
                                        uint32 modifiers;
                                        if (message->FindInt32("raw_char", (int32*)&key) >= B_OK
                                                && message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK)
                                                if (fTarget->HandleKeyUp(key, modifiers))
                                                        result = B_SKIP_MESSAGE;
                                        break;

                                }
                                case B_MODIFIERS_CHANGED:
                                        *target = fTarget;
                                        break;

                                case B_MOUSE_WHEEL_CHANGED: {
                                        float x;
                                        float y;
                                        if (message->FindFloat("be:wheel_delta_x", &x) >= B_OK
                                                && message->FindFloat("be:wheel_delta_y", &y) >= B_OK) {
                                                if (fTarget->MouseWheelChanged(
                                                                fTarget->MouseInfo()->position, x, y))
                                                        result = B_SKIP_MESSAGE;
                                        }
                                        break;
                                }
                                default:
                                        break;
                        }
                        return result;
                }
 private:
        StateView*              fTarget;
};


// #pragma mark -


StateView::StateView(BRect frame, const char* name,
                                         uint32 resizingMode, uint32 flags)
        : BView(frame, name, resizingMode, flags),
          fStartingRect(frame),

          fCurrentState(NULL),
          fDropAnticipatingState(NULL),

          fMouseInfo(),

          fCommandStack(NULL),
          fLocker(NULL),

          fEventFilter(NULL),
          fCatchAllEvents(false),

          fUpdateTarget(NULL),
          fUpdateCommand(0)
{
}


StateView::~StateView()
{
        delete fEventFilter;
}


// #pragma mark -


void
StateView::AttachedToWindow()
{
        _InstallEventFilter();

        BView::AttachedToWindow();
}


void
StateView::DetachedFromWindow()
{
        _RemoveEventFilter();

        BView::DetachedFromWindow();
}


void
StateView::Draw(BRect updateRect)
{
        Draw(this, updateRect);
}


void
StateView::MessageReceived(BMessage* message)
{
        // let the state handle the message if it wants
        if (fCurrentState) {
                AutoWriteLocker locker(fLocker);
                if (fLocker && !locker.IsLocked())
                        return;

                Command* command = NULL;
                if (fCurrentState->MessageReceived(message, &command)) {
                        Perform(command);
                        return;
                }
        }

        switch (message->what) {
                case B_MODIFIERS_CHANGED:
                        // NOTE: received only if the view has focus!!
                        if (fCurrentState) {
                                uint32 mods;
                                if (message->FindInt32("modifiers", (int32*)&mods) != B_OK)
                                        mods = modifiers();
                                fCurrentState->ModifiersChanged(mods);
                                fMouseInfo.modifiers = mods;
                        }
                        break;
                default:
                        BView::MessageReceived(message);
        }
}


// #pragma mark -


void
StateView::MouseDown(BPoint where)
{
        if (fLocker && !fLocker->WriteLock())
                return;

        // query more info from the windows current message if available
        uint32 buttons;
        uint32 clicks;
        BMessage* message = Window() ? Window()->CurrentMessage() : NULL;
        if (!message || message->FindInt32("buttons", (int32*)&buttons) != B_OK)
                buttons = B_PRIMARY_MOUSE_BUTTON;
        if (!message || message->FindInt32("clicks", (int32*)&clicks) != B_OK)
                clicks = 1;

        if (fCurrentState)
                fCurrentState->MouseDown(where, buttons, clicks);

        // update mouse info *after* having called the ViewState hook
        fMouseInfo.buttons = buttons;
        fMouseInfo.position = where;

        if (fLocker)
                fLocker->WriteUnlock();
}


void
StateView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
{
        if (fLocker && !fLocker->WriteLock())
                return;

        if (dragMessage && !fDropAnticipatingState) {
                // switch to a drop anticipating state if there is one available
                fDropAnticipatingState = StateForDragMessage(dragMessage);
                if (fDropAnticipatingState)
                        fDropAnticipatingState->Init();
        }

        // TODO: I don't like this too much
        if (!dragMessage && fDropAnticipatingState) {
                fDropAnticipatingState->Cleanup();
                fDropAnticipatingState = NULL;
        }

        if (fDropAnticipatingState)
                fDropAnticipatingState->MouseMoved(where, transit, dragMessage);
        else {
                if (fCurrentState) {
                        fCurrentState->MouseMoved(where, transit, dragMessage);
                        if (fMouseInfo.buttons != 0)
                                _TriggerUpdate();
                }
        }

        // update mouse info *after* having called the ViewState hook
        fMouseInfo.position = where;
        fMouseInfo.transit = transit;

        if (fLocker)
                fLocker->WriteUnlock();
}


void
StateView::MouseUp(BPoint where)
{
        if (fLocker && !fLocker->WriteLock())
                return;

        if (fDropAnticipatingState) {
                Perform(fDropAnticipatingState->MouseUp());
                fDropAnticipatingState->Cleanup();
                fDropAnticipatingState = NULL;

                if (fCurrentState) {
                        fCurrentState->MouseMoved(fMouseInfo.position, fMouseInfo.transit,
                                                                          NULL);
                }
        } else {
                if (fCurrentState) {
                        Perform(fCurrentState->MouseUp());
                        _TriggerUpdate();
                }
        }

        // update mouse info *after* having called the ViewState hook
        fMouseInfo.buttons = 0;

        if (fLocker)
                fLocker->WriteUnlock();
}


// #pragma mark -


void
StateView::KeyDown(const char* bytes, int32 numBytes)
{
        uint32 key;
        uint32 modifiers;
        BMessage* message = Window() ? Window()->CurrentMessage() : NULL;
        if (message
                && message->FindInt32("raw_char", (int32*)&key) >= B_OK
                && message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK) {
                if (HandleKeyDown(key, modifiers))
                        return;
        }
        BView::KeyDown(bytes, numBytes);
}


void
StateView::KeyUp(const char* bytes, int32 numBytes)
{
        uint32 key;
        uint32 modifiers;
        BMessage* message = Window() ? Window()->CurrentMessage() : NULL;
        if (message
                && message->FindInt32("raw_char", (int32*)&key) >= B_OK
                && message->FindInt32("modifiers", (int32*)&modifiers) >= B_OK) {
                if (HandleKeyUp(key, modifiers))
                        return;
        }
        BView::KeyUp(bytes, numBytes);
}


// #pragma mark -


status_t
StateView::Perform(perform_code code, void* data)
{
        return BView::Perform(code, data);
}


// #pragma mark -


void
StateView::GetPreferredSize(float* width, float* height)
{
        if (width != NULL)
                *width = fStartingRect.Width();

        if (height != NULL)
                *height = fStartingRect.Height();
}


// #pragma mark -


void
StateView::SetState(ViewState* state)
{
        if (fCurrentState == state)
                return;

        // switch states as appropriate
        if (fCurrentState)
                fCurrentState->Cleanup();

        fCurrentState = state;

        if (fCurrentState)
                fCurrentState->Init();
}


void
StateView::UpdateStateCursor()
{
        if (!fCurrentState || !fCurrentState->UpdateCursor()) {
                SetViewCursor(B_CURSOR_SYSTEM_DEFAULT, true);
        }
}


void
StateView::Draw(BView* into, BRect updateRect)
{
        if (fLocker && !fLocker->ReadLock()) {
                return;
        }

        if (fCurrentState)
                fCurrentState->Draw(into, updateRect);

        if (fDropAnticipatingState)
                fDropAnticipatingState->Draw(into, updateRect);

        if (fLocker)
                fLocker->ReadUnlock();
}


bool
StateView::MouseWheelChanged(BPoint where, float x, float y)
{
        return false;
}


bool
StateView::HandleKeyDown(uint32 key, uint32 modifiers)
{
        // down't allow key events if mouse already pressed
        // (central place to prevent command stack mix up)
        if (fMouseInfo.buttons != 0)
                return false;

        AutoWriteLocker locker(fLocker);
        if (fLocker && !locker.IsLocked())
                return false;

        if (_HandleKeyDown(key, modifiers))
                return true;

        if (fCurrentState) {
                Command* command = NULL;
                if (fCurrentState->HandleKeyDown(key, modifiers, &command)) {
                        Perform(command);
                        return true;
                }
        }
        return false;
}


bool
StateView::HandleKeyUp(uint32 key, uint32 modifiers)
{
        // down't allow key events if mouse already pressed
        // (central place to prevent command stack mix up)
        if (fMouseInfo.buttons != 0)
                return false;

        AutoWriteLocker locker(fLocker);
        if (fLocker && !locker.IsLocked())
                return false;

        if (_HandleKeyUp(key, modifiers))
                return true;

        if (fCurrentState) {
                Command* command = NULL;
                if (fCurrentState->HandleKeyUp(key, modifiers, &command)) {
                        Perform(command);
                        return true;
                }
        }
        return false;
}


void
StateView::FilterMouse(BPoint* where) const
{
}


ViewState*
StateView::StateForDragMessage(const BMessage* message)
{
        return NULL;
}


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


void
StateView::SetLocker(RWLocker* locker)
{
        fLocker = locker;
}


void
StateView::SetUpdateTarget(BHandler* target, uint32 command)
{
        fUpdateTarget = target;
        fUpdateCommand = command;
}


void
StateView::SetCatchAllEvents(bool catchAll)
{
        if (fCatchAllEvents == catchAll)
                return;

        fCatchAllEvents = catchAll;

        if (fCatchAllEvents)
                _InstallEventFilter();
        else
                _RemoveEventFilter();
}


status_t
StateView::Perform(Command* command)
{
        if (fCommandStack)
                return fCommandStack->Perform(command);

        // if there is no command stack, then nobody
        // else feels responsible...
        delete command;

        return B_NO_INIT;
}


// #pragma mark -


bool
StateView::_HandleKeyDown(uint32 key, uint32 modifiers)
{
        return false;
}


bool
StateView::_HandleKeyUp(uint32 key, uint32 modifiers)
{
        return false;
}


void
StateView::_InstallEventFilter()
{
        if (!fCatchAllEvents)
                return;

        if (!fEventFilter)
                fEventFilter = new (nothrow) EventFilter(this);

        if (!fEventFilter || !Window())
                return;

        Window()->AddCommonFilter(fEventFilter);
}


void
StateView::_RemoveEventFilter()
{
        if (!fEventFilter || !Window())
                return;

        Window()->RemoveCommonFilter(fEventFilter);
}

// _TriggerUpdate
void
StateView::_TriggerUpdate()
{
        if (fUpdateTarget && fUpdateTarget->Looper()) {
                fUpdateTarget->Looper()->PostMessage(fUpdateCommand);
        }
}