1# nb.rb -- translation of nb.scm
2
3# Translator/Author: Michael Scholz <mi-scholz@users.sourceforge.net>
4# Created: 02/12/10 22:08:15
5# Changed: 14/11/13 03:02:23
6
7# Tested with Snd 15.x, Ruby 2.x.x
8#
9# type nb = make_nb
10#      nb.help
11# or   xnb = make_nb_motif (installs a popup menu on info widget)
12#
13# global variable:
14#   $nb_database
15#
16# make_nb(path)
17#
18# class NB
19#   initialize(path)
20#
21# getter and setter:
22#   name=(filename)
23#   name
24#   notes=(new_notes)
25#   notes
26#
27# interactive methods:
28#   unb
29#   prune_db
30#   open(path)
31#   close
32#   help           (alias info and description)
33#
34=begin
35nb = make_nb
36nb.name
37nb.notes
38nb.notes = "new text"
39nb.prune_db                  # deletes empty entries
40nb.unb                       # deletes entry of current file
41nb.open                      # adds mouse hooks
42nb.close                     # removes mouse hooks
43nb.help                      # this help
44=end
45
46require "hooks"
47with_silence do
48  unless defined? DBM.open
49    require "dbm"
50  end
51end
52
53$nb_database = "nb" unless defined? $nb_database
54
55if provided?("snd-motif") and (not provided?("xm"))
56  with_silence(LoadError) do
57    require "libxm"
58  end
59end
60
61module Kernel
62  # XM_NB should only create one instance of the popup menu on the
63  # info_widget.
64  @@XM_NB = false
65
66  def Kernel.xm_nb
67    @@XM_NB
68  end
69
70  def Kernel.xm_nb=(val)
71    @@XM_NB = val
72  end
73end
74
75def make_nb(path = $nb_database)
76  NB.new(path)
77end
78
79def make_nb_motif(path = $nb_database)
80  if Kernel.xm_nb.kind_of?(XM_NB)
81    Kernel.xm_nb.open(path)
82  else
83    Kernel.xm_nb = XM_NB.new(path)
84  end
85end if provided?("xm")
86
87class NB
88  include Info
89
90  Region_viewer = 2
91  View_files_dialog = 8
92  Info_dialog = 20
93
94  def initialize(path)
95    @nb_database = path
96    @type = nil
97    @position = nil
98    @name = nil
99    @notes = ""
100    @alert_color = make_color(1.0, 1.0, 0.94)
101    @db_hook_name = format("%s-nb-hook", @nb_database)
102    set_help
103    create
104  end
105  attr_reader :name, :notes
106  alias help description
107
108  def inspect
109    format("#<%s: nb_database: %s, open: %s, name: %s>",
110           self.class,
111           @nb_database.inspect,
112           $mouse_enter_label_hook.member?(@db_hook_name).inspect,
113           @name.inspect)
114  end
115
116  def name=(filename)
117    if filename and File.exist?(File.expand_path(filename))
118      @name = filename
119      show_popup_info
120    else
121      Snd.warning("no such file: %s", filename.inspect)
122    end
123  end
124
125  def notes=(new_notes)
126    @notes = new_notes
127    nb
128    @notes
129  end
130
131  def with_dbm(&body)
132    ret = nil
133    db = DBM.open(@nb_database)
134    ret = body.call(db)
135    db.close
136    ret
137  rescue
138    Snd.warning("%s#%s", self.class, get_func_name)
139  end
140
141  def prune_db
142    with_dbm do |db|
143      db.delete_if do |k, v| k.empty? end
144    end
145    self
146  end
147
148  def open(path = @nb_database)
149    @nb_database = path
150    create
151    self
152  end
153
154  def close
155    $mouse_enter_label_hook.remove_hook!(@db_hook_name)
156    $mouse_leave_label_hook.remove_hook!(@db_hook_name)
157    self
158  end
159
160  def unb
161    if @name and File.exist?(File.expand_path(@name))
162      with_dbm do |db|
163        db.delete(@name)
164      end
165      show_popup_info
166    else
167      Snd.warning("no such file: %s", @name.inspect)
168    end
169  end
170
171  private
172  def create
173    close
174    $mouse_enter_label_hook.add_hook!(@db_hook_name) do |t, p, n|
175      files_popup_info(t, p, n) unless t == Region_viewer
176    end
177  end
178
179  def nb
180    if @name and File.exist?(File.expand_path(@name))
181      with_dbm do |db|
182        db[@name] = @notes
183      end
184      show_popup_info
185    else
186      Snd.warning("no such file: %s", @name.inspect)
187    end
188  end
189
190  def files_popup_info(type, position, name)
191    @type = type
192    @position = position
193    @name = name
194    show_popup_info
195  end
196
197  def show_popup_info
198    let(dialog_widgets[Info_dialog]) do |info_exists_p|
199      info_dialog(@name, file_info)
200      if info_widget = dialog_widgets[Info_dialog]
201        unless info_exists_p
202          width, height = widget_size(dialog_widgets[View_files_dialog])
203          set_widget_position(info_widget, [width + 10, 10])
204        end
205      end
206    end
207    @name
208  end
209
210  def file_info
211    with_dbm do |db|
212      @notes = (db[@name] or "")
213    end
214    cs = mus_sound_chans(@name)
215    sr = mus_sound_srate(@name)
216    len = format("%1.3f", mus_sound_samples(@name).to_f / (cs * sr.to_f))
217    d_format = mus_sample_type_name(mus_sound_sample_type(@name))
218    h_type = mus_header_type_name(mus_sound_header_type(@name))
219    frms = mus_sound_framples(@name)
220    max_amp = ""
221    if mus_sound_maxamp_exists?(@name)
222      str = ""
223      mus_sound_maxamp(@name).each_pair do |s, v|
224        str << format("%1.3f (%1.3fs), ", v, s / sr.to_f)
225      end
226      max_amp = format("\n maxamp: [%s]", str[0..-3])
227    end
228    fdate = Time.at(mus_sound_write_date(@name))
229    date = fdate.localtime.strftime("%a %d-%b-%y %H:%M %z")
230    info_string = format("\
231  chans: %d, srate: %d
232 length: %1.3f (%d frms)
233 format: %s [%s]%s
234written: %s\n", cs, sr, len, frms, d_format, h_type, max_amp, date)
235    if defined?($info_comment_hook) and hook?($info_comment_hook)
236      if $info_comment_hook.empty?
237        if s = mus_sound_comment(@name)
238          info_string += format("comment: %s\n", s)
239        end
240      else
241        $info_comment_hook.run_hook do |prc|
242          info_string = prc.call(@name, info_string)
243        end
244      end
245    else
246      if s = mus_sound_comment(@name)
247        info_string += format("comment: %s\n", s)
248      end
249    end
250    info_string += "\n" + @notes
251  end
252
253  def set_help
254    self.description = "\
255# global variable:
256#   $nb_database (#{$nb_database})
257#
258# make_nb(path)
259# make_nb_motif(path)
260#
261# class NB
262#   initialize(path)
263#
264# getter and setter:
265#   name=(filename)
266#   name
267#   notes=(new_notes)
268#   notes
269#
270# interactive methods:
271#   unb
272#   prune_db
273#   open(path)
274#   close
275#   help           (alias info and description)
276
277nb = make_nb
278nb.name
279nb.notes
280nb.notes = \"new text\"
281nb.prune_db                  # deletes empty entries
282nb.unb                       # deletes entry of current file
283nb.open                      # adds mouse hooks
284nb.close                     # removes mouse hooks
285nb.help                      # this help
286"
287  end
288end
289
290class XM_NB < NB
291  require "popup"
292
293  def initialize(path)
294    @dialog = nil
295    @file_name = nil
296    @text_widget = nil
297    @message_widget = nil
298    super
299    @db_str = format("DB: %s", File.basename(@nb_database))
300    @popup_nb_hook = Hook.new("@popup_nb_hook", 2, "\
301lambda do |snd, info| ... \"new info\" end: called in popup.rb on
302graph-popup-menu entry `Info'.  Its primary use is to communicate
303between popup.rb and nb.rb.  To add your own information to the info
304string, you may use $info_comment_hook.
305
306The current selected SND is called with string INFO.  If more than one
307hook procedures exists, each procedure's result is passed as input to
308the next.  E.g. if an instance of NB or XM_NB is created (see nb.rb),
309the $nb_database entries of SND will be returned.")
310    @popup_nb_hook.add_hook!("initialize-nb-hook") do |snd, info|
311      @name = file_name(snd)
312      with_dbm do |db|
313        @notes = (db[@name] or "")
314      end
315      unless @notes.empty?
316        info += "\n" unless info.empty?
317        info += @notes
318      end
319      info
320    end
321    install_menu
322  end
323  attr_reader :popup_nb_hook
324
325  def close
326    if @dialog.kind_of?(Dialog) and
327       RWidget?(@dialog.dialog) and
328       RXtIsManaged(@dialog.dialog)
329      RXtUnmanageChild(@dialog.dialog)
330    end
331    super
332  end
333
334  protected
335  def post_edit
336    if !@name and RWidget?(@message_widget)
337      @name = if File.exist?(file = current_label(@message_widget).split[0])
338                file
339              else
340                format("no such file: %s", file.inspect)
341              end
342    end
343    unless @dialog.kind_of?(Dialog) and RWidget?(@dialog.dialog)
344      @dialog = make_dialog(@db_str,
345                            :help_cb, lambda do |w, c, i|
346                              help_cb
347                            end, :clear_cb, lambda do |w, c, i|
348                              RXmTextSetString(@text_widget, "")
349                            end) do |w, c, i|
350        self.notes = RXmTextGetString(@text_widget)
351      end
352      @file_name = @dialog.add_label(@name)
353      @text_widget = @dialog.add_text(:rows, 16, :columns, 60,
354                                      :wordwrap, true, :value, @notes)
355      @dialog.doit_string("Submit")
356    end
357    activate_dialog(@dialog.dialog)
358    show_edit_info
359  end
360
361  def help_cb
362    help_dialog(@db_str,
363                  "Edit info DB of sound files (see nb.scm).
364
365Provides pop-up help in the Files viewer.  \
366If you have `dbm', any data associated with \
367the file in the dbm database will also be posted.  \
368The database name is defined by $nb_database \
369(#{$nb_database.inspect}).
370
371o Edit info: opens the edit widget
372
373o Prune DB:  clears non-existent file references
374             out of the database
375
376o Clear:     removes info entry from current file
377
378o Close:     removes mouse hooks and popup menu;
379             type `make_nb_motif' to reinstall
380             the hooks and popup menu
381
382o Submit:    submits info from edit widget
383             to file info database
384
385#{self.description}",
386                  ["{Libxm}: graphics module",
387                   "{Ruby}: extension language",
388                   "{Motif}: Motif extensions via libxm"])
389    end
390
391  def post_popup?
392    $mouse_enter_label_hook.member?(@db_hook_name) and
393      File.exist?(current_label(@message_widget).split[0])
394  end
395
396  private
397  def create
398    @db_hook_name = format("%s-xm-nb-hook", @nb_database)
399    super
400  end
401
402  def install_menu
403    if RWidget?(wid = dialog_widgets[Info_dialog])
404      setup_menu(wid)
405    else
406      $new_widget_hook.add_hook!("nb-edit-hook") do |w|
407        if w == dialog_widgets[Info_dialog]
408          setup_menu(w)
409          $new_widget_hook.remove_hook!("nb-edit-hook")
410        end
411      end
412    end
413  end
414
415  def setup_menu(wid)
416    @message_widget = find_child(wid, "Message")
417    make_snd_popup("NB Edit Info",
418                   :where, :event,
419                   :parent, find_child(wid, "post-it-text")) do
420      entry("Edit Info") do |w, snd, chn| Kernel.xm_nb.post_edit end
421      entry("Prune DB") do |w, snd, chn| Kernel.xm_nb.prune_db end
422      entry("Clear current Info") do |w, snd, chn| Kernel.xm_nb.unb end
423      entry("Close NB Edit") do |w, snd, chn| Kernel.xm_nb.close end
424      separator
425      entry("Help") do |w, snd, chn| Kernel.xm_nb.help_cb end
426      before_popup_hook.add_hook!("NB Edit Info") do |d1, d2, d3|
427        Kernel.xm_nb.post_popup?
428      end
429    end
430  end
431
432  def files_popup_info(type, position, name)
433    super
434    show_edit_info
435  end
436
437  def show_edit_info
438    xfname = string2compound(@name)
439    if RWidget?(@file_name)
440      RXtVaSetValues(@file_name, [RXmNlabelString, xfname])
441    end
442    if RWidget?(@text_widget)
443      RXtVaSetValues(@text_widget, [RXmNvalue, @notes])
444    end
445    RXmStringFree(xfname)
446  end
447end if provided?("xm")
448
449# nb.rb ends here
450