root/src/apps/debugger/user_interface/gui/team_settings_window/SignalsConfigView.cpp
/*
 * Copyright 2013-2015, Rene Gollent, rene@gollent.com.
 * Distributed under the terms of the MIT License.
 */
#include "SignalsConfigView.h"

#include <Box.h>
#include <Button.h>
#include <LayoutBuilder.h>
#include <MenuField.h>

#include <AutoDeleter.h>
#include <AutoLocker.h>

#include "table/TableColumns.h"

#include "AppMessageCodes.h"
#include "MessageCodes.h"
#include "SignalDispositionEditWindow.h"
#include "SignalDispositionMenu.h"
#include "SignalDispositionTypes.h"
#include "UiUtils.h"
#include "UserInterface.h"


enum {
        MSG_ADD_DISPOSITION_EXCEPTION                   = 'adex',
        MSG_EDIT_DISPOSITION_EXCEPTION                  = 'edex',
        MSG_REMOVE_DISPOSITION_EXCEPTION                = 'rdex'
};


struct SignalDispositionInfo {
        SignalDispositionInfo(int32 _signal, int32 _disposition)
                :
                signal(_signal),
                disposition(_disposition)
        {
        }

        int32 signal;
        int32 disposition;
};



class SignalsConfigView::SignalDispositionModel : public TableModel {
public:

        SignalDispositionModel(::Team* team)
                :
                fDispositions(),
                fTeam(team)
        {
        }

        virtual ~SignalDispositionModel()
        {
        }

        virtual int32 CountColumns() const
        {
                return 2;
        }

        virtual int32 CountRows() const
        {
                return fDispositions.CountItems();
        }

        virtual bool GetValueAt(int32 rowIndex, int32 columnIndex, BVariant& value)
        {
                SignalDispositionInfo* info = fDispositions.ItemAt(rowIndex);
                if (info == NULL)
                        return false;

                switch (columnIndex) {
                        case 0:
                        {
                                BString tempValue;
                                value.SetTo(UiUtils::SignalNameToString(info->signal,
                                        tempValue));
                                return true;
                        }
                        case 1:
                        {
                                value.SetTo(UiUtils::SignalDispositionToString(
                                                info->disposition), B_VARIANT_DONT_COPY_DATA);
                                return true;
                        }
                        default:
                                break;
                }

                return false;
        }

        bool SignalDispositionInfoAt(int32 rowIndex, SignalDispositionInfo*& _info)
        {
                _info = fDispositions.ItemAt(rowIndex);
                if (_info == NULL)
                        return false;

                return true;
        }

        void Update(int32 signal, int32 disposition)
        {
                for (int32 i = 0; i < fDispositions.CountItems(); i++) {
                        SignalDispositionInfo* info = fDispositions.ItemAt(i);
                        if (info->signal == signal) {
                                info->disposition = disposition;
                                NotifyRowsChanged(i, 1);
                                return;
                        }
                }

                SignalDispositionInfo* info = new(std::nothrow) SignalDispositionInfo(
                        signal, disposition);
                if (info == NULL)
                        return;

                ObjectDeleter<SignalDispositionInfo> infoDeleter(info);
                if (!fDispositions.AddItem(info))
                        return;

                infoDeleter.Detach();
                NotifyRowsAdded(fDispositions.CountItems() - 1, 1);
        }

        void Remove(int32 signal)
        {
                for (int32 i = 0; i < fDispositions.CountItems(); i++) {
                        SignalDispositionInfo* info = fDispositions.ItemAt(i);
                        if (info->signal == signal) {
                                fDispositions.RemoveItemAt(i);
                                delete info;
                                NotifyRowsRemoved(i, 1);
                                return;
                        }
                }
        }

        status_t Init()
        {
                AutoLocker< ::Team> locker(fTeam);

                const SignalDispositionMappings& dispositions
                        = fTeam->GetSignalDispositionMappings();

                for (SignalDispositionMappings::const_iterator it
                        = dispositions.begin(); it != dispositions.end(); ++it) {
                        SignalDispositionInfo* info
                                = new(std::nothrow) SignalDispositionInfo(it->first,
                                it->second);
                        if (info == NULL)
                                return B_NO_MEMORY;

                        ObjectDeleter<SignalDispositionInfo> infoDeleter(info);

                        if (!fDispositions.AddItem(info))
                                return B_NO_MEMORY;

                        infoDeleter.Detach();
                }

                return B_OK;
        }

private:
        typedef BObjectList<SignalDispositionInfo> DispositionList;

private:
        DispositionList         fDispositions;
        ::Team*                         fTeam;
};


