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


// ValControl.cpp

#include "ValControl.h"
#include "ValControlSegment.h"

#include "TextControlFloater.h"

#include <Debug.h>
#include <String.h>
#include <Window.h>

#include <algorithm>
#include <functional>
#include <cstdio>

using namespace std;

__USE_CORTEX_NAMESPACE


const float ValControl::fSegmentPadding = 2.0;

// the decimal point covers one more pixel x and y-ward:
const float ValControl::fDecimalPointWidth = 2.0;
const float ValControl::fDecimalPointHeight = 2.0;


/*protected*/
ValControl::ValControl(BRect frame, const char* name, const char* label,
                BMessage* message, align_mode alignMode, align_flags alignFlags,
                update_mode updateMode, bool backBuffer)
        : BControl(frame, name, label, message, B_FOLLOW_TOP|B_FOLLOW_LEFT,
                B_WILL_DRAW|B_FRAME_EVENTS),
        fDirty(true),
        fUpdateMode(updateMode),
        fLabelFont(be_bold_font),
        fValueFont(be_bold_font),
        fAlignMode(alignMode),
        fAlignFlags(alignFlags),
        fOrigBounds(Bounds()),
        fHaveBackBuffer(backBuffer),
        fBackBuffer(NULL),
        fBackBufferView(NULL)
{
        if (fHaveBackBuffer)
                _AllocBackBuffer(frame.Width(), frame.Height());

//      m_font.SetSize(13.0);   
//      rgb_color red = {255,0,0,255};
//      SetViewColor(red);
}


ValControl::~ValControl()
{
        delete fBackBuffer;
}


ValControl::update_mode
ValControl::updateMode() const
{
        return fUpdateMode;
}


void
ValControl::setUpdateMode(update_mode mode)
{
        fUpdateMode = mode;
}


const BFont*
ValControl::labelFont() const
{
        return &fLabelFont;
}


void
ValControl::setLabelFont(const BFont* labelFont)
{
        fLabelFont = labelFont;
        // inform label segments
        _InvalidateAll();
}       


const BFont*
ValControl::valueFont() const
{
        return &fValueFont;
}


void
ValControl::setValueFont(const BFont* valueFont)
{
        fValueFont = valueFont;
        
        // inform value segments
        for (int n = CountEntries(); n > 0; --n) {
                const ValCtrlLayoutEntry& e = _EntryAt(n-1);
                if (e.type != ValCtrlLayoutEntry::SEGMENT_ENTRY)
                        continue;
                        
                ValControlSegment* s = dynamic_cast<ValControlSegment*>(e.pView);
                ASSERT(s);
                s->SetFont(&fValueFont);
                s->fontChanged(&fValueFont);
        }
}


float
ValControl::baselineOffset() const
{
        font_height h;
        be_plain_font->GetHeight(&h);
        return ceil(h.ascent);
}


float
ValControl::segmentPadding() const
{
        return fSegmentPadding;
}


BView*
ValControl::backBufferView() const
{
        return fBackBufferView;
}


BBitmap*
ValControl::backBuffer() const
{
        return fBackBuffer;
}


