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.
- checksums.yaml +7 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +48 -0
- data/Rakefile +2 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/rbpad +3 -0
- data/lib/rbpad/blockmatch.rb +136 -0
- data/lib/rbpad/config/actions_menubar.xml +166 -0
- data/lib/rbpad/config/actions_menubar_template.xml +126 -0
- data/lib/rbpad/config/actions_menubar_toggle.xml +27 -0
- data/lib/rbpad/config/find_next.xpm +196 -0
- data/lib/rbpad/config/find_prev.xpm +196 -0
- data/lib/rbpad/config/menu.xml +63 -0
- data/lib/rbpad/config/messages.xml +64 -0
- data/lib/rbpad/config/platform.xml +41 -0
- data/lib/rbpad/config/run.xpm +44 -0
- data/lib/rbpad/config/sourceview.xml +132 -0
- data/lib/rbpad/config.rb +216 -0
- data/lib/rbpad/editor.rb +141 -0
- data/lib/rbpad/error_jp.rb +176 -0
- data/lib/rbpad/input.rb +27 -0
- data/lib/rbpad/output.rb +51 -0
- data/lib/rbpad/pad.rb +656 -0
- data/lib/rbpad/page.rb +257 -0
- data/lib/rbpad/util.rb +30 -0
- data/lib/rbpad/version.rb +3 -0
- data/lib/rbpad.rb +32 -0
- data/rbpad.gemspec +43 -0
- data/rbpad3_en.png +0 -0
- data/rbpad3_jp.png +0 -0
- metadata +95 -0
data/lib/rbpad/output.rb
ADDED
@@ -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
|
+
|