root/src/apps/cortex/TransportView/TransportView.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.
 */


// TransportView.cpp

#include "TransportView.h"

#include "RouteApp.h"
#include "RouteWindow.h"

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

#include "NumericValControl.h"
#include "TextControlFloater.h"

#include <Button.h>
#include <Debug.h>
#include <Font.h>
#include <Invoker.h>
#include <StringView.h>
#include <MediaRoster.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <PopUpMenu.h>
#include <String.h>
#include <StringFormat.h>
#include <TextControl.h>

#include <algorithm>
#include <functional>

#undef B_CATALOG
#define B_CATALOG (&sCatalog)

#include <Catalog.h>

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TransportView"

using namespace std;

__USE_CORTEX_NAMESPACE

static BCatalog sCatalog("x-vnd.Cortex.TransportView");

// -------------------------------------------------------- //
// _GroupInfoView
// -------------------------------------------------------- //

__BEGIN_CORTEX_NAMESPACE
class _GroupInfoView :
        public  BView {
        typedef BView _inherited;

public:                                                                                         // ctor/dtor
        _GroupInfoView(
                BRect                                                                                   frame,
                TransportView*                                          parent,
                const char*                                                             name,
                uint32                                                                          resizeMode =B_FOLLOW_LEFT|B_FOLLOW_TOP,
                uint32                                                                          flags =B_WILL_DRAW|B_FRAME_EVENTS) :

                BView(frame, name, resizeMode, flags),
                m_parent(parent),
                m_plainFont(be_plain_font),
                m_boldFont(be_bold_font) {

                _initViews();
                _initColors();
                _updateLayout();
        }

public:                                                                                         // BView
        virtual void FrameResized(
                float                                                                                   width,
                float                                                                                   height) {

                _inherited::FrameResized(width, height);
                _updateLayout();
                Invalidate();
        }

        virtual void GetPreferredSize(
                float*                                                                          width,
                float*                                                                          height) {
                font_height fh;
                m_plainFont.GetHeight(&fh);

                *width = 0.0;
                *height = (fh.ascent+fh.descent+fh.leading) * 2;
                *height += 4.0; //+++++
        }


        virtual void Draw(
                BRect                                                                                   updateRect) {

                NodeGroup* g = m_parent->m_group;
                BRect b = Bounds();

                // border
                rgb_color hi = tint_color(ViewColor(), B_LIGHTEN_2_TINT);
                rgb_color lo = tint_color(ViewColor(), B_DARKEN_2_TINT);
                SetHighColor(lo);
                StrokeLine(
                        b.LeftTop(), b.RightTop());
                StrokeLine(
                        b.LeftTop(), b.LeftBottom());

                SetHighColor(hi);
                StrokeLine(
                        b.LeftBottom(), b.RightBottom());
                StrokeLine(
                        b.RightTop(), b.RightBottom());

                SetHighColor(255,255,255,255);

                // background +++++

                // name
                BString name = g ? g->name() : B_TRANSLATE("(no group)");
                // +++++ constrain width
                SetFont(&m_boldFont);
                DrawString(name.String(), m_namePosition);

                SetFont(&m_plainFont);

                // node count
                uint32 count;
                if (g != NULL)
                        count = g->countNodes();
                else
                        count = 0;

                BString nodeCount = "";
                static BStringFormat format(
                        B_TRANSLATE("{0, plural, one{# node} other{# nodes}}"));
                format.Format(nodeCount, count);

                // +++++ constrain width
                DrawString(nodeCount.String(), m_nodeCountPosition);

                // status
                BString status = B_TRANSLATE("No errors.");
                // +++++ constrain width
                DrawString(status.String(), m_statusPosition);
        }

        virtual void MouseDown(
                BPoint                                                                          point) {

                NodeGroup* g = m_parent->m_group;
                if(!g)
                        return;

                font_height fh;
                m_boldFont.GetHeight(&fh);

                BRect nameBounds(
                        m_namePosition.x,
                        m_namePosition.y - fh.ascent,
                        m_namePosition.x + m_maxNameWidth,
                        m_namePosition.y + (fh.ascent+fh.leading-4.0));
                if(nameBounds.Contains(point)) {
                        ConvertToScreen(&nameBounds);
                        nameBounds.OffsetBy(-7.0, -3.0);
                        new TextControlFloater(
                                nameBounds,
                                B_ALIGN_LEFT,
                                &m_boldFont,
                                g->name(),
                                m_parent,
                                new BMessage(TransportView::M_SET_NAME));
                }
        }

public:                                                                                         // implementation
        void _initViews() {
                // +++++
        }

        void _initColors() {
                // +++++ these colors need to be centrally defined
                SetViewColor(16, 64, 96, 255);
                SetLowColor(16, 64, 96, 255);
                SetHighColor(255,255,255,255);
        }

        void _updateLayout() {
                float _edge_pad_x = 3.0;
                float _edge_pad_y = 1.0;

                BRect b = Bounds();
                font_height fh;
                m_plainFont.GetHeight(&fh);

                float realWidth = b.Width() - (_edge_pad_x * 2);

                m_maxNameWidth = realWidth * 0.7;
                m_maxNodeCountWidth = realWidth - m_maxNameWidth;
                m_namePosition.x = _edge_pad_x;
                m_namePosition.y = _edge_pad_x + fh.ascent - 2.0;
                m_nodeCountPosition = m_namePosition;
                m_nodeCountPosition.x = m_maxNameWidth;

                m_maxStatusWidth = realWidth;
                m_statusPosition.x = _edge_pad_x;
                m_statusPosition.y = b.Height() - (fh.descent + fh.leading + _edge_pad_y);
        }

private:
        TransportView*                                                  m_parent;

        BFont                                                                                           m_plainFont;
        BFont                                                                                           m_boldFont;

        BPoint                                                                                  m_namePosition;
        float                                                                                           m_maxNameWidth;

        BPoint                                                                                  m_nodeCountPosition;
        float                                                                                           m_maxNodeCountWidth;

        BPoint                                                                                  m_statusPosition;
        float                                                                                           m_maxStatusWidth;
};
__END_CORTEX_NAMESPACE