SignalsConfigView::SignalsConfigView(::Team* team,
        UserInterfaceListener* listener)
        :
        BGroupView(B_VERTICAL),
        fTeam(team),
        fListener(listener),
        fDefaultSignalDisposition(NULL),
        fDispositionExceptions(NULL),
        fAddDispositionButton(NULL),
        fEditDispositionButton(NULL),
        fRemoveDispositionButton(NULL),
        fDispositionModel(NULL),
        fEditWindow(NULL)
{
        SetName("Signals");
        fTeam->AddListener(this);
}


SignalsConfigView::~SignalsConfigView()
{
        fTeam->RemoveListener(this);
        BMessenger(fEditWindow).SendMessage(B_QUIT_REQUESTED);
}


SignalsConfigView*
SignalsConfigView::Create(::Team* team, UserInterfaceListener* listener)
{
        SignalsConfigView* self = new SignalsConfigView(team, listener);

        try {
                self->_Init();
        } catch (...) {
                delete self;
                throw;
        }

        return self;
}


void
SignalsConfigView::AttachedToWindow()
{
        fDefaultSignalDisposition->Menu()->SetTargetForItems(this);
        fAddDispositionButton->SetTarget(this);
        fEditDispositionButton->SetTarget(this);
        fRemoveDispositionButton->SetTarget(this);

        fEditDispositionButton->SetEnabled(false);
        fRemoveDispositionButton->SetEnabled(false);

        AutoLocker< ::Team> teamLocker(fTeam);
        _UpdateSignalConfigState();

        BGroupView::AttachedToWindow();
}


void
SignalsConfigView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case MSG_SIGNAL_DISPOSITION_EDIT_WINDOW_CLOSED:
                {
                        fEditWindow = NULL;
                }
                case MSG_SET_DEFAULT_SIGNAL_DISPOSITION:
                {
                        int32 disposition;
                        if (message->FindInt32("disposition", &disposition) != B_OK)
                                break;

                        fListener->SetDefaultSignalDispositionRequested(disposition);
                        break;
                }
                case MSG_ADD_DISPOSITION_EXCEPTION:
                case MSG_EDIT_DISPOSITION_EXCEPTION:
                {
                        if (fEditWindow != NULL) {
                                AutoLocker<BWindow> lock(fEditWindow);
                                if (lock.IsLocked())
                                        fEditWindow->Activate(true);
                        } else {
                                int32 signal = 0;
                                if (message->what == MSG_EDIT_DISPOSITION_EXCEPTION) {
                                        TableSelectionModel* model
                                                = fDispositionExceptions->SelectionModel();
                                        SignalDispositionInfo* info;
                                        if (fDispositionModel->SignalDispositionInfoAt(
                                                        model->RowAt(0), info)) {
                                                signal = info->signal;
                                        }
                                }

                                try {
                                        fEditWindow = SignalDispositionEditWindow::Create(fTeam,
                                                signal, fListener, this);
                                        if (fEditWindow != NULL)
                                                fEditWindow->Show();
                        } catch (...) {
                                // TODO: notify user
                        }
                        }
                        break;
                }
                case MSG_REMOVE_DISPOSITION_EXCEPTION:
                {
                        TableSelectionModel* model
                                = fDispositionExceptions->SelectionModel();
                        for (int32 i = 0; i < model->CountRows(); i++) {
                                SignalDispositionInfo* info;
                                if (fDispositionModel->SignalDispositionInfoAt(model->RowAt(i),
                                                info)) {
                                        fListener->RemoveCustomSignalDispositionRequested(
                                                info->signal);
                                }
                        }
                        break;
                }
                case MSG_SET_CUSTOM_SIGNAL_DISPOSITION:
                {
                        int32 signal;
                        int32 disposition;
                        if (message->FindInt32("signal", &signal) == B_OK
                                && message->FindInt32("disposition", &disposition) == B_OK) {
                                fDispositionModel->Update(signal, disposition);
                        }
                        break;
                }
                case MSG_REMOVE_CUSTOM_SIGNAL_DISPOSITION:
                {
                        int32 signal;
                        if (message->FindInt32("signal", &signal) == B_OK)
                                fDispositionModel->Remove(signal);
                        break;
                }
                default:
                        BGroupView::MessageReceived(message);
                        break;
        }
}


