1 /*
2  * Copyright (C) 2009, 2010 Hermann Meyer, James Warden, Andreas Degert
3  * Copyright (C) 2011 Pete Shorthose
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  * ---------------------------------------------------------------------------
19  *
20  *    This file is part of the guitarix GUI main class
21  *
22  * ----------------------------------------------------------------------------
23  */
24 
25 #include "guitarix.h"     // NOLINT
26 
27 /****************************************************************
28  ** MidiControllerTable
29  */
30 namespace gx_main_midi {
31 
32 GtkWidget *MidiControllerTable::window = 0;
33 
response_cb(GtkWidget * widget,gint response_id,gpointer data)34 void MidiControllerTable::response_cb(GtkWidget *widget, gint response_id, gpointer data) {
35     MidiControllerTable& m = *reinterpret_cast<MidiControllerTable*>(data);
36     if (response_id == RESPONSE_DELETE_SELECTED) {
37         GtkTreeModel *model;
38         GList *list = gtk_tree_selection_get_selected_rows(m.selection, &model);
39         gtk_tree_selection_unselect_all(m.selection);
40         for (GList *p = g_list_last(list); p; p = g_list_previous(p)) {
41             GtkTreeIter iter;
42             gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter,
43                                     reinterpret_cast<GtkTreePath*>(p->data));
44             const char* id;
45             gtk_tree_model_get(GTK_TREE_MODEL(model), &iter, 7, &id, -1);
46 	    m.midi_conn.block();
47             m.machine.midi_deleteParameter(m.machine.get_parameter(id));
48 	    m.midi_conn.unblock();
49             gtk_tree_path_free(reinterpret_cast<GtkTreePath*>(p->data));
50         }
51         g_list_free(list);
52 	m.machine.signal_midi_changed()();
53         return;
54     }
55     m.menuaction->set_active(false);
56 }
57 
destroy_cb(GtkWidget *,gpointer data)58 void MidiControllerTable::destroy_cb(GtkWidget*, gpointer data) {
59     delete reinterpret_cast<MidiControllerTable*>(data);
60 }
61 
edited_cb(GtkCellRendererText * renderer,gchar * path,gchar * new_text,gpointer data)62 void  MidiControllerTable::edited_cb(
63     GtkCellRendererText *renderer, gchar *path, gchar *new_text, gpointer data) {
64     GtkListStore *store = GTK_LIST_STORE(data);
65     GtkTreeIter iter;
66     gtk_tree_model_get_iter_from_string(GTK_TREE_MODEL(store), &iter, path);
67     int ctrl;
68     gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &ctrl, -1);
69     gx_engine::midi_std_ctr.replace(ctrl, new_text);
70     bool valid = gtk_tree_model_get_iter_first(GTK_TREE_MODEL(store), &iter);
71     std::string name = gx_engine::midi_std_ctr[ctrl];
72     while (valid) {
73         int n;
74         gtk_tree_model_get(GTK_TREE_MODEL(store), &iter, 0, &n, -1);
75         if (n == ctrl) {
76             gtk_list_store_set(store, &iter, 1, name.c_str(), -1);
77         }
78         valid = gtk_tree_model_iter_next(GTK_TREE_MODEL(store), &iter);
79     }
80 }
81 
toggleButtonSetSwitch(GtkWidget * w,gpointer data)82 void MidiControllerTable::toggleButtonSetSwitch(GtkWidget *w, gpointer data) {
83     gx_engine::BoolParameter *p = (gx_engine::BoolParameter*)data;
84     p->set(gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)));
85 }
86 
set(bool v)87 void MidiControllerTable::set(bool v) {
88     gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(togglebutton), v);
89 }
90 
load()91 void MidiControllerTable::load() {
92     GtkTreeIter iter;
93     gtk_list_store_clear(store);
94     for (int i = 0; i < machine.midi_size(); i++) {
95         gx_engine::midi_controller_list& cl = machine.midi_get(i);
96         for (gx_engine::midi_controller_list::iterator j = cl.begin(); j != cl.end(); ++j) {
97             gx_engine::Parameter& p = j->getParameter();
98             string low, up;
99             const char *tp;
100             float step = p.getStepAsFloat();
101             if (p.getControlType() == gx_engine::Parameter::Continuous) {
102                 tp = "Scale";
103                 low = gx_gui::fformat(j->lower(), step);
104                 up = gx_gui::fformat(j->upper(), step);
105             } else if (p.getControlType() == gx_engine::Parameter::Enum) {
106                 tp = "Select";
107                 low = gx_gui::fformat(j->lower(), step);
108                 up = gx_gui::fformat(j->upper(), step);
109             } else if (p.getControlType() == gx_engine::Parameter::Switch) {
110 		if (j->is_toggle()) {
111 		    tp = "Toggle";
112 		} else {
113 		    tp = "Switch";
114 		}
115                 low = up = "";
116             } else {
117                 tp = "??";
118                 assert(false);
119             }
120             gtk_list_store_append(store, &iter);
121             gtk_list_store_set(store, &iter,
122                                0, i,
123                                1, gx_engine::midi_std_ctr[i].c_str(),
124                                2, p.l_group().c_str(),
125                                3, p.l_name().c_str(),
126                                4, tp,
127                                5, low.c_str(),
128                                6, up.c_str(),
129                                7, p.id().c_str(),
130                                -1);
131         }
132     }
133 }
134 
toggle(gx_engine::GxMachineBase & machine,Glib::RefPtr<ToggleAction> item)135 void MidiControllerTable::toggle(gx_engine::GxMachineBase& machine, Glib::RefPtr<ToggleAction> item) {
136     if (!item->get_active()) {
137         if (window) {
138             gtk_widget_destroy(window);
139         }
140     } else {
141         if (!window) {
142             new MidiControllerTable(machine, item);
143         }
144     }
145 }
146 
~MidiControllerTable()147 MidiControllerTable::~MidiControllerTable() {
148     window = NULL;
149 }
150 
MidiControllerTable(gx_engine::GxMachineBase & machine_,Glib::RefPtr<ToggleAction> item)151 MidiControllerTable::MidiControllerTable(gx_engine::GxMachineBase& machine_, Glib::RefPtr<ToggleAction> item)
152     : menuaction(item),
153       machine(machine_),
154       midi_conn() {
155 
156     GtkBuilder * builder = gtk_builder_new();
157     window = gx_gui::load_toplevel(builder, "midi.glade", "MidiControllerTable");
158     store = GTK_LIST_STORE(gtk_builder_get_object(builder, "liststore1"));
159     togglebutton = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "save_controller"));
160 
161     gx_engine::BoolParameter& p = machine.get_parameter("system.midi_in_preset").getBool();
162     gtk_toggle_button_set_active(togglebutton, p.get_value());
163     machine.signal_parameter_value<bool>("system.midi_in_preset").connect(sigc::mem_fun(*this, &MidiControllerTable::set));
164     g_signal_connect(G_OBJECT(togglebutton), "toggled",
165                      G_CALLBACK(toggleButtonSetSwitch), (gpointer)&p);
166     //g_signal_connect(gtk_builder_get_object(builder, "dialog-vbox1"),"expose-event",
167                      //G_CALLBACK(gx_cairo::rectangle_skin_color_expose), NULL);
168     //g_signal_connect(gtk_builder_get_object(builder, "dialog-vbox2"),"expose-event",
169                      //G_CALLBACK(gx_cairo::rectangle_skin_color_expose), NULL);
170     selection = gtk_tree_view_get_selection(
171         GTK_TREE_VIEW(gtk_builder_get_object(builder, "treeview1")));
172     gtk_tree_selection_set_mode(selection, GTK_SELECTION_MULTIPLE);
173     gtk_widget_set_redraw_on_allocate(GTK_WIDGET(gtk_builder_get_object(builder, "dialog-vbox1")),true);
174     gtk_widget_set_redraw_on_allocate(GTK_WIDGET(gtk_builder_get_object(builder, "dialog-vbox2")),true);
175     load();
176 
177     g_signal_connect(window, "destroy", G_CALLBACK(destroy_cb), this);
178     g_signal_connect(window, "response", G_CALLBACK(response_cb), this);
179     g_signal_connect(G_OBJECT(gtk_builder_get_object(builder, "cellrenderertext2")),
180                      "edited", G_CALLBACK(edited_cb), store);
181 
182     //gtk_window_add_accel_group(GTK_WINDOW(window),
183     //                           gx_gui::GxMainInterface::get_instance().fAccelGroup->gobj());
184 
185     gtk_widget_show(window);
186     g_object_unref(G_OBJECT(builder));
187     midi_conn = machine.signal_midi_changed().connect(
188 	sigc::mem_fun(*this, &MidiControllerTable::load));
189 }
190 
191 
192 /*****************************************************************
193  ** Midi Control
194  */
195 
ctr_desc(int ctr)196 string MidiConnect::ctr_desc(int ctr) {
197     string p = gx_engine::midi_std_ctr[ctr];
198     if (p.empty())
199         return p;
200     return "(" + p + ")";
201 }
202 
203 
midi_response_cb(GtkWidget * widget,gint response_id,gpointer data)204 void MidiConnect::midi_response_cb(GtkWidget *widget, gint response_id, gpointer data) {
205     MidiConnect* m = reinterpret_cast<MidiConnect*>(data);
206     switch (response_id) {
207     case GTK_RESPONSE_OK:
208         if (m->param.getControlType() == gx_engine::Parameter::Continuous ||
209             m->param.getControlType() == gx_engine::Parameter::Enum) {
210             assert(m->adj_lower);
211             assert(m->adj_upper);
212             float lower = gtk_adjustment_get_value(m->adj_lower);
213             float upper = gtk_adjustment_get_value(m->adj_upper);
214             m->machine.midi_modifyCurrent(m->param, lower, upper, false, gx_engine::Parameter::toggle_type::OnOff);
215         } else {
216             bool toggle = gtk_toggle_button_get_active(m->use_toggle);
217             int toggle_behaviour = gtk_combo_box_get_active(GTK_COMBO_BOX(m->toggle_behaviours));
218             m->machine.midi_modifyCurrent(m->param, 0, 0, toggle, toggle_behaviour);
219         }
220         break;
221     case RESPONSE_DELETE:
222         m->machine.midi_deleteParameter(m->param);
223         break;
224     case GTK_RESPONSE_HELP:
225         static string midiconnecthelp;
226     if (midiconnecthelp.empty()) {
227         midiconnecthelp +=_("\n     Guitarix:Midi learn \n");
228         midiconnecthelp +=
229             _("    Just move your midi controller to connect it \n"
230    "    with the selected guitarix Controller. \n"
231    "    As soon the Midi Controller is detected,  \n"
232    "    you will see the Controller Number in the   \n"
233    "    Midi Controller Number field. Press 'OK' to connect it,   \n"
234    "    or move a other Midi controller.  \n"
235    "    A exception is the MIDI BEAT CLOCK, \n"
236    "    which isn't a Controller itself,\n"
237    "    but could be used here to sync BPM controllers    \n"
238    "    with external devices.  \n"
239    "    To use it, you must insert '22' as Midi Controller Number   \n"
240    "      \n"
241    "    The same is true for the MIDI CLOCK start/stop function,    \n"
242    "    which could be used to switch effects on/off.   \n"
243    "    To use it, you must insert '23' as Midi Controller Number.   \n\n"
244    "    Also Jack Transport is supported and can be used to control    \n"
245    "    switch controllers (on/off) by connect then to    \n"
246    "    Midi Controller Number '24'.    \n"
247 
248           "");
249 
250     }
251 
252     gx_gui::gx_message_popup(midiconnecthelp.c_str());
253         return;
254         break;
255     }
256     gtk_widget_destroy(m->dialog);
257 }
258 
midi_destroy_cb(GtkWidget * widget,gpointer data)259 void MidiConnect::midi_destroy_cb(GtkWidget *widget, gpointer data) {
260     MidiConnect *m = reinterpret_cast<MidiConnect*>(data);
261     m->machine.midi_set_config_mode(false);
262 }
263 
toggle_behaviours_visibility(GtkWidget * widget,gpointer data)264 void MidiConnect::toggle_behaviours_visibility(GtkWidget *widget, gpointer data) {
265     MidiConnect *m = reinterpret_cast<MidiConnect*>(data);
266     int b = gtk_toggle_button_get_active(m->use_toggle);
267     gtk_widget_set_sensitive(gtk_widget_get_parent(m->toggle_behaviours), b);
268 }
269 
ctl_to_str(int n)270 const char* MidiConnect::ctl_to_str(int n) {
271     static char buf[12];
272     if (n < 0)
273         strcpy(buf, "---");     //  NOLINT
274     else
275         snprintf(buf, sizeof(buf), "%3d", n);
276     return buf;
277 }
278 
check_midi_cb(gpointer data)279 gboolean MidiConnect::check_midi_cb(gpointer data) {
280     MidiConnect *m = reinterpret_cast<MidiConnect*>(data);
281     int ctl;
282     if (!m->machine.midi_get_config_mode(&ctl)) {
283 	delete m;
284         return FALSE;
285     }
286     if (m->current_control == ctl)
287         return TRUE;
288     m->current_control = ctl;
289     gtk_entry_set_text(GTK_ENTRY(m->entry_new), ctl_to_str(ctl));
290     if ( ctl>200) {
291         gtk_toggle_button_set_active(m->use_toggle, true);
292         gtk_combo_box_set_active(GTK_COMBO_BOX(m->toggle_behaviours), gx_engine::Parameter::toggle_type::Constant);
293     }
294     return TRUE;
295 }
296 
changed_text_handler(GtkEditable * editable,gpointer data)297 void MidiConnect::changed_text_handler(GtkEditable *editable, gpointer data) {
298     MidiConnect *m = reinterpret_cast<MidiConnect*>(data);
299     gchar *p = gtk_editable_get_chars(editable, 0, -1);
300     ostringstream buf;
301     for (const char *q = p; *q; q++) {
302         if (isdigit(*q)) {
303             buf << *q;
304         }
305     }
306     string str = buf.str();
307     int n = -1;
308     if (!str.empty()) {
309         istringstream i(buf.str());
310         i >> n;
311         if (n > 327) {
312             n = 327;
313         }
314         ostringstream b;
315         b << n;
316         str = b.str().substr(0, 3);
317     }
318     // prevent infinite loop because after it has changed the text
319     // the handler will be called again (and make sure the text
320     // transformation in this handler is idempotent!)
321     if (str == p) {
322         if (str.empty()) {
323             gtk_dialog_set_response_sensitive(GTK_DIALOG(m->dialog), GTK_RESPONSE_OK, FALSE);
324             gtk_dialog_set_default_response(GTK_DIALOG(m->dialog), GTK_RESPONSE_CANCEL);
325         } else {
326             gtk_dialog_set_response_sensitive(GTK_DIALOG(m->dialog), GTK_RESPONSE_OK, TRUE);
327             gtk_dialog_set_default_response(GTK_DIALOG(m->dialog), GTK_RESPONSE_OK);
328         }
329         gtk_label_set_text(GTK_LABEL(m->label_desc), ctr_desc(n).c_str());
330         m->machine.midi_set_current_control(n);
331         m->current_control = n;
332         return;
333     }
334     gtk_editable_delete_text(editable, 0, -1);
335     gint position = 0;
336     gtk_editable_insert_text(editable, str.c_str(), str.size(), &position);
337 }
338 
339 
MidiConnect(GdkEventButton * event,gx_engine::Parameter & param_,gx_engine::GxMachineBase & machine_)340 MidiConnect::MidiConnect(GdkEventButton *event, gx_engine::Parameter &param_, gx_engine::GxMachineBase& machine_)
341     : param(param_),
342       machine(machine_),
343       current_control(-1),
344       adj_lower(),
345       adj_upper() {
346     GtkBuilder * builder = gtk_builder_new();
347     dialog = gx_gui::load_toplevel(builder, "midi.glade", "MidiConnect");
348     use_toggle = GTK_TOGGLE_BUTTON(gtk_builder_get_object(builder, "use_toggle"));
349     toggle_behaviours = GTK_WIDGET(gtk_builder_get_object(builder, "toggle_behaviours"));
350     GtkWidget *zn = GTK_WIDGET(gtk_builder_get_object(builder, "zone_name"));
351     gtk_label_set_text(GTK_LABEL(zn), (param.l_group() + ": " + param.l_name()).c_str());
352     gtk_widget_set_tooltip_text(zn, (_("Parameter ID: ")+param.id()).c_str());
353     zn = GTK_WIDGET(gtk_builder_get_object(builder, "desc_box"));
354     if (param.desc().empty()) {
355 	gtk_widget_hide(zn);
356     } else {
357 	GtkWidget *desc = GTK_WIDGET(gtk_builder_get_object(builder, "desc"));
358 	gtk_label_set_text(GTK_LABEL(desc), param.l_desc().c_str());
359 	gtk_widget_show(zn);
360     }
361     const gx_engine::MidiController *pctrl;
362     int nctl = machine.midi_param2controller(param, &pctrl);
363     if (param.getControlType() == gx_engine::Parameter::Continuous ||
364         param.getControlType() == gx_engine::Parameter::Enum) {
365         float lower = param.getLowerAsFloat();
366         float upper = param.getUpperAsFloat();
367         float step = param.getStepAsFloat();
368         GtkSpinButton *spinner;
369         adj_lower = GTK_ADJUSTMENT(gtk_adjustment_new(lower, lower, upper, step, 10*step, 0));
370         spinner = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "lower"));
371         float climb_rate = 0.0;
372         gtk_spin_button_configure(spinner, adj_lower, climb_rate, gx_gui::precision(step));
373         adj_upper = GTK_ADJUSTMENT(gtk_adjustment_new(upper, lower, upper, step, 10*step, 0));
374         spinner = GTK_SPIN_BUTTON(gtk_builder_get_object(builder, "upper"));
375         gtk_spin_button_configure(spinner, adj_upper, climb_rate, gx_gui::precision(step));
376         if (nctl != -1) {
377             gtk_adjustment_set_value(adj_lower, pctrl->lower());
378             gtk_adjustment_set_value(adj_upper, pctrl->upper());
379         }
380         gtk_widget_hide(gtk_widget_get_parent(gtk_widget_get_parent(GTK_WIDGET(use_toggle))));
381     } else {
382         gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, "range_label")));
383         gtk_widget_hide(GTK_WIDGET(gtk_builder_get_object(builder, "range_box")));
384 
385         store = GTK_LIST_STORE(gtk_builder_get_object(builder, "liststore2"));
386         GtkTreeIter iter;
387         gtk_list_store_clear(store);
388         for (std::map<gx_engine::Parameter::toggle_type, const char*>::iterator it = toggle_behaviour_descriptions.begin();
389                it!=toggle_behaviour_descriptions.end(); ++it) {
390                 gtk_list_store_append(store, &iter);
391                 gtk_list_store_set(store, &iter, 0, it->first, 1, it->second, -1);
392         }
393         gtk_combo_box_set_model(GTK_COMBO_BOX(toggle_behaviours), GTK_TREE_MODEL(store));
394         //g_object_unref(store); // this THROW A GLib-GObject-CRITICAL WARNING
395         GtkCellRenderer *renderer = gtk_cell_renderer_text_new();
396         gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(toggle_behaviours), renderer, TRUE);
397         gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(toggle_behaviours), renderer, "text", 1, NULL); // "cell-background", 0, -> throw Gtk-WARNING **: Don't know color `0'
398         gtk_combo_box_set_active(GTK_COMBO_BOX(toggle_behaviours), gx_engine::Parameter::toggle_type::OnOff);
399         if (nctl != -1) {
400             gtk_toggle_button_set_active(use_toggle, pctrl->is_toggle());
401             gtk_combo_box_set_active(GTK_COMBO_BOX(toggle_behaviours), pctrl->toggle_behaviour());
402         }
403         int b = gtk_toggle_button_get_active(use_toggle);
404         gtk_widget_set_sensitive(gtk_widget_get_parent(toggle_behaviours), b);
405     }
406     entry_new = GTK_WIDGET(gtk_builder_get_object(builder, "new"));
407     label_desc = GTK_WIDGET(gtk_builder_get_object(builder, "new_desc"));
408 
409     g_signal_connect(dialog, "response", G_CALLBACK(midi_response_cb), this);
410     g_signal_connect(dialog, "destroy", G_CALLBACK(midi_destroy_cb), this);
411     g_signal_connect(entry_new, "changed", G_CALLBACK(changed_text_handler), this);
412     g_signal_connect(use_toggle, "toggled", G_CALLBACK(toggle_behaviours_visibility), this);
413     if (nctl == -1) {
414         gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), RESPONSE_DELETE, FALSE);
415         gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_OK, FALSE);
416         gtk_dialog_set_response_sensitive(GTK_DIALOG(dialog), GTK_RESPONSE_HELP, TRUE);
417         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_CANCEL);
418     }
419     machine.midi_set_config_mode(true, nctl);
420     check_midi_cb(this);
421     gtk_widget_show(dialog);
422     g_timeout_add(40, check_midi_cb, this);
423     g_object_unref(G_OBJECT(builder));
424 }
425 } // end namespace
426