rbpad 3.1.0

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.
@@ -0,0 +1,51 @@
1
+
2
+ require 'gtk3'
3
+
4
+
5
+ module Rbpad
6
+
7
+ class ConsoleOutput < Gtk::ScrolledWindow
8
+ def initialize(conf)
9
+ super()
10
+ @view = Gtk::TextView.new
11
+ @view.override_font(Pango::FontDescription.new("#{conf.platform[:font][:output]}"))
12
+ @view.left_margin = 6
13
+ @view.pixels_above_lines = 1
14
+ @view.pixels_below_lines = 1
15
+ @view.accepts_tab = false # 'TAB' is used for moving focus
16
+ @view.buffer.create_tag('info', {foreground: 'gray'})
17
+ @view.buffer.create_tag('result', {weight: 600}) # SEMI-BOLD
18
+ @view.buffer.create_tag('error_jp', {foreground: 'red'})
19
+ @view.set_editable(false)
20
+ self.add(@view)
21
+ end
22
+
23
+ # line feed other than at the beginning of a line
24
+ def newline
25
+ @view.buffer.insert(@view.buffer.end_iter, "\n") if @view.buffer.end_iter.line_offset != 0
26
+ end
27
+
28
+ # insert text at bottom line
29
+ def add_tail(text, tag = nil)
30
+ @view.buffer.insert(@view.buffer.end_iter, text.scrub)
31
+ unless tag == nil # format by specified tag
32
+ iter_s = @view.buffer.get_iter_at(offset: @view.buffer.end_iter.offset - text.size)
33
+ iter_e = @view.buffer.end_iter
34
+ @view.buffer.apply_tag(tag, iter_s, iter_e)
35
+ end
36
+ end
37
+
38
+ # scroll to bottom line
39
+ def scroll_tail
40
+ mark = @view.buffer.create_mark(nil, @view.buffer.end_iter, true)
41
+ @view.scroll_to_mark(mark, 0, false, 0, 1) # ('scroll_to_iter' does not work)
42
+ end
43
+
44
+ # clear
45
+ def clear
46
+ @view.buffer.delete(@view.buffer.start_iter, @view.buffer.end_iter)
47
+ end
48
+ end
49
+
50
+ end
51
+
data/lib/rbpad/pad.rb ADDED
@@ -0,0 +1,656 @@
1
+
2
+ require 'gtk3'
3
+ require 'tempfile'
4
+
5
+ begin
6
+ require 'win32ole'
7
+ rescue LoadError => e
8
+ end
9
+
10
+ require 'rbpad/output'
11
+ require 'rbpad/input'
12
+ require 'rbpad/editor'
13
+ require 'rbpad/config'
14
+ require 'rbpad/util'
15
+ require 'rbpad/error_jp'
16
+ require 'rbpad/version'
17
+
18
+
19
+ module Rbpad
20
+
21
+ class Pad < Gtk::Window
22
+ DEFAULT_W = 800
23
+ DEFAULT_H = 650
24
+ def initialize(lang = :jp, w = DEFAULT_W, h = DEFAULT_H)
25
+ super("rbpad (#{VERSION})")
26
+ if w <= 0 and h <= 0
27
+ self.set_size_request(DEFAULT_W, DEFAULT_H)
28
+ self.maximize
29
+ else
30
+ self.set_size_request(w, h)
31
+ end
32
+
33
+ @lang = lang # language(:jp/:en)
34
+ @drb_portno = 49323 # port number for DRb
35
+ @q = Queue.new # queue for Inter-thread communication
36
+ @last_dir = nil # directory of last opened or saved
37
+
38
+ @conf = Config.new(
39
+ binding, # to create Proc instance in `class Pad` context
40
+ Utility.get_os,
41
+ @lang
42
+ )
43
+
44
+ # menu
45
+ @uimanager = Gtk::UIManager.new # UIManager
46
+ _set_menu # set menu
47
+ self.add_accel_group(@uimanager.accel_group) # enable to use shortcut keys
48
+
49
+ # console for output
50
+ @console_output = ConsoleOutput.new(@conf)
51
+ frame_output = Gtk::Frame.new(@conf.messages[:frame_output])
52
+ frame_output.add(@console_output)
53
+
54
+ # console for input
55
+ @console_input = ConsoleInput.new(@conf, @drb_portno)
56
+ frame_input = Gtk::Frame.new(@conf.messages[:frame_input])
57
+ frame_input.add(@console_input)
58
+
59
+ # statu area
60
+ status_area = Gtk::Label.new
61
+ status_area.set_alignment(0, 0.5)
62
+ status_area.override_font(Pango::FontDescription.new("#{@conf.platform[:font][:status]}"))
63
+ frame_status = Gtk::Frame.new
64
+ frame_status.add(status_area)
65
+
66
+ # editor
67
+ @editor = Editor.new(@conf, status_area)
68
+ @editor.set_draw_spaces(@uimanager.get_widget("/MenuBar/option/drawspaces").active?)
69
+ @editor.set_block_match(@uimanager.get_widget("/MenuBar/option/blockmatch").active?)
70
+
71
+ # vertical pane for edtor and output console
72
+ vpaned = Gtk::Paned.new(:vertical)
73
+ vpaned.pack1(@editor, resize: true, shrink: false)
74
+ vpaned.pack2(frame_output.set_shadow_type(:etched_in), resize: true, shrink: false)
75
+ vpaned.position = self.size[1] * 0.55 # <-- 300
76
+
77
+ # horizontal box for input console and status area
78
+ hbox = Gtk::Box.new(:horizontal)
79
+ hbox.pack_start(frame_input.set_shadow_type(:etched_in), expand: true, fill: true)
80
+ hbox.pack_start(frame_status.set_shadow_type(:etched_in), expand: false, fill: false)
81
+ frame_status.set_size_request(200, -1)
82
+
83
+ # toolbar
84
+ hbox_tool = Gtk::Box.new(:horizontal, 0)
85
+ hbox_tool.pack_start(_create_toolbox1, expand: false, fill: false, padding: 0)
86
+ hbox_tool.pack_end(_create_toolbox2, expand: false, fill: false, padding: 10)
87
+
88
+ # container
89
+ vbox_all = Gtk::Box.new(:vertical, 0)
90
+ vbox_all.pack_start(@uimanager.get_widget("/MenuBar"), expand: false, fill: true, padding: 0)
91
+ vbox_all.pack_start(hbox_tool, expand: false, fill: true, padding: 0)
92
+ vbox_all.pack_start(vpaned, expand: true, fill: true, padding: 0)
93
+ vbox_all.pack_start(hbox, expand: false, fill: false, padding: 0)
94
+
95
+ # window
96
+ @editor.page_focus # set focus to page of editor
97
+ self.add(vbox_all)
98
+ self.set_window_position(:center)
99
+ self.show_all
100
+
101
+ # handler
102
+ self.signal_connect(:delete_event) do
103
+ puts "clicked [x]"
104
+ _close_page_all # in case of being returned 'true', signal of ':destroy' is not happend
105
+ end
106
+
107
+ self.signal_connect(:destroy) do
108
+ # _quit
109
+ end
110
+ end
111
+
112
+ # set menu
113
+ private def _set_menu
114
+ menu = @conf.menu
115
+ actions_menubar = @conf.actions_menubar
116
+ actions_toggle = @conf.actions_menubar_toggle
117
+
118
+ menubar_group = Gtk::ActionGroup.new("menubar_group")
119
+ menubar_group.add_actions(actions_menubar)
120
+ @uimanager.insert_action_group(menubar_group, 0)
121
+
122
+ toggle_group = Gtk::ActionGroup.new("toggle_group")
123
+ toggle_group.add_toggle_actions(actions_toggle)
124
+ @uimanager.insert_action_group(toggle_group, 0)
125
+
126
+ @uimanager.add_ui(menu)
127
+
128
+ @uimanager.action_groups.each do |ag|
129
+ ag.actions.each do |a|
130
+ a.hide_if_empty = false # if true, couldn't show empty node
131
+ end
132
+ end
133
+
134
+ @uimanager.get_action("/MenuBar/file/kill").sensitive = false # disable 'kill(terminate)' item
135
+ end
136
+
137
+ # toolbox for toolbutton
138
+ def _create_toolbox1
139
+ toolbar = Gtk::Toolbar.new
140
+ f = "#{File.expand_path(File.dirname(__FILE__))}/config/run.xpm"
141
+ @toolitem_run = Gtk::ToolButton.new(icon_widget: Gtk::Image.new(file: f), label: @conf.messages[:exec])
142
+ toolbar.insert(@toolitem_run, 0)
143
+ toolbar.style = Gtk::ToolbarStyle::BOTH
144
+ @toolitem_run.signal_connect(:clicked) {
145
+ _exec
146
+ }
147
+
148
+ toolbar
149
+ end
150
+
151
+ # toolbox for finding function
152
+ def _create_toolbox2
153
+ boxf = Gtk::Box.new(:vertical)
154
+ box = Gtk::Box.new(:horizontal)
155
+
156
+ f = "#{File.expand_path(File.dirname(__FILE__))}/config/find_next.xpm"
157
+ item = Gtk::ToolButton.new(icon_widget: Gtk::Image.new(file: f))
158
+ #item.set_icon_name("go-down")
159
+ box.pack_end(item)
160
+ item.signal_connect(:clicked) {
161
+ _find_and_select(@word, :forward)
162
+ }
163
+
164
+ f = "#{File.expand_path(File.dirname(__FILE__))}/config/find_prev.xpm"
165
+ item = Gtk::ToolButton.new(icon_widget: Gtk::Image.new(file: f))
166
+ #item.set_icon_name("go-up")
167
+ box.pack_end(item)
168
+ item.signal_connect(:clicked) {
169
+ _find_and_select(@word, :backward)
170
+ }
171
+
172
+ entry = Gtk::Entry.new
173
+ entry.set_size_request(0, 0)
174
+ entry.override_font(Pango::FontDescription.new("#{@conf.platform[:font][:find]}"))
175
+ box.pack_end(entry, fill: false, expand: false, padding: 5) # want to be minimum ...?
176
+ entry.signal_connect(:changed) {
177
+ @word = entry.text
178
+ }
179
+ entry.signal_connect("activate") {
180
+ _find_and_select(@word, :forward)
181
+ }
182
+ item = Gtk::Label.new(@conf.messages[:find])
183
+ box.pack_end(item, fill: false, expand: false, padding: 5) # want to be minimum ...?
184
+
185
+ boxf.pack_start(Gtk::Label.new(''), fill: false, expand: false)
186
+ boxf.pack_start(box, fill: false, expand: false)
187
+ boxf.pack_start(Gtk::Label.new(''), fill: false, expand: false)
188
+
189
+ boxf
190
+ end
191
+
192
+ # execute
193
+ private def _exec
194
+ @thread = Thread.start do
195
+ # disabled/enable menu
196
+ @toolitem_run.sensitive = false
197
+ @uimanager.get_action("/MenuBar/file/run").sensitive = false
198
+ @uimanager.get_action("/MenuBar/file/kill").sensitive = true
199
+
200
+ begin
201
+ dirname, basename, tabname, status = @editor.get_page_properties
202
+ if dirname and Dir.exist?(dirname)
203
+ # in case of already existing the file in directory
204
+ run_dirname = dirname # run at there directory
205
+ run_filename = @editor.save_tmp(dirname)
206
+ else
207
+ tmp_dirname = Dir.mktmpdir(["ruby_", "_tmp"])
208
+ run_dirname = tmp_dirname # run at temporary directory
209
+ run_filename = @editor.save_tmp(tmp_dirname)
210
+ end
211
+
212
+ # save temporary file for DRb in directory for executing
213
+ required_filename = _save_required_file(run_dirname)
214
+
215
+ #puts "tmp_dirname : #{tmp_dirname}"
216
+ #puts "run_dirname : #{run_dirname}"
217
+ #puts "run_filename : #{run_filename}"
218
+ #puts "required_filename : #{required_filename}"
219
+
220
+ # create executing command
221
+ # cmd = %Q{ruby -E UTF-8 -r #{required_filename} -C #{run_dirname} #{run_filename}} # -C (couldn't run about directory named by Japanese, above Ruby 2.2)
222
+ # cmd = %Q{cd /d "#{run_dirname}" & ruby -E UTF-8 -r "#{required_filename}" #{run_filename}} # (couldn't delete temporary directory when process has killed)
223
+ cmd = %Q{ruby -E UTF-8 -r "#{required_filename}" #{run_filename}}
224
+ puts cmd
225
+
226
+ # starting message
227
+ @console_output.add_tail("> start: #{tabname} (#{Time.now.strftime('%H:%M:%S')})\n", "info") # insert to bottom line
228
+ @console_output.scroll_tail # scroll till bottom line
229
+
230
+ # move current directory while running, and go back when finished
231
+ current_dir = Dir.pwd
232
+ Dir.chdir(run_dirname)
233
+
234
+ # run
235
+ jpmsg = nil
236
+ @q << io = IO.popen(cmd, err: [:child, :out]) # merge stderr to stdout
237
+ io.each do |line|
238
+ line.gsub!(run_filename, tabname) # replace temporary file name to tab name for output
239
+ line.gsub!("\x03\n", "") # correspond the 'print' command which has no line-feed (delete line-feed including "\x03")
240
+ @console_output.add_tail(line, "result") # insert to bottom line
241
+ @console_output.scroll_tail # scroll till bottom line
242
+ if @uimanager.get_action("/MenuBar/option/errorjp") and
243
+ @uimanager.get_action("/MenuBar/option/errorjp").active?
244
+ jpmsg = Error_jp.get_msg(line) if line =~ /^#{tabname}/ or line =~ /\(.*Error\)$/
245
+ end
246
+ end
247
+ if jpmsg
248
+ @console_output.add_tail(jpmsg, "error_jp") # insert to bottom line
249
+ @console_output.scroll_tail # scroll till bottom line
250
+ end
251
+
252
+ ensure
253
+ # close IO
254
+ io.close
255
+
256
+ # go back to the original directory
257
+ Dir.chdir(current_dir)
258
+
259
+ # ending message
260
+ @console_output.newline # insert line-feed when not head of line
261
+ @console_output.add_tail("> end : #{tabname} (#{Time.now.strftime('%H:%M:%S')})\n\n", "info") # insert to bottom line
262
+ @console_output.scroll_tail # scroll till bottom line
263
+
264
+ # delete temporary directory including files in it
265
+ FileUtils.remove_entry_secure "#{run_dirname}/#{run_filename}"
266
+ FileUtils.remove_entry_secure required_filename
267
+ puts "Thread Terminate... #{tmp_dirname}"
268
+ FileUtils.remove_entry_secure tmp_dirname if tmp_dirname # couldn't delete current directory if stay here
269
+ puts "Thread Terminate..."
270
+
271
+ # clear the queue
272
+ @q.clear
273
+ end
274
+
275
+ # enabled/disable menu
276
+ @toolitem_run.sensitive = true
277
+ @uimanager.get_action("/MenuBar/file/run").sensitive = true
278
+ @uimanager.get_action("/MenuBar/file/kill").sensitive = false
279
+ end
280
+ end
281
+
282
+ # terminate
283
+ private def _kill
284
+ pid = (@q.empty? ? -1 : @q.pop.pid)
285
+ if pid > 0 and @thread
286
+ p pid
287
+ p @thread
288
+ puts "kill tprocess => " + pid.to_s
289
+ if @os == :windows
290
+ system("taskkill /f /pid #{pid}") # terminate the process (Windows)
291
+ else
292
+ Process.kill(:KILL, pid) # terminate the process (not Windows)
293
+ end
294
+
295
+ @thread.kill # kill thread
296
+ @thread = nil
297
+
298
+ # enabled/disable menu
299
+ @toolitem_run.sensitive = true
300
+ @uimanager.get_action("/MenuBar/file/run").sensitive = true
301
+ @uimanager.get_action("/MenuBar/file/kill").sensitive = false
302
+ end
303
+ end
304
+
305
+ # code for DRb server to emulate STDIN (to be required when starting-up)
306
+ private def _save_required_file(tmp_dirname)
307
+ # $stdin <-- (druby) -- $in
308
+ script = <<~"EOS" # exclude indent
309
+ require 'drb/drb'
310
+ require 'drb/acl'
311
+ $stdout.sync = true
312
+ $stderr.sync = true
313
+ $stdin, $in = IO.pipe
314
+ module Kernel
315
+ alias_method :__print__, :print
316
+ def print(*args)
317
+ __print__(*args, "\\x03\\n") # correspond the 'print' command which has no line-feed
318
+ end
319
+ end
320
+ list = %w[
321
+ deny all
322
+ allow 127.0.0.1
323
+ ]
324
+ # -- <DRb::DRbConnError: An existing connection was forcibly closed by the remote host.> if use 'localhost'
325
+ DRb.install_acl(ACL.new(list, ACL::DENY_ALLOW))
326
+ begin
327
+ DRb.start_service("druby://127.0.0.1:#{@drb_portno}", $in, safe_level: 1)
328
+ rescue
329
+ end
330
+ EOS
331
+ basename = "rq_#{Utility.get_uniqname}.rb"
332
+ tmpfilepath = "#{tmp_dirname}/#{basename}"
333
+ File.open(tmpfilepath, "w") do |fp|
334
+ fp.puts script
335
+ end
336
+ return tmpfilepath # path of temporary file
337
+ end
338
+
339
+ # show properties
340
+ private def _info
341
+ dirname, basename, tabname, status = @editor.get_page_properties
342
+ # show
343
+ @console_output.add_tail("[#{tabname}]\n")
344
+ @console_output.add_tail(" #{@conf.messages[:info_stat]} : #{status}\n")
345
+ @console_output.add_tail(" #{@conf.messages[:info_dir]} : #{dirname}\n")
346
+ @console_output.add_tail(" #{@conf.messages[:info_base]} : #{basename}\n\n")
347
+ @console_output.scroll_tail # scroll till bottom line
348
+ end
349
+
350
+ # show version of Ruby
351
+ private def _ruby_ver
352
+ thread = Thread.start do
353
+ IO.popen("ruby -v", err: [:child, :out]) do |pipe|
354
+ pipe.each do |line|
355
+ @console_output.add_tail(line) # insert to bottom line
356
+ end
357
+ end
358
+ #@console_output.add_tail("\n") # insert to bottom line (empty-line)
359
+ @console_output.scroll_tail # scroll till bottom line
360
+ end
361
+ end
362
+
363
+ # open file
364
+ private def _open
365
+ dialog = Gtk::FileChooserDialog.new(
366
+ title: @conf.messages[:dialog_open],
367
+ parent: self,
368
+ action: :open,
369
+ back: nil,
370
+ buttons: [[Gtk::Stock::OK, :accept],
371
+ [Gtk::Stock::CANCEL, :cancel]]
372
+ )
373
+ # set current directory
374
+ if @last_dir
375
+ # last specified directory
376
+ dialog.current_folder = @last_dir
377
+ else
378
+ # desktop
379
+ if ENV.has_key?("HOME") and Dir.exists?(ENV["HOME"] + "/Desktop")
380
+ dialog.current_folder = ENV["HOME"] + "/Desktop"
381
+ elsif ENV.has_key?("USERPROFILE") and Dir.exists?(ENV["USERPROFILE"] + "/Desktop")
382
+ dialog.current_folder = ENV["USERPROFILE"] + "/Desktop"
383
+ end
384
+ end
385
+
386
+ # set filters
387
+ filter1 = Gtk::FileFilter.new
388
+ filter2 = Gtk::FileFilter.new
389
+ filter1.name = "Ruby Program Files (*.rb)"
390
+ filter2.name = "All Files (*.*)"
391
+ filter1.add_pattern('*.rb')
392
+ filter2.add_pattern('*')
393
+ dialog.add_filter(filter1) # default is first added filter
394
+ dialog.add_filter(filter2)
395
+
396
+ while true
397
+ filename = nil
398
+ res = dialog.run
399
+ if res == :accept
400
+ filename = dialog.filename.gsub('\\', '/') # /foo/bar/zzz
401
+ end
402
+
403
+ # close dialog when canceled
404
+ break if filename == nil
405
+
406
+ if File.extname(filename) != ".lnk"
407
+ puts "filename #{filename}"
408
+ if @editor.load(filename) # load the file from specified directory
409
+ @last_dir = File.dirname(filename)
410
+ end
411
+ break
412
+ else
413
+ begin
414
+ # Windows only
415
+ sh = WIN32OLE.new('WScript.Shell')
416
+ lnkfile = sh.CreateShortcut(filename) # create WshShortcut object from *.lnk file (WIN32OLE)
417
+ if FileTest.directory?(lnkfile.TargetPath)
418
+ dialog.current_folder = lnkfile.TargetPath # open linked directory if link to directory (show dialog again)
419
+ elsif FileTest.readable_real?(lnkfile.TargetPath)
420
+ @editor.load(lnkfile.TargetPath) # load the linked file from specified directory
421
+ @last_dir = File.dirname(lnkfile.TargetPath)
422
+ break
423
+ end
424
+ rescue
425
+ break
426
+ end
427
+ end
428
+ end
429
+ dialog.destroy
430
+ end
431
+
432
+ # save file
433
+ private def _save
434
+ # do nothing if status is :EMPTY or :SAVED
435
+ # overwrite silently if 'dirname' is existing directory
436
+ # otherwise, call '_save_as'
437
+ dirname, basename, tabname, status = @editor.get_page_properties
438
+ puts "status #{status} basename #{basename} dirname #{dirname}"
439
+ return if status == :EMPTY
440
+ return if status == :SAVED
441
+ if dirname and Dir.exist?(dirname) and basename
442
+ @editor.save("#{dirname}/#{basename}") # save the file where specified directory
443
+ else
444
+ _save_as # show dialog for save as
445
+ end
446
+ end
447
+
448
+ # save file as
449
+ private def _save_as
450
+ dialog = Gtk::FileChooserDialog.new(
451
+ title: @conf.messages[:dialog_saveas],
452
+ parent: self,
453
+ action: :save,
454
+ back: nil,
455
+ buttons: [[Gtk::Stock::OK, :accept],
456
+ [Gtk::Stock::CANCEL, :cancel]]
457
+ )
458
+ # set current directory
459
+ if @last_dir
460
+ # last specified directory
461
+ dialog.current_folder = @last_dir
462
+ else
463
+ # desktop
464
+ if ENV.has_key?("HOME") and Dir.exists?(ENV["HOME"] + "/Desktop")
465
+ dialog.current_folder = ENV["HOME"] + "/Desktop"
466
+ elsif ENV.has_key?("USERPROFILE") and Dir.exists?(ENV["USERPROFILE"] + "/Desktop")
467
+ dialog.current_folder = ENV["USERPROFILE"] + "/Desktop"
468
+ end
469
+ end
470
+
471
+ # confirm whether overwrite, when already exist file which has same name
472
+ dialog.do_overwrite_confirmation = true
473
+ dialog.signal_connect("confirm_overwrite") do |fc|
474
+ #puts "confirm #{dialog.uri}"
475
+ :confirm
476
+ end
477
+
478
+ # set filters
479
+ filter1 = Gtk::FileFilter.new
480
+ filter2 = Gtk::FileFilter.new
481
+ filter1.name = "Ruby Program Files (*.rb)"
482
+ filter2.name = "All Files (*.*)"
483
+ filter1.add_pattern('*.rb')
484
+ filter2.add_pattern('*')
485
+ dialog.add_filter(filter1) # default is first added filter
486
+ dialog.add_filter(filter2)
487
+
488
+ # res = dialog.run
489
+ # if res == :accept
490
+ # puts dialog.filename
491
+ # filename = dialog.filename.gsub('\\', '/') # /foo/bar/zzz
492
+ # @editor.save(filename) # save the file where specified directory
493
+ # end
494
+
495
+ while true
496
+ filename = nil
497
+ res = dialog.run
498
+ if res == :accept
499
+ filename = dialog.filename.gsub('\\', '/') # /foo/bar/zzz
500
+ end
501
+
502
+ # close dialog when canceled
503
+ break if filename == nil
504
+
505
+ if File.extname(filename) != ".lnk"
506
+ puts "filename #{filename}"
507
+ @editor.save(filename) # save the file where specified directory
508
+ @last_dir = File.dirname(filename)
509
+ break
510
+ else
511
+ begin
512
+ # Windows only
513
+ sh = WIN32OLE.new('WScript.Shell')
514
+ lnkfile = sh.CreateShortcut(filename) # create WshShortcut object from *.lnk file (WIN32OLE)
515
+ if FileTest.directory?(lnkfile.TargetPath)
516
+ dialog.current_folder = lnkfile.TargetPath # open linked directory if link to directory (show dialog again)
517
+ elsif FileTest.readable_real?(lnkfile.TargetPath)
518
+ @editor.save(lnkfile.TargetPath) # save the linked file where specified directory
519
+ @last_dir = File.dirname(lnkfile.TargetPath)
520
+ break
521
+ end
522
+ rescue
523
+ break
524
+ end
525
+ end
526
+ end
527
+
528
+ dialog.destroy
529
+ end
530
+
531
+ # confirmation dialog (Yes/No/Cancel)
532
+ private def _draw_confirm_dialog(title, labeltext, parent)
533
+ dialog = Gtk::Dialog.new(title: title, parent: parent, flags: :modal)
534
+
535
+ dialog.child.pack_start(Gtk::Label.new(labeltext), expand: true, fill: true, padding: 30)
536
+
537
+ dialog.add_button(Gtk::Stock::YES, :yes)
538
+ dialog.add_button(Gtk::Stock::NO, :no)
539
+ dialog.add_button(Gtk::Stock::CANCEL, :cancel)
540
+ dialog.default_response = Gtk::ResponseType::CANCEL # set default (= CANCEL button)
541
+ dialog.show_all
542
+
543
+ res = dialog.run
544
+ dialog.destroy
545
+ return res
546
+ end
547
+
548
+ # open the references
549
+ private def _startcmd(uri)
550
+ if uri =~ /^http/
551
+ cmd = "#{@conf.platform[:command][:browse]} #{uri}"
552
+ else
553
+ # decide the relative path based on the place of script when local files
554
+ cmd = "#{@conf.platform[:command][:browse]} #{File.expand_path(File.dirname(__FILE__))}/#{uri}"
555
+ end
556
+
557
+ Thread.start do
558
+ p cmd
559
+ system(cmd) # open by default application
560
+ end
561
+ end
562
+
563
+ # append page
564
+ private def _new
565
+ @editor.append
566
+ @editor.page_focus # set focus to current page in editor
567
+ end
568
+
569
+ # close
570
+ private def _close
571
+ _close_page
572
+ if @editor.n_pages <= 0 # quit if all pages are closed
573
+ _kill
574
+ _quit
575
+ end
576
+ end
577
+
578
+ # close all pages
579
+ private def _close_page_all
580
+ until @editor.n_pages <= 0
581
+ return true if _close_page == :CANCEL_CLOSE # if true, not to be destroyed when call from delete_event
582
+ end
583
+ _kill
584
+ _quit
585
+ true # if true, not to be destroyed when call from delete_event
586
+ end
587
+
588
+ # quit
589
+ private def _quit
590
+ # -- pending the following codes for persistance
591
+ # options = {}
592
+ # @conf.option_ids.each do |o|
593
+ # if @uimanager.get_widget("/MenuBar/option/#{o}")
594
+ # options[o.to_sym] = @uimanager.get_widget("/MenuBar/option/#{o}").active?
595
+ # end
596
+ # end
597
+ # @conf.persist(options)
598
+ Gtk.main_quit
599
+ end
600
+
601
+ # close page
602
+ private def _close_page
603
+ dirname, basename, tabname, status = @editor.get_page_properties
604
+ if status == :UNSAVED
605
+ # show dialog when not saved
606
+ res = _draw_confirm_dialog("rbpad", " #{tabname} #{@conf.messages[:confirm_save]}", self)
607
+ if res == :yes
608
+ if dirname and basename
609
+ _save
610
+ else
611
+ _save_as
612
+ end
613
+ @editor.close
614
+ elsif res == :no
615
+ @editor.close
616
+ else
617
+ return :CANCEL_CLOSE
618
+ end
619
+ else
620
+ # close as it is, when saved or empty
621
+ @editor.close
622
+ end
623
+ end
624
+
625
+ ###
626
+ private def _undo ; @editor.undo ; end
627
+ private def _redo ; @editor.redo ; end
628
+ private def _cut ; @editor.cut ; end
629
+ private def _copy ; @editor.copy ; end
630
+ private def _paste ; @editor.paste ; end
631
+ private def _select_all ; @editor.select_all ; end
632
+ private def _clear_output ; @console_output.clear ; end
633
+
634
+ private def _find_and_select(word, direction)
635
+ unless @editor.find_and_select(word, direction)
636
+ @console_output.add_tail("'#{word}' #{@conf.messages[:not_found]}\n") # insert to bottom line
637
+ @console_output.scroll_tail # scroll till bottom line
638
+ end
639
+ end
640
+
641
+ private def _drawspaces(ag, action)
642
+ @editor.set_draw_spaces(action.active?)
643
+ end
644
+
645
+ private def _blockmatch(ag, action)
646
+ @editor.set_block_match(action.active?)
647
+ end
648
+
649
+ # insert block to new page (for tamplates)
650
+ private def _load_block(statement)
651
+ @editor.load_block(statement)
652
+ end
653
+
654
+ end
655
+ end
656
+