root/src/kits/interface/StringView.cpp
/*
 * Copyright 2001-2015, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Axel Dörfler, axeld@pinc-software.de
 *              Frans van Nispen (xlr8@tref.nl)
 *              Ingo Weinhold <ingo_weinhold@gmx.de>
 */


//!     BStringView draws a non-editable text string.


#include <StringView.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <LayoutUtils.h>
#include <Message.h>
#include <PropertyInfo.h>
#include <StringList.h>
#include <View.h>
#include <Window.h>

#include <binary_compatibility/Interface.h>


static property_info sPropertyList[] = {
        {
                "Text",
                { B_GET_PROPERTY, B_SET_PROPERTY },
                { B_DIRECT_SPECIFIER },
                NULL, 0,
                { B_STRING_TYPE }
        },
        {
                "Alignment",
                { B_GET_PROPERTY, B_SET_PROPERTY },
                { B_DIRECT_SPECIFIER },
                NULL, 0,
                { B_INT32_TYPE }
        },

        { 0 }
};


BStringView::BStringView(BRect frame, const char* name, const char* text,
        uint32 resizingMode, uint32 flags)
        :
        BView(frame, name, resizingMode, flags | B_FULL_UPDATE_ON_RESIZE),
        fText(text ? strdup(text) : NULL),
        fTruncation(B_NO_TRUNCATION),
        fAlign(B_ALIGN_LEFT),
        fPreferredSize(text ? _StringWidth(text) : 0.0, -1)
{
}


BStringView::BStringView(const char* name, const char* text, uint32 flags)
        :
        BView(name, flags | B_FULL_UPDATE_ON_RESIZE),
        fText(text ? strdup(text) : NULL),
        fTruncation(B_NO_TRUNCATION),
        fAlign(B_ALIGN_LEFT),
        fPreferredSize(text ? _StringWidth(text) : 0.0, -1)
{
}


BStringView::BStringView(BMessage* archive)
        :
        BView(archive),
        fText(NULL),
        fTruncation(B_NO_TRUNCATION),
        fPreferredSize(0, -1)
{
        fAlign = (alignment)archive->GetInt32("_align", B_ALIGN_LEFT);
        fTruncation = (uint32)archive->GetInt32("_truncation", B_NO_TRUNCATION);

        const char* text = archive->GetString("_text", NULL);

        SetText(text);
        SetFlags(Flags() | B_FULL_UPDATE_ON_RESIZE);
}


BStringView::~BStringView()
{
        free(fText);
}


// #pragma mark - Archiving methods


BArchivable*
BStringView::Instantiate(BMessage* data)
{
        if (!validate_instantiation(data, "BStringView"))
                return NULL;

        return new BStringView(data);
}


status_t
BStringView::Archive(BMessage* data, bool deep) const
{
        status_t status = BView::Archive(data, deep);

        if (status == B_OK && fText)
                status = data->AddString("_text", fText);
        if (status == B_OK && fTruncation != B_NO_TRUNCATION)
                status = data->AddInt32("_truncation", fTruncation);
        if (status == B_OK)
                status = data->AddInt32("_align", fAlign);

        return status;
}


// #pragma mark - Hook methods


void
BStringView::AttachedToWindow()
{
        if (HasDefaultColors())
                SetHighUIColor(B_PANEL_TEXT_COLOR);

        BView* parent = Parent();

        if (parent != NULL) {
                float tint = B_NO_TINT;
                color_which which = parent->ViewUIColor(&tint);

                if (which != B_NO_COLOR) {
                        SetViewUIColor(which, tint);
                        SetLowUIColor(which, tint);
                } else {
                        SetViewColor(parent->ViewColor());
                        SetLowColor(ViewColor());
                }
        }

        if (ViewColor() == B_TRANSPARENT_COLOR)
                AdoptSystemColors();
}


void
BStringView::DetachedFromWindow()
{
        BView::DetachedFromWindow();
}


void
BStringView::AllAttached()
{
        BView::AllAttached();
}


void
BStringView::AllDetached()
{
        BView::AllDetached();
}


// #pragma mark - Layout methods


void
BStringView::MakeFocus(bool focus)
{
        BView::MakeFocus(focus);
}


void
BStringView::GetPreferredSize(float* _width, float* _height)
{
        _ValidatePreferredSize();

        if (_width)
                *_width = fPreferredSize.width;

        if (_height)
                *_height = fPreferredSize.height;
}


BSize
BStringView::MinSize()
{
        return BLayoutUtils::ComposeSize(ExplicitMinSize(),
                _ValidatePreferredSize());
}


BSize
BStringView::MaxSize()
{
        return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
                _ValidatePreferredSize());
}


BSize
BStringView::PreferredSize()
{
        return BLayoutUtils::ComposeSize(ExplicitPreferredSize(),
                _ValidatePreferredSize());
}


