Ver código fonte

just saving the code on git

Nicole Portas 1 mês atrás
pai
commit
2d1b206148
3 arquivos alterados com 531 adições e 0 exclusões
  1. 57 0
      Makefile
  2. 51 0
      configure
  3. 423 0
      src/main.cpp

+ 57 - 0
Makefile

@@ -0,0 +1,57 @@
+# Load configuration from ./configure
+include config.mk
+
+TARGET = visual-renamer
+SRC = src/main.cpp
+OBJ = $(SRC:.cpp=.o)
+
+# Default target
+all: check_config $(TARGET)
+
+# Check if config.mk exists
+check_config:
+	@if [ ! -f config.mk ]; then \
+		echo "Error: config.mk not found. Please run ./configure first."; \
+		exit 1; \
+	fi
+
+# Link the executable
+$(TARGET): $(OBJ)
+	@echo "Linking $(TARGET)..."
+	@$(CXX) $(OBJ) -o $(TARGET) $(LIBS)
+	@echo "Build complete."
+
+# Compile source files
+.cpp.o:
+	@echo "Compiling $<..."
+	@$(CXX) $(CXXFLAGS) -c $< -o $@
+
+# Install to system
+install: all
+	@echo "Installing to $(PREFIX)/bin..."
+	@mkdir -p $(PREFIX)/bin
+	@cp $(TARGET) $(PREFIX)/bin/
+	@chmod 755 $(PREFIX)/bin/$(TARGET)
+	@echo "creating desktop entry..."
+	@mkdir -p $(HOME)/.local/share/applications
+	@echo "[Desktop Entry]" > $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Type=Application" >> $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Name=Visual Renamer" >> $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Exec=$(PREFIX)/bin/$(TARGET)" >> $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Icon=system-file-manager" >> $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Categories=Utility;" >> $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Installation successful!"
+
+# Uninstall
+uninstall:
+	@echo "Removing $(PREFIX)/bin/$(TARGET)..."
+	@rm -f $(PREFIX)/bin/$(TARGET)
+	@rm -f $(HOME)/.local/share/applications/visual-renamer.desktop
+	@echo "Uninstall complete."
+
+# Clean build files
+clean:
+	@echo "Cleaning up..."
+	@rm -f $(OBJ) $(TARGET) config.mk
+
+.PHONY: all check_config install uninstall clean

+ 51 - 0
configure

@@ -0,0 +1,51 @@
+#!/bin/bash
+
+# Configuration settings
+OUTPUT_CONFIG="config.mk"
+PKG_NAME="gtkmm-3.0"
+MIN_GTK_VERSION="3.0.0"
+
+echo "Configuring Visual Renamer..."
+
+# 1. Check for C++ Compiler
+if [ -z "$CXX" ]; then
+    CXX=g++
+fi
+echo "Checking for C++ compiler... $CXX"
+if ! command -v $CXX >/dev/null 2>&1; then
+    echo "Error: C++ compiler '$CXX' not found."
+    exit 1
+fi
+
+# 2. Check for pkg-config
+echo "Checking for pkg-config..."
+if ! command -v pkg-config >/dev/null 2>&1; then
+    echo "Error: pkg-config not found."
+    exit 1
+fi
+
+# 3. Check for GTKmm Libraries
+echo "Checking for $PKG_NAME..."
+if pkg-config --exists "$PKG_NAME >= $MIN_GTK_VERSION"; then
+    GTK_CFLAGS=$(pkg-config --cflags "$PKG_NAME")
+    GTK_LIBS=$(pkg-config --libs "$PKG_NAME")
+    echo "  Found $PKG_NAME."
+else
+    echo "Error: $PKG_NAME not found (or version too old)."
+    echo "  On Debian/Ubuntu, try: sudo apt-get install libgtkmm-3.0-dev"
+    exit 1
+fi
+
+# 4. Generate config.mk
+echo "Generating $OUTPUT_CONFIG..."
+
+cat > $OUTPUT_CONFIG <<EOF
+# Auto-generated by ./configure
+CXX = $CXX
+CXXFLAGS = -std=c++17 -Wall -O2 $GTK_CFLAGS
+LIBS = $GTK_LIBS -pthread
+PREFIX ?= /usr/local
+EOF
+
+echo "Configuration successful!"
+echo "Run 'make' to build the project."

