vimamsa 0.1.21 → 0.1.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -74,7 +74,7 @@ class Editor
74
74
 
75
75
  # build_key_bindings_tree
76
76
  @kbd = KeyBindingTree.new()
77
- $kbd = @kbd
77
+ $kbd = @kbd #TODO: remove global
78
78
  require "vimamsa/key_bindings_vimlike"
79
79
 
80
80
  $buffers = BufferList.new
@@ -110,8 +110,7 @@ class Editor
110
110
 
111
111
  settings_path = get_dot_path("settings.rb")
112
112
  if File.exist?(settings_path)
113
- # = eval(IO.read(settings_path))
114
- #TODO
113
+ eval(IO.read(settings_path))
115
114
  end
116
115
 
117
116
  custom_script = read_file("", custom_fn)
@@ -151,6 +150,8 @@ class Editor
151
150
  end
152
151
  end
153
152
 
153
+ argv_has_files = ARGV.any? { |a| File.file?(File.expand_path(a)) }
154
+
154
155
  if fname
155
156
  open_new_file(fname)
156
157
  else
@@ -167,6 +168,8 @@ class Editor
167
168
  # To access via vma.FileFinder
168
169
  # self.define_singleton_method(:FileFinder) { @_plugins[:FileFinder] }
169
170
 
171
+ check_session_restore unless argv_has_files
172
+
170
173
  @hook.call(:after_init)
171
174
  end
172
175
 
@@ -225,6 +228,7 @@ class Editor
225
228
  end
226
229
 
227
230
  def shutdown()
231
+ save_session
228
232
  @hook.call(:shutdown)
229
233
  save_state
230
234
  @gui.quit
@@ -233,6 +237,44 @@ class Editor
233
237
  def save_state
234
238
  end
235
239
 
240
+ def save_session
241
+ fnames = vma.buffers.list
242
+ .map { |b| b.fname }
243
+ .compact
244
+ .select { |f| File.exist?(f) }
245
+ IO.write(get_dot_path("session.txt"), fnames.join("\n") + "\n")
246
+ rescue => ex
247
+ debug "save_session failed: #{ex}"
248
+ end
249
+
250
+ def check_session_restore
251
+ session_path = get_dot_path("session.txt")
252
+ return unless File.exist?(session_path)
253
+
254
+ fnames = File.read(session_path).lines.map(&:strip).reject(&:empty?)
255
+ fnames.select! { |f| File.exist?(f) }
256
+ return if fnames.empty?
257
+
258
+ n = fnames.size
259
+ label = n == 1 ? "1 file" : "#{n} files"
260
+ params = {
261
+ "title" => "Restore previous session? (#{label})",
262
+ "inputs" => {
263
+ "yes_btn" => { :label => "Restore", :type => :button, :default_focus => true },
264
+ "no_btn" => { :label => "No", :type => :button },
265
+ },
266
+ :callback => proc { |x|
267
+ if x["yes_btn"] == "submit"
268
+ initial = vma.buffers.list.find { |b| b.fname.nil? }
269
+ fnames.each { |f| load_buffer(f) }
270
+ initial&.close
271
+ message("Session restored: #{label}")
272
+ end
273
+ },
274
+ }
275
+ PopupFormGenerator.new(params).run
276
+ end
277
+
236
278
  def add_content_search_path(pathstr)
237
279
  p = File.expand_path(pathstr)
238
280
  if !@file_content_search_paths.include?(p)
@@ -261,6 +303,9 @@ class Editor
261
303
  if p and !@file_content_search_paths.include?(p)
262
304
  r.insert(0, p)
263
305
  end
306
+
307
+ # Ensure that paths are in correct format
308
+ r = r.map{|x|File.expand_path(x)}
264
309
 
265
310
  return r
266
311
  end
@@ -365,35 +410,13 @@ def start_minibuffer_cmd(bufname, bufstr, cmd)
365
410
  $minibuffer.call_func = method(cmd)
366
411
  end
367
412
 
