root/src/preferences/filetypes/ApplicationTypeWindow.cpp
/*
 * Copyright 2006-2010, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */


#include "ApplicationTypeWindow.h"
#include "DropTargetListView.h"
#include "FileTypes.h"
#include "IconView.h"
#include "PreferredAppMenu.h"
#include "StringView.h"
#include "TypeListWindow.h"

#include <Application.h>
#include <Bitmap.h>
#include <Box.h>
#include <Button.h>
#include <Catalog.h>
#include <CheckBox.h>
#include <ControlLook.h>
#include <File.h>
#include <GroupView.h>
#include <LayoutBuilder.h>
#include <ListView.h>
#include <Locale.h>
#include <MenuBar.h>
#include <MenuField.h>
#include <MenuItem.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <PopUpMenu.h>
#include <RadioButton.h>
#include <Roster.h>
#include <ScrollView.h>
#include <StringView.h>
#include <TextControl.h>

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


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "Application Type Window"


const uint32 kMsgSave = 'save';
const uint32 kMsgSignatureChanged = 'sgch';
const uint32 kMsgToggleAppFlags = 'tglf';
const uint32 kMsgAppFlagsChanged = 'afch';

const uint32 kMsgIconChanged = 'icch';
const uint32 kMsgTypeIconsChanged = 'tich';

const uint32 kMsgVersionInfoChanged = 'tvch';

const uint32 kMsgTypeSelected = 'tpsl';
const uint32 kMsgAddType = 'adtp';
const uint32 kMsgTypeAdded = 'tpad';
const uint32 kMsgRemoveType = 'rmtp';
const uint32 kMsgTypeRemoved = 'tprm';


//! TextView that filters the tab key to be able to tab-navigate while editing
class TabFilteringTextView : public BTextView {
public:
                                                                TabFilteringTextView(const char* name,
                                                                        uint32 changedMessageWhat = 0);
        virtual                                         ~TabFilteringTextView();

        virtual void                            InsertText(const char* text, int32 length,
                                                                        int32 offset, const text_run_array* runs);
        virtual void                            DeleteText(int32 fromOffset, int32 toOffset);
        virtual void                            KeyDown(const char* bytes, int32 count);
        virtual void                            TargetedByScrollView(BScrollView* scroller);
        virtual void                            MakeFocus(bool focused = true);

private:
                        BScrollView*            fScrollView;
                        uint32                          fChangedMessageWhat;
};


class SupportedTypeItem : public BStringItem {
public:
                                                                SupportedTypeItem(const char* type);
        virtual                                         ~SupportedTypeItem();

                        const char*                     Type() const { return fType.String(); }
                        ::Icon&                         Icon() { return fIcon; }

                        void                            SetIcon(::Icon* icon);
                        void                            SetIcon(entry_ref& ref, const char* type);

        static  int                                     Compare(const void* _a, const void* _b);

private:
                        BString                         fType;
                        ::Icon                          fIcon;
};


class SupportedTypeListView : public DropTargetListView {
public:
                                                                SupportedTypeListView(const char* name,
                                                                        list_view_type
                                                                                type = B_SINGLE_SELECTION_LIST,
                                                                        uint32 flags = B_WILL_DRAW
                                                                                | B_FRAME_EVENTS | B_NAVIGABLE);
        virtual                                         ~SupportedTypeListView();

        virtual void                            MessageReceived(BMessage* message);
        virtual bool                            AcceptsDrag(const BMessage* message);
};


// #pragma mark -


TabFilteringTextView::TabFilteringTextView(const char* name,
        uint32 changedMessageWhat)
        :
        BTextView(name, B_WILL_DRAW | B_PULSE_NEEDED | B_NAVIGABLE),
        fScrollView(NULL),
        fChangedMessageWhat(changedMessageWhat)
{
}


TabFilteringTextView::~TabFilteringTextView()
{
}


void
TabFilteringTextView::InsertText(const char* text, int32 length, int32 offset,
        const text_run_array* runs)
{
        BTextView::InsertText(text, length, offset, runs);
        if (fChangedMessageWhat != 0)
                Window()->PostMessage(fChangedMessageWhat);
}


void
TabFilteringTextView::DeleteText(int32 fromOffset, int32 toOffset)
{
        BTextView::DeleteText(fromOffset, toOffset);
        if (fChangedMessageWhat != 0)
                Window()->PostMessage(fChangedMessageWhat);
}


