Forráskód Böngészése

Update 'src/main.cpp'

Nicole Portas 1 hónapja
szülő
commit
671a86a752
1 módosított fájl, 525 hozzáadás és 219 törlés
  1. 525 219
      src/main.cpp

+ 525 - 219
src/main.cpp

@@ -8,133 +8,227 @@
 #include <atomic>
 #include <mutex>
 #include <deque>
+#include <stack>
+#include <map>
 
 namespace fs = std::filesystem;
 
 // --- Natural Sort Helper ---
 struct NaturalSort {
-    bool operator()(const fs::path& a, const fs::path& b) const {
-        std::string s1 = a.filename().string();
-        std::string s2 = b.filename().string();
-        
+    bool operator()(const std::string& a, const std::string& b) const {
         size_t i = 0, j = 0;
-        while (i < s1.length() && j < s2.length()) {
-            if (isdigit(s1[i]) && isdigit(s2[j])) {
+        while (i < a.length() && j < b.length()) {
+            if (isdigit(a[i]) && isdigit(b[j])) {
                 size_t start1 = i, start2 = j;
-                while (i < s1.length() && isdigit(s1[i])) i++;
-                while (j < s2.length() && isdigit(s2[j])) j++;
+                while (i < a.length() && isdigit(a[i])) i++;
+                while (j < b.length() && isdigit(b[j])) j++;
                 
-                long n1 = std::stol(s1.substr(start1, i - start1));
-                long n2 = std::stol(s2.substr(start2, j - start2));
+                long n1 = std::stol(a.substr(start1, i - start1));
+                long n2 = std::stol(b.substr(start2, j - start2));
                 
                 if (n1 != n2) return n1 < n2;
             } else {
-                if (s1[i] != s2[j]) return s1[i] < s2[j];
+                if (a[i] != b[j]) return a[i] < b[j];
                 i++; j++;
             }
         }
-        return s1.length() < s2.length();
+        return a.length() < b.length();
     }
 };
 
-// --- Data Structure for Thread Communication ---
+// --- Undo History Structure ---
+struct RenameAction {
+    std::string original_path;
+    std::string new_path;
+};
+struct UndoStep {
+    std::vector<RenameAction> moves;
+};
+
+// --- Data for Worker Thread ---
 struct LoadedItem {
     std::string path;
     std::string filename;
+    fs::file_time_type last_write_time;
     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
 };
 
 class RenamerWindow : public Gtk::Window {
 public:
     RenamerWindow() : m_Dispatcher() {
-        set_title("Visual Renamer Pro");
-        set_default_size(1200, 800);
+        set_title("Simple Image Renamer 0.1");
+        set_default_size(1280, 850);
         
         m_Dispatcher.connect(sigc::mem_fun(*this, &RenamerWindow::on_worker_notification));
 
-        // --- Layout ---
+        // --- Main Layout ---
         m_VBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
         add(m_VBox);
 
-        // --- Toolbar ---
-        m_Toolbar.set_margin_top(10); m_Toolbar.set_margin_bottom(10);
-        m_Toolbar.set_margin_left(10); m_Toolbar.set_margin_right(10);
-        m_VBox.pack_start(m_Toolbar, Gtk::PACK_SHRINK);
+        // ===========================
+        // 1. TOP TOOLBAR (File Ops)
+        // ===========================
+        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);
 
-        // Open Button
+        // Open Folder
         Gtk::Image* icon_open = Gtk::manage(new Gtk::Image);
         icon_open->set_from_icon_name("folder-open", Gtk::ICON_SIZE_BUTTON);
         m_BtnOpen.set_image(*icon_open);
         m_BtnOpen.set_label("Open Folder");
         m_BtnOpen.set_always_show_image(true);
         m_BtnOpen.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_open_folder_clicked));
-        m_Toolbar.pack_start(m_BtnOpen, Gtk::PACK_SHRINK, 5);
-
-        // Pattern Inputs
-        m_LblPattern.set_text("  Pattern:");
-        m_Toolbar.pack_start(m_LblPattern, Gtk::PACK_SHRINK, 5);
+        m_ToolbarTop.pack_start(m_BtnOpen, Gtk::PACK_SHRINK, 5);
+
+        // Sort Combo
+        m_LblSort.set_text("Sort:");
+        m_ToolbarTop.pack_start(m_LblSort, Gtk::PACK_SHRINK, 5);
+        m_ComboSort.append("Name (Natural)");
+        m_ComboSort.append("Date (Oldest First)");
+        m_ComboSort.append("Date (Newest First)");
+        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);
+
+        // Size Combo
+        m_LblSize.set_text("View:");
+        m_ToolbarTop.pack_start(m_LblSize, Gtk::PACK_SHRINK, 5);
+        m_ComboSize.append("Tiny (100px)");
+        m_ComboSize.append("Normal (220px)");
+        m_ComboSize.append("Huge (450px)");
+        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);
+
+        // Undo Button
+        Gtk::Image* icon_undo = Gtk::manage(new Gtk::Image);
+        icon_undo->set_from_icon_name("edit-undo", Gtk::ICON_SIZE_BUTTON);
+        m_BtnUndo.set_image(*icon_undo);
+        m_BtnUndo.set_label("Undo");
+        m_BtnUndo.set_always_show_image(true);
+        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);
+
+        // ===========================
+        // 2. CONTROL TOOLBAR (Logic)
+        // ===========================
+        m_FrameControls.set_shadow_type(Gtk::SHADOW_ETCHED_IN);
+        m_FrameControls.set_margin_left(10); m_FrameControls.set_margin_right(10);
+        m_VBox.pack_start(m_FrameControls, Gtk::PACK_SHRINK);
+        
+        m_ToolbarControls.set_margin_top(10); m_ToolbarControls.set_margin_bottom(10);
+        m_ToolbarControls.set_margin_left(10); m_ToolbarControls.set_margin_right(10);
+        m_FrameControls.add(m_ToolbarControls);
+
+        // Mode Switcher (Pattern vs Find/Replace)
+        m_ComboMode.append("Numbering Pattern");
+        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);
+
+        // --- STACK FOR MODES ---
+        m_StackModes.set_transition_type(Gtk::STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
+        m_ToolbarControls.pack_start(m_StackModes, Gtk::PACK_EXPAND_WIDGET, 5);
+
+        // MODE 1: Pattern
+        m_BoxPattern.set_spacing(5);
+        m_LblPattern.set_text("Pattern:");
+        m_BoxPattern.pack_start(m_LblPattern, Gtk::PACK_SHRINK);
+        
         m_EntryPattern.set_text("image_###");
-        m_Toolbar.pack_start(m_EntryPattern, Gtk::PACK_SHRINK, 5);
+        m_EntryPattern.set_placeholder_text("example_###");
+        m_EntryPattern.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
+        m_BoxPattern.pack_start(m_EntryPattern, Gtk::PACK_EXPAND_WIDGET);
+
+        m_LblStartNum.set_text("Start #:");
+        m_BoxPattern.pack_start(m_LblStartNum, Gtk::PACK_SHRINK);
         
-        // --- SIZE DROPDOWN ---
-        m_LblSize.set_text("  Size:");
-        m_Toolbar.pack_start(m_LblSize, Gtk::PACK_SHRINK, 5);
+        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_ComboSize.append("Small (100px)");
-        m_ComboSize.append("Medium (250px)");
-        m_ComboSize.append("Large (500px)");
-        m_ComboSize.set_active(1); 
-        m_ComboSize.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_size_changed));
-        m_Toolbar.pack_start(m_ComboSize, Gtk::PACK_SHRINK, 5);
+        m_StackModes.add(m_BoxPattern, "Pattern");
+
+        // MODE 2: Find & Replace
+        m_BoxReplace.set_spacing(5);
+        m_LblFind.set_text("Find:");
+        m_BoxReplace.pack_start(m_LblFind, 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);
 
