forked from polluxsynth/xtor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathxtor.c
1702 lines (1434 loc) · 55 KB
/
xtor.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/****************************************************************************
* xtor - GTK based editor for MIDI synthesisers
*
* xtor.c - Main program, with most of the synth-agnostic UI
* implementation.
*
* Copyright (C) 2014 Ricard Wanderlof <ricard2013@butoba.net>
*
* Originally based on:
* MIDI Controller - A program that runs MIDI controller GUIs built in Glade
* Copyright (C) 2004 Lars Luthman <larsl@users.sourceforge.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
****************************************************************************/
#include <stdio.h>
#include <getopt.h>
#include <poll.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "xtor.h"
#include "dialog.h"
#include "param.h"
#include "blofeld_params.h"
#include "controller.h"
#include "knob_mapper.h"
#include "nocturn.h"
#include "beatstep.h"
#include "midi.h"
#include "debug.h"
const char *main_window_name = "Main Window";
GtkWidget *main_window = NULL;
GtkMenu *popup_menu = NULL;
GtkWidget *about_window = NULL;
/* Parameter handler */
struct param_handler phandler;
struct param_handler *param_handler = &phandler;
/* MIDI controller surface */
struct controller ctrler;
struct controller *controller = &ctrler;
/* Controller knob mapping */
struct knob_mapper kmap;
struct knob_mapper *knob_mapper = &kmap;
/* Each adjustor represent a synth parameter, and contains a list of
* widgets which govern that parameter, making it possible to have
* the same parameter adjusted by widgets in different places. */
struct adjustor {
const char *id; /* name of parameter, e.g. "Filter 1 Cutoff" */
int parnum; /* parameter number. Redundant, but practical */
GList *widgets; /* list of widgets controlling parameter */
};
/* List of all adjustors, indexed by parameter number. */
struct adjustor **adjustors;
/* A f_k_map contains a frame, representing a synth module such as
* Osc 1 or Filt Env, and a reference to a knob map for that frame. */
struct f_k_map {
GtkFrame *frame;
void *knobmap;
};
/* List of all frame maps */
GList *f_k_maps = NULL;
/* used to temporarily block updates to MIDI */
int block_updates;
/* buffer number currently shown */
int current_buffer_no;
/* current patch name */
char current_patch_name[BLOFELD_PATCH_NAME_LEN_MAX + 1] = { 0 };
int current_patch_name_max = BLOFELD_PATCH_NAME_LEN_MAX;
/* structure for mapping keys to specific widget focus */
struct keymap {
const gchar *key_name; /* e.g. "s" */
guint keyval; /* GDK_ code for key_name */
const gchar *jump_button; /* e.g. "P1" */
const gchar *param_name;
GtkWidget *widget;
int param_arg;
const gchar *parent_name;
GtkWidget *parent;
int parent_arg;
};
GList *keymaps = NULL;
/* How the UI behaves */
struct ui_settings {
int scroll_focused_only;
int xtor_navigation;
int knobs_grab_focus;
};
/* Enabling knobs_grab_focus causes focus flutter if more than one knob is
* Technically it is not a problem, but it doesn't look nice on the screen. */
struct ui_settings ui_settings = { TRUE, TRUE, FALSE };
/* Global settings in Popup menu */
struct setting {
int *valueptr;
const char *name;
GtkWidget *widget;
};
struct setting settings[] = {
{ &ui_settings.scroll_focused_only, "Scrollfocus", NULL },
{ &ui_settings.xtor_navigation, "Navigation", NULL },
{ &ui_settings.knobs_grab_focus, "Knobsfocus", NULL },
{ &debug, "Debug", NULL },
{ NULL, NULL }
};
GtkWidget *ticked_widget = NULL;
/* Trim spaces from end of string, returning string. */
static char *
trim(char *buf)
{
if (!buf) return buf;
int len = strlen(buf);
while (len && buf[len - 1] == ' ')
len--;
buf[len] = '\0';
return buf;
}
/* Set application's title, from various global variables. */
void
set_title(void)
{
char title[80];
sprintf(title, "Xtor %s - %s (Part %d)",
param_handler->name, current_patch_name, current_buffer_no + 1);
if (main_window && GTK_IS_WINDOW(main_window))
gtk_window_set_title(GTK_WINDOW(main_window), title);
}
/* GCompareFunc for find_frame_in_knob_maps */
static int
same_frame(gconstpointer data, gconstpointer user_data)
{
const struct f_k_map *f_k_map = data;
const GtkFrame *searched_frame = user_data;
return !(f_k_map->frame == searched_frame);
}
/* Find frame in our list of frame-knobs maps mapping given frame */
static struct f_k_map *
find_frame_in_f_k_maps(GList *f_k_maps, GtkFrame *frame)
{
GList *found_map = g_list_find_custom(f_k_maps, frame, same_frame);
if (!found_map) return NULL;
return found_map->data;
}
static GtkWidget *
get_parent_frame(GtkWidget *widget)
{
dprintf("Scanning for parent for %s (%p)\n",
gtk_buildable_get_name(GTK_BUILDABLE(widget)), widget);
while (widget && !GTK_IS_FRAME(widget)) {
widget = gtk_widget_get_parent(widget);
}
if (widget)
dprintf("Found %s: %s (%p)\n",
gtk_widget_get_name(widget),
gtk_buildable_get_name(GTK_BUILDABLE(widget)), widget);
return widget;
}
void
invalidate_knob_mappings(GtkWidget *widget)
{
widget = get_parent_frame(widget);
if (!widget) return;
struct f_k_map *f_k_map = find_frame_in_f_k_maps(f_k_maps, GTK_FRAME(widget));
if (!f_k_map) return;
knob_mapper->invalidate(f_k_map->knobmap);
}
/* General signal handlers */
void
on_Main_Window_destroy(GtkObject *object, gpointer user_data)
{
gtk_main_quit();
}
void
on_midi_input(gpointer data, gint fd, GdkInputCondition condition)
{
dprintf("Received MIDI data on fd %d\n", fd);
midi_input();
}
void
on_Device_Name_activate(GtkObject *object, gpointer user_data)
{
if (!GTK_IS_ENTRY(object)) return;
GtkEntry *device_name_entry = GTK_ENTRY(object);
/* If user says empty string, go back to parameter handler's default */
if (!strcmp(gtk_entry_get_text(device_name_entry), ""))
gtk_entry_set_text(device_name_entry, param_handler->remote_midi_device);
if (midi_connect(SYNTH_PORT, gtk_entry_get_text(device_name_entry)) < 0)
report("Can't establish MIDI connection!", "", GTK_MESSAGE_ERROR, main_window);
;
}
/* Popup menu signal handlers */
/* Show popup menu when mouse right button pressed. */
static gboolean
menu_button_event(GtkWidget *widget, GdkEventButton *event)
{
static int count = 0;
dprintf("mouse button %d: %d, state %d, widget is a %s, name %s\n", ++count,
event->button, event->state, gtk_widget_get_name(widget),
gtk_buildable_get_name(GTK_BUILDABLE(widget)));
if (event->button == 3) {
gtk_menu_popup(popup_menu, NULL, NULL, NULL, NULL, 0, event->time);
return TRUE;
}
return FALSE;
}
gboolean
activate_About(GtkObject *object, gpointer user_data)
{
dprintf("activate About: object %p is %s\n", object, gtk_widget_get_name(GTK_WIDGET(object)));
/* Set which window to be our parant. This places the About box in the
* middle of the main window which looks nice. */
gtk_window_set_transient_for(GTK_WINDOW(about_window), GTK_WINDOW(main_window));
gtk_widget_show(about_window);
return TRUE;
}
/* Need to have this, or the default signal handler destroys the About box */
gboolean
on_About_delete(GtkObject *object, gpointer user_data)
{
dprintf("About deleted\n");
gtk_widget_hide(GTK_WIDGET(object));
return TRUE;
}
/* Basically when Closed is pressed in About box, but also ESC or window X */
gboolean
on_About_response(GtkObject *object, gpointer user_data)
{
dprintf("About response\n");
gtk_widget_hide(GTK_WIDGET(object));
return TRUE;
}
/* Change underlying value when setting changed in popup menu. */
gboolean
on_Setting_changed(GtkObject *object, gpointer user_data)
{
dprintf("Setting changed: object is a %s, name %s\n",
gtk_widget_get_name(GTK_WIDGET(object)),
gtk_buildable_get_name(GTK_BUILDABLE(object)));
if (!GTK_IS_WIDGET(object)) return;
GtkWidget *widget = GTK_WIDGET(object);
struct setting *setting = settings;
/* Scan our settings for one with a matching widget pointer */
while (setting->valueptr) {
if (widget == setting->widget) {
if (GTK_IS_CHECK_MENU_ITEM(object)) {
*setting->valueptr =
gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(object));
dprintf("Setting %s to %d\n", setting->name, *setting->valueptr);
}
break;
}
setting++;
}
return TRUE;
}
/* Popup menu settings */
/* Assign menu item settings to appropriate setting[] */
static void
assign_setting(gpointer data, gpointer user_data)
{
struct setting *setting = user_data;
if (!GTK_IS_CHECK_MENU_ITEM(data)) return;
GtkCheckMenuItem *menuitem = data;
const char *name = gtk_buildable_get_name(GTK_BUILDABLE(menuitem));
dprintf("Scanning settings for %s\n", name);
while (setting->valueptr) {
if (!strcmp(name, setting->name)) {
dprintf("Found it!\n");
setting->widget = GTK_WIDGET(menuitem);
gtk_check_menu_item_set_active(menuitem, *setting->valueptr);
return;
}
setting++;
}
}
/* Set up all settings in the Popup menu to match our settings[] */
static void
setup_settings(GtkMenu *menu)
{
if (!GTK_IS_CONTAINER(menu)) return;
g_list_foreach(gtk_container_get_children(GTK_CONTAINER(menu)),
assign_setting, settings);
}
/* Parameter editing */
struct adj_update {
GtkWidget *widget; /* widget requesting update, or NULL */
const void *valptr; /* pointer to new value to set */
};
/* foreach function for update_adjustors */
static void
update_adjustor(gpointer data, gpointer user_data)
{
GtkWidget *widget = data;
struct adj_update *adj_update = user_data;
const void *valptr = adj_update->valptr;
/* We only update adjustors that aren't the same as the widget generating
* the update. For updates arriving from MIDI, there is no such widget,
* and it is set to NULL, so we update all widgets.
*/
if (widget != adj_update->widget) {
if (GTK_IS_RANGE(widget))
gtk_range_set_value(GTK_RANGE(widget), *(const int *)valptr);
else if (GTK_IS_COMBO_BOX(widget))
gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *(const int *)valptr);
else if (GTK_IS_TOGGLE_BUTTON(widget))
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget),
!!*(const int *)valptr);
else if (GTK_IS_ENTRY(widget))
gtk_entry_set_text(GTK_ENTRY(widget), valptr);
}
}
/* Ultimately called whenever a parameter is updated. Either becuase a
* parameter was changed on the synth, and we want to display it, or because
* the UI updated one parameter, and we want to update any other widgets
* on the screen (hidden or not) which have the same underlying parameter.
* For the latter case, updating_widget is set to the widget that has
* caused the update, so we don't try to update it again here. */
static void
update_adjustors(struct adjustor *adjustor, const void *valptr,
GtkWidget *updating_widget)
{
struct adj_update adj_update = { .widget = updating_widget,
.valptr = valptr };
block_updates = 1;
/* Go through the list of widgets that are specified for this parameter,
* attempting to update them. */
g_list_foreach(adjustor->widgets, update_adjustor, &adj_update);
block_updates = 0;
}
/* Called whenever parameter change arrives from the synth via MIDI */
static void
param_changed(int parnum, int buffer_no, void *valptr, void *ref)
{
if (buffer_no == current_buffer_no && valptr) {
struct adjustor *adjustor = adjustors[parnum];
if (adjustor)
update_adjustors(adjustor, valptr, NULL);
}
}
/* Called whenever a widget's value changes.
* We send the update to the synth via MIDI, but also to other widgets with
* same parameter name (e.g. on other editor pages or tabs.) */
static void
update_parameter(struct adjustor *adjustor, const void *valptr,
GtkWidget *widget)
{
param_handler->param_update_parameter(adjustor->parnum, current_buffer_no,
valptr);
update_adjustors(adjustor, valptr, widget);
}
/* Handlers called when widgets change value. */
void
on_patch_name_changed(GtkObject *object, gpointer user_data)
{
GtkEntry *gtkentry = GTK_ENTRY (object);
if (gtkentry) {
const char *stringptr = gtk_entry_get_text(gtkentry);
/* Set our global patch name if respective widget and update title */
strncpy(current_patch_name, stringptr, current_patch_name_max);
trim(current_patch_name);
set_title();
}
}
void
on_entry_changed(GtkObject *object, gpointer user_data)
{
GtkEntry *gtkentry = GTK_ENTRY (object);
struct adjustor *adjustor = user_data;
const char *stringptr;
if (block_updates)
return;
if (gtkentry) {
stringptr = gtk_entry_get_text(gtkentry);
dprintf("Entry %p: name %s, value \"%s\", parnum %d\n",
gtkentry, gtk_buildable_get_name(GTK_BUILDABLE(gtkentry)),
stringptr, adjustor->parnum);
/* Set our global patch name if respective widget and update title */
update_parameter(adjustor, stringptr, GTK_WIDGET(object));
}
}
/* Called whenever user attempts to change the value of a GtkRange parameter. */
/* Handle the increment/decrement as we see fit, then return TRUE so Gtk
* knows we've handled it. */
gboolean
on_change_value(GtkObject *object, GtkScrollType scrolltype,
gdouble dvalue, gpointer user_data)
{
GtkRange *gtkrange = GTK_RANGE (object);
if (gtkrange) {
struct adjustor *adjustor = user_data;
int new_value = (int) dvalue;
int old_value = (int) gtk_range_get_value(gtkrange);
int delta = 0;
dprintf("Change %s, new value %d, old value %d, scrolltype %d, parnum %d\n",
gtk_buildable_get_name(GTK_BUILDABLE(gtkrange)),
new_value, old_value, scrolltype, adjustor->parnum);
switch (scrolltype) {
case GTK_SCROLL_STEP_BACKWARD: delta = -1; break;
case GTK_SCROLL_STEP_FORWARD: delta = 1; break;
case GTK_SCROLL_PAGE_BACKWARD: delta = -10; break;
case GTK_SCROLL_PAGE_FORWARD: delta = 10; break;
case GTK_SCROLL_JUMP: /* this happens when dragging mouse pointer */
default: delta = 0; break;
}
/* Call param handler so that we get the next value in sequence.
* This is really only significant when there are gaps in the UI values
* (e.g. Blofeld Keytrack parameters, which have a parameter range of
* -200..+196 but are represented as 0..127, giving gaps in the
* UI values), but we do it for all parameters for consistency. */
int new = param_handler->param_update_value(adjustor->parnum,
old_value, new_value, delta);
gtk_range_set_value(gtkrange, new);
return TRUE;
}
return FALSE;
}
/* Called when the value of a GtkRange has been changed. */
void
on_value_changed (GtkObject *object, gpointer user_data)
{
GtkRange *gtkrange = GTK_RANGE (object);
struct adjustor *adjustor = user_data;
int value;
if (block_updates)
return;
if (gtkrange) {
dprintf("Range %p: name %s, value %d, parnum %d\n",
gtkrange, gtk_buildable_get_name(GTK_BUILDABLE(gtkrange)),
(int) gtk_range_get_value(gtkrange), adjustor->parnum);
value = (int) gtk_range_get_value(gtkrange);
update_parameter(adjustor, &value, GTK_WIDGET(object));
}
}
void
on_combobox_changed (GtkObject *object, gpointer user_data)
{
GtkComboBox *cb = GTK_COMBO_BOX (object);
struct adjustor *adjustor = user_data;
int value;
if (block_updates)
return;
if (cb) {
dprintf("Combobox %p: name %s, value %d, parnum %d\n",
cb, gtk_buildable_get_name(GTK_BUILDABLE(cb)),
gtk_combo_box_get_active(cb), adjustor->parnum);
value = (int) gtk_combo_box_get_active(cb);
update_parameter(adjustor, &value, GTK_WIDGET(object));
}
}
void
on_togglebutton_changed (GtkObject *object, gpointer user_data)
{
GtkToggleButton *tb = GTK_TOGGLE_BUTTON (object);
struct adjustor *adjustor = user_data;
int value;
if (block_updates)
return;
if (tb) {
dprintf("Togglebutton %p: name %s, value %d, parnum %d\n",
tb, gtk_buildable_get_name(GTK_BUILDABLE(tb)),
gtk_toggle_button_get_active(tb), adjustor->parnum);
value = gtk_toggle_button_get_active(tb);
update_parameter(adjustor, &value, GTK_WIDGET(object));
}
}
/* We call this when we need to emit a 'change value' signal as a result
* of a key press or mouse scroll event. */
static gboolean
change_value(GtkWidget *what, int shifted, int dir, int compensate)
{
GtkWidget *parent;
int delta;
const char *signal = NULL;
dprintf("Change value for %s:%s\n", gtk_widget_get_name(what),
gtk_buildable_get_name(GTK_BUILDABLE(what)));
/* This takes a bit of explaining: Normally all vertical sliders are
* 'inverted' from Gtk's point of view, meaning that their max value is
* upwards. However, for some parameters, such as filter balance, it makes
* more sense to have the max value downwards, as -64 corresponds to
* Filter 1 and +63 corresponds to Filter 2. This affects the perceived
* scroll direction, so we take care of that here.
* (For horizontal sliders, Gtk sees them as we do, so we invert the scroll
* direction if they are inverted.)
*/
if (compensate &&
(GTK_IS_VSCALE(what) && !gtk_range_get_inverted(GTK_RANGE(what)) ||
GTK_IS_HSCALE(what) && gtk_range_get_inverted(GTK_RANGE(what))))
dir = -dir;
if (dir == 1)
delta = shifted ? GTK_SCROLL_PAGE_FORWARD : GTK_SCROLL_STEP_FORWARD;
else if (dir == -1)
delta = shifted ? GTK_SCROLL_PAGE_BACKWARD : GTK_SCROLL_STEP_BACKWARD;
if (GTK_IS_RANGE(what))
signal = "move-slider";
if (GTK_IS_SPIN_BUTTON(what))
signal = "change-value";
/* When called with a widget in focus, the focus will be on a toggle button */
else if (GTK_IS_TOGGLE_BUTTON(what) &&
(parent = gtk_widget_get_parent(what)) &&
GTK_IS_COMBO_BOX(parent)) {
what = parent;
signal = "move-active";
/* When called from a controller update, we'll be called with the actual
* combo box widget. */
} else if (GTK_IS_COMBO_BOX(what))
signal ="move-active";
else if (GTK_IS_TOGGLE_BUTTON(what)) /* including check button */
signal = "activate";
if (what && signal) {
g_signal_emit_by_name(GTK_OBJECT(what), signal, delta);
return TRUE;
}
return FALSE;
}
/* Handle focus and value navigation. Apart from hotkeys and the keys
* used to reach the popup menu, these are all the keys that Xtor handles
* of its own accord. */
static gboolean
navigation(GtkWidget *widget, GtkWidget *focus, GdkEventKey *event)
{
GtkWidget *parent;
int shifted = event->state & GDK_SHIFT_MASK;
int ctrl = event->state & GDK_CONTROL_MASK;
int arg = -1;
#define SET_ARG(value) if (arg < 0) arg = (value)
GtkWidget *what = NULL;
const char *signal = NULL;
gboolean handled = FALSE;
switch (event->keyval) {
case GDK_Right:
arg = GTK_DIR_RIGHT;
case GDK_Left:
if (arg < 0) arg = GTK_DIR_LEFT;
case GDK_Up:
if (arg < 0) arg = GTK_DIR_UP;
case GDK_Down:
if (arg < 0) arg = GTK_DIR_DOWN;
what = widget;
signal = "move-focus";
g_signal_emit_by_name(GTK_OBJECT(what), signal, arg);
handled = TRUE;
break;
case GDK_Forward:
case GDK_Page_Up:
case GDK_plus:
handled = change_value(focus, shifted, 1, 1);
if (ctrl && ticked_widget)
handled |= change_value(ticked_widget, shifted, 1, 1);
break;
case GDK_Back:
case GDK_Page_Down:
case GDK_minus:
handled = change_value(focus, shifted, -1, 1);
if (ctrl && ticked_widget)
handled |= change_value(ticked_widget, shifted, -1, 1);
break;
case GDK_apostrophe:
ticked_widget = GTK_WINDOW(widget)->focus_widget;
handled = 1;
break;
case GDK_space:
what = ticked_widget;
if (what) {
ticked_widget = GTK_WINDOW(widget)->focus_widget;
gtk_widget_grab_focus(what);
}
handled = 1;
break;
default:
break;
}
return handled;
}
/* Is 'parent' identical to or a parent of 'widget' ? */
static int
is_parent(GtkWidget *widget, GtkWidget *parent)
{
do {
dprintf("Scanning %s (%p), looking for %s (%p)\n",
gtk_buildable_get_name(GTK_BUILDABLE(widget)), widget,
gtk_buildable_get_name(GTK_BUILDABLE(parent)), parent);
if (widget == parent)
return 1;
} while (widget = gtk_widget_get_parent(widget));
return 0;
}
/* Used for searching for valid key map given key val and current focus */
struct key_search_spec {
const gchar *button_name; /* NULL for key search mode, else button string */
guint keyval; /* GDK_ value in key search mode */
GtkWidget *focus_widget; /* currently focused widget */
};
/* Used for g_list_find_custom to find key val in keymaps */
static gint
find_keymap(gconstpointer data, gconstpointer user_data)
{
const struct keymap *keymap = data;
const struct key_search_spec *search = user_data;
dprintf("Scan keymap %s: %s: %s\n", keymap->key_name, keymap->param_name,
keymap->jump_button);
if (search->button_name) { /* jump button search mode */
if (!keymap->jump_button || !*keymap->jump_button)
return 1; /* no jump button specified, or zero length */
if (strcmp(keymap->jump_button, search->button_name))
return 1; /* not this one */
} else { /* key search mode */
if (keymap->keyval != search->keyval)
return 1; /* not the key we're looking for */
}
if (!keymap->widget)
return 1; /* Widget not set, UI specified unknown Param or Parent */
dprintf("Found keymap\n");
if (!keymap->parent) /* keymap has no parent specified; we're done */
return 0; /* found */
dprintf("Has parent %s\n", keymap->parent_name);
/* If parent is a notebook, then check for the relevant notebook page. */
if (GTK_IS_NOTEBOOK(keymap->parent))
return gtk_notebook_get_current_page(GTK_NOTEBOOK(keymap->parent)) !=
keymap->parent_arg; /* 0 if on correct page */
dprintf("Parent is not a notebook\n");
/* Otherwise check if the currently focused widget has the same parent
* as the parameter specified in the keymap. */
return !is_parent(search->focus_widget, keymap->parent); /* 0 if found */
}
/* Handle hotkey, once it has been found in keymaps */
static gboolean
hotkey(struct key_search_spec *key_search_spec)
{
GList *keymap_l = g_list_find_custom(keymaps, key_search_spec, find_keymap);
if (!keymap_l)
return FALSE; /* can't find valid key mapping */
struct keymap *keymap = keymap_l->data;
dprintf("Found key map for %s: widget %s (%p)\n",
keymap->key_name, keymap->param_name, keymap->widget);
if (!keymap->widget) /* Could happen if ParamName not found */
return FALSE;
if (GTK_IS_NOTEBOOK(keymap->widget)) {
dprintf("Setting notebook page to %d\n", keymap->param_arg);
gtk_notebook_set_current_page(GTK_NOTEBOOK(keymap->widget),
keymap->param_arg);
return TRUE;
}
if (GTK_IS_BUTTON(keymap->widget)) {
gtk_button_pressed(GTK_BUTTON(keymap->widget));
return TRUE;
}
/* All other widget types */
gtk_widget_grab_focus(keymap->widget);
return TRUE; /* key handled */
}
/* Handle keys mapped in UI KeyMapping liststore ("hotkeys") */
static gboolean
mapped_key(GtkWidget *focus, GdkEventKey *event)
{
struct key_search_spec key_search_spec;
key_search_spec.button_name = NULL; /* set key search mode */
key_search_spec.keyval = event->keyval; /* event to search for in keymaps */
key_search_spec.focus_widget = focus; /* currently focused widget */
return hotkey(&key_search_spec);
}
/* Handle all key events arriving in the main window */
static gboolean
key_event(GtkWidget *widget, GdkEventKey *event)
{
GtkWidget *focus = GTK_WINDOW(widget)->focus_widget;
dprintf("Key pressed: \"%s\" (0x%08x), widget %p, focus widget %p, "
"(main window %p)\n", gdk_keyval_name(event->keyval), event->keyval,
widget, focus, main_window);
dprintf("Focused widget is a %s, name %s\n",gtk_widget_get_name(focus),
gtk_buildable_get_name(GTK_BUILDABLE(focus)));
if (event->keyval == GDK_F1 || event->keyval == GDK_F10 ||
event->keyval == GDK_Menu) {
gtk_menu_popup(popup_menu, NULL, NULL, NULL, NULL, 0, event->time);
return TRUE;
}
if (GTK_IS_ENTRY(focus) && !GTK_IS_SPIN_BUTTON(focus))
return FALSE; /* We let GTK handle all key events for GtkEntries. */
if (ui_settings.xtor_navigation && navigation(widget, focus, event))
return TRUE;
if (mapped_key(focus, event))
return TRUE;
return FALSE; /* key not handled - defer to GTK defaults */
}
/* Handle jump buttons from MIDI controller */
static void
jump_button(int button_row, int button_no, void *ref)
{
struct key_search_spec key_search_spec;
GtkWidget *focus = GTK_WINDOW(main_window)->focus_widget;
char jump_button_name[20];
sprintf(jump_button_name, "J%d%d", button_row, button_no);
key_search_spec.button_name = jump_button_name;
key_search_spec.focus_widget = focus; /* currently focused widget */
hotkey(&key_search_spec);
}
/* Handle mouse scrolling events arriving in slider widgets */
static gboolean
scroll_event(GtkWidget *widget, GdkEventScroll *event)
{
static int count = 0;
int shifted = 0;
int ctrl = 0;
/* Depending on whether ui_settings.scroll_focused_only is set,
* we don't want to scroll the widget currently pointed to, we want
* to scroll the one that has focus. */
GtkWidget *toplevel = gtk_widget_get_toplevel(widget);
if (!toplevel) return FALSE;
GtkWidget *focus = GTK_WINDOW(toplevel)->focus_widget;
if (!focus) return FALSE;
dprintf("scroll %d: widget is a %s, name %s, focus is a %s, name %s\n",
++count,
gtk_widget_get_name(widget),
gtk_buildable_get_name(GTK_BUILDABLE(widget)),
gtk_widget_get_name(focus),
gtk_buildable_get_name(GTK_BUILDABLE(focus)));
/* If UI is set to scroll_focused_only (Xtor default mode), always
* scroll the widget that is focused, regardless of where the mouse
* pointer is. Handy when using Xtor's key based navigation.
* Otherwise, use the Gnome default of scrolling whatever the mouse
* pointer points to. Handy when navigating using the mouse, as we
* don't need to left-click to focus an item before scrolling. */
if (ui_settings.scroll_focused_only)
widget = focus;
if (event->state & (GDK_SHIFT_MASK | GDK_BUTTON2_MASK))
shifted = 1;
if (event->state & GDK_CONTROL_MASK)
ctrl = 1;
switch (event->direction) {
case GDK_SCROLL_UP:
case GDK_SCROLL_LEFT:
change_value(widget, shifted, 1, 1);
if (ctrl && ticked_widget)
change_value(ticked_widget, shifted, 1, 1);
break;
case GDK_SCROLL_DOWN:
case GDK_SCROLL_RIGHT:
change_value(widget, shifted, -1, 1);
if (ctrl && ticked_widget)
change_value(ticked_widget, shifted, -1, 1);
break;
default:
break;
}
return TRUE;
}
/* Handle all mouse button events arriving in slider widgets */
static gboolean
button_event(GtkWidget *widget, GdkEventButton *event)
{
static int count = 0;
dprintf("mouse button %d: %d, state %d, widget is a %s, name %s\n", ++count,
event->button, event->state, gtk_widget_get_name(widget),
gtk_buildable_get_name(GTK_BUILDABLE(widget)));
/* What we want to is stop the default action of jumping to the pointed-to
* value when the middle button is pressed or released, so get out of
* here if we're not dealing with the middle button. */
if (event->button != 2)
return FALSE;
/* We want to keep the action of setting focus however when pressed. */
if (event->type & GDK_BUTTON_PRESS)
gtk_widget_grab_focus(widget);
/* Nothing else to do, just swallow event. */
return TRUE;
}
#if 0 /* need this ? */
static void
show_widget(gpointer data, gpointer user_data)
{
if (!GTK_IS_WIDGET(data)) return;
GtkWidget *widget = data;
dprintf("%s: %s (%p)\n",
gtk_widget_get_name(widget),
gtk_buildable_get_name(GTK_BUILDABLE(widget)), widget);
}
#endif
/* Get widget corresponding to currently turned knob */
static GtkWidget *
get_knob_widget(struct f_k_map *f_k_map, int control_no, int alt_control_no,
int row)
{
dprintf("f_k_map: frame %p:%s:%s, knobmap %p\n", f_k_map->frame,
gtk_widget_get_name(GTK_WIDGET(f_k_map->frame)),
gtk_buildable_get_name(GTK_BUILDABLE(f_k_map->frame)), f_k_map->knobmap);
/* Get the knob_descriptor for the current knob (controller_number)
* from the knob_mapper. */
struct knob_descriptor *knob_descriptor =
knob_mapper->knob(f_k_map->knobmap, control_no-1, alt_control_no-1, row);
dprintf("Knob descriptor %p\n", knob_descriptor);
if (!knob_descriptor) return NULL;
/* Finally, extract the widget from the knob_descriptor */
GtkWidget *widget = knob_descriptor->widget;
dprintf("Widget %p\n", widget);
dprintf("Control %d (alt %d) referencing %s:%s\n", control_no, alt_control_no,
gtk_widget_get_name(widget),
gtk_buildable_get_name(GTK_BUILDABLE(widget)));
return widget;
}
struct focus {
GtkWidget *widget;
GtkWidget *parent_frame;
struct f_k_map *f_k_map;
};
/* Get parent frame and f_k_map for widget, if available */
static struct focus *
current_focus(GtkWidget *new_focus)
{
/* Currently focused widget, parent frame and frame-knob map */
static struct focus focus = { 0 };
/* When focus changes, we set new focus_widget, and recalculate
* focus.parent_frame and focus.f_k_map. We only do this when changing
* focus to avoid loading the CPU with having to do it every time. */
if (new_focus == focus.widget)
goto out; /* nothing changed, so leave everything as it was */
focus.widget = new_focus;
dprintf("Focus set to %s:%s\n",
gtk_widget_get_name(new_focus),
gtk_buildable_get_name(GTK_BUILDABLE(new_focus)));
if (!focus.widget) { /* No focus, so nothing to edit */
focus.parent_frame = NULL;
focus.f_k_map = NULL;
goto out;
}
GtkWidget *new_parent_frame = get_parent_frame(focus.widget);
if (new_parent_frame == focus.parent_frame) /* same parent frame */
goto out;
focus.parent_frame = new_parent_frame;
if (!focus.parent_frame) {
/* No parent frame, so nothing to edit (except focused parameter) */
focus.f_k_map = NULL;
goto out;
}
focus.f_k_map = find_frame_in_f_k_maps(f_k_maps, GTK_FRAME(focus.parent_frame));
out: