remove gui/jack_proxy module
[ladish.git] / gui / Patchage.cpp
blob4129b5f915f22d1301ea62bf105b43a9f88cbf3c
1 /* -*- Mode: C ; c-basic-offset: 2 -*- */
2 /*
3 * LADI Session Handler (ladish)
5 * Copyright (C) 2008, 2009 Nedko Arnaudov <nedko@arnaudov.name>
6 * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
8 **************************************************************************
9 * This file contains implementation of the application class
10 **************************************************************************
12 * LADI Session Handler is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
17 * LADI Session Handler is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with LADI Session Handler. If not, see <http://www.gnu.org/licenses/>
24 * or write to the Free Software Foundation, Inc.,
25 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
28 #include "common.h"
30 #include <string.h>
31 #include <cmath>
32 #include <sstream>
33 #include <fstream>
34 #include <libgnomecanvasmm.h>
35 #include <libglademm/xml.h>
36 #include <gtk/gtkwindow.h>
37 #include <boost/format.hpp>
39 #include "Patchage.hpp"
40 #include "lash_proxy.hpp"
41 #include "project_list.hpp"
42 #include "session.hpp"
43 #include "globals.hpp"
44 #include "a2j_proxy.hpp"
45 #include "dbus_helpers.h"
46 #include "load_projects_dialog.hpp"
47 #include "graph_canvas.h"
49 Patchage * g_app;
51 #define LOG_TO_STD
52 //#define LOG_TO_STATUS
54 graph_canvas_handle g_jack_graph_canvas;
56 /* Gtk helpers (resize combo boxes) */
58 static void
59 gtkmm_get_ink_pixel_size (Glib::RefPtr<Pango::Layout> layout,
60 int& width,
61 int& height)
63 Pango::Rectangle ink_rect = layout->get_ink_extents ();
65 width = (ink_rect.get_width() + PANGO_SCALE / 2) / PANGO_SCALE;
66 height = (ink_rect.get_height() + PANGO_SCALE / 2) / PANGO_SCALE;
69 static void
70 gtkmm_set_width_for_given_text (Gtk::Widget &w, const gchar *text,
71 gint hpadding/*, gint vpadding*/)
74 int old_width, old_height;
75 w.get_size_request(old_width, old_height);
77 int width, height;
78 w.ensure_style ();
80 gtkmm_get_ink_pixel_size (w.create_pango_layout (text), width, height);
81 w.set_size_request(width + hpadding, old_height);//height + vpadding);
84 /* end Gtk helpers */
87 #define INIT_WIDGET(x) x(g_xml, ((const char*)#x) + 1)
89 Patchage::Patchage(int argc, char** argv)
90 : _max_dsp_load(0.0)
91 , INIT_WIDGET(_about_win)
92 , INIT_WIDGET(_buffer_size_combo)
93 , INIT_WIDGET(_clear_load_but)
94 , INIT_WIDGET(_main_scrolledwin)
95 , INIT_WIDGET(_main_win)
96 , INIT_WIDGET(_main_xrun_progress)
97 , INIT_WIDGET(_main_a2j_status_label)
98 , INIT_WIDGET(_menu_file_quit)
99 , INIT_WIDGET(_menu_help_about)
100 , INIT_WIDGET(_menu_jack_start)
101 , INIT_WIDGET(_menu_jack_stop)
102 , INIT_WIDGET(_menu_a2j_start)
103 , INIT_WIDGET(_menu_a2j_stop)
104 , INIT_WIDGET(_menu_load_project)
105 , INIT_WIDGET(_menu_save_all_projects)
106 , INIT_WIDGET(_menu_close_all_projects)
107 , INIT_WIDGET(_menu_store_positions)
108 , INIT_WIDGET(_menu_view_arrange)
109 , INIT_WIDGET(_menu_view_projects)
110 , INIT_WIDGET(_menu_view_refresh)
111 , INIT_WIDGET(_menu_view_toolbar)
112 , INIT_WIDGET(_project_list_viewport)
113 , INIT_WIDGET(_sample_rate_label)
114 , INIT_WIDGET(_toolbar)
115 , INIT_WIDGET(_zoom_full_but)
116 , INIT_WIDGET(_zoom_normal_but)
118 g_app = this;
120 graph_canvas_create(1600 * 2, 1200 * 2, &g_jack_graph_canvas);
122 while (argc > 0) {
123 if (!strcmp(*argv, "--help")) {
124 std::cout << "Usage: patchage [OPTIONS]\nOptions: --no-alsa" << std::endl;
125 exit(0);
128 argv++;
129 argc--;
132 patchage_dbus_init();
134 Glib::set_application_name("Patchage");
135 _about_win->property_program_name() = "Patchage";
136 _about_win->property_logo_icon_name() = "gladish";
137 gtk_window_set_default_icon_name("gladish");
139 gtkmm_set_width_for_given_text(*_buffer_size_combo, "4096 frames", 40);
141 _main_scrolledwin->add(*Glib::wrap(canvas_get_widget(graph_canvas_get_canvas(g_jack_graph_canvas))));
143 // _canvas->scroll_to(static_cast<int>(_canvas->width()/2 - 320),
144 // static_cast<int>(_canvas->height()/2 - 240)); // FIXME: hardcoded
146 _main_scrolledwin->property_hadjustment().get_value()->set_step_increment(10);
147 _main_scrolledwin->property_vadjustment().get_value()->set_step_increment(10);
149 _main_scrolledwin->signal_scroll_event().connect(
150 sigc::mem_fun(this, &Patchage::on_scroll));
152 _buffer_size_combo->signal_changed().connect(
153 sigc::mem_fun(this, &Patchage::buffer_size_changed));
154 _clear_load_but->signal_clicked().connect(
155 sigc::mem_fun(this, &Patchage::clear_load));
156 _zoom_normal_but->signal_clicked().connect(sigc::bind(
157 sigc::mem_fun(this, &Patchage::zoom), 1.0));
158 // _zoom_full_but->signal_clicked().connect(
159 // sigc::mem_fun(_canvas.get(), &PatchageCanvas::zoom_full));
161 _menu_load_project->signal_activate().connect(
162 sigc::mem_fun(this, &Patchage::load_project_ask));
163 _menu_save_all_projects->signal_activate().connect(
164 sigc::mem_fun(this, &Patchage::save_all_projects));
165 _menu_close_all_projects->signal_activate().connect(
166 sigc::mem_fun(this, &Patchage::close_all_projects));
168 _menu_file_quit->signal_activate().connect(
169 sigc::mem_fun(this, &Patchage::on_quit));
170 _menu_view_refresh->signal_activate().connect(
171 sigc::mem_fun(this, &Patchage::refresh));
172 _menu_view_arrange->signal_activate().connect(
173 sigc::mem_fun(this, &Patchage::on_arrange));
174 _menu_view_toolbar->signal_activate().connect(
175 sigc::mem_fun(this, &Patchage::on_view_toolbar));
176 _menu_view_projects->signal_toggled().connect(
177 sigc::mem_fun(this, &Patchage::on_show_projects));
178 _menu_help_about->signal_activate().connect(
179 sigc::mem_fun(this, &Patchage::on_help_about));
181 Glib::wrap(canvas_get_widget(graph_canvas_get_canvas(g_jack_graph_canvas)))->show();
182 _main_win->present();
184 _about_win->set_transient_for(*_main_win);
186 _a2j = new a2j_proxy;
188 //info_msg(str(boost::format("a2j jack client name is '%s'") % _a2j->get_jack_client_name()));
190 _session = new session();
192 _project_list = new project_list(this, _session);
193 _project_list_viewport->hide();
195 _lash = new lash_proxy(_session);
197 //_menu_jack_start->signal_activate().connect(sigc::mem_fun(_jack, &jack_proxy::start_server));
198 //_menu_jack_stop->signal_activate().connect(sigc::mem_fun(_jack, &jack_proxy::stop_server));
200 _menu_a2j_start->signal_activate().connect(
201 sigc::mem_fun(_a2j, &a2j_proxy::start_bridge));
202 _menu_a2j_stop->signal_activate().connect(
203 sigc::mem_fun(_a2j, &a2j_proxy::stop_bridge));
205 //jack_status_changed(_jack->is_started());
207 connect_widgets();
208 update_state();
210 // _canvas->grab_focus();
212 // Idle callback, check if we need to refresh
213 Glib::signal_timeout().connect(
214 sigc::mem_fun(this, &Patchage::idle_callback), 100);
217 Patchage::~Patchage()
219 delete _lash;
220 delete _project_list;
221 delete _session;
222 delete _a2j;
224 _about_win.destroy();
225 //_main_win.destroy();
227 patchage_dbus_uninit();
230 bool
231 Patchage::idle_callback()
233 update_load();
235 return true;
238 void
239 Patchage::update_toolbar()
241 #if 0
242 bool started;
244 started = _jack->is_started();
246 _buffer_size_combo->set_sensitive(started);
248 if (started)
250 _buffer_size_combo->set_active((int)log2f(_jack->buffer_size()) - 5);
252 #endif
255 void
256 Patchage::update_load()
258 #if 0
259 if (!_jack->is_started())
261 _main_xrun_progress->set_text("JACK stopped");
262 return;
265 char tmp_buf[8];
266 snprintf(tmp_buf, 8, "%zd", _jack->xruns());
268 _main_xrun_progress->set_text(std::string(tmp_buf) + " Dropouts");
270 float load = _jack->get_dsp_load();
272 load /= 100.0; // dbus returns it in percents, we use 0..1
274 if (load > _max_dsp_load)
276 _max_dsp_load = load;
277 _main_xrun_progress->set_fraction(load);
279 #endif
283 void
284 Patchage::zoom(double z)
286 // _canvas->set_zoom(z);
290 void
291 Patchage::refresh()
293 // assert(_canvas);
295 // _canvas->destroy();
297 // if (_jack)
298 // _jack->refresh();
300 // for (ItemList::iterator i = _canvas->items().begin(); i != _canvas->items().end(); ++i) {
301 // (*i)->resize();
302 // }
305 void
306 Patchage::clear_load()
308 #if 0
309 _main_xrun_progress->set_fraction(0.0);
310 _jack->reset_xruns();
311 _max_dsp_load = 0.0;
312 #endif
316 void
317 Patchage::error_msg(const std::string& msg)
319 #if defined(LOG_TO_STATUS)
320 status_msg(msg);
321 #endif
322 #if defined(LOG_TO_STD)
323 std::cerr << msg << std::endl;
324 #endif
328 void
329 Patchage::info_msg(const std::string& msg)
331 #if defined(LOG_TO_STATUS)
332 status_msg(msg);
333 #endif
334 #if defined(LOG_TO_STD)
335 std::cerr << msg << std::endl;
336 #endif
340 void
341 Patchage::status_msg(const std::string& msg)
346 void
347 Patchage::update_state()
349 // for (ItemList::iterator i = _canvas->items().begin(); i != _canvas->items().end(); ++i) {
350 // shared_ptr<Module> module = dynamic_pointer_cast<Module>(*i);
351 // if (module)
352 // module->load_location();
353 // }
357 /** Update the sensitivity status of menus to reflect the present.
359 * (eg. disable "Connect to Jack" when Patchage is already connected to Jack)
361 void
362 Patchage::connect_widgets()
364 //_jack->signal_started.connect(sigc::bind(sigc::mem_fun(this, &Patchage::jack_status_changed), true));
365 //_jack->signal_stopped.connect(sigc::bind(sigc::mem_fun(this, &Patchage::jack_status_changed), false));
368 void
369 Patchage::jack_status_changed(
370 bool started)
372 update_toolbar();
374 _menu_jack_start->set_sensitive(!started);
375 _menu_jack_stop->set_sensitive(started);
376 _clear_load_but->set_sensitive(started);
377 if (!started)
379 _main_xrun_progress->set_fraction(0.0);
383 void
384 Patchage::on_arrange()
386 // assert(_canvas);
388 // _canvas->arrange();
392 void
393 Patchage::on_help_about()
395 _about_win->run();
396 _about_win->hide();
400 void
401 Patchage::on_quit()
403 _main_win->hide();
406 void
407 Patchage::on_show_projects()
409 if (_menu_view_projects->get_active())
410 _project_list_viewport->show();
411 else
412 _project_list_viewport->hide();
415 void
416 Patchage::on_view_toolbar()
418 if (_menu_view_toolbar->get_active())
419 _toolbar->show();
420 else
421 _toolbar->hide();
425 bool
426 Patchage::on_scroll(GdkEventScroll* ev)
428 std::cout << "ON SCROLL" << std::endl;
429 return false;
433 void
434 Patchage::buffer_size_changed()
436 #if 0
437 const int selected = _buffer_size_combo->get_active_row_number();
439 if (selected == -1)
441 update_toolbar();
443 else
445 uint32_t buffer_size = 1 << (selected+5);
447 // this check is temporal workaround for jack bug
448 // we skip setting buffer size if it same as acutal one
449 // proper place for such check is in jack
450 if (_jack->buffer_size() != buffer_size)
452 if (!_jack->set_buffer_size(buffer_size))
454 update_toolbar(); // reset combo box to actual value
458 #endif
461 void
462 Patchage::set_studio_availability(
463 bool available)
465 if (available)
467 _main_win->set_title("Active Studio - LADI Session Handler");
469 else
471 _main_win->set_title("LADI Session Handler");
473 _project_list->set_lash_availability(available);
474 _menu_view_projects->set_active(available);
477 void
478 Patchage::set_a2j_status(
479 unsigned int status)
481 const char * status_text;
483 switch (status)
485 case A2J_STATUS_NO_RESPONSE:
486 status_text = "A2J N/A";
487 _menu_a2j_start->set_sensitive(false);
488 _menu_a2j_stop->set_sensitive(false);
489 break;
490 case A2J_STATUS_BRIDGE_STOPPED:
491 status_text = "A2J bridge stopped";
492 _menu_a2j_start->set_sensitive(true);
493 _menu_a2j_stop->set_sensitive(false);
494 break;
495 case A2J_STATUS_BRIDGE_STARTED:
496 status_text = "A2J bridge started";
497 _menu_a2j_start->set_sensitive(false);
498 _menu_a2j_stop->set_sensitive(true);
499 break;
500 default:
501 error_msg(str(boost::format("Unknown A2J status %u") % status));
502 status_text = "Unknown A2J status";
503 _menu_a2j_start->set_sensitive(true);
504 _menu_a2j_stop->set_sensitive(true);
505 break;
508 _main_a2j_status_label->set_text(status_text);
511 void
512 Patchage::load_project_ask()
514 std::list<lash_project_info> projects;
516 _lash->get_available_projects(projects);
517 run_load_project_dialog(projects);
520 void
521 Patchage::load_project(
522 const std::string& project_name)
524 _lash->load_project(project_name);
527 void
528 Patchage::save_all_projects()
530 _lash->save_all_projects();
533 void
534 Patchage::save_project(
535 const std::string& project_name)
537 _lash->save_project(project_name);
540 void
541 Patchage::close_project(
542 const std::string& project_name)
544 _lash->close_project(project_name);
547 void
548 Patchage::close_all_projects()
550 _lash->close_all_projects();
553 #if 0
554 void
555 Patchage::on_port_added(
556 const char * jack_client_name,
557 const char * jack_port_name,
558 PortType port_type,
559 bool is_input,
560 bool is_terminal)
562 bool is_a2j_mapped;
563 std::string canvas_client_name;
564 std::string canvas_port_name;
565 uint32_t alsa_client_id;
566 boost::shared_ptr<PatchageModule> module;
568 is_a2j_mapped = strcmp(_a2j->get_jack_client_name(), jack_client_name) == 0;
569 if (is_a2j_mapped)
571 if (!_a2j->map_jack_port(jack_port_name, canvas_client_name, canvas_port_name, alsa_client_id))
573 return;
576 canvas_port_name = str(boost::format(canvas_port_name + " [a2j:%u]") % alsa_client_id);
578 else
580 canvas_client_name = jack_client_name;
581 canvas_port_name = jack_port_name;
584 ModuleType module_type = InputOutput;
585 if (_state_manager->get_module_split(canvas_client_name, is_terminal && !is_a2j_mapped)) {
586 if (is_input) {
587 module_type = Input;
588 } else {
589 module_type = Output;
593 module = _canvas->find_module(canvas_client_name, module_type);
594 if (!module) {
595 module = boost::shared_ptr<PatchageModule>(new PatchageModule(this, canvas_client_name, module_type));
596 module->load_location();
597 _canvas->add_item(module);
600 if (module->get_port(canvas_port_name)) {
601 return;
604 boost::shared_ptr<PatchagePort> port = boost::shared_ptr<PatchagePort>(
605 new PatchagePort(
606 module,
607 canvas_port_name,
608 is_input,
609 _state_manager->get_port_color(port_type)));
611 port->type = port_type;
612 port->is_a2j_mapped = is_a2j_mapped;
613 if (is_a2j_mapped)
615 port->a2j_jack_port_name = jack_port_name;
618 module->add_port(port);
620 module->resize();
623 boost::shared_ptr<PatchagePort>
624 Patchage::lookup_port(
625 const char * jack_client_name,
626 const char * jack_port_name)
628 if (strcmp(_a2j->get_jack_client_name(), jack_client_name) == 0)
630 return _canvas->lookup_port_by_a2j_jack_port_name(jack_port_name);
633 return _canvas->get_port(jack_client_name, jack_port_name);
636 void
637 Patchage::on_port_removed(
638 const char * jack_client_name,
639 const char * jack_port_name)
641 boost::shared_ptr<PatchagePort> port = lookup_port(jack_client_name, jack_port_name);
642 if (!port) {
643 error_msg(str(boost::format("Unable to remove unknown port '%s':'%s'") % jack_client_name % jack_port_name));
644 return;
647 boost::shared_ptr<PatchageModule> module = dynamic_pointer_cast<PatchageModule>(port->module().lock());
649 module->remove_port(port);
650 port.reset();
652 // No empty modules (for now)
653 if (module->num_ports() == 0) {
654 _canvas->remove_item(module);
655 module.reset();
656 } else {
657 module->resize();
661 void
662 Patchage::on_ports_connected(
663 const char * jack_client1_name,
664 const char * jack_port1_name,
665 const char * jack_client2_name,
666 const char * jack_port2_name)
668 boost::shared_ptr<PatchagePort> port1 = lookup_port(jack_client1_name, jack_port1_name);
669 if (!port1) {
670 error_msg((std::string)"Unable to connect unknown port '" + jack_port1_name + "' of client '" + jack_client1_name + "'");
671 return;
674 boost::shared_ptr<PatchagePort> port2 = lookup_port(jack_client2_name, jack_port2_name);
675 if (!port2) {
676 error_msg((std::string)"Unable to connect unknown port '" + jack_port2_name + "' of client '" + jack_client2_name + "'");
677 return;
680 _canvas->add_connection(port1, port2, port1->color() + 0x22222200);
683 void
684 Patchage::on_ports_disconnected(
685 const char * jack_client1_name,
686 const char * jack_port1_name,
687 const char * jack_client2_name,
688 const char * jack_port2_name)
690 boost::shared_ptr<PatchagePort> port1 = lookup_port(jack_client1_name, jack_port1_name);
691 if (!port1) {
692 error_msg((std::string)"Unable to disconnect unknown port '" + jack_port1_name + "' of client '" + jack_client1_name + "'");
693 return;
696 boost::shared_ptr<PatchagePort> port2 = lookup_port(jack_client2_name, jack_port2_name);
697 if (!port2) {
698 error_msg((std::string)"Unable to disconnect unknown port '" + jack_port2_name + "' of client '" + jack_client2_name + "'");
699 return;
702 _canvas->remove_connection(port1, port2);
705 static
706 bool
707 port_type_match(
708 boost::shared_ptr<PatchagePort> port1,
709 boost::shared_ptr<PatchagePort> port2)
711 return port1->type == port2->type;
714 void
715 Patchage::get_port_jack_names(
716 boost::shared_ptr<PatchagePort> port,
717 std::string& jack_client_name,
718 std::string& jack_port_name)
720 if (port->is_a2j_mapped)
722 jack_client_name = _a2j->get_jack_client_name();
723 jack_port_name = port->a2j_jack_port_name;
725 else
727 jack_client_name = port->module().lock()->name();
728 jack_port_name = port->name();
732 void
733 Patchage::connect(
734 boost::shared_ptr<PatchagePort> port1,
735 boost::shared_ptr<PatchagePort> port2)
737 std::string jack_client1_name;
738 std::string jack_port1_name;
739 std::string jack_client2_name;
740 std::string jack_port2_name;
742 if (port_type_match(port1, port2))
744 get_port_jack_names(port1, jack_client1_name, jack_port1_name);
745 get_port_jack_names(port2, jack_client2_name, jack_port2_name);
747 _jack->connect(
748 jack_client1_name.c_str(),
749 jack_port1_name.c_str(),
750 jack_client2_name.c_str(),
751 jack_port2_name.c_str());
753 else
755 status_msg("ERROR: Attempt to connect ports with mismatched types");
759 void
760 Patchage::disconnect(
761 boost::shared_ptr<PatchagePort> port1,
762 boost::shared_ptr<PatchagePort> port2)
764 std::string jack_client1_name;
765 std::string jack_port1_name;
766 std::string jack_client2_name;
767 std::string jack_port2_name;
769 if (port_type_match(port1, port2))
771 get_port_jack_names(port1, jack_client1_name, jack_port1_name);
772 get_port_jack_names(port2, jack_client2_name, jack_port2_name);
774 _jack->disconnect(
775 jack_client1_name.c_str(),
776 jack_port1_name.c_str(),
777 jack_client2_name.c_str(),
778 jack_port2_name.c_str());
780 else
782 status_msg("ERROR: Attempt to disconnect ports with mismatched types");
786 /** Destroy all JACK (canvas) ports.
788 void
789 Patchage::clear_canvas()
791 ItemList modules = _canvas->items(); // copy
792 for (ItemList::iterator m = modules.begin(); m != modules.end(); ++m) {
793 shared_ptr<Module> module = dynamic_pointer_cast<Module>(*m);
794 if (!module)
795 continue;
797 PortVector ports = module->ports(); // copy
798 for (PortVector::iterator p = ports.begin(); p != ports.end(); ++p) {
799 boost::shared_ptr<PatchagePort> port = boost::dynamic_pointer_cast<PatchagePort>(*p);
800 if (port)
802 module->remove_port(port);
803 port->hide();
807 if (module->ports().empty())
808 _canvas->remove_item(module);
809 else
810 module->resize();
814 bool
815 Patchage::is_canvas_empty()
817 return _canvas->items().empty();
819 #endif