root/src/apps/cortex/RouteApp/RouteWindow.cpp
/*
 * Copyright (c) 1999-2000, Eric Moon.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions, and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


// RouteWindow.cpp
// e.moon 14may99

#include "RouteApp.h"
#include "RouteWindow.h"
#include "MediaRoutingView.h"
#include "StatusView.h"

#include "DormantNodeWindow.h"
#include "TransportWindow.h"

#include "RouteAppNodeManager.h"
#include "NodeGroup.h"
#include "TipManager.h"

#include <Alert.h>
#include <Autolock.h>
#include <Debug.h>
#include <Font.h>
#include <MenuBar.h>
#include <Menu.h>
#include <MenuItem.h>
#include <Message.h>
#include <Messenger.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollView.h>
#include <StringView.h>

#include <algorithm>

#define D_HOOK(x) //PRINT (x)
#define D_INTERNAL(x) //PRINT (x)

// Locale Kit
#include <Catalog.h>

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "CortexRouteApp"

__USE_CORTEX_NAMESPACE


const char* const RouteWindow::s_windowName = B_TRANSLATE("Cortex");

const BRect RouteWindow::s_initFrame(100,100,700,550);

const char* const g_aboutText =
        B_TRANSLATE("Cortex/Route 2.1.2\n\n"
        "Copyright 1999-2000 Eric Moon\n"
        "All rights reserved.\n\n"
        "The Cortex Team:\n\n"
        "Christopher Lenz: UI\n"
        "Eric Moon: UI, back-end\n\n"
        "Thanks to:\nJohn Ashmun\nJon Watte\nDoug Wright\n<your name here>\n\n"
        "Certain icons used herein are the property of\n"
        "Be, Inc. and are used by permission.");


RouteWindow::~RouteWindow()
{
}


RouteWindow::RouteWindow(RouteAppNodeManager* manager)
        :
        BWindow(s_initFrame, s_windowName, B_DOCUMENT_WINDOW, 0),
        m_hScrollBar(0),
        m_vScrollBar(0),
        m_transportWindow(0),
        m_dormantNodeWindow(0),
        m_selectedGroupID(0),
        m_zoomed(false),
        m_zooming(false)
{
        BRect b = Bounds();

        // initialize the menu bar: add all menus that target this window
        BMenuBar* pMenuBar = new BMenuBar(b, "menuBar");
        BMenu* pFileMenu = new BMenu(B_TRANSLATE("File"));
        BMenuItem* item = new BMenuItem(B_TRANSLATE("Open" B_UTF8_ELLIPSIS),
                new BMessage(RouteApp::M_SHOW_OPEN_PANEL), 'O');
        item->SetTarget(be_app);
        pFileMenu->AddItem(item);
        pFileMenu->AddItem(new BSeparatorItem());
        item = new BMenuItem(B_TRANSLATE("Save nodes" B_UTF8_ELLIPSIS),
                new BMessage(RouteApp::M_SHOW_SAVE_PANEL), 'S');
        item->SetTarget(be_app);
        pFileMenu->AddItem(item);
        pFileMenu->AddItem(new BSeparatorItem());
        pFileMenu->AddItem(new BMenuItem(
                B_TRANSLATE("About Cortex/Route" B_UTF8_ELLIPSIS),
                new BMessage(B_ABOUT_REQUESTED)));
        pFileMenu->AddItem(new BSeparatorItem());
        pFileMenu->AddItem(new BMenuItem(B_TRANSLATE("Quit"),
                new BMessage(B_QUIT_REQUESTED)));
        pMenuBar->AddItem(pFileMenu);
        AddChild(pMenuBar);

        // build the routing view
        BRect rvBounds = b;
        rvBounds.top = pMenuBar->Frame().bottom+1;
        rvBounds.right -= B_V_SCROLL_BAR_WIDTH;
        rvBounds.bottom -= B_H_SCROLL_BAR_HEIGHT;
        m_routingView = new MediaRoutingView(manager, rvBounds, "routingView");

        BRect hsBounds = rvBounds;
        hsBounds.left = rvBounds.left + 199;
        hsBounds.top = hsBounds.bottom + 1;
        hsBounds.right++;
        hsBounds.bottom = b.bottom + 1;

        m_hScrollBar = new BScrollBar(hsBounds, "hScrollBar", m_routingView,
                0, 0, B_HORIZONTAL);
        AddChild(m_hScrollBar);

        BRect vsBounds = rvBounds;
        vsBounds.left = vsBounds.right + 1;
        vsBounds.top--;
        vsBounds.right = b.right + 1;
        vsBounds.bottom++;

        m_vScrollBar = new BScrollBar(vsBounds, "vScrollBar", m_routingView,
                0, 0, B_VERTICAL);
        AddChild(m_vScrollBar);

        BRect svBounds = rvBounds;
        svBounds.left -= 1;
        svBounds.right = hsBounds.left - 1;
        svBounds.top = svBounds.bottom + 1;
        svBounds.bottom = b.bottom + 1;

        m_statusView = new StatusView(svBounds, manager, m_hScrollBar);
        AddChild(m_statusView);

        AddChild(m_routingView);

        float minWidth, maxWidth, minHeight, maxHeight;
        GetSizeLimits(&minWidth, &maxWidth, &minHeight, &maxHeight);
        minWidth = m_statusView->Frame().Width() + 6 * B_V_SCROLL_BAR_WIDTH;
        minHeight = 6 * B_H_SCROLL_BAR_HEIGHT;
        SetSizeLimits(minWidth, maxWidth, minHeight, maxHeight);

        // construct the Window menu
        BMenu* windowMenu = new BMenu(B_TRANSLATE("Window"));
        m_transportWindowItem = new BMenuItem(
                B_TRANSLATE("Show transport"),
                new BMessage(M_TOGGLE_TRANSPORT_WINDOW));
        windowMenu->AddItem(m_transportWindowItem);

        m_dormantNodeWindowItem = new BMenuItem(
                B_TRANSLATE("Show add-ons"),
                new BMessage(M_TOGGLE_DORMANT_NODE_WINDOW));
        windowMenu->AddItem(m_dormantNodeWindowItem);

        windowMenu->AddItem(new BSeparatorItem());

        m_pullPalettesItem = new BMenuItem(
                B_TRANSLATE("Pull palettes"),
                new BMessage(M_TOGGLE_PULLING_PALETTES));
        windowMenu->AddItem(m_pullPalettesItem);

        pMenuBar->AddItem(windowMenu);

        // create the dormant-nodes palette
        _toggleDormantNodeWindow();

        // display group inspector
        _toggleTransportWindow();
}


//      #pragma mark - operations


/*!     Enable/disable palette position-locking (when the main
        window is moved, all palettes follow).
*/
bool
RouteWindow::isPullPalettes() const
{
        return m_pullPalettesItem->IsMarked();
}


