소스 검색

Corrected bugs

Nicole Portas 1 개월 전
부모
커밋
ab215ef724
2개의 변경된 파일202개의 추가작업 그리고 459개의 파일을 삭제
  1. 202 459
      src/main.cpp
  2. BIN
      visual-renamer

+ 202 - 459
src/main.cpp

@@ -9,7 +9,8 @@
 #include <mutex>
 #include <deque>
 #include <stack>
-#include <map>
+#include <iomanip>
+#include <sstream>
 
 namespace fs = std::filesystem;
 
@@ -22,11 +23,14 @@ struct NaturalSort {
                 size_t start1 = i, start2 = j;
                 while (i < a.length() && isdigit(a[i])) i++;
                 while (j < b.length() && isdigit(b[j])) j++;
-                
-                long n1 = std::stol(a.substr(start1, i - start1));
-                long n2 = std::stol(b.substr(start2, j - start2));
-                
-                if (n1 != n2) return n1 < n2;
+                try {
+                    long n1 = std::stol(a.substr(start1, i - start1));
+                    long n2 = std::stol(b.substr(start2, j - start2));
+                    if (n1 != n2) return n1 < n2;
+                } catch (...) {
+                    if (a.substr(start1, i - start1) != b.substr(start2, j - start2))
+                        return a.substr(start1, i - start1) < b.substr(start2, j - start2);
+                }
             } else {
                 if (a[i] != b[j]) return a[i] < b[j];
                 i++; j++;
@@ -36,173 +40,140 @@ struct NaturalSort {
     }
 };
 
-// --- Undo History Structure ---
-struct RenameAction {
-    std::string original_path;
-    std::string new_path;
-};
-struct UndoStep {
-    std::vector<RenameAction> moves;
+// --- Custom Renderer to draw a Frame ---
+class CellRendererFrame : public Gtk::CellRendererPixbuf {
+public:
+    CellRendererFrame() {
+        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
+        
+        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);
+    }
 };
 
-// --- Data for Worker Thread ---
+struct RenameAction { std::string original_path; std::string new_path; };
+struct UndoStep { std::vector<RenameAction> moves; };
+
 struct LoadedItem {
-    std::string path;
-    std::string filename;
+    std::string path, filename, ext;
     fs::file_time_type last_write_time;
     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
+    int orig_w = 0, orig_h = 0;
+    uintmax_t filesize = 0;
 };
 
 class RenamerWindow : public Gtk::Window {
 public:
     RenamerWindow() : m_Dispatcher() {
-        set_title("Simple Image Renamer 0.2 (Patched)");
+        set_title("Simple Image Renamer 0.1");
         set_default_size(1280, 850);
         
         m_Dispatcher.connect(sigc::mem_fun(*this, &RenamerWindow::on_worker_notification));
 
-        // --- Main Layout ---
         m_VBox.set_orientation(Gtk::ORIENTATION_VERTICAL);
         add(m_VBox);
 
-        // ===========================
-        // 1. TOP TOOLBAR (File Ops)
-        // ===========================
+        // 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);
 
-        // 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_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.append("Manual (Drag & Drop)"); 
+        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_ComboSort.append("Name"); m_ComboSort.append("Oldest"); m_ComboSort.append("Newest"); m_ComboSort.append("Manual"); 
         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.append("Tiny"); m_ComboSize.append("Normal"); m_ComboSize.append("Huge");
         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)
-        // ===========================
+        // Controls Frame
         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.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_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);
-        
         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");
 
-        // 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);
-
-        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);
-
         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_ToolbarControls.pack_start(m_StackModes, Gtk::PACK_EXPAND_WIDGET, 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);
+        // IconView setup
+        m_ScrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS);
         m_VBox.pack_start(m_ScrolledWindow);
 
         m_RefListStore = Gtk::ListStore::create(m_Columns);
-        
-        // Connect signal to update sequence numbers on Drag & Drop
-        m_RefListStore->signal_row_inserted().connect([this](const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator&){
-            // When a row is dropped (inserted), refresh the numbering
-            update_preview(); 
-        });
+        m_RefListStore->signal_row_inserted().connect([this](const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator&){ update_preview(); });
 
         m_IconView.set_model(m_RefListStore);
         
-        // 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);
+        // 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);
@@ -210,502 +181,274 @@ public:
         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_margin(10);
-        m_IconView.set_column_spacing(10);
-        m_IconView.set_row_spacing(15);
-        m_IconView.set_tooltip_column(m_Columns.m_col_path.index());
-
         m_IconView.set_reorderable(true); 
+        m_IconView.set_columns(-1);
+        m_IconView.set_spacing(20); // Spacing between thumbnails
 
         m_ScrolledWindow.add(m_IconView);
 