void
TabFilteringTextView::KeyDown(const char* bytes, int32 count)
{
        if (bytes[0] == B_TAB)
                BView::KeyDown(bytes, count);
        else
                BTextView::KeyDown(bytes, count);
}


void
TabFilteringTextView::TargetedByScrollView(BScrollView* scroller)
{
        fScrollView = scroller;
}


void
TabFilteringTextView::MakeFocus(bool focused)
{
        BTextView::MakeFocus(focused);

        if (fScrollView)
                fScrollView->SetBorderHighlighted(focused);
}


// #pragma mark -


SupportedTypeItem::SupportedTypeItem(const char* type)
        : BStringItem(type),
        fType(type)
{
        BMimeType mimeType(type);

        char description[B_MIME_TYPE_LENGTH];
        if (mimeType.GetShortDescription(description) == B_OK && description[0])
                SetText(description);
}


SupportedTypeItem::~SupportedTypeItem()
{
}


void
SupportedTypeItem::SetIcon(::Icon* icon)
{
        if (icon != NULL)
                fIcon = *icon;
        else
                fIcon.Unset();
}


void
SupportedTypeItem::SetIcon(entry_ref& ref, const char* type)
{
        fIcon.SetTo(ref, type);
}


/*static*/
int
SupportedTypeItem::Compare(const void* _a, const void* _b)
{
        const SupportedTypeItem* a = *(const SupportedTypeItem**)_a;
        const SupportedTypeItem* b = *(const SupportedTypeItem**)_b;

        int compare = strcasecmp(a->Text(), b->Text());
        if (compare != 0)
                return compare;

        return strcasecmp(a->Type(), b->Type());
}


//      #pragma mark -


SupportedTypeListView::SupportedTypeListView(const char* name,
        list_view_type type, uint32 flags)
        :
        DropTargetListView(name, type, flags)
{
}


SupportedTypeListView::~SupportedTypeListView()
{
}


void
SupportedTypeListView::MessageReceived(BMessage* message)
{
        if (message->WasDropped() && AcceptsDrag(message)) {
                // Add unique types
                entry_ref ref;
                for (int32 index = 0; message->FindRef("refs", index++, &ref) == B_OK; ) {
                        BNode node(&ref);
                        BNodeInfo info(&node);
                        if (node.InitCheck() != B_OK || info.InitCheck() != B_OK)
                                continue;

                        // TODO: we could identify the file in case it doesn't have a type...
                        char type[B_MIME_TYPE_LENGTH];
                        if (info.GetType(type) != B_OK)
                                continue;

                        // check if that type is already in our list
                        bool found = false;
                        for (int32 i = CountItems(); i-- > 0;) {
                                SupportedTypeItem* item = (SupportedTypeItem*)ItemAt(i);
                                if (!strcmp(item->Text(), type)) {
                                        found = true;
                                        break;
                                }
                        }

                        if (!found) {
                                // add type
                                AddItem(new SupportedTypeItem(type));
                        }
                }

                SortItems(&SupportedTypeItem::Compare);
        } else
                DropTargetListView::MessageReceived(message);
}


bool
SupportedTypeListView::AcceptsDrag(const BMessage* message)
{
        type_code type;
        return message->GetInfo("refs", &type) == B_OK && type == B_REF_TYPE;
}


//      #pragma mark -


