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
@@ -0,0 +1,84 @@
|
|
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 'gtkhex'
|
22
|
+
|
23
|
+
module PDFWalker
|
24
|
+
|
25
|
+
class Walker < Window
|
26
|
+
private
|
27
|
+
|
28
|
+
def create_hexview
|
29
|
+
@hexview = DumpView.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
class DumpView < ScrolledWindow
|
33
|
+
|
34
|
+
def initialize(parent)
|
35
|
+
@parent = parent
|
36
|
+
super()
|
37
|
+
|
38
|
+
set_policy(POLICY_AUTOMATIC, POLICY_AUTOMATIC)
|
39
|
+
|
40
|
+
@current_obj = nil
|
41
|
+
|
42
|
+
@view = HexEditor.new
|
43
|
+
@view.show_offsets(true)
|
44
|
+
|
45
|
+
add_with_viewport @view
|
46
|
+
end
|
47
|
+
|
48
|
+
def clear
|
49
|
+
@view.set_data ''
|
50
|
+
end
|
51
|
+
|
52
|
+
def load(object)
|
53
|
+
return if @current_obj.equal?(object)
|
54
|
+
|
55
|
+
self.clear
|
56
|
+
|
57
|
+
case object
|
58
|
+
when Origami::Stream
|
59
|
+
load_stream(object)
|
60
|
+
when Origami::String
|
61
|
+
load_string(object)
|
62
|
+
end
|
63
|
+
|
64
|
+
@current_obj = object
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def load_stream(object)
|
70
|
+
begin
|
71
|
+
@view.set_data(object.data)
|
72
|
+
rescue Origami::Filter::Error
|
73
|
+
@view.set_data($!.input_data) if $!.input_data
|
74
|
+
|
75
|
+
@parent.error("#{$!.class}: #{$!.message}") unless object.filters == [ :DCTDecode ]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def load_string(object)
|
80
|
+
@view.set_data(object.value)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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 ImgViewer < Window
|
24
|
+
attr_reader :image
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
super()
|
28
|
+
|
29
|
+
set_title "Image view"
|
30
|
+
set_decorated false
|
31
|
+
set_resizable false
|
32
|
+
|
33
|
+
add_events(Gdk::Event::KEY_RELEASE_MASK)
|
34
|
+
signal_connect('key_release_event') { |_, event|
|
35
|
+
destroy if event.keyval == Gdk::Keyval::GDK_Escape
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def show_raw_img(data, w, h, bpc, bpr)
|
40
|
+
set_default_size w,h
|
41
|
+
|
42
|
+
pixbuf = GdkPixbuf::Pixbuf.new data: data,
|
43
|
+
colorspace: GdkPixbuf::Colorspace::RGB,
|
44
|
+
has_alpha: false,
|
45
|
+
bits_per_sample: bpc,
|
46
|
+
width: w, height: h,
|
47
|
+
row_stride: bpr
|
48
|
+
|
49
|
+
@image = Gtk::Image.new(pixbuf)
|
50
|
+
add @image
|
51
|
+
|
52
|
+
show_all
|
53
|
+
end
|
54
|
+
|
55
|
+
def show_compressed_img(data)
|
56
|
+
loader = GdkPixbuf::PixbufLoader.new
|
57
|
+
loader.last_write data
|
58
|
+
|
59
|
+
pixbuf = loader.pixbuf
|
60
|
+
set_default_size pixbuf.width, pixbuf.height
|
61
|
+
|
62
|
+
@image = Gtk::Image.new(pixbuf)
|
63
|
+
add @image
|
64
|
+
|
65
|
+
show_all
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,382 @@
|
|
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
|
+
module PopupMenu
|
24
|
+
|
25
|
+
@@menus = Hash.new([])
|
26
|
+
@@menus['PDF'] =
|
27
|
+
[
|
28
|
+
{
|
29
|
+
Name: Stock::SAVE_AS,
|
30
|
+
Sensitive: true,
|
31
|
+
Callback: lambda { |_widget, viewer, _path|
|
32
|
+
viewer.parent.save
|
33
|
+
}
|
34
|
+
},
|
35
|
+
{
|
36
|
+
Name: :"---"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
Name: Stock::PROPERTIES,
|
40
|
+
Sensitive: true,
|
41
|
+
Callback: lambda { |_widget, viewer, _path|
|
42
|
+
viewer.parent.display_file_properties
|
43
|
+
}
|
44
|
+
},
|
45
|
+
{
|
46
|
+
Name: :"---"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
Name: Stock::CLOSE,
|
50
|
+
Sensitive: true,
|
51
|
+
Callback: lambda { |_widget, viewer, _path|
|
52
|
+
viewer.parent.close
|
53
|
+
}
|
54
|
+
}
|
55
|
+
]
|
56
|
+
|
57
|
+
@@menus['Reference'] =
|
58
|
+
[
|
59
|
+
{
|
60
|
+
Name: Stock::JUMP_TO,
|
61
|
+
Sensitive: true,
|
62
|
+
Callback: lambda { |_widget, viewer, path|
|
63
|
+
viewer.row_activated(path, viewer.get_column(viewer.class::TEXTCOL))
|
64
|
+
}
|
65
|
+
}
|
66
|
+
]
|
67
|
+
|
68
|
+
@@menus['Revision'] =
|
69
|
+
[
|
70
|
+
{
|
71
|
+
Name: "Save to this revision",
|
72
|
+
Sensitive: true,
|
73
|
+
Callback: lambda { |_widget, viewer, path|
|
74
|
+
revstr = viewer.model.get_value(viewer.model.get_iter(path), viewer.class::TEXTCOL)
|
75
|
+
revstr.slice!(0, "Revision ".size)
|
76
|
+
|
77
|
+
revnum = revstr.to_i
|
78
|
+
|
79
|
+
dialog = Gtk::FileChooserDialog.new("Save PDF File",
|
80
|
+
viewer.parent,
|
81
|
+
Gtk::FileChooser::ACTION_SAVE,
|
82
|
+
nil,
|
83
|
+
[Gtk::Stock::CANCEL, Gtk::Dialog::RESPONSE_CANCEL],
|
84
|
+
[Gtk::Stock::SAVE, Gtk::Dialog::RESPONSE_ACCEPT]
|
85
|
+
)
|
86
|
+
|
87
|
+
dialog.filter = FileFilter.new.add_pattern("*.pdf")
|
88
|
+
|
89
|
+
if dialog.run == Gtk::Dialog::RESPONSE_ACCEPT
|
90
|
+
viewer.parent.opened.save_upto(revnum, dialog.filename)
|
91
|
+
end
|
92
|
+
|
93
|
+
dialog.destroy
|
94
|
+
}
|
95
|
+
}
|
96
|
+
]
|
97
|
+
|
98
|
+
@@menus['Stream'] =
|
99
|
+
[
|
100
|
+
{
|
101
|
+
Name: "Dump encoded stream",
|
102
|
+
Sensitive: true,
|
103
|
+
Callback: lambda { |_widget, viewer, path|
|
104
|
+
stream = viewer.object_by_path(path)
|
105
|
+
|
106
|
+
viewer.parent.save_data("Save encoded stream to file", stream.encoded_data)
|
107
|
+
}
|
108
|
+
},
|
109
|
+
{
|
110
|
+
Name: "Dump decoded stream",
|
111
|
+
Sensitive: true,
|
112
|
+
Callback: lambda { |_widget, viewer, path|
|
113
|
+
stream = viewer.object_by_path(path)
|
114
|
+
|
115
|
+
viewer.parent.save_data("Save decoded stream to file", stream.data)
|
116
|
+
}
|
117
|
+
}
|
118
|
+
]
|
119
|
+
|
120
|
+
@@menus['String'] =
|
121
|
+
[
|
122
|
+
{
|
123
|
+
Name: "Dump string",
|
124
|
+
Sensitive: true,
|
125
|
+
Callback: lambda { |_widget, viewer, path|
|
126
|
+
string = viewer.object_by_path(path)
|
127
|
+
|
128
|
+
viewer.parent.save_data("Save string to file", string.value)
|
129
|
+
}
|
130
|
+
}
|
131
|
+
]
|
132
|
+
|
133
|
+
@@menus['Image'] = @@menus['Stream'] +
|
134
|
+
[
|
135
|
+
{
|
136
|
+
Name: :"---"
|
137
|
+
},
|
138
|
+
{
|
139
|
+
Name: "View image",
|
140
|
+
Sensitive: true,
|
141
|
+
Callback: lambda { |_widget, viewer, path|
|
142
|
+
stm = viewer.object_by_path(path)
|
143
|
+
w,h = stm.Width, stm.Height
|
144
|
+
|
145
|
+
if stm.ColorSpace.nil?
|
146
|
+
colors = 1
|
147
|
+
else
|
148
|
+
colors =
|
149
|
+
case stm.ColorSpace.value
|
150
|
+
when :DeviceGray then 1
|
151
|
+
when :DeviceRGB then 3
|
152
|
+
when :DeviceCMYK then 4
|
153
|
+
else
|
154
|
+
1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
bpc = stm.BitsPerComponent || 8
|
159
|
+
bpr = (w * colors * bpc + 7) >> 3
|
160
|
+
data = stm.data
|
161
|
+
|
162
|
+
begin
|
163
|
+
imgview = ImgViewer.new
|
164
|
+
if stm.Filter == :DCTDecode or (stm.Filter.is_a?(Array) and stm.Filter[0] == :DCTDecode)
|
165
|
+
imgview.show_compressed_img data
|
166
|
+
else
|
167
|
+
imgview.show_raw_img data, w, h, bpc, bpr
|
168
|
+
end
|
169
|
+
rescue
|
170
|
+
viewer.parent.error("#{$!.class}: #{$!.message}")
|
171
|
+
end
|
172
|
+
}
|
173
|
+
}
|
174
|
+
]
|
175
|
+
|
176
|
+
def popup_menu(obj, event, path)
|
177
|
+
menu = Menu.new
|
178
|
+
|
179
|
+
type = popup_menu_key(obj)
|
180
|
+
|
181
|
+
# Create menu title.
|
182
|
+
title = obj.is_a?(Origami::Object) ? "Object : " : ""
|
183
|
+
title << type.to_s
|
184
|
+
menu.append(MenuItem.new(title).set_sensitive(false).modify_text(Gtk::STATE_INSENSITIVE, Gdk::Color.new(255,0,255)))
|
185
|
+
|
186
|
+
# Object information.
|
187
|
+
create_object_menu(menu, obj) if obj.is_a?(Origami::Object)
|
188
|
+
|
189
|
+
# Type-specific menu.
|
190
|
+
create_type_menu(menu, type, path)
|
191
|
+
|
192
|
+
menu.show_all
|
193
|
+
menu.popup(nil, nil, event.button, event.time)
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
def create_object_menu(menu, object)
|
199
|
+
if object.indirect?
|
200
|
+
menu.append(MenuItem.new("Number : #{object.no}; Generation : #{object.generation}").set_sensitive(false))
|
201
|
+
menu.append(MenuItem.new("File offset : #{object.file_offset}").set_sensitive(false))
|
202
|
+
|
203
|
+
getxrefs = MenuItem.new("Search references to this object").set_sensitive(true)
|
204
|
+
getxrefs.signal_connect("activate") do
|
205
|
+
self.parent.show_xrefs(object)
|
206
|
+
end
|
207
|
+
menu.append(getxrefs)
|
208
|
+
|
209
|
+
elsif not object.parent.nil?
|
210
|
+
gotoparent = MenuItem.new("Goto Parent Object").set_sensitive(true)
|
211
|
+
gotoparent.signal_connect("activate") do
|
212
|
+
self.goto(object.parent)
|
213
|
+
end
|
214
|
+
menu.append(gotoparent)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def create_type_menu(menu, type, path)
|
219
|
+
items = @@menus[type]
|
220
|
+
menu.append(SeparatorMenuItem.new) if not items.empty?
|
221
|
+
|
222
|
+
items.each do |item|
|
223
|
+
if item[:Name] == :"---"
|
224
|
+
entry = SeparatorMenuItem.new
|
225
|
+
else
|
226
|
+
if item[:Name].is_a?(String)
|
227
|
+
entry = MenuItem.new(item[:Name])
|
228
|
+
else
|
229
|
+
entry = ImageMenuItem.new(item[:Name])
|
230
|
+
end
|
231
|
+
|
232
|
+
entry.set_sensitive(item[:Sensitive])
|
233
|
+
entry.signal_connect("activate", self, path, &item[:Callback])
|
234
|
+
end
|
235
|
+
|
236
|
+
menu.append(entry)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
def popup_menu_key(object)
|
241
|
+
if object.is_a?(Origami::Object)
|
242
|
+
popup_menu_object_key(object)
|
243
|
+
else
|
244
|
+
popup_menu_struct_key(object)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def popup_menu_object_key(object)
|
249
|
+
if object.is_a?(Origami::Graphics::ImageXObject)
|
250
|
+
'Image'
|
251
|
+
else
|
252
|
+
object.native_type.to_s.split("::").last
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def popup_menu_struct_key(struct)
|
257
|
+
case struct
|
258
|
+
when ::Array
|
259
|
+
'Body'
|
260
|
+
when Origami::XRef, Origami::XRefToCompressedObject
|
261
|
+
'XRef'
|
262
|
+
else
|
263
|
+
struct.class.name.split('::').last
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class Walker < Window
|
269
|
+
|
270
|
+
private
|
271
|
+
|
272
|
+
def create_menus
|
273
|
+
AccelMap.add_entry("<PDF Walker>/File/Open", Gdk::Keyval::GDK_O, Gdk::Window::CONTROL_MASK)
|
274
|
+
AccelMap.add_entry("<PDF Walker>/File/Refresh", Gdk::Keyval::GDK_R, Gdk::Window::CONTROL_MASK)
|
275
|
+
AccelMap.add_entry("<PDF Walker>/File/Close", Gdk::Keyval::GDK_W, Gdk::Window::CONTROL_MASK)
|
276
|
+
AccelMap.add_entry("<PDF Walker>/File/Save", Gdk::Keyval::GDK_S, Gdk::Window::CONTROL_MASK)
|
277
|
+
AccelMap.add_entry("<PDF Walker>/File/Quit", Gdk::Keyval::GDK_Q, Gdk::Window::CONTROL_MASK)
|
278
|
+
AccelMap.add_entry("<PDF Walker>/Document/Search", Gdk::Keyval::GDK_F, Gdk::Window::CONTROL_MASK)
|
279
|
+
|
280
|
+
@menu = MenuBar.new
|
281
|
+
|
282
|
+
create_file_menu
|
283
|
+
create_document_menu
|
284
|
+
create_help_menu
|
285
|
+
end
|
286
|
+
|
287
|
+
def create_file_menu
|
288
|
+
file_ag = Gtk::AccelGroup.new
|
289
|
+
@file_menu = Menu.new.set_accel_group(file_ag).set_accel_path("<PDF Walker>/File")
|
290
|
+
add_accel_group(file_ag)
|
291
|
+
|
292
|
+
entries = [
|
293
|
+
@file_menu_open = ImageMenuItem.new(Stock::OPEN).set_accel_path("<PDF Walker>/File/Open"),
|
294
|
+
@file_menu_recent = MenuItem.new("Last opened"),
|
295
|
+
@file_menu_refresh = ImageMenuItem.new(Stock::REFRESH).set_sensitive(false).set_accel_path("<PDF Walker>/File/Refresh"),
|
296
|
+
@file_menu_close = ImageMenuItem.new(Stock::CLOSE).set_sensitive(false).set_accel_path("<PDF Walker>/File/Close"),
|
297
|
+
@file_menu_saveas = ImageMenuItem.new(Stock::SAVE_AS).set_sensitive(false).set_accel_path("<PDF Walker>/File/Save"),
|
298
|
+
@file_menu_exit = ImageMenuItem.new(Stock::QUIT).set_accel_path("<PDF Walker>/File/Quit"),
|
299
|
+
]
|
300
|
+
|
301
|
+
@file_menu_open.signal_connect('activate') { open }
|
302
|
+
@file_menu_refresh.signal_connect('activate') { open(@filename) }
|
303
|
+
@file_menu_close.signal_connect('activate') { close }
|
304
|
+
@file_menu_saveas.signal_connect('activate') { save }
|
305
|
+
@file_menu_exit.signal_connect('activate') { self.destroy }
|
306
|
+
|
307
|
+
update_recent_menu
|
308
|
+
|
309
|
+
entries.each do |entry|
|
310
|
+
@file_menu.append(entry)
|
311
|
+
end
|
312
|
+
|
313
|
+
@menu.append(MenuItem.new('_File').set_submenu(@file_menu))
|
314
|
+
end
|
315
|
+
|
316
|
+
def create_document_menu
|
317
|
+
doc_ag = Gtk::AccelGroup.new
|
318
|
+
@document_menu = Menu.new.set_accel_group(doc_ag)
|
319
|
+
add_accel_group(doc_ag)
|
320
|
+
|
321
|
+
entries = [
|
322
|
+
@document_menu_search = ImageMenuItem.new(Stock::FIND).set_sensitive(false).set_accel_path("<PDF Walker>/Document/Search"),
|
323
|
+
MenuItem.new,
|
324
|
+
@document_menu_gotocatalog = MenuItem.new("Jump to Catalog").set_sensitive(false),
|
325
|
+
@document_menu_gotodocinfo = MenuItem.new("Jump to Document Info").set_sensitive(false),
|
326
|
+
@document_menu_gotometadata = MenuItem.new("Jump to Metadata").set_sensitive(false),
|
327
|
+
@document_menu_gotorev = MenuItem.new("Jump to Revision...").set_sensitive(false),
|
328
|
+
@document_menu_gotopage = MenuItem.new("Jump to Page...").set_sensitive(false),
|
329
|
+
@document_menu_gotofield = MenuItem.new("Jump to Field...").set_sensitive(false),
|
330
|
+
@document_menu_gotoobj = MenuItem.new("Jump to Object...").set_sensitive(false),
|
331
|
+
MenuItem.new,
|
332
|
+
@document_menu_sign = MenuItem.new("Sign the document").set_sensitive(false),
|
333
|
+
@document_menu_ur = MenuItem.new("Enable Usage Rights").set_sensitive(false),
|
334
|
+
@document_menu_properties = ImageMenuItem.new(Stock::PROPERTIES).set_sensitive(false),
|
335
|
+
]
|
336
|
+
|
337
|
+
@document_menu_search.signal_connect('activate') { search }
|
338
|
+
@document_menu_gotocatalog.signal_connect('activate') { goto_catalog }
|
339
|
+
@document_menu_gotodocinfo.signal_connect('activate') { goto_docinfo }
|
340
|
+
@document_menu_gotometadata.signal_connect('activate') { goto_metadata }
|
341
|
+
@document_menu_gotoobj.signal_connect('activate') { goto_object }
|
342
|
+
@document_menu_properties.signal_connect('activate') { display_file_properties }
|
343
|
+
@document_menu_sign.signal_connect('activate') { display_signing_wizard }
|
344
|
+
@document_menu_ur.signal_connect('activate') { display_usage_rights_wizard }
|
345
|
+
|
346
|
+
entries.each do |entry|
|
347
|
+
@document_menu.append(entry)
|
348
|
+
end
|
349
|
+
|
350
|
+
@menu.append(MenuItem.new('_Document').set_submenu(@document_menu))
|
351
|
+
end
|
352
|
+
|
353
|
+
def create_help_menu
|
354
|
+
@help_menu = Menu.new
|
355
|
+
|
356
|
+
@help_menu_profile = CheckMenuItem.new("Profiling (Debug purposes only)").set_active(@config.profile?)
|
357
|
+
@help_menu_about = ImageMenuItem.new(Stock::ABOUT)
|
358
|
+
|
359
|
+
@help_menu_profile.signal_connect('toggled') do @config.set_profiling(@help_menu_profile.active?) end
|
360
|
+
@help_menu_about.signal_connect('activate') { about }
|
361
|
+
|
362
|
+
@help_menu.append(@help_menu_profile)
|
363
|
+
@help_menu.append(@help_menu_about)
|
364
|
+
|
365
|
+
@menu.append(MenuItem.new('_Help').set_submenu(@help_menu))
|
366
|
+
end
|
367
|
+
|
368
|
+
def update_recent_menu
|
369
|
+
@recent_menu = Menu.new
|
370
|
+
@config.recent_files.each { |file|
|
371
|
+
menu = MenuItem.new(file)
|
372
|
+
menu.signal_connect('activate') do open(file) end
|
373
|
+
|
374
|
+
@recent_menu.append(menu)
|
375
|
+
}
|
376
|
+
|
377
|
+
@file_menu_recent.set_submenu(@recent_menu)
|
378
|
+
@file_menu_recent.show_all
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
end
|