void
SignalsConfigView::CustomSignalDispositionChanged(
        const Team::CustomSignalDispositionEvent& event)
{
        BMessage message(MSG_SET_CUSTOM_SIGNAL_DISPOSITION);
        message.AddInt32("signal", event.Signal());
        message.AddInt32("disposition", event.Disposition());

        BMessenger(this).SendMessage(&message);
}


void
SignalsConfigView::CustomSignalDispositionRemoved(
        const Team::CustomSignalDispositionEvent& event)
{
        BMessage message(MSG_REMOVE_CUSTOM_SIGNAL_DISPOSITION);
        message.AddInt32("signal", event.Signal());

        BMessenger(this).SendMessage(&message);
}


void
SignalsConfigView::TableSelectionChanged(Table* table)
{
        TableSelectionModel* model = fDispositionExceptions->SelectionModel();
        int32 rowCount = model->CountRows();
        fEditDispositionButton->SetEnabled(rowCount == 1);
        fRemoveDispositionButton->SetEnabled(rowCount > 0);
}


void
SignalsConfigView::_Init()
{
        SignalDispositionMenu* dispositionMenu = new SignalDispositionMenu(
                "signalDispositionsMenu",
                new BMessage(MSG_SET_DEFAULT_SIGNAL_DISPOSITION));

        BGroupView* customDispositionsGroup = new BGroupView();
        BLayoutBuilder::Group<>(customDispositionsGroup, B_VERTICAL, 0.0)
                .SetInsets(B_USE_SMALL_INSETS)
                .Add(fDispositionExceptions = new Table("customDispositions",
                        B_WILL_DRAW, B_FANCY_BORDER))
                .AddGroup(B_HORIZONTAL)
                        .SetInsets(B_USE_SMALL_INSETS)
                        .AddGlue()
                        .Add(fAddDispositionButton = new BButton("Add" B_UTF8_ELLIPSIS,
                                new BMessage(MSG_ADD_DISPOSITION_EXCEPTION)))
                        .Add(fEditDispositionButton = new BButton("Edit" B_UTF8_ELLIPSIS,
                                new BMessage(MSG_EDIT_DISPOSITION_EXCEPTION)))
                        .Add(fRemoveDispositionButton = new BButton("Remove",
                                new BMessage(MSG_REMOVE_DISPOSITION_EXCEPTION)))
                .End();

        BBox* dispositionsBox = new BBox("custom dispositions box");
        dispositionsBox->AddChild(customDispositionsGroup);
        dispositionsBox->SetLabel("Custom dispositions");

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .SetInsets(B_USE_DEFAULT_SPACING)
                .Add(fDefaultSignalDisposition = new BMenuField(
                        "stopTypes", "Default disposition:", dispositionMenu))
                .Add(dispositionsBox);

        dispositionMenu->SetLabelFromMarked(true);

        // columns
        fDispositionExceptions->AddColumn(new StringTableColumn(0, "Signal",
                80, 40, 1000, B_TRUNCATE_END, B_ALIGN_LEFT));
        fDispositionExceptions->AddColumn(new StringTableColumn(1, "Disposition",
                200, 40, 1000, B_TRUNCATE_END, B_ALIGN_RIGHT));
        fDispositionExceptions->SetExplicitMinSize(BSize(400.0, 200.0));


        fDispositionModel = new SignalDispositionModel(fTeam);
        if (fDispositionModel->Init() != B_OK) {
                delete fDispositionModel;
                return;
        }

        fDispositionExceptions->SetTableModel(fDispositionModel);
        fDispositionExceptions->AddTableListener(this);
}


void
SignalsConfigView::_UpdateSignalConfigState()
{
        int32 defaultDisposition = fTeam->DefaultSignalDisposition();
        fDefaultSignalDisposition->Menu()->ItemAt(defaultDisposition)
                ->SetMarked(true);
}