aseprite/src/app_menus.cpp

392 lines
12 KiB
C++

/* ASEPRITE
* Copyright (C) 2001-2012 David Capello
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "config.h"
#include "app_menus.h"
#include "app.h"
#include "base/bind.h"
#include "commands/command.h"
#include "commands/commands.h"
#include "commands/params.h"
#include "console.h"
#include "gui_xml.h"
#include "modules/gui.h"
#include "recent_files.h"
#include "tools/tool_box.h"
#include "ui/gui.h"
#include "util/filetoks.h"
#include "widgets/main_window.h"
#include "widgets/menuitem2.h"
#include "tinyxml.h"
#include <allegro/file.h>
#include <allegro/unicode.h>
#include <stdio.h>
#include <string.h>
using namespace ui;
static void destroy_instance(AppMenus* instance)
{
delete instance;
}
// static
AppMenus* AppMenus::instance()
{
static AppMenus* instance = NULL;
if (!instance) {
instance = new AppMenus;
App::instance()->Exit.connect(Bind<void>(&destroy_instance, instance));
}
return instance;
}
AppMenus::AppMenus()
: m_recentListMenuitem(NULL)
{
}
void AppMenus::reload()
{
TiXmlDocument& doc(GuiXml::instance()->doc());
TiXmlHandle handle(&doc);
const char* path = GuiXml::instance()->filename();
/**************************************************/
/* load menus */
/**************************************************/
PRINTF(" - Loading menus from \"%s\"...\n", path);
m_rootMenu.reset(loadMenuById(handle, "main_menu"));
// Add a warning element because the user is not using the last well-known gui.xml file.
if (GuiXml::instance()->version() != VERSION)
m_rootMenu->insertChild(0, createInvalidVersionMenuitem());
PRINTF("Main menu loaded.\n");
m_documentTabPopupMenu.reset(loadMenuById(handle, "document_tab_popup"));
m_layerPopupMenu.reset(loadMenuById(handle, "layer_popup"));
m_framePopupMenu.reset(loadMenuById(handle, "frame_popup"));
m_celPopupMenu.reset(loadMenuById(handle, "cel_popup"));
m_celMovementPopupMenu.reset(loadMenuById(handle, "cel_movement_popup"));
/**************************************************/
/* load keyboard shortcuts for commands */
/**************************************************/
PRINTF(" - Loading commands keyboard shortcuts from \"%s\"...\n", path);
// <gui><keyboard><commands><key>
TiXmlElement* xmlKey = handle
.FirstChild("gui")
.FirstChild("keyboard")
.FirstChild("commands")
.FirstChild("key").ToElement();
while (xmlKey) {
const char* command_name = xmlKey->Attribute("command");
const char* command_key = xmlKey->Attribute("shortcut");
if (command_name && command_key) {
Command* command = CommandsModule::instance()->getCommandByName(command_name);
if (command) {
// Read params
Params params;
TiXmlElement* xmlParam = xmlKey->FirstChildElement("param");
while (xmlParam) {
const char* param_name = xmlParam->Attribute("name");
const char* param_value = xmlParam->Attribute("value");
if (param_name && param_value)
params.set(param_name, param_value);
xmlParam = xmlParam->NextSiblingElement();
}
bool first_shortcut =
(get_accel_to_execute_command(command_name, &params) == NULL);
PRINTF(" - Shortcut for command `%s' <%s>\n", command_name, command_key);
// add the keyboard shortcut to the command
Accelerator* accel =
add_keyboard_shortcut_to_execute_command(command_key, command_name, &params);
// add the shortcut to the menuitems with this
// command (this is only visual, the "manager_msg_proc"
// is the only one that process keyboard shortcuts)
if (first_shortcut)
applyShortcutToMenuitemsWithCommand(m_rootMenu, command, &params, accel);
}
}
xmlKey = xmlKey->NextSiblingElement();
}
// Load keyboard shortcuts for tools
PRINTF(" - Loading tools keyboard shortcuts from \"%s\"...\n", path);
// <gui><keyboard><tools><key>
xmlKey = handle
.FirstChild("gui")
.FirstChild("keyboard")
.FirstChild("tools")
.FirstChild("key").ToElement();
while (xmlKey) {
const char* tool_id = xmlKey->Attribute("tool");
const char* tool_key = xmlKey->Attribute("shortcut");
if (tool_id && tool_key) {
tools::Tool* tool = App::instance()->getToolBox()->getToolById(tool_id);
if (tool) {
PRINTF(" - Shortcut for tool `%s': <%s>\n", tool_id, tool_key);
add_keyboard_shortcut_to_change_tool(tool_key, tool);
}
}
xmlKey = xmlKey->NextSiblingElement();
}
// Load keyboard shortcuts for quicktools
// <gui><keyboard><quicktools><key>
xmlKey = handle
.FirstChild("gui")
.FirstChild("keyboard")
.FirstChild("quicktools")
.FirstChild("key").ToElement();
while (xmlKey) {
const char* tool_id = xmlKey->Attribute("tool");
const char* tool_key = xmlKey->Attribute("shortcut");
if (tool_id && tool_key) {
tools::Tool* tool = App::instance()->getToolBox()->getToolById(tool_id);
if (tool) {
PRINTF(" - Shortcut for quicktool `%s': <%s>\n", tool_id, tool_key);
add_keyboard_shortcut_to_quicktool(tool_key, tool);
}
}
xmlKey = xmlKey->NextSiblingElement();
}
// Load special keyboard shortcuts for sprite editor customization
// <gui><keyboard><spriteeditor>
xmlKey = handle
.FirstChild("gui")
.FirstChild("keyboard")
.FirstChild("spriteeditor")
.FirstChild("key").ToElement();
while (xmlKey) {
const char* tool_action = xmlKey->Attribute("action");
const char* tool_key = xmlKey->Attribute("shortcut");
if (tool_action && tool_key) {
PRINTF(" - Shortcut for sprite editor `%s': <%s>\n", tool_action, tool_key);
add_keyboard_shortcut_to_spriteeditor(tool_key, tool_action);
}
xmlKey = xmlKey->NextSiblingElement();
}
}
bool AppMenus::rebuildRecentList()
{
MenuItem* list_menuitem = m_recentListMenuitem;
MenuItem* menuitem;
// Update the recent file list menu item
if (list_menuitem) {
if (list_menuitem->hasSubmenuOpened())
return false;
Command* cmd_open_file = CommandsModule::instance()->getCommandByName(CommandId::OpenFile);
Menu* submenu = list_menuitem->getSubmenu();
if (submenu) {
list_menuitem->setSubmenu(NULL);
submenu->deferDelete();
}
// Build the menu of recent files
submenu = new Menu();
list_menuitem->setSubmenu(submenu);
RecentFiles::const_iterator it = App::instance()->getRecentFiles()->files_begin();
RecentFiles::const_iterator end = App::instance()->getRecentFiles()->files_end();
if (it != end) {
Params params;
for (; it != end; ++it) {
const char* filename = it->c_str();
params.set("filename", filename);
menuitem = new MenuItem2(get_filename(filename), cmd_open_file, &params);
submenu->addChild(menuitem);
}
}
else {
menuitem = new MenuItem2("Nothing", NULL, NULL);
menuitem->setEnabled(false);
submenu->addChild(menuitem);
}
}
return true;
}
Menu* AppMenus::loadMenuById(TiXmlHandle& handle, const char* id)
{
ASSERT(id != NULL);
//PRINTF("loadMenuById(%s)\n", id);
// <gui><menus><menu>
TiXmlElement* xmlMenu = handle
.FirstChild("gui")
.FirstChild("menus")
.FirstChild("menu").ToElement();
while (xmlMenu) {
const char* menu_id = xmlMenu->Attribute("id");
if (menu_id && strcmp(menu_id, id) == 0)
return convertXmlelemToMenu(xmlMenu);
xmlMenu = xmlMenu->NextSiblingElement();
}
throw base::Exception("Error loading menu '%s'\nReinstall the application.", id);
}
Menu* AppMenus::convertXmlelemToMenu(TiXmlElement* elem)
{
Menu* menu = new Menu();
//PRINTF("convertXmlelemToMenu(%s, %s, %s)\n", elem->Value(), elem->Attribute("id"), elem->Attribute("text"));
TiXmlElement* child = elem->FirstChildElement();
while (child) {
Widget* menuitem = convertXmlelemToMenuitem(child);
if (menuitem)
menu->addChild(menuitem);
else
throw base::Exception("Error converting the element \"%s\" to a menu-item.\n",
static_cast<const char*>(child->Value()));
child = child->NextSiblingElement();
}
return menu;
}
Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem)
{
// is it a <separator>?
if (strcmp(elem->Value(), "separator") == 0)
return new Separator(NULL, JI_HORIZONTAL);
const char* command_name = elem->Attribute("command");
Command* command =
command_name ? CommandsModule::instance()->getCommandByName(command_name):
NULL;
// load params
Params params;
if (command) {
TiXmlElement* xmlParam = elem->FirstChildElement("param");
while (xmlParam) {
const char* param_name = xmlParam->Attribute("name");
const char* param_value = xmlParam->Attribute("value");
if (param_name && param_value)
params.set(param_name, param_value);
xmlParam = xmlParam->NextSiblingElement();
}
}
/* create the item */
MenuItem2* menuitem = new MenuItem2(elem->Attribute("text"),
command, command ? &params: NULL);
if (!menuitem)
return NULL;
/* has it a ID? */
const char* id = elem->Attribute("id");
if (id) {
/* recent list menu */
if (strcmp(id, "recent_list") == 0) {
m_recentListMenuitem = menuitem;
}
}
// Has it a sub-menu (<menu>)?
if (strcmp(elem->Value(), "menu") == 0) {
// Create the sub-menu
Menu* subMenu = convertXmlelemToMenu(elem);
if (!subMenu)
throw base::Exception("Error reading the sub-menu\n");
menuitem->setSubmenu(subMenu);
}
return menuitem;
}
Widget* AppMenus::createInvalidVersionMenuitem()
{
MenuItem2* menuitem = new MenuItem2("WARNING!", NULL, NULL);
Menu* subMenu = new Menu();
subMenu->addChild(new MenuItem2(PACKAGE " is using a customized gui.xml (maybe from your HOME directory).", NULL, NULL));
subMenu->addChild(new MenuItem2("You should update your customized gui.xml file to the new version to get", NULL, NULL));
subMenu->addChild(new MenuItem2("the latest commands available.", NULL, NULL));
subMenu->addChild(new Separator(NULL, JI_HORIZONTAL));
subMenu->addChild(new MenuItem2("You can bypass this validation adding the correct version", NULL, NULL));
subMenu->addChild(new MenuItem2("number in <gui version=\"" VERSION "\"> element.", NULL, NULL));
menuitem->setSubmenu(subMenu);
return menuitem;
}
void AppMenus::applyShortcutToMenuitemsWithCommand(Menu* menu, Command *command, Params* params, Accelerator* accel)
{
UI_FOREACH_WIDGET(menu->getChildren(), it) {
Widget* child = *it;
if (child->getType() == JI_MENUITEM) {
ASSERT(dynamic_cast<MenuItem2*>(child) != NULL);
MenuItem2* menuitem = static_cast<MenuItem2*>(child);
Command* mi_command = menuitem->getCommand();
Params* mi_params = menuitem->getParams();
if (mi_command &&
ustricmp(mi_command->short_name(), command->short_name()) == 0 &&
((mi_params && *mi_params == *params) ||
(Params() == *params))) {
// Set the accelerator to be shown in this menu-item
menuitem->setAccel(new Accelerator(*accel));
}
if (Menu* submenu = menuitem->getSubmenu())
applyShortcutToMenuitemsWithCommand(submenu, command, params, accel);
}
}
}