VimMate 0.6.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,395 @@
1
+ =begin
2
+ = VimMate: Vim graphical add-on
3
+ Copyright (c) 2006 Guillaume Benny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ require 'gtk2'
25
+ require 'set'
26
+ require 'thread'
27
+ require 'vimmatelib/config'
28
+ require 'vimmatelib/files'
29
+ require 'vimmatelib/icons'
30
+ require 'vimmatelib/search_window'
31
+
32
+ module VimMate
33
+
34
+ # The window that contains the file tree
35
+ class FilesWindow
36
+
37
+ # Column for the file name
38
+ NAME = 0
39
+ # Column for the full path of the file
40
+ PATH = 1
41
+ # Column for the icon of the file
42
+ ICON = 2
43
+ # Column used to sort the files
44
+ SORT = 3
45
+ # Column used to store the type of row
46
+ TYPE = 4
47
+ # Column used to store the status of the file
48
+ STATUS = 5
49
+ # Type of row: file
50
+ TYPE_FILE = 0
51
+ # Type of row: directory
52
+ TYPE_DIRECTORY = 1
53
+ # Type of row: separator
54
+ TYPE_SEPARATOR = 2
55
+
56
+ # Create a FilesWindow
57
+ def initialize(exclude_file_list = [])
58
+ @open_signal = Set.new
59
+ @menu_signal = Set.new
60
+ @expander_signal = Set.new
61
+
62
+ @filter_string = ""
63
+
64
+ # Tree Store: Filename, Full path, Icon, Sort, Type, Status
65
+ @gtk_tree_store = Gtk::TreeStore.new(String,
66
+ String,
67
+ Gdk::Pixbuf,
68
+ String,
69
+ Fixnum,
70
+ String)
71
+ @gtk_tree_store.set_sort_column_id(SORT)
72
+
73
+ # Filtered Tree Store
74
+ @gtk_filtered_tree_model = Gtk::TreeModelFilter.new(@gtk_tree_store)
75
+ @gtk_filtered_tree_model.set_visible_func do |model, iter|
76
+ if @filter_string.nil? or @filter_string.empty?
77
+ true
78
+ elsif iter[TYPE] == TYPE_DIRECTORY or iter[TYPE] == TYPE_SEPARATOR
79
+ true
80
+ else
81
+ if not iter[NAME] or iter[NAME].index(@filter_string)
82
+ true
83
+ else
84
+ false
85
+ end
86
+ end
87
+ end
88
+
89
+ # Tree View
90
+ @gtk_tree_view = Gtk::TreeView.new(@gtk_filtered_tree_model)
91
+ @gtk_tree_view.selection.mode = Gtk::SELECTION_SINGLE
92
+ @gtk_tree_view.headers_visible = Config[:file_headers_visible]
93
+ @gtk_tree_view.hover_selection = Config[:file_hover_selection]
94
+
95
+ # Double-click, Enter, Space: Signal to open the file
96
+ @gtk_tree_view.signal_connect("row-activated") do |view, path, column|
97
+ path = @gtk_filtered_tree_model.get_iter(path)[PATH]
98
+ @open_signal.each do |signal|
99
+ signal.call(path,
100
+ Config[:files_default_open_in_tabs] ? :tab_open : :open)
101
+ end
102
+ end
103
+
104
+ # Left-click: Select and Signal to open the menu
105
+ @gtk_tree_view.signal_connect("button_press_event") do |widget, event|
106
+ if event.kind_of? Gdk::EventButton and event.button == 3
107
+ path = @gtk_tree_view.get_path_at_pos(event.x, event.y)
108
+ @gtk_tree_view.selection.select_path(path[0]) if path
109
+
110
+ selected = @gtk_tree_view.selection.selected
111
+ if selected
112
+ @menu_signal.each do |signal|
113
+ signal.call(selected[PATH])
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Create a label to show the path of the file
120
+ gtk_label = Gtk::Label.new
121
+ gtk_label.ellipsize = Pango::Layout::EllipsizeMode::START
122
+
123
+ # When a selection is changed in the tree view, we change the label
124
+ # to show the path of the file
125
+ @gtk_tree_view.selection.signal_connect("changed") do
126
+ gtk_label.text = ""
127
+ next if (selected_row = @gtk_tree_view.selection.selected).nil?
128
+ gtk_label.text = File.join(File.dirname(selected_row[PATH]), selected_row[NAME])
129
+ end
130
+
131
+ # Same thing as Left-click, but with the keyboard
132
+ @gtk_tree_view.signal_connect("popup_menu") do
133
+ selected = @gtk_tree_view.selection.selected
134
+ if selected
135
+ @menu_signal.each do |signal|
136
+ signal.call(selected[PATH])
137
+ end
138
+ end
139
+ end
140
+
141
+ # Separator between directories
142
+ @gtk_tree_view.set_row_separator_func do |model, iter|
143
+ iter[TYPE] == TYPE_SEPARATOR
144
+ end
145
+
146
+ # Add the columns
147
+ column = Gtk::TreeViewColumn.new
148
+ column.title = "Files"
149
+
150
+ # Icon
151
+ icon_cell_renderer = Gtk::CellRendererPixbuf.new
152
+ column.pack_start(icon_cell_renderer, false)
153
+ column.set_attributes(icon_cell_renderer, :pixbuf => ICON)
154
+
155
+ # File name
156
+ text_cell_renderer = Gtk::CellRendererText.new
157
+ if Config[:files_use_ellipsis]
158
+ text_cell_renderer.ellipsize = Pango::Layout::EllipsizeMode::MIDDLE
159
+ end
160
+ column.pack_start(text_cell_renderer, true)
161
+ column.set_attributes(text_cell_renderer, :text => NAME)
162
+
163
+ # Status
164
+ text_cell_renderer2 = Gtk::CellRendererText.new
165
+ if Config[:files_use_ellipsis]
166
+ text_cell_renderer2.ellipsize = Pango::Layout::EllipsizeMode::END
167
+ end
168
+ column.pack_start(text_cell_renderer2, true)
169
+ column.set_attributes(text_cell_renderer2, :text => STATUS)
170
+
171
+ @gtk_tree_view.append_column(column)
172
+
173
+ # Put the tree view in a scroll window
174
+ @gtk_scrolled_window = Gtk::ScrolledWindow.new
175
+ @gtk_scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC,
176
+ Gtk::POLICY_AUTOMATIC)
177
+ @gtk_scrolled_window.add(@gtk_tree_view)
178
+
179
+ # Set the default size for the file list
180
+ @gtk_scrolled_window.set_size_request(Config[:files_opened_width], -1)
181
+
182
+ # Create a box to filter the list
183
+ gtk_filter_box = Gtk::HBox.new
184
+ gtk_filter_box.pack_start(gtk_filter_button = Gtk::ToggleButton.new("Filter"), false, false)
185
+ gtk_filter_box.pack_start(gtk_entry = Gtk::Entry.new, true, true)
186
+ changed_lambda = lambda do
187
+ if gtk_filter_button.active?
188
+ self.filter = gtk_entry.text
189
+ else
190
+ self.clear_filter
191
+ end
192
+ end
193
+ gtk_entry.signal_connect("changed", &changed_lambda)
194
+ gtk_filter_button.signal_connect("toggled", &changed_lambda)
195
+ gtk_filter_button.active = Config[:files_filter_active]
196
+ gtk_filter_box.spacing = 10
197
+ gtk_filter_box.border_width = 10
198
+
199
+ # Create the file tree
200
+ initialize_file_tree(exclude_file_list)
201
+
202
+ gtk_top_box = Gtk::VBox.new
203
+ gtk_top_box.pack_start(gtk_filter_box, false, false)
204
+ gtk_top_box.pack_start(@gtk_scrolled_window, true, true)
205
+ gtk_top_box.pack_start(gtk_label, false, false)
206
+
207
+ # Create the search file list if it's enabled
208
+ if Config[:files_use_search]
209
+ @gtk_paned_box = Gtk::VPaned.new
210
+ @gtk_paned_box.add(gtk_top_box)
211
+ @gtk_paned_box.add((search_window = SearchWindow.new(@file_tree)).gtk_window)
212
+ @gtk_paned_box.position = Config[:files_search_separator_position]
213
+
214
+ # Set the signals for the search window
215
+ search_window.add_open_signal do |path, kind|
216
+ @open_signal.each do |signal|
217
+ signal.call(path, kind)
218
+ end
219
+ end
220
+ search_window.add_menu_signal do |path|
221
+ @menu_signal.each do |signal|
222
+ signal.call(path)
223
+ end
224
+ end
225
+ end
226
+
227
+
228
+ @gtk_expander = Gtk::Expander.new("File list")
229
+ @gtk_expander.expanded = Config[:files_expanded]
230
+ if Config[:files_use_search]
231
+ @gtk_expander.add(@gtk_paned_box)
232
+ else
233
+ @gtk_expander.add(gtk_top_box)
234
+ end
235
+ @gtk_expander.signal_connect("notify::expanded") do
236
+ @expander_signal.each do |signal|
237
+ signal.call(@gtk_expander.expanded?)
238
+ end
239
+ end
240
+
241
+ gtk_window.border_width = 5
242
+
243
+ # Launch a timer to refresh the file list
244
+ @file_tree_mutex = Mutex.new
245
+ Gtk.timeout_add(Config[:files_refresh_interval] * 1000) do
246
+ do_refresh
247
+ true
248
+ end
249
+ end
250
+
251
+ # Recursively add a path at the root of the tree
252
+ def add_path(path)
253
+ @file_tree_mutex.synchronize do
254
+ @file_tree.add_path(path)
255
+ end
256
+ self
257
+ end
258
+
259
+ # The "window" for this object
260
+ def gtk_window
261
+ @gtk_expander
262
+ end
263
+
264
+ # Refresh the file list
265
+ def refresh
266
+ do_refresh
267
+ self
268
+ end
269
+
270
+ # Get the filter: files must contain this string
271
+ def filter
272
+ @filter_string
273
+ end
274
+
275
+ # Set a filter: files must contain this string
276
+ def filter=(filter)
277
+ @filter_string = filter
278
+ @gtk_filtered_tree_model.refilter
279
+ end
280
+
281
+ # Clear the filter
282
+ def clear_filter
283
+ @filter_string = ""
284
+ @gtk_filtered_tree_model.refilter
285
+ filter
286
+ end
287
+
288
+ # Expand the first row of the file tree
289
+ def expand_first_row
290
+ @gtk_tree_view.collapse_all
291
+ @gtk_tree_view.expand_row(Gtk::TreePath.new("0"), false)
292
+ end
293
+
294
+ # Add a block that will be called when the user choose to open a file
295
+ # The block take two argument: the path to the file to open, and a
296
+ # symbol to indicate the kind: :open, :split_open, :tab_open
297
+ def add_open_signal(&block)
298
+ @open_signal << block
299
+ end
300
+
301
+ # Add a block that will be called when the user choose to open the
302
+ # menu. The block takes one argument: the path to the file to open.
303
+ def add_menu_signal(&block)
304
+ @menu_signal << block
305
+ end
306
+
307
+ # Add a block that will be called when the user choose to expand or
308
+ # close the expander. The block takes one argument: if the expander
309
+ # is opened or closed
310
+ def add_expander_signal(&block)
311
+ @expander_signal << block
312
+ end
313
+
314
+ private
315
+
316
+ # Lunch the refresh of the tree
317
+ def do_refresh
318
+ @file_tree_mutex.synchronize do
319
+ @file_tree.refresh
320
+ end
321
+ end
322
+
323
+ # Create the file tree
324
+ def initialize_file_tree(exclude_file_list)
325
+ @file_tree = ListedTree.new(exclude_file_list)
326
+
327
+ # Register to receive a signal when a file is added or removed
328
+ @file_tree.add_refresh_signal do |method, file|
329
+ case method
330
+ when :add
331
+ # A file is added. Find it's parent and add it there
332
+ if file.parent
333
+ @gtk_tree_store.each do |model,path,iter|
334
+ if iter[PATH] == file.parent.path
335
+ add_to_tree(file, iter)
336
+ break
337
+ end
338
+ end
339
+ else
340
+ add_to_tree(file)
341
+ end
342
+ when :remove
343
+ # A file is removed. Find it and remove it
344
+ to_remove = []
345
+ @gtk_tree_store.each do |model,path,iter|
346
+ if iter[PATH] == file.path
347
+ to_remove << Gtk::TreeRowReference.new(model, path)
348
+ if iter.next! and iter[TYPE] == TYPE_SEPARATOR
349
+ to_remove << Gtk::TreeRowReference.new(model, path.next!)
350
+ end
351
+ break
352
+ end
353
+ end
354
+ to_remove.each do |element|
355
+ @gtk_tree_store.remove(@gtk_tree_store.get_iter(element.path))
356
+ end
357
+ when :refresh
358
+ # Called when the status of the file has changed
359
+ @gtk_tree_store.each do |model,path,iter|
360
+ if iter[PATH] == file.path
361
+ iter[ICON] = file.icon
362
+ iter[STATUS] = file.status_text
363
+ break
364
+ end
365
+ end
366
+ end
367
+ @gtk_filtered_tree_model.refilter
368
+ end
369
+ end
370
+
371
+ # Add a file to the tree
372
+ def add_to_tree(file, parent = nil)
373
+ # If we need a separator and it's a directory, we add it
374
+ if Config[:file_directory_separator] and file.instance_of? ListedDirectory
375
+ new_row = @gtk_tree_store.append(parent)
376
+ new_row[TYPE] = TYPE_SEPARATOR
377
+ new_row[SORT] = "1-#{file.path}-2"
378
+ end
379
+ # Add the row for the file
380
+ new_row = @gtk_tree_store.append(parent)
381
+ new_row[NAME] = file.name
382
+ new_row[PATH] = file.path
383
+ new_row[ICON] = file.icon
384
+ new_row[STATUS] = file.status_text
385
+ if file.instance_of? ListedDirectory
386
+ new_row[SORT] = "1-#{file.path}-1"
387
+ new_row[TYPE] = TYPE_DIRECTORY
388
+ else
389
+ new_row[SORT] = "2-#{file.path}-1"
390
+ new_row[TYPE] = TYPE_FILE
391
+ end
392
+ end
393
+ end
394
+ end
395
+
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,104 @@
1
+ =begin
2
+ = VimMate: Vim graphical add-on
3
+ Copyright (c) 2006 Guillaume Benny
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
6
+ this software and associated documentation files (the "Software"), to deal in
7
+ the Software without restriction, including without limitation the rights to
8
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9
+ of the Software, and to permit persons to whom the Software is furnished to do
10
+ so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+ =end
23
+
24
+ require 'vimmatelib/nice_singleton'
25
+
26
+ module VimMate
27
+
28
+ # Manages the icons that can be loaded from the disk
29
+ class Icons
30
+ include NiceSingleton
31
+
32
+ # The filenames for the icons of the windows
33
+ WINDOW_ICON_FILENAME = 'vimmate%d.png'.freeze
34
+ # The size for the icons of the windows
35
+ WINDOW_ICON_SIZES = [16, 32, 48].freeze
36
+
37
+ # Name of the icons to load. Will create methods named after
38
+ # the icon's name, with _icon: folder_icon for example.
39
+ ICONS_NAME = [:folder, :file].collect do |f|
40
+ ["", :green, :orange, :red].collect do |c|
41
+ if c.to_s.empty?
42
+ f.to_sym
43
+ else
44
+ "#{f}_#{c}".to_sym
45
+ end
46
+ end
47
+ end.flatten.freeze
48
+
49
+ # Create the Icons class. Cannot be called directly
50
+ def initialize
51
+ @gtk_window_icons = []
52
+ end
53
+
54
+ # Get an array of icons for the windows
55
+ def window_icons
56
+ # Load them
57
+ load_window_icons
58
+ @gtk_window_icons.freeze
59
+ # Once loaded, we only need a reader
60
+ self.class.send(:define_method, :window_icons) do
61
+ @gtk_window_icons
62
+ end
63
+ # Return the value
64
+ window_icons
65
+ end
66
+
67
+ # Define a method with _icon for each icon's name
68
+ ICONS_NAME.each do |method|
69
+ define_method("#{method}_icon") do
70
+ # Load the file
71
+ icon = nil
72
+ file = File.join(Config.lib_path, "#{method}.png")
73
+ begin
74
+ icon = Gdk::Pixbuf.new(file) if File.exist? file
75
+ rescue StandardError => e
76
+ $stderr.puts e.to_s
77
+ $stderr.puts "Problem loading #{method} icon #{file}"
78
+ end
79
+ icon.freeze
80
+ # Once loaded, we only need a reader
81
+ self.class.send(:define_method, "#{method}_icon") do
82
+ icon
83
+ end
84
+ icon
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ # Load the icons for the windows
91
+ def load_window_icons
92
+ WINDOW_ICON_SIZES.each do |size|
93
+ file = File.join(Config.lib_path, WINDOW_ICON_FILENAME % [size])
94
+ begin
95
+ @gtk_window_icons << Gdk::Pixbuf.new(file) if File.exist? file
96
+ rescue StandardError => e
97
+ $stderr.puts e.to_s
98
+ $stderr.puts "Problem loading window icon #{file}"
99
+ end
100
+ end
101
+ end
102
+
103
+ end
104
+ end