/[svn]/gigedit/trunk/src/gigedit/paramedit.cpp
ViewVC logotype

Contents of /gigedit/trunk/src/gigedit/paramedit.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3460 - (show annotations) (download)
Sat Feb 2 07:48:50 2019 UTC (20 months, 2 weeks ago) by persson
File size: 20030 byte(s)
* Use GDK Seat API if available to grab pointer
* Improve version checks for pangomm
* Fix the instrument list tooltip handling so it doesn't generate GTK
  assertion error messages
* Use English quotation marks in tooltips instead of German
* Use multiple columns in controller value popups, as tall popup menus
  work really badly in some GTK environments

1 /*
2 * Copyright (C) 2006-2019 Andreas Persson
3 *
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2, or (at
7 * your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with program; see the file COPYING. If not, write to the Free
16 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
17 * 02110-1301 USA.
18 */
19
20 #include "global.h"
21 #include "paramedit.h"
22
23 #include "compat.h"
24 #include "Settings.h"
25
26 #ifdef GLIBMM_HEADER_FILE
27 # include GLIBMM_HEADER_FILE(glibmm.h)
28 #else
29 # include <glibmm.h>
30 #endif
31
32 #include <gtkmm/messagedialog.h>
33
34 namespace {
35 struct CCText {
36 const char* const txt;
37 bool isExtension; ///< True if this is a controller only supported by LinuxSampler, but not supperted by Gigasampler/GigaStudio.
38 };
39 static const CCText controlChangeTexts[] = {
40 // 3 special ones (not being CCs)
41 { _("none") }, { _("channelaftertouch") }, { _("velocity") },
42 {0}, // bank select MSB (hard coded in sampler, so discouraged to be used here, even though considerable)
43 { _("modwheel") }, // "Modulation Wheel or Lever",
44 { _("breath") }, // "Breath Controller",
45 { _("undefined"), true },
46 { _("foot") }, // "Foot Controller",
47 { _("portamentotime") }, // "Portamento Time",
48 { _("data entry MSB"), true },
49 { _("volume"), true },
50 { _("balance"), true },
51 { _("undefined"), true },
52 { _("pan"), true },
53 { _("expression"), true },
54 { _("effect1") }, // "Effect Control 1",
55 { _("effect2") }, // "Effect Control 2",
56 { _("undefined"), true },
57 { _("undefined"), true },
58 { _("genpurpose1") }, // "General Purpose Controller 1",
59 { _("genpurpose2") }, // "General Purpose Controller 2",
60 { _("genpurpose3") }, // "General Purpose Controller 3",
61 { _("genpurpose4") }, // "General Purpose Controller 4",
62 { _("undefined"), true },
63 { _("undefined"), true },
64 { _("undefined"), true },
65 { _("undefined"), true },
66 { _("undefined"), true },
67 { _("undefined"), true },
68 { _("undefined"), true },
69 { _("undefined"), true },
70 { _("undefined"), true },
71 { _("undefined"), true },
72 { _("undefined"), true },
73 { _("undefined"), true },
74
75 // LSB variant of the various controllers above
76 // (so discouraged to be used here for now)
77 {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0},
78 {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0},
79 {0}, {0}, {0}, {0},
80
81 { _("sustainpedal") }, // "Damper Pedal on/off (Sustain)",
82 { _("portamento") }, // "Portamento On/Off",
83 { _("sostenuto") }, // "Sustenuto On/Off",
84 { _("softpedal") }, // "Soft Pedal On/Off",
85 { _("legato"), true },
86 { _("hold2"), true },
87 { _("soundvariation"), true },
88 { _("timbre"), true },
89 { _("releasetime"), true },
90 { _("attacktime"), true },
91 { _("brightness"), true },
92 { _("decaytime"), true },
93 { _("vibratorate"), true },
94 { _("vibratodepth"), true },
95 { _("vibratodelay"), true },
96 { _("undefined"), true },
97 { _("genpurpose5") }, // "General Purpose Controller 5",
98 { _("genpurpose6") }, // "General Purpose Controller 6",
99 { _("genpurpose7") }, // "General Purpose Controller 7",
100 { _("genpurpose8") }, // "General Purpose Controller 8",
101 { _("portamentoctrl"), true },
102 { _("undefined"), true },
103 { _("undefined"), true },
104 { _("undefined"), true },
105 {0}, // high resolution velocity prefix (so discouraged to be used here)
106 { _("undefined"), true },
107 { _("undefined"), true },
108 { _("effect1depth") }, // "Effects 1 Depth",
109 { _("effect2depth") }, // "Effects 2 Depth",
110 { _("effect3depth") }, // "Effects 3 Depth",
111 { _("effect4depth") }, // "Effects 4 Depth",
112 { _("effect5depth") }, // "Effects 5 Depth"
113 { _("dataincrement"), true },
114 { _("datadecrement"), true },
115 {0}, // NRPN LSB (so discouraged to be used here)
116 {0}, // NRPN MSB (so discouraged to be used here)
117 {0}, // RPN LSB (so discouraged to be used here)
118 {0}, // RPN MSB (so discouraged to be used here)
119 { _("undefined"), true },
120 { _("undefined"), true },
121 { _("undefined"), true },
122 { _("undefined"), true },
123 { _("undefined"), true },
124 { _("undefined"), true },
125 { _("undefined"), true },
126 { _("undefined"), true },
127 { _("undefined"), true },
128 { _("undefined"), true },
129 { _("undefined"), true },
130 { _("undefined"), true },
131 { _("undefined"), true },
132 { _("undefined"), true },
133 { _("undefined"), true },
134 { _("undefined"), true },
135 { _("undefined"), true },
136 { _("undefined"), true } // CC 119
137 // (all other ones that follow [CC 120- CC 127] are hard coded channel
138 // mode messages, so those are discouraged to be used here)
139 };
140 }
141
142 #define controlChangeTextsSize (sizeof(controlChangeTexts) / sizeof(CCText))
143
144 LabelWidget::LabelWidget(const char* labelText, Gtk::Widget& widget) :
145 label(Glib::ustring(labelText) + ":"),
146 widget(widget)
147 {
148 #if HAS_GTKMM_ALIGNMENT
149 label.set_alignment(Gtk::ALIGN_START);
150 #else
151 label.set_halign(Gtk::Align::START);
152 #endif
153 Settings::singleton()->showTooltips.get_proxy().signal_changed().connect(
154 sigc::mem_fun(this, &LabelWidget::on_show_tooltips_changed)
155 );
156
157 // workaround for a crash with certain gtkmm versions: postpone calling
158 // on_show_tooltips_changed() because widget.gobj() might be uninitialized
159 // at this point yet
160 Glib::signal_idle().connect_once( // timeout starts given amount of ms after the main loop became idle again ...
161 sigc::mem_fun(*this, &LabelWidget::on_show_tooltips_changed),
162 300
163 );
164 }
165
166 void LabelWidget::on_show_tooltips_changed() {
167 const bool b = Settings::singleton()->showTooltips;
168 label.set_has_tooltip(b);
169 widget.set_has_tooltip(b);
170 }
171
172 void LabelWidget::set_sensitive(bool sensitive)
173 {
174 label.set_sensitive(sensitive);
175 widget.set_sensitive(sensitive);
176 }
177
178 ReadOnlyLabelWidget::ReadOnlyLabelWidget(const char* leftHandText)
179 : LabelWidget(leftHandText, text)
180 {
181 #if HAS_GTKMM_ALIGNMENT
182 text.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START);
183 #else
184 label.set_halign(Gtk::Align::START);
185 label.set_valign(Gtk::Align::START);
186 #endif
187 }
188
189 ReadOnlyLabelWidget::ReadOnlyLabelWidget(const char* leftHandText, const char* rightHandText)
190 : LabelWidget(leftHandText, text)
191 {
192 #if HAS_GTKMM_ALIGNMENT
193 text.set_alignment(Gtk::ALIGN_START, Gtk::ALIGN_START);
194 #else
195 text.set_halign(Gtk::Align::START);
196 text.set_valign(Gtk::Align::START);
197 #endif
198 text.set_text(rightHandText);
199 }
200
201 NumEntry::NumEntry(const char* labelText, double lower, double upper,
202 int decimals) :
203 LabelWidget(labelText, box),
204 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 90) || GTKMM_MAJOR_VERSION < 2
205 adjust(lower, lower, upper, 1, 10),
206 #else
207 adjust(Gtk::Adjustment::create(lower, lower, upper, 1, 10)),
208 #endif
209 scale(adjust),
210 spinbutton(adjust)
211 {
212 scale.set_size_request(70);
213 spinbutton.set_digits(decimals);
214 spinbutton.set_value(0);
215 spinbutton.set_numeric();
216 scale.set_draw_value(false);
217 box.pack_start(spinbutton, Gtk::PACK_SHRINK);
218 box.add(scale);
219 }
220
221 void NumEntry::on_show_tooltips_changed() {
222 LabelWidget::on_show_tooltips_changed();
223
224 const bool b = Settings::singleton()->showTooltips;
225 spinbutton.set_has_tooltip(b);
226 scale.set_has_tooltip(b);
227 }
228
229 NumEntryGain::NumEntryGain(const char* labelText,
230 double lower, double upper,
231 int decimals, double coeff) :
232 NumEntry(labelText, lower, upper, decimals),
233 value(0),
234 coeff(coeff),
235 connected(true)
236 {
237 spinbutton.signal_value_changed().connect(
238 sigc::mem_fun(*this, &NumEntryGain::value_changed));
239 }
240
241 void NumEntryGain::value_changed()
242 {
243 if (!connected) return;
244
245 const double f = pow(10, spinbutton.get_digits());
246 int new_value = round_to_int(spinbutton.get_value() * f);
247 if (new_value != round_to_int(value / coeff * f)) {
248 value = round_to_int(new_value / f * coeff);
249 sig_changed();
250 }
251 }
252
253 void NumEntryGain::set_value(int32_t value)
254 {
255 if (value != this->value) {
256 this->value = value;
257
258 connected = false;
259 bool plus6 = value < 0;
260 spinbutton.set_value(plus6 ? 0 : value / coeff);
261 set_sensitive(!plus6);
262 connected = true;
263
264 sig_changed();
265 }
266 }
267
268
269 BoolEntryPlus6::BoolEntryPlus6(const char* labelText, NumEntryGain& eGain, int32_t plus6value) :
270 LabelWidget(labelText, checkbutton),
271 checkbutton(labelText),
272 eGain(eGain),
273 plus6value(plus6value)
274 {
275 checkbutton.signal_toggled().connect(
276 sigc::mem_fun(*this, &BoolEntryPlus6::value_changed));
277 }
278
279 void BoolEntryPlus6::on_show_tooltips_changed() {
280 LabelWidget::on_show_tooltips_changed();
281
282 eGain.on_show_tooltips_changed();
283 }
284
285 void BoolEntryPlus6::value_changed()
286 {
287 if (checkbutton.get_active()) eGain.set_value(plus6value);
288 else if (eGain.get_value() < 0) eGain.set_value(0);
289 }
290
291 int32_t BoolEntryPlus6::get_value() const
292 {
293 return eGain.get_value();
294 }
295
296 void BoolEntryPlus6::set_value(int32_t value)
297 {
298 checkbutton.set_active(value < 0);
299 }
300
301 NumEntryPermille::NumEntryPermille(const char* labelText,
302 double lower, double upper, int decimals) :
303 NumEntry(labelText, lower, upper, decimals),
304 value(0)
305 {
306 spinbutton.signal_value_changed().connect(
307 sigc::mem_fun(*this, &NumEntryPermille::value_changed));
308 }
309
310 void NumEntryPermille::value_changed()
311 {
312 uint16_t new_value = uint16_t(spinbutton.get_value() * 10 + 0.5);
313 if (new_value != value) {
314 value = uint16_t(spinbutton.get_value() * 10 + 0.5);
315 sig_changed();
316 }
317 }
318
319 void NumEntryPermille::set_value(uint16_t value)
320 {
321 if (value != this->value) {
322 spinbutton.set_value(value / 10.0);
323 }
324 }
325
326
327 NoteEntry::NoteEntry(const char* labelText) :
328 NumEntryTemp<uint8_t>(labelText)
329 {
330 spin_button_show_notes(spinbutton);
331 }
332
333 namespace {
334 const char* notes[] = {
335 _("C"), _("C#"), _("D"), _("D#"), _("E"), _("F"),_("F#"),
336 _("G"), _("G#"), _("A"), _("A#"), _("B")
337 };
338
339 int note_value(const Glib::ustring& note, double* value)
340 {
341 const char* str = note.c_str();
342
343 int i;
344 for (i = 11 ; i >= 0 ; i--) {
345 if (strncasecmp(str, notes[i], strlen(notes[i])) == 0) break;
346 }
347 if (i >= 0) {
348 char* endptr;
349 long x = strtol(str + strlen(notes[i]), &endptr, 10);
350 if (endptr != str + strlen(notes[i])) {
351 *value = std::max(0L, std::min(i + (x + 1) * 12, 127L));
352 return true;
353 }
354 } else {
355 char* endptr;
356 long x = strtol(str, &endptr, 10);
357 if (endptr != str) {
358 *value = std::max(0L, std::min(x, 127L));
359 return true;
360 }
361 }
362
363 #if HAS_GTKMM_CPP11_ENUMS
364 return Gtk::SpinButton::INPUT_ERROR;
365 #else
366 return Gtk::INPUT_ERROR;
367 #endif
368 }
369 }
370
371 int note_value(const Glib::ustring& note)
372 {
373 double value = 0;
374 note_value(note, &value);
375 return value;
376 }
377
378 Glib::ustring note_str(int note)
379 {
380 char buf[10];
381 sprintf(buf, "%s%d", notes[note % 12], note / 12 - 1);
382 return buf;
383 }
384
385 namespace {
386 // Convert the Entry text to a number
387 #if GTKMM_MAJOR_VERSION > 3 || (GTKMM_MAJOR_VERSION == 3 && (GTKMM_MINOR_VERSION > 91 || (GTKMM_MINOR_VERSION == 91 && GTKMM_MICRO_VERSION >= 2))) // GTKMM >= 3.91.2
388 int on_input(double& new_value, Gtk::SpinButton* spinbutton) {
389 return note_value(spinbutton->get_text(), &new_value);
390 }
391 #else
392 int on_input(double* new_value, Gtk::SpinButton* spinbutton) {
393 return note_value(spinbutton->get_text(), new_value);
394 }
395 #endif
396
397 // Convert the Adjustment position to text
398 bool on_output(Gtk::SpinButton* spinbutton) {
399 spinbutton->set_text(
400 note_str(spinbutton->get_adjustment()->get_value() + 0.5));
401 return true;
402 }
403 }
404
405 // Make a SpinButton show notes instead of numbers
406 void spin_button_show_notes(Gtk::SpinButton& spin_button)
407 {
408 spin_button.set_numeric(false);
409 spin_button.set_width_chars(4);
410 spin_button.signal_input().connect(
411 sigc::bind(sigc::ptr_fun(&on_input), &spin_button));
412 spin_button.signal_output().connect(
413 sigc::bind(sigc::ptr_fun(&on_output), &spin_button));
414 }
415
416 void ChoiceEntryBase::on_show_tooltips_changed() {
417 LabelWidget::on_show_tooltips_changed();
418
419 const bool b = Settings::singleton()->showTooltips;
420 combobox.set_has_tooltip(b);
421 }
422
423 ChoiceEntryLeverageCtrl::ChoiceEntryLeverageCtrl(const char* labelText) :
424 #if HAS_GTKMM_ALIGNMENT
425 LabelWidget(labelText, align),
426 align(0, 0, 0, 0)
427 #else
428 LabelWidget(labelText, combobox)
429 #endif
430 {
431 for (int i = 0 ; i < controlChangeTextsSize ; i++) {
432 if (controlChangeTexts[i].txt) {
433 const int cc = i - 3;
434 Glib::ustring s = (i < 3)
435 ? controlChangeTexts[i].txt
436 : Glib::ustring::compose("CC%1: %2%3", cc, controlChangeTexts[i].txt, controlChangeTexts[i].isExtension ? " [EXT]" : "");
437 #if (GTKMM_MAJOR_VERSION == 2 && GTKMM_MINOR_VERSION < 24) || GTKMM_MAJOR_VERSION < 2
438 combobox.append_text(s);
439 #else
440 combobox.append(s);
441 #endif
442 }
443 }
444 combobox.set_wrap_width(4);
445 combobox.signal_changed().connect(
446 sigc::mem_fun(*this, &ChoiceEntryLeverageCtrl::value_changed));
447 #if HAS_GTKMM_ALIGNMENT
448 align.add(combobox);
449 #else
450 combobox.set_halign(Gtk::Align::FILL);
451 combobox.set_valign(Gtk::Align::FILL);
452 #endif
453 value.type = gig::leverage_ctrl_t::type_none;
454 value.controller_number = 0;
455 }
456
457 void ChoiceEntryLeverageCtrl::on_show_tooltips_changed() {
458 LabelWidget::on_show_tooltips_changed();
459
460 const bool b = Settings::singleton()->showTooltips;
461 combobox.set_has_tooltip(b);
462 }
463
464 void ChoiceEntryLeverageCtrl::value_changed()
465 {
466 int rowno = combobox.get_active_row_number();
467 switch (rowno)
468 {
469 case -1:
470 break;
471 case 0:
472 value.type = gig::leverage_ctrl_t::type_none;
473 break;
474 case 1:
475 value.type = gig::leverage_ctrl_t::type_channelaftertouch;
476 break;
477 case 2:
478 value.type = gig::leverage_ctrl_t::type_velocity;
479 break;
480 default:
481 value.type = gig::leverage_ctrl_t::type_controlchange;
482 int x = 3;
483 for (uint cc = 0 ; cc < controlChangeTextsSize - 3 ; cc++) {
484 if (controlChangeTexts[cc + 3].txt) {
485 if (rowno == x) {
486 value.controller_number = cc;
487 if (controlChangeTexts[cc + 3].isExtension &&
488 Settings::singleton()->warnUserOnExtensions)
489 {
490 Glib::ustring txt = _("<b>Format Extension</b>\n\nAll controllers marked with \"<b>[EXT]</b>\" are an extension to the original gig sound format. They will only work with LinuxSampler, but they will <b>not work</b> with Gigasampler/GigaStudio!\n\n(You may disable this warning in the <i>Settings</i> menu.)");
491 Gtk::MessageDialog msg(
492 txt, true, Gtk::MESSAGE_WARNING
493 );
494 msg.run();
495 }
496 break;
497 }
498 x++;
499 }
500 }
501 break;
502 }
503 if (rowno >= 0) sig_changed();
504 }
505
506 void ChoiceEntryLeverageCtrl::set_value(gig::leverage_ctrl_t value)
507 {
508 int comboIndex;
509 switch (value.type)
510 {
511 case gig::leverage_ctrl_t::type_none:
512 comboIndex = 0;
513 break;
514 case gig::leverage_ctrl_t::type_channelaftertouch:
515 comboIndex = 1;
516 break;
517 case gig::leverage_ctrl_t::type_velocity:
518 comboIndex = 2;
519 break;
520 case gig::leverage_ctrl_t::type_controlchange: {
521 comboIndex = -1;
522 int x = 3;
523 for (uint cc = 0 ; cc < controlChangeTextsSize - 3 ; cc++) {
524 if (controlChangeTexts[cc + 3].txt) {
525 if (value.controller_number == cc) {
526 comboIndex = x;
527 break;
528 }
529 x++;
530 }
531 }
532 break;
533 }
534 default:
535 comboIndex = -1;
536 break;
537 }
538 combobox.set_active(comboIndex);
539 }
540
541
542 BoolBox::BoolBox(const char* labelText) : Gtk::CheckButton(labelText) {
543 signal_toggled().connect(sig_changed.make_slot());
544 Settings::singleton()->showTooltips.get_proxy().signal_changed().connect(
545 sigc::mem_fun(this, &BoolBox::on_show_tooltips_changed)
546 );
547 on_show_tooltips_changed();
548 }
549
550 void BoolBox::on_show_tooltips_changed() {
551 const bool b = Settings::singleton()->showTooltips;
552 set_has_tooltip(b);
553 }
554
555
556 BoolEntry::BoolEntry(const char* labelText) :
557 LabelWidget(labelText, checkbutton),
558 checkbutton(labelText)
559 {
560 checkbutton.signal_toggled().connect(sig_changed.make_slot());
561 }
562
563
564 StringEntry::StringEntry(const char* labelText) :
565 LabelWidget(labelText, entry)
566 {
567 entry.signal_changed().connect(sig_changed.make_slot());
568 }
569
570 gig::String StringEntry::get_value() const
571 {
572 return gig_from_utf8(entry.get_text());
573 }
574
575 void StringEntry::set_value(const gig::String& value) {
576 entry.set_text(gig_to_utf8(value));
577 }
578
579
580 StringEntryMultiLine::StringEntryMultiLine(const char* labelText) :
581 LabelWidget(labelText, frame)
582 {
583 text_buffer = text_view.get_buffer();
584 frame.set_shadow_type(Gtk::SHADOW_IN);
585 frame.add(text_view);
586 text_buffer->signal_changed().connect(sig_changed.make_slot());
587 }
588
589 gig::String StringEntryMultiLine::get_value() const
590 {
591 Glib::ustring value = text_buffer->get_text();
592 for (int i = 0 ; (i = value.find("\x0a", i)) >= 0 ; i += 2)
593 value.replace(i, 1, "\x0d\x0a");
594 return gig_from_utf8(value);
595 }
596
597 void StringEntryMultiLine::set_value(const gig::String& value)
598 {
599 Glib::ustring text = gig_to_utf8(value);
600 for (int i = 0 ; (i = text.find("\x0d\x0a", i, 2)) >= 0 ; i++)
601 text.replace(i, 2, "\x0a");
602 text_buffer->set_text(text);
603 }
604
605 void StringEntryMultiLine::on_show_tooltips_changed() {
606 LabelWidget::on_show_tooltips_changed();
607
608 const bool b = Settings::singleton()->showTooltips;
609 text_view.set_has_tooltip(b);
610 }
611
612
613 Table::Table(int x, int y) :
614 #if USE_GTKMM_GRID
615 Gtk::Grid(),
616 cols(x),
617 #else
618 Gtk::Table(x, y),
619 #endif
620 rowno(0)
621 {
622 }
623
624 void Table::add(BoolEntry& boolentry)
625 {
626 #if USE_GTKMM_GRID
627 attach(boolentry.widget, 0, rowno, 2);
628 #else
629 attach(boolentry.widget, 0, 2, rowno, rowno + 1,
630 Gtk::FILL, Gtk::SHRINK);
631 #endif
632 rowno++;
633 }
634
635 void Table::add(BoolEntryPlus6& boolentry)
636 {
637 #if USE_GTKMM_GRID
638 attach(boolentry.widget, 0, rowno, 2);
639 #else
640 attach(boolentry.widget, 0, 2, rowno, rowno + 1,
641 Gtk::FILL, Gtk::SHRINK);
642 #endif
643 rowno++;
644 }
645
646 void Table::add(LabelWidget& prop)
647 {
648 #if USE_GTKMM_GRID
649 attach(prop.label, 1, rowno);
650 attach(prop.widget, 2, rowno);
651 #else
652 attach(prop.label, 1, 2, rowno, rowno + 1,
653 Gtk::FILL, Gtk::SHRINK);
654 attach(prop.widget, 2, 3, rowno, rowno + 1,
655 Gtk::EXPAND | Gtk::FILL, Gtk::SHRINK);
656 #endif
657 rowno++;
658 }

  ViewVC Help
Powered by ViewVC