ApplicationTypeWindow::ApplicationTypeWindow(const BMessage& settings, const BEntry& entry)
        :
        BWindow(_Frame(settings), B_TRANSLATE("Application type"), B_TITLED_WINDOW,
                B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS | B_FRAME_EVENTS | B_AUTO_UPDATE_SIZE_LIMITS),
        fChangedProperties(0)
{
        float padding = be_control_look->DefaultItemSpacing();
        BAlignment labelAlignment = be_control_look->DefaultLabelAlignment();

        BMenuBar* menuBar = new BMenuBar((char*)NULL);
        menuBar->SetExplicitAlignment(BAlignment(B_ALIGN_LEFT, B_ALIGN_TOP));

        BMenu* menu = new BMenu(B_TRANSLATE("File"));
        fSaveMenuItem = new BMenuItem(B_TRANSLATE("Save"),
                new BMessage(kMsgSave), 'S');
        fSaveMenuItem->SetEnabled(false);
        menu->AddItem(fSaveMenuItem);
        BMenuItem* item;
        menu->AddItem(item = new BMenuItem(
                B_TRANSLATE("Save into resource file" B_UTF8_ELLIPSIS), NULL));
        item->SetEnabled(false);

        menu->AddSeparatorItem();
        menu->AddItem(new BMenuItem(B_TRANSLATE("Close"),
                new BMessage(B_QUIT_REQUESTED), 'W', B_COMMAND_KEY));
        menuBar->AddItem(menu);

        // Signature

        fSignatureControl = new BTextControl("signature",
                B_TRANSLATE("Signature:"), NULL, new BMessage(kMsgSignatureChanged));
        fSignatureControl->SetModificationMessage(
                new BMessage(kMsgSignatureChanged));

        // filter out invalid characters that can't be part of a MIME type name
        BTextView* textView = fSignatureControl->TextView();
        textView->SetMaxBytes(B_MIME_TYPE_LENGTH);
        const char* disallowedCharacters = "<>@,;:\"()[]?= ";
        for (int32 i = 0; disallowedCharacters[i]; i++) {
                textView->DisallowChar(disallowedCharacters[i]);
        }

        // "Application Flags" group

        BBox* flagsBox = new BBox("flagsBox");

        fFlagsCheckBox = new BCheckBox("flags", B_TRANSLATE("Application flags"),
                new BMessage(kMsgToggleAppFlags));
        fFlagsCheckBox->SetValue(B_CONTROL_ON);

        fSingleLaunchButton = new BRadioButton("single",
                B_TRANSLATE("Single launch"), new BMessage(kMsgAppFlagsChanged));

        fMultipleLaunchButton = new BRadioButton("multiple",
                B_TRANSLATE("Multiple launch"), new BMessage(kMsgAppFlagsChanged));

        fExclusiveLaunchButton = new BRadioButton("exclusive",
                B_TRANSLATE("Exclusive launch"), new BMessage(kMsgAppFlagsChanged));

        fArgsOnlyCheckBox = new BCheckBox("args only", B_TRANSLATE("Args only"),
                new BMessage(kMsgAppFlagsChanged));

        fBackgroundAppCheckBox = new BCheckBox("background",
                B_TRANSLATE("Background app"), new BMessage(kMsgAppFlagsChanged));

        BLayoutBuilder::Grid<>(flagsBox, 0, 0)
                .SetInsets(padding, padding * 2, padding, padding)
                .Add(fSingleLaunchButton, 0, 0).Add(fArgsOnlyCheckBox, 1, 0)
                .Add(fMultipleLaunchButton, 0, 1).Add(fBackgroundAppCheckBox, 1, 1)
                .Add(fExclusiveLaunchButton, 0, 2);
        flagsBox->SetLabel(fFlagsCheckBox);

        // "Icon" group

        BBox* iconBox = new BBox("IconBox");
        iconBox->SetLabel(B_TRANSLATE("Icon"));
        fIconView = new IconView("icon");
        fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));
        BLayoutBuilder::Group<>(iconBox, B_HORIZONTAL)
                .SetInsets(padding, padding * 2, padding, padding)
                .Add(fIconView);

        // "Supported Types" group

        BBox* typeBox = new BBox("typesBox");
        typeBox->SetLabel(B_TRANSLATE("Supported types"));

        fTypeListView = new SupportedTypeListView("Suppported Types",
                B_SINGLE_SELECTION_LIST);
        fTypeListView->SetSelectionMessage(new BMessage(kMsgTypeSelected));

        BScrollView* scrollView = new BScrollView("type scrollview", fTypeListView,
                B_FRAME_EVENTS | B_WILL_DRAW, false, true);

        fAddTypeButton = new BButton("add type",
                B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddType));

        fRemoveTypeButton = new BButton("remove type", B_TRANSLATE("Remove"),
                new BMessage(kMsgRemoveType));

        fTypeIconView = new IconView("type icon");
        BGroupView* iconHolder = new BGroupView(B_HORIZONTAL);
        iconHolder->AddChild(fTypeIconView);
        fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));

        BLayoutBuilder::Grid<>(typeBox, padding, padding)
                .SetInsets(padding, padding * 2, padding, padding)
                .Add(scrollView, 0, 0, 1, 5)
                .Add(fAddTypeButton, 1, 0, 1, 2)
                .Add(fRemoveTypeButton, 1, 2, 1, 2)
                .Add(iconHolder, 2, 1, 1, 2)
                .SetColumnWeight(0, 3)
                .SetColumnWeight(1, 2)
                .SetColumnWeight(2, 1);

        iconHolder->SetExplicitAlignment(
                BAlignment(B_ALIGN_CENTER, B_ALIGN_MIDDLE));

        // "Version Info" group

        BBox* versionBox = new BBox("versionBox");
        versionBox->SetLabel(B_TRANSLATE("Version info"));

        fMajorVersionControl = new BTextControl("version major",
                B_TRANSLATE("Version:"), NULL, new BMessage(kMsgVersionInfoChanged));
        _MakeNumberTextControl(fMajorVersionControl);

        fMiddleVersionControl = new BTextControl("version middle", ".", NULL,
                new BMessage(kMsgVersionInfoChanged));
        _MakeNumberTextControl(fMiddleVersionControl);

        fMinorVersionControl = new BTextControl("version minor", ".", NULL,
                new BMessage(kMsgVersionInfoChanged));
        _MakeNumberTextControl(fMinorVersionControl);

        fVarietyMenu = new BPopUpMenu("variety", true, true);
        fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Development"),
                new BMessage(kMsgVersionInfoChanged)));
        fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Alpha"),
                new BMessage(kMsgVersionInfoChanged)));
        fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Beta"),
                new BMessage(kMsgVersionInfoChanged)));
        fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Gamma"),
                new BMessage(kMsgVersionInfoChanged)));
        item = new BMenuItem(B_TRANSLATE("Golden master"),
                new BMessage(kMsgVersionInfoChanged));
        fVarietyMenu->AddItem(item);
        item->SetMarked(true);
        fVarietyMenu->AddItem(new BMenuItem(B_TRANSLATE("Final"),
                new BMessage(kMsgVersionInfoChanged)));

        BMenuField* varietyField = new BMenuField("", fVarietyMenu);
        fInternalVersionControl = new BTextControl("version internal", "/", NULL,
                new BMessage(kMsgVersionInfoChanged));
        fShortDescriptionControl = new BTextControl("short description",
                B_TRANSLATE("Short description:"), NULL,
                new BMessage(kMsgVersionInfoChanged));

        // TODO: workaround for a GCC 4.1.0 bug? Or is that really what the standard says?
        version_info versionInfo;
        fShortDescriptionControl->TextView()->SetMaxBytes(
                sizeof(versionInfo.short_info));

        BStringView* longLabel = new BStringView(NULL,
                B_TRANSLATE("Long description:"));
        longLabel->SetExplicitAlignment(labelAlignment);
        fLongDescriptionView = new TabFilteringTextView("long desc",
                kMsgVersionInfoChanged);
        fLongDescriptionView->SetMaxBytes(sizeof(versionInfo.long_info));

        scrollView = new BScrollView("desc scrollview", fLongDescriptionView,
                B_FRAME_EVENTS | B_WILL_DRAW, false, true);

        // Manually set a minimum size for the version text controls
        // TODO: the same does not work when applied to the layout items
        float width = be_plain_font->StringWidth("99") + 16;
        fMajorVersionControl->TextView()->SetExplicitMinSize(
                BSize(width, B_SIZE_UNSET));
        fMiddleVersionControl->TextView()->SetExplicitMinSize(
                BSize(width, B_SIZE_UNSET));
        fMinorVersionControl->TextView()->SetExplicitMinSize(
                BSize(width, B_SIZE_UNSET));
        fInternalVersionControl->TextView()->SetExplicitMinSize(
                BSize(width, B_SIZE_UNSET));

        BLayoutBuilder::Grid<>(versionBox, padding / 2, padding / 2)
                .SetInsets(padding, padding * 2, padding, padding)
                .AddTextControl(fMajorVersionControl, 0, 0)
                .Add(fMiddleVersionControl, 2, 0, 2)
                .Add(fMinorVersionControl, 4, 0, 2)
                .Add(varietyField, 6, 0, 3)
                .Add(fInternalVersionControl, 9, 0, 2)
                .AddTextControl(fShortDescriptionControl, 0, 1,
                        B_ALIGN_HORIZONTAL_UNSET, 1, 10)
                .Add(longLabel, 0, 2)
                .Add(scrollView, 1, 2, 10, 3)
                .SetRowWeight(3, 3);

        // put it all together
        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .SetInsets(0, 0, 0, 0)
                .Add(menuBar)
                .AddGroup(B_VERTICAL, padding)
                        .SetInsets(padding, padding, padding, padding)
                        .Add(fSignatureControl)
                        .AddGroup(B_HORIZONTAL, padding)
                                .Add(flagsBox, 3)
                                .Add(iconBox, 1)
                                .End()
                        .Add(typeBox, 2)
                        .Add(versionBox);

        SetKeyMenuBar(menuBar);

        fSignatureControl->MakeFocus(true);
        BMimeType::StartWatching(this);
        _SetTo(entry);

        Layout(false);
}