void
ValControl::dump()
{
#if defined(DEBUG)
        BRect f = Frame();

        PRINT((
                "*** ValControl::dump():\n"
                "    FRAME    (%.1f,%.1f)-(%.1f,%.1f)\n"
                "    ENTRIES:\n",
                f.left, f.top, f.right, f.bottom));

        for (layout_set::const_iterator it = fLayoutSet.begin();
                it != fLayoutSet.end(); ++it) {
                const ValCtrlLayoutEntry& e = *it;
                switch (e.type) {
                        case ValCtrlLayoutEntry::SEGMENT_ENTRY:
                                PRINT(("    Segment       "));
                                break;

                        case ValCtrlLayoutEntry::VIEW_ENTRY:
                                PRINT(("    View          "));
                                break;

                        case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY:
                                PRINT(("    Decimal Point "));
                                break;

                        default:
                                PRINT(("    ???           "));
                                break;
                }

                PRINT(("\n      cached frame (%.1f,%.1f)-(%.1f,%.1f) + pad(%.1f)\n",
                        e.frame.left, e.frame.top, e.frame.right, e.frame.bottom,
                        e.fPadding));

                if (e.type == ValCtrlLayoutEntry::SEGMENT_ENTRY
                        || e.type == ValCtrlLayoutEntry::VIEW_ENTRY) {
                        if (e.pView) {
                                PRINT(("      real frame   (%.1f,%.1f)-(%.1f,%.1f)\n\n",
                                        e.pView->Frame().left, e.pView->Frame().top,
                                        e.pView->Frame().right, e.pView->Frame().bottom));
                        } else
                                PRINT(("      (no view!)\n\n"));
                }
        }
        PRINT(("\n"));
#endif
}


void
ValControl::SetEnabled(bool enabled)
{
        // redraw if enabled-state changes
        _Inherited::SetEnabled(enabled);

        _InvalidateAll();
}


void
ValControl::_InvalidateAll()
{
        Invalidate();
        int c = CountChildren();
        for (int n = 0; n < c; ++n)
                ChildAt(n)->Invalidate();
}


void
ValControl::AttachedToWindow()
{
        // adopt parent view's color
        if (Parent())
                SetViewColor(Parent()->ViewColor());
}


void
ValControl::AllAttached()
{
        // move children to requested positions
        BWindow* pWnd = Window();
        pWnd->BeginViewTransaction();
        
        for_each(fLayoutSet.begin(), fLayoutSet.end(),
#if __GNUC__ <= 2
                ptr_fun(&ValCtrlLayoutEntry::InitChildFrame)
#else
                [](ValCtrlLayoutEntry& entry) { ValCtrlLayoutEntry::InitChildFrame(entry); }
#endif
        );
        
        pWnd->EndViewTransaction();
}


//! Paint decorations (& decimal point)
void
ValControl::Draw(BRect updateRect)
{       
        // draw lightweight entries:
        for (unsigned int n = 0; n < fLayoutSet.size(); n++) {
                if (fLayoutSet[n].type == ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY)
                        drawDecimalPoint(fLayoutSet[n]);
        }
}


void
ValControl::drawDecimalPoint(ValCtrlLayoutEntry& e)
{       
        rgb_color dark = {0, 0, 0, 255};
        rgb_color med = {200, 200, 200, 255};
//      rgb_color light = {244,244,244,255};
        
        BPoint center;
        center.x = e.frame.left + 1;
        center.y = baselineOffset() - 1;
        
        SetHighColor(dark);
        StrokeLine(center, center);
        SetHighColor(med);
        StrokeLine(center - BPoint(0, 1), center + BPoint(1, 0));
        StrokeLine(center - BPoint(1, 0), center + BPoint(0, 1));

//      SetHighColor(light);
//      StrokeLine(center+BPoint(-1,1), center+BPoint(-1,1));
//      StrokeLine(center+BPoint(1,1), center+BPoint(1,1));
//      StrokeLine(center+BPoint(-1,-1), center+BPoint(-1,-1));
//      StrokeLine(center+BPoint(1,-1), center+BPoint(1,-1));
}


void
ValControl::FrameResized(float width, float height)
{       
        _Inherited::FrameResized(width,height);
        if (fHaveBackBuffer)
                _AllocBackBuffer(width, height);
//
//      PRINT((
//              "# ValControl::FrameResized(): %.1f, %.1f\n",
//              width, height));
}


