/*
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 "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