version 2

This commit is contained in:
master
2025-11-15 21:49:04 -05:00
parent 8c55eeffd4
commit f2cf0d7d89
28 changed files with 1315 additions and 1007 deletions
+57 -7
View File
@@ -9,6 +9,11 @@ DatabaseManager::DatabaseManager(QObject *parent)
: QObject(parent)
, m_initialized(false)
{
// Initialize search cache
m_searchCache.clear();
// Create index on ocr_text if it doesn't exist
// This will be executed once the database is initialized
}
DatabaseManager::~DatabaseManager()
@@ -78,6 +83,10 @@ bool DatabaseManager::initialize(const QString &dbPath)
return false;
}
// Create an index on the ocr_text column if it doesn't exist
// This will speed up text searches dramatically
query.exec("CREATE INDEX IF NOT EXISTS idx_ocr_text ON ocr_results(ocr_text)");
m_initialized = true;
qDebug() << "Database initialized successfully.";
return true;
@@ -98,6 +107,9 @@ QList<DatabaseManager::ImageItem> DatabaseManager::getAllImages()
m_initialized = false;
return images;
}
// Start transaction to speed up query
m_db.transaction();
QSqlQuery query;
query.prepare("SELECT full_path, ocr_text FROM ocr_results");
@@ -108,6 +120,9 @@ QList<DatabaseManager::ImageItem> DatabaseManager::getAllImages()
}
// Check if files exist as we add them
// Reserve space for results to avoid reallocations
images.reserve(query.size() > 0 ? query.size() : 100);
while (query.next()) {
ImageItem item;
item.filePath = query.value(0).toString();
@@ -119,6 +134,7 @@ QList<DatabaseManager::ImageItem> DatabaseManager::getAllImages()
}
}
m_db.commit();
return images;
}
@@ -140,24 +156,43 @@ QList<DatabaseManager::ImageItem> DatabaseManager::searchImages(const QString &s
// If search text is empty, return all images
if (searchText.isEmpty()) {
// Clear the search cache when empty search is performed
m_searchCache.clear();
return getAllImages();
}
// Check if we have a cached result for this search query
if (m_searchCache.contains(searchText)) {
return m_searchCache[searchText];
}
// Start transaction to speed up queries
m_db.transaction();
QSqlQuery query;
// Use LIKE query with wildcards for flexible searching
query.prepare("SELECT full_path, ocr_text FROM ocr_results WHERE ocr_text LIKE :search");
// Ensure search text is properly sanitized
QString sanitizedSearch = searchText;
sanitizedSearch.replace('\'', "''"); // Escape single quotes
query.bindValue(":search", "%" + sanitizedSearch + "%");
// Optimize the query based on length of search text
if (searchText.length() <= 3) {
// For short search terms, use a more targeted approach
query.prepare("SELECT full_path, ocr_text FROM ocr_results WHERE ocr_text LIKE :search");
query.bindValue(":search", "%" + searchText + "%");
} else {
// For longer search terms, use LIKE with a more specific pattern at start
// which can utilize indexes better if they exist
query.prepare("SELECT full_path, ocr_text FROM ocr_results WHERE ocr_text LIKE :search OR ocr_text LIKE :wordstart");
query.bindValue(":search", "%" + searchText + "%");
query.bindValue(":wordstart", "% " + searchText + "%");
}
if (!query.exec()) {
qDebug() << "Failed to search images:" << query.lastError().text();
m_db.rollback();
return images;
}
// Reserve space for results to avoid reallocations
images.reserve(query.size() > 0 ? query.size() : 100);
while (query.next()) {
ImageItem item;
item.filePath = query.value(0).toString();
@@ -169,5 +204,20 @@ QList<DatabaseManager::ImageItem> DatabaseManager::searchImages(const QString &s
}
}
m_db.commit();
// Cache the result for future queries
if (images.size() > 0) {
m_searchCache.insert(searchText, images);
// Limit cache size to avoid memory issues
if (m_searchCache.size() > MAX_CACHE_SIZE) {
// Remove the first key (oldest entry)
if (!m_searchCache.isEmpty()) {
m_searchCache.remove(m_searchCache.firstKey());
}
}
}
return images;
}
+7
View File
@@ -10,6 +10,7 @@
#include <QList>
#include <QPair>
#include <QThread>
#include <QMap>
/**
* @brief The DatabaseManager class handles all database operations
@@ -63,6 +64,12 @@ public slots:
private:
QSqlDatabase m_db;
bool m_initialized;
// Cache for search results to improve response time
QMap<QString, QList<ImageItem>> m_searchCache;
// Maximum number of cached search queries
static const int MAX_CACHE_SIZE = 50;
};
#endif // DATABASEMANAGER_H
+182 -16
View File
@@ -6,6 +6,9 @@
#include <QUrl>
#include <QPainter>
#include <QApplication>
#include <QFrame>
#include <QHBoxLayout>
#include <QTimer>
// ImageThumbnail implementation
ImageThumbnail::ImageThumbnail(const QString &filePath, QWidget *parent)
@@ -18,7 +21,7 @@ ImageThumbnail::ImageThumbnail(const QString &filePath, QWidget *parent)
setLineWidth(2);
setScaledContents(false);
setCursor(Qt::PointingHandCursor);
setToolTip(filePath);
// We're removing tooltips as requested
// Enable text wrapping and text interaction
setWordWrap(true);
@@ -44,13 +47,22 @@ ImageGallery::ImageGallery(QWidget *parent)
// Create scroll area
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); // Prevent horizontal scrollbar
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
// Create container widget for the grid layout
m_containerWidget = new QWidget(m_scrollArea);
// Make container expand to fill the available width
m_containerWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::MinimumExpanding);
// Create grid layout with minimal spacing
m_gridLayout = new QGridLayout(m_containerWidget);
m_gridLayout->setSpacing(10);
m_gridLayout->setSpacing(THUMBNAIL_SPACING);
m_gridLayout->setContentsMargins(THUMBNAIL_SPACING, THUMBNAIL_SPACING, THUMBNAIL_SPACING, THUMBNAIL_SPACING);
// Initialize column count based on container width
m_columnsCount = 4; // Default value, will be updated in updateGridLayout
m_scrollArea->setWidget(m_containerWidget);
@@ -58,6 +70,19 @@ ImageGallery::ImageGallery(QWidget *parent)
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(m_scrollArea);
setLayout(mainLayout);
// No need to connect to container widget - we'll use resizeEvent instead
// Initial layout update
QTimer::singleShot(0, this, &ImageGallery::updateGridLayout);
// Install event filter on viewport to catch resize events
m_scrollArea->viewport()->installEventFilter(this);
// We'll still keep a periodic check as backup
QTimer *resizeTimer = new QTimer(this);
connect(resizeTimer, &QTimer::timeout, this, &ImageGallery::handleContainerResized);
resizeTimer->start(300); // Check every 300ms
}
ImageGallery::~ImageGallery()
@@ -75,6 +100,9 @@ void ImageGallery::displayImages(const QList<DatabaseManager::ImageItem> &images
// Clear existing thumbnails
clearGallery();
// Update grid layout to ensure correct column count before adding images
updateGridLayout();
const int numImages = images.size();
int row = 0, col = 0;
@@ -93,17 +121,29 @@ void ImageGallery::displayImages(const QList<DatabaseManager::ImageItem> &images
ImageThumbnail *thumbnailLabel = new ImageThumbnail(item.filePath, this);
thumbnailLabel->setPixmap(thumbnail);
// Set tooltip to show file path and partial OCR text
QString tooltipText = item.filePath;
if (!item.ocrText.isEmpty()) {
// Limit OCR text length in tooltip
QString shortOcrText = item.ocrText;
if (shortOcrText.length() > 100) {
shortOcrText = shortOcrText.left(97) + "...";
}
tooltipText += "\n\nOCR Text:\n" + shortOcrText;
}
thumbnailLabel->setToolTip(tooltipText);
// Add filename overlay at the bottom of the thumbnail
QFileInfo fileNameInfo(item.filePath);
QString fileName = fileNameInfo.fileName();
// Create overlay container with dark background
QFrame* overlay = new QFrame(thumbnailLabel);
overlay->setStyleSheet("background-color: rgba(0, 0, 0, 0.7);");
overlay->setFixedHeight(20);
overlay->setFixedWidth(THUMBNAIL_WIDTH);
// Create label for the filename
QLabel* fileNameLabel = new QLabel(fileName, overlay);
fileNameLabel->setStyleSheet("color: white; background: transparent;");
fileNameLabel->setAlignment(Qt::AlignCenter);
fileNameLabel->setFixedWidth(THUMBNAIL_WIDTH - 10);
// Layout for the overlay
QHBoxLayout* overlayLayout = new QHBoxLayout(overlay);
overlayLayout->setContentsMargins(5, 0, 5, 0);
overlayLayout->addWidget(fileNameLabel);
// Position the overlay at the bottom of the thumbnail
overlay->move(0, THUMBNAIL_HEIGHT - overlay->height());
// Connect the thumbnail click signal
connect(thumbnailLabel, &ImageThumbnail::thumbnailClicked,
@@ -115,7 +155,7 @@ void ImageGallery::displayImages(const QList<DatabaseManager::ImageItem> &images
// Update row and column
col++;
if (col >= COLUMNS) {
if (col >= m_columnsCount) {
col = 0;
row++;
}
@@ -127,7 +167,7 @@ void ImageGallery::displayImages(const QList<DatabaseManager::ImageItem> &images
ImageThumbnail *noImagesLabel = new ImageThumbnail("", this);
noImagesLabel->setText("No images found matching your search criteria");
noImagesLabel->setAlignment(Qt::AlignCenter);
m_gridLayout->addWidget(noImagesLabel, 0, 0, 1, COLUMNS);
m_gridLayout->addWidget(noImagesLabel, 0, 0, 1, m_columnsCount);
m_thumbnails.append(noImagesLabel);
}
@@ -202,6 +242,132 @@ QPixmap ImageGallery::createThumbnail(const QString &filePath, int width, int he
return pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
void ImageGallery::resizeEvent(QResizeEvent *event)
{
QWidget::resizeEvent(event);
// Trigger grid layout update when gallery is resized
QTimer::singleShot(0, this, &ImageGallery::updateGridLayout);
// Ensure the container widget fills the viewport width
if (m_scrollArea && m_containerWidget) {
int viewportWidth = m_scrollArea->viewport()->width();
m_containerWidget->setMinimumWidth(viewportWidth);
// Immediately update layout to avoid visual glitches during resize
m_gridLayout->activate();
m_containerWidget->updateGeometry();
}
}
// Event filter to catch viewport resize events
bool ImageGallery::eventFilter(QObject *watched, QEvent *event)
{
// Check if this is a resize event on the viewport
if (watched == m_scrollArea->viewport() && event->type() == QEvent::Resize) {
// Update the grid layout
updateGridLayout();
return false; // Allow event to propagate
}
// Pass unhandled events to parent
return QWidget::eventFilter(watched, event);
}
void ImageGallery::handleContainerResized()
{
// This will be called periodically to check if container size changed
static int lastWidth = -1;
int currentWidth = m_containerWidget->width();
if (currentWidth > 0 && currentWidth != lastWidth) {
lastWidth = currentWidth;
updateGridLayout();
}
}
void ImageGallery::updateGridLayout()
{
// Get the viewport width to determine available space
int viewportWidth = m_scrollArea->viewport()->width();
if (viewportWidth <= 0) {
return; // Can't determine width yet
}
// Force container width to match the viewport
m_containerWidget->setMinimumWidth(viewportWidth);
// Account for left and right margins in the grid layout
int availableWidth = viewportWidth - (m_gridLayout->contentsMargins().left() + m_gridLayout->contentsMargins().right());
// Calculate how many thumbnails can fit, accounting for spacing between them
int effectiveThumbnailWidth = THUMBNAIL_WIDTH + THUMBNAIL_SPACING;
// Calculate column count, allowing even very narrow views to show at least 1 column
int newColumnCount = std::max(1, availableWidth / (THUMBNAIL_WIDTH + 2 * THUMBNAIL_SPACING));
// Only update if column count changes
if (newColumnCount != m_columnsCount) {
m_columnsCount = newColumnCount;
// If we have thumbnails, rearrange them in the grid
if (!m_thumbnails.isEmpty()) {
// We need to readjust all the thumbnails to the new grid layout
// First remove all widgets from the grid
for (auto thumbnail : m_thumbnails) {
m_gridLayout->removeWidget(thumbnail);
}
// Then add them back in the new arrangement
int row = 0, col = 0;
for (auto thumbnail : m_thumbnails) {
m_gridLayout->addWidget(thumbnail, row, col);
col++;
if (col >= m_columnsCount) {
col = 0;
row++;
}
}
// Force layout update
m_gridLayout->invalidate();
m_containerWidget->adjustSize();
m_containerWidget->updateGeometry();
// Handle single column mode specially
if (m_columnsCount == 1) {
// Center the thumbnails in the viewport
int centeringMargin = (viewportWidth - THUMBNAIL_WIDTH) / 2;
centeringMargin = std::max(THUMBNAIL_SPACING, centeringMargin);
m_gridLayout->setContentsMargins(centeringMargin, THUMBNAIL_SPACING, centeringMargin, THUMBNAIL_SPACING);
// Let all thumbnails expand to fill available width in single column mode
for (auto thumbnail : m_thumbnails) {
thumbnail->setMaximumWidth(THUMBNAIL_WIDTH);
thumbnail->setAlignment(Qt::AlignCenter);
}
} else {
// For multi-column, use minimal margins
m_gridLayout->setContentsMargins(THUMBNAIL_SPACING, THUMBNAIL_SPACING, THUMBNAIL_SPACING, THUMBNAIL_SPACING);
// Reset thumbnail constraints
for (auto thumbnail : m_thumbnails) {
thumbnail->setMaximumWidth(QWIDGETSIZE_MAX);
}
// For multi-column, let the container fill the viewport
m_containerWidget->setMinimumWidth(viewportWidth);
}
// Update the layout again after a short delay to handle edge cases
QTimer::singleShot(10, this, [this](){
m_gridLayout->update();
m_containerWidget->update();
});
}
}
}
QPixmap ImageGallery::createPlaceholderThumbnail(int width, int height, const QString &message)
{
// Create a placeholder image with text
+8 -1
View File
@@ -14,9 +14,9 @@
#include "databasemanager.h"
// Global constants
static const int COLUMNS = 4;
static const int THUMBNAIL_WIDTH = 256;
static const int THUMBNAIL_HEIGHT = 256;
static const int THUMBNAIL_SPACING = 2; // Reduced spacing between thumbnails
class ImageThumbnail : public QLabel
{
@@ -52,6 +52,12 @@ public:
public slots:
void handleSearchTextChanged(const QString &searchText);
void handleThumbnailClicked(const QString &filePath);
void handleContainerResized(); // New slot to handle resize events
void updateGridLayout(); // Adjusts grid based on current window size
protected:
void resizeEvent(QResizeEvent *event) override;
bool eventFilter(QObject *watched, QEvent *event) override;
private:
QGridLayout *m_gridLayout;
@@ -59,6 +65,7 @@ private:
QWidget *m_containerWidget;
DatabaseManager *m_dbManager;
QList<ImageThumbnail*> m_thumbnails;
int m_columnsCount; // Dynamic column count based on window size
QPixmap createThumbnail(const QString &filePath, int width, int height);
QPixmap createPlaceholderThumbnail(int width, int height, const QString &message);
+25 -7
View File
@@ -19,6 +19,9 @@ MainWindow::MainWindow(QWidget *parent)
setWindowTitle(tr("Screenshot OCR Gallery"));
resize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
// Remove fixed minimum size to allow for single column layout at any width
// setMinimumSize(640, 480);
// Center window on screen
QScreen *screen = QGuiApplication::primaryScreen();
if (screen) {
@@ -61,6 +64,8 @@ void MainWindow::createLayout()
// Create main layout
m_mainLayout = new QVBoxLayout(m_centralWidget);
m_mainLayout->setSpacing(5); // Reduce spacing between elements
m_mainLayout->setContentsMargins(5, 5, 5, 5); // Reduce margins
// Create title label
m_titleLabel = new QLabel(tr("Screenshot OCR Gallery"), this);
@@ -77,6 +82,7 @@ void MainWindow::createLayout()
// Create gallery widget
m_imageGallery = new ImageGallery(this);
m_imageGallery->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
// Add widgets to layout
m_mainLayout->addWidget(m_titleLabel);
@@ -140,15 +146,16 @@ void MainWindow::handleSearchTextChanged()
return;
}
// Restart the timer each time the user types
m_searchDelayTimer->start();
// If search bar is cleared, immediately show all images
if (m_searchBar->text().isEmpty() && !m_lastSearchText.isEmpty()) {
if (m_searchBar->text().isEmpty()) {
m_lastSearchText.clear();
m_searchDelayTimer->stop();
performSearch();
displayAllImages(); // Show all images immediately
return; // Skip the timer since we've already updated
}
// For non-empty searches, restart the timer each time the user types
m_searchDelayTimer->start();
}
void MainWindow::performSearch()
@@ -203,6 +210,17 @@ void MainWindow::resizeEvent(QResizeEvent *event)
{
QMainWindow::resizeEvent(event);
// The ImageGallery widget will automatically adjust to the new size
// because of the layout system, but you can add custom resize handling here if needed
// Notify the image gallery of the resize event
if (m_imageGallery) {
// The gallery will handle its own layout updates through its resize event
// Just make sure it's visible and has the correct size policy
m_imageGallery->setVisible(true);
}
// Update status bar to show current window dimensions
// This helps with debugging layout issues
if (statusBar() && statusBar()->isVisible()) {
QString sizeInfo = QString("Window size: %1×%2").arg(width()).arg(height());
statusBar()->showMessage(sizeInfo, 1000);
}
}
+1 -1
View File
@@ -46,7 +46,7 @@ private:
bool m_hasValidDatabase;
// Constants
static constexpr int SEARCH_DELAY_MS = 300; // Delay for search typing
static constexpr int SEARCH_DELAY_MS = 50; // Reduced delay for more responsive typing
static constexpr int DEFAULT_WINDOW_WIDTH = 1200;
static constexpr int DEFAULT_WINDOW_HEIGHT = 800;
static const QString DEFAULT_DB_PATH;