1# snd-xm.rb -- snd-motif classes and functions
2
3# Author: Michael Scholz <mi-scholz@users.sourceforge.net>
4# Created: 04/02/25 05:31:02
5# Changed: 20/09/19 00:41:29
6
7# Requires --with-motif
8#
9# Tested with Snd 20.x
10#             Ruby 2.6
11#             Motif 2.3.3 X11R6
12#
13# module Snd_XM
14#  make_snd_menu(name, args) do ... end
15#  make_menu(name, parent) do ... end
16#  make_popup_menu(name, parent) do ... end
17#  make_dialog(label, *rest) do |w, c, i| ... end
18#  format_sound_comment(comment)
19#  scale_log2linear(lo, val, hi)
20#  scale_linear2log(lo, val, hi)
21#  scale_log_label(lo, val, hi)
22#  semi_scale_label(val)
23#  semitones2ratio
24#  ratio2semitones
25#  get_color(*new_color)
26#  create_color(color)
27#  yellow_pixel
28#  red_pixel
29#  white_pixel
30#  black_pixel
31#  update_label(list)
32#  change_label(widget, string, property)
33#  find_child(widget, name)
34#  each_child(widget) do |w| .... end
35#  widget?(obj)
36#  is_managed?(widget)
37#  widget_name(widget)
38#  set_scale_value(widget, value, scaler)
39#  get_scale_value(widget, info, scaler)
40#  raise_dialog(widget)
41#  activate_dialog(dialog)
42#  set_label_sensitive(widget, name, set_p)
43#  set_sensitive(widget, flag)
44#  add_main_pane(name, type, *args)
45#  show_disk_space(snd)
46#
47#  class Scale_widget
48#    initialize(parent)
49#    scale
50#    label
51#    add_scale(title, low, init, high, scale, kind)
52#
53#  class Dialog_base
54#    initialize(label, ok_cb, reset_cb, clear_cb, target_cb, help_cb)
55#    dialog
56#    parent
57#    okay_button
58#    doit_string(*args)
59#    dismiss_string(*args)
60#    help_string(*args)
61#    reset_string(*args)
62#    clear_string(*args)
63#
64#  class Dialog < Dialog_base
65#    add_slider(title, low, init, high, scl, kind, parent) do |w, c, i| ... end
66#    add_toggle(label, value) do |val| ... end
67#    add_target(labels) do |val| ... end
68#
69#  module Snd_Motif
70#   string2compound(*args)
71#   compound2string(xstr)
72#   get_xtvalue(widget, item)
73#   current_label(widget)
74#   current_screen
75#   get_pixmap(screen, file)
76#   screen_depth
77#   display_widget_tree(widget, spaces)
78#   add_sound_pane(snd, name, type, *args)
79#   add_channel_pane(snd, chn, name, type, *args)
80#   menu_option(name)
81#   set_main_color_of_widget(w)
82#
83#   add_mark_pane
84#
85#   class Mark_pane
86#     initialize
87#     make_list(snd, chn)
88#     deactivate_channel(snd, chn)
89#
90#   class Variable_display
91#     initialize(page_name, variable_name)
92#     inspect
93#     make_dialog
94#     create
95#     close
96#     reset
97#
98#   class Variable_display_text < Variable_display
99#     create
100#     display(var)
101#
102#   class Variable_display_scale < Variable_display
103#     initialize(page_name, variable_name, range)
104#     inspect
105#     create
106#     display(var)
107#
108#   class Variable_display_graph < Variable_display
109#     create
110#     display(var)
111#     reset
112#
113#   class Variable_display_spectrum < Variable_display
114#     create
115#     display(var)
116#     reset
117#
118#   make_variable_display(page_name, variable_name, type, range)
119#   variable_display(vd, var)
120#   variable_display_close(vd)
121#   variable_display_reset(vd)
122#   variable_display?(vd)
123#
124#   class Dialog
125#     add_frame(args)
126#     add_label(label, args)
127#     add_textfield(string, label, columns) do |w, c, i| ... end
128#     add_text(*args)
129#
130# class Menu
131#   initialize(name, menu, args)
132#   menu
133#   each_entry do |child| ... end
134#   change_menu_color(new_color)
135#
136# class Snd_main_menu < Menu
137#   initialize(name, parent, args) do ... end
138#   menu_number
139#   entry(klass, *rest) or entry(name) do ... end
140#   separator
141#   cascade(name, args) do ... end
142
143require "clm"
144
145$with_motif = provided?(:snd_motif)
146
147unless $with_motif
148  Snd.raise(:runtime_error, __FILE__, "--with-motif required")
149end
150
151#
152# --- Motif functions ---
153#
154module Snd_XM
155  class SndXError < StandardError
156  end
157  Ruby_exceptions[:snd_x_error] = SndXError
158
159  Dismiss_string = "Go Away"
160  Help_string    = "Help"
161  Okay_string    = "DoIt"
162  Reset_string   = "Reset"
163  Clear_string   = "Clear"
164
165  # main_widgets
166  Top_level_application = 0
167  Top_level_shell       = 1
168  Main_pane_shell       = 2
169  Main_sound_pane       = 3
170  Listener_pane         = 4
171  Notebook_outer_pane   = 5
172
173  # menu_widgets
174  Top_menu_bar = 0
175  File_menu    = 1
176  Edit_menu    = 2
177  View_menu    = 3
178  Options_menu = 4
179  Help_menu    = 5
180
181  # sound_widgets
182  Main_pane        = 0
183  Name_label       = 1
184  Control_panel    = 2
185  Minibuffer       = 3
186  Play             = 4
187  Filter_graph     = 5
188  Unite            = 6
189  Minibuffer_label = 7
190  Name_icon        = 8
191  Sync             = 9
192
193  # channel_widgets
194  Graph  = 0
195  W      = 1
196  F      = 2
197  Sx     = 3
198  Sy     = 4
199  Zx     = 5
200  Zy     = 6
201  Edhist = 7
202  Gsy    = 8
203  Gzy    = 9
204  Channel_main_pane = 10
205
206  # dialog_widgets
207  Orientation_dialog  =  0
208  Enved_dialog        =  1
209  Transform_dialog    =  2
210  File_open_dialog    =  3
211  File_save_as_dialog =  4
212  View_files_dialog   =  5
213  Raw_data_dialog     =  6
214  New_file_dialog     =  7
215  File_mix_dialog     =  8
216  Edit_header_dialog  =  9
217  Find_dialog         = 10
218  Help_dialog         = 11
219  Mix_panel_dialog    = 12
220  Print_dialog        = 13
221  Region_dialog       = 14
222  Info_dialog         = 15
223  Extra_controls_dialog = 16
224  Save_selection_dialog = 17
225  Insert_file_dialog  = 18
226  Save_region_dialog  = 19
227  Preferences_dialog  = 20
228  #
229  # old names
230  #
231  Color_dialog        =  0
232  Error_dialog        =  0
233  Yes_or_no_dialog    =  0
234  Completion_dialog   =  0
235  Recorder_dialog     =  0
236  Track_dialog        =  0
237
238  # MAKE_MENU as well as MAKE_POPUP_MENU may be used in non-Snd
239  # scripts.  MAKE_SND_MENU and MAKE_SND_POPUP (see popup.rb) are
240  # specialized using them in Snd.
241  #
242  #  [...]
243  #  main_widget = RXmCreateMainWindow(top_level, "main", [])
244  #  menu_bar = RXmCreateMenuBar(main_widget, "menu-bar", [])
245  #  RXtManageChild(menu_bar)
246  #  make_menu("file-button", menu_bar) do
247  #    entry("quit") do |w, c, i| exit(0) end
248  #  end
249  #  make_menu("help-button", menu_bar) do
250  #    entry("general-help") do |w, c, i| ... end
251  #  end
252  def make_menu(name, parent, &body)
253    Main_menu.new(name, parent, [], &body)
254  end
255
256  #  play = lambda do |w, c, i|
257  #    @play = Rset(i)
258  #    send_values(!@play)
259  #  end
260  #  make_popup_menu("popup", main_widget) do
261  #    entry("play", :widget_class, RxmToggleButtonWidgetClass, &play)
262  #    separator
263  #    entry("quit") do |w, c, i| exit(0) end
264  #  end
265  def make_popup_menu(name, parent, &body)
266    Main_popup_menu.new(name, parent, [], &body)
267  end
268
269  def make_dialog(label, *rest, &ok_cb)
270    reset_cb, clear_cb, target_cb, help_cb, help_str = optkey(rest,
271                                                              :reset_cb,
272                                                              :clear_cb,
273                                                              :target_cb,
274                                                              :help_cb,
275                                                              :info)
276    unless proc?(help_cb)
277      if string?(help_str) and !help_str.empty?
278        help_cb = lambda do |w, c, i|
279          help_dialog(label, help_str)
280        end
281      end
282    end
283    d = Dialog.new(label, ok_cb, reset_cb, clear_cb, target_cb, help_cb)
284    d.create_dialog
285    d
286  end
287
288  # simple comment formatter, indents comment text in popup.rb and nb.rb
289  # comment: long comment
290  #          text ...
291  #          and so on
292  def format_sound_comment(com)
293    if com.empty?
294      com
295    else
296      len = 0
297      text_length = if widget?(wid = dialog_widgets[Info_dialog])
298                      widget_size(wid).first / 10
299                    else
300                      56
301                    end
302      indent_length = "comment: ".length
303      str = ""
304      format("comment: %s", com).split(/ /).each do |s|
305        unless (len += s.length + 1) < text_length
306          len = indent_length + s.length
307          str << "\n" << " " * indent_length
308        end
309        str << s << " "
310      end
311      str << "\n"
312    end
313  end
314
315  $semi_range = 24 unless defined? $semi_range
316  $log_scale_ticks = 500 unless defined? $log_scale_ticks
317
318  Log2 = log(2.0)
319
320  def scale_log2linear(lo, val, hi)
321    log_lo = log([lo, 1.0].max) / Log2
322    log_hi = log(hi) / Log2
323    log_val = log(val) / Log2
324    $log_scale_ticks.to_f * ((log_val - log_lo) / (log_hi - log_lo))
325  end
326
327  def scale_linear2log(lo, val, hi)
328    log_lo = log([lo, 1.0].max) / Log2
329    log_hi = log(hi) / Log2
330    log_val = log_lo + ((val / $log_scale_ticks.to_f) * (log_hi - log_lo))
331    2.0 ** log_val
332  end
333
334  def scale_log_label(lo, val, hi)
335    format("%1.2f", scale_linear2log(lo, val, hi))
336  end
337
338  def semi_scale_label(val)
339    format("semitones: %d", val - $semi_range)
340  end
341
342  def semitones2ratio(val)
343    (2.0 ** val) / 12.0
344  end
345
346  def ratio2semitones(ratio)
347    (12.0 * (log(ratio) / log(2.0))).round
348  end
349
350  # get_color("ivory2")
351  # get_color(0.93, 0.93, 0.87)
352  # get_color(Ivory2) # from rgb.rb
353  def get_color(*new_color)
354    if string?(new_color[0])
355      create_color(new_color[0])
356    elsif new_color.length == 3
357      make_color(*new_color)
358    elsif color?(new_color[0])
359      new_color[0]
360    else
361      make_color(0.0, 0.0, 0.0)
362    end
363  end
364
365  def yellow_pixel
366    create_color("yellow")
367  end
368
369  def red_pixel
370    create_color("red")
371  end
372
373  def update_label(list)
374    if array?(list) and (not widget?(list))
375      list.each do |prc|
376        prc.call
377      end
378    end
379  end
380
381  add_help(:find_child,
382           "find_child(widget, name)  \
383Returns a widget named NAME, \
384if one can be found in the widget hierarchy beneath WIDGET.")
385  def find_child(widget, name)
386    res = false
387    each_child(widget) do |child|
388      if widget_name(child) == name
389        # INFO
390        # Wed Nov 17 14:41:50 CET 2010
391        # "return child"
392        # RETURN seems not to return a value with RUBY_VERSION 1.8.0
393        res = child
394        return res
395      end
396    end
397    if res
398      res
399    else
400      Snd.raise(:no_such_widget, name)
401    end
402  end
403
404  def set_label_sensitive(widget, name, set_p = false)
405    wid = Snd.catch(:no_such_widget) do
406      find_child(widget, name)
407    end.first
408    if widget?(wid)
409      set_sensitive(wid, set_p)
410    end
411  end
412
413  set_property(:show_disk_space, :labelled_snds, [])
414
415  def labelled_snds
416    property(:show_disk_space, :labelled_snds)
417  end
418
419  def kmg(num)
420    if num <= 0
421      "disk full!"
422    else
423      if num > 1024
424        if num > 1024 * 1024
425          format("space: %6.3fG", (num / (1024.0 * 1024)).round)
426        else
427          format("space: %6.3fM", (num / 1024.0).round)
428        end
429      else
430        format("space: %10dK", num)
431      end
432    end
433  end
434end
435
436class Dialog_base
437  def initialize(label, ok_cb, reset_cb, clear_cb, target_cb, help_cb)
438    @label     = label
439    @ok_cb     = ok_cb
440    @reset_cb  = reset_cb
441    @clear_cb  = clear_cb
442    @target_cb = target_cb
443    @help_cb   = help_cb
444    @doit      = Okay_string
445    @dismiss   = Dismiss_string
446    @help      = Help_string
447    @reset     = Reset_string
448    @clear     = Clear_string
449    @dialog    = nil
450    @parent    = nil
451    @reset_button   = nil
452    @clear_button   = nil
453    @okay_button    = nil
454    @dismiss_button = nil
455    @help_button    = nil
456  end
457  attr_reader :dialog, :parent, :okay_button
458
459  def doit_string(*args)
460    change_label(@okay_button, @doit = format(*args))
461  end
462
463  def dismiss_string(*args)
464    change_label(@dismiss_button, @dismiss = format(*args))
465  end
466
467  def help_string(*args)
468    change_label(@help_button, @help = format(*args))
469  end
470
471  def reset_string(*args)
472    change_label(@reset_button, @reset = format(*args))
473  end
474
475  def clear_string(*args)
476    change_label(@clear_button, @clear = format(*args))
477  end
478end
479
480#
481# --- Motif ---
482#
483module Snd_Motif
484  def make_snd_menu(name, args = [RXmNbackground, basic_color], &body)
485    Snd_main_menu.new(name, nil, args, &body)
486  end
487
488  def create_color(color)
489    col = RXColor()
490    dpy = RXtDisplay(main_widgets[Top_level_shell])
491    c = RXAllocNamedColor(dpy,
492                          RDefaultColormap(dpy, RDefaultScreen(dpy)),
493                          color, col, col)
494    if c.zero?
495      Snd.raise(:no_such_color, color, "can't allocate")
496    else
497      Rpixel(col)
498    end
499  end
500
501  def white_pixel
502    RWhitePixelOfScreen(current_screen)
503  end
504
505  def black_pixel
506    RBlackPixelOfScreen(current_screen)
507  end
508
509  def change_label(widget, string, property = RXmNlabelString)
510    xs = string2compound(string)
511    RXtSetValues(widget, [property, xs])
512    RXmStringFree(xs)
513  end
514
515  add_help(:each_child,
516           "each_child(w, &func)  \
517Applies FUNC to W and each of its children.")
518  add_help(:for_each_child,
519           "for_each_child(w, &func)  \
520Applies FUNC to W and each of its children.")
521  def each_child(widget, &body)
522    if RWidget?(widget)
523      body.call(widget)
524      if RXtIsComposite(widget)
525        (get_xtvalue(widget, RXmNchildren) or []).each do |wid|
526          each_child(wid, &body)
527        end
528      end
529    end
530  end
531  alias for_each_child each_child
532
533  def widget?(obj)
534    RWidget?(obj)
535  end
536
537  def is_managed?(wid)
538    RXtIsManaged(wid)
539  end
540
541  def widget_name(wid)
542    RXtName(wid)
543  end
544
545  def set_scale_value(widget, value, scaler = 1.0)
546    RXmScaleSetValue(widget, (value * Float(scaler)).round)
547  end
548
549  def get_scale_value(widget, info, scaler = 1.0)
550    Rvalue(info) / Float(scaler)
551  end
552
553  def raise_dialog(widget)
554    if RWidget?(widget) and RXtIsManaged(widget)
555      parent = RXtParent(widget)
556      if RWidget?(parent) and RXtIsSubclass(parent, RxmDialogShellWidgetClass)
557        RXtPopup(parent, RXtGrabNone)
558      end
559    end
560  end
561
562  def activate_dialog(dialog)
563    RXtIsManaged(dialog) ? raise_dialog(dialog) : RXtManageChild(dialog)
564  end
565
566  def set_sensitive(widget, flag)
567    RXtSetSensitive(widget, flag)
568  end
569
570  def add_main_pane(name, type, *args)
571    w = main_widgets[Notebook_outer_pane] or main_widgets[Main_sound_pane]
572    RXtCreateManagedWidget(name, type, w, *args)
573  end
574
575  def add_sound_pane(snd, name, type, *args)
576    RXtCreateManagedWidget(name, type, sound_widgets(snd)[Main_pane], *args)
577  end
578
579  def add_channel_pane(snd, chn, name, type, *args)
580    xp = RXtParent(RXtParent(channel_widgets(snd, chn)[Edhist]))
581    RXtCreateManagedWidget(name, type, xp, *args)
582  end
583
584  # string must be freed
585  def string2compound(*args)
586    args[0] = String(args[0])
587    RXmStringCreateLocalized(format(*args))
588  end
589
590  def compound2string(xstr)
591    RXmStringUnparse(xstr, false, RXmCHARSET_TEXT, RXmCHARSET_TEXT,
592                     false, 0, RXmOUTPUT_ALL)
593  end
594
595  def get_xtvalue(widget, item)
596    RXtVaGetValues(widget, [item, 0])[1]
597  end
598
599  def current_label(widget)
600    compound2string(get_xtvalue(widget, RXmNlabelString))
601  end
602
603  add_help(:current_screen,
604           "current_screen()  \
605Returns the current X screen number of the current display.")
606  def current_screen
607    RDefaultScreenOfDisplay(RXtDisplay(main_widgets[Top_level_shell]))
608  end
609
610  def get_pixmap(screen, file)
611    pix = RXmGetPixmap(screen, file, RBlackPixelOfScreen(screen),
612                       RWhitePixelOfScreen(screen))
613    if pix == RXmUNSPECIFIED_PIXMAP
614      Snd.raise(:snd_x_error, pix, "can't create pixmap")
615    else
616      pix
617    end
618  end
619
620  def screen_depth
621    RDefaultDepthOfScreen(current_screen)
622  end
623
624  add_help(:display_widget_tree,
625           "display_widget_tree(widget, spaces=\"\")  \
626Displays the hierarchy of widgets beneath WIDGET." )
627  def display_widget_tree(widget, spaces = "")
628    if (name = RXtName(widget)).null?
629      name = "<unnamed>"
630    end
631    Snd.display("%s%s", spaces, name)
632    if RXtIsComposite(widget)
633      (get_xtvalue(widget, RXmNchildren) or []).each do |w|
634        display_widget_tree(w, spaces + "  ")
635      end
636    end
637  end
638
639  add_help(:show_disk_space,
640           "show_disk_space(snd)  \
641Adds a label to the minibuffer area showing \
642the current free space (for use with $after_open_hook).")
643  def show_disk_space(snd)
644    previous_label = labelled_snds.detect do |n|
645      n.first == snd
646    end
647    unless previous_label
648      app = main_widgets[Top_level_application]
649      minibuffer = sound_widgets(snd)[Minibuffer]
650      name_form = RXtParent(minibuffer)
651      space = kmg(disk_kspace(file_name(snd)))
652      str = RXmStringCreateLocalized(space)
653      new_label = RXtCreateManagedWidget("space:",
654                                         RxmLabelWidgetClass, name_form,
655                                         [RXmNbackground, basic_color,
656                                          RXmNleftAttachment, RXmATTACH_WIDGET,
657                                          RXmNleftWidget, minibuffer,
658                                          RXmNlabelString, str,
659                                          RXmNrightAttachment, RXmATTACH_NONE,
660                                          RXmNtopAttachment, RXmATTACH_FORM])
661      RXmStringFree(str)
662      previous_label = [snd, new_label, app]
663      labelled_snds.push(previous_label)
664    end
665    show_label = lambda do |data, id|
666      if sound?(data.first)
667        space = kmg(disk_kspace(file_name(data.first)))
668        str = RXmStringCreateLocalized(space)
669        RXtSetValues(data[1], [RXmNlabelString, str])
670        RXmStringFree(str)
671        RXtAppAddTimeOut(data[2], 10000, show_label, data)
672      end
673    end
674    RXtAppAddTimeOut(previous_label[2], 10000, show_label, previous_label)
675  end
676  # $after_open_hook.add_hook!("disk-space", &method(:show_disk_space).to_proc)
677
678  add_help(:menu_option,
679           "menu_option(name)  \
680Finds the widget associated with a given menu item NAME.")
681  def menu_option(name)
682    menu_widgets.cdr.each do |top_menu|
683      each_child(top_menu) do |w|
684        option_holder = RXtGetValues(w, [RXmNsubMenuId, 0]).cadr
685        each_child(option_holder) do |menu|
686          if name == RXtName(menu)
687            return menu
688          else
689            if RXmIsCascadeButton(menu)
690              options = RXtGetValues(menu, [RXmNsubMenuId, 0]).cadr
691              each_child(options) do |inner_menu|
692                if name == RXtName(inner_menu)
693                  return inner_menu
694                end
695              end
696            end
697          end
698        end
699      end
700    end
701    Snd.raise(:no_such_menu, name)
702  end
703
704  add_help(:set_main_color_of_widget,
705           "set_main_color_of_widget(widget)  \
706Sets the background color of WIDGET.")
707  def set_main_color_of_widget(w)
708    each_child(w) do |n|
709      if RXtIsWidget(n)
710        if RXmIsScrollBar(n)
711          RXmChangeColor(n, position_color)
712        else
713          RXmChangeColor(n, basic_color)
714        end
715      end
716    end
717  end
718
719  #
720  # add_mark_pane
721  #
722  # Adds a pane to each channel giving the current mark locations
723  # (sample values).  These can be edited to move the mark, or deleted
724  # to delete the mark.  Can't use channel-property here because the
725  # widget lists are permanent (just unmanaged)
726
727  $including_mark_pane = false    # for prefs
728
729  def add_mark_pane
730    mark_pane = Mark_pane.new
731    $mark_hook.add_hook!("remark") do |id, snd, chn, reason|
732      mark_pane.make_list(snd, chn)
733    end
734    $close_hook.add_hook!("unremark") do |snd|
735      channels(snd).times do |chn|
736        mark_pane.deactivate_channel(snd, chn)
737      end
738    end
739    $after_open_hook.add_hook!("open-remarks") do |snd|
740      chans(snd).times do |chn|
741        after_edit_hook(snd, chn).add_hook!("open-remarks") do | |
742          if RWidget?(mark_pane.list(snd, chn))
743            mark_pane.make_list(snd, chn)
744          end
745        end
746        undo_hook(snd, chn).add_hook!("open-remarks") do | |
747          if RWidget?(mark_pane.list(snd, chn))
748            mark_pane.make_list(snd, chn)
749          end
750        end
751      end
752    end
753    $update_hook.add_hook!("") do |snd|
754      lambda do |update_snd|
755        chans(update_snd).times do |chn|
756          mark_pane.make_list(update_snd, chn)
757        end
758      end
759    end
760    $including_mark_pane = true
761  end
762
763  class Mark_pane
764    def initialize
765      @mark_list_lengths = []
766      @mark_lists = []
767    end
768
769    def make_list(snd, chn)
770      deactivate_channel(snd, chn)
771      unless RWidget?(list(snd, chn))
772        mark_box = add_channel_pane(snd, chn, "mark-box", RxmFormWidgetClass,
773                                    [RXmNbackground, basic_color,
774                                     RXmNorientation, RXmVERTICAL,
775                                     RXmNpaneMinimum, 100,
776                                     RXmNbottomAttachment, RXmATTACH_FORM])
777        ls = [RXmNbackground, highlight_color,
778              RXmNleftAttachment, RXmATTACH_FORM,
779              RXmNrightAttachment, RXmATTACH_FORM,
780              RXmNalignment, RXmALIGNMENT_CENTER,
781              RXmNtopAttachment, RXmATTACH_FORM]
782        mark_label = RXtCreateManagedWidget("Marks",
783                                            RxmLabelWidgetClass, mark_box, ls)
784        ls = [RXmNbackground, basic_color,
785              RXmNscrollingPolicy, RXmAUTOMATIC,
786              RXmNscrollBarDisplayPolicy, RXmSTATIC,
787              RXmNleftAttachment, RXmATTACH_FORM,
788              RXmNrightAttachment, RXmATTACH_FORM,
789              RXmNtopAttachment, RXmATTACH_WIDGET,
790              RXmNtopWidget, mark_label,
791              RXmNbottomAttachment, RXmATTACH_FORM]
792        mark_scr = RXtCreateManagedWidget("mark-scr",
793                                          RxmScrolledWindowWidgetClass,
794                                          mark_box, ls)
795        ls = [RXmNorientation, RXmVERTICAL,
796              RXmNtopAttachment, RXmATTACH_FORM,
797              RXmNbottomAttachment, RXmATTACH_FORM,
798              RXmNspacing, 0]
799        mlist = RXtCreateManagedWidget("mark-list",
800                                       RxmRowColumnWidgetClass, mark_scr, ls)
801        set_main_color_of_widget(mark_scr)
802        RXtSetValues(mark_box, [RXmNpaneMinimum, 1])
803        set_list(snd, chn, mlist)
804      end
805      lst = list(snd, chn)
806      new_marks = Snd.marks(snd, chn)
807      current_list_length = @mark_list_lengths.length
808      if new_marks.length > current_list_length
809        current_list_length.upto(new_marks.length) do
810          tf = RXtCreateWidget("field", RxmTextFieldWidgetClass, lst,
811                               [RXmNbackground, basic_color])
812          RXtAddCallback(tf, RXmNfocusCallback,
813                         lambda do |w, c, i|
814                           RXtSetValues(w, [RXmNbackground, text_focus_color])
815                         end)
816          RXtAddCallback(tf, RXmNlosingFocusCallback,
817                         lambda do |w, c, i|
818                           RXtSetValues(w, [RXmNbackground, basic_color])
819                         end)
820          RXtAddCallback(tf, RXmNactivateCallback,
821                         lambda do |w, c, i|
822                           id = RXtGetValues(w, [RXmNuserData, 0]).cadr
823                           txt = RXtGetValues(w, [RXmNvalue, 0]).cadr
824                           if string?(txt) and txt.length > 0
825                             set_mark_sample(id, txt.to_i)
826                           else
827                             delete_mark(id)
828                           end
829                           RXtSetValues(w, [RXmNbackground, basic_color])
830                         end)
831          RXtAddEventHandler(tf, REnterWindowMask, false,
832                             lambda do |w, c, i, f|
833                               $mouse_enter_text_hook.call(w)
834                             end)
835          RXtAddEventHandler(tf, RLeaveWindowMask, false,
836                             lambda do |w, c, i, f|
837                               $mouse_leave_text_hook.call(w)
838                             end)
839        end
840      end
841      set_length(snd, chn, new_marks.length)
842      RXtGetValues(lst, [RXmNchildren, 0], 1).cadr.each do |n|
843        break if new_marks.empty?
844        if RXmIsTextField(n)
845          mk = new_marks.shift
846          RXtSetValues(n, [RXmNvalue, mark_sample(mk).to_s, RXmNuserData, mk])
847          RXtManageChild(n)
848        end
849      end
850      false
851    end
852
853    def deactivate_channel(snd, chn)
854      if length(snd, chn) > 0 and RWidget?(list(snd, chn))
855        RXtGetValues(list(snd, chn), [RXmNchildren, 0], 1).cadr.each do |n|
856          RXtUnmanageChild(n)
857        end
858      end
859    end
860
861    def list(snd, chn)
862      find(snd, chn, @mark_lists)
863    end
864
865    private
866    def find(snd, chn, dats)
867      val = dats.detect do |dat|
868        snd == dat.car and chn == dat.cadr
869      end
870      if val
871        val.caddr
872      else
873        false
874      end
875    end
876
877    def length(snd, chn)
878      find(snd, chn, @mark_list_lengths) or 0
879    end
880
881    def set_length(snd, chn, len)
882      @mark_list_lengths.delete_if do |dat|
883        snd == dat.car and chn == dat.cadr
884      end
885      @mark_list_lengths.push([snd, chn, len])
886    end
887
888    def set_list(snd, chn, wid)
889      @mark_lists.push([snd, chn, wid])
890    end
891  end
892
893  class Variable_display
894    include Snd_XM
895
896    def initialize(page_name, variable_name)
897      @name = page_name
898      @variable = variable_name
899      @@dialog = nil unless defined? @@dialog
900      @@pages = {} unless defined? @@pages
901      @@notebook = nil unless defined? @@notebook
902      @widget = nil
903      @snd = false
904      @data = nil
905      @default_background = nil
906      create
907    end
908    attr_reader :snd, :data
909
910    def dialog_widget
911      @@dialog
912    end
913
914    def inspect
915      format("%s.new(%s, %s)", self.class, @name, @variable)
916    end
917
918    def make_dialog
919      xdismiss = RXmStringCreateLocalized("Dismiss")
920      titlestr = RXmStringCreateLocalized("Variables")
921      @@dialog = RXmCreateTemplateDialog(main_widgets[Top_level_shell],
922                                         "variables-dialog",
923                                         [RXmNokLabelString, xdismiss,
924                                          RXmNautoUnmanage, false,
925                                          RXmNdialogTitle, titlestr,
926                                          RXmNresizePolicy, RXmRESIZE_GROW,
927                                          RXmNnoResize, false,
928                                          RXmNtransient, false,
929                                          RXmNheight, 400,
930                                          RXmNwidth, 400,
931                                          RXmNbackground, basic_color])
932      RXtAddCallback(@@dialog, RXmNokCallback,
933                     lambda do |w, c, i|
934                       RXtUnmanageChild(@@dialog)
935                     end)
936      RXmStringFree(xdismiss)
937      RXmStringFree(titlestr)
938      ls = [RXmNleftAttachment, RXmATTACH_FORM,
939            RXmNrightAttachment, RXmATTACH_FORM,
940            RXmNtopAttachment, RXmATTACH_FORM,
941            RXmNbottomAttachment, RXmATTACH_WIDGET,
942            RXmNbottomWidget,
943            RXmMessageBoxGetChild(@@dialog, RXmDIALOG_SEPARATOR),
944            RXmNbackground, basic_color,
945            RXmNframeBackground, zoom_color,
946            RXmNbindingWidth, 14]
947      @@notebook = RXtCreateManagedWidget("variables-notebook",
948                                          RxmNotebookWidgetClass, @@dialog, ls)
949      RXtManageChild(@@dialog)
950      c = RDefaultScreenOfDisplay(RXtDisplay(@@dialog))
951      @default_background = RWhitePixelOfScreen(c)
952    end
953
954    def create
955      unless RWidget?(@@dialog)
956        make_dialog
957      end
958      unless @@pages[@name]
959        panes = RXtCreateManagedWidget(@name, RxmPanedWindowWidgetClass,
960                                       @@notebook, [])
961        simple_cases = RXtCreateManagedWidget(@name,
962                                              RxmRowColumnWidgetClass, panes,
963                                              [RXmNorientation, RXmVERTICAL,
964                                               RXmNpaneMinimum, 30,
965                                               RXmNbackground, basic_color])
966        RXtCreateManagedWidget(@name, RxmPushButtonWidgetClass, @@notebook,
967                               [RXmNnotebookChildType, RXmMAJOR_TAB,
968                                RXmNbackground, basic_color])
969        @@pages[@name] = [@name, panes, simple_cases]
970      end
971      @@pages[@name]
972    end
973
974    def close
975      RXtUnmanageChild(@@dialog)
976    end
977
978    def reset
979    end
980  end
981
982  class Variable_display_text < Variable_display
983    def create
984      page_info = super
985      row_pane = page_info[2]
986      var_label = @variable + ":"
987      row = RXtCreateManagedWidget(@variable + "-row",
988                                   RxmRowColumnWidgetClass, row_pane,
989                                   [RXmNorientation, RXmHORIZONTAL,
990                                    RXmNbackground, basic_color])
991      RXtCreateManagedWidget(var_label, RxmLabelWidgetClass, row,
992                             [RXmNbackground, basic_color])
993      @widget = RXtCreateManagedWidget(@variable + "-value",
994                                       RxmTextFieldWidgetClass, row,
995                                       [RXmNeditable, false,
996                                        RXmNresizeWidth, true,
997                                        RXmNbackground, @default_background])
998    end
999
1000    def display(var)
1001      old_str = RXmTextFieldGetString(@widget)
1002      new_str = var.to_s
1003      if old_str != new_str
1004        RXmTextFieldSetString(@widget, new_str)
1005        if RXtIsManaged(@widget)
1006          RXmUpdateDisplay(@widget)
1007        end
1008      end
1009      var
1010    end
1011  end
1012
1013  class Variable_display_scale < Variable_display
1014    def initialize(page_name, variable_name, range = [0.0, 1.0])
1015      @range = range
1016      super(page_name, variable_name)
1017    end
1018
1019    def inspect
1020      format("%s.new(%s, %s, %s)", self.class, @name, @variable, @range)
1021    end
1022
1023    def create
1024      page_info = super()
1025      row_pane = page_info[2]
1026      var_label = @variable + ":"
1027      title = RXmStringCreateLocalized(var_label)
1028      @widget = RXtCreateManagedWidget(@variable,
1029                                       RxmScaleWidgetClass, row_pane,
1030                                       [RXmNbackground, basic_color,
1031                                        RXmNslidingMode, RXmTHERMOMETER,
1032                                        RXmNminimum, (100.0 * @range[0]).floor,
1033                                        RXmNmaximum, (100.0 * @range[1]).floor,
1034                                        RXmNdecimalPoints, 2,
1035                                        RXmNtitleString, title,
1036                                        RXmNorientation, RXmHORIZONTAL,
1037                                        RXmNshowValue, RXmNEAR_BORDER])
1038      wid = Snd.catch(:no_such_widget) do
1039        find_child(@widget, "Scrollbar")
1040      end.first
1041      if widget?(wid)
1042        RXtVaSetValues(wid, [RXmNtroughColor, red_pixel])
1043      end
1044      RXmStringFree(title)
1045    end
1046
1047    def display(var)
1048      RXmScaleSetValue(@widget, (100.0 * var).floor)
1049      var
1050    end
1051  end
1052
1053  class Variable_display_graph < Variable_display
1054    def create
1055      page_info = super
1056      pane = page_info[1]
1057      var_label = @variable + ":"
1058      form = RXtCreateManagedWidget(var_label, RxmFormWidgetClass, pane,
1059                                    [RXmNpaneMinimum, 100])
1060      @snd = make_variable_graph(form, @variable + ": time",
1061                                 2048, mus_srate.to_i)
1062      @data = channel_data(@snd, 0)
1063    end
1064
1065    def display(var)
1066      frames = @data.length
1067      loc = cursor(snd, 0)
1068      @data[loc] = var
1069      if time_graph?(@snd)
1070        update_time_graph(@snd)
1071      end
1072      if transform_graph?(@snd)
1073        update_transform_graph(@snd)
1074      end
1075      if loc + 1 == frames
1076        set_cursor(0, @snd, 0)
1077      else
1078        set_cursor(loc + 1, @snd, 0)
1079      end
1080      var
1081    end
1082
1083    def reset
1084      set_cursor(0, @snd, 0)
1085      @data.fill(0.0)
1086    end
1087  end
1088
1089  class Variable_display_spectrum < Variable_display
1090    def create
1091      page_info = super
1092      pane = page_info[1]
1093      var_label = @variable + ":"
1094      form = RXtCreateManagedWidget(var_label, RxmFormWidgetClass, pane,
1095                                    [RXmNpaneMinimum, 100])
1096      @snd = make_variable_graph(form, @variable, 2048, mus_srate.to_i)
1097      set_time_graph?(false, @snd, 0)
1098      set_transform_graph?(true, @snd, 0)
1099      set_x_axis_label(@variable + ": frequency", @snd, 0, Transform_graph)
1100      @data = channel_data(@snd, 0)
1101    end
1102
1103    def display(var)
1104      frames = @data.length
1105      loc = cursor(snd, 0)
1106      @data[loc] = var
1107      if time_graph?(@snd)
1108        update_time_graph(@snd)
1109      end
1110      if transform_graph?(@snd)
1111        update_transform_graph(@snd)
1112      end
1113      if loc + 1 == frames
1114        set_cursor(0, @snd, 0)
1115      else
1116        set_cursor(loc + 1, @snd, 0)
1117      end
1118      var
1119    end
1120
1121    def reset
1122      set_cursor(0, @snd, 0)
1123      @data.fill(0.0)
1124    end
1125  end
1126
1127  def make_variable_display(page_name, variable_name,
1128                            type = :text, range = [0.0, 1.0])
1129    case type
1130    when :text
1131      Variable_display_text.new(page_name, variable_name)
1132    when :scale
1133      Variable_display_scale.new(page_name, variable_name, range)
1134    when :graph
1135      Variable_display_graph.new(page_name, variable_name)
1136    when :spectrum
1137      Variable_display_spectrum.new(page_name, variable_name)
1138    else
1139      nil
1140    end
1141  end
1142
1143  def variable_display(vd, var)
1144    vd.display(var)
1145  end
1146
1147  def variable_display_close(vd)
1148    vd.close
1149  end
1150
1151  def variable_display_reset(vd)
1152    vd.reset
1153  end
1154
1155  def variable_display?(vd)
1156    vd.kind_of?(Variable_display)
1157  end
1158
1159  class Scale_widget
1160    include Snd_XM
1161
1162    def initialize(parent)
1163      @parent = parent
1164      @scale = nil
1165      @label = nil
1166    end
1167    attr_reader :scale, :label
1168
1169    def add_scale(title, low, init, high, scale, kind)
1170      xtitle = string2compound(title)
1171      rc = RXtCreateManagedWidget("rc", RxmRowColumnWidgetClass, @parent,
1172                                  [RXmNorientation, RXmVERTICAL,
1173                                   RXmNbackground, highlight_color])
1174      case kind
1175      when :log
1176        s = format("%1.2f", init),
1177        @label = RXtCreateManagedWidget(s, RxmLabelWidgetClass, rc,
1178                                        [RXmNalignment, RXmALIGNMENT_BEGINNING,
1179                                         RXmNbackground, basic_color])
1180        @scale = general_scale(rc, title, xtitle)
1181        RXtVaSetValues(@scale,
1182                       [RXmNmaximum, $log_scale_ticks,
1183                        RXmNvalue, scale_log2linear(low, init, high).round])
1184        RXtAddCallback(@scale, RXmNvalueChangedCallback,
1185                       lambda do |w, c, i|
1186                         change_label(@label,
1187                                      scale_log_label(low, Rvalue(i), high))
1188                       end)
1189        RXtAddCallback(@scale, RXmNdragCallback,
1190                       lambda do |w, c, i|
1191                         change_label(@label,
1192                                      scale_log_label(low, Rvalue(i), high))
1193                       end)
1194      when :semi
1195        s = format("semitones: %d", ratio2semitones(init))
1196        @label = RXtCreateManagedWidget(s, RxmLabelWidgetClass, rc,
1197                                        [RXmNalignment, RXmALIGNMENT_BEGINNING,
1198                                         RXmNbackground, basic_color])
1199        @scale = general_scale(rc, title, xtitle)
1200        RXtVaSetValues(@scale,
1201                       [RXmNmaximum, 2 * $semi_range,
1202                        RXmNvalue, $semi_range + ratio2semitones(init)])
1203        RXtAddCallback(@scale, RXmNvalueChangedCallback,
1204                       lambda do |w, c, i|
1205                         change_label(@label, semi_scale_label(Rvalue(i)))
1206                       end)
1207        RXtAddCallback(@scale, RXmNdragCallback,
1208                       lambda do |w, c, i|
1209                         change_label(@label, semi_scale_label(Rvalue(i)))
1210                       end)
1211      else
1212        @scale = linear_scale(rc, title, xtitle, low, init, high, scale)
1213      end
1214      RXmStringFree(xtitle)
1215    end
1216
1217    private
1218    def linear_scale(parent, title, xtitle, low, init, high, scale)
1219      RXtCreateManagedWidget(title, RxmScaleWidgetClass, parent,
1220                             [RXmNorientation, RXmHORIZONTAL,
1221                              RXmNshowValue, true,
1222                              RXmNminimum, (low * scale).round,
1223                              RXmNmaximum, (high * scale).round,
1224                              RXmNtitleString, xtitle,
1225                              RXmNbackground, basic_color,
1226                              RXmNvalue, (init * scale).round,
1227                              RXmNdecimalPoints, case scale
1228                                                 when 1000
1229                                                   3
1230                                                 when 100
1231                                                   2
1232                                                 when 10
1233                                                   1
1234                                                 else
1235                                                   0
1236                                                 end])
1237    end
1238
1239    def general_scale(parent, title, xtitle)
1240      RXtCreateManagedWidget(title, RxmScaleWidgetClass, parent,
1241                             [RXmNorientation,   RXmHORIZONTAL,
1242                              RXmNshowValue,     false,
1243                              RXmNminimum,       0,
1244                              RXmNdecimalPoints, 0,
1245                              RXmNtitleString,   xtitle,
1246                              RXmNbackground,    basic_color])
1247    end
1248  end
1249
1250  class Dialog < Dialog_base
1251    include Snd_XM
1252
1253    def create_dialog
1254      xdismiss = RXmStringCreateLocalized(@dismiss)
1255      xhelp    = RXmStringCreateLocalized(@help)
1256      xok      = RXmStringCreateLocalized(@doit)
1257      titlestr = RXmStringCreateLocalized(@label)
1258      @dialog  = RXmCreateTemplateDialog(main_widgets[Top_level_shell], @label,
1259                                         [RXmNcancelLabelString, xdismiss,
1260                                          RXmNhelpLabelString,   xhelp,
1261                                          RXmNokLabelString,     xok,
1262                                          RXmNautoUnmanage,      false,
1263                                          RXmNdialogTitle,       titlestr,
1264                                          RXmNresizePolicy,      RXmRESIZE_GROW,
1265                                          RXmNnoResize,          false,
1266                                          RXmNbackground,        basic_color,
1267                                          RXmNtransient,         false])
1268      RXmStringFree(xhelp)
1269      RXmStringFree(xok)
1270      RXmStringFree(xdismiss)
1271      RXmStringFree(titlestr)
1272      if defined?(R_XEditResCheckMessages())
1273        RXtAddEventHandler(RXtParent(@dialog), 0, true,
1274                           lambda do |w, c, i, f|
1275                             R_XEditResCheckMessages(w, c, i, f)
1276                           end)
1277      end
1278      [[RXmDIALOG_HELP_BUTTON,   highlight_color],
1279       [RXmDIALOG_CANCEL_BUTTON, highlight_color],
1280       [RXmDIALOG_OK_BUTTON,     highlight_color]].each do |button, color|
1281        RXtVaSetValues(RXmMessageBoxGetChild(@dialog, button),
1282                       [RXmNarmColor,   selection_color,
1283                        RXmNbackground, color])
1284      end
1285      RXtAddCallback(@dialog, RXmNcancelCallback,
1286                     lambda do |w, c, i|
1287                       RXtUnmanageChild(@dialog)
1288                     end)
1289      RXtAddCallback(@dialog, RXmNhelpCallback,
1290                     lambda do |w, c, i|
1291                       @help_cb.call(w, c, i)
1292                     end)
1293      RXtAddCallback(@dialog, RXmNokCallback,
1294                     lambda do |w, c, i|
1295                       @ok_cb.call(w, c, i)
1296                     end)
1297      vals = [RXmNbackground, highlight_color,
1298              RXmNforeground, black_pixel,
1299              RXmNarmColor,   selection_color]
1300      if @clear_cb
1301        @clear_button = RXtCreateManagedWidget(@clear,
1302                                               RxmPushButtonWidgetClass,
1303                                               @dialog, vals)
1304        RXtAddCallback(@clear_button, RXmNactivateCallback,
1305                       lambda do |w, c, i|
1306                         @clear_cb.call(w, c, i)
1307                       end)
1308      end
1309      if @reset_cb
1310        @reset_button = RXtCreateManagedWidget(@reset,
1311                                               RxmPushButtonWidgetClass,
1312                                               @dialog, vals)
1313        RXtAddCallback(@reset_button, RXmNactivateCallback,
1314                       lambda do |w, c, i|
1315                         @reset_cb.call(w, c, i)
1316                       end)
1317      end
1318      @help_button    = RXmMessageBoxGetChild(@dialog, RXmDIALOG_HELP_BUTTON)
1319      @dismiss_button = RXmMessageBoxGetChild(@dialog, RXmDIALOG_CANCEL_BUTTON)
1320      @okay_button    = RXmMessageBoxGetChild(@dialog, RXmDIALOG_OK_BUTTON)
1321      if @target_cb
1322        RXtSetSensitive(@okay_button, @target_cb.call())
1323        $effects_hook.add_hook!("create-dialog-target") do | |
1324          RXtSetSensitive(@okay_button, @target_cb.call())
1325        end
1326      else
1327        RXtSetSensitive(@okay_button, (not Snd.sounds.empty?))
1328        $effects_hook.add_hook!("create-dialog-target") do | |
1329          RXtSetSensitive(@okay_button, (not Snd.sounds.empty?))
1330        end
1331      end
1332      @parent = RXtCreateManagedWidget("pane",
1333                                       RxmPanedWindowWidgetClass, @dialog,
1334                                       [RXmNsashHeight,  1,
1335                                        RXmNsashWidth,   1,
1336                                        RXmNbackground,  basic_color,
1337                                        RXmNseparatorOn, true,
1338                                        RXmNalignment,   RXmALIGNMENT_BEGINNING,
1339                                        RXmNorientation, RXmVERTICAL])
1340    end
1341
1342    # kind :log, :semi, :linear
1343    # returns instance of Scale_widget not widget
1344    # so we can access the widget and label if needed
1345    # slider = @dialog.add_slider(...)
1346    # slider.scale --> widget
1347    # slider.label --> label
1348    def add_slider(title, low, init, high,
1349                   scale = 1, kind = :linear, parent = @parent, &func)
1350      slider = Scale_widget.new(parent)
1351      slider.add_scale(title, low, init, high, scale, kind)
1352      unless proc?(func) and func.arity == 3
1353        func = lambda do |w, c, i|
1354          func.call
1355        end
1356      end
1357      RXtAddCallback(slider.scale, RXmNvalueChangedCallback, func)
1358      slider
1359    end
1360
1361    # change_cb.arity == 1
1362    def add_toggle(label = "truncate at end", value = true, &change_cb)
1363      button = RXtCreateManagedWidget(label,
1364                                      RxmToggleButtonWidgetClass, @parent,
1365                                      [RXmNbackground, basic_color,
1366                                       RXmNalignment, RXmALIGNMENT_BEGINNING,
1367                                       RXmNset, value,
1368                                       RXmNselectColor, yellow_pixel])
1369      RXtAddCallback(button, RXmNvalueChangedCallback,
1370                     lambda do |w, c, i|
1371                       change_cb.call(Rset(i))
1372                     end)
1373      h = get_xtvalue(button, RXmNheight)
1374      h += (h * 0.1).round
1375      RXtVaSetValues(button, [RXmNpaneMinimum, h, RXmNpaneMaximum, h])
1376      button
1377    end
1378
1379    # target_cb.arity == 1
1380    def add_target(labels = [["entire sound",  :sound,     true],
1381                             ["selection",     :selection, false],
1382                             ["between marks", :marks,     false]], &target_cb)
1383      RXtCreateManagedWidget("sep", RxmSeparatorWidgetClass, @parent,
1384                             [RXmNorientation,   RXmHORIZONTAL,
1385                              RXmNseparatorType, RXmSHADOW_ETCHED_OUT,
1386                              RXmNbackground,    basic_color])
1387      ls = [RXmNorientation,      RXmHORIZONTAL,
1388            RXmNbackground,       basic_color,
1389            RXmNradioBehavior,    true,
1390            RXmNradioAlwaysOne,   true,
1391            RXmNbottomAttachment, RXmATTACH_FORM,
1392            RXmNleftAttachment,   RXmATTACH_FORM,
1393            RXmNrightAttachment,  RXmATTACH_FORM,
1394            RXmNentryClass,       RxmToggleButtonWidgetClass,
1395            RXmNisHomogeneous,    true]
1396      rc = RXtCreateManagedWidget("rc", RxmRowColumnWidgetClass, @parent, ls)
1397      labels.map do |name, type, on|
1398        RXtCreateManagedWidget(name, RxmToggleButtonWidgetClass, rc,
1399                               [RXmNbackground, basic_color,
1400                                RXmNselectColor, yellow_pixel,
1401                                RXmNset, on,
1402                                RXmNindicatorType, RXmONE_OF_MANY_ROUND,
1403                                RXmNarmCallback, [lambda do |w, c, i|
1404                                                    target_cb.call(type)
1405                                                  end, false]])
1406      end
1407      rc
1408    end
1409
1410    def add_frame(args = [])
1411      RXtCreateManagedWidget("frame", RxmFrameWidgetClass, @parent, args)
1412      # [RXmNshadowThickness, 4, RXmNshadowType, RXmSHADOW_ETCHED_OUT]
1413    end
1414
1415    def add_label(label, args = [])
1416      RXtCreateManagedWidget(label, RxmLabelWidgetClass, @parent,
1417                             [RXmNalignment, RXmALIGNMENT_BEGINNING,
1418                              RXmNbackground, basic_color] + args)
1419    end
1420
1421    def add_textfield(string, label = nil, columns = 80, &activate_cb)
1422      rc = RXtCreateManagedWidget("rc", RxmRowColumnWidgetClass, @parent,
1423                                  [RXmNorientation, RXmVERTICAL,
1424                                   RXmNbackground, basic_color])
1425      if string?(label)
1426        RXtCreateManagedWidget(label, RxmLabelWidgetClass, rc,
1427                               [RXmNalignment, RXmALIGNMENT_BEGINNING,
1428                                RXmNbackground, basic_color])
1429      end
1430      text_field = RXtCreateManagedWidget("text", RxmTextFieldWidgetClass, rc,
1431                                          [RXmNvalue, string,
1432                                           RXmNresizeWidth, false,
1433                                           RXmNcolumns, columns,
1434                                           RXmNbackground, basic_color])
1435      RXtAddCallback(text_field, RXmNactivateCallback, activate_cb)
1436      RXtAddCallback(text_field, RXmNfocusCallback,
1437                     lambda do |w, c, i|
1438                       RXtSetValues(w, [RXmNbackground, text_focus_color])
1439                     end)
1440      RXtAddCallback(text_field, RXmNlosingFocusCallback,
1441                     lambda do |w, c, i|
1442                       RXtSetValues(w, [RXmNbackground, basic_color])
1443                     end)
1444      RXtAddEventHandler(text_field, REnterWindowMask, false,
1445                         lambda do |w, c, i, f|
1446                           $mouse_enter_text_hook.call(w)
1447                         end)
1448      RXtAddEventHandler(text_field, RLeaveWindowMask, false,
1449                         lambda do |w, c, i, f|
1450                           $mouse_leave_text_hook.call(w)
1451                         end)
1452      text_field
1453    end
1454
1455    def add_text(*args)
1456      rows, columns, wordwrap, value, horizontal = optkey(args,
1457                                                          [:rows, 16],
1458                                                          [:columns, 60],
1459                                                          [:wordwrap, true],
1460                                                          [:value, ""],
1461                                                          [:scroll_horizontal,
1462                                                           false])
1463      text = RXmCreateScrolledText(@parent, "text",
1464                                   [RXmNtopAttachment, RXmATTACH_WIDGET,
1465                                    RXmNeditMode, RXmMULTI_LINE_EDIT,
1466                                    RXmNrows, rows,
1467                                    RXmNcolumns, columns,
1468                                    RXmNwordWrap, wordwrap,
1469                                    RXmNscrollHorizontal, horizontal,
1470                                    RXmNvalue, value,
1471                                    RXmNbackground, basic_color])
1472      RXtAddCallback(text, RXmNfocusCallback,
1473                     lambda do |w, c, i|
1474                       RXtSetValues(w, [RXmNbackground, text_focus_color])
1475                     end)
1476      RXtAddCallback(text, RXmNlosingFocusCallback,
1477                     lambda do |w, c, i|
1478                       RXtSetValues(w, [RXmNbackground, basic_color])
1479                     end)
1480      RXtAddEventHandler(text, REnterWindowMask, false,
1481                         lambda do |w, c, i, f|
1482                           $mouse_enter_text_hook.call(w)
1483                         end)
1484      RXtAddEventHandler(text, RLeaveWindowMask, false,
1485                         lambda do |w, c, i, f|
1486                           $mouse_leave_text_hook.call(w)
1487                         end)
1488      RXtManageChild(text)
1489      text
1490    end
1491  end
1492end
1493
1494module Snd_XM
1495  include Snd_Motif
1496  alias is_managed is_managed?
1497end
1498
1499=begin
1500add_channel_pane(0, 0, "new-pane", RxmDrawingAreaWidgetClass,
1501                 [RXmNbackground, graph_color, RXmNforeground, data_color])
1502=end
1503
1504# SND_MAIN_MENU (for a similar popup menu class see popup.rb)
1505#
1506# make_snd_menu(name, args) do ... end
1507#
1508# class Menu
1509#   initialize(name, menu, args)
1510#   menu
1511#   each_entry do |child| ... end
1512#   change_menu_color(new_color)
1513#
1514# class Snd_main_menu < Menu
1515#   initialize(name, parent, args) do ... end
1516#   menu_number
1517#   entry(klass, *rest) or entry(name) do ... end
1518#   separator
1519#   cascade(name, args) do ... end
1520#
1521# `Snd_main_menu#entry(arg, *rest, &body)': If ARG is of kind Class,
1522# `entry' calls klass.new(*rest), so you can set initialize values
1523# (e.g. the label or other args).  If ARG is not of kind Class it is
1524# taken as a label string and a block must exist.  Classes for the
1525# menu must have a method `post_dialog' and `inspect'.  `inspect'
1526# shows the values in the menu label.  See the various examples in
1527# effects.rb.
1528#
1529# class Foo
1530#   def initialize(label, val1, val2)
1531#     @label = label
1532#     @val1 = val1
1533#     @val2 = val2
1534#     @dialog = nil
1535#     ...
1536#   end
1537#
1538#   def inspect
1539#     format("%s (%.3f %.3f", @label, @val1, @val2)
1540#   end
1541#
1542#   def post_dialog
1543#     ...
1544#     unless @dialog.kind_of?(Dialog) and RWidget?(@dialog.dialog)
1545#       ...
1546#       @dialog = make_dialog(@label,
1547#                             :info, "Help text",
1548#                             :reset_cb, lambda do |w, c, i|
1549#                               ... (reset your values)
1550#                             end) do |w, c, i|
1551#         ... (main action)
1552#       end
1553#       ...
1554#     end
1555#     activate_dialog(@dialog.dialog)
1556#   end
1557# end
1558#
1559# make_snd_menu("Foo Menu") do
1560#   entry(Foo, 3.14, 0.0)
1561# end
1562
1563=begin
1564# example menu using Effects (see effects.rb and new-effects.scm)
1565require "effects"
1566
1567make_snd_menu("Effects") do
1568  cascade("Amplitude Effects") do
1569    entry(Gain, "Gain")
1570    entry(Normalize, "Normalize")
1571    entry(Gate, "Gate")
1572  end
1573  cascade("Delay Effects") do
1574    entry(Echo, "Echo")
1575    entry(Filtered_echo, "Filtered echo")
1576    entry(Modulated_echo, "Modulated echo")
1577  end
1578  separator
1579  entry("Octave-down") do
1580    down_oct
1581  end
1582  entry("Remove DC") do
1583    lastx = lasty = 0.0
1584    map_chan(lambda do |inval|
1585               lasty = inval + (0.999 * lasty - lastx)
1586               lastx = inval
1587               lasty
1588             end)
1589  end
1590  entry("Spiker") do
1591    spike
1592  end
1593end
1594=end
1595
1596class Menu
1597  include Snd_XM
1598
1599  def initialize(name, menu, args)
1600    @label = name
1601    @menu = menu
1602    @args = args
1603  end
1604  attr_reader :menu
1605
1606  def inspect
1607    format("#<%s: label: %p, menu: %p, args: %p>",
1608           self.class, @label, @menu, @args)
1609  end
1610
1611  def entry(name, *rest, &body)
1612    child = false
1613    args, widget_class = optkey(rest,
1614                                [:args, @args],
1615                                [:widget_class, RxmPushButtonWidgetClass])
1616    child = RXtCreateManagedWidget(name, widget_class, @menu, args)
1617    case widget_class
1618    when RxmPushButtonWidgetClass
1619      RXtAddCallback(child, RXmNactivateCallback, body)
1620    when RxmToggleButtonWidgetClass
1621      RXtAddCallback(child, RXmNvalueChangedCallback, body)
1622    end
1623    child
1624  end
1625
1626  def label(name, args = @args)
1627    RXtCreateManagedWidget(name, RxmLabelWidgetClass, @menu, args)
1628  end
1629
1630  def separator(single = :single)
1631    line = (single == :double ? RXmDOUBLE_LINE : RXmSINGLE_LINE)
1632    RXtCreateManagedWidget("s", RxmSeparatorWidgetClass, @menu,
1633                           [RXmNseparatorType, line])
1634  end
1635
1636  def each_entry(&body)
1637    each_child(@menu, &body)
1638  end
1639
1640  # $menu.change_menu_color("ivory2")
1641  # $menu.change_menu_color([0.93, 0.93, 0.87])
1642  # require 'rgb'
1643  # $menu.change_menu_color(Ivory2)
1644  def change_menu_color(new_color)
1645    color_pixel = get_color(new_color)
1646    each_child(@menu) do |child|
1647      RXmChangeColor(child, color_pixel)
1648    end
1649  end
1650end
1651
1652class Snd_main_menu < Menu
1653  def initialize(name, parent, args, &body)
1654    if widget? parent
1655      @menu_number = -1
1656      super(name, parent, args)
1657    else
1658      @menu_number = add_to_main_menu(name, lambda do | | end)
1659      super(name, main_menu(@menu_number), args)
1660      instance_eval(&body) if block_given?
1661    end
1662  end
1663  attr_reader :menu_number
1664
1665  def entry(arg, *rest, &body)
1666    if arg.class == Class
1667      menu = arg.new(*rest)
1668      if menu.respond_to?(:post_dialog)
1669        child = RXtCreateManagedWidget(rest[0].to_s,
1670                                       RxmPushButtonWidgetClass, @menu, @args)
1671        RXtAddCallback(child, RXmNactivateCallback,
1672                       lambda do |w, c, i|
1673                         menu.post_dialog
1674                       end)
1675        child
1676      else
1677        Snd.raise(:snd_x_error, arg.class,
1678                  "class does not respond to `post_dialog'")
1679      end
1680    else
1681      if block_given?
1682        add_to_menu(@menu_number, arg, body)
1683      else
1684        Snd.raise(:wrong_number_of_args, "no block given")
1685      end
1686    end
1687  end
1688
1689  def separator
1690    add_to_menu(@menu_number, false, false)
1691  end
1692
1693  def cascade(name, args = @args, &body)
1694    cas = Cascade.new(name, @menu, args)
1695    cas.instance_eval(&body) if block_given?
1696    cas
1697  end
1698
1699  class Cascade < Snd_main_menu
1700    def initialize(name, parent, args)
1701      super
1702      @children = []
1703      @menu = RXmCreatePulldownMenu(parent, @label, @args)
1704      cascade = RXtCreateManagedWidget(@label,
1705                                       RxmCascadeButtonWidgetClass,
1706                                       parent,
1707                                       [RXmNsubMenuId, @menu] + @args)
1708      RXtAddCallback(cascade, RXmNcascadingCallback,
1709                     lambda do |w, c, i|
1710                       update_label(@children)
1711                     end)
1712    end
1713
1714    def entry(arg, *rest, &body)
1715      child = false
1716      if arg.class == Class
1717        menu = arg.new(*rest)
1718        if menu.respond_to?(:post_dialog)
1719          child = RXtCreateManagedWidget(rest[0].to_s,
1720                                         RxmPushButtonWidgetClass,
1721                                         @menu, @args)
1722          RXtAddCallback(child, RXmNactivateCallback,
1723                         lambda do |w, c, i|
1724                           menu.post_dialog
1725                         end)
1726          @children.push(lambda do | |
1727                           change_label(child, menu.inspect)
1728                         end)
1729        else
1730          Snd.raise(:snd_x_error, arg.class,
1731                    "class does not respond to `post_dialog'")
1732        end
1733      else
1734        if block_given?
1735          child = RXtCreateManagedWidget(arg.to_s,
1736                                         RxmPushButtonWidgetClass,
1737                                         @menu, @args)
1738          RXtAddCallback(child, RXmNactivateCallback,
1739                         lambda do |w, c, i|
1740                           body.call
1741                         end)
1742          change_label(child, arg)
1743        else
1744          Snd.raise(:wrong_number_of_args, "no block given")
1745        end
1746      end
1747      child
1748    end
1749
1750    def separator(single = :single)
1751      line = (single == :double ? RXmDOUBLE_LINE : RXmSINGLE_LINE)
1752      RXtCreateManagedWidget("s", RxmSeparatorWidgetClass, @menu,
1753                             [RXmNseparatorType, line])
1754    end
1755  end
1756end
1757
1758# non-Snd menu functions, may be used outside Snd scripts
1759class Main_menu < Menu
1760  def initialize(name, parent, args, &body)
1761    super(name, parent, args)
1762    @menu = RXmCreatePulldownMenu(parent, "pulldown-menu", @args)
1763    wid = RXtCreateManagedWidget(@label, RxmCascadeButtonWidgetClass, parent,
1764                                 [RXmNsubMenuId, @menu] + @args)
1765    RXtVaSetValues(parent, [RXmNmenuHelpWidget, wid]) if name =~ /help/
1766    if block_given?
1767      instance_eval(&body)
1768    end
1769  end
1770end
1771
1772class Main_popup_menu < Menu
1773  def initialize(name, parent, args, &body)
1774    super(name, parent, args)
1775    @parent = parent
1776    @menu = RXmCreatePopupMenu(@parent, "popup-menu",
1777                               [RXmNpopupEnabled, RXmPOPUP_AUTOMATIC] + @args)
1778    RXtAddEventHandler(@parent, RButtonPressMask, false,
1779                       lambda do |w, c, i, f|
1780                         if Rbutton(i) == 3
1781                           RXmMenuPosition(@menu, i)
1782                           RXtManageChild(@menu)
1783                         end
1784                       end)
1785    unless @label.empty?
1786      label(@label)
1787      separator
1788    end
1789    if block_given?
1790      instance_eval(&body)
1791    end
1792  end
1793end
1794
1795include Snd_XM
1796
1797# snd-xm.rb ends here
1798