// -------------------------------------------------------- //
// *** ctors
// -------------------------------------------------------- //

TransportView::TransportView(
        NodeManager*                                            manager,
        const char*                                                     name) :

        BView(
                BRect(),
                name,
                B_FOLLOW_ALL_SIDES,
                B_WILL_DRAW|B_FRAME_EVENTS),
        m_manager(manager),
        m_group(0) {

        // initialize
        _initLayout();
        _constructControls();
//      _updateLayout(); deferred until AttachedToWindow(): 24aug99
        _disableControls();

        SetViewColor(
                tint_color(
                        ui_color(B_PANEL_BACKGROUND_COLOR),
                        B_LIGHTEN_1_TINT));
}

// -------------------------------------------------------- //
// *** BView
// -------------------------------------------------------- //

void TransportView::AttachedToWindow() {
        _inherited::AttachedToWindow();

        // finish layout
        _updateLayout();

        // watch the node manager (for time-source create/delete notification)
        RouteApp* app = dynamic_cast<RouteApp*>(be_app);
        ASSERT(app);
        add_observer(this, app->manager);
}

void TransportView::AllAttached() {
        _inherited::AllAttached();

        // set message targets for view-configuation controls
        for(target_set::iterator it = m_localTargets.begin();
                it != m_localTargets.end(); ++it) {
                ASSERT(*it);
                (*it)->SetTarget(this);
        }
}

void TransportView::DetachedFromWindow() {
        _inherited::DetachedFromWindow();

        RouteApp* app = dynamic_cast<RouteApp*>(be_app);
        ASSERT(app);
        remove_observer(this, app->manager);
}

void TransportView::FrameResized(
        float                                                                           width,
        float                                                                           height) {

        _inherited::FrameResized(width, height);
//      _updateLayout();
}

void TransportView::KeyDown(
        const char*                                                     bytes,
        int32                                                                           count) {

        switch(bytes[0]) {
                case B_SPACE: {
                        RouteApp* app = dynamic_cast<RouteApp*>(be_app);
                        ASSERT(app);
                        BMessenger(app->routeWindow).SendMessage(
                                RouteWindow::M_TOGGLE_GROUP_ROLLING);
                        break;
                }

                default:
                        _inherited::KeyDown(bytes, count);
        }
}


void TransportView::MouseDown(
        BPoint                                                                  where) {

        MakeFocus(true);
}


// -------------------------------------------------------- //
// *** BHandler
// -------------------------------------------------------- //

