/***************************************************************************
 *   Copyright (c) 2004 Jürgen Riegel <juergen.riegel@web.de>              *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  is distributed in the hope that it will be useful,      *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"

#ifndef _PreComp_
# include "InventorAll.h"
# include <boost/signals.hpp>
# include <boost/bind.hpp>
# include <sstream>
# include <stdexcept>
# include <QCloseEvent>
# include <QDir>
# include <QFileInfo>
# include <QLocale>
# include <QMessageBox>
# include <QPointer>
# include <QGLFormat>
# include <QGLPixelBuffer>
#if QT_VERSION >= 0x040200
# include <QGLFramebufferObject>
#endif
# include <QSessionManager>
# include <QStatusBar>
# include <QTextStream>
# include <QTimer>
#endif

#include <boost/interprocess/sync/file_lock.hpp>


// FreeCAD Base header
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <Base/Parameter.h>
#include <Base/Exception.h>
#include <Base/Factory.h>
#include <Base/FileInfo.h>
#include <Base/Tools.h>
#include <Base/UnitsApi.h>
#include <App/Document.h>
#include <App/DocumentObjectPy.h>

#include "Application.h"
#include "AutoSaver.h"
#include "GuiApplication.h"
#include "MainWindow.h"
#include "Document.h"
#include "View.h"
#include "View3DPy.h"
#include "WidgetFactory.h"
#include "Command.h"
#include "Macro.h"
#include "ProgressBar.h"
#include "Workbench.h"
#include "WorkbenchManager.h"
#include "ToolBoxManager.h"
#include "WaitCursor.h"
#include "MenuManager.h"
#include "Window.h"
#include "Selection.h"
#include "BitmapFactory.h"
#include "SoFCDB.h"
#include "PythonConsolePy.h"
#include "PythonDebugger.h"
#include "View3DPy.h"
#include "DlgOnlineHelpImp.h"
#include "SpaceballEvent.h"
#include "Control.h"
#include "DocumentRecovery.h"
#include "TaskView/TaskView.h"

#include "SplitView3DInventor.h"
#include "View3DInventor.h"
#include "ViewProvider.h"
#include "ViewProviderExtern.h"
#include "ViewProviderFeature.h"
#include "ViewProviderPythonFeature.h"
#include "ViewProviderDocumentObjectGroup.h"
#include "ViewProviderGeometryObject.h"
#include "ViewProviderInventorObject.h"
#include "ViewProviderVRMLObject.h"
#include "ViewProviderAnnotation.h"
#include "ViewProviderMeasureDistance.h"
#include "ViewProviderPlacement.h"
#include "ViewProviderPlane.h"
#include "ViewProviderMaterialObject.h"

#include "Language/Translator.h"
#include "TaskView/TaskDialogPython.h"
#include <Gui/Quarter/Quarter.h>
#include "View3DViewerPy.h"
#include "GuiInitScript.h"


using namespace Gui;
using namespace Gui::DockWnd;
using namespace std;


Application* Application::Instance = 0L;

namespace Gui {

// Pimpl class
struct ApplicationP
{
    ApplicationP() : 
    activeDocument(0L), 
    isClosing(false), 
    startingUp(true)
    {
        // create the macro manager
        macroMngr = new MacroManager();
    }

    ~ApplicationP()
    {
        delete macroMngr;
    }

    /// list of all handled documents
    std::map<const App::Document*, Gui::Document*> documents;
    /// Active document
    Gui::Document*   activeDocument;
    MacroManager*  macroMngr;
    /// List of all registered views
    std::list<Gui::BaseView*> passive;
    bool isClosing;
    bool startingUp;
    /// Handles all commands 
    CommandManager commandManager;
};

/** Observer that watches relabeled objects and make sure that the labels inside
 * a document are unique.
 * @note In the FreeCAD design it is explicitly allowed to have duplicate labels
 * (i.e. the user visible text e.g. in the tree view) while the internal names
 * are always guaranteed to be unique.
 */
class ObjectLabelObserver
{
public:
    /// The one and only instance.
    static ObjectLabelObserver* instance();
    /// Destructs the sole instance.
    static void destruct ();

    /** Checks the new label of the object and relabel it if needed
     * to make it unique document-wide
     */
    void slotRelabelObject(const App::DocumentObject&, const App::Property&);

private:
    static ObjectLabelObserver* _singleton;

    ObjectLabelObserver();
    ~ObjectLabelObserver();
    const App::DocumentObject* current;
    ParameterGrp::handle _hPGrp;
};

ObjectLabelObserver* ObjectLabelObserver::_singleton = 0;

ObjectLabelObserver* ObjectLabelObserver::instance()
{
    if (!_singleton)
        _singleton = new ObjectLabelObserver;
    return _singleton;
}

void ObjectLabelObserver::destruct ()
{
    delete _singleton;
    _singleton = 0;
}

void ObjectLabelObserver::slotRelabelObject(const App::DocumentObject& obj, const App::Property& prop)
{
    // observe only the Label property
    if (&prop == &obj.Label) {
        // have we processed this (or another?) object right now?
        if (current) {
            return;
        }

        std::string label = obj.Label.getValue();
        App::Document* doc = obj.getDocument();
        if (doc && !_hPGrp->GetBool("DuplicateLabels")) {
            std::vector<std::string> objectLabels;
            std::vector<App::DocumentObject*>::const_iterator it;
            std::vector<App::DocumentObject*> objs = doc->getObjects();
            bool match = false;

            for (it = objs.begin();it != objs.end();++it) {
                if (*it == &obj)
                    continue; // don't compare object with itself
                std::string objLabel = (*it)->Label.getValue();
                if (!match && objLabel == label)
                    match = true;
                objectLabels.push_back(objLabel);
            }

            // make sure that there is a name conflict otherwise we don't have to do anything
            if (match && !label.empty()) {
                // remove number from end to avoid lengthy names
                size_t lastpos = label.length()-1;
                while (label[lastpos] >= 48 && label[lastpos] <= 57) {
                    // if 'lastpos' becomes 0 then all characters are digits. In this case we use
                    // the complete label again
                    if (lastpos == 0) {
                        lastpos = label.length()-1;
                        break;
                    }
                    lastpos--;
                }

                label = label.substr(0, lastpos+1);
                label = Base::Tools::getUniqueName(label, objectLabels, 3);
                this->current = &obj;
                const_cast<App::DocumentObject&>(obj).Label.setValue(label);
                this->current = 0;
            }
        }
    }
}

ObjectLabelObserver::ObjectLabelObserver() : current(0)
{
    App::GetApplication().signalChangedObject.connect(boost::bind
        (&ObjectLabelObserver::slotRelabelObject, this, _1, _2));
    _hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp");
    _hPGrp = _hPGrp->GetGroup("Preferences")->GetGroup("Document");
}

ObjectLabelObserver::~ObjectLabelObserver()
{
}

static PyObject *
FreeCADGui_subgraphFromObject(PyObject * /*self*/, PyObject *args)
{
    PyObject *o;
    if (!PyArg_ParseTuple(args, "O!",&(App::DocumentObjectPy::Type), &o))
        return NULL;
    App::DocumentObject* obj = static_cast<App::DocumentObjectPy*>(o)->getDocumentObjectPtr();
    std::string vp = obj->getViewProviderName();
    SoNode* node = 0;
    try {
        Base::BaseClass* base = static_cast<Base::BaseClass*>(Base::Type::createInstanceByName(vp.c_str(), true));
        if (base && base->getTypeId().isDerivedFrom(Gui::ViewProviderDocumentObject::getClassTypeId())) {
            std::auto_ptr<Gui::ViewProviderDocumentObject> vp(static_cast<Gui::ViewProviderDocumentObject*>(base));
            std::map<std::string, App::Property*> Map;
            obj->getPropertyMap(Map);
            vp->attach(obj);
            for (std::map<std::string, App::Property*>::iterator it = Map.begin(); it != Map.end(); ++it) {
                vp->updateData(it->second);
            }

            std::vector<std::string> modes = vp->getDisplayModes();
            if (!modes.empty())
                vp->setDisplayMode(modes.front().c_str());
            node = vp->getRoot()->copy();
            node->ref();
            std::string type = "So";
            type += node->getTypeId().getName().getString();
            type += " *";
            PyObject* proxy = 0;
            proxy = Base::Interpreter().createSWIGPointerObj("pivy.coin", type.c_str(), (void*)node, 1);
            return Py::new_reference_to(Py::Object(proxy, true));
        }
    }
    catch (const Base::Exception& e) {
        if (node) node->unref();
        PyErr_SetString(PyExc_RuntimeError, e.what());
        return 0;
    }

    Py_INCREF(Py_None);
    return Py_None;
}