ApplicationTypeWindow::~ApplicationTypeWindow()
{
        BMimeType::StopWatching(this);
}

BRect
ApplicationTypeWindow::_Frame(const BMessage& settings) const
{
        BRect rect;
        if (settings.FindRect("app_type_next_frame", &rect) == B_OK)
                return rect;

        return BRect(100.0f, 110.0f, 250.0f, 340.0f);
}

BString
ApplicationTypeWindow::_Title(const BEntry& entry)
{
        char name[B_FILE_NAME_LENGTH];
        if (entry.GetName(name) != B_OK)
                strcpy(name, "\"-\"");

        BString title = B_TRANSLATE("%1 application type");
        title.ReplaceFirst("%1", name);
        return title;
}


void
ApplicationTypeWindow::_SetTo(const BEntry& entry)
{
        SetTitle(_Title(entry).String());
        fEntry = entry;

        // Retrieve Info

        BFile file(&entry, B_READ_ONLY);
        if (file.InitCheck() != B_OK)
                return;

        BAppFileInfo info(&file);
        if (info.InitCheck() != B_OK)
                return;

        char signature[B_MIME_TYPE_LENGTH];
        if (info.GetSignature(signature) != B_OK)
                signature[0] = '\0';

        bool gotFlags = false;
        uint32 flags;
        if (info.GetAppFlags(&flags) == B_OK)
                gotFlags = true;
        else
                flags = B_MULTIPLE_LAUNCH;

        version_info versionInfo;
        if (info.GetVersionInfo(&versionInfo, B_APP_VERSION_KIND) != B_OK)
                memset(&versionInfo, 0, sizeof(version_info));

        // Set Controls

        fSignatureControl->SetModificationMessage(NULL);
        fSignatureControl->SetText(signature);
        fSignatureControl->SetModificationMessage(
                new BMessage(kMsgSignatureChanged));

        // flags

        switch (flags & (B_SINGLE_LAUNCH | B_MULTIPLE_LAUNCH | B_EXCLUSIVE_LAUNCH)) {
                case B_SINGLE_LAUNCH:
                        fSingleLaunchButton->SetValue(B_CONTROL_ON);
                        break;

                case B_EXCLUSIVE_LAUNCH:
                        fExclusiveLaunchButton->SetValue(B_CONTROL_ON);
                        break;

                case B_MULTIPLE_LAUNCH:
                default:
                        fMultipleLaunchButton->SetValue(B_CONTROL_ON);
                        break;
        }

        fArgsOnlyCheckBox->SetValue((flags & B_ARGV_ONLY) != 0);
        fBackgroundAppCheckBox->SetValue((flags & B_BACKGROUND_APP) != 0);
        fFlagsCheckBox->SetValue(gotFlags);

        _UpdateAppFlagsEnabled();

        // icon

        entry_ref ref;
        if (entry.GetRef(&ref) == B_OK)
                fIcon.SetTo(ref);
        else
                fIcon.Unset();

        fIconView->SetModificationMessage(NULL);
        fIconView->SetTo(&fIcon);
        fIconView->SetModificationMessage(new BMessage(kMsgIconChanged));

        // supported types

        BMessage supportedTypes;
        info.GetSupportedTypes(&supportedTypes);

        for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
                BListItem* item = fTypeListView->RemoveItem(i);
                delete item;
        }

        const char* type;
        for (int32 i = 0; supportedTypes.FindString("types", i, &type) == B_OK; i++) {
                SupportedTypeItem* item = new SupportedTypeItem(type);

                entry_ref ref;
                if (fEntry.GetRef(&ref) == B_OK)
                        item->SetIcon(ref, type);

                fTypeListView->AddItem(item);
        }
        fTypeListView->SortItems(&SupportedTypeItem::Compare);
        fTypeIconView->SetModificationMessage(NULL);
        fTypeIconView->SetTo(NULL);
        fTypeIconView->SetModificationMessage(new BMessage(kMsgTypeIconsChanged));
        fTypeIconView->SetEnabled(false);
        fRemoveTypeButton->SetEnabled(false);

        // version info

        char text[256];
        snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.major);
        fMajorVersionControl->SetText(text);
        snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.middle);
        fMiddleVersionControl->SetText(text);
        snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.minor);
        fMinorVersionControl->SetText(text);

        if (versionInfo.variety >= (uint32)fVarietyMenu->CountItems())
                versionInfo.variety = 0;
        BMenuItem* item = fVarietyMenu->ItemAt(versionInfo.variety);
        if (item != NULL)
                item->SetMarked(true);

        snprintf(text, sizeof(text), "%" B_PRId32, versionInfo.internal);
        fInternalVersionControl->SetText(text);

        fShortDescriptionControl->SetText(versionInfo.short_info);
        fLongDescriptionView->SetText(versionInfo.long_info);

        // store original data

        fOriginalInfo.signature = signature;
        fOriginalInfo.gotFlags = gotFlags;
        fOriginalInfo.flags = gotFlags ? flags : 0;
        fOriginalInfo.versionInfo = versionInfo;
        fOriginalInfo.supportedTypes = _SupportedTypes();
                // The list view has the types sorted possibly differently
                // to the supportedTypes message, so don't use that here, but
                // get the sorted message instead.
        fOriginalInfo.iconChanged = false;
        fOriginalInfo.typeIconsChanged = false;

        fChangedProperties = 0;
        _CheckSaveMenuItem(0);
}


