first commit

This commit is contained in:
master
2025-11-15 21:14:36 -05:00
commit 8c55eeffd4
78 changed files with 29430 additions and 0 deletions
+173
View File
@@ -0,0 +1,173 @@
#include "databasemanager.h"
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QVariant>
#include <QFileInfo>
DatabaseManager::DatabaseManager(QObject *parent)
: QObject(parent)
, m_initialized(false)
{
}
DatabaseManager::~DatabaseManager()
{
if (m_db.isOpen()) {
m_db.close();
}
}
bool DatabaseManager::initialize(const QString &dbPath)
{
// Check if database is already initialized
if (m_initialized) {
return true;
}
// Check if file exists
QFileInfo fileInfo(dbPath);
if (!fileInfo.exists() || !fileInfo.isFile()) {
qDebug() << "Database file does not exist:" << dbPath;
return false;
}
// Set up database connection
m_db = QSqlDatabase::addDatabase("QSQLITE");
m_db.setDatabaseName(dbPath);
// Open database
if (!m_db.open()) {
qDebug() << "Failed to open database:" << m_db.lastError().text();
return false;
}
// Verify required table exists
QSqlQuery query;
if (!query.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='ocr_results'")) {
qDebug() << "Failed to execute query:" << query.lastError().text();
m_db.close();
return false;
}
if (!query.next()) {
qDebug() << "The required table 'ocr_results' does not exist in the database.";
m_db.close();
return false;
}
// Verify the table has the required columns
if (!query.exec("PRAGMA table_info(ocr_results)")) {
qDebug() << "Failed to get table info:" << query.lastError().text();
m_db.close();
return false;
}
bool hasFullPath = false;
bool hasOcrText = false;
while (query.next()) {
QString columnName = query.value(1).toString();
if (columnName == "full_path") hasFullPath = true;
if (columnName == "ocr_text") hasOcrText = true;
}
if (!hasFullPath || !hasOcrText) {
qDebug() << "Missing required columns in ocr_results table. Need 'full_path' and 'ocr_text'";
m_db.close();
return false;
}
m_initialized = true;
qDebug() << "Database initialized successfully.";
return true;
}
QList<DatabaseManager::ImageItem> DatabaseManager::getAllImages()
{
QList<ImageItem> images;
if (!m_initialized) {
qDebug() << "Database not initialized.";
return images;
}
// Verify database is still connected
if (!m_db.isOpen() && !m_db.open()) {
qDebug() << "Database connection lost and cannot be reopened:" << m_db.lastError().text();
m_initialized = false;
return images;
}
QSqlQuery query;
query.prepare("SELECT full_path, ocr_text FROM ocr_results");
if (!query.exec()) {
qDebug() << "Failed to fetch images:" << query.lastError().text();
return images;
}
// Check if files exist as we add them
while (query.next()) {
ImageItem item;
item.filePath = query.value(0).toString();
item.ocrText = query.value(1).toString();
// Only add images that have a non-empty path
if (!item.filePath.isEmpty()) {
images.append(item);
}
}
return images;
}
QList<DatabaseManager::ImageItem> DatabaseManager::searchImages(const QString &searchText)
{
QList<ImageItem> images;
if (!m_initialized) {
qDebug() << "Database not initialized.";
return images;
}
// Verify database is still connected
if (!m_db.isOpen() && !m_db.open()) {
qDebug() << "Database connection lost and cannot be reopened:" << m_db.lastError().text();
m_initialized = false;
return images;
}
// If search text is empty, return all images
if (searchText.isEmpty()) {
return getAllImages();
}
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 + "%");
if (!query.exec()) {
qDebug() << "Failed to search images:" << query.lastError().text();
return images;
}
while (query.next()) {
ImageItem item;
item.filePath = query.value(0).toString();
item.ocrText = query.value(1).toString();
// Only add images that have a non-empty path
if (!item.filePath.isEmpty()) {
images.append(item);
}
}
return images;
}
+68
View File
@@ -0,0 +1,68 @@
#ifndef DATABASEMANAGER_H
#define DATABASEMANAGER_H
#include <QObject>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlError>
#include <QDebug>
#include <QString>
#include <QList>
#include <QPair>
#include <QThread>
/**
* @brief The DatabaseManager class handles all database operations
* for the screenshot gallery application.
*/
class DatabaseManager : public QObject
{
Q_OBJECT
public:
/**
* @brief Struct to represent an image item with its path and OCR text
*/
struct ImageItem {
QString filePath;
QString ocrText;
};
/**
* @brief DatabaseManager constructor
* @param parent The parent QObject
*/
explicit DatabaseManager(QObject *parent = nullptr);
/**
* @brief DatabaseManager destructor
*/
~DatabaseManager();
/**
* @brief Initialize the database connection
* @param dbPath Path to the SQLite database
* @return True if connection was successful, false otherwise
*/
bool initialize(const QString &dbPath);
/**
* @brief Get all images from the database
* @return List of ImageItem objects
*/
QList<ImageItem> getAllImages();
public slots:
/**
* @brief Search for images matching the search text
* @param searchText Text to search for in OCR results
* @return List of ImageItem objects that match the search
*/
QList<ImageItem> searchImages(const QString &searchText);
private:
QSqlDatabase m_db;
bool m_initialized;
};
#endif // DATABASEMANAGER_H
+229
View File
@@ -0,0 +1,229 @@
#include "imagegallery.h"
#include <QMouseEvent>
#include <QFileInfo>
#include <QProcess>
#include <QDesktopServices>
#include <QUrl>
#include <QPainter>
#include <QApplication>
// ImageThumbnail implementation
ImageThumbnail::ImageThumbnail(const QString &filePath, QWidget *parent)
: QLabel(parent)
, m_filePath(filePath)
{
setFixedSize(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
setAlignment(Qt::AlignCenter);
setFrameStyle(QFrame::Panel | QFrame::Sunken);
setLineWidth(2);
setScaledContents(false);
setCursor(Qt::PointingHandCursor);
setToolTip(filePath);
// Enable text wrapping and text interaction
setWordWrap(true);
setTextInteractionFlags(Qt::TextSelectableByMouse);
// Set a minimum size policy
setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
}
void ImageThumbnail::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
emit thumbnailClicked(m_filePath);
}
QLabel::mousePressEvent(event);
}
// ImageGallery implementation
ImageGallery::ImageGallery(QWidget *parent)
: QWidget(parent)
, m_dbManager(nullptr)
{
// Create scroll area
m_scrollArea = new QScrollArea(this);
m_scrollArea->setWidgetResizable(true);
m_scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
// Create container widget for the grid layout
m_containerWidget = new QWidget(m_scrollArea);
m_gridLayout = new QGridLayout(m_containerWidget);
m_gridLayout->setSpacing(10);
m_scrollArea->setWidget(m_containerWidget);
// Create layout for the gallery widget
QVBoxLayout *mainLayout = new QVBoxLayout(this);
mainLayout->addWidget(m_scrollArea);
setLayout(mainLayout);
}
ImageGallery::~ImageGallery()
{
clearGallery();
}
void ImageGallery::setDatabaseManager(DatabaseManager *dbManager)
{
m_dbManager = dbManager;
}
void ImageGallery::displayImages(const QList<DatabaseManager::ImageItem> &images)
{
// Clear existing thumbnails
clearGallery();
const int numImages = images.size();
int row = 0, col = 0;
for (int i = 0; i < numImages; ++i) {
const auto &item = images[i];
// Check if file exists before creating thumbnail
QFileInfo fileInfo(item.filePath);
if (!fileInfo.exists() || !fileInfo.isReadable()) {
qDebug() << "Image file does not exist or is not readable:" << item.filePath;
continue; // Skip this image
}
// Create and add thumbnail
QPixmap thumbnail = createThumbnail(item.filePath, THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT);
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);
// Connect the thumbnail click signal
connect(thumbnailLabel, &ImageThumbnail::thumbnailClicked,
this, &ImageGallery::handleThumbnailClicked);
// Add to grid
m_gridLayout->addWidget(thumbnailLabel, row, col);
m_thumbnails.append(thumbnailLabel);
// Update row and column
col++;
if (col >= COLUMNS) {
col = 0;
row++;
}
}
// Display a message when no images are found
if (m_thumbnails.isEmpty()) {
// Create a special ImageThumbnail for the "no images" message
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_thumbnails.append(noImagesLabel);
}
// Add stretch to the bottom of the grid
m_gridLayout->setRowStretch(row + 1, 1);
}
void ImageGallery::clearGallery()
{
// Remove all thumbnails
for (auto thumbnail : m_thumbnails) {
m_gridLayout->removeWidget(thumbnail);
disconnect(thumbnail, nullptr, this, nullptr);
delete thumbnail;
}
m_thumbnails.clear();
}
void ImageGallery::handleSearchTextChanged(const QString &searchText)
{
if (m_dbManager) {
QList<DatabaseManager::ImageItem> images = m_dbManager->searchImages(searchText);
displayImages(images);
}
}
void ImageGallery::handleThumbnailClicked(const QString &filePath)
{
// Show a wait cursor while attempting to open the file
QApplication::setOverrideCursor(Qt::WaitCursor);
// Check if file exists
QFileInfo fileInfo(filePath);
if (fileInfo.exists() && fileInfo.isFile()) {
try {
// Use QDesktopServices to open the file with the default application
bool success = QDesktopServices::openUrl(QUrl::fromLocalFile(filePath));
if (!success) {
qDebug() << "Failed to open file with default application:" << filePath;
}
} catch (const std::exception &e) {
qDebug() << "Exception occurred while opening file:" << e.what();
}
} else {
qDebug() << "File does not exist:" << filePath;
}
// Restore cursor
QApplication::restoreOverrideCursor();
}
QPixmap ImageGallery::createThumbnail(const QString &filePath, int width, int height)
{
// Verify file exists before attempting to load
QFileInfo fileInfo(filePath);
if (!fileInfo.exists() || !fileInfo.isReadable()) {
qDebug() << "Image file does not exist or is not readable:" << filePath;
return createPlaceholderThumbnail(width, height, "File not found");
}
// Try to load the image
QPixmap pixmap(filePath);
// If loading failed, create a placeholder
if (pixmap.isNull()) {
qDebug() << "Failed to load image:" << filePath;
return createPlaceholderThumbnail(width, height, "Failed to load");
}
// Scale pixmap while maintaining aspect ratio
return pixmap.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
}
QPixmap ImageGallery::createPlaceholderThumbnail(int width, int height, const QString &message)
{
// Create a placeholder image with text
QPixmap placeholder(width, height);
placeholder.fill(Qt::lightGray);
// Draw some text on the placeholder
QPainter painter(&placeholder);
painter.setPen(Qt::darkGray);
// Use a nice font
QFont font = painter.font();
font.setPointSize(12);
font.setBold(true);
painter.setFont(font);
// Draw the text centered in the pixmap
painter.drawText(placeholder.rect(), Qt::AlignCenter, message);
// Add a border
painter.setPen(QPen(Qt::darkGray, 2));
painter.drawRect(1, 1, width-2, height-2);
return placeholder;
}
+67
View File
@@ -0,0 +1,67 @@
#ifndef IMAGEGALLERY_H
#define IMAGEGALLERY_H
#include <QWidget>
#include <QGridLayout>
#include <QLabel>
#include <QPixmap>
#include <QList>
#include <QScrollArea>
#include <QProcess>
#include <QDebug>
#include <QPushButton>
#include <QResizeEvent>
#include "databasemanager.h"
// Global constants
static const int COLUMNS = 4;
static const int THUMBNAIL_WIDTH = 256;
static const int THUMBNAIL_HEIGHT = 256;
class ImageThumbnail : public QLabel
{
Q_OBJECT
public:
explicit ImageThumbnail(const QString &filePath, QWidget *parent = nullptr);
QString getFilePath() const { return m_filePath; }
protected:
void mousePressEvent(QMouseEvent *event) override;
signals:
void thumbnailClicked(const QString &filePath);
private:
QString m_filePath;
};
class ImageGallery : public QWidget
{
Q_OBJECT
public:
explicit ImageGallery(QWidget *parent = nullptr);
~ImageGallery();
void setDatabaseManager(DatabaseManager *dbManager);
void displayImages(const QList<DatabaseManager::ImageItem> &images);
void clearGallery();
public slots:
void handleSearchTextChanged(const QString &searchText);
void handleThumbnailClicked(const QString &filePath);
private:
QGridLayout *m_gridLayout;
QScrollArea *m_scrollArea;
QWidget *m_containerWidget;
DatabaseManager *m_dbManager;
QList<ImageThumbnail*> m_thumbnails;
QPixmap createThumbnail(const QString &filePath, int width, int height);
QPixmap createPlaceholderThumbnail(int width, int height, const QString &message);
};
#endif // IMAGEGALLERY_H
+18
View File
@@ -0,0 +1,18 @@
#include <QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Set application properties
app.setApplicationName("Screenshot Gallery");
app.setApplicationVersion("1.0.0");
// Create and show main window
MainWindow mainWindow;
mainWindow.show();
// Enter application event loop
return app.exec();
}
+208
View File
@@ -0,0 +1,208 @@
#include "mainwindow.h"
#include <QApplication>
#include <QScreen>
#include <QResizeEvent>
#include <QFileInfo>
#include <QMessageBox>
#include <stdexcept>
// Define the static constant for the default database path
const QString MainWindow::DEFAULT_DB_PATH = "/home/master/screenshot_ocr.db";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, m_dbManager(new DatabaseManager(this))
, m_searchDelayTimer(new QTimer(this))
, m_hasValidDatabase(false)
{
// Set window title and size
setWindowTitle(tr("Screenshot OCR Gallery"));
resize(DEFAULT_WINDOW_WIDTH, DEFAULT_WINDOW_HEIGHT);
// Center window on screen
QScreen *screen = QGuiApplication::primaryScreen();
if (screen) {
const QRect availableGeometry = screen->availableGeometry();
const QSize size = geometry().size();
move((availableGeometry.width() - size.width()) / 2,
(availableGeometry.height() - size.height()) / 2);
}
// Set up UI
createLayout();
// Initialize timer for delayed search
m_searchDelayTimer->setSingleShot(true);
m_searchDelayTimer->setInterval(SEARCH_DELAY_MS);
connect(m_searchDelayTimer, &QTimer::timeout, this, &MainWindow::performSearch);
// Initialize database and display images
initializeDatabase();
displayAllImages();
// Set focus to search bar
m_searchBar->setFocus();
// Set status bar
statusBar()->showMessage(tr("Ready"));
updateStatusBar();
}
MainWindow::~MainWindow()
{
// All QObjects will be automatically deleted via parent-child relationship
}
void MainWindow::createLayout()
{
// Create central widget
m_centralWidget = new QWidget(this);
setCentralWidget(m_centralWidget);
// Create main layout
m_mainLayout = new QVBoxLayout(m_centralWidget);
// Create title label
m_titleLabel = new QLabel(tr("Screenshot OCR Gallery"), this);
QFont titleFont = m_titleLabel->font();
titleFont.setPointSize(16);
titleFont.setBold(true);
m_titleLabel->setFont(titleFont);
m_titleLabel->setAlignment(Qt::AlignCenter);
// Create search bar
m_searchBar = new QLineEdit(this);
m_searchBar->setPlaceholderText(tr("Search OCR text..."));
m_searchBar->setClearButtonEnabled(true);
// Create gallery widget
m_imageGallery = new ImageGallery(this);
// Add widgets to layout
m_mainLayout->addWidget(m_titleLabel);
m_mainLayout->addWidget(m_searchBar);
m_mainLayout->addWidget(m_imageGallery, 1);
// Connect signals
connect(m_searchBar, &QLineEdit::textChanged, this, &MainWindow::handleSearchTextChanged);
}
void MainWindow::initializeDatabase()
{
// Check if database file exists
QFileInfo dbFileInfo(DEFAULT_DB_PATH);
if (!dbFileInfo.exists() || !dbFileInfo.isFile()) {
QString errorMsg = tr("Database file not found: %1").arg(DEFAULT_DB_PATH);
statusBar()->showMessage(errorMsg, 10000);
QMessageBox::critical(this, tr("Database Error"), errorMsg);
qDebug() << "Database file not found:" << DEFAULT_DB_PATH;
m_hasValidDatabase = false;
return;
}
// Try to initialize database
if (!m_dbManager->initialize(DEFAULT_DB_PATH)) {
QString errorMsg = tr("Failed to connect to database");
statusBar()->showMessage(errorMsg, 10000);
QMessageBox::warning(this, tr("Database Error"),
tr("Failed to connect to database: %1\nThe application will continue with limited functionality.")
.arg(DEFAULT_DB_PATH));
qDebug() << "Failed to initialize database";
m_hasValidDatabase = false;
} else {
statusBar()->showMessage(tr("Connected to database"), 3000);
m_imageGallery->setDatabaseManager(m_dbManager);
m_hasValidDatabase = true;
}
}
void MainWindow::displayAllImages()
{
if (!m_hasValidDatabase) {
m_imageGallery->clearGallery();
statusBar()->showMessage(tr("No database connection available. Cannot display images."), 5000);
return;
}
QList<DatabaseManager::ImageItem> allImages = m_dbManager->getAllImages();
if (allImages.isEmpty()) {
statusBar()->showMessage(tr("No images found in database"), 5000);
}
m_imageGallery->displayImages(allImages);
updateStatusBar();
}
void MainWindow::handleSearchTextChanged()
{
if (!m_hasValidDatabase) {
statusBar()->showMessage(tr("Database not available for search"), 3000);
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()) {
m_lastSearchText.clear();
m_searchDelayTimer->stop();
performSearch();
}
}
void MainWindow::performSearch()
{
if (!m_hasValidDatabase) {
statusBar()->showMessage(tr("Cannot perform search: No database connection"), 3000);
return;
}
QString currentText = m_searchBar->text();
// Only perform search if text has changed
if (currentText != m_lastSearchText) {
m_lastSearchText = currentText;
try {
m_imageGallery->handleSearchTextChanged(currentText);
updateStatusBar();
} catch (const std::exception &e) {
QString errorMsg = tr("Search error: %1").arg(e.what());
statusBar()->showMessage(errorMsg, 5000);
qDebug() << errorMsg;
}
}
}
void MainWindow::updateStatusBar()
{
if (!m_hasValidDatabase) {
statusBar()->showMessage(tr("Database not connected"));
return;
}
try {
int imageCount = m_dbManager->searchImages(m_lastSearchText).count();
int totalImages = m_dbManager->getAllImages().count();
if (m_lastSearchText.isEmpty()) {
statusBar()->showMessage(tr("Displaying all %1 images").arg(totalImages));
} else {
statusBar()->showMessage(tr("Found %1 of %2 images matching \"%3\"")
.arg(imageCount)
.arg(totalImages)
.arg(m_lastSearchText));
}
} catch (const std::exception &e) {
statusBar()->showMessage(tr("Error updating status: %1").arg(e.what()), 5000);
}
}
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
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLineEdit>
#include <QVBoxLayout>
#include <QLabel>
#include <QStatusBar>
#include <QTimer>
#include <QDebug>
#include "databasemanager.h"
#include "imagegallery.h"
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
void resizeEvent(QResizeEvent *event) override;
private slots:
void handleSearchTextChanged();
void performSearch();
void updateStatusBar();
private:
void createLayout();
void initializeDatabase();
void displayAllImages();
// UI Components
QWidget *m_centralWidget;
QVBoxLayout *m_mainLayout;
QLineEdit *m_searchBar;
QLabel *m_titleLabel;
ImageGallery *m_imageGallery;
// Data
DatabaseManager *m_dbManager;
QString m_lastSearchText;
QTimer *m_searchDelayTimer;
bool m_hasValidDatabase;
// Constants
static constexpr int SEARCH_DELAY_MS = 300; // Delay for search typing
static constexpr int DEFAULT_WINDOW_WIDTH = 1200;
static constexpr int DEFAULT_WINDOW_HEIGHT = 800;
static const QString DEFAULT_DB_PATH;
};
#endif // MAINWINDOW_H