-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.rb
More file actions
356 lines (287 loc) · 12.4 KB
/
main.rb
File metadata and controls
356 lines (287 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# frozen_string_literal: true
require "gtk3"
require_relative "explorer"
Gtk.init
PACKAGE_NAME = "fr.gabhas.explorer"
APP_NAME = "File Explorer"
APP_MIN_WIDTH = 120
APP_MIN_HEIGHT = 80
APP_DEFAULT_WIDTH = 720
APP_DEFAULT_HEIGHT = 480
APP_START_DIR = Dir.pwd
ICON_DIR = "#{APP_START_DIR}/assets/icons"
ICON_BASE_NAME = "folder-icon"
ICON_SIZES = [16, 32, 64, 128]
icons = []
ICON_SIZES.each { |size| icons << GdkPixbuf::Pixbuf.new(file: "#{ICON_DIR}/folder-icon-#{size}.png") }
explorer = Explorer.new(path: APP_START_DIR)
$window = Gtk::Window.new(:toplevel)
$window.title = APP_NAME
# Application size
$window.set_size_request(APP_MIN_WIDTH, APP_MIN_HEIGHT)
$window.set_default_size(APP_DEFAULT_WIDTH, APP_DEFAULT_HEIGHT)
$window.set_icon_list(icons)
$window.signal_connect("destroy") { Gtk.main_quit }
$css_provider = Gtk::CssProvider.new
$css_provider.load(path: "./assets/style/default-light.css")
$clipboard = Gtk::Clipboard.get(Gdk::Atom.intern("CLIPBOARD", false))
$hand_cursor = Gdk::Cursor.new(:HAND1)
$normal_cursor = Gdk::Cursor.new(:ARROW)
def create_right_click_menu(parent_label, parent_entry, current_path)
parent_label.style_context.add_class("clicked")
menu = Gtk::Menu.new
copy_filename = Gtk::MenuItem.new(label: "📋 Copy file name")
copy_filepath = Gtk::MenuItem.new(label: "📋 Copy file path")
close = Gtk::MenuItem.new(label: "🎯 Close")
copy_filename.signal_connect "activate" do
$clipboard.set_text(parent_entry[:filename])
end
copy_filepath.signal_connect "activate" do
$clipboard.set_text("#{current_path}/#{parent_entry[:filename]}")
end
menu.append(copy_filename)
menu.append(copy_filepath)
menu.append(Gtk::SeparatorMenuItem.new)
menu.append(close)
menu.signal_connect "hide" do
parent_label.style_context.remove_class("clicked")
end
menu
end
def update_app(ex, container, search_bar)
# This function updates the displayed content of the explorer (the list of files/ folders)
# it also updates the search bar content and window title
# Remove the file list
container.children.each { | child | container.remove(child) }
ex.listdir # Does the indexing of the directory
ex.sort # Apply the configured sorting
# The scrollable window that will contain the list of files
scroll_view = Gtk::ScrolledWindow.new
scroll_view.set_size_request(APP_DEFAULT_WIDTH, APP_DEFAULT_HEIGHT)
scroll_view.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC)
file_box = Gtk::Box.new(:vertical)
# Generate the header of the table
grid = Gtk::Grid.new
grid.set_column_homogeneous(true)
possible_sort = %w[Filename Size Created]
column_labels = []
possible_sort.each_with_index do |name, index|
label_event_box = Gtk::EventBox.new
label_event_box.add_events([Gdk::EventMask::BUTTON_PRESS_MASK])
label = Gtk::Label.new(name)
# Add an arrow corresponding to the current sorting
if ex.configuration[:sort] == name
if ex.configuration[:reverse_sort]
label.set_text("#{label.text} ⬇️")
else
label.set_text("#{label.text} ⬆️")
end
end
label.style_context.add_provider($css_provider, Gtk::StyleProvider::PRIORITY_USER)
label.style_context.add_class("title")
label.add_events([Gdk::EventMask::BUTTON_PRESS_MASK])
column_labels << label
label_event_box.signal_connect "button-press-event" do |_, event|
# The clicked column is already the short criteria -> reverse
if ex.configuration[:sort] == name
ex.configuration[:reverse_sort] = !(ex.configuration[:reverse_sort])
else
ex.configuration[:sort] = name
ex.configuration[:reverse_sort] = false
end
# Reset the style of every column header label
column_labels.each { |lab|
lab.text.gsub!("⬆️", "")
lab.text.gsub!("⬇️", "")
}
update_app(ex, container, search_bar)
end
label_event_box.add(label)
grid.attach(label_event_box, index, 0, 1, 1)
end
container.add(grid)
# Listing the files/ directories
max_allowed_len = 20
ex.current_entries.each do |entry|
# Crop the longest names
if entry[:filename].length > max_allowed_len
entry[:filename] = "#{entry[:filename][0..(max_allowed_len - 3)]}..."
end
name_field = Gtk::EventBox.new # An EventBox is required to handle click event
name_field.add_events([Gdk::EventMask::BUTTON_PRESS_MASK])
name_label = Gtk::Label.new("\t#{entry[:filename]}").set_xalign(0.0)
name_label.style_context.add_provider($css_provider, Gtk::StyleProvider::PRIORITY_USER)
# Setting the correct icon and CSS class depending on the file type
case entry[:type]
when "file"
name_label.set_text("\t📄 #{entry[:filename]}")
name_label.style_context.add_class("file")
when "directory"
name_label.set_text("\t📁 #{entry[:filename]}")
name_label.style_context.add_class("directory")
when "link"
name_label.set_text("\t🔗 #{entry[:filename]}")
name_label.style_context.add_class("link")
else
name_label.set_text("\t<UNK> #{entry[:filename]}")
end
# The action to perform on click
name_field.signal_connect "button-press-event" do |_, event|
# If the clicked element is a folder and is double-clicked, explore it
if name_label.style_context.has_class?("directory") && event.event_type == Gdk::EventType::DOUBLE_BUTTON_PRESS
if entry[:filename] == "." # Special case: Stay in the current directory
elsif entry[:filename] == ".." #Special case: Go to the previous directory in the path
upper_dir = ex.current_path.split("/")
if upper_dir.length > 1
upper_dir = upper_dir[0..(upper_dir.length - 2)].join("/")
ex.chdir(next_path: upper_dir)
end
else # Move to the clicked directory normally
ex.chdir(next_path: "#{ex.current_path}/#{entry[:filename]}")
end
update_app(ex, container, search_bar)
elsif event.button == 3 # Event is a right-click, open the context menu
popover_menu = create_right_click_menu(name_label, entry, ex.current_path)
popover_menu.show_all
popover_menu.popup_at_pointer(event)
end
end
name_field.signal_connect "enter-notify-event" do |widget, _|
name_label.style_context.add_class("hovered-label")
if name_label.style_context.has_class?("directory")
widget.window.cursor = $hand_cursor
end
end
name_field.signal_connect "leave-notify-event" do |widget, _|
name_label.style_context.remove_class("hovered-label")
widget.window.cursor = $normal_cursor
end
name_field.add(name_label)
grid = Gtk::Grid.new
grid.set_column_homogeneous(true)
grid.attach(name_field, 0, 0, 1, 1)
grid.attach(Gtk::Label.new("\t#{entry[:size]}").set_xalign(0.0), 1, 0, 1, 1)
grid.attach(Gtk::Label.new("\t#{entry[:date]}"), 2, 0, 1, 1)
file_box.add(grid)
end
scroll_view.add(file_box)
container.pack_start(scroll_view, expand: true, fill: true, padding: 0)
container.show_all
$window.title = "#{APP_NAME} - #{ex.current_path}"
search_bar.text = ex.current_path
end
app_box = Gtk::Box.new(:vertical)
main_box = Gtk::Box.new(:vertical, 5) # File/ directory list container
# The search bar
search_bar = Gtk::Box.new(:horizontal, 0)
current_path_entry = Gtk::Entry.new.set_text(explorer.current_path)
current_path_entry.signal_connect "key-press-event" do |_, event|
if event.keyval == Gdk::Keyval::KEY_Return
explorer.chdir(next_path: current_path_entry.text) # Set the new path
update_app(explorer, main_box, current_path_entry) # Update the displayed elements to match the current directory
end
end
previous_btn = Gtk::Button.new(label: "⬅️")
copy_btn = Gtk::Button.new(label: "📋")
# Apply the style to the previous buttons
[previous_btn, copy_btn].each do |btn|
btn.style_context.add_provider($css_provider, Gtk::StyleProvider::PRIORITY_USER)
end
previous_btn.signal_connect "button-press-event" do
upper_dir = explorer.current_path.split("/")
if upper_dir.length > 1
upper_dir = upper_dir[0..(upper_dir.length - 2)].join("/")
explorer.chdir(next_path: upper_dir)
update_app(explorer, main_box, current_path_entry)
end
end
copy_btn.signal_connect "button-press-event" do
$clipboard.set_text(explorer.current_path)
end
search_bar.pack_start(current_path_entry, expand: true, fill: true, padding: 0)
search_bar.pack_start(previous_btn, expand: false, fill: false, padding: 0)
search_bar.pack_start(copy_btn, expand: false, fill: false, padding: 0)
search_bar.show_all
app_box.pack_start(search_bar, expand: false, fill: false, padding: 0) # Packing the search bar
app_box.pack_start(main_box, expand: true, fill: true, padding: 0) # Packing the file list container
# The bottom menu bar
menubar = Gtk::MenuBar.new
menubar.style_context.add_provider($css_provider, Gtk::StyleProvider::PRIORITY_USER)
app_box.pack_start(menubar, expand: false, fill: false, padding: 0)
menubar_item_settings = Gtk::MenuItem.new(label: "Settings")
# The settings menu and submenu
settings_submenu = Gtk::Menu.new
toggle_hidden_files = Gtk::CheckMenuItem.new(label: "Show hidden files")
toggle_hidden_files.signal_connect "activate" do
explorer.configuration[:show_hidden] = !explorer.configuration[:show_hidden] # Invert the current setting
update_app(explorer, main_box, current_path_entry)
end
toggle_history_view = Gtk::CheckMenuItem.new(label: "History")
toggle_history_view.set_active(true) # This setting is enabled by default
toggle_history_view.signal_connect "activate" do
explorer.configuration[:keep_history] = !explorer.configuration[:keep_history]
end
toggle_filesize_formating = Gtk::CheckMenuItem.new(label: "Format filesize")
toggle_filesize_formating.set_active(true) # This setting is enabled by default
toggle_filesize_formating.signal_connect "activate" do
explorer.configuration[:format_filesize] = !explorer.configuration[:format_filesize]
update_app(explorer, main_box, current_path_entry)
end
toggle_size_estimation = Gtk::CheckMenuItem.new(label: "Compute folder size")
toggle_size_estimation.set_active(false) # This setting is disabled by default
toggle_size_estimation.signal_connect "activate" do
explorer.configuration[:estimate_folder_size] = !explorer.configuration[:estimate_folder_size]
update_app(explorer, main_box, current_path_entry)
end
settings_submenu.append(toggle_hidden_files)
settings_submenu.append(toggle_history_view)
settings_submenu.append(toggle_filesize_formating)
settings_submenu.append(toggle_size_estimation)
menubar_item_settings.set_submenu(settings_submenu)
# End of the settings menu and submenu
menubar_item_themes = Gtk::MenuItem.new(label: "Themes")
# The themes menu and submenu
theme_submenu = Gtk::Menu.new
Dir.entries("#{APP_START_DIR}/assets/style").each do |filepath|
if %w[. ..].include?(filepath) || File.directory?(filepath) || File.extname(filepath) != ".css"
next
end
theme_btn = Gtk::CheckMenuItem.new(label: "#{filepath.gsub(".css", "")}")
theme_btn.signal_connect "activate" do
# Only load the theme if it's not the current active one
if explorer.current_theme != filepath
$css_provider.load(path: "./assets/style/#{filepath}")
explorer.current_theme = filepath
theme_submenu.children.each { |child| child.set_active(false) if child.is_a?(Gtk::CheckMenuItem) && child != theme_btn }
end
end
theme_submenu.append(theme_btn)
end
menubar_item_themes.set_submenu(theme_submenu)
# End of the themes menu and submenu
# Previous/ Next visited path menu
menubar_item_history_p = Gtk::MenuItem.new(label: "Previous")
menubar_item_history_n = Gtk::MenuItem.new(label: ">")
menubar_item_history_p.signal_connect "activate" do
if explorer.history.length > 0
next_p = explorer.history[-1]
explorer.chdir(next_path: next_p, ignore_history: true)
explorer.history = explorer.history[0..-2]
update_app(explorer, main_box, current_path_entry)
end
end
menubar_item_history_n.signal_connect "activate" do
unless explorer.history[explorer.history_pos+1].nil?
explorer.chdir(next_path: explorer.history[explorer.history_pos+1], ignore_history: true)
update_app(explorer, main_box, current_path_entry)
end
end
# End of the Previous/ Next visited path menu
menubar.append(menubar_item_settings)
menubar.append(menubar_item_themes)
menubar.append(menubar_item_history_p)
menubar.append(menubar_item_history_n)
update_app(explorer, main_box, current_path_entry) # The initial displaying of files
$window.add(app_box)
$window.show_all
Gtk.main