root/src/apps/resedit/ResView.cpp
/*
 * Copyright (c) 2005-2010, Haiku, Inc.
 * Distributed under the terms of the MIT license.
 *
 * Author:
 *              DarkWyrm <darkwyrm@gmail.com>
 */
#include "ResView.h"

#include <Application.h>
#include <File.h>
#include <Menu.h>
#include <MenuItem.h>
#include <Path.h>
#include <ScrollView.h>
#include <TranslatorRoster.h>
#include <TypeConstants.h>

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

#include "App.h"
#include "ColumnTypes.h"
#include "ResourceData.h"
#include "ResFields.h"
#include "ResListView.h"
#include "ResWindow.h"
#include "PreviewColumn.h"
#include "Editor.h"

static int32 sUntitled = 1;

ResourceRoster gResRoster;

enum {
        M_NEW_FILE = 'nwfl',
        M_OPEN_FILE,
        M_SAVE_FILE,
        M_QUIT,
        M_SELECT_FILE,
        M_DELETE_RESOURCE,
        M_EDIT_RESOURCE
};

ResView::ResView(const BRect &frame, const char *name, const int32 &resize,
                                const int32 &flags, const entry_ref *ref)
  :     BView(frame, name, resize, flags),
        fRef(NULL),
        fSaveStatus(FILE_INIT),
        fOpenPanel(NULL),
        fSavePanel(NULL)
{
        SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
        if (ref) {
                fRef = new entry_ref;
                *fRef = *ref;
                fFileName = fRef->name;
        } else {
                fFileName = "Untitled ";
                fFileName << sUntitled;
                sUntitled++;
        }
        
        BRect r(Bounds());
        r.bottom = 16;
        fBar = new BMenuBar(r, "bar");
        AddChild(fBar);
        
        BuildMenus(fBar);
        
        r = Bounds();
        r.top = fBar->Frame().bottom + 4;
        fListView = new ResListView(r, "gridview", B_FOLLOW_ALL, B_WILL_DRAW, B_FANCY_BORDER);
        AddChild(fListView);
        
        float width = be_plain_font->StringWidth("00000") + 20;
        fListView->AddColumn(new BStringColumn("ID", width, width, 100, B_TRUNCATE_END), 0);
        
        fListView->AddColumn(new BStringColumn("Type", width, width, 100, B_TRUNCATE_END), 1);
        fListView->AddColumn(new BStringColumn("Name", 150, 50, 300, B_TRUNCATE_END), 2);
        fListView->AddColumn(new PreviewColumn("Data", 150, 50, 300), 3);
        
        // Editing is disabled for now
        fListView->SetInvocationMessage(new BMessage(M_EDIT_RESOURCE));
        
        width = be_plain_font->StringWidth("1000 bytes") + 20;
        fListView->AddColumn(new BSizeColumn("Size", width, 10, 100), 4);
        
        fOpenPanel = new BFilePanel(B_OPEN_PANEL);
        if (ref)
                OpenFile(*ref);
        
        fSavePanel = new BFilePanel(B_SAVE_PANEL);
}


ResView::~ResView(void)
{
        EmptyDataList();
        delete fRef;
        delete fOpenPanel;
        delete fSavePanel;
}


void
ResView::AttachedToWindow(void)
{
        for (int32 i = 0; i < fBar->CountItems(); i++)
                fBar->SubmenuAt(i)->SetTargetForItems(this);
        fListView->SetTarget(this);
        
        BMessenger messenger(this);
        fOpenPanel->SetTarget(messenger);
        fSavePanel->SetTarget(messenger);
        
        Window()->Lock();
        BString title("ResEdit: ");
        title << fFileName;
        Window()->SetTitle(title.String());
        Window()->Unlock();
}


