Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 25 additions & 17 deletions bulky.pot
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-07-25 15:24+0200\n"
"POT-Creation-Date: 2026-08-07 22:02+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: usr/lib/bulky/bulky.py:45
Expand All @@ -25,69 +25,77 @@ msgstr ""
msgid "Add"
msgstr ""

#: usr/lib/bulky/bulky.py:222 usr/share/bulky/bulky.ui.h:33
#: usr/lib/bulky/bulky.py:223 usr/share/bulky/bulky.ui.h:33
msgid "Rename..."
msgstr ""

#: usr/lib/bulky/bulky.py:260 usr/lib/bulky/bulky.py:437
#: usr/lib/bulky/bulky.py:262 usr/lib/bulky/bulky.py:454
msgid "About"
msgstr ""

#: usr/lib/bulky/bulky.py:265
#: usr/lib/bulky/bulky.py:267
msgid "Quit"
msgstr ""

#: usr/lib/bulky/bulky.py:282
#: usr/lib/bulky/bulky.py:284
msgid "Name"
msgstr ""

#: usr/lib/bulky/bulky.py:293
#: usr/lib/bulky/bulky.py:295
msgid "New name"
msgstr ""

#: usr/lib/bulky/bulky.py:402
#: usr/lib/bulky/bulky.py:404
msgid "Use %n, %0n, %00n, %000n, etc. to enumerate."
msgstr ""

#: usr/lib/bulky/bulky.py:439
#: usr/lib/bulky/bulky.py:456
msgid "Rename Files"
msgstr ""

#: usr/lib/bulky/bulky.py:487
#: usr/lib/bulky/bulky.py:510
msgid "Add files"
msgstr ""

#: usr/lib/bulky/bulky.py:516
#: usr/lib/bulky/bulky.py:542
#, python-format
msgid "Unable to rename '%s': File name too long"
msgstr ""

#: usr/lib/bulky/bulky.py:519
#: usr/lib/bulky/bulky.py:545
#, python-format
msgid ""
"Unable to rename '%s': Remote operation failed. This may be due to "
"insufficient permissions or a server error."
msgstr ""

#: usr/lib/bulky/bulky.py:523
#: usr/lib/bulky/bulky.py:549
#, python-format
msgid "Unable to rename '%s': %s"
msgstr ""

#: usr/lib/bulky/bulky.py:599 generate_desktop_files:25
#: usr/lib/bulky/bulky.py:592 usr/lib/bulky/bulky.py:618
msgid "Renaming files…"
msgstr ""

#: usr/lib/bulky/bulky.py:666 usr/lib/bulky/bulky.py:680
msgid "Loading files…"
msgstr ""

#: usr/lib/bulky/bulky.py:693 generate_desktop_files:25
msgid "File Renamer"
msgstr ""

#: usr/lib/bulky/bulky.py:600 generate_desktop_files:25
#: usr/lib/bulky/bulky.py:694 generate_desktop_files:25
msgid "Rename files and directories"
msgstr ""

#: usr/lib/bulky/bulky.py:700
#: usr/lib/bulky/bulky.py:793
#, python-format
msgid "Name collision on '%s'."
msgstr ""

#: usr/lib/bulky/bulky.py:704 usr/lib/bulky/bulky.py:708
#: usr/lib/bulky/bulky.py:797 usr/lib/bulky/bulky.py:801
#, python-format
msgid "'%s' is not writeable."
msgstr ""
Expand Down
139 changes: 116 additions & 23 deletions usr/lib/bulky/bulky.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ def __init__(self, application):
# used to prevent collisions
self.uris = []
self.renamed_uris = []
self.is_processing = False
self.last_chooser_location = Gio.File.new_for_path(GLib.get_home_dir())

# Set the Glade file
Expand All @@ -224,6 +225,7 @@ def __init__(self, application):

# Create variables to quickly access dynamic widgets
self.headerbar = self.builder.get_object("headerbar")
self.file_progress = self.builder.get_object("file_progress")
self.add_button = self.builder.get_object("add_button")
self.remove_button = self.builder.get_object("remove_button")
self.clear_button = self.builder.get_object("clear_button")
Expand Down Expand Up @@ -405,8 +407,23 @@ def __init__(self, application):

self.load_files(sys.argv[1:], initial_load=True)

def show_progress(self, text):
self.file_progress.set_fraction(0)
self.file_progress.set_text(text)
self.file_progress.show()

def update_progress(self, current, total, text):
fraction = current / total if total else 0
self.file_progress.set_fraction(fraction)
self.file_progress.set_text("{} {} / {}".format(text, current, total))

def hide_progress(self):
self.file_progress.hide()
self.file_progress.set_fraction(0)
self.file_progress.set_text("")

def on_drag_data_received(self, widget, context, x, y, data, info, _time, user_data=None):
if data:
if data and not self.is_processing:
dtype = data.get_data_type().name()
if context.get_selected_action() == Gdk.DragAction.COPY:
if dtype.startswith("text/uri-list"):
Expand Down Expand Up @@ -462,6 +479,9 @@ def on_files_selected(self, selection):
self.remove_button.set_sensitive(len(paths) > 0)

def on_key_press_event(self, widget, event):
if self.is_processing:
return

ctrl = (event.state & Gdk.ModifierType.CONTROL_MASK)
if ctrl:
if event.keyval == Gdk.KEY_n:
Expand All @@ -484,6 +504,9 @@ def on_remove_button(self, widget):
self.preview_changes()

def on_add_button(self, widget):
if self.is_processing:
return