static PyObject *
FreeCADGui_getSoDBVersion(PyObject * /*self*/, PyObject *args)
{
    if (!PyArg_ParseTuple(args, ""))
        return NULL;
    return PyString_FromString(SoDB::getVersion());
}

struct PyMethodDef FreeCADGui_methods[] = {
    {"subgraphFromObject",FreeCADGui_subgraphFromObject,METH_VARARGS,
     "subgraphFromObject(object) -> Node\n\n"
     "Return the Inventor subgraph to an object"},
    {"getSoDBVersion",FreeCADGui_getSoDBVersion,METH_VARARGS,
     "getSoDBVersion() -> String\n\n"
     "Return a text string containing the name\n"
     "of the Coin library and version information"},
    {NULL, NULL}  /* sentinel */
};

} // namespace Gui

Application::Application(bool GUIenabled)
{
    //App::GetApplication().Attach(this);
    if (GUIenabled) {
        App::GetApplication().signalNewDocument.connect(boost::bind(&Gui::Application::slotNewDocument, this, _1));
        App::GetApplication().signalDeleteDocument.connect(boost::bind(&Gui::Application::slotDeleteDocument, this, _1));
        App::GetApplication().signalRenameDocument.connect(boost::bind(&Gui::Application::slotRenameDocument, this, _1));
        App::GetApplication().signalActiveDocument.connect(boost::bind(&Gui::Application::slotActiveDocument, this, _1));
        App::GetApplication().signalRelabelDocument.connect(boost::bind(&Gui::Application::slotRelabelDocument, this, _1));


        // install the last active language
        ParameterGrp::handle hPGrp = App::GetApplication().GetUserParameter().GetGroup("BaseApp");
        hPGrp = hPGrp->GetGroup("Preferences")->GetGroup("General");
        QString lang = QLocale::languageToString(QLocale::system().language());
        Translator::instance()->activateLanguage(hPGrp->GetASCII("Language", (const char*)lang.toLatin1()).c_str());
        GetWidgetFactorySupplier();

        ParameterGrp::handle hUnits = App::GetApplication().GetParameterGroupByPath
            ("User parameter:BaseApp/Preferences/Units");
        Base::UnitsApi::setDecimals(hUnits->GetInt("Decimals", Base::UnitsApi::getDecimals()));

        // Check for the symbols for group separator and deciaml point. They must be different otherwise
        // Qt doesn't work properly.
#if defined(Q_OS_WIN32)
        if (QLocale::system().groupSeparator() == QLocale::system().decimalPoint()) {
            QMessageBox::critical(0, QLatin1String("Invalid system settings"),
                QLatin1String("Your system uses the same symbol for decimal point and group separator.\n\n"
                              "This causes serious problems and makes the application fail to work properly.\n"
                              "Go to the system configuration panel of the OS and fix this issue, please."));
            throw Base::Exception("Invalid system settings");
        }
#endif
#if 0 // QuantitySpinBox and InputField try to handle the group separator now
        // http://forum.freecadweb.org/viewtopic.php?f=10&t=6910
        // A workaround is to disable the group separator for double-to-string conversion, i.e.
        // setting the flag 'OmitGroupSeparator'.
        QLocale loc = QLocale::system();
        loc.setNumberOptions(QLocale::OmitGroupSeparator);
        QLocale::setDefault(loc);
#endif

        // setting up Python binding
        Base::PyGILStateLocker lock;
        PyObject* module = Py_InitModule3("FreeCADGui", Application::Methods,
            "The functions in the FreeCADGui module allow working with GUI documents,\n"
            "view providers, views, workbenches and much more.\n\n"
            "The FreeCADGui instance provides a list of references of GUI documents which\n"
            "can be addressed by a string. These documents contain the view providers for\n"
            "objects in the associated App document. An App and GUI document can be\n"
            "accessed with the same name.\n\n"
            "The FreeCADGui module also provides a set of functions to work with so called\n"
            "workbenches.");
        Py::Module(module).setAttr(std::string("ActiveDocument"),Py::None());

        UiLoaderPy::init_type();
        Base::Interpreter().addType(UiLoaderPy::type_object(),
            module,"UiLoader");
        PyResource::init_type();

        // PySide additions
        PySideUicModule* pySide = new PySideUicModule();
        Py_INCREF(pySide->module().ptr());
        PyModule_AddObject(module, "PySideUic", pySide->module().ptr());

        //insert Selection module
        PyObject* pSelectionModule = Py_InitModule3("Selection", SelectionSingleton::Methods,
            "Selection module");
        Py_INCREF(pSelectionModule);
        PyModule_AddObject(module, "Selection", pSelectionModule);

        SelectionFilterPy::init_type();
        Base::Interpreter().addType(SelectionFilterPy::type_object(),
            pSelectionModule,"Filter");

        Gui::TaskView::ControlPy::init_type();
        Py::Module(module).setAttr(std::string("Control"),
            Py::Object(Gui::TaskView::ControlPy::getInstance(), true));
    }

    Base::PyGILStateLocker lock;
    PyObject *module = PyImport_AddModule("FreeCADGui");
    PyMethodDef *meth = FreeCADGui_methods;
    PyObject *dict = PyModule_GetDict(module);
    for (; meth->ml_name != NULL; meth++) {
        PyObject *descr;
        descr = PyCFunction_NewEx(meth,0,0);
        if (descr == NULL)
            break;
        if (PyDict_SetItemString(dict, meth->ml_name, descr) != 0)
            break;
        Py_DECREF(descr);
    }

    // Python console binding
    PythonDebugModule           ::init_module();
    PythonStdout                ::init_type();
    PythonStderr                ::init_type();
    OutputStdout                ::init_type();
    OutputStderr                ::init_type();
    PythonStdin                 ::init_type();
    View3DInventorPy            ::init_type();
    View3DInventorViewerPy      ::init_type();

    d = new ApplicationP;

    // global access 
    Instance = this;

    // instanciate the workbench dictionary
    _pcWorkbenchDictionary = PyDict_New();

    createStandardOperations();
    MacroCommand::load();
    ObjectLabelObserver::instance();
}

Application::~Application()
{
    Base::Console().Log("Destruct Gui::Application\n");
    WorkbenchManager::destruct();
    SelectionSingleton::destruct();
    Translator::destruct();
    WidgetFactorySupplier::destruct();
    BitmapFactoryInst::destruct();

#if 0
    // we must run the garbage collector before shutting down the SoDB 
    // subsystem because we may reference some class objects of them in Python
    Base::Interpreter().cleanupSWIG("SoBase *");
    // finish also Inventor subsystem
    SoFCDB::finish();

#if (COIN_MAJOR_VERSION >= 2) && (COIN_MINOR_VERSION >= 4)
    SoDB::finish();
#elif (COIN_MAJOR_VERSION >= 3)
    SoDB::finish();
#else
    SoDB::cleanup();
#endif
#endif
    {
    Base::PyGILStateLocker lock;
    Py_DECREF(_pcWorkbenchDictionary);
    }

    // save macros
    MacroCommand::save();
    //App::GetApplication().Detach(this);

    delete d;
    Instance = 0;
}


//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
// creating std commands
//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

