refactor: more comments and minor optimizations
This commit is contained in:
@@ -44,4 +44,3 @@ jobs:
|
|||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
with:
|
with:
|
||||||
files: wallpaper-carousel-linux-x64.tar.gz
|
files: wallpaper-carousel-linux-x64.tar.gz
|
||||||
generate_release_notes: true
|
|
||||||
|
|||||||
+35
-15
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:34:52
|
* @Date: 2025-08-05 01:34:52
|
||||||
* @LastEditTime: 2026-01-15 03:40:25
|
* @LastEditTime: 2026-01-15 07:18:46
|
||||||
* @Description: Configuration manager.
|
* @Description: Configuration manager.
|
||||||
*/
|
*/
|
||||||
#ifndef CONFIG_H
|
#ifndef CONFIG_H
|
||||||
@@ -11,6 +11,26 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
|
|
||||||
|
// Config entries:
|
||||||
|
//
|
||||||
|
// wallpaper.paths array image paths
|
||||||
|
// wallpaper.dirs array directories to search for images.
|
||||||
|
// all images in these directories will be added.
|
||||||
|
// NOT recursive.
|
||||||
|
// wallpaper.excludes array exclude patterns
|
||||||
|
//
|
||||||
|
// action.confirm string command to execute on confirm
|
||||||
|
//
|
||||||
|
// style.aspect_ratio number (width / height) of each image
|
||||||
|
// style.image_width number width of each image
|
||||||
|
// style.image_focus_width number width of focused image
|
||||||
|
// style.window_width number fixed window width
|
||||||
|
// style.window_height number fixed window height
|
||||||
|
// style.no_loading_screen boolean disable loading screen and load images while updating UI in batches
|
||||||
|
//
|
||||||
|
// sort.type string sorting type: "none", "name", "date", "size"
|
||||||
|
// sort.reverse boolean whether to reverse the sorting order
|
||||||
|
|
||||||
class Config : public QObject {
|
class Config : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
@@ -23,33 +43,33 @@ class Config : public QObject {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct WallpaperConfigItems {
|
struct WallpaperConfigItems {
|
||||||
QStringList paths;
|
QStringList paths; // "wallpaper.paths"
|
||||||
QStringList dirs;
|
QStringList dirs; // "wallpaper.dirs"
|
||||||
QStringList excludes;
|
QStringList excludes; // "wallpaper.excludes"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ActionConfigItems {
|
struct ActionConfigItems {
|
||||||
QString confirm;
|
QString confirm; // "action.confirm"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct StyleConfigItems {
|
struct StyleConfigItems {
|
||||||
double aspectRatio = 1.6;
|
double aspectRatio = 1.6; // "style.aspect_ratio"
|
||||||
int imageWidth = 320;
|
int imageWidth = 320; // "style.image_width"
|
||||||
int imageFocusWidth = 480;
|
int imageFocusWidth = 480; // "style.image_focus_width"
|
||||||
int windowWidth = 750;
|
int windowWidth = 750; // "style.window_width"
|
||||||
int windowHeight = 500;
|
int windowHeight = 500; // "style.window_height"
|
||||||
bool noLoadingScreen = false;
|
bool noLoadingScreen = false; // "style.no_loading_screen"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SortConfigItems {
|
struct SortConfigItems {
|
||||||
SortType type = SortType::Name;
|
SortType type = SortType::Name; // "sort.type"
|
||||||
bool reverse = false;
|
bool reverse = false; // "sort.reverse"
|
||||||
};
|
};
|
||||||
|
|
||||||
Config(
|
Config(
|
||||||
const QString& configDir,
|
const QString& configDir, // Fixed, usually "~/.config/wallpaper-carousel"
|
||||||
const QStringList& searchDirs = {},
|
const QStringList& searchDirs = {},
|
||||||
const QString& configPath = "",
|
const QString& configPath = "", // Override the default config path
|
||||||
QObject* parent = nullptr);
|
QObject* parent = nullptr);
|
||||||
|
|
||||||
~Config();
|
~Config();
|
||||||
|
|||||||
+4
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-11-30 20:32:27
|
* @Date: 2025-11-30 20:32:27
|
||||||
* @LastEditTime: 2026-01-15 00:48:24
|
* @LastEditTime: 2026-01-15 07:09:29
|
||||||
* @Description: Image item widget for displaying an image.
|
* @Description: Image item widget for displaying an image.
|
||||||
*/
|
*/
|
||||||
#include "image_item.h"
|
#include "image_item.h"
|
||||||
@@ -16,6 +16,7 @@ ImageData* ImageData::create(const QString& p, const int initWidth, const int in
|
|||||||
ImageData* data = new ImageData(p);
|
ImageData* data = new ImageData(p);
|
||||||
data->image = new QImage();
|
data->image = new QImage();
|
||||||
|
|
||||||
|
// Use QImageReader for better performance
|
||||||
QImageReader reader(p);
|
QImageReader reader(p);
|
||||||
if (!reader.canRead()) {
|
if (!reader.canRead()) {
|
||||||
warn(QString("Failed to load image from path: %1").arg(p));
|
warn(QString("Failed to load image from path: %1").arg(p));
|
||||||
@@ -26,6 +27,7 @@ ImageData* ImageData::create(const QString& p, const int initWidth, const int in
|
|||||||
const QSize targetSize(initWidth, initHeight);
|
const QSize targetSize(initWidth, initHeight);
|
||||||
const QSize originalSize = reader.size();
|
const QSize originalSize = reader.size();
|
||||||
|
|
||||||
|
// Scale the image to fit the target size while maintaining aspect ratio
|
||||||
if (originalSize.isValid()) {
|
if (originalSize.isValid()) {
|
||||||
double widthRatio = (double)targetSize.width() / originalSize.width();
|
double widthRatio = (double)targetSize.width() / originalSize.width();
|
||||||
double heightRatio = (double)targetSize.height() / originalSize.height();
|
double heightRatio = (double)targetSize.height() / originalSize.height();
|
||||||
@@ -41,6 +43,7 @@ ImageData* ImageData::create(const QString& p, const int initWidth, const int in
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Crop to target size if necessary
|
||||||
if (data->image->size() != targetSize) {
|
if (data->image->size() != targetSize) {
|
||||||
int x = (data->image->width() - targetSize.width()) / 2;
|
int x = (data->image->width() - targetSize.width()) / 2;
|
||||||
int y = (data->image->height() - targetSize.height()) / 2;
|
int y = (data->image->height() - targetSize.height()) / 2;
|
||||||
|
|||||||
+11
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-11-30 20:31:15
|
* @Date: 2025-11-30 20:31:15
|
||||||
* @LastEditTime: 2026-01-14 23:32:58
|
* @LastEditTime: 2026-01-15 07:10:21
|
||||||
* @Description: Image item widget for displaying an image.
|
* @Description: Image item widget for displaying an image.
|
||||||
*/
|
*/
|
||||||
#ifndef IMAGE_ITEM_H
|
#ifndef IMAGE_ITEM_H
|
||||||
@@ -13,6 +13,9 @@
|
|||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPropertyAnimation>
|
#include <QPropertyAnimation>
|
||||||
|
|
||||||
|
class ImageData;
|
||||||
|
class ImageItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Data structure to hold image information
|
* @brief Data structure to hold image information
|
||||||
* and can be safely created and passed between threads.
|
* and can be safely created and passed between threads.
|
||||||
@@ -26,6 +29,7 @@ class ImageData {
|
|||||||
|
|
||||||
~ImageData() { releaseImage(); }
|
~ImageData() { releaseImage(); }
|
||||||
|
|
||||||
|
// Optimization: release image data as soon as they are no longer needed
|
||||||
void releaseImage() { delete image, image = nullptr; }
|
void releaseImage() { delete image, image = nullptr; }
|
||||||
|
|
||||||
[[nodiscard]] const QImage& getImage() const { return *image; }
|
[[nodiscard]] const QImage& getImage() const { return *image; }
|
||||||
@@ -67,6 +71,12 @@ class ImageItem : public QLabel {
|
|||||||
|
|
||||||
[[nodiscard]] qint64 getFileSize() const { return m_data->getFileInfo().size(); }
|
[[nodiscard]] qint64 getFileSize() const { return m_data->getFileInfo().size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set focus state by scaling the image label
|
||||||
|
*
|
||||||
|
* @param focus whether to focus
|
||||||
|
* @param animate whether to animate the transition
|
||||||
|
*/
|
||||||
void setFocus(bool focus = true, bool animate = true);
|
void setFocus(bool focus = true, bool animate = true);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
+15
-13
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:22:53
|
* @Date: 2025-08-05 01:22:53
|
||||||
* @LastEditTime: 2026-01-15 05:11:52
|
* @LastEditTime: 2026-01-15 07:24:52
|
||||||
* @Description: Animated carousel widget for displaying and selecting images.
|
* @Description: Animated carousel widget for displaying and selecting images.
|
||||||
*/
|
*/
|
||||||
#include "images_carousel.h"
|
#include "images_carousel.h"
|
||||||
@@ -50,11 +50,6 @@ ImagesCarousel::ImagesCarousel(const Config::StyleConfigItems& styleConfig,
|
|||||||
this,
|
this,
|
||||||
&ImagesCarousel::_onImagesLoaded);
|
&ImagesCarousel::_onImagesLoaded);
|
||||||
|
|
||||||
connect(this,
|
|
||||||
&ImagesCarousel::stopped,
|
|
||||||
this,
|
|
||||||
&ImagesCarousel::_onImagesLoaded);
|
|
||||||
|
|
||||||
// Auto focus when scrolling
|
// Auto focus when scrolling
|
||||||
m_scrollDebounceTimer = new QTimer(this);
|
m_scrollDebounceTimer = new QTimer(this);
|
||||||
m_scrollDebounceTimer->setSingleShot(true);
|
m_scrollDebounceTimer->setSingleShot(true);
|
||||||
@@ -78,6 +73,12 @@ ImagesCarousel::ImagesCarousel(const Config::StyleConfigItems& styleConfig,
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ImagesCarousel::_onImagesLoaded() {
|
void ImagesCarousel::_onImagesLoaded() {
|
||||||
|
// reset stop sign
|
||||||
|
{
|
||||||
|
// No need to lock m_countMutex here, but just for safety
|
||||||
|
QMutexLocker locker(&m_stopSignMutex);
|
||||||
|
m_stopSign = false;
|
||||||
|
}
|
||||||
m_animationEnabled = true;
|
m_animationEnabled = true;
|
||||||
if (!m_noLoadingScreen) {
|
if (!m_noLoadingScreen) {
|
||||||
_enableUIUpdates(true);
|
_enableUIUpdates(true);
|
||||||
@@ -86,7 +87,6 @@ void ImagesCarousel::_onImagesLoaded() {
|
|||||||
m_imageInsertQueueTimer->deleteLater();
|
m_imageInsertQueueTimer->deleteLater();
|
||||||
m_imageInsertQueueTimer = nullptr;
|
m_imageInsertQueueTimer = nullptr;
|
||||||
}
|
}
|
||||||
if (m_initialImagesLoaded) {
|
|
||||||
// No images loaded
|
// No images loaded
|
||||||
if (!getLoadedImagesCount()) {
|
if (!getLoadedImagesCount()) {
|
||||||
return;
|
return;
|
||||||
@@ -94,12 +94,11 @@ void ImagesCarousel::_onImagesLoaded() {
|
|||||||
// Focus the first image
|
// Focus the first image
|
||||||
if (m_currentIndex < 0) {
|
if (m_currentIndex < 0) {
|
||||||
m_currentIndex = 0;
|
m_currentIndex = 0;
|
||||||
// Ensure the layout events are processed
|
// Ensure the layout events are processed before focusing
|
||||||
QTimer::singleShot(0, this, [this]() {
|
QTimer::singleShot(0, this, [this]() {
|
||||||
focusCurrImage();
|
focusCurrImage();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// exit(1); // for debug
|
// exit(1); // for debug
|
||||||
}
|
}
|
||||||
@@ -156,7 +155,7 @@ void ImagesCarousel::_insertImageQueue(ImageData* data) {
|
|||||||
}
|
}
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_imageInsertQueueMutex);
|
QMutexLocker locker(&m_imageInsertQueueMutex);
|
||||||
m_imageInsertQueue.enqueue(const_cast<ImageData*>(data));
|
m_imageInsertQueue.enqueue(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,15 +165,16 @@ int ImagesCarousel::_insertImage(ImageData* data) {
|
|||||||
emit imageLoaded(getLoadedImagesCount());
|
emit imageLoaded(getLoadedImagesCount());
|
||||||
{
|
{
|
||||||
QMutexLocker countLocker(&m_countMutex);
|
QMutexLocker countLocker(&m_countMutex);
|
||||||
if (++m_loadedImagesCount >= m_addedImagesCount) {
|
if (++m_processedImagesCount >= m_addedImagesCount) {
|
||||||
|
{
|
||||||
QMutexLocker stopSignLocker(&m_stopSignMutex);
|
QMutexLocker stopSignLocker(&m_stopSignMutex);
|
||||||
if (m_stopSign) {
|
if (m_stopSign) {
|
||||||
// if all stopped
|
// if all stopped
|
||||||
emit stopped();
|
emit stopped();
|
||||||
} else {
|
|
||||||
emit loadingCompleted(m_loadedImagesCount);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
emit loadingCompleted(m_processedImagesCount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
@@ -241,6 +241,7 @@ void ImagesCarousel::_processImageInsertQueue() {
|
|||||||
int currPos = m_currentIndex;
|
int currPos = m_currentIndex;
|
||||||
for (ImageData* data : batch) {
|
for (ImageData* data : batch) {
|
||||||
int pos = _insertImage(data);
|
int pos = _insertImage(data);
|
||||||
|
// Keep the focusing index correct
|
||||||
if (pos >= 0 && pos <= currPos) {
|
if (pos >= 0 && pos <= currPos) {
|
||||||
currPos++;
|
currPos++;
|
||||||
}
|
}
|
||||||
@@ -276,6 +277,7 @@ void ImageLoader::run() {
|
|||||||
Qt::QueuedConnection,
|
Qt::QueuedConnection,
|
||||||
Q_ARG(ImageData*, data));
|
Q_ARG(ImageData*, data));
|
||||||
});
|
});
|
||||||
|
// We need to call _insertImageQueue even if stopped to increase the loaded count
|
||||||
{
|
{
|
||||||
QMutexLocker stopSignLocker(&m_carousel->m_stopSignMutex);
|
QMutexLocker stopSignLocker(&m_carousel->m_stopSignMutex);
|
||||||
if (m_carousel->m_stopSign) return;
|
if (m_carousel->m_stopSign) return;
|
||||||
|
|||||||
+85
-13
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 01:22:53
|
* @Date: 2025-08-05 01:22:53
|
||||||
* @LastEditTime: 2026-01-15 03:42:17
|
* @LastEditTime: 2026-01-15 07:06:46
|
||||||
* @Description: Animated carousel widget for displaying and selecting images.
|
* @Description: Animated carousel widget for displaying and selecting images.
|
||||||
*/
|
*/
|
||||||
#ifndef IMAGES_CAROUSEL_H
|
#ifndef IMAGES_CAROUSEL_H
|
||||||
@@ -20,8 +20,57 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "image_item.h"
|
#include "image_item.h"
|
||||||
|
|
||||||
class ImageData;
|
// Two different image loading strategies:
|
||||||
class ImageItem;
|
// - With loading screen: load all images directly
|
||||||
|
// 1. appendImages called -> increace m_addedImagesCount & spawn all ImageLoader
|
||||||
|
// threads
|
||||||
|
// 2. Each ImageLoader calls _insertImageQueue with queued connection
|
||||||
|
// 3. _insertImageQueue calls _insertImage directly
|
||||||
|
// - Without loading screen: queue loaded images and insert them in batches
|
||||||
|
// 1. appendImages called -> increace m_addedImagesCount & spawn all ImageLoader
|
||||||
|
// threads and a timer m_imageInsertQueueTimer, disable UI updates
|
||||||
|
// 2. Each ImageLoader calls _insertImageQueue with queued connection
|
||||||
|
// 3. _insertImageQueue enqueues the ImageData
|
||||||
|
// 4. m_imageInsertQueueTimer calls _processImageInsertQueue every
|
||||||
|
// s_processBatchTimeout ms
|
||||||
|
// 5. _processImageInsertQueue processes up to s_processBatchSize items from the
|
||||||
|
// queue and calls _insertImage for each
|
||||||
|
//
|
||||||
|
// The stop logic is identical:
|
||||||
|
// - Force stop
|
||||||
|
// 1. Set m_stopSign to true
|
||||||
|
// 2. ImageLoader::run checks m_stopSign and returns early if true
|
||||||
|
// and calls _insertImageQueue using queued connection, but passing a
|
||||||
|
// nullptr as parameter
|
||||||
|
// 3. The callstack from _insertImageQueue to _insertImage is same as above
|
||||||
|
// 4. _insertImage ignores nullptr and just increases m_processedImagesCount
|
||||||
|
// 5. when m_processedImagesCount >= m_addedImagesCount, emit stopped()
|
||||||
|
// 6. Call ImagesCarousel::_onImagesLoaded
|
||||||
|
// - Normal completion
|
||||||
|
// 1. Same as above until _insertImage, but m_stopSign is false and ImageLoader::run
|
||||||
|
// passes valid ImageData pointer to _insertImageQueue
|
||||||
|
// 2. When m_processedImagesCount >= m_addedImagesCount, emit loadingCompleted()
|
||||||
|
// 3. Call ImagesCarousel::_onImagesLoaded
|
||||||
|
//
|
||||||
|
// 3 different ways to change focusing image:
|
||||||
|
// - focusNextImage / focusPrevImage: directly change m_currentIndex and call
|
||||||
|
// focusCurrImage
|
||||||
|
// These can be triggered by different events, e.g. key press, button click, etc.
|
||||||
|
// - Auto focus on scroll: debounce scroll events and calculate the nearest image
|
||||||
|
// index to focus, then change m_currentIndex and call focusCurrImage
|
||||||
|
// - Initial focus: set m_currentIndex to 0 and call focusCurrImage
|
||||||
|
//
|
||||||
|
// Note:
|
||||||
|
// - All methods and slots of ImageCarousel should be called from the main thread.
|
||||||
|
// - ImageCarousel::m_addedImagesCount and ImageCarousel::m_processedImagesCount
|
||||||
|
// should be identical after loading is finished, regardless of whether loading is
|
||||||
|
// forcedly stopped or completed normally.
|
||||||
|
// - ImageCarousel::getLoadedImagesCount() returns the number of images currently
|
||||||
|
// displayed in the carousel, which may be less than m_addedImagesCount if loading
|
||||||
|
// is not yet completed or some images failed to load.
|
||||||
|
// - The current implementation actually supports dynamic addition of images during runtime,
|
||||||
|
// but the UI does not provide such functionality yet and thus it is not tested :)
|
||||||
|
|
||||||
class ImageLoader;
|
class ImageLoader;
|
||||||
class ImagesCarousel;
|
class ImagesCarousel;
|
||||||
class ImagesCarouselScrollArea;
|
class ImagesCarouselScrollArea;
|
||||||
@@ -56,11 +105,19 @@ class ImagesCarousel : public QWidget {
|
|||||||
QWidget* parent = nullptr);
|
QWidget* parent = nullptr);
|
||||||
~ImagesCarousel();
|
~ImagesCarousel();
|
||||||
|
|
||||||
static constexpr int s_debounceInterval = 200;
|
static constexpr int s_debounceInterval = 200; // ms
|
||||||
static constexpr int s_animationDuration = 300;
|
static constexpr int s_animationDuration = 300; // ms
|
||||||
|
|
||||||
static constexpr int s_processBatchTimeout = 50; // ms
|
static constexpr int s_processBatchTimeout = 50; // ms
|
||||||
static constexpr int s_processBatchSize = 30; // items
|
static constexpr int s_processBatchSize = 30; // items
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the Current Image Path
|
||||||
|
*
|
||||||
|
* @return QString
|
||||||
|
*
|
||||||
|
* @note This method should be always called from the main thread.
|
||||||
|
*/
|
||||||
[[nodiscard]] QString getCurrentImagePath() const {
|
[[nodiscard]] QString getCurrentImagePath() const {
|
||||||
if (m_currentIndex >= 0 && m_currentIndex < getLoadedImagesCount()) {
|
if (m_currentIndex >= 0 && m_currentIndex < getLoadedImagesCount()) {
|
||||||
auto item = getImageItemAt(m_currentIndex);
|
auto item = getImageItemAt(m_currentIndex);
|
||||||
@@ -71,11 +128,25 @@ class ImagesCarousel : public QWidget {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should always be called in the main thread
|
/**
|
||||||
|
* @brief Get count of loaded images
|
||||||
|
*
|
||||||
|
* @return qsizetype
|
||||||
|
*
|
||||||
|
* @note This method should be always called from the main thread.
|
||||||
|
*/
|
||||||
[[nodiscard]] qsizetype getLoadedImagesCount() const {
|
[[nodiscard]] qsizetype getLoadedImagesCount() const {
|
||||||
return m_imagesLayout->count();
|
return m_imagesLayout->count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the Image object at index
|
||||||
|
*
|
||||||
|
* @param index
|
||||||
|
* @return ImageItem*
|
||||||
|
*
|
||||||
|
* @note This method should be always called from the main thread.
|
||||||
|
*/
|
||||||
[[nodiscard]] ImageItem* getImageItemAt(int index) const {
|
[[nodiscard]] ImageItem* getImageItemAt(int index) const {
|
||||||
if (index < 0 || index >= getLoadedImagesCount()) {
|
if (index < 0 || index >= getLoadedImagesCount()) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -86,6 +157,11 @@ class ImagesCarousel : public QWidget {
|
|||||||
->widget());
|
->widget());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get count of added images
|
||||||
|
*
|
||||||
|
* @return qsizetype
|
||||||
|
*/
|
||||||
[[nodiscard]] qsizetype getAddedImagesCount() {
|
[[nodiscard]] qsizetype getAddedImagesCount() {
|
||||||
QMutexLocker locker(&m_countMutex);
|
QMutexLocker locker(&m_countMutex);
|
||||||
return m_addedImagesCount;
|
return m_addedImagesCount;
|
||||||
@@ -110,8 +186,7 @@ class ImagesCarousel : public QWidget {
|
|||||||
private slots:
|
private slots:
|
||||||
void _onScrollBarValueChanged(int value);
|
void _onScrollBarValueChanged(int value);
|
||||||
void _onItemClicked(const QString& path);
|
void _onItemClicked(const QString& path);
|
||||||
void _onImagesLoaded();
|
void _onImagesLoaded(); // Called when loading is completed or stopped
|
||||||
|
|
||||||
void _processImageInsertQueue();
|
void _processImageInsertQueue();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -131,9 +206,9 @@ class ImagesCarousel : public QWidget {
|
|||||||
ImagesCarouselScrollArea* m_scrollArea = nullptr;
|
ImagesCarouselScrollArea* m_scrollArea = nullptr;
|
||||||
|
|
||||||
// Items and counters
|
// Items and counters
|
||||||
int m_loadedImagesCount = 0; // increase when _insertImage is called OR ImageLoader::run() is called with m_stopSign as true
|
int m_processedImagesCount = 0; // increase when _insertImage is called OR ImageLoader::run() is called with m_stopSign as true
|
||||||
int m_addedImagesCount = 0; // increase when appendImages called
|
int m_addedImagesCount = 0; // increase when appendImages called
|
||||||
QMutex m_countMutex; // for m_loadedImagesCount and m_addedImagesCount
|
QMutex m_countMutex; // for m_processedImagesCount and m_addedImagesCount
|
||||||
int m_currentIndex = -1; // initially no focus
|
int m_currentIndex = -1; // initially no focus
|
||||||
|
|
||||||
// Threading
|
// Threading
|
||||||
@@ -154,9 +229,6 @@ class ImagesCarousel : public QWidget {
|
|||||||
QMutex m_stopSignMutex;
|
QMutex m_stopSignMutex;
|
||||||
bool m_stopSign = false;
|
bool m_stopSign = false;
|
||||||
|
|
||||||
// Flags
|
|
||||||
bool m_initialImagesLoaded = true;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void imageFocused(const QString& path, const int index, const int count);
|
void imageFocused(const QString& path, const int index, const int count);
|
||||||
|
|
||||||
|
|||||||
+5
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-07 01:12:37
|
* @Date: 2025-08-07 01:12:37
|
||||||
* @LastEditTime: 2026-01-15 04:07:40
|
* @LastEditTime: 2026-01-15 06:26:35
|
||||||
* @Description: Implementation of logger.
|
* @Description: Implementation of logger.
|
||||||
*/
|
*/
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
@@ -23,6 +23,7 @@ static QTextStream* s_logStream = nullptr;
|
|||||||
static bool s_isColored = false;
|
static bool s_isColored = false;
|
||||||
static QMutex s_logMutex;
|
static QMutex s_logMutex;
|
||||||
|
|
||||||
|
// Check if the output stream supports colored output
|
||||||
static bool checkIsColored(FILE* stream) {
|
static bool checkIsColored(FILE* stream) {
|
||||||
if (!stream || !isatty(fileno(stream))) {
|
if (!stream || !isatty(fileno(stream))) {
|
||||||
return false;
|
return false;
|
||||||
@@ -35,6 +36,7 @@ static bool checkIsColored(FILE* stream) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Custom message handler
|
||||||
static void messageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
|
static void messageOutput(QtMsgType type, const QMessageLogContext& context, const QString& msg) {
|
||||||
Q_UNUSED(context);
|
Q_UNUSED(context);
|
||||||
|
|
||||||
@@ -126,3 +128,5 @@ void GeneralLogger::warn(const QString& msg) {
|
|||||||
void GeneralLogger::critical(const QString& msg) {
|
void GeneralLogger::critical(const QString& msg) {
|
||||||
qCCritical(logMain).noquote() << msg;
|
qCCritical(logMain).noquote() << msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No fatal because qCFatal does not exist before Qt 6.5
|
||||||
|
|||||||
+15
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 10:43:31
|
* @Date: 2025-08-05 10:43:31
|
||||||
* @LastEditTime: 2026-01-15 04:07:46
|
* @LastEditTime: 2026-01-15 06:25:57
|
||||||
* @Description: A simple thread-safe logger.
|
* @Description: A simple thread-safe logger.
|
||||||
*/
|
*/
|
||||||
#ifndef GENERAL_LOGGER_H
|
#ifndef GENERAL_LOGGER_H
|
||||||
@@ -27,10 +27,24 @@ void critical(const QString& msg);
|
|||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Initialize the logger and set the output stream.
|
||||||
|
*
|
||||||
|
* @param stream
|
||||||
|
*/
|
||||||
static void init(FILE* stream = stderr);
|
static void init(FILE* stream = stderr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the log level.
|
||||||
|
*
|
||||||
|
* @param level
|
||||||
|
*/
|
||||||
static void setLogLevel(QtMsgType level);
|
static void setLogLevel(QtMsgType level);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Suppress all log output.
|
||||||
|
*
|
||||||
|
*/
|
||||||
static void quiet();
|
static void quiet();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+18
-2
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 00:37:58
|
* @Date: 2025-08-05 00:37:58
|
||||||
* @LastEditTime: 2026-01-15 03:41:55
|
* @LastEditTime: 2026-01-15 05:58:06
|
||||||
* @Description: Argument parser and entry point.
|
* @Description: Argument parser and entry point.
|
||||||
*/
|
*/
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
@@ -16,21 +16,32 @@
|
|||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A static & single-instance class to handle application options.
|
||||||
|
*
|
||||||
|
*/
|
||||||
static class AppOptions {
|
static class AppOptions {
|
||||||
QCommandLineParser parser{};
|
QCommandLineParser parser{};
|
||||||
|
|
||||||
|
// The following 3 functions handle specific command line options
|
||||||
|
// and mark doReturn as true to indicate that the application should exit
|
||||||
|
// after parsing arguments.
|
||||||
|
|
||||||
|
// -v --version
|
||||||
void printVersion() {
|
void printVersion() {
|
||||||
QTextStream out(stdout);
|
QTextStream out(stdout);
|
||||||
out << APP_NAME << " version " << APP_VERSION << Qt::endl;
|
out << APP_NAME << " version " << APP_VERSION << Qt::endl;
|
||||||
doReturn = true;
|
doReturn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -h --help
|
||||||
void printHelp() {
|
void printHelp() {
|
||||||
QTextStream out(stdout);
|
QTextStream out(stdout);
|
||||||
out << parser.helpText() << Qt::endl;
|
out << parser.helpText() << Qt::endl;
|
||||||
doReturn = true;
|
doReturn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print error message and help
|
||||||
void printError() {
|
void printError() {
|
||||||
if (!errorText.isEmpty()) {
|
if (!errorText.isEmpty()) {
|
||||||
QTextStream out(stderr);
|
QTextStream out(stderr);
|
||||||
@@ -44,7 +55,7 @@ static class AppOptions {
|
|||||||
QString configPath = "";
|
QString configPath = "";
|
||||||
QStringList appendDirs;
|
QStringList appendDirs;
|
||||||
QString errorText = "";
|
QString errorText = "";
|
||||||
bool doReturn = false;
|
bool doReturn = false; ///< Indicates whether the application should exit after parsing arguments.
|
||||||
|
|
||||||
void parseArgs(QApplication* a) {
|
void parseArgs(QApplication* a) {
|
||||||
parser.setApplicationDescription("A small wallpaper utility made with Qt");
|
parser.setApplicationDescription("A small wallpaper utility made with Qt");
|
||||||
@@ -64,6 +75,9 @@ static class AppOptions {
|
|||||||
QCommandLineOption configFileOption(QStringList() << "c" << "config-file", "Specify a custom configuration file", "file");
|
QCommandLineOption configFileOption(QStringList() << "c" << "config-file", "Specify a custom configuration file", "file");
|
||||||
parser.addOption(configFileOption);
|
parser.addOption(configFileOption);
|
||||||
|
|
||||||
|
// Not parser.process(a->arguments()) because we want to handle exit logics ourselves.
|
||||||
|
// parser.process(...) will do something like exit(...) that will terminate
|
||||||
|
// the application brutally and produce unwanted warnings.
|
||||||
if (!parser.parse(a->arguments())) {
|
if (!parser.parse(a->arguments())) {
|
||||||
errorText = parser.errorText();
|
errorText = parser.errorText();
|
||||||
doReturn = true;
|
doReturn = true;
|
||||||
@@ -85,6 +99,7 @@ static class AppOptions {
|
|||||||
} else if (parser.isSet(quietOption)) {
|
} else if (parser.isSet(quietOption)) {
|
||||||
Logger::quiet();
|
Logger::quiet();
|
||||||
} else {
|
} else {
|
||||||
|
// Default to INFO level
|
||||||
Logger::setLogLevel(QtInfoMsg);
|
Logger::setLogLevel(QtInfoMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +128,7 @@ static class AppOptions {
|
|||||||
} s_options;
|
} s_options;
|
||||||
|
|
||||||
static QString getConfigDir() {
|
static QString getConfigDir() {
|
||||||
|
// This will be ~/.config/AppName, where AppName is the name of executable target in CMakeLists.txt
|
||||||
auto configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
auto configDir = QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation);
|
||||||
if (configDir.isEmpty()) {
|
if (configDir.isEmpty()) {
|
||||||
configDir = QDir::homePath() + QDir::separator() + ".config" + QDir::separator() + APP_NAME;
|
configDir = QDir::homePath() + QDir::separator() + ".config" + QDir::separator() + APP_NAME;
|
||||||
|
|||||||
+22
-17
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 00:37:58
|
* @Date: 2025-08-05 00:37:58
|
||||||
* @LastEditTime: 2026-01-15 05:30:06
|
* @LastEditTime: 2026-01-15 07:25:41
|
||||||
* @Description: MainWindow implementation.
|
* @Description: MainWindow implementation.
|
||||||
*/
|
*/
|
||||||
#include "main_window.h"
|
#include "main_window.h"
|
||||||
@@ -15,14 +15,10 @@
|
|||||||
#include "./ui_main_window.h"
|
#include "./ui_main_window.h"
|
||||||
#include "images_carousel.h"
|
#include "images_carousel.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
using namespace GeneralLogger;
|
using namespace GeneralLogger;
|
||||||
|
|
||||||
static QString splitNameFromPath(const QString& path) {
|
|
||||||
QFileInfo fileInfo(path);
|
|
||||||
return fileInfo.fileName();
|
|
||||||
}
|
|
||||||
|
|
||||||
MainWindow::MainWindow(const Config& config, QWidget* parent)
|
MainWindow::MainWindow(const Config& config, QWidget* parent)
|
||||||
: QMainWindow(parent), ui(new Ui::MainWindow), m_config(config) {
|
: QMainWindow(parent), ui(new Ui::MainWindow), m_config(config) {
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
@@ -85,6 +81,7 @@ void MainWindow::_setupUI() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::keyPressEvent(QKeyEvent* event) {
|
void MainWindow::keyPressEvent(QKeyEvent* event) {
|
||||||
|
// Same effects as clicking the confirm/cancel buttons
|
||||||
if (event->key() == Qt::Key_Escape) {
|
if (event->key() == Qt::Key_Escape) {
|
||||||
_onCancelPressed();
|
_onCancelPressed();
|
||||||
return;
|
return;
|
||||||
@@ -136,6 +133,8 @@ void MainWindow::keyPressEvent(QKeyEvent* event) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop loading images and call onStopped when loading is really stopped
|
||||||
|
// emit stop() -> ImagesCarousel::onStop() -> ImagesCarousel::stopped() -> call onStopped
|
||||||
void MainWindow::_stopLoadingAndQuit(const std::function<void()>& onStopped) {
|
void MainWindow::_stopLoadingAndQuit(const std::function<void()>& onStopped) {
|
||||||
if (m_state != Loading) {
|
if (m_state != Loading) {
|
||||||
return;
|
return;
|
||||||
@@ -170,8 +169,7 @@ void MainWindow::_onCancelPressed() {
|
|||||||
debug("Stopping loading and displaying loaded images...");
|
debug("Stopping loading and displaying loaded images...");
|
||||||
_stopLoadingAndQuit([this]() {
|
_stopLoadingAndQuit([this]() {
|
||||||
debug("Loading stopped.");
|
debug("Loading stopped.");
|
||||||
_onLoadingCompleted(m_carousel->getLoadedImagesCount());
|
// and do nothing
|
||||||
m_carousel->focusCurrImage();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -190,13 +188,13 @@ void MainWindow::_onConfirmPressed() {
|
|||||||
// case loading screen is disabled, confirm the selection
|
// case loading screen is disabled, confirm the selection
|
||||||
if (m_config.getStyleConfig().noLoadingScreen) {
|
if (m_config.getStyleConfig().noLoadingScreen) {
|
||||||
debug("Stopping loading and confirming selection...");
|
debug("Stopping loading and confirming selection...");
|
||||||
connect(
|
// Save current path because the stopping process may take some time
|
||||||
m_carousel,
|
const QString currentPath = m_carousel->getCurrentImagePath();
|
||||||
&ImagesCarousel::stopped,
|
debug("Loading stopped. Confirming selection...");
|
||||||
this,
|
_stopLoadingAndQuit([this, currentPath]() {
|
||||||
&MainWindow::onConfirm);
|
close();
|
||||||
m_state = Stopping;
|
_runConfirmAction(currentPath);
|
||||||
emit stop();
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Ready:
|
case Ready:
|
||||||
@@ -213,6 +211,7 @@ void MainWindow::wheelEvent(QWheelEvent* event) {
|
|||||||
event->ignore();
|
event->ignore();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// angleDelta().x() is handled by QScrollArea
|
||||||
if (event->angleDelta().y() > 0) {
|
if (event->angleDelta().y() > 0) {
|
||||||
m_carousel->focusPrevImage();
|
m_carousel->focusPrevImage();
|
||||||
} else if (event->angleDelta().y() < 0) {
|
} else if (event->angleDelta().y() < 0) {
|
||||||
@@ -226,7 +225,7 @@ void MainWindow::closeEvent(QCloseEvent* event) {
|
|||||||
if (m_state == Loading) {
|
if (m_state == Loading) {
|
||||||
event->ignore();
|
event->ignore();
|
||||||
_stopLoadingAndQuit([this]() {
|
_stopLoadingAndQuit([this]() {
|
||||||
debug("Quitting app.");
|
debug("Quitting app...");
|
||||||
close();
|
close();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -236,12 +235,16 @@ void MainWindow::closeEvent(QCloseEvent* event) {
|
|||||||
|
|
||||||
void MainWindow::onConfirm() {
|
void MainWindow::onConfirm() {
|
||||||
close();
|
close();
|
||||||
const auto path = m_carousel->getCurrentImagePath();
|
_runConfirmAction(m_carousel->getCurrentImagePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::_runConfirmAction(const QString& path) {
|
||||||
if (path.isEmpty()) {
|
if (path.isEmpty()) {
|
||||||
warn("No image selected");
|
warn("No image selected");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
info(QString("Selected image: %1").arg(path));
|
info(QString("Selected image: %1").arg(path));
|
||||||
|
// Output the selected path to stdout
|
||||||
QTextStream out(stdout);
|
QTextStream out(stdout);
|
||||||
out << path << Qt::endl;
|
out << path << Qt::endl;
|
||||||
const auto cmdOrig = m_config.getActionConfig().confirm;
|
const auto cmdOrig = m_config.getActionConfig().confirm;
|
||||||
@@ -270,11 +273,13 @@ void MainWindow::_onLoadingStarted(const qsizetype amount) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_loadingIndicator->setMaximum(amount);
|
m_loadingIndicator->setMaximum(amount);
|
||||||
|
// Change to loading indicator view
|
||||||
ui->stackedWidget->setCurrentIndex(m_loadingIndicatorIndex);
|
ui->stackedWidget->setCurrentIndex(m_loadingIndicatorIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::_onLoadingCompleted(const qsizetype amount) {
|
void MainWindow::_onLoadingCompleted(const qsizetype amount) {
|
||||||
info(QString("Loading completed, loaded %1 images").arg(amount));
|
info(QString("Loading completed, loaded %1 images").arg(amount));
|
||||||
|
// Change to carousel view
|
||||||
ui->stackedWidget->setCurrentIndex(m_carouselIndex);
|
ui->stackedWidget->setCurrentIndex(m_carouselIndex);
|
||||||
m_state = Ready;
|
m_state = Ready;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-08-05 00:37:58
|
* @Date: 2025-08-05 00:37:58
|
||||||
* @LastEditTime: 2025-12-01 00:37:35
|
* @LastEditTime: 2026-01-15 06:16:07
|
||||||
* @Description: MainWindow implementation.
|
* @Description: MainWindow implementation.
|
||||||
*/
|
*/
|
||||||
#ifndef MAINWINDOW_H
|
#ifndef MAINWINDOW_H
|
||||||
@@ -39,6 +39,7 @@ class MainWindow : public QMainWindow {
|
|||||||
private:
|
private:
|
||||||
void _setupUI();
|
void _setupUI();
|
||||||
void _stopLoadingAndQuit(const std::function<void()>& onStopped = nullptr);
|
void _stopLoadingAndQuit(const std::function<void()>& onStopped = nullptr);
|
||||||
|
void _runConfirmAction(const QString& path = "");
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void _onImageFocused(const QString& path, const int index, const int count);
|
void _onImageFocused(const QString& path, const int index, const int count);
|
||||||
@@ -56,6 +57,10 @@ class MainWindow : public QMainWindow {
|
|||||||
Ready,
|
Ready,
|
||||||
} m_state = Init;
|
} m_state = Init;
|
||||||
|
|
||||||
|
// Init -> Loading -> Ready -> (Quit)
|
||||||
|
// Init -> Loading -> Stopping -> Ready -> (Quit) (with loading screen)
|
||||||
|
// Init -> Loading -> Stopping -> (Quit) (without loading screen)
|
||||||
|
|
||||||
Ui::MainWindow* ui;
|
Ui::MainWindow* ui;
|
||||||
ImagesCarousel* m_carousel = nullptr;
|
ImagesCarousel* m_carousel = nullptr;
|
||||||
LoadingIndicator* m_loadingIndicator = nullptr;
|
LoadingIndicator* m_loadingIndicator = nullptr;
|
||||||
|
|||||||
+43
-1
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* @Author: Uyanide pywang0608@foxmail.com
|
* @Author: Uyanide pywang0608@foxmail.com
|
||||||
* @Date: 2025-11-30 20:59:57
|
* @Date: 2025-11-30 20:59:57
|
||||||
* @LastEditTime: 2026-01-15 03:11:08
|
* @LastEditTime: 2026-01-15 06:00:51
|
||||||
* @Description: THE utils header that every project needs :)
|
* @Description: THE utils header that every project needs :)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -15,6 +15,11 @@
|
|||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defer execution of a callable until the end of the current scope.
|
||||||
|
*
|
||||||
|
* @tparam Callable
|
||||||
|
*/
|
||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
class Defer {
|
class Defer {
|
||||||
Callable m_func;
|
Callable m_func;
|
||||||
@@ -31,16 +36,34 @@ class Defer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a file exists, is a regular file, and is readable.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
inline bool checkFile(const QString& path) {
|
inline bool checkFile(const QString& path) {
|
||||||
QFileInfo checkFile(path);
|
QFileInfo checkFile(path);
|
||||||
|
// According to Qt docs, "exists() returns true if the symlink points to an existing target, otherwise it returns false."
|
||||||
|
// So no need to separately check for isSymbolicLink() or isSymLink().
|
||||||
return checkFile.exists() && checkFile.isFile() && checkFile.isReadable();
|
return checkFile.exists() && checkFile.isFile() && checkFile.isReadable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a directory exists, is a directory, and is readable.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
*/
|
||||||
inline bool checkDir(const QString& path) {
|
inline bool checkDir(const QString& path) {
|
||||||
QFileInfo checkFile(path);
|
QFileInfo checkFile(path);
|
||||||
return checkFile.exists() && checkFile.isDir() && checkFile.isReadable();
|
return checkFile.exists() && checkFile.isDir() && checkFile.isReadable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Expand environment variables and ~ in a given path.
|
||||||
|
*
|
||||||
|
* @param path Input path
|
||||||
|
* @return QString Expanded path
|
||||||
|
*/
|
||||||
inline QString expandPath(const QString& path) {
|
inline QString expandPath(const QString& path) {
|
||||||
QString expandedPath = path;
|
QString expandedPath = path;
|
||||||
|
|
||||||
@@ -66,6 +89,25 @@ inline QString expandPath(const QString& path) {
|
|||||||
return QDir::cleanPath(expandedPath);
|
return QDir::cleanPath(expandedPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Split the file name from a given path.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @return QString
|
||||||
|
*/
|
||||||
|
static QString splitNameFromPath(const QString& path) {
|
||||||
|
QFileInfo fileInfo(path);
|
||||||
|
return fileInfo.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief In addition to checking if the file exists and is readable,
|
||||||
|
* also checks if the file has a valid image extension.
|
||||||
|
*
|
||||||
|
* @param filePath
|
||||||
|
* @return true
|
||||||
|
* @return false
|
||||||
|
*/
|
||||||
inline bool checkImageFile(const QString& filePath) {
|
inline bool checkImageFile(const QString& filePath) {
|
||||||
static const QStringList validExtensions = {
|
static const QStringList validExtensions = {
|
||||||
".jpg",
|
".jpg",
|
||||||
|
|||||||
Reference in New Issue
Block a user