dialog = FolderFileChooserDialog(_("Add files"), self.window, self.last_chooser_location)

def update_last_location(dialog, response_id, data=None):
Expand All @@ -494,11 +517,14 @@ def update_last_location(dialog, response_id, data=None):
dialog.connect("response", update_last_location)

response = dialog.run()
if response == Gtk.ResponseType.OK:
for uri in dialog.get_uris():
self.add_file(uri)
self.preview_changes()
if response != Gtk.ResponseType.OK:
dialog.destroy()
self.preview_changes()
return

uris = dialog.get_uris()
dialog.destroy()
self.load_files(uris)

def on_clear_button(self, widget):
self.model.clear()
Expand Down Expand Up @@ -527,6 +553,9 @@ def report_os_error(self, file_obj, new_path, error):
self.infobar.show()

def on_rename_button(self, widget):
if self.is_processing:
return

iters = []
iter = self.model.get_iter_first()
while iter != None:
Expand All @@ -546,26 +575,58 @@ def on_rename_button(self, widget):
print(e)

rename_list = self.sort_list_by_depth(rename_list)
rename_list = [tup for tup in rename_list if tup[2] != tup[3]]

# for i in rename_list:
# print(i[0].gfile.get_uri())

for tup in rename_list:
iter, file_obj, name, new_name = tup
if new_name != name:
try:
old_uri = file_obj.uri
# print("Renaming %s --> %s" % (file_obj.get_path_or_uri_for_display(), new_name))
if file_obj.rename(new_name):
self.uris.remove(old_uri)
self.uris.append(file_obj.uri)
self.model.set_value(iter, COL_NAME, new_name)
except GLib.Error as e:
self.report_os_error(file_obj, new_name, e)
break

total = len(rename_list)
current = 0
self.rename_button.set_sensitive(False)

if total == 0:
return

self.is_processing = True
self.builder.get_object("file_toolbox").set_sensitive(False)
self.show_progress(_("Renaming files…"))

def rename_next_file():
nonlocal current

iter, file_obj, name, new_name = rename_list[current]
try:
old_uri = file_obj.uri
# print("Renaming %s --> %s" % (file_obj.get_path_or_uri_for_display(), new_name))
if file_obj.rename(new_name):
self.uris.remove(old_uri)
self.uris.append(file_obj.uri)
self.model.set_value(iter, COL_NAME, new_name)
except GLib.Error as e:
self.report_os_error(file_obj, new_name, e)
self.hide_progress()
self.builder.get_object("file_toolbox").set_sensitive(True)
self.is_processing = False
return False
except Exception:
self.hide_progress()
self.builder.get_object("file_toolbox").set_sensitive(True)
self.is_processing = False
raise

current += 1
self.update_progress(current, total, _("Renaming files…"))

if current == total:
self.hide_progress()
self.builder.get_object("file_toolbox").set_sensitive(True)
self.is_processing = False
return False

return True

GLib.idle_add(rename_next_file)

def sort_list_by_depth(self, rename_list):
# Rename files first, followed by directories from deep to shallow.
def file_cmp(tup_a, tup_b):
Expand All @@ -590,16 +651,48 @@ def file_cmp(tup_a, tup_b):

def load_files(self, uris, initial_load=False):
# Clear treeview and selection
if self.is_processing:
return

if len(uris) > 0:
if initial_load:
self.builder.get_object("file_toolbox").hide()
for path in uris:
self.add_file(path)
total = len(uris)
current = 0
uri_iterator = iter(uris)

self.is_processing = True
self.builder.get_object("file_toolbox").set_sensitive(False)
self.show_progress(_("Loading files…"))

def load_next_file():
nonlocal current

try:
self.add_file(next(uri_iterator))
except Exception:
self.hide_progress()
self.builder.get_object("file_toolbox").set_sensitive(True)
self.is_processing = False
raise

current += 1
self.update_progress(current, total, _("Loading files…"))

if current == total:
self.hide_progress()
self.builder.get_object("file_toolbox").set_sensitive(True)
self.preview_changes()
self.is_processing = False
return False

return True

GLib.idle_add(load_next_file)
else:
self.builder.get_object("headerbar").set_title(_("File Renamer"))
self.builder.get_object("headerbar").set_subtitle(_("Rename files and directories"))

self.preview_changes()
self.preview_changes()

def add_file(self, uri_or_path):
file_obj = FileObject(uri_or_path, self.window.get_scale_factor())
Expand Down
34 changes: 27 additions & 7 deletions usr/share/bulky/bulky.ui
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@
<property name="can-focus">True</property>
<property name="shadow-type">in</property>
<child>
<object class="GtkViewport">
<property name="visible">True</property>
<property name="can-focus">False</property>
<child>
<object class="GtkTreeView" id="treeview">
<property name="visible">True</property>
<property name="can-focus">True</property>
Expand All @@ -123,8 +119,6 @@
<object class="GtkTreeSelection"/>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
Expand All @@ -134,7 +128,26 @@
</packing>
</child>
<child>
<object class="GtkToolbar" id="file_toolbox">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">horizontal</property>
<child>
<object class="GtkProgressBar" id="file_progress">
<property name="visible">False</property>
<property name="can-focus">False</property>
<property name="orientation">horizontal</property>
<property name="fraction">0</property>
<property name="show-text">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkToolbar" id="file_toolbox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="toolbar-style">icons</property>
Expand Down Expand Up @@ -224,6 +237,13 @@
<style>
<class name="inline-toolbar"/>
</style>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
Expand Down