-        // Rename Button
-        m_BtnRename.set_label("Rename Files");
-        m_BtnRename.set_sensitive(false); 
-        m_BtnRename.get_style_context()->add_class("suggested-action"); 
-        m_BtnRename.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_rename_files));
-        m_Toolbar.pack_end(m_BtnRename, Gtk::PACK_SHRINK, 5);
+        m_LblReplace.set_text("Replace:");
+        m_BoxReplace.pack_start(m_LblReplace, 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);
 
-        // --- Icon View Area ---
+        m_StackModes.add(m_BoxReplace, "Replace");
+
+        // --- Action Buttons ---
+        m_BtnSelectAll.set_label("Select All");
+        m_BtnSelectAll.signal_clicked().connect([this](){ set_all_checked(true); });
+        m_ToolbarControls.pack_start(m_BtnSelectAll, Gtk::PACK_SHRINK, 5);
+
+        m_BtnSelectNone.set_label("None");
+        m_BtnSelectNone.signal_clicked().connect([this](){ set_all_checked(false); });
+        m_ToolbarControls.pack_start(m_BtnSelectNone, Gtk::PACK_SHRINK, 5);
+
+        m_BtnRename.set_label("Rename Selected");
+        m_BtnRename.set_sensitive(false);
+        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);
+
+        // ===========================
+        // 3. MAIN VIEW
+        // ===========================
         m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
         m_ScrolledWindow.set_shadow_type(Gtk::SHADOW_IN);
         m_VBox.pack_start(m_ScrolledWindow);
 
         m_RefListStore = Gtk::ListStore::create(m_Columns);
         m_IconView.set_model(m_RefListStore);