void TransportView::MessageReceived(
        BMessage*                                                               message) {
        status_t err;
        uint32 groupID;

//      PRINT((
//              "TransportView::MessageReceived()\n"));
//      message->PrintToStream();

        switch(message->what) {

                case NodeGroup::M_RELEASED:
                        {
                                err = message->FindInt32("groupID", (int32*)&groupID);
                                if(err < B_OK) {
                                        PRINT((
                                                "* TransportView::MessageReceived(NodeGroup::M_RELEASED)\n"
                                                "  no groupID!\n"));
                                }
                                if(!m_group || groupID != m_group->id()) {
                                        PRINT((
                                                "* TransportView::MessageReceived(NodeGroup::M_RELEASED)\n"
                                                "  mismatched groupID.\n"));
                                        break;
                                }

                                _releaseGroup();
//
//                              BMessage m(M_REMOVE_OBSERVER);
//                              m.AddMessenger("observer", BMessenger(this));
//                              message->SendReply(&m);
                        }
                        break;

                case NodeGroup::M_OBSERVER_ADDED:
                        err = message->FindInt32("groupID", (int32*)&groupID);
                        if(err < B_OK) {
                                PRINT((
                                        "* TransportView::MessageReceived(NodeGroup::M_OBSERVER_ADDED)\n"
                                        "  no groupID!\n"));
                                break;
                        }
                        if(!m_group || groupID != m_group->id()) {
                                PRINT((
                                        "* TransportView::MessageReceived(NodeGroup::M_OBSERVER_ADDED)\n"
                                        "  mismatched groupID; ignoring.\n"));
                                break;
                        }

                        _enableControls();
                        break;

                case NodeGroup::M_TRANSPORT_STATE_CHANGED:
                        uint32 groupID;
                        err = message->FindInt32("groupID", (int32*)&groupID);
                        if(err < B_OK) {
                                PRINT((
                                        "* TransportView::MessageReceived(NodeGroup::M_TRANSPORT_STATE_CHANGED)\n"
                                        "  no groupID!\n"));
                                break;
                        }
                        if(!m_group || groupID != m_group->id()) {
                                PRINT((
                                        "* TransportView::MessageReceived(NodeGroup::M_TRANSPORT_STATE_CHANGED)\n"
                                        "  mismatched groupID; ignoring.\n"));
                                break;
                        }

                        _updateTransportButtons();
                        break;

                case NodeGroup::M_TIME_SOURCE_CHANGED:
                        //_updateTimeSource(); +++++ check group?
                        break;

                case NodeGroup::M_RUN_MODE_CHANGED:
                        //_updateRunMode(); +++++ check group?
                        break;

                // * CONTROL PROCESSING

                case NodeGroup::M_SET_START_POSITION: {

                        if(!m_group)
                                break;

                        bigtime_t position = _scalePosition(
                                m_regionStartView->value());
                        message->AddInt64("position", position);
                        BMessenger(m_group).SendMessage(message);

                        // update start-button duty/label [e.moon 11oct99]
                        if(m_group->transportState() == NodeGroup::TRANSPORT_STOPPED)
                                _updateTransportButtons();

                        break;
                }

                case NodeGroup::M_SET_END_POSITION: {

                        if(!m_group)
                                break;

                        bigtime_t position = _scalePosition(
                                m_regionEndView->value());
                        message->AddInt64("position", position);
                        BMessenger(m_group).SendMessage(message);

                        // update start-button duty/label [e.moon 11oct99]
                        if(m_group->transportState() == NodeGroup::TRANSPORT_STOPPED)
                                _updateTransportButtons();

                        break;
                }

                case M_SET_NAME:
                        {
                                const char* name;
                                err = message->FindString("_value", &name);
                                if(err < B_OK) {
                                        PRINT((
                                                "! TransportView::MessageReceived(M_SET_NAME): no _value!\n"));
                                        break;
                                }
                                if(m_group) {
                                        m_group->setName(name);
                                        m_infoView->Invalidate();
                                }
                        }
                        break;

                case RouteAppNodeManager::M_TIME_SOURCE_CREATED:
                        _timeSourceCreated(message);
                        break;

                case RouteAppNodeManager::M_TIME_SOURCE_DELETED:
                        _timeSourceDeleted(message);
                        break;

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

// -------------------------------------------------------- //
// *** BHandler impl.
// -------------------------------------------------------- //

void TransportView::_handleSelectGroup(
        BMessage*                                                               message) {

        uint32 groupID;
        status_t err = message->FindInt32("groupID", (int32*)&groupID);
        if(err < B_OK) {
                PRINT((
                        "* TransportView::_handleSelectGroup(): no groupID\n"));
                return;
        }

        if(m_group && groupID != m_group->id())
                _releaseGroup();

        _selectGroup(groupID);

        Invalidate();
}

// -------------------------------------------------------- //
// *** internal operations
// -------------------------------------------------------- //

// select the given group; initialize controls
// (if 0, gray out all controls)
void TransportView::_selectGroup(
        uint32                                                                  groupID) {
        status_t err;

        if(m_group)
                _releaseGroup();

        if(!groupID) {
                _disableControls();
                return;
        }

        err = m_manager->findGroup(groupID, &m_group);
        if(err < B_OK) {
                PRINT((
                        "* TransportView::_selectGroup(%" B_PRId32 "): findGroup() failed:\n"
                        "  %s\n",
                        groupID,
                        strerror(err)));
                return;
        }

        _observeGroup();
}

void TransportView::_observeGroup() {
        ASSERT(m_group);

        add_observer(this, m_group);
}

void TransportView::_releaseGroup() {
        ASSERT(m_group);

        remove_observer(this, m_group);
        m_group = 0;
}
// -------------------------------------------------------- //
// *** CONTROLS
// -------------------------------------------------------- //

const char _run_mode_strings[][20] = {
        B_TRANSLATE_MARK("Offline"),
        B_TRANSLATE_MARK("Decrease precision"),
        B_TRANSLATE_MARK("Increase latency"),
        B_TRANSLATE_MARK("Drop data"),
        B_TRANSLATE_MARK("Recording")
};
const int _run_modes = 5;

//const char _time_source_strings[][20] = {
//      "DAC time source",
//      "System clock"
//};
//const int _time_sources = 2;

const char* _region_start_label = B_TRANSLATE("From:");
const char* _region_end_label = B_TRANSLATE("To:");

void TransportView::_constructControls() {

        BMessage* m;

        // * create and populate, but don't position, the views:

//      // create and populate, but don't position, the views:
//      m_nameView = new BStringView(
//              BRect(),
//              "nameView",
//              "Group Name",
//              B_FOLLOW_NONE);
//      AddChild(m_nameView);

        m_infoView = new _GroupInfoView(
                BRect(),
                this,
                "infoView");
        AddChild(m_infoView);

        m_runModeView = new BMenuField(
                BRect(),
                "runModeView",
                B_TRANSLATE("Run mode:"),
                new BPopUpMenu("runModeMenu"));
        _populateRunModeMenu(
                m_runModeView->Menu());
        AddChild(m_runModeView);

        m_timeSourceView = new BMenuField(
                BRect(),
                "timeSourceView",
                B_TRANSLATE("Time source:"),
                new BPopUpMenu("timeSourceMenu"));
        _populateTimeSourceMenu(
                m_timeSourceView->Menu());
        AddChild(m_timeSourceView);


        m_fromLabel = new BStringView(BRect(), 0, B_TRANSLATE("Roll from"));
        AddChild(m_fromLabel);


        m = new BMessage(NodeGroup::M_SET_START_POSITION);
        m_regionStartView = new NumericValControl(
                BRect(),
                "regionStartView",
                m,
                2, 4, // * DIGITS
                false, ValControl::ALIGN_FLUSH_LEFT);

        // redirect view back to self.  this gives me the chance to
        // augment the message with the position before sending
        _addLocalTarget(m_regionStartView);
        AddChild(m_regionStartView);

        m_toLabel = new BStringView(BRect(), 0, B_TRANSLATE("to"));
        AddChild(m_toLabel);

        m = new BMessage(NodeGroup::M_SET_END_POSITION);
        m_regionEndView = new NumericValControl(
                BRect(),
                "regionEndView",
                m,
                2, 4, // * DIGITS
                false, ValControl::ALIGN_FLUSH_LEFT);

        // redirect view back to self.  this gives me the chance to
        // augment the message with the position before sending
        _addLocalTarget(m_regionEndView);
        AddChild(m_regionEndView);

//      m = new BMessage(NodeGroup::M_SET_START_POSITION);
//      m_regionStartView = new BTextControl(
//              BRect(),
//              "regionStartView",
//              _region_start_label,
//              "0",
//              m);
//
//      _addGroupTarget(m_regionStartView);
////    m_regionStartView->TextView()->SetAlignment(B_ALIGN_RIGHT);
//
//      AddChild(m_regionStartView);
//
//      m = new BMessage(NodeGroup::M_SET_END_POSITION);
//      m_regionEndView = new BTextControl(
//              BRect(),
//              "regionEndView",
//              _region_end_label,
//              "0",
//              m);
//
//      _addGroupTarget(m_regionEndView);
////    m_regionEndView->TextView()->SetAlignment(B_ALIGN_RIGHT);
//      AddChild(m_regionEndView);

        m = new BMessage(NodeGroup::M_START);
        m_startButton = new BButton(
                BRect(),
                "startButton",
                B_TRANSLATE("Start"),
                m);
        _addGroupTarget(m_startButton);
        AddChild(m_startButton);

        m = new BMessage(NodeGroup::M_STOP);
        m_stopButton = new BButton(
                BRect(),
                "stopButton",
                B_TRANSLATE("Stop"),
                m);
        _addGroupTarget(m_stopButton);
        AddChild(m_stopButton);

        m = new BMessage(NodeGroup::M_PREROLL);
        m_prerollButton = new BButton(
                BRect(),
                "prerollButton",
                B_TRANSLATE("Preroll"),
                m);
        _addGroupTarget(m_prerollButton);
        AddChild(m_prerollButton);
}

// convert a position control's value to bigtime_t
// [e.moon 11oct99]
bigtime_t TransportView::_scalePosition(
        double                                                                  value) {

        return bigtime_t(value * 1000000.0);
}

void TransportView::_populateRunModeMenu(
        BMenu*                                                                  menu) {

        BMessage* m;
        for(int n = 0; n < _run_modes; ++n) {
                m = new BMessage(NodeGroup::M_SET_RUN_MODE);
                m->AddInt32("runMode", n+1);

                BMenuItem* i = new BMenuItem(
                        B_TRANSLATE(_run_mode_strings[n]), m);
                menu->AddItem(i);
                _addGroupTarget(i);
        }
}

void TransportView::_populateTimeSourceMenu(
        BMenu*                                                                  menu) {

        // find the standard time sources
        status_t err;
        media_node dacTimeSource, systemTimeSource;
        BMessage* m;
        BMenuItem* i;
        err = m_manager->roster->GetTimeSource(&dacTimeSource);
        if(err == B_OK) {
                m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
                m->AddData(
                        "timeSourceNode",
                        B_RAW_TYPE,
                        &dacTimeSource,
                        sizeof(media_node));
                i = new BMenuItem(
                        B_TRANSLATE("DAC time source"),
                        m);
                menu->AddItem(i);
                _addGroupTarget(i);
        }

        err = m_manager->roster->GetSystemTimeSource(&systemTimeSource);
        if(err == B_OK) {
                m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
                m->AddData(
                        "timeSourceNode",
                        B_RAW_TYPE,
                        &systemTimeSource,
                        sizeof(media_node));
                i = new BMenuItem(
                        B_TRANSLATE("System clock"),
                        m);
                menu->AddItem(i);
                _addGroupTarget(i);
        }

        // find other time source nodes

        Autolock _l(m_manager);
        void* cookie = 0;
        NodeRef* r;
        char nameBuffer[B_MEDIA_NAME_LENGTH+16];
        bool needSeparator = (menu->CountItems() > 0);
        while(m_manager->getNextRef(&r, &cookie) == B_OK) {
                if(!(r->kind() & B_TIME_SOURCE))
                        continue;
                if(r->id() == dacTimeSource.node)
                        continue;
                if(r->id() == systemTimeSource.node)
                        continue;

                if(needSeparator) {
                        needSeparator = false;
                        menu->AddItem(new BSeparatorItem());
                }

                m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
                m->AddData(
                        "timeSourceNode",
                        B_RAW_TYPE,
                        &r->node(),
                        sizeof(media_node));
                sprintf(nameBuffer, "%s: %" B_PRId32,
                        r->name(),
                        r->id());
                i = new BMenuItem(
                        nameBuffer,
                        m);
                menu->AddItem(i);
                _addGroupTarget(i);
        }

//      BMessage* m;
//      for(int n = 0; n < _time_sources; ++n) {
//              m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
//              m->AddData(
//                      "timeSourceNode",
//                      B_RAW_TYPE,
//                      &m_timeSourcePresets[n],
//                      sizeof(media_node));
//              // +++++ copy media_node of associated time source!
////            m->AddInt32("timeSource", n);
//
//              BMenuItem* i = new BMenuItem(
//                              _time_source_strings[n], m);
//              menu->AddItem(i);
//              _addGroupTarget(i);
//      }
}

// add the given invoker to be retargeted to this
// view (used for controls whose messages need a bit more
// processing before being forwarded to the NodeManager.)

void TransportView::_addLocalTarget(
        BInvoker*                                                               invoker) {

        m_localTargets.push_back(invoker);
        if(Window())
                invoker->SetTarget(this);
}

void TransportView::_addGroupTarget(
        BInvoker*                                                               invoker) {

        m_groupTargets.push_back(invoker);
        if(m_group)
                invoker->SetTarget(m_group);
}

void TransportView::_refreshTransportSettings() {
        if(m_group)
                _updateTransportButtons();
}


void TransportView::_disableControls() {

//      PRINT((
//              "*** _disableControls()\n"));

        BWindow* w = Window();
        if(w)
                w->BeginViewTransaction();

//      m_nameView->SetText("(no group)");
        m_infoView->Invalidate();

        m_runModeView->SetEnabled(false);
        m_runModeView->Menu()->Superitem()->SetLabel(B_TRANSLATE("(none)"));

        m_timeSourceView->SetEnabled(false);
        m_timeSourceView->Menu()->Superitem()->SetLabel(B_TRANSLATE("(none)"));

        m_regionStartView->SetEnabled(false);
        m_regionStartView->setValue(0);

        m_regionEndView->SetEnabled(false);
        m_regionEndView->setValue(0);

        m_startButton->SetEnabled(false);
        m_stopButton->SetEnabled(false);
        m_prerollButton->SetEnabled(false);

        if(w)
                w->EndViewTransaction();


//      PRINT((
//              "*** DONE: _disableControls()\n"));
}

void TransportView::_enableControls() {

//      PRINT((
//              "*** _enableControls()\n"));
//
        ASSERT(m_group);
        Autolock _l(m_group); // +++++ why?
        BWindow* w = Window();
        if(w) {
                w->BeginViewTransaction();

                // clear focused view
                // 19sep99: TransportWindow is currently a B_AVOID_FOCUS window; the
                // only way views get focused is if they ask to be (which ValControl
                // currently does.)
                BView* focused = w->CurrentFocus();
                if(focused)
                        focused->MakeFocus(false);
        }

//      BString nameViewText = m_group->name();
//      nameViewText << ": " << m_group->countNodes() << " nodes.";
//      m_nameView->SetText(nameViewText.String());

        m_infoView->Invalidate();

        m_runModeView->SetEnabled(true);
        _updateRunMode();

        m_timeSourceView->SetEnabled(true);
        _updateTimeSource();

        _updateTransportButtons();


        // +++++ ick. hard-coded scaling factors make me queasy

        m_regionStartView->SetEnabled(true);
        m_regionStartView->setValue(
                ((double)m_group->startPosition()) / 1000000.0);

        m_regionEndView->SetEnabled(true);
        m_regionEndView->setValue(
                ((double)m_group->endPosition()) / 1000000.0);

        // target controls on group
        for(target_set::iterator it = m_groupTargets.begin();
                it != m_groupTargets.end(); ++it) {
                ASSERT(*it);
                (*it)->SetTarget(m_group);
        }

        if(w) {
                w->EndViewTransaction();
        }

//      PRINT((
//              "*** DONE: _enableControls()\n"));
}

void TransportView::_updateTransportButtons() {

        ASSERT(m_group);

        switch(m_group->transportState()) {
                case NodeGroup::TRANSPORT_INVALID:
                case NodeGroup::TRANSPORT_STARTING:
                case NodeGroup::TRANSPORT_STOPPING:
                        m_startButton->SetEnabled(false);
                        m_stopButton->SetEnabled(false);
                        m_prerollButton->SetEnabled(false);
                        break;

                case NodeGroup::TRANSPORT_STOPPED:
                        m_startButton->SetEnabled(true);
                        m_stopButton->SetEnabled(false);
                        m_prerollButton->SetEnabled(true);
                        break;

                case NodeGroup::TRANSPORT_RUNNING:
                case NodeGroup::TRANSPORT_ROLLING:
                        m_startButton->SetEnabled(false);
                        m_stopButton->SetEnabled(true);
                        m_prerollButton->SetEnabled(false);
                        break;
        }

        // based on group settings, set the start button to do either
        // a simple start or a roll (atomic start/stop.) [e.moon 11oct99]
        ASSERT(m_startButton->Message());

        // get current region settings
        bigtime_t startPosition = _scalePosition(m_regionStartView->value());
        bigtime_t endPosition = _scalePosition(m_regionEndView->value());

        // get current run-mode setting
        uint32 runMode = 0;
        BMenuItem* i = m_runModeView->Menu()->FindMarked();
        ASSERT(i);
        runMode = m_runModeView->Menu()->IndexOf(i) + 1;

        if(
                endPosition > startPosition &&
                (runMode == BMediaNode::B_OFFLINE ||
                        !m_group->canCycle())) {

                m_startButton->SetLabel(B_TRANSLATE("Roll"));
                m_startButton->Message()->what = NodeGroup::M_ROLL;

        } else {
                m_startButton->SetLabel(B_TRANSLATE("Start"));
                m_startButton->Message()->what = NodeGroup::M_START;
        }
}

void TransportView::_updateTimeSource() {
        ASSERT(m_group);

        media_node tsNode;
        status_t err = m_group->getTimeSource(&tsNode);
        if(err < B_OK) {
                PRINT((
                        "! TransportView::_updateTimeSource(): m_group->getTimeSource():\n"
                        "  %s\n",
                        strerror(err)));
                return;
        }

        BMenu* menu = m_timeSourceView->Menu();
        ASSERT(menu);
        if(tsNode == media_node::null) {
                menu->Superitem()->SetLabel(B_TRANSLATE("(none)"));
                return;
        }

        // find menu item
        int32 n;
        for(n = menu->CountItems()-1; n >= 0; --n) {
                const void* data;
                media_node itemNode;
                BMessage* message = menu->ItemAt(n)->Message();
                if(!message)
                        continue;

                ssize_t size = sizeof(media_node);
                err = message->FindData(
                        "timeSourceNode",
                        B_RAW_TYPE,
                        &data,
                        &size);
                if(err < B_OK)
                        continue;

                itemNode = *((media_node*)data);
                if(itemNode != tsNode)
                        continue;

                // found it
                PRINT((
                        "### _updateTimeSource: %s\n",
                        menu->ItemAt(n)->Label()));
                menu->ItemAt(n)->SetMarked(true);
                break;
        }
//      ASSERT(m_timeSourcePresets);
//      int n;
//      for(n = 0; n < _time_sources; ++n) {
//              if(m_timeSourcePresets[n] == tsNode) {
//                      BMenuItem* i = m_timeSourceView->Menu()->ItemAt(n);
//                      ASSERT(i);
//                      i->SetMarked(true);
//                      break;
//              }
//      }
        if(n < 0)
                menu->Superitem()->SetLabel(B_TRANSLATE("(\?\?\?)"));

}
void TransportView::_updateRunMode() {
        ASSERT(m_group);

        BMenu* menu = m_runModeView->Menu();
        uint32 runMode = m_group->runMode() - 1; // real run mode starts at 1
        ASSERT((uint32)menu->CountItems() > runMode);
        menu->ItemAt(runMode)->SetMarked(true);
}

//void TransportView::_initTimeSources() {
//
//      status_t err;
//      media_node node;
//      err = m_manager->roster->GetTimeSource(&node);
//      if(err == B_OK) {
//              m_timeSources.push_back(node.node);
//      }
//
//      err = m_manager->roster->GetSystemTimeSource(&m_timeSourcePresets[1]);
//      if(err < B_OK) {
//              PRINT((
//                      "* TransportView::_initTimeSources():\n"
//                      "  GetSystemTimeSource() failed: %s\n", strerror(err)));
//      }
//}

// [e.moon 2dec99]
void TransportView::_timeSourceCreated(
        BMessage*                                                               message) {

        status_t err;
        media_node_id id;
        err = message->FindInt32("nodeID", &id);
        if(err < B_OK)
                return;

//      PRINT(("### _timeSourceCreated(): %" B_PRId32 "\n", id));
        NodeRef* ref;
        err = m_manager->getNodeRef(id, &ref);
        if(err < B_OK) {
                PRINT((
                        "!!! TransportView::_timeSourceCreated(): node %" B_PRId32 " doesn't exist\n",
                        id));
                return;
        }

        char nameBuffer[B_MEDIA_NAME_LENGTH+16];
        BMessage* m = new BMessage(NodeGroup::M_SET_TIME_SOURCE);
        m->AddData(
                "timeSourceNode",
                B_RAW_TYPE,
                &ref->node(),
                sizeof(media_node));
        sprintf(nameBuffer, "%s: %" B_PRId32,
                ref->name(),
                ref->id());
        BMenuItem* i = new BMenuItem(
                nameBuffer,
                m);
        BMenu* menu = m_timeSourceView->Menu();
        menu->AddItem(i);
        _addGroupTarget(i);
}

// +++++
void TransportView::_timeSourceDeleted(
        BMessage*                                                               message) {

        status_t err;
        media_node_id id;
        err = message->FindInt32("nodeID", &id);
        if(err < B_OK)
                return;

//      PRINT(("### _timeSourceDeleted(): %" B_PRId32 "\n", id));

        BMenu* menu = m_timeSourceView->Menu();
        ASSERT(menu);

        // find menu item
        int32 n;
        for(n = menu->CountItems()-1; n >= 0; --n) {
                const void* data;
                media_node itemNode;
                BMessage* message = menu->ItemAt(n)->Message();
                if(!message)
                        continue;

                ssize_t size = sizeof(media_node);
                err = message->FindData(
                        "timeSourceNode",
                        B_RAW_TYPE,
                        &data,
                        &size);
                if(err < B_OK)
                        continue;

                itemNode = *((media_node*)data);
                if(itemNode.node != id)
                        continue;

                // found it; remove the item
                menu->RemoveItem(n);
                break;
        }
}

// -------------------------------------------------------- //
// *** LAYOUT ***
// -------------------------------------------------------- //

const float _edge_pad_x                                                                 = 3.0;
const float _edge_pad_y                                                                 = 3.0;

const float _column_pad_x                                                       = 4.0;

const float _field_pad_x                                                                = 20.0;

const float _text_view_pad_x                                            = 10.0;

const float _control_pad_x                                                      = 5.0;
const float _control_pad_y                                                      = 10.0;
const float _menu_field_pad_x                                   = 30.0;

const float _label_pad_x                                                                = 8.0;

const float _transport_pad_y                                            = 4.0;
const float _transport_button_width                     = 60.0;
const float _transport_button_height            = 22.0;
const float _transport_button_pad_x                     = 4.0;

const float _lock_button_width                                  = 42.0;
const float _lock_button_height                                 = 16.0;

class TransportView::_layout_state {
public:
        _layout_state() {}

        // +++++
};

inline float _menu_width(
        const BMenu* menu,
        const BFont* font) {

        float max = 0.0;

        int total = menu->CountItems();
        for(int n = 0; n < total; ++n) {
                float w = font->StringWidth(
                        menu->ItemAt(n)->Label());
                if(w > max)
                        max = w;
        }

        return max;
}

// -------------------------------------------------------- //

void TransportView::_initLayout() {
        m_layout = new _layout_state();
}


void TransportView::_updateLayout() // +++++
{
        BWindow* window = Window();
        if(window)
                window->BeginViewTransaction();

        // calculate the ideal size of the view
        // * max label width
        float maxLabelWidth = 0.0;
        float w;

        maxLabelWidth = be_bold_font->StringWidth(
                m_runModeView->Label());

        w = be_bold_font->StringWidth(
                m_timeSourceView->Label());
        if(w > maxLabelWidth)
                maxLabelWidth = w;

//      w = be_bold_font->StringWidth(
//              m_regionStartView->Label());
//      if(w > maxLabelWidth)
//              maxLabelWidth = w;
//
//      w = be_bold_font->StringWidth(
//              m_regionEndView->Label());
//      if(w > maxLabelWidth)
//              maxLabelWidth = w;

        // * max field width
        float maxFieldWidth = 0.0;
        maxFieldWidth = _menu_width(
                m_runModeView->Menu(), be_plain_font);

        w = _menu_width(
                m_timeSourceView->Menu(), be_plain_font);
        if(w > maxFieldWidth)
                maxFieldWidth = w;

        // * column width
        float columnWidth =
                maxLabelWidth +
                maxFieldWidth + _label_pad_x + _field_pad_x;

        // figure columns
        float column1_x = _edge_pad_x;
        float column2_x = column1_x + columnWidth + _column_pad_x;

        // * sum to figure view width
        float viewWidth =
                column2_x + columnWidth + _edge_pad_x;

        // make room for buttons
        float buttonSpan =
                (_transport_button_width*3) +
                (_transport_button_pad_x*2);
        if(columnWidth < buttonSpan)
                viewWidth += (buttonSpan - columnWidth);

//      float insideWidth = viewWidth - (_edge_pad_x*2);

        // * figure view height a row at a time
        font_height fh;
        be_plain_font->GetHeight(&fh);
        float lineHeight = fh.ascent + fh.descent + fh.leading;

        float prefInfoWidth, prefInfoHeight;
        m_infoView->GetPreferredSize(&prefInfoWidth, &prefInfoHeight);
        float row_1_height = max(prefInfoHeight, _transport_button_height);

        float row1_y = _edge_pad_y;
        float row2_y = row1_y + row_1_height + _transport_pad_y;
        float row3_y = row2_y + lineHeight + _control_pad_y;
//      float row4_y = row3_y + lineHeight + _control_pad_y + _transport_pad_y;
//      float row5_y = row4_y + lineHeight + _control_pad_y;
        float viewHeight = row3_y + lineHeight + _control_pad_y + _edge_pad_y;

        // Place controls
        m_infoView->MoveTo(
                column1_x+1.0, row1_y+1.0);
        m_infoView->ResizeTo(
                columnWidth, prefInfoHeight);

        BRect br(
                column2_x, row1_y,
                column2_x+_transport_button_width,
                row1_y+_transport_button_height);
        if(prefInfoHeight > _transport_button_height)
                br.OffsetBy(0.0, (prefInfoHeight - _transport_button_height)/2);

        m_startButton->MoveTo(br.LeftTop());
        m_startButton->ResizeTo(br.Width(), br.Height());
        br.OffsetBy(_transport_button_width + _transport_button_pad_x, 0.0);

        m_stopButton->MoveTo(br.LeftTop());
        m_stopButton->ResizeTo(br.Width(), br.Height());
        br.OffsetBy(_transport_button_width + _transport_button_pad_x, 0.0);

        m_prerollButton->MoveTo(br.LeftTop());
        m_prerollButton->ResizeTo(br.Width(), br.Height());

        m_runModeView->MoveTo(
                column2_x, row2_y);
        m_runModeView->ResizeTo(
                columnWidth, lineHeight);
        m_runModeView->SetDivider(
                maxLabelWidth+_label_pad_x);
        m_runModeView->SetAlignment(
                B_ALIGN_LEFT);

        m_timeSourceView->MoveTo(
                column2_x, row3_y);
        m_timeSourceView->ResizeTo(
                columnWidth, lineHeight);
        m_timeSourceView->SetDivider(
                maxLabelWidth+_label_pad_x);
        m_timeSourceView->SetAlignment(
                B_ALIGN_LEFT);

//      float regionControlWidth = columnWidth;
//      float regionControlHeight = lineHeight + 4.0;

//      m_regionStartView->TextView()->SetResizingMode(
//              B_FOLLOW_LEFT_RIGHT|B_FOLLOW_TOP);

        // "FROM"

        BPoint rtLeftTop(column1_x, row2_y + 5.0);
        BPoint rtRightBottom;

        m_fromLabel->MoveTo(rtLeftTop);
        m_fromLabel->ResizeToPreferred();
        rtRightBottom = rtLeftTop + BPoint(
                m_fromLabel->Bounds().Width(),
                m_fromLabel->Bounds().Height());


        // (region-start)

        rtLeftTop.x = rtRightBottom.x+4;

        m_regionStartView->MoveTo(rtLeftTop + BPoint(0.0, 2.0));
        m_regionStartView->ResizeToPreferred();
        rtRightBottom = rtLeftTop + BPoint(
                m_regionStartView->Bounds().Width(),
                m_regionStartView->Bounds().Height());

//      m_regionStartView->SetDivider(
//              maxLabelWidth);
//      m_regionStartView->TextView()->ResizeTo(
//              regionControlWidth-(maxLabelWidth+_text_view_pad_x),
//              regionControlHeight-4.0);

        // "TO"

        rtLeftTop.x = rtRightBottom.x + 6;

        m_toLabel->MoveTo(rtLeftTop);
        m_toLabel->ResizeToPreferred();
        rtRightBottom = rtLeftTop + BPoint(
                m_toLabel->Bounds().Width(),
                m_toLabel->Bounds().Height());

        // (region-end)

        rtLeftTop.x = rtRightBottom.x + 4;

        m_regionEndView->MoveTo(rtLeftTop + BPoint(0.0, 2.0));
        m_regionEndView->ResizeToPreferred();
//      m_regionEndView->SetDivider(
//              maxLabelWidth);
//      m_regionEndView->TextView()->ResizeTo(
//              regionControlWidth-(maxLabelWidth+_text_view_pad_x),
//              regionControlHeight-4.0);


        BRect b = Bounds();
        float targetWidth = (b.Width() < viewWidth) ?
                viewWidth :
                b.Width();
        float targetHeight = (b.Height() < viewHeight) ?
                viewHeight :
                b.Height();

        // Resize view to fit contents
        ResizeTo(targetWidth, targetHeight);

        if(window) {
                window->ResizeTo(targetWidth, targetHeight);
        }

//      // +++++ testing NumericValControl [23aug99]
//      float valWidth, valHeight;
//      m_valView->GetPreferredSize(&valWidth, &valHeight);
//      PRINT((
//              "\n\nm_valView preferred size: %.1f x %.1f\n\n",
//              valWidth, valHeight));
//
        if(window)
                window->EndViewTransaction();
}

// -------------------------------------------------------- //
// *** dtor
// -------------------------------------------------------- //

TransportView::~TransportView() {
        if(m_group)
                _releaseGroup();
        if(m_layout)
                delete m_layout;
}


// END -- TransportView.cpp --