void
BStringView::ResizeToPreferred()
{
        float width, height;
        GetPreferredSize(&width, &height);

        // Resize the width only for B_ALIGN_LEFT (if its large enough already, that is)
        if (Bounds().Width() > width && Alignment() != B_ALIGN_LEFT)
                width = Bounds().Width();

        BView::ResizeTo(width, height);
}


BAlignment
BStringView::LayoutAlignment()
{
        return BLayoutUtils::ComposeAlignment(ExplicitAlignment(),
                BAlignment(fAlign, B_ALIGN_MIDDLE));
}


// #pragma mark - More hook methods


void
BStringView::FrameMoved(BPoint newPosition)
{
        BView::FrameMoved(newPosition);
}


void
BStringView::FrameResized(float newWidth, float newHeight)
{
        BView::FrameResized(newWidth, newHeight);
}


void
BStringView::Draw(BRect updateRect)
{
        if (!fText)
                return;

        if (LowUIColor() == B_NO_COLOR)
                SetLowColor(ViewColor());

        font_height fontHeight;
        GetFontHeight(&fontHeight);

        BRect bounds = Bounds();

        BStringList lines;
        BString(fText).Split("\n", false, lines);
        for (int i = 0; i < lines.CountStrings(); i++) {
                const char* text = lines.StringAt(i).String();
                float width = StringWidth(text);
                BString truncated;
                if (fTruncation != B_NO_TRUNCATION && width > bounds.Width()) {
                        // The string needs to be truncated
                        // TODO: we should cache this
                        truncated = lines.StringAt(i);
                        TruncateString(&truncated, fTruncation, bounds.Width());
                        text = truncated.String();
                        width = StringWidth(text);
                }

                float y = (bounds.top + bounds.bottom - ceilf(fontHeight.descent))
                        - ceilf(fontHeight.ascent + fontHeight.descent + fontHeight.leading)
                                * (lines.CountStrings() - i - 1);
                float x;
                switch (fAlign) {
                        case B_ALIGN_RIGHT:
                                x = bounds.Width() - width;
                                break;

                        case B_ALIGN_CENTER:
                                x = (bounds.Width() - width) / 2.0;
                                break;

                        default:
                                x = 0.0;
                                break;
                }

                DrawString(text, BPoint(x, y));
        }
}


void
BStringView::MessageReceived(BMessage* message)
{
        if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) {
                int32 index;
                BMessage specifier;
                int32 form;
                const char* property;
                if (message->GetCurrentSpecifier(&index, &specifier, &form, &property)
                                != B_OK) {
                        BView::MessageReceived(message);
                        return;
                }

                BMessage reply(B_REPLY);
                bool handled = false;
                if (strcmp(property, "Text") == 0) {
                        if (message->what == B_GET_PROPERTY) {
                                reply.AddString("result", fText);
                                handled = true;
                        } else {
                                const char* text;
                                if (message->FindString("data", &text) == B_OK) {
                                        SetText(text);
                                        reply.AddInt32("error", B_OK);
                                        handled = true;
                                }
                        }
                } else if (strcmp(property, "Alignment") == 0) {
                        if (message->what == B_GET_PROPERTY) {
                                reply.AddInt32("result", (int32)fAlign);
                                handled = true;
                        } else {
                                int32 align;
                                if (message->FindInt32("data", &align) == B_OK) {
                                        SetAlignment((alignment)align);
                                        reply.AddInt32("error", B_OK);
                                        handled = true;
                                }
                        }
                }

                if (handled) {
                        message->SendReply(&reply);
                        return;
                }
        }

        BView::MessageReceived(message);
}


void
BStringView::MouseDown(BPoint point)
{
        BView::MouseDown(point);
}


void
BStringView::MouseUp(BPoint point)
{
        BView::MouseUp(point);
}


void
BStringView::MouseMoved(BPoint point, uint32 transit, const BMessage* msg)
{
        BView::MouseMoved(point, transit, msg);
}


// #pragma mark -


void
BStringView::SetText(const char* text)
{
        if ((text && fText && !strcmp(text, fText)) || (!text && !fText))
                return;

        free(fText);
        fText = text ? strdup(text) : NULL;

        float newStringWidth = _StringWidth(fText);
        if (fPreferredSize.width != newStringWidth) {
                fPreferredSize.width = newStringWidth;
                InvalidateLayout();
        }

        Invalidate();
}


const char*
BStringView::Text() const
{
        return fText;
}


void
BStringView::SetAlignment(alignment flag)
{
        fAlign = flag;
        Invalidate();
}


alignment
BStringView::Alignment() const
{
        return fAlign;
}


void
BStringView::SetTruncation(uint32 truncationMode)
{
        if (fTruncation != truncationMode) {
                fTruncation = truncationMode;
                Invalidate();
        }
}