+ 423 - 0
src/main.cpp

@@ -0,0 +1,423 @@
+#include <gtkmm.h>
+#include <iostream>
+#include <filesystem>
+#include <vector>
+#include <algorithm>
+#include <string>
+#include <thread>
+#include <atomic>
+#include <mutex>
+#include <deque>
+
+namespace fs = std::filesystem;
+
+// --- Natural Sort Helper ---
+// Sorts strings like humans do: "img1.jpg", "img2.jpg", "img10.jpg"
+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();
+        
+        size_t i = 0, j = 0;
+        while (i < s1.length() && j < s2.length()) {
+            if (isdigit(s1[i]) && isdigit(s2[j])) {
+                size_t start1 = i, start2 = j;
+                while (i < s1.length() && isdigit(s1[i])) i++;
+                while (j < s2.length() && isdigit(s2[j])) j++;
+                
+                long n1 = std::stol(s1.substr(start1, i - start1));
+                long n2 = std::stol(s2.substr(start2, j - start2));
+                
+                if (n1 != n2) return n1 < n2;
+            } else {
+                if (s1[i] != s2[j]) return s1[i] < s2[j];
+                i++; j++;
+            }
+        }
+        return s1.length() < s2.length();
+    }
+};
+
+// --- Data Structure for Thread Communication ---
+struct LoadedItem {
+    std::string path;
+    std::string filename;
+    Glib::RefPtr<Gdk::Pixbuf> pixbuf;
+};
+
+class RenamerWindow : public Gtk::Window {
+public:
+    RenamerWindow() : m_Dispatcher() {
+        set_title("Visual Renamer Pro");
+        set_default_size(1200, 800);
+        
+        // Connect the background thread signal to the UI thread
+        m_Dispatcher.connect(sigc::mem_fun(*this, &RenamerWindow::on_worker_notification));
+
+        // --- 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);
+
+        // Open Button
+        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_EntryPattern.set_text("image_###");
+        m_Toolbar.pack_start(m_EntryPattern, Gtk::PACK_SHRINK, 5);
+        
+        // --- SIZE DROPDOWN ---
+        m_LblSize.set_text("  Size:");
+        m_Toolbar.pack_start(m_LblSize, Gtk::PACK_SHRINK, 5);
+        
+        m_ComboSize.append("Small (100px)");
+        m_ComboSize.append("Medium (250px)");
+        m_ComboSize.append("Large (500px)");
+        m_ComboSize.set_active(1); // Default to Medium (Index 1)
+        m_ComboSize.signal_changed().connect(sigc::mem_fun(*this, &RenamerWindow::on_size_changed));
+        m_Toolbar.pack_start(m_ComboSize, Gtk::PACK_SHRINK, 5);
+        // ---------------------
+
+        // 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);
+
+        // --- Icon View Area ---
+        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); 
+        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);
+        
+        // FIX: Use .index() to prevent compile error
+        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_ScrolledWindow.add(m_IconView);
+
+        // --- Status Bar ---
+        m_Statusbar.push("Ready.");
+        m_VBox.pack_end(m_Statusbar, Gtk::PACK_SHRINK);
+
+        // --- Drag and Drop (System -> Window) ---
+        std::vector<Gtk::TargetEntry> listTargets;
+        listTargets.push_back(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();
+    }
+
+    ~RenamerWindow() override {
+        m_stop_flag = true;
+        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+    }
+
+protected:
+    // Widgets
+    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;
+    Gtk::ScrolledWindow m_ScrolledWindow;
+    Gtk::IconView m_IconView;
+    Gtk::Statusbar m_Statusbar;
+    Gtk::Menu m_ContextMenu;
+
+    // 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); }
+        Gtk::TreeModelColumn<std::string> m_col_path;
+        Gtk::TreeModelColumn<std::string> m_col_name;
+        Gtk::TreeModelColumn<Glib::RefPtr<Gdk::Pixbuf>> m_col_pixbuf;
+        Gtk::TreeModelColumn<std::string> m_col_orig_name;
+    };
+    ModelColumns m_Columns;
+    Glib::RefPtr<Gtk::ListStore> m_RefListStore;
+
+    // Threading
+    std::thread m_WorkerThread;
+    std::atomic<bool> m_stop_flag{false};
+    Glib::Dispatcher m_Dispatcher; 
+    std::mutex m_QueueMutex;
+    std::deque<LoadedItem> m_ResultQueue; 
+
+    // --- Helper Logic ---
+
+    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;
+    }
+
+    // Called when Dropdown Changes
+    void on_size_changed() {
+        int new_size = get_current_size();
+        
+        // 1. Collect current files (preserve order)
+        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]));
+        }
+
+        if (current_files.empty()) return;
+
+        // 2. Stop Thread
+        m_stop_flag = true;
+        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+
+        // 3. Clear UI & Restart Thread
+        m_RefListStore->clear();
+        m_IconView.set_item_width(new_size); 
+        m_Statusbar.push("Resizing...");
+
+        m_stop_flag = false;
+        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_files, this, current_files, new_size);
+    }
+
+    void start_loading_folder(std::string path) {
+        m_stop_flag = true;
+        if (m_WorkerThread.joinable()) m_WorkerThread.join();
+
+        m_RefListStore->clear();
+        m_BtnRename.set_sensitive(false);
+        m_Statusbar.push("Loading images from: " + path);
+        
+        int size = get_current_size();
+        m_IconView.set_item_width(size);
+
+        m_stop_flag = false;
+        m_WorkerThread = std::thread(&RenamerWindow::worker_thread_folder, this, path, 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 {
+            // Load parent folder if a single file is dropped
+            start_loading_folder(fs::path(filename).parent_path().string());
+            context->drag_finish(true, false, time);
+        }
+    }
+
+    // FIX: Correct logic for getting path at position
+    bool on_iconview_button_press(GdkEventButton* event) {
+        if (event->type == GDK_BUTTON_PRESS && event->button == 3) { 
+            // FIX: Cast to int to match GTKmm signature
+            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;
+            }
+        }
+        return false;
+    }
+
+    bool on_iconview_key_press(GdkEventKey* event) {
+        if (event->keyval == GDK_KEY_Delete) {
+            on_menu_remove();
+            return true;
+        }
+        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(); 
+    }
+
+    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;
+        }
+        m_Statusbar.push("Items remaining: " + std::to_string(index - 1));
+    }
+
+    // --- WORKER: Load from Directory ---
+    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());
+                }
+            }
+        } catch (...) { return; }
+
+        std::sort(files.begin(), files.end(), NaturalSort());
+        process_files(files, size);
+    }
+
+    // --- WORKER: Load from List ---
+    void worker_thread_files(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();
+
+            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;
+                
+                // Scale in background thread
+                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)); 
+            } catch (...) {}
+        }
+    }
+
+    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();
+
+            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_pixbuf] = item.pixbuf;
+        }
+        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;
+
+        auto children = m_RefListStore->children();
+        int index = 1;
+        std::vector<std::pair<std::string, std::string>> rename_map;
+
+        // 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(...) {}
+        }
+
+        // 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;
+        }
+
+        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() < 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_Statusbar.push("Rename Complete.");
+    }
+};
+
+int main(int argc, char *argv[]) {
+    Glib::thread_init(); 
+    auto app = Gtk::Application::create(argc, argv, "org.gtkmm.renamer_dropdown");
+    RenamerWindow window;
+    return app->run(window);
+}