void
ResView::MessageReceived(BMessage *msg)
{
        switch (msg->what) {
                case M_NEW_FILE: {
                        BRect r(100, 100, 400, 400);
                        if (Window())
                                r = Window()->Frame().OffsetByCopy(10, 10);
                        ResWindow *win = new ResWindow(r);
                        win->Show();
                        break;
                }
                case M_OPEN_FILE: {
                        be_app->PostMessage(M_SHOW_OPEN_PANEL);
                        break;
                }
                case B_CANCEL: {
                        if (fSaveStatus == FILE_QUIT_AFTER_SAVE)
                                SetSaveStatus(FILE_DIRTY);
                        break;
                }
                case B_SAVE_REQUESTED: {
                        entry_ref saveDir;
                        BString name;
                        if (msg->FindRef("directory",&saveDir) == B_OK &&
                                msg->FindString("name",&name) == B_OK) {
                                SetTo(saveDir,name);
                                SaveFile();
                        }
                        break;
                }
                case M_SAVE_FILE: {
                        if (!fRef)
                                fSavePanel->Show();
                        else
                                SaveFile();
                        break;
                }
                case M_SHOW_SAVE_PANEL: {
                        fSavePanel->Show();
                        break;
                }
                case M_QUIT: {
                        be_app->PostMessage(B_QUIT_REQUESTED);
                        break;
                }
                case B_REFS_RECEIVED: {
                        int32 i = 0;
                        entry_ref ref;
                        while (msg->FindRef("refs", i++, &ref) == B_OK)
                                AddResource(ref);
                        break;
                }
                case M_SELECT_FILE: {
                        fOpenPanel->Show();
                        break;
                }
                case M_DELETE_RESOURCE: {
                        DeleteSelectedResources();
                        break;
                }
                case M_EDIT_RESOURCE: {
                        BRow *row = fListView->CurrentSelection();
                        TypeCodeField *field = (TypeCodeField*)row->GetField(1);
                        gResRoster.SpawnEditor(field->GetResourceData(), this);
                        break;
                }
                case M_UPDATE_RESOURCE: {
                        ResourceData *item;
                        if (msg->FindPointer("item", (void **)&item) != B_OK)
                                break;
                        
                        for (int32 i = 0; i < fListView->CountRows(); i++) {
                                BRow *row = fListView->RowAt(i);
                                TypeCodeField *field = (TypeCodeField*)row->GetField(1);
                                if (!field || field->GetResourceData() != item)
                                        continue;
                                
                                UpdateRow(row);
                                break;
                        }
                        break;
                }
                default:
                        BView::MessageReceived(msg);
        }
}


status_t
ResView::SetTo(const entry_ref &dir, const BString &name)
{
        entry_ref fileRef;
        
        BPath path(&dir);
        path.Append(name.String());
        BFile file(path.Path(), B_CREATE_FILE | B_READ_WRITE);
        if (file.InitCheck() != B_OK)
                return B_ERROR;
        
        if (!fRef)
                fRef = new entry_ref();
        
        BEntry entry(path.Path());
        entry.GetRef(fRef);
        fFileName = name;
        return B_OK;
}


void
ResView::OpenFile(const entry_ref &ref)
{
        // Add all the 133t resources and attributes of the file
        BFile file(&ref, B_READ_ONLY);
        BResources resources;
        if (resources.SetTo(&file) != B_OK)
                return;
        file.Unset();
        
        resources.PreloadResourceType();
        
        int32 index = 0;
        ResDataRow *row;
        ResourceData *resData = new ResourceData();
        while (resData->SetFromResource(index, resources)) {
                row = new ResDataRow(resData);
                fListView->AddRow(row);
                fDataList.AddItem(resData);
                resData = new ResourceData();
                index++;
        }
        delete resData;

        BNode node;
        if (node.SetTo(&ref) == B_OK) {
                char attrName[B_ATTR_NAME_LENGTH];
                node.RewindAttrs();
                resData = new ResourceData();
                while (node.GetNextAttrName(attrName) == B_OK) {
                        if (resData->SetFromAttribute(attrName, node)) {
                                row = new ResDataRow(resData);
                                fListView->AddRow(row);
                                fDataList.AddItem(resData);
                                resData = new ResourceData();
                        }
                }
                delete resData;
        }
}


void
ResView::SaveFile(void)
{
        if (fSaveStatus == FILE_CLEAN || !fRef)
                return;
        
        BFile file(fRef,B_READ_WRITE);
        BResources res(&file,true);
        file.Unset();
        
        for (int32 i = 0; i < fListView->CountRows(); i++) {
                ResDataRow *row = (ResDataRow*)fListView->RowAt(i);
                ResourceData *data = row->GetData();
                res.AddResource(data->GetType(), data->GetID(), data->GetData(),
                                                data->GetLength(), data->GetName());
        }
        
        res.Sync();
        
        if (fSaveStatus == FILE_QUIT_AFTER_SAVE && Window())
                Window()->PostMessage(B_QUIT_REQUESTED);
        SetSaveStatus(FILE_CLEAN);
}


void
ResView::SaveAndQuit(void)
{
        SetSaveStatus(FILE_QUIT_AFTER_SAVE);
        if (!fRef) {
                fSavePanel->Show();
                return;
        }
        
        SaveFile();
}