void Application::open(const char* FileName, const char* Module)
{
    WaitCursor wc;
    wc.setIgnoreEvents(WaitCursor::NoEvents);
    Base::FileInfo File(FileName);
    string te = File.extension();
    string unicodepath = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str());
    // if the active document is empty and not modified, close it
    // in case of an automatically created empty document at startup
    App::Document* act = App::GetApplication().getActiveDocument();
    Gui::Document* gui = this->getDocument(act);
    if (act && act->countObjects() == 0 && gui && gui->isModified() == false){
        Command::doCommand(Command::App, "App.closeDocument('%s')", act->getName());
        qApp->processEvents(); // an update is needed otherwise the new view isn't shown
    }

    if (Module != 0) {
        // issue module loading
        Command::doCommand(Command::App, "import %s", Module);
        try {
            // load the file with the module
            Command::doCommand(Command::App, "%s.open(u\"%s\")", Module, unicodepath.c_str());
            // ViewFit
            if (!File.hasExtension("FCStd") && sendHasMsgToActiveView("ViewFit")) {
                ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath
                    ("User parameter:BaseApp/Preferences/View");
                if (hGrp->GetBool("AutoFitToView", true))
                    Command::doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"ViewFit\")");
            }
            // the original file name is required
            getMainWindow()->appendRecentFile(QString::fromUtf8(File.filePath().c_str()));
        }
        catch (const Base::PyException& e){
            // Usually thrown if the file is invalid somehow
            e.ReportException();
        }
    }
    else {
        wc.restoreCursor();
        QMessageBox::warning(getMainWindow(), QObject::tr("Unknown filetype"),
            QObject::tr("Cannot open unknown filetype: %1").arg(QLatin1String(te.c_str())));
        wc.setWaitCursor();
        return;
    }
}

void Application::importFrom(const char* FileName, const char* DocName, const char* Module)
{
    WaitCursor wc;
    wc.setIgnoreEvents(WaitCursor::NoEvents);
    Base::FileInfo File(FileName);
    std::string te = File.extension();
    string unicodepath = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str());

    if (Module != 0) {
        // issue module loading
        Command::doCommand(Command::App, "import %s", Module);

        try {
            // load the file with the module
            if (File.hasExtension("FCStd")) {
                Command::doCommand(Command::App, "%s.open(u\"%s\")"
                                               , Module, unicodepath.c_str());
                if (activeDocument())
                    activeDocument()->setModified(false);
            }
            else {
                Command::doCommand(Command::App, "%s.insert(u\"%s\",\"%s\")"
                                               , Module, unicodepath.c_str(), DocName);
                ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath
                    ("User parameter:BaseApp/Preferences/View");
                if (hGrp->GetBool("AutoFitToView", true))
                    Command::doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"ViewFit\")");
                if (getDocument(DocName))
                    getDocument(DocName)->setModified(true);
            }

            // the original file name is required
            getMainWindow()->appendRecentFile(QString::fromUtf8(File.filePath().c_str()));
        }
        catch (const Base::PyException& e){
            // Usually thrown if the file is invalid somehow
            e.ReportException();
        }
    }
    else {
        wc.restoreCursor();
        QMessageBox::warning(getMainWindow(), QObject::tr("Unknown filetype"),
            QObject::tr("Cannot open unknown filetype: %1").arg(QLatin1String(te.c_str())));
        wc.setWaitCursor();
    }
}

void Application::exportTo(const char* FileName, const char* DocName, const char* Module)
{
    WaitCursor wc;
    Base::FileInfo File(FileName);
    std::string te = File.extension();
    string unicodepath = Base::Tools::escapedUnicodeFromUtf8(File.filePath().c_str());

    if (Module != 0) {
        try {
            std::vector<App::DocumentObject*> sel = Gui::Selection().getObjectsOfType
                (App::DocumentObject::getClassTypeId(),DocName);
            if (sel.empty()) {
                App::Document* doc = App::GetApplication().getDocument(DocName);
                sel = doc->getObjectsOfType(App::DocumentObject::getClassTypeId());
            }

            std::stringstream str;
            str << "__objs__=[]" << std::endl;
            for (std::vector<App::DocumentObject*>::iterator it = sel.begin(); it != sel.end(); ++it) {
                str << "__objs__.append(FreeCAD.getDocument(\"" << DocName << "\").getObject(\""
                    << (*it)->getNameInDocument() << "\"))" << std::endl;
            }

            str << "import " << Module << std::endl;
            str << Module << ".export(__objs__,u\"" << unicodepath << "\")" << std::endl;
            //str << "del __objs__" << std::endl;

            std::string code = str.str();
            // the original file name is required
            if (runPythonCode(code.c_str(), false)) {
                // search for a module that is able to open the exported file because otherwise
                // it doesn't need to be added to the recent files list (#0002047)
                std::map<std::string, std::string> importMap = App::GetApplication().getImportFilters(te.c_str());
                if (!importMap.empty())
                    getMainWindow()->appendRecentFile(QString::fromUtf8(File.filePath().c_str()));
            }

            // allow exporters to pass _objs__ to submodules before deleting it
            runPythonCode("del __objs__", false);
        }
        catch (const Base::PyException& e){
            // Usually thrown if the file is invalid somehow
            e.ReportException();
        }
    }
    else {
        wc.restoreCursor();
        QMessageBox::warning(getMainWindow(), QObject::tr("Unknown filetype"),
            QObject::tr("Cannot save to unknown filetype: %1").arg(QLatin1String(te.c_str())));
        wc.setWaitCursor();
    }
}

void Application::createStandardOperations()
{
    // register the application Standard commands from CommandStd.cpp
    Gui::CreateStdCommands();
    Gui::CreateDocCommands();
    Gui::CreateFeatCommands();
    Gui::CreateMacroCommands();
    Gui::CreateViewStdCommands();
    Gui::CreateWindowStdCommands();
    Gui::CreateTestCommands();
}

void Application::slotNewDocument(const App::Document& Doc)
{
#ifdef FC_DEBUG
    std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(&Doc);
    assert(it==d->documents.end());
#endif
    Gui::Document* pDoc = new Gui::Document(const_cast<App::Document*>(&Doc),this);
    d->documents[&Doc] = pDoc;

    // connect the signals to the application for the new document
    pDoc->signalNewObject.connect(boost::bind(&Gui::Application::slotNewObject, this, _1));
    pDoc->signalDeletedObject.connect(boost::bind(&Gui::Application::slotDeletedObject, this, _1));
    pDoc->signalChangedObject.connect(boost::bind(&Gui::Application::slotChangedObject, this, _1, _2));
    pDoc->signalRelabelObject.connect(boost::bind(&Gui::Application::slotRelabelObject, this, _1));
    pDoc->signalActivatedObject.connect(boost::bind(&Gui::Application::slotActivatedObject, this, _1));


    signalNewDocument(*pDoc);
    pDoc->createView(View3DInventor::getClassTypeId());
    qApp->processEvents(); // make sure to show the window stuff on the right place
}

void Application::slotDeleteDocument(const App::Document& Doc)
{
    std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
    if (doc == d->documents.end()) {
        Base::Console().Log("GUI document '%s' already deleted\n", Doc.getName());
        return;
    }

    // We must clear the selection here to notify all observers
    Gui::Selection().clearSelection(doc->second->getDocument()->getName());
    signalDeleteDocument(*doc->second);

    // If the active document gets destructed we must set it to 0. If there are further existing documents then the 
    // view that becomes active sets the active document again. So, we needn't worry about this.
    if (d->activeDocument == doc->second)
        setActiveDocument(0);

    // For exception-safety use a smart pointer
    auto_ptr<Document> delDoc (doc->second);
    d->documents.erase(doc);
}

void Application::slotRelabelDocument(const App::Document& Doc)
{
    std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
#ifdef FC_DEBUG
    assert(doc!=d->documents.end());
#endif

    signalRelabelDocument(*doc->second);
    doc->second->onRelabel();
}

void Application::slotRenameDocument(const App::Document& Doc)
{
    std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
#ifdef FC_DEBUG
    assert(doc!=d->documents.end());
#endif

    signalRenameDocument(*doc->second);
}