368
- def show_key_bindings()
369
- kbd_s = "❙Key bindings❙\n"
370
- kbd_s << "\n⦁[Mode] keys : action⦁\n"
371
-
372
- kbd_s << "[B]=Browse, [C]=Command, [I]=Insert, [V]=Visual\n"
373
- kbd_s << "key!: Press key once, release before pressing any other keys\n"
374
-
375
- kbd_s << "===============================================\n"
376
- kbd_s << vma.kbd.to_s
377
- kbd_s << "\n"
378
- kbd_s << "===============================================\n"
379
- b = create_new_buffer(kbd_s, "key-bindings")
380
- gui_set_file_lang(b.id, "hyperplaintext")
381
- #
382
- end
383
-
384
- def diff_buffer()
385
- bufstr = ""
386
- orig_path = vma.buf.fname
387
- infile = Tempfile.new("out")
388
- infile = Tempfile.new("in")
389
- infile.write(vma.buf.to_s)
390
- infile.flush
391
- cmd = "diff -w '#{orig_path}' #{infile.path}"
392
- # debug cmd
393
- bufstr << run_cmd(cmd)
394
- # debug bufstr
395
- infile.close; infile.unlink
396
- create_new_file(nil, bufstr)
413
+ def if_cmd_exists(cmd)
414
+ cmd = cmd.gsub(/[^a-zA-Z0-9_\-]/, '')
415
+ if !system("which #{cmd} > /dev/null 2>&1")
416
+ message("Command \"#{cmd}\" not found!")
417
+ return false
418
+ end
419
+ return true
397
420
  end
398
421
 
399
422
  def invoke_command()
@@ -412,14 +435,14 @@ end
412
435
 
413
436
  def minibuffer_end()
414
437
  debug "minibuffer_end"
415
- $kbd.set_mode(:command)
438
+ vma.kbd.set_mode(:command)
416
439
  minibuffer_input = $minibuffer.to_s[0..-2]
417
440
  return $minibuffer.call_func.call(minibuffer_input)
418
441
  end
419
442
 
420
443
  def minibuffer_cancel()
421
444
  debug "minibuffer_cancel"
422
- $kbd.set_mode(:command)
445
+ vma.kbd.set_mode(:command)
423
446
  minibuffer_input = $minibuffer.to_s[0..-2]
424
447
  # $minibuffer.call_func.call('')
425
448
  end
@@ -597,6 +620,7 @@ def open_new_file(filename, file_contents = "")
597
620
  fname = filename
598
621
  bu = load_buffer(fname)
599
622
  vma.buffers.set_current_buffer_by_id(bu.id)
623
+ bu.check_autosave_load
600
624
  end
601
625
  return bu
602
626
  end
@@ -659,3 +683,49 @@ def find_project_dir_of_cur_buffer()
659
683
  # debug "Proj dir of current file: #{pdir}"
660
684
  return pdir
661
685
  end
686
+
687
+ def reload_customrb
688
+ custom_fn = get_dot_path("custom.rb")
689
+ custom_script = read_file("", custom_fn)
690
+ eval(custom_script) if custom_script
691
+ message("Reloaded #{custom_fn}")
692
+ end
693
+
694
+ def save_settings_to_file
695
+ settings_path = get_dot_path("settings.rb")
696
+ lines = SETTINGS_DEFS.flat_map { |section| section[:settings] }.map do |s|
697
+ key_str = "cnf." + s[:key].join(".")
698
+ val = get(s[:key])
699
+ "#{key_str} = #{val.inspect}"
700
+ end
701
+ IO.write(settings_path, lines.join("\n") + "\n")
702
+ message("Settings saved to #{settings_path}")
703
+ end
704
+
705
+ DEMO_FILES = ["demo.txt", "sheep.jpg", "README.md"]
706
+
707
+ def install_demo_files
708
+ dest_dir = File.expand_path("~/Documents/VimamsaDemo")
709
+ mkdir_if_not_exists dest_dir
710
+ title = "Install demo files to #{dest_dir}?"
711
+ Gui.confirm(title, proc { |x| install_demo_files_callback(x) })
712
+ end
713
+
714
+ def install_demo_files_callback(x)
715
+ return unless x["yes_btn"] == "submit"
716
+ src_dir = File.expand_path("../..", __dir__)
717
+ dest_dir = File.expand_path("~/Documents/VimamsaDemo")
718
+ FileUtils.mkdir_p(dest_dir)
719
+ DEMO_FILES.each do |fname|
720
+ src = File.join(src_dir, fname)
721
+ dst = File.join(dest_dir, fname)
722
+ if File.exist?(src)
723
+ FileUtils.cp(src, dst)
724
+ else
725
+ message("Demo file not found: #{src}")
726
+ end
727
+ end
728
+ open_new_file(File.join(dest_dir, "demo.txt"))
729
+ message("Demo files installed to #{dest_dir}")
730
+ end
731
+
@@ -18,7 +18,7 @@ class FileManager
18
18
  end