void
ApplicationTypeWindow::_UpdateAppFlagsEnabled()
{
        bool enabled = fFlagsCheckBox->Value() != B_CONTROL_OFF;

        fSingleLaunchButton->SetEnabled(enabled);
        fMultipleLaunchButton->SetEnabled(enabled);
        fExclusiveLaunchButton->SetEnabled(enabled);
        fArgsOnlyCheckBox->SetEnabled(enabled);
        fBackgroundAppCheckBox->SetEnabled(enabled);
}


void
ApplicationTypeWindow::_MakeNumberTextControl(BTextControl* control)
{
        // filter out invalid characters that can't be part of a MIME type name
        BTextView* textView = control->TextView();
        textView->SetMaxBytes(10);

        for (int32 i = 0; i < 256; i++) {
                if (!isdigit(i))
                        textView->DisallowChar(i);
        }
}


void
ApplicationTypeWindow::_Save()
{
        BFile file;
        status_t status = file.SetTo(&fEntry, B_READ_WRITE);
        if (status != B_OK)
                return;

        BAppFileInfo info(&file);
        status = info.InitCheck();
        if (status != B_OK)
                return;

        // Retrieve Info

        uint32 flags = 0;
        bool gotFlags = _Flags(flags);
        BMessage supportedTypes = _SupportedTypes();
        version_info versionInfo = _VersionInfo();

        // Save

        status = info.SetSignature(fSignatureControl->Text());
        if (status == B_OK) {
                if (gotFlags)
                        status = info.SetAppFlags(flags);
                else
                        status = info.RemoveAppFlags();
        }
        if (status == B_OK)
                status = info.SetVersionInfo(&versionInfo, B_APP_VERSION_KIND);
        if (status == B_OK)
                fIcon.CopyTo(info, NULL, true);

        // supported types and their icons
        if (status == B_OK)
                status = info.SetSupportedTypes(&supportedTypes);

        for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
                SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
                        fTypeListView->ItemAt(i));

                if (item != NULL)
                        item->Icon().CopyTo(info, item->Type(), true);
        }

        // reset the saved info
        fOriginalInfo.signature = fSignatureControl->Text();
        fOriginalInfo.gotFlags = gotFlags;
        fOriginalInfo.flags = flags;
        fOriginalInfo.versionInfo = versionInfo;
        fOriginalInfo.supportedTypes = supportedTypes;
        fOriginalInfo.iconChanged = false;
        fOriginalInfo.typeIconsChanged = false;

        fChangedProperties = 0;
        _CheckSaveMenuItem(0);
}