void Application::slotActiveDocument(const App::Document& Doc)
{
    std::map<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);
    // this can happen when closing a document with two views opened
    if (doc != d->documents.end()) {
        // this can happen when calling App.setActiveDocument directly from Python
        // because no MDI view will be activated
        if (d->activeDocument != doc->second) {
            d->activeDocument = doc->second;
            if (d->activeDocument) {
                Base::PyGILStateLocker lock;
                Py::Object active(d->activeDocument->getPyObject(), true);
                Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"),active);
            }
            else {
                Base::PyGILStateLocker lock;
                Py::Module("FreeCADGui").setAttr(std::string("ActiveDocument"),Py::None());
            }
        }
        signalActiveDocument(*doc->second);
    }
}

void Application::slotNewObject(const ViewProvider& vp)
{
    this->signalNewObject(vp);
}

void Application::slotDeletedObject(const ViewProvider& vp)
{
    this->signalDeletedObject(vp);
}

void Application::slotChangedObject(const ViewProvider& vp, const App::Property& prop)
{
    this->signalChangedObject(vp,prop);
}

void Application::slotRelabelObject(const ViewProvider& vp)
{
    this->signalRelabelObject(vp);
}

void Application::slotActivatedObject(const ViewProvider& vp)
{
    this->signalActivatedObject(vp);
}

void Application::onLastWindowClosed(Gui::Document* pcDoc)
{
    if (!d->isClosing && pcDoc) {
        try {
            // Call the closing mechanism from Python. This also checks whether pcDoc is the last open document.
            Command::doCommand(Command::Doc, "App.closeDocument(\"%s\")", pcDoc->getDocument()->getName());
        }
        catch (const Base::PyException& e) {
            e.ReportException();
        }
    }
}

/// send Messages to the active view
bool Application::sendMsgToActiveView(const char* pMsg, const char** ppReturn)
{
    MDIView* pView = getMainWindow()->activeWindow();
    return pView ? pView->onMsg(pMsg,ppReturn) : false;
}

bool Application::sendHasMsgToActiveView(const char* pMsg)
{
    MDIView* pView = getMainWindow()->activeWindow();
    return pView ? pView->onHasMsg(pMsg) : false;
}

/// Getter for the active view
Gui::Document* Application::activeDocument(void) const
{
    return d->activeDocument;
}

void Application::setActiveDocument(Gui::Document* pcDocument)
{
    if (d->activeDocument == pcDocument)
        return; // nothing needs to be done
    if (pcDocument) {
        // This happens if a document with more than one view is about being
        // closed and a second view is activated. The document is still not
        // removed from the map.
        App::Document* doc = pcDocument->getDocument();
        if (d->documents.find(doc) == d->documents.end())
            return;
    }
    d->activeDocument = pcDocument;
    std::string nameApp, nameGui;
 
    // This adds just a line to the macro file but does not set the active document
    // Macro recording of this is problematic, thus it's written out as comment.
    if (pcDocument){
        nameApp += "App.setActiveDocument(\"";
        nameApp += pcDocument->getDocument()->getName(); 
        nameApp +=  "\")\n";
        nameApp += "App.ActiveDocument=App.getDocument(\"";
        nameApp += pcDocument->getDocument()->getName(); 
        nameApp +=  "\")";
        macroManager()->addLine(MacroManager::Cmt,nameApp.c_str());
        nameGui += "Gui.ActiveDocument=Gui.getDocument(\"";
        nameGui += pcDocument->getDocument()->getName(); 
        nameGui +=  "\")";
        macroManager()->addLine(MacroManager::Cmt,nameGui.c_str());
    }
    else {
        nameApp += "App.setActiveDocument(\"\")\n";
        nameApp += "App.ActiveDocument=None";
        macroManager()->addLine(MacroManager::Cmt,nameApp.c_str());
        nameGui += "Gui.ActiveDocument=None";
        macroManager()->addLine(MacroManager::Cmt,nameGui.c_str());
    }

    // Sets the currently active document
    try {
        Base::Interpreter().runString(nameApp.c_str());
        Base::Interpreter().runString(nameGui.c_str());
    }
    catch (const Base::Exception& e) {
        Base::Console().Warning(e.what());
        return;
    }

    // May be useful for error detection
    if (d->activeDocument) {
        App::Document* doc = d->activeDocument->getDocument();
        Base::Console().Log("Active document is %s (at %p)\n",doc->getName(), doc);
    }
    else {
        Base::Console().Log("No active document\n");
    }

    // notify all views attached to the application (not views belong to a special document)
    for(list<Gui::BaseView*>::iterator It=d->passive.begin();It!=d->passive.end();++It)
        (*It)->setDocument(pcDocument);
}

Gui::Document* Application::getDocument(const char* name) const
{
    App::Document* pDoc = App::GetApplication().getDocument( name );
    std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(pDoc);
    if ( it!=d->documents.end() )
        return it->second;
    else
        return 0;
}

Gui::Document* Application::getDocument(const App::Document* pDoc) const
{
    std::map<const App::Document*, Gui::Document*>::const_iterator it = d->documents.find(pDoc);
    if ( it!=d->documents.end() )
        return it->second;
    else
        return 0;
}

void Application::showViewProvider(const App::DocumentObject* obj)
{
    ViewProvider* vp = getViewProvider(obj);
    if (vp) vp->show();
}

void Application::hideViewProvider(const App::DocumentObject* obj)
{
    ViewProvider* vp = getViewProvider(obj);
    if (vp) vp->hide();
}

Gui::ViewProvider* Application::getViewProvider(const App::DocumentObject* obj) const
{
    App::Document* doc = obj ? obj->getDocument() : 0;
    if (doc) {
        Gui::Document* gui = getDocument(doc);
        if (gui) {
            ViewProvider* vp = gui->getViewProvider(obj);
            return vp;
        }
    }

    return 0;
}

void Application::attachView(Gui::BaseView* pcView)
{
    d->passive.push_back(pcView);
}

void Application::detachView(Gui::BaseView* pcView)
{
    d->passive.remove(pcView);
}

void Application::onUpdate(void)
{
    // update all documents
    std::map<const App::Document*, Gui::Document*>::iterator It;
    for (It = d->documents.begin();It != d->documents.end();++It)
        It->second->onUpdate();
    // update all the independed views
    for (std::list<Gui::BaseView*>::iterator It2 = d->passive.begin();It2 != d->passive.end();++It2)
        (*It2)->onUpdate();
}

/// Gets called if a view gets activated, this manages the whole activation scheme
void Application::viewActivated(MDIView* pcView)
{
    // May be useful for error detection
    Base::Console().Log("Active view is %s (at %p)\n",
                 (const char*)pcView->windowTitle().toUtf8(),pcView);

    signalActivateView(pcView);

    // Set the new active document which is taken of the activated view. If, however,
    // this view is passive we let the currently active document unchanged as we would
    // have no document active which is causing a lot of trouble.
    if (!pcView->isPassive())
        setActiveDocument(pcView->getGuiDocument());
}


void Application::updateActive(void)
{
    activeDocument()->onUpdate();
}

void Application::tryClose(QCloseEvent * e)
{
    if (d->documents.size() == 0) {
        e->accept();
    }
    else {
        // ask all documents if closable
        std::map<const App::Document*, Gui::Document*>::iterator It;
        for (It = d->documents.begin();It!=d->documents.end();++It) {
            // a document may have several views attached, so ask it directly
#if 0
            MDIView* active = It->second->getActiveView();
            e->setAccepted(active->canClose());
#else
            e->setAccepted(It->second->canClose());
#endif
            if (!e->isAccepted())
                return;
        }
    }

    // ask all passive views if closable
    for (std::list<Gui::BaseView*>::iterator It = d->passive.begin();It!=d->passive.end();++It) {
        e->setAccepted((*It)->canClose());
        if (!e->isAccepted())
            return;
    }

    if (e->isAccepted()) {
        d->isClosing = true;

        std::map<const App::Document*, Gui::Document*>::iterator It;

        //detach the passive views
        //SetActiveDocument(0);
        std::list<Gui::BaseView*>::iterator itp = d->passive.begin();
        while (itp != d->passive.end()) {
            (*itp)->onClose();
            itp = d->passive.begin();
        }

        // remove all documents
        size_t cnt = d->documents.size();
        while (d->documents.size() > 0 && cnt > 0) {
            // destroys also the Gui document
            It = d->documents.begin();
            App::GetApplication().closeDocument(It->second->getDocument()->getName());
            --cnt; // avoid infinite loop
        }
    }
}