-        m_IconView.set_text_column(m_Columns.m_col_name);
-        m_IconView.set_pixbuf_column(m_Columns.m_col_pixbuf);
         
-        m_IconView.set_item_width(250); 
+        // Manual Packing for Layout
+        m_IconView.pack_start(m_cell_pixbuf, false);
+        m_IconView.add_attribute(m_cell_pixbuf, "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);
+        // We use 'markup' for rich text (Blue preview)
+        m_IconView.add_attribute(m_cell_text, "markup", m_Columns.m_col_markup);
+        m_cell_text.property_ellipsize() = Pango::ELLIPSIZE_MIDDLE;
+
+        m_IconView.set_item_width(220);
         m_IconView.set_selection_mode(Gtk::SELECTION_MULTIPLE);
-        m_IconView.set_reorderable(true);
         m_IconView.set_margin(10);
         m_IconView.set_column_spacing(10);
-        m_IconView.set_row_spacing(10);
-        
-        m_IconView.set_tooltip_column(m_Columns.m_col_orig_name.index());
-
-        m_IconView.signal_button_press_event().connect(sigc::mem_fun(*this, &RenamerWindow::on_iconview_button_press), false);
-        m_IconView.signal_key_press_event().connect(sigc::mem_fun(*this, &RenamerWindow::on_iconview_key_press), false);
+        m_IconView.set_row_spacing(15);
+        m_IconView.set_tooltip_column(m_Columns.m_col_path.index());
 
         m_ScrolledWindow.add(m_IconView);
 
-        // --- Status Bar ---
-        m_Statusbar.push("Ready.");
+        // ===========================
+        // 4. STATUS BAR
+        // ===========================
+        m_Statusbar.push("Welcome to Simple Image Renamer 0.1");
         m_VBox.pack_end(m_Statusbar, Gtk::PACK_SHRINK);
 
-        // --- Drag and Drop ---
-        std::vector<Gtk::TargetEntry> listTargets;
-        listTargets.push_back(Gtk::TargetEntry("text/uri-list"));
+        // --- Drag & Drop ---
+        std::vector<Gtk::TargetEntry> listTargets = { Gtk::TargetEntry("text/uri-list") };
         drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY);
         signal_drag_data_received().connect(sigc::mem_fun(*this, &RenamerWindow::on_drag_data_received));
 
-        // --- Context Menu ---
-        auto item = Gtk::manage(new Gtk::MenuItem("Remove Selected"));
-        item->signal_activate().connect(sigc::mem_fun(*this, &RenamerWindow::on_menu_remove));
-        m_ContextMenu.append(*item);
-        m_ContextMenu.show_all();
-
         show_all_children();
+        // Hide the non-active mode initially
+        m_StackModes.set_visible_child("Pattern");
     }
 
     ~RenamerWindow() override {
@@ -143,194 +237,396 @@ public:
     }
 
 protected:
-    Gtk::Box m_VBox, m_Toolbar;
-    Gtk::Button m_BtnOpen, m_BtnRename;
-    Gtk::Label m_LblPattern, m_LblSize;
-    Gtk::Entry m_EntryPattern;
-    Gtk::ComboBoxText m_ComboSize;
+    // UI Elements
+    Gtk::Box m_VBox, m_ToolbarTop, m_ToolbarControls;
+    Gtk::Box m_BoxPattern, m_BoxReplace;
+    Gtk::Frame m_FrameControls;
+    Gtk::Button m_BtnOpen, m_BtnRename, m_BtnSelectAll, m_BtnSelectNone, m_BtnUndo;
+    Gtk::Label m_LblSort, m_LblSize, m_LblPattern, m_LblStartNum, m_LblFind, m_LblReplace;
+    Gtk::Entry m_EntryPattern, m_EntryFind, m_EntryReplace;
+    Gtk::ComboBoxText m_ComboSize, m_ComboSort, m_ComboMode;
+    Gtk::SpinButton m_SpinStartNum;
+    Glib::RefPtr<Gtk::Adjustment> m_AdjStartNum;
+    Gtk::Stack m_StackModes;
+    
     Gtk::ScrolledWindow m_ScrolledWindow;
     Gtk::IconView m_IconView;
     Gtk::Statusbar m_Statusbar;