19
19
 
20
20
  def self.init()
21
- reg_act(:start_file_selector, proc { FileManager.new.run; vma.kbd.set_mode(:file_exp); }, "File selector")
21
+ reg_act(:start_file_selector, proc { FileManager.new.run; vma.kbd.set_mode(:file_exp) }, "File selector")
22
22
 
23
23
  reg_act(:fexp_chdir_parent, proc { FileManager.chdir_parent }, "File selector")
24
24
  reg_act(:fexp_select, proc { buf.module.select_line }, "")
@@ -162,13 +162,14 @@ class FileManager
162
162
  #TODO:
163
163
  end
164
164
 
165
+ # Main inteface, show contents of current dir
165
166
  def dir_to_buf(dirpath, b = nil)
166
167
  # File.stat("testfile").mtime
167
168
 
168
169
  debug "last file: #{vma.buffers.last_file}", 2
169
170
  lastf = vma.buffers.last_file
170
171
  jumpto = nil
171
- if File.dirname(lastf) == dirpath
172
+ if !lastf.nil? and File.dirname(lastf) == dirpath
172
173
  jumpto = File.basename(lastf)
173
174
  end
174
175
  vma.buffers.last_dir = dirpath
@@ -231,6 +232,13 @@ class FileManager
231
232
  else
232
233
  @buf.set_line_and_column_pos(@header.size, 0)
233
234
  end
235
+
236
+ if @cdirs.size > 0
237
+ r = vma.buf.line_range(2, @cdirs.size+1)
238
+
239
+ # Hilight works only if done after buffer is drawn
240
+ run_as_idle proc { Gui.hilight_range(vma.buf, r, color: "#4488ffff") }
241
+ end
234
242
  end
235
243
 
236
244
  def fullp(fn)
@@ -267,7 +275,6 @@ class FileManager
267
275
  jump_to_file(fn)
268
276
  # vma.buffers.set_current_buffer(idx)
269
277
  vma.buffers.close_other_buffer(@buf.id)
270
-
271
278
  else
272
279
  open_with_default_program(fn)
273
280
  end
data/lib/vimamsa/gui.rb CHANGED
@@ -148,6 +148,12 @@ def gui_create_buffer(id, bufo)
148
148
  $vmag.buffers[id] = view
149
149
  end
150
150
 
151
+ def gui_close_buffer(id)
152
+ view = vma.gui.buffers.delete(id)
153
+ return if view.nil?
154
+ view.unparent if view.parent
155
+ end
156
+
151
157
  def gui_set_file_lang(id, lname)
152
158
  view = $vmag.buffers[id]
153
159
  lm = GtkSource::LanguageManager.new
@@ -183,7 +189,8 @@ def gui_set_current_buffer(id)
183
189
  end
184
190
 
185
191
  def gui_set_window_title(wtitle, subtitle = "")
186
- $vmag.window.title = wtitle
192
+ wtitle = wtitle[0..150]
193
+ $vmag.window.title = "Vimamsa - #{wtitle}"
187
194
  # $vmag.subtitle.markup = "<span weight='ultrabold'>#{subtitle}</span>"
188
195
  $vmag.subtitle.markup = "<span weight='light' size='small'>#{subtitle}</span>"
189
196
  # $vmag.window.titlebar.subtitle = subtitle #TODO:gtk4
@@ -191,7 +198,7 @@ end
191
198
 
192
199
  class VMAgui
193
200
  attr_accessor :buffers, :sw1, :sw2, :view, :buf1, :window, :delex, :statnfo, :overlay, :sws, :two_c
194
- attr_reader :two_column, :windows, :subtitle, :app, :active_window
201
+ attr_reader :two_column, :windows, :subtitle, :app, :active_window, :action_trail_label, :file_panel
195
202
 
196
203
  def initialize()
197
204
  @two_column = false
@@ -348,88 +355,35 @@ class VMAgui
348
355
  sw.set_child(view)
349
356
  end