/**
 * Activate the matching workbench to the registered workbench handler with name \a name.
 * The handler must be an instance of a class written in Python.
 * Normally, if a handler gets activated a workbench with the same name gets created unless it
 * already exists. 
 *
 * The old workbench gets deactivated before. If the workbench to the handler is already
 * active or if the switch fails false is returned. 
 */
bool Application::activateWorkbench(const char* name)
{
    bool ok = false;
    WaitCursor wc;
    Workbench* oldWb = WorkbenchManager::instance()->active();
    if (oldWb && oldWb->name() == name)
        return false; // already active

    // we check for the currently active workbench and call its 'Deactivated'
    // method, if available
    PyObject* pcOldWorkbench = 0;
    if (oldWb) {
        pcOldWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, oldWb->name().c_str());
    }

    // get the python workbench object from the dictionary
    Base::PyGILStateLocker lock;
    PyObject* pcWorkbench = 0;
    pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, name);
    // test if the workbench exists
    if (!pcWorkbench)
        return false;

    try {
        std::string type;
        Py::Object handler(pcWorkbench);
        if (!handler.hasAttr(std::string("__Workbench__"))) {
            // call its GetClassName method if possible
            Py::Callable method(handler.getAttr(std::string("GetClassName")));
            Py::Tuple args;
            Py::String result(method.apply(args));
            type = result.as_std_string("ascii");
            if (Base::Type::fromName(type.c_str()).isDerivedFrom(Gui::PythonBaseWorkbench::getClassTypeId())) {
                Workbench* wb = WorkbenchManager::instance()->createWorkbench(name, type);
                if (!wb)
                    throw Py::RuntimeError("Failed to instantiate workbench of type " + type);
                handler.setAttr(std::string("__Workbench__"), Py::Object(wb->getPyObject(), true));
            }

            // import the matching module first
            Py::Callable activate(handler.getAttr(std::string("Initialize")));
            activate.apply(args);

            // Dependent on the implementation of a workbench handler the type
            // can be defined after the call of Initialize()
            if (type.empty()) {
                Py::String result(method.apply(args));
                type = result.as_std_string("ascii");
            }
        }

        // does the Python workbench handler have changed the workbench?
        Workbench* curWb = WorkbenchManager::instance()->active();
        if (curWb && curWb->name() == name)
            ok = true; // already active
        // now try to create and activate the matching workbench object
        else if (WorkbenchManager::instance()->activate(name, type)) {
            getMainWindow()->activateWorkbench(QString::fromLatin1(name));
            this->signalActivateWorkbench(name);
            ok = true;
        }

        // if we still not have this member then it must be built-in C++ workbench
        // which could be created after loading the appropriate module
        if (!handler.hasAttr(std::string("__Workbench__"))) {
            Workbench* wb = WorkbenchManager::instance()->getWorkbench(name);
            if (wb) handler.setAttr(std::string("__Workbench__"), Py::Object(wb->getPyObject(), true));
        }

        // If the method Deactivate is available we call it
        if (pcOldWorkbench) {
            Py::Object handler(pcOldWorkbench);
            if (handler.hasAttr(std::string("Deactivated"))) {
                Py::Object method(handler.getAttr(std::string("Deactivated")));
                if (method.isCallable()) {
                    Py::Tuple args;
                    Py::Callable activate(method);
                    activate.apply(args);
                }
            }
        }

        if (oldWb)
            oldWb->deactivated();

        // If the method Activate is available we call it
        if (handler.hasAttr(std::string("Activated"))) {
            Py::Object method(handler.getAttr(std::string("Activated")));
            if (method.isCallable()) {
                Py::Tuple args;
                Py::Callable activate(method);
                activate.apply(args);
            }
        }

        // now get the newly activated workbench
        Workbench* newWb = WorkbenchManager::instance()->active();
        if (newWb)
            newWb->activated();
    }
    catch (Py::Exception&) {
        Base::PyException e; // extract the Python error text
        QString msg = QString::fromLatin1(e.what());
        QRegExp rx;
        // ignore '<type 'exceptions.ImportError'>' prefixes
        rx.setPattern(QLatin1String("^\\s*<type 'exceptions.ImportError'>:\\s*"));
        int pos = rx.indexIn(msg);
        while ( pos != -1 ) {
            msg = msg.mid(rx.matchedLength());
            pos = rx.indexIn(msg);
        }

        Base::Console().Error("%s\n", (const char*)msg.toLatin1());
        Base::Console().Log("%s\n", e.getStackTrace().c_str());
        if (!d->startingUp) {
            wc.restoreCursor();
            QMessageBox::critical(getMainWindow(), QObject::tr("Workbench failure"), 
                QObject::tr("%1").arg(msg));
            wc.setWaitCursor();
        }
    }

    return ok;
}

QPixmap Application::workbenchIcon(const QString& wb) const
{
    Base::PyGILStateLocker lock;
    // get the python workbench object from the dictionary
    PyObject* pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, wb.toLatin1());
    // test if the workbench exists
    if (pcWorkbench) {
        // make a unique icon name
        std::stringstream str;
        str << static_cast<const void *>(pcWorkbench) << std::ends;
        std::string iconName = str.str();
        QPixmap icon;
        if (BitmapFactory().findPixmapInCache(iconName.c_str(), icon))
            return icon;

        // get its Icon member if possible
        try {
            Py::Object handler(pcWorkbench);
            if (handler.hasAttr(std::string("Icon"))) {
                Py::Object member = handler.getAttr(std::string("Icon"));
                Py::String data(member);
                std::string content = data.as_std_string("utf-8");

                // test if in XPM format
                QByteArray ary;
                int strlen = (int)content.size();
                ary.resize(strlen);
                for (int j=0; j<strlen; j++)
                    ary[j]=content[j];
                if (ary.indexOf("/* XPM */") > 0) {
                    // Make sure to remove crap around the XPM data
                    QList<QByteArray> lines = ary.split('\n');
                    QByteArray buffer;
                    buffer.reserve(ary.size()+lines.size());
                    for (QList<QByteArray>::iterator it = lines.begin(); it != lines.end(); ++it) {
                        QByteArray trim = it->trimmed();
                        if (!trim.isEmpty()) {
                            buffer.append(trim);
                            buffer.append('\n');
                        }
                    }
                    icon.loadFromData(buffer, "XPM");
                }
                else {
                    // is it a file name...
                    QString file = QString::fromUtf8(content.c_str());
                    icon.load(file);
                    if (icon.isNull()) {
                        // ... or the name of another icon?
                        icon = BitmapFactory().pixmap(file.toUtf8());
                    }
                }

                if (!icon.isNull()) {
                    BitmapFactory().addPixmapToCache(iconName.c_str(), icon);
                }

                return icon;
            }
        }
        catch (Py::Exception& e) {
            e.clear();
        }
    }

    QIcon icon = QApplication::windowIcon();
    if (!icon.isNull()) {
        QList<QSize> s = icon.availableSizes();
        if (!s.isEmpty())
            return icon.pixmap(s[0]);
    }
    return QPixmap();
}

QString Application::workbenchToolTip(const QString& wb) const
{
    // get the python workbench object from the dictionary
    Base::PyGILStateLocker lock;
    PyObject* pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, wb.toLatin1());
    // test if the workbench exists
    if (pcWorkbench) {
        // get its ToolTip member if possible
        try {
            Py::Object handler(pcWorkbench);
            Py::Object member = handler.getAttr(std::string("ToolTip"));
            if (member.isString()) {
                Py::String tip(member);
                return QString::fromUtf8(tip.as_std_string("utf-8").c_str());
            }
        }
        catch (Py::Exception& e) {
            e.clear();
        }
    }

    return QString();
}

