|
|
@@ -47,34 +47,24 @@ public:
|
|
|
property_xpad() = 10;
|
|
|
property_ypad() = 10;
|
|
|
}
|
|
|
-
|
|
|
protected:
|
|
|
void render_vfunc(const Cairo::RefPtr<Cairo::Context>& cr,
|
|
|
Gtk::Widget& widget,
|
|
|
const Gdk::Rectangle& background_area,
|
|
|
const Gdk::Rectangle& cell_area,
|
|
|
Gtk::CellRendererState flags) override {
|
|
|
-
|
|
|
- // 1. Draw the Frame Border
|
|
|
cr->save();
|
|
|
-
|
|
|
- // Get background color to determine "opposite" color
|
|
|
auto style = widget.get_style_context();
|
|
|
Gdk::RGBA bg = style->get_background_color();
|
|
|
-
|
|
|
- // If background is light, use dark grey. If dark, use light grey.
|
|
|
double brightness = (bg.get_red() + bg.get_green() + bg.get_blue()) / 3.0;
|
|
|
- if (brightness > 0.5) cr->set_source_rgb(0.5, 0.5, 0.5); // Medium Grey
|
|
|
- else cr->set_source_rgb(0.8, 0.8, 0.8); // Light Grey for dark themes
|
|
|
+ if (brightness > 0.5) cr->set_source_rgb(0.5, 0.5, 0.5);
|
|
|
+ else cr->set_source_rgb(0.8, 0.8, 0.8);
|
|
|
|
|
|
cr->set_line_width(2.0);
|
|
|
- // Draw rectangle with a slight margin inside the cell area
|
|
|
cr->rectangle(cell_area.get_x() + 2, cell_area.get_y() + 2,
|
|
|
cell_area.get_width() - 4, cell_area.get_height() - 4);
|
|
|
cr->stroke();
|
|
|
cr->restore();
|
|
|
-
|
|
|
- // 2. Call base class to draw the actual Pixbuf inside our frame
|
|
|
Gtk::CellRendererPixbuf::render_vfunc(cr, widget, background_area, cell_area, flags);
|
|
|
}
|
|
|
};
|
|
|
@@ -88,20 +78,25 @@ struct LoadedItem {
|
|
|
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
|
|
int orig_w = 0, orig_h = 0;
|
|
|
uintmax_t filesize = 0;
|
|
|
+ bool is_update = false;
|
|
|
+ Gtk::TreeModel::Path model_path;
|
|
|
};
|
|
|
|
|
|
class RenamerWindow : public Gtk::Window {
|
|
|
public:
|
|
|
RenamerWindow() : m_Dispatcher() {
|
|
|
- set_title("Simple Image Renamer 0.1");
|
|
|
+ set_title("Simple Image Renamer 0.2");
|
|
|
set_default_size(1280, 850);
|
|
|
|
|
|
+ // --- ADDED WMCLASS ---
|
|
|
+ set_wmclass("simpleimagerenamer", "simpleimagerenamer");
|
|
|
+
|
|
|
m_Dispatcher.connect(sigc::mem_fun(*this, &RenamerWindow::on_worker_notification));
|
|
|
|
|
|
m_VBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
|
|
|
add(m_VBox);
|
|
|
|
|
|
- // Toolbar
|
|
|
+ // --- Toolbar Top ---
|
|
|
m_ToolbarTop.set_margin_top(5); m_ToolbarTop.set_margin_bottom(5);
|
|
|
m_ToolbarTop.set_margin_left(10); m_ToolbarTop.set_margin_right(10);
|
|
|
m_VBox.pack_start(m_ToolbarTop, Gtk::PACK_SHRINK);
|
|
|
@@ -110,59 +105,68 @@ public:
|
|
|
m_BtnOpen.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_open_folder_clicked));
|
|
|
m_ToolbarTop.pack_start(m_BtnOpen, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
- m_BtnStop.set_label("Stop");
|
|
|
- m_BtnStop.set_sensitive(false);
|
|
|
- m_BtnStop.signal_clicked().connect([this](){ m_stop_flag = true; m_BtnStop.set_sensitive(false); });
|
|
|
- m_ToolbarTop.pack_start(m_BtnStop, Gtk::PACK_SHRINK, 5);
|
|
|
+ m_BtnStopReload.set_label("Reload Folder");
|
|
|
+ m_BtnStopReload.set_sensitive(false);
|
|
|
+ m_BtnStopReload.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_stop_reload_clicked));
|
|
|
+ m_ToolbarTop.pack_start(m_BtnStopReload, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
- m_ComboSort.append("Name"); m_ComboSort.append("Oldest"); m_ComboSort.append("Newest"); m_ComboSort.append("Manual");
|
|
|
+ m_ToolbarTop.pack_start(*Gtk::manage(new Gtk::Label(" Sort By: ")), Gtk::PACK_SHRINK);
|
|
|
+ m_ComboSort.append("Name (Natural)"); m_ComboSort.append("Oldest First"); m_ComboSort.append("Newest First"); m_ComboSort.append("Manual (Drag & Drop)");
|
|
|
m_ComboSort.set_active(0);
|
|
|
m_ComboSort.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_sort_changed));
|
|
|
m_ToolbarTop.pack_start(m_ComboSort, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
- m_ComboSize.append("Tiny"); m_ComboSize.append("Normal"); m_ComboSize.append("Huge");
|
|
|
+ m_ToolbarTop.pack_start(*Gtk::manage(new Gtk::Label(" Zoom: ")), Gtk::PACK_SHRINK);
|
|
|
+ m_ComboSize.append("Small"); m_ComboSize.append("Medium"); m_ComboSize.append("Large");
|
|
|
m_ComboSize.set_active(1);
|
|
|
m_ComboSize.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_size_changed));
|
|
|
m_ToolbarTop.pack_start(m_ComboSize, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
- m_BtnUndo.set_label("Undo");
|
|
|
+ m_BtnUndo.set_label("Undo Last Rename");
|
|
|
m_BtnUndo.set_sensitive(false);
|
|
|
m_BtnUndo.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_undo_clicked));
|
|
|
m_ToolbarTop.pack_end(m_BtnUndo, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
- // Controls Frame
|
|
|
+ // --- Controls Frame ---
|
|
|
m_FrameControls.set_shadow_type(Gtk::SHADOW_ETCHED_IN);
|
|
|
m_VBox.pack_start(m_FrameControls, Gtk::PACK_SHRINK);
|
|
|
m_ToolbarControls.set_margin_top(10); m_ToolbarControls.set_margin_bottom(10);
|
|
|
m_FrameControls.add(m_ToolbarControls);
|
|
|
|
|
|
- m_ComboMode.append("Numbering Pattern"); m_ComboMode.append("Find & Replace");
|
|
|
+ m_ToolbarControls.pack_start(*Gtk::manage(new Gtk::Label(" Mode: ")), Gtk::PACK_SHRINK);
|
|
|
+ m_ComboMode.append("Sequential Numbering"); m_ComboMode.append("Find & Replace");
|
|
|
m_ComboMode.set_active(0);
|
|
|
m_ComboMode.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_mode_changed));
|
|
|
m_ToolbarControls.pack_start(m_ComboMode, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
+ // Sequential Numbering Widgets
|
|
|
+ m_BoxPattern.pack_start(*Gtk::manage(new Gtk::Label(" Pattern: ")), Gtk::PACK_SHRINK);
|
|
|
m_EntryPattern.set_text("image_###");
|
|
|
m_EntryPattern.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
|
|
|
m_BoxPattern.pack_start(m_EntryPattern, Gtk::PACK_EXPAND_WIDGET);
|
|
|
+ m_BoxPattern.pack_start(*Gtk::manage(new Gtk::Label(" Start at: ")), Gtk::PACK_SHRINK);
|
|
|
m_AdjStartNum = Gtk::Adjustment::create(1, 0, 100000, 1, 10, 0);
|
|
|
m_SpinStartNum.set_adjustment(m_AdjStartNum);
|
|
|
m_SpinStartNum.signal_value_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
|
|
|
m_BoxPattern.pack_start(m_SpinStartNum, Gtk::PACK_SHRINK);
|
|
|
m_StackModes.add(m_BoxPattern, "Pattern");
|
|
|
|
|
|
+ // Find & Replace Widgets
|
|
|
+ m_BoxReplace.pack_start(*Gtk::manage(new Gtk::Label(" Find Text: ")), Gtk::PACK_SHRINK);
|
|
|
m_EntryFind.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
|
|
|
m_BoxReplace.pack_start(m_EntryFind, Gtk::PACK_EXPAND_WIDGET);
|
|
|
+ m_BoxReplace.pack_start(*Gtk::manage(new Gtk::Label(" Replace With: ")), Gtk::PACK_SHRINK);
|
|
|
m_EntryReplace.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
|
|
|
m_BoxReplace.pack_start(m_EntryReplace, Gtk::PACK_EXPAND_WIDGET);
|
|
|
m_StackModes.add(m_BoxReplace, "Replace");
|
|
|
m_ToolbarControls.pack_start(m_StackModes, Gtk::PACK_EXPAND_WIDGET, 5);
|
|
|
|
|
|
- m_BtnRename.set_label("Rename Selected");
|
|
|
+ m_BtnRename.set_label("Apply New Names");
|
|
|
m_BtnRename.get_style_context()->add_class("suggested-action");
|
|
|
m_BtnRename.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_rename_execute));
|
|
|
m_ToolbarControls.pack_end(m_BtnRename, Gtk::PACK_SHRINK, 5);
|
|
|
|
|
|
- // IconView setup
|
|
|
+ // --- IconView setup ---
|
|
|
m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
|
|
|
m_VBox.pack_start(m_ScrolledWindow);
|
|
|
|
|
|
@@ -170,39 +174,32 @@ public:
|
|
|
m_RefListStore->signal_row_inserted().connect([this](const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator&){ update_preview(); });
|
|
|
|
|
|
m_IconView.set_model(m_RefListStore);
|
|
|
-
|
|
|
- // Pack our custom framed renderer
|
|
|
m_IconView.pack_start(m_cell_frame, false);
|
|
|
m_IconView.add_attribute(m_cell_frame, "pixbuf", m_Columns.m_col_pixbuf);
|
|
|
-
|
|
|
m_IconView.pack_start(m_cell_toggle, false);
|
|
|
m_IconView.add_attribute(m_cell_toggle, "active", m_Columns.m_col_checked);
|
|
|
m_cell_toggle.property_activatable() = true;
|
|
|
m_cell_toggle.signal_toggled().connect(sigc::mem_fun(*this, &RenamerWindow::on_cell_toggled));
|
|
|
-
|
|
|
m_IconView.pack_start(m_cell_text, false);
|
|
|
m_IconView.add_attribute(m_cell_text, "markup", m_Columns.m_col_markup);
|
|
|
|
|
|
m_IconView.set_item_width(220);
|
|
|
- m_IconView.set_selection_mode(Gtk::SELECTION_MULTIPLE);
|
|
|
- m_IconView.set_reorderable(true);
|
|
|
m_IconView.set_columns(-1);
|
|
|
- m_IconView.set_spacing(20); // Spacing between thumbnails
|
|
|
+ m_IconView.set_spacing(20);
|
|
|
+
|
|
|
+ m_IconView.set_selection_mode(Gtk::SELECTION_NONE);
|
|
|
+ m_IconView.set_reorderable(false);
|
|
|
|
|
|
m_ScrolledWindow.add(m_IconView);
|
|
|
-
|
|
|
- m_ProgressBar.set_no_show_all(true);
|
|
|
- m_ProgressBar.hide();
|
|
|
m_VBox.pack_start(m_ProgressBar, Gtk::PACK_SHRINK);
|
|
|
m_VBox.pack_end(m_Statusbar, Gtk::PACK_SHRINK);
|
|
|
|
|
|
- // D&D
|
|
|
std::vector<Gtk::TargetEntry> listTargets = { Gtk::TargetEntry("text/uri-list") };
|
|
|
this->drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY);
|
|
|
this->signal_drag_data_received().connect(sigc::mem_fun(*this, &RenamerWindow::on_drag_data_received));
|
|
|
|
|
|
show_all_children();
|
|
|
- m_StackModes.set_visible_child("Pattern");
|
|
|
+ m_ProgressBar.hide();
|
|
|
}
|
|
|
|
|
|
~RenamerWindow() { m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join(); }
|
|
|
@@ -210,7 +207,7 @@ public:
|
|
|
protected:
|
|
|
Gtk::Box m_VBox, m_ToolbarTop, m_ToolbarControls, m_BoxPattern, m_BoxReplace;
|
|
|
Gtk::Frame m_FrameControls;
|
|
|
- Gtk::Button m_BtnOpen, m_BtnRename, m_BtnUndo, m_BtnStop;
|
|
|
+ Gtk::Button m_BtnOpen, m_BtnRename, m_BtnUndo, m_BtnStopReload;
|
|
|
Gtk::Entry m_EntryPattern, m_EntryFind, m_EntryReplace;
|
|
|
Gtk::ComboBoxText m_ComboSize, m_ComboSort, m_ComboMode;
|
|
|
Gtk::SpinButton m_SpinStartNum;
|
|
|
@@ -220,25 +217,22 @@ protected:
|
|
|
Gtk::IconView m_IconView;
|
|
|
Gtk::ProgressBar m_ProgressBar;
|
|
|
Gtk::Statusbar m_Statusbar;
|
|
|
-
|
|
|
- CellRendererFrame m_cell_frame; // Custom Framed Renderer
|
|
|
+ CellRendererFrame m_cell_frame;
|
|
|
Gtk::CellRendererText m_cell_text;
|
|
|
Gtk::CellRendererToggle m_cell_toggle;
|
|
|
|
|
|
- class ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
|
|
- public:
|
|
|
+ struct ModelColumns : public Gtk::TreeModel::ColumnRecord {
|
|
|
ModelColumns() { add(m_col_path); add(m_col_filename); add(m_col_pixbuf); add(m_col_checked); add(m_col_markup); add(m_col_time); add(m_col_info_str); }
|
|
|
Gtk::TreeModelColumn<std::string> m_col_path, m_col_filename, m_col_markup, m_col_info_str;
|
|
|
Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_col_pixbuf;
|
|
|
Gtk::TreeModelColumn<bool> m_col_checked;
|
|
|
Gtk::TreeModelColumn<long long> m_col_time;
|
|
|
- };
|
|
|
- ModelColumns m_Columns;
|
|
|
+ } m_Columns;
|
|
|
Glib::RefPtr<Gtk::ListStore> m_RefListStore;
|
|
|
|
|
|
std::string m_current_path;
|
|
|
std::thread m_WorkerThread;
|
|
|
- std::atomic<bool> m_stop_flag{false};
|
|
|
+ std::atomic<bool> m_stop_flag{false}, m_is_loading{false};
|
|
|
std::atomic<int> m_total_files{0}, m_processed_files{0};
|
|
|
Glib::Dispatcher m_Dispatcher;
|
|
|
std::mutex m_QueueMutex;
|
|
|
@@ -246,7 +240,7 @@ protected:
|
|
|
std::stack<UndoStep> m_UndoStack;
|
|
|
|
|
|
std::string format_size(uintmax_t bytes) {
|
|
|
- double sz = static_cast<double>(bytes);
|
|
|
+ double sz = (double)bytes;
|
|
|
const char* units[] = {"B", "KB", "MB", "GB"};
|
|
|
int i = 0; while (sz >= 1024 && i < 3) { sz /= 1024; i++; }
|
|
|
std::stringstream ss; ss << std::fixed << std::setprecision(1) << sz << " " << units[i];
|
|
|
@@ -254,201 +248,229 @@ protected:
|
|
|
}
|
|
|
|
|
|
void on_mode_changed() {
|
|
|
- if (m_ComboMode.get_active_row_number() == 0) m_StackModes.set_visible_child("Pattern");
|
|
|
- else m_StackModes.set_visible_child("Replace");
|
|
|
+ m_StackModes.set_visible_child(m_ComboMode.get_active_row_number() == 0 ? "Pattern" : "Replace");
|
|
|
update_preview();
|
|
|
}
|
|
|
|
|
|
- void on_cell_toggled(const Glib::ustring& path_string) {
|
|
|
- auto iter = m_RefListStore->get_iter(Gtk::TreePath(path_string));
|
|
|
- if(iter) { (*iter)[m_Columns.m_col_checked] = !(*iter)[m_Columns.m_col_checked]; update_preview(); }
|
|
|
+ void on_cell_toggled(const Glib::ustring& p) {
|
|
|
+ auto it = m_RefListStore->get_iter(Gtk::TreePath(p));
|
|
|
+ if(it) { (*it)[m_Columns.m_col_checked] = !(*it)[m_Columns.m_col_checked]; update_preview(); }
|
|
|
+ }
|
|
|
+
|
|
|
+ void on_stop_reload_clicked() {
|
|
|
+ if (m_is_loading) m_stop_flag = true;
|
|
|
+ else if (!m_current_path.empty()) start_loading_folder(m_current_path);
|
|
|
}
|
|
|
|
|
|
void update_preview() {
|
|
|
+ if (!m_RefListStore) return;
|
|
|
int mode = m_ComboMode.get_active_row_number();
|
|
|
std::string pattern = m_EntryPattern.get_text();
|
|
|
int start_num = m_SpinStartNum.get_value_as_int();
|
|
|
size_t hash_pos = pattern.find('#');
|
|
|
- int pad_width = (hash_pos != std::string::npos) ? (pattern.find_last_of('#') - hash_pos + 1) : 0;
|
|
|
+ int pad = (hash_pos != std::string::npos) ? (pattern.find_last_of('#') - hash_pos + 1) : 0;
|
|
|
int count = 0;
|
|
|
for (auto row : m_RefListStore->children()) {
|
|
|
- std::string original = row[m_Columns.m_col_filename], meta_info = row[m_Columns.m_col_info_str], new_name = original;
|
|
|
+ std::string original = row[m_Columns.m_col_filename], meta = row[m_Columns.m_col_info_str], new_name = original;
|
|
|
if (row[m_Columns.m_col_checked]) {
|
|
|
if (mode == 0 && !pattern.empty()) {
|
|
|
- std::string num_str = std::to_string(start_num + count);
|
|
|
- if (pad_width > 0) {
|
|
|
- while (num_str.length() < (size_t)pad_width) num_str = "0" + num_str;
|
|
|
- std::string temp = pattern; temp.replace(hash_pos, pad_width, num_str);
|
|
|
- new_name = temp + fs::path(original).extension().string();
|
|
|
- } else { new_name = pattern + "_" + num_str + fs::path(original).extension().string(); }
|
|
|
+ std::string num = std::to_string(start_num + count);
|
|
|
+ while (num.length() < (size_t)pad) num = "0" + num;
|
|
|
+ if (hash_pos != std::string::npos) {
|
|
|
+ std::string t = pattern; t.replace(hash_pos, pad, num);
|
|
|
+ new_name = t + fs::path(original).extension().string();
|
|
|
+ } else new_name = pattern + "_" + num + fs::path(original).extension().string();
|
|
|
count++;
|
|
|
} else if (mode == 1 && !m_EntryFind.get_text().empty()) {
|
|
|
size_t pos = original.find(m_EntryFind.get_text());
|
|
|
if (pos != std::string::npos) { new_name = original; new_name.replace(pos, m_EntryFind.get_text().length(), m_EntryReplace.get_text()); }
|
|
|
}
|
|
|
}
|
|
|
- std::stringstream markup;
|
|
|
- markup << "<span font='9'>";
|
|
|
- if (new_name != original) markup << "<span size='small' alpha='60%'>" << Glib::Markup::escape_text(original) << "</span>\n<span foreground='#3584e4' weight='bold'>" << Glib::Markup::escape_text(new_name) << "</span>";
|
|
|
- else markup << "<span weight='bold'>" << Glib::Markup::escape_text(original) << "</span>";
|
|
|
- markup << "\n<span size='x-small' alpha='70%'>" << Glib::Markup::escape_text(meta_info) << "</span></span>";
|
|
|
- row[m_Columns.m_col_markup] = markup.str();
|
|
|
+ std::stringstream mu; mu << "<span font='9'>";
|
|
|
+ if (new_name != original) mu << "<span size='small' alpha='60%'>" << Glib::Markup::escape_text(original) << "</span>\n<span foreground='#3584e4' weight='bold'>" << Glib::Markup::escape_text(new_name) << "</span>";
|
|
|
+ else mu << "<span weight='bold'>" << Glib::Markup::escape_text(original) << "</span>";
|
|
|
+ mu << "\n<span size='x-small' alpha='70%'>" << Glib::Markup::escape_text(meta) << "</span></span>";
|
|
|
+ row[m_Columns.m_col_markup] = mu.str();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void on_rename_execute() {
|
|
|
- UndoStep undo_step;
|
|
|
- int mode = m_ComboMode.get_active_row_number(), start_num = m_SpinStartNum.get_value_as_int();
|
|
|
- std::string pattern = m_EntryPattern.get_text();
|
|
|
- size_t hash_pos = pattern.find('#');
|
|
|
- int pad_width = (hash_pos != std::string::npos && mode == 0) ? (pattern.find_last_of('#') - hash_pos + 1) : 0;
|
|
|
- int iter_count = 0;
|
|
|
+ UndoStep step;
|
|
|
+ int mode = m_ComboMode.get_active_row_number(), start = m_SpinStartNum.get_value_as_int();
|
|
|
+ std::string pat = m_EntryPattern.get_text();
|
|
|
+ size_t hpos = pat.find('#');
|
|
|
+ int pad = (hpos != std::string::npos && mode == 0) ? (pat.find_last_of('#') - hpos + 1) : 0;
|
|
|
+ int count = 0;
|
|
|
for (auto row : m_RefListStore->children()) {
|
|
|
if (!row[m_Columns.m_col_checked]) continue;
|
|
|
- std::string original = row[m_Columns.m_col_filename], new_filename = original;
|
|
|
+ std::string orig = row[m_Columns.m_col_filename], next = orig;
|
|
|
if (mode == 0) {
|
|
|
- std::string num_str = std::to_string(start_num + iter_count);
|
|
|
- if (pad_width > 0) {
|
|
|
- while (num_str.length() < (size_t)pad_width) num_str = "0" + num_str;
|
|
|
- std::string temp = pattern; temp.replace(hash_pos, pad_width, num_str);
|
|
|
- new_filename = temp + fs::path(original).extension().string();
|
|
|
- } else { new_filename = pattern + "_" + num_str + fs::path(original).extension().string(); }
|
|
|
- iter_count++;
|
|
|
+ std::string num = std::to_string(start + count);
|
|
|
+ while (num.length() < (size_t)pad) num = "0" + num;
|
|
|
+ if (hpos != std::string::npos) { std::string t = pat; t.replace(hpos, pad, num); next = t + fs::path(orig).extension().string(); }
|
|
|
+ else next = pat + "_" + num + fs::path(orig).extension().string();
|
|
|
+ count++;
|
|
|
} else if (mode == 1 && !m_EntryFind.get_text().empty()) {
|
|
|
- size_t pos = original.find(m_EntryFind.get_text());
|
|
|
- if (pos != std::string::npos) { new_filename = original; new_filename.replace(pos, m_EntryFind.get_text().length(), m_EntryReplace.get_text()); }
|
|
|
+ size_t p = orig.find(m_EntryFind.get_text());
|
|
|
+ if (p != std::string::npos) { next = orig; next.replace(p, m_EntryFind.get_text().length(), m_EntryReplace.get_text()); }
|
|
|
}
|
|
|
- if (new_filename != original) undo_step.moves.push_back({(std::string)row[m_Columns.m_col_path], (fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / new_filename).string()});
|
|
|
+ if (next != orig) step.moves.push_back({(std::string)row[m_Columns.m_col_path], (fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / next).string()});
|
|
|
}
|
|
|
- if (undo_step.moves.empty()) return;
|
|
|
- int t_idx = 0; std::vector<std::pair<std::string, std::string>> final_stage;
|
|
|
- for (auto& move : undo_step.moves) {
|
|
|
- fs::path temp = fs::path(move.original_path).parent_path() / ("__tmp_" + std::to_string(t_idx++) + fs::path(move.original_path).extension().string());
|
|
|
- try { fs::rename(move.original_path, temp); final_stage.push_back({temp.string(), move.new_path}); } catch (...) {}
|
|
|
+ if (step.moves.empty()) return;
|
|
|
+ int ti = 0; std::vector<std::pair<std::string, std::string>> stage;
|
|
|
+ for (auto& m : step.moves) {
|
|
|
+ fs::path t = fs::path(m.original_path).parent_path() / ("__tmp_" + std::to_string(ti++) + fs::path(m.original_path).extension().string());
|
|
|
+ try { fs::rename(m.original_path, t); stage.push_back({t.string(), m.new_path}); } catch (...) {}
|
|
|
}
|
|
|
- for (auto& item : final_stage) { try { fs::rename(item.first, item.second); } catch (...) {} }
|
|
|
- m_UndoStack.push(undo_step); m_BtnUndo.set_sensitive(true); start_loading_folder(m_current_path);
|
|
|
+ for (auto& s : stage) try { fs::rename(s.first, s.second); } catch (...) {}
|
|
|
+ m_UndoStack.push(step); m_BtnUndo.set_sensitive(true); start_loading_folder(m_current_path);
|
|
|
}
|
|
|
|
|
|
void on_undo_clicked() {
|
|
|
if (m_UndoStack.empty()) return;
|
|
|
UndoStep last = m_UndoStack.top(); m_UndoStack.pop(); m_BtnUndo.set_sensitive(!m_UndoStack.empty());
|
|
|
- int t_idx = 0; std::vector<std::pair<std::string, std::string>> final_stage;
|
|
|
- for (const auto& move : last.moves) {
|
|
|
- if (fs::exists(move.new_path)) {
|
|
|
- fs::path temp = fs::path(move.new_path).parent_path() / ("__u_tmp_" + std::to_string(t_idx++) + fs::path(move.new_path).extension().string());
|
|
|
- try { fs::rename(move.new_path, temp); final_stage.push_back({temp.string(), move.original_path}); } catch (...) {}
|
|
|
+ int ti = 0; std::vector<std::pair<std::string, std::string>> stage;
|
|
|
+ for (const auto& m : last.moves) {
|
|
|
+ if (fs::exists(m.new_path)) {
|
|
|
+ fs::path t = fs::path(m.new_path).parent_path() / ("__u_" + std::to_string(ti++) + fs::path(m.new_path).extension().string());
|
|
|
+ try { fs::rename(m.new_path, t); stage.push_back({t.string(), m.original_path}); } catch (...) {}
|
|
|
}
|
|
|
}
|
|
|
- for (auto& item : final_stage) { try { fs::rename(item.first, item.second); } catch(...) {} }
|
|
|
+ for (auto& s : stage) try { fs::rename(s.first, s.second); } catch(...) {}
|
|
|
start_loading_folder(m_current_path);
|
|
|
}
|
|
|
|
|
|
void on_sort_changed() {
|
|
|
- if (m_RefListStore->children().empty()) return;
|
|
|
+ if (!m_RefListStore || m_RefListStore->children().empty()) return;
|
|
|
int mode = m_ComboSort.get_active_row_number();
|
|
|
- if (mode == 3) { m_RefListStore->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); return; }
|
|
|
- if (mode == 0) {
|
|
|
- m_RefListStore->set_sort_func(m_Columns.m_col_filename, [](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
|
|
|
- return NaturalSort()((*a)[ModelColumns().m_col_filename], (*b)[ModelColumns().m_col_filename]) ? -1 : 1;
|
|
|
- });
|
|
|
+ if (mode == 3) {
|
|
|
+ m_IconView.set_selection_mode(Gtk::SELECTION_MULTIPLE);
|
|
|
+ m_IconView.set_reorderable(true);
|
|
|
+ m_RefListStore->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
|
|
|
} else {
|
|
|
- m_RefListStore->set_sort_func(m_Columns.m_col_time, [mode](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
|
|
|
- long long ta = (*a)[ModelColumns().m_col_time], tb = (*b)[ModelColumns().m_col_time];
|
|
|
- return (mode == 1) ? (ta < tb ? -1 : 1) : (ta > tb ? -1 : 1);
|
|
|
- });
|
|
|
+ m_IconView.set_selection_mode(Gtk::SELECTION_NONE);
|
|
|
+ m_IconView.set_reorderable(false);
|
|
|
+ if (mode == 0) {
|
|
|
+ m_RefListStore->set_sort_func(m_Columns.m_col_filename, [this](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
|
|
|
+ return NaturalSort()((*a)[m_Columns.m_col_filename], (*b)[m_Columns.m_col_filename]) ? -1 : 1;
|
|
|
+ });
|
|
|
+ m_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING);
|
|
|
+ } else {
|
|
|
+ m_RefListStore->set_sort_func(m_Columns.m_col_time, [mode, this](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
|
|
|
+ long long ta = (*a)[m_Columns.m_col_time], tb = (*b)[m_Columns.m_col_time];
|
|
|
+ if (ta == tb) return 0;
|
|
|
+ return (mode == 1) ? (ta < tb ? -1 : 1) : (ta > tb ? -1 : 1);
|
|
|
+ });
|
|
|
+ m_RefListStore->set_sort_column(m_Columns.m_col_time, Gtk::SORT_ASCENDING);
|
|
|
+ }
|
|
|
}
|
|
|
- m_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING); update_preview();
|
|
|
+ Glib::signal_idle().connect_once([this](){ update_preview(); });
|
|
|
}
|
|
|
|
|
|
- void worker_thread_folder(std::string dir_path, int size) {
|
|
|
- std::vector<fs::path> files;
|
|
|
- try {
|
|
|
- for (const auto& entry : fs::directory_iterator(dir_path)) {
|
|
|
- if (m_stop_flag) return;
|
|
|
- std::string ext = entry.path().extension().string(); std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
|
|
|
- if (ext == ".jpg" || ext == ".png" || ext == ".jpeg" || ext == ".gif" || ext == ".bmp") files.push_back(entry.path());
|
|
|
- }
|
|
|
- } catch (...) { return; }
|
|
|
- std::sort(files.begin(), files.end(), [](const fs::path& a, const fs::path& b){ return NaturalSort()(a.filename().string(), b.filename().string()); });
|
|
|
- m_total_files = files.size(); m_processed_files = 0; process_files(files, size);
|
|
|
- }
|
|
|
-
|
|
|
- void process_files(const std::vector<fs::path>& files, int size) {
|
|
|
- for (const auto& path : files) {
|
|
|
+ void worker_thread_logic(std::vector<fs::path> paths, int sz, bool is_zoom_only) {
|
|
|
+ m_is_loading = true; m_total_files = paths.size(); m_processed_files = 0;
|
|
|
+ for (size_t idx = 0; idx < paths.size(); ++idx) {
|
|
|
if (m_stop_flag) break;
|
|
|
- LoadedItem item; item.path = path.string(); item.filename = path.filename().string();
|
|
|
- item.ext = path.extension().string(); if (!item.ext.empty() && item.ext[0] == '.') item.ext = item.ext.substr(1);
|
|
|
- std::transform(item.ext.begin(), item.ext.end(), item.ext.begin(), ::toupper);
|
|
|
- try { item.last_write_time = fs::last_write_time(path); item.filesize = fs::file_size(path); } catch(...) {}
|
|
|
+ const auto& p = paths[idx];
|
|
|
+ LoadedItem item; item.path = p.string(); item.is_update = is_zoom_only;
|
|
|
+ if (is_zoom_only) item.model_path = Gtk::TreePath(std::to_string(idx));
|
|
|
try {
|
|
|
- auto pixbuf = Gdk::Pixbuf::create_from_file(path.string());
|
|
|
- item.orig_w = pixbuf->get_width(); item.orig_h = pixbuf->get_height();
|
|
|
- float ratio = (float)item.orig_w / item.orig_h;
|
|
|
- int dw = (ratio > 1) ? size : size * ratio, dh = (ratio > 1) ? size / ratio : size;
|
|
|
- item.pixbuf = pixbuf->scale_simple(dw, dh, Gdk::INTERP_BILINEAR);
|
|
|
- { std::lock_guard<std::mutex> lock(m_QueueMutex); m_ResultQueue.push_back(item); }
|
|
|
- m_processed_files++; m_Dispatcher.emit(); std::this_thread::sleep_for(std::chrono::microseconds(100));
|
|
|
+ auto pb = Gdk::Pixbuf::create_from_file(p.string());
|
|
|
+ float r = (float)pb->get_width() / pb->get_height();
|
|
|
+ int dw = (r > 1) ? sz : sz * r, dh = (r > 1) ? sz / r : sz;
|
|
|
+ item.pixbuf = pb->scale_simple(dw, dh, Gdk::INTERP_BILINEAR);
|
|
|
+ if (!is_zoom_only) {
|
|
|
+ item.filename = p.filename().string();
|
|
|
+ item.ext = p.extension().string(); if (!item.ext.empty() && item.ext[0] == '.') item.ext = item.ext.substr(1);
|
|
|
+ std::transform(item.ext.begin(), item.ext.end(), item.ext.begin(), ::toupper);
|
|
|
+ item.orig_w = pb->get_width(); item.orig_h = pb->get_height();
|
|
|
+ item.last_write_time = fs::last_write_time(p); item.filesize = fs::file_size(p);
|
|
|
+ }
|
|
|
+ { std::lock_guard<std::mutex> l(m_QueueMutex); m_ResultQueue.push_back(item); }
|
|
|
+ m_processed_files++; m_Dispatcher.emit();
|
|
|
+ std::this_thread::sleep_for(std::chrono::microseconds(50));
|
|
|
} catch (...) { m_processed_files++; }
|
|
|
}
|
|
|
- m_total_files = 0; m_Dispatcher.emit();
|
|
|
+ m_is_loading = false; m_total_files = 0; m_Dispatcher.emit();
|
|
|
}
|
|
|
|
|
|
void on_worker_notification() {
|
|
|
- std::lock_guard<std::mutex> lock(m_QueueMutex);
|
|
|
+ std::lock_guard<std::mutex> l(m_QueueMutex);
|
|
|
while (!m_ResultQueue.empty()) {
|
|
|
- LoadedItem item = m_ResultQueue.front(); m_ResultQueue.pop_front();
|
|
|
- auto row = *(m_RefListStore->append());
|
|
|
- row[m_Columns.m_col_path] = item.path; row[m_Columns.m_col_filename] = item.filename;
|
|
|
- row[m_Columns.m_col_pixbuf] = item.pixbuf; row[m_Columns.m_col_checked] = true;
|
|
|
- auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(item.last_write_time - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
|
|
- std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
|
|
|
- std::stringstream ss; ss << std::put_time(std::localtime(&tt), "%H:%M %d-%b-%Y");
|
|
|
- row[m_Columns.m_col_info_str] = item.ext + " | " + std::to_string(item.orig_w) + "x" + std::to_string(item.orig_h) + " | " + format_size(item.filesize) + " | " + ss.str();
|
|
|
- row[m_Columns.m_col_time] = sctp.time_since_epoch().count();
|
|
|
+ LoadedItem i = m_ResultQueue.front(); m_ResultQueue.pop_front();
|
|
|
+ if (i.is_update) {
|
|
|
+ auto it = m_RefListStore->get_iter(i.model_path);
|
|
|
+ if (it) (*it)[m_Columns.m_col_pixbuf] = i.pixbuf;
|
|
|
+ } else {
|
|
|
+ auto r = *(m_RefListStore->append());
|
|
|
+ r[m_Columns.m_col_path] = i.path; r[m_Columns.m_col_filename] = i.filename;
|
|
|
+ r[m_Columns.m_col_pixbuf] = i.pixbuf; r[m_Columns.m_col_checked] = true;
|
|
|
+ auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(i.last_write_time - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
|
|
+ std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
|
|
|
+ std::stringstream ss; ss << std::put_time(std::localtime(&tt), "%H:%M %d-%b-%Y");
|
|
|
+ r[m_Columns.m_col_info_str] = i.ext + " | " + std::to_string(i.orig_w) + "x" + std::to_string(i.orig_h) + " | " + format_size(i.filesize) + " | " + ss.str();
|
|
|
+ r[m_Columns.m_col_time] = sctp.time_since_epoch().count();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (m_total_files > 0) {
|
|
|
+ m_ProgressBar.set_fraction(std::min(1.0, (double)m_processed_files / m_total_files));
|
|
|
+ m_ProgressBar.show();
|
|
|
+ m_BtnStopReload.set_label("Stop Loading"); m_BtnStopReload.set_sensitive(true);
|
|
|
+ } else {
|
|
|
+ m_ProgressBar.hide();
|
|
|
+ m_BtnStopReload.set_label("Reload Folder");
|
|
|
+ m_BtnStopReload.set_sensitive(!m_current_path.empty());
|
|
|
}
|
|
|
- if (m_total_files > 0) { m_ProgressBar.set_fraction(std::min(1.0, (double)m_processed_files / m_total_files)); m_ProgressBar.show(); m_BtnStop.set_sensitive(true); }
|
|
|
- else { m_ProgressBar.hide(); m_BtnStop.set_sensitive(false); }
|
|
|
- if (!m_RefListStore->children().empty()) update_preview();
|
|
|
+ if (m_total_files == 0) update_preview();
|
|
|
}
|
|
|
|
|
|
void on_size_changed() {
|
|
|
- std::vector<fs::path> current; for(auto row : m_RefListStore->children()) current.push_back(fs::path((std::string)row[m_Columns.m_col_path]));
|
|
|
- if (current.empty()) return;
|
|
|
+ if (m_RefListStore->children().empty()) return;
|
|
|
m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join();
|
|
|
- m_RefListStore->clear();
|
|
|
int sz = (m_ComboSize.get_active_row_number() == 0) ? 100 : (m_ComboSize.get_active_row_number() == 2 ? 450 : 220);
|
|
|
- m_IconView.set_item_width(sz); m_total_files = current.size(); m_processed_files = 0; m_stop_flag = false;
|
|
|
- m_WorkerThread = std::thread(&RenamerWindow::process_files, this, current, sz);
|
|
|
+ m_IconView.set_item_width(sz);
|
|
|
+ std::vector<fs::path> paths;
|
|
|
+ for(auto r : m_RefListStore->children()) paths.push_back(fs::path((std::string)r[m_Columns.m_col_path]));
|
|
|
+ m_stop_flag = false;
|
|
|
+ m_WorkerThread = std::thread(&RenamerWindow::worker_thread_logic, this, paths, sz, true);
|
|
|
}
|
|
|
|
|
|
- void start_loading_folder(std::string path) {
|
|
|
- m_current_path = path; m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join();
|
|
|
+ void start_loading_folder(std::string p) {
|
|
|
+ m_current_path = p; m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join();
|
|
|
m_RefListStore->clear();
|
|
|
+ std::vector<fs::path> f;
|
|
|
+ try {
|
|
|
+ for (const auto& e : fs::directory_iterator(p)) {
|
|
|
+ std::string x = e.path().extension().string(); std::transform(x.begin(), x.end(), x.begin(), ::tolower);
|
|
|
+ if (x == ".jpg" || x == ".png" || x == ".jpeg" || x == ".gif" || x == ".bmp") f.push_back(e.path());
|
|
|
+ }
|
|
|
+ } catch (...) {}
|
|
|
+ std::sort(f.begin(), f.end(), [](const fs::path& a, const fs::path& b){ return NaturalSort()(a.filename().string(), b.filename().string()); });
|
|
|
int sz = (m_ComboSize.get_active_row_number() == 0) ? 100 : (m_ComboSize.get_active_row_number() == 2 ? 450 : 220);
|
|
|
m_IconView.set_item_width(sz); m_stop_flag = false;
|
|
|
- m_WorkerThread = std::thread(&RenamerWindow::worker_thread_folder, this, path, sz);
|
|
|
+ m_WorkerThread = std::thread(&RenamerWindow::worker_thread_logic, this, f, sz, false);
|
|
|
+ m_BtnStopReload.set_sensitive(true);
|
|
|
}
|
|
|
|
|
|
void on_open_folder_clicked() {
|
|
|
- Gtk::FileChooserDialog d(*this, "Select Folder", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
|
|
|
+ Gtk::FileChooserDialog d(*this, "Select Image Folder", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
|
|
|
d.add_button("Cancel", Gtk::RESPONSE_CANCEL); d.add_button("Select", Gtk::RESPONSE_OK);
|
|
|
if (d.run() == Gtk::RESPONSE_OK) start_loading_folder(d.get_filename());
|
|
|
}
|
|
|
|
|
|
- void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int, int, const Gtk::SelectionData& sd, guint, guint t) {
|
|
|
- auto uris = sd.get_uris();
|
|
|
+ void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& c, int, int, const Gtk::SelectionData& s, guint, guint t) {
|
|
|
+ auto uris = s.get_uris();
|
|
|
if (!uris.empty()) {
|
|
|
- std::string fn = Glib::filename_from_uri(uris[0]);
|
|
|
- start_loading_folder(fs::is_directory(fn) ? fn : fs::path(fn).parent_path().string());
|
|
|
- context->drag_finish(true, false, t);
|
|
|
- } else context->drag_finish(false, false, t);
|
|
|
+ std::string f = Glib::filename_from_uri(uris[0]);
|
|
|
+ start_loading_folder(fs::is_directory(f) ? f : fs::path(f).parent_path().string());
|
|
|
+ c->drag_finish(true, false, t);
|
|
|
+ } else c->drag_finish(false, false, t);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
|
Glib::thread_init();
|
|
|
- auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_v01");
|
|
|
+ auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_v02");
|
|
|
RenamerWindow window;
|
|
|
return app->run(window);
|
|
|
}
|