350
357
 
351
- #TODO: implement in gtk4
358
+ def make_header_button(action_id, icon, cb)
359
+ act = Gio::SimpleAction.new(action_id)
360
+ @app.add_action(act)
361
+ act.signal_connect("activate") { |_a, _p| cb.call }
362
+ btn = Gtk::Button.new
363
+ btn.set_child(Gtk::Image.new(icon_name: icon))
364
+ btn.action_name = "app.#{action_id}"
365
+ btn
366
+ end
367
+
352
368
  def init_header_bar()
353
369
  header = Gtk::HeaderBar.new
354
370
  @header = header
355
- header.show_close_button = true
356
- # header.title = ""#TODO:gtk4
357
- # header.has_subtitle = true#TODO:gtk4
358
- header.subtitle = ""
359
-
360
- # icon = Gio::ThemedIcon.new("mail-send-receive-symbolic")
361
- # icon = Gio::ThemedIcon.new("document-open-symbolic")
362
- # icon = Gio::ThemedIcon.new("dialog-password")
363
-
364
- #edit-redo edit-paste edit-find-replace edit-undo edit-find edit-cut edit-copy
365
- #document-open document-save document-save-as document-properties document-new
366
- # document-revert-symbolic
367
- #
368
-
369
- #TODO:
370
- # button = Gtk::Button.new
371
- # icon = Gio::ThemedIcon.new("open-menu-symbolic")
372
- # image = Gtk::Image.new(:icon => icon, :size => :button)
373
- # button.add(image)
374
- # header.append(button)
375
-
376
- button = Gtk::Button.new
377
- icon = Gio::ThemedIcon.new("document-open-symbolic")
378
- image = Gtk::Image.new(:icon => icon, :size => :button)
379
- button.add(image)
380
- header.append(button)
381
-
382
- button.signal_connect "clicked" do |_widget|
383
- open_file_dialog
384
- end
385
-
386
- button = Gtk::Button.new
387
- icon = Gio::ThemedIcon.new("document-save-symbolic")
388
- image = Gtk::Image.new(:icon => icon, :size => :button)
389
- button.add(image)
390
- header.append(button)
391
- button.signal_connect "clicked" do |_widget|
392
- buf.save
393
- end
394
-
395
- button = Gtk::Button.new
396
- icon = Gio::ThemedIcon.new("document-new-symbolic")
397
- image = Gtk::Image.new(:icon => icon, :size => :button)
398
- button.add(image)
399
- header.append(button)
400
- button.signal_connect "clicked" do |_widget|
401
- create_new_file
402
- end
403
-
404
- box = Gtk::Box.new(:horizontal, 0)
405
- box.style_context.add_class("linked")
406
371
 
407
- button = Gtk::Button.new
408
- image = Gtk::Image.new(:icon_name => "pan-start-symbolic", :size => :button)
409
- button.add(image)
410
- box.add(button)
411
- button.signal_connect "clicked" do |_widget|
412
- history_switch_backwards
413
- end
372
+ file_box = Gtk::Box.new(:horizontal, 0)
373
+ file_box.style_context.add_class("linked")
374
+ file_box.append(make_header_button("hdr-open", "document-open-symbolic", proc { open_file_dialog }))
375
+ file_box.append(make_header_button("hdr-save", "document-save-symbolic", proc { buf.save }))
376
+ file_box.append(make_header_button("hdr-new", "document-new-symbolic", proc { create_new_file }))
377
+ header.pack_start(file_box)
414
378
 
415
- button = Gtk::Button.new
416
- image = Gtk::Image.new(:icon_name => "pan-end-symbolic", :size => :button)
417
- button.add(image)
418
- box.add(button)
419
- button.signal_connect "clicked" do |_widget|
420
- history_switch_forwards
421
- end
379
+ nav_box = Gtk::Box.new(:horizontal, 0)
380
+ nav_box.style_context.add_class("linked")
381
+ nav_box.append(make_header_button("hdr-prev", "pan-start-symbolic", proc { history_switch_backwards }))
382
+ nav_box.append(make_header_button("hdr-next", "pan-end-symbolic", proc { history_switch_forwards }))
383
+ header.pack_start(nav_box)
422
384
 