void
RouteWindow::setPullPalettes(bool enabled)
{
        m_pullPalettesItem->SetMarked(enabled);
}


void
RouteWindow::constrainToScreen()
{
        BScreen screen(this);

        const BRect sr = screen.Frame();

        // [c.lenz 1mar2000] this should be handled by every window
        // itself. will probably change soon ;-)
        _constrainToScreen();
/*      // main window
        BRect r = Frame();
        BPoint offset(0.0, 0.0);
        if(r.left < 0.0)
                offset.x = -r.left;
        if(r.top < 0.0)
                offset.y = -r.top;
        if(r.left >= (sr.right - 20.0))
                offset.x -= (r.left - (sr.Width()/2));
        if(r.top >= (sr.bottom - 20.0))
                offset.y -= (r.top - (sr.Height()/2));
        if(offset.x != 0.0 || offset.y != 0.0) {
                setPullPalettes(false);
                MoveBy(offset.x, offset.y);
        }*/

        // transport window
        BPoint offset = BPoint(0.0, 0.0);
        BRect r = (m_transportWindow) ?
                           m_transportWindow->Frame() :
                           m_transportWindowFrame;
        if(r.left < 0.0)
                offset.x = (sr.Width()*.75) - r.left;
        if(r.top < 0.0)
                offset.y = (sr.Height()*.25) - r.top;
        if(r.left >= (sr.right - 20.0))
                offset.x -= (r.left - (sr.Width()/2));
        if(r.top >= (sr.bottom - 20.0))
                offset.y -= (r.top - (sr.Height()/2));

        if(offset.x != 0.0 || offset.y != 0.0) {
                if(m_transportWindow)
                        m_transportWindow->MoveBy(offset.x, offset.y);
                else
                        m_transportWindowFrame.OffsetBy(offset.x, offset.y);
        }

        // addon palette
        offset = BPoint(0.0, 0.0);
        r = (m_dormantNodeWindow) ?
                m_dormantNodeWindow->Frame() :
                m_dormantNodeWindowFrame;
        if(r.left < 0.0)
                offset.x = (sr.Width()*.25) - r.left;
        if(r.top < 0.0)
                offset.y = (sr.Height()*.125) - r.top;
        if(r.left >= (sr.right - 20.0))
                offset.x -= (r.left - (sr.Width()/2));
        if(r.top >= (sr.bottom - 20.0))
                offset.y -= (r.top - (sr.Height()/2));

        if(offset.x != 0.0 || offset.y != 0.0) {
                if(m_dormantNodeWindow)
                        m_dormantNodeWindow->MoveBy(offset.x, offset.y);
                else
                        m_dormantNodeWindowFrame.OffsetBy(offset.x, offset.y);
        }

}


