#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; // --- Natural Sort Helper --- struct NaturalSort { bool operator()(const std::string& a, const std::string& b) const { size_t i = 0, j = 0; while (i < a.length() && j < b.length()) { if (isdigit(a[i]) && isdigit(b[j])) { size_t s1 = i, s2 = j; while (i < a.length() && isdigit(a[i])) i++; while (j < b.length() && isdigit(b[j])) j++; try { long n1 = std::stol(a.substr(s1, i - s1)); long n2 = std::stol(b.substr(s2, j - s2)); if (n1 != n2) return n1 < n2; } catch (...) { if (a.substr(s1, i - s1) != b.substr(s2, j - s2)) return a.substr(s1, i - s1) < b.substr(s2, j - s2); } } else { if (a[i] != b[j]) return a[i] < b[j]; i++; j++; } } return a.length() < b.length(); } }; class CellRendererFrame : public Gtk::CellRendererPixbuf { public: CellRendererFrame() { property_xpad() = 10; property_ypad() = 10; } protected: void render_vfunc(const Cairo::RefPtr& cr, Gtk::Widget& widget, const Gdk::Rectangle&, const Gdk::Rectangle& cell_area, Gtk::CellRendererState flags) override { cr->save(); auto style = widget.get_style_context(); Gdk::RGBA bg = style->get_background_color(); if ((bg.get_red() + bg.get_green() + bg.get_blue()) / 3.0 > 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); 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(); Gtk::CellRendererPixbuf::render_vfunc(cr, widget, cell_area, cell_area, flags); } }; struct RenameAction { std::string original_path; std::string new_path; }; struct UndoStep { std::vector moves; }; struct LoadedItem { std::string path, filename, ext; std::string sort_time_str; long long sort_time_raw; Glib::RefPtr pixbuf; int orig_w = 0, orig_h = 0; uintmax_t filesize = 0; bool is_zoom_update = false; Gtk::TreeModel::Path model_path; }; class RenamerWindow : public Gtk::Window { public: RenamerWindow() : m_Dispatcher() { set_title("Simple Image Renamer 1.1"); set_default_size(1280, 850); 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 --- 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); m_BtnOpen.set_label("Open Folder"); 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_ToolbarTop.pack_start(*Gtk::manage(new Gtk::Label(" Sort: ")), Gtk::PACK_SHRINK); m_ComboSort.append("Manual (Drag & Drop)"); m_ComboSort.append("Name"); m_ComboSort.append("Oldest (EXIF/File)"); m_ComboSort.append("Newest (EXIF/File)"); 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); // --- Zoom Slider --- m_ToolbarTop.pack_start(*Gtk::manage(new Gtk::Label(" Zoom: ")), Gtk::PACK_SHRINK); m_AdjZoom = Gtk::Adjustment::create(220, 60, 600, 10, 50, 0); m_ScaleZoom.set_adjustment(m_AdjZoom); m_ScaleZoom.set_draw_value(false); m_ScaleZoom.set_size_request(150, -1); m_ScaleZoom.signal_value_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_size_changed)); m_ToolbarTop.pack_start(m_ScaleZoom, Gtk::PACK_SHRINK, 5); m_BtnUndo.set_label("Undo 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 --- 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("Sequential"); m_ComboMode.append("Replace"); m_ComboMode.append("Change Date"); 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 Pattern Mode --- 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_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 Mode --- m_EntryFind.set_placeholder_text("Find string"); m_EntryFind.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview)); m_BoxReplace.pack_start(*Gtk::manage(new Gtk::Label("Find:")), Gtk::PACK_SHRINK, 5); m_BoxReplace.pack_start(m_EntryFind, Gtk::PACK_EXPAND_WIDGET, 5); m_EntryReplace.set_placeholder_text("Replacement string"); m_EntryReplace.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview)); m_BoxReplace.pack_start(*Gtk::manage(new Gtk::Label("Replace:")), Gtk::PACK_SHRINK, 5); m_BoxReplace.pack_start(m_EntryReplace, Gtk::PACK_EXPAND_WIDGET, 5); m_StackModes.add(m_BoxReplace, "Replace"); // --- Change Date Mode --- m_EntryDate.set_placeholder_text("YYYY-MM-DD HH:MM:SS (Empty = 1970)"); m_EntryDate.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview)); m_BoxDate.pack_start(*Gtk::manage(new Gtk::Label("Target Date:")), Gtk::PACK_SHRINK, 5); m_BoxDate.pack_start(m_EntryDate, Gtk::PACK_EXPAND_WIDGET, 5); m_StackModes.add(m_BoxDate, "Date"); m_ToolbarControls.pack_start(m_StackModes, Gtk::PACK_EXPAND_WIDGET, 5); 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); // --- Main View --- m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS); m_VBox.pack_start(m_ScrolledWindow); m_RefListStore = Gtk::ListStore::create(m_Columns); m_RefListStore->signal_row_deleted().connect([this](const Gtk::TreeModel::Path&){ update_preview(); }); m_IconView.set_model(m_RefListStore); m_IconView.set_reorderable(true); 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_spacing(20); m_IconView.signal_item_activated().connect(sigc::mem_fun(*this, &RenamerWindow::on_item_activated)); m_MenuItemSelectAll.set_label("Select All"); m_MenuItemSelectAll.signal_activate().connect([this](){ on_selection_change(true); }); m_MenuPopup.append(m_MenuItemSelectAll); m_MenuItemSelectNone.set_label("Select None"); m_MenuItemSelectNone.signal_activate().connect([this](){ on_selection_change(false); }); m_MenuPopup.append(m_MenuItemSelectNone); m_MenuPopup.append(*Gtk::manage(new Gtk::SeparatorMenuItem())); m_MenuItemOpenWith.set_label("Open With..."); m_MenuItemOpenWith.signal_activate().connect(sigc::mem_fun(*this, &RenamerWindow::on_open_with_clicked)); m_MenuPopup.append(m_MenuItemOpenWith); m_MenuItemTerminal.set_label("Open Terminal Here"); m_MenuItemTerminal.signal_activate().connect([this](){ launch_terminal(); }); m_MenuPopup.append(m_MenuItemTerminal); m_MenuPopup.append(*Gtk::manage(new Gtk::SeparatorMenuItem())); m_MenuItemDelete.set_label("Move to Trash"); m_MenuItemDelete.signal_activate().connect(sigc::mem_fun(*this, &RenamerWindow::on_delete_selected)); m_MenuPopup.append(m_MenuItemDelete); m_MenuPopup.show_all(); m_MenuPopup.attach_to_widget(m_IconView); m_IconView.signal_button_press_event().connect(sigc::mem_fun(*this, &RenamerWindow::on_iconview_button_press), false); m_ScrolledWindow.add(m_IconView); m_VBox.pack_start(m_ProgressBar, Gtk::PACK_SHRINK); m_VBox.pack_end(m_Statusbar, Gtk::PACK_SHRINK); this->signal_key_press_event().connect(sigc::mem_fun(*this, &RenamerWindow::on_window_key_press), false); show_all_children(); m_ProgressBar.hide(); } ~RenamerWindow() { m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join(); } protected: Gtk::Box m_VBox, m_ToolbarTop, m_ToolbarControls, m_BoxPattern, m_BoxReplace, m_BoxDate; Gtk::Frame m_FrameControls; Gtk::Button m_BtnOpen, m_BtnRename, m_BtnUndo; Gtk::Entry m_EntryPattern, m_EntryFind, m_EntryReplace, m_EntryDate; Gtk::ComboBoxText m_ComboSort, m_ComboMode; Gtk::Scale m_ScaleZoom; Gtk::SpinButton m_SpinStartNum; Glib::RefPtr m_AdjStartNum, m_AdjZoom; Gtk::Stack m_StackModes; Gtk::ScrolledWindow m_ScrolledWindow; Gtk::IconView m_IconView; Gtk::ProgressBar m_ProgressBar; Gtk::Statusbar m_Statusbar; CellRendererFrame m_cell_frame; Gtk::CellRendererText m_cell_text; Gtk::CellRendererToggle m_cell_toggle; Gtk::Menu m_MenuPopup; Gtk::MenuItem m_MenuItemSelectAll, m_MenuItemSelectNone, m_MenuItemDelete, m_MenuItemOpenWith, m_MenuItemTerminal; 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 m_col_path, m_col_filename, m_col_markup, m_col_info_str; Gtk::TreeModelColumn> m_col_pixbuf; Gtk::TreeModelColumn m_col_checked; Gtk::TreeModelColumn m_col_time; } m_Columns; Glib::RefPtr m_RefListStore; std::string m_current_path; std::thread m_WorkerThread; std::atomic m_stop_flag{false}; std::atomic m_total_files{0}, m_processed_files{0}; Glib::Dispatcher m_Dispatcher; std::mutex m_QueueMutex; std::deque m_ResultQueue; std::stack m_UndoStack; void launch_terminal() { if (m_current_path.empty()) return; std::string cmd = "gnome-terminal --working-directory='" + m_current_path + "'"; std::vector> no_files; Gio::AppInfo::create_from_commandline(cmd, "Terminal", Gio::APP_INFO_CREATE_NONE)->launch(no_files); } bool on_window_key_press(GdkEventKey* event) { if ((event->state & GDK_CONTROL_MASK)) { if (event->keyval == GDK_KEY_o) { on_open_folder_clicked(); return true; } if (event->keyval == GDK_KEY_z) { on_undo_clicked(); return true; } if (event->keyval == GDK_KEY_e) { on_open_with_clicked(); return true; } if (event->keyval == GDK_KEY_t) { launch_terminal(); return true; } } if (event->keyval == GDK_KEY_Delete) { on_delete_selected(); return true; } if (event->keyval == GDK_KEY_Return) { on_rename_execute(); return true; } return false; } void on_item_activated(const Gtk::TreeModel::Path& path) { auto it = m_RefListStore->get_iter(path); if (it) Gio::AppInfo::launch_default_for_uri("file://" + (std::string)(*it)[m_Columns.m_col_path]); } void on_open_with_clicked() { for (auto row : m_RefListStore->children()) if (row[m_Columns.m_col_checked]) { Gtk::AppChooserDialog dialog(Gio::File::create_for_path((std::string)row[m_Columns.m_col_path]), *this); if (dialog.run() == Gtk::RESPONSE_OK) { std::vector> files = { Gio::File::create_for_path((std::string)row[m_Columns.m_col_path]) }; dialog.get_app_info()->launch(files); } break; } } void on_delete_selected() { Gtk::MessageDialog d(*this, "Move to Trash?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO); if (d.run() == Gtk::RESPONSE_YES) { for (auto it = m_RefListStore->children().begin(); it != m_RefListStore->children().end(); ) { if ((*it)[m_Columns.m_col_checked]) { Gio::File::create_for_path((std::string)(*it)[m_Columns.m_col_path])->trash(); it = m_RefListStore->erase(it); } else ++it; } } } void update_preview() { int mode = m_ComboMode.get_active_row_number(), start = m_SpinStartNum.get_value_as_int(), count = 0; std::string pat = m_EntryPattern.get_text(); size_t hpos = pat.find('#'); int pad = (hpos != std::string::npos) ? (pat.find_last_of('#') - hpos + 1) : 0; std::set seen; for (auto row : m_RefListStore->children()) { std::string orig = row[m_Columns.m_col_filename], next = orig; if (mode == 2 && row[m_Columns.m_col_checked]) { std::string input = m_EntryDate.get_text(); if (input.empty()) input = "1970-01-01 00:00:00"; std::stringstream mu; mu << "" << Glib::Markup::escape_text(orig) << "\n"; mu << "DATE INJECTION: " << Glib::Markup::escape_text(input) << ""; row[m_Columns.m_col_markup] = mu.str(); continue; } if (row[m_Columns.m_col_checked]) { if (mode == 0 && !pat.empty()) { 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(); } else if (mode == 1 && !m_EntryFind.get_text().empty()) { 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()); } } } std::stringstream mu; mu << ""; if (next != orig && seen.count(next)) mu << "CONFLICT: " << Glib::Markup::escape_text(next) << ""; else if (next != orig) mu << "" << Glib::Markup::escape_text(next) << ""; else mu << "" << Glib::Markup::escape_text(orig) << ""; mu << "\n" << Glib::Markup::escape_text((std::string)row[m_Columns.m_col_info_str]) << ""; row[m_Columns.m_col_markup] = mu.str(); seen.insert(next); } } void on_rename_execute() { int mode = m_ComboMode.get_active_row_number(); // Handle Date Application logic if (mode == 2) { std::string input = m_EntryDate.get_text(); if (input.empty()) input = "1970-01-01 00:00:00"; struct tm tm = {}; if (!strptime(input.c_str(), "%Y-%m-%d %H:%M:%S", &tm)) { m_Statusbar.push("Error: Invalid date format. Use YYYY-MM-DD HH:MM:SS"); return; } time_t target_epoch = mktime(&tm); char exif_buf[32]; strftime(exif_buf, sizeof(exif_buf), "%Y:%m:%d %H:%M:%S", &tm); std::string exif_date = exif_buf; for (auto row : m_RefListStore->children()) { if (row[m_Columns.m_col_checked]) { std::string file_path = (std::string)row[m_Columns.m_col_path]; // Rewrite EXIF via exiv2 try { auto img = Exiv2::ImageFactory::open(file_path); if (img->good()) { img->readMetadata(); Exiv2::ExifData &exifData = img->exifData(); exifData["Exif.Image.DateTime"] = exif_date; exifData["Exif.Photo.DateTimeOriginal"] = exif_date; exifData["Exif.Photo.DateTimeDigitized"] = exif_date; img->setExifData(exifData); img->writeMetadata(); } } catch (...) { // Fallthrough, skip EXIF if format doesn't support it or is violently corrupted } // Rewrite Filesystem mtime and atime struct utimbuf new_times; new_times.actime = target_epoch; new_times.modtime = target_epoch; utime(file_path.c_str(), &new_times); } } m_Statusbar.push("Dates Updated (EXIF + FS)."); start_loading_folder(m_current_path); // Refresh UI to pull new metadata return; } // Standard Rename Logic UndoStep step; int start = m_SpinStartNum.get_value_as_int(), count = 0; std::string pat = m_EntryPattern.get_text(); size_t hpos = pat.find('#'); int pad = (hpos != std::string::npos) ? (pat.find_last_of('#') - hpos + 1) : 0; for (auto row : m_RefListStore->children()) if (row[m_Columns.m_col_checked]) { std::string orig = row[m_Columns.m_col_filename], next = orig; if (mode == 0) { 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(); } else if (mode == 1) { 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()); } } std::string final_name = next; int res = 1; while (fs::exists(fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / final_name)) final_name = fs::path(next).stem().string() + "_" + std::to_string(res++) + fs::path(next).extension().string(); if (final_name != orig) { fs::rename((std::string)row[m_Columns.m_col_path], fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / final_name); step.moves.push_back({(std::string)row[m_Columns.m_col_path], (fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / final_name).string()}); } } m_UndoStack.push(step); m_BtnUndo.set_sensitive(true); m_Statusbar.push("Rename Finished."); 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()); for (const auto& m : last.moves) if (fs::exists(m.new_path)) fs::rename(m.new_path, m.original_path); start_loading_folder(m_current_path); } void on_sort_changed() { int s = m_ComboSort.get_active_row_number(); if (s == 0) m_RefListStore->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); else if (s == 1) m_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING); else m_RefListStore->set_sort_column(m_Columns.m_col_time, (s == 2 ? Gtk::SORT_ASCENDING : Gtk::SORT_DESCENDING)); update_preview(); } void on_worker_notification() { std::lock_guard l(m_QueueMutex); while (!m_ResultQueue.empty()) { LoadedItem i = m_ResultQueue.front(); m_ResultQueue.pop_front(); if (i.is_zoom_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; r[m_Columns.m_col_info_str] = i.sort_time_str + " | " + std::to_string(i.orig_w) + "x" + std::to_string(i.orig_h) + " | " + std::to_string(i.filesize/1024) + "KB"; r[m_Columns.m_col_time] = i.sort_time_raw; } m_processed_files++; } if (m_total_files > 0) { m_ProgressBar.show(); m_ProgressBar.set_fraction(std::min(1.0, (double)m_processed_files / m_total_files)); if (m_processed_files >= m_total_files) { m_ProgressBar.hide(); m_total_files = 0; m_processed_files = 0; } } update_preview(); } void worker_thread_logic(std::vector paths, int sz, bool is_zoom) { for (size_t idx = 0; idx < paths.size(); ++idx) { if (m_stop_flag) break; LoadedItem item; item.path = paths[idx].string(); item.is_zoom_update = is_zoom; if (is_zoom) item.model_path = Gtk::TreePath(std::to_string(idx)); try { auto pb = Gdk::Pixbuf::create_from_file(paths[idx].string()); item.pixbuf = pb->scale_simple(sz, sz * pb->get_height() / pb->get_width(), Gdk::INTERP_BILINEAR); if (!is_zoom) { item.filename = paths[idx].filename().string(); item.orig_w = pb->get_width(); item.orig_h = pb->get_height(); item.filesize = fs::file_size(paths[idx]); try { auto img = Exiv2::ImageFactory::open(paths[idx].string()); img->readMetadata(); auto tag = img->exifData().findKey(Exiv2::ExifKey("Exif.Photo.DateTimeDigitized")); if (tag != img->exifData().end()) { item.sort_time_str = tag->toString(); std::string c = item.sort_time_str; c.erase(std::remove_if(c.begin(), c.end(), [](char x){return !isdigit(x);}), c.end()); item.sort_time_raw = std::stoll(c); } else throw std::runtime_error(""); } catch (...) { item.sort_time_raw = fs::last_write_time(paths[idx]).time_since_epoch().count(); item.sort_time_str = "File Date"; } } { std::lock_guard l(m_QueueMutex); m_ResultQueue.push_back(item); } m_Dispatcher.emit(); } catch (...) {} } } void start_loading_folder(std::string p) { m_current_path = p; m_RefListStore->clear(); std::vector f; for (const auto& e : fs::directory_iterator(p)) { std::string ext = e.path().extension().string(); std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower); if (ext == ".jpg" || ext == ".png" || ext == ".jpeg" || ext == ".webp" || ext == ".tif" || ext == ".tiff") f.push_back(e.path()); } m_total_files = f.size(); m_processed_files = 0; if (m_WorkerThread.joinable()) { m_stop_flag = true; m_WorkerThread.join(); } m_stop_flag = false; m_WorkerThread = std::thread(&RenamerWindow::worker_thread_logic, this, f, (int)m_ScaleZoom.get_value(), false); } void on_size_changed() { if (m_current_path.empty()) return; int sz = (int)m_ScaleZoom.get_value(); m_IconView.set_item_width(sz); std::vector f; for (auto r : m_RefListStore->children()) f.push_back(fs::path((std::string)r[m_Columns.m_col_path])); m_total_files = f.size(); m_processed_files = 0; if (m_WorkerThread.joinable()) { m_stop_flag = true; m_WorkerThread.join(); } m_stop_flag = false; m_WorkerThread = std::thread(&RenamerWindow::worker_thread_logic, this, f, sz, true); } void on_open_folder_clicked() { Gtk::FileChooserDialog d(*this, "Open", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER); d.set_show_hidden(true); d.add_button("Cancel", Gtk::RESPONSE_CANCEL); d.add_button("Open", Gtk::RESPONSE_OK); if (d.run() == Gtk::RESPONSE_OK) start_loading_folder(d.get_filename()); } void on_selection_change(bool s) { for (auto row : m_RefListStore->children()) row[m_Columns.m_col_checked] = s; update_preview(); } void on_mode_changed() { int mode = m_ComboMode.get_active_row_number(); if (mode == 0) m_StackModes.set_visible_child("Pattern"); else if (mode == 1) m_StackModes.set_visible_child("Replace"); else if (mode == 2) m_StackModes.set_visible_child("Date"); m_BtnRename.set_label(mode == 2 ? "Apply Target Date (EXIF+FS)" : "Apply New Names"); 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(); } } bool on_iconview_button_press(GdkEventButton* e) { if (e->button == 3) { m_MenuPopup.popup(e->button, e->time); return true; } return false; } }; int main(int argc, char *argv[]) { auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer"); RenamerWindow window; return app->run(window); }