void
ResView::BuildMenus(BMenuBar *menuBar)
{
        BMenu *menu = new BMenu("File");
        menu->AddItem(new BMenuItem("New" B_UTF8_ELLIPSIS, new BMessage(M_NEW_FILE), 'N'));
        menu->AddSeparatorItem();
        menu->AddItem(new BMenuItem("Open" B_UTF8_ELLIPSIS, new BMessage(M_OPEN_FILE), 'O'));
        menu->AddSeparatorItem();
        menu->AddItem(new BMenuItem("Save", new BMessage(M_SAVE_FILE), 'S'));
        menu->AddItem(new BMenuItem("Save As" B_UTF8_ELLIPSIS, new BMessage(M_SHOW_SAVE_PANEL), 'S',
                                                                B_COMMAND_KEY | B_SHIFT_KEY));
        menuBar->AddItem(menu);
        
        menu = new BMenu("Resource");
        menu->AddItem(new BMenuItem("Add" B_UTF8_ELLIPSIS, new BMessage(M_SELECT_FILE), 'F'));
        menu->AddItem(new BMenuItem("Delete", new BMessage(M_DELETE_RESOURCE), 'D'));
        menuBar->AddItem(menu);
}


void
ResView::EmptyDataList(void)
{
        for (int32 i = 0; i < fDataList.CountItems(); i++) {
                ResourceData *data = (ResourceData*) fDataList.ItemAt(i);
                delete data;
        }
        fDataList.MakeEmpty();
}


void
ResView::UpdateRow(BRow *row)
{
        TypeCodeField *typeField = (TypeCodeField*) row->GetField(1);
        ResourceData *resData = typeField->GetResourceData();
        BStringField *strField = (BStringField *)row->GetField(0);
        
        if (strcmp("(attr)", strField->String()) != 0)
                strField->SetString(resData->GetIDString());
        
        strField = (BStringField *)row->GetField(2);
        if (strField)
                strField->SetString(resData->GetName());
        
        PreviewField *preField = (PreviewField*)row->GetField(3);
        if (preField)
                preField->SetData(resData->GetData(), resData->GetLength());
        
        BSizeField *sizeField = (BSizeField*)row->GetField(4);
        if (sizeField)
                sizeField->SetSize(resData->GetLength());
}


void
ResView::AddResource(const entry_ref &ref)
{
        BFile file(&ref, B_READ_ONLY);
        if (file.InitCheck() != B_OK)
                return;
        
        BString mime;
        file.ReadAttrString("BEOS:TYPE", &mime);
        
        if (mime == "application/x-be-resource") {
                BMessage msg(B_REFS_RECEIVED);
                msg.AddRef("refs", &ref);
                be_app->PostMessage(&msg);
                return;
        }
        
        type_code fileType = 0;
        
        BTranslatorRoster *roster = BTranslatorRoster::Default();
        translator_info info;
        if (roster->Identify(&file, NULL, &info, 0, mime.String()) == B_OK)
                fileType = info.type;
        else
                fileType = B_RAW_TYPE;
        
        int32 lastID = -1;
        for (int32 i = 0; i < fDataList.CountItems(); i++) {
                ResourceData *resData = (ResourceData*)fDataList.ItemAt(i);
                if (resData->GetType() == fileType && resData->GetID() > lastID)
                        lastID = resData->GetID();
        }
        
        off_t fileSize;
        file.GetSize(&fileSize);
        
        if (fileSize < 1)
                return;
        
        char *fileData = (char *)malloc(fileSize);
        file.Read(fileData, fileSize);
        
        ResourceData *resData = new ResourceData(fileType, lastID + 1, ref.name,
                                                                                        fileData, fileSize);
        fDataList.AddItem(resData);
        
        ResDataRow *row = new ResDataRow(resData);
        fListView->AddRow(row);
        
        SetSaveStatus(FILE_DIRTY);
}


void
ResView::DeleteSelectedResources(void)
{
        ResDataRow *selection = (ResDataRow*)fListView->CurrentSelection();
        if (!selection)
                return;
        
        SetSaveStatus(FILE_DIRTY);
        
        while (selection) {
                ResourceData *data = selection->GetData();
                fListView->RemoveRow(selection);
                fDataList.RemoveItem(data);
                delete data;
                selection = (ResDataRow*)fListView->CurrentSelection();
        }
}


void
ResView::SetSaveStatus(uint8 value)
{
        if (value == fSaveStatus)
                return;
        
        fSaveStatus = value;
        
        BString title("ResEdit: ");
        title << fFileName;
        if (fSaveStatus == FILE_DIRTY)
                title << "*";
        
        if (Window()) {
                Window()->Lock();
                Window()->SetTitle(title.String());
                Window()->Unlock();
        }
}


ResDataRow::ResDataRow(ResourceData *data)
  :     fResData(data)
{
        if (data) {
                SetField(new BStringField(fResData->GetIDString()), 0);
                SetField(new TypeCodeField(fResData->GetType(), fResData), 1);
                SetField(new BStringField(fResData->GetName()), 2);
                BField *field = gResRoster.MakeFieldForType(fResData->GetType(),
                                                                                                        fResData->GetData(),
                                                                                                        fResData->GetLength());
                if (field)
                        SetField(field, 3);
                SetField(new BSizeField(fResData->GetLength()), 4);
        }
}
        

ResourceData *
ResDataRow::GetData(void) const
{
        return fResData;
}