-        // ===========================
-        // 4. STATUS BAR
-        // ===========================
-        m_Statusbar.push("Welcome to Simple Image Renamer 0.2");
+        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);
 
-        // --- Drag & Drop (External Files) ---
+        // D&D
         std::vector<Gtk::TargetEntry> listTargets = { Gtk::TargetEntry("text/uri-list") };
-        m_IconView.drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY);
-        m_IconView.signal_drag_data_received().connect(sigc::mem_fun(*this, &RenamerWindow::on_drag_data_received));
+        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();
-        // Hide the non-active mode initially
         m_StackModes.set_visible_child("Pattern");
     }
 
-    ~RenamerWindow() override {
-        m_stop_flag = true;
-        if (m_WorkerThread.joinable()) m_WorkerThread.join();
-    }
+    ~RenamerWindow() { m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join(); }
 
 protected:
-    // UI Elements
-    Gtk::Box m_VBox, m_ToolbarTop, m_ToolbarControls;
-    Gtk::Box m_BoxPattern, m_BoxReplace;
+    Gtk::Box m_VBox, m_ToolbarTop, m_ToolbarControls, 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::Button m_BtnOpen, m_BtnRename, m_BtnUndo, m_BtnStop;
     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::ProgressBar m_ProgressBar;
     Gtk::Statusbar m_Statusbar;
-
-    // Renderers
-    Gtk::CellRendererPixbuf m_cell_pixbuf;
+    
+    CellRendererFrame m_cell_frame; // Custom Framed Renderer
     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_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_filename;
+        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<std::string> m_col_markup; // Shows "Old -> New"
-        Gtk::TreeModelColumn<long long> m_col_time; // For sorting
+        Gtk::TreeModelColumn<long long> m_col_time;
     };
     ModelColumns m_Columns;
     Glib::RefPtr<Gtk::ListStore> m_RefListStore;
 
-    // State
     std::string m_current_path;
     std::thread m_WorkerThread;
     std::atomic<bool> m_stop_flag{false};
+    std::atomic<int> m_total_files{0}, m_processed_files{0};
     Glib::Dispatcher m_Dispatcher; 
     std::mutex m_QueueMutex;
     std::deque<LoadedItem> m_ResultQueue;
     std::stack<UndoStep> m_UndoStack;
 
-    // --- LOGIC: Helper Functions ---
+    std::string format_size(uintmax_t bytes) {
+        double sz = static_cast<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];
+        return ss.str();
+    }
 
     void on_mode_changed() {
-        int active = m_ComboMode.get_active_row_number();
-        if (active == 0) m_StackModes.set_visible_child("Pattern");
+        if (m_ComboMode.get_active_row_number() == 0) m_StackModes.set_visible_child("Pattern");
         else m_StackModes.set_visible_child("Replace");
         update_preview();
     }
 
     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(); 
-        }
+        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 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() {
-        int mode = m_ComboMode.get_active_row_number(); // 0=Pattern, 1=Replace
-        
+        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();
-
-        // 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;
-            }
-        }
-
+        size_t hash_pos = pattern.find('#');
+        int pad_width = (hash_pos != std::string::npos) ? (pattern.find_last_of('#') - hash_pos + 1) : 0;
         int count = 0;
-        int selected_count = 0;
-
         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) {
-                row[m_Columns.m_col_markup] = Glib::Markup::escape_text(original);
-                continue;
-            }
-
-            selected_count++;
-            std::string new_name = original; 
-
-            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;
+            std::string original = row[m_Columns.m_col_filename], meta_info = 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(); }
+                    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()); }
                 }
             }
-
-            // 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);
-            }
+            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();
         }
-        
-        // Update Stats
-        std::string stat = "Selected: " + std::to_string(selected_count) + " files.";
-        m_Statusbar.pop();
-        m_Statusbar.push(stat);
     }
 
-    // --- EXECUTE RENAME ---
     void on_rename_execute() {
-        int count = 0;
-        for (auto row : m_RefListStore->children()) if (row[m_Columns.m_col_checked]) count++;
-        if (count == 0) return;
-
         UndoStep undo_step;
-        
-        int mode = m_ComboMode.get_active_row_number();
+        int mode = m_ComboMode.get_active_row_number(), start_num = m_SpinStartNum.get_value_as_int();
         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;
-        }
-
+        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;
-        
-        // PREPARE
         for (auto row : m_RefListStore->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;
-
+            std::string original = row[m_Columns.m_col_filename], new_filename = original;
             if (mode == 0) {
-                int current_num = start_num + iter_count;
-                std::string num_str = std::to_string(current_num);
+                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_filename).extension().string();
-                } else {
-                    new_filename = pattern + "_" + num_str + fs::path(original_filename).extension().string();
-                }
+                    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++;
-            } else if (mode == 1 && !find_str.empty()) {
-                size_t pos = original_filename.find(find_str);
-                if (pos != std::string::npos) {
-                    new_filename = original_filename; 
-                    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);
+            } 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()); }
             }