void
ValControl::GetPreferredSize(float* outWidth, float* outHeight)
{
        ASSERT(fLayoutSet.size() > 0);
        
        *outWidth =
                fLayoutSet.back().frame.right -
                fLayoutSet.front().frame.left;
                
        *outHeight = 0;
        for(layout_set::const_iterator it = fLayoutSet.begin();
                it != fLayoutSet.end(); ++it) {
                if((*it).frame.Height() > *outHeight)
                        *outHeight = (*it).frame.Height();
        }
//
//      PRINT((
//              "# ValControl::GetPreferredSize(): %.1f, %.1f\n",
//              *outWidth, *outHeight));
}


void
ValControl::MakeFocus(bool focused)
{
        _Inherited::MakeFocus(focused);

        // +++++ only the underline needs to be redrawn 
        _InvalidateAll();
}


void
ValControl::MouseDown(BPoint where)
{
        MakeFocus(true);
}


void
ValControl::MessageReceived(BMessage* message)
{
        status_t err;
        const char* stringValue;

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

        switch (message->what) {
                case M_SET_VALUE:
                        err = message->FindString("_value", &stringValue);
                        if(err < B_OK) {
                                PRINT((
                                        "! ValControl::MessageReceived(): no _value found!\n"));
                                break;
                        }
                                
                        // set from string
                        err = setValueFrom(stringValue);
                        if (err < B_OK) {
                                PRINT((
                                        "! ValControl::MessageReceived(): setValueFrom('%s'):\n"
                                        "  %s\n",
                                        stringValue,
                                        strerror(err)));
                        }
                        
                        // +++++ broadcast new value +++++ [23aug99]
                        break;
                        
                case M_GET_VALUE: // +++++
                        break;
                        
                default:
                        _Inherited::MessageReceived(message);
        }
}


// -------------------------------------------------------- //
// archiving/instantiation
// -------------------------------------------------------- //

ValControl::ValControl(BMessage* archive)
        : BControl(archive),
        fDirty(true)
{
        // fetch parameters
        archive->FindInt32("updateMode", (int32*)&fUpdateMode);
        archive->FindInt32("alignMode", (int32*)&fAlignMode);
        archive->FindInt32("alignFlags", (int32*)&fAlignFlags);

        // original bounds
        archive->FindRect("origBounds", &fOrigBounds);
}


status_t
ValControl::Archive(BMessage* archive, bool deep) const
{
        status_t err = _Inherited::Archive(archive, deep);
        
        // write parameters
        if (err == B_OK)
                err = archive->AddInt32("updateMode", (int32)fUpdateMode);
        if (err == B_OK)
                err = archive->AddInt32("alignMode", (int32)fAlignMode);
        if (err == B_OK)
                err = archive->AddInt32("alignFlags", (int32)fAlignFlags);
        if (err == B_OK)
                err = archive->AddRect("origBounds", fOrigBounds);
        if (err < B_OK)
                return err;

        // write layout set?
        if (!deep)
                return B_OK;

        // yes; spew it:
        for (layout_set::const_iterator it = fLayoutSet.begin();
                it != fLayoutSet.end(); it++) {

                // archive entry
                BMessage layoutSet;
                ASSERT((*it).pView);
                err = (*it).pView->Archive(&layoutSet, true);
                ASSERT(err == B_OK);
                
                // write it
                archive->AddMessage("layoutSet", &layoutSet);
        }

        return B_OK;
}


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

// add segment view (which is responsible for generating its
// own ValCtrlLayoutEntry)
void
ValControl::_Add(ValControlSegment* segment, entry_location from,
        uint16 distance)
{
        BWindow* pWnd = Window();
        if(pWnd)
                pWnd->BeginViewTransaction();
        
        AddChild(segment);
        
        segment->SetFont(&fValueFont);
        segment->fontChanged(&fValueFont);
        
        uint16 nIndex = _LocationToIndex(from, distance);
        ValCtrlLayoutEntry entry = segment->makeLayoutEntry();
        _InsertEntry(entry, nIndex);
//      linkSegment(segment, nIndex);
        
        if (pWnd)
                pWnd->EndViewTransaction();
}


