diff options
author | David Robillard <d@drobilla.net> | 2008-08-22 03:26:33 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2008-08-22 03:26:33 +0000 |
commit | 4637ceb876904731d2628563e96a89961f9b7781 (patch) | |
tree | b20bd41c8fcdee1b8a5e76a0aa4a3ca213f82473 | |
parent | 503944e72ce146e5bed2556e7b2caa5a41edd7ea (diff) | |
download | patchage-4637ceb876904731d2628563e96a89961f9b7781.tar.gz patchage-4637ceb876904731d2628563e96a89961f9b7781.tar.bz2 patchage-4637ceb876904731d2628563e96a89961f9b7781.zip |
Lash D-Bus support and projects list from LADI Patchage, with improvements/cleanup/sanification/etc.
Remove liblash stuff (meh, what the hell... here's to new beginnings).
Enable/disable/hide/etc patchage widgets better based on available (compiled in) functionality.
git-svn-id: http://svn.drobilla.net/lad/patchage@1462 a436a847-0d15-0410-975c-d299462d15a1
-rw-r--r-- | configure.ac | 27 | ||||
-rw-r--r-- | src/AlsaDriver.cpp | 14 | ||||
-rw-r--r-- | src/DBus.cpp | 121 | ||||
-rw-r--r-- | src/DBus.hpp | 61 | ||||
-rw-r--r-- | src/JackDriver.cpp | 18 | ||||
-rw-r--r-- | src/JackSettingsDialog.hpp | 84 | ||||
-rw-r--r-- | src/LashClient.cpp | 83 | ||||
-rw-r--r-- | src/LashClient.hpp | 61 | ||||
-rw-r--r-- | src/LashDriver.cpp | 170 | ||||
-rw-r--r-- | src/LashDriver.hpp | 82 | ||||
-rw-r--r-- | src/LashProxy.cpp | 781 | ||||
-rw-r--r-- | src/LashProxy.hpp | 61 | ||||
-rw-r--r-- | src/LoadProjectDialog.cpp | 154 | ||||
-rw-r--r-- | src/LoadProjectDialog.hpp | 53 | ||||
-rw-r--r-- | src/Makefile.am | 25 | ||||
-rw-r--r-- | src/Patchage.cpp | 208 | ||||
-rw-r--r-- | src/Patchage.hpp | 65 | ||||
-rw-r--r-- | src/PatchageCanvas.cpp | 2 | ||||
-rw-r--r-- | src/Project.cpp | 175 | ||||
-rw-r--r-- | src/Project.hpp | 73 | ||||
-rw-r--r-- | src/ProjectList.cpp | 305 | ||||
-rw-r--r-- | src/ProjectList.hpp | 38 | ||||
-rw-r--r-- | src/ProjectPropertiesDialog.cpp | 83 | ||||
-rw-r--r-- | src/ProjectPropertiesDialog.hpp | 38 | ||||
-rw-r--r-- | src/Session.cpp | 124 | ||||
-rw-r--r-- | src/Session.hpp | 54 | ||||
-rw-r--r-- | src/Widget.hpp | 1 | ||||
-rw-r--r-- | src/patchage.glade | 483 |
28 files changed, 2703 insertions, 741 deletions
diff --git a/configure.ac b/configure.ac index ccddd6a..4e33b11 100644 --- a/configure.ac +++ b/configure.ac @@ -147,24 +147,21 @@ else ALSA_FOUND="no" fi -# LASH support -build_lash="yes" -AC_ARG_ENABLE(lash, - [AS_HELP_STRING(--enable-lash, [Enable LASH session management support (yes)])], - [ if test x$enable_lash = xno ; then build_lash=no ; fi ]) - -have_lash="no" -if test "$build_lash" = "yes"; then - PKG_CHECK_MODULES(LASH, lash-1.0 >= 0.5.2, have_lash="yes", have_lash="no") +# DBUS (for new LASH) +build_dbus="yes" +AC_ARG_ENABLE(dbus, + [AS_HELP_STRING(--enable-dbus, [Enable D-Bus (for new LASH) (yes if available)])], + [ if test x$enable_dbus = xno ; then build_dbus=no ; fi ]) +have_dbus="no" +if test "$build_dbus" = "yes"; then + PKG_CHECK_MODULES(DBUS, dbus-glib-1, [have_dbus="yes"], [have_dbus="no"]) fi -if test "$have_lash" = "yes"; then - AC_DEFINE(HAVE_LASH, 1, [Has lash.h]) -else - AC_MSG_WARN([LASH not found, session support will not be built.]) +if test "$have_dbus" = "yes"; then + AC_DEFINE(HAVE_DBUS, 1, [Has DBUS]) fi AM_CONDITIONAL(WITH_ALSA, [test "x$ALSA_FOUND" = "xyes"]) -AM_CONDITIONAL(WITH_LASH, [test "x$have_lash" = "xyes"]) +AM_CONDITIONAL(WITH_DBUS, [test "x$have_dbus" = "xyes"]) AM_CONDITIONAL(WITH_JACK, [test "x$build_jack" = "xyes"]) AM_CONDITIONAL(WITH_JACK_DBUS, [test "x$build_jack_dbus" = "xyes"]) @@ -206,7 +203,7 @@ AC_MSG_RESULT([Jack support: yes (libjack)]) else AC_MSG_RESULT([Jack support: no]) fi -AC_MSG_RESULT([LASH support: $build_lash]) +AC_MSG_RESULT([DBUS/LASH support: $build_dbus]) AC_MSG_RESULT([ALSA support: $ALSA_FOUND]) AC_MSG_RESULT([]) AC_MSG_RESULT([C FLAGS: $CFLAGS]) diff --git a/src/AlsaDriver.cpp b/src/AlsaDriver.cpp index 8794386..1662471 100644 --- a/src/AlsaDriver.cpp +++ b/src/AlsaDriver.cpp @@ -53,10 +53,10 @@ AlsaDriver::attach(bool /*launch_daemon*/) SND_SEQ_OPEN_DUPLEX, SND_SEQ_NONBLOCK); if (ret) { - _app->status_message("[ALSA] Unable to attach"); + _app->status_msg("[ALSA] Unable to attach"); _seq = NULL; } else { - _app->status_message("[ALSA] Attached"); + _app->status_msg("[ALSA] Attached"); snd_seq_set_client_name(_seq, "Patchage"); @@ -82,7 +82,7 @@ AlsaDriver::detach() snd_seq_close(_seq); _seq = NULL; signal_detached.emit(); - _app->status_message("[ALSA] Detached"); + _app->status_msg("[ALSA] Detached"); } } @@ -395,10 +395,10 @@ AlsaDriver::connect(boost::shared_ptr<PatchagePort> src_port, boost::shared_ptr< } if (result) - _app->status_message(string("[ALSA] Connected ") + _app->status_msg(string("[ALSA] Connected ") + src_port->full_name() + " -> " + dst_port->full_name()); else - _app->status_message(string("[ALSA] Unable to connect ") + _app->status_msg(string("[ALSA] Unable to connect ") + src_port->full_name() + " -> " + dst_port->full_name()); return (!result); @@ -438,10 +438,10 @@ AlsaDriver::disconnect(boost::shared_ptr<PatchagePort> src_port, boost::shared_p } if (result) - _app->status_message(string("[ALSA] Disconnected ") + _app->status_msg(string("[ALSA] Disconnected ") + src_port->full_name() + " -> " + dst_port->full_name()); else - _app->status_message(string("[ALSA] Unable to disconnect ") + _app->status_msg(string("[ALSA] Unable to disconnect ") + src_port->full_name() + " -> " + dst_port->full_name()); return (!result); diff --git a/src/DBus.cpp b/src/DBus.cpp new file mode 100644 index 0000000..5bd648b --- /dev/null +++ b/src/DBus.cpp @@ -0,0 +1,121 @@ +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "DBus.hpp" +#include <stdexcept> +#include <boost/format.hpp> +#include "Patchage.hpp" + +#define DBUS_CALL_DEFAULT_TIMEOUT 1000 // in milliseconds + + +DBus::DBus(Patchage* app) + : _app(app) + , _connection(NULL) +{ + dbus_error_init(&_error); + + // Connect to the bus + _connection = dbus_bus_get(DBUS_BUS_SESSION, &_error); + if (dbus_error_is_set(&_error)) { + app->error_msg("dbus_bus_get() failed"); + app->error_msg(_error.message); + dbus_error_free(&_error); + } + + dbus_connection_setup_with_g_main(_connection, NULL); +} + + +bool +DBus::call( + bool response_expected, + const char * service, + const char * object, + const char * iface, + const char * method, + DBusMessage ** reply_ptr_ptr, + int in_type, + va_list ap) +{ + DBusMessage* request_ptr; + DBusMessage* reply_ptr; + + request_ptr = dbus_message_new_method_call( + service, + object, + iface, + method); + if (!request_ptr) { + throw std::runtime_error("dbus_message_new_method_call() returned 0"); + } + + dbus_message_append_args_valist(request_ptr, in_type, ap); + + // send message and get a handle for a reply + reply_ptr = dbus_connection_send_with_reply_and_block( + _connection, + request_ptr, + DBUS_CALL_DEFAULT_TIMEOUT, + &_error); + + dbus_message_unref(request_ptr); + + if (!reply_ptr) { + if (response_expected) { + _app->error_msg(str(boost::format("no reply from server when calling method '%s'" + ", error is '%s'") % method % _error.message)); + } + dbus_error_free(&_error); + } else { + *reply_ptr_ptr = reply_ptr; + } + + return reply_ptr; +} + +bool +DBus::call( + bool response_expected, + const char * serivce, + const char * object, + const char * iface, + const char * method, + DBusMessage ** reply_ptr_ptr, + int in_type, + ...) +{ + bool ret; + va_list ap; + + va_start(ap, in_type); + + ret = _app->dbus()->call( + response_expected, + serivce, + object, + iface, + method, + reply_ptr_ptr, + in_type, + ap); + + va_end(ap); + + return (ap != NULL); +} diff --git a/src/DBus.hpp b/src/DBus.hpp new file mode 100644 index 0000000..ffcabbb --- /dev/null +++ b/src/DBus.hpp @@ -0,0 +1,61 @@ +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <http://drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_DBUS_HPP +#define PATCHAGE_DBUS_HPP + +#include <dbus/dbus.h> +#include <dbus/dbus-glib.h> +#include <dbus/dbus-glib-lowlevel.h> + +class Patchage; + +class DBus { +public: + DBus(Patchage* app); + + bool call( + bool response_expected, + const char* service, + const char* object, + const char* iface, + const char* method, + DBusMessage** reply_ptr, + int in_type, + va_list ap); + + bool call( + bool response_expected, + const char* service, + const char* object, + const char* iface, + const char* method, + DBusMessage** reply_ptr, + int in_type, + ...); + + DBusConnection* connection() { return _connection; } + DBusError& error() { return _error; } + +private: + Patchage* _app; + DBusConnection* _connection; + DBusError _error; +}; + +#endif // PATCHAGE_DBUS_HPP diff --git a/src/JackDriver.cpp b/src/JackDriver.cpp index 2b98ecb..d31d499 100644 --- a/src/JackDriver.cpp +++ b/src/JackDriver.cpp @@ -68,7 +68,7 @@ JackDriver::attach(bool launch_daemon) jack_options_t options = (!launch_daemon) ? JackNoStartServer : JackNullOption; _client = jack_client_open("Patchage", options, NULL); if (_client == NULL) { - _app->status_message("[JACK] Unable to create client"); + _app->status_msg("[JACK] Unable to create client"); _is_activated = false; } else { jack_client_t* const client = _client; @@ -87,9 +87,9 @@ JackDriver::attach(bool launch_daemon) if (!jack_activate(client)) { _is_activated = true; signal_attached.emit(); - _app->status_message("[JACK] Attached"); + _app->status_msg("[JACK] Attached"); } else { - _app->status_message("[JACK] ERROR: Failed to attach"); + _app->status_msg("[JACK] ERROR: Failed to attach"); _is_activated = false; } } @@ -108,7 +108,7 @@ JackDriver::detach() destroy_all_ports(); _is_activated = false; signal_detached.emit(); - _app->status_message("[JACK] Detached"); + _app->status_msg("[JACK] Detached"); } } @@ -388,10 +388,10 @@ JackDriver::connect(boost::shared_ptr<PatchagePort> src_port, boost::shared_ptr< int result = jack_connect(_client, src_port->full_name().c_str(), dst_port->full_name().c_str()); if (result == 0) - _app->status_message(string("[JACK] Connected ") + _app->status_msg(string("[JACK] Connected ") + src_port->full_name() + " -> " + dst_port->full_name()); else - _app->status_message(string("[JACK] Unable to connect ") + _app->status_msg(string("[JACK] Unable to connect ") + src_port->full_name() + " -> " + dst_port->full_name()); return (!result); @@ -411,10 +411,10 @@ JackDriver::disconnect(boost::shared_ptr<PatchagePort> const src_port, boost::sh int result = jack_disconnect(_client, src_port->full_name().c_str(), dst_port->full_name().c_str()); if (result == 0) - _app->status_message(string("[JACK] Disconnected ") + _app->status_msg(string("[JACK] Disconnected ") + src_port->full_name() + " -> " + dst_port->full_name()); else - _app->status_message(string("[JACK] Unable to disconnect ") + _app->status_msg(string("[JACK] Unable to disconnect ") + src_port->full_name() + " -> " + dst_port->full_name()); return (!result); @@ -574,7 +574,7 @@ JackDriver::set_buffer_size(jack_nframes_t size) } if (jack_set_buffer_size(_client, size)) { - _app->status_message("[JACK] ERROR: Unable to set buffer size"); + _app->status_msg("[JACK] ERROR: Unable to set buffer size"); return false; } else { return true; diff --git a/src/JackSettingsDialog.hpp b/src/JackSettingsDialog.hpp deleted file mode 100644 index f26fa91..0000000 --- a/src/JackSettingsDialog.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* This file is part of Patchage. - * Copyright (C) 2007 Dave Robillard <http://drobilla.net> - * - * Patchage 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. - * - * Patchage 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 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., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#ifndef PATCHAGE_JACK_SETTINGS_DIALOG_HPP -#define PATCHAGE_JACK_SETTINGS_DIALOG_HPP - -#include <libglademm/xml.h> -#include <libglademm.h> -#include <gtkmm/dialog.h> -#include <fstream> -#include <string> - - -class JackSettingsDialog : public Gtk::Dialog { -public: - JackSettingsDialog(BaseObjectType* cobject, const Glib::RefPtr<Gnome::Glade::Xml>& xml) - : Gtk::Dialog(cobject) - { - xml->get_widget("jack_settings_command_entry", _command_entry); - xml->get_widget("jack_settings_cancel_but", _cancel_but); - xml->get_widget("jack_settings_ok_but", _ok_but); - - _cancel_but->signal_clicked().connect( - sigc::mem_fun(this, &JackSettingsDialog::on_cancel)); - _ok_but->signal_clicked().connect( - sigc::mem_fun(this, &JackSettingsDialog::on_ok)); - - _command_entry->set_text(current_jack_command()); - } - -private: - void on_cancel() { hide(); } - - std::string current_jack_command() { - std::string result; - - const char* const home = getenv("HOME"); - if (home) { - std::string jackdrc_path(home); - jackdrc_path += "/.jackdrc"; - - std::ifstream jackdrc(jackdrc_path.c_str()); - std::getline(jackdrc, result); - jackdrc.close(); - } - - return result; - } - - void on_ok() { - hide(); - const char* const home = getenv("HOME"); - if (home) { - std::string jackdrc_path(home); - jackdrc_path += "/.jackdrc"; - - std::ofstream jackdrc(jackdrc_path.c_str()); - jackdrc << _command_entry->get_text() << std::endl; - jackdrc.close(); - } - } - - Gtk::Entry* _command_entry; - Gtk::Button* _cancel_but; - Gtk::Button* _ok_but; -}; - - -#endif // PATCHAGE_JACK_SETTINGS_DIALOG_HPP - diff --git a/src/LashClient.cpp b/src/LashClient.cpp new file mode 100644 index 0000000..c41fef6 --- /dev/null +++ b/src/LashClient.cpp @@ -0,0 +1,83 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "LashClient.hpp" +#include "Patchage.hpp" + +using namespace std; + +struct LashClientImpl { + LashProxy* proxy; + Project* project; + string id; + string name; +}; + +LashClient::LashClient( + LashProxy* proxy, + Project* project, + const string& id, + const string& name) +{ + _impl = new LashClientImpl; + _impl->proxy = proxy; + _impl->project = project; + _impl->id = id; + _impl->name = name; + + //g_app->info_msg("client created"); +} + +LashClient::~LashClient() +{ + delete _impl; + //g_app->info_msg("client destroyed"); +} + +Project* +LashClient::get_project() +{ + return _impl->project; +} + +const string& +LashClient::get_id() const +{ + return _impl->id; +} + +const string& +LashClient::get_name() const +{ + return _impl->name; +} + +void +LashClient::do_rename(const string& name) +{ + if (_impl->name != name) { + //_impl->proxy->client_rename(_impl->id, name); + } +} + +void +LashClient::on_name_changed(const string& name) +{ + _impl->name = name; + _signal_renamed.emit(); +} diff --git a/src/LashClient.hpp b/src/LashClient.hpp new file mode 100644 index 0000000..65f6caa --- /dev/null +++ b/src/LashClient.hpp @@ -0,0 +1,61 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_LASH_CLIENT_HPP +#define PATCHAGE_LASH_CLIENT_HPP + +#include <string> +#include <sigc++/signal.h> + +class LashClientImpl; +class LashProxy; +class LashProxyImpl; +class Project; + +class LashClient +{ +public: + LashClient( + LashProxy* proxy, + Project* project, + const std::string& id, + const std::string& name); + + ~LashClient(); + + Project* + get_project(); + + const std::string& get_id() const; + const std::string& get_name() const; + + void do_rename(const std::string& name); + + sigc::signal<void> _signal_renamed; + +private: + friend class LashProxyImpl; + + void + on_name_changed(const std::string& name); + + LashClientImpl* _impl; +}; + +#endif // PATCHAGE_LASH_CLIENT_HPP diff --git a/src/LashDriver.cpp b/src/LashDriver.cpp deleted file mode 100644 index 856ea2d..0000000 --- a/src/LashDriver.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/* This file is part of Patchage. - * Copyright (C) 2007 Dave Robillard <http://drobilla.net> - * - * Patchage 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. - * - * Patchage 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 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., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#include <iostream> -#include <string> -#include CONFIG_H_PATH -#include "LashDriver.hpp" -#include "Patchage.hpp" -#include "StateManager.hpp" - -using namespace std; - - -LashDriver::LashDriver(Patchage* app, int argc, char** argv) - : Driver(2) - , _app(app) - , _args(NULL) -{ - _args = lash_extract_args(&argc, &argv); -} - - -LashDriver::~LashDriver() -{ - if (_args) - lash_args_destroy(_args); -} - - -void -LashDriver::attach(bool launch_daemon) -{ - // Already connected - if (_server_interface && _server_interface->enabled()) - return; - - int lash_flags = LASH_Server_Interface | LASH_Config_File; - if (!launch_daemon) - lash_flags |= LASH_No_Start_Server; - - _server_interface = Raul::LashServerInterface::create( - _args, PACKAGE_NAME, lash_flags); - - if (_server_interface) { - _server_interface->signal_project_add.connect(sigc::mem_fun(this, &LashDriver::on_project_add)); - _server_interface->signal_quit.connect(sigc::mem_fun(this, &LashDriver::on_quit)); - signal_attached.emit(); - _app->status_message("[LASH] Attached"); - } else { - _app->status_message("[LASH] Unable to attach to server"); - } -} - - -void -LashDriver::detach() -{ - _server_interface.reset(); - _app->status_message("[LASH] Detached"); - signal_detached.emit(); -} - - -void -LashDriver::on_project_add(const SharedPtr<Raul::LashProject> project) -{ - cout << "[LashDriver] Add project - " << project->name() << endl; - _project_name = project->name(); - project->signal_save_file.connect(sigc::mem_fun(this, &LashDriver::on_save_file)); - project->signal_restore_file.connect(sigc::mem_fun(this, &LashDriver::on_restore_file)); -} - - -void -LashDriver::on_save_file(const string& directory) -{ - cout << "[LashDriver] Save File - " << directory << endl; - _app->store_window_location(); - _app->state_manager()->save(directory + "/locations"); -} - - -void -LashDriver::on_restore_file(const string& directory) -{ - cout << "[LashDriver] Restore File - " << directory << endl; - _app->state_manager()->load(directory + "/locations"); - _app->update_state(); -} - - -void -LashDriver::on_quit() -{ - cout << "[LashDriver] Quit" << endl; -} - - -void -LashDriver::handle_config(lash_config_t* conf) -{ - const char* key = NULL; - const void* val = NULL; - size_t val_size = 0; - - cout << "[LashDriver] LASH Config. Key = " << key << endl; - - key = lash_config_get_key(conf); - val = lash_config_get_value(conf); - val_size = lash_config_get_value_size(conf); -} - - -void -LashDriver::restore_project(const std::string& directory) -{ - _project_name = ""; - _server_interface->restore_project(directory); -} - - -void -LashDriver::set_project_directory(const std::string& directory) -{ - SharedPtr<Raul::LashProject> project = _server_interface->project(_project_name); - - if (project) - project->set_directory(directory); - else - cerr << "[LashDriver] No project \'" << _project_name << "\' to set directory!" << endl; -} - - -void -LashDriver::save_project() -{ - SharedPtr<Raul::LashProject> project = _server_interface->project(_project_name); - - if (project) - project->save(); - else - cerr << "[LashDriver] No project \'" << _project_name << "\' to save!" << endl; -} - - -void -LashDriver::close_project() -{ - SharedPtr<Raul::LashProject> project = _server_interface->project(_project_name); - - if (project) - project->close(); - else - cerr << "[LashDriver] No project \'" << _project_name << "\' to close!" << endl; -} - diff --git a/src/LashDriver.hpp b/src/LashDriver.hpp deleted file mode 100644 index ffe656c..0000000 --- a/src/LashDriver.hpp +++ /dev/null @@ -1,82 +0,0 @@ -/* This file is part of Patchage. - * Copyright (C) 2007 Dave Robillard <http://drobilla.net> - * - * Patchage 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. - * - * Patchage 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 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., - * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA - */ - -#ifndef PATCHAGE_LASHDRIVER_HPP -#define PATCHAGE_LASHDRIVER_HPP - -#include <lash/lash.h> -#include <raul/LashServerInterface.hpp> -#include "Driver.hpp" - -class Patchage; - -class LashDriver : public Driver { -public: - LashDriver(Patchage* app, int argc, char** argv); - ~LashDriver(); - - void attach(bool launch_daemon); - void detach(); - - bool is_attached() const - { return _server_interface && _server_interface->enabled(); } - - boost::shared_ptr<PatchagePort> find_port_view( - Patchage* patchage, - const PatchageEvent::PortRef& ref) { - return boost::shared_ptr<PatchagePort>(); - } - - boost::shared_ptr<PatchagePort> create_port_view( - Patchage* patchage, - const PatchageEvent::PortRef& ref) { - return boost::shared_ptr<PatchagePort>(); - } - - bool connect(boost::shared_ptr<PatchagePort>, boost::shared_ptr<PatchagePort>) - { return false; } - - bool disconnect(boost::shared_ptr<PatchagePort>, boost::shared_ptr<PatchagePort>) - { return false; } - - void refresh() {} - - void process_events(Patchage* app) { _server_interface->process_events(); } - - void restore_project(const std::string& directory); - void set_project_directory(const std::string& directory); - void save_project(); - void close_project(); - -private: - Patchage* _app; - std::string _project_name; - - lash_args_t* _args; - SharedPtr<Raul::LashServerInterface> _server_interface; - - void on_project_add(const SharedPtr<Raul::LashProject> project); - void on_save_file(const std::string& directory); - void on_restore_file(const std::string& directory); - void on_quit(); - - void handle_event(lash_event_t* conf); - void handle_config(lash_config_t* conf); -}; - - -#endif // PATCHAGE_LASHDRIVER_HPP diff --git a/src/LashProxy.cpp b/src/LashProxy.cpp new file mode 100644 index 0000000..1d1580d --- /dev/null +++ b/src/LashProxy.cpp @@ -0,0 +1,781 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <string.h> +#include <boost/shared_ptr.hpp> +#include <boost/format.hpp> + +#include "LashProxy.hpp" +#include "Session.hpp" +#include "Project.hpp" +#include "LashClient.hpp" +#include "DBus.hpp" + +#define LASH_SERVICE "org.nongnu.LASH" +#define LASH_OBJECT "/" +#define LASH_IFACE_CONTROL "org.nongnu.LASH.Control" + +using namespace std; +using boost::shared_ptr; + +struct LashProxyImpl { + void init(Patchage* app); + + void fetch_loaded_projects(); + void fetch_project_clients(shared_ptr<Project> project); + + void error_msg(const std::string& msg); + void info_msg(const std::string& msg); + + static + DBusHandlerResult + dbus_message_hook( + DBusConnection* connection, + DBusMessage* message, + void* proxy); + + bool + call( + bool response_expected, + const char* iface, + const char* method, + DBusMessage** reply_ptr, + int in_type, + ...); + + shared_ptr<Project> + on_project_added(const string& name); + + shared_ptr<LashClient> + on_client_added( + shared_ptr<Project> project, + string id, + string name); + + bool _server_responding; + Session* _session; + LashProxy* _interface; + Patchage* _app; +}; + +LashProxy::LashProxy(Patchage* app, Session* session) +{ + _impl = new LashProxyImpl; + _impl->_interface = this; + _impl->_session = session; + _impl->_app = app; + _impl->init(app); +} + +LashProxy::~LashProxy() +{ + delete _impl; +} + +void +LashProxyImpl::init(Patchage* app) +{ + _server_responding = false; + + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" DBUS_INTERFACE_DBUS "',member=NameOwnerChanged,arg0='" LASH_SERVICE "'", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ProjectAppeared", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ProjectDisappeared", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ProjectNameChanged", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ProjectModifiedStatusChanged", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ProjectDescriptionChanged", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ProjectNotesChanged", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ClientAppeared", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ClientDisappeared", NULL); + dbus_bus_add_match(app->dbus()->connection(), "type='signal',interface='" LASH_IFACE_CONTROL "',member=ClientNameChanged", NULL); + + dbus_connection_add_filter(app->dbus()->connection(), dbus_message_hook, this, NULL); + + // get initial list of projects + // calling any method to updates server responding status + // this also actiavtes lash object if it not activated already + fetch_loaded_projects(); + + app->set_lash_available(_server_responding); +} + +void +LashProxyImpl::error_msg(const std::string& msg) +{ + _app->error_msg((std::string)"[LASH] " + msg); +} + +void +LashProxyImpl::info_msg(const std::string& msg) +{ + _app->info_msg((std::string)"[LASH] " + msg); +} + +DBusHandlerResult +LashProxyImpl::dbus_message_hook( + DBusConnection* connection, + DBusMessage* message, + void* proxy) +{ + const char* project_name; + const char* new_project_name; + const char* object_name; + const char* old_owner; + const char* new_owner; + const char* value_string; + const char* client_id; + const char* client_name; + dbus_bool_t modified_status; + shared_ptr<Project> project; + shared_ptr<LashClient> client; + + assert(proxy); + LashProxyImpl* me = reinterpret_cast<LashProxyImpl*>(proxy); + assert(me->_app->dbus()->connection()); + + Patchage* const app = me->_app; + + //info_msg("dbus_message_hook() called."); + + // Handle signals we have subscribed for in attach() + + if (dbus_message_is_signal(message, DBUS_INTERFACE_DBUS, "NameOwnerChanged")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &object_name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract NameOwnerChanged signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + if ((string)object_name != LASH_SERVICE) { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (old_owner[0] == '\0') { + me->info_msg("LASH activated."); + app->set_lash_available(true); + } else if (new_owner[0] == '\0') { + me->info_msg((string)"LASH deactivated."); + app->set_lash_available(false); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ProjectAppeared")) { + if (!dbus_message_get_args( message, &app->dbus()->error(), + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ProjectAppeared signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Project '" + project_name + "' appeared."); + me->on_project_added(project_name); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ProjectDisappeared")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ProjectDisappeared signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Project '" + project_name + "' disappeared."); + me->_session->project_close(project_name); + + return DBUS_HANDLER_RESULT_HANDLED; + } + + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ProjectNameChanged")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_STRING, &new_project_name, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ProjectNameChanged signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Project '" + project_name + "' renamed to '" + new_project_name + "'."); + + project = me->_session->find_project_by_name(project_name); + if (project) { + project->on_name_changed(new_project_name); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ProjectModifiedStatusChanged")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_BOOLEAN, &modified_status, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ProjectModifiedStatusChanged signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Project '" + project_name + "' modified status changed to '" + (modified_status ? "true" : "false") + "'."); + + project = me->_session->find_project_by_name(project_name); + if (project) { + project->on_modified_status_changed(modified_status); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ProjectDescriptionChanged")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_STRING, &value_string, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ProjectDescriptionChanged signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Project '" + project_name + "' description changed."); + + project = me->_session->find_project_by_name(project_name); + if (project) { + project->on_description_changed(value_string); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ProjectNotesChanged")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_STRING, &value_string, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ProjectNotesChanged signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Project '" + project_name + "' notes changed."); + + project = me->_session->find_project_by_name(project_name); + if (project) { + project->on_notes_changed(value_string); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ClientAppeared")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &client_id, + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_STRING, &client_name, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ClientAppeared signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Client '" + client_id + "':'" + client_name + "' appeared in project '" + project_name + "'."); + + project = me->_session->find_project_by_name(project_name); + if (project) { + me->on_client_added(project, client_id, client_name); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ClientDisappeared")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &client_id, + DBUS_TYPE_STRING, &project_name, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ClientDisappeared signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Client '" + client_id + "' of project '" + project_name + "' disappeared."); + + client = me->_session->find_client_by_id(client_id); + if (client) { + client->get_project()->on_client_removed(client_id); + me->_session->client_remove(client_id); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + if (dbus_message_is_signal(message, LASH_IFACE_CONTROL, "ClientNameChanged")) { + if (!dbus_message_get_args( + message, &app->dbus()->error(), + DBUS_TYPE_STRING, &client_id, + DBUS_TYPE_STRING, &client_name, + DBUS_TYPE_INVALID)) { + me->error_msg(str(boost::format("dbus_message_get_args() failed to extract ClientNameChanged signal arguments (%s)") % app->dbus()->error().message)); + dbus_error_free(&app->dbus()->error()); + return DBUS_HANDLER_RESULT_HANDLED; + } + + me->info_msg((string)"Client '" + client_id + "' name changed to '" + client_name + "'."); + + client = me->_session->find_client_by_id(client_id); + if (client) { + client->on_name_changed(client_name); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +bool +LashProxyImpl::call( + bool response_expected, + const char* iface, + const char* method, + DBusMessage** reply_ptr, + int in_type, + ...) +{ + va_list ap; + va_start(ap, in_type); + + _server_responding = _app->dbus()->call( + response_expected, + LASH_SERVICE, + LASH_OBJECT, + iface, + method, + reply_ptr, + in_type, + ap); + + va_end(ap); + return _server_responding; +} + +void +LashProxyImpl::fetch_loaded_projects() +{ + DBusMessage* reply_ptr; + const char* reply_signature; + DBusMessageIter iter; + DBusMessageIter array_iter; + const char* project_name; + shared_ptr<Project> project; + + if (!call(true, LASH_IFACE_CONTROL, "ProjectsGet", &reply_ptr, DBUS_TYPE_INVALID)) { + return; + } + + reply_signature = dbus_message_get_signature(reply_ptr); + + if (strcmp(reply_signature, "as") != 0) { + error_msg((string)"ProjectsGet() reply signature mismatch. " + reply_signature); + goto unref; + } + + dbus_message_iter_init(reply_ptr, &iter); + + for (dbus_message_iter_recurse(&iter, &array_iter); + dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID; + dbus_message_iter_next(&array_iter)) { + dbus_message_iter_get_basic(&array_iter, &project_name); + project = on_project_added(project_name); + fetch_project_clients(project); + } + +unref: + dbus_message_unref(reply_ptr); +} + +void +LashProxyImpl::fetch_project_clients(shared_ptr<Project> project) +{ + DBusMessage* reply_ptr; + const char* reply_signature; + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + const char* client_id; + const char* client_name; + + const string& project_name = project->get_name(); + const char* const project_name_cstr = project_name.c_str(); + + if (!call( + true, + LASH_IFACE_CONTROL, + "ProjectGetClients", + &reply_ptr, + DBUS_TYPE_STRING, &project_name_cstr, + DBUS_TYPE_INVALID)) { + return; + } + + reply_signature = dbus_message_get_signature(reply_ptr); + + if (strcmp(reply_signature, "a(ss)") != 0) { + error_msg((string)"ProjectGetClients() reply signature mismatch. " + reply_signature); + goto unref; + } + + dbus_message_iter_init(reply_ptr, &iter); + + for (dbus_message_iter_recurse(&iter, &array_iter); + dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID; + dbus_message_iter_next(&array_iter)) { + dbus_message_iter_recurse(&array_iter, &struct_iter); + + dbus_message_iter_get_basic(&struct_iter, &client_id); + dbus_message_iter_next(&struct_iter); + dbus_message_iter_get_basic(&struct_iter, &client_name); + dbus_message_iter_next(&struct_iter); + + on_client_added(project, client_id, client_name); + } + +unref: + dbus_message_unref(reply_ptr); +} + +shared_ptr<Project> +LashProxyImpl::on_project_added(const string& name) +{ + shared_ptr<Project> project(new Project(_interface, name)); + + _session->project_add(project); + + return project; +} + +shared_ptr<LashClient> +LashProxyImpl::on_client_added( + shared_ptr<Project> project, + string id, + string name) +{ + shared_ptr<LashClient> client( + new LashClient( + _interface, + project.get(), + id, + name)); + + project->on_client_added(client); + _session->client_add(client); + + return client; +} + +void +LashProxy::get_available_projects(list<ProjectInfo>& projects) +{ + DBusMessage* reply_ptr; + const char* reply_signature; + DBusMessageIter iter; + DBusMessageIter array_iter; + DBusMessageIter struct_iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter variant_iter; + const char* project_name; + const char* key; + const char* value_type; + dbus_uint32_t value_uint32; + const char* value_string; + ProjectInfo project_info; + + if (!_impl->call(true, LASH_IFACE_CONTROL, "ProjectsGetAvailable", &reply_ptr, DBUS_TYPE_INVALID)) { + return; + } + + reply_signature = dbus_message_get_signature(reply_ptr); + + if (strcmp(reply_signature, "a(sa{sv})") != 0) { + _impl->error_msg((string)"ProjectsGetAvailable() reply signature mismatch. " + reply_signature); + goto unref; + } + + dbus_message_iter_init(reply_ptr, &iter); + + for (dbus_message_iter_recurse(&iter, &array_iter); + dbus_message_iter_get_arg_type(&array_iter) != DBUS_TYPE_INVALID; + dbus_message_iter_next(&array_iter)) { + dbus_message_iter_recurse(&array_iter, &struct_iter); + + dbus_message_iter_get_basic(&struct_iter, &project_name); + + project_info.name = project_name; + project_info.modification_time = 0; + project_info.description.erase(); + + dbus_message_iter_next(&struct_iter); + + for (dbus_message_iter_recurse(&struct_iter, &dict_iter); + dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID; + dbus_message_iter_next(&dict_iter)) { + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + dbus_message_iter_get_basic(&dict_entry_iter, &key); + dbus_message_iter_next(&dict_entry_iter); + dbus_message_iter_recurse(&dict_entry_iter, &variant_iter); + value_type = dbus_message_iter_get_signature(&variant_iter); + if (value_type[0] != 0 && value_type[1] == 0) { + switch (*value_type) { + case DBUS_TYPE_UINT32: + if (strcmp(key, "Modification Time") == 0) { + dbus_message_iter_get_basic(&variant_iter, &value_uint32); + project_info.modification_time = value_uint32; + } + break; + case DBUS_TYPE_STRING: + if (strcmp(key, "Description") == 0) { + dbus_message_iter_get_basic(&variant_iter, &value_string); + project_info.description = value_string; + } + break; + } + } + } + + projects.push_back(project_info); + } + +unref: + dbus_message_unref(reply_ptr); +} + +void +LashProxy::load_project(const string& project_name) +{ + DBusMessage* reply_ptr; + const char* const project_name_cstr = project_name.c_str(); + + if (!_impl->call(true, LASH_IFACE_CONTROL, "ProjectOpen", &reply_ptr, DBUS_TYPE_STRING, &project_name_cstr, DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::save_all_projects() +{ + DBusMessage* reply_ptr; + + if (!_impl->call(true, LASH_IFACE_CONTROL, "ProjectsSaveAll", &reply_ptr, DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::save_project(const string& project_name) +{ + DBusMessage* reply_ptr; + const char* const project_name_cstr = project_name.c_str(); + + if (!_impl->call(true, LASH_IFACE_CONTROL, "ProjectSave", &reply_ptr, DBUS_TYPE_STRING, &project_name_cstr, DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::close_project(const string& project_name) +{ + DBusMessage* reply_ptr; + const char* const project_name_cstr = project_name.c_str(); + + if (!_impl->call(true, LASH_IFACE_CONTROL, "ProjectClose", &reply_ptr, DBUS_TYPE_STRING, &project_name_cstr, DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::close_all_projects() +{ + DBusMessage* reply_ptr; + + if (!_impl->call(true, LASH_IFACE_CONTROL, "ProjectsCloseAll", &reply_ptr, DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::project_rename(const string& old_name, const string& new_name) +{ + DBusMessage* reply_ptr; + const char* const old_name_cstr = old_name.c_str(); + const char* const new_name_cstr = new_name.c_str(); + + if (!_impl->call( + true, + LASH_IFACE_CONTROL, + "ProjectRename", + &reply_ptr, + DBUS_TYPE_STRING, &old_name_cstr, + DBUS_TYPE_STRING, &new_name_cstr, + DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::get_loaded_project_properties( + const string& name, + LoadedProjectProperties& properties) +{ + DBusMessage* reply_ptr; + const char* reply_signature; + DBusMessageIter iter; + DBusMessageIter dict_iter; + DBusMessageIter dict_entry_iter; + DBusMessageIter variant_iter; + const char* key; + const char* value_type; + dbus_bool_t value_bool; + const char* value_string; + + const char* const project_name_cstr = name.c_str(); + + if (!_impl->call( + true, + LASH_IFACE_CONTROL, + "ProjectGetProperties", + &reply_ptr, + DBUS_TYPE_STRING, &project_name_cstr, + DBUS_TYPE_INVALID)) { + return; + } + + reply_signature = dbus_message_get_signature(reply_ptr); + + if (strcmp(reply_signature, "a{sv}") != 0) { + _impl->error_msg((string)"ProjectGetProperties() reply signature mismatch. " + reply_signature); + goto unref; + } + + dbus_message_iter_init(reply_ptr, &iter); + + for (dbus_message_iter_recurse(&iter, &dict_iter); + dbus_message_iter_get_arg_type(&dict_iter) != DBUS_TYPE_INVALID; + dbus_message_iter_next(&dict_iter)) { + dbus_message_iter_recurse(&dict_iter, &dict_entry_iter); + dbus_message_iter_get_basic(&dict_entry_iter, &key); + dbus_message_iter_next(&dict_entry_iter); + dbus_message_iter_recurse(&dict_entry_iter, &variant_iter); + value_type = dbus_message_iter_get_signature(&variant_iter); + if (value_type[0] != 0 && value_type[1] == 0) { + switch (*value_type) { + case DBUS_TYPE_BOOLEAN: + if (strcmp(key, "Modified Status") == 0) { + dbus_message_iter_get_basic(&variant_iter, &value_bool); + properties.modified_status = value_bool; + } + break; + case DBUS_TYPE_STRING: + if (strcmp(key, "Description") == 0) { + dbus_message_iter_get_basic(&variant_iter, &value_string); + properties.description = value_string; + } else if (strcmp(key, "Notes") == 0) { + dbus_message_iter_get_basic(&variant_iter, &value_string); + properties.notes = value_string; + } + break; + } + } + } + +unref: + dbus_message_unref(reply_ptr); +} + +void +LashProxy::project_set_description(const string& project_name, const string& description) +{ + DBusMessage* reply_ptr; + const char* const project_name_cstr = project_name.c_str(); + const char* const description_cstr = description.c_str(); + + if (!_impl->call( + true, + LASH_IFACE_CONTROL, + "ProjectSetDescription", + &reply_ptr, + DBUS_TYPE_STRING, &project_name_cstr, + DBUS_TYPE_STRING, &description_cstr, + DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + +void +LashProxy::project_set_notes( + const string& project_name, + const string& notes) +{ + DBusMessage* reply_ptr; + const char* const project_name_cstr = project_name.c_str(); + const char* const notes_cstr = notes.c_str(); + + if (!_impl->call( + true, + LASH_IFACE_CONTROL, + "ProjectSetNotes", + &reply_ptr, + DBUS_TYPE_STRING, &project_name_cstr, + DBUS_TYPE_STRING, ¬es_cstr, + DBUS_TYPE_INVALID)) { + return; + } + + dbus_message_unref(reply_ptr); +} + diff --git a/src/LashProxy.hpp b/src/LashProxy.hpp new file mode 100644 index 0000000..fb2f3ff --- /dev/null +++ b/src/LashProxy.hpp @@ -0,0 +1,61 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_LASH_PROXY_HPP +#define PATCHAGE_LASH_PROXY_HPP + +#include "Patchage.hpp" + +struct ProjectInfo { + std::string name; + time_t modification_time; + std::string description; +}; + +struct LoadedProjectProperties { + bool modified_status; + std::string description; + std::string notes; +}; + +class Patchage; +class Session; +class LashProxyImpl; + +class LashProxy { +public: + LashProxy(Patchage* app, Session* session); + ~LashProxy(); + + void get_available_projects(std::list<ProjectInfo>& projects); + void load_project(const std::string& project_name); + void save_all_projects(); + void save_project(const std::string& project_name); + void close_project(const std::string& project_name); + void close_all_projects(); + void project_rename(const std::string& old_name, const std::string& new_name); + void get_loaded_project_properties(const std::string& name, LoadedProjectProperties& properties); + void project_set_description(const std::string& project_name, const std::string& description); + void project_set_notes(const std::string& project_name, const std::string& notes); + +private: + LashProxyImpl* _impl; +}; + +#endif // PATCHAGE_LASH_PROXY_HPP diff --git a/src/LoadProjectDialog.cpp b/src/LoadProjectDialog.cpp new file mode 100644 index 0000000..0a4c92a --- /dev/null +++ b/src/LoadProjectDialog.cpp @@ -0,0 +1,154 @@ +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "LoadProjectDialog.hpp" +#include "Patchage.hpp" +#include "LashProxy.hpp" + +static void +convert_timestamp_to_string( + const time_t timestamp, + std::string& timestamp_string) +{ + GDate mtime, now; + gint days_diff; + struct tm tm_mtime; + time_t time_now; + const gchar *format; + gchar buf[256]; + + if (timestamp == 0) { + timestamp_string = "Unknown"; + return; + } + + localtime_r(×tamp, &tm_mtime); + + g_date_set_time_t(&mtime, timestamp); + time_now = time(NULL); + g_date_set_time_t(&now, time_now); + + days_diff = g_date_get_julian(&now) - g_date_get_julian(&mtime); + + if (days_diff == 0) { + format = "Today at %H:%M"; + } else if (days_diff == 1) { + format = "Yesterday at %H:%M"; + } else { + if (days_diff > 1 && days_diff < 7) { + format = "%A"; /* Days from last week */ + } else { + format = "%x"; /* Any other date */ + } + } + + if (strftime(buf, sizeof(buf), format, &tm_mtime) != 0) { + timestamp_string = buf; + } else { + timestamp_string = "Unknown"; + } +} + + +LoadProjectDialog::LoadProjectDialog(Patchage* app) + : _app(app) + , _dialog(app->xml(), "load_project_dialog") + , _widget(app->xml(), "loadable_projects_list") +{ + _columns.add(_columns.name); + _columns.add(_columns.modified); + _columns.add(_columns.description); + + _model = Gtk::ListStore::create(_columns); + _widget->set_model(_model); + + _widget->remove_all_columns(); + _widget->append_column("Project Name", _columns.name); + _widget->append_column("Modified", _columns.modified); + _widget->append_column("Description", _columns.description); +} + + +void +LoadProjectDialog::run(std::list<ProjectInfo>& projects) +{ + Gtk::TreeModel::Row row; + int result; + + for (std::list<ProjectInfo>::iterator iter = projects.begin(); iter != projects.end(); iter++) { + std::string str; + row = *(_model->append()); + row[_columns.name] = iter->name; + convert_timestamp_to_string(iter->modification_time, str); + row[_columns.modified] = str; + row[_columns.description] = iter->description; + } + + _widget->signal_button_press_event().connect(sigc::mem_fun(*this, &LoadProjectDialog::on_button_press_event), false); + _widget->signal_key_press_event().connect(sigc::mem_fun(*this, &LoadProjectDialog::on_key_press_event), false); + +loop: + result = _dialog->run(); + + if (result == 2) { + Glib::RefPtr<Gtk::TreeView::Selection> selection = _widget->get_selection(); + Gtk::TreeIter iter = selection->get_selected(); + if (!iter) + goto loop; + + Glib::ustring project_name = (*iter)[_columns.name]; + _app->lash_proxy()->load_project(project_name); + } + + _dialog->hide(); +} + + +void +LoadProjectDialog::load_selected_project() +{ + Glib::RefPtr<Gtk::TreeView::Selection> selection = _widget->get_selection(); + Glib::ustring name = (*selection->get_selected())[_columns.name]; + _app->lash_proxy()->load_project(name); + _dialog->hide(); +} + + +bool +LoadProjectDialog::on_button_press_event(GdkEventButton * event_ptr) +{ + if (event_ptr->type == GDK_2BUTTON_PRESS && event_ptr->button == 1) { + load_selected_project(); + return true; + } + return false; +} + + +bool +LoadProjectDialog::on_key_press_event(GdkEventKey * event_ptr) +{ + if (event_ptr->type == GDK_KEY_PRESS && + (event_ptr->keyval == GDK_Return || + event_ptr->keyval == GDK_KP_Enter)) { + load_selected_project(); + return true; + } + return false; +} + diff --git a/src/LoadProjectDialog.hpp b/src/LoadProjectDialog.hpp new file mode 100644 index 0000000..3f6c2c1 --- /dev/null +++ b/src/LoadProjectDialog.hpp @@ -0,0 +1,53 @@ +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_LOAD_PROJECT_DIALOG_H +#define PATCHAGE_LOAD_PROJECT_DIALOG_H + +#include <list> +#include <gtkmm.h> +#include "Widget.hpp" + +class Patchage; +class ProjectInfo; + +class LoadProjectDialog { +public: + LoadProjectDialog(Patchage* app); + + void run(std::list<ProjectInfo>& projects); + +private: + struct Record : public Gtk::TreeModel::ColumnRecord { + Gtk::TreeModelColumn<Glib::ustring> name; + Gtk::TreeModelColumn<Glib::ustring> modified; + Gtk::TreeModelColumn<Glib::ustring> description; + }; + + void load_selected_project(); + bool on_button_press_event(GdkEventButton* event_ptr); + bool on_key_press_event(GdkEventKey* event_ptr); + + Patchage* _app; + Widget<Gtk::Dialog> _dialog; + Widget<Gtk::TreeView> _widget; + Record _columns; + Glib::RefPtr<Gtk::ListStore> _model; +}; + +#endif // PATCHAGE_LOAD_PROJECT_DIALOG_H diff --git a/src/Makefile.am b/src/Makefile.am index 3d8c76b..389bd15 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,5 +1,5 @@ -AM_CXXFLAGS = -I.. -I$(top_srcdir)/raul -I$(top_srcdir)/flowcanvas -DDATA_DIR=\"$(pkgdatadir)\" @GTHREAD_CFLAGS@ @LIBGLADEMM_CFLAGS@ @GNOMECANVASMM_CFLAGS@ @ALSA_CFLAGS@ @LASH_CFLAGS@ -patchage_LDADD = @GTHREAD_LIBS@ @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @ALSA_LIBS@ @LASH_LIBS@ @RAUL_LIBS@ @FLOWCANVAS_LIBS@ +AM_CXXFLAGS = -I.. -I$(top_srcdir)/raul -I$(top_srcdir)/flowcanvas -DDATA_DIR=\"$(pkgdatadir)\" @GTHREAD_CFLAGS@ @LIBGLADEMM_CFLAGS@ @GNOMECANVASMM_CFLAGS@ @ALSA_CFLAGS@ @DBUS_CFLAGS@ +patchage_LDADD = @GTHREAD_LIBS@ @LIBGLADEMM_LIBS@ @GNOMECANVASMM_LIBS@ @ALSA_LIBS@ @RAUL_LIBS@ @FLOWCANVAS_LIBS@ @DBUS_LIBS@ EXTRA_DIST = patchage.gladep @@ -10,7 +10,6 @@ bin_PROGRAMS = patchage patchage_SOURCES = \ Driver.hpp \ GladeFile.hpp \ - JackSettingsDialog.hpp \ Patchage.cpp \ Patchage.hpp \ PatchageCanvas.cpp \ @@ -38,8 +37,24 @@ patchage_LDADD += @JACK_LIBS@ endif endif -if WITH_LASH -patchage_SOURCES += LashDriver.hpp LashDriver.cpp +if WITH_DBUS +patchage_SOURCES += \ + DBus.cpp \ + DBus.hpp \ + LashClient.cpp \ + LashClient.hpp \ + LashProxy.cpp \ + LashProxy.hpp \ + LoadProjectDialog.cpp \ + LoadProjectDialog.hpp \ + Project.cpp \ + Project.hpp \ + ProjectList.cpp \ + ProjectList.hpp \ + ProjectPropertiesDialog.cpp \ + ProjectPropertiesDialog.hpp \ + Session.cpp \ + Session.hpp endif if WITH_ALSA diff --git a/src/Patchage.cpp b/src/Patchage.cpp index caf0c10..8b222db 100644 --- a/src/Patchage.cpp +++ b/src/Patchage.cpp @@ -32,7 +32,6 @@ #include "JackDriver.hpp" #include <jack/statistics.h> #endif -#include "JackSettingsDialog.hpp" #include "Patchage.hpp" #include "PatchageCanvas.hpp" #include "PatchageEvent.hpp" @@ -40,10 +39,16 @@ #ifdef HAVE_ALSA #include "AlsaDriver.hpp" #endif -#ifdef HAVE_LASH -#include "LashDriver.hpp" +#ifdef HAVE_DBUS +#include "DBus.hpp" +#include "LashProxy.hpp" +#include "LoadProjectDialog.hpp" +#include "ProjectList.hpp" +#include "Session.hpp" #endif +#define LOG_TO_STATUS 1 + using namespace std; /* Gtk helpers (resize combo boxes) */ @@ -77,50 +82,48 @@ gtkmm_set_width_for_given_text (Gtk::Widget &w, const gchar *text, /* end Gtk helpers */ -#define INIT_WIDGET(x) x(xml, ((const char*)#x) + 1) +#define INIT_WIDGET(x) x(_xml, ((const char*)#x) + 1) Patchage::Patchage(int argc, char** argv) - : xml(GladeFile::open("patchage")) -#ifdef HAVE_LASH - , _lash_driver(NULL) - , INIT_WIDGET(_menu_open_session) - , INIT_WIDGET(_menu_save_session) - , INIT_WIDGET(_menu_save_session_as) - , INIT_WIDGET(_menu_close_session) - , INIT_WIDGET(_menu_lash_connect) - , INIT_WIDGET(_menu_lash_disconnect) + : _xml(GladeFile::open("patchage")) +#ifdef HAVE_DBUS + , _lash_proxy(NULL) + , _dbus(NULL) + , _project_list(NULL) + , _session(NULL) #endif #ifdef HAVE_ALSA , _alsa_driver(NULL) , _alsa_driver_autoattach(true) - , INIT_WIDGET(_menu_alsa_connect) - , INIT_WIDGET(_menu_alsa_disconnect) #endif , _jack_driver(NULL) , _state_manager(NULL) , _attach(true) , _refresh(false) , _enable_refresh(true) - , _jack_settings_dialog(NULL) , INIT_WIDGET(_about_win) , INIT_WIDGET(_buffer_size_combo) , INIT_WIDGET(_clear_load_but) , INIT_WIDGET(_main_scrolledwin) , INIT_WIDGET(_main_win) , INIT_WIDGET(_main_xrun_progress) + , INIT_WIDGET(_menu_alsa_connect) + , INIT_WIDGET(_menu_alsa_disconnect) , INIT_WIDGET(_menu_file_quit) , INIT_WIDGET(_menu_help_about) , INIT_WIDGET(_menu_jack_connect) , INIT_WIDGET(_menu_jack_disconnect) - , INIT_WIDGET(_menu_jack_settings) + , INIT_WIDGET(_menu_open_session) , INIT_WIDGET(_menu_store_positions) , INIT_WIDGET(_menu_view_arrange) , INIT_WIDGET(_menu_view_messages) + , INIT_WIDGET(_menu_view_projects) , INIT_WIDGET(_menu_view_refresh) , INIT_WIDGET(_menu_view_toolbar) - , INIT_WIDGET(_messages_win) , INIT_WIDGET(_messages_clear_but) , INIT_WIDGET(_messages_close_but) + , INIT_WIDGET(_messages_win) + , INIT_WIDGET(_project_list_viewport) , INIT_WIDGET(_sample_rate_label) , INIT_WIDGET(_status_text) , INIT_WIDGET(_toolbar) @@ -146,8 +149,6 @@ Patchage::Patchage(int argc, char** argv) argc--; } - xml->get_widget_derived("jack_settings_win", _jack_settings_dialog); - Glib::set_application_name("Patchage"); _about_win->property_program_name() = "Patchage"; _about_win->property_logo_icon_name() = "patchage"; @@ -173,22 +174,12 @@ Patchage::Patchage(int argc, char** argv) sigc::mem_fun(this, &Patchage::zoom), 1.0)); _zoom_full_but->signal_clicked().connect( sigc::mem_fun(_canvas.get(), &PatchageCanvas::zoom_full)); - _menu_jack_settings->signal_activate().connect(sigc::hide_return( - sigc::mem_fun(_jack_settings_dialog, &JackSettingsDialog::run))); -#ifdef HAVE_LASH +#ifdef HAVE_DBUS _menu_open_session->signal_activate().connect( - sigc::mem_fun(this, &Patchage::menu_open_session)); - _menu_save_session->signal_activate().connect( - sigc::mem_fun(this, &Patchage::menu_save_session)); - _menu_save_session_as->signal_activate().connect( - sigc::mem_fun(this, &Patchage::menu_save_session_as)); - _menu_close_session->signal_activate().connect( - sigc::mem_fun(this, &Patchage::menu_close_session)); - _menu_lash_connect->signal_activate().connect( - sigc::mem_fun(this, &Patchage::menu_lash_connect)); - _menu_lash_disconnect->signal_activate().connect( - sigc::mem_fun(this, &Patchage::menu_lash_disconnect)); + sigc::mem_fun(this, &Patchage::show_load_project_dialog)); +#else + _menu_open_session->set_sensitive(false); #endif #ifdef HAVE_ALSA @@ -196,6 +187,9 @@ Patchage::Patchage(int argc, char** argv) sigc::mem_fun(this, &Patchage::menu_alsa_connect)); _menu_alsa_disconnect->signal_activate().connect( sigc::mem_fun(this, &Patchage::menu_alsa_disconnect)); +#else + _menu_alsa_connect->set_sensitive(false); + _menu_alsa_disconnect->set_sensitive(false); #endif _menu_store_positions->signal_activate().connect( @@ -210,6 +204,8 @@ Patchage::Patchage(int argc, char** argv) sigc::mem_fun(this, &Patchage::on_view_toolbar)); _menu_view_messages->signal_toggled().connect( sigc::mem_fun(this, &Patchage::on_show_messages)); + _menu_view_projects->signal_toggled().connect( + sigc::mem_fun(this, &Patchage::on_show_projects)); _menu_help_about->signal_activate().connect( sigc::mem_fun(this, &Patchage::on_help_about)); @@ -249,8 +245,13 @@ Patchage::Patchage(int argc, char** argv) _alsa_driver = new AlsaDriver(this); #endif -#ifdef HAVE_LASH - _lash_driver = new LashDriver(this, argc, argv); +#ifdef HAVE_DBUS + _dbus = new DBus(this); + _session = new Session(); + _project_list = new ProjectList(this, _session); + _lash_proxy = new LashProxy(this, _session); +#else + _project_list_viewport->hide(); #endif connect_widgets(); @@ -272,14 +273,14 @@ Patchage::~Patchage() #ifdef HAVE_ALSA delete _alsa_driver; #endif -#ifdef HAVE_LASH - delete _lash_driver; +#ifdef HAVE_DBUS + delete _lash_proxy; #endif delete _state_manager; - + _about_win.destroy(); _messages_win.destroy(); - _main_win.destroy(); + //_main_win.destroy(); } @@ -292,9 +293,6 @@ Patchage::attach() _jack_driver->attach(true); #endif -#ifdef HAVE_LASH - _lash_driver->attach(true); -#endif #ifdef HAVE_ALSA if (_alsa_driver_autoattach) _alsa_driver->attach(); @@ -333,11 +331,6 @@ Patchage::idle_callback() } #endif -#ifdef HAVE_LASH - if (_lash_driver->is_attached()) - _lash_driver->process_events(this); -#endif - // Do a full refresh (ie user clicked refresh) if (_refresh) { refresh(); @@ -451,7 +444,31 @@ Patchage::clear_load() void -Patchage::status_message(const string& msg) +Patchage::error_msg(const std::string& msg) +{ +#if defined(LOG_TO_STATUS) + status_msg(msg); +#endif +#if defined(LOG_TO_STD) + cerr << msg << endl; +#endif +} + + +void +Patchage::info_msg(const std::string& msg) +{ +#if defined(LOG_TO_STATUS) + status_msg(msg); +#endif +#if defined(LOG_TO_STD) + cerr << msg << endl; +#endif +} + + +void +Patchage::status_msg(const string& msg) { if (_status_text->get_buffer()->size() > 0) _status_text->get_buffer()->insert(_status_text->get_buffer()->end(), "\n"); @@ -479,18 +496,6 @@ Patchage::update_state() void Patchage::connect_widgets() { -#ifdef HAVE_LASH - _lash_driver->signal_attached.connect(sigc::bind( - sigc::mem_fun(*_menu_lash_connect, &Gtk::MenuItem::set_sensitive), false)); - _lash_driver->signal_attached.connect(sigc::bind( - sigc::mem_fun(*_menu_lash_disconnect, &Gtk::MenuItem::set_sensitive), true)); - - _lash_driver->signal_detached.connect(sigc::bind( - sigc::mem_fun(*_menu_lash_connect, &Gtk::MenuItem::set_sensitive), true)); - _lash_driver->signal_detached.connect(sigc::bind( - sigc::mem_fun(*_menu_lash_disconnect, &Gtk::MenuItem::set_sensitive), false)); -#endif - #if defined(HAVE_JACK) || defined(HAVE_JACK_DBUS) _jack_driver->signal_attached.connect( sigc::mem_fun(this, &Patchage::update_toolbar)); @@ -520,72 +525,25 @@ Patchage::connect_widgets() #endif } - -#ifdef HAVE_LASH +#ifdef HAVE_DBUS void -Patchage::menu_open_session() +Patchage::show_load_project_dialog() { - Gtk::FileChooserDialog dialog(*_main_win, "Open LASH Session", - Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); + std::list<ProjectInfo> projects; + _lash_proxy->get_available_projects(projects); - dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); - dialog.add_button(Gtk::Stock::OPEN, Gtk::RESPONSE_OK); - - const int result = dialog.run(); - - if (result == Gtk::RESPONSE_OK) { - _lash_driver->restore_project(dialog.get_filename()); - } -} - - -void -Patchage::menu_save_session() -{ - if (_lash_driver) - _lash_driver->save_project(); -} - - -void -Patchage::menu_save_session_as() -{ - if (!_lash_driver) - return; - - Gtk::FileChooserDialog dialog(*_main_win, "Save LASH Session", - Gtk::FILE_CHOOSER_ACTION_SAVE); - - dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); - dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); - - const int result = dialog.run(); - - if (result == Gtk::RESPONSE_OK) { - _lash_driver->set_project_directory(dialog.get_filename()); - _lash_driver->save_project(); - } -} - - -void -Patchage::menu_close_session() -{ - _lash_driver->close_project(); -} - - -void -Patchage::menu_lash_connect() -{ - _lash_driver->attach(true); + LoadProjectDialog dialog(this); + dialog.run(projects); } +#endif - +#ifdef HAVE_DBUS void -Patchage::menu_lash_disconnect() +Patchage::set_lash_available(bool available) { - _lash_driver->detach(); + _project_list->set_lash_available(available); + if (!available) + _session->clear(); } #endif @@ -670,6 +628,16 @@ Patchage::on_show_messages() _messages_win->hide(); } + +void +Patchage::on_show_projects() +{ + if (_menu_view_projects->get_active()) + _project_list_viewport->show(); + else + _project_list_viewport->hide(); +} + void Patchage::on_store_positions() diff --git a/src/Patchage.hpp b/src/Patchage.hpp index 18f95d3..05cff7b 100644 --- a/src/Patchage.hpp +++ b/src/Patchage.hpp @@ -28,9 +28,12 @@ class PatchageCanvas; class JackDriver; class AlsaDriver; -class LashDriver; +class LashProxy; class StateManager; class JackSettingsDialog; +class Session; +class DBus; +class ProjectList; class Patchage { public: @@ -46,10 +49,16 @@ public: #ifdef HAVE_ALSA AlsaDriver* alsa_driver() const { return _alsa_driver; } #endif -#ifdef HAVE_LASH - LashDriver* lash_driver() const { return _lash_driver; } +#ifdef HAVE_DBUS + LashProxy* lash_proxy() const { return _lash_proxy; } + DBus* dbus() const { return _dbus; } + + void show_load_project_dialog(); + void set_lash_available(bool available); #endif + Glib::RefPtr<Gnome::Glade::Xml> xml() { return _xml; } + void attach(); void quit() { _main_win->hide(); } @@ -57,9 +66,12 @@ public: inline void queue_refresh() { _refresh = true; } void clear_load(); - void status_message(const std::string& msg); + void info_msg(const std::string& msg); + void error_msg(const std::string& msg); + void status_msg(const std::string& msg); void update_state(); void store_window_location(); + protected: void connect_widgets(); @@ -71,6 +83,7 @@ protected: bool on_messages_delete(GdkEventAny*); void on_quit(); void on_show_messages(); + void on_show_projects(); void on_store_positions(); void on_view_toolbar(); bool on_scroll(GdkEventScroll* ev); @@ -82,37 +95,26 @@ protected: void buffer_size_changed(); - Glib::RefPtr<Gnome::Glade::Xml> xml; - -#ifdef HAVE_LASH - LashDriver* _lash_driver; - Widget<Gtk::MenuItem> _menu_open_session; - Widget<Gtk::MenuItem> _menu_save_session; - Widget<Gtk::MenuItem> _menu_save_session_as; - Widget<Gtk::MenuItem> _menu_close_session; - Widget<Gtk::MenuItem> _menu_lash_connect; - Widget<Gtk::MenuItem> _menu_lash_disconnect; - void menu_open_session(); - void menu_save_session(); - void menu_save_session_as(); - void menu_close_session(); - void menu_lash_connect(); - void menu_lash_disconnect(); + Glib::RefPtr<Gnome::Glade::Xml> _xml; + +#ifdef HAVE_DBUS + LashProxy* _lash_proxy; + DBus* _dbus; + ProjectList* _project_list; + Session* _session; #endif #ifdef HAVE_ALSA - AlsaDriver* _alsa_driver; + AlsaDriver* _alsa_driver; bool _alsa_driver_autoattach; - Widget<Gtk::MenuItem> _menu_alsa_connect; - Widget<Gtk::MenuItem> _menu_alsa_disconnect; void menu_alsa_connect(); void menu_alsa_disconnect(); #endif boost::shared_ptr<PatchageCanvas> _canvas; - JackDriver* _jack_driver; - StateManager* _state_manager; + JackDriver* _jack_driver; + StateManager* _state_manager; Gtk::Main* _gtk_main; @@ -120,11 +122,6 @@ protected: bool _attach; bool _refresh; bool _enable_refresh; - bool _pane_closed; - bool _update_pane_position; - int _user_pane_position; - - JackSettingsDialog* _jack_settings_dialog; Widget<Gtk::AboutDialog> _about_win; Widget<Gtk::ComboBox> _buffer_size_combo; @@ -132,19 +129,23 @@ protected: Widget<Gtk::ScrolledWindow> _main_scrolledwin; Widget<Gtk::Window> _main_win; Widget<Gtk::ProgressBar> _main_xrun_progress; + Widget<Gtk::MenuItem> _menu_alsa_connect; + Widget<Gtk::MenuItem> _menu_alsa_disconnect; Widget<Gtk::MenuItem> _menu_file_quit; Widget<Gtk::MenuItem> _menu_help_about; Widget<Gtk::MenuItem> _menu_jack_connect; Widget<Gtk::MenuItem> _menu_jack_disconnect; - Widget<Gtk::MenuItem> _menu_jack_settings; + Widget<Gtk::MenuItem> _menu_open_session; Widget<Gtk::MenuItem> _menu_store_positions; Widget<Gtk::MenuItem> _menu_view_arrange; Widget<Gtk::CheckMenuItem> _menu_view_messages; + Widget<Gtk::CheckMenuItem> _menu_view_projects; Widget<Gtk::MenuItem> _menu_view_refresh; Widget<Gtk::CheckMenuItem> _menu_view_toolbar; - Widget<Gtk::Dialog> _messages_win; Widget<Gtk::Button> _messages_clear_but; Widget<Gtk::Button> _messages_close_but; + Widget<Gtk::Dialog> _messages_win; + Widget<Gtk::Viewport> _project_list_viewport; Widget<Gtk::Label> _sample_rate_label; Widget<Gtk::TextView> _status_text; Widget<Gtk::Toolbar> _toolbar; diff --git a/src/PatchageCanvas.cpp b/src/PatchageCanvas.cpp index d88399a..e6f348b 100644 --- a/src/PatchageCanvas.cpp +++ b/src/PatchageCanvas.cpp @@ -199,7 +199,7 @@ PatchageCanvas::disconnect(boost::shared_ptr<Connectable> port1, boost::shared_p void PatchageCanvas::status_message(const string& msg) { - _app->status_message(string("[Canvas] ").append(msg)); + _app->status_msg(string("[Canvas] ").append(msg)); } diff --git a/src/Project.cpp b/src/Project.cpp new file mode 100644 index 0000000..28d6aaa --- /dev/null +++ b/src/Project.cpp @@ -0,0 +1,175 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "Project.hpp" +#include "LashProxy.hpp" +#include "LashClient.hpp" + +using namespace std; +using boost::shared_ptr; + +struct ProjectImpl { + LashProxy* proxy; + string name; + string description; + string notes; + bool modified_status; + list< shared_ptr<LashClient> > clients; +}; + +Project::Project(LashProxy* proxy, const string& name) +{ + LoadedProjectProperties properties; + + proxy->get_loaded_project_properties(name, properties); + + _impl = new ProjectImpl; + _impl->proxy = proxy; + _impl->name = name; + + _impl->description = properties.description; + _impl->notes = properties.notes; + _impl->modified_status = properties.modified_status; + + //g_app->info_msg("project created"); +} + +Project::~Project() +{ + delete _impl; + //g_app->info_msg("project destroyed"); +} + +void +Project::clear() +{ + shared_ptr<LashClient> client; + + while (!_impl->clients.empty()) { + client = _impl->clients.front(); + _impl->clients.pop_front(); + _signal_client_removed.emit(client); + } +} + +const string& +Project::get_name() const +{ + return _impl->name; +} + +void +Project::on_name_changed(const string& name) +{ + _impl->name = name; + _signal_renamed.emit(); +} + +const string& +Project::get_description() const +{ + return _impl->description; +} + +const string& +Project::get_notes() const +{ + return _impl->notes; +} + +bool +Project::get_modified_status() const +{ + return _impl->modified_status; +} + +const Project::Clients& +Project::get_clients() const +{ + return _impl->clients; +} + +void +Project::on_client_added(shared_ptr<LashClient> client) +{ + _impl->clients.push_back(client); + _signal_client_added.emit(client); +} + +void +Project::on_client_removed(const string& id) +{ + shared_ptr<LashClient> client; + + for (list< shared_ptr<LashClient> >::iterator iter = _impl->clients.begin(); iter != _impl->clients.end(); iter++) { + client = *iter; + + if (client->get_id() == id) { + _impl->clients.erase(iter); + _signal_client_removed.emit(client); + return; + } + } +} + +void +Project::on_modified_status_changed(bool modified_status) +{ + _impl->modified_status = modified_status; + _signal_modified_status_changed.emit(); +} + +void +Project::on_description_changed(const string& description) +{ + _impl->description = description; + _signal_description_changed.emit(); +} + +void +Project::on_notes_changed(const string& notes) +{ + _impl->notes = notes; + _signal_notes_changed.emit(); +} + +void +Project::do_rename(const string& name) +{ + if (_impl->name != name) { + _impl->proxy->project_rename(_impl->name, name); + } +} + +void +Project::do_change_description(const string& description) +{ + if (_impl->description != description) { + _impl->proxy->project_set_description(_impl->name, description); + } +} + +void +Project::do_change_notes(const string& notes) +{ + if (_impl->notes != notes) { + _impl->proxy->project_set_notes(_impl->name, notes); + } +} + diff --git a/src/Project.hpp b/src/Project.hpp new file mode 100644 index 0000000..15e2b26 --- /dev/null +++ b/src/Project.hpp @@ -0,0 +1,73 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef LASH_PROJECT_HPP +#define LASH_PROJECT_HPP + +#include <string> +#include <list> +#include <boost/shared_ptr.hpp> +#include <sigc++/signal.h> + +struct ProjectImpl; +class LashProxy; +class LashProxyImpl; +class LashClient; + +class Project { +public: + Project(LashProxy* proxy, const std::string& name); + + ~Project(); + + void clear(); + + typedef std::list< boost::shared_ptr<LashClient> > Clients; + + const std::string& get_name() const; + const std::string& get_description() const; + const std::string& get_notes() const; + const Clients& get_clients() const; + bool get_modified_status() const; + + void do_rename(const std::string& name); + void do_change_description(const std::string& description); + void do_change_notes(const std::string& notes); + + sigc::signal<void> _signal_renamed; + sigc::signal<void> _signal_modified_status_changed; + sigc::signal<void> _signal_description_changed; + sigc::signal<void> _signal_notes_changed; + + sigc::signal< void, boost::shared_ptr<LashClient> > _signal_client_added; + sigc::signal< void, boost::shared_ptr<LashClient> > _signal_client_removed; + +private: + friend class LashProxyImpl; + + void on_name_changed(const std::string& name); + void on_modified_status_changed(bool modified_status); + void on_description_changed(const std::string& description); + void on_notes_changed(const std::string& notes); + void on_client_added(boost::shared_ptr<LashClient> client); + void on_client_removed(const std::string& id); + + ProjectImpl* _impl; +}; + +#endif // LASH_PROJECT_HPP diff --git a/src/ProjectList.cpp b/src/ProjectList.cpp new file mode 100644 index 0000000..4b87e31 --- /dev/null +++ b/src/ProjectList.cpp @@ -0,0 +1,305 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <iostream> +#include <gtkmm.h> +#include <libglademm/xml.h> + +#include "ProjectList.hpp" +#include "Widget.hpp" +#include "Session.hpp" +#include "Project.hpp" +#include "ProjectPropertiesDialog.hpp" +#include "LashClient.hpp" +#include "LashProxy.hpp" + +using boost::shared_ptr; +using namespace std; + +struct ProjectList_column_record : public Gtk::TreeModel::ColumnRecord { + Gtk::TreeModelColumn<Glib::ustring> name; + Gtk::TreeModelColumn< shared_ptr<Project> > project; +}; + +struct ProjectListImpl : public sigc::trackable { + Patchage* _app; + Widget<Gtk::TreeView> _widget; + ProjectList_column_record _columns; + Glib::RefPtr<Gtk::TreeStore> _model; + Gtk::Menu _menu_popup; + + ProjectListImpl( + Glib::RefPtr<Gnome::Glade::Xml> xml, + Patchage* app); + + void project_added(shared_ptr<Project> project); + void project_closed(shared_ptr<Project> project); + void project_renamed(Gtk::TreeModel::iterator iter); + void client_added(shared_ptr<LashClient> client, Gtk::TreeModel::iterator iter); + void client_removed(shared_ptr<LashClient> client, Gtk::TreeModel::iterator iter); + + bool on_button_press_event(GdkEventButton * event); + + void on_menu_popup_load_project(); + void on_menu_popup_save_all_projects(); + void on_menu_popup_save_project(shared_ptr<Project> project); + void on_menu_popup_close_project(shared_ptr<Project> project); + void on_menu_popup_project_properties(shared_ptr<Project> project); + void on_menu_popup_close_all_projects(); +}; + +ProjectList::ProjectList( + Patchage* app, + Session* session) +{ + _impl = new ProjectListImpl(app->xml(), app); + session->_signal_project_added.connect(mem_fun(_impl, &ProjectListImpl::project_added)); + session->_signal_project_closed.connect(mem_fun(_impl, &ProjectListImpl::project_closed)); +} + +ProjectList::~ProjectList() +{ + delete _impl; +} + +ProjectListImpl::ProjectListImpl(Glib::RefPtr<Gnome::Glade::Xml> xml, Patchage* app) + : _app(app) + , _widget(xml, "projects_list") +{ + _columns.add(_columns.name); + _columns.add(_columns.project); + + _model = Gtk::TreeStore::create(_columns); + _widget->set_model(_model); + + _widget->append_column("LASH projects", _columns.name); + + _menu_popup.accelerate(*_widget); + + _widget->signal_button_press_event().connect(sigc::mem_fun(*this, &ProjectListImpl::on_button_press_event), false); +} + +bool +ProjectListImpl::on_button_press_event(GdkEventButton* event) +{ + // Then do our custom stuff: + if (event->type == GDK_BUTTON_PRESS && event->button == 3) { + Glib::RefPtr<Gtk::TreeView::Selection> selection = _widget->get_selection(); + + Gtk::TreeModel::Path path; + Gtk::TreeViewColumn * column_ptr; + int cell_x; + int cell_y; + + Gtk::Menu::MenuList& menulist = _menu_popup.items(); + + menulist.clear(); + + menulist.push_back(Gtk::Menu_Helpers::MenuElem("_Load project...", sigc::mem_fun(*this, &ProjectListImpl::on_menu_popup_load_project))); + + menulist.push_back(Gtk::Menu_Helpers::MenuElem("Save _all projects", sigc::mem_fun(*this, &ProjectListImpl::on_menu_popup_save_all_projects))); + + if (_widget->get_path_at_pos((int)event->x, (int)event->y, path, column_ptr, cell_x, cell_y)) { + //cout << path.to_string() << endl; + selection->unselect_all(); + selection->select(path); + + Gtk::TreeIter iter = selection->get_selected(); + shared_ptr<Project> project = (*iter)[_columns.project]; + + if (project) { + const string& name = project->get_name(); + + menulist.push_back(Gtk::Menu_Helpers::MenuElem( + (string)"_Save project '" + name + "'", + sigc::bind( + sigc::mem_fun(*this, + &ProjectListImpl::on_menu_popup_save_project), + project))); + + menulist.push_back(Gtk::Menu_Helpers::MenuElem( + (string)"_Close project '" + name + "'", + sigc::bind( + sigc::mem_fun(*this, + &ProjectListImpl::on_menu_popup_close_project), + project))); + + menulist.push_back(Gtk::Menu_Helpers::MenuElem( + (string)"_Project '" + name + "' properties", + sigc::bind( + sigc::mem_fun(*this, + &ProjectListImpl::on_menu_popup_project_properties), + project))); + } + } else { + //cout << "No row" << endl; + selection->unselect_all(); + } + + menulist.push_back(Gtk::Menu_Helpers::MenuElem("Cl_ose all projects", sigc::mem_fun(*this, &ProjectListImpl::on_menu_popup_close_all_projects))); + + _menu_popup.popup(event->button, event->time); + + return true; + } + + return false; +} + +void +ProjectListImpl::on_menu_popup_load_project() +{ + _app->show_load_project_dialog(); +} + +void +ProjectListImpl::on_menu_popup_save_all_projects() +{ + _app->lash_proxy()->save_all_projects(); +} + +void +ProjectListImpl::on_menu_popup_save_project(shared_ptr<Project> project) +{ + _app->lash_proxy()->save_project(project->get_name()); +} + +void +ProjectListImpl::on_menu_popup_close_project(shared_ptr<Project> project) +{ + _app->lash_proxy()->close_project(project->get_name()); +} + +void +ProjectListImpl::on_menu_popup_project_properties(shared_ptr<Project> project) +{ + ProjectPropertiesDialog dialog(_app->xml()); + dialog.run(project); +} + +void +ProjectListImpl::on_menu_popup_close_all_projects() +{ + _app->lash_proxy()->close_all_projects(); +} + +void +ProjectListImpl::project_added( + shared_ptr<Project> project) +{ + Gtk::TreeModel::iterator iter; + Gtk::TreeModel::iterator child_iter; + Gtk::TreeModel::Row row; + string project_name = project->get_name(); + + if (project->get_modified_status()) { + project_name += " *"; + } + + iter = _model->append(); + row = *iter; + row[_columns.name] = project_name; + row[_columns.project] = project; + + project->_signal_renamed.connect(bind(mem_fun(this, &ProjectListImpl::project_renamed), iter)); + project->_signal_modified_status_changed.connect(bind(mem_fun(this, &ProjectListImpl::project_renamed), iter)); + project->_signal_client_added.connect(bind(mem_fun(this, &ProjectListImpl::client_added), iter)); + project->_signal_client_removed.connect(bind(mem_fun(this, &ProjectListImpl::client_removed), iter)); +} + +void +ProjectListImpl::project_closed( + shared_ptr<Project> project) +{ + shared_ptr<Project> temp_project; + Gtk::TreeModel::Children children = _model->children(); + Gtk::TreeModel::Children::iterator iter = children.begin(); + + while (iter != children.end()) { + Gtk::TreeModel::Row row = *iter; + + temp_project = row[_columns.project]; + + if (temp_project == project) { + _model->erase(iter); + return; + } + + iter++; + } +} + +void +ProjectListImpl::project_renamed( + Gtk::TreeModel::iterator iter) +{ + shared_ptr<Project> project; + + Gtk::TreeModel::Row row = *iter; + + project = row[_columns.project]; + string project_name = project->get_name(); + + if (project->get_modified_status()) + project_name += " *"; + + row[_columns.name] = project_name; +} + +void +ProjectListImpl::client_added( + shared_ptr<LashClient> client, + Gtk::TreeModel::iterator iter) +{ + Gtk::TreeModel::Path path = _model->get_path(iter); + Gtk::TreeModel::Row row = *iter; + + iter = _model->append(row.children()); + row = *iter; + row[_columns.name] = client->get_name(); + + _widget->expand_row(path, false); +} + +void +ProjectListImpl::client_removed( + shared_ptr<LashClient> client, + Gtk::TreeModel::iterator iter) +{ + Gtk::TreeModel::Path path = _model->get_path(iter); + Gtk::TreeModel::Row row = *iter; + + Gtk::TreeNodeChildren childs = row.children(); + + for (Gtk::TreeModel::iterator child_iter = childs.begin(); iter != childs.end(); child_iter++) { + row = *child_iter; + + if (row[_columns.name] == client->get_name()) { + _widget->expand_row(path, false); + _model->erase(child_iter); + return; + } + } +} + +void +ProjectList::set_lash_available(bool lash_active) +{ + _impl->_widget->set_sensitive(lash_active); +} + diff --git a/src/ProjectList.hpp b/src/ProjectList.hpp new file mode 100644 index 0000000..558fe48 --- /dev/null +++ b/src/ProjectList.hpp @@ -0,0 +1,38 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Dave Robillard <dave@drobilla.net> + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_PROJECT_LIST_HPP +#define PATCHAGE_PROJECT_LIST_HPP + +struct ProjectListImpl; +class Patchage; +class Session; + +class ProjectList { +public: + ProjectList(Patchage* app, Session* session); + ~ProjectList(); + + void set_lash_available(bool lash_active); + +private: + ProjectListImpl* _impl; +}; + +#endif // PATCHAGE_PROJECT_LIST_HPP diff --git a/src/ProjectPropertiesDialog.cpp b/src/ProjectPropertiesDialog.cpp new file mode 100644 index 0000000..ef52c3d --- /dev/null +++ b/src/ProjectPropertiesDialog.cpp @@ -0,0 +1,83 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include <gtkmm.h> +#include <libglademm/xml.h> +#include "Patchage.hpp" +#include "Project.hpp" +#include "ProjectPropertiesDialog.hpp" +#include "Widget.hpp" + +using boost::shared_ptr; +using namespace std; + + +struct ProjectPropertiesDialogImpl { + ProjectPropertiesDialogImpl(Glib::RefPtr<Gnome::Glade::Xml> xml); + + Widget<Gtk::Dialog> _dialog; + Widget<Gtk::Entry> _name; + Widget<Gtk::Entry> _description; + Widget<Gtk::TextView> _notes; +}; + + +ProjectPropertiesDialog::ProjectPropertiesDialog(Glib::RefPtr<Gnome::Glade::Xml> xml) +{ + _impl = new ProjectPropertiesDialogImpl(xml); +} + + +ProjectPropertiesDialog::~ProjectPropertiesDialog() +{ + delete _impl; +} + + +void +ProjectPropertiesDialog::run(shared_ptr<Project> project) +{ + Glib::RefPtr<Gtk::TextBuffer> buffer; + int result; + + _impl->_name->set_text(project->get_name()); + + _impl->_description->set_text(project->get_description()); + + buffer = _impl->_notes->get_buffer(); + buffer->set_text(project->get_notes()); + + result = _impl->_dialog->run(); + if (result == 2) { + project->do_change_description(_impl->_description->get_text()); + project->do_change_notes(buffer->get_text()); + project->do_rename(_impl->_name->get_text()); + } + + _impl->_dialog->hide(); +} + + +ProjectPropertiesDialogImpl::ProjectPropertiesDialogImpl(Glib::RefPtr<Gnome::Glade::Xml> xml) + : _dialog(xml, "project_properties_dialog") + , _name(xml, "project_name") + , _description(xml, "project_description") + , _notes(xml, "project_notes") +{ +} + diff --git a/src/ProjectPropertiesDialog.hpp b/src/ProjectPropertiesDialog.hpp new file mode 100644 index 0000000..e20cc20 --- /dev/null +++ b/src/ProjectPropertiesDialog.hpp @@ -0,0 +1,38 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_PROJECT_PROPERTIES_DIALOG_HPP +#define PATCHAGE_PROJECT_PROPERTIES_DIALOG_HPP + +#include <boost/shared_ptr.hpp> + +struct ProjectPropertiesDialogImpl; +class Project; + +class ProjectPropertiesDialog { +public: + ProjectPropertiesDialog(Glib::RefPtr<Gnome::Glade::Xml> xml); + ~ProjectPropertiesDialog(); + + void run(boost::shared_ptr<Project> project); + +private: + ProjectPropertiesDialogImpl* _impl; +}; + +#endif // PATCHAGE_PROJECT_PROPERTIES_DIALOG_HPP diff --git a/src/Session.cpp b/src/Session.cpp new file mode 100644 index 0000000..ec508a6 --- /dev/null +++ b/src/Session.cpp @@ -0,0 +1,124 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "Project.hpp" +#include "Session.hpp" +#include "LashClient.hpp" + +using namespace std; +using boost::shared_ptr; + +struct SessionImpl { + list< shared_ptr<Project> > projects; + list< shared_ptr<LashClient> > clients; +}; + +Session::Session() +{ + _impl = new SessionImpl; +} + +Session::~Session() +{ + delete _impl; +} + +void +Session::clear() +{ + shared_ptr<Project> project; + + _impl->clients.clear(); + + while (!_impl->projects.empty()) { + project = _impl->projects.front(); + _impl->projects.pop_front(); + project->clear(); + _signal_project_closed.emit(project); + } +} + +void +Session::project_add(shared_ptr<Project> project) +{ + _impl->projects.push_back(project); + + _signal_project_added.emit(project); +} + +shared_ptr<Project> +Session::find_project_by_name(const string& name) +{ + shared_ptr<Project> project; + for (list< shared_ptr<Project> >::iterator i = _impl->projects.begin(); i != _impl->projects.end(); i++) + if ((*i)->get_name() == name) + return (*i); + + return shared_ptr<Project>(); +} + +void +Session::project_close(const string& project_name) +{ + shared_ptr<Project> project; + Project::Clients clients; + + for (list<shared_ptr<Project> >::iterator iter = _impl->projects.begin(); iter != _impl->projects.end(); iter++) { + project = *iter; + + if (project->get_name() == project_name) { + _impl->projects.erase(iter); + _signal_project_closed.emit(project); + + // remove clients from session, if not removed already + clients = project->get_clients(); + for (Project::Clients::const_iterator i = clients.begin(); i != clients.end(); i++) + client_remove((*i)->get_id()); + + return; + } + } +} + +void +Session::client_add(shared_ptr<LashClient> client) +{ + _impl->clients.push_back(client); +} + +void +Session::client_remove(const string& id) +{ + for (list<shared_ptr<LashClient> >::iterator i = _impl->clients.begin(); i != _impl->clients.end(); i++) { + if ((*i)->get_id() == id) { + _impl->clients.erase(i); + return; + } + } +} + +shared_ptr<LashClient> +Session::find_client_by_id(const string& id) +{ + for (list<shared_ptr<LashClient> >::iterator i = _impl->clients.begin(); i != _impl->clients.end(); i++) + if ((*i)->get_id() == id) + return *i; + + return shared_ptr<LashClient>(); +} + diff --git a/src/Session.hpp b/src/Session.hpp new file mode 100644 index 0000000..4fc0591 --- /dev/null +++ b/src/Session.hpp @@ -0,0 +1,54 @@ +// -*- Mode: C++ ; indent-tabs-mode: t -*- +/* This file is part of Patchage. + * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name> + * + * Patchage 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. + * + * Patchage 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 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., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PATCHAGE_SESSION_HPP +#define PATCHAGE_SESSION_HPP + +#include <boost/shared_ptr.hpp> + +struct SessionImpl; +class Project; +class LashClient; + +class Session +{ +public: + Session(); + ~Session(); + + void clear(); + + void project_add(boost::shared_ptr<Project> project); + void project_close(const std::string& project_name); + + boost::shared_ptr<Project> find_project_by_name(const std::string& name); + + void client_add(boost::shared_ptr<LashClient> client); + void client_remove(const std::string& id); + + boost::shared_ptr<LashClient> find_client_by_id(const std::string& id); + + sigc::signal< void, boost::shared_ptr<Project> > _signal_project_added; + sigc::signal< void, boost::shared_ptr<Project> > _signal_project_closed; + +private: + SessionImpl* _impl; +}; + +#endif // PATCHAGE_SESSION_HPP + diff --git a/src/Widget.hpp b/src/Widget.hpp index 7d03922..56eb6d8 100644 --- a/src/Widget.hpp +++ b/src/Widget.hpp @@ -20,6 +20,7 @@ #include <string> #include <boost/utility.hpp> +#include <libglademm/xml.h> template <typename W> class Widget : public boost::noncopyable { diff --git a/src/patchage.glade b/src/patchage.glade index 7559db8..c9325c6 100644 --- a/src/patchage.glade +++ b/src/patchage.glade @@ -35,58 +35,6 @@ </widget> </child> <child> - <widget class="GtkImageMenuItem" id="menu_save_session"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Save Project</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_save_session_menuitem_activate"/> - <accelerator key="S" modifiers="GDK_CONTROL_MASK" signal="activate"/> - <child internal-child="image"> - <widget class="GtkImage" id="image824"> - <property name="visible">True</property> - <property name="stock">gtk-save</property> - <property name="icon_size">1</property> - </widget> - </child> - </widget> - </child> - <child> - <widget class="GtkImageMenuItem" id="menu_save_session_as"> - <property name="visible">True</property> - <property name="label" translatable="yes">Save Project _As...</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_save_session_as_menuitem_activate"/> - <accelerator key="S" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK" signal="activate"/> - <child internal-child="image"> - <widget class="GtkImage" id="image825"> - <property name="visible">True</property> - <property name="stock">gtk-save-as</property> - <property name="icon_size">1</property> - </widget> - </child> - </widget> - </child> - <child> - <widget class="GtkImageMenuItem" id="menu_close_session"> - <property name="visible">True</property> - <property name="label" translatable="yes">_Close Project</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_close_session_menuitem_activate"/> - <child internal-child="image"> - <widget class="GtkImage" id="image826"> - <property name="visible">True</property> - <property name="stock">gtk-close</property> - <property name="icon_size">1</property> - </widget> - </child> - </widget> - </child> - <child> - <widget class="GtkSeparatorMenuItem" id="separator5"> - <property name="visible">True</property> - </widget> - </child> - <child> <widget class="GtkImageMenuItem" id="menu_store_positions"> <property name="visible">True</property> <property name="tooltip" translatable="yes">Store the current module positions as the defaults</property> @@ -128,27 +76,6 @@ <child> <widget class="GtkMenu" id="file_system_menuitem_menu"> <child> - <widget class="GtkImageMenuItem" id="menu_jack_settings"> - <property name="visible">True</property> - <property name="label" translatable="yes">Jack _Settings</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_jack_settings1_activate"/> - <accelerator key="P" modifiers="GDK_CONTROL_MASK" signal="activate"/> - <child internal-child="image"> - <widget class="GtkImage" id="image828"> - <property name="visible">True</property> - <property name="stock">gtk-preferences</property> - <property name="icon_size">1</property> - </widget> - </child> - </widget> - </child> - <child> - <widget class="GtkSeparatorMenuItem" id="separator6"> - <property name="visible">True</property> - </widget> - </child> - <child> <widget class="GtkImageMenuItem" id="menu_jack_connect"> <property name="visible">True</property> <property name="label" translatable="yes">Connect to _JACK</property> @@ -219,44 +146,6 @@ </child> </widget> </child> - <child> - <widget class="GtkSeparatorMenuItem" id="separator1"> - <property name="visible">True</property> - </widget> - </child> - <child> - <widget class="GtkImageMenuItem" id="menu_lash_connect"> - <property name="visible">True</property> - <property name="label" translatable="yes">Connect to _LASH</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_menu_lash_connect"/> - <accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/> - <child internal-child="image"> - <widget class="GtkImage" id="image833"> - <property name="visible">True</property> - <property name="stock">gtk-connect</property> - <property name="icon_size">1</property> - </widget> - </child> - </widget> - </child> - <child> - <widget class="GtkImageMenuItem" id="menu_lash_disconnect"> - <property name="visible">True</property> - <property name="sensitive">False</property> - <property name="label" translatable="yes">Disconnect from LASH</property> - <property name="use_underline">True</property> - <signal name="activate" handler="on_menu_lash_disconnect_activate"/> - <accelerator key="L" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK" signal="activate"/> - <child internal-child="image"> - <widget class="GtkImage" id="image834"> - <property name="visible">True</property> - <property name="stock">gtk-disconnect</property> - <property name="icon_size">1</property> - </widget> - </child> - </widget> - </child> </widget> </child> </widget> @@ -279,9 +168,18 @@ </widget> </child> <child> + <widget class="GtkCheckMenuItem" id="menu_view_projects"> + <property name="visible">True</property> + <property name="tooltip" translatable="yes">Show LASH projects list</property> + <property name="label" translatable="yes">_Projects</property> + <property name="use_underline">True</property> + <accelerator key="P" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + <child> <widget class="GtkCheckMenuItem" id="menu_view_messages"> <property name="visible">True</property> - <property name="tooltip" translatable="yes">View "console" messages</property> + <property name="tooltip" translatable="yes">View log messages</property> <property name="label" translatable="yes">_Messages</property> <property name="use_underline">True</property> <signal name="activate" handler="on_messages1_activate"/> @@ -527,19 +425,49 @@ The bar represents the percentage of available time used for audio processing (i </packing> </child> <child> - <widget class="GtkScrolledWindow" id="main_scrolledwin"> + <widget class="GtkHPaned" id="hpaned1"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="has_focus">True</property> - <property name="is_focus">True</property> - <property name="can_default">True</property> - <property name="has_default">True</property> - <property name="receives_default">True</property> - <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="position">204</property> <child> - <placeholder/> + <widget class="GtkViewport" id="project_list_viewport"> + <property name="visible">True</property> + <property name="resize_mode">GTK_RESIZE_QUEUE</property> + <child> + <widget class="GtkTreeView" id="projects_list"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="headers_clickable">True</property> + </widget> + </child> + </widget> + <packing> + <property name="resize">False</property> + <property name="shrink">True</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="main_scrolledwin"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="is_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="resize">True</property> + <property name="shrink">True</property> + </packing> </child> </widget> <packing> @@ -552,9 +480,9 @@ The bar represents the percentage of available time used for audio processing (i <widget class="GtkAboutDialog" id="about_win"> <property name="destroy_with_parent">True</property> <property name="type_hint">GDK_WINDOW_TYPE_HINT_NORMAL</property> - <property name="copyright" translatable="yes">© 2007 Dave Robillard</property> - <property name="comments" translatable="yes">A modular patch bay for audio and MIDI systems -based on JACK, LASH, and ALSA.</property> + <property name="copyright" translatable="yes">© 2005-2008 Dave Robillard +© 2008 Nedko Arnaudov</property> + <property name="comments" translatable="yes">A LASH, JACK, and ALSA front-end.</property> <property name="website">http://drobilla.net/software/patchage</property> <property name="license" translatable="yes">Patchage is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -571,7 +499,7 @@ along with Patchage; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA </property> <property name="authors">Dave Robillard <dave@drobilla.net> -JACK D-Bus driver by Nedko Arnaudov <nedko@arnaudov.name></property> +Nedko Arnaudov <nedko@arnaudov.name></property> <property name="translator_credits" translatable="yes" comments="TRANSLATORS: Replace this string with your names, one name per line.">translator-credits</property> <property name="artists">Icon: Lapo Calamandrei</property> @@ -588,106 +516,147 @@ JACK D-Bus driver by Nedko Arnaudov <nedko@arnaudov.name></property> </widget> </child> </widget> - <widget class="GtkDialog" id="jack_settings_win"> - <property name="width_request">400</property> - <property name="height_request">200</property> - <property name="border_width">8</property> - <property name="title" translatable="yes">JACK Settings</property> + <widget class="GtkDialog" id="messages_win"> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Patchage - Messages</property> <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> - <property name="default_width">400</property> - <property name="default_height">200</property> - <property name="destroy_with_parent">True</property> <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="has_separator">False</property> <child internal-child="vbox"> - <widget class="GtkVBox" id="dialog-vbox2"> + <widget class="GtkVBox" id="dialog-vbox3"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">2</property> <child> - <widget class="GtkVBox" id="vbox2"> + <widget class="GtkScrolledWindow" id="scrolledwindow2"> <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> <child> - <widget class="GtkLabel" id="label1"> + <widget class="GtkTextView" id="status_text"> + <property name="width_request">640</property> + <property name="height_request">480</property> <property name="visible">True</property> - <property name="xalign">0</property> - <property name="ypad">6</property> - <property name="label" translatable="yes"><b>Command Line</b></property> - <property name="use_markup">True</property> - <property name="wrap">True</property> + <property name="can_focus">True</property> + <property name="editable">False</property> + <property name="wrap_mode">GTK_WRAP_WORD</property> + <property name="cursor_visible">False</property> + <property name="accepts_tab">False</property> </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> <child> - <widget class="GtkHBox" id="hbox2"> + <widget class="GtkButton" id="messages_clear_but"> <property name="visible">True</property> - <child> - <widget class="GtkLabel" id="label2"> - <property name="visible">True</property> - <property name="label" translatable="yes"> </property> - </widget> - <packing> - <property name="expand">False</property> - <property name="fill">False</property> - </packing> - </child> - <child> - <widget class="GtkEntry" id="jack_settings_command_entry"> - <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="invisible_char">*</property> - <property name="activates_default">True</property> - </widget> - <packing> - <property name="position">1</property> - </packing> - </child> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-clear</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + </widget> + </child> + <child> + <widget class="GtkButton" id="messages_close_but"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-close</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> </widget> <packing> - <property name="expand">False</property> - <property name="fill">False</property> <property name="position">1</property> </packing> </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkDialog" id="load_project_dialog"> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Load project</property> + <property name="modal">True</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="transient_for">main_win</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox4"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">2</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="width_request">400</property> + <property name="height_request">400</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> <child> - <widget class="GtkLabel" id="label3"> - <property name="width_request">323</property> - <property name="height_request">200</property> + <widget class="GtkTreeView" id="loadable_projects_list"> <property name="visible">True</property> - <property name="ypad">12</property> - <property name="label" translatable="yes">Note: This will change your default setting in ~/.jackdrc</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="headers_clickable">True</property> </widget> - <packing> - <property name="position">2</property> - </packing> </child> </widget> <packing> - <property name="position">2</property> + <property name="position">1</property> </packing> </child> <child internal-child="action_area"> - <widget class="GtkHButtonBox" id="dialog-action_area2"> + <widget class="GtkHButtonBox" id="dialog-action_area4"> <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="layout_style">GTK_BUTTONBOX_END</property> <child> - <widget class="GtkButton" id="jack_settings_cancel_but"> + <widget class="GtkButton" id="cancel_button"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="label">gtk-cancel</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-cancel</property> <property name="use_stock">True</property> - <property name="response_id">-6</property> + <property name="response_id">1</property> </widget> </child> <child> - <widget class="GtkButton" id="jack_settings_ok_but"> + <widget class="GtkButton" id="ok_button"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="has_default">True</property> - <property name="label">gtk-ok</property> + <property name="receives_default">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">gtk-ok</property> <property name="use_stock">True</property> - <property name="response_id">-5</property> + <property name="response_id">2</property> </widget> <packing> <property name="position">1</property> @@ -702,40 +671,123 @@ JACK D-Bus driver by Nedko Arnaudov <nedko@arnaudov.name></property> </widget> </child> </widget> - <widget class="GtkDialog" id="messages_win"> - <property name="can_default">True</property> - <property name="has_default">True</property> - <property name="receives_default">True</property> + <widget class="GtkDialog" id="project_properties_dialog"> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="border_width">5</property> - <property name="title" translatable="yes">Patchage - Messages</property> + <property name="title" translatable="yes">Project properties</property> + <property name="modal">True</property> <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="transient_for">main_win</property> <property name="has_separator">False</property> <child internal-child="vbox"> - <widget class="GtkVBox" id="dialog-vbox3"> + <widget class="GtkVBox" id="dialog-vbox5"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="spacing">2</property> <child> - <widget class="GtkScrolledWindow" id="scrolledwindow2"> + <widget class="GtkVBox" id="vbox1"> <property name="visible">True</property> - <property name="can_focus">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> - <property name="shadow_type">GTK_SHADOW_IN</property> + <property name="spacing">10</property> <child> - <widget class="GtkTextView" id="status_text"> - <property name="width_request">640</property> - <property name="height_request">480</property> + <widget class="GtkFrame" id="frame1"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="editable">False</property> - <property name="wrap_mode">GTK_WRAP_WORD</property> - <property name="cursor_visible">False</property> - <property name="accepts_tab">False</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkEntry" id="project_name"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes"><b>Project name</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkEntry" id="project_description"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes"><b>Description</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <child> + <widget class="GtkTextView" id="project_notes"> + <property name="width_request">300</property> + <property name="height_request">200</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label3"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes"><b>Notes</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> </widget> + <packing> + <property name="position">2</property> + </packing> </child> </widget> <packing> @@ -743,31 +795,30 @@ JACK D-Bus driver by Nedko Arnaudov <nedko@arnaudov.name></property> </packing> </child> <child internal-child="action_area"> - <widget class="GtkHButtonBox" id="dialog-action_area3"> + <widget class="GtkHButtonBox" id="dialog-action_area5"> <property name="visible">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> <property name="layout_style">GTK_BUTTONBOX_END</property> <child> - <widget class="GtkButton" id="messages_clear_but"> + <widget class="GtkButton" id="cancel_button1"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">gtk-clear</property> + <property name="label" translatable="yes">gtk-cancel</property> <property name="use_stock">True</property> - <property name="response_id">0</property> + <property name="response_id">1</property> </widget> </child> <child> - <widget class="GtkButton" id="messages_close_but"> + <widget class="GtkButton" id="ok_buton"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="can_default">True</property> - <property name="has_default">True</property> + <property name="receives_default">True</property> <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes">gtk-close</property> + <property name="label" translatable="yes">gtk-ok</property> <property name="use_stock">True</property> - <property name="response_id">0</property> + <property name="response_id">2</property> </widget> <packing> <property name="position">1</property> |