+            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 (undo_step.moves.empty()) {
-             Gtk::MessageDialog d(*this, "No changes detected based on inputs.", false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_OK);
-             d.run();
-             return;
-        }
-
-        // EXECUTE with Temp Safety
-        std::vector<std::pair<std::string, std::string>> final_stage;
-        
-        int temp_idx = 0;
+        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 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;
-            }
+            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 (...) {}
         }
-
-        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;
-            }
-        }
-
-        m_UndoStack.push(undo_step);
-        m_BtnUndo.set_sensitive(true);
-
-        start_loading_folder(m_current_path);
+        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);
     }
 
     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);
-
-        std::vector<std::pair<std::string, std::string>> final_stage;
-        int temp_idx = 0;
-
-        for (const auto& move : last_step.moves) {
+        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 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 (...) {}
+                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 (...) {}
             }
         }
-
-        for (auto& item : final_stage) {
-            try { fs::rename(item.first, item.second); } catch(...) {}
-        }
-
+        for (auto& item : final_stage) { try { fs::rename(item.first, item.second); } catch(...) {} }
         start_loading_folder(m_current_path);
-        m_Statusbar.push("Undo successful.");
     }
 
-    // --- SORTING LOGIC ---
     void on_sort_changed() {
         if (m_RefListStore->children().empty()) return;
-        
-        int sort_mode = m_ComboSort.get_active_row_number(); 
-        // 0=Name, 1=DateOld, 2=DateNew, 3=Manual
-
-        if (sort_mode == 3) {
-            // [FIXED] Use C macro directly for unsorted column
-            m_RefListStore->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING);
-            m_Statusbar.push("Manual mode: Drag images to reorder.");
-            return;
-        }
-        
-        if (sort_mode == 0) {
-            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;
+        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;
             });
         } else {
-            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;
-                return (ta > tb) ? -1 : 1;
+            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_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING); 
-        update_preview(); 
+        m_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING); update_preview(); 
     }
 
-    // --- 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;
-                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());
-                    }
-                }
+                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());
-        });
-
-        process_files(files, size);
+        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 worker_thread_reload(std::vector<fs::path> files, int size) {
-        process_files(files, size);
-    }
-
     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();
-            
-            try {
-                auto ftime = fs::last_write_time(path);
-                item.last_write_time = ftime; 
-            } catch(...) {}
-
+            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(...) {}
             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(200)); 
-            } catch (...) {}
+                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)); 
+            } catch (...) { m_processed_files++; }
         }
+        m_total_files = 0; m_Dispatcher.emit();
     }
 
     void on_worker_notification() {
         std::lock_guard<std::mutex> lock(m_QueueMutex);
         while (!m_ResultQueue.empty()) {
-            LoadedItem item = m_ResultQueue.front();
-            m_ResultQueue.pop_front();
-
+            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;
-            row[m_Columns.m_col_markup] = Glib::Markup::escape_text(item.filename); 
-            
+            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();
         }
-        if (!m_RefListStore->children().empty()) {
-            m_BtnRename.set_sensitive(true);
-            update_preview(); 
-        }
-    }
-
-    // --- 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;
+        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();
     }
 
     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;
-
-        m_stop_flag = true;
-        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+        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;
+        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);
+        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);
     }
 
     void start_loading_folder(std::string path) {
-        m_current_path = path; 
-        m_stop_flag = true;
-        if (m_WorkerThread.joinable()) m_WorkerThread.join();
-
+        m_current_path = path; m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join();
         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());
+        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);
     }
 
     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());
+        Gtk::FileChooserDialog d(*this, "Select 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 x, int y, const Gtk::SelectionData& selection_data, guint info, guint time) {
-        std::vector<Glib::ustring> uris = selection_data.get_uris();
-        
-        if (uris.empty()) {
-            context->drag_finish(false, false, time);
-            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 {
-            if (fs::exists(filename)) {
-                start_loading_folder(fs::path(filename).parent_path().string());
-                context->drag_finish(true, false, time);
-            }
-        }
+    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();
+        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);
     }
 };
 
 int main(int argc, char *argv[]) {
     Glib::thread_init(); 
-    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_v02");
+    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_v01");
     RenamerWindow window;
     return app->run(window);
 }

BIN
visual-renamer