//      #pragma mark - BWindow implementation


void
RouteWindow::FrameMoved(BPoint point)
{
        // ignore notification if the window isn't yet visible
        if(IsHidden())
                return;

        BPoint delta = point - m_lastFramePosition;
        m_lastFramePosition = point;


        if (m_pullPalettesItem->IsMarked())
                _movePalettesBy(delta.x, delta.y);
}


void
RouteWindow::FrameResized(float width, float height)
{
        D_HOOK(("RouteWindow::FrameResized()\n"));

        if (!m_zooming) {
                m_zoomed = false;
        }
        else {
                m_zooming = false;
        }
}


bool
RouteWindow::QuitRequested()
{
        be_app->PostMessage(B_QUIT_REQUESTED);
        return false; // [e.moon 20oct99] app now quits window
}


void
RouteWindow::Zoom(BPoint origin, float width, float height)
{
        D_HOOK(("RouteWindow::Zoom()\n"));

        m_zooming = true;

        BScreen screen(this);
        if (!screen.Frame().Contains(Frame())) {
                m_zoomed = false;
        }

        if (!m_zoomed) {
                // resize to the ideal size
                m_manualSize = Bounds();
                float width, height;
                m_routingView->GetPreferredSize(&width, &height);
                width += B_V_SCROLL_BAR_WIDTH;
                height += B_H_SCROLL_BAR_HEIGHT;
                if (KeyMenuBar()) {
                        height += KeyMenuBar()->Frame().Height();
                }
                ResizeTo(width, height);
                _constrainToScreen();
                m_zoomed = true;
        }
        else {
                // resize to the most recent manual size
                ResizeTo(m_manualSize.Width(), m_manualSize.Height());
                m_zoomed = false;
        }
}


//      #pragma mark - BHandler implemenation


void
RouteWindow::MessageReceived(BMessage* pMsg)
{
//      PRINT((
//              "RouteWindow::MessageReceived()\n"));
//      pMsg->PrintToStream();
//
        switch (pMsg->what) {
                case B_ABOUT_REQUESTED:
                {
                        BAlert* alert = new BAlert("About", g_aboutText, B_TRANSLATE("OK"));
                        alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
                        alert->Go();
                        break;
                }
                case MediaRoutingView::M_GROUP_SELECTED:
                        _handleGroupSelected(pMsg);
                        break;

                case MediaRoutingView::M_SHOW_ERROR_MESSAGE:
                        _handleShowErrorMessage(pMsg);
                        break;

                case M_TOGGLE_TRANSPORT_WINDOW:
                        _toggleTransportWindow();
                        break;

                case M_REFRESH_TRANSPORT_SETTINGS:
                        _refreshTransportSettings(pMsg);
                        break;

                case M_TOGGLE_PULLING_PALETTES:
                        _togglePullPalettes();
                        break;

                case M_TOGGLE_DORMANT_NODE_WINDOW:
                        _toggleDormantNodeWindow();
                        break;

                case M_TOGGLE_GROUP_ROLLING:
                        _toggleGroupRolling();
                        break;

                default:
                        _inherited::MessageReceived(pMsg);
                        break;
        }
}


//      #pragma mark - IStateArchivable


