1 /*
2  * Copyright (C) 2019 Johannes Mueller <github@johannes-mueller.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  */
18 
19 #include <libusb.h>
20 
21 #include <gtkmm/adjustment.h>
22 #include <gtkmm/box.h>
23 #include <gtkmm/comboboxtext.h>
24 #include <gtkmm/frame.h>
25 #include <gtkmm/label.h>
26 #include <gtkmm/liststore.h>
27 #include <gtkmm/spinbutton.h>
28 #include <gtkmm/table.h>
29 
30 #include "pbd/unwind.h"
31 
32 #include "ardour/debug.h"
33 
34 #include "gtkmm2ext/gtk_ui.h"
35 #include "gtkmm2ext/gui_thread.h"
36 #include "gtkmm2ext/utils.h"
37 
38 #include "widgets/ardour_button.h"
39 
40 #include "contourdesign.h"
41 #include "jump_distance_widget.h"
42 #include "button_config_widget.h"
43 
44 #include "pbd/i18n.h"
45 
46 using namespace ArdourSurface;
47 
48 class ContourDesignGUI : public Gtk::VBox, public PBD::ScopedConnectionList
49 {
50 public:
51 	ContourDesignGUI (ContourDesignControlProtocol& ccp);
~ContourDesignGUI()52 	~ContourDesignGUI () {}
53 
54 private:
55 	ContourDesignControlProtocol& _ccp;
56 
57 	ArdourWidgets::ArdourButton _test_button;
58 
59 	Gtk::CheckButton _keep_rolling;
60 	void toggle_keep_rolling ();
61 
62 	std::vector<boost::shared_ptr<Gtk::Adjustment> > _shuttle_speed_adjustments;
63 	void set_shuttle_speed (int index);
64 
65 	JumpDistanceWidget _jog_distance;
66 	void update_jog_distance ();
67 
68 	void update_action(unsigned int index, ButtonConfigWidget* sender);
69 
70 	void toggle_test_mode ();
71 
72 	void test_button_press (unsigned short btn);
73 	void test_button_release (unsigned short btn);
74 
75 	std::vector<boost::shared_ptr<ArdourWidgets::ArdourButton> > _btn_leds;
76 
77 	void init_on_show ();
78 	bool reset_test_state (GdkEventAny* = 0);
79 	bool update_device_state ();
80 
81 	Gtk::Label _device_state_lbl;
82 
83 	sigc::signal<void, bool> ProButtonsSensitive;
84 	sigc::signal<void, bool> XpressButtonsSensitive;
85 };
86 
87 
88 using namespace PBD;
89 using namespace ARDOUR;
90 using namespace std;
91 using namespace Gtk;
92 using namespace Gtkmm2ext;
93 using namespace Glib;
94 using namespace ArdourWidgets;
95 
96 
ContourDesignGUI(ContourDesignControlProtocol & ccp)97 ContourDesignGUI::ContourDesignGUI (ContourDesignControlProtocol& ccp)
98 	: _ccp (ccp)
99 	, _test_button (_("Button Test"), ArdourButton::led_default_elements)
100 	, _keep_rolling (_("Keep rolling after jumps"))
101 	, _jog_distance (ccp.jog_distance ())
102 	, _device_state_lbl ()
103 {
104 	Frame* dg_sample = manage (new Frame (_("Device")));
105 	dg_sample->set_size_request (300, -1);
106 	VBox* dg_box = manage (new VBox);
107 	dg_sample->add (*dg_box);
108 	dg_box->set_border_width (6);
109 	dg_box->pack_start (_device_state_lbl);
110 
111 	_device_state_lbl.set_line_wrap (true);
112 
113 	Frame* sj_sample = manage (new Frame (_("Shuttle speeds and jog jump distances")));
114 	Table* sj_table = manage (new Table);
115 	sj_sample->set_border_width (6);
116 	sj_table->set_border_width (12);
117 	sj_sample->add (*sj_table);
118 
119 	Label* speed_label = manage (new Label (_("Transport speeds for the shuttle positions:"), ALIGN_START));
120 	sj_table->attach (*speed_label, 0,1, 0,1, FILL|EXPAND, FILL|EXPAND, /* xpadding = */ 12);
121 
122 	HBox* speed_box = manage (new HBox);
123 	for (int i=0; i != ContourDesignControlProtocol::num_shuttle_speeds; ++i) {
124 		double speed = ccp.shuttle_speed (i);
125 		boost::shared_ptr<Gtk::Adjustment> adj (new Gtk::Adjustment (speed, 0.0, 100.0, 0.25));
126 		_shuttle_speed_adjustments.push_back (adj);
127 		SpinButton* sb = manage (new SpinButton (*adj, 0.25, 2));
128 		speed_box->pack_start (*sb);
129 		sb->signal_value_changed().connect (sigc::bind (sigc::mem_fun(*this, &ContourDesignGUI::set_shuttle_speed), i));
130 	}
131 	sj_table->attach (*speed_box, 1,2, 0,1);
132 
133 	Label* jog_label = manage (new Label (_("Jump distance for jog wheel:"), ALIGN_START));
134 	_jog_distance.Changed.connect (sigc::mem_fun (*this, &ContourDesignGUI::update_jog_distance));
135 
136 	sj_table->attach (*jog_label, 0,1, 1,2, FILL|EXPAND, FILL|EXPAND, /* xpadding = */ 12);
137 	sj_table->attach (_jog_distance, 1,2, 1,2);
138 
139 	_keep_rolling.set_tooltip_text (_("If checked Ardour keeps rolling after jog or shuttle events. If unchecked it stops."));
140 	_keep_rolling.signal_toggled().connect (sigc::mem_fun (*this, &ContourDesignGUI::toggle_keep_rolling));
141 	_keep_rolling.set_active (_ccp.keep_rolling ());
142 
143 	sj_table->attach (_keep_rolling, 0,1, 2,3);
144 
145 
146 	Frame* btn_action_sample = manage (new Frame (_("Actions or jumps for buttons")));
147 	HBox* btn_action_box = manage (new HBox);
148 	btn_action_sample->set_border_width (6);
149 	btn_action_box->set_border_width (12);
150 	btn_action_sample->add (*btn_action_box);
151 
152 	VBox* tbb = manage (new VBox);
153 	_test_button.set_tooltip_text (_("If the button is active, all the button presses are not handled, "
154 					 "but in the corresponding line in the button table the LED will light up."));
155 	_test_button.signal_clicked.connect (sigc::mem_fun (*this, &ContourDesignGUI::toggle_test_mode));
156 	_test_button.set_size_request (-1, 64);
157 	tbb->pack_start(_test_button, true, false);
158 	btn_action_box->pack_start (*tbb, true, false, 12);
159 
160 
161 	Table* table = manage (new Table);
162 	table->set_row_spacings (6);
163 	table->set_col_spacings (6);;
164 
165 	for (int btn_idx=0; btn_idx < _ccp.get_button_count(); ++btn_idx) {
166 		boost::shared_ptr<ArdourButton> b (new ArdourButton (string_compose (_("Setting for button %1"), btn_idx+1),
167 								     ArdourButton::Element(ArdourButton::Indicator|ArdourButton::Text|ArdourButton::Inactive)));
168 		table->attach (*b, 0, 2, btn_idx, btn_idx+1);
169 		_btn_leds.push_back (b);
170 
171 		ButtonConfigWidget* bcw = manage (new ButtonConfigWidget);
172 
173 		boost::shared_ptr<ButtonBase> btn_act = _ccp.get_button_action (btn_idx);
174 		assert (btn_act);
175 		bcw->set_current_config (btn_act);
176 
177 		bcw->Changed.connect (sigc::bind (sigc::mem_fun (*this, &ContourDesignGUI::update_action), btn_idx, bcw));
178 		table->attach (*bcw, 3, 5, btn_idx, btn_idx+1);
179 
180 		if (btn_idx > 3 && btn_idx < 9) {
181 			this->XpressButtonsSensitive.connect (sigc::mem_fun (*b, &ArdourButton::set_sensitive));
182 			this->XpressButtonsSensitive.connect (sigc::mem_fun (*bcw, &ButtonConfigWidget::set_sensitive));
183 		} else {
184 			this->ProButtonsSensitive.connect (sigc::mem_fun (*b, &ArdourButton::set_sensitive));
185 			this->ProButtonsSensitive.connect (sigc::mem_fun (*bcw, &ButtonConfigWidget::set_sensitive));
186 		}
187 	}
188 
189 	set_spacing (6);
190 	btn_action_box->pack_start (*table, false, false);
191 
192 	HBox* top_box = manage (new HBox);
193 	top_box->pack_start (*dg_sample);
194 	top_box->pack_start (*sj_sample);
195 	pack_start (*top_box);
196 	pack_start (*btn_action_sample);
197 
198 	_ccp.ButtonPress.connect (*this, invalidator (*this), boost::bind (&ContourDesignGUI::test_button_press, this, _1), gui_context ());
199 	_ccp.ButtonRelease.connect (*this, invalidator (*this), boost::bind (&ContourDesignGUI::test_button_release, this, _1), gui_context ());
200 
201 	signal_map().connect (sigc::mem_fun (*this, &ContourDesignGUI::init_on_show));
202 	update_device_state ();
203 }
204 
205 void
toggle_keep_rolling()206 ContourDesignGUI::toggle_keep_rolling ()
207 {
208 	_ccp.set_keep_rolling (_keep_rolling.get_active ());
209 }
210 
211 void
set_shuttle_speed(int index)212 ContourDesignGUI::set_shuttle_speed (int index)
213 {
214 	double speed = _shuttle_speed_adjustments[index]->get_value ();
215 	_ccp.set_shuttle_speed (index, speed);
216 }
217 
218 void
update_jog_distance()219 ContourDesignGUI::update_jog_distance ()
220 {
221 	_ccp.set_jog_distance (_jog_distance.get_distance ());
222 }
223 
224 void
update_action(unsigned int index,ButtonConfigWidget * sender)225 ContourDesignGUI::update_action (unsigned int index, ButtonConfigWidget* sender)
226 {
227 	_ccp.set_button_action (index, sender->get_current_config (_ccp));
228 }
229 
230 void
toggle_test_mode()231 ContourDesignGUI::toggle_test_mode ()
232 {
233 	bool testmode = ! _ccp.test_mode(); // toggle
234 	_ccp.set_test_mode (testmode);
235 	if (testmode) {
236 		_test_button.set_active_state (Gtkmm2ext::ExplicitActive);
237 	} else {
238 		reset_test_state ();
239 	}
240 }
241 
242 void
init_on_show()243 ContourDesignGUI::init_on_show ()
244 {
245 	Gtk::Widget* p = get_parent();
246 	if (p) {
247 		p->signal_delete_event().connect (sigc::mem_fun (*this, &ContourDesignGUI::reset_test_state));
248 	}
249 }
250 
251 bool
reset_test_state(GdkEventAny *)252 ContourDesignGUI::reset_test_state (GdkEventAny*)
253 {
254 	_ccp.set_test_mode (false);
255 	_test_button.set_active (Gtkmm2ext::Off);
256 	vector<boost::shared_ptr<ArdourButton> >::const_iterator it;
257 	for (it = _btn_leds.begin(); it != _btn_leds.end(); ++it) {
258 		(*it)->set_active_state (Gtkmm2ext::Off);
259 	}
260 
261 	return false;
262 }
263 
264 void
test_button_press(unsigned short btn)265 ContourDesignGUI::test_button_press (unsigned short btn)
266 {
267 	_btn_leds[btn]->set_active_state (Gtkmm2ext::ExplicitActive);
268 }
269 
270 void
test_button_release(unsigned short btn)271 ContourDesignGUI::test_button_release (unsigned short btn)
272 {
273 	_btn_leds[btn]->set_active_state (Gtkmm2ext::Off);
274 }
275 
276 bool
update_device_state()277 ContourDesignGUI::update_device_state ()
278 {
279 	switch (_ccp.device_type ()) {
280 	case ContourDesignControlProtocol::ShuttlePRO:
281 		_device_state_lbl.set_markup ("<span weight=\"bold\" foreground=\"green\">Found ShuttlePRO</span>");
282 		XpressButtonsSensitive (true);
283 		ProButtonsSensitive (true);
284 		break;
285 	case ContourDesignControlProtocol::ShuttlePRO_v2:
286 		_device_state_lbl.set_markup ("<span weight=\"bold\" foreground=\"green\">Found ShuttlePRO v2</span>");
287 		XpressButtonsSensitive (true);
288 		ProButtonsSensitive (true);
289 		break;
290 	case ContourDesignControlProtocol::ShuttleXpress:
291 		_device_state_lbl.set_markup ("<span weight=\"bold\" foreground=\"green\">Found shuttleXpress</span>");
292 		XpressButtonsSensitive (true);
293 		ProButtonsSensitive (false);
294 		break;
295 	default:
296 		XpressButtonsSensitive (false);
297 		ProButtonsSensitive (false);
298 		_device_state_lbl.set_markup (string_compose ("<span weight=\"bold\" foreground=\"red\">Device not working:</span> %1",
299 		                              libusb_strerror ((libusb_error)_ccp.usb_errorcode ())));
300 	}
301 
302 	return false;
303 }
304 
305 void*
get_gui() const306 ContourDesignControlProtocol::get_gui () const
307 {
308 	if (!_gui) {
309 		const_cast<ContourDesignControlProtocol*>(this)->build_gui ();
310 	}
311 	_gui->show_all();
312 	return (void*) _gui;
313 }
314 
315 void
build_gui()316 ContourDesignControlProtocol::build_gui ()
317 {
318 	_gui = new ContourDesignGUI (*this);
319 }
320 
321 void
tear_down_gui()322 ContourDesignControlProtocol::tear_down_gui ()
323 {
324 	if (_gui) {
325 		Gtk::Widget *w = _gui->get_parent();
326 		if (w) {
327 			w->hide();
328 			delete w;
329 		}
330 	}
331 	delete _gui;
332 	_gui = 0;
333 }
334