rbpad 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+