/* This file is part of Ingen. * Copyright (C) 2007-2009 Dave Robillard * * 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 "PatchWindow.hpp" #include #include #include #include #include #include "raul/AtomRDF.hpp" #include "interface/EngineInterface.hpp" #include "client/PatchModel.hpp" #include "client/ClientStore.hpp" #include "App.hpp" #include "PatchCanvas.hpp" #include "LoadPluginWindow.hpp" #include "NewSubpatchWindow.hpp" #include "LoadPatchWindow.hpp" #include "LoadSubpatchWindow.hpp" #include "NodeControlWindow.hpp" #include "PatchPropertiesWindow.hpp" #include "Configuration.hpp" #include "MessagesWindow.hpp" #include "PatchTreeWindow.hpp" #include "BreadCrumbs.hpp" #include "ConnectWindow.hpp" #include "ThreadedLoader.hpp" #include "WindowFactory.hpp" #include "PatchView.hpp" 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& xml) : Window(cobject) , _enable_signal(true) , _position_stored(false) , _x(0) , _y(0) , _breadcrumbs(NULL) { property_visible() = false; xml->get_widget("patch_win_vbox", _vbox); xml->get_widget("patch_win_viewport", _viewport); 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_import_location_menuitem", _menu_import_location); //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_upload_menuitem", _menu_upload); 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_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); _menu_view_control_window->property_sensitive() = false; string engine_name = App::instance().engine()->uri().str(); if (engine_name == "ingen:internal") engine_name = "internal engine"; _status_bar->push(string("Connected to ") + engine_name, STATUS_CONTEXT_ENGINE); _menu_import->signal_activate().connect( sigc::mem_fun(this, &PatchWindow::event_import)); _menu_import_location->signal_activate().connect( sigc::mem_fun(this, &PatchWindow::event_import_location)); _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_upload->signal_activate().connect( sigc::mem_fun(this, &PatchWindow::event_upload)); _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_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)); _menu_view_messages_window->signal_activate().connect( sigc::mem_fun(App::instance().messages_dialog(), &MessagesWindow::present)); _menu_view_patch_tree_window->signal_activate().connect( sigc::mem_fun(App::instance().patch_tree(), &PatchTreeWindow::present)); _menu_help_about->signal_activate().connect(sigc::hide_return( sigc::mem_fun(App::instance(), &App::show_about))); _breadcrumbs = new BreadCrumbs(); _breadcrumbs->signal_patch_selected.connect(sigc::mem_fun(this, &PatchWindow::set_patch_from_path)); #ifndef HAVE_CURL _menu_upload->hide(); #endif Glib::RefPtr clipboard = Gtk::Clipboard::get(); clipboard->signal_owner_change().connect(sigc::mem_fun(this, &PatchWindow::event_clipboard_changed)); } PatchWindow::~PatchWindow() { // Prevents deletion //m_patch->claim_patch_view(); delete _breadcrumbs; } /** Set the patch controller from a Path (for use by eg. BreadCrumbs) */ void PatchWindow::set_patch_from_path(const Path& path, SharedPtr view) { if (view) { assert(view->patch()->path() == path); App::instance().window_factory()->present_patch(view->patch(), this, view); } else { SharedPtr model = PtrCast(App::instance().store()->object(path)); if (model) App::instance().window_factory()->present_patch(model, this); } } /** 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 patch, SharedPtr 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(patch); assert(_view); // Add view to our viewport if (_view->get_parent()) _view->get_parent()->remove(*_view.get()); _viewport->remove(); _viewport->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 ((*p)->type().is_control() && (*p)->is_input()) { _menu_view_control_window->property_sensitive() = true; break; } } int width, height; get_size(width, height); _view->canvas()->scroll_to( ((int)_view->canvas()->width() - width)/2, ((int)_view->canvas()->height() - height)/2); 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_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 port) { if (port->type().is_control() && port->is_input()) { _menu_view_control_window->property_sensitive() = true; } } void PatchWindow::patch_port_removed(SharedPtr port) { if (port->type().is_control() && port->is_input()) { bool found_control = false; for (NodeModel::Ports::const_iterator i = _patch->ports().begin(); i != _patch->ports().end(); ++i) { if ((*i)->type().is_control() && (*i)->is_input()) { found_control = true; break; } } _menu_view_control_window->property_sensitive() = found_control; } } void PatchWindow::show_status(ObjectModel* model) { std::stringstream msg; msg << model->path().chop_scheme(); PortModel* port = 0; NodeModel* node = 0; if ((port = dynamic_cast(model))) { show_port_status(port, port->value()); } else if ((node = dynamic_cast(model))) { PluginModel* plugin = dynamic_cast(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(PortModel* port, const Raul::Atom& value) { std::stringstream msg; msg << port->path().chop_scheme(); NodeModel* parent = dynamic_cast(port->parent().get()); if (parent) { const PluginModel* plugin = dynamic_cast(parent->plugin()); if (plugin) { const string& human_name = plugin->port_human_name(port->index()); if (human_name != "") 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(ObjectModel* model) { show_status(model); } void PatchWindow::object_left(ObjectModel* model) { _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::instance().connect_window()->show(); } void PatchWindow::event_clipboard_changed(GdkEventOwnerChange* ev) { Glib::RefPtr clipboard = Gtk::Clipboard::get(); _menu_paste->set_sensitive(clipboard->wait_is_text_available()); } void PatchWindow::event_show_controls() { App::instance().window_factory()->present_controls(_patch); } void PatchWindow::event_show_properties() { App::instance().window_factory()->present_properties(_patch); } void PatchWindow::event_import() { App::instance().window_factory()->present_load_patch(_patch); } void PatchWindow::event_import_location() { App::instance().window_factory()->present_load_remote_patch(_patch); } void PatchWindow::event_save() { const Raul::Atom& document = _patch->get_property("ingen:document"); if (!document.is_valid() || document.type() != Raul::Atom::URI) { event_save_as(); } else { App::instance().loader()->save_patch(_patch, document.get_uri()); _status_bar->push( (boost::format("Wrote %1% to %2%") % _patch->path() % document.get_uri()).str(), STATUS_CONTEXT_PATCH); } } void PatchWindow::event_save_as() { 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.ttl"); filt.set_name("Ingen patches"); filt.add_pattern("*.ingen.lv2"); filt.set_name("Ingen bundles"); dialog.set_filter(filt); // Set current folder to most sensible default const Raul::Atom& document = _patch->get_property("ingen:document"); if (document.type() == Raul::Atom::URI) dialog.set_uri(document.get_uri()); else if (App::instance().configuration()->patch_folder().length() > 0) dialog.set_current_folder(App::instance().configuration()->patch_folder()); if (dialog.run() != Gtk::RESPONSE_OK) break; string filename = dialog.get_filename(); string base = filename.substr(0, filename.find(".")); if (base.find("/") != string::npos) base = base.substr(base.find_last_of("/") + 1); if (!Symbol::is_valid(base)) { Gtk::MessageDialog error_dialog(*this, "Ingen patch file names must be valid symbols", true, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); error_dialog.set_secondary_text( "The first character must be _, a-z, or A-Z, and " "subsequent characters must be _, a-z, A-Z, or 0-9."); error_dialog.run(); continue; } const bool is_bundle = filename.find(".ingen.lv2") != string::npos; const bool is_patch = filename.find(".ingen.ttl") != string::npos; // Save a bundle by default if (!is_bundle && !is_patch) filename += ".ingen.ttl"; _patch->set_property("lv2:symbol", Atom(base.c_str())); bool confirm = true; std::fstream fin; fin.open(filename.c_str(), std::ios::in); if (fin.is_open()) { // File exists string msg = "File already exists! Are you sure you want to overwrite "; msg += filename + "?"; Gtk::MessageDialog confirm_dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true); confirm = (confirm_dialog.run() == Gtk::RESPONSE_YES); } fin.close(); if (confirm) { const Glib::ustring uri = Glib::filename_to_uri(filename); App::instance().loader()->save_patch(_patch, uri); _patch->set_property("ingen:document", Atom(Atom::URI, uri.c_str())); _status_bar->push( (boost::format("Wrote %1% to %2%") % _patch->path() % uri).str(), STATUS_CONTEXT_PATCH); } App::instance().configuration()->set_patch_folder(dialog.get_current_folder()); break; } } void PatchWindow::event_upload() { App::instance().window_factory()->present_upload_patch(_patch); } 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; std::fstream fin; fin.open(filename.c_str(), std::ios::in); if (fin.is_open()) { // File 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); } fin.close(); 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() { if (_position_stored) move(_x, _y); Gtk::Window::on_show(); } void PatchWindow::on_hide() { _position_stored = true; get_position(_x, _y); Gtk::Window::on_hide(); } bool PatchWindow::on_key_press_event(GdkEventKey* event) { bool ret = false; ret = _view->canvas()->canvas_key_event(event); if (!ret) ret = Gtk::Window::on_key_press_event(event); return ret; } bool PatchWindow::on_key_release_event(GdkEventKey* event) { bool ret = false; ret = _view->canvas()->canvas_key_event(event); if (!ret) ret = Gtk::Window::on_key_release_event(event); return ret; } void PatchWindow::event_close() { App::instance().window_factory()->remove_patch_window(this); } void PatchWindow::event_quit() { Gtk::Widget* kill_img = Gtk::manage( new Gtk::Image(Gtk::Stock::CLOSE, Gtk::ICON_SIZE_BUTTON)); Gtk::Widget* close_img = Gtk::manage( new Gtk::Image(Gtk::Stock::QUIT, Gtk::ICON_SIZE_BUTTON)); const char* msg = (App::instance().world()->local_engine) ? "This will kill the engine as well.\nAre you sure you want to quit?" : "Would you like to quit just the GUI,\nor kill the engine as well?"; Gtk::MessageDialog d(*this, msg, true, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE, true); d.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL); if (!App::instance().world()->local_engine) { Gtk::Button* b = d.add_button(Gtk::Stock::REMOVE, 2); b->set_label("_Kill Engine"); b->set_image(*kill_img); } Gtk::Button* b = d.add_button(Gtk::Stock::QUIT, 1); b->set_label("_Quit"); b->set_image(*close_img); b->grab_default(); switch (d.run()) { case 1: App::instance().quit(); break; case 2: App::instance().engine()->quit(); App::instance().quit(); break; default: break; } } 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::instance().configuration()->set_name_style(Configuration::HUMAN); else App::instance().configuration()->set_name_style(Configuration::PATH); } void PatchWindow::event_port_names_toggled() { if (_menu_show_port_names->get_active()) { _view->canvas()->set_direction(Canvas::HORIZONTAL); _view->canvas()->show_port_names(true); } else { _view->canvas()->set_direction(Canvas::VERTICAL); _view->canvas()->show_port_names(false); } } } // namespace GUI } // namespace Ingen