root/src/kits/tracker/infowindow/AttributesView.cpp
/*
 * Copyright (C) 2019-2020 Adrien Destugues <pulkomandy@pulkomandy.tk>
 *
 * Distributed under terms of the MIT license.
 */

#include "AttributesView.h"

#include <Catalog.h>
#include <ColumnListView.h>
#include <ColumnTypes.h>
#include <DateTimeFormat.h>
#include <FormattingConventions.h>
#include <fs_attr.h>
#include <Node.h>
#include <StringFormat.h>


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "AttributesView"

int kValueColumn = 1;
int kTypeColumn = 2;


AttributesView::AttributesView(Model* model)
        :
        BGroupView(B_VERTICAL, 0),
        fListView(new BColumnListView("attrs", 0, B_PLAIN_BORDER, false))
{
        SetName(B_TRANSLATE("Attributes"));
        AddChild(fListView);
        GroupLayout()->SetInsets(-1, -1, -1, -1);

        float nameWidth = StringWidth("SYS:PACKAGE_FILE") + 16;
        float typeMaxWidth = StringWidth(B_TRANSLATE(
                "Double-precision floating point number")) + 16;
        float typeWidth = StringWidth(B_TRANSLATE("64-bit unsigned integer")) + 16;
        float valueMaxWidth = StringWidth("W") * 64 + 16;
        float valueWidth = StringWidth("(94.00, 95.00) (1920, 1080)") + 16;
        BStringColumn* nameColumn = new BStringColumn(B_TRANSLATE("Name"),
                nameWidth, nameWidth, nameWidth, 0);
        BStringColumn* typeColumn = new BStringColumn(B_TRANSLATE("Type"),
                typeWidth, typeWidth, typeMaxWidth, 0);
        BStringColumn* valueColumn = new BStringColumn(B_TRANSLATE("Value"),
                valueWidth, valueWidth, valueMaxWidth, 0);

        fListView->AddColumn(nameColumn, 0);
        fListView->AddColumn(valueColumn, 1);
        fListView->AddColumn(typeColumn, 2);

        SetExplicitMinSize(BSize(typeWidth + valueWidth + nameWidth + 40,
                B_SIZE_UNSET));

        BNode* node = model->Node();

        node->RewindAttrs();
        char name[B_ATTR_NAME_LENGTH];

        // Initialize formatters only once for all attributes
        BDateTimeFormat dateTimeFormatter;
        BStringFormat multiValueFormat(B_TRANSLATE(
                "{0, plural, other{<# values>}}"));

        while (node->GetNextAttrName(name) == B_OK) {
                // Skip well-known attributes already shown elsewhere in the window
                if (strcmp(name, "BEOS:TYPE") == 0)
                        continue;

                attr_info info;
                node->GetAttrInfo(name, &info);
                BRow* row = new BRow;
                row->SetField(new BStringField(name), 0);

                BString representation;
                switch(info.type) {
                        case B_STRING_TYPE:
                        case B_MIME_STRING_TYPE:
                        case 'MSIG':
                        case 'MSDC':
                        case 'MPTH':
                        {
                                // Use a small buffer, long strings will be truncated
                                char buffer[64];
                                ssize_t size = node->ReadAttr(name, info.type, 0, buffer,
                                        sizeof(buffer));
                                if (size > 0)
                                        representation.SetTo(buffer, size);
                                break;
                        }
                        case B_BOOL_TYPE:
                        {
                                if (info.size == sizeof(bool)) {
                                        bool value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation = value ? B_TRANSLATE("yes")
                                                : B_TRANSLATE("no");
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(bool));
                                }
                                break;
                        }
                        case B_INT16_TYPE:
                        {
                                if (info.size == sizeof(int16)) {
                                        int16 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRId16, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(int16));
                                }
                                break;
                        }
                        case B_INT32_TYPE:
                        {
                                if (info.size == sizeof(int32)) {
                                        int32 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRId32, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(int32));
                                }
                                break;
                        }
                        case B_INT64_TYPE:
                        {
                                if (info.size == sizeof(int64)) {
                                        int64 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRId64, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(int64));
                                }
                                break;
                        }
                        case B_INT8_TYPE:
                        {
                                if (info.size == sizeof(int8)) {
                                        int8 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRId8, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(int8));
                                }
                                break;
                        }
                        case B_RECT_TYPE:
                        {
                                if (info.size == sizeof(BRect)) {
                                        BRect value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("(%g,%g) (%g,%g)", value.left,
                                                value.top, value.right, value.bottom);
                                } else {
                                        BStringFormat multiRectFormat(B_TRANSLATE(
                                                "{0, plural, other{<# rectangles>}}"));
                                        multiRectFormat.Format(representation,
                                                info.size / sizeof(BRect));
                                }
                                break;
                        }
                        case B_DOUBLE_TYPE:
                        {
                                if (info.size == sizeof(double)) {
                                        double value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%f", value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(double));
                                }
                                break;
                        }
                        case B_FLOAT_TYPE:
                        {
                                if (info.size == sizeof(float)) {
                                        float value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%f", value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(float));
                                }
                                break;
                        }
                        case B_TIME_TYPE:
                        {
                                // Try to handle attributes written on both 32 and 64bit systems
                                if (info.size == sizeof(int32)) {
                                        int32 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        dateTimeFormatter.Format(representation, value,
                                                B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT);
                                } else if (info.size == sizeof(int64)) {
                                        int64 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        dateTimeFormatter.Format(representation, value,
                                                B_SHORT_DATE_FORMAT, B_SHORT_TIME_FORMAT);
                                } else {
                                        BStringFormat multiDateFormat(B_TRANSLATE(
                                                "{0, plural, other{<# dates>}}"));
                                        multiDateFormat.Format(representation,
                                                info.size / sizeof(time_t));
                                }
                                break;
                        }
                        case B_UINT16_TYPE:
                        {
                                if (info.size == sizeof(uint16)) {
                                        uint16 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRIu16, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(uint16));
                                }
                                break;
                        }
                        case B_UINT32_TYPE:
                        {
                                if (info.size == sizeof(uint32)) {
                                        uint32 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRIu32, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(uint32));
                                }
                                break;
                        }
                        case B_UINT64_TYPE:
                        {
                                if (info.size == sizeof(uint64)) {
                                        uint64 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRIu64, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(uint64));
                                }
                                break;
                        }
                        case B_UINT8_TYPE:
                        {
                                if (info.size == sizeof(uint8)) {
                                        uint8 value;
                                        node->ReadAttr(name, info.type, 0, &value, sizeof(value));
                                        representation.SetToFormat("%" B_PRIu8, value);
                                } else {
                                        multiValueFormat.Format(representation,
                                                info.size / sizeof(uint8));
                                }
                                break;
                        }
                        default:
                        {
                                BStringFormat sizeFormat(B_TRANSLATE(
                                        "{0, plural, one{<# data byte>} other{<# bytes of data>}}"));
                                sizeFormat.Format(representation, info.size);
                                break;
                        }
                }
                row->SetField(new BStringField(representation), kValueColumn);

                switch(info.type) {
                        case B_AFFINE_TRANSFORM_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Affine transform")),
                                        kTypeColumn);
                                break;
                        case B_ALIGNMENT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Alignment")),
                                        kTypeColumn);
                                break;
                        case B_ANY_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Any")),
                                        kTypeColumn);
                                break;
                        case B_ATOM_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Atom")),
                                        kTypeColumn);
                                break;
                        case B_ATOMREF_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Atom reference")),
                                        kTypeColumn);
                                break;
                        case B_BOOL_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Boolean")),
                                        kTypeColumn);
                                break;
                        case B_CHAR_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Character")),
                                        kTypeColumn);
                                break;
                        case B_COLOR_8_BIT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Palette-indexed picture")), kTypeColumn);
                                break;
                        case B_DOUBLE_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Double-precision floating point number")), kTypeColumn);
                                break;
                        case B_FLOAT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Floating point number")), kTypeColumn);
                                break;
                        case B_GRAYSCALE_8_BIT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Grayscale picture")), kTypeColumn);
                                break;
                        case B_INT16_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("16-bit integer")),
                                        kTypeColumn);
                                break;
                        case B_INT32_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("32-bit integer")),
                                        kTypeColumn);
                                break;
                        case B_INT64_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("64-bit integer")),
                                        kTypeColumn);
                                break;
                        case B_INT8_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("8-bit integer")),
                                        kTypeColumn);
                                break;
                        case B_LARGE_ICON_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Bitmap icon")),
                                        kTypeColumn);
                                break;
                        case B_MEDIA_PARAMETER_GROUP_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Media parameter group")), kTypeColumn);
                                break;
                        case B_MEDIA_PARAMETER_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Media parameter")),
                                        kTypeColumn);
                                break;
                        case B_MEDIA_PARAMETER_WEB_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Media parameter web")), kTypeColumn);
                                break;
                        case B_MESSAGE_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Message")),
                                        kTypeColumn);
                                break;
                        case B_MESSENGER_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Messenger")),
                                        kTypeColumn);
                                break;
                        case B_MIME_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("MIME Type")),
                                        kTypeColumn);
                                break;
                        case B_MINI_ICON_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Small bitmap icon")), kTypeColumn);
                                break;
                        case B_MONOCHROME_1_BIT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Monochrome picture")), kTypeColumn);
                                break;
                        case B_OBJECT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Object")),
                                        kTypeColumn);
                                break;
                        case B_OFF_T_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("File offset")),
                                        kTypeColumn);
                                break;
                        case B_PATTERN_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Drawing pattern")),
                                        kTypeColumn);
                                break;
                        case B_POINTER_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Memory pointer")),
                                        kTypeColumn);
                                break;
                        case B_POINT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Point")),
                                        kTypeColumn);
                                break;
                        case B_PROPERTY_INFO_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Property info")),
                                        kTypeColumn);
                                break;
                        case B_RAW_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Raw data")),
                                        kTypeColumn);
                                break;
                        case B_RECT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Rectangle")),
                                        kTypeColumn);
                                break;
                        case B_REF_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Entry ref")),
                                        kTypeColumn);
                                break;
                        case B_NODE_REF_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Node ref")),
                                        kTypeColumn);
                                break;
                        case B_RGB_32_BIT_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "True-color picture")), kTypeColumn);
                                break;
                        case B_RGB_COLOR_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Color")),
                                        kTypeColumn);
                                break;
                        case B_SIZE_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Geometric size")),
                                        kTypeColumn);
                                break;
                        case B_SIZE_T_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Memory size")),
                                        kTypeColumn);
                                break;
                        case B_SSIZE_T_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Signed memory size")), kTypeColumn);
                                break;
                        case B_STRING_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Plain text")),
                                        kTypeColumn);
                                break;
                        case B_STRING_LIST_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Text list")),
                                        kTypeColumn);
                                break;
                        case B_TIME_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Time")),
                                        kTypeColumn);
                                break;
                        case B_UINT16_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "16-bit unsigned integer")), kTypeColumn);
                                break;
                        case B_UINT32_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "32-bit unsigned integer")), kTypeColumn);
                                break;
                        case B_UINT64_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "64-bit unsigned integer")), kTypeColumn);
                                break;
                        case B_UINT8_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "8-bit unsigned integer")), kTypeColumn);
                                break;
                        case B_VECTOR_ICON_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Icon")),
                                        kTypeColumn);
                                break;
                        case B_XATTR_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE(
                                        "Extended attribute")), kTypeColumn);
                                break;
                        case B_NETWORK_ADDRESS_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("Network address")),
                                        kTypeColumn);
                                break;
                        case B_MIME_STRING_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("MIME String")),
                                        kTypeColumn);
                                break;
                        case 'MSIG':
                                row->SetField(new BStringField(B_TRANSLATE("MIME Signature")),
                                        kTypeColumn);
                                break;
                        case 'MSDC':
                                row->SetField(new BStringField(B_TRANSLATE("MIME Description")),
                                        kTypeColumn);
                                break;
                        case 'MPTH':
                                row->SetField(new BStringField(B_TRANSLATE("MIME Path")),
                                        kTypeColumn);
                                break;
                        case B_ASCII_TYPE:
                                row->SetField(new BStringField(B_TRANSLATE("ASCII Text")),
                                        kTypeColumn);
                                break;
                        default:
                                row->SetField(new BStringField(B_TRANSLATE("(unknown)")),
                                        kTypeColumn);
                                break;
                }
                fListView->AddRow(row);
        }

        int32 rows = fListView->CountRows(NULL);
        if (rows < 5)
                rows = 5;
        BRow* first = fListView->RowAt(0, NULL);
        if (first != NULL) {
                float height = first->Height() * (rows + 2);
                SetExplicitMaxSize(BSize(B_SIZE_UNSET, height));
        }

}