Greatly improved file handling flow and added popup guards.

This commit is contained in:
itdominator 2021-12-05 21:18:39 -06:00
parent cbf5706845
commit bed8af256c
9 changed files with 482 additions and 162 deletions

View File

@ -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>

View File

@ -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):

View 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()

View File

@ -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 = []

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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);