status_t
RouteWindow::importState(const BMessage* archive)
{
        status_t err;

        // frame rect
        BRect r;
        err = archive->FindRect("frame", &r);
        if(err == B_OK) {
                MoveTo(r.LeftTop());
                ResizeTo(r.Width(), r.Height());
                m_lastFramePosition = r.LeftTop();
        }

        // status view width
        int32 i;
        err = archive->FindInt32("statusViewWidth", &i);
        if (err == B_OK) {
                float diff = i - m_statusView->Bounds().IntegerWidth();
                m_statusView->ResizeBy(diff, 0.0);
                m_hScrollBar->ResizeBy(-diff, 0.0);
                m_hScrollBar->MoveBy(diff, 0.0);
        }

        // settings
        bool b;
        err = archive->FindBool("pullPalettes", &b);
        if(err == B_OK)
                m_pullPalettesItem->SetMarked(b);

//      const char* p;
//      err = archive->FindString("saveDir", &p);
//      if(err == B_OK) {
//              m_openPanel.SetPanelDirectory(p);
//              m_savePanel.SetPanelDirectory(p);
//      }
//
        // dormant-node window
        err = archive->FindRect("addonPaletteFrame", &r);
        if (err == B_OK)
                m_dormantNodeWindowFrame = r;
        err = archive->FindBool("addonPaletteVisible", &b);
        if (err == B_OK && (b != (m_dormantNodeWindow != 0))) {
                _toggleDormantNodeWindow();
                if(!m_dormantNodeWindow)
                        m_dormantNodeWindowFrame = r;
        }

        if (m_dormantNodeWindow) {
                m_dormantNodeWindow->MoveTo(m_dormantNodeWindowFrame.LeftTop());
                m_dormantNodeWindow->ResizeTo(
                        m_dormantNodeWindowFrame.Width(),
                        m_dormantNodeWindowFrame.Height());
        }

        // transport window
        err = archive->FindRect("transportFrame", &r);
        if (err == B_OK)
                m_transportWindowFrame = r;
        err = archive->FindBool("transportVisible", &b);
        if (err == B_OK && (b != (m_transportWindow != 0))) {
                _toggleTransportWindow();
                if (!m_transportWindow)
                        m_transportWindowFrame = r;
        }

        if (m_transportWindow) {
                m_transportWindow->MoveTo(m_transportWindowFrame.LeftTop());
                m_transportWindow->ResizeTo(
                        m_transportWindowFrame.Width(),
                        m_transportWindowFrame.Height());
        }

        return B_OK;
}


status_t
RouteWindow::exportState(BMessage* archive) const
{
        BRect r = Frame();
        archive->AddRect("frame", r);
        archive->AddBool("pullPalettes", m_pullPalettesItem->IsMarked());

        bool b = (m_dormantNodeWindow != 0);
        r = b ? m_dormantNodeWindow->Frame() : m_dormantNodeWindowFrame;
        archive->AddRect("addonPaletteFrame", r);
        archive->AddBool("addonPaletteVisible", b);

        b = (m_transportWindow != 0);
        r = b ? m_transportWindow->Frame() : m_transportWindowFrame;

        archive->AddRect("transportFrame", r);
        archive->AddBool("transportVisible", b);

        // [c.lenz 23may00] remember status view width
        int i = m_statusView->Bounds().IntegerWidth();
        archive->AddInt32("statusViewWidth", i);

//      entry_ref saveRef;
//      m_savePanel.GetPanelDirectory(&saveRef);
//      BEntry saveEntry(&saveRef);
//      if(saveEntry.InitCheck() == B_OK) {
//              BPath p;
//              saveEntry.GetPath(&p);
//              archive->AddString("saveDir", p.Path());
//      }

        return B_OK;
}


//      #pragma mark - implementation


void
RouteWindow::_constrainToScreen()
{
        D_INTERNAL(("RouteWindow::_constrainToScreen()\n"));

        BScreen screen(this);
        BRect screenRect = screen.Frame();
        BRect windowRect = Frame();

        // if the window is outside the screen rect
        // move it to the default position
        if (!screenRect.Intersects(windowRect)) {
                windowRect.OffsetTo(screenRect.LeftTop());
                MoveTo(windowRect.LeftTop());
                windowRect = Frame();
        }

        // if the window is larger than the screen rect
        // resize it to fit at each side
        if (!screenRect.Contains(windowRect)) {
                if (windowRect.left < screenRect.left) {
                        windowRect.left = screenRect.left + 5.0;
                        MoveTo(windowRect.LeftTop());
                        windowRect = Frame();
                }
                if (windowRect.top < screenRect.top) {
                        windowRect.top = screenRect.top + 5.0;
                        MoveTo(windowRect.LeftTop());
                        windowRect = Frame();
                }
                if (windowRect.right > screenRect.right) {
                        windowRect.right = screenRect.right - 5.0;
                }
                if (windowRect.bottom > screenRect.bottom) {
                        windowRect.bottom = screenRect.bottom - 5.0;
                }
                ResizeTo(windowRect.Width(), windowRect.Height());
        }
}