void
ApplicationTypeWindow::_CheckSaveMenuItem(uint32 flags)
{
        fChangedProperties = _NeedsSaving(flags);
        fSaveMenuItem->SetEnabled(fChangedProperties != 0);
}


bool
operator!=(const version_info& a, const version_info& b)
{
        return a.major != b.major || a.middle != b.middle || a.minor != b.minor
                || a.variety != b.variety || a.internal != b.internal
                || strcmp(a.short_info, b.short_info) != 0
                || strcmp(a.long_info, b.long_info) != 0;
}


uint32
ApplicationTypeWindow::_NeedsSaving(uint32 _flags) const
{
        uint32 flags = fChangedProperties;
        if (_flags & CHECK_SIGNATUR) {
                if (fOriginalInfo.signature != fSignatureControl->Text())
                        flags |= CHECK_SIGNATUR;
                else
                        flags &= ~CHECK_SIGNATUR;
        }

        if (_flags & CHECK_FLAGS) {
                uint32 appFlags = 0;
                bool gotFlags = _Flags(appFlags);
                if (fOriginalInfo.gotFlags != gotFlags
                        || fOriginalInfo.flags != appFlags) {
                        flags |= CHECK_FLAGS;
                } else
                        flags &= ~CHECK_FLAGS;
        }

        if (_flags & CHECK_VERSION) {
                if (fOriginalInfo.versionInfo != _VersionInfo())
                        flags |= CHECK_VERSION;
                else
                        flags &= ~CHECK_VERSION;
        }

        if (_flags & CHECK_ICON) {
                if (fOriginalInfo.iconChanged)
                        flags |= CHECK_ICON;
                else
                        flags &= ~CHECK_ICON;
        }

        if (_flags & CHECK_TYPES) {
                if (!fOriginalInfo.supportedTypes.HasSameData(_SupportedTypes()))
                        flags |= CHECK_TYPES;
                else
                        flags &= ~CHECK_TYPES;
        }

        if (_flags & CHECK_TYPE_ICONS) {
                if (fOriginalInfo.typeIconsChanged)
                        flags |= CHECK_TYPE_ICONS;
                else
                        flags &= ~CHECK_TYPE_ICONS;
        }

        return flags;
}