uint32
BStringView::Truncation() const
{
        return fTruncation;
}


BHandler*
BStringView::ResolveSpecifier(BMessage* message, int32 index,
        BMessage* specifier, int32 form, const char* property)
{
        BPropertyInfo propInfo(sPropertyList);
        if (propInfo.FindMatch(message, 0, specifier, form, property) >= B_OK)
                return this;

        return BView::ResolveSpecifier(message, index, specifier, form, property);
}


status_t
BStringView::GetSupportedSuites(BMessage* data)
{
        if (data == NULL)
                return B_BAD_VALUE;

        status_t status = data->AddString("suites", "suite/vnd.Be-string-view");
        if (status != B_OK)
                return status;

        BPropertyInfo propertyInfo(sPropertyList);
        status = data->AddFlat("messages", &propertyInfo);
        if (status != B_OK)
                return status;

        return BView::GetSupportedSuites(data);
}


void
BStringView::SetFont(const BFont* font, uint32 mask)
{
        BView::SetFont(font, mask);

        fPreferredSize.width = _StringWidth(fText);

        Invalidate();
        InvalidateLayout();
}


void
BStringView::LayoutInvalidated(bool descendants)
{
        // invalidate cached preferred size
        fPreferredSize.height = -1;
}


// #pragma mark - Perform


status_t
BStringView::Perform(perform_code code, void* _data)
{
        switch (code) {
                case PERFORM_CODE_MIN_SIZE:
                        ((perform_data_min_size*)_data)->return_value
                                = BStringView::MinSize();
                        return B_OK;

                case PERFORM_CODE_MAX_SIZE:
                        ((perform_data_max_size*)_data)->return_value
                                = BStringView::MaxSize();
                        return B_OK;

                case PERFORM_CODE_PREFERRED_SIZE:
                        ((perform_data_preferred_size*)_data)->return_value
                                = BStringView::PreferredSize();
                        return B_OK;

                case PERFORM_CODE_LAYOUT_ALIGNMENT:
                        ((perform_data_layout_alignment*)_data)->return_value
                                = BStringView::LayoutAlignment();
                        return B_OK;

                case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH:
                        ((perform_data_has_height_for_width*)_data)->return_value
                                = BStringView::HasHeightForWidth();
                        return B_OK;

                case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH:
                {
                        perform_data_get_height_for_width* data
                                = (perform_data_get_height_for_width*)_data;
                        BStringView::GetHeightForWidth(data->width, &data->min, &data->max,
                                &data->preferred);
                        return B_OK;
                }

                case PERFORM_CODE_SET_LAYOUT:
                {
                        perform_data_set_layout* data = (perform_data_set_layout*)_data;
                        BStringView::SetLayout(data->layout);
                        return B_OK;
                }

                case PERFORM_CODE_LAYOUT_INVALIDATED:
                {
                        perform_data_layout_invalidated* data
                                = (perform_data_layout_invalidated*)_data;
                        BStringView::LayoutInvalidated(data->descendants);
                        return B_OK;
                }

                case PERFORM_CODE_DO_LAYOUT:
                {
                        BStringView::DoLayout();
                        return B_OK;
                }
        }

        return BView::Perform(code, _data);
}


// #pragma mark - FBC padding methods


void BStringView::_ReservedStringView1() {}
void BStringView::_ReservedStringView2() {}
void BStringView::_ReservedStringView3() {}


// #pragma mark - Private methods


BStringView&
BStringView::operator=(const BStringView&)
{
        // Assignment not allowed (private)
        return *this;
}


BSize
BStringView::_ValidatePreferredSize()
{
        if (fPreferredSize.height < 0) {
                // height
                font_height fontHeight;
                GetFontHeight(&fontHeight);

                int32 lines = 1;
                char* temp = fText ? strchr(fText, '\n') : NULL;
                while (temp != NULL) {
                        temp = strchr(temp + 1, '\n');
                        lines++;
                };

                fPreferredSize.height = ceilf(fontHeight.ascent + fontHeight.descent
                        + fontHeight.leading) * lines;

                ResetLayoutInvalidation();
        }

        return fPreferredSize;
}


float
BStringView::_StringWidth(const char* text)
{
        if(text == NULL)
                return 0.0f;

        float maxWidth = 0.0f;
        BStringList lines;
        BString(fText).Split("\n", false, lines);
        for (int i = 0; i < lines.CountStrings(); i++) {
                float width = StringWidth(lines.StringAt(i));
                if (maxWidth < width)
                        maxWidth = width;
        }
        return maxWidth;
}


extern "C" void
B_IF_GCC_2(InvalidateLayout__11BStringViewb,
        _ZN11BStringView16InvalidateLayoutEb)(BView* view, bool descendants)
{
        perform_data_layout_invalidated data;
        data.descendants = descendants;

        view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data);
}