423
- button = Gtk::Button.new
424
- icon = Gio::ThemedIcon.new("window-close-symbolic")
425
- image = Gtk::Image.new(:icon => icon, :size => :button)
426
- button.add(image)
427
- box.add(button)
428
- button.signal_connect "clicked" do |_widget|
429
- bufs.close_current_buffer
430
- end
385
+ header.pack_end(make_header_button("hdr-close", "window-close-symbolic", proc { bufs.close_current_buffer }))
431
386
 
432
- header.pack_start(box)
433
387
  @window.titlebar = header
434
388
  end
435
389
 
@@ -555,6 +509,8 @@ class VMAgui
555
509
  @last_debug_idle = Time.now
556
510
  app = Gtk::Application.new("net.samiddhi.vimamsa.r#{rand(1000)}", :flags_none)
557
511
  @app = app
512
+
513
+
558
514
 
559
515
  Gtk::Settings.default.gtk_application_prefer_dark_theme = true
560
516
  Gtk::Settings.default.gtk_theme_name = "Adwaita"
@@ -569,7 +525,7 @@ class VMAgui
569
525
  @vpaned = Gtk::Paned.new(:vertical)
570
526
 
571
527
  @vbox = Gtk::Grid.new()
572
- @window.add(@vbox)
528
+ @window.set_child(@vbox)
573
529
 
574
530
  Thread.new {
575
531
  GLib::Idle.add(proc { debug_idle_func })
@@ -577,17 +533,36 @@ class VMAgui
577
533
 
578
534
  reset_controllers
579
535
 
536
+ focus_controller = Gtk::EventControllerFocus.new
537
+
538
+ focus_controller.signal_connect("enter") do
539
+ debug "Gained focus"
540
+ draw_cursor_bug_workaround
541
+ end
542
+ @window.add_controller(focus_controller)
543
+
544
+ motion_controller = Gtk::EventControllerMotion.new
545
+ motion_controller.signal_connect("motion") do |controller, x, y|
546
+ # label.set_text("Mouse at: (%.1f, %.1f)" % [x, y])
547
+ # puts "MOVE #{x} #{y}"
548
+
549
+ # Cursor vanishes when hovering over menubar
550
+ draw_cursor_bug_workaround if y < 30
551
+ @last_cursor = [x, y]
552
+ @cursor_move_time = Time.now
553
+ end
554
+ @window.add_controller(motion_controller)
555
+
580
556
  @windows[1] = new_window(1)
581
557
 
582
558
  @last_adj_time = Time.now
583
559
 
584
-
585
560
  # To show keyboard key binding state
586
561
  @statnfo = Gtk::Label.new
587
-
562
+
588
563
  # To show e.g. current folder
589
564
  @subtitle = Gtk::Label.new("")
590
-
565
+
591
566
  @statbox = Gtk::Box.new(:horizontal, 2)
592
567
  @statnfo.set_size_request(150, 10)
593
568
  @statbox.append(@subtitle)
@@ -609,15 +584,35 @@ class VMAgui
609
584
  init_minibuffer
610
585
 
611
586
  menubar = Gio::Menu.new
612
- app.menubar = menubar
613
- @window.show_menubar = true
614
-
615
587
  @menubar = menubar
616
588
 
589
+ # TODO: Doesn't work, why?:
590
+ # menubar_bar = Gtk::PopoverMenuBar.new(menu_model: menubar)
591
+
592
+ menubar_bar = Gtk::PopoverMenuBar.new()
593
+ menubar_bar.set_menu_model(menubar)
594
+
595
+ menubar_bar.hexpand = true
596
+ @action_trail_label = Gtk::Label.new("")
597
+ @action_trail_label.add_css_class("action-trail")
598
+ menubar_row = Gtk::Box.new(:horizontal, 0)
599
+ menubar_row.append(menubar_bar)
600
+ menubar_row.append(@action_trail_label)
601
+ @vbox.attach(menubar_row, 0, 0, 2, 1)
602
+
617
603
  @active_window = @windows[1]
618
604
 
605
+ init_header_bar
606
+ file_panel_init
607
+
619
608
  @window.show
620
609
 
610
+ surface = @window.native.surface
611
+ tt = Time.now
612
+ surface.signal_connect("layout") do
613
+ # puts "Window resized or moved, other redraw."
614
+ end
615
+
621
616
  run_as_idle proc { idle_set_size }
622
617
 
623
618
  prov = Gtk::CssProvider.new
@@ -645,7 +640,9 @@ class VMAgui
645
640
  min-width: 15px;
646
641
  }