// #pragma mark -


bool
ApplicationTypeWindow::_Flags(uint32& flags) const
{
        flags = 0;
        if (fFlagsCheckBox->Value() != B_CONTROL_OFF) {
                if (fSingleLaunchButton->Value() != B_CONTROL_OFF)
                        flags |= B_SINGLE_LAUNCH;
                else if (fMultipleLaunchButton->Value() != B_CONTROL_OFF)
                        flags |= B_MULTIPLE_LAUNCH;
                else if (fExclusiveLaunchButton->Value() != B_CONTROL_OFF)
                        flags |= B_EXCLUSIVE_LAUNCH;

                if (fArgsOnlyCheckBox->Value() != B_CONTROL_OFF)
                        flags |= B_ARGV_ONLY;
                if (fBackgroundAppCheckBox->Value() != B_CONTROL_OFF)
                        flags |= B_BACKGROUND_APP;
                return true;
        }
        return false;
}


BMessage
ApplicationTypeWindow::_SupportedTypes() const
{
        BMessage supportedTypes;
        for (int32 i = 0; i < fTypeListView->CountItems(); i++) {
                SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
                        fTypeListView->ItemAt(i));

                if (item != NULL)
                        supportedTypes.AddString("types", item->Type());
        }
        return supportedTypes;
}


version_info
ApplicationTypeWindow::_VersionInfo() const
{
        version_info versionInfo;
        versionInfo.major = atol(fMajorVersionControl->Text());
        versionInfo.middle = atol(fMiddleVersionControl->Text());
        versionInfo.minor = atol(fMinorVersionControl->Text());
        versionInfo.variety = fVarietyMenu->IndexOf(fVarietyMenu->FindMarked());
        versionInfo.internal = atol(fInternalVersionControl->Text());
        strlcpy(versionInfo.short_info, fShortDescriptionControl->Text(),
                sizeof(versionInfo.short_info));
        strlcpy(versionInfo.long_info, fLongDescriptionView->Text(),
                sizeof(versionInfo.long_info));
        return versionInfo;
}


// #pragma mark -


