Cleanup: Fix inconsistent TransformDialog code style
[ardour.git] / gtk2_ardour / transform_dialog.cc
1 /*
2    Copyright (C) 2009-2014 Paul Davis
3    Author: David Robillard
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., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <gtkmm/box.h>
21 #include <gtkmm/label.h>
22 #include <gtkmm/stock.h>
23
24 #include "transform_dialog.h"
25
26 #include "pbd/i18n.h"
27
28 using namespace std;
29 using namespace Gtk;
30 using namespace ARDOUR;
31
32 TransformDialog::Model::Model()
33         : source_list(Gtk::ListStore::create(source_cols))
34         , property_list(Gtk::ListStore::create(property_cols))
35         , operator_list(Gtk::ListStore::create(operator_cols))
36 {
37         static const char* source_labels[] = {
38                 /* no NOTHING */
39                 _("this note's"),
40                 _("the previous note's"),
41                 _("this note's index"),
42                 _("the number of notes"),
43                 _("exactly"),
44                 _("a random number from"),
45                 NULL
46         };
47         for (int s = 0; source_labels[s]; ++s) {
48                 Gtk::TreeModel::Row row = *(source_list->append());
49                 row[source_cols.source] = (Source)(s + 1);  // Skip NOTHING
50                 row[source_cols.label]  = source_labels[s];
51         }
52         // Special row for ramp, which doesn't correspond to a source
53         Gtk::TreeModel::Row row = *(source_list->append());
54         row[source_cols.source] = Value::NOWHERE;
55         row[source_cols.label]  = _("equal steps from");
56
57         static const char* property_labels[] = {
58                 _("note number"),
59                 _("velocity"),
60                 _("start time"),
61                 _("length"),
62                 _("channel"),
63                 NULL
64         };
65         for (int p = 0; property_labels[p]; ++p) {
66                 Gtk::TreeModel::Row row = *(property_list->append());
67                 row[property_cols.property] = (Property)p;
68                 row[property_cols.label]    = property_labels[p];
69         }
70
71         static const char* operator_labels[] = {
72                 /* no PUSH */ "+", "-", "*", "/", "mod", NULL
73         };
74         for (int o = 0; operator_labels[o]; ++o) {
75                 Gtk::TreeModel::Row row = *(operator_list->append());
76                 row[operator_cols.op]    = (Operator)(o + 1);  // Skip PUSH
77                 row[operator_cols.label] = operator_labels[o];
78         }
79 }
80
81 TransformDialog::TransformDialog()
82         : ArdourDialog(_("Transform"), false, false)
83 {
84         _property_combo.set_model(_model.property_list);
85         _property_combo.pack_start(_model.property_cols.label);
86         _property_combo.set_active(1);
87         _property_combo.signal_changed().connect(
88                 sigc::mem_fun(this, &TransformDialog::property_changed));
89
90         Gtk::HBox* property_hbox = Gtk::manage(new Gtk::HBox);
91         property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_("Set "))), false, false);
92         property_hbox->pack_start(_property_combo, false, false);
93         property_hbox->pack_start(*Gtk::manage(new Gtk::Label(_(" to "))), false, false);
94
95         _seed_chooser = Gtk::manage(new ValueChooser(_model));
96         _seed_chooser->set_target_property(MidiModel::NoteDiffCommand::Velocity);
97         _seed_chooser->source_combo.set_active(0);
98         property_hbox->pack_start(*_seed_chooser, false, false);
99
100         Gtk::HBox* add_hbox = Gtk::manage(new Gtk::HBox);
101         _add_button.add(
102                 *manage(new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON)));
103         add_hbox->pack_start(_add_button, false, false);
104         _add_button.signal_clicked().connect(
105                 sigc::mem_fun(*this, &TransformDialog::add_clicked));
106
107         get_vbox()->set_spacing(6);
108         get_vbox()->pack_start(*property_hbox, false, false);
109         get_vbox()->pack_start(_operations_box, false, false);
110         get_vbox()->pack_start(*add_hbox, false, false);
111
112         add_button(Stock::CANCEL, Gtk::RESPONSE_CANCEL);
113         add_button(_("Transform"), Gtk::RESPONSE_OK);
114
115         show_all();
116         _seed_chooser->value_spinner.hide();
117         _seed_chooser->source_changed();
118 }
119
120 TransformDialog::ValueChooser::ValueChooser(const Model& model)
121         : model(model)
122         , target_property((Property)1)
123         , to_label(" to ")
124 {
125         source_combo.set_model(model.source_list);
126         source_combo.pack_start(model.source_cols.label);
127         source_combo.signal_changed().connect(
128                 sigc::mem_fun(this, &TransformDialog::ValueChooser::source_changed));
129
130         property_combo.set_model(model.property_list);
131         property_combo.pack_start(model.property_cols.label);
132
133         set_spacing(4);
134         pack_start(source_combo, false, false);
135         pack_start(property_combo, false, false);
136         pack_start(value_spinner, false, false);
137         pack_start(to_label, false, false);
138         pack_start(max_spinner, false, false);
139         show_all();
140
141         source_combo.set_active(4);
142         property_combo.set_active(1);
143         set_target_property(MidiModel::NoteDiffCommand::Velocity);
144         max_spinner.set_value(127);
145         source_changed();
146 }
147
148 static void
149 set_spinner_for(Gtk::SpinButton&                     spinner,
150                 MidiModel::NoteDiffCommand::Property prop)
151 {
152         switch (prop) {
153         case MidiModel::NoteDiffCommand::NoteNumber:
154         case MidiModel::NoteDiffCommand::Velocity:
155                 spinner.get_adjustment()->set_lower(1); // no 0, note off
156                 spinner.get_adjustment()->set_upper(127);
157                 spinner.get_adjustment()->set_step_increment(1);
158                 spinner.get_adjustment()->set_page_increment(10);
159                 spinner.set_digits(0);
160                 break;
161         case MidiModel::NoteDiffCommand::StartTime:
162                 spinner.get_adjustment()->set_lower(0);
163                 spinner.get_adjustment()->set_upper(1024);
164                 spinner.get_adjustment()->set_step_increment(0.125);
165                 spinner.get_adjustment()->set_page_increment(1.0);
166                 spinner.set_digits(2);
167                 break;
168         case MidiModel::NoteDiffCommand::Length:
169                 spinner.get_adjustment()->set_lower(1.0 / 64.0);
170                 spinner.get_adjustment()->set_upper(32);
171                 spinner.get_adjustment()->set_step_increment(1.0 / 64.0);
172                 spinner.get_adjustment()->set_page_increment(1.0);
173                 spinner.set_digits(2);
174                 break;
175         case MidiModel::NoteDiffCommand::Channel:
176                 spinner.get_adjustment()->set_lower(1);
177                 spinner.get_adjustment()->set_upper(16);
178                 spinner.get_adjustment()->set_step_increment(1);
179                 spinner.get_adjustment()->set_page_increment(10);
180                 spinner.set_digits(0);
181                 break;
182         }
183         spinner.set_value(
184                 std::min(spinner.get_adjustment()->get_upper(),
185                          std::max(spinner.get_adjustment()->get_lower(), spinner.get_value())));
186 }
187
188 void
189 TransformDialog::ValueChooser::set_target_property(Property prop)
190 {
191         target_property = prop;
192         set_spinner_for(value_spinner, prop);
193         set_spinner_for(max_spinner, prop);
194 }
195
196 void
197 TransformDialog::ValueChooser::source_changed()
198 {
199         Gtk::TreeModel::const_iterator s      = source_combo.get_active();
200         const Source                   source = (*s)[model.source_cols.source];
201
202         value_spinner.hide();
203         to_label.hide();
204         max_spinner.hide();
205         if (source == Value::LITERAL) {
206                 value_spinner.show();
207                 property_combo.hide();
208         } else if (source == Value::RANDOM) {
209                 value_spinner.show();
210                 to_label.show();
211                 max_spinner.show();
212                 property_combo.hide();
213         } else if (source == Value::NOWHERE) {
214                 /* Bit of a kludge, hijack this for ramps since it's the only thing
215                    that doesn't correspond to a source.  When we add more fancy
216                    code-generating value chooser options, the column model will need to
217                    be changed a bit to reflect this. */
218                 value_spinner.show();
219                 to_label.show();
220                 max_spinner.show();
221                 property_combo.hide();
222         } else if (source == Value::INDEX || source == Value::N_NOTES) {
223                 value_spinner.hide();
224                 property_combo.hide();
225         } else {
226                 value_spinner.hide();
227                 property_combo.show();
228         }
229 }
230
231 double
232 TransformDialog::ValueChooser::get_value() const
233 {
234         const bool is_channel = target_property == MidiModel::NoteDiffCommand::Channel;
235         return value_spinner.get_value() + (is_channel ? -1.0 : 0.0);
236 }
237
238 double
239 TransformDialog::ValueChooser::get_max() const
240 {
241         const bool is_channel = target_property == MidiModel::NoteDiffCommand::Channel;
242         return max_spinner.get_value() + (is_channel ? -1.0 : 0.0);
243 }
244
245 void
246 TransformDialog::ValueChooser::get(std::list<Operation>& ops)
247 {
248         Gtk::TreeModel::const_iterator s      = source_combo.get_active();
249         const Source                   source = (*s)[model.source_cols.source];
250
251         if (source == Transform::Value::RANDOM) {
252                 /* Special case: a RANDOM value is always 0..1, so here we produce some
253                    code to produce a random number in a range: "rand value *". */
254                 const double a     = get_value();
255                 const double b     = get_max();
256                 const double min   = std::min(a, b);
257                 const double max   = std::max(a, b);
258                 const double range = max - min;
259
260                 // "rand range * min +" ((rand * range) + min)
261                 ops.push_back(Operation(Operation::PUSH, Value(Value::RANDOM)));
262                 ops.push_back(Operation(Operation::PUSH, Value(range)));
263                 ops.push_back(Operation(Operation::MULT));
264                 ops.push_back(Operation(Operation::PUSH, Value(min)));
265                 ops.push_back(Operation(Operation::ADD));
266                 return;
267         } else if (source == Transform::Value::NOWHERE) {
268                 /* Special case: hijack NOWHERE for ramps (see above).  The language
269                    knows nothing of ramps, we generate code to calculate the
270                    appropriate value here. */
271                 const double first = get_value();
272                 const double last  = get_max();
273                 const double rise  = last - first;
274
275                 // "index rise * n_notes 1 - / first +" (index * rise / (n_notes - 1) + first)
276                 ops.push_back(Operation(Operation::PUSH, Value(Value::INDEX)));
277                 ops.push_back(Operation(Operation::PUSH, Value(rise)));
278                 ops.push_back(Operation(Operation::MULT));
279                 ops.push_back(Operation(Operation::PUSH, Value(Value::N_NOTES)));
280                 ops.push_back(Operation(Operation::PUSH, Value(1)));
281                 ops.push_back(Operation(Operation::SUB));
282                 ops.push_back(Operation(Operation::DIV));
283                 ops.push_back(Operation(Operation::PUSH, Value(first)));
284                 ops.push_back(Operation(Operation::ADD));
285                 return;
286         }
287
288         // Produce a simple Value
289         Value val((*s)[model.source_cols.source]);
290         if (val.source == Transform::Value::THIS_NOTE ||
291             val.source == Transform::Value::PREV_NOTE) {
292                 Gtk::TreeModel::const_iterator p = property_combo.get_active();
293                 val.prop = (*p)[model.property_cols.property];
294         } else if (val.source == Transform::Value::LITERAL) {
295                 val.value = Variant(
296                         MidiModel::NoteDiffCommand::value_type(target_property),
297                         get_value());
298         }
299         ops.push_back(Operation(Operation::PUSH, val));
300 }
301
302 TransformDialog::OperationChooser::OperationChooser(const Model& model)
303         : model(model)
304         , value_chooser(model)
305 {
306         operator_combo.set_model(model.operator_list);
307         operator_combo.pack_start(model.operator_cols.label);
308         operator_combo.set_active(0);
309
310         pack_start(operator_combo, false, false);
311         pack_start(value_chooser, false, false);
312         pack_start(*Gtk::manage(new Gtk::Label(" ")), true, true);
313         pack_start(remove_button, false, false);
314
315         remove_button.add(
316                 *manage(new Gtk::Image(Gtk::Stock::REMOVE, Gtk::ICON_SIZE_BUTTON)));
317
318         remove_button.signal_clicked().connect(
319                 sigc::mem_fun(*this, &TransformDialog::OperationChooser::remove_clicked));
320
321         value_chooser.source_combo.set_active(0);
322
323         show_all();
324         value_chooser.property_combo.hide();
325         value_chooser.value_spinner.set_value(1);
326         value_chooser.source_changed();
327 }
328
329 void
330 TransformDialog::OperationChooser::get(std::list<Operation>& ops)
331 {
332         Gtk::TreeModel::const_iterator o = operator_combo.get_active();
333
334         value_chooser.get(ops);
335         ops.push_back(Operation((*o)[model.operator_cols.op]));
336 }
337
338 void
339 TransformDialog::OperationChooser::remove_clicked()
340 {
341         delete this;
342 }
343
344 Transform::Program
345 TransformDialog::get()
346 {
347         Transform::Program prog;
348
349         // Set target property
350         prog.prop = (*_property_combo.get_active())[_model.property_cols.property];
351
352         // Append code to push seed to stack
353         _seed_chooser->get(prog.ops);
354
355         // Append all operations' code to program
356         const std::vector<Gtk::Widget*>& choosers = _operations_box.get_children();
357         for (std::vector<Gtk::Widget*>::const_iterator o = choosers.begin();
358              o != choosers.end(); ++o) {
359                 OperationChooser* chooser = dynamic_cast<OperationChooser*>(*o);
360                 if (chooser) {
361                         chooser->get(prog.ops);
362                 }
363         }
364
365         return prog;
366 }
367
368 void
369 TransformDialog::property_changed()
370 {
371         Gtk::TreeModel::const_iterator i = _property_combo.get_active();
372         _seed_chooser->set_target_property((*i)[_model.property_cols.property]);
373 }
374
375 void
376 TransformDialog::add_clicked()
377 {
378         _operations_box.pack_start(
379                 *Gtk::manage(new OperationChooser(_model)), false, false);
380 }