-    Gtk::Menu m_ContextMenu;
 
+    // Renderers
+    Gtk::CellRendererPixbuf m_cell_pixbuf;
+    Gtk::CellRendererText m_cell_text;
+    Gtk::CellRendererToggle m_cell_toggle;
+
+    // Model
     class ModelColumns : public Gtk::TreeModel::ColumnRecord {
     public:
-        ModelColumns() { add(m_col_path); add(m_col_name); add(m_col_pixbuf); add(m_col_orig_name); }
+        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);
+        }
         Gtk::TreeModelColumn<std::string> m_col_path;
-        Gtk::TreeModelColumn<std::string> m_col_name;
+        Gtk::TreeModelColumn<std::string> m_col_filename;
         Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_col_pixbuf;
-        Gtk::TreeModelColumn<std::string> m_col_orig_name;
+        Gtk::TreeModelColumn<bool> m_col_checked;
+        Gtk::TreeModelColumn<std::string> m_col_markup; // Shows "Old -> New"
+        Gtk::TreeModelColumn<long long> m_col_time; // For sorting
     };
     ModelColumns m_Columns;
     Glib::RefPtr<Gtk::ListStore> m_RefListStore;
 
     // State
-    std::string m_current_path; // <--- Tracks current folder for reloading
+    std::string m_current_path;
     std::thread m_WorkerThread;
     std::atomic<bool> m_stop_flag{false};
     Glib::Dispatcher m_Dispatcher; 
     std::mutex m_QueueMutex;
-    std::deque<LoadedItem> m_ResultQueue; 
+    std::deque<LoadedItem> m_ResultQueue;
+    std::stack<UndoStep> m_UndoStack;
 
-    // --- Helper Logic ---
+    // --- LOGIC: Helper Functions ---
 
