Greatly improved file handling flow and added popup guards.
This commit is contained in:
parent
cbf5706845
commit
bed8af256c
@ -13,7 +13,6 @@ sudo apt-get install python3 wget ffmpegthumbnailer steamcmd
|
||||
|
||||
# TODO
|
||||
<ul>
|
||||
<li><b>Fix the wonky file handler situation and add prompt guards for actions.</b></li>
|
||||
<li>Add path bar search dropdown.</li>
|
||||
<li>Add "clear trash", "restore from trash" options.</li>
|
||||
<li>Add drive size free and consumed info to bottom bar.</li>
|
||||
|
@ -44,7 +44,7 @@ class Launcher:
|
||||
os.system(command)
|
||||
else:
|
||||
DEVNULL = open(os.devnull, 'w')
|
||||
subprocess.Popen(command, cwd=start_dir, shell=False, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
|
||||
subprocess.Popen(command, cwd=start_dir, shell=True, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True)
|
||||
|
||||
|
||||
def remux_video(self, hash, file):
|
||||
|
@ -127,7 +127,6 @@ class Controller(Controller_Data, ShowHideMixin, KeyboardSignalsMixin, \
|
||||
if action == "execute_in_terminal":
|
||||
self.execute_files(in_terminal=True)
|
||||
if action == "rename":
|
||||
self.to_rename_files = self.selected_files
|
||||
self.rename_files()
|
||||
if action == "cut":
|
||||
self.to_copy_files.clear()
|
||||
|
@ -31,10 +31,11 @@ class Controller_Data:
|
||||
self.arc_command_buffer = self.builder.get_object("arc_command_buffer")
|
||||
|
||||
self.warning_alert = self.builder.get_object("warning_alert")
|
||||
self.exists_alert = self.builder.get_object("exists_alert")
|
||||
self.exists_from_label = self.builder.get_object("exists_from_label")
|
||||
self.exists_to_label = self.builder.get_object("exists_to_label")
|
||||
self.edit_file_menu = self.builder.get_object("edit_file_menu")
|
||||
self.file_exists_dialog = self.builder.get_object("file_exists_dialog")
|
||||
self.exists_file_label = self.builder.get_object("exists_file_label")
|
||||
self.exists_file_field = self.builder.get_object("exists_file_field")
|
||||
self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn")
|
||||
|
||||
self.bottom_size_label = self.builder.get_object("bottom_size_label")
|
||||
self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label")
|
||||
@ -72,7 +73,6 @@ class Controller_Data:
|
||||
|
||||
self.notebooks = [self.window1, self.window2, self.window3, self.window4]
|
||||
self.selected_files = []
|
||||
self.to_rename_files = []
|
||||
self.to_copy_files = []
|
||||
self.to_cut_files = []
|
||||
|
||||
|
@ -73,7 +73,7 @@ class KeyboardSignalsMixin:
|
||||
if keyname == "delete":
|
||||
self.delete_files()
|
||||
if keyname == "f2":
|
||||
self.do_edit_files()
|
||||
self.rename_files()
|
||||
if keyname == "f4":
|
||||
wid, tid = self.window_controller.get_active_data()
|
||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||
|
@ -15,29 +15,32 @@ class ShowHideMixin:
|
||||
|
||||
|
||||
def show_exists_page(self, widget=None, eve=None):
|
||||
# self.exists_alert = self.builder.get_object("exists_alert")
|
||||
# self.exists_from_label = self.builder.get_object("exists_from_label")
|
||||
# self.exists_to_label = self.builder.get_object("exists_to_label")
|
||||
# self.exists_file_field = self.builder.get_object("exists_file_field")
|
||||
response = self.file_exists_dialog.run()
|
||||
self.file_exists_dialog.hide()
|
||||
|
||||
if response == Gtk.ResponseType.OK:
|
||||
return "rename"
|
||||
if response == Gtk.ResponseType.ACCEPT:
|
||||
return "rename_auto"
|
||||
if response == Gtk.ResponseType.CLOSE:
|
||||
return "rename_auto_all"
|
||||
if response == Gtk.ResponseType.YES:
|
||||
return "overwrite"
|
||||
if response == Gtk.ResponseType.APPLY:
|
||||
return "overwrite_all"
|
||||
if response == Gtk.ResponseType.NO:
|
||||
return "skip"
|
||||
if response == Gtk.ResponseType.REJECT:
|
||||
return "skip_all"
|
||||
|
||||
response = self.exists_alert.run()
|
||||
if response == Gtk.ResponseType.OK: # Rename
|
||||
print(response)
|
||||
return "rename", Gio.FileCreateFlags.NONE
|
||||
if response == Gtk.ResponseType.ACCEPT: # Auto rename
|
||||
return "rename_auto", Gio.FileCreateFlags.NONE
|
||||
if response == Gtk.ResponseType.CLOSE: # Auto rename all
|
||||
return "rename_auto_all", Gio.FileCreateFlags.NONE
|
||||
if response == Gtk.ResponseType.YES: # Overwrite
|
||||
return "overwrite", Gio.FileCreateFlags.OVERWRITE
|
||||
if response == Gtk.ResponseType.APPLY: # Overwrite all
|
||||
return "overwrite_all", Gio.FileCreateFlags.OVERWRITE
|
||||
if response == Gtk.ResponseType.NO: # Skip
|
||||
return "skip", Gio.FileCreateFlags.NONE
|
||||
if response == Gtk.ResponseType.CANCEL: # Skip all
|
||||
return "skip_all", Gio.FileCreateFlags.NONE
|
||||
def hide_exists_page_rename(self, widget=None, eve=None):
|
||||
self.file_exists_dialog.response(Gtk.ResponseType.OK)
|
||||
|
||||
def hide_exists_page_auto_rename(self, widget=None, eve=None):
|
||||
self.file_exists_dialog.response(Gtk.ResponseType.ACCEPT)
|
||||
|
||||
def hide_exists_page_auto_rename_all(self, widget=None, eve=None):
|
||||
self.file_exists_dialog.response(Gtk.ResponseType.CLOSE)
|
||||
|
||||
|
||||
def show_about_page(self, widget=None, eve=None):
|
||||
@ -49,6 +52,7 @@ class ShowHideMixin:
|
||||
def hide_about_page(self, widget=None, eve=None):
|
||||
self.builder.get_object("about_page").hide()
|
||||
|
||||
|
||||
def show_archiver_dialogue(self, widget=None, eve=None):
|
||||
wid, tid = self.window_controller.get_active_data()
|
||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||
@ -68,6 +72,7 @@ class ShowHideMixin:
|
||||
def hide_archiver_dialogue(self, widget=None, eve=None):
|
||||
self.builder.get_object("archiver_dialogue").hide()
|
||||
|
||||
|
||||
def show_appchooser_menu(self, widget=None, eve=None):
|
||||
appchooser_menu = self.builder.get_object("appchooser_menu")
|
||||
appchooser_widget = self.builder.get_object("appchooser_widget")
|
||||
@ -86,36 +91,38 @@ class ShowHideMixin:
|
||||
dialog = widget.get_parent().get_parent()
|
||||
dialog.response(Gtk.ResponseType.OK)
|
||||
|
||||
|
||||
def show_context_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("context_menu").run()
|
||||
|
||||
def hide_context_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("context_menu").hide()
|
||||
|
||||
|
||||
def show_new_file_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("new_file_menu").run()
|
||||
|
||||
def hide_new_file_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("new_file_menu").hide()
|
||||
|
||||
|
||||
def show_edit_file_menu(self, widget=None, eve=None):
|
||||
self.builder.get_object("edit_file_menu").run()
|
||||
response = self.edit_file_menu.run()
|
||||
if response == Gtk.ResponseType.CLOSE:
|
||||
self.skip_edit = True
|
||||
if response == Gtk.ResponseType.CANCEL:
|
||||
self.cancel_edit = True
|
||||
|
||||
def hide_edit_file_menu(self, widget=None, eve=None):
|
||||
if widget:
|
||||
name = widget.get_name()
|
||||
if name == "rename":
|
||||
self.builder.get_object("edit_file_menu").hide()
|
||||
else:
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
if "return" in keyname or "enter" in keyname:
|
||||
self.builder.get_object("edit_file_menu").hide()
|
||||
self.builder.get_object("edit_file_menu").hide()
|
||||
|
||||
def hide_edit_file_menu_enter_key(self, widget=None, eve=None):
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
if "return" in keyname or "enter" in keyname:
|
||||
self.builder.get_object("edit_file_menu").hide()
|
||||
|
||||
def hide_edit_file_menu_skip(self, widget=None, eve=None):
|
||||
self.skip_edit = True
|
||||
self.builder.get_object("edit_file_menu").hide()
|
||||
self.edit_file_menu.response(Gtk.ResponseType.CLOSE)
|
||||
|
||||
def hide_edit_file_menu_cancel(self, widget=None, eve=None):
|
||||
self.cancel_edit = True
|
||||
self.builder.get_object("edit_file_menu").hide()
|
||||
self.edit_file_menu.response(Gtk.ResponseType.CANCEL)
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Python imports
|
||||
import os
|
||||
import threading, os
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
@ -10,6 +10,12 @@ from gi.repository import Gtk, GObject, Gio
|
||||
|
||||
|
||||
|
||||
def threaded(fn):
|
||||
def wrapper(*args, **kwargs):
|
||||
threading.Thread(target=fn, args=args, kwargs=kwargs).start()
|
||||
return wrapper
|
||||
|
||||
|
||||
class WidgetFileActionMixin:
|
||||
def set_file_watcher(self, view):
|
||||
if view.get_dir_watcher():
|
||||
@ -69,13 +75,18 @@ class WidgetFileActionMixin:
|
||||
for file in uris:
|
||||
view.open_file_locally(file)
|
||||
|
||||
@threaded
|
||||
def open_with_files(self, appchooser_widget):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
file = Gio.File.new_for_uri(uris[0])
|
||||
app_info = appchooser_widget.get_app_info()
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
||||
files = []
|
||||
|
||||
app_info.launch([file], None)
|
||||
for uri in uris:
|
||||
gio_file = Gio.File.new_for_path(uri)
|
||||
files.append(gio_file)
|
||||
|
||||
app_info.launch(files, None)
|
||||
|
||||
def execute_files(self, in_terminal=False):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
@ -85,64 +96,58 @@ class WidgetFileActionMixin:
|
||||
|
||||
for path in paths:
|
||||
command = f"sh -c '{path}'" if not in_terminal else f"{view.terminal_app} -e '{path}'"
|
||||
view.execute(command.split(), current_dir)
|
||||
view.execute(command, start_dir=view.get_current_directory(), use_os_system=False)
|
||||
|
||||
def archive_files(self, archiver_dialogue):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
paths = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
||||
|
||||
save_target = archiver_dialogue.get_filename();
|
||||
sItr, eItr = self.arc_command_buffer.get_bounds()
|
||||
pre_command = self.arc_command_buffer.get_text(sItr, eItr, False)
|
||||
pre_command = pre_command.replace("%o", save_target)
|
||||
pre_command = pre_command.replace("%N", ' '.join(paths))
|
||||
command = f"{view.terminal_app} -e '{command}'"
|
||||
|
||||
|
||||
|
||||
##################################################################################################
|
||||
|
||||
# NOTE: Everything below is trash and needs yet another rewrite because it doesn't work properly.
|
||||
|
||||
##################################################################################################
|
||||
view.execute(command, start_dir=None, use_os_system=True)
|
||||
|
||||
def rename_files(self):
|
||||
rename_label = self.builder.get_object("file_to_rename_label")
|
||||
rename_input = self.builder.get_object("new_rename_fname")
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
uris = self.format_to_uris(store, wid, tid, self.to_rename_files)
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
|
||||
# The rename button hides the rename dialog box which lets the loop continue.
|
||||
# Weirdly, the show at the end is needed to flow through all the list properly
|
||||
# than auto chosing the first rename entry you do.
|
||||
for uri in uris:
|
||||
entry = uri.split("/")[-1]
|
||||
rename_label.set_label(entry)
|
||||
rename_input.set_text(entry)
|
||||
if self.skip_edit:
|
||||
self.skip_edit = False
|
||||
self.show_edit_file_menu()
|
||||
|
||||
# Yes...this step is required even with the above... =/
|
||||
self.show_edit_file_menu()
|
||||
|
||||
if self.skip_edit:
|
||||
self.skip_edit = False
|
||||
continue
|
||||
if self.cancel_edit:
|
||||
self.cancel_edit = False
|
||||
break
|
||||
|
||||
rname_to = rename_input.get_text().strip()
|
||||
target = f"file://{view.get_current_directory()}/{rname_to}"
|
||||
self.handle_file([uri], "edit", target)
|
||||
self.handle_file([uri], "rename", target)
|
||||
|
||||
self.show_edit_file_menu()
|
||||
|
||||
self.skip_edit = False
|
||||
self.cancel_edit = False
|
||||
self.hide_new_file_menu()
|
||||
self.to_rename_files.clear()
|
||||
self.hide_edit_file_menu()
|
||||
self.selected_files.clear()
|
||||
|
||||
def cut_files(self):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
self.to_cut_files = uris
|
||||
|
||||
def copy_files(self):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
print(self.selected_files)
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
print(uris)
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
self.to_copy_files = uris
|
||||
|
||||
def paste_files(self):
|
||||
@ -155,28 +160,35 @@ class WidgetFileActionMixin:
|
||||
elif len(self.to_cut_files) > 0:
|
||||
self.handle_file(self.to_cut_files, "move", target)
|
||||
|
||||
def archive_files(self, archiver_dialogue):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
paths = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
|
||||
save_target = archiver_dialogue.get_filename();
|
||||
start_itr, end_itr = self.arc_command_buffer.get_bounds()
|
||||
command = self.arc_command_buffer.get_text(start_itr, end_itr, False)
|
||||
|
||||
command = command.replace("%o", save_target)
|
||||
command = command.replace("%N", ' '.join(paths))
|
||||
final_command = f"terminator -e '{command}'"
|
||||
self.execute(final_command, start_dir=None, use_os_system=True)
|
||||
|
||||
def delete_files(self):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
self.handle_file(uris, "delete")
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
response = None
|
||||
|
||||
self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?")
|
||||
for uri in uris:
|
||||
file = Gio.File.new_for_uri(uri)
|
||||
|
||||
if not response:
|
||||
response = self.warning_alert.run()
|
||||
self.warning_alert.hide()
|
||||
if response == Gtk.ResponseType.YES:
|
||||
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
|
||||
if type == Gio.FileType.DIRECTORY:
|
||||
view.delete_file( file.get_path() )
|
||||
else:
|
||||
file.delete(cancellable=None)
|
||||
else:
|
||||
break
|
||||
|
||||
|
||||
def trash_files(self):
|
||||
wid, tid, view, iconview, store = self.get_current_state()
|
||||
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||
self.handle_file(uris, "trash")
|
||||
for uri in uris:
|
||||
file = Gio.File.new_for_uri(uri)
|
||||
file.trash(cancellable=None)
|
||||
|
||||
|
||||
|
||||
@ -191,104 +203,146 @@ class WidgetFileActionMixin:
|
||||
target = f"{view.get_current_directory()}"
|
||||
|
||||
if file_name:
|
||||
file_name = "file://" + target + "/" + file_name
|
||||
path = "file://{target}/{file_name}"
|
||||
|
||||
if type == True: # Create File
|
||||
self.handle_file([file_name], "create_file")
|
||||
self.handle_file([path], "create_file")
|
||||
else: # Create Folder
|
||||
self.handle_file([file_name], "create_dir")
|
||||
self.handle_file([path], "create_dir")
|
||||
|
||||
fname_field.set_text("")
|
||||
|
||||
def move_files(self, files, target):
|
||||
self.handle_file(files, "move", target)
|
||||
|
||||
# NOTE: While not fully race condition proof, we happy path it first
|
||||
# and then handle anything after as a conflict for renaming before
|
||||
# copy, move, or edit. This is literally the oppopsite of what Gtk says to do.
|
||||
# But, they can't even delete directories properly. So... f**k them.
|
||||
# NOTE: Gtk recommends using fail flow than pre check existence which is more
|
||||
# race condition proof. They're right; but, they can't even delete
|
||||
# directories properly. So... f**k them. I'll do it my way.
|
||||
def handle_file(self, paths, action, _target_path=None):
|
||||
paths = self.preprocess_paths(paths)
|
||||
target = None
|
||||
response = None
|
||||
self.warning_alert.format_secondary_text(f"Do you really want to {action} the {len(paths)} file(s)?")
|
||||
target = None
|
||||
_file = None
|
||||
response = None
|
||||
overwrite_all = False
|
||||
rename_auto_all = False
|
||||
|
||||
for path in paths:
|
||||
try:
|
||||
file = Gio.File.new_for_uri(path)
|
||||
|
||||
if action == "trash":
|
||||
file.trash(cancellable=None)
|
||||
|
||||
if (action == "create_file" or action == "create_dir") and not file.query_exists():
|
||||
if action == "create_file":
|
||||
file.create(flags=Gio.FileCreateFlags.NONE, cancellable=None)
|
||||
continue
|
||||
if action == "create_dir":
|
||||
file.make_directory(cancellable=None)
|
||||
continue
|
||||
|
||||
|
||||
if _target_path:
|
||||
if os.path.isdir(_target_path.split("file://")[1]):
|
||||
info = file.query_info("standard::display-name", 0, cancellable=None)
|
||||
_target = f"{_target_path}/{info.get_display_name()}"
|
||||
target = Gio.File.new_for_uri(_target)
|
||||
_file = Gio.File.new_for_uri(_target)
|
||||
else:
|
||||
target = Gio.File.new_for_uri(_target_path)
|
||||
|
||||
if target and not target.query_exists():
|
||||
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
|
||||
if type == Gio.FileType.DIRECTORY:
|
||||
wid, tid = self.window_controller.get_active_data()
|
||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||
fPath = file.get_path()
|
||||
tPath = None
|
||||
state = True
|
||||
|
||||
if action == "copy":
|
||||
tPath = target.get_path()
|
||||
view.copy_file(fPath, tPath)
|
||||
if action == "move" or action == "edit":
|
||||
tPath = target.get_parent().get_path()
|
||||
view.move_file(fPath, tPath)
|
||||
else:
|
||||
if action == "copy":
|
||||
file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
|
||||
if action == "move" or action == "edit":
|
||||
file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
|
||||
_file = Gio.File.new_for_uri(_target_path)
|
||||
else:
|
||||
_file = Gio.File.new_for_uri(path)
|
||||
|
||||
|
||||
if _file.query_exists():
|
||||
if not overwrite_all and not rename_auto_all:
|
||||
self.exists_file_label.set_label(_file.get_basename())
|
||||
self.exists_file_field.set_text(_file.get_basename())
|
||||
response = self.show_exists_page()
|
||||
|
||||
if response == "overwrite_all":
|
||||
overwrite_all = True
|
||||
if response == "rename_auto_all":
|
||||
rename_auto_all = True
|
||||
|
||||
# NOTE: Past here, we need to handle detected conflicts.
|
||||
# Maybe create a collection of file and target pares
|
||||
# that then get passed to a handler who calls show_exists_page?
|
||||
if response == "rename":
|
||||
base_path = _file.get_parent().get_path()
|
||||
new_name = self.exists_file_field.get_text().strip()
|
||||
rfPath = f"{base_path}/{new_name}"
|
||||
_file = Gio.File.new_for_path(rfPath)
|
||||
|
||||
if action == "delete":
|
||||
if not response:
|
||||
response = self.warning_alert.run()
|
||||
self.warning_alert.hide()
|
||||
if response == Gtk.ResponseType.YES:
|
||||
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
if response == "rename_auto" or rename_auto_all:
|
||||
_file = self.rename_proc(_file)
|
||||
|
||||
if response == "overwrite" or overwrite_all:
|
||||
type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
|
||||
if type == Gio.FileType.DIRECTORY:
|
||||
wid, tid = self.window_controller.get_active_data()
|
||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||
view.delete_file( file.get_path() )
|
||||
view.delete_file( _file.get_path() )
|
||||
else:
|
||||
file.delete(cancellable=None)
|
||||
else:
|
||||
_file.delete(cancellable=None)
|
||||
|
||||
if response == "skip":
|
||||
continue
|
||||
if response == "skip_all":
|
||||
break
|
||||
|
||||
if _target_path:
|
||||
target = _file
|
||||
else:
|
||||
file = _file
|
||||
|
||||
|
||||
if action == "create_file":
|
||||
file.create(flags=Gio.FileCreateFlags.NONE, cancellable=None)
|
||||
continue
|
||||
if action == "create_dir":
|
||||
file.make_directory(cancellable=None)
|
||||
continue
|
||||
|
||||
|
||||
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
|
||||
if type == Gio.FileType.DIRECTORY:
|
||||
wid, tid = self.window_controller.get_active_data()
|
||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||
fPath = file.get_path()
|
||||
tPath = None
|
||||
state = True
|
||||
|
||||
if action == "copy":
|
||||
tPath = target.get_path()
|
||||
view.copy_file(fPath, tPath)
|
||||
if action == "move" or action == "rename":
|
||||
tPath = target.get_parent().get_path()
|
||||
view.move_file(fPath, tPath)
|
||||
else:
|
||||
if action == "copy":
|
||||
file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
|
||||
if action == "move" or action == "rename":
|
||||
file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None)
|
||||
|
||||
except GObject.GError as e:
|
||||
raise OSError(e)
|
||||
|
||||
self.exists_file_rename_bttn.set_sensitive(False)
|
||||
|
||||
def preprocess_paths(self, paths):
|
||||
if not isinstance(paths, list):
|
||||
paths = [paths]
|
||||
# Convert items such as pathlib paths to strings
|
||||
paths = [path.__fspath__() if hasattr(path, "__fspath__") else path for path in paths]
|
||||
return paths
|
||||
|
||||
|
||||
def rename_proc(self, gio_file):
|
||||
full_path = gio_file.get_path()
|
||||
base_path = gio_file.get_parent().get_path()
|
||||
file_name = os.path.splitext(gio_file.get_basename())[0]
|
||||
extension = os.path.splitext(full_path)[-1]
|
||||
target = Gio.File.new_for_path(full_path)
|
||||
|
||||
if debug:
|
||||
print(f"Path: {full_path}")
|
||||
print(f"Base Path: {base_path}")
|
||||
print(f'Name: {file_name}')
|
||||
print(f"Extension: {extension}")
|
||||
|
||||
i = 2
|
||||
while target.query_exists():
|
||||
target = Gio.File.new_for_path(f"{base_path}/{file_name}-copy{i}{extension}")
|
||||
i += 1
|
||||
|
||||
return target
|
||||
|
||||
|
||||
def exists_rename_field_changed(self, widget):
|
||||
nfile_name = widget.get_text().strip()
|
||||
ofile_name = self.exists_file_label.get_label()
|
||||
|
||||
if nfile_name:
|
||||
if nfile_name == ofile_name:
|
||||
self.exists_file_rename_bttn.set_sensitive(False)
|
||||
else:
|
||||
self.exists_file_rename_bttn.set_sensitive(True)
|
||||
else:
|
||||
self.exists_file_rename_bttn.set_sensitive(False)
|
||||
|
@ -729,7 +729,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
@ -773,6 +773,242 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
<property name="can-focus">False</property>
|
||||
<property name="stock">gtk-execute</property>
|
||||
</object>
|
||||
<object class="GtkDialog" id="file_exists_dialog">
|
||||
<property name="height-request">120</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window-position">center</property>
|
||||
<property name="type-hint">splashscreen</property>
|
||||
<property name="skip-taskbar-hint">True</property>
|
||||
<property name="skip-pager-hint">True</property>
|
||||
<property name="urgency-hint">True</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="gravity">center</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkButtonBox">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="button5">
|
||||
<property name="label" translatable="yes">Overwrite</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button6">
|
||||
<property name="label" translatable="yes">Overwrite (All)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button7">
|
||||
<property name="label" translatable="yes">Skip</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="button8">
|
||||
<property name="label" translatable="yes">Skip (All)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Filename already exists. Please rename or select an action.</property>
|
||||
<property name="xalign">0.10000000149011612</property>
|
||||
<attributes>
|
||||
<attribute name="scale" value="1.5"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="label" translatable="yes">Filename:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="exists_file_label">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="exists_file_field">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<signal name="changed" handler="exists_rename_field_changed" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButtonBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
<property name="spacing">20</property>
|
||||
<property name="baseline-position">top</property>
|
||||
<property name="layout-style">start</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="exists_file_rename_bttn">
|
||||
<property name="label" translatable="yes">Rename</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="button-release-event" handler="hide_exists_page_rename" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Auto Rename</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="button-release-event" handler="hide_exists_page_auto_rename" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Auto Rename All</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<signal name="button-release-event" handler="hide_exists_page_auto_rename_all" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
<property name="secondary">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-8">button5</action-widget>
|
||||
<action-widget response="-10">button6</action-widget>
|
||||
<action-widget response="-9">button7</action-widget>
|
||||
<action-widget response="-2">button8</action-widget>
|
||||
</action-widgets>
|
||||
<style>
|
||||
<class name="alert-border"/>
|
||||
</style>
|
||||
</object>
|
||||
<object class="GtkImage" id="image1">
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">False</property>
|
||||
@ -856,12 +1092,15 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
</object>
|
||||
<object class="GtkDialog" id="edit_file_menu">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window-position">center</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">splashscreen</property>
|
||||
<property name="skip-taskbar-hint">True</property>
|
||||
<property name="skip-pager-hint">True</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="gravity">center</property>
|
||||
<signal name="focus-out-event" handler="hide_edit_file_menu_cancel" swapped="no"/>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">False</property>
|
||||
@ -872,14 +1111,14 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
<property name="can-focus">False</property>
|
||||
<property name="layout-style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<object class="GtkButton" id="button1">
|
||||
<property name="label" translatable="yes">Skip</property>
|
||||
<property name="name">skip_renames</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">skip_img</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="button-release-event" handler="hide_edit_file_menu_skip" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@ -888,13 +1127,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<object class="GtkButton" id="button2">
|
||||
<property name="label">gtk-cancel</property>
|
||||
<property name="name">cancel_renames</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="use-stock">True</property>
|
||||
<signal name="button-release-event" handler="hide_edit_file_menu_cancel" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
@ -957,7 +1196,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
<property name="has-focus">True</property>
|
||||
<property name="primary-icon-stock">gtk-edit</property>
|
||||
<property name="placeholder-text" translatable="yes">To:</property>
|
||||
<signal name="key-release-event" handler="hide_edit_file_menu" swapped="no"/>
|
||||
<signal name="key-release-event" handler="hide_edit_file_menu_enter_key" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@ -991,6 +1230,10 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="-7">button1</action-widget>
|
||||
<action-widget response="-6">button2</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
<object class="GtkImage" id="tggl_notebook_1_img">
|
||||
<property name="visible">True</property>
|
||||
@ -1730,12 +1973,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Open With</property>
|
||||
<property name="name">open_with</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can-focus">True</property>
|
||||
<property name="receives-default">True</property>
|
||||
<property name="image">open_with_img</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="button-release-event" handler="show_appchooser_menu" swapped="no"/>
|
||||
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@ -1786,7 +2030,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
<property name="margin-top">20</property>
|
||||
<property name="image">rename_img2</property>
|
||||
<property name="always-show-image">True</property>
|
||||
<signal name="button-release-event" handler="do_edit_files" swapped="no"/>
|
||||
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
@ -1923,16 +2167,26 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
</object>
|
||||
<object class="GtkMessageDialog" id="warning_alert">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="resizable">False</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window-position">center</property>
|
||||
<property name="destroy-with-parent">True</property>
|
||||
<property name="type-hint">dialog</property>
|
||||
<property name="skip-taskbar-hint">True</property>
|
||||
<property name="skip-pager-hint">True</property>
|
||||
<property name="urgency-hint">True</property>
|
||||
<property name="decorated">False</property>
|
||||
<property name="deletable">False</property>
|
||||
<property name="gravity">center</property>
|
||||
<property name="message-type">warning</property>
|
||||
<property name="text" translatable="yes">Warning!</property>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkBox">
|
||||
<property name="can-focus">False</property>
|
||||
<property name="margin-left">5</property>
|
||||
<property name="margin-right">5</property>
|
||||
<property name="margin-top">5</property>
|
||||
<property name="margin-bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">2</property>
|
||||
<child internal-child="action_area">
|
||||
@ -1982,5 +2236,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||
<action-widget response="-9">button4</action-widget>
|
||||
<action-widget response="-8">button3</action-widget>
|
||||
</action-widgets>
|
||||
<style>
|
||||
<class name="alert-border"/>
|
||||
</style>
|
||||
</object>
|
||||
</interface>
|
||||
|
@ -39,6 +39,10 @@ notebook > header > tabs > tab:checked {
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.alert-border {
|
||||
border: 2px solid rgba(116, 0, 0, 0.64);
|
||||
}
|
||||
|
||||
|
||||
/* * {
|
||||
background: rgba(0, 0, 0, 0.14);
|
||||
|
Loading…
Reference in New Issue
Block a user