소스 검색

added exiv2 library, linker and changed UI to add a slider

Nicole Portas 1 개월 전
부모
커밋
8d70568e3f
3개의 변경된 파일169개의 추가작업 그리고 343개의 파일을 삭제
  1. 3 4
      Makefile
  2. 2 1
      README.md
  3. 164 338
      src/main.cpp

+ 3 - 4
Makefile

@@ -1,7 +1,6 @@
-# Compiler and Linker
-CXX = g++
-CXXFLAGS = -std=c++17 -Wall -Wextra `pkg-config --cflags gtkmm-3.0`
-LDFLAGS = `pkg-config --libs gtkmm-3.0`
+# Add exiv2 to the pkg-config calls
+CXXFLAGS = -std=c++17 -Wall -Wextra `pkg-config --cflags gtkmm-3.0 exiv2`
+LDFLAGS = `pkg-config --libs gtkmm-3.0 exiv2`
 
 # Target Executable Name
 TARGET = visual-renamer

+ 2 - 1
README.md

@@ -26,8 +26,9 @@ To build on **Debian** or similar distributions, you need:
 
 * `g++` (C++17 support)
 * `libgtkmm-3.0-dev`
+* `libexiv2-dev`
 * `pkg-config`
 
 ```bash
 sudo apt update
-sudo apt install build-essential libgtkmm-3.0-dev pkg-config
+sudo apt install build-essential libgtkmm-3.0-dev libexiv2-dev pkg-config

+ 164 - 338
src/main.cpp

@@ -1,5 +1,6 @@
 #include <gtkmm.h>
 #include <giomm.h> 
+#include <exiv2/exiv2.hpp>
 #include <iostream>
 #include <filesystem>
 #include <vector>
@@ -22,16 +23,16 @@ struct NaturalSort {
         size_t i = 0, j = 0;
         while (i < a.length() && j < b.length()) {
             if (isdigit(a[i]) && isdigit(b[j])) {
-                size_t start1 = i, start2 = 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(start1, i - start1));
-                    long n2 = std::stol(b.substr(start2, j - start2));
+                    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(start1, i - start1) != b.substr(start2, j - start2))
-                        return a.substr(start1, i - start1) < b.substr(start2, j - start2);
+                    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];
@@ -42,32 +43,20 @@ struct NaturalSort {
     }
 };
 
-// --- Custom Renderer to draw a Frame ---
 class CellRendererFrame : public Gtk::CellRendererPixbuf {
 public:
-    CellRendererFrame() {
-        property_xpad() = 10;
-        property_ypad() = 10;
-    }
+    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 {
+    void render_vfunc(const Cairo::RefPtr<Cairo::Context>& 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();
-        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); 
+        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, background_area, cell_area, flags);
+        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);
     }
 };
 
@@ -76,12 +65,13 @@ struct UndoStep { std::vector<RenameAction> moves; };
 
 struct LoadedItem {
     std::string path, filename, ext;
-    fs::file_time_type last_write_time;
+    std::string sort_time_str; 
+    long long sort_time_raw;   
     Glib::RefPtr<Gdk::Pixbuf> pixbuf;
     int orig_w = 0, orig_h = 0;
     uintmax_t filesize = 0;
-    bool is_update = false;
-    Gtk::TreeModel::Path model_path; 
+    bool is_zoom_update = false;
+    Gtk::TreeModel::Path model_path;
 };
 
 class RenamerWindow : public Gtk::Window {
@@ -92,11 +82,10 @@ public:
         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 Top ---
+        // --- 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);
@@ -105,57 +94,50 @@ public:
         m_BtnOpen.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_open_folder_clicked));
         m_ToolbarTop.pack_start(m_BtnOpen, Gtk::PACK_SHRINK, 5);
 
-        m_BtnStopReload.set_label("Reload Folder");
-        m_BtnStopReload.set_sensitive(false);
-        m_BtnStopReload.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_stop_reload_clicked));
-        m_ToolbarTop.pack_start(m_BtnStopReload, Gtk::PACK_SHRINK, 5);
-
-        m_ToolbarTop.pack_start(*Gtk::manage(new Gtk::Label("  Sort By: ")), Gtk::PACK_SHRINK);
-        m_ComboSort.append("Name (Natural)"); m_ComboSort.append("Oldest First"); m_ComboSort.append("Newest First"); m_ComboSort.append("Manual (Drag & Drop)"); 
+        m_ToolbarTop.pack_start(*Gtk::manage(new Gtk::Label("  Sort: ")), Gtk::PACK_SHRINK);
+        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_ComboSize.append("Small"); m_ComboSize.append("Medium"); m_ComboSize.append("Large");
-        m_ComboSize.set_active(1);
-        m_ComboSize.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_size_changed));
-        m_ToolbarTop.pack_start(m_ComboSize, Gtk::PACK_SHRINK, 5);
+        m_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");
         m_BtnUndo.set_sensitive(false);
         m_BtnUndo.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_undo_clicked));
         m_ToolbarTop.pack_end(m_BtnUndo, Gtk::PACK_SHRINK, 5);
 
-        // --- Controls Frame ---
+        // --- Controls ---
         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_ToolbarControls.pack_start(*Gtk::manage(new Gtk::Label(" Mode: ")), Gtk::PACK_SHRINK);
-        m_ComboMode.append("Sequential Numbering"); m_ComboMode.append("Find & Replace");
+        m_ComboMode.append("Sequential"); m_ComboMode.append("Replace");
         m_ComboMode.set_active(0);
         m_ComboMode.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_mode_changed));
         m_ToolbarControls.pack_start(m_ComboMode, Gtk::PACK_SHRINK, 5);
 
-        // Sequential Numbering Widgets
-        m_BoxPattern.pack_start(*Gtk::manage(new Gtk::Label(" Pattern: ")), Gtk::PACK_SHRINK);
         m_EntryPattern.set_text("image_###");
         m_EntryPattern.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
         m_BoxPattern.pack_start(m_EntryPattern, Gtk::PACK_EXPAND_WIDGET);
-        m_BoxPattern.pack_start(*Gtk::manage(new Gtk::Label(" Start at: ")), Gtk::PACK_SHRINK);
         m_AdjStartNum = Gtk::Adjustment::create(1, 0, 100000, 1, 10, 0);
         m_SpinStartNum.set_adjustment(m_AdjStartNum);
         m_SpinStartNum.signal_value_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
         m_BoxPattern.pack_start(m_SpinStartNum, Gtk::PACK_SHRINK);
         m_StackModes.add(m_BoxPattern, "Pattern");
 
-        // Find & Replace Widgets
-        m_BoxReplace.pack_start(*Gtk::manage(new Gtk::Label(" Find Text: ")), Gtk::PACK_SHRINK);
         m_EntryFind.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
         m_BoxReplace.pack_start(m_EntryFind, Gtk::PACK_EXPAND_WIDGET);
-        m_BoxReplace.pack_start(*Gtk::manage(new Gtk::Label(" Replace With: ")), Gtk::PACK_SHRINK);
         m_EntryReplace.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::update_preview));
         m_BoxReplace.pack_start(m_EntryReplace, Gtk::PACK_EXPAND_WIDGET);
         m_StackModes.add(m_BoxReplace, "Replace");
@@ -166,13 +148,11 @@ public:
         m_BtnRename.signal_clicked().connect(sigc::mem_fun(*this, &RenamerWindow::on_rename_execute));
         m_ToolbarControls.pack_end(m_BtnRename, Gtk::PACK_SHRINK, 5);
 
-        // --- IconView setup ---
+        // --- 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_inserted().connect([this](const Gtk::TreeModel::Path&, const Gtk::TreeModel::iterator&){ update_preview(); });
-
         m_IconView.set_model(m_RefListStore);
         m_IconView.pack_start(m_cell_frame, false);
         m_IconView.add_attribute(m_cell_frame, "pixbuf", m_Columns.m_col_pixbuf);
@@ -182,64 +162,36 @@ public:
         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_columns(-1);
-        m_IconView.set_spacing(20);
-        
-        m_IconView.set_selection_mode(Gtk::SELECTION_NONE);
-        m_IconView.set_reorderable(false);
+        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));
 
-        // --- Context Menu Setup (Right Click) ---
         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(m_MenuSeparator1);
-
+        m_MenuPopup.append(*Gtk::manage(new Gtk::SeparatorMenuItem()));
         m_MenuItemOpenWith.set_label("Open With...");
-        auto open_img = Gtk::manage(new Gtk::Image());
-        open_img->set_from_icon_name("document-open", Gtk::ICON_SIZE_MENU);
-        m_MenuItemOpenWith.set_image(*open_img);
-        m_MenuItemOpenWith.set_always_show_image(true);
         m_MenuItemOpenWith.signal_activate().connect(sigc::mem_fun(*this, &RenamerWindow::on_open_with_clicked));
         m_MenuPopup.append(m_MenuItemOpenWith);
-
-        m_MenuPopup.append(m_MenuSeparator2);
-
+        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");
-        auto delete_img = Gtk::manage(new Gtk::Image());
-        delete_img->set_from_icon_name("user-trash", Gtk::ICON_SIZE_MENU);
-        m_MenuItemDelete.set_image(*delete_img);
-        m_MenuItemDelete.set_always_show_image(true);
         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.add_events(Gdk::BUTTON_PRESS_MASK); 
+        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);
 
-        // --- Drag and Drop ---
-        std::vector<Gtk::TargetEntry> listTargets = { Gtk::TargetEntry("text/uri-list") };
-        this->drag_dest_set(listTargets, Gtk::DEST_DEFAULT_ALL, Gdk::ACTION_COPY);
-        this->signal_drag_data_received().connect(sigc::mem_fun(*this, &RenamerWindow::on_drag_data_received));
-
-        // --- Keyboard Shortcuts ---
         this->signal_key_press_event().connect(sigc::mem_fun(*this, &RenamerWindow::on_window_key_press), false);
-
-        show_all_children();
-        m_ProgressBar.hide();
+        show_all_children(); m_ProgressBar.hide();
     }
 
     ~RenamerWindow() { m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join(); }
@@ -247,11 +199,12 @@ public:
 protected:
     Gtk::Box m_VBox, m_ToolbarTop, m_ToolbarControls, m_BoxPattern, m_BoxReplace;
     Gtk::Frame m_FrameControls;
-    Gtk::Button m_BtnOpen, m_BtnRename, m_BtnUndo, m_BtnStopReload;
+    Gtk::Button m_BtnOpen, m_BtnRename, m_BtnUndo;
     Gtk::Entry m_EntryPattern, m_EntryFind, m_EntryReplace;
-    Gtk::ComboBoxText m_ComboSize, m_ComboSort, m_ComboMode;
+    Gtk::ComboBoxText m_ComboSort, m_ComboMode;
+    Gtk::Scale m_ScaleZoom;
     Gtk::SpinButton m_SpinStartNum;
-    Glib::RefPtr<Gtk::Adjustment> m_AdjStartNum;
+    Glib::RefPtr<Gtk::Adjustment> m_AdjStartNum, m_AdjZoom;
     Gtk::Stack m_StackModes;
     Gtk::ScrolledWindow m_ScrolledWindow;
     Gtk::IconView m_IconView;
@@ -260,12 +213,8 @@ protected:
     CellRendererFrame m_cell_frame;
     Gtk::CellRendererText m_cell_text;
     Gtk::CellRendererToggle m_cell_toggle;
-
     Gtk::Menu m_MenuPopup;
-    Gtk::MenuItem m_MenuItemSelectAll;
-    Gtk::MenuItem m_MenuItemSelectNone;
-    Gtk::SeparatorMenuItem m_MenuSeparator1, m_MenuSeparator2;
-    Gtk::ImageMenuItem m_MenuItemDelete, m_MenuItemOpenWith;
+    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); }
@@ -278,335 +227,212 @@ protected:
 
     std::string m_current_path;
     std::thread m_WorkerThread;
-    std::atomic<bool> m_stop_flag{false}, m_is_loading{false};
+    std::atomic<bool> m_stop_flag{false};
     std::atomic<int> m_total_files{0}, m_processed_files{0};
-    Glib::Dispatcher m_Dispatcher; 
+    Glib::Dispatcher m_Dispatcher;
     std::mutex m_QueueMutex;
     std::deque<LoadedItem> m_ResultQueue;
     std::stack<UndoStep> m_UndoStack;
 
-    // --- Key Press Handler ---
+    void launch_terminal() {
+        if (m_current_path.empty()) return;
+        std::string cmd = "gnome-terminal --working-directory='" + m_current_path + "'";
+        // Fixed: Explicitly creating the vector to avoid ambiguity
+        std::vector<Glib::RefPtr<Gio::File>> 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 || event->keyval == GDK_KEY_O) { on_open_folder_clicked(); return true; }
-            if (event->keyval == GDK_KEY_z || event->keyval == GDK_KEY_Z) { on_undo_clicked(); return true; }
-            if (event->keyval == GDK_KEY_e || event->keyval == GDK_KEY_E) { on_open_with_clicked(); return true; }
+            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 || event->keyval == GDK_KEY_KP_Enter) { on_rename_execute(); return true; }
+        if (event->keyval == GDK_KEY_Return) { on_rename_execute(); return true; }
         return false;
     }
 
-    // --- Quick View (Double Click) ---
     void on_item_activated(const Gtk::TreeModel::Path& path) {
         auto it = m_RefListStore->get_iter(path);
-        if (it) {
-            std::string file_path = (*it)[m_Columns.m_col_path];
-            try {
-                Gio::AppInfo::launch_default_for_uri("file://" + file_path);
-            } catch (const Glib::Error& ex) {
-                std::cerr << "Failed to open image: " << ex.what() << std::endl;
-            }
-        }
+        if (it) Gio::AppInfo::launch_default_for_uri("file://" + (std::string)(*it)[m_Columns.m_col_path]);
     }
 
-    // --- Open With Handler ---
     void on_open_with_clicked() {
-        std::string target_path;
-        for (auto row : m_RefListStore->children()) {
-            if (row[m_Columns.m_col_checked]) {
-                target_path = (std::string)row[m_Columns.m_col_path];
-                break;
+        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<Glib::RefPtr<Gio::File>> files = { Gio::File::create_for_path((std::string)row[m_Columns.m_col_path]) };
+                dialog.get_app_info()->launch(files);
             }
+            break;
         }
-
-        if (target_path.empty()) return;
-
-        auto file = Gio::File::create_for_path(target_path);
-        // Corrected constructor: (file, parent_window)
-        Gtk::AppChooserDialog dialog(file, *this); 
-        dialog.set_title("Open With...");
-        
-        if (dialog.run() == Gtk::RESPONSE_OK) {
-            auto app_info = dialog.get_app_info();
-            if (app_info) {
-                std::vector<Glib::RefPtr<Gio::File>> files = { file };
-                app_info->launch(files);
-            }
-        }
-    }
-
-    std::string format_size(uintmax_t bytes) {
-        double sz = (double)bytes;
-        const char* units[] = {"B", "KB", "MB", "GB"};
-        int i = 0; while (sz >= 1024 && i < 3) { sz /= 1024; i++; }
-        std::stringstream ss; ss << std::fixed << std::setprecision(1) << sz << " " << units[i];
-        return ss.str();
-    }
-
-    void on_mode_changed() {
-        m_StackModes.set_visible_child(m_ComboMode.get_active_row_number() == 0 ? "Pattern" : "Replace");
-        update_preview();
-    }
-
-    void on_cell_toggled(const Glib::ustring& p) {
-        auto it = m_RefListStore->get_iter(Gtk::TreePath(p));
-        if(it) { (*it)[m_Columns.m_col_checked] = !(*it)[m_Columns.m_col_checked]; update_preview(); }
-    }
-
-    void on_selection_change(bool select_all) {
-        if (!m_RefListStore) return;
-        for (auto row : m_RefListStore->children()) {
-            row[m_Columns.m_col_checked] = select_all;
-        }
-        update_preview();
-    }
-
-    bool on_iconview_button_press(GdkEventButton* event) {
-        if (event->type == GDK_BUTTON_PRESS && event->button == 3) { 
-            m_MenuPopup.popup(event->button, event->time);
-            return true;
-        }
-        return false;
     }
 
     void on_delete_selected() {
-        int count = 0;
-        for (auto row : m_RefListStore->children()) if (row[m_Columns.m_col_checked]) count++;
-        if (count == 0) return;
-
-        Gtk::MessageDialog dialog(*this, "Move to Trash?", false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_YES_NO);
-        dialog.set_secondary_text("Move " + std::to_string(count) + " selected pictures to the system trash?");
-        if (dialog.run() != Gtk::RESPONSE_YES) return;
-
-        for (auto it = m_RefListStore->children().begin(); it != m_RefListStore->children().end(); ) {
-            if ((*it)[m_Columns.m_col_checked]) {
-                try {
-                    auto file = Gio::File::create_for_path((std::string)(*it)[m_Columns.m_col_path]);
-                    if (file->trash()) {
-                        it = m_RefListStore->erase(it);
-                    } else { ++it; }
-                } catch (...) { ++it; }
-            } else ++it;
+        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;
+            }
         }
-        update_preview();
-    }
-
-    void on_stop_reload_clicked() {
-        if (m_is_loading) m_stop_flag = true;
-        else if (!m_current_path.empty()) start_loading_folder(m_current_path);
     }
 
     void update_preview() {
-        if (!m_RefListStore) return;
-        int mode = m_ComboMode.get_active_row_number();
-        std::string pattern = m_EntryPattern.get_text();
-        int start_num = m_SpinStartNum.get_value_as_int();
-        size_t hash_pos = pattern.find('#');
-        int pad = (hash_pos != std::string::npos) ? (pattern.find_last_of('#') - hash_pos + 1) : 0;
-        int count = 0;
-
-        std::set<std::string> existing_names;
-        for (auto row : m_RefListStore->children()) existing_names.insert((std::string)row[m_Columns.m_col_filename]);
-
+        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<std::string> seen;
         for (auto row : m_RefListStore->children()) {
-            std::string original = row[m_Columns.m_col_filename], meta = row[m_Columns.m_col_info_str], new_name = original;
-            bool conflict = false;
-
+            std::string orig = row[m_Columns.m_col_filename], next = orig;
             if (row[m_Columns.m_col_checked]) {
-                if (mode == 0 && !pattern.empty()) {
-                    std::string num = std::to_string(start_num + count);
+                if (mode == 0 && !pat.empty()) {
+                    std::string num = std::to_string(start + count++);
                     while (num.length() < (size_t)pad) num = "0" + num;
-                    if (hash_pos != std::string::npos) {
-                        std::string t = pattern; t.replace(hash_pos, pad, num);
-                        new_name = t + fs::path(original).extension().string();
-                    } else new_name = pattern + "_" + num + fs::path(original).extension().string();
-                    count++;
+                    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 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()); }
+                    size_t p = orig.find(m_EntryFind.get_text());
+                    if (p != std::string::npos) { next = orig; next.replace(p, m_EntryFind.get_text().length(), m_EntryReplace.get_text()); }
                 }
-                if (new_name != original && existing_names.count(new_name)) conflict = true;
             }
-
             std::stringstream mu; mu << "<span font='9'>";
-            if (new_name != original) {
-                mu << "<span size='small' alpha='60%'>" << Glib::Markup::escape_text(original) << "</span>\n";
-                if (conflict) mu << "<span foreground='red' weight='bold'>CONFLICT: " << Glib::Markup::escape_text(new_name) << "</span>";
-                else mu << "<span foreground='#3584e4' weight='bold'>" << Glib::Markup::escape_text(new_name) << "</span>";
-            } else mu << "<span weight='bold'>" << Glib::Markup::escape_text(original) << "</span>";
-            mu << "\n<span size='x-small' alpha='70%'>" << Glib::Markup::escape_text(meta) << "</span></span>";
-            row[m_Columns.m_col_markup] = mu.str();
+            if (next != orig && seen.count(next)) mu << "<span foreground='red' weight='bold'>CONFLICT: " << Glib::Markup::escape_text(next) << "</span>";
+            else if (next != orig) mu << "<span foreground='#3584e4' weight='bold'>" << Glib::Markup::escape_text(next) << "</span>";
+            else mu << "<span weight='bold'>" << Glib::Markup::escape_text(orig) << "</span>";
+            mu << "\n<span size='x-small' alpha='70%'>" << Glib::Markup::escape_text((std::string)row[m_Columns.m_col_info_str]) << "</span></span>";
+            row[m_Columns.m_col_markup] = mu.str(); seen.insert(next);
         }
     }
 
     void on_rename_execute() {
-        UndoStep step;
-        int mode = m_ComboMode.get_active_row_number(), start = m_SpinStartNum.get_value_as_int();
+        UndoStep step; 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 && mode == 0) ? (pat.find_last_of('#') - hpos + 1) : 0;
-        int count = 0;
-        for (auto row : m_RefListStore->children()) {
-            if (!row[m_Columns.m_col_checked]) continue; 
+        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);
+                std::string num = std::to_string(start + count++);
                 while (num.length() < (size_t)pad) num = "0" + num;
                 if (hpos != std::string::npos) { std::string t = pat; t.replace(hpos, pad, num); next = t + fs::path(orig).extension().string(); }
                 else next = pat + "_" + num + fs::path(orig).extension().string();
-                count++;
-            } else if (mode == 1 && !m_EntryFind.get_text().empty()) {
+            } 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()); }
             }
-            if (next != orig) {
-                if (fs::exists(fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / next)) {
-                    std::cerr << "Rename skipped: Conflict with " << next << std::endl;
-                    continue;
-                }
-                step.moves.push_back({(std::string)row[m_Columns.m_col_path], (fs::path((std::string)row[m_Columns.m_col_path]).parent_path() / next).string()});
+            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()}); 
             }
         }
-        if (step.moves.empty()) return;
-        for (auto& m : step.moves) try { fs::rename(m.original_path, m.new_path); } catch (...) {}
-        m_UndoStack.push(step); m_BtnUndo.set_sensitive(true); start_loading_folder(m_current_path);
+        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) try { if (fs::exists(m.new_path)) fs::rename(m.new_path, m.original_path); } catch(...) {}
+        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() {
-        if (!m_RefListStore || m_RefListStore->children().empty()) return;
-        int mode = m_ComboSort.get_active_row_number(); 
-        if (mode == 3) {
-            m_IconView.set_selection_mode(Gtk::SELECTION_MULTIPLE);
-            m_IconView.set_reorderable(true);
-            m_RefListStore->set_sort_column(GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, Gtk::SORT_ASCENDING); 
-        } else {
-            m_IconView.set_selection_mode(Gtk::SELECTION_NONE);
-            m_IconView.set_reorderable(false);
-            if (mode == 0) {
-                m_RefListStore->set_sort_func(m_Columns.m_col_filename, [this](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
-                    return NaturalSort()((*a)[m_Columns.m_col_filename], (*b)[m_Columns.m_col_filename]) ? -1 : 1;
-                });
-                m_RefListStore->set_sort_column(m_Columns.m_col_filename, Gtk::SORT_ASCENDING);
-            } else {
-                m_RefListStore->set_sort_func(m_Columns.m_col_time, [mode, this](const Gtk::TreeModel::iterator& a, const Gtk::TreeModel::iterator& b){
-                    long long ta = (*a)[m_Columns.m_col_time], tb = (*b)[m_Columns.m_col_time];
-                    if (ta == tb) return 0;
-                    return (mode == 1) ? (ta < tb ? -1 : 1) : (ta > tb ? -1 : 1);
-                });
-                m_RefListStore->set_sort_column(m_Columns.m_col_time, Gtk::SORT_ASCENDING);
-            }
-        }
-        Glib::signal_idle().connect_once([this](){ update_preview(); });
-    }
-
-    void worker_thread_logic(std::vector<fs::path> paths, int sz, bool is_zoom_only) {
-        m_is_loading = true; m_total_files = paths.size(); m_processed_files = 0;
-        for (size_t idx = 0; idx < paths.size(); ++idx) {
-            if (m_stop_flag) break;
-            const auto& p = paths[idx];
-            LoadedItem item; item.path = p.string(); item.is_update = is_zoom_only;
-            if (is_zoom_only) item.model_path = Gtk::TreePath(std::to_string(idx));
-            try {
-                auto pb = Gdk::Pixbuf::create_from_file(p.string());
-                float r = (float)pb->get_width() / pb->get_height();
-                int dw = (r > 1) ? sz : sz * r, dh = (r > 1) ? sz / r : sz;
-                item.pixbuf = pb->scale_simple(dw, dh, Gdk::INTERP_BILINEAR);
-                if (!is_zoom_only) {
-                    item.filename = p.filename().string();
-                    item.ext = p.extension().string(); if (!item.ext.empty() && item.ext[0] == '.') item.ext = item.ext.substr(1);
-                    std::transform(item.ext.begin(), item.ext.end(), item.ext.begin(), ::toupper);
-                    item.orig_w = pb->get_width(); item.orig_h = pb->get_height();
-                    item.last_write_time = fs::last_write_time(p); item.filesize = fs::file_size(p);
-                }
-                { std::lock_guard<std::mutex> l(m_QueueMutex); m_ResultQueue.push_back(item); }
-                m_processed_files++; m_Dispatcher.emit();
-            } catch (...) { m_processed_files++; }
-        }
-        m_is_loading = false; m_total_files = 0; m_Dispatcher.emit();
+        int s = m_ComboSort.get_active_row_number();
+        if (s == 0) 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 == 1 ? Gtk::SORT_ASCENDING : Gtk::SORT_DESCENDING));
+        update_preview();
     }
 
     void on_worker_notification() {
         std::lock_guard<std::mutex> l(m_QueueMutex);
         while (!m_ResultQueue.empty()) {
             LoadedItem i = m_ResultQueue.front(); m_ResultQueue.pop_front();
-            if (i.is_update) {
+            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;
-                auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(i.last_write_time - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
-                std::time_t tt = std::chrono::system_clock::to_time_t(sctp);
-                std::stringstream ss; ss << std::put_time(std::localtime(&tt), "%H:%M %d-%b-%Y");
-                r[m_Columns.m_col_info_str] = i.ext + " | " + std::to_string(i.orig_w) + "x" + std::to_string(i.orig_h) + " | " + format_size(i.filesize) + " | " + ss.str();
-                r[m_Columns.m_col_time] = sctp.time_since_epoch().count();
+                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.set_fraction(std::min(1.0, (double)m_processed_files / m_total_files));
-            m_ProgressBar.show();
-            m_BtnStopReload.set_sensitive(true);
-        } else {
-            m_ProgressBar.hide();
-            m_BtnStopReload.set_sensitive(!m_current_path.empty());
+            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; }
         }
-        if (m_total_files == 0) update_preview();
+        update_preview();
     }
 
-    void on_size_changed() {
-        if (m_RefListStore->children().empty()) return;
-        m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join();
-        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);
-        std::vector<fs::path> paths;
-        for(auto r : m_RefListStore->children()) paths.push_back(fs::path((std::string)r[m_Columns.m_col_path]));
-        m_stop_flag = false;
-        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_logic, this, paths, sz, true);
+    void worker_thread_logic(std::vector<fs::path> 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<std::mutex> l(m_QueueMutex); m_ResultQueue.push_back(item); }
+                m_Dispatcher.emit();
+            } catch (...) {}
+        }
     }
 
     void start_loading_folder(std::string p) {
-        m_current_path = p; m_stop_flag = true; if (m_WorkerThread.joinable()) m_WorkerThread.join();
-        m_RefListStore->clear();
+        m_current_path = p; m_RefListStore->clear(); std::vector<fs::path> 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") 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<fs::path> f;
-        try {
-            for (const auto& e : fs::directory_iterator(p)) {
-                std::string x = e.path().extension().string(); std::transform(x.begin(), x.end(), x.begin(), ::tolower);
-                if (x == ".jpg" || x == ".png" || x == ".jpeg" || x == ".gif" || x == ".bmp") f.push_back(e.path());
-            }
-        } catch (...) {}
-        std::sort(f.begin(), f.end(), [](const fs::path& a, const fs::path& b){ return NaturalSort()(a.filename().string(), b.filename().string()); });
-        int sz = (m_ComboSize.get_active_row_number() == 0) ? 100 : (m_ComboSize.get_active_row_number() == 2 ? 450 : 220);
-        m_IconView.set_item_width(sz); m_stop_flag = false;
-        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_logic, this, f, sz, false);
+        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, "Select Image Folder", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
-        d.add_button("Cancel", Gtk::RESPONSE_CANCEL); d.add_button("Select", Gtk::RESPONSE_OK);
+        Gtk::FileChooserDialog d(*this, "Open", Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);
+        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_drag_data_received(const Glib::RefPtr<Gdk::DragContext>& c, int, int, const Gtk::SelectionData& s, guint, guint t) {
-        auto uris = s.get_uris();
-        if (!uris.empty()) {
-            std::string f = Glib::filename_from_uri(uris[0]);
-            start_loading_folder(fs::is_directory(f) ? f : fs::path(f).parent_path().string());
-            c->drag_finish(true, false, t);
-        } else c->drag_finish(false, false, t);
-    }
+    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() { m_StackModes.set_visible_child(m_ComboMode.get_active_row_number() == 0 ? "Pattern" : "Replace"); 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);
+    RenamerWindow window; return app->run(window);
 }