// add general view (manipulator, label, etc.)
// the entry's frame rectangle will be filled in
void
ValControl::_Add(ValCtrlLayoutEntry& entry, entry_location from)
{
        BWindow* window = Window();
        if (window)
                window->BeginViewTransaction();

        if (entry.pView)        
                AddChild(entry.pView);

        uint16 index = _LocationToIndex(from, 0);
        _InsertEntry(entry, index);

        if (window)
                window->EndViewTransaction();
}


// access child-view ValCtrlLayoutEntry
// (_IndexOf returns index from left)
const ValCtrlLayoutEntry&
ValControl::_EntryAt(entry_location from, uint16 distance) const
{
        uint16 nIndex = _LocationToIndex(from, distance);
        ASSERT(nIndex < fLayoutSet.size());
        return fLayoutSet[nIndex];
}


const ValCtrlLayoutEntry&
ValControl::_EntryAt(uint16 offset) const
{
        uint16 nIndex = _LocationToIndex(FROM_LEFT, offset);
        ASSERT(nIndex < fLayoutSet.size());
        return fLayoutSet[nIndex];
}


uint16
ValControl::_IndexOf(BView* child) const
{
        for (uint16 n = 0; n < fLayoutSet.size(); n++) {
                if (fLayoutSet[n].pView == child)
                        return n;
        }

        ASSERT(!"shouldn't be here");
        return 0;
}


uint16
ValControl::CountEntries() const
{
        return fLayoutSet.size();
}


// pop up keyboard input field +++++
void
ValControl::showEditField()
{
        BString valueString;

#if defined(DEBUG)
        status_t err = getString(valueString);
        ASSERT(err == B_OK);
#endif  // DEBUG
        
        BRect f = Bounds().OffsetByCopy(4.0, -4.0);
        ConvertToScreen(&f);
        //PRINT((
        //"# ValControl::showEditField(): base bounds (%.1f, %.1f)-(%.1f,%.1f)\n",
        //f.left, f.top, f.right, f.bottom));
        
        new TextControlFloater(f, B_ALIGN_RIGHT, &fValueFont, valueString.String(),
                BMessenger(this), new BMessage(M_SET_VALUE));
                // TextControlFloater embeds new value
                // in message: _value (string) +++++ DO NOT HARDCODE
}


//! (Re-)initialize backbuffer
void
ValControl::_AllocBackBuffer(float width, float height)
{
        ASSERT(fHaveBackBuffer);
        if (fBackBuffer && fBackBuffer->Bounds().Width() >= width
                && fBackBuffer->Bounds().Height() >= height)
                return;

        if (fBackBuffer) {
                delete fBackBuffer;
                fBackBuffer = NULL;
                fBackBufferView = NULL;
        }

        BRect bounds(0, 0, width, height);
        fBackBuffer = new BBitmap(bounds, B_RGB32, true);
        fBackBufferView = new BView(bounds, "back", B_FOLLOW_NONE, B_WILL_DRAW);
        fBackBuffer->AddChild(fBackBufferView);
}