void
RouteWindow::_toggleTransportWindow()
{
        if (m_transportWindow) {
                m_transportWindowFrame = m_transportWindow->Frame();
                m_transportWindow->Lock();
                m_transportWindow->Quit();
                m_transportWindow = 0;
                m_transportWindowItem->SetMarked(false);
        } else {
                m_transportWindow = new TransportWindow(m_routingView->manager,
                        this, B_TRANSLATE("Transport"));

                // ask for a selection update
                BMessenger(m_routingView).SendMessage(
                        MediaRoutingView::M_BROADCAST_SELECTION);

                // place & display the window
                if (m_transportWindowFrame.IsValid()) {
                        m_transportWindow->MoveTo(m_transportWindowFrame.LeftTop());
                        m_transportWindow->ResizeTo(m_transportWindowFrame.Width(),
                                m_transportWindowFrame.Height());
                }

                m_transportWindow->Show();
                m_transportWindowItem->SetMarked(true);
        }
}


void
RouteWindow::_togglePullPalettes()
{
        m_pullPalettesItem->SetMarked(!m_pullPalettesItem->IsMarked());
}


void
RouteWindow::_toggleDormantNodeWindow()
{
        if (m_dormantNodeWindow) {
                m_dormantNodeWindowFrame = m_dormantNodeWindow->Frame();
                m_dormantNodeWindow->Lock();
                m_dormantNodeWindow->Quit();
                m_dormantNodeWindow = 0;
                m_dormantNodeWindowItem->SetMarked(false);
        } else {
                m_dormantNodeWindow = new DormantNodeWindow(this);
                if (m_dormantNodeWindowFrame.IsValid()) {
                        m_dormantNodeWindow->MoveTo(m_dormantNodeWindowFrame.LeftTop());
                        m_dormantNodeWindow->ResizeTo(m_dormantNodeWindowFrame.Width(),
                                m_dormantNodeWindowFrame.Height());
                }
                m_dormantNodeWindow->Show();
                m_dormantNodeWindowItem->SetMarked(true);
        }
}


void
RouteWindow::_handleGroupSelected(BMessage* message)
{
        status_t err;
        uint32 groupID;

        err = message->FindInt32("groupID", (int32*)&groupID);
        if (err < B_OK) {
                PRINT((
                        "! RouteWindow::_handleGroupSelected(): no groupID in message!\n"));
                return;
        }

        if (!m_transportWindow)
                return;

        BMessage m(TransportWindow::M_SELECT_GROUP);
        m.AddInt32("groupID", groupID);
        BMessenger(m_transportWindow).SendMessage(&m);

        m_selectedGroupID = groupID;
}


void
RouteWindow::_handleShowErrorMessage(BMessage* message)
{
        status_t err;
        BString text;

        err = message->FindString("text", &text);
        if (err < B_OK) {
                PRINT((
                        "! RouteWindow::_handleShowErrorMessage(): no text in message!\n"));
                return;
        }

        m_statusView->setErrorMessage(text.String(), message->HasBool("error"));
}


//! Refresh the transport window for the given group, if any
void
RouteWindow::_refreshTransportSettings(BMessage* message)
{
        status_t err;
        uint32 groupID;

        err = message->FindInt32("groupID", (int32*)&groupID);
        if (err < B_OK) {
                PRINT((
                        "! RouteWindow::_refreshTransportSettings(): no groupID in message!\n"));
                return;
        }

        if(m_transportWindow) {
                // relay the message
                BMessenger(m_transportWindow).SendMessage(message);
        }
}


void
RouteWindow::_closePalettes()
{
        BAutolock _l(this);

        if (m_transportWindow) {
                m_transportWindow->Lock();
                m_transportWindow->Quit();
                m_transportWindow = 0;
        }
}


//!     Move all palette windows by the specified amounts
void RouteWindow::_movePalettesBy(float xDelta, float yDelta)
{
        if (m_transportWindow)
                m_transportWindow->MoveBy(xDelta, yDelta);

        if (m_dormantNodeWindow)
                m_dormantNodeWindow->MoveBy(xDelta, yDelta);
}


//!     Toggle group playback
void
RouteWindow::_toggleGroupRolling()
{
        if (!m_selectedGroupID)
                return;

        NodeGroup* g;
        status_t err = m_routingView->manager->findGroup(m_selectedGroupID, &g);
        if (err < B_OK)
                return;

        Autolock _l(g);
        uint32 startAction = (g->runMode() == BMediaNode::B_OFFLINE)
                ? NodeGroup::M_ROLL : NodeGroup::M_START;

        BMessenger m(g);
        switch (g->transportState()) {
                case NodeGroup::TRANSPORT_STOPPED:
                        m.SendMessage(startAction);
                        break;

                case NodeGroup::TRANSPORT_RUNNING:
                case NodeGroup::TRANSPORT_ROLLING:
                        m.SendMessage(NodeGroup::M_STOP);
                        break;

                default:
                        break;
        }
}