-    int get_current_size() {
-        std::string txt = m_ComboSize.get_active_text();
-        if (txt.find("100px") != std::string::npos) return 100;
-        if (txt.find("500px") != std::string::npos) return 500;
-        return 250;
+    void on_mode_changed() {
+        int active = m_ComboMode.get_active_row_number();
+        if (active == 0) m_StackModes.set_visible_child("Pattern");
+        else m_StackModes.set_visible_child("Replace");
+        update_preview();
     }
 
-    void on_size_changed() {
-        int new_size = get_current_size();
+    void on_cell_toggled(const Glib::ustring& path_string) {
+        Gtk::TreePath path(path_string);
+        auto iter = m_RefListStore->get_iter(path);
+        if(iter) {
+            bool val = !(*iter)[m_Columns.m_col_checked];
+            (*iter)[m_Columns.m_col_checked] = val;
+            update_preview(); // Refresh preview (optional, but consistent)
+        }
+    }
+
+    void set_all_checked(bool active) {
+        for(auto row : m_RefListStore->children()) row[m_Columns.m_col_checked] = active;
+        update_preview();
+    }
+
+    // --- PREVIEW LOGIC (Live Update) ---
+    void update_preview() {
+        // Debounce could be added here for huge lists, but for <2000 items direct update is fine.
+        int mode = m_ComboMode.get_active_row_number(); // 0=Pattern, 1=Replace
         
-        std::vector<fs::path> current_files;
-        auto children = m_RefListStore->children();
-        for(auto iter = children.begin(); iter != children.end(); ++iter) {
-            current_files.push_back(fs::path((std::string)(*iter)[m_Columns.m_col_path]));
+        std::string pattern = m_EntryPattern.get_text();
+        int start_num = m_SpinStartNum.get_value_as_int();
+        
+        std::string find_str = m_EntryFind.get_text();
+        std::string replace_str = m_EntryReplace.get_text();
+
+        // Pattern Helpers
+        size_t hash_pos = std::string::npos;
+        int pad_width = 0;
+        if (mode == 0) {
+            hash_pos = pattern.find('#');
+            if (hash_pos != std::string::npos) {
+                size_t end = pattern.find_last_of('#');
+                pad_width = end - hash_pos + 1;
+            }
         }
 
-        if (current_files.empty()) return;
+        int count = 0;
+        int selected_count = 0;
 
-        m_stop_flag = true;
-        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+        for (auto row : m_RefListStore->children()) {
+            std::string original = row[m_Columns.m_col_filename];
+            bool checked = row[m_Columns.m_col_checked];
+            
+            if (!checked) {
+                // If not checked, just show original name in grey
+                row[m_Columns.m_col_markup] = Glib::Markup::escape_text(original);
+                continue;
+            }
 
-        m_RefListStore->clear();
-        m_IconView.set_item_width(new_size); 
-        m_Statusbar.push("Resizing...");
+            selected_count++;
+            std::string new_name = original; // Default if logic fails
 
-        m_stop_flag = false;
-        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_files, this, current_files, new_size);
-    }
+            if (mode == 0 && !pattern.empty()) {
+                // PATTERN LOGIC
+                int current_num = start_num + count;
+                std::string num_str = std::to_string(current_num);
+                
+                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();
+                }
+                count++;
+            } 
+            else if (mode == 1 && !find_str.empty()) {
+                // FIND REPLACE LOGIC
+                size_t pos = original.find(find_str);
+                if (pos != std::string::npos) {
+                    std::string temp = original;
+                    temp.replace(pos, find_str.length(), replace_str);
+                    new_name = temp;
+                }
+            }
 
-    void start_loading_folder(std::string path) {
-        m_current_path = path; // <--- Store path
-        m_stop_flag = true;
-        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+            // Generate Markup
+            if (new_name != original) {
+                 std::string esc_orig = Glib::Markup::escape_text(original);
+                 std::string esc_new = Glib::Markup::escape_text(new_name);
+                 row[m_Columns.m_col_markup] = 
+                     "<small>" + esc_orig + "</small>\n" + 
+                     "<span foreground='blue' weight='bold'>" + esc_new + "</span>";
+            } else {
+                 row[m_Columns.m_col_markup] = Glib::Markup::escape_text(original);
+            }
+        }
+        
+        // Update Stats
+        std::string stat = "Selected: " + std::to_string(selected_count) + " files.";
+        m_Statusbar.pop();
+        m_Statusbar.push(stat);
+    }
 
-        m_RefListStore->clear();
-        m_BtnRename.set_sensitive(false);
-        m_Statusbar.push("Loading images from: " + path);
+    // --- EXECUTE RENAME ---
+    void on_rename_execute() {
+        // Collect moves based on current Preview logic
+        // We can actually just regenerate the names to be safe.
         
-        int size = get_current_size();
-        m_IconView.set_item_width(size);
+        // 1. Validate
+        int count = 0;
+        for (auto row : m_RefListStore->children()) if (row[m_Columns.m_col_checked]) count++;
+        if (count == 0) return;
+
+        // 2. Generate Plan
+        UndoStep undo_step;
+        std::vector<std::pair<std::string, std::string>> pending_renames; // current_path, new_name
+
+        // Reuse preview logic to determine targets
+        // (Copied strictly for safety to ensure What You See Is What You Get)
+        int mode = m_ComboMode.get_active_row_number();
+        std::string pattern = m_EntryPattern.get_text();
+        int start_num = m_SpinStartNum.get_value_as_int();
+        std::string find_str = m_EntryFind.get_text();
+        std::string replace_str = m_EntryReplace.get_text();
+        
+        size_t hash_pos = std::string::npos;
+        int pad_width = 0;
+        if (mode == 0) {
+            hash_pos = pattern.find('#');
+            if (hash_pos != std::string::npos) pad_width = pattern.find_last_of('#') - hash_pos + 1;
+        }
 
-        m_stop_flag = false;
-        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_folder, this, path, size);
-    }
+        int iter_count = 0;
+        auto children = m_RefListStore->children();
+        
+        // PREPARE
+        for (auto row : children) {
+            if (!row[m_Columns.m_col_checked]) continue;
+
+            std::string original_filename = row[m_Columns.m_col_filename];
+            std::string current_path = row[m_Columns.m_col_path];
+            std::string new_filename = original_filename;
+
+            if (mode == 0) {
+                int current_num = start_num + iter_count;
+                std::string num_str = std::to_string(current_num);
+                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_filename).extension().string();
+                } else {
+                    new_filename = pattern + "_" + num_str + fs::path(original_filename).extension().string();
+                }
+                iter_count++;
+            } else if (mode == 1 && !find_str.empty()) {
+                size_t pos = original_filename.find(find_str);
+                if (pos != std::string::npos) {
+                    new_filename = original_filename; // copy
+                    new_filename.replace(pos, find_str.length(), replace_str);
+                }
+            }
+            
+            if (new_filename != original_filename) {
+                fs::path p(current_path);
+                fs::path new_p = p.parent_path() / new_filename;
+                
+                RenameAction action;
+                action.original_path = current_path;
+                action.new_path = new_p.string();
+                undo_step.moves.push_back(action);
+            }
+        }
 