647
642
 
648
- popover background > contents { padding: 8px; border-radius: 20px; }
643
+ popover background > contents { padding: 8px; border-radius: 20px; }
644
+
645
+ label.action-trail { font-family: monospace; font-size: 10pt; margin-right: 8px; color: #aaaaaa; }
649
646
  ")
650
647
  @window.style_context.add_provider(prov)
651
648
 
@@ -666,9 +663,9 @@ class VMAgui
666
663
  return true if Time.now - @monitor_time < 0.2
667
664
  # Detect element resize
668
665
  if swa.width != @sw_width
669
- # puts "@sw.width=#{@sw.width}"
666
+ debug "Width change sw_width #{@sw_width}"
670
667
  @sw_width = swa.width
671
- DelayExecutioner.exec(id: :scale_images, wait: 0.7, callable: proc { vma.gui.scale_all_images })
668
+ DelayExecutioner.exec(id: :scale_images, wait: 0.7, callable: proc { debug ":scale_images"; vma.gui.scale_all_images })
672
669
  end
673
670
  @monitor_time = Time.now
674
671
  return true
@@ -915,4 +912,43 @@ class VMAgui
915
912
  # view.place_cursor_onscreen #TODO: needed?
916
913
  view.draw_cursor
917
914
  end
915
+
916
+ def file_panel_init
917
+ @file_panel = FileTreePanel.new
918
+ @file_panel_shown = false
919
+ end
920
+
921
+ def file_panel_refresh
922
+ return unless @file_panel_shown
923
+ @file_panel.refresh
924
+ end
925
+
926
+ def show_file_panel
927
+ return if @file_panel_shown
928
+ inner = @two_column ? @pane : @windows[1][:overlay]
929
+ @vbox.remove(inner)
930
+ @file_panel_pane = Gtk::Paned.new(:horizontal)
931
+ @file_panel_pane.hexpand = true
932
+ @file_panel_pane.vexpand = true
933
+ @file_panel_pane.set_start_child(@file_panel.widget)
934
+ @file_panel_pane.set_end_child(inner)
935
+ @file_panel_pane.set_position(180)
936
+ @vbox.attach(@file_panel_pane, 0, 2, 2, 1)
937
+ @file_panel_shown = true
938
+ @file_panel.refresh
939
+ end
940
+
941
+ def hide_file_panel
942
+ return unless @file_panel_shown
943
+ inner = @file_panel_pane.end_child
944
+ @file_panel_pane.set_start_child(nil)
945
+ @file_panel_pane.set_end_child(nil)
946
+ @vbox.remove(@file_panel_pane)
947
+ @vbox.attach(inner, 0, 2, 2, 1)
948
+ @file_panel_shown = false
949
+ end
950
+
951
+ def toggle_file_panel
952
+ @file_panel_shown ? hide_file_panel : show_file_panel
953
+ end
918
954
  end
@@ -39,6 +39,8 @@ end
39
39
  class OneInputAction
40
40
  def initialize(main_window, title, field_label, button_title, callback, opt = {})
41
41
  @window = Gtk::Window.new()
42
+ @window.set_transient_for($vmag.window) if $vmag&.window
43
+ @window.modal = true
42
44
  # @window.screen = main_window.screen
43
45
  @window.title = ""
44
46
  # @window.width_request = 800
@@ -0,0 +1,93 @@
1
+ class FileTreePanel
2
+ COL_LABEL = 0
3
+ COL_BUF_ID = 1 # 0 = folder row (not selectable)
4
+
5
+ def initialize
6
+ @store = Gtk::TreeStore.new(String, Integer)
7
+ @tree = Gtk::TreeView.new(@store)
8
+ @tree.headers_visible = false
9
+ @tree.activate_on_single_click = true
10
+
11
+ renderer = Gtk::CellRendererText.new
12
+ renderer.ellipsize = Pango::EllipsizeMode::START
13
+ col = Gtk::TreeViewColumn.new("", renderer, text: COL_LABEL)
14
+ col.expand = true
15
+ @tree.append_column(col)
16
+
17
+ @tree.signal_connect("row-activated") do |tv, path, _col|
18
+ iter = @store.get_iter(path)
19
+ next if iter.nil?
20
+ buf_id = iter[COL_BUF_ID]
21
+ next if buf_id.nil? || buf_id == 0
22
+ vma.buffers.set_current_buffer(buf_id)
23
+ end
24
+
25
+ @context_menu = nil
26
+ rightclick = Gtk::GestureClick.new
27
+ rightclick.button = 3
28
+ @tree.add_controller(rightclick)
29
+ rightclick.signal_connect("pressed") do |gesture, n_press, x, y|
30
+ result = @tree.get_path_at_pos(x.to_i, y.to_i)
31
+ next unless result
32
+ iter = @store.get_iter(result[0])
33
+ next unless iter
34
+ buf_id = iter[COL_BUF_ID]
35
+ next unless buf_id && buf_id != 0
36
+ @context_buf_id = buf_id
37
+ show_context_menu(x, y)
38
+ end
39
+
40
+ @sw = Gtk::ScrolledWindow.new
41
+ @sw.set_policy(:never, :automatic)
42
+ @sw.set_child(@tree)
43
+ @sw.set_size_request(180, -1)
44
+ @sw.vexpand = true
45
+ end
46
+
47
+ def widget
48
+ @sw
49
+ end
50
+
51
+ def init_context_menu
52
+ act = Gio::SimpleAction.new("fp_close_buffer")
53
+ vma.gui.app.add_action(act)
54
+ act.signal_connect("activate") { vma.buffers.close_buffer(@context_buf_id) if @context_buf_id }
55
+ @context_menu = Gtk::PopoverMenu.new
56
+ @context_menu.set_parent(@tree)
57
+ @context_menu.has_arrow = false
58
+ end
59
+
60
+ def show_context_menu(x, y)
61
+ init_context_menu if @context_menu.nil?
62
+ menu = Gio::Menu.new
63
+ menu.append("Close file", "app.fp_close_buffer")
64
+ @context_menu.set_menu_model(menu)
65
+ @context_menu.set_pointing_to(Gdk::Rectangle.new(x.to_i, y.to_i, 1, 1))
66
+ @context_menu.popup
67
+ end
68
+
69
+ def refresh
70
+ @store.clear
71
+ bh = {}
72
+ vma.buffers.list.each do |b|
73
+ dname = b.fname ? File.dirname(b.fname) : "*"
74
+ bname = b.fname ? File.basename(b.fname) : (b.list_str || "(untitled)")
75
+ bh[dname] ||= []
76
+ bh[dname] << { bname: bname, buf: b }
77
+ end
78
+
79
+ bh.keys.sort.each do |dname|
80
+ dir_iter = @store.append(nil)
81
+ dir_iter[COL_LABEL] = "📂 #{tilde_path(dname)}"
82
+ dir_iter[COL_BUF_ID] = 0
83
+ bh[dname].sort_by { |x| x[:bname] }.each do |bnfo|
84
+ active_mark = bnfo[:buf].is_active? ? "● " : " "
85
+ file_iter = @store.append(dir_iter)
86
+ file_iter[COL_LABEL] = "#{active_mark}#{bnfo[:bname]}"
87
+ file_iter[COL_BUF_ID] = bnfo[:buf].id
88
+ end
89
+ end
90
+
91
+ @tree.expand_all
92
+ end
93
+ end
@@ -25,6 +25,8 @@ class PopupFormGenerator
25
25
 
26
26
  @callback = params[:callback]
27
27
  @window.title = ""
28
+ @window.set_transient_for($vmag.window) if $vmag&.window
29
+ @window.modal = true
28
30
 
29
31
  frame = Gtk::Frame.new()
30
32
  frame.margin_bottom = 8
@@ -56,7 +58,7 @@ class PopupFormGenerator
56
58
  @vals = {}
57
59
  @default_button = nil
58
60
 
59
- for id, elem in params["inputs"]
61
+ params["inputs"].each do |id, elem|
60
62
  if elem[:type] == :button
61
63
  button = Gtk::Button.new(:label => elem[:label])
62
64
  hbox.append(button)
@@ -92,7 +94,7 @@ class PopupFormGenerator
92
94
  end
93
95
  end
94
96
  end
95
- end
97
+ end # each
96
98
 
97
99
  vbox.append(hbox)
98
100