root/src/apps/diskprobe/TypeEditors.cpp
/*
 * Copyright 2004-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "TypeEditors.h"
#include "DataEditor.h"

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

#include <Alert.h>
#include <Autolock.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <IconUtils.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Mime.h>
#include <PopUpMenu.h>
#include <ScrollBar.h>
#include <ScrollView.h>
#include <Slider.h>
#include <String.h>
#include <StringView.h>
#include <TextControl.h>
#include <TextView.h>
#include <TranslationUtils.h>
#include <TranslatorFormats.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "TypeEditors"

static const uint32 kMsgValueChanged = 'vlch';
static const uint32 kMsgScaleChanged = 'scch';
static const uint32 kMimeTypeItem = 'miti';


class StringEditor : public TypeEditorView {
        public:
                StringEditor(DataEditor& editor);

                virtual void AttachedToWindow();
                virtual void DetachedFromWindow();
                virtual void MessageReceived(BMessage* message);

                virtual void CommitChanges();

        private:
                void _UpdateText();

                BTextView*              fTextView;
                BString                 fPreviousText;
};


class MimeTypeEditor : public TypeEditorView {
        public:
                MimeTypeEditor(DataEditor& editor);

                virtual void AttachedToWindow();
                virtual void DetachedFromWindow();
                virtual void MessageReceived(BMessage* message);

                virtual void CommitChanges();
                virtual bool TypeMatches();

        private:
                void _UpdateText();

                BTextControl*   fTextControl;
                BString                 fPreviousText;
};


class NumberEditor : public TypeEditorView {
        public:
                NumberEditor(DataEditor& editor);

                virtual void AttachedToWindow();
                virtual void DetachedFromWindow();
                virtual void MessageReceived(BMessage* message);

                virtual void CommitChanges();
                virtual bool TypeMatches();

        private:
                void _UpdateText();
                const char* _TypeLabel();
                status_t _Format(char* buffer);
                size_t _Size();

                BTextControl*   fTextControl;
                BString                 fPreviousText;
};


class BooleanEditor : public TypeEditorView {
        public:
                BooleanEditor(DataEditor& editor);

                virtual void AttachedToWindow();
                virtual void DetachedFromWindow();
                virtual void MessageReceived(BMessage* message);

                virtual void CommitChanges();
                virtual bool TypeMatches();

        private:
                void _UpdateMenuField();

                BMenuItem*              fFalseMenuItem;
                BMenuItem*              fTrueMenuItem;
};


class ImageView : public TypeEditorView {
        public:
                ImageView(DataEditor &editor);
                virtual ~ImageView();

                virtual void AttachedToWindow();
                virtual void DetachedFromWindow();
                virtual void MessageReceived(BMessage *message);
                virtual void Draw(BRect updateRect);

        private:
                void _UpdateImage();

                BBitmap*                fBitmap;
                BStringView*    fDescriptionView;
                BSlider*                fScaleSlider;
};


class MessageView : public TypeEditorView {
        public:
                MessageView(DataEditor& editor);
                virtual ~MessageView();

                virtual void AttachedToWindow();
                virtual void DetachedFromWindow();
                virtual void MessageReceived(BMessage* message);

                void SetTo(BMessage& message);

        private:
                BString _TypeToString(type_code type);
                void _UpdateMessage();

                BTextView*              fTextView;
};


//      #pragma mark - TypeEditorView


TypeEditorView::TypeEditorView(const char *name, uint32 flags,
                DataEditor& editor)
        : BView(name, flags),
        fEditor(editor)
{
}


TypeEditorView::~TypeEditorView()
{
}


void
TypeEditorView::CommitChanges()
{
        // the default just does nothing here
}


bool
TypeEditorView::TypeMatches()
{
        // the default is to accept anything that easily fits into memory

        system_info info;
        get_system_info(&info);

        return uint64(fEditor.FileSize()) / B_PAGE_SIZE < info.max_pages / 2;
}


//      #pragma mark - StringEditor


StringEditor::StringEditor(DataEditor& editor)
        : TypeEditorView(B_TRANSLATE("String editor"), 0, editor)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

        BStringView *stringView = new BStringView(B_EMPTY_STRING,
                B_TRANSLATE("Contents:"));

        fTextView = new BTextView(B_EMPTY_STRING, B_WILL_DRAW);
        BScrollView* scrollView = new BScrollView("scroller", fTextView, 0, true, true);

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .SetInsets(0, B_USE_WINDOW_INSETS)
                .Add(stringView)
                .Add(scrollView);
}


void
StringEditor::_UpdateText()
{
        BAutolock locker(fEditor);

        size_t viewSize = fEditor.ViewSize();
        // that may need some more memory...
        if ((off_t)viewSize < fEditor.FileSize())
                fEditor.SetViewSize(fEditor.FileSize());

        const char *buffer;
        if (fEditor.GetViewBuffer((const uint8 **)&buffer) == B_OK) {
                fTextView->SetText(buffer);
                fPreviousText.SetTo(buffer);
        }

        // restore old view size
        fEditor.SetViewSize(viewSize);
}


void
StringEditor::CommitChanges()
{
        if (fPreviousText != fTextView->Text()) {
                fEditor.Replace(0, (const uint8 *)fTextView->Text(),
                        fTextView->TextLength() + 1);
        }
}


void
StringEditor::AttachedToWindow()
{
        fEditor.StartWatching(this);

        _UpdateText();
}


void
StringEditor::DetachedFromWindow()
{
        fEditor.StopWatching(this);

        CommitChanges();
}


void
StringEditor::MessageReceived(BMessage *message)
{
        BView::MessageReceived(message);
}


//      #pragma mark - MimeTypeEditor


MimeTypeEditor::MimeTypeEditor(DataEditor& editor)
        : TypeEditorView(B_TRANSLATE("MIME type editor"), 0, editor)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetHighUIColor(B_PANEL_TEXT_COLOR);

        fTextControl = new BTextControl(B_EMPTY_STRING, B_TRANSLATE("MIME type:"), NULL,
                new BMessage(kMsgValueChanged));

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .SetInsets(0, B_USE_WINDOW_INSETS)
                .Add(fTextControl);
}


void
MimeTypeEditor::_UpdateText()
{
        BAutolock locker(fEditor);

        const char* mimeType;
        if (fEditor.GetViewBuffer((const uint8 **)&mimeType) == B_OK) {
                fTextControl->SetText(mimeType);
                fPreviousText.SetTo(mimeType);
        }
}


void
MimeTypeEditor::CommitChanges()
{
        if (fPreviousText != fTextControl->Text()) {
                fEditor.Replace(0, (const uint8*)fTextControl->Text(),
                        strlen(fTextControl->Text()) + 1);
        }
}


bool
MimeTypeEditor::TypeMatches()
{
        // TODO: check contents?
        return fEditor.FileSize() <= B_MIME_TYPE_LENGTH;
}


void
MimeTypeEditor::AttachedToWindow()
{
        fTextControl->SetTarget(this);
        fEditor.StartWatching(this);

        _UpdateText();
}


void
MimeTypeEditor::DetachedFromWindow()
{
        fEditor.StopWatching(this);

        CommitChanges();
}


void
MimeTypeEditor::MessageReceived(BMessage *message)
{
        switch (message->what) {
                case kMsgValueChanged:
                        fEditor.Replace(0, (const uint8 *)fTextControl->Text(),
                                strlen(fTextControl->Text()) + 1);
                        break;

                case kMsgDataEditorUpdate:
                        _UpdateText();
                        break;

                default:
                        BView::MessageReceived(message);
        }
}


//      #pragma mark - NumberEditor


NumberEditor::NumberEditor(DataEditor &editor)
        : TypeEditorView(B_TRANSLATE("Number editor"), 0, editor)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetHighUIColor(B_PANEL_TEXT_COLOR);

        fTextControl = new BTextControl(B_EMPTY_STRING, _TypeLabel(), NULL,
                new BMessage(kMsgValueChanged));
        fTextControl->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_RIGHT);

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .SetInsets(0, B_USE_WINDOW_INSETS)
                .Add(fTextControl);
}


void
NumberEditor::_UpdateText()
{
        if (fEditor.Lock()) {
                const char* number;
                if (fEditor.GetViewBuffer((const uint8**)&number) == B_OK) {
                        char buffer[64];
                        char format[16];
                        switch (fEditor.Type()) {
                                case B_FLOAT_TYPE:
                                {
                                        float value = *(float*)number;
                                        snprintf(buffer, sizeof(buffer), "%g", value);
                                        break;
                                }
                                case B_DOUBLE_TYPE:
                                {
                                        double value = *(double *)number;
                                        snprintf(buffer, sizeof(buffer), "%g", value);
                                        break;
                                }
                                case B_SSIZE_T_TYPE:
                                case B_INT8_TYPE:
                                case B_INT16_TYPE:
                                case B_INT32_TYPE:
                                case B_INT64_TYPE:
                                case B_OFF_T_TYPE:
                                {
                                        _Format(format);
                                        switch (_Size()) {
                                                case 1:
                                                {
                                                        int8 value = *(int8 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                                case 2:
                                                {
                                                        int16 value = *(int16 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                                case 4:
                                                {
                                                        int32 value = *(int32 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                                case 8:
                                                {
                                                        int64 value = *(int64 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                        }
                                        break;
                                }

                                default:
                                {
                                        _Format(format);
                                        switch (_Size()) {
                                                case 1:
                                                {
                                                        uint8 value = *(uint8 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                                case 2:
                                                {
                                                        uint16 value = *(uint16 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                                case 4:
                                                {
                                                        uint32 value = *(uint32 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                                case 8:
                                                {
                                                        uint64 value = *(uint64 *)number;
                                                        snprintf(buffer, sizeof(buffer), format, value);
                                                        break;
                                                }
                                        }
                                        break;
                                }
                        }
                        fTextControl->SetText(buffer);
                        fPreviousText.SetTo(buffer);
                }

                fEditor.Unlock();
        }
}


bool
NumberEditor::TypeMatches()
{
        return fEditor.FileSize() >= (off_t)_Size();
                // we only look at as many bytes we need to
}


void
NumberEditor::CommitChanges()
{
        if (fPreviousText == fTextControl->Text())
                return;

        const char *number = fTextControl->Text();
        uint8 buffer[8];

        switch (fEditor.Type()) {
                case B_FLOAT_TYPE:
                {
                        float value = strtod(number, NULL);
                        *(float *)buffer = value;
                        break;
                }
                case B_DOUBLE_TYPE:
                {
                        double value = strtod(number, NULL);
                        *(double *)buffer = value;
                        break;
                }
                case B_INT8_TYPE:
                {
                        int64 value = strtoll(number, NULL, 0);
                        if (value > CHAR_MAX)
                                value = CHAR_MAX;
                        else if (value < CHAR_MIN)
                                value = CHAR_MIN;
                        *(int8 *)buffer = (int8)value;
                        break;
                }
                case B_UINT8_TYPE:
                {
                        int64 value = strtoull(number, NULL, 0);
                        if (value > UCHAR_MAX)
                                value = UCHAR_MAX;
                        *(uint8 *)buffer = (uint8)value;
                        break;
                }
                case B_INT16_TYPE:
                {
                        int64 value = strtoll(number, NULL, 0);
                        if (value > SHRT_MAX)
                                value = SHRT_MAX;
                        else if (value < SHRT_MIN)
                                value = SHRT_MIN;
                        *(int16 *)buffer = (int16)value;
                        break;
                }
                case B_UINT16_TYPE:
                {
                        int64 value = strtoull(number, NULL, 0);
                        if (value > USHRT_MAX)
                                value = USHRT_MAX;
                        *(uint16 *)buffer = (uint16)value;
                        break;
                }
                case B_INT32_TYPE:
                case B_SSIZE_T_TYPE:
                {
                        int64 value = strtoll(number, NULL, 0);
                        if (value > LONG_MAX)
                                value = LONG_MAX;
                        else if (value < LONG_MIN)
                                value = LONG_MIN;
                        *(int32 *)buffer = (int32)value;
                        break;
                }
                case B_UINT32_TYPE:
                case B_SIZE_T_TYPE:
                case B_POINTER_TYPE:
                {
                        uint64 value = strtoull(number, NULL, 0);
                        if (value > ULONG_MAX)
                                value = ULONG_MAX;
                        *(uint32 *)buffer = (uint32)value;
                        break;
                }
                case B_INT64_TYPE:
                case B_OFF_T_TYPE:
                {
                        int64 value = strtoll(number, NULL, 0);
                        *(int64 *)buffer = value;
                        break;
                }
                case B_UINT64_TYPE:
                {
                        uint64 value = strtoull(number, NULL, 0);
                        *(uint64 *)buffer = value;
                        break;
                }
                default:
                        return;
        }

        fEditor.Replace(0, buffer, _Size());
        fPreviousText.SetTo((char *)buffer);
}


const char*
NumberEditor::_TypeLabel()
{
        switch (fEditor.Type()) {
                case B_INT8_TYPE:
                        return B_TRANSLATE("8 bit signed value:");
                case B_UINT8_TYPE:
                        return B_TRANSLATE("8 bit unsigned value:");
                case B_INT16_TYPE:
                        return B_TRANSLATE("16 bit signed value:");
                case B_UINT16_TYPE:
                        return B_TRANSLATE("16 bit unsigned value:");
                case B_INT32_TYPE:
                        return B_TRANSLATE("32 bit signed value:");
                case B_UINT32_TYPE:
                        return B_TRANSLATE("32 bit unsigned value:");
                case B_INT64_TYPE:
                        return B_TRANSLATE("64 bit signed value:");
                case B_UINT64_TYPE:
                        return B_TRANSLATE("64 bit unsigned value:");
                case B_FLOAT_TYPE:
                        return B_TRANSLATE("Floating-point value:");
                case B_DOUBLE_TYPE:
                        return B_TRANSLATE("Double precision floating-point value:");
                case B_SSIZE_T_TYPE:
                        return B_TRANSLATE("32 bit size or status:");
                case B_SIZE_T_TYPE:
                        return B_TRANSLATE("32 bit unsigned size:");
                case B_OFF_T_TYPE:
                        return B_TRANSLATE("64 bit signed offset:");
                case B_POINTER_TYPE:
                        return B_TRANSLATE("32 bit unsigned pointer:");
                default:
                        return B_TRANSLATE("Number:");
        }
}


size_t
NumberEditor::_Size()
{
        switch (fEditor.Type()) {
                case B_INT8_TYPE:
                case B_UINT8_TYPE:
                        return 1;
                case B_INT16_TYPE:
                case B_UINT16_TYPE:
                        return 2;
                case B_SSIZE_T_TYPE:
                case B_INT32_TYPE:
                case B_SIZE_T_TYPE:
                case B_POINTER_TYPE:
                case B_UINT32_TYPE:
                        return 4;
                case B_INT64_TYPE:
                case B_OFF_T_TYPE:
                case B_UINT64_TYPE:
                        return 8;
                case B_FLOAT_TYPE:
                        return 4;
                case B_DOUBLE_TYPE:
                        return 8;

                default:
                        return 0;
        }
}


status_t
NumberEditor::_Format(char *buffer)
{
        switch (fEditor.Type()) {
                case B_INT8_TYPE:
                        strcpy(buffer, "%hd");
                        return B_OK;
                case B_UINT8_TYPE:
                        strcpy(buffer, "%hu");
                        return B_OK;
                case B_INT16_TYPE:
                        strcpy(buffer, "%hd");
                        return B_OK;
                case B_UINT16_TYPE:
                        strcpy(buffer, "%hu");
                        return B_OK;
                case B_SSIZE_T_TYPE:
                case B_INT32_TYPE:
                        strcpy(buffer, "%ld");
                        return B_OK;
                case B_SIZE_T_TYPE:
                case B_POINTER_TYPE:
                case B_UINT32_TYPE:
                        strcpy(buffer, "%lu");
                        return B_OK;
                case B_INT64_TYPE:
                case B_OFF_T_TYPE:
                        strcpy(buffer, "%lld");
                        return B_OK;
                case B_UINT64_TYPE:
                        strcpy(buffer, "%Lu");
                        return B_OK;
                case B_FLOAT_TYPE:
                        strcpy(buffer, "%g");
                        return B_OK;
                case B_DOUBLE_TYPE:
                        strcpy(buffer, "%lg");
                        return B_OK;

                default:
                        return B_ERROR;
        }
}


void
NumberEditor::AttachedToWindow()
{
        fTextControl->SetTarget(this);
        fEditor.StartWatching(this);

        _UpdateText();
}


void
NumberEditor::DetachedFromWindow()
{
        fEditor.StopWatching(this);

        CommitChanges();
}


void
NumberEditor::MessageReceived(BMessage *message)
{
        switch (message->what) {
                case kMsgValueChanged:
                        CommitChanges();
                        break;
                case kMsgDataEditorUpdate:
                        _UpdateText();
                        break;

                default:
                        BView::MessageReceived(message);
        }
}


//      #pragma mark - BooleanEditor


BooleanEditor::BooleanEditor(DataEditor &editor)
        : TypeEditorView(B_TRANSLATE("Boolean editor"), 0, editor)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetHighUIColor(B_PANEL_TEXT_COLOR);

        BPopUpMenu *menu = new BPopUpMenu("bool");
        BMessage *message;
        menu->AddItem(fFalseMenuItem = new BMenuItem("false",
                new BMessage(kMsgValueChanged)));
        menu->AddItem(fTrueMenuItem = new BMenuItem("true",
                message = new BMessage(kMsgValueChanged)));
        message->AddInt8("value", 1);

        BMenuField *menuField = new BMenuField(B_EMPTY_STRING, B_TRANSLATE("Boolean value:"), menu);

        _UpdateMenuField();

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .SetInsets(0, B_USE_WINDOW_INSETS)
                .Add(menuField);
}


bool
BooleanEditor::TypeMatches()
{
        // we accept everything: we just look at the first byte, anyway
        return true;
}


void
BooleanEditor::_UpdateMenuField()
{
        if (fEditor.FileSize() != 1)
                return;

        if (fEditor.Lock()) {
                const char *buffer;
                if (fEditor.GetViewBuffer((const uint8 **)&buffer) == B_OK)
                        (buffer[0] != 0 ? fTrueMenuItem : fFalseMenuItem)->SetMarked(true);

                fEditor.Unlock();
        }
}


void
BooleanEditor::CommitChanges()
{
        // we're commiting the changes as they happen
}


void
BooleanEditor::AttachedToWindow()
{
        fTrueMenuItem->SetTarget(this);
        fFalseMenuItem->SetTarget(this);

        fEditor.StartWatching(this);
}


void
BooleanEditor::DetachedFromWindow()
{
        fEditor.StopWatching(this);
}


void
BooleanEditor::MessageReceived(BMessage *message)
{
        switch (message->what) {
                case kMsgValueChanged:
                {
                        uint8 boolean = message->FindInt8("value");
                        fEditor.Replace(0, (const uint8 *)&boolean, 1);
                        break;
                }
                case kMsgDataEditorUpdate:
                        _UpdateMenuField();
                        break;

                default:
                        BView::MessageReceived(message);
        }
}


//      #pragma mark - ImageView


ImageView::ImageView(DataEditor &editor)
        : TypeEditorView(B_TRANSLATE_COMMENT("Image view", "Image means here a "
                "picture file, not a disk image."),
                B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE, editor),
        fBitmap(NULL),
        fScaleSlider(NULL)
{
        if (editor.Type() == B_MINI_ICON_TYPE
                || editor.Type() == B_LARGE_ICON_TYPE
                || editor.Type() == B_VECTOR_ICON_TYPE) {
                SetName(B_TRANSLATE("Icon view"));
        }


        fDescriptionView = new BStringView("",
                B_TRANSLATE_COMMENT("Could not read image", "Image means "
                "here a picture file, not a disk image."));
        fDescriptionView->SetAlignment(B_ALIGN_CENTER);

        if (editor.Type() == B_VECTOR_ICON_TYPE) {
                // vector icon
                fScaleSlider = new BSlider("", NULL,
                        new BMessage(kMsgScaleChanged), 2, 32, B_HORIZONTAL);
                fScaleSlider->SetModificationMessage(new BMessage(kMsgScaleChanged));
                fScaleSlider->ResizeToPreferred();
                fScaleSlider->SetValue(8);
                fScaleSlider->SetHashMarks(B_HASH_MARKS_BOTH);
                fScaleSlider->SetHashMarkCount(15);

                BLayoutBuilder::Group<>(this, B_HORIZONTAL)
                        .SetInsets(B_USE_WINDOW_SPACING, 256, B_USE_WINDOW_SPACING,
                                B_H_SCROLL_BAR_HEIGHT) // Leave space for the icon
                        .AddGlue()
                        .AddGroup(B_VERTICAL)
                                .Add(fDescriptionView)
                                .Add(fScaleSlider)
                        .End()
                        .AddGlue();
        } else {
                BLayoutBuilder::Group<>(this, B_HORIZONTAL)
                        .SetInsets(B_USE_WINDOW_SPACING, 256, B_USE_WINDOW_SPACING,
                                B_USE_WINDOW_SPACING) // Leave space for the icon
                        .AddGlue()
                        .Add(fDescriptionView)
                        .AddGlue();
        }
}


ImageView::~ImageView()
{
        delete fBitmap;
}


void
ImageView::AttachedToWindow()
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);

        fEditor.StartWatching(this);
        if (fScaleSlider != NULL)
                fScaleSlider->SetTarget(this);

        _UpdateImage();
}


void
ImageView::DetachedFromWindow()
{
        fEditor.StopWatching(this);
}


void
ImageView::MessageReceived(BMessage *message)
{
        switch (message->what) {
                case kMsgDataEditorUpdate:
                        _UpdateImage();
                        break;

                case kMsgScaleChanged:
                        _UpdateImage();
                        break;

                default:
                        BView::MessageReceived(message);
        }
}


void
ImageView::Draw(BRect updateRect)
{
        if (fBitmap != NULL) {
                SetDrawingMode(B_OP_ALPHA);
                DrawBitmap(fBitmap, BPoint((Bounds().Width() - fBitmap->Bounds().Width()) / 2,
                        (Bounds().Height() - fBitmap->Bounds().Height() - 60) / 2));
                SetDrawingMode(B_OP_COPY);
        }
}


void
ImageView::_UpdateImage()
{
        // ToDo: add scroller if necessary?!

        BAutolock locker(fEditor);

        // we need all the data...

        size_t viewSize = fEditor.ViewSize();
        // that may need some more memory...
        if ((off_t)viewSize < fEditor.FileSize())
                fEditor.SetViewSize(fEditor.FileSize());

        const char *data;
        if (fEditor.GetViewBuffer((const uint8 **)&data) != B_OK) {
                fEditor.SetViewSize(viewSize);
                return;
        }

        if (fBitmap != NULL && (fEditor.Type() == B_MINI_ICON_TYPE
                        || fEditor.Type() == B_LARGE_ICON_TYPE)) {
                // optimize icon update...
                fBitmap->SetBits(data, fEditor.FileSize(), 0, B_CMAP8);
                fEditor.SetViewSize(viewSize);
                return;
        }
        if (fBitmap != NULL && fEditor.Type() == B_VECTOR_ICON_TYPE
                && fScaleSlider->Value() * 8 - 1 == fBitmap->Bounds().Width()) {
                if (BIconUtils::GetVectorIcon((const uint8 *)data,
                                (size_t)fEditor.FileSize(), fBitmap) == B_OK) {
                        fEditor.SetViewSize(viewSize);
                        return;
                }
        }

        delete fBitmap;
        fBitmap = NULL;

        switch (fEditor.Type()) {
                case B_MINI_ICON_TYPE:
                        fBitmap = new BBitmap(BRect(0, 0, 15, 15), B_CMAP8);
                        if (fBitmap->InitCheck() == B_OK)
                                fBitmap->SetBits(data, fEditor.FileSize(), 0, B_CMAP8);
                        break;
                case B_LARGE_ICON_TYPE:
                        fBitmap = new BBitmap(BRect(0, 0, 31, 31), B_CMAP8);
                        if (fBitmap->InitCheck() == B_OK)
                                fBitmap->SetBits(data, fEditor.FileSize(), 0, B_CMAP8);
                        break;
                case B_VECTOR_ICON_TYPE:
                        fBitmap = new BBitmap(BRect(0, 0, fScaleSlider->Value() * 8 - 1,
                                fScaleSlider->Value() * 8 - 1), B_RGB32);
                        if (fBitmap->InitCheck() != B_OK
                                || BIconUtils::GetVectorIcon((const uint8 *)data,
                                        (size_t)fEditor.FileSize(), fBitmap) != B_OK) {
                                delete fBitmap;
                                fBitmap = NULL;
                        }
                        break;
                case B_PNG_FORMAT:
                {
                        BMemoryIO stream(data, fEditor.FileSize());
                        fBitmap = BTranslationUtils::GetBitmap(&stream);
                        break;
                }
                case B_MESSAGE_TYPE:
                {
                        BMessage message;
                        // ToDo: this could be problematic if the data is not large
                        //              enough to contain the message...
                        if (message.Unflatten(data) == B_OK)
                                fBitmap = new BBitmap(&message);
                        break;
                }
        }

        // Update the bitmap description. If no image can be displayed,
        // we will show our "unsupported" message

        if (fBitmap != NULL && fBitmap->InitCheck() != B_OK) {
                delete fBitmap;
                fBitmap = NULL;
        }

        if (fBitmap != NULL) {
                char buffer[256];
                const char *type = B_TRANSLATE("Unknown type");
                switch (fEditor.Type()) {
                        case B_MINI_ICON_TYPE:
                        case B_LARGE_ICON_TYPE:
                        case B_VECTOR_ICON_TYPE:
                                type = B_TRANSLATE("Icon");
                                break;
                        case B_PNG_FORMAT:
                                type = B_TRANSLATE("PNG format");
                                break;
                        case B_MESSAGE_TYPE:
                                type = B_TRANSLATE("Flattened bitmap");
                                break;
                        default:
                                break;
                }
                const char *colorSpace;
                switch (fBitmap->ColorSpace()) {
                        case B_GRAY1:
                        case B_GRAY8:
                                colorSpace = B_TRANSLATE("Grayscale");
                                break;
                        case B_CMAP8:
                                colorSpace = B_TRANSLATE("8 bit palette");
                                break;
                        case B_RGB32:
                        case B_RGBA32:
                        case B_RGB32_BIG:
                        case B_RGBA32_BIG:
                                colorSpace = B_TRANSLATE("32 bit");
                                break;
                        case B_RGB15:
                        case B_RGBA15:
                        case B_RGB15_BIG:
                        case B_RGBA15_BIG:
                                colorSpace = B_TRANSLATE("15 bit");
                                break;
                        case B_RGB16:
                        case B_RGB16_BIG:
                                colorSpace = B_TRANSLATE("16 bit");
                                break;
                        default:
                                colorSpace = B_TRANSLATE("Unknown format");
                                break;
                }
                snprintf(buffer, sizeof(buffer), B_TRANSLATE_COMMENT("%s, %g × %g, %s",
                        "The '×' is the Unicode multiplication sign U+00D7"), type,
                        fBitmap->Bounds().Width() + 1, fBitmap->Bounds().Height() + 1,
                        colorSpace);
                fDescriptionView->SetText(buffer);
        } else
                fDescriptionView->SetText(B_TRANSLATE_COMMENT("Could not read image",
                        "Image means here a picture file, not a disk image."));

        if (fBitmap != NULL) {
                if (fScaleSlider != NULL) {
                        if (fScaleSlider->IsHidden())
                                fScaleSlider->Show();
                }
        } else if (fScaleSlider != NULL && !fScaleSlider->IsHidden())
                fScaleSlider->Hide();

        Invalidate();

        // restore old view size
        fEditor.SetViewSize(viewSize);
}


//      #pragma mark - MessageView


MessageView::MessageView(DataEditor &editor)
        : TypeEditorView(B_TRANSLATE("Message View"), 0, editor)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        SetHighUIColor(B_PANEL_TEXT_COLOR);

        fTextView = new BTextView(B_EMPTY_STRING, B_WILL_DRAW);
        fTextView->SetViewUIColor(ViewUIColor());
        fTextView->SetLowUIColor(ViewUIColor());

        BScrollView *scrollView = new BScrollView("scroller", fTextView,
                B_FOLLOW_ALL, B_WILL_DRAW, true, true);

        BLayoutBuilder::Group<>(this, B_VERTICAL)
                .SetInsets(0, B_USE_WINDOW_INSETS)
                .Add(scrollView);
}


MessageView::~MessageView()
{
}


BString
MessageView::_TypeToString(type_code type)
{
        // TODO: move this to a utility function (it's a copy from something
        // similar in ProbeView.cpp)
        char text[32];
        for (int32 i = 0; i < 4; i++) {
                text[i] = type >> (24 - 8 * i);
                if (text[i] < ' ' || text[i] == 0x7f) {
                        snprintf(text, sizeof(text), "0x%04" B_PRIx32, type);
                        break;
                } else if (i == 3)
                        text[4] = '\0';
        }

        return BString(text);
}


void
MessageView::SetTo(BMessage& message)
{
        // TODO: when we have a real column list/tree view, redo this using
        // it with nice editors as well.

        fTextView->SetText("");

        char text[512];
        snprintf(text, sizeof(text), B_TRANSLATE_COMMENT("what: '%.4s'\n\n",
                "'What' is a message specifier that defines the type of the message."),
                (char*)&message.what);
        fTextView->Insert(text);

        type_code type;
        int32 count;
        char* name;
        for (int32 i = 0; message.GetInfo(B_ANY_TYPE, i, &name, &type, &count)
                        == B_OK; i++) {
                snprintf(text, sizeof(text), "%s\t", _TypeToString(type).String());
                fTextView->Insert(text);

                text_run_array array;
                array.count = 1;
                array.runs[0].offset = 0;
                array.runs[0].font.SetFace(B_BOLD_FACE);
                array.runs[0].color = (rgb_color){0, 0, 0, 255};

                fTextView->Insert(name, &array);

                array.runs[0].font = be_plain_font;
                fTextView->Insert("\n", &array);

                for (int32 j = 0; j < count; j++) {
                        const char* data;
                        ssize_t size;
                        if (message.FindData(name, type, j, (const void**)&data, &size)
                                        != B_OK)
                                continue;

                        text[0] = '\0';

                        switch (type) {
                                case B_INT64_TYPE:
                                        snprintf(text, sizeof(text), "%" B_PRId64, *(int64*)data);
                                        break;
                                case B_UINT64_TYPE:
                                        snprintf(text, sizeof(text), "%" B_PRIu64, *(uint64*)data);
                                        break;
                                case B_INT32_TYPE:
                                        snprintf(text, sizeof(text), "%" B_PRId32, *(int32*)data);
                                        break;
                                case B_UINT32_TYPE:
                                        snprintf(text, sizeof(text), "%" B_PRIu32, *(uint32*)data);
                                        break;
                                case B_BOOL_TYPE:
                                        if (*(uint8*)data)
                                                strcpy(text, "true");
                                        else
                                                strcpy(text, "false");
                                        break;
                                case B_STRING_TYPE:
                                case B_MIME_STRING_TYPE:
                                {
                                        snprintf(text, sizeof(text), "%s", data);
                                        break;
                                }
                        }

                        if (text[0]) {
                                fTextView->Insert("\t\t");
                                if (count > 1) {
                                        char index[16];
                                        snprintf(index, sizeof(index), "%" B_PRId32 ".\t", j);
                                        fTextView->Insert(index);
                                }
                                fTextView->Insert(text);
                                fTextView->Insert("\n");
                        }
                }
        }
}


void
MessageView::_UpdateMessage()
{
        BAutolock locker(fEditor);

        size_t viewSize = fEditor.ViewSize();
        // that may need some more memory...
        if ((off_t)viewSize < fEditor.FileSize())
                fEditor.SetViewSize(fEditor.FileSize());

        const char *buffer;
        if (fEditor.GetViewBuffer((const uint8 **)&buffer) == B_OK) {
                BMessage message;
                message.Unflatten(buffer);
                SetTo(message);
        }

        // restore old view size
        fEditor.SetViewSize(viewSize);
}


void
MessageView::AttachedToWindow()
{
        fEditor.StartWatching(this);

        _UpdateMessage();
}


void
MessageView::DetachedFromWindow()
{
        fEditor.StopWatching(this);
}


void
MessageView::MessageReceived(BMessage* message)
{
        BView::MessageReceived(message);
}


//      #pragma mark -


TypeEditorView*
GetTypeEditorFor(DataEditor& editor)
{
        switch (editor.Type()) {
                case B_STRING_TYPE:
                        return new StringEditor(editor);
                case B_MIME_STRING_TYPE:
                        return new MimeTypeEditor(editor);
                case B_BOOL_TYPE:
                        return new BooleanEditor(editor);
                case B_INT8_TYPE:
                case B_UINT8_TYPE:
                case B_INT16_TYPE:
                case B_UINT16_TYPE:
                case B_INT32_TYPE:
                case B_UINT32_TYPE:
                case B_INT64_TYPE:
                case B_UINT64_TYPE:
                case B_FLOAT_TYPE:
                case B_DOUBLE_TYPE:
                case B_SSIZE_T_TYPE:
                case B_SIZE_T_TYPE:
                case B_OFF_T_TYPE:
                case B_POINTER_TYPE:
                        return new NumberEditor(editor);
                case B_MESSAGE_TYPE:
                        // TODO: check for archived bitmaps!!!
                        return new MessageView(editor);
                case B_MINI_ICON_TYPE:
                case B_LARGE_ICON_TYPE:
                case B_PNG_FORMAT:
                case B_VECTOR_ICON_TYPE:
                        return new ImageView(editor);
        }

        return NULL;
}


status_t
GetNthTypeEditor(int32 index, const char** _name)
{
        static const char* kEditors[] = {
                B_TRANSLATE_COMMENT("Text", "This is the type of editor"),
                B_TRANSLATE_COMMENT("Number", "This is the type of editor"),
                B_TRANSLATE_COMMENT("Boolean", "This is the type of editor"),
                B_TRANSLATE_COMMENT("Message", "This is the type of view"),
                B_TRANSLATE_COMMENT("Image", "This is the type of view")
        };

        if (index < 0 || index >= int32(sizeof(kEditors) / sizeof(kEditors[0])))
                return B_BAD_VALUE;

        *_name = kEditors[index];
        return B_OK;
}


TypeEditorView*
GetTypeEditorAt(int32 index, DataEditor& editor)
{
        TypeEditorView* view = NULL;

        switch (index) {
                case 0:
                        view = new StringEditor(editor);
                        break;
                case 1:
                        view = new NumberEditor(editor);
                        break;
                case 2:
                        view = new BooleanEditor(editor);
                        break;
                case 3:
                        view = new MessageView(editor);
                        break;
                case 4:
                        view = new ImageView(editor);
                        break;

                default:
                        return NULL;
        }

        if (view == NULL)
                return NULL;

        if (!view->TypeMatches()) {
                delete view;
                return NULL;
        }

        return view;
}