pdfwalker 1.0.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/COPYING +674 -0
- data/bin/pdfwalker +30 -0
- data/lib/pdfwalker.rb +21 -0
- data/lib/pdfwalker/about.rb +37 -0
- data/lib/pdfwalker/config.rb +120 -0
- data/lib/pdfwalker/file.rb +320 -0
- data/lib/pdfwalker/hexview.rb +84 -0
- data/lib/pdfwalker/imgview.rb +69 -0
- data/lib/pdfwalker/menu.rb +382 -0
- data/lib/pdfwalker/properties.rb +126 -0
- data/lib/pdfwalker/signing.rb +558 -0
- data/lib/pdfwalker/textview.rb +88 -0
- data/lib/pdfwalker/treeview.rb +416 -0
- data/lib/pdfwalker/version.rb +23 -0
- data/lib/pdfwalker/walker.rb +300 -0
- data/lib/pdfwalker/xrefs.rb +75 -0
- metadata +103 -0
data/bin/pdfwalker
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'pdfwalker'
|
5
|
+
|
6
|
+
OptionParser.new do |opts|
|
7
|
+
opts.banner = <<-BANNER
|
8
|
+
Usage: #{$0} [options] [PDF FILE]
|
9
|
+
PDFWalker is a frontend for exploring PDF objects, based on Origami.
|
10
|
+
|
11
|
+
Options:
|
12
|
+
BANNER
|
13
|
+
|
14
|
+
opts.on("-v", "--version", "Print version number.") do
|
15
|
+
puts PDFWalker::VERSION
|
16
|
+
exit
|
17
|
+
end
|
18
|
+
|
19
|
+
opts.on_tail("-h", "--help", "Show this message.") do
|
20
|
+
puts opts
|
21
|
+
exit
|
22
|
+
end
|
23
|
+
end
|
24
|
+
.parse!(ARGV)
|
25
|
+
|
26
|
+
if ARGV.size > 1
|
27
|
+
abort "Error: too many arguments."
|
28
|
+
end
|
29
|
+
|
30
|
+
PDFWalker::Walker.start(ARGV[0])
|
data/lib/pdfwalker.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
4
|
+
Copyright (C) 2017 Guillaume Delugré.
|
5
|
+
|
6
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
PDF Walker is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require_relative 'pdfwalker/walker'
|
@@ -0,0 +1,37 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
4
|
+
Copyright (C) 2017 Guillaume Delugré.
|
5
|
+
|
6
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
PDF Walker is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
module PDFWalker
|
22
|
+
|
23
|
+
class Walker < Window
|
24
|
+
|
25
|
+
def about
|
26
|
+
AboutDialog.show(self,
|
27
|
+
name: "PDF Walker",
|
28
|
+
program_name: "PDF Walker",
|
29
|
+
version: "#{PDFWalker::VERSION}",
|
30
|
+
copyright: "Copyright © 2017\nGuillaume Delugré",
|
31
|
+
comments: "A PDF file explorer, based on Origami",
|
32
|
+
license: File.read(File.join(__dir__, "../..", "COPYING")),
|
33
|
+
wrap_license: true,
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
4
|
+
Copyright (C) 2017 Guillaume Delugré.
|
5
|
+
|
6
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
PDF Walker is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'origami'
|
22
|
+
require 'yaml'
|
23
|
+
|
24
|
+
module PDFWalker
|
25
|
+
|
26
|
+
class Walker < Window
|
27
|
+
|
28
|
+
class Config
|
29
|
+
DEFAULT_CONFIG_FILE = "#{File.expand_path("~")}/.pdfwalker.conf.yml"
|
30
|
+
DEFAULT_CONFIG =
|
31
|
+
{
|
32
|
+
"Debug" =>
|
33
|
+
{
|
34
|
+
"Profiling" => false,
|
35
|
+
"ProfilingOutputDir" => "prof",
|
36
|
+
"Verbosity" => Origami::Parser::VERBOSE_TRACE,
|
37
|
+
"IgnoreFileHeader" => true
|
38
|
+
},
|
39
|
+
|
40
|
+
"UI" =>
|
41
|
+
{
|
42
|
+
"LastOpenedDocuments" => []
|
43
|
+
}
|
44
|
+
}
|
45
|
+
NLOG_RECENT_FILES = 5
|
46
|
+
|
47
|
+
def initialize(configfile = DEFAULT_CONFIG_FILE)
|
48
|
+
begin
|
49
|
+
@conf = YAML.load(File.open(configfile))
|
50
|
+
rescue
|
51
|
+
@conf = DEFAULT_CONFIG
|
52
|
+
ensure
|
53
|
+
@filename = configfile
|
54
|
+
set_missing_values
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def last_opened_file(filepath)
|
59
|
+
@conf["UI"]['LastOpenedDocuments'].push(filepath).uniq!
|
60
|
+
@conf["UI"]['LastOpenedDocuments'].delete_at(0) while @conf["UI"]['LastOpenedDocuments'].size > NLOG_RECENT_FILES
|
61
|
+
|
62
|
+
save
|
63
|
+
end
|
64
|
+
|
65
|
+
def recent_files(n = NLOG_RECENT_FILES)
|
66
|
+
@conf["UI"]['LastOpenedDocuments'].last(n).reverse
|
67
|
+
end
|
68
|
+
|
69
|
+
def set_profiling(bool)
|
70
|
+
@conf["Debug"]['Profiling'] = bool
|
71
|
+
save
|
72
|
+
end
|
73
|
+
|
74
|
+
def profile?
|
75
|
+
@conf["Debug"]['Profiling']
|
76
|
+
end
|
77
|
+
|
78
|
+
def profile_output_dir
|
79
|
+
@conf["Debug"]['ProfilingOutputDir']
|
80
|
+
end
|
81
|
+
|
82
|
+
def set_ignore_header(bool)
|
83
|
+
@conf["Debug"]['IgnoreFileHeader'] = bool
|
84
|
+
save
|
85
|
+
end
|
86
|
+
|
87
|
+
def ignore_header?
|
88
|
+
@conf["Debug"]['IgnoreFileHeader']
|
89
|
+
end
|
90
|
+
|
91
|
+
def set_verbosity(level)
|
92
|
+
@conf["Debug"]['Verbosity'] = level
|
93
|
+
save
|
94
|
+
end
|
95
|
+
|
96
|
+
def verbosity
|
97
|
+
@conf["Debug"]['Verbosity']
|
98
|
+
end
|
99
|
+
|
100
|
+
def save
|
101
|
+
File.open(@filename, "w").write(@conf.to_yaml)
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def set_missing_values
|
107
|
+
@conf ||= {}
|
108
|
+
|
109
|
+
DEFAULT_CONFIG.each_key do |cat|
|
110
|
+
@conf[cat] = {} unless @conf.include?(cat)
|
111
|
+
|
112
|
+
DEFAULT_CONFIG[cat].each_pair do |key, value|
|
113
|
+
@conf[cat][key] = value unless @conf[cat].include?(key)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
This file is part of PDF Walker, a graphical PDF file browser
|
4
|
+
Copyright (C) 2017 Guillaume Delugré.
|
5
|
+
|
6
|
+
PDF Walker is free software: you can redistribute it and/or modify
|
7
|
+
it under the terms of the GNU General Public License as published by
|
8
|
+
the Free Software Foundation, either version 3 of the License, or
|
9
|
+
(at your option) any later version.
|
10
|
+
|
11
|
+
PDF Walker is distributed in the hope that it will be useful,
|
12
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
GNU General Public License for more details.
|
15
|
+
|
16
|
+
You should have received a copy of the GNU General Public License
|
17
|
+
along with PDF Walker. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
=end
|
20
|
+
|
21
|
+
require 'origami'
|
22
|
+
|
23
|
+
module PDFWalker
|
24
|
+
|
25
|
+
class Walker < Window
|
26
|
+
attr_reader :opened
|
27
|
+
attr_reader :explore_history
|
28
|
+
|
29
|
+
def close
|
30
|
+
@opened = nil
|
31
|
+
@filename = ''
|
32
|
+
@explorer_history.clear
|
33
|
+
|
34
|
+
@treeview.clear
|
35
|
+
@objectview.clear
|
36
|
+
@hexview.clear
|
37
|
+
|
38
|
+
# disable all menus.
|
39
|
+
[
|
40
|
+
@file_menu_close, @file_menu_saveas, @file_menu_refresh,
|
41
|
+
@document_menu_search,
|
42
|
+
@document_menu_gotocatalog, @document_menu_gotodocinfo, @document_menu_gotometadata,
|
43
|
+
@document_menu_gotopage, @document_menu_gotofield, @document_menu_gotorev, @document_menu_gotoobj,
|
44
|
+
@document_menu_properties, @document_menu_sign, @document_menu_ur
|
45
|
+
].each do |menu|
|
46
|
+
menu.sensitive = false
|
47
|
+
end
|
48
|
+
|
49
|
+
@statusbar.pop(@main_context)
|
50
|
+
|
51
|
+
GC.start
|
52
|
+
end
|
53
|
+
|
54
|
+
def open(filename = nil)
|
55
|
+
dialog = Gtk::FileChooserDialog.new("Open PDF File",
|
56
|
+
self,
|
57
|
+
FileChooser::ACTION_OPEN,
|
58
|
+
nil,
|
59
|
+
[Stock::CANCEL, Dialog::RESPONSE_CANCEL],
|
60
|
+
[Stock::OPEN, Dialog::RESPONSE_ACCEPT])
|
61
|
+
|
62
|
+
last_file = @config.recent_files.first
|
63
|
+
unless last_file.nil?
|
64
|
+
last_folder = File.dirname(last_file)
|
65
|
+
dialog.set_current_folder(last_folder) if File.directory?(last_folder)
|
66
|
+
end
|
67
|
+
|
68
|
+
dialog.filter = FileFilter.new.add_pattern("*.acrodata").add_pattern("*.pdf").add_pattern("*.fdf")
|
69
|
+
|
70
|
+
if filename.nil? and dialog.run != Gtk::Dialog::RESPONSE_ACCEPT
|
71
|
+
dialog.destroy
|
72
|
+
return
|
73
|
+
end
|
74
|
+
|
75
|
+
create_progressbar
|
76
|
+
|
77
|
+
filename ||= dialog.filename
|
78
|
+
dialog.destroy
|
79
|
+
|
80
|
+
begin
|
81
|
+
document = start_profiling do
|
82
|
+
parse_file(filename)
|
83
|
+
end
|
84
|
+
|
85
|
+
set_active_document(filename, document)
|
86
|
+
|
87
|
+
rescue
|
88
|
+
error("Error while parsing file.\n#{$!} (#{$!.class})\n" + $!.backtrace.join("\n"))
|
89
|
+
ensure
|
90
|
+
close_progressbar
|
91
|
+
self.activate_focus
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def save_data(caption, data, filename = "")
|
96
|
+
dialog = Gtk::FileChooserDialog.new(caption,
|
97
|
+
self,
|
98
|
+
Gtk::FileChooser::ACTION_SAVE,
|
99
|
+
nil,
|
100
|
+
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
|
101
|
+
[Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT]
|
102
|
+
)
|
103
|
+
|
104
|
+
dialog.do_overwrite_confirmation = true
|
105
|
+
dialog.current_name = File.basename(filename)
|
106
|
+
dialog.filter = FileFilter.new.add_pattern("*.*")
|
107
|
+
|
108
|
+
if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
|
109
|
+
begin
|
110
|
+
File.binwrite(dialog.filename, data)
|
111
|
+
rescue
|
112
|
+
error("Error: #{$!.message}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
dialog.destroy
|
117
|
+
end
|
118
|
+
|
119
|
+
def save
|
120
|
+
dialog = Gtk::FileChooserDialog.new("Save PDF file",
|
121
|
+
self,
|
122
|
+
Gtk::FileChooser::ACTION_SAVE,
|
123
|
+
nil,
|
124
|
+
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
|
125
|
+
[Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT]
|
126
|
+
)
|
127
|
+
|
128
|
+
dialog.filter = FileFilter.new.add_pattern("*.acrodata").add_pattern("*.pdf").add_pattern("*.fdf")
|
129
|
+
|
130
|
+
folder = File.dirname(@filename)
|
131
|
+
dialog.set_current_folder(folder)
|
132
|
+
|
133
|
+
if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
|
134
|
+
begin
|
135
|
+
@opened.save(dialog.filename)
|
136
|
+
rescue
|
137
|
+
error("#{$!.class}: #{$!.message}\n#{$!.backtrace.join($/)}")
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
dialog.destroy
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def set_active_document(filename, document)
|
147
|
+
close if @opened
|
148
|
+
@opened = document
|
149
|
+
@filename = filename
|
150
|
+
|
151
|
+
@config.last_opened_file(filename)
|
152
|
+
@config.save
|
153
|
+
update_recent_menu
|
154
|
+
|
155
|
+
@last_search_result = []
|
156
|
+
@last_search =
|
157
|
+
{
|
158
|
+
:expr => "",
|
159
|
+
:regexp => false,
|
160
|
+
:type => :body
|
161
|
+
}
|
162
|
+
|
163
|
+
self.reload
|
164
|
+
|
165
|
+
# Enable basic file menus.
|
166
|
+
[
|
167
|
+
@file_menu_close, @file_menu_refresh,
|
168
|
+
].each do |menu|
|
169
|
+
menu.sensitive = true
|
170
|
+
end
|
171
|
+
|
172
|
+
@explorer_history.clear
|
173
|
+
|
174
|
+
@statusbar.push(@main_context, "Viewing #{filename}")
|
175
|
+
|
176
|
+
setup_pdf_interface if @opened.is_a?(Origami::PDF)
|
177
|
+
end
|
178
|
+
|
179
|
+
def setup_pdf_interface
|
180
|
+
# Enable save and document menu.
|
181
|
+
[
|
182
|
+
@file_menu_saveas,
|
183
|
+
@document_menu_search,
|
184
|
+
@document_menu_gotocatalog, @document_menu_gotopage, @document_menu_gotorev, @document_menu_gotoobj,
|
185
|
+
@document_menu_properties, @document_menu_sign, @document_menu_ur
|
186
|
+
].each do |menu|
|
187
|
+
menu.sensitive = true
|
188
|
+
end
|
189
|
+
|
190
|
+
@document_menu_gotodocinfo.sensitive = true if @opened.document_info?
|
191
|
+
@document_menu_gotometadata.sensitive = true if @opened.metadata?
|
192
|
+
@document_menu_gotofield.sensitive = true if @opened.form?
|
193
|
+
|
194
|
+
setup_page_menu
|
195
|
+
setup_field_menu
|
196
|
+
setup_revision_menu
|
197
|
+
|
198
|
+
goto_catalog
|
199
|
+
end
|
200
|
+
|
201
|
+
def setup_page_menu
|
202
|
+
page_menu = Menu.new
|
203
|
+
@document_menu_gotopage.remove_submenu
|
204
|
+
@opened.each_page.with_index(1) do |page, index|
|
205
|
+
page_menu.append(item = MenuItem.new(index.to_s).show)
|
206
|
+
item.signal_connect("activate") { @treeview.goto(page) }
|
207
|
+
end
|
208
|
+
@document_menu_gotopage.set_submenu(page_menu)
|
209
|
+
end
|
210
|
+
|
211
|
+
def setup_field_menu
|
212
|
+
field_menu = Menu.new
|
213
|
+
@document_menu_gotofield.remove_submenu
|
214
|
+
@opened.each_field do |field|
|
215
|
+
field_name =
|
216
|
+
if field.T.is_a?(Origami::String)
|
217
|
+
field.T.to_utf8
|
218
|
+
else
|
219
|
+
"<unnamed field>"
|
220
|
+
end
|
221
|
+
|
222
|
+
field_menu.append(item = MenuItem.new(field_name).show)
|
223
|
+
item.signal_connect("activate") { @treeview.goto(field) }
|
224
|
+
end
|
225
|
+
@document_menu_gotofield.set_submenu(field_menu)
|
226
|
+
end
|
227
|
+
|
228
|
+
def setup_revision_menu
|
229
|
+
rev_menu = Menu.new
|
230
|
+
@document_menu_gotorev.remove_submenu
|
231
|
+
@opened.revisions.each.with_index(1) do |rev, index|
|
232
|
+
rev_menu.append(item = MenuItem.new(index.to_s).show)
|
233
|
+
item.signal_connect("activate") { @treeview.goto(rev) }
|
234
|
+
end
|
235
|
+
@document_menu_gotorev.set_submenu(rev_menu)
|
236
|
+
end
|
237
|
+
|
238
|
+
def parse_file(path)
|
239
|
+
#
|
240
|
+
# Try to detect the file type of the document.
|
241
|
+
# Fallback to PDF if none is found.
|
242
|
+
#
|
243
|
+
file_type = detect_file_type(path)
|
244
|
+
if file_type.nil?
|
245
|
+
file_type = Origami::PDF
|
246
|
+
force_mode = true
|
247
|
+
else
|
248
|
+
force_mode = false
|
249
|
+
end
|
250
|
+
|
251
|
+
file_type.read(path,
|
252
|
+
verbosity: Origami::Parser::VERBOSE_TRACE,
|
253
|
+
ignore_errors: false,
|
254
|
+
callback: method(:update_progressbar),
|
255
|
+
prompt_password: method(:prompt_password),
|
256
|
+
force: force_mode
|
257
|
+
)
|
258
|
+
end
|
259
|
+
|
260
|
+
def update_progressbar(_obj)
|
261
|
+
@progressbar.pulse if @progressbar
|
262
|
+
Gtk.main_iteration while Gtk.events_pending?
|
263
|
+
end
|
264
|
+
|
265
|
+
def prompt_password
|
266
|
+
passwd = ""
|
267
|
+
|
268
|
+
dialog = Gtk::Dialog.new(
|
269
|
+
"This document is encrypted",
|
270
|
+
nil,
|
271
|
+
Gtk::Dialog::MODAL,
|
272
|
+
[ Gtk::Stock::OK, Gtk::Dialog::RESPONSE_OK ],
|
273
|
+
[ Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL ]
|
274
|
+
)
|
275
|
+
|
276
|
+
dialog.set_default_response(Gtk::Dialog::RESPONSE_OK)
|
277
|
+
|
278
|
+
label = Gtk::Label.new("Please enter password:")
|
279
|
+
entry = Gtk::Entry.new
|
280
|
+
entry.signal_connect('activate') {
|
281
|
+
dialog.response(Gtk::Dialog::RESPONSE_OK)
|
282
|
+
}
|
283
|
+
|
284
|
+
dialog.vbox.add(label)
|
285
|
+
dialog.vbox.add(entry)
|
286
|
+
dialog.show_all
|
287
|
+
|
288
|
+
dialog.run do |response|
|
289
|
+
passwd = entry.text if response == Gtk::Dialog::RESPONSE_OK
|
290
|
+
end
|
291
|
+
|
292
|
+
dialog.destroy
|
293
|
+
passwd
|
294
|
+
end
|
295
|
+
|
296
|
+
def detect_file_type(path)
|
297
|
+
supported_types = [ Origami::PDF, Origami::FDF, Origami::PPKLite ]
|
298
|
+
|
299
|
+
File.open(path, 'rb') do |file|
|
300
|
+
data = file.read(128)
|
301
|
+
|
302
|
+
supported_types.each do |type|
|
303
|
+
return type if data.match(type::Header::MAGIC)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
nil
|
308
|
+
end
|
309
|
+
|
310
|
+
def create_progressbar
|
311
|
+
@progresswin = Dialog.new("Parsing file...", self, Dialog::MODAL)
|
312
|
+
@progresswin.vbox.add(@progressbar = ProgressBar.new.set_pulse_step(0.05))
|
313
|
+
@progresswin.show_all
|
314
|
+
end
|
315
|
+
|
316
|
+
def close_progressbar
|
317
|
+
@progresswin.close
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|