/* This file is part of Ingen. Copyright 2007-2012 David Robillard Ingen is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version. Ingen 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 Affero General Public License for details. You should have received a copy of the GNU Affero General Public License along with Ingen. If not, see . */ #include #include #include #include #include #include #include "ganv/Edge.hpp" #include "ingen/EngineBase.hpp" #include "ingen/Interface.hpp" #include "ingen/client/ClientStore.hpp" #include "ingen/client/ObjectModel.hpp" #include "ingen/client/PatchModel.hpp" #include "ingen/client/SigClientInterface.hpp" #include "ingen/shared/World.hpp" #include "ingen/shared/runtime_paths.hpp" #include "lilv/lilv.h" #include "raul/Path.hpp" #include "raul/log.hpp" #include "App.hpp" #include "Configuration.hpp" #include "ConnectWindow.hpp" #include "LoadPluginWindow.hpp" #include "MessagesWindow.hpp" #include "NodeModule.hpp" #include "PatchTreeWindow.hpp" #include "PatchWindow.hpp" #include "Port.hpp" #include "SubpatchModule.hpp" #include "ThreadedLoader.hpp" #include "WidgetFactory.hpp" #include "WindowFactory.hpp" #define LOG(s) (s("[GUI] ")) using namespace std; using namespace Raul; using namespace Ingen::Client; namespace Raul { class Deletable; } namespace Ingen { namespace Client { class PluginModel; } using namespace Client; using namespace Shared; namespace GUI { class Port; Gtk::Main* App::_main = 0; App::App(Ingen::Shared::World* world) : _configuration(new Configuration(*this)) , _about_dialog(NULL) , _window_factory(new WindowFactory(*this)) , _world(world) , _sample_rate(48000) , _enable_signal(true) { Glib::set_application_name("Ingen"); gtk_window_set_default_icon_name("ingen"); WidgetFactory::get_widget_derived("connect_win", _connect_window); WidgetFactory::get_widget_derived("messages_win", _messages_window); WidgetFactory::get_widget_derived("patch_tree_win", _patch_tree_window); WidgetFactory::get_widget("about_win", _about_dialog); _connect_window->init_dialog(*this); _messages_window->init_window(*this); _patch_tree_window->init_window(*this); _about_dialog->property_program_name() = "Ingen"; _about_dialog->property_logo_icon_name() = "ingen"; PluginModel::set_rdf_world(*world->rdf_world()); PluginModel::set_lilv_world(world->lilv_world()); } App::~App() { delete _configuration; delete _window_factory; } SharedPtr App::create(Ingen::Shared::World* world) { _main = new Gtk::Main(&world->argc(), &world->argv()); App* app = new App(world); // Load configuration settings app->configuration()->load_settings(); app->configuration()->apply_settings(); // Set default window icon app->_about_dialog->property_program_name() = "Ingen"; app->_about_dialog->property_logo_icon_name() = "ingen"; gtk_window_set_default_icon_name("ingen"); // Set style for embedded node GUIs const string rc_style = "style \"ingen_embedded_node_gui_style\" {\n" "bg[NORMAL] = \"#212222\"\n" "bg[ACTIVE] = \"#505050\"\n" "bg[PRELIGHT] = \"#525454\"\n" "bg[SELECTED] = \"#99A0A0\"\n" "bg[INSENSITIVE] = \"#F03030\"\n" "fg[NORMAL] = \"#FFFFFF\"\n" "fg[ACTIVE] = \"#FFFFFF\"\n" "fg[PRELIGHT] = \"#FFFFFF\"\n" "fg[SELECTED] = \"#FFFFFF\"\n" "fg[INSENSITIVE] = \"#FFFFFF\"\n" "}\n" "widget \"*ingen_embedded_node_gui_container*\" style \"ingen_embedded_node_gui_style\"\n"; Gtk::RC::parse_string(rc_style); return SharedPtr(app); } void App::run() { _connect_window->start(*this, world()); // Run main iterations here until we're attached to the engine. Otherwise // with 'ingen -egl' we'd get a bunch of notifications about load // immediately before even knowing about the root patch or plugins) while (!_connect_window->attached()) if (_main->iteration()) break; _main->run(); LOG(info)("Exiting\n"); } void App::attach(SharedPtr client) { assert(!_client); assert(!_store); assert(!_loader); if (_world->engine()) { _world->engine()->register_client(client->uri(), client); } _client = client; _store = SharedPtr(new ClientStore(_world->uris(), _world->interface(), client)); _loader = SharedPtr(new ThreadedLoader(*this, _world->interface())); _patch_tree_window->init(*this, *_store); _client->signal_response().connect( sigc::mem_fun(this, &App::response)); _client->signal_error().connect( sigc::mem_fun(this, &App::error_message)); _client->signal_property_change().connect( sigc::mem_fun(this, &App::property_change)); } void App::detach() { if (_world->interface()) { _window_factory->clear(); _store->clear(); _loader.reset(); _store.reset(); _client.reset(); _world->set_interface(SharedPtr()); } } SharedPtr App::serialiser() { if (!_world->serialiser()) _world->load_module("serialisation"); return _world->serialiser(); } void App::response(int32_t id, Status status) { if (status) { error_message(ingen_status_string(status)); } } void App::error_message(const string& str) { _messages_window->post(str); if (!_messages_window->is_visible()) _messages_window->present(); _messages_window->set_urgency_hint(true); } void App::property_change(const Raul::URI& subject, const Raul::URI& key, const Raul::Atom& value) { if (subject == uris().ingen_engine && key == uris().ingen_sampleRate) { if (value.type() == forge().Int) { LOG(info)(Raul::fmt("Sample rate: %1%\n") % uris().forge.str(value)); _sample_rate = value.get_int32(); } else { error << "Engine sample rate property is not an integer" << std::endl; } } } void App::port_activity(Port* port) { std::pair inserted = _activity_ports.insert(make_pair(port, false)); if (inserted.second) inserted.first->second = false; port->set_highlighted(true); } void App::activity_port_destroyed(Port* port) { ActivityPorts::iterator i = _activity_ports.find(port); if (i != _activity_ports.end()) _activity_ports.erase(i); return; } bool App::animate() { for (ActivityPorts::iterator i = _activity_ports.begin(); i != _activity_ports.end(); ) { ActivityPorts::iterator next = i; ++next; if ((*i).second) { // saw it last time, unhighlight and pop (*i).first->set_highlighted(false); _activity_ports.erase(i); } else { (*i).second = true; } i = next; } return true; } /******** Event Handlers ************/ void App::register_callbacks() { Glib::signal_timeout().connect( sigc::mem_fun(*this, &App::gtk_main_iteration), 25, G_PRIORITY_DEFAULT); Glib::signal_timeout().connect( sigc::mem_fun(*this, &App::animate), 50, G_PRIORITY_DEFAULT); } bool App::gtk_main_iteration() { if (!_client) return false; if (_world->engine()) { if (!_world->engine()->main_iteration()) { Gtk::Main::quit(); return false; } } else { _enable_signal = false; _client->emit_signals(); _enable_signal = true; } return true; } void App::show_about() { _about_dialog->run(); _about_dialog->hide(); } /** Prompt (if necessary) and quit application (if confirmed). * @return true iff the application quit. */ bool App::quit(Gtk::Window* dialog_parent) { bool quit = true; if (_world->engine()) { Gtk::MessageDialog d( "The engine is running in this process. Quitting will terminate Ingen." "\n\n" "Are you sure you want to quit?", true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true); if (dialog_parent) { d.set_transient_for(*dialog_parent); } d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); d.add_button(Gtk::Stock::QUIT, Gtk::RESPONSE_CLOSE); quit = (d.run() == Gtk::RESPONSE_CLOSE); } if (quit) Gtk::Main::quit(); return quit; } struct IconDestroyNotification { IconDestroyNotification(App& a, pair k) : app(a) , key(k) {} App& app; pair key; }; Glib::RefPtr App::icon_from_path(const string& path, int size) { /* If weak references to Glib::Objects are needed somewhere else it will probably be a good idea to create a proper WeakPtr class instead of using raw pointers, but for a single use this will do. */ Glib::RefPtr buf; if (path.length() == 0) return buf; Icons::iterator iter = _icons.find(make_pair(path, size)); if (iter != _icons.end()) { // we need to reference manually since the RefPtr constructor doesn't do it iter->second->reference(); return Glib::RefPtr(iter->second); } try { buf = Gdk::Pixbuf::create_from_file(path, size, size); _icons.insert(make_pair(make_pair(path, size), buf.operator->())); buf->add_destroy_notify_callback( new IconDestroyNotification(*this, make_pair(path, size)), &App::icon_destroyed); } catch (Glib::Error e) { warn << "Error loading icon: " << e.what() << endl; } return buf; } void* App::icon_destroyed(void* data) { IconDestroyNotification* note = (IconDestroyNotification*)data; Icons::iterator iter = note->app._icons.find(note->key); if (iter != note->app._icons.end()) note->app._icons.erase(iter); delete note; // allocated in App::icon_from_path return NULL; } bool App::can_control(const Client::PortModel* port) const { return port->is_a(uris().lv2_ControlPort) || port->is_a(uris().lv2_CVPort) || (port->is_a(uris().atom_AtomPort) && (port->supports(uris().atom_Float) || port->supports(uris().atom_String))); } uint32_t App::sample_rate() const { return _sample_rate; } } // namespace GUI } // namespace Ingen