void
ApplicationTypeWindow::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case kMsgToggleAppFlags:
                        _UpdateAppFlagsEnabled();
                        _CheckSaveMenuItem(CHECK_FLAGS);
                        break;

                case kMsgSignatureChanged:
                        _CheckSaveMenuItem(CHECK_SIGNATUR);
                        break;

                case kMsgAppFlagsChanged:
                        _CheckSaveMenuItem(CHECK_FLAGS);
                        break;

                case kMsgIconChanged:
                        fOriginalInfo.iconChanged = true;
                        _CheckSaveMenuItem(CHECK_ICON);
                        break;

                case kMsgTypeIconsChanged:
                        fOriginalInfo.typeIconsChanged = true;
                        _CheckSaveMenuItem(CHECK_TYPE_ICONS);
                        break;

                case kMsgVersionInfoChanged:
                        _CheckSaveMenuItem(CHECK_VERSION);
                        break;

                case kMsgSave:
                        _Save();
                        break;

                case kMsgTypeSelected:
                {
                        int32 index;
                        if (message->FindInt32("index", &index) == B_OK) {
                                SupportedTypeItem* item
                                        = (SupportedTypeItem*)fTypeListView->ItemAt(index);

                                fTypeIconView->SetModificationMessage(NULL);
                                fTypeIconView->SetTo(item != NULL ? &item->Icon() : NULL);
                                fTypeIconView->SetModificationMessage(
                                        new BMessage(kMsgTypeIconsChanged));
                                fTypeIconView->SetEnabled(item != NULL);
                                fRemoveTypeButton->SetEnabled(item != NULL);

                                _CheckSaveMenuItem(CHECK_TYPES);
                        }
                        break;
                }

                case kMsgAddType:
                {
                        BWindow* window = new TypeListWindow(NULL,
                                kMsgTypeAdded, this);
                        window->Show();
                        break;
                }

                case kMsgTypeAdded:
                {
                        const char* type;
                        if (message->FindString("type", &type) != B_OK)
                                break;

                        // check if this type already exists

                        SupportedTypeItem* newItem = new SupportedTypeItem(type);
                        int32 insertAt = 0;

                        for (int32 i = fTypeListView->CountItems(); i-- > 0;) {
                                SupportedTypeItem* item = dynamic_cast<SupportedTypeItem*>(
                                        fTypeListView->ItemAt(i));
                                if (item == NULL)
                                        continue;

                                int compare = strcasecmp(item->Type(), type);
                                if (!compare) {
                                        // type does already exist, select it and bail out
                                        delete newItem;
                                        newItem = NULL;
                                        fTypeListView->Select(i);
                                        break;
                                }
                                if (compare < 0)
                                        insertAt = i + 1;
                        }

                        if (newItem == NULL)
                                break;

                        fTypeListView->AddItem(newItem, insertAt);
                        fTypeListView->Select(insertAt);

                        _CheckSaveMenuItem(CHECK_TYPES);
                        break;
                }

                case kMsgRemoveType:
                {
                        int32 index = fTypeListView->CurrentSelection();
                        if (index < 0)
                                break;

                        delete fTypeListView->RemoveItem(index);
                        fTypeIconView->SetModificationMessage(NULL);
                        fTypeIconView->SetTo(NULL);
                        fTypeIconView->SetModificationMessage(
                                new BMessage(kMsgTypeIconsChanged));
                        fTypeIconView->SetEnabled(false);
                        fRemoveTypeButton->SetEnabled(false);

                        _CheckSaveMenuItem(CHECK_TYPES);
                        break;
                }

                case B_SIMPLE_DATA:
                {
                        entry_ref ref;
                        if (message->FindRef("refs", &ref) != B_OK)
                                break;

                        // TODO: add to supported types
                        break;
                }

                case B_META_MIME_CHANGED:
                        const char* type;
                        int32 which;
                        if (message->FindString("be:type", &type) != B_OK
                                || message->FindInt32("be:which", &which) != B_OK)
                                break;

                        // TODO: update supported types names
//                      if (which == B_MIME_TYPE_DELETED)

//                      _CheckSaveMenuItem(...);
                        break;

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


bool
ApplicationTypeWindow::QuitRequested()
{
        if (_NeedsSaving(CHECK_ALL) != 0) {
                BAlert* alert = new BAlert(B_TRANSLATE("Save request"),
                        B_TRANSLATE("Save changes before closing?"),
                        B_TRANSLATE("Cancel"), B_TRANSLATE("Don't save"),
                        B_TRANSLATE("Save"), B_WIDTH_AS_USUAL, B_OFFSET_SPACING,
                        B_WARNING_ALERT);
                alert->SetShortcut(0, B_ESCAPE);
                alert->SetShortcut(1, 'd');
                alert->SetShortcut(2, 's');

                int32 choice = alert->Go();
                switch (choice) {
                        case 0:
                                return false;
                        case 1:
                                break;
                        case 2:
                                _Save();
                                break;
                }
        }

        BMessage update(kMsgSettingsChanged);
        update.AddRect("app_type_next_frame", Frame());
        be_app_messenger.SendMessage(&update);

        be_app->PostMessage(kMsgTypeWindowClosed);
        return true;
}