1 /* This file is part of Patchage.
2 * Copyright (C) 2007 Dave Robillard <http://drobilla.net>
3 * Copyright (C) 2008 Nedko Arnaudov <nedko@arnaudov.name>
5 * Patchage is free software; you can redistribute it and/or modify it under the
6 * terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 2 of the License, or (at your option) any later
10 * Patchage is distributed in the hope that it will be useful, but WITHOUT ANY
11 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 #include <libgnomecanvasmm.h>
24 #include <libglademm/xml.h>
25 #include <gtk/gtkwindow.h>
26 #include <boost/format.hpp>
30 #include "jack_proxy.hpp"
31 #include "Patchage.hpp"
32 #include "PatchageCanvas.hpp"
33 #include "StateManager.hpp"
34 #include "lash_proxy.hpp"
35 #include "project_list.hpp"
36 #include "session.hpp"
37 #include "globals.hpp"
38 #include "a2j_proxy.hpp"
39 #include "PatchageModule.hpp"
40 #include "dbus_helpers.h"
41 #include "load_projects_dialog.hpp"
50 /* Gtk helpers (resize combo boxes) */
53 gtkmm_get_ink_pixel_size (Glib::RefPtr
<Pango::Layout
> layout
,
57 Pango::Rectangle ink_rect
= layout
->get_ink_extents ();
59 width
= (ink_rect
.get_width() + PANGO_SCALE
/ 2) / PANGO_SCALE
;
60 height
= (ink_rect
.get_height() + PANGO_SCALE
/ 2) / PANGO_SCALE
;
64 gtkmm_set_width_for_given_text (Gtk::Widget
&w
, const gchar
*text
,
65 gint hpadding
/*, gint vpadding*/)
68 int old_width
, old_height
;
69 w
.get_size_request(old_width
, old_height
);
74 gtkmm_get_ink_pixel_size (w
.create_pango_layout (text
), width
, height
);
75 w
.set_size_request(width
+ hpadding
, old_height
);//height + vpadding);
81 #define INIT_WIDGET(x) x(g_xml, ((const char*)#x) + 1)
83 Patchage::Patchage(int argc
, char** argv
)
85 , INIT_WIDGET(_about_win
)
86 , INIT_WIDGET(_buffer_size_combo
)
87 , INIT_WIDGET(_clear_load_but
)
88 , INIT_WIDGET(_main_scrolledwin
)
89 , INIT_WIDGET(_main_win
)
90 , INIT_WIDGET(_main_xrun_progress
)
91 , INIT_WIDGET(_main_a2j_status_label
)
92 , INIT_WIDGET(_main_lash_status_label
)
93 , INIT_WIDGET(_menu_file_quit
)
94 , INIT_WIDGET(_menu_help_about
)
95 , INIT_WIDGET(_menu_jack_start
)
96 , INIT_WIDGET(_menu_jack_stop
)
97 , INIT_WIDGET(_menu_a2j_start
)
98 , INIT_WIDGET(_menu_a2j_stop
)
99 , INIT_WIDGET(_menu_lash_activate
)
100 , INIT_WIDGET(_menu_lash_deactivate
)
101 , INIT_WIDGET(_menu_load_project
)
102 , INIT_WIDGET(_menu_save_all_projects
)
103 , INIT_WIDGET(_menu_close_all_projects
)
104 , INIT_WIDGET(_menu_store_positions
)
105 , INIT_WIDGET(_menu_view_arrange
)
106 , INIT_WIDGET(_menu_view_messages
)
107 , INIT_WIDGET(_menu_view_projects
)
108 , INIT_WIDGET(_menu_view_refresh
)
109 , INIT_WIDGET(_menu_view_toolbar
)
110 , INIT_WIDGET(_messages_clear_but
)
111 , INIT_WIDGET(_messages_close_but
)
112 , INIT_WIDGET(_messages_win
)
113 , INIT_WIDGET(_project_list_viewport
)
114 , INIT_WIDGET(_sample_rate_label
)
115 , INIT_WIDGET(_status_text
)
116 , INIT_WIDGET(_toolbar
)
117 , INIT_WIDGET(_zoom_full_but
)
118 , INIT_WIDGET(_zoom_normal_but
)
122 _settings_filename
= getenv("HOME");
123 _settings_filename
+= "/." PATCHAGE_APPNAME
"rc";
124 _state_manager
= new StateManager();
125 _canvas
= boost::shared_ptr
<PatchageCanvas
>(new PatchageCanvas(this, 1600*2, 1200*2));
128 if (!strcmp(*argv
, "--help")) {
129 cout
<< "Usage: patchage [OPTIONS]\nOptions: --no-alsa" << endl
;
137 patchage_dbus_init();
139 Glib::set_application_name("Patchage");
140 _about_win
->property_program_name() = "Patchage";
141 _about_win
->property_logo_icon_name() = PATCHAGE_APPNAME
;
142 gtk_window_set_default_icon_name(PATCHAGE_APPNAME
);
144 gtkmm_set_width_for_given_text(*_buffer_size_combo
, "4096 frames", 40);
146 _main_scrolledwin
->add(*_canvas
);
147 _canvas
->scroll_to(static_cast<int>(_canvas
->width()/2 - 320),
148 static_cast<int>(_canvas
->height()/2 - 240)); // FIXME: hardcoded
150 _main_scrolledwin
->property_hadjustment().get_value()->set_step_increment(10);
151 _main_scrolledwin
->property_vadjustment().get_value()->set_step_increment(10);
153 _main_scrolledwin
->signal_scroll_event().connect(
154 sigc::mem_fun(this, &Patchage::on_scroll
));
156 _buffer_size_combo
->signal_changed().connect(
157 sigc::mem_fun(this, &Patchage::buffer_size_changed
));
158 _clear_load_but
->signal_clicked().connect(
159 sigc::mem_fun(this, &Patchage::clear_load
));
160 _zoom_normal_but
->signal_clicked().connect(sigc::bind(
161 sigc::mem_fun(this, &Patchage::zoom
), 1.0));
162 _zoom_full_but
->signal_clicked().connect(
163 sigc::mem_fun(_canvas
.get(), &PatchageCanvas::zoom_full
));
165 _menu_load_project
->signal_activate().connect(
166 sigc::mem_fun(this, &Patchage::load_project_ask
));
167 _menu_save_all_projects
->signal_activate().connect(
168 sigc::mem_fun(this, &Patchage::save_all_projects
));
169 _menu_close_all_projects
->signal_activate().connect(
170 sigc::mem_fun(this, &Patchage::close_all_projects
));
172 _menu_store_positions
->signal_activate().connect(
173 sigc::mem_fun(this, &Patchage::on_store_positions
));
174 _menu_file_quit
->signal_activate().connect(
175 sigc::mem_fun(this, &Patchage::on_quit
));
176 _menu_view_refresh
->signal_activate().connect(
177 sigc::mem_fun(this, &Patchage::refresh
));
178 _menu_view_arrange
->signal_activate().connect(
179 sigc::mem_fun(this, &Patchage::on_arrange
));
180 _menu_view_toolbar
->signal_activate().connect(
181 sigc::mem_fun(this, &Patchage::on_view_toolbar
));
182 _menu_view_messages
->signal_toggled().connect(
183 sigc::mem_fun(this, &Patchage::on_show_messages
));
184 _menu_view_projects
->signal_toggled().connect(
185 sigc::mem_fun(this, &Patchage::on_show_projects
));
186 _menu_help_about
->signal_activate().connect(
187 sigc::mem_fun(this, &Patchage::on_help_about
));
189 _messages_clear_but
->signal_clicked().connect(
190 sigc::mem_fun(this, &Patchage::on_messages_clear
));
191 _messages_close_but
->signal_clicked().connect(
192 sigc::mem_fun(this, &Patchage::on_messages_close
));
193 _messages_win
->signal_delete_event().connect(
194 sigc::mem_fun(this, &Patchage::on_messages_delete
));
197 _main_win
->present();
199 _state_manager
->load(_settings_filename
);
202 static_cast<int>(_state_manager
->get_window_size().x
),
203 static_cast<int>(_state_manager
->get_window_size().y
));
206 static_cast<int>(_state_manager
->get_window_location().x
),
207 static_cast<int>(_state_manager
->get_window_location().y
));
209 _about_win
->set_transient_for(*_main_win
);
211 _a2j
= new a2j_proxy
;
213 //info_msg(str(boost::format("a2j jack client name is '%s'") % _a2j->get_jack_client_name()));
215 _session
= new session();
217 _project_list
= new project_list(this, _session
);
219 _lash
= new lash_proxy(_session
);
221 _jack
= new jack_proxy(this);
223 _menu_jack_start
->signal_activate().connect(
224 sigc::mem_fun(_jack
, &jack_proxy::start_server
));
225 _menu_jack_stop
->signal_activate().connect(
226 sigc::mem_fun(_jack
, &jack_proxy::stop_server
));
228 _menu_a2j_start
->signal_activate().connect(
229 sigc::mem_fun(_a2j
, &a2j_proxy::start_bridge
));
230 _menu_a2j_stop
->signal_activate().connect(
231 sigc::mem_fun(_a2j
, &a2j_proxy::stop_bridge
));
233 _menu_lash_activate
->signal_activate().connect(
234 sigc::mem_fun(_lash
, &lash_proxy::try_activate
));
235 _menu_lash_deactivate
->signal_activate().connect(
236 sigc::mem_fun(_lash
, &lash_proxy::deactivate
));
238 jack_status_changed(_jack
->is_started());
243 _canvas
->grab_focus();
245 // Idle callback, check if we need to refresh
246 Glib::signal_timeout().connect(
247 sigc::mem_fun(this, &Patchage::idle_callback
), 100);
250 Patchage::~Patchage()
254 delete _project_list
;
256 delete _state_manager
;
259 _about_win
.destroy();
260 _messages_win
.destroy();
261 //_main_win.destroy();
263 patchage_dbus_uninit();
268 Patchage::idle_callback()
277 Patchage::update_toolbar()
281 started
= _jack
->is_started();
283 _buffer_size_combo
->set_sensitive(started
);
287 _buffer_size_combo
->set_active((int)log2f(_jack
->buffer_size()) - 5);
293 Patchage::update_load()
295 if (!_jack
->is_started())
297 _main_xrun_progress
->set_text("JACK stopped");
302 snprintf(tmp_buf
, 8, "%zd", _jack
->xruns());
304 _main_xrun_progress
->set_text(string(tmp_buf
) + " Dropouts");
306 float load
= _jack
->get_dsp_load();
308 load
/= 100.0; // dbus returns it in percents, we use 0..1
310 if (load
> _max_dsp_load
)
312 _max_dsp_load
= load
;
313 _main_xrun_progress
->set_fraction(load
);
319 Patchage::zoom(double z
)
321 _state_manager
->set_zoom(z
);
322 _canvas
->set_zoom(z
);
336 for (ItemList::iterator i
= _canvas
->items().begin(); i
!= _canvas
->items().end(); ++i
) {
342 /** Update the stored window location and size in the StateManager (in memory).
345 Patchage::store_window_location()
347 int loc_x
, loc_y
, size_x
, size_y
;
348 _main_win
->get_position(loc_x
, loc_y
);
349 _main_win
->get_size(size_x
, size_y
);
350 Coord window_location
;
351 window_location
.x
= loc_x
;
352 window_location
.y
= loc_y
;
354 window_size
.x
= size_x
;
355 window_size
.y
= size_y
;
356 _state_manager
->set_window_location(window_location
);
357 _state_manager
->set_window_size(window_size
);
362 Patchage::clear_load()
364 _main_xrun_progress
->set_fraction(0.0);
365 _jack
->reset_xruns();
371 Patchage::error_msg(const std::string
& msg
)
373 #if defined(LOG_TO_STATUS)
376 #if defined(LOG_TO_STD)
383 Patchage::info_msg(const std::string
& msg
)
385 #if defined(LOG_TO_STATUS)
388 #if defined(LOG_TO_STD)
395 Patchage::status_msg(const string
& msg
)
397 if (_status_text
->get_buffer()->size() > 0)
398 _status_text
->get_buffer()->insert(_status_text
->get_buffer()->end(), "\n");
400 _status_text
->get_buffer()->insert(_status_text
->get_buffer()->end(), msg
);
401 _status_text
->scroll_to_mark(_status_text
->get_buffer()->get_insert(), 0);
406 Patchage::update_state()
408 for (ItemList::iterator i
= _canvas
->items().begin(); i
!= _canvas
->items().end(); ++i
) {
409 shared_ptr
<Module
> module
= dynamic_pointer_cast
<Module
>(*i
);
411 module
->load_location();
416 /** Update the sensitivity status of menus to reflect the present.
418 * (eg. disable "Connect to Jack" when Patchage is already connected to Jack)
421 Patchage::connect_widgets()
423 _jack
->signal_started
.connect(
424 sigc::bind(sigc::mem_fun(this, &Patchage::jack_status_changed
), true));
426 _jack
->signal_stopped
.connect(
427 sigc::bind(sigc::mem_fun(this, &Patchage::jack_status_changed
), false));
431 Patchage::jack_status_changed(
436 _menu_jack_start
->set_sensitive(!started
);
437 _menu_jack_stop
->set_sensitive(started
);
438 _clear_load_but
->set_sensitive(started
);
441 _main_xrun_progress
->set_fraction(0.0);
446 Patchage::on_arrange()
455 Patchage::on_help_about()
463 Patchage::on_messages_clear()
465 _status_text
->get_buffer()->erase(
466 _status_text
->get_buffer()->begin(),
467 _status_text
->get_buffer()->end());
472 Patchage::on_messages_close()
474 _menu_view_messages
->set_active(false);
479 Patchage::on_messages_delete(GdkEventAny
*)
481 _menu_view_messages
->set_active(false);
494 Patchage::on_show_messages()
496 if (_menu_view_messages
->get_active())
497 _messages_win
->present();
499 _messages_win
->hide();
504 Patchage::on_show_projects()
506 if (_menu_view_projects
->get_active())
507 _project_list_viewport
->show();
509 _project_list_viewport
->hide();
514 Patchage::on_store_positions()
516 store_window_location();
517 _state_manager
->save(_settings_filename
);
522 Patchage::on_view_toolbar()
524 if (_menu_view_toolbar
->get_active())
532 Patchage::on_scroll(GdkEventScroll
* ev
)
534 cout
<< "ON SCROLL" << endl
;
540 Patchage::buffer_size_changed()
542 const int selected
= _buffer_size_combo
->get_active_row_number();
544 if (selected
== -1) {
547 uint32_t buffer_size
= 1 << (selected
+5);
549 // this check is temporal workaround for jack bug
550 // we skip setting buffer size if it same as acutal one
551 // proper place for such check is in jack
552 if (_jack
->buffer_size() != buffer_size
)
554 if (!_jack
->set_buffer_size(buffer_size
))
556 update_toolbar(); // reset combo box to actual value
563 Patchage::set_lash_availability(
566 _project_list
->set_lash_availability(lash_active
);
567 _menu_view_projects
->set_active(lash_active
);
568 _menu_lash_activate
->set_sensitive(!lash_active
);
569 _menu_lash_deactivate
->set_sensitive(lash_active
);
573 _main_lash_status_label
->set_text("LASH N/A");
574 _project_list_viewport
->hide();
578 _main_lash_status_label
->set_text("LASH active");
579 _project_list_viewport
->show();
584 Patchage::set_a2j_status(
587 const char * status_text
;
591 case A2J_STATUS_NO_RESPONSE
:
592 status_text
= "A2J N/A";
593 _menu_a2j_start
->set_sensitive(false);
594 _menu_a2j_stop
->set_sensitive(false);
596 case A2J_STATUS_BRIDGE_STOPPED
:
597 status_text
= "A2J bridge stopped";
598 _menu_a2j_start
->set_sensitive(true);
599 _menu_a2j_stop
->set_sensitive(false);
601 case A2J_STATUS_BRIDGE_STARTED
:
602 status_text
= "A2J bridge started";
603 _menu_a2j_start
->set_sensitive(false);
604 _menu_a2j_stop
->set_sensitive(true);
607 error_msg(str(boost::format("Unknown A2J status %u") % status
));
608 status_text
= "Unknown A2J status";
609 _menu_a2j_start
->set_sensitive(true);
610 _menu_a2j_stop
->set_sensitive(true);
614 _main_a2j_status_label
->set_text(status_text
);
618 Patchage::load_project_ask()
620 std::list
<lash_project_info
> projects
;
622 _lash
->get_available_projects(projects
);
623 run_load_project_dialog(projects
);
627 Patchage::load_project(
628 const std::string
& project_name
)
630 _lash
->load_project(project_name
);
634 Patchage::save_all_projects()
636 _lash
->save_all_projects();
640 Patchage::save_project(
641 const std::string
& project_name
)
643 _lash
->save_project(project_name
);
647 Patchage::close_project(
648 const std::string
& project_name
)
650 _lash
->close_project(project_name
);
654 Patchage::close_all_projects()
656 _lash
->close_all_projects();
660 Patchage::on_port_added(
661 const char * jack_client_name
,
662 const char * jack_port_name
,
668 string canvas_client_name
;
669 string canvas_port_name
;
670 uint32_t alsa_client_id
;
671 boost::shared_ptr
<PatchageModule
> module
;
673 is_a2j_mapped
= strcmp(_a2j
->get_jack_client_name(), jack_client_name
) == 0;
676 if (!_a2j
->map_jack_port(jack_port_name
, canvas_client_name
, canvas_port_name
, alsa_client_id
))
681 canvas_port_name
= str(boost::format(canvas_port_name
+ " [a2j:%u]") % alsa_client_id
);
685 canvas_client_name
= jack_client_name
;
686 canvas_port_name
= jack_port_name
;
689 ModuleType module_type
= InputOutput
;
690 if (_state_manager
->get_module_split(canvas_client_name
, is_terminal
&& !is_a2j_mapped
)) {
694 module_type
= Output
;
698 module
= _canvas
->find_module(canvas_client_name
, module_type
);
700 module
= boost::shared_ptr
<PatchageModule
>(new PatchageModule(this, canvas_client_name
, module_type
));
701 module
->load_location();
702 _canvas
->add_item(module
);
705 if (module
->get_port(canvas_port_name
)) {
709 boost::shared_ptr
<PatchagePort
> port
= boost::shared_ptr
<PatchagePort
>(
714 _state_manager
->get_port_color(port_type
)));
716 port
->type
= port_type
;
717 port
->is_a2j_mapped
= is_a2j_mapped
;
720 port
->a2j_jack_port_name
= jack_port_name
;
723 module
->add_port(port
);
728 boost::shared_ptr
<PatchagePort
>
729 Patchage::lookup_port(
730 const char * jack_client_name
,
731 const char * jack_port_name
)
733 if (strcmp(_a2j
->get_jack_client_name(), jack_client_name
) == 0)
735 return _canvas
->lookup_port_by_a2j_jack_port_name(jack_port_name
);
738 return _canvas
->get_port(jack_client_name
, jack_port_name
);
742 Patchage::on_port_removed(
743 const char * jack_client_name
,
744 const char * jack_port_name
)
746 boost::shared_ptr
<PatchagePort
> port
= lookup_port(jack_client_name
, jack_port_name
);
748 error_msg(str(boost::format("Unable to remove unknown port '%s':'%s'") % jack_client_name
% jack_port_name
));
752 boost::shared_ptr
<PatchageModule
> module
= dynamic_pointer_cast
<PatchageModule
>(port
->module().lock());
754 module
->remove_port(port
);
757 // No empty modules (for now)
758 if (module
->num_ports() == 0) {
759 _canvas
->remove_item(module
);
767 Patchage::on_ports_connected(
768 const char * jack_client1_name
,
769 const char * jack_port1_name
,
770 const char * jack_client2_name
,
771 const char * jack_port2_name
)
773 boost::shared_ptr
<PatchagePort
> port1
= lookup_port(jack_client1_name
, jack_port1_name
);
775 error_msg((string
)"Unable to connect unknown port '" + jack_port1_name
+ "' of client '" + jack_client1_name
+ "'");
779 boost::shared_ptr
<PatchagePort
> port2
= lookup_port(jack_client2_name
, jack_port2_name
);
781 error_msg((string
)"Unable to connect unknown port '" + jack_port2_name
+ "' of client '" + jack_client2_name
+ "'");
785 _canvas
->add_connection(port1
, port2
, port1
->color() + 0x22222200);
789 Patchage::on_ports_disconnected(
790 const char * jack_client1_name
,
791 const char * jack_port1_name
,
792 const char * jack_client2_name
,
793 const char * jack_port2_name
)
795 boost::shared_ptr
<PatchagePort
> port1
= lookup_port(jack_client1_name
, jack_port1_name
);
797 error_msg((string
)"Unable to disconnect unknown port '" + jack_port1_name
+ "' of client '" + jack_client1_name
+ "'");
801 boost::shared_ptr
<PatchagePort
> port2
= lookup_port(jack_client2_name
, jack_port2_name
);
803 error_msg((string
)"Unable to disconnect unknown port '" + jack_port2_name
+ "' of client '" + jack_client2_name
+ "'");
807 _canvas
->remove_connection(port1
, port2
);
813 boost::shared_ptr
<PatchagePort
> port1
,
814 boost::shared_ptr
<PatchagePort
> port2
)
816 return port1
->type
== port2
->type
;
820 Patchage::get_port_jack_names(
821 boost::shared_ptr
<PatchagePort
> port
,
822 string
& jack_client_name
,
823 string
& jack_port_name
)
825 if (port
->is_a2j_mapped
)
827 jack_client_name
= _a2j
->get_jack_client_name();
828 jack_port_name
= port
->a2j_jack_port_name
;
832 jack_client_name
= port
->module().lock()->name();
833 jack_port_name
= port
->name();
839 boost::shared_ptr
<PatchagePort
> port1
,
840 boost::shared_ptr
<PatchagePort
> port2
)
842 string jack_client1_name
;
843 string jack_port1_name
;
844 string jack_client2_name
;
845 string jack_port2_name
;
847 if (port_type_match(port1
, port2
))
849 get_port_jack_names(port1
, jack_client1_name
, jack_port1_name
);
850 get_port_jack_names(port2
, jack_client2_name
, jack_port2_name
);
853 jack_client1_name
.c_str(),
854 jack_port1_name
.c_str(),
855 jack_client2_name
.c_str(),
856 jack_port2_name
.c_str());
860 status_msg("ERROR: Attempt to connect ports with mismatched types");
865 Patchage::disconnect(
866 boost::shared_ptr
<PatchagePort
> port1
,
867 boost::shared_ptr
<PatchagePort
> port2
)
869 string jack_client1_name
;
870 string jack_port1_name
;
871 string jack_client2_name
;
872 string jack_port2_name
;
874 if (port_type_match(port1
, port2
))
876 get_port_jack_names(port1
, jack_client1_name
, jack_port1_name
);
877 get_port_jack_names(port2
, jack_client2_name
, jack_port2_name
);
880 jack_client1_name
.c_str(),
881 jack_port1_name
.c_str(),
882 jack_client2_name
.c_str(),
883 jack_port2_name
.c_str());
887 status_msg("ERROR: Attempt to disconnect ports with mismatched types");
891 /** Destroy all JACK (canvas) ports.
894 Patchage::clear_canvas()
896 ItemList modules
= _canvas
->items(); // copy
897 for (ItemList::iterator m
= modules
.begin(); m
!= modules
.end(); ++m
) {
898 shared_ptr
<Module
> module
= dynamic_pointer_cast
<Module
>(*m
);
902 PortVector ports
= module
->ports(); // copy
903 for (PortVector::iterator p
= ports
.begin(); p
!= ports
.end(); ++p
) {
904 boost::shared_ptr
<PatchagePort
> port
= boost::dynamic_pointer_cast
<PatchagePort
>(*p
);
907 module
->remove_port(port
);
912 if (module
->ports().empty())
913 _canvas
->remove_item(module
);
920 Patchage::is_canvas_empty()
922 return _canvas
->items().empty();