-    void on_open_folder_clicked() {
-        Gtk::FileChooserDialog dialog(*this, "Select Folder", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
-        dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL);
-        dialog.add_button("Select", Gtk::RESPONSE_OK);
-        if (dialog.run() == Gtk::RESPONSE_OK) {
-            start_loading_folder(dialog.get_filename());
+        if (undo_step.moves.empty()) {
+             Gtk::MessageDialog d(*this, "No changes detected based on inputs.", false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK);
+             d.run();
+             return;
         }
-    }
 
-    void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int, int, const Gtk::SelectionData& selection_data, guint, guint time) {
-        std::vector<Glib::ustring> uris = selection_data.get_uris();
-        if (uris.empty()) return;
+        // 3. EXECUTE with Temp Safety
+        // To prevent collisions (renaming 1.jpg to 2.jpg while 2.jpg exists), rename all to temp first
+        std::vector<std::pair<std::string, std::string>> final_stage;
+        
+        int temp_idx = 0;
+        for (auto& move : undo_step.moves) {
+            fs::path src(move.original_path);
+            std::string temp_name = "__tmp_ren_" + std::to_string(temp_idx++) + src.extension().string();
+            fs::path temp_path = src.parent_path() / temp_name;
+            
+            try {
+                fs::rename(src, temp_path);
+                final_stage.push_back({temp_path.string(), move.new_path});
+            } catch (const std::exception& e) {
+                std::cerr << "Error temp rename: " << e.what() << std::endl;
+            }
+        }
 
-        std::string filename = Glib::filename_from_uri(uris[0]);
-        if (fs::is_directory(filename)) {
-            start_loading_folder(filename);
-            context->drag_finish(true, false, time);
-        } else {
-            start_loading_folder(fs::path(filename).parent_path().string());
-            context->drag_finish(true, false, time);
+        for (auto& item : final_stage) {
+            try {
+                fs::rename(item.first, item.second);
+            } catch (const std::exception& e) {
+                std::cerr << "Error final rename: " << e.what() << std::endl;
+            }
         }
+
+        // 4. Update Undo Stack
+        m_UndoStack.push(undo_step);
+        m_BtnUndo.set_sensitive(true);
+
+        // 5. Reload
+        start_loading_folder(m_current_path);
     }
 
-    bool on_iconview_button_press(GdkEventButton* event) {
-        if (event->type == GDK_BUTTON_PRESS && event->button == 3) { 
-            Gtk::TreeModel::Path path = m_IconView.get_path_at_pos((int)event->x, (int)event->y);
-            if (path) {
-                 if (!m_IconView.path_is_selected(path)) {
-                     m_IconView.unselect_all();
-                     m_IconView.select_path(path);
-                 }
-                 m_ContextMenu.popup_at_pointer((GdkEvent*)event);
-                 return true;
+    void on_undo_clicked() {
+        if (m_UndoStack.empty()) return;
+        
+        UndoStep last_step = m_UndoStack.top();
+        m_UndoStack.pop();
+        if (m_UndoStack.empty()) m_BtnUndo.set_sensitive(false);
+
+        // Revert is tricky because of collisions. We use the temp strategy again.
+        std::vector<std::pair<std::string, std::string>> final_stage;
+        int temp_idx = 0;
+
+        // 1. Move current (new_path) to temp
+        for (const auto& move : last_step.moves) {
+            if (fs::exists(move.new_path)) {
+                fs::path src(move.new_path);
+                std::string temp_name = "__undo_tmp_" + std::to_string(temp_idx++) + src.extension().string();
+                fs::path temp_path = src.parent_path() / temp_name;
+                try {
+                    fs::rename(src, temp_path);
+                    final_stage.push_back({temp_path.string(), move.original_path});
+                } catch (...) {}
             }
         }
-        return false;
-    }
 
-    bool on_iconview_key_press(GdkEventKey* event) {
-        if (event->keyval == GDK_KEY_Delete) {
-            on_menu_remove();
-            return true;
+        // 2. Move temp to original
+        for (auto& item : final_stage) {
+            try { fs::rename(item.first, item.second); } catch(...) {}
         }
-        return false;
-    }
 
-    void on_menu_remove() {
-        auto selection = m_IconView.get_selected_items();
-        std::vector<Gtk::TreeRowReference> refs;
-        for (auto path : selection) refs.push_back(Gtk::TreeRowReference(m_RefListStore, path));
-        for (auto ref : refs) if (ref.is_valid()) m_RefListStore->erase(m_RefListStore->get_iter(ref.get_path()));
-        update_labels(); 
+        start_loading_folder(m_current_path);
+        m_Statusbar.push("Undo successful.");
     }
 
-    void update_labels() {
-        int index = 1;
-        for (auto row : m_RefListStore->children()) {
-            std::string filename = row[m_Columns.m_col_orig_name];
-            std::string label = "#" + std::to_string(index++) + "\n" + filename;
-            row[m_Columns.m_col_name] = label;
+    // --- SORTING LOGIC ---
+    void on_sort_changed() {
+        if (m_RefListStore->children().empty()) return;
+        
+        // Get all items out
+        std::vector<Gtk::TreeRowReference> rows;
+        // This is complex to re-sort inside ListStore without reloading images.
+        // Easiest reliable way: Reload from folder (fast enough usually) 
+        // OR implement custom sort function for the TreeModel.
+        
+        // Let's use ListStore's built-in set_sort_func for cleanliness
+        int sort_mode = m_ComboSort.get_active_row_number(); // 0=Name, 1=DateOld, 2=DateNew
+        
+        if (sort_mode == 0) {
+            // Natural Sort
+            m_RefListStore->set_sort_func(m_Columns.m_col_filename, [this](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
+                std::string sa = (*a)[m_Columns.m_col_filename];
+                std::string sb = (*b)[m_Columns.m_col_filename];
+                NaturalSort sorter;
+                return sorter(sa, sb) ? -1 : 1;
+            });
+        } else {
+            // Date Sort
+            m_RefListStore->set_sort_func(m_Columns.m_col_time, [sort_mode, this](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
+                long long ta = (*a)[m_Columns.m_col_time];
+                long long tb = (*b)[m_Columns.m_col_time];
+                if (ta == tb) return 0;
+                if (sort_mode == 1) return (ta < tb) ? -1 : 1; // Oldest first
+                return (ta > tb) ? -1 : 1; // Newest first
+            });
         }
-        m_Statusbar.push("Items remaining: " + std::to_string(index - 1));
+        
+        m_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING); // Trigger sort
+        update_preview(); // IDs change order, so numbers change
     }
 
-    // --- WORKER: Load from Directory ---
+    // --- WORKER: Load Data ---
     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") {
-                    files.push_back(entry.path());
+                if (entry.is_regular_file()) {
+                    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(), NaturalSort());
+        // Initial Sort (Natural)
+        std::sort(files.begin(), files.end(), [](const fs::path& a, const fs::path& b){
+            return NaturalSort()(a.filename().string(), b.filename().string());
+        });
+
         process_files(files, size);
     }
-
-    // --- WORKER: Load from List ---
-    void worker_thread_files(std::vector<fs::path> files, int size) {
+    
+    // --- WORKER: Reload Existing List (for resizing) ---
+    void worker_thread_reload(std::vector<fs::path> files, int size) {
         process_files(files, size);
     }
 
-    // --- Shared Processing Logic ---
     void process_files(const std::vector<fs::path>& files, int size) {
         for (const auto& path : files) {
             if (m_stop_flag) return;
             LoadedItem item;
             item.path = path.string();
             item.filename = path.filename().string();
+            
+            // Get Time
+            try {
+                auto ftime = fs::last_write_time(path);
+                // Convert to simpler count for sorting
+                item.last_write_time = ftime; 
+            } catch(...) {}
 
             try {
                 auto pixbuf = Gdk::Pixbuf::create_from_file(path.string());
                 int w = pixbuf->get_width();
                 int h = pixbuf->get_height();
                 float ratio = (float)w / h;
-                
                 int dest_w = (ratio > 1) ? size : size * ratio;
                 int dest_h = (ratio > 1) ? size / ratio : size;
-                
                 item.pixbuf = pixbuf->scale_simple(dest_w, dest_h, Gdk::INTERP_BILINEAR);
-
+                
                 {
                     std::lock_guard<std::mutex> lock(m_QueueMutex);
                     m_ResultQueue.push_back(item);
                 }
                 m_Dispatcher.emit();
-                std::this_thread::sleep_for(std::chrono::microseconds(500)); 
+                std::this_thread::sleep_for(std::chrono::microseconds(200)); 
             } catch (...) {}
         }
     }
@@ -343,69 +639,79 @@ protected:
 
             auto row = *(m_RefListStore->append());
             row[m_Columns.m_col_path] = item.path;
-            row[m_Columns.m_col_orig_name] = item.filename;
+            row[m_Columns.m_col_filename] = item.filename;
             row[m_Columns.m_col_pixbuf] = item.pixbuf;
+            row[m_Columns.m_col_checked] = true;
+            row[m_Columns.m_col_markup] = Glib::Markup::escape_text(item.filename); // Init label
+            
+            // Convert time to long long for sorting
+            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());
+            row[m_Columns.m_col_time] = sctp.time_since_epoch().count();
+        }
+        if (!m_RefListStore->children().empty()) {
+            m_BtnRename.set_sensitive(true);
+            update_preview(); // Apply current naming rules immediately
         }
-        update_labels();
-        if (!m_RefListStore->children().empty()) m_BtnRename.set_sensitive(true);
     }
 
-    void on_rename_files() {
-        std::string pattern = m_EntryPattern.get_text();
-        if (pattern.empty()) return;
-        
-        Gtk::MessageDialog dialog(*this, "Confirm Rename", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
-        dialog.set_secondary_text("Renaming " + std::to_string(m_RefListStore->children().size()) + " files.");
-        if (dialog.run() != Gtk::RESPONSE_YES) return;
+    // --- GENERIC HELPERS ---
+    int get_current_size() {
+        std::string txt = m_ComboSize.get_active_text();
+        if (txt.find("100px") != std::string::npos) return 100;
+        if (txt.find("450px") != std::string::npos) return 450;
+        return 220;
+    }
 
-        auto children = m_RefListStore->children();
-        int index = 1;
-        std::vector<std::pair<std::string, std::string>> rename_map;
+    void on_size_changed() {
+        int new_size = get_current_size();
+        std::vector<fs::path> current_files;
+        for(auto row : m_RefListStore->children()) current_files.push_back(fs::path((std::string)row[m_Columns.m_col_path]));
+        if (current_files.empty()) return;
 
-        // 1. Temp Rename
-        for (auto row : children) {
-            fs::path p((std::string)row[m_Columns.m_col_path]);
-            std::string temp = "__temp_" + std::to_string(index++) + p.extension().string();
-            try {
-                fs::rename(p, p.parent_path() / temp);
-                rename_map.push_back({(p.parent_path() / temp).string(), p.extension().string()});
-            } catch(...) {}
-        }
+        m_stop_flag = true;
+        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+        m_RefListStore->clear();
+        m_IconView.set_item_width(new_size); 
+        m_stop_flag = false;
+        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_reload, this, current_files, new_size);
+    }
 
-        // 2. Final Rename
-        index = 1;
-        size_t hash_pos = pattern.find('#');
-        int pad_width = 0;
-        if (hash_pos != std::string::npos) {
-            size_t end = pattern.find_last_of('#');
-            pad_width = end - hash_pos + 1;
-        }
+    void start_loading_folder(std::string path) {
+        m_current_path = path; 
+        m_stop_flag = true;
+        if (m_WorkerThread.joinable()) m_WorkerThread.join();
 
-        for (const auto& item : rename_map) {
-            fs::path src(item.first);
-            std::string new_name;
-            if (pad_width > 0) {
-                 std::string num = std::to_string(index);
-                 while (num.length() < (size_t)pad_width) num = "0" + num;
-                 std::string pat = pattern;
-                 pat.replace(hash_pos, pad_width, num);
-                 new_name = pat + item.second;
-            } else {
-                new_name = pattern + "_" + std::to_string(index) + item.second;
-            }
-            try { fs::rename(src, src.parent_path() / new_name); } catch(...) {}
-            index++;
+        m_RefListStore->clear();
+        m_BtnRename.set_sensitive(false);
+        m_IconView.set_item_width(get_current_size());
+        m_stop_flag = false;
+        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_folder, this, path, get_current_size());
+    }
+
+    void on_open_folder_clicked() {
+        Gtk::FileChooserDialog dialog(*this, "Select Folder", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
+        dialog.add_button("Cancel", Gtk::RESPONSE_CANCEL);
+        dialog.add_button("Select", Gtk::RESPONSE_OK);
+        if (dialog.run() == Gtk::RESPONSE_OK) start_loading_folder(dialog.get_filename());
+    }
+
+    void on_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& context, int, int, const Gtk::SelectionData& selection_data, guint, guint time) {
+        std::vector<Glib::ustring> uris = selection_data.get_uris();
+        if (uris.empty()) return;
+        std::string filename = Glib::filename_from_uri(uris[0]);
+        if (fs::is_directory(filename)) {
+            start_loading_folder(filename);
+            context->drag_finish(true, false, time);
+        } else {
+            start_loading_folder(fs::path(filename).parent_path().string());
+            context->drag_finish(true, false, time);
         }
-        
-        // --- RELOAD LOGIC ---
-        m_Statusbar.push("Rename Complete. Reloading...");
-        start_loading_folder(m_current_path); // Automatically refresh directory
     }
 };
 
 int main(int argc, char *argv[]) {
     Glib::thread_init(); 
-    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_dropdown");
+    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_v01");
     RenamerWindow window;
     return app->run(window);
 }