QString Application::workbenchMenuText(const QString& wb) const
{
    // get the python workbench object from the dictionary
    Base::PyGILStateLocker lock;
    PyObject* pcWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, wb.toLatin1());
    // test if the workbench exists
    if (pcWorkbench) {
        // get its ToolTip member if possible
        Base::PyGILStateLocker locker;
        try {
            Py::Object handler(pcWorkbench);
            Py::Object member = handler.getAttr(std::string("MenuText"));
            if (member.isString()) {
                Py::String tip(member);
                return QString::fromUtf8(tip.as_std_string("utf-8").c_str());
            }
        }
        catch (Py::Exception& e) {
            e.clear();
        }
    }

    return QString();
}

QStringList Application::workbenches(void) const
{
    // If neither 'HiddenWorkbench' nor 'ExtraWorkbench' is set then all workbenches are returned.
    const std::map<std::string,std::string>& config = App::Application::Config();
    std::map<std::string, std::string>::const_iterator ht = config.find("HiddenWorkbench");
    std::map<std::string, std::string>::const_iterator et = config.find("ExtraWorkbench");
    std::map<std::string, std::string>::const_iterator st = config.find("StartWorkbench");
    const char* start = (st != config.end() ? st->second.c_str() : "<none>");
    QStringList hidden, extra;
    if (ht != config.end()) { 
        QString items = QString::fromLatin1(ht->second.c_str());
        hidden = items.split(QLatin1Char(';'), QString::SkipEmptyParts);
        if (hidden.isEmpty())
            hidden.push_back(QLatin1String(""));
    }
    if (et != config.end()) { 
        QString items = QString::fromLatin1(et->second.c_str());
        extra = items.split(QLatin1Char(';'), QString::SkipEmptyParts);
        if (extra.isEmpty())
            extra.push_back(QLatin1String(""));
    }

    PyObject *key, *value;
    Py_ssize_t pos = 0;
    QStringList wb;
    // insert all items
    while (PyDict_Next(_pcWorkbenchDictionary, &pos, &key, &value)) {
        /* do something interesting with the values... */
        const char* wbName = PyString_AsString(key);
        // add only allowed workbenches
        bool ok = true;
        if (!extra.isEmpty()&&ok) {
            ok = (extra.indexOf(QString::fromLatin1(wbName)) != -1);
        }
        if (!hidden.isEmpty()&&ok) {
            ok = (hidden.indexOf(QString::fromLatin1(wbName)) == -1);
        }
    
        // okay the item is visible
        if (ok)
            wb.push_back(QString::fromLatin1(wbName));
        // also allow start workbench in case it is hidden
        else if (strcmp(wbName, start) == 0)
            wb.push_back(QString::fromLatin1(wbName));
    }

    return wb;
}

void Application::setupContextMenu(const char* recipient, MenuItem* items) const
{
    Workbench* actWb = WorkbenchManager::instance()->active();
    if (actWb) {
        // when populating the context-menu of a Python workbench invoke the method 
        // 'ContextMenu' of the handler object
        if (actWb->getTypeId().isDerivedFrom(PythonWorkbench::getClassTypeId())) {
            static_cast<PythonWorkbench*>(actWb)->clearContextMenu();
            Base::PyGILStateLocker lock;
            PyObject* pWorkbench = 0;
            pWorkbench = PyDict_GetItemString(_pcWorkbenchDictionary, actWb->name().c_str());

            try {
                // call its GetClassName method if possible
                Py::Object handler(pWorkbench);
                Py::Callable method(handler.getAttr(std::string("ContextMenu")));
                Py::Tuple args(1);
                args.setItem(0, Py::String(recipient));
                method.apply(args);
            }
            catch (Py::Exception& e) {
                Py::Object o = Py::type(e);
                e.clear();
                if (o.isString()) {
                    Py::String s(o);
                    std::clog << "Application::setupContextMenu: " << s.as_std_string("utf-8") << std::endl;
                }
            }
        }
        actWb->setupContextMenu(recipient, items);
    }
}

bool Application::isClosing(void)
{
    return d->isClosing;
}

MacroManager *Application::macroManager(void)
{
    return d->macroMngr;
}

CommandManager &Application::commandManager(void)
{
    return d->commandManager;
}

void Application::runCommand(bool bForce, const char* sCmd,...)
{
    // temp buffer
    size_t format_len = std::strlen(sCmd)+4024;
    char* format = (char*) malloc(format_len);
    va_list namelessVars;
    va_start(namelessVars, sCmd);  // Get the "..." vars
    vsnprintf(format, format_len, sCmd, namelessVars);
    va_end(namelessVars);

    if (bForce)
        d->macroMngr->addLine(MacroManager::App,format);
    else
        d->macroMngr->addLine(MacroManager::Gui,format);

    try { 
        Base::Interpreter().runString(format);
    }
    catch (...) {
        // free memory to avoid a leak if an exception occurred
        free (format);
        throw;
    }

    free (format);
}

bool Application::runPythonCode(const char* cmd, bool gui, bool pyexc)
{
    if (gui)
        d->macroMngr->addLine(MacroManager::Gui,cmd);
    else
        d->macroMngr->addLine(MacroManager::App,cmd);

    try {
        Base::Interpreter().runString(cmd);
        return true;
    }
    catch (Base::PyException &e) {
        if (pyexc) {
            e.ReportException();
            Base::Console().Error("Stack Trace: %s\n",e.getStackTrace().c_str());
        }
        else {
            throw; // re-throw to handle in calling instance
        }
    }
    catch (Base::AbortException&) {
    }
    catch (Base::Exception &e) {
        e.ReportException();
    }
    catch (std::exception &e) {
        std::string str;
        str += "C++ exception thrown (";
        str += e.what();
        str += ")";
        Base::Console().Error(str.c_str());
    }
    catch (const char* e) {
        Base::Console().Error("%s\n", e);
    }
#ifndef FC_DEBUG
    catch (...) {
        Base::Console().Error("Unknown C++ exception in command thrown\n");
    }
#endif
    return false;
}

//**************************************************************************
// Init, Destruct and ingleton

typedef void (*_qt_msg_handler_old)(QtMsgType type, const char *msg);
_qt_msg_handler_old old_qtmsg_handler = 0;

void messageHandler(QtMsgType type, const char *msg)
{
#ifdef FC_DEBUG
    switch (type)
    {
    case QtDebugMsg:
        Base::Console().Message("%s\n", msg);
        break;
    case QtWarningMsg:
        Base::Console().Warning("%s\n", msg);
        break;
    case QtCriticalMsg:
        Base::Console().Error("%s\n", msg);
        break;
    case QtFatalMsg:
        Base::Console().Error("%s\n", msg);
        abort();                    // deliberately core dump
    }
#ifdef FC_OS_WIN32
    if (old_qtmsg_handler)
        (*old_qtmsg_handler)(type, msg);
#endif
#else
    // do not stress user with Qt internals but write to log file if enabled
    Base::Console().Log("%s\n", msg);
#endif
}

#ifdef FC_DEBUG // redirect Coin messages to FreeCAD
void messageHandlerCoin(const SoError * error, void * userdata)
{
    if (error && error->getTypeId() == SoDebugError::getClassTypeId()) {
        const SoDebugError* dbg = static_cast<const SoDebugError*>(error);
        const char* msg = error->getDebugString().getString();
        switch (dbg->getSeverity())
        {
        case SoDebugError::INFO:
            Base::Console().Message("%s\n", msg);
            break;
        case SoDebugError::WARNING:
            Base::Console().Warning("%s\n", msg);
            break;
        default: // error
            Base::Console().Error("%s\n", msg);
            break;
        }
#ifdef FC_OS_WIN32
    if (old_qtmsg_handler)
        (*old_qtmsg_handler)(QtDebugMsg, msg);
#endif
    }
    else if (error) {
        const char* msg = error->getDebugString().getString();
        Base::Console().Log( msg );
    }
}

#endif