// ref'd view must already be a child +++++
// (due to GetPreferredSize implementation in segment views)
void
ValControl::_InsertEntry(ValCtrlLayoutEntry& entry, uint16 index)
{
        // view ptr must be 0, or a ValControlSegment that's already a child
        ValControlSegment* pSeg = dynamic_cast<ValControlSegment*>(entry.pView);
        if (entry.pView)
                ASSERT(pSeg);
        if (pSeg)
                ASSERT(this == pSeg->Parent());

        // entry must be at one side or the other:
        ASSERT(!index || index == fLayoutSet.size());

        // figure padding
        bool bNeedsPadding =
                !(entry.flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING ||
                 ((index - 1 >= 0 &&
                  fLayoutSet[index - 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)) ||
                 ((index + 1 < static_cast<uint16>(fLayoutSet.size()) &&
                  fLayoutSet[index + 1].flags & ValCtrlLayoutEntry::LAYOUT_NO_PADDING)));                 

        entry.fPadding = (bNeedsPadding) ? fSegmentPadding : 0.0;               

        // fetch (and grant) requested frame size
        BRect frame(0, 0, 0, 0);
        if (pSeg)
                pSeg->GetPreferredSize(&frame.right, &frame.bottom);
        else
                _GetDefaultEntrySize(entry.type, &frame.right, &frame.bottom);

        // figure amount this entry will displace:
        float fDisplacement = frame.Width() + entry.fPadding + 1;

        // set entry's top-left position:
        if (!fLayoutSet.size()) {
                // sole entry:
                if (fAlignMode == ALIGN_FLUSH_RIGHT)
                        frame.OffsetBy(Bounds().right - frame.Width(), 0.0);
        } else if (index) {
                // insert at right side
                if (fAlignMode == ALIGN_FLUSH_LEFT)
                        frame.OffsetBy(fLayoutSet.back().frame.right + 1 + entry.fPadding, 0.0);
                else
                        frame.OffsetBy(fLayoutSet.back().frame.right - frame.Width(), 0.0); //+++++
        } else {
                // insert at left side
                if (fAlignMode == ALIGN_FLUSH_RIGHT)
                        frame.OffsetBy(fLayoutSet.front().frame.left - fDisplacement, 0.0);
        }

        // add to layout set
        entry.frame = frame;
        fLayoutSet.insert(
                index ? fLayoutSet.end() : fLayoutSet.begin(),
                entry);

        // slide following or preceding entries (depending on align mode)
        // to make room:
        switch (fAlignMode) {
                case ALIGN_FLUSH_LEFT:
                        // following entries are shifted to the right
                        for(uint32 n = index+1; n < fLayoutSet.size(); n++)
                                _SlideEntry(n, fDisplacement);
                        break;
                        
                case ALIGN_FLUSH_RIGHT: {
                        // preceding entries are shifted to the left
                        for(int n = index-1; n >= 0; n--)
                                _SlideEntry(n, -fDisplacement);

                        break;
                }
        }
//      
//      PRINT((
//              "### added entry: (%.1f,%.1f)-(%.1f,%.1f)\n",
//              frame.left, frame.top, frame.right, frame.bottom));
}


void
ValControl::_SlideEntry(int index, float delta)
{
        ValCtrlLayoutEntry& e = fLayoutSet[index];
        e.frame.OffsetBy(delta, 0.0);

        // move & possibly resize view:
        if (e.pView) {
                e.pView->MoveTo(e.frame.LeftTop());

                BRect curFrame = e.pView->Frame();
                float fWidth = e.frame.Width();
                float fHeight = e.frame.Height();
                if (curFrame.Width() != fWidth
                        || curFrame.Height() != fHeight)
                        e.pView->ResizeTo(fWidth + 5.0, fHeight);
        }
}


uint16
ValControl::_LocationToIndex(entry_location from, uint16 distance) const
{
        uint16 nResult = 0;

        switch (from) {
                case FROM_LEFT:
                        nResult = distance;
                        break;

                case FROM_RIGHT:
                        nResult = fLayoutSet.size() - distance;
                        break;
        }

        ASSERT(nResult <= fLayoutSet.size());
        return nResult;
}


void
ValControl::_GetDefaultEntrySize(ValCtrlLayoutEntry::entry_type type,
        float* outWidth, float* outHeight)
{
        switch (type) {
                case ValCtrlLayoutEntry::SEGMENT_ENTRY:
                case ValCtrlLayoutEntry::VIEW_ENTRY:
                        *outWidth = 1.0;
                        *outHeight = 1.0;
                        break;

                case ValCtrlLayoutEntry::DECIMAL_POINT_ENTRY:
                        *outWidth = fDecimalPointWidth;
                        *outHeight = fDecimalPointHeight;
                        break;
        }
}