1 /* -*- Mode: C ; c-basic-offset: 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.
34 #include <libgnomecanvasmm.h>
35 #include <libglademm/xml.h>
36 #include <gtk/gtkwindow.h>
37 #include <boost/format.hpp>
39 #include "jack_proxy.hpp"
40 #include "Patchage.hpp"
41 #include "PatchageCanvas.hpp"
42 #include "StateManager.hpp"
43 #include "lash_proxy.hpp"
44 #include "project_list.hpp"
45 #include "session.hpp"
46 #include "globals.hpp"
47 #include "a2j_proxy.hpp"
48 #include "PatchageModule.hpp"
49 #include "dbus_helpers.h"
50 #include "load_projects_dialog.hpp"
55 //#define LOG_TO_STATUS
57 /* Gtk helpers (resize combo boxes) */
60 gtkmm_get_ink_pixel_size (Glib::RefPtr
<Pango::Layout
> layout
,
64 Pango::Rectangle ink_rect
= layout
->get_ink_extents ();
66 width
= (ink_rect
.get_width() + PANGO_SCALE
/ 2) / PANGO_SCALE
;
67 height
= (ink_rect
.get_height() + PANGO_SCALE
/ 2) / PANGO_SCALE
;
71 gtkmm_set_width_for_given_text (Gtk::Widget
&w
, const gchar
*text
,
72 gint hpadding
/*, gint vpadding*/)
75 int old_width
, old_height
;
76 w
.get_size_request(old_width
, old_height
);
81 gtkmm_get_ink_pixel_size (w
.create_pango_layout (text
), width
, height
);
82 w
.set_size_request(width
+ hpadding
, old_height
);//height + vpadding);
88 #define INIT_WIDGET(x) x(g_xml, ((const char*)#x) + 1)
90 Patchage::Patchage(int argc
, char** argv
)
92 , INIT_WIDGET(_about_win
)
93 , INIT_WIDGET(_buffer_size_combo
)
94 , INIT_WIDGET(_clear_load_but
)
95 , INIT_WIDGET(_main_scrolledwin
)
96 , INIT_WIDGET(_main_win
)
97 , INIT_WIDGET(_main_xrun_progress
)
98 , INIT_WIDGET(_main_a2j_status_label
)
99 , INIT_WIDGET(_menu_file_quit
)
100 , INIT_WIDGET(_menu_help_about
)
101 , INIT_WIDGET(_menu_jack_start
)
102 , INIT_WIDGET(_menu_jack_stop
)
103 , INIT_WIDGET(_menu_a2j_start
)
104 , INIT_WIDGET(_menu_a2j_stop
)
105 , INIT_WIDGET(_menu_load_project
)
106 , INIT_WIDGET(_menu_save_all_projects
)
107 , INIT_WIDGET(_menu_close_all_projects
)
108 , INIT_WIDGET(_menu_store_positions
)
109 , INIT_WIDGET(_menu_view_arrange
)
110 , INIT_WIDGET(_menu_view_projects
)
111 , INIT_WIDGET(_menu_view_refresh
)
112 , INIT_WIDGET(_menu_view_toolbar
)
113 , INIT_WIDGET(_project_list_viewport
)
114 , INIT_WIDGET(_sample_rate_label
)
115 , INIT_WIDGET(_toolbar
)
116 , INIT_WIDGET(_zoom_full_but
)
117 , INIT_WIDGET(_zoom_normal_but
)
121 _settings_filename
= getenv("HOME");
122 _settings_filename
+= "/.gladishrc";
123 _state_manager
= new StateManager();
124 //_canvas = boost::shared_ptr<PatchageCanvas>(new PatchageCanvas(this, 1600*2, 1200*2));
127 if (!strcmp(*argv
, "--help")) {
128 std::cout
<< "Usage: patchage [OPTIONS]\nOptions: --no-alsa" << std::endl
;
136 patchage_dbus_init();
138 Glib::set_application_name("Patchage");
139 _about_win
->property_program_name() = "Patchage";
140 _about_win
->property_logo_icon_name() = "gladish";
141 gtk_window_set_default_icon_name("gladish");
143 gtkmm_set_width_for_given_text(*_buffer_size_combo
, "4096 frames", 40);
145 // _main_scrolledwin->add(*_canvas);
146 // _canvas->scroll_to(static_cast<int>(_canvas->width()/2 - 320),
147 // static_cast<int>(_canvas->height()/2 - 240)); // FIXME: hardcoded
149 _main_scrolledwin
->property_hadjustment().get_value()->set_step_increment(10);
150 _main_scrolledwin
->property_vadjustment().get_value()->set_step_increment(10);
152 _main_scrolledwin
->signal_scroll_event().connect(
153 sigc::mem_fun(this, &Patchage::on_scroll
));
155 _buffer_size_combo
->signal_changed().connect(
156 sigc::mem_fun(this, &Patchage::buffer_size_changed
));
157 _clear_load_but
->signal_clicked().connect(
158 sigc::mem_fun(this, &Patchage::clear_load
));
159 _zoom_normal_but
->signal_clicked().connect(sigc::bind(
160 sigc::mem_fun(this, &Patchage::zoom
), 1.0));
161 // _zoom_full_but->signal_clicked().connect(
162 // sigc::mem_fun(_canvas.get(), &PatchageCanvas::zoom_full));
164 _menu_load_project
->signal_activate().connect(
165 sigc::mem_fun(this, &Patchage::load_project_ask
));
166 _menu_save_all_projects
->signal_activate().connect(
167 sigc::mem_fun(this, &Patchage::save_all_projects
));
168 _menu_close_all_projects
->signal_activate().connect(
169 sigc::mem_fun(this, &Patchage::close_all_projects
));
171 _menu_store_positions
->signal_activate().connect(
172 sigc::mem_fun(this, &Patchage::on_store_positions
));
173 _menu_file_quit
->signal_activate().connect(
174 sigc::mem_fun(this, &Patchage::on_quit
));
175 _menu_view_refresh
->signal_activate().connect(
176 sigc::mem_fun(this, &Patchage::refresh
));
177 _menu_view_arrange
->signal_activate().connect(
178 sigc::mem_fun(this, &Patchage::on_arrange
));
179 _menu_view_toolbar
->signal_activate().connect(
180 sigc::mem_fun(this, &Patchage::on_view_toolbar
));
181 _menu_view_projects
->signal_toggled().connect(
182 sigc::mem_fun(this, &Patchage::on_show_projects
));
183 _menu_help_about
->signal_activate().connect(
184 sigc::mem_fun(this, &Patchage::on_help_about
));
187 _main_win
->present();
189 _state_manager
->load(_settings_filename
);
192 static_cast<int>(_state_manager
->get_window_size().x
),
193 static_cast<int>(_state_manager
->get_window_size().y
));
196 static_cast<int>(_state_manager
->get_window_location().x
),
197 static_cast<int>(_state_manager
->get_window_location().y
));
199 _about_win
->set_transient_for(*_main_win
);
201 _a2j
= new a2j_proxy
;
203 //info_msg(str(boost::format("a2j jack client name is '%s'") % _a2j->get_jack_client_name()));
205 _session
= new session();
207 _project_list
= new project_list(this, _session
);
208 _project_list_viewport
->hide();
210 _lash
= new lash_proxy(_session
);
212 _jack
= new jack_proxy(this);
214 _menu_jack_start
->signal_activate().connect(
215 sigc::mem_fun(_jack
, &jack_proxy::start_server
));
216 _menu_jack_stop
->signal_activate().connect(
217 sigc::mem_fun(_jack
, &jack_proxy::stop_server
));
219 _menu_a2j_start
->signal_activate().connect(
220 sigc::mem_fun(_a2j
, &a2j_proxy::start_bridge
));
221 _menu_a2j_stop
->signal_activate().connect(
222 sigc::mem_fun(_a2j
, &a2j_proxy::stop_bridge
));
224 jack_status_changed(_jack
->is_started());
229 // _canvas->grab_focus();
231 // Idle callback, check if we need to refresh
232 Glib::signal_timeout().connect(
233 sigc::mem_fun(this, &Patchage::idle_callback
), 100);
236 Patchage::~Patchage()
240 delete _project_list
;
242 delete _state_manager
;
245 _about_win
.destroy();
246 //_main_win.destroy();
248 patchage_dbus_uninit();
253 Patchage::idle_callback()
262 Patchage::update_toolbar()
266 started
= _jack
->is_started();
268 _buffer_size_combo
->set_sensitive(started
);
272 _buffer_size_combo
->set_active((int)log2f(_jack
->buffer_size()) - 5);
278 Patchage::update_load()
280 if (!_jack
->is_started())
282 _main_xrun_progress
->set_text("JACK stopped");
287 snprintf(tmp_buf
, 8, "%zd", _jack
->xruns());
289 _main_xrun_progress
->set_text(std::string(tmp_buf
) + " Dropouts");
291 float load
= _jack
->get_dsp_load();
293 load
/= 100.0; // dbus returns it in percents, we use 0..1
295 if (load
> _max_dsp_load
)
297 _max_dsp_load
= load
;
298 _main_xrun_progress
->set_fraction(load
);
304 Patchage::zoom(double z
)
306 _state_manager
->set_zoom(z
);
307 // _canvas->set_zoom(z);
316 // _canvas->destroy();
321 // for (ItemList::iterator i = _canvas->items().begin(); i != _canvas->items().end(); ++i) {
327 /** Update the stored window location and size in the StateManager (in memory).
330 Patchage::store_window_location()
332 int loc_x
, loc_y
, size_x
, size_y
;
333 _main_win
->get_position(loc_x
, loc_y
);
334 _main_win
->get_size(size_x
, size_y
);
335 Coord window_location
;
336 window_location
.x
= loc_x
;
337 window_location
.y
= loc_y
;
339 window_size
.x
= size_x
;
340 window_size
.y
= size_y
;
341 _state_manager
->set_window_location(window_location
);
342 _state_manager
->set_window_size(window_size
);
347 Patchage::clear_load()
349 _main_xrun_progress
->set_fraction(0.0);
350 _jack
->reset_xruns();
356 Patchage::error_msg(const std::string
& msg
)
358 #if defined(LOG_TO_STATUS)
361 #if defined(LOG_TO_STD)
362 std::cerr
<< msg
<< std::endl
;
368 Patchage::info_msg(const std::string
& msg
)
370 #if defined(LOG_TO_STATUS)
373 #if defined(LOG_TO_STD)
374 std::cerr
<< msg
<< std::endl
;
380 Patchage::status_msg(const std::string
& msg
)
386 Patchage::update_state()
388 // for (ItemList::iterator i = _canvas->items().begin(); i != _canvas->items().end(); ++i) {
389 // shared_ptr<Module> module = dynamic_pointer_cast<Module>(*i);
391 // module->load_location();
396 /** Update the sensitivity status of menus to reflect the present.
398 * (eg. disable "Connect to Jack" when Patchage is already connected to Jack)
401 Patchage::connect_widgets()
403 _jack
->signal_started
.connect(
404 sigc::bind(sigc::mem_fun(this, &Patchage::jack_status_changed
), true));
406 _jack
->signal_stopped
.connect(
407 sigc::bind(sigc::mem_fun(this, &Patchage::jack_status_changed
), false));
411 Patchage::jack_status_changed(
416 _menu_jack_start
->set_sensitive(!started
);
417 _menu_jack_stop
->set_sensitive(started
);
418 _clear_load_but
->set_sensitive(started
);
421 _main_xrun_progress
->set_fraction(0.0);
426 Patchage::on_arrange()
430 // _canvas->arrange();
435 Patchage::on_help_about()
449 Patchage::on_show_projects()
451 if (_menu_view_projects
->get_active())
452 _project_list_viewport
->show();
454 _project_list_viewport
->hide();
459 Patchage::on_store_positions()
461 store_window_location();
462 _state_manager
->save(_settings_filename
);
467 Patchage::on_view_toolbar()
469 if (_menu_view_toolbar
->get_active())
477 Patchage::on_scroll(GdkEventScroll
* ev
)
479 std::cout
<< "ON SCROLL" << std::endl
;
485 Patchage::buffer_size_changed()
487 const int selected
= _buffer_size_combo
->get_active_row_number();
489 if (selected
== -1) {
492 uint32_t buffer_size
= 1 << (selected
+5);
494 // this check is temporal workaround for jack bug
495 // we skip setting buffer size if it same as acutal one
496 // proper place for such check is in jack
497 if (_jack
->buffer_size() != buffer_size
)
499 if (!_jack
->set_buffer_size(buffer_size
))
501 update_toolbar(); // reset combo box to actual value
508 Patchage::set_studio_availability(
513 _main_win
->set_title("Active Studio - LADI Session Handler");
517 _main_win
->set_title("LADI Session Handler");
519 _project_list
->set_lash_availability(available
);
520 _menu_view_projects
->set_active(available
);
524 Patchage::set_a2j_status(
527 const char * status_text
;
531 case A2J_STATUS_NO_RESPONSE
:
532 status_text
= "A2J N/A";
533 _menu_a2j_start
->set_sensitive(false);
534 _menu_a2j_stop
->set_sensitive(false);
536 case A2J_STATUS_BRIDGE_STOPPED
:
537 status_text
= "A2J bridge stopped";
538 _menu_a2j_start
->set_sensitive(true);
539 _menu_a2j_stop
->set_sensitive(false);
541 case A2J_STATUS_BRIDGE_STARTED
:
542 status_text
= "A2J bridge started";
543 _menu_a2j_start
->set_sensitive(false);
544 _menu_a2j_stop
->set_sensitive(true);
547 error_msg(str(boost::format("Unknown A2J status %u") % status
));
548 status_text
= "Unknown A2J status";
549 _menu_a2j_start
->set_sensitive(true);
550 _menu_a2j_stop
->set_sensitive(true);
554 _main_a2j_status_label
->set_text(status_text
);
558 Patchage::load_project_ask()
560 std::list
<lash_project_info
> projects
;
562 _lash
->get_available_projects(projects
);
563 run_load_project_dialog(projects
);
567 Patchage::load_project(
568 const std::string
& project_name
)
570 _lash
->load_project(project_name
);
574 Patchage::save_all_projects()
576 _lash
->save_all_projects();
580 Patchage::save_project(
581 const std::string
& project_name
)
583 _lash
->save_project(project_name
);
587 Patchage::close_project(
588 const std::string
& project_name
)
590 _lash
->close_project(project_name
);
594 Patchage::close_all_projects()
596 _lash
->close_all_projects();
600 Patchage::on_port_added(
601 const char * jack_client_name
,
602 const char * jack_port_name
,
609 std::string canvas_client_name
;
610 std::string canvas_port_name
;
611 uint32_t alsa_client_id
;
612 boost::shared_ptr
<PatchageModule
> module
;
614 is_a2j_mapped
= strcmp(_a2j
->get_jack_client_name(), jack_client_name
) == 0;
617 if (!_a2j
->map_jack_port(jack_port_name
, canvas_client_name
, canvas_port_name
, alsa_client_id
))
622 canvas_port_name
= str(boost::format(canvas_port_name
+ " [a2j:%u]") % alsa_client_id
);
626 canvas_client_name
= jack_client_name
;
627 canvas_port_name
= jack_port_name
;
630 ModuleType module_type
= InputOutput
;
631 if (_state_manager
->get_module_split(canvas_client_name
, is_terminal
&& !is_a2j_mapped
)) {
635 module_type
= Output
;
639 module
= _canvas
->find_module(canvas_client_name
, module_type
);
641 module
= boost::shared_ptr
<PatchageModule
>(new PatchageModule(this, canvas_client_name
, module_type
));
642 module
->load_location();
643 _canvas
->add_item(module
);
646 if (module
->get_port(canvas_port_name
)) {
650 boost::shared_ptr
<PatchagePort
> port
= boost::shared_ptr
<PatchagePort
>(
655 _state_manager
->get_port_color(port_type
)));
657 port
->type
= port_type
;
658 port
->is_a2j_mapped
= is_a2j_mapped
;
661 port
->a2j_jack_port_name
= jack_port_name
;
664 module
->add_port(port
);
671 boost::shared_ptr
<PatchagePort
>
672 Patchage::lookup_port(
673 const char * jack_client_name
,
674 const char * jack_port_name
)
676 if (strcmp(_a2j
->get_jack_client_name(), jack_client_name
) == 0)
678 return _canvas
->lookup_port_by_a2j_jack_port_name(jack_port_name
);
681 return _canvas
->get_port(jack_client_name
, jack_port_name
);
686 Patchage::on_port_removed(
687 const char * jack_client_name
,
688 const char * jack_port_name
)
691 boost::shared_ptr
<PatchagePort
> port
= lookup_port(jack_client_name
, jack_port_name
);
693 error_msg(str(boost::format("Unable to remove unknown port '%s':'%s'") % jack_client_name
% jack_port_name
));
697 boost::shared_ptr
<PatchageModule
> module
= dynamic_pointer_cast
<PatchageModule
>(port
->module().lock());
699 module
->remove_port(port
);
702 // No empty modules (for now)
703 if (module
->num_ports() == 0) {
704 _canvas
->remove_item(module
);
713 Patchage::on_ports_connected(
714 const char * jack_client1_name
,
715 const char * jack_port1_name
,
716 const char * jack_client2_name
,
717 const char * jack_port2_name
)
720 boost::shared_ptr
<PatchagePort
> port1
= lookup_port(jack_client1_name
, jack_port1_name
);
722 error_msg((std::string
)"Unable to connect unknown port '" + jack_port1_name
+ "' of client '" + jack_client1_name
+ "'");
726 boost::shared_ptr
<PatchagePort
> port2
= lookup_port(jack_client2_name
, jack_port2_name
);
728 error_msg((std::string
)"Unable to connect unknown port '" + jack_port2_name
+ "' of client '" + jack_client2_name
+ "'");
732 _canvas
->add_connection(port1
, port2
, port1
->color() + 0x22222200);
737 Patchage::on_ports_disconnected(
738 const char * jack_client1_name
,
739 const char * jack_port1_name
,
740 const char * jack_client2_name
,
741 const char * jack_port2_name
)
744 boost::shared_ptr
<PatchagePort
> port1
= lookup_port(jack_client1_name
, jack_port1_name
);
746 error_msg((std::string
)"Unable to disconnect unknown port '" + jack_port1_name
+ "' of client '" + jack_client1_name
+ "'");
750 boost::shared_ptr
<PatchagePort
> port2
= lookup_port(jack_client2_name
, jack_port2_name
);
752 error_msg((std::string
)"Unable to disconnect unknown port '" + jack_port2_name
+ "' of client '" + jack_client2_name
+ "'");
756 _canvas
->remove_connection(port1
, port2
);
764 boost::shared_ptr
<PatchagePort
> port1
,
765 boost::shared_ptr
<PatchagePort
> port2
)
767 return port1
->type
== port2
->type
;
772 Patchage::get_port_jack_names(
773 boost::shared_ptr
<PatchagePort
> port
,
774 std::string
& jack_client_name
,
775 std::string
& jack_port_name
)
778 if (port
->is_a2j_mapped
)
780 jack_client_name
= _a2j
->get_jack_client_name();
781 jack_port_name
= port
->a2j_jack_port_name
;
785 jack_client_name
= port
->module().lock()->name();
786 jack_port_name
= port
->name();
793 boost::shared_ptr
<PatchagePort
> port1
,
794 boost::shared_ptr
<PatchagePort
> port2
)
797 std::string jack_client1_name
;
798 std::string jack_port1_name
;
799 std::string jack_client2_name
;
800 std::string jack_port2_name
;
802 if (port_type_match(port1
, port2
))
804 get_port_jack_names(port1
, jack_client1_name
, jack_port1_name
);
805 get_port_jack_names(port2
, jack_client2_name
, jack_port2_name
);
808 jack_client1_name
.c_str(),
809 jack_port1_name
.c_str(),
810 jack_client2_name
.c_str(),
811 jack_port2_name
.c_str());
815 status_msg("ERROR: Attempt to connect ports with mismatched types");
821 Patchage::disconnect(
822 boost::shared_ptr
<PatchagePort
> port1
,
823 boost::shared_ptr
<PatchagePort
> port2
)
826 std::string jack_client1_name
;
827 std::string jack_port1_name
;
828 std::string jack_client2_name
;
829 std::string jack_port2_name
;
831 if (port_type_match(port1
, port2
))
833 get_port_jack_names(port1
, jack_client1_name
, jack_port1_name
);
834 get_port_jack_names(port2
, jack_client2_name
, jack_port2_name
);
837 jack_client1_name
.c_str(),
838 jack_port1_name
.c_str(),
839 jack_client2_name
.c_str(),
840 jack_port2_name
.c_str());
844 status_msg("ERROR: Attempt to disconnect ports with mismatched types");
849 /** Destroy all JACK (canvas) ports.
852 Patchage::clear_canvas()
855 ItemList modules
= _canvas
->items(); // copy
856 for (ItemList::iterator m
= modules
.begin(); m
!= modules
.end(); ++m
) {
857 shared_ptr
<Module
> module
= dynamic_pointer_cast
<Module
>(*m
);
861 PortVector ports
= module
->ports(); // copy
862 for (PortVector::iterator p
= ports
.begin(); p
!= ports
.end(); ++p
) {
863 boost::shared_ptr
<PatchagePort
> port
= boost::dynamic_pointer_cast
<PatchagePort
>(*p
);
866 module
->remove_port(port
);
871 if (module
->ports().empty())
872 _canvas
->remove_item(module
);
880 Patchage::is_canvas_empty()
882 // return _canvas->items().empty();