root/src/apps/cortex/RouteApp/RouteApp.cpp
/*
 * Copyright (c) 1999-2000, Eric Moon.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions, and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions, and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


// RouteApp.cpp
// e.moon 14may99

#include "RouteApp.h"
#include "RouteWindow.h"
#include "DormantNodeWindow.h"
#include "MediaRoutingView.h"
#include "MediaNodePanel.h"

#include "RouteAppNodeManager.h"
#include "NodeRef.h"

#include "TipManager.h"

#include "AddOnHost.h"

#include "route_app_io.h"
#include "XML.h"
#include "MessageIO.h"
#include "NodeSetIOContext.h"

#include <Debug.h>
#include <OS.h>
#include <Roster.h>
#include <Directory.h>
#include <FindDirectory.h>
#include <NodeInfo.h>
#include <Path.h>
#include <Entry.h>

extern "C" void SetNewLeakChecking(bool);
extern "C" void SetMallocLeakChecking(bool);

using namespace std;

__USE_CORTEX_NAMESPACE

const char* const               RouteApp::s_settingsDirectory = "Cortex";
const char* const               RouteApp::s_settingsFile = "cortex_settings";

const char* const               RouteApp::s_appSignature = "application/x-vnd.Cortex.Route";

BMimeType                                               RouteApp::s_nodeSetType("text/x-vnd.Cortex.NodeSet");

const char* const               RouteApp::s_rootElement = "cortex_settings";
const char* const               RouteApp::s_mediaRoutingViewElement = "MediaRoutingView";
const char* const               RouteApp::s_routeWindowElement = "RouteWindow";

// -------------------------------------------------------- //
// ctor/dtor
// -------------------------------------------------------- //

RouteApp::~RouteApp() {
//      PRINT((
//              "RouteApp::~RouteApp()\n"));

        ASSERT(manager);
        thread_id id = manager->Thread();
        manager->release();
        
//      PRINT((
//              "- waiting for manager to die\n"));
        if(id >= B_OK) {
                status_t err;
                while(wait_for_thread(id, &err) == B_INTERRUPTED) {
                        PRINT((" * RouteApp::~RouteApp(): B_INTERRUPTED\n"));
                }
        }
//      PRINT((
//              "- RouteApp done.\n"));

        // [e.moon 6nov99] kill off the AddOnHost app, if any
        AddOnHost::Kill();
        
        if(m_settingsDocType)
                delete m_settingsDocType;
                
        if(m_nodeSetDocType)
                delete m_nodeSetDocType;
}

RouteApp::RouteApp() :
        BApplication(s_appSignature),
        manager(new RouteAppNodeManager(true)),
        routeWindow(0),
        m_settingsDocType(_createSettingsDocType()),
        m_nodeSetDocType(_createNodeSetDocType()),
        m_openPanel(B_OPEN_PANEL),
        m_savePanel(B_SAVE_PANEL) {
        
        // register MIME type(s)
        _InitMimeTypes();
        
        // create the window hierarchy
        RouteWindow*& r = const_cast<RouteWindow*&>(routeWindow);
        r = new RouteWindow(manager);
        
        // restore settings
        _readSettings();
        
        // fit windows to screen
        routeWindow->constrainToScreen();

        // show main window & palettes  
        routeWindow->Show();
}


bool
RouteApp::QuitRequested()
{
        // [e.moon 20oct99] make sure the main window is dead before quitting

        // store window positions & other settings
        // write settings file
        _writeSettings();

        routeWindow->_closePalettes();
        routeWindow->Lock();
        routeWindow->Quit();
        RouteWindow*& r = const_cast<RouteWindow*&>(routeWindow);
        r = 0;
                
        // clean up the TipManager [e.moon 19oct99]
        TipManager::QuitInstance();

        return true;
}

// -------------------------------------------------------- //
// *** BHandler
// -------------------------------------------------------- //

void RouteApp::MessageReceived(
        BMessage*                                                                       message) {
        
        status_t err;
        
        entry_ref ref;
        const char* name;
        
        switch(message->what) {
                
                case M_SHOW_OPEN_PANEL:
                        m_openPanel.Show();
                        break;

                case M_SHOW_SAVE_PANEL:
                        m_savePanel.Show();
                        break;

                case B_SAVE_REQUESTED: {
                        err = message->FindRef("directory", &ref);
                        if(err < B_OK)
                                break;
                        err = message->FindString("name", &name);
                        if(err < B_OK)
                                break;
                        
                        _writeSelectedNodeSet(&ref, name);

                        m_savePanel.GetPanelDirectory(&ref);
                        BEntry e(&ref);
                        m_lastIODir.SetTo(&e);
                        break;
                }
                
                default:
                        _inherited::MessageReceived(message);
        }
}

// -------------------------------------------------------- //
// *** BApplication
// -------------------------------------------------------- //

void RouteApp::RefsReceived(
        BMessage*                                                                       message) {

        PRINT(("### RefsReceived\n"));

        status_t err;
        
        entry_ref ref;

        for(int32 n = 0; ; ++n) {       
                err = message->FindRef("refs", n, &ref);
                if(err < B_OK)
                        break;
                
                _readNodeSet(&ref);
                
                m_openPanel.GetPanelDirectory(&ref);
                BEntry e(&ref);
                m_lastIODir.SetTo(&e);
        }
}

// -------------------------------------------------------- //
// *** IPersistent
// -------------------------------------------------------- //

// EXPORT

void RouteApp::xmlExportBegin(
        ExportContext&                                          context) const {
        context.beginElement(s_rootElement);
}
        
void RouteApp::xmlExportAttributes(
        ExportContext&                                          context) const {} //nyi: write version info +++++

// +++++
void RouteApp::xmlExportContent(
        ExportContext&                                          context) const {

        context.beginContent();

        // export app settings
        {
                BMessage m;
                exportState(&m);
                MessageIO io(&m);
                status_t err __attribute__((unused)) = context.writeObject(&io);
                ASSERT(err == B_OK);
        }
        
        if(routeWindow) {
                // export main routing window (frame/palette) settings
                context.beginElement(s_routeWindowElement);
                context.beginContent();
                BMessage m;
                if (routeWindow->Lock()) {
                        routeWindow->exportState(&m);
                        routeWindow->Unlock();
                }
                MessageIO io(&m);
                context.writeObject(&io);
                context.endElement();

                // export routing view (content) settings
                m.MakeEmpty();
                ASSERT(routeWindow->m_routingView);
                context.beginElement(s_mediaRoutingViewElement);
                context.beginContent();
                routeWindow->m_routingView->exportState(&m);
                context.writeObject(&io);
                context.endElement();
        }       
}
        
void RouteApp::xmlExportEnd(
        ExportContext&                                          context) const {
        context.endElement();
}

// IMPORT

void RouteApp::xmlImportBegin(
        ImportContext&                                          context) {

        m_readState = _READ_ROOT;
}

void RouteApp::xmlImportAttribute(
        const char*                                                             key,
        const char*                                                             value,
        ImportContext&                                          context) {} //nyi
        
void RouteApp::xmlImportContent(
        const char*                                                             data,
        uint32                                                                          length,
        ImportContext&                                          context) {} //nyi
        
void RouteApp::xmlImportChild(
        IPersistent*                                                    child,
        ImportContext&                                          context) {
        
        MessageIO* io = dynamic_cast<MessageIO*>(child);
        if(io) {
                ASSERT(io->message());
//              PRINT(("* RouteApp::xmlImportChild() [flat message]:\n"));
//              io->message()->PrintToStream();
                
                switch(m_readState) {
                        case _READ_ROOT:
                                importState(io->message());
                                break;
                        
                        case _READ_ROUTE_WINDOW:
                                ASSERT(routeWindow);
                                routeWindow->importState(io->message());
                                break;
                                
                        case _READ_MEDIA_ROUTING_VIEW:
                                ASSERT(routeWindow);
                                ASSERT(routeWindow->m_routingView);
                                routeWindow->m_routingView->importState(io->message());
                                break;

                        default:
                                PRINT(("! RouteApp::xmlImportChild(): unimplemented target\n"));
                                break;
                }
        }
}
        
void RouteApp::xmlImportComplete(
        ImportContext&                                          context) {} //nyi

void RouteApp::xmlImportChildBegin(
        const char*                                                             name,
        ImportContext&                                          context) {

        if(m_readState != _READ_ROOT) {
                context.reportError("RouteApp import: invalid nested element");
                return;
        }

        if(!strcmp(name, s_routeWindowElement)) {
                m_readState = _READ_ROUTE_WINDOW;
        }
        else if(!strcmp(name, s_mediaRoutingViewElement)) {
                m_readState = _READ_MEDIA_ROUTING_VIEW;
        }
        else {
                context.reportError("RouteApp import: unknown child element");
        }
}

void RouteApp::xmlImportChildComplete(
        const char*                                                             name,
        ImportContext&                                          context) {

        if(m_readState == _READ_ROOT) {
                context.reportError("RouteApp import: garbled state");
                return;
        }
        m_readState = _READ_ROOT;
}

// -------------------------------------------------------- //
// *** IStateArchivable
// -------------------------------------------------------- //

status_t RouteApp::importState(
        const BMessage*                                         archive) {

        const char* last;
        if(archive->FindString("lastDir", &last) == B_OK) {
                m_lastIODir.SetTo(last);
                m_openPanel.SetPanelDirectory(last);
                m_savePanel.SetPanelDirectory(last);
        }
        
        return B_OK;
}

status_t RouteApp::exportState(
        BMessage*                                                                       archive) const {
        
        if(m_lastIODir.InitCheck() == B_OK)
                archive->AddString("lastDir", m_lastIODir.Path());
        
        return B_OK;
}

// -------------------------------------------------------- //
// implementation
// -------------------------------------------------------- //

XML::DocumentType* RouteApp::_createSettingsDocType() {

        XML::DocumentType* docType = new XML::DocumentType(
                s_rootElement);
        MessageIO::AddTo(docType);
        
        return docType;
}

XML::DocumentType* RouteApp::_createNodeSetDocType() {

        XML::DocumentType* docType = new XML::DocumentType(
                _NODE_SET_ELEMENT);
        RouteAppNodeManager::AddTo(docType);
        
        return docType;
}

status_t RouteApp::_readSettings() {

        // figure path
        BPath path;
        status_t err = find_directory(
                B_USER_SETTINGS_DIRECTORY,
                &path);
        ASSERT(err == B_OK);
        
        path.Append(s_settingsDirectory);
        BEntry entry(path.Path());
        if(!entry.Exists())
                return B_ENTRY_NOT_FOUND;

        path.Append(s_settingsFile);
        entry.SetTo(path.Path());
        if(!entry.Exists())
                return B_ENTRY_NOT_FOUND;

        // open the settings file
        BFile file(&entry, B_READ_ONLY);
        if(file.InitCheck() != B_OK)
                return file.InitCheck();
        
        // read it:
        list<BString> errors;
        err = XML::Read(
                &file,
                this,
                m_settingsDocType,
                &errors);

        if(errors.size()) {
                fputs("!!! RouteApp::_readSettings():", stderr);
                for(list<BString>::iterator it = errors.begin();
                        it != errors.end(); ++it)
                        fputs((*it).String(), stderr);
        }
        return err;                     
}

status_t RouteApp::_writeSettings() {
        // figure path, creating settings folder if necessary
        BPath path;
        status_t err = find_directory(
                B_USER_SETTINGS_DIRECTORY,
                &path);
        ASSERT(err == B_OK);

        BDirectory baseDirectory, settingsDirectory;
        
        err = baseDirectory.SetTo(path.Path());
        if(err < B_OK)
                return err;
        
        path.Append(s_settingsDirectory);

        BEntry folderEntry(path.Path());
        if(!folderEntry.Exists()) {
                // create folder
                err = baseDirectory.CreateDirectory(s_settingsDirectory, &settingsDirectory);
                ASSERT(err == B_OK);
        }
        else
                settingsDirectory.SetTo(&folderEntry);
                
        // open/clobber file
        BFile file(
                &settingsDirectory,
                s_settingsFile,
                B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
        err = file.InitCheck();
        if(err < B_OK)
                return err;

        // write document header
        const char* header = "<?xml version=\"1.0\"?>\n";
        file.Write((const void*)header, strlen(header));
        
        // write content
        BString errorText;
        err = XML::Write(
                &file,
                this,
                &errorText);
        
        if(err < B_OK) {
                fprintf(stderr,
                        "!!! RouteApp::_writeSettings() failed: %s\n",
                        errorText.String());
        }

        return err;
}

// -------------------------------------------------------- //

class _RouteAppImportContext :
        public  ImportContext,
        public  NodeSetIOContext {

public:
        _RouteAppImportContext(
                list<BString>&                                                  errors,
                MediaRoutingView*                                               routingView) :
                ImportContext(errors),
                m_routingView(routingView) {}

public:                                                                                                 // *** hooks
        virtual void importUIState(
                const BMessage*                                                 archive) {
        
                PRINT((
                        "### importUIState\n"));        
                
                if(m_routingView) {
//                      m_routingView->LockLooper();
                        m_routingView->DeselectAll();
                        status_t err = m_routingView->importStateFor(
                                this,
                                archive);
                        if(err < B_OK) {
                                PRINT((
                                        "!!! _RouteAppImportContext::importStateFor() failed:\n"
                                        "    %s\n", strerror(err)));
                        }
                        m_routingView->Invalidate(); // +++++ not particularly clean
//                      m_routingView->UnlockLooper();
                }
        }
        
        MediaRoutingView*                                                       m_routingView;
};

status_t RouteApp::_readNodeSet(
        entry_ref*                                                              ref) {

        BFile file(ref, B_READ_ONLY);
        status_t err = file.InitCheck();
        if(err < B_OK)
                return err;

        routeWindow->Lock();
        
        list<BString> errors;

        err = XML::Read(
                &file,
                manager,
                m_nodeSetDocType,
                new _RouteAppImportContext(errors, routeWindow->m_routingView));

        routeWindow->Unlock();

        if(errors.size()) {
                fputs("!!! RouteApp::_readNodeSet():", stderr);
                for(list<BString>::iterator it = errors.begin();
                        it != errors.end(); ++it)
                        fputs((*it).String(), stderr);
        }
        return err;                     
}

// -------------------------------------------------------- //

class _RouteAppExportContext :
        public  ExportContext,
        public  NodeSetIOContext {

public:
        _RouteAppExportContext(
                MediaRoutingView*                                               routingView) :
                m_routingView(routingView) {}

public:                                                                                                 // *** hooks
        virtual void exportUIState(
                BMessage*                                                                               archive) {
        
                PRINT((
                        "### exportUIState\n"));        

                if(m_routingView) {
                        m_routingView->LockLooper();
                        m_routingView->exportStateFor(
                                this,
                                archive);
                        m_routingView->UnlockLooper();
                }
        }

        MediaRoutingView*                                                       m_routingView;
};

status_t RouteApp::_writeSelectedNodeSet(
        entry_ref*                                                              dirRef,
        const char*                                                             filename) {
        
        status_t err;


        // sanity-check & fetch the selection
        routeWindow->Lock();
        
        MediaRoutingView* v = routeWindow->m_routingView;
        ASSERT(v);

        if(
                v->CountSelectedItems() < 0 ||
                v->SelectedType() != DiagramItem::M_BOX) {
                PRINT((
                        "!!! RouteApp::_writeSelectedNodeSet():\n"
                        "    Invalid selection!\n"));
                        
                routeWindow->Unlock();
                return B_NOT_ALLOWED;
        }

        _RouteAppExportContext context(v);
        
        for(uint32 i = 0; i < v->CountSelectedItems(); ++i) {
                MediaNodePanel* panel = dynamic_cast<MediaNodePanel*>(v->SelectedItemAt(i));
                if(!panel)
                        continue;
                err = context.addNode(panel->ref->id());
                if(err < B_OK) {
                        PRINT(( 
                                "!!! context.addNode() failed: '%s\n", strerror(err)));
                }
        }
        routeWindow->Unlock();

        // open/clobber file
        BDirectory dir(dirRef);
        err = dir.InitCheck();
        if(err < B_OK)
                return err;

        BFile file(
                &dir,
                filename,
                B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
        err = file.InitCheck();
        if(err < B_OK)
                return err;
        
        // write document header
        const char* header = "<?xml version=\"1.0\"?>\n";
        file.Write((const void*)header, strlen(header));
        
        // export nodes
        context.stream = &file;
        err = context.writeObject(manager);
        if(err < B_OK) {
                PRINT((
                        "!!! RouteApp::_writeSelectedNodeSet(): error:\n"
                        "    %s\n", context.errorText()));

                // +++++ delete the malformed file
                
        }


        // write MIME type
        BNodeInfo* fileInfo = new BNodeInfo(&file);
        fileInfo->SetType(s_nodeSetType.Type());
        fileInfo->SetPreferredApp(s_appSignature);
        delete fileInfo;        
        
        return B_OK;
}

/*static*/
status_t RouteApp::_InitMimeTypes() {
        
        status_t err;
        
        ASSERT(s_nodeSetType.IsValid());
        
        if(!s_nodeSetType.IsInstalled()) {
                err = s_nodeSetType.Install();
                if(err < B_OK) {
                        PRINT((
                                "!!! RouteApp::_InitMimeTypes(): Install():\n"
                                "    %s\n", strerror(err)));
                        return err;
                }
                
                err = s_nodeSetType.SetPreferredApp(s_appSignature);
                if(err < B_OK) {
                        PRINT((
                                "!!! RouteApp::_InitMimeTypes(): SetPreferredApp():\n"
                                "    %s\n", strerror(err)));
                        return err;
                }
        }
        
        return B_OK;
}

// -------------------------------------------------------- //
// main() stub
// -------------------------------------------------------- //

int main(int argc, char** argv) {
//      SetNewLeakChecking(true);
//      SetMallocLeakChecking(true);
        
        RouteApp app;
        app.Run();

        return 0;
}

// END -- RouteApp.cpp --