/***************************************************************************
 *   Copyright (c) 2005 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   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 <QApplication>
# include <QMenu>
# include <QMenuBar>
#endif

#include "MenuManager.h"
#include "Application.h"
#include "MainWindow.h"
#include "Command.h"

using namespace Gui;


MenuItem::MenuItem()
{
}

MenuItem::MenuItem(MenuItem* item)
{
    if (item)
        item->appendItem(this);
}

MenuItem::~MenuItem()
{
    clear();
}

void MenuItem::setCommand(const std::string& name)
{
    _name = name;
}

std::string MenuItem::command() const
{
    return _name;
}

bool MenuItem::hasItems() const
{
    return _items.count() > 0;
}

MenuItem* MenuItem::findItem(const std::string& name)
{
    if (_name == name)
    {
        return this;
    }
    else
    {
        for (QList<MenuItem*>::Iterator it = _items.begin(); it != _items.end(); ++it)
        {
            if ((*it)->_name == name)
                return *it;
        }
    }

    return 0;
}

MenuItem* MenuItem::copy() const
{
    MenuItem* root = new MenuItem;
    root->setCommand(command());

    QList<MenuItem*> items = getItems();
    for (QList<MenuItem*>::ConstIterator it = items.begin(); it != items.end(); ++it)
    {
        root->appendItem((*it)->copy());
    }

    return root;
}

uint MenuItem::count() const
{
    return _items.count();
}

void MenuItem::appendItem(MenuItem* item)
{
    _items.push_back(item);
}

bool MenuItem::insertItem(MenuItem* before, MenuItem* item)
{
    int pos = _items.indexOf(before);
    if (pos != -1)
    {
        _items.insert(pos, item);
        return true;
    }
    else
        return false;
}

MenuItem* MenuItem::afterItem(MenuItem* item) const
{
    int pos = _items.indexOf(item);
    if (pos < 0 || pos+1 == _items.size())
        return 0;
    return _items.at(pos+1);
}

void MenuItem::removeItem(MenuItem* item)
{
    int pos = _items.indexOf(item);
    if (pos != -1)
        _items.removeAt(pos);
}

void MenuItem::clear()
{
    for (QList<MenuItem*>::Iterator it = _items.begin(); it != _items.end(); ++it)
        delete *it;
    _items.clear();
}

MenuItem& MenuItem::operator << (const std::string& command)
{
    MenuItem* item = new MenuItem(this);
    item->setCommand(command);
    return *this;
}

MenuItem& MenuItem::operator << (MenuItem* item)
{
    appendItem(item);
    return *this;
}

QList<MenuItem*> MenuItem::getItems() const
{
    return _items;
}

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

MenuManager* MenuManager::_instance=0;

MenuManager* MenuManager::getInstance()
{
    if ( !_instance )
        _instance = new MenuManager;
    return _instance;
}

void MenuManager::destruct()
{
    delete _instance;
    _instance = 0;
}

MenuManager::MenuManager()
{
}

MenuManager::~MenuManager()
{
}

void MenuManager::setup(MenuItem* menuItems) const
{
    if (!menuItems)
        return; // empty menu bar

    QMenuBar* menuBar = getMainWindow()->menuBar();
    //menuBar->setUpdatesEnabled(false);

    QList<MenuItem*> items = menuItems->getItems();
    QList<QAction*> actions = menuBar->actions();
    for (QList<MenuItem*>::ConstIterator it = items.begin(); it != items.end(); ++it)
    {
        // search for the menu action
        QAction* action = findAction(actions, QString::fromAscii((*it)->command().c_str()));
        if (!action) {
            // There must be not more than one separator in the menu bar, so
            // we can safely remove it if available and append it at the end
            if ((*it)->command() == "Separator") {
                action = menuBar->addSeparator();
                action->setObjectName(QLatin1String("Separator"));
            }
            else {
                // create a new menu
                std::string menuName = (*it)->command();
                QMenu* menu = menuBar->addMenu(
                    QApplication::translate("Workbench", menuName.c_str(),
                                            0, QApplication::UnicodeUTF8));
                action = menu->menuAction();
                menu->setObjectName(QString::fromAscii(menuName.c_str()));
                action->setObjectName(QString::fromAscii(menuName.c_str()));
            }

            // set the menu user data
            action->setData(QString::fromAscii((*it)->command().c_str()));
        }
        else {
            // put the menu at the end
            menuBar->removeAction(action);
            menuBar->addAction(action);
            action->setVisible(true);
            int index = actions.indexOf(action);
            actions.removeAt(index);
        }

        // flll up the menu
        if (!action->isSeparator())
            setup(*it, action->menu());
    }

    // hide all menus which we don't need for the moment
    for (QList<QAction*>::Iterator it = actions.begin(); it != actions.end(); ++it) {
        (*it)->setVisible(false);
    }

    // enable update again
    //menuBar->setUpdatesEnabled(true);
}

void MenuManager::setup(MenuItem* item, QMenu* menu) const
{
    CommandManager& mgr = Application::Instance->commandManager();
    QList<MenuItem*> items = item->getItems();
    QList<QAction*> actions = menu->actions();
    for (QList<MenuItem*>::ConstIterator it = items.begin(); it != items.end(); ++it) {
        // search for the menu item
        QList<QAction*> used_actions = findActions(actions, QString::fromAscii((*it)->command().c_str()));
        if (used_actions.isEmpty()) {
            if ((*it)->command() == "Separator") {
                QAction* action = menu->addSeparator();
                action->setObjectName(QLatin1String("Separator"));
                // set the menu user data
                action->setData(QLatin1String("Separator"));
                used_actions.append(action);
            }
            else {
                if ((*it)->hasItems()) {
                    // Creste a submenu
                    std::string menuName = (*it)->command();
                    QMenu* submenu = menu->addMenu(
                        QApplication::translate("Workbench", menuName.c_str(),
                                                0, QApplication::UnicodeUTF8));
                    QAction* action = submenu->menuAction();
                    submenu->setObjectName(QString::fromAscii((*it)->command().c_str()));
                    action->setObjectName(QString::fromAscii((*it)->command().c_str()));
                    // set the menu user data
                    action->setData(QString::fromAscii((*it)->command().c_str()));
                    used_actions.append(action);
                }
                else {
                    // A command can have more than one QAction
                    int count = menu->actions().count();
                    // Check if action was added successfully
                    if (mgr.addTo((*it)->command().c_str(), menu)) {
                        QList<QAction*> acts = menu->actions();
                        for (int i=count; i < acts.count(); i++) {
                            QAction* a = acts[i];
                            // set the menu user data
                            a->setData(QString::fromAscii((*it)->command().c_str()));
                            used_actions.append(a);
                        }
                    }
                }
            }
        }
        else {
            for (QList<QAction*>::Iterator it = used_actions.begin(); it != used_actions.end(); ++it) {
                // put the menu item at the end
                menu->removeAction(*it);
                menu->addAction(*it);
                int index = actions.indexOf(*it);
                actions.removeAt(index);
            }
        }

        // fill up the submenu
        if ((*it)->hasItems())
            setup(*it, used_actions.front()->menu());
    }

    // remove all menu items which we don't need for the moment
    for (QList<QAction*>::Iterator it = actions.begin(); it != actions.end(); ++it) {
        menu->removeAction(*it);
    }
}

void MenuManager::retranslate() const
{
    QMenuBar* menuBar = getMainWindow()->menuBar();
    QList<QAction*> actions = menuBar->actions();
    for (QList<QAction*>::Iterator it = actions.begin(); it != actions.end(); ++it) {
        if ((*it)->menu())
            retranslate((*it)->menu());
    }
}

void MenuManager::retranslate(QMenu* menu) const
{
    // Note: Here we search for all menus and submenus to retranslate their
    // titles. To ease the translation for each menu the native name is set
    // as user data. However, there are special menus that are created by
    // actions for which the name of the according command name is set. For
    // such menus we have to use the command's menu text instaed. Examples
    // for such actions are Std_RecentFiles, Std_Workbench or Std_FreezeViews.
    CommandManager& mgr = Application::Instance->commandManager();
    QByteArray menuName = menu->menuAction()->data().toByteArray();
    Command* cmd = mgr.getCommandByName(menuName);
    if (cmd) {
        menu->setTitle(
            QApplication::translate(cmd->className(),
                                    cmd->getMenuText(),
                                    0, QCoreApplication::CodecForTr));
    }
    else {
        menu->setTitle(
            QApplication::translate("Workbench",
                                    (const char*)menuName,
                                    0, QApplication::UnicodeUTF8));
    }
    QList<QAction*> actions = menu->actions();
    for (QList<QAction*>::Iterator it = actions.begin(); it != actions.end(); ++it) {
        if ((*it)->menu()) {
            retranslate((*it)->menu());
        }
    }
}

QAction* MenuManager::findAction(const QList<QAction*>& acts, const QString& item) const
{
    for (QList<QAction*>::ConstIterator it = acts.begin(); it != acts.end(); ++it) {
        if ((*it)->data().toString() == item)
            return *it;
    }

    return 0; // no item with the user data found
}

QList<QAction*> MenuManager::findActions(const QList<QAction*>& acts, const QString& item) const
{
    // It is possible that the user text of several actions match with 'item'.
    // But for the first match all following actions must match. For example
    // the Std_WindowsMenu command provides several actions with the same user
    // name.
    bool first_match = false;
    QList<QAction*> used;
    for (QList<QAction*>::ConstIterator it = acts.begin(); it != acts.end(); ++it) {
        if ((*it)->data().toString() == item) {
            used.append(*it);
            first_match = true;
            // get only one separator per request
            if (item == QLatin1String("Separator"))
                break;
        }
        else if (first_match)
            break;
    }

    return used;
}

void MenuManager::setupContextMenu(MenuItem* item, QMenu &menu) const
{
    setup(item, &menu);
}
