diff options
author | David Robillard <d@drobilla.net> | 2012-03-16 22:27:16 +0000 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2012-03-16 22:27:16 +0000 |
commit | bc3afd8380d59c750c8f8e9bf1ed1b8d4a6826e9 (patch) | |
tree | b42f56620ce85f6207568eadfb901360436c6f74 /src | |
parent | 7126f005be3e49818dafe0d2666b6745e09f8aff (diff) | |
download | ingen-bc3afd8380d59c750c8f8e9bf1ed1b8d4a6826e9.tar.gz ingen-bc3afd8380d59c750c8f8e9bf1ed1b8d4a6826e9.tar.bz2 ingen-bc3afd8380d59c750c8f8e9bf1ed1b8d4a6826e9.zip |
Preliminary work towards native LV2 UI.
git-svn-id: http://svn.drobilla.net/lad/trunk/ingen@4074 a436a847-0d15-0410-975c-d299462d15a1
Diffstat (limited to 'src')
-rw-r--r-- | src/client/ClientStore.cpp | 2 | ||||
-rw-r--r-- | src/client/PluginUI.cpp | 3 | ||||
-rw-r--r-- | src/gui/App.cpp | 7 | ||||
-rw-r--r-- | src/gui/App.hpp | 2 | ||||
-rw-r--r-- | src/gui/ConnectWindow.cpp | 2 | ||||
-rw-r--r-- | src/gui/PatchBox.cpp | 710 | ||||
-rw-r--r-- | src/gui/PatchBox.hpp | 170 | ||||
-rw-r--r-- | src/gui/PatchView.hpp | 2 | ||||
-rw-r--r-- | src/gui/PatchWindow.cpp | 647 | ||||
-rw-r--r-- | src/gui/PatchWindow.hpp | 133 | ||||
-rw-r--r-- | src/gui/Port.cpp | 26 | ||||
-rw-r--r-- | src/gui/Port.hpp | 4 | ||||
-rw-r--r-- | src/gui/WindowFactory.cpp | 18 | ||||
-rw-r--r-- | src/gui/WindowFactory.hpp | 9 | ||||
-rw-r--r-- | src/gui/ingen_gui_lv2.cpp | 179 | ||||
-rw-r--r-- | src/gui/wscript | 11 | ||||
-rw-r--r-- | src/server/ServerInterfaceImpl.hpp | 2 | ||||
-rw-r--r-- | src/server/events/Get.cpp | 1 | ||||
-rw-r--r-- | src/shared/AtomSink.hpp | 34 | ||||
-rw-r--r-- | src/shared/AtomWriter.cpp | 48 | ||||
-rw-r--r-- | src/shared/AtomWriter.hpp | 89 | ||||
-rw-r--r-- | src/shared/LV2Atom.cpp | 4 | ||||
-rw-r--r-- | src/shared/URIs.cpp | 2 |
23 files changed, 1200 insertions, 905 deletions
diff --git a/src/client/ClientStore.cpp b/src/client/ClientStore.cpp index 83815187..ecf03dad 100644 --- a/src/client/ClientStore.cpp +++ b/src/client/ClientStore.cpp @@ -31,7 +31,7 @@ #define LOG(s) s << "[ClientStore] " -//#define INGEN_CLIENT_STORE_DUMP 1 +// #define INGEN_CLIENT_STORE_DUMP 1 using namespace std; using namespace Raul; diff --git a/src/client/PluginUI.cpp b/src/client/PluginUI.cpp index a3a2dabb..c20d41b1 100644 --- a/src/client/PluginUI.cpp +++ b/src/client/PluginUI.cpp @@ -94,10 +94,13 @@ lv2_ui_write(SuilController controller, } } else if (format == uris.atom_eventTransfer.id) { + std::cerr << "FIXME: atom event transfer" << std::endl; + #if 0 LV2_Atom* buf = (LV2_Atom*)buffer; Raul::Atom val; Shared::LV2Atom::to_atom(uris, buf, val); ui->world()->engine()->set_property(port->path(), uris.ingen_value, val); + #endif } else { warn << "Unknown value format " << format diff --git a/src/gui/App.cpp b/src/gui/App.cpp index 620aceb4..ad70a7f9 100644 --- a/src/gui/App.cpp +++ b/src/gui/App.cpp @@ -317,14 +317,17 @@ App::show_about() * @return true iff the application quit. */ bool -App::quit(Gtk::Window& dialog_parent) +App::quit(Gtk::Window* dialog_parent) { bool quit = true; if (_world->local_engine()) { - Gtk::MessageDialog d(dialog_parent, + Gtk::MessageDialog d( "The engine is running in this process. Quitting will terminate Ingen." "\n\n" "Are you sure you want to quit?", true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE, true); + if (dialog_parent) { + d.set_transient_for(*dialog_parent); + } d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); d.add_button(Gtk::Stock::QUIT, Gtk::RESPONSE_CLOSE); quit = (d.run() == Gtk::RESPONSE_CLOSE); diff --git a/src/gui/App.hpp b/src/gui/App.hpp index 6f2cd592..7b45a6e8 100644 --- a/src/gui/App.hpp +++ b/src/gui/App.hpp @@ -81,7 +81,7 @@ public: bool gtk_main_iteration(); void show_about(); - bool quit(Gtk::Window& dialog_parent); + bool quit(Gtk::Window* dialog_parent); void port_activity(Port* port); void activity_port_destroyed(Port* port); diff --git a/src/gui/ConnectWindow.cpp b/src/gui/ConnectWindow.cpp index dfb285a4..8e4b7cc3 100644 --- a/src/gui/ConnectWindow.cpp +++ b/src/gui/ConnectWindow.cpp @@ -321,7 +321,7 @@ ConnectWindow::on_hide() void ConnectWindow::quit_clicked() { - if (_app->quit(*this)) + if (_app->quit(this)) _quit_flag = true; } diff --git a/src/gui/PatchBox.cpp b/src/gui/PatchBox.cpp new file mode 100644 index 00000000..127da746 --- /dev/null +++ b/src/gui/PatchBox.cpp @@ -0,0 +1,710 @@ +/* This file is part of Ingen. + * Copyright 2007-2012 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU 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 <cassert> +#include <sstream> + +#include <boost/format.hpp> +#include <glib/gstdio.h> +#include <glibmm/fileutils.h> + +#include "raul/AtomRDF.hpp" + +#include "ingen/Interface.hpp" +#include "ingen/client/ClientStore.hpp" +#include "ingen/client/PatchModel.hpp" +#include "ingen/shared/LV2URIMap.hpp" + +#include "App.hpp" +#include "BreadCrumbs.hpp" +#include "Configuration.hpp" +#include "ConnectWindow.hpp" +#include "LoadPatchWindow.hpp" +#include "LoadPluginWindow.hpp" +#include "MessagesWindow.hpp" +#include "NewSubpatchWindow.hpp" +#include "NodeControlWindow.hpp" +#include "PatchCanvas.hpp" +#include "PatchTreeWindow.hpp" +#include "PatchView.hpp" +#include "PatchWindow.hpp" +#include "ThreadedLoader.hpp" +#include "WindowFactory.hpp" +#include "ingen_config.h" + +#ifdef HAVE_WEBKIT +#include <webkit/webkit.h> +#endif + +using namespace Raul; + +namespace Ingen { +namespace GUI { + +static const int STATUS_CONTEXT_ENGINE = 0; +static const int STATUS_CONTEXT_PATCH = 1; +static const int STATUS_CONTEXT_HOVER = 2; + +PatchBox::PatchBox(BaseObjectType* cobject, + const Glib::RefPtr<Gtk::Builder>& xml) + : Gtk::VBox(cobject) + , _app(NULL) + , _window(NULL) + , _breadcrumbs(NULL) + , _has_shown_documentation(false) + , _enable_signal(true) +{ + property_visible() = false; + + xml->get_widget("patch_win_alignment", _alignment); + xml->get_widget("patch_win_status_bar", _status_bar); + //xml->get_widget("patch_win_status_bar", _status_bar); + //xml->get_widget("patch_open_menuitem", _menu_open); + xml->get_widget("patch_import_menuitem", _menu_import); + //xml->get_widget("patch_open_into_menuitem", _menu_open_into); + xml->get_widget("patch_save_menuitem", _menu_save); + xml->get_widget("patch_save_as_menuitem", _menu_save_as); + xml->get_widget("patch_draw_menuitem", _menu_draw); + xml->get_widget("patch_edit_controls_menuitem", _menu_edit_controls); + xml->get_widget("patch_cut_menuitem", _menu_cut); + xml->get_widget("patch_copy_menuitem", _menu_copy); + xml->get_widget("patch_paste_menuitem", _menu_paste); + xml->get_widget("patch_delete_menuitem", _menu_delete); + xml->get_widget("patch_select_all_menuitem", _menu_select_all); + xml->get_widget("patch_close_menuitem", _menu_close); + xml->get_widget("patch_quit_menuitem", _menu_quit); + xml->get_widget("patch_view_control_window_menuitem", _menu_view_control_window); + xml->get_widget("patch_view_engine_window_menuitem", _menu_view_engine_window); + xml->get_widget("patch_properties_menuitem", _menu_view_patch_properties); + xml->get_widget("patch_fullscreen_menuitem", _menu_fullscreen); + xml->get_widget("patch_human_names_menuitem", _menu_human_names); + xml->get_widget("patch_show_port_names_menuitem", _menu_show_port_names); + xml->get_widget("patch_zoom_in_menuitem", _menu_zoom_in); + xml->get_widget("patch_zoom_out_menuitem", _menu_zoom_out); + xml->get_widget("patch_zoom_normal_menuitem", _menu_zoom_normal); + xml->get_widget("patch_status_bar_menuitem", _menu_show_status_bar); + xml->get_widget("patch_arrange_menuitem", _menu_arrange); + xml->get_widget("patch_view_messages_window_menuitem", _menu_view_messages_window); + xml->get_widget("patch_view_patch_tree_window_menuitem", _menu_view_patch_tree_window); + xml->get_widget("patch_help_about_menuitem", _menu_help_about); + xml->get_widget("patch_documentation_paned", _doc_paned); + xml->get_widget("patch_documentation_scrolledwindow", _doc_scrolledwindow); + + _menu_view_control_window->property_sensitive() = false; + _menu_import->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_import)); + _menu_save->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_save)); + _menu_save_as->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_save_as)); + _menu_draw->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_draw)); + _menu_edit_controls->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_edit_controls)); + _menu_copy->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_copy)); + _menu_paste->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_paste)); + _menu_delete->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_delete)); + _menu_select_all->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_select_all)); + _menu_close->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_close)); + _menu_quit->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_quit)); + _menu_fullscreen->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_fullscreen_toggled)); + _menu_human_names->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_human_names_toggled)); + _menu_show_status_bar->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_status_bar_toggled)); + _menu_show_port_names->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_port_names_toggled)); + _menu_arrange->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_arrange)); + _menu_quit->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_quit)); + _menu_zoom_in->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_zoom_in)); + _menu_zoom_out->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_zoom_out)); + _menu_zoom_normal->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_zoom_normal)); + _menu_view_engine_window->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_show_engine)); + _menu_view_control_window->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_show_controls)); + _menu_view_patch_properties->signal_activate().connect( + sigc::mem_fun(this, &PatchBox::event_show_properties)); + + Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get(); + clipboard->signal_owner_change().connect( + sigc::mem_fun(this, &PatchBox::event_clipboard_changed)); +} + +PatchBox::~PatchBox() +{ + delete _breadcrumbs; +} + +void +PatchBox::init_box(App& app) +{ + _app = &app; + + string engine_name = _app->engine()->uri().str(); + if (engine_name == "http://drobilla.net/ns/ingen#internal") { + engine_name = "internal engine"; + } + _status_bar->push(string("Connected to ") + engine_name, STATUS_CONTEXT_ENGINE); + + _menu_view_messages_window->signal_activate().connect( + sigc::mem_fun<void>(_app->messages_dialog(), &MessagesWindow::present)); + _menu_view_patch_tree_window->signal_activate().connect( + sigc::mem_fun<void>(_app->patch_tree(), &PatchTreeWindow::present)); + + _menu_help_about->signal_activate().connect(sigc::hide_return( + sigc::mem_fun(_app, &App::show_about))); + + _breadcrumbs = new BreadCrumbs(*_app); + _breadcrumbs->signal_patch_selected.connect( + sigc::mem_fun(this, &PatchBox::set_patch_from_path)); +} + +void +PatchBox::set_patch_from_path(const Raul::Path& path, SharedPtr<PatchView> view) +{ + std::cerr << "FIXME: Set patch from path" << std::endl; +} + +/** Sets the patch for this box and initializes everything. + * + * If @a view is NULL, a new view will be created. + */ +void +PatchBox::set_patch(SharedPtr<const PatchModel> patch, + SharedPtr<PatchView> view) +{ + if (!patch || patch == _patch) + return; + + _enable_signal = false; + + new_port_connection.disconnect(); + removed_port_connection.disconnect(); + edit_mode_connection.disconnect(); + _entered_connection.disconnect(); + _left_connection.disconnect(); + + _status_bar->pop(STATUS_CONTEXT_PATCH); + + _patch = patch; + _view = view; + + if (!_view) + _view = _breadcrumbs->view(patch->path()); + + if (!_view) + _view = PatchView::create(*_app, patch); + + assert(_view); + + // Add view to our alignment + if (_view->get_parent()) + _view->get_parent()->remove(*_view.get()); + + _alignment->remove(); + _alignment->add(*_view.get()); + + if (_breadcrumbs->get_parent()) + _breadcrumbs->get_parent()->remove(*_breadcrumbs); + + _view->breadcrumb_container()->remove(); + _view->breadcrumb_container()->add(*_breadcrumbs); + _view->breadcrumb_container()->show(); + + _breadcrumbs->build(patch->path(), _view); + _breadcrumbs->show(); + + _menu_view_control_window->property_sensitive() = false; + + for (NodeModel::Ports::const_iterator p = patch->ports().begin(); + p != patch->ports().end(); ++p) { + if (_app->can_control(p->get())) { + _menu_view_control_window->property_sensitive() = true; + break; + } + } + + new_port_connection = patch->signal_new_port().connect( + sigc::mem_fun(this, &PatchBox::patch_port_added)); + removed_port_connection = patch->signal_removed_port().connect( + sigc::mem_fun(this, &PatchBox::patch_port_removed)); + removed_port_connection = patch->signal_editable().connect( + sigc::mem_fun(this, &PatchBox::editable_changed)); + + show(); + _alignment->show_all(); + + _view->signal_object_entered.connect( + sigc::mem_fun(this, &PatchBox::object_entered)); + _view->signal_object_left.connect( + sigc::mem_fun(this, &PatchBox::object_left)); + + _enable_signal = true; +} + +void +PatchBox::patch_port_added(SharedPtr<const PortModel> port) +{ + if (port->is_input() && _app->can_control(port.get())) { + _menu_view_control_window->property_sensitive() = true; + } +} + +void +PatchBox::patch_port_removed(SharedPtr<const PortModel> port) +{ + if (!(port->is_input() && _app->can_control(port.get()))) + return; + + for (NodeModel::Ports::const_iterator i = _patch->ports().begin(); + i != _patch->ports().end(); ++i) { + if ((*i)->is_input() && _app->can_control(i->get())) { + _menu_view_control_window->property_sensitive() = true; + return; + } + } + + _menu_view_control_window->property_sensitive() = false; +} + +void +PatchBox::show_documentation(const std::string& doc, bool html) +{ +#ifdef HAVE_WEBKIT + WebKitWebView* view = WEBKIT_WEB_VIEW(webkit_web_view_new()); + webkit_web_view_load_html_string(view, doc.c_str(), ""); + _doc_scrolledwindow->add(*Gtk::manage(Glib::wrap(GTK_WIDGET(view)))); + _doc_scrolledwindow->show_all(); +#else + Gtk::TextView* view = Gtk::manage(new Gtk::TextView()); + view->get_buffer()->set_text(doc); + _doc_scrolledwindow->add(*view); + _doc_scrolledwindow->show_all(); +#endif + if (!_has_shown_documentation) { + const Gtk::Allocation allocation = get_allocation(); + _doc_paned->set_position(allocation.get_width() / 1.61803399); + } + _has_shown_documentation = true; +} + +void +PatchBox::hide_documentation() +{ + _doc_scrolledwindow->remove(); + _doc_scrolledwindow->hide(); +} + +void +PatchBox::show_status(const ObjectModel* model) +{ + std::stringstream msg; + msg << model->path().chop_scheme(); + + const PortModel* port = 0; + const NodeModel* node = 0; + + if ((port = dynamic_cast<const PortModel*>(model))) { + show_port_status(port, port->value()); + + } else if ((node = dynamic_cast<const NodeModel*>(model))) { + const PluginModel* plugin = dynamic_cast<const PluginModel*>(node->plugin()); + if (plugin) + msg << ((boost::format(" (%1%)") % plugin->human_name()).str()); + _status_bar->push(msg.str(), STATUS_CONTEXT_HOVER); + } +} + +void +PatchBox::show_port_status(const PortModel* port, const Raul::Atom& value) +{ + std::stringstream msg; + msg << port->path().chop_scheme(); + + const NodeModel* parent = dynamic_cast<const NodeModel*>(port->parent().get()); + if (parent) { + const PluginModel* plugin = dynamic_cast<const PluginModel*>(parent->plugin()); + if (plugin) { + const string& human_name = plugin->port_human_name(port->index()); + if (!human_name.empty()) + msg << " (" << human_name << ")"; + } + } + + if (value.is_valid()) { + msg << " = " << value; + } + + _status_bar->pop(STATUS_CONTEXT_HOVER); + _status_bar->push(msg.str(), STATUS_CONTEXT_HOVER); +} + +void +PatchBox::object_entered(const ObjectModel* model) +{ + show_status(model); +} + +void +PatchBox::object_left(const ObjectModel* model) +{ + _status_bar->pop(STATUS_CONTEXT_PATCH); + _status_bar->pop(STATUS_CONTEXT_HOVER); +} + +void +PatchBox::editable_changed(bool editable) +{ + _menu_edit_controls->set_active(editable); +} + +void +PatchBox::event_show_engine() +{ + if (_patch) + _app->connect_window()->show(); +} + +void +PatchBox::event_clipboard_changed(GdkEventOwnerChange* ev) +{ + Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get(); + _menu_paste->set_sensitive(clipboard->wait_is_text_available()); +} + +void +PatchBox::event_show_controls() +{ + _app->window_factory()->present_controls(_patch); +} + +void +PatchBox::event_show_properties() +{ + _app->window_factory()->present_properties(_patch); +} + +void +PatchBox::event_import() +{ + _app->window_factory()->present_load_patch(_patch); +} + +void +PatchBox::event_save() +{ + const Raul::Atom& document = _patch->get_property(_app->uris().ingen_document); + if (!document.is_valid() || document.type() != Raul::Atom::URI) { + event_save_as(); + } else { + _app->loader()->save_patch(_patch, document.get_uri()); + _status_bar->push( + (boost::format("Saved %1% to %2%") % _patch->path().chop_scheme() + % document.get_uri()).str(), + STATUS_CONTEXT_PATCH); + } +} + +int +PatchBox::message_dialog(const Glib::ustring& message, + const Glib::ustring& secondary_text, + Gtk::MessageType type, + Gtk::ButtonsType buttons) +{ + Gtk::MessageDialog dialog(message, true, type, buttons, true); + dialog.set_secondary_text(secondary_text); + if (_window) { + dialog.set_transient_for(*_window); + } + return dialog.run(); +} + +void +PatchBox::event_save_as() +{ + const Shared::URIs& uris = _app->uris(); + while (true) { + Gtk::FileChooserDialog dialog("Save Patch", Gtk::FILE_CHOOSER_ACTION_SAVE); + if (_window) { + dialog.set_transient_for(*_window); + } + + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + Gtk::Button* save_button = dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); + save_button->property_has_default() = true; + + Gtk::FileFilter filt; + filt.add_pattern("*.ingen"); + filt.set_name("Ingen bundles"); + dialog.set_filter(filt); + + // Set current folder to most sensible default + const Raul::Atom& document = _patch->get_property(uris.ingen_document); + if (document.type() == Raul::Atom::URI) + dialog.set_uri(document.get_uri()); + else if (_app->configuration()->patch_folder().length() > 0) + dialog.set_current_folder(_app->configuration()->patch_folder()); + + if (dialog.run() != Gtk::RESPONSE_OK) + break; + + std::string filename = dialog.get_filename(); + std::string basename = Glib::path_get_basename(filename); + + if (basename.find('.') == string::npos) { + filename += ".ingen"; + basename += ".ingen"; + } else if (filename.substr(filename.length() - 10) != ".ingen") { + message_dialog( + "<b>Ingen patches must be saved to Ingen bundles (*.ingen).</b>", + "", Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK); + continue; + } + + const std::string symbol(basename.substr(0, basename.find('.'))); + + if (!Symbol::is_valid(symbol)) { + message_dialog( + "<b>Ingen bundle names must be valid symbols.</b>", + "All characters must be _, a-z, A-Z, or 0-9, but the first may not be 0-9.", + Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK); + continue; + } + + //_patch->set_property(uris.lv2_symbol, Atom(symbol.c_str())); + + bool confirm = true; + if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) { + if (Glib::file_test(Glib::build_filename(filename, "manifest.ttl"), + Glib::FILE_TEST_EXISTS)) { + int ret = message_dialog( + (boost::format("<b>A bundle named \"%1%\" already exists." + " Replace it?</b>") % basename).str(), + "", Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO); + confirm = (ret == Gtk::RESPONSE_YES); + } else { + int ret = message_dialog( + (boost::format("<b>A directory named \"%1%\" already exists," + "but is not an Ingen bundle. " + "Save into it anyway?</b>") % basename).str(), + "This will create at least 2 .ttl files in this directory," + "and possibly several more files and/or directories, recursively. " + "Existing files will be overwritten.", + Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO); + confirm = (ret == Gtk::RESPONSE_YES); + } + } else if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) { + int ret = message_dialog( + (boost::format("<b>A file named \"%1%\" already exists. " + "Replace it with an Ingen bundle?</b>") + % basename).str(), + "", Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO); + confirm = (ret == Gtk::RESPONSE_YES); + if (confirm) + ::g_remove(filename.c_str()); + } + + if (confirm) { + const Glib::ustring uri = Glib::filename_to_uri(filename); + _app->loader()->save_patch(_patch, uri); + const_cast<PatchModel*>(_patch.get())->set_property( + uris.ingen_document, + _app->forge().alloc(Atom::URI, uri.c_str()), + Resource::EXTERNAL); + _status_bar->push( + (boost::format("Saved %1% to %2%") % _patch->path().chop_scheme() + % filename).str(), + STATUS_CONTEXT_PATCH); + } + + _app->configuration()->set_patch_folder(dialog.get_current_folder()); + break; + } +} + +void +PatchBox::event_draw() +{ + Gtk::FileChooserDialog dialog("Draw to DOT", Gtk::FILE_CHOOSER_ACTION_SAVE); + if (_window) { + dialog.set_transient_for(*_window); + } + + dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); + Gtk::Button* save_button = dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); + save_button->property_has_default() = true; + + int result = dialog.run(); + + if (result == Gtk::RESPONSE_OK) { + string filename = dialog.get_filename(); + if (filename.find(".") == string::npos) + filename += ".dot"; + + bool confirm = true; + if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) { + int ret = message_dialog( + (boost::format("File exists! Overwrite %1%?") % filename).str(), + "", Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO); + confirm = (ret == Gtk::RESPONSE_YES); + } + + if (confirm) { + _view->canvas()->render_to_dot(filename); + _status_bar->push( + (boost::format("Rendered %1% to %2%") % _patch->path() % filename).str(), + STATUS_CONTEXT_PATCH); + } + } +} + +void +PatchBox::event_edit_controls() +{ + if (_view) + _view->set_editable(_menu_edit_controls->get_active()); +} + +void +PatchBox::event_copy() +{ + if (_view) + _view->canvas()->copy_selection(); +} + +void +PatchBox::event_paste() +{ + if (_view) + _view->canvas()->paste(); +} + +void +PatchBox::event_delete() +{ + if (_view) + _view->canvas()->destroy_selection(); +} + +void +PatchBox::event_select_all() +{ + if (_view) + _view->canvas()->select_all(); +} + +void +PatchBox::event_close() +{ + if (_window) { + _app->window_factory()->remove_patch_window(_window); + } +} + +void +PatchBox::event_quit() +{ + _app->quit(_window); +} + +void +PatchBox::event_zoom_in() +{ + _view->canvas()->set_font_size(_view->canvas()->get_font_size() + 1.0); +} + +void +PatchBox::event_zoom_out() +{ + _view->canvas()->set_font_size(_view->canvas()->get_font_size() - 1.0); +} + +void +PatchBox::event_zoom_normal() +{ + _view->canvas()->set_zoom_and_font_size(1.0, _view->canvas()->get_default_font_size()); +} + +void +PatchBox::event_arrange() +{ + _view->canvas()->arrange(false); +} + +void +PatchBox::event_fullscreen_toggled() +{ + // FIXME: ugh, use GTK signals to track state and know for sure + static bool is_fullscreen = false; + + if (_window) { + if (!is_fullscreen) { + _window->fullscreen(); + is_fullscreen = true; + } else { + _window->unfullscreen(); + is_fullscreen = false; + } + } +} + +void +PatchBox::event_status_bar_toggled() +{ + if (_menu_show_status_bar->get_active()) + _status_bar->show(); + else + _status_bar->hide(); +} + +void +PatchBox::event_human_names_toggled() +{ + _view->canvas()->show_human_names(_menu_human_names->get_active()); + if (_menu_human_names->get_active()) + _app->configuration()->set_name_style(Configuration::HUMAN); + else + _app->configuration()->set_name_style(Configuration::PATH); +} + +void +PatchBox::event_port_names_toggled() +{ + if (_menu_show_port_names->get_active()) { + _view->canvas()->set_direction(GANV_DIRECTION_RIGHT); + _view->canvas()->show_port_names(true); + } else { + _view->canvas()->set_direction(GANV_DIRECTION_DOWN); + _view->canvas()->show_port_names(false); + } +} + +} // namespace GUI +} // namespace Ingen diff --git a/src/gui/PatchBox.hpp b/src/gui/PatchBox.hpp new file mode 100644 index 00000000..bd275cbb --- /dev/null +++ b/src/gui/PatchBox.hpp @@ -0,0 +1,170 @@ +/* This file is part of Ingen. + * Copyright 2007-2012 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU 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 INGEN_GUI_PATCH_BOX_HPP +#define INGEN_GUI_PATCH_BOX_HPP + +#include <gtkmm.h> + +#include "raul/SharedPtr.hpp" + +#include "Window.hpp" + +namespace Raul { class Atom; class Path; } + +namespace Ingen { + +namespace Client { + class PatchModel; + class PortModel; + class ObjectModel; +} +using namespace Ingen::Client; + +namespace GUI { + +class BreadCrumbs; +class LoadPatchBox; +class LoadPluginWindow; +class NewSubpatchWindow; +class NodeControlWindow; +class PatchDescriptionWindow; +class PatchView; +class PatchWindow; +class SubpatchModule; + +/** A window for a patch. + * + * \ingroup GUI + */ +class PatchBox : public Gtk::VBox +{ +public: + PatchBox(BaseObjectType* cobject, + const Glib::RefPtr<Gtk::Builder>& xml); + ~PatchBox(); + + void init_box(App& app); + void set_patch(SharedPtr<const PatchModel> pc, SharedPtr<PatchView> view); + void set_window(PatchWindow* win) { _window = win; } + + void show_documentation(const std::string& doc, bool html); + void hide_documentation(); + + SharedPtr<const PatchModel> patch() const { return _patch; } + SharedPtr<PatchView> view() const { return _view; } + + void show_port_status(const PortModel* model, const Raul::Atom& value); + + void set_patch_from_path(const Raul::Path& path, SharedPtr<PatchView> view); + + void object_entered(const ObjectModel* model); + void object_left(const ObjectModel* model); + +private: + void patch_port_added(SharedPtr<const PortModel> port); + void patch_port_removed(SharedPtr<const PortModel> port); + void show_status(const ObjectModel* model); + void editable_changed(bool editable); + + int message_dialog(const Glib::ustring& message, + const Glib::ustring& secondary_text, + Gtk::MessageType type, + Gtk::ButtonsType buttons); + + void event_import(); + void event_save(); + void event_save_as(); + void event_draw(); + void event_edit_controls(); + void event_copy(); + void event_paste(); + void event_delete(); + void event_select_all(); + void event_close(); + void event_quit(); + void event_fullscreen_toggled(); + void event_status_bar_toggled(); + void event_human_names_toggled(); + void event_port_names_toggled(); + void event_zoom_in(); + void event_zoom_out(); + void event_zoom_normal(); + void event_arrange(); + void event_show_properties(); + void event_show_controls(); + void event_show_engine(); + void event_clipboard_changed(GdkEventOwnerChange* ev); + + App* _app; + SharedPtr<const PatchModel> _patch; + SharedPtr<PatchView> _view; + PatchWindow* _window; + + sigc::connection new_port_connection; + sigc::connection removed_port_connection; + sigc::connection edit_mode_connection; + + Gtk::MenuItem* _menu_import; + Gtk::MenuItem* _menu_save; + Gtk::MenuItem* _menu_save_as; + Gtk::MenuItem* _menu_draw; + Gtk::CheckMenuItem* _menu_edit_controls; + Gtk::MenuItem* _menu_cut; + Gtk::MenuItem* _menu_copy; + Gtk::MenuItem* _menu_paste; + Gtk::MenuItem* _menu_delete; + Gtk::MenuItem* _menu_select_all; + Gtk::MenuItem* _menu_close; + Gtk::MenuItem* _menu_quit; + Gtk::CheckMenuItem* _menu_human_names; + Gtk::CheckMenuItem* _menu_show_port_names; + Gtk::CheckMenuItem* _menu_show_status_bar; + Gtk::MenuItem* _menu_zoom_in; + Gtk::MenuItem* _menu_zoom_out; + Gtk::MenuItem* _menu_zoom_normal; + Gtk::MenuItem* _menu_fullscreen; + Gtk::MenuItem* _menu_arrange; + Gtk::MenuItem* _menu_view_engine_window; + Gtk::MenuItem* _menu_view_control_window; + Gtk::MenuItem* _menu_view_patch_properties; + Gtk::MenuItem* _menu_view_messages_window; + Gtk::MenuItem* _menu_view_patch_tree_window; + Gtk::MenuItem* _menu_help_about; + + Gtk::VBox* _vbox; + Gtk::Alignment* _alignment; + BreadCrumbs* _breadcrumbs; + Gtk::Statusbar* _status_bar; + + Gtk::HPaned* _doc_paned; + Gtk::ScrolledWindow* _doc_scrolledwindow; + + sigc::connection _entered_connection; + sigc::connection _left_connection; + + /** Invisible bin used to store breadcrumbs when not shown by a view */ + Gtk::Alignment _breadcrumb_bin; + + bool _has_shown_documentation; + bool _enable_signal; +}; + +} // namespace GUI +} // namespace Ingen + +#endif // INGEN_GUI_PATCH_BOX_HPP diff --git a/src/gui/PatchView.hpp b/src/gui/PatchView.hpp index 8d7d78a7..f6306a1a 100644 --- a/src/gui/PatchView.hpp +++ b/src/gui/PatchView.hpp @@ -23,6 +23,7 @@ #include <gtkmm.h> #include "raul/SharedPtr.hpp" +#include "raul/URI.hpp" namespace Raul { class Atom; } namespace Ganv { class Port; class Item; } @@ -39,6 +40,7 @@ using namespace Ingen::Client; namespace GUI { +class App; class LoadPluginWindow; class NewSubpatchWindow; class NodeControlWindow; diff --git a/src/gui/PatchWindow.cpp b/src/gui/PatchWindow.cpp index c97ae89a..f555ff37 100644 --- a/src/gui/PatchWindow.cpp +++ b/src/gui/PatchWindow.cpp @@ -1,5 +1,5 @@ /* This file is part of Ingen. - * Copyright 2007-2011 David Robillard <http://drobilla.net> + * Copyright 2007-2012 David Robillard <http://drobilla.net> * * Ingen 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 @@ -15,183 +15,48 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#include <cassert> -#include <sstream> - -#include <boost/format.hpp> -#include <glib/gstdio.h> -#include <glibmm/fileutils.h> - -#include "raul/AtomRDF.hpp" - -#include "ingen/Interface.hpp" #include "ingen/client/ClientStore.hpp" #include "ingen/client/PatchModel.hpp" -#include "ingen/shared/LV2URIMap.hpp" #include "App.hpp" -#include "BreadCrumbs.hpp" -#include "Configuration.hpp" -#include "ConnectWindow.hpp" -#include "LoadPatchWindow.hpp" -#include "LoadPluginWindow.hpp" -#include "MessagesWindow.hpp" -#include "NewSubpatchWindow.hpp" -#include "NodeControlWindow.hpp" #include "PatchCanvas.hpp" -#include "PatchTreeWindow.hpp" #include "PatchView.hpp" #include "PatchWindow.hpp" -#include "ThreadedLoader.hpp" #include "WindowFactory.hpp" -#include "ingen_config.h" - -#ifdef HAVE_WEBKIT -#include <webkit/webkit.h> -#endif - -using namespace Raul; namespace Ingen { namespace GUI { -static const int STATUS_CONTEXT_ENGINE = 0; -static const int STATUS_CONTEXT_PATCH = 1; -static const int STATUS_CONTEXT_HOVER = 2; - PatchWindow::PatchWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& xml) : Window(cobject) - , _enable_signal(true) + , _box(NULL) , _position_stored(false) , _x(0) , _y(0) - , _breadcrumbs(NULL) - , _has_shown_documentation(false) { property_visible() = false; - xml->get_widget("patch_win_vbox", _vbox); - xml->get_widget("patch_win_alignment", _alignment); - xml->get_widget("patch_win_status_bar", _status_bar); - //xml->get_widget("patch_win_status_bar", _status_bar); - //xml->get_widget("patch_open_menuitem", _menu_open); - xml->get_widget("patch_import_menuitem", _menu_import); - //xml->get_widget("patch_open_into_menuitem", _menu_open_into); - xml->get_widget("patch_save_menuitem", _menu_save); - xml->get_widget("patch_save_as_menuitem", _menu_save_as); - xml->get_widget("patch_draw_menuitem", _menu_draw); - xml->get_widget("patch_edit_controls_menuitem", _menu_edit_controls); - xml->get_widget("patch_cut_menuitem", _menu_cut); - xml->get_widget("patch_copy_menuitem", _menu_copy); - xml->get_widget("patch_paste_menuitem", _menu_paste); - xml->get_widget("patch_delete_menuitem", _menu_delete); - xml->get_widget("patch_select_all_menuitem", _menu_select_all); - xml->get_widget("patch_close_menuitem", _menu_close); - xml->get_widget("patch_quit_menuitem", _menu_quit); - xml->get_widget("patch_view_control_window_menuitem", _menu_view_control_window); - xml->get_widget("patch_view_engine_window_menuitem", _menu_view_engine_window); - xml->get_widget("patch_properties_menuitem", _menu_view_patch_properties); - xml->get_widget("patch_fullscreen_menuitem", _menu_fullscreen); - xml->get_widget("patch_human_names_menuitem", _menu_human_names); - xml->get_widget("patch_show_port_names_menuitem", _menu_show_port_names); - xml->get_widget("patch_zoom_in_menuitem", _menu_zoom_in); - xml->get_widget("patch_zoom_out_menuitem", _menu_zoom_out); - xml->get_widget("patch_zoom_normal_menuitem", _menu_zoom_normal); - xml->get_widget("patch_status_bar_menuitem", _menu_show_status_bar); - xml->get_widget("patch_arrange_menuitem", _menu_arrange); - xml->get_widget("patch_view_messages_window_menuitem", _menu_view_messages_window); - xml->get_widget("patch_view_patch_tree_window_menuitem", _menu_view_patch_tree_window); - xml->get_widget("patch_help_about_menuitem", _menu_help_about); - xml->get_widget("patch_documentation_paned", _doc_paned); - xml->get_widget("patch_documentation_scrolledwindow", _doc_scrolledwindow); - - _menu_view_control_window->property_sensitive() = false; - _menu_import->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_import)); - _menu_save->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_save)); - _menu_save_as->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_save_as)); - _menu_draw->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_draw)); - _menu_edit_controls->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_edit_controls)); - _menu_copy->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_copy)); - _menu_paste->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_paste)); - _menu_delete->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_delete)); - _menu_select_all->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_select_all)); - _menu_close->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_close)); - _menu_quit->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_quit)); - _menu_fullscreen->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_fullscreen_toggled)); - _menu_human_names->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_human_names_toggled)); - _menu_show_status_bar->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_status_bar_toggled)); - _menu_show_port_names->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_port_names_toggled)); - _menu_arrange->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_arrange)); - _menu_quit->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_quit)); - _menu_zoom_in->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_zoom_in)); - _menu_zoom_out->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_zoom_out)); - _menu_zoom_normal->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_zoom_normal)); - _menu_view_engine_window->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_show_engine)); - _menu_view_control_window->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_show_controls)); - _menu_view_patch_properties->signal_activate().connect( - sigc::mem_fun(this, &PatchWindow::event_show_properties)); + xml->get_widget_derived("patch_win_vbox", _box); - Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get(); - clipboard->signal_owner_change().connect( - sigc::mem_fun(this, &PatchWindow::event_clipboard_changed)); + //set_title(_patch->path().chop_scheme() + " - Ingen"); + set_title("Ingen"); } PatchWindow::~PatchWindow() { - delete _breadcrumbs; + delete _box; } void PatchWindow::init_window(App& app) { Window::init_window(app); - - string engine_name = _app->engine()->uri().str(); - if (engine_name == "http://drobilla.net/ns/ingen#internal") { - engine_name = "internal engine"; - } - _status_bar->push(string("Connected to ") + engine_name, STATUS_CONTEXT_ENGINE); - - _menu_view_messages_window->signal_activate().connect( - sigc::mem_fun<void>(_app->messages_dialog(), &MessagesWindow::present)); - _menu_view_patch_tree_window->signal_activate().connect( - sigc::mem_fun<void>(_app->patch_tree(), &PatchTreeWindow::present)); - - _menu_help_about->signal_activate().connect(sigc::hide_return( - sigc::mem_fun(_app, &App::show_about))); - - _breadcrumbs = new BreadCrumbs(*_app); - _breadcrumbs->signal_patch_selected.connect( - sigc::mem_fun(this, &PatchWindow::set_patch_from_path)); + _box->init_box(app); } -/** Set the patch controller from a Path (for use by eg. BreadCrumbs) - */ void -PatchWindow::set_patch_from_path(const Path& path, SharedPtr<PatchView> view) +PatchWindow::set_patch_from_path(const Raul::Path& path, SharedPtr<PatchView> view) { if (view) { assert(view->patch()->path() == path); @@ -204,418 +69,6 @@ PatchWindow::set_patch_from_path(const Path& path, SharedPtr<PatchView> view) } } -/** Sets the patch controller for this window and initializes everything. - * - * If @a view is NULL, a new view will be created. - */ -void -PatchWindow::set_patch(SharedPtr<const PatchModel> patch, - SharedPtr<PatchView> view) -{ - if (!patch || patch == _patch) - return; - - _enable_signal = false; - - new_port_connection.disconnect(); - removed_port_connection.disconnect(); - edit_mode_connection.disconnect(); - _entered_connection.disconnect(); - _left_connection.disconnect(); - - _status_bar->pop(STATUS_CONTEXT_PATCH); - - _patch = patch; - _view = view; - - if (!_view) - _view = _breadcrumbs->view(patch->path()); - - if (!_view) - _view = PatchView::create(*_app, patch); - - assert(_view); - - // Add view to our alignment - if (_view->get_parent()) - _view->get_parent()->remove(*_view.get()); - - _alignment->remove(); - _alignment->add(*_view.get()); - - if (_breadcrumbs->get_parent()) - _breadcrumbs->get_parent()->remove(*_breadcrumbs); - - _view->breadcrumb_container()->remove(); - _view->breadcrumb_container()->add(*_breadcrumbs); - _view->breadcrumb_container()->show(); - - _breadcrumbs->build(patch->path(), _view); - _breadcrumbs->show(); - - _menu_view_control_window->property_sensitive() = false; - - for (NodeModel::Ports::const_iterator p = patch->ports().begin(); - p != patch->ports().end(); ++p) { - if (_app->can_control(p->get())) { - _menu_view_control_window->property_sensitive() = true; - break; - } - } - - set_title(_patch->path().chop_scheme() + " - Ingen"); - - new_port_connection = patch->signal_new_port().connect( - sigc::mem_fun(this, &PatchWindow::patch_port_added)); - removed_port_connection = patch->signal_removed_port().connect( - sigc::mem_fun(this, &PatchWindow::patch_port_removed)); - removed_port_connection = patch->signal_editable().connect( - sigc::mem_fun(this, &PatchWindow::editable_changed)); - - show(); - _alignment->show_all(); - - _view->signal_object_entered.connect( - sigc::mem_fun(this, &PatchWindow::object_entered)); - _view->signal_object_left.connect( - sigc::mem_fun(this, &PatchWindow::object_left)); - - _enable_signal = true; -} - -void -PatchWindow::patch_port_added(SharedPtr<const PortModel> port) -{ - if (port->is_input() && _app->can_control(port.get())) { - _menu_view_control_window->property_sensitive() = true; - } -} - -void -PatchWindow::patch_port_removed(SharedPtr<const PortModel> port) -{ - if (!(port->is_input() && _app->can_control(port.get()))) - return; - - for (NodeModel::Ports::const_iterator i = _patch->ports().begin(); - i != _patch->ports().end(); ++i) { - if ((*i)->is_input() && _app->can_control(i->get())) { - _menu_view_control_window->property_sensitive() = true; - return; - } - } - - _menu_view_control_window->property_sensitive() = false; -} - -void -PatchWindow::show_documentation(const std::string& doc, bool html) -{ -#ifdef HAVE_WEBKIT - WebKitWebView* view = WEBKIT_WEB_VIEW(webkit_web_view_new()); - webkit_web_view_load_html_string(view, doc.c_str(), ""); - _doc_scrolledwindow->add(*Gtk::manage(Glib::wrap(GTK_WIDGET(view)))); - _doc_scrolledwindow->show_all(); -#else - Gtk::TextView* view = Gtk::manage(new Gtk::TextView()); - view->get_buffer()->set_text(doc); - _doc_scrolledwindow->add(*view); - _doc_scrolledwindow->show_all(); -#endif - if (!_has_shown_documentation) { - int width, height; - get_size(width, height); - _doc_paned->set_position(width / 1.61803399); - } - _has_shown_documentation = true; -} - -void -PatchWindow::hide_documentation() -{ - _doc_scrolledwindow->remove(); - _doc_scrolledwindow->hide(); -} - -void -PatchWindow::show_status(const ObjectModel* model) -{ - std::stringstream msg; - msg << model->path().chop_scheme(); - - const PortModel* port = 0; - const NodeModel* node = 0; - - if ((port = dynamic_cast<const PortModel*>(model))) { - show_port_status(port, port->value()); - - } else if ((node = dynamic_cast<const NodeModel*>(model))) { - const PluginModel* plugin = dynamic_cast<const PluginModel*>(node->plugin()); - if (plugin) - msg << ((boost::format(" (%1%)") % plugin->human_name()).str()); - _status_bar->push(msg.str(), STATUS_CONTEXT_HOVER); - } -} - -void -PatchWindow::show_port_status(const PortModel* port, const Raul::Atom& value) -{ - std::stringstream msg; - msg << port->path().chop_scheme(); - - const NodeModel* parent = dynamic_cast<const NodeModel*>(port->parent().get()); - if (parent) { - const PluginModel* plugin = dynamic_cast<const PluginModel*>(parent->plugin()); - if (plugin) { - const string& human_name = plugin->port_human_name(port->index()); - if (!human_name.empty()) - msg << " (" << human_name << ")"; - } - } - - if (value.is_valid()) { - msg << " = " << value; - } - - _status_bar->pop(STATUS_CONTEXT_HOVER); - _status_bar->push(msg.str(), STATUS_CONTEXT_HOVER); -} - -void -PatchWindow::object_entered(const ObjectModel* model) -{ - show_status(model); -} - -void -PatchWindow::object_left(const ObjectModel* model) -{ - _status_bar->pop(STATUS_CONTEXT_PATCH); - _status_bar->pop(STATUS_CONTEXT_HOVER); -} - -void -PatchWindow::editable_changed(bool editable) -{ - _menu_edit_controls->set_active(editable); -} - -void -PatchWindow::event_show_engine() -{ - if (_patch) - _app->connect_window()->show(); -} - -void -PatchWindow::event_clipboard_changed(GdkEventOwnerChange* ev) -{ - Glib::RefPtr<Gtk::Clipboard> clipboard = Gtk::Clipboard::get(); - _menu_paste->set_sensitive(clipboard->wait_is_text_available()); -} - -void -PatchWindow::event_show_controls() -{ - _app->window_factory()->present_controls(_patch); -} - -void -PatchWindow::event_show_properties() -{ - _app->window_factory()->present_properties(_patch); -} - -void -PatchWindow::event_import() -{ - _app->window_factory()->present_load_patch(_patch); -} - -void -PatchWindow::event_save() -{ - const Raul::Atom& document = _patch->get_property(_app->uris().ingen_document); - if (!document.is_valid() || document.type() != Raul::Atom::URI) { - event_save_as(); - } else { - _app->loader()->save_patch(_patch, document.get_uri()); - _status_bar->push( - (boost::format("Saved %1% to %2%") % _patch->path().chop_scheme() - % document.get_uri()).str(), - STATUS_CONTEXT_PATCH); - } -} - -void -PatchWindow::event_save_as() -{ - const Shared::URIs& uris = _app->uris(); - while (true) { - Gtk::FileChooserDialog dialog(*this, "Save Patch", Gtk::FILE_CHOOSER_ACTION_SAVE); - - dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); - Gtk::Button* save_button = dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); - save_button->property_has_default() = true; - - Gtk::FileFilter filt; - filt.add_pattern("*.ingen"); - filt.set_name("Ingen bundles"); - dialog.set_filter(filt); - - // Set current folder to most sensible default - const Raul::Atom& document = _patch->get_property(uris.ingen_document); - if (document.type() == Raul::Atom::URI) - dialog.set_uri(document.get_uri()); - else if (_app->configuration()->patch_folder().length() > 0) - dialog.set_current_folder(_app->configuration()->patch_folder()); - - if (dialog.run() != Gtk::RESPONSE_OK) - break; - - std::string filename = dialog.get_filename(); - std::string basename = Glib::path_get_basename(filename); - - if (basename.find('.') == string::npos) { - filename += ".ingen"; - basename += ".ingen"; - } else if (filename.substr(filename.length() - 10) != ".ingen") { - Gtk::MessageDialog error_dialog(*this, -"<b>" "Ingen patches must be saved to Ingen bundles (*.ingen)." "</b>", - true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); - error_dialog.run(); - continue; - } - - const std::string symbol(basename.substr(0, basename.find('.'))); - - if (!Symbol::is_valid(symbol)) { - Gtk::MessageDialog error_dialog(*this, - "<b>" "Ingen bundle names must be valid symbols." "</b>", - true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); - error_dialog.set_secondary_text( -"All characters must be _, a-z, A-Z, or 0-9, but the first may not be 0-9."); - error_dialog.run(); - continue; - } - - //_patch->set_property(uris.lv2_symbol, Atom(symbol.c_str())); - - bool confirm = true; - if (Glib::file_test(filename, Glib::FILE_TEST_IS_DIR)) { - if (Glib::file_test(Glib::build_filename(filename, "manifest.ttl"), - Glib::FILE_TEST_EXISTS)) { - Gtk::MessageDialog confirm_dialog(*this, (boost::format("<b>" - "A bundle named \"%1%\" already exists. Replace it?" - "</b>") % basename).str(), - true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); - confirm = (confirm_dialog.run() == Gtk::RESPONSE_YES); - } else { - Gtk::MessageDialog confirm_dialog(*this, (boost::format("<b>" -"A directory named \"%1%\" already exists, but is not an Ingen bundle. \ -Save into it anyway?" "</b>") % basename).str(), - true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); - confirm_dialog.set_secondary_text( -"This will create at least 2 .ttl files in this directory, and possibly several \ -more files and/or directories, recursively. Existing files will be overwritten."); - confirm = (confirm_dialog.run() == Gtk::RESPONSE_YES); - } - } else if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) { - Gtk::MessageDialog confirm_dialog(*this, (boost::format("<b>" -"A file named \"%1%\" already exists. Replace it with an Ingen bundle?" "</b>") % basename).str(), - true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); - confirm = (confirm_dialog.run() == Gtk::RESPONSE_YES); - if (confirm) - ::g_remove(filename.c_str()); - } - - if (confirm) { - const Glib::ustring uri = Glib::filename_to_uri(filename); - _app->loader()->save_patch(_patch, uri); - const_cast<PatchModel*>(_patch.get())->set_property( - uris.ingen_document, - _app->forge().alloc(Atom::URI, uri.c_str()), - Resource::EXTERNAL); - _status_bar->push( - (boost::format("Saved %1% to %2%") % _patch->path().chop_scheme() - % filename).str(), - STATUS_CONTEXT_PATCH); - } - - _app->configuration()->set_patch_folder(dialog.get_current_folder()); - break; - } -} - -void -PatchWindow::event_draw() -{ - Gtk::FileChooserDialog dialog(*this, "Draw to DOT", Gtk::FILE_CHOOSER_ACTION_SAVE); - - dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); - Gtk::Button* save_button = dialog.add_button(Gtk::Stock::SAVE, Gtk::RESPONSE_OK); - save_button->property_has_default() = true; - - int result = dialog.run(); - - if (result == Gtk::RESPONSE_OK) { - string filename = dialog.get_filename(); - if (filename.find(".") == string::npos) - filename += ".dot"; - - bool confirm = true; - if (Glib::file_test(filename, Glib::FILE_TEST_EXISTS)) { - const string msg = string("File exists!\n") - + "Are you sure you want to overwrite " + filename + "?"; - Gtk::MessageDialog confirm_dialog(*this, - msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); - confirm = (confirm_dialog.run() == Gtk::RESPONSE_YES); - } - - if (confirm) { - _view->canvas()->render_to_dot(filename); - _status_bar->push( - (boost::format("Rendered %1% to %2%") % _patch->path() % filename).str(), - STATUS_CONTEXT_PATCH); - } - } -} - -void -PatchWindow::event_edit_controls() -{ - if (_view) - _view->set_editable(_menu_edit_controls->get_active()); -} - -void -PatchWindow::event_copy() -{ - if (_view) - _view->canvas()->copy_selection(); -} - -void -PatchWindow::event_paste() -{ - if (_view) - _view->canvas()->paste(); -} - -void -PatchWindow::event_delete() -{ - if (_view) - _view->canvas()->destroy_selection(); -} - -void -PatchWindow::event_select_all() -{ - if (_view) - _view->canvas()->select_all(); -} - void PatchWindow::on_show() { @@ -637,94 +90,12 @@ bool PatchWindow::on_event(GdkEvent* event) { if ((event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE) - && _view->canvas()->on_event(event)) { + && box()->view()->canvas()->on_event(event)) { return true; } else { return Gtk::Window::on_event(event); } } -void -PatchWindow::event_close() -{ - _app->window_factory()->remove_patch_window(this); -} - -void -PatchWindow::event_quit() -{ - _app->quit(*this); -} - -void -PatchWindow::event_zoom_in() -{ - _view->canvas()->set_font_size(_view->canvas()->get_font_size() + 1.0); -} - -void -PatchWindow::event_zoom_out() -{ - _view->canvas()->set_font_size(_view->canvas()->get_font_size() - 1.0); -} - -void -PatchWindow::event_zoom_normal() -{ - _view->canvas()->set_zoom_and_font_size(1.0, _view->canvas()->get_default_font_size()); -} - -void -PatchWindow::event_arrange() -{ - _view->canvas()->arrange(false); -} - -void -PatchWindow::event_fullscreen_toggled() -{ - // FIXME: ugh, use GTK signals to track state and know for sure - static bool is_fullscreen = false; - - if (!is_fullscreen) { - fullscreen(); - is_fullscreen = true; - } else { - unfullscreen(); - is_fullscreen = false; - } -} - -void -PatchWindow::event_status_bar_toggled() -{ - if (_menu_show_status_bar->get_active()) - _status_bar->show(); - else - _status_bar->hide(); -} - -void -PatchWindow::event_human_names_toggled() -{ - _view->canvas()->show_human_names(_menu_human_names->get_active()); - if (_menu_human_names->get_active()) - _app->configuration()->set_name_style(Configuration::HUMAN); - else - _app->configuration()->set_name_style(Configuration::PATH); -} - -void -PatchWindow::event_port_names_toggled() -{ - if (_menu_show_port_names->get_active()) { - _view->canvas()->set_direction(GANV_DIRECTION_RIGHT); - _view->canvas()->show_port_names(true); - } else { - _view->canvas()->set_direction(GANV_DIRECTION_DOWN); - _view->canvas()->show_port_names(false); - } -} - } // namespace GUI } // namespace Ingen diff --git a/src/gui/PatchWindow.hpp b/src/gui/PatchWindow.hpp index fcb0e3f7..b1407f1e 100644 --- a/src/gui/PatchWindow.hpp +++ b/src/gui/PatchWindow.hpp @@ -15,37 +15,25 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef INGEN_GUI_PATCHWINDOW_HPP -#define INGEN_GUI_PATCHWINDOW_HPP +#ifndef INGEN_GUI_PATCH_WINDOW_HPP +#define INGEN_GUI_PATCH_WINDOW_HPP #include <gtkmm.h> #include "raul/SharedPtr.hpp" +#include "PatchBox.hpp" #include "Window.hpp" -namespace Raul { class Atom; class Path; } - namespace Ingen { namespace Client { class PatchModel; - class PortModel; - class ObjectModel; } using namespace Ingen::Client; namespace GUI { -class BreadCrumbs; -class LoadPatchWindow; -class LoadPluginWindow; -class NewSubpatchWindow; -class NodeControlWindow; -class PatchDescriptionWindow; -class PatchView; -class SubpatchModule; - /** A window for a patch. * * \ingroup GUI @@ -55,116 +43,41 @@ class PatchWindow : public Window public: PatchWindow(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& xml); + ~PatchWindow(); void init_window(App& app); - void set_patch_from_path(const Raul::Path& path, SharedPtr<PatchView> view); - void set_patch(SharedPtr<const PatchModel> pc, SharedPtr<PatchView> view); - void show_documentation(const std::string& doc, bool html); - void hide_documentation(); + SharedPtr<const PatchModel> patch() const { return _box->patch(); } + PatchBox* box() const { return _box; } - SharedPtr<const PatchModel> patch() const { return _patch; } + void set_patch_from_path(const Raul::Path& path, SharedPtr<PatchView> view); - Gtk::MenuItem* menu_view_control_window() { return _menu_view_control_window; } + void show_documentation(const std::string& doc, bool html) { + _box->show_documentation(doc, html); + } - void show_port_status(const PortModel* model, const Raul::Atom& value); + void hide_documentation() { + _box->hide_documentation(); + } - void object_entered(const ObjectModel* model); - void object_left(const ObjectModel* model); + void show_port_status(const PortModel* model, const Raul::Atom& value) { + _box->show_port_status(model, value); + } protected: - void on_show(); - void on_hide(); bool on_event(GdkEvent* event); + void on_hide(); + void on_show(); private: - void patch_port_added(SharedPtr<const PortModel> port); - void patch_port_removed(SharedPtr<const PortModel> port); - void show_status(const ObjectModel* model); - void editable_changed(bool editable); - - void event_import(); - void event_save(); - void event_save_as(); - void event_draw(); - void event_edit_controls(); - void event_copy(); - void event_paste(); - void event_delete(); - void event_select_all(); - void event_close(); - void event_quit(); - void event_fullscreen_toggled(); - void event_status_bar_toggled(); - void event_human_names_toggled(); - void event_port_names_toggled(); - void event_zoom_in(); - void event_zoom_out(); - void event_zoom_normal(); - void event_arrange(); - void event_show_properties(); - void event_show_controls(); - void event_show_engine(); - void event_clipboard_changed(GdkEventOwnerChange* ev); - - SharedPtr<const PatchModel> _patch; - SharedPtr<PatchView> _view; - - sigc::connection new_port_connection; - sigc::connection removed_port_connection; - sigc::connection edit_mode_connection; - - bool _enable_signal; - bool _position_stored; - int _x; - int _y; - - Gtk::MenuItem* _menu_import; - Gtk::MenuItem* _menu_save; - Gtk::MenuItem* _menu_save_as; - Gtk::MenuItem* _menu_draw; - Gtk::CheckMenuItem* _menu_edit_controls; - Gtk::MenuItem* _menu_cut; - Gtk::MenuItem* _menu_copy; - Gtk::MenuItem* _menu_paste; - Gtk::MenuItem* _menu_delete; - Gtk::MenuItem* _menu_select_all; - Gtk::MenuItem* _menu_close; - Gtk::MenuItem* _menu_quit; - Gtk::CheckMenuItem* _menu_human_names; - Gtk::CheckMenuItem* _menu_show_port_names; - Gtk::CheckMenuItem* _menu_show_status_bar; - Gtk::MenuItem* _menu_zoom_in; - Gtk::MenuItem* _menu_zoom_out; - Gtk::MenuItem* _menu_zoom_normal; - Gtk::MenuItem* _menu_fullscreen; - Gtk::MenuItem* _menu_arrange; - Gtk::MenuItem* _menu_view_engine_window; - Gtk::MenuItem* _menu_view_control_window; - Gtk::MenuItem* _menu_view_patch_properties; - Gtk::MenuItem* _menu_view_messages_window; - Gtk::MenuItem* _menu_view_patch_tree_window; - Gtk::MenuItem* _menu_help_about; - - Gtk::VBox* _vbox; - Gtk::Alignment* _alignment; - BreadCrumbs* _breadcrumbs; - Gtk::Statusbar* _status_bar; - - Gtk::HPaned* _doc_paned; - Gtk::ScrolledWindow* _doc_scrolledwindow; - - sigc::connection _entered_connection; - sigc::connection _left_connection; - - /** Invisible bin used to store breadcrumbs when not shown by a view */ - Gtk::Alignment _breadcrumb_bin; - - bool _has_shown_documentation; + PatchBox* _box; + bool _position_stored; + int _x; + int _y; }; } // namespace GUI } // namespace Ingen -#endif // INGEN_GUI_PATCHWINDOW_HPP +#endif // INGEN_GUI_PATCH_WINDOW_HPP diff --git a/src/gui/Port.cpp b/src/gui/Port.cpp index 7bf3412d..aa3786e3 100644 --- a/src/gui/Port.cpp +++ b/src/gui/Port.cpp @@ -154,9 +154,9 @@ Port::on_value_changed(const Glib::VariantBase& value) world->uris()->ingen_value, _app.forge().make(fval)); - PatchWindow* pw = get_patch_window(); - if (pw) { - pw->show_port_status(model().get(), _app.forge().make(fval)); + PatchBox* box = get_patch_box(); + if (box) { + box->show_port_status(model().get(), _app.forge().make(fval)); } } @@ -171,18 +171,18 @@ Port::value_changed(const Atom& value) bool Port::on_event(GdkEvent* ev) { - PatchWindow* win = NULL; + PatchBox* box = NULL; switch (ev->type) { case GDK_ENTER_NOTIFY: - win = get_patch_window(); - if (win) { - win->object_entered(model().get()); + box = get_patch_box(); + if (box) { + box->object_entered(model().get()); } break; case GDK_LEAVE_NOTIFY: - win = get_patch_window(); - if (win) { - win->object_left(model().get()); + box = get_patch_box(); + if (box) { + box->object_left(model().get()); } break; case GDK_BUTTON_PRESS: @@ -263,15 +263,15 @@ Port::activity(const Raul::Atom& value) } } -PatchWindow* -Port::get_patch_window() const +PatchBox* +Port::get_patch_box() const { SharedPtr<const PatchModel> patch = PtrCast<const PatchModel>(model()->parent()); if (!patch) { patch = PtrCast<const PatchModel>(model()->parent()->parent()); } - return _app.window_factory()->patch_window(patch); + return _app.window_factory()->patch_box(patch); } void diff --git a/src/gui/Port.hpp b/src/gui/Port.hpp index 7eef8c56..aaa0ffc8 100644 --- a/src/gui/Port.hpp +++ b/src/gui/Port.hpp @@ -34,7 +34,7 @@ using Ingen::Client::PortModel; namespace GUI { class App; -class PatchWindow; +class PatchBox; /** A Port on an Module. * @@ -71,7 +71,7 @@ private: const std::string& name, bool flip = false); - PatchWindow* get_patch_window() const; + PatchBox* get_patch_box() const; void property_changed(const Raul::URI& key, const Raul::Atom& value); void moved(); diff --git a/src/gui/WindowFactory.cpp b/src/gui/WindowFactory.cpp index c60d493f..811e697e 100644 --- a/src/gui/WindowFactory.cpp +++ b/src/gui/WindowFactory.cpp @@ -37,6 +37,7 @@ namespace GUI { WindowFactory::WindowFactory(App& app) : _app(app) + , _main_box(NULL) , _load_plugin_win(NULL) , _load_patch_win(NULL) , _new_subpatch_win(NULL) @@ -98,6 +99,17 @@ WindowFactory::num_open_patch_windows() return ret; } +PatchBox* +WindowFactory::patch_box(SharedPtr<const PatchModel> patch) +{ + PatchWindow* window = patch_window(patch); + if (window) { + return window->box(); + } else { + return _main_box; + } +} + PatchWindow* WindowFactory::patch_window(SharedPtr<const PatchModel> patch) { @@ -147,7 +159,7 @@ WindowFactory::present_patch(SharedPtr<const PatchModel> patch, w = _patch_windows.find(preferred->patch()->path()); assert((*w).second == preferred); - preferred->set_patch(patch, view); + preferred->box()->set_patch(patch, view); _patch_windows.erase(w); _patch_windows[patch->path()] = preferred; preferred->present(); @@ -168,7 +180,7 @@ WindowFactory::new_patch_window(SharedPtr<const PatchModel> patch, WidgetFactory::get_widget_derived("patch_win", win); win->init_window(_app); - win->set_patch(patch, view); + win->box()->set_patch(patch, view); _patch_windows[patch->path()] = win; win->signal_delete_event().connect(sigc::bind<0>( @@ -181,7 +193,7 @@ bool WindowFactory::remove_patch_window(PatchWindow* win, GdkEventAny* ignored) { if (_patch_windows.size() <= 1) - return !_app.quit(*win); + return !_app.quit(win); PatchWindowMap::iterator w = _patch_windows.find(win->patch()->path()); diff --git a/src/gui/WindowFactory.hpp b/src/gui/WindowFactory.hpp index d520740f..7bee1fd4 100644 --- a/src/gui/WindowFactory.hpp +++ b/src/gui/WindowFactory.hpp @@ -44,6 +44,7 @@ class LoadPluginWindow; class NewSubpatchWindow; class NodeControlWindow; class PropertiesWindow; +class PatchBox; class PatchView; class PatchWindow; class RenameWindow; @@ -62,13 +63,14 @@ public: size_t num_open_patch_windows(); + PatchBox* patch_box(SharedPtr<const PatchModel> patch); PatchWindow* patch_window(SharedPtr<const PatchModel> patch); PatchWindow* parent_patch_window(SharedPtr<const NodeModel> node); NodeControlWindow* control_window(SharedPtr<const NodeModel> node); void present_patch(SharedPtr<const PatchModel> model, - PatchWindow* preferred = NULL, - SharedPtr<PatchView> view = SharedPtr<PatchView>()); + PatchWindow* preferred = NULL, + SharedPtr<PatchView> view = SharedPtr<PatchView>()); void present_controls(SharedPtr<const NodeModel> node); @@ -83,6 +85,8 @@ public: bool remove_patch_window(PatchWindow* win, GdkEventAny* ignored = NULL); + void set_main_box(PatchBox* box) { _main_box = box; } + void clear(); private: @@ -97,6 +101,7 @@ private: GdkEventAny* ignored); App& _app; + PatchBox* _main_box; PatchWindowMap _patch_windows; ControlWindowMap _control_windows; LoadPluginWindow* _load_plugin_win; diff --git a/src/gui/ingen_gui_lv2.cpp b/src/gui/ingen_gui_lv2.cpp new file mode 100644 index 00000000..9898005e --- /dev/null +++ b/src/gui/ingen_gui_lv2.cpp @@ -0,0 +1,179 @@ +/* This file is part of Ingen. + * Copyright 2012 David Robillard <http://drobilla.net> + * + * Ingen 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. + * + * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY + * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU 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 "ingen/client/ClientStore.hpp" +#include "ingen/client/PatchModel.hpp" +#include "ingen/client/SigClientInterface.hpp" +#include "ingen/shared/AtomSink.hpp" +#include "ingen/shared/AtomWriter.hpp" +#include "ingen/shared/Configuration.hpp" +#include "ingen/shared/World.hpp" +#include "ingen/shared/runtime_paths.hpp" +#include "lv2/lv2plug.in/ns/extensions/ui/ui.h" + +#include "App.hpp" +#include "PatchView.hpp" + +#define INGEN_LV2_UI_URI "http://drobilla.net/ns/ingen#ui" + +/** A sink that writes atoms to a port via the UI extension. */ +struct IngenLV2AtomSink : public Ingen::Shared::AtomSink { + IngenLV2AtomSink(Ingen::Shared::URIs& uris, + LV2UI_Write_Function ui_write, + LV2UI_Controller ui_controller) + : _uris(uris) + , _ui_write(ui_write) + , _ui_controller(ui_controller) + {} + + void write(const LV2_Atom* atom) { + _ui_write(_ui_controller, + 0, + lv2_atom_total_size(atom), + _uris.atom_eventTransfer, + atom); + } + + Ingen::Shared::URIs& _uris; + LV2UI_Write_Function _ui_write; + LV2UI_Controller _ui_controller; +}; + +struct IngenLV2UI { + IngenLV2UI() + : conf(&forge) + , sink(NULL) + { + } + + int argc; + char** argv; + Raul::Forge forge; + Ingen::Shared::Configuration conf; + Ingen::Shared::World* world; + IngenLV2AtomSink* sink; + SharedPtr<Ingen::GUI::App> app; + SharedPtr<Ingen::GUI::PatchView> view; + SharedPtr<Ingen::Interface> engine; + SharedPtr<Ingen::Client::SigClientInterface> client; +}; + +static LV2UI_Handle +instantiate(const LV2UI_Descriptor* descriptor, + const char* plugin_uri, + const char* bundle_path, + LV2UI_Write_Function write_function, + LV2UI_Controller controller, + LV2UI_Widget* widget, + const LV2_Feature* const* features) +{ + Ingen::Shared::set_bundle_path(bundle_path); + + IngenLV2UI* ui = new IngenLV2UI(); + + LV2_URID_Map* map = NULL; + LV2_URID_Unmap* unmap = NULL; + for (int i = 0; features[i]; ++i) { + if (!strcmp(features[i]->URI, LV2_URID_URI "#map")) { + map = (LV2_URID_Map*)features[i]->data; + } else if (!strcmp(features[i]->URI, LV2_URID_URI "#unmap")) { + unmap = (LV2_URID_Unmap*)features[i]->data; + } + } + + ui->world = new Ingen::Shared::World( + &ui->conf, ui->argc, ui->argv, map, unmap); + + if (!ui->world->load_module("client")) { + delete ui; + return NULL; + } + + ui->sink = new IngenLV2AtomSink( + *ui->world->uris().get(), write_function, controller); + + // Set up an engine interface that writes LV2 atoms + ui->engine = SharedPtr<Ingen::Interface>( + new Ingen::Shared::AtomWriter(*ui->world->lv2_uri_map().get(), + *ui->world->uris().get(), + *ui->sink)); + + ui->world->set_engine(ui->engine); + + // Create App and client + ui->app = Ingen::GUI::App::create(ui->world); + ui->client = SharedPtr<Ingen::Client::SigClientInterface>( + new Ingen::Client::SigClientInterface()); + ui->app->attach(ui->client); + + // Create empty root patch model + Ingen::Resource::Properties props; + props.insert(std::make_pair(ui->app->uris().rdf_type, + ui->app->uris().ingen_Patch)); + ui->app->store()->put("path:/", props); + + // Create a PatchView for the root and set as the UI widget + SharedPtr<const Ingen::Client::PatchModel> root = PtrCast<const Ingen::Client::PatchModel>( + ui->app->store()->object("path:/")); + ui->view = Ingen::GUI::PatchView::create(*ui->app, root); + ui->view->unparent(); + *widget = ui->view->gobj(); + + return ui; +} + +static void +cleanup(LV2UI_Handle handle) +{ + IngenLV2UI* ui = (IngenLV2UI*)handle; + delete ui; +} + +static void +port_event(LV2UI_Handle handle, + uint32_t port_index, + uint32_t buffer_size, + uint32_t format, + const void* buffer) +{ +} + +const void* +extension_data(const char* uri) +{ + return NULL; +} + +static const LV2UI_Descriptor descriptor = { + INGEN_LV2_UI_URI, + instantiate, + cleanup, + port_event, + extension_data +}; + +LV2_SYMBOL_EXPORT +const LV2UI_Descriptor* +lv2ui_descriptor(uint32_t index) +{ + switch (index) { + case 0: + return &descriptor; + default: + return NULL; + } +} diff --git a/src/gui/wscript b/src/gui/wscript index ec6e451a..8fc5058e 100644 --- a/src/gui/wscript +++ b/src/gui/wscript @@ -42,6 +42,7 @@ def build(bld): NodeMenu.cpp NodeModule.cpp ObjectMenu.cpp + PatchBox.cpp PatchCanvas.cpp PatchPortModule.cpp PatchTreeWindow.cpp @@ -67,3 +68,13 @@ def build(bld): install_path = '${DATADIR}/ingen', chmod = Utils.O755, INGEN_VERSION = bld.env['INGEN_VERSION']) + + # LV2 UI + obj = bld(features = 'cxx cxxshlib', + source = 'ingen_gui_lv2.cpp', + includes = ['.', '../..'], + name = 'ingen_gui_lv2', + target = 'ingen_gui_lv2', + install_path = '${LV2DIR}/ingen.lv2/', + use = 'libingen_gui libingen_shared') + autowaf.use_lib(bld, obj, 'LV2_UI') diff --git a/src/server/ServerInterfaceImpl.hpp b/src/server/ServerInterfaceImpl.hpp index ef9042bf..0ba3a1b9 100644 --- a/src/server/ServerInterfaceImpl.hpp +++ b/src/server/ServerInterfaceImpl.hpp @@ -83,8 +83,6 @@ public: virtual void disconnect_all(const Raul::Path& parent_patch_path, const Raul::Path& path); - virtual void ping(); - virtual void get(const Raul::URI& uri); virtual void response(int32_t id, Status status) {} ///< N/A diff --git a/src/server/events/Get.cpp b/src/server/events/Get.cpp index 404cd981..06e30a47 100644 --- a/src/server/events/Get.cpp +++ b/src/server/events/Get.cpp @@ -69,6 +69,7 @@ Get::post_process() _engine.broadcaster()->send_plugins_to(_request_client, _plugins); } } else if (_uri == "ingen:engine") { + respond(SUCCESS); // TODO: Keep a proper RDF model of the engine if (_request_client) { Shared::URIs& uris = *_engine.world()->uris().get(); diff --git a/src/shared/AtomSink.hpp b/src/shared/AtomSink.hpp deleted file mode 100644 index 5bc5aef6..00000000 --- a/src/shared/AtomSink.hpp +++ /dev/null @@ -1,34 +0,0 @@ -/* This file is part of Ingen. - * Copyright 2012 David Robillard <http://drobilla.net> - * - * Ingen 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. - * - * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU 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 INGEN_ATOMSINK_HPP -#define INGEN_ATOMSINK_HPP - -namespace Ingen { -namespace Shared { - -class AtomSink -{ -public: - virtual void write(const LV2_Atom* msg) = 0; -}; - -} // namespace Shared -} // namespace Ingen - -#endif // INGEN_ATOMSINK_HPP - diff --git a/src/shared/AtomWriter.cpp b/src/shared/AtomWriter.cpp index a5317bfb..c86ac96f 100644 --- a/src/shared/AtomWriter.cpp +++ b/src/shared/AtomWriter.cpp @@ -15,19 +15,50 @@ * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +#include "ingen/shared/AtomSink.hpp" +#include "ingen/shared/AtomWriter.hpp" #include "raul/Path.hpp" - -#include "AtomWriter.hpp" +#include "serd/serd.h" namespace Ingen { namespace Shared { -AtomWriter::AtomWriter(LV2URIMap& map, URIs& uris) +static LV2_Atom_Forge_Ref +forge_sink(LV2_Atom_Forge_Sink_Handle handle, + const void* buf, + uint32_t size) +{ + SerdChunk* chunk = (SerdChunk*)handle; + const LV2_Atom_Forge_Ref ref = chunk->len + 1; + serd_chunk_sink(buf, size, chunk); + return ref; +} + +static LV2_Atom* +forge_deref(LV2_Atom_Forge_Sink_Handle handle, LV2_Atom_Forge_Ref ref) +{ + SerdChunk* chunk = (SerdChunk*)handle; + return (LV2_Atom*)(chunk->buf + ref - 1); +} + + +AtomWriter::AtomWriter(LV2URIMap& map, URIs& uris, AtomSink& sink) : _map(map) , _uris(uris) - , _id(-1) + , _sink(sink) + , _id(1) { + _out.buf = NULL; + _out.len = 0; lv2_atom_forge_init(&_forge, &map.urid_map_feature()->urid_map); + lv2_atom_forge_set_sink(&_forge, forge_sink, forge_deref, &_out); +} + +void +AtomWriter::finish_msg() +{ + _sink.write((LV2_Atom*)_out.buf); + _out.len = 0; } int32_t @@ -55,6 +86,11 @@ AtomWriter::put(const Raul::URI& uri, const Resource::Properties& properties, Resource::Graph ctx) { + LV2_Atom_Forge_Frame msg; + lv2_atom_forge_blank(&_forge, &msg, next_id(), _uris.patch_Put); + // ... + lv2_atom_forge_pop(&_forge, &msg); + finish_msg(); } void @@ -92,6 +128,8 @@ AtomWriter::connect(const Raul::Path& src, lv2_atom_forge_pop(&_forge, &body); lv2_atom_forge_pop(&_forge, &msg); + + finish_msg(); } void @@ -126,6 +164,7 @@ AtomWriter::get(const Raul::URI& uri) lv2_atom_forge_property_head(&_forge, _uris.patch_subject, 0); lv2_atom_forge_uri(&_forge, uri.c_str(), uri.length()); lv2_atom_forge_pop(&_forge, &msg); + finish_msg(); } void @@ -136,6 +175,7 @@ AtomWriter::response(int32_t id, Status status) lv2_atom_forge_property_head(&_forge, _uris.patch_request, 0); lv2_atom_forge_int32(&_forge, id); lv2_atom_forge_pop(&_forge, &msg); + finish_msg(); } void diff --git a/src/shared/AtomWriter.hpp b/src/shared/AtomWriter.hpp deleted file mode 100644 index 2983848c..00000000 --- a/src/shared/AtomWriter.hpp +++ /dev/null @@ -1,89 +0,0 @@ -/* This file is part of Ingen. - * Copyright 2012 David Robillard <http://drobilla.net> - * - * Ingen 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. - * - * Ingen is distributed in the hope that it will be useful, but WITHOUT ANY - * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU 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 INGEN_SHARED_ATOM_WRITER_HPP -#define INGEN_SHARED_ATOM_WRITER_HPP - -#include "ingen/Interface.hpp" -#include "ingen/shared/LV2URIMap.hpp" -#include "ingen/shared/URIs.hpp" -#include "lv2/lv2plug.in/ns/ext/atom/forge.h" - -namespace Ingen { -namespace Shared { - -/** An Interface that writes LV2 atoms. */ -class AtomWriter : public Interface -{ -public: - AtomWriter(LV2URIMap& map, URIs& uris); - ~AtomWriter() {} - - Raul::URI uri() const { return "http://drobilla.net/ns/ingen#AtomWriter"; } - - void bundle_begin(); - - void bundle_end(); - - void put(const Raul::URI& uri, - const Resource::Properties& properties, - Resource::Graph ctx = Resource::DEFAULT); - - void delta(const Raul::URI& uri, - const Resource::Properties& remove, - const Resource::Properties& add); - - void move(const Raul::Path& old_path, - const Raul::Path& new_path); - - void del(const Raul::URI& uri); - - void connect(const Raul::Path& src_port_path, - const Raul::Path& dst_port_path); - - void disconnect(const Raul::URI& src, - const Raul::URI& dst); - - void disconnect_all(const Raul::Path& parent_patch_path, - const Raul::Path& path); - - void set_property(const Raul::URI& subject, - const Raul::URI& predicate, - const Raul::Atom& value); - - void set_response_id(int32_t id); - - void get(const Raul::URI& uri); - - void response(int32_t id, Status status); - - void error(const std::string& msg); - -private: - int32_t next_id(); - - LV2URIMap& _map; - URIs& _uris; - LV2_Atom_Forge _forge; - int32_t _id; -}; - -} // namespace Shared -} // namespace Ingen - -#endif // INGEN_SHARED_ATOM_WRITER_HPP - diff --git a/src/shared/LV2Atom.cpp b/src/shared/LV2Atom.cpp index 63686bf5..ccd5f352 100644 --- a/src/shared/LV2Atom.cpp +++ b/src/shared/LV2Atom.cpp @@ -43,7 +43,7 @@ to_atom(Raul::Forge* forge, } else if (object->type == uris.atom_Bool.id) { atom = forge->make((bool)(int32_t*)(object + 1)); return true; - } else if (object->type == uris.atom_Int32.id) { + } else if (object->type == uris.atom_Int.id) { atom = forge->make((int32_t*)(object + 1)); return true; } else if (object->type == uris.atom_Float.id) { @@ -67,7 +67,7 @@ from_atom(const Shared::URIs& uris, const Raul::Atom& atom, LV2_Atom* object) *(float*)(object + 1) = atom.get_float(); break; case Raul::Atom::INT: - object->type = uris.atom_Int32.id; + object->type = uris.atom_Int.id; object->size = sizeof(int32_t); *(int32_t*)(object + 1) = atom.get_int32(); break; diff --git a/src/shared/URIs.cpp b/src/shared/URIs.cpp index 3ca8a69d..73718fab 100644 --- a/src/shared/URIs.cpp +++ b/src/shared/URIs.cpp @@ -52,7 +52,7 @@ URIs::URIs(Raul::Forge& f, LV2URIMap* map) : forge(f) , atom_Bool (map, LV2_ATOM__Bool) , atom_Float (map, LV2_ATOM__Float) - , atom_Int32 (map, LV2_ATOM__Int32) + , atom_Int (map, LV2_ATOM__Int) , atom_MessagePort (map, LV2_ATOM__MessagePort) , atom_String (map, LV2_ATOM__String) , atom_ValuePort (map, LV2_ATOM__ValuePort) |