// To fix bug #0000345 move Q_INIT_RESOURCE() outside initApplication()
static void init_resources()
{
    // init resources
    Q_INIT_RESOURCE(resource);
    Q_INIT_RESOURCE(translation);
}

void Application::initApplication(void)
{
    static bool init = false;
    if (init) {
        Base::Console().Error("Tried to run Gui::Application::initApplication() twice!\n");
        return;
    }

    try {
        initTypes();
        new Base::ScriptProducer( "FreeCADGuiInit", FreeCADGuiInit );
        init_resources();
        old_qtmsg_handler = qInstallMsgHandler(messageHandler);
        init = true;
    }
    catch (...) {
        // force to flush the log
        App::Application::destructObserver();
        throw;
    }
}

void Application::initTypes(void)
{
    // views
    Gui::BaseView                               ::init();
    Gui::MDIView                                ::init();
    Gui::View3DInventor                         ::init();
    Gui::AbstractSplitView                      ::init();
    Gui::SplitView3DInventor                    ::init();
    // View Provider
    Gui::ViewProvider                           ::init();
    Gui::ViewProviderExtern                     ::init();
    Gui::ViewProviderDocumentObject             ::init();
    Gui::ViewProviderFeature                    ::init();
    Gui::ViewProviderDocumentObjectGroup        ::init();
    Gui::ViewProviderDocumentObjectGroupPython  ::init();
    Gui::ViewProviderGeometryObject             ::init();
    Gui::ViewProviderInventorObject             ::init();
    Gui::ViewProviderVRMLObject                 ::init();
    Gui::ViewProviderAnnotation                 ::init();
    Gui::ViewProviderAnnotationLabel            ::init();
    Gui::ViewProviderPointMarker                ::init();
    Gui::ViewProviderMeasureDistance            ::init();
    Gui::ViewProviderPythonFeature              ::init();
    Gui::ViewProviderPythonGeometry             ::init();
    Gui::ViewProviderPlacement                  ::init();
    Gui::ViewProviderPlane                      ::init();
    Gui::ViewProviderMaterialObject             ::init();
    Gui::ViewProviderMaterialObjectPython       ::init();

    // Workbench
    Gui::Workbench                              ::init();
    Gui::StdWorkbench                           ::init();
    Gui::BlankWorkbench                         ::init();
    Gui::NoneWorkbench                          ::init();
    Gui::TestWorkbench                          ::init();
    Gui::PythonBaseWorkbench                    ::init();
    Gui::PythonBlankWorkbench                   ::init();
    Gui::PythonWorkbench                        ::init();
}

void Application::runApplication(void)
{
    const std::map<std::string,std::string>& cfg = App::Application::Config();
    std::map<std::string,std::string>::const_iterator it;

    // A new QApplication
    Base::Console().Log("Init: Creating Gui::Application and QApplication\n");
    // if application not yet created by the splasher
    int argc = App::Application::GetARGC();
    GUISingleApplication mainApp(argc, App::Application::GetARGV());

    // check if a single or multiple instances can run
    it = cfg.find("SingleInstance");
    if (it != cfg.end() && mainApp.isRunning()) {
        // send the file names to be opened to the server application so that this
        // opens them
        std::list<std::string> files = App::Application::getCmdLineFiles();
        for (std::list<std::string>::iterator jt = files.begin(); jt != files.end(); ++jt) {
            QByteArray msg(jt->c_str(), static_cast<int>(jt->size()));
            msg.prepend("OpenFile:");
            if (!mainApp.sendMessage(msg)) {
                qWarning("Failed to send message to server");
                break;
            }
        }
        return;
    }

    // set application icon and window title
    it = cfg.find("Application");
    if (it != cfg.end()) {
        mainApp.setApplicationName(QString::fromUtf8(it->second.c_str()));
    }
    else {
        mainApp.setApplicationName(QString::fromUtf8(App::GetApplication().getExecutableName()));
    }
    mainApp.setWindowIcon(Gui::BitmapFactory().pixmap(App::Application::Config()["AppIcon"].c_str()));
    QString plugin;
    plugin = QString::fromUtf8(App::GetApplication().getHomePath());
    plugin += QLatin1String("/plugins");
    QCoreApplication::addLibraryPath(plugin);

    // setup the search paths for Qt style sheets
    QStringList qssPaths;
    qssPaths << QString::fromUtf8((App::Application::getUserAppDataDir() + "Gui/Stylesheets/").c_str())
             << QString::fromUtf8((App::Application::getResourceDir() + "Gui/Stylesheets/").c_str())
             << QLatin1String(":/stylesheets");
    QDir::setSearchPaths(QString::fromLatin1("qss"), qssPaths);

    // register action style event type
    ActionStyleEvent::EventType = QEvent::registerEventType(QEvent::User + 1);

    // check for OpenGL
    if (!QGLFormat::hasOpenGL()) {
        QMessageBox::critical(0, QObject::tr("No OpenGL"), QObject::tr("This system does not support OpenGL"));
        throw Base::Exception("This system does not support OpenGL");
    }
#if QT_VERSION >= 0x040200
    if (!QGLFramebufferObject::hasOpenGLFramebufferObjects()) {
        Base::Console().Log("This system does not support framebuffer objects\n");
    }
#endif
    if (!QGLPixelBuffer::hasOpenGLPbuffers()) {
        Base::Console().Log("This system does not support pbuffers\n");
    }

    QGLFormat::OpenGLVersionFlags version = QGLFormat::openGLVersionFlags ();
#if QT_VERSION >= 0x040500
    if (version & QGLFormat::OpenGL_Version_3_0)
        Base::Console().Log("OpenGL version 3.0 or higher is present\n");
    else
#endif
    if (version & QGLFormat::OpenGL_Version_2_1)
        Base::Console().Log("OpenGL version 2.1 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_2_0)
        Base::Console().Log("OpenGL version 2.0 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_1_5)
        Base::Console().Log("OpenGL version 1.5 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_1_4)
        Base::Console().Log("OpenGL version 1.4 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_1_3)
        Base::Console().Log("OpenGL version 1.3 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_1_2)
        Base::Console().Log("OpenGL version 1.2 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_1_1)
        Base::Console().Log("OpenGL version 1.1 or higher is present\n");
    else if (version & QGLFormat::OpenGL_Version_None)
        Base::Console().Log("No OpenGL is present or no OpenGL context is current\n");

#if !defined(Q_WS_X11)
    QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << QString::fromLatin1(":/icons/FreeCAD-default"));
    QIcon::setThemeName(QLatin1String("FreeCAD-default"));
#endif

    Application app(true);
    MainWindow mw;
    mw.setWindowTitle(mainApp.applicationName());
    QObject::connect(&mainApp, SIGNAL(messageReceived(const QList<QByteArray> &)),
                     &mw, SLOT(processMessages(const QList<QByteArray> &)));

    ParameterGrp::handle hDocGrp = WindowParameter::getDefaultParameter()->GetGroup("Document");
    int timeout = hDocGrp->GetInt("AutoSaveTimeout", 15); // 15 min
    if (!hDocGrp->GetBool("AutoSaveEnabled", true))
        timeout = 0;
    AutoSaver::instance()->setTimeout(timeout * 60000);
    AutoSaver::instance()->setCompressed(hDocGrp->GetBool("AutoSaveCompressed", true));

    // set toolbar icon size
    ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
    int size = hGrp->GetInt("ToolbarIconSize", 0);
    if (size >= 16) // must not be lower than this
        mw.setIconSize(QSize(size,size));

    // init the Inventor subsystem
    SoDB::init();
    SIM::Coin3D::Quarter::Quarter::init();
    SoFCDB::init();

    QString home = QString::fromUtf8(App::GetApplication().getHomePath());

    it = cfg.find("WindowTitle");
    if (it != cfg.end()) {
        QString title = QString::fromUtf8(it->second.c_str());
        mw.setWindowTitle(title);
    }
    it = cfg.find("WindowIcon");
    if (it != cfg.end()) {
        QString path = QString::fromUtf8(it->second.c_str());
        if (QDir(path).isRelative()) {
            path = QFileInfo(QDir(home), path).absoluteFilePath();
        }
        QApplication::setWindowIcon(QIcon(path));
    }
    it = cfg.find("ProgramLogo");
    if (it != cfg.end()) {
        QString path = QString::fromUtf8(it->second.c_str());
        if (QDir(path).isRelative()) {
            path = QFileInfo(QDir(home), path).absoluteFilePath();
        }
        QPixmap px(path);
        if (!px.isNull()) {
            QLabel* logo = new QLabel();
            logo->setPixmap(px.scaledToHeight(32));
            mw.statusBar()->addPermanentWidget(logo, 0);
            logo->setFrameShape(QFrame::NoFrame);
        }
    }
    bool hidden = false;
    it = cfg.find("StartHidden");
    if (it != cfg.end()) {
        hidden = true;
    }

    // show splasher while initializing the GUI
    if (!hidden)
        mw.startSplasher();

    // running the GUI init script
    try {
        Base::Console().Log("Run Gui init script\n");
        Base::Interpreter().runString(Base::ScriptFactory().ProduceScript("FreeCADGuiInit"));
    }
    catch (const Base::Exception& e) {
        Base::Console().Error("Error in FreeCADGuiInit.py: %s\n", e.what());
        mw.stopSplasher();
        throw;
    }

    // stop splash screen and set immediately the active window that may be of interest
    // for scripts using Python binding for Qt
    mw.stopSplasher();
    mainApp.setActiveWindow(&mw);

    // Activate the correct workbench
    std::string start = App::Application::Config()["StartWorkbench"];
    Base::Console().Log("Init: Activating default workbench %s\n", start.c_str());
    start = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
                           GetASCII("AutoloadModule", start.c_str());
    // if the auto workbench is not visible then force to use the default workbech
    // and replace the wrong entry in the parameters
    QStringList wb = app.workbenches();
    if (!wb.contains(QString::fromLatin1(start.c_str()))) {
        start = App::Application::Config()["StartWorkbench"];
        App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General")->
                              SetASCII("AutoloadModule", start.c_str());
    }

    // Call this before showing the main window because otherwise:
    // 1. it shows a white window for a few seconds which doesn't look nice
    // 2. the layout of the toolbars is completely broken
    app.activateWorkbench(start.c_str());

    // show the main window
    if (!hidden) {
        Base::Console().Log("Init: Showing main window\n");
        mw.loadWindowSettings();
    }

    hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow");
    QMdiArea* mdi = mw.findChild<QMdiArea*>();
    mdi->setProperty("showImage", hGrp->GetBool("TiledBackground", false));

    std::string style = hGrp->GetASCII("StyleSheet");
    if (!style.empty()) {
        QFile f(QLatin1String(style.c_str()));
        if (f.open(QFile::ReadOnly)) {
            mdi->setBackground(QBrush(Qt::NoBrush));
            QTextStream str(&f);
            qApp->setStyleSheet(str.readAll());

            ActionStyleEvent e(ActionStyleEvent::Clear);
            qApp->sendEvent(&mw, &e);
        }
    }

    //initialize spaceball.
    mainApp.initSpaceball(&mw);

#ifdef FC_DEBUG // redirect Coin messages to FreeCAD
    SoDebugError::setHandlerCallback( messageHandlerCoin, 0 );
#endif


    Instance->d->startingUp = false;

    // gets called once we start the event loop
    QTimer::singleShot(0, &mw, SLOT(delayedStartup()));

    // run the Application event loop
    Base::Console().Log("Init: Entering event loop\n");

    try {
        std::stringstream s;
        s << App::Application::getTempPath() << App::GetApplication().getExecutableName()
          << "_" << QCoreApplication::applicationPid() << ".lock";
        // open a lock file with the PID
        Base::FileInfo fi(s.str());
        Base::ofstream lock(fi);
        boost::interprocess::file_lock flock(s.str().c_str());
        flock.lock();

        mainApp.exec();
        // Qt can't handle exceptions thrown from event handlers, so we need
        // to manually rethrow SystemExitExceptions.
        if(mainApp.caughtException.get())
            throw Base::SystemExitException(*mainApp.caughtException.get());

        // close the lock file, in case of a crash we can see the existing lock file
        // on the next restart and try to repair the documents, if needed.
        flock.unlock();
        lock.close();
        fi.deleteFile();
    }
    catch (const Base::SystemExitException&) {
        Base::Console().Message("System exit\n");
        throw;
    }
    catch (...) {
        // catching nasty stuff coming out of the event loop
        App::Application::destructObserver();
        Base::Console().Error("Event loop left through unhandled exception\n");
        throw;
    }

    Base::Console().Log("Finish: Event loop left\n");
}

void Application::checkForPreviousCrashes()
{
    QDir tmp = QString::fromUtf8(App::Application::getTempPath().c_str());
    tmp.setNameFilters(QStringList() << QString::fromLatin1("*.lock"));
    tmp.setFilter(QDir::Files);

    QList<QFileInfo> restoreDocFiles;
    QString exeName = QString::fromLatin1(App::GetApplication().getExecutableName());
    QList<QFileInfo> locks = tmp.entryInfoList();
    for (QList<QFileInfo>::iterator it = locks.begin(); it != locks.end(); ++it) {
        QString bn = it->baseName();
        // ignore the lock file for this instance
        QString pid = QString::number(QCoreApplication::applicationPid());
        if (bn.startsWith(exeName) && bn.indexOf(pid) < 0) {
            QString fn = it->absoluteFilePath();
            boost::interprocess::file_lock flock((const char*)fn.toLocal8Bit());
            if (flock.try_lock()) {
                // OK, this file is a leftover from a previous crash
                QString crashed_pid = bn.mid(exeName.length()+1);
                // search for transient directories with this PID
                QString filter;
                QTextStream str(&filter);
                str << exeName << "_Doc_*_" << crashed_pid;
                tmp.setNameFilters(QStringList() << filter);
                tmp.setFilter(QDir::Dirs);
                QList<QFileInfo> dirs = tmp.entryInfoList();
                if (dirs.isEmpty()) {
                    // delete the lock file immediately if no transient directories are related
                    tmp.remove(fn);
                }
                else {
                    int countDeletedDocs = 0;
                    QString recovery_files = QString::fromLatin1("fc_recovery_files");
                    for (QList<QFileInfo>::iterator it = dirs.begin(); it != dirs.end(); ++it) {
                        QDir doc_dir(it->absoluteFilePath());
                        doc_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
                        uint entries = doc_dir.entryList().count();
                        if (entries == 0) {
                            // in this case we can delete the transient directory because
                            // we cannot do anything
                            if (tmp.rmdir(it->filePath()))
                                countDeletedDocs++;
                        }
                        // search for the existence of a recovery file
                        else if (doc_dir.exists(QLatin1String("fc_recovery_file.xml"))) {
                            // store the transient directory in case it's not empty
                            restoreDocFiles << *it;
                        }
                        // search for the 'fc_recovery_files' sub-directory and check that it's the only entry
                        else if (entries == 1 && doc_dir.exists(recovery_files)) {
                            // if the sub-directory is empty delete the transient directory
                            QDir rec_dir(doc_dir.absoluteFilePath(recovery_files));
                            rec_dir.setFilter(QDir::NoDotAndDotDot|QDir::AllEntries);
                            if (rec_dir.entryList().isEmpty()) {
                                doc_dir.rmdir(recovery_files);
                                if (tmp.rmdir(it->filePath()))
                                    countDeletedDocs++;
                            }
                        }
                    }

                    // all directories corresponding to the lock file have been deleted
                    // so delete the lock file, too
                    if (countDeletedDocs == dirs.size()) {
                        tmp.remove(fn);
                    }
                }
            }
        }
    }

    if (!restoreDocFiles.isEmpty()) {
        Gui::Dialog::DocumentRecovery dlg(restoreDocFiles, Gui::getMainWindow());
        if (dlg.foundDocuments())
            dlg.exec();
    }
}
