Compare commits

...

63 Commits

Author SHA1 Message Date
Luis Ángel San Martín
a4ab11d44d
Merge pull request #475 from YACReader/custom_comic_covers 2025-05-10 07:52:28 +02:00
Luis Ángel San Martín
4adad5f966 Format 2025-05-09 21:33:14 +02:00
Luis Ángel San Martín
bed3503aae Update CHANGELOG 2025-05-09 21:15:47 +02:00
Luis Ángel San Martín
5950c52e19 Support editing the cover image of various comics at once 2025-05-09 21:05:42 +02:00
Luis Ángel San Martín
8b159c9da6 Support drag&drop for setting a custom cover in the properties dialog 2025-05-09 19:58:52 +02:00
Luis Ángel San Martín
28952180ab Remove commented code 2025-05-09 19:27:35 +02:00
Luis Ángel San Martín
508ff6d547 Add support for loading custom comic images 2025-05-09 19:25:02 +02:00
Luis Ángel San Martín
0457c08fca Add button for reseting the cover of a comic to the default one 2025-05-09 15:23:00 +02:00
Luis Ángel San Martín
97685dca73 Remove unneeded property 2025-05-09 14:30:11 +02:00
Luis Ángel San Martín
b064095809 Make the cover selection button circular 2025-05-09 14:05:17 +02:00
Luis Ángel San Martín
0ac22cdc5e
Merge pull request #474 from YACReader/custom_folder_covers
Add support for setting custom covers on folders
2025-05-09 08:46:42 +02:00
Luis Ángel San Martín
156de8db9e Fix YACReaderLibraryServer build 2025-05-08 22:53:00 +02:00
Luis Ángel San Martín
b976b7f809 Add support for setting custom covers on folders 2025-05-08 22:00:55 +02:00
Luis Ángel San Martín
f0b9d45033
Merge pull request #473 from YACReader/comic_vine_integration_improvements
Comic vine integration improvements
2025-05-06 08:20:55 +02:00
Luis Ángel San Martín
879d316391 Update CHANGELOG 2025-05-05 19:29:17 +02:00
Luis Ángel San Martín
6b1987a16a Use a better default search string from comic files 2025-05-05 19:20:51 +02:00
Luis Ángel San Martín
dfb1cb9b70 Include missing image in qrc 2025-05-05 18:36:17 +02:00
Luis Ángel San Martín
73bde73fdb Add some minimum width to the scraper table view columns 2025-05-05 18:35:44 +02:00
Luis Ángel San Martín
868de856a7 Add support for exact match searches
It will be enabled by default.
2025-05-05 18:35:07 +02:00
Luis Ángel San Martín
e5f02bebe5 Try to always get some description for single volume issues
If the issue doesn't have a description it will get the volume description.
2025-05-04 21:50:36 +02:00
Luis Ángel San Martín
e7652355de Fix status check 2025-05-04 18:13:33 +02:00
Luis Ángel San Martín
51590aaa4f Rename method 2025-05-04 18:12:37 +02:00
Luis Ángel San Martín
fca1e2004e Add new ScraperStatus for exact volume searches 2025-05-04 17:46:50 +02:00
Luis Ángel San Martín
1b4116db1a Remove default cases from switches using enum class 2025-05-04 17:33:39 +02:00
Luis Ángel San Martín
9d7a554ab3 Convert ScraperMode and ScraperStatus into enum class 2025-05-04 17:26:05 +02:00
Luis Ángel San Martín
89d7d76080 Typo 2025-05-04 15:14:45 +02:00
Luis Ángel San Martín
b81b7908f3
Merge pull request #472 from YACReader/add_mouse_nativation_modes
Add mouse navigation modes
2025-05-04 12:56:43 +02:00
Luis Ángel San Martín
bba15bef4d Fix Qt5 compilation 2025-05-04 10:30:54 +02:00
Luis Ángel San Martín
90a370680e Update CHANGELOG 2025-05-04 10:02:13 +02:00
Luis Ángel San Martín
77e3f6ffb1 Implement support all the new mouse modes 2025-05-04 09:53:53 +02:00
Luis Ángel San Martín
d291569e5f Move drag origin coordinates to MouseHandler 2025-05-04 09:53:31 +02:00
Luis Ángel San Martín
8b41bbf711 Check the load state before trying to turn pages 2025-05-04 09:52:13 +02:00
Luis Ángel San Martín
a51252ca0d Add settings to the options dialog to control the mouse behavior 2025-05-04 09:51:17 +02:00
Luis Ángel San Martín
25a569cfa6 Add support for mouse mode in Configuration 2025-05-03 16:11:58 +02:00
Luis Ángel San Martín
257436c040 Move FitMode to configuration.h 2025-05-03 15:44:41 +02:00
Luis Ángel San Martín
7db47f9147 Extract mouse handling events to it's own class 2025-05-03 15:10:16 +02:00
Luis Ángel San Martín
9e37947479 Fix layout warning
sortButtonsLayout was already part of the layout.
2025-04-21 19:21:48 +02:00
Luis Ángel San Martín Rodríguez
a425aef45e Fix macos compilation with Qt5 2025-04-20 14:52:50 +02:00
Luis Ángel San Martín
cb24540766 Update CHAGELOG 2025-04-20 10:33:36 +02:00
Luis Ángel San Martín
ed7e9f98f0 Format 2025-04-20 10:30:05 +02:00
Luis Ángel San Martín Rodríguez
3632ebab12 Implement native toolbars on macos on Qt6 to have a modern looking unified toolbars 2025-04-20 09:38:58 +02:00
Luis Ángel San Martín
d9b9fda337
Merge pull request #471 from BSDKaffee/qt-6.9-fixes
Qt 6.9 fixes
2025-04-19 17:35:24 +02:00
Jason E. Hale
6e0e6f3bc3
Fix build with Qt 6.9.0 (fixes #469)
Support for std::array was added to QDebug in 6.9.0
2025-04-19 06:28:17 -04:00
Jason E. Hale
254652f03e
Add missing include
Fixes build with Qt 6.9.0
2025-04-19 06:25:11 -04:00
Luis Ángel San Martín
53b63de10f Update CHANGELOG 2025-03-30 22:25:33 +02:00
Luis Ángel San Martín
8f84fc7902 Log libraries validation when YACReaderLibrary and YACReaderLibraryServer start 2025-03-30 22:24:17 +02:00
Luis Ángel San Martín
c5890ca7bf Include windows.h before shellapi.h
This fixes compilation with some compilers.
2025-03-30 22:22:17 +02:00
Luis Ángel San Martín
b73e8e60b1
Merge pull request #467 from YACReader/library-path-cleaning
Unify library related paths creation
2025-03-30 22:20:55 +02:00
Luis Ángel San Martín
52f1a57c47 Fix compilation 2025-03-30 21:23:54 +02:00
Luis Ángel San Martín
181884a85f More migrations to LibraryPaths 2025-03-30 15:17:24 +02:00
Luis Ángel San Martín
7f08ad0776 Try Python 3.10 2025-03-30 08:51:52 +02:00
Luis Ángel San Martín
a0407c910c Update pip before installing aqt 2025-03-29 19:47:09 +01:00
Luis Ángel San Martín
c142c2eeae Force Python 3.13
It seems that aqtinstall is having some troubles and it could be related to python 3.9
2025-03-29 18:53:58 +01:00
Luis Ángel San Martín
b0d2d05bc9 Change the type of path passed to DataBaseManagement::updateToCurrentVersion 2025-03-29 13:29:09 +01:00
Luis Ángel San Martín
d4b7c6dd8a Extract library paths methods to it's own struct in yacreader_global.h and use it everywhere 2025-03-29 11:31:53 +01:00
Luis Ángel San Martín
5aa637fdbe Use YACReaderLibrary::libraryDataPath in YACReaderLibraries::getDBPath 2025-03-27 21:19:12 +01:00
Luis Ángel San Martín
4e675d4326 Use YACReaderLibrary to create paths in ConsoleUILibraryCreator 2025-03-27 20:28:21 +01:00
Luis Ángel San Martín
c148a96d7f Add static methods to get the data paths in a library 2025-03-27 20:19:08 +01:00
Luis Ángel San Martín
af1af3976d Remove extra parameter from aqt 2025-01-26 14:36:04 +01:00
Luis Ángel San Martín
bfc0e1bc95 Update aqtinstall syntax to the latest version 2025-01-26 13:55:59 +01:00
Luis Ángel San Martín
89d16b620a Use the right data index for getting the rating 2025-01-26 11:22:43 +01:00
Luis Ángel San Martín Rodríguez
31971c2348 Don't use scroll animations on macos by default, it where hdpi scroll is most likely to be used 2025-01-06 10:24:16 +01:00
Luis Ángel San Martín Rodríguez
1cd8635808 Use QProcess as intented by parsing the input command arguments 2025-01-06 09:24:07 +01:00
88 changed files with 2186 additions and 509 deletions

1
.gitignore vendored
View File

@ -9,6 +9,7 @@ libp7zip
YACReader/build
YACReaderLibrary/build
YACReaderLibraryServer/build
build/
# C++ objects and libs
*.slo

View File

@ -2,6 +2,29 @@
Version counting is based on semantic versioning (Major.Feature.Patch)
## WIP (9.15.1)
### YACReader
* Don't use scroll animations on macos by default, it where hdpi scroll is most likely to be used.
* New toolbar on macos.
* New mouse modes to turn pages. You can setup the app to use the left/right buttons to turn pages directly or you can click on the left/right part of the screen to turn pages.
### YACReaderLibrary
* Improve flexibility of the open comic in third party app setting so more complex commands can be used. e.g. `open -a "/Applications/My Reader.app" "{comic_file_path}"`.
* Fix setting the comic rating in the table view.
* Log libraries validation when the app starts.
* New toolbar on macos.
* New setting in Comic Vine scraper to force exact volume matches.
* Better default search query in the Comic Vine scraper.
* Improved navigation in Comic Vine scraper, including keeping the current query around to make edits and refined searches easier.
* Add support for custom covers for any folder using the context menu.
* The edit cover buttons now support looping through pages, going forward from the last returns to the first, and going backward from the first jumps to the last.
* Add support for custom covers for comics using the edit metadata dialog, you can use a pick file button or drag&drop an image into the cover view in the dialog.
* Covers can be set in bulk for various comics at once.
* New button to reset to the default cover of a comic.
### YACReaderLibraryServer
* Log libraries validation when the app starts.
## 9.15.0
### YACReader

View File

@ -80,6 +80,7 @@ HEADERS += ../common/comic.h \
goto_dialog.h \
magnifying_glass.h \
main_window_viewer.h \
mouse_handler.h \
viewer.h \
goto_flow.h \
options_dialog.h \
@ -119,6 +120,7 @@ SOURCES += ../common/comic.cpp \
goto_dialog.cpp \
magnifying_glass.cpp \
main_window_viewer.cpp \
mouse_handler.cpp \
viewer.cpp \
goto_flow.cpp \
options_dialog.cpp \

View File

@ -1,5 +1,6 @@
#ifndef __CONFIGURATION_H
#define __CONFIGURATION_H
#include <QByteArray>
#include <QString>
#include <QSize>
@ -15,6 +16,21 @@
using namespace YACReader;
namespace YACReader {
enum FitMode {
ToWidth = 0x01,
ToHeight = 0x02,
FullRes = 0x03,
FullPage = 0x04
};
enum MouseMode {
Normal,
LeftRightNavigation,
HotAreas
};
class Configuration : public QObject
{
Q_OBJECT
@ -84,7 +100,21 @@ public:
bool getDoNotTurnPageOnScroll() { return settings->value(DO_NOT_TURN_PAGE_ON_SCROLL, false).toBool(); }
bool getUseSingleScrollStepToTurnPage() { return settings->value(USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE, false).toBool(); }
void setDisableScrollAnimation(bool b) { settings->setValue(DISABLE_SCROLL_ANIMATION, b); }
bool getDisableScrollAnimation() { return settings->value(DISABLE_SCROLL_ANIMATION, false).toBool(); }
bool getDisableScrollAnimation()
{
#ifdef Q_OS_MACOS
auto defaultValue = true;
#else
auto defaultValue = false;
#endif
return settings->value(DISABLE_SCROLL_ANIMATION, defaultValue).toBool();
}
MouseMode getMouseMode() { return static_cast<MouseMode>(settings->value(MOUSE_MODE, MouseMode::Normal).toInt()); }
void setMouseMode(MouseMode mouseMode) { settings->setValue(MOUSE_MODE, static_cast<int>(mouseMode)); }
};
}
#endif

View File

@ -625,7 +625,7 @@ void MainWindowViewer::createToolBars()
viewer->addAction(closeAction);
viewer->setContextMenuPolicy(Qt::ActionsContextMenu);
updateContextMenuPolicy();
// MacOSX app menus
#ifdef Q_OS_MACOS
@ -777,9 +777,25 @@ void MainWindowViewer::openComicFromRecentAction(QAction *action)
void MainWindowViewer::reloadOptions()
{
updateContextMenuPolicy();
viewer->updateConfig(settings);
}
void MainWindowViewer::updateContextMenuPolicy()
{
auto mouseMode = Configuration::getConfiguration().getMouseMode();
switch (mouseMode) {
case Normal:
case HotAreas:
viewer->setContextMenuPolicy(Qt::ActionsContextMenu);
break;
case LeftRightNavigation:
viewer->setContextMenuPolicy(Qt::NoContextMenu);
break;
}
}
void MainWindowViewer::open()
{
QFileDialog openDialog;
@ -970,9 +986,11 @@ void MainWindowViewer::disablePreviousNextComicActions()
void MainWindowViewer::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
toggleFullScreen();
event->accept();
if (Configuration::getConfiguration().getMouseMode() == MouseMode::Normal) {
if (event->button() == Qt::LeftButton) {
toggleFullScreen();
event->accept();
}
}
}
@ -1319,7 +1337,11 @@ void MainWindowViewer::toggleFitToWidthSlider()
if (zoomSliderAction->isVisible()) {
zoomSliderAction->hide();
} else {
#if defined(Y_MAC_UI) && (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
zoomSliderAction->move((this->width() - zoomSliderAction->width()) / 2, y);
#else
zoomSliderAction->move(250, y);
#endif
zoomSliderAction->show();
}
}

View File

@ -67,6 +67,7 @@ public slots:
void increasePageZoomLevel();
void decreasePageZoomLevel();
void reloadOptions();
void updateContextMenuPolicy();
void fitToWidth();
void fitToHeight();
void toggleWidthHeight();

149
YACReader/mouse_handler.cpp Normal file
View File

@ -0,0 +1,149 @@
#include "mouse_handler.h"
#include <QtWidgets>
#include "configuration.h"
#include "magnifying_glass.h"
#include "render.h"
#include "viewer.h"
#include "goto_flow.h"
#ifndef NO_OPENGL
#include "goto_flow_gl.h"
#else
#include <QtWidgets>
#endif
using namespace YACReader;
YACReader::MouseHandler::MouseHandler(Viewer *viewer)
: viewer(viewer)
{
}
void YACReader::MouseHandler::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
viewer->drag = true;
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto position = event->position();
#else
auto position = QPointF(event->x(), event->y());
#endif
dragOrigin = dragLatestPosition = position;
viewer->setCursor(Qt::ClosedHandCursor);
event->accept();
return;
}
}
void YACReader::MouseHandler::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::ForwardButton) {
viewer->right();
event->accept();
return;
}
if (event->button() == Qt::BackButton) {
viewer->left();
event->accept();
return;
}
auto wasDragging = viewer->drag;
if (event->button() == Qt::LeftButton) {
viewer->drag = false;
viewer->setCursor(Qt::OpenHandCursor);
event->accept();
}
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto position = event->position();
#else
auto position = QPointF(event->x(), event->y());
#endif
auto dragDistance = QLineF(position, dragOrigin).length();
auto mouseMode = Configuration::getConfiguration().getMouseMode();
switch (mouseMode) {
case Normal:
return;
case LeftRightNavigation:
if (wasDragging && (dragDistance > 25)) {
return;
}
if (event->button() == Qt::LeftButton) {
viewer->left();
event->accept();
return;
}
if (event->button() == Qt::RightButton) {
viewer->right();
event->accept();
return;
}
break;
case HotAreas:
if (wasDragging && (dragDistance > 25)) {
return;
}
if (event->button() == Qt::LeftButton) {
if (position.x() < viewer->width() / 2) {
viewer->left();
} else {
viewer->right();
}
}
break;
};
}
void YACReader::MouseHandler::mouseMoveEvent(QMouseEvent *event)
{
viewer->showCursor();
viewer->hideCursorTimer->start(2500);
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
auto position = event->position();
#else
auto position = QPointF(event->x(), event->y());
#endif
if (viewer->magnifyingGlassShown)
viewer->mglass->move(static_cast<int>(position.x() - float(viewer->mglass->width()) / 2), static_cast<int>(position.y() - float(viewer->mglass->height()) / 2));
if (viewer->render->hasLoadedComic()) {
if (viewer->showGoToFlowAnimation->state() != QPropertyAnimation::Running) {
if (Configuration::getConfiguration().getDisableShowOnMouseOver() == false) {
if (viewer->goToFlow->isVisible()) {
QPoint gtfPos = viewer->goToFlow->mapFrom(this->viewer, event->pos());
if (gtfPos.y() < 0 || gtfPos.x() < 0 || gtfPos.x() > viewer->goToFlow->width()) // TODO this extra check is for Mavericks (mouseMove over goToFlowGL seems to be broken)
viewer->animateHideGoToFlow();
// goToFlow->hide();
} else {
int umbral = (viewer->width() - viewer->goToFlow->width()) / 2;
if ((position.y() > viewer->height() - 15) && (position.x() > umbral) && (position.x() < viewer->width() - umbral)) {
viewer->animateShowGoToFlow();
viewer->hideCursorTimer->stop();
}
}
}
}
if (viewer->drag) {
int currentPosY = viewer->verticalScrollBar()->sliderPosition();
int currentPosX = viewer->horizontalScrollBar()->sliderPosition();
viewer->verticalScrollBar()->setSliderPosition(currentPosY + (dragLatestPosition.y() - position.y()));
viewer->horizontalScrollBar()->setSliderPosition(currentPosX + (dragLatestPosition.x() - position.x()));
dragLatestPosition = position;
}
}
}

25
YACReader/mouse_handler.h Normal file
View File

@ -0,0 +1,25 @@
#ifndef MOUSE_HANDLER_H
#define MOUSE_HANDLER_H
#include <QMouseEvent>
class Viewer;
namespace YACReader {
class MouseHandler
{
public:
MouseHandler(Viewer *viewer);
void mousePressEvent(QMouseEvent *event);
void mouseReleaseEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
private:
Viewer *viewer;
QPointF dragOrigin;
QPointF dragLatestPosition;
};
}
#endif // MOUSE_HANDLER_H

View File

@ -81,11 +81,25 @@ OptionsDialog::OptionsDialog(QWidget *parent)
scrollBox->setLayout(scrollLayout);
auto mouseModeBox = new QGroupBox(tr("Mouse mode"));
auto mouseModeLayout = new QVBoxLayout();
normalMouseModeRadioButton = new QRadioButton(tr("Only Back/Forward buttons can turn pages"));
leftRightNavigationMouseModeRadioButton = new QRadioButton(tr("Use the Left/Right buttons to turn pages."));
hotAreasMouseModeRadioButton = new QRadioButton(tr("Click left or right half of the screen to turn pages."));
mouseModeLayout->addWidget(normalMouseModeRadioButton);
mouseModeLayout->addWidget(leftRightNavigationMouseModeRadioButton);
mouseModeLayout->addWidget(hotAreasMouseModeRadioButton);
mouseModeBox->setLayout(mouseModeLayout);
layoutGeneral->addWidget(pathBox);
layoutGeneral->addWidget(slideSizeBox);
// layoutGeneral->addWidget(fitBox);
layoutGeneral->addWidget(colorBox);
layoutGeneral->addWidget(scrollBox);
layoutGeneral->addWidget(mouseModeBox);
layoutGeneral->addWidget(shortcutsBox);
layoutGeneral->addStretch();
@ -246,6 +260,18 @@ void OptionsDialog::saveOptions()
settings->setValue(USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE, useSingleScrollStepToTurnPage->isChecked());
settings->setValue(DISABLE_SCROLL_ANIMATION, disableScrollAnimations->isChecked());
// get checked radio button to get the mouse mode
YACReader::MouseMode mouseMode = Normal;
if (normalMouseModeRadioButton->isChecked()) {
mouseMode = Normal;
;
} else if (leftRightNavigationMouseModeRadioButton->isChecked()) {
mouseMode = LeftRightNavigation;
} else if (hotAreasMouseModeRadioButton->isChecked()) {
mouseMode = HotAreas;
}
Configuration::getConfiguration().setMouseMode(mouseMode);
YACReaderOptionsDialog::saveOptions();
}
@ -286,7 +312,27 @@ void OptionsDialog::restoreOptions(QSettings *settings)
doNotTurnPageOnScroll->setChecked(settings->value(DO_NOT_TURN_PAGE_ON_SCROLL, false).toBool());
useSingleScrollStepToTurnPage->setChecked(settings->value(USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE, false).toBool());
disableScrollAnimations->setChecked(settings->value(DISABLE_SCROLL_ANIMATION, false).toBool());
#ifdef Q_OS_MACOS
auto defaultDisableScrollAnimationsValue = true;
#else
auto defaultDisableScrollAnimationsValue = false;
#endif
disableScrollAnimations->setChecked(settings->value(DISABLE_SCROLL_ANIMATION, defaultDisableScrollAnimationsValue).toBool());
auto mouseMode = Configuration::getConfiguration().getMouseMode();
switch (mouseMode) {
case Normal:
normalMouseModeRadioButton->setChecked(true);
break;
case LeftRightNavigation:
leftRightNavigationMouseModeRadioButton->setChecked(true);
break;
case HotAreas:
hotAreasMouseModeRadioButton->setChecked(true);
break;
}
}
void OptionsDialog::updateColor(const QColor &color)

View File

@ -51,6 +51,11 @@ private:
YACReaderSpinSliderWidget *gammaS;
QColor currentColor;
QRadioButton *normalMouseModeRadioButton;
QRadioButton *leftRightNavigationMouseModeRadioButton;
QRadioButton *hotAreasMouseModeRadioButton;
public slots:
void saveOptions() override;
void restoreOptions(QSettings *settings) override;

View File

@ -1,5 +1,4 @@
#include "viewer.h"
#include "magnifying_glass.h"
#include "configuration.h"
#include "magnifying_glass.h"
#include "goto_flow.h"
@ -38,7 +37,8 @@ Viewer::Viewer(QWidget *parent)
shouldOpenNext(false),
shouldOpenPrevious(false),
magnifyingGlassShown(false),
restoreMagnifyingGlass(false)
restoreMagnifyingGlass(false),
mouseHandler(std::make_unique<YACReader::MouseHandler>(this))
{
translator = new YACReaderTranslator(this);
translator->hide();
@ -260,6 +260,10 @@ void Viewer::processCRCError(QString message)
void Viewer::next()
{
if (!render->hasLoadedComic()) {
return;
}
direction = 1;
if (doublePage && render->currentPageIsDoublePage()) {
render->nextDoublePage();
@ -272,6 +276,10 @@ void Viewer::next()
void Viewer::left()
{
if (!render->hasLoadedComic()) {
return;
}
if (doubleMangaPage) {
next();
} else {
@ -281,6 +289,10 @@ void Viewer::left()
void Viewer::right()
{
if (!render->hasLoadedComic()) {
return;
}
if (doubleMangaPage) {
prev();
} else {
@ -290,6 +302,10 @@ void Viewer::right()
void Viewer::prev()
{
if (!render->hasLoadedComic()) {
return;
}
direction = -1;
if (doublePage && render->previousPageIsDoublePage()) {
render->previousDoublePage();
@ -767,44 +783,6 @@ void Viewer::resizeEvent(QResizeEvent *event)
QScrollArea::resizeEvent(event);
}
void Viewer::mouseMoveEvent(QMouseEvent *event)
{
showCursor();
hideCursorTimer->start(2500);
if (magnifyingGlassShown)
mglass->move(static_cast<int>(event->x() - float(mglass->width()) / 2), static_cast<int>(event->y() - float(mglass->height()) / 2));
if (render->hasLoadedComic()) {
if (showGoToFlowAnimation->state() != QPropertyAnimation::Running) {
if (Configuration::getConfiguration().getDisableShowOnMouseOver() == false) {
if (goToFlow->isVisible()) {
QPoint gtfPos = goToFlow->mapFrom(this, event->pos());
if (gtfPos.y() < 0 || gtfPos.x() < 0 || gtfPos.x() > goToFlow->width()) // TODO this extra check is for Mavericks (mouseMove over goToFlowGL seems to be broken)
animateHideGoToFlow();
// goToFlow->hide();
} else {
int umbral = (width() - goToFlow->width()) / 2;
if ((event->y() > height() - 15) && (event->x() > umbral) && (event->x() < width() - umbral)) {
animateShowGoToFlow();
hideCursorTimer->stop();
}
}
}
}
if (drag) {
int currentPosY = verticalScrollBar()->sliderPosition();
int currentPosX = horizontalScrollBar()->sliderPosition();
verticalScrollBar()->setSliderPosition(currentPosY + (yDragOrigin - event->y()));
horizontalScrollBar()->setSliderPosition(currentPosX + (xDragOrigin - event->x()));
yDragOrigin = event->y();
xDragOrigin = event->x();
}
}
}
QPixmap Viewer::pixmap() const
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
@ -1093,36 +1071,17 @@ void Viewer::animateHideTranslator()
void Viewer::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
drag = true;
yDragOrigin = event->y();
xDragOrigin = event->x();
setCursor(Qt::ClosedHandCursor);
event->accept();
return;
}
mouseHandler->mousePressEvent(event);
}
void Viewer::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
drag = false;
setCursor(Qt::OpenHandCursor);
event->accept();
return;
}
mouseHandler->mouseReleaseEvent(event);
}
if (event->button() == Qt::ForwardButton) {
right();
event->accept();
return;
}
if (event->button() == Qt::BackButton) {
left();
event->accept();
return;
}
void Viewer::mouseMoveEvent(QMouseEvent *event)
{
mouseHandler->mouseMoveEvent(event);
}
void Viewer::updateZoomRatio(int ratio)

View File

@ -17,6 +17,7 @@
#include <QSettings>
#include "scroll_management.h"
#include "mouse_handler.h"
class ComicDB;
class Comic;
@ -147,9 +148,6 @@ private:
int translatorXPos;
QPropertyAnimation *translatorAnimation;
int yDragOrigin;
int xDragOrigin;
NotificationsLabelWidget *notificationsLabel;
bool shouldOpenNext;
@ -185,6 +183,9 @@ private:
int animationDuration() const;
void animateScroll(QPropertyAnimation &scroller, const QScrollBar &scrollBar, int delta);
//! Mouse handler
std::unique_ptr<YACReader::MouseHandler> mouseHandler;
public:
Viewer(QWidget *parent = nullptr);
~Viewer();
@ -213,6 +214,8 @@ signals:
void magnifyingGlassZoomIn();
void magnifyingGlassZoomOut();
void resetMagnifyingGlass();
friend class YACReader::MouseHandler;
};
#endif

View File

@ -74,6 +74,7 @@ greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets core5compat
# Input
HEADERS += comic_flow.h \
../common/concurrent_queue.h \
../common/cover_utils.h \
create_library_dialog.h \
db/comic_query_result_processor.h \
db/folder_query_result_processor.h \
@ -163,6 +164,7 @@ HEADERS += comic_flow.h \
SOURCES += comic_flow.cpp \
../common/concurrent_queue.cpp \
../common/cover_utils.cpp \
create_library_dialog.cpp \
db/comic_query_result_processor.cpp \
db/folder_query_result_processor.cpp \

View File

@ -1,6 +1,9 @@
HEADERS += \
$$PWD/comic_vine_json_parser.h \
$$PWD/model/selected_volume_info.h \
$$PWD/model/volume_search_query.h \
$$PWD/scraper_checkbox.h \
comic_vine/comic_vine_dialog.h \
comic_vine/comic_vine_client.h \
comic_vine/scraper_lineedit.h \
@ -20,12 +23,12 @@ HEADERS += \
comic_vine/model/volume_comics_model.h \
comic_vine/scraper_scroll_label.h \
comic_vine/scraper_results_paginator.h \
comic_vine/scraper_selector.h \
comic_vine/api_key_dialog.h \
$$PWD/comic_vine_all_volume_comics_retriever.h
SOURCES += \
$$PWD/comic_vine_json_parser.cpp \
$$PWD/scraper_checkbox.cpp \
comic_vine/comic_vine_dialog.cpp \
comic_vine/comic_vine_client.cpp \
comic_vine/scraper_lineedit.cpp \
@ -45,6 +48,5 @@ SOURCES += \
comic_vine/model/volume_comics_model.cpp \
comic_vine/scraper_scroll_label.cpp \
comic_vine/scraper_results_paginator.cpp \
comic_vine/scraper_selector.cpp \
comic_vine/api_key_dialog.cpp \
$$PWD/comic_vine_all_volume_comics_retriever.cpp

View File

@ -18,6 +18,13 @@ static const QString CV_SEARCH = CV_WEB_ADDRESS + "/search/?api_key=" + CV_API_K
"&query=%1&page=%2";
// http://www.comicvine.com/api/search/?api_key=46680bebb358f1de690a5a365e15d325f9649f91&format=json&limit=100&resources=volume&field_list=name,start_year,publisher,id,image,count_of_issues,deck&query=superman
// look for exact match volumes
static const QString CV_EXACT_VOLUME_SEARCH = CV_WEB_ADDRESS + "/volumes/?api_key=" + CV_API_KEY +
"&format=json&limit=100"
"&field_list=name,start_year,publisher,id,image,count_of_issues,deck"
"&sort=name:asc"
"&filter=name:%1&offset=%2";
// gets the detail for a volume %1
static const QString CV_SERIES_DETAIL = CV_WEB_ADDRESS + "/volume/4050-%1/?api_key=" + CV_API_KEY +
"&format=json&field_list=name,start_year,publisher,image,count_of_issues,id,description";
@ -69,6 +76,17 @@ void ComicVineClient::search(const QString &query, int page)
connect(search, &QThread::finished, search, &QObject::deleteLater);
search->get();
}
// CV_EXACT_VOLUME_SEARCH
void ComicVineClient::searchExactVolume(const QString &query, int page)
{
HttpWorker *search = new HttpWorker(QString(CV_EXACT_VOLUME_SEARCH).replace(CV_WEB_ADDRESS, baseURL).replace(CV_API_KEY, settings->value(COMIC_VINE_API_KEY, CV_API_KEY_DEFAULT).toString()).arg(query).arg((page - 1) * 100));
connect(search, &HttpWorker::dataReady, this, &ComicVineClient::proccessVolumesSearchData);
connect(search, &HttpWorker::timeout, this, &ComicVineClient::timeOut);
connect(search, &QThread::finished, search, &QObject::deleteLater);
search->get();
}
// CV_SEARCH result
void ComicVineClient::proccessVolumesSearchData(const QByteArray &data)
{
@ -120,7 +138,7 @@ void ComicVineClient::getSeriesCover(const QString &url)
// CV_COMIC_IDS
void ComicVineClient::getVolumeComicsInfo(const QString &idVolume, int page)
{
HttpWorker *search = new HttpWorker(QString(CV_COMICS_INFO).replace(CV_WEB_ADDRESS, baseURL).replace(CV_API_KEY, settings->value(COMIC_VINE_API_KEY, CV_API_KEY_DEFAULT).toString()).arg(idVolume).arg((page - 1) * 100)); // page doesn't work for search, using offset instead
HttpWorker *search = new HttpWorker(QString(CV_COMICS_INFO).replace(CV_WEB_ADDRESS, baseURL).replace(CV_API_KEY, settings->value(COMIC_VINE_API_KEY, CV_API_KEY_DEFAULT).toString()).arg(idVolume).arg((page - 1) * 100));
connect(search, &HttpWorker::dataReady, this, &ComicVineClient::processVolumeComicsInfo);
connect(search, &HttpWorker::timeout, this, &ComicVineClient::timeOut); // TODO
connect(search, &QThread::finished, search, &QObject::deleteLater);

View File

@ -24,6 +24,7 @@ signals:
void finished();
public slots:
void search(const QString &query, int page = 1);
void searchExactVolume(const QString &query, int page = 1);
void getSeriesDetail(const QString &id);
void getSeriesCover(const QString &url);
void getVolumeComicsInfo(const QString &idVolume, int page = 1);

View File

@ -22,6 +22,7 @@
#include "search_volume.h"
#include "select_comic.h"
#include "select_volume.h"
#include "selected_volume_info.h"
#include "sort_volume_comics.h"
#include "db_helper.h"
#include "response_parser.h"
@ -93,7 +94,7 @@ void ComicVineDialog::doLayout()
setLayout(mainLayout);
setWindowTitle("Comic Vine Scraper (beta)");
setWindowTitle("Comic Vine Scraper");
}
void ComicVineDialog::doStackedWidgets()
@ -115,9 +116,9 @@ void ComicVineDialog::doConnections()
connect(searchButton, &QAbstractButton::clicked, this, &ComicVineDialog::search);
connect(skipButton, &QAbstractButton::clicked, this, &ComicVineDialog::goToNextComic);
connect(selectVolumeWidget, &ScraperSelector::loadPage, this, &ComicVineDialog::searchVolume);
connect(selectComicWidget, &ScraperSelector::loadPage, this, &ComicVineDialog::getVolumeComicsInfo);
connect(sortVolumeComicsWidget, &ScraperSelector::loadPage, this, &ComicVineDialog::getVolumeComicsInfo);
connect(selectVolumeWidget, &SelectVolume::loadPage, this, &ComicVineDialog::searchVolume);
connect(selectComicWidget, &SelectComic::loadPage, this, &ComicVineDialog::getVolumeComicsInfo);
connect(sortVolumeComicsWidget, &SortVolumeComics::loadPage, this, &ComicVineDialog::getVolumeComicsInfo);
connect(this, &QDialog::accepted, this, &QWidget::close, Qt::QueuedConnection);
}
@ -130,21 +131,22 @@ void ComicVineDialog::goNext()
if (content->currentWidget() == seriesQuestionWidget) {
if (seriesQuestionWidget->getYes()) {
QString volumeSearchString = comics[0].getParentFolderName();
mode = Volume;
mode = ScraperMode::Volume;
showSearchVolume(volumeSearchString);
} else {
status = AutoSearching;
mode = SingleComicInList;
status = ScraperStatus::AutoSearching;
mode = ScraperMode::SingleComicInList;
ComicDB comic = comics[currentIndex];
QString title = comic.getTitleOrFileName();
titleHeader->setSubTitle(tr("comic %1 of %2 - %3").arg(currentIndex + 1).arg(comics.length()).arg(title));
showLoading(tr("Looking for volume..."));
searchVolume(title);
searchVolume({ volumeSearchStringFromComic(comic), 1, true });
}
} else if (content->currentWidget() == selectVolumeWidget) {
currentVolumeId = selectVolumeWidget->getSelectedVolumeId();
currentVolumeId = selectVolumeWidget->getSelectedVolumeInfo().id;
getVolumeComicsInfo(currentVolumeId);
} else if (content->currentWidget() == sortVolumeComicsWidget) {
@ -152,24 +154,23 @@ void ComicVineDialog::goNext()
// ComicDB-ComicVineID
QList<QPair<ComicDB, QString>> matchingInfo = sortVolumeComicsWidget->getMatchingInfo();
int count = selectVolumeWidget->getSelectedVolumeNumIssues();
QString publisher = selectVolumeWidget->getSelectedVolumePublisher();
auto volumeInfo = selectVolumeWidget->getSelectedVolumeInfo();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QtConcurrent::run(&ComicVineDialog::getComicsInfo, this, matchingInfo, count, publisher);
QtConcurrent::run(&ComicVineDialog::getComicsInfo, this, matchingInfo, volumeInfo);
#else
QtConcurrent::run(this, &ComicVineDialog::getComicsInfo, matchingInfo, count, publisher);
QtConcurrent::run(this, &ComicVineDialog::getComicsInfo, matchingInfo, volumeInfo);
#endif
} else if (content->currentWidget() == selectComicWidget) {
showLoading();
QString comicId = selectComicWidget->getSelectedComicId();
int count = selectVolumeWidget->getSelectedVolumeNumIssues();
QString publisher = selectVolumeWidget->getSelectedVolumePublisher();
auto volumeInfo = selectVolumeWidget->getSelectedVolumeInfo();
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
QtConcurrent::run(&ComicVineDialog::getComicInfo, this, comicId, count, publisher);
QtConcurrent::run(&ComicVineDialog::getComicInfo, this, comicId, volumeInfo);
#else
QtConcurrent::run(this, &ComicVineDialog::getComicInfo, comicId, count, publisher);
QtConcurrent::run(this, &ComicVineDialog::getComicInfo, comicId, volumeInfo);
#endif
}
}
@ -179,30 +180,35 @@ void ComicVineDialog::goBack()
clearState();
switch (status) {
case SelectingSeries:
if (mode == Volume)
showSearchVolume();
case ScraperStatus::SelectingSeries:
if (mode == ScraperMode::Volume)
showSearchVolume(currentVolumeSearchQuery.volume);
else
showSearchSingleComic();
showSearchSingleComic(currentVolumeSearchQuery.volume);
break;
case SortingComics:
case ScraperStatus::SortingComics:
showSelectVolume();
break;
case SelectingComic:
if (mode == SingleComic)
case ScraperStatus::SelectingComic:
if (mode == ScraperMode::SingleComic)
showSelectVolume();
break;
case AutoSearching:
if (mode == Volume)
showSearchVolume();
case ScraperStatus::AutoSearching:
if (mode == ScraperMode::Volume)
showSearchVolume(currentVolumeSearchQuery.volume);
else
showSearchSingleComic();
showSearchSingleComic(currentVolumeSearchQuery.volume);
break;
default:
if (mode == Volume)
showSearchVolume();
case ScraperStatus::AskingForInfo:
case ScraperStatus::SearchingSingleComic:
case ScraperStatus::SearchingVolume:
case ScraperStatus::SearchingExactVolume:
case ScraperStatus::GettingVolumeComics:
if (mode == ScraperMode::Volume)
showSearchVolume(currentVolumeSearchQuery.volume);
else
showSearchSingleComic();
showSearchSingleComic(currentVolumeSearchQuery.volume);
break;
}
}
@ -248,16 +254,15 @@ void ComicVineDialog::show()
searchVolumeWidget->clean();
if (comics.length() == 1) {
status = AutoSearching;
mode = SingleComic;
status = ScraperStatus::AskingForInfo;
mode = ScraperMode::SingleComic;
ComicDB singleComic = comics[0];
QString title = singleComic.getTitleOrFileName();
titleHeader->setSubTitle(title);
showLoading(tr("Looking for volume..."));
searchVolume(singleComic.getParentFolderName());
QLOG_TRACE() << singleComic.getParentFolderName();
showSearchSingleComic(volumeSearchStringFromComic(singleComic));
} else if (comics.length() > 1) {
titleHeader->setSubTitle(tr("%1 comics selected").arg(comics.length()));
showSeriesQuestion();
@ -286,7 +291,7 @@ void ComicVineDialog::doLoading()
content->addWidget(w);
}
void ComicVineDialog::debugClientResults(const QString &string)
void ComicVineDialog::processClientResults(const QString &string)
{
ResponseParser p;
p.loadJSONResponse(string);
@ -295,19 +300,20 @@ void ComicVineDialog::debugClientResults(const QString &string)
QMessageBox::critical(0, tr("Error connecting to ComicVine"), p.errorDescription());
goBack();
} else {
switch (mode) {
case SingleComic:
case SingleComicInList:
case ScraperMode::SingleComic:
case ScraperMode::SingleComicInList:
if (p.getNumResults() == 0)
showSearchSingleComic();
else if (status == SearchingVolume)
showSearchSingleComic(currentVolumeSearchQuery.volume);
else if (status == ScraperStatus::SearchingVolume || status == ScraperStatus::SearchingExactVolume)
showSelectVolume(string);
else
showSelectComic(string);
break;
case Volume:
case ScraperMode::Volume:
if (p.getNumResults() == 0)
showSearchVolume();
showSearchVolume(currentVolumeSearchQuery.volume);
else
showSelectVolume(string);
break;
@ -317,7 +323,7 @@ void ComicVineDialog::debugClientResults(const QString &string)
void ComicVineDialog::showSeriesQuestion()
{
status = AskingForInfo;
status = ScraperStatus::AskingForInfo;
content->setCurrentWidget(seriesQuestionWidget);
backButton->setHidden(true);
skipButton->setHidden(true);
@ -330,9 +336,11 @@ void ComicVineDialog::showSeriesQuestion()
toggleSkipButton();
}
void ComicVineDialog::showSearchSingleComic()
void ComicVineDialog::showSearchSingleComic(const QString &volume)
{
status = AskingForInfo;
searchSingleComicWidget->setVolumeInfo(volume);
status = ScraperStatus::AskingForInfo;
content->setCurrentWidget(searchSingleComicWidget);
backButton->setHidden(true);
skipButton->setHidden(true);
@ -349,7 +357,7 @@ void ComicVineDialog::showSearchVolume(const QString &volume)
{
searchVolumeWidget->setVolumeInfo(volume);
status = AskingForInfo;
status = ScraperStatus::AskingForInfo;
content->setCurrentWidget(searchVolumeWidget);
backButton->setHidden(true);
nextButton->setHidden(true);
@ -364,12 +372,12 @@ void ComicVineDialog::showSearchVolume(const QString &volume)
void ComicVineDialog::showSelectVolume(const QString &json)
{
showSelectVolume();
selectVolumeWidget->load(json, currentVolumeSearchString);
selectVolumeWidget->load(json, currentVolumeSearchQuery);
}
void ComicVineDialog::showSelectVolume()
{
status = SelectingSeries;
status = ScraperStatus::SelectingSeries;
content->setCurrentWidget(selectVolumeWidget);
@ -385,7 +393,7 @@ void ComicVineDialog::showSelectVolume()
void ComicVineDialog::showSelectComic(const QString &json)
{
status = SelectingComic;
status = ScraperStatus::SelectingComic;
content->setCurrentWidget(selectComicWidget);
selectComicWidget->load(json, currentVolumeId);
@ -402,7 +410,7 @@ void ComicVineDialog::showSelectComic(const QString &json)
void ComicVineDialog::showSortVolumeComics(const QString &json)
{
status = SortingComics;
status = ScraperStatus::SortingComics;
content->setCurrentWidget(sortVolumeComicsWidget);
@ -423,30 +431,35 @@ void ComicVineDialog::queryTimeOut()
QMessageBox::warning(this, "Comic Vine error", "Time out connecting to Comic Vine");
switch (status) {
case AutoSearching:
if (mode == Volume)
showSearchVolume();
case ScraperStatus::AutoSearching:
if (mode == ScraperMode::Volume)
showSearchVolume(currentVolumeSearchQuery.volume);
else
showSearchSingleComic();
showSearchSingleComic(currentVolumeSearchQuery.volume);
break;
case SearchingVolume:
if (mode == Volume)
showSearchVolume();
case ScraperStatus::SearchingVolume:
case ScraperStatus::SearchingExactVolume:
if (mode == ScraperMode::Volume)
showSearchVolume(currentVolumeSearchQuery.volume);
else
showSearchSingleComic();
showSearchSingleComic(currentVolumeSearchQuery.volume);
break;
case SearchingSingleComic:
showSearchSingleComic();
case ScraperStatus::SearchingSingleComic:
showSearchSingleComic(currentVolumeSearchQuery.volume);
break;
case GettingVolumeComics:
case ScraperStatus::GettingVolumeComics:
showSelectVolume();
break;
default:
case ScraperStatus::AskingForInfo:
case ScraperStatus::SelectingComic:
case ScraperStatus::SelectingSeries:
case ScraperStatus::SortingComics:
break;
}
}
void ComicVineDialog::getComicsInfo(QList<QPair<ComicDB, QString>> matchingInfo, int count, const QString &publisher)
void ComicVineDialog::getComicsInfo(QList<QPair<ComicDB, QString>> matchingInfo, const SelectedVolumeInfo &volumeInfo)
{
QPair<ComicDB, QString> p;
QList<ComicDB> comics;
@ -460,7 +473,7 @@ void ComicVineDialog::getComicsInfo(QList<QPair<ComicDB, QString>> matchingInfo,
QByteArray result = comicVineClient->getComicDetail(p.second, error, timeout); // TODO check timeOut or Connection error
if (error || timeout)
continue; // TODO
ComicDB comic = YACReader::parseCVJSONComicInfo(p.first, result, count, publisher); // TODO check result error
ComicDB comic = YACReader::parseCVJSONComicInfo(p.first, result, volumeInfo); // TODO check result error
comic.info.comicVineID = p.second;
comics.push_back(comic);
@ -472,7 +485,7 @@ void ComicVineDialog::getComicsInfo(QList<QPair<ComicDB, QString>> matchingInfo,
emit accepted();
}
void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QString &publisher)
void ComicVineDialog::getComicInfo(const QString &comicId, const SelectedVolumeInfo &volumeInfo)
{
auto comicVineClient = new ComicVineClient;
@ -481,14 +494,14 @@ void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QStr
QByteArray result = comicVineClient->getComicDetail(comicId, error, timeout); // TODO check timeOut or Connection error
if (error || timeout) {
// TODO
if (mode == SingleComic || currentIndex == (comics.count() - 1)) {
if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) {
emit accepted();
} else {
goToNextComic();
}
}
ComicDB comic = YACReader::parseCVJSONComicInfo(comics[currentIndex], result, count, publisher); // TODO check result error
ComicDB comic = YACReader::parseCVJSONComicInfo(comics[currentIndex], result, volumeInfo); // TODO check result error
comic.info.comicVineID = comicId;
setLoadingMessage(tr("Retrieving tags for : %1").arg(comics[currentIndex].getFileName()));
QString connectionName = "";
@ -504,7 +517,7 @@ void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QStr
}
QSqlDatabase::removeDatabase(connectionName);
if (mode == SingleComic || currentIndex == (comics.count() - 1)) {
if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) {
emit accepted();
} else {
goToNextComic();
@ -513,26 +526,46 @@ void ComicVineDialog::getComicInfo(const QString &comicId, int count, const QStr
void ComicVineDialog::toggleSkipButton()
{
if (mode == SingleComicInList)
if (mode == ScraperMode::SingleComicInList)
skipButton->setVisible(true);
else
skipButton->setHidden(true);
}
QString ComicVineDialog::volumeSearchStringFromComic(const ComicDB &comic)
{
auto volume = comic.info.volume.toString().trimmed();
if (!volume.isEmpty())
return volume;
auto series = comic.info.series.toString().trimmed();
if (!series.isEmpty())
return series;
auto alternateSeries = comic.info.alternateSeries.toString().trimmed();
if (!alternateSeries.isEmpty())
return alternateSeries;
// extract information from file name
auto parentFolderName = comic.getParentFolderName();
return parentFolderName;
}
void ComicVineDialog::goToNextComic()
{
if (mode == SingleComic || currentIndex == (comics.count() - 1)) {
if (mode == ScraperMode::SingleComic || currentIndex == (comics.count() - 1)) {
emit accepted();
return;
}
currentIndex++;
showSearchSingleComic();
ComicDB comic = comics[currentIndex];
QString title = comic.getTitleOrFileName();
titleHeader->setSubTitle(tr("comic %1 of %2 - %3").arg(currentIndex + 1).arg(comics.length()).arg(title));
showSearchSingleComic(volumeSearchStringFromComic(comic));
}
void ComicVineDialog::clearState()
@ -559,38 +592,44 @@ void ComicVineDialog::setLoadingMessage(const QString &message)
void ComicVineDialog::search()
{
switch (mode) {
case Volume:
case ScraperMode::Volume:
launchSearchVolume();
break;
default:
case ScraperMode::SingleComic:
case ScraperMode::SingleComicInList:
launchSearchComic();
break;
}
}
void ComicVineDialog::searchVolume(const QString &v, int page)
void ComicVineDialog::searchVolume(const VolumeSearchQuery &query)
{
showLoading(tr("Looking for volume..."));
currentVolumeSearchString = v;
currentVolumeSearchQuery = query;
auto comicVineClient = new ComicVineClient;
connect(comicVineClient, &ComicVineClient::searchResult, this, &ComicVineDialog::debugClientResults);
connect(comicVineClient, &ComicVineClient::searchResult, this, &ComicVineDialog::processClientResults);
connect(comicVineClient, &ComicVineClient::timeOut, this, &ComicVineDialog::queryTimeOut);
connect(comicVineClient, &ComicVineClient::finished, comicVineClient, &QObject::deleteLater);
comicVineClient->search(v, page);
status = SearchingVolume;
if (query.exactMatch) {
status = ScraperStatus::SearchingExactVolume;
comicVineClient->searchExactVolume(query.volume, query.page);
} else {
status = ScraperStatus::SearchingVolume;
comicVineClient->search(query.volume, query.page);
}
}
void ComicVineDialog::getVolumeComicsInfo(const QString &vID, int /* page */)
{
showLoading(tr("Retrieving volume info..."));
status = GettingVolumeComics;
status = ScraperStatus::GettingVolumeComics;
auto comicVineClient = new ComicVineClient;
if (mode == Volume)
if (mode == ScraperMode::Volume)
connect(comicVineClient, &ComicVineClient::volumeComicsInfo, this, &ComicVineDialog::showSortVolumeComics);
else
connect(comicVineClient, &ComicVineClient::volumeComicsInfo, this, &ComicVineDialog::showSelectComic);
@ -602,21 +641,28 @@ void ComicVineDialog::getVolumeComicsInfo(const QString &vID, int /* page */)
comicVineClient->getAllVolumeComicsInfo(vID);
}
// TODO: get the search configuration for exact match or not
void ComicVineDialog::launchSearchVolume()
{
showLoading(tr("Looking for volume..."));
// TODO: check if volume info is empty.
searchVolume(searchVolumeWidget->getVolumeInfo());
QString volumeInfo = searchVolumeWidget->getVolumeInfo();
bool exactMatch = searchVolumeWidget->getExactMatch();
searchVolume({ volumeInfo, 1, exactMatch });
}
// TODO: get the search configuration for exact match or not
void ComicVineDialog::launchSearchComic()
{
showLoading(tr("Looking for comic..."));
QString volumeInfo = searchSingleComicWidget->getVolumeInfo();
bool exactMatch = searchSingleComicWidget->getExactMatch();
// QString comicInfo = searchSingleComicWidget->getComicInfo();
// int comicNumber = searchSingleComicWidget->getComicNumber();
// if(comicInfo.isEmpty() && comicNumber == -1)
searchVolume(volumeInfo);
searchVolume({ volumeInfo, 1, exactMatch });
}

View File

@ -4,6 +4,7 @@
#include <QDialog>
#include "comic_db.h"
#include "volume_search_query.h"
class QPushButton;
class QStackedWidget;
@ -17,7 +18,9 @@ class SearchSingleComic;
class SearchVolume;
class SelectComic;
class SelectVolume;
struct SelectedVolumeInfo;
class SortVolumeComics;
struct VolumeSearchQuery;
// TODO this should use a QStateMachine
//----------------------------------------
@ -31,8 +34,8 @@ public:
void setComics(const QList<ComicDB> &comics);
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
void getComicsInfo(QList<QPair<ComicDB, QString>> matchingInfo, int count, const QString &publisher);
void getComicInfo(const QString &comicId, int count, const QString &publisher);
void getComicsInfo(QList<QPair<ComicDB, QString>> matchingInfo, const SelectedVolumeInfo &volumeInfo);
void getComicInfo(const QString &comicId, const SelectedVolumeInfo &volumeInfo);
void closeEvent(QCloseEvent *event) override;
signals:
@ -42,14 +45,14 @@ public slots:
protected slots:
void goNext();
void goBack();
void debugClientResults(const QString &string);
void processClientResults(const QString &string);
// show widget methods
void showSeriesQuestion();
void showSearchSingleComic();
void showSearchSingleComic(const QString &volume = "");
void showSearchVolume(const QString &volume = "");
void showLoading(const QString &message = "");
void search();
void searchVolume(const QString &v, int page = 1);
void searchVolume(const VolumeSearchQuery &query);
void getVolumeComicsInfo(const QString &vID, int page = 1);
void launchSearchVolume();
void launchSearchComic();
@ -63,22 +66,23 @@ protected slots:
private:
void clearState();
void toggleSkipButton();
QString volumeSearchStringFromComic(const ComicDB &comic);
enum ScraperMode {
enum class ScraperMode {
SingleComic, // the scraper has been opened for a single comic
Volume, // the scraper is trying to get comics info for a whole volume
SingleComicInList // the scraper has been opened for a list of unrelated comics
};
enum ScraperStatus {
AutoSearching,
AskingForInfo,
enum class ScraperStatus {
AutoSearching, // Searching for volumes maching a single comic
AskingForInfo, // The dialog is showing some UI to ask the user for some info
SelectingComic,
SelectingSeries,
SearchingSingleComic,
SearchingVolume,
SearchingExactVolume,
SortingComics,
GettingVolumeComics
};
@ -118,7 +122,7 @@ private:
SelectComic *selectComicWidget;
SortVolumeComics *sortVolumeComicsWidget;
QString currentVolumeSearchString;
VolumeSearchQuery currentVolumeSearchQuery;
QString currentVolumeId;
};

View File

@ -2,6 +2,7 @@
#include "comic_vine_json_parser.h"
#include "comic_vine_client.h"
#include "selected_volume_info.h"
#include <QJsonDocument>
#include <QJsonParseError>
@ -12,7 +13,7 @@ QPair<QString, QString> getFirstStoryArcIdAndName(const QVariant &json_story_arc
QPair<QString, QString> getArcNumberAndArcCount(const QString &storyArcId, const QString &comicId);
QList<QString> getNamesFromList(const QVariant &json_list);
ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, int count, const QString &publisher)
ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, const SelectedVolumeInfo &volumeInfo)
{
QJsonParseError Err;
@ -61,8 +62,12 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, int
}
}
if (result.contains("description") && !result.value("description").isNull()) {
if (result.contains("description") && !result.value("description").isNull() && !result.value("description").toString().trimmed().isEmpty()) {
comic.info.synopsis = result.value("description");
} else if (result.contains("deck") && !result.value("deck").isNull() && !result.value("deck").toString().trimmed().isEmpty()) {
comic.info.synopsis = result.value("deck");
} else if (!volumeInfo.description.trimmed().isEmpty() && volumeInfo.numIssues < 2) {
comic.info.synopsis = volumeInfo.description.trimmed();
}
if (result.contains("character_credits") && !result.value("character_credits").isNull()) {
@ -101,9 +106,9 @@ ComicDB YACReader::parseCVJSONComicInfo(ComicDB &comic, const QString &json, int
comic.info.characters = getNamesFromList(result.value("character_credits")).join("\n");
}
comic.info.count = count;
comic.info.count = volumeInfo.numIssues;
comic.info.publisher = publisher;
comic.info.publisher = volumeInfo.publisher;
comic.info.edited = true;
}

View File

@ -4,9 +4,11 @@
#include "comic_db.h"
struct SelectedVolumeInfo;
namespace YACReader {
ComicDB parseCVJSONComicInfo(ComicDB &comic, const QString &json, int count, const QString &publisher);
ComicDB parseCVJSONComicInfo(ComicDB &comic, const QString &json, const SelectedVolumeInfo &volumeInfo);
}

View File

@ -2,6 +2,7 @@
#include <QDebug>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QVariantMap>
ResponseParser::ResponseParser(QObject *parent)
: QObject(parent), error(false), errorTxt("None"), numResults(-1), currentPage(-1), totalPages(-1)

View File

@ -0,0 +1,13 @@
#ifndef SELECTED_VOLUME_INFO_H
#define SELECTED_VOLUME_INFO_H
#include <QString>
struct SelectedVolumeInfo {
QString id;
int numIssues;
QString publisher;
QString description;
};
#endif // SELECTED_VOLUME_INFO_H

View File

@ -0,0 +1,12 @@
#ifndef VOLUME_SEARCH_QUERY_H
#define VOLUME_SEARCH_QUERY_H
#include <QString>
struct VolumeSearchQuery {
QString volume;
int page;
bool exactMatch;
};
#endif // VOLUME_SEARCH_QUERY_H

View File

@ -0,0 +1,27 @@
#include "scraper_checkbox.h"
#include "qwidget.h"
ScraperCheckBox::ScraperCheckBox(const QString &text, QWidget *parent)
: QCheckBox(text, parent)
{
setStyleSheet(
"QCheckBox {"
" color: white;"
" font-size: 12px;"
" font-family: Arial;"
" spacing: 10px;"
"}"
"QCheckBox::indicator {"
" width: 13px;"
" height: 13px;"
" border: 1px solid #242424;"
" background: #2e2e2e;"
"}"
"QCheckBox::indicator:checked {"
" image: url(:/images/comic_vine/checkBoxTick.svg);"
" background: #2e2e2e;"
"}"
"QCheckBox::indicator:unchecked {"
" background: #2e2e2e;"
"}");
}

View File

@ -0,0 +1,12 @@
#ifndef SCRAPER_CHECKBOX_H
#define SCRAPER_CHECKBOX_H
#include <QCheckBox>
class ScraperCheckBox : public QCheckBox
{
public:
ScraperCheckBox(const QString &text, QWidget *parent = nullptr);
};
#endif // SCRAPER_CHECKBOX_H

View File

@ -1,25 +0,0 @@
#include "scraper_selector.h"
ScraperSelector::ScraperSelector(QWidget *parent)
: QWidget(parent)
{
paginator = new ScraperResultsPaginator;
connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &ScraperSelector::loadNextPage);
connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &ScraperSelector::loadPreviousPage);
}
void ScraperSelector::load(const QString &json, const QString &searchString)
{
currentSearchString = searchString;
paginator->update(json);
}
void ScraperSelector::loadNextPage()
{
emit loadPage(currentSearchString, paginator->getCurrentPage() + 1);
}
void ScraperSelector::loadPreviousPage()
{
emit loadPage(currentSearchString, paginator->getCurrentPage() - 1);
}

View File

@ -1,28 +0,0 @@
#ifndef SCRAPER_SELECTOR_H
#define SCRAPER_SELECTOR_H
#include <QWidget>
#include "scraper_results_paginator.h"
class ScraperSelector : public QWidget
{
Q_OBJECT
public:
explicit ScraperSelector(QWidget *parent = nullptr);
virtual void load(const QString &json, const QString &searchString);
public slots:
signals:
void loadPage(QString, int);
private slots:
void loadNextPage();
void loadPreviousPage();
protected:
QString currentSearchString;
ScraperResultsPaginator *paginator;
};
#endif // SCRAPER_SELECTOR_H

View File

@ -54,6 +54,8 @@ ScraperTableView::ScraperTableView(QWidget *parent)
setAlternatingRowColors(true);
horizontalHeader()->setMinimumSectionSize(85);
verticalHeader()->hide();
setSelectionMode(QAbstractItemView::SingleSelection);

View File

@ -11,12 +11,16 @@ SearchSingleComic::SearchSingleComic(QWidget *parent)
{
// QLabel * label = new QLabel(tr("Please provide some additional information. At least one field is needed."));
QLabel *label = new QLabel(tr("Please provide some additional information."));
QLabel *label = new QLabel(tr("Please provide some additional information for this comic."));
label->setStyleSheet("QLabel {color:white; font-size:12px;font-family:Arial;}");
// titleEdit = new ScraperLineEdit(tr("Title:"));
// numberEdit = new ScraperLineEdit(tr("Number:"));
volumeEdit = new ScraperLineEdit(tr("Series:"));
volumeEdit->setClearButtonEnabled(true);
exactMatchCheckBox = new ScraperCheckBox(tr("Use exact match search. Disable if you want to find volumes that match some of the words in the name."));
exactMatchCheckBox->setChecked(true);
// numberEdit->setMaximumWidth(126);
@ -29,6 +33,7 @@ SearchSingleComic::SearchSingleComic(QWidget *parent)
l->addWidget(label);
// l->addLayout(hl);
l->addWidget(volumeEdit);
l->addWidget(exactMatchCheckBox);
l->addStretch();
l->setContentsMargins(0, 0, 0, 0);
@ -36,11 +41,16 @@ SearchSingleComic::SearchSingleComic(QWidget *parent)
setContentsMargins(0, 0, 0, 0);
}
QString SearchSingleComic::getVolumeInfo()
QString SearchSingleComic::getVolumeInfo() const
{
return volumeEdit->text();
}
void SearchSingleComic::setVolumeInfo(const QString &volume)
{
volumeEdit->setText(volume);
}
QString SearchSingleComic::getComicInfo()
{
// return titleEdit->text();

View File

@ -1,7 +1,9 @@
#ifndef SEARCH_SINGLE_COMIC_H
#define SEARCH_SINGLE_COMIC_H
#include <QWidget>
#include <QtWidgets>
#include "scraper_checkbox.h"
class ScraperLineEdit;
@ -10,14 +12,17 @@ class SearchSingleComic : public QWidget
Q_OBJECT
public:
SearchSingleComic(QWidget *parent = nullptr);
QString getVolumeInfo();
QString getVolumeInfo() const;
bool getExactMatch() const { return exactMatchCheckBox->isChecked(); }
void setVolumeInfo(const QString &volume);
QString getComicInfo();
int getComicNumber();
void clean();
private:
ScraperLineEdit *titleEdit;
ScraperLineEdit *numberEdit;
// ScraperLineEdit *titleEdit;
// ScraperLineEdit *numberEdit;
ScraperLineEdit *volumeEdit;
ScraperCheckBox *exactMatchCheckBox;
};
#endif // SEARCH_SINGLE_COMIC_H

View File

@ -1,6 +1,7 @@
#include "search_volume.h"
#include "scraper_lineedit.h"
#include "scraper_checkbox.h"
#include <QLabel>
#include <QVBoxLayout>
@ -12,12 +13,17 @@ SearchVolume::SearchVolume(QWidget *parent)
label->setStyleSheet("QLabel {color:white; font-size:12px;font-family:Arial;}");
volumeEdit = new ScraperLineEdit(tr("Series:"));
volumeEdit->setClearButtonEnabled(true);
exactMatchCheckBox = new ScraperCheckBox(tr("Use exact match search. Disable if you want to find volumes that match some of the words in the name."), this);
exactMatchCheckBox->setChecked(true);
QVBoxLayout *l = new QVBoxLayout;
l->addSpacing(35);
l->addWidget(label);
l->addWidget(volumeEdit);
l->addWidget(exactMatchCheckBox);
l->addStretch();
l->setContentsMargins(0, 0, 0, 0);

View File

@ -1,9 +1,11 @@
#ifndef SEARCH_VOLUME_H
#define SEARCH_VOLUME_H
#include <QWidget>
#include <QtWidgets>
#include "scraper_checkbox.h"
class ScraperLineEdit;
class ScraperCheckBox;
class SearchVolume : public QWidget
{
@ -13,9 +15,11 @@ public:
void clean();
void setVolumeInfo(const QString &volume);
QString getVolumeInfo() const;
bool getExactMatch() const { return exactMatchCheckBox->isChecked(); }
private:
ScraperLineEdit *volumeEdit;
ScraperCheckBox *exactMatchCheckBox;
};
#endif // SEARCH_VOLUME_H

View File

@ -11,7 +11,7 @@
#include <QLayout>
SelectComic::SelectComic(QWidget *parent)
: ScraperSelector(parent), model(0)
: QWidget(parent), model(0)
{
QString labelStylesheet = "QLabel {color:white; font-size:12px;font-family:Arial;}";
@ -35,6 +35,9 @@ SelectComic::SelectComic(QWidget *parent)
// connections
connect(tableComics, &QAbstractItemView::clicked, this, &SelectComic::loadComicInfo);
paginator = new ScraperResultsPaginator;
connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &SelectComic::loadNextPage);
connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &SelectComic::loadPreviousPage);
paginator->setCustomLabel(tr("comics"));
left->addWidget(cover);
@ -62,7 +65,7 @@ SelectComic::SelectComic(QWidget *parent)
setContentsMargins(0, 0, 0, 0);
}
void SelectComic::load(const QString &json, const QString &searchString)
void SelectComic::load(const QString &json, const QString &volumeId)
{
auto tempM = new VolumeComicsModel();
tempM->load(json);
@ -80,7 +83,18 @@ void SelectComic::load(const QString &json, const QString &searchString)
tableComics->resizeColumnToContents(0);
ScraperSelector::load(json, searchString);
currentVolumeId = volumeId;
paginator->update(json);
}
void SelectComic::loadNextPage()
{
emit loadPage(currentVolumeId, paginator->getCurrentPage() + 1);
}
void SelectComic::loadPreviousPage()
{
emit loadPage(currentVolumeId, paginator->getCurrentPage() - 1);
}
SelectComic::~SelectComic() { }
@ -130,9 +144,20 @@ void SelectComic::setDescription(const QString &jsonDetail)
return;
}
QVariant descriptionValues = sc.value("results").toMap().value("description");
bool valid = !descriptionValues.isNull() && descriptionValues.isValid();
detailLabel->setText(valid ? descriptionValues.toString().replace("<a", "<a style = 'color:#827A68; text-decoration:none;'") : tr("description unavailable"));
auto resultMap = sc.value("results").toMap();
QVariant descriptionValues = resultMap.value("description");
auto description = descriptionValues.toString().trimmed();
QVariant deckValues = resultMap.value("deck");
auto deck = deckValues.toString().trimmed();
bool valid = !descriptionValues.isNull() && descriptionValues.isValid() && !description.isEmpty();
bool validDeck = !deckValues.isNull() && deckValues.isValid() && !deck.isEmpty();
if (valid) {
detailLabel->setText(description.replace("<a", "<a style = 'color:#827A68; text-decoration:none;'"));
} else if (validDeck) {
detailLabel->setText(deck.replace("<a", "<a style = 'color:#827A68; text-decoration:none;'"));
} else {
detailLabel->setText(tr("comic description unavailable"));
}
}
QString SelectComic::getSelectedComicId()

View File

@ -1,7 +1,9 @@
#ifndef SELECT_COMIC_H
#define SELECT_COMIC_H
#include "scraper_selector.h"
#include <QtWidgets>
#include "scraper_results_paginator.h"
class QLabel;
class VolumeComicsModel;
@ -10,12 +12,12 @@ class QModelIndex;
class ScraperScrollLabel;
class ScraperTableView;
class SelectComic : public ScraperSelector
class SelectComic : public QWidget
{
Q_OBJECT
public:
SelectComic(QWidget *parent = nullptr);
void load(const QString &json, const QString &searchString) override;
void load(const QString &json, const QString &volumeId);
virtual ~SelectComic();
public slots:
@ -24,11 +26,20 @@ public slots:
void setDescription(const QString &jsonDetail);
QString getSelectedComicId();
signals:
void loadPage(QString, int);
private slots:
void loadNextPage();
void loadPreviousPage();
private:
QLabel *cover;
ScraperScrollLabel *detailLabel;
ScraperTableView *tableComics;
VolumeComicsModel *model;
QString currentVolumeId;
ScraperResultsPaginator *paginator;
};
#endif // SELECT_COMIC_H

View File

@ -24,8 +24,10 @@
#include "response_parser.h"
#include "scraper_results_paginator.h"
#include "selected_volume_info.h"
SelectVolume::SelectVolume(QWidget *parent)
: ScraperSelector(parent), model(0)
: QWidget(parent), model(0)
{
proxyModel = new QSortFilterProxyModel;
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
@ -61,6 +63,9 @@ SelectVolume::SelectVolume(QWidget *parent)
connect(tableVolumes->horizontalHeader(), qOverload<int, Qt::SortOrder>(&QHeaderView::sortIndicatorChanged), tableVolumes, qOverload<int, Qt::SortOrder>(&QTableView::sortByColumn));
connect(tableVolumes, &QAbstractItemView::clicked, this, &SelectVolume::loadVolumeInfo);
paginator = new ScraperResultsPaginator;
connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &SelectVolume::loadNextPage);
connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &SelectVolume::loadPreviousPage);
paginator->setCustomLabel(tr("volumes"));
top->addWidget(label);
@ -91,7 +96,7 @@ SelectVolume::SelectVolume(QWidget *parent)
setContentsMargins(0, 0, 0, 0);
}
void SelectVolume::load(const QString &json, const QString &searchString)
void SelectVolume::load(const QString &json, const VolumeSearchQuery &searchQuery)
{
auto tempM = new VolumesModel();
tempM->load(json);
@ -114,7 +119,18 @@ void SelectVolume::load(const QString &json, const QString &searchString)
tableVolumes->setColumnWidth(0, 350);
ScraperSelector::load(json, searchString);
currentSearchQuery = searchQuery;
paginator->update(json);
}
void SelectVolume::loadNextPage()
{
emit loadPage({ currentSearchQuery.volume, paginator->getCurrentPage() + 1, currentSearchQuery.exactMatch });
}
void SelectVolume::loadPreviousPage()
{
emit loadPage({ currentSearchQuery.volume, paginator->getCurrentPage() - 1, currentSearchQuery.exactMatch });
}
void SelectVolume::clearFilter()
@ -176,22 +192,29 @@ void SelectVolume::setDescription(const QString &jsonDetail)
return;
}
QVariant descriptionValues = sc.value("results").toMap().value("description");
bool valid = !descriptionValues.isNull() && descriptionValues.isValid();
detailLabel->setText(valid ? descriptionValues.toString().replace("<a", "<a style = 'color:#827A68; text-decoration:none;'") : tr("description unavailable"));
auto resultMap = sc.value("results").toMap();
QVariant descriptionValues = resultMap.value("description");
auto description = descriptionValues.toString().trimmed();
QVariant deckValues = resultMap.value("deck");
auto deck = deckValues.toString().trimmed();
bool valid = !descriptionValues.isNull() && descriptionValues.isValid() && !description.isEmpty();
bool validDeck = !deckValues.isNull() && deckValues.isValid() && !deck.isEmpty();
if (valid) {
selectedVolumeDescription = description;
detailLabel->setText(description.replace("<a", "<a style = 'color:#827A68; text-decoration:none;'"));
} else if (validDeck) {
selectedVolumeDescription = deck;
detailLabel->setText(deck.replace("<a", "<a style = 'color:#827A68; text-decoration:none;'"));
} else {
detailLabel->setText(tr("volume description unavailable"));
}
}
QString SelectVolume::getSelectedVolumeId()
SelectedVolumeInfo SelectVolume::getSelectedVolumeInfo()
{
return model->getVolumeId(proxyModel->mapToSource(tableVolumes->currentIndex()));
}
auto volumeId = model->getVolumeId(proxyModel->mapToSource(tableVolumes->currentIndex()));
auto numIssues = model->getNumIssues(proxyModel->mapToSource(tableVolumes->currentIndex()));
auto publisher = model->getPublisher(proxyModel->mapToSource(tableVolumes->currentIndex()));
int SelectVolume::getSelectedVolumeNumIssues()
{
return model->getNumIssues(proxyModel->mapToSource(tableVolumes->currentIndex()));
}
QString SelectVolume::getSelectedVolumePublisher()
{
return model->getPublisher(proxyModel->mapToSource(tableVolumes->currentIndex()));
return { volumeId, numIssues, publisher, selectedVolumeDescription };
}

View File

@ -1,7 +1,11 @@
#ifndef SELECT_VOLUME_H
#define SELECT_VOLUME_H
#include "scraper_selector.h"
#include <QtWidgets>
#include "scraper_results_paginator.h"
#include "selected_volume_info.h"
#include "volume_search_query.h"
class QLabel;
class VolumesModel;
@ -13,12 +17,12 @@ class ScraperScrollLabel;
class ScraperTableView;
class ScraperLineEdit;
class SelectVolume : public ScraperSelector
class SelectVolume : public QWidget
{
Q_OBJECT
public:
SelectVolume(QWidget *parent = nullptr);
void load(const QString &json, const QString &searchString) override;
void load(const QString &json, const VolumeSearchQuery &searchQuery);
void clearFilter();
virtual ~SelectVolume();
@ -26,9 +30,14 @@ public slots:
void loadVolumeInfo(const QModelIndex &mi);
void setCover(const QByteArray &);
void setDescription(const QString &jsonDetail);
QString getSelectedVolumeId();
int getSelectedVolumeNumIssues();
QString getSelectedVolumePublisher();
SelectedVolumeInfo getSelectedVolumeInfo();
signals:
void loadPage(VolumeSearchQuery);
private slots:
void loadNextPage();
void loadPreviousPage();
private:
QLabel *cover;
@ -37,6 +46,9 @@ private:
VolumesModel *model;
QSortFilterProxyModel *proxyModel;
ScraperLineEdit *filterEdit;
QString selectedVolumeDescription;
VolumeSearchQuery currentSearchQuery;
ScraperResultsPaginator *paginator;
};
#endif // SELECT_VOLUME_H

View File

@ -11,7 +11,7 @@
#include "volume_comics_model.h"
SortVolumeComics::SortVolumeComics(QWidget *parent)
: ScraperSelector(parent)
: QWidget(parent)
{
QString labelStylesheet = "QLabel {color:white; font-size:12px;font-family:Arial;}";
@ -55,6 +55,9 @@ SortVolumeComics::SortVolumeComics(QWidget *parent)
// connect(tableVolumeComics, SIGNAL(pressed(QModelIndex)), tableFiles, SLOT(setCurrentIndex(QModelIndex)));
// connect(tableFiles, SIGNAL(pressed(QModelIndex)), tableVolumeComics, SLOT(setCurrentIndex(QModelIndex)));
paginator = new ScraperResultsPaginator;
connect(paginator, &ScraperResultsPaginator::loadNextPage, this, &SortVolumeComics::loadNextPage);
connect(paginator, &ScraperResultsPaginator::loadPreviousPage, this, &SortVolumeComics::loadPreviousPage);
paginator->setCustomLabel(tr("issues"));
paginator->setMinimumWidth(422);
@ -77,7 +80,6 @@ SortVolumeComics::SortVolumeComics(QWidget *parent)
l->addWidget(label, 0);
l->addSpacing(5);
l->addLayout(content, 1);
l->addLayout(sortButtonsLayout, 0);
l->setContentsMargins(0, 0, 0, 0);
setLayout(l);
@ -120,7 +122,18 @@ void SortVolumeComics::setData(QList<ComicDB> &comics, const QString &json, cons
tableVolumeComics->resizeColumnToContents(0);
ScraperSelector::load(json, vID);
currentVolumeId = vID;
paginator->update(json);
}
void SortVolumeComics::loadNextPage()
{
emit loadPage(currentVolumeId, paginator->getCurrentPage() + 1);
}
void SortVolumeComics::loadPreviousPage()
{
emit loadPage(currentVolumeId, paginator->getCurrentPage() - 1);
}
void SortVolumeComics::synchronizeScroll(int pos)

View File

@ -1,13 +1,13 @@
#ifndef SORT_VOLUME_COMICS_H
#define SORT_VOLUME_COMICS_H
#include "scraper_selector.h"
#include <QtWidgets>
#include <QModelIndex>
#include <QPushButton>
#include <QPainter>
#include "comic_db.h"
#include "scraper_results_paginator.h"
class ScraperTableView;
class LocalComicListModel;
@ -63,14 +63,12 @@ private:
Appearance appearance;
};
class SortVolumeComics : public ScraperSelector
class SortVolumeComics : public QWidget
{
Q_OBJECT
public:
explicit SortVolumeComics(QWidget *parent = nullptr);
signals:
public slots:
void setData(QList<ComicDB> &comics, const QString &json, const QString &vID);
QList<QPair<ComicDB, QString>> getMatchingInfo();
@ -86,6 +84,13 @@ protected slots:
void restoreAllComics();
void showRemovedComicsSelector();
signals:
void loadPage(QString, int);
private slots:
void loadNextPage();
void loadPreviousPage();
private:
ScraperTableView *tableFiles;
ScraperTableView *tableVolumeComics;
@ -97,6 +102,9 @@ private:
ScrapperToolButton *moveDownButtonCL;
ScrapperToolButton *moveUpButtonIL;
ScrapperToolButton *moveDownButtonIL;
QString currentVolumeId;
ScraperResultsPaginator *paginator;
};
#endif // SORT_VOLUME_COMICS_H

View File

@ -1,4 +1,5 @@
#include "create_library_dialog.h"
#include "yacreader_global.h"
#include <QHBoxLayout>
#include <QVBoxLayout>
@ -6,6 +7,8 @@
#include <QSizePolicy>
#include <QMessageBox>
using namespace YACReader;
CreateLibraryDialog::CreateLibraryDialog(QWidget *parent)
: QDialog(parent)
{
@ -87,7 +90,7 @@ void CreateLibraryDialog::create()
QFileInfo f(path->text());
if (f.exists() && f.isDir() && f.isWritable()) {
if (!libraries.contains(nameEdit->text())) {
emit createLibrary(QDir::cleanPath(path->text()), QDir::cleanPath(path->text()) + "/.yacreaderlibrary", nameEdit->text());
emit createLibrary(QDir::cleanPath(path->text()), LibraryPaths::libraryDataPath(QDir::cleanPath(path->text())), nameEdit->text());
close();
} else
emit libraryExists(nameEdit->text());

View File

@ -11,6 +11,7 @@
#include "comic_db.h"
#include "db_helper.h"
#include "reading_list_model.h"
#ifdef use_unarr
#include <unarr.h>
#endif
@ -489,11 +490,10 @@ int ComicModel::rowCount(const QModelIndex &parent) const
QStringList ComicModel::getPaths(const QString &_source)
{
QStringList paths;
QString source = _source + "/.yacreaderlibrary/covers/";
QList<ComicItem *>::ConstIterator itr;
for (itr = _data.constBegin(); itr != _data.constEnd(); itr++) {
QString hash = (*itr)->data(ComicModel::Hash).toString();
paths << source + hash + ".jpg";
paths << LibraryPaths::coverPath(_source, hash);
}
return paths;
@ -1228,10 +1228,10 @@ void ComicModel::notifyCoverChange(const ComicDB &comic)
// this doesn't work in QML -> emit dataChanged(index(itemIndex, 0), index(itemIndex, 0), QVector<int>() << CoverPathRole);
}
// ????
QUrl ComicModel::getCoverUrlPathForComicHash(const QString &hash) const
{
return QUrl::fromLocalFile(_databasePath + "/covers/" + hash + ".jpg");
auto coverPath = LibraryPaths::coverPathFromLibraryDataPath(_databasePath, hash);
return QUrl::fromLocalFile(coverPath);
}
void ComicModel::addComicsToFavorites(const QList<qulonglong> &comicIds)

View File

@ -4,6 +4,7 @@
#include "initial_comic_info_extractor.h"
#include "check_new_version.h"
#include "db_helper.h"
#include "yacreader_libraries.h"
#include "QsLog.h"
@ -118,15 +119,15 @@ QSqlDatabase DataBaseManagement::createDatabase(QString dest)
return db;
}
QSqlDatabase DataBaseManagement::loadDatabase(QString path)
QSqlDatabase DataBaseManagement::loadDatabase(QString libraryDataPath)
{
if (!QFile::exists(path + "/library.ydb")) {
if (!QFile::exists(libraryDataPath + "/library.ydb")) {
return QSqlDatabase();
}
QString threadId = QString::number((long long)QThread::currentThreadId(), 16);
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", path + threadId);
db.setDatabaseName(path + "/library.ydb");
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", libraryDataPath + threadId);
db.setDatabaseName(libraryDataPath + "/library.ydb");
if (!db.open()) {
return QSqlDatabase();
}
@ -667,7 +668,8 @@ bool DataBaseManagement::importComicsInfo(QString source, QString dest)
QString basePath = QString(dest).remove("/.yacreaderlibrary/library.ydb");
QString path = basePath + getComic.record().value("path").toString();
int coverPage = getComic.record().value("coverPage").toInt();
InitialComicInfoExtractor ie(path, basePath + "/.yacreaderlibrary/covers/" + hash + ".jpg", coverPage);
auto coverPath = LibraryPaths::coverPath(basePath, hash);
InitialComicInfoExtractor ie(path, coverPath, coverPage);
ie.extract();
}
}
@ -851,7 +853,7 @@ int DataBaseManagement::compareVersions(const QString &v1, const QString v2)
return 0;
}
bool DataBaseManagement::updateToCurrentVersion(const QString &path)
bool DataBaseManagement::updateToCurrentVersion(const QString &libraryPath)
{
bool pre7 = false;
bool pre7_1 = false;
@ -861,28 +863,28 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path)
bool pre9_13 = false;
bool pre9_14 = false;
QString fullPath = path + "/library.ydb";
QString libraryDatabasePath = LibraryPaths::libraryDatabasePath(libraryPath);
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "7.0.0") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "7.0.0") < 0)
pre7 = true;
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "7.0.3") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "7.0.3") < 0)
pre7_1 = true;
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "8.0.0") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "8.0.0") < 0)
pre8 = true;
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.5.0") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.5.0") < 0)
pre9_5 = true;
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.8.0") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.8.0") < 0)
pre9_8 = true;
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.13.0") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.13.0") < 0)
pre9_13 = true;
if (compareVersions(DataBaseManagement::checkValidDB(fullPath), "9.14.0") < 0)
if (compareVersions(DataBaseManagement::checkValidDB(libraryDatabasePath), "9.14.0") < 0)
pre9_14 = true;
QString connectionName = "";
bool returnValue = true;
{
QSqlDatabase db = loadDatabaseFromFile(fullPath);
QSqlDatabase db = loadDatabaseFromFile(libraryDatabasePath);
if (db.isValid() && db.isOpen()) {
if (pre7) // TODO: execute only if previous version was < 7.0
{
@ -966,7 +968,8 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path)
QImageReader thumbnail;
while (selectQuery.next()) {
thumbnail.setFileName(path % "/covers/" % selectQuery.value(1).toString() % ".jpg");
auto coverPath = LibraryPaths::coverPath(libraryPath, selectQuery.value(1).toString());
thumbnail.setFileName(coverPath);
float coverSizeRatio = static_cast<float>(thumbnail.size().width()) / thumbnail.size().height();
updateCoverInfo.bindValue(":coverSizeRatio", coverSizeRatio);
@ -1094,6 +1097,52 @@ bool DataBaseManagement::updateToCurrentVersion(const QString &path)
return returnValue;
}
DatabaseAccess DataBaseManagement::getDatabaseAccess(const QString &libraryPath)
{
DatabaseAccess access = { false, false, false, false };
auto libraryDataPath = LibraryPaths::libraryDataPath(libraryPath);
auto libraryDatabasePath = LibraryPaths::libraryDatabasePath(libraryPath);
QFile libraryDatabase(libraryDatabasePath);
if (!libraryDatabase.exists()) {
return access;
}
access.libraryExists = true;
QDir libraryData(libraryDataPath);
QFile testFile(libraryData.filePath("test"));
if (testFile.open(QIODevice::WriteOnly)) {
access.canWriteToFolder = true;
testFile.close();
testFile.remove();
}
QString connectionName = "test";
{
QSqlDatabase db = DataBaseManagement::loadDatabaseFromFile(libraryDatabasePath);
QSqlQuery versionQuery(db);
bool read = versionQuery.exec("SELECT version FROM db_info");
read = read && versionQuery.next();
read = read && !versionQuery.record().value(0).toString().isEmpty();
access.canRead = read;
QSqlQuery writeQuery(db);
bool write = db.transaction();
write = write && writeQuery.exec("CREATE TABLE test_write (id INTEGER);");
write = write && db.rollback();
access.canWrite = write;
}
QSqlDatabase::removeDatabase(connectionName);
return access;
}
// COMICS_INFO_EXPORTER
ComicsInfoExporter::ComicsInfoExporter()
: QThread()

View File

@ -29,6 +29,27 @@ private:
void run() override;
};
struct DatabaseAccess {
bool libraryExists;
bool canRead; // db read
bool canWrite; // db write
bool canWriteToFolder; // disk write
operator QString() const
{
if (libraryExists && canRead && canWrite && canWriteToFolder) {
return "OK";
} else if (!libraryExists) {
return "WARNING! Library does not exist on disk";
} else {
return QString("WARNING! DB read access: %1, DB write access: %2, can write to disk: %3")
.arg(canRead ? "YES" : "NO")
.arg(canWrite ? "YES" : "NO")
.arg(canWriteToFolder ? "YES" : "NO");
}
}
};
class DataBaseManagement : public QObject
{
Q_OBJECT
@ -46,7 +67,7 @@ public:
static QSqlDatabase createDatabase(QString name, QString path);
static QSqlDatabase createDatabase(QString dest);
// carga una base de datos desde la ruta path
static QSqlDatabase loadDatabase(QString path);
static QSqlDatabase loadDatabase(QString libraryDataPath);
static QSqlDatabase loadDatabaseFromFile(QString path);
static bool createTables(QSqlDatabase &database);
static bool createComicInfoTable(QSqlDatabase &database, QString tableName);
@ -57,7 +78,9 @@ public:
static QString checkValidDB(const QString &fullPath); // retorna "" si la DB es inválida ó la versión si es válida.
static int compareVersions(const QString &v1, const QString v2); // retorna <0 si v1 < v2, 0 si v1 = v2 y >0 si v1 > v2
static bool updateToCurrentVersion(const QString &path);
static bool updateToCurrentVersion(const QString &libraryPath);
static DatabaseAccess getDatabaseAccess(const QString &libraryPath);
};
#endif

View File

@ -13,6 +13,8 @@
#include <algorithm>
using namespace YACReader;
#ifdef Y_MAC_UI
#include <QFileIconProvider>
QIcon finishedFolderIcon;
@ -368,8 +370,12 @@ QVariant FolderModel::data(const QModelIndex &index, int role) const
if (role == FolderModel::IdRole)
return item->id;
if (role == FolderModel::CoverPathRole)
return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString());
if (role == FolderModel::CoverPathRole) {
if (item->data(FolderModel::CustomImage).toString().isEmpty())
return getCoverUrlPathForComicHash(item->data(FirstChildHash).toString());
else
return getCoverUrlPathForFolderId(item->id);
}
if (role == FolderModel::NumChildrenRole)
return item->data(NumChildren);
@ -673,6 +679,50 @@ void FolderModel::updateTreeType(YACReader::FileType type)
QSqlDatabase::removeDatabase(connectionName);
}
void FolderModel::setCustomFolderCover(const QModelIndex &index, const QString &path)
{
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
db.transaction();
auto item = static_cast<FolderItem *>(index.internalPointer());
item->setData(FolderModel::CustomImage, path);
Folder f = DBHelper::loadFolder(item->id, db);
f.customImage = path;
DBHelper::update(f, db);
db.commit();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
emit dataChanged(index, index);
}
void FolderModel::resetFolderCover(const QModelIndex &index)
{
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(_databasePath);
db.transaction();
auto item = static_cast<FolderItem *>(index.internalPointer());
item->setData(FolderModel::CustomImage, "");
Folder f = DBHelper::loadFolder(item->id, db);
f.customImage = "";
DBHelper::update(f, db);
db.commit();
connectionName = db.connectionName();
}
QSqlDatabase::removeDatabase(connectionName);
emit dataChanged(index, index);
}
QStringList FolderModel::getSubfoldersNames(const QModelIndex &mi)
{
QStringList result;
@ -852,7 +902,14 @@ QModelIndex FolderModel::addFolderAtParent(const QString &folderName, const QMod
QUrl FolderModel::getCoverUrlPathForComicHash(const QString &hash) const
{
return QUrl::fromLocalFile(_databasePath + "/covers/" + hash + ".jpg");
auto coverPath = LibraryPaths::coverPathFromLibraryDataPath(_databasePath, hash);
return QUrl::fromLocalFile(coverPath);
}
QUrl FolderModel::getCoverUrlPathForFolderId(qulonglong folderId) const
{
auto coverPath = LibraryPaths::customFolderCoverPathFromDataPath(_databasePath, QString::number(folderId));
return QUrl::fromLocalFile(coverPath);
}
void FolderModel::setShowRecent(bool showRecent)

View File

@ -70,6 +70,8 @@ public:
void updateFolderFinishedStatus(const QModelIndexList &list, bool status);
void updateFolderType(const QModelIndexList &list, YACReader::FileType type);
void updateTreeType(YACReader::FileType type);
void setCustomFolderCover(const QModelIndex &index, const QString &path);
void resetFolderCover(const QModelIndex &index);
QStringList getSubfoldersNames(const QModelIndex &mi);
FolderModel *getSubfoldersModel(const QModelIndex &mi); // it creates a model that contains just the direct subfolders
@ -81,6 +83,7 @@ public:
QModelIndex addFolderAtParent(const QString &folderName, const QModelIndex &parent);
Q_INVOKABLE QUrl getCoverUrlPathForComicHash(const QString &hash) const;
Q_INVOKABLE QUrl getCoverUrlPathForFolderId(qulonglong folderId) const;
void setShowRecent(bool showRecent);
void setRecentRange(int days);

View File

@ -21,10 +21,14 @@
#include "data_base_management.h"
#include "folder.h"
#include "yacreader_libraries.h"
#include "yacreader_global.h"
#include "qnaturalsorting.h"
#include "QsLog.h"
using namespace YACReader;
// server
YACReaderLibraries DBHelper::getLibraries()
@ -40,7 +44,7 @@ QList<LibraryItem *> DBHelper::getFolderSubfoldersFromLibrary(qulonglong library
QString connectionName = "";
QList<LibraryItem *> list;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
list = DBHelper::getFoldersFromParent(folderId, db, false);
connectionName = db.connectionName();
@ -82,7 +86,7 @@ QList<LibraryItem *> DBHelper::getFolderComicsFromLibrary(qulonglong libraryId,
QString connectionName = "";
QList<LibraryItem *> list;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
list = DBHelper::getComicsFromParent(folderId, db, sort);
connectionName = db.connectionName();
@ -98,7 +102,7 @@ quint32 DBHelper::getNumChildrenFromFolder(qulonglong libraryId, qulonglong fold
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery(db);
selectQuery.prepare("SELECT count(*) FROM folder WHERE parentId = :parentId and id <> 1");
@ -126,7 +130,7 @@ qulonglong DBHelper::getParentFromComicFolderId(qulonglong libraryId, qulonglong
QString connectionName = "";
Folder f;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
f = DBHelper::loadFolder(id, db);
connectionName = db.connectionName();
@ -141,7 +145,7 @@ ComicDB DBHelper::getComicInfo(qulonglong libraryId, qulonglong id)
QString connectionName = "";
ComicDB comic;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
bool found;
comic = DBHelper::loadComic(id, db, found);
@ -157,7 +161,7 @@ QList<ComicDB> DBHelper::getSiblings(qulonglong libraryId, qulonglong parentId)
QString connectionName = "";
QList<ComicDB> comics;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
comics = DBHelper::getSortedComicsFromParent(parentId, db);
connectionName = db.connectionName();
}
@ -174,7 +178,7 @@ QString DBHelper::getFolderName(qulonglong libraryId, qulonglong id)
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery(db); // TODO check
selectQuery.prepare("SELECT name FROM folder WHERE id = :id");
selectQuery.bindValue(":id", id);
@ -198,7 +202,7 @@ Folder DBHelper::getFolder(qulonglong libraryId, qulonglong id)
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery(db); // TODO check
selectQuery.prepare("SELECT * FROM folder WHERE id = :id");
selectQuery.bindValue(":id", id);
@ -259,7 +263,7 @@ QList<ComicDB> DBHelper::getLabelComics(qulonglong libraryId, qulonglong labelId
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery(db);
selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio "
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
@ -300,7 +304,7 @@ QList<ComicDB> DBHelper::getFavorites(qulonglong libraryId)
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery(db);
selectQuery.prepare("SELECT c.id,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio "
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
@ -341,7 +345,7 @@ QList<ComicDB> DBHelper::getReading(qulonglong libraryId)
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery(db);
selectQuery.prepare("SELECT c.id,c.parentId,c.fileName,ci.title,ci.currentPage,ci.numPages,ci.hash,ci.read,ci.coverSizeRatio,ci.number "
"FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) "
@ -381,7 +385,7 @@ QList<ReadingList> DBHelper::getReadingLists(qulonglong libraryId)
QList<ReadingList> list;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery("SELECT * from reading_list WHERE parentId IS NULL ORDER BY name DESC", db);
@ -420,7 +424,7 @@ QList<ComicDB> DBHelper::getReadingListFullContent(qulonglong libraryId, qulongl
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QList<qulonglong> ids;
ids << readingListId;
@ -618,7 +622,7 @@ void DBHelper::update(qulonglong libraryId, ComicInfo &comicInfo)
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
DBHelper::update(&comicInfo, db);
connectionName = db.connectionName();
}
@ -820,13 +824,34 @@ void DBHelper::updateAdded(ComicInfo *comicInfo, QSqlDatabase &db)
void DBHelper::update(const Folder &folder, QSqlDatabase &db)
{
QSqlQuery updateFolderInfo(db);
updateFolderInfo.prepare("UPDATE folder SET "
"parentId = :parentId, "
"name = :name, "
"path = :path, "
"finished = :finished, "
"completed = :completed "
"WHERE id = :id ");
"completed = :completed, "
"numChildren = :numChildren, "
"firstChildHash = :firstChildHash, "
"customImage = :customImage, "
"type = :type, "
"added = :added, "
"updated = :updated "
"WHERE id = :id");
updateFolderInfo.bindValue(":parentId", folder.parentId);
updateFolderInfo.bindValue(":name", folder.name);
updateFolderInfo.bindValue(":path", folder.path);
updateFolderInfo.bindValue(":finished", folder.finished ? 1 : 0);
updateFolderInfo.bindValue(":completed", folder.completed ? 1 : 0);
updateFolderInfo.bindValue(":numChildren", folder.numChildren);
updateFolderInfo.bindValue(":firstChildHash", folder.firstChildHash);
updateFolderInfo.bindValue(":customImage", folder.customImage);
updateFolderInfo.bindValue(":type", static_cast<int>(folder.type));
updateFolderInfo.bindValue(":added", folder.added);
updateFolderInfo.bindValue(":updated", folder.updated);
updateFolderInfo.bindValue(":id", folder.id);
updateFolderInfo.exec();
}
@ -872,6 +897,10 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db)
}
}
if (folder.numChildren == subfolders.count() + comics.count() && folder.firstChildHash == coverHash) {
return folder;
}
folder.numChildren = subfolders.count() + comics.count();
folder.firstChildHash = coverHash;
@ -894,6 +923,9 @@ Folder DBHelper::updateChildrenInfo(qulonglong folderId, QSqlDatabase &db)
void DBHelper::updateChildrenInfo(QSqlDatabase &db)
{
// measure time
auto start = std::chrono::high_resolution_clock::now();
QSqlQuery selectQuery(db); // TODO check
selectQuery.prepare("SELECT id FROM folder f WHERE f.parentId = 1 AND f.id <> 1");
selectQuery.exec();
@ -901,6 +933,12 @@ void DBHelper::updateChildrenInfo(QSqlDatabase &db)
while (selectQuery.next()) {
DBHelper::updateChildrenInfo(selectQuery.value(0).toULongLong(), db);
}
auto end = std::chrono::high_resolution_clock::now();
QString time = QString::number(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count());
QString message = "updateChildrenInfo took " + time + "ms";
QLOG_INFO() << message;
}
void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo)
@ -908,7 +946,7 @@ void DBHelper::updateProgress(qulonglong libraryId, const ComicInfo &comicInfo)
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
bool found;
ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found);
@ -929,7 +967,7 @@ void DBHelper::setComicAsReading(qulonglong libraryId, const ComicInfo &comicInf
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
bool found;
ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found);
@ -970,7 +1008,7 @@ void DBHelper::updateFromRemoteClient(qulonglong libraryId, const ComicInfo &com
QString libraryPath = DBHelper::getLibraries().getPath(libraryId);
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
bool found;
ComicDB comic = DBHelper::loadComic(comicInfo.id, db, found);
@ -1009,7 +1047,7 @@ QMap<qulonglong, QList<ComicDB>> DBHelper::updateFromRemoteClient(const QMap<qul
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
db.transaction();
@ -1100,7 +1138,7 @@ void DBHelper::updateFromRemoteClientWithHash(const QList<ComicInfo> &comics)
QString libraryPath = DBHelper::getLibraries().getPath(libraries.getId(name));
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
db.transaction();
@ -1595,7 +1633,7 @@ QList<Label> DBHelper::getLabels(qulonglong libraryId)
QString connectionName = "";
QList<Label> labels;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
QSqlQuery selectQuery("SELECT * FROM label ORDER BY ordering,name", db); // TODO add some kind of
QSqlRecord record = selectQuery.record();
@ -2033,7 +2071,7 @@ QString DBHelper::getLibraryInfo(QUuid id)
QString connectionName = "";
QList<LibraryItem *> list;
{
QSqlDatabase db = DataBaseManagement::loadDatabase(libraryPath + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(libraryPath));
connectionName = db.connectionName();
// num folders

View File

@ -67,7 +67,7 @@ public:
static void update(ComicInfo *comicInfo, QSqlDatabase &db);
static void updateRead(ComicInfo *comicInfo, QSqlDatabase &db);
static void updateAdded(ComicInfo *comicInfo, QSqlDatabase &db);
static void update(const Folder &folder, QSqlDatabase &db); // only for finished/completed fields
static void update(const Folder &folder, QSqlDatabase &db);
static void propagateFolderUpdatesToParent(const Folder &folder, QSqlDatabase &db);
static Folder updateChildrenInfo(qulonglong folderId, QSqlDatabase &db);
static void updateChildrenInfo(QSqlDatabase &db);

View File

@ -10,6 +10,7 @@
<file>../images/comic_vine/rowDown.png</file>
<file>../images/comic_vine/rowUp.png</file>
<file>../images/comic_vine/upArrow.png</file>
<file>../images/comic_vine/checkBoxTick.svg</file>
<file>../images/comics_view_toolbar/asignNumber.svg</file>
<file>../images/comics_view_toolbar/big_size_grid_zoom.svg</file>
<file>../images/comics_view_toolbar/editComic.svg</file>
@ -42,6 +43,7 @@
<file>../images/flow4.png</file>
<file>../images/flow5.png</file>
<file>../images/glowLine.png</file>
<file>../images/loadCustomCover.svg</file>
<file>../images/hiddenCovers.png</file>
<file>../images/icon-new.svg</file>
<file>../images/iconLibrary.png</file>
@ -69,6 +71,7 @@
<file>../images/previousCoverPage.png</file>
<file>../images/readingRibbon.png</file>
<file>../images/readRibbon.png</file>
<file>../images/resetCover.svg</file>
<file>../images/searching_icon.png</file>
<file>../images/serverConfigBackground.png</file>
<file>../images/shortcuts_group_comics.svg</file>

View File

@ -5,6 +5,7 @@
#include "comic.h"
#include "compressed_archive.h"
#include "qnaturalsorting.h"
#include "cover_utils.h"
using namespace YACReader;
@ -154,17 +155,5 @@ QByteArray InitialComicInfoExtractor::getXMLInfoRawData()
void InitialComicInfoExtractor::saveCover(const QString &path, const QImage &cover)
{
QImage scaled;
if (cover.width() > cover.height()) {
scaled = cover.scaledToWidth(640, Qt::SmoothTransformation);
} else {
auto aspectRatio = static_cast<double>(cover.width()) / static_cast<double>(cover.height());
auto maxAllowedAspectRatio = 0.5;
if (aspectRatio < maxAllowedAspectRatio) { // cover is too tall, e.g. webtoon
scaled = cover.scaledToHeight(960, Qt::SmoothTransformation);
} else {
scaled = cover.scaledToWidth(480, Qt::SmoothTransformation);
}
}
scaled.save(_target, 0, 75);
YACReader::saveCover(path, cover);
}

View File

@ -5,6 +5,8 @@
#include "yacreader_libraries.h"
#include "yacreader_global.h"
using namespace YACReader;
LibrariesUpdateCoordinator::LibrariesUpdateCoordinator(QSettings *settings, YACReaderLibraries &libraries, const std::function<bool()> &canStartUpdateProvider, QObject *parent)
: QObject(parent), libraries(libraries), canStartUpdateProvider(canStartUpdateProvider)
{
@ -121,7 +123,7 @@ void LibrariesUpdateCoordinator::updateLibrary(const QString &path)
QString cleanPath = QDir::cleanPath(pathDir.absolutePath());
libraryCreator->updateLibrary(cleanPath, QDir::cleanPath(pathDir.absolutePath() + "/.yacreaderlibrary"));
libraryCreator->updateLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath));
connect(libraryCreator, &LibraryCreator::finished, &eventLoop, &QEventLoop::quit);

View File

@ -47,15 +47,45 @@ bool YACReader::openComic(const ComicDB &comic,
return yacreaderFound;
}
QStringList parseCommand(const QString &input)
{
QRegularExpression regex(R"((?:\"([^\"]*)\")|(\S+))");
QStringList result;
auto it = regex.globalMatch(input);
while (it.hasNext()) {
QRegularExpressionMatch match = it.next();
if (match.hasMatch()) {
result << (match.captured(1).isEmpty() ? match.captured(2) : match.captured(1));
}
}
return result;
}
bool YACReader::openComicInThirdPartyApp(const QString &command, const QString &path)
{
QString mutableCommand = command;
QString fullCommand;
if (mutableCommand.contains("{comic_file_path}")) {
fullCommand = mutableCommand.replace("{comic_file_path}", "\"" + path + "\"");
} else {
fullCommand = mutableCommand + " \"" + path + "\"";
QStringList parsed = parseCommand(command);
if (parsed.isEmpty()) {
qDebug() << "Empty command";
return false;
}
return QProcess::startDetached(fullCommand, {});
QString program = parsed.takeFirst();
QStringList rawArguments = parsed;
QStringList arguments;
auto placeholderFound = false;
for (auto argument : rawArguments) {
if (argument.contains("{comic_file_path}")) {
placeholderFound = true;
arguments << argument.replace("{comic_file_path}", path);
} else {
arguments << argument;
}
}
if (!placeholderFound) {
arguments << path;
}
return QProcess::startDetached(program, arguments);
}

View File

@ -154,7 +154,7 @@ void LibraryCreator::run()
_currentPathFolders.clear();
// se crean los directorios .yacreaderlibrary y .yacreaderlibrary/covers
QDir dir;
dir.mkpath(_target + "/covers");
dir.mkpath(LibraryPaths::libraryCoversPathFromLibraryDataPath(_target));
// se crea la base de datos .yacreaderlibrary/library.ydb
{
@ -269,7 +269,8 @@ void LibraryCreator::cancel()
void LibraryCreator::cleanup(QSqlDatabase &db, const QString &target)
{
QDir coversDir(target + "/covers/");
auto coversPath = LibraryPaths::libraryCoversPathFromLibraryDataPath(target);
QDir coversDir(coversPath);
if (!coversDir.exists()) {
return;
}
@ -285,10 +286,8 @@ void LibraryCreator::cleanup(QSqlDatabase &db, const QString &target)
while (infoToDeleteQuery.next()) {
QString hash = infoToDeleteQuery.value(1).toString();
QString cover = hash + ".jpg";
auto fullPath = coversDir.absoluteFilePath(cover);
QFile::remove(fullPath);
QString coverPath = LibraryPaths::coverPathFromLibraryDataPath(target, hash);
QFile::remove(coverPath);
}
QSqlQuery deleteQuery(db);
@ -358,7 +357,7 @@ void LibraryCreator::create(QDir dir)
bool LibraryCreator::checkCover(const QString &hash)
{
return QFile::exists(_target + "/covers/" + hash + ".jpg");
return QFile::exists(LibraryPaths::coverPathFromLibraryDataPath(_target, hash));
}
QString pseudoHash(const QFileInfo &fileInfo)
@ -383,14 +382,15 @@ void LibraryCreator::insertComic(const QString &relativePath, const QFileInfo &f
QPair<int, int> originalCoverSize = { 0, 0 };
bool exists = checkCover(hash);
YACReader::InitialComicInfoExtractor ie(QDir::cleanPath(fileInfo.absoluteFilePath()), _target + "/covers/" + hash + ".jpg", comic.info.coverPage.toInt(), settings->value(IMPORT_COMIC_INFO_XML_METADATA, false).toBool());
auto coverPath = LibraryPaths::coverPathFromLibraryDataPath(_target, hash);
YACReader::InitialComicInfoExtractor ie(QDir::cleanPath(fileInfo.absoluteFilePath()), coverPath, comic.info.coverPage.toInt(), settings->value(IMPORT_COMIC_INFO_XML_METADATA, false).toBool());
if (!(comic.hasCover() && exists)) {
ie.extract();
numPages = ie.getNumPages();
originalCoverSize = ie.getOriginalCoverSize();
if (numPages > 0) {
emit comicAdded(relativePath, _target + "/covers/" + hash + ".jpg");
emit comicAdded(relativePath, coverPath);
}
}

View File

@ -1,6 +1,7 @@
#include "library_window.h"
#include "yacreader_global.h"
#include "yacreader_global_gui.h"
#include <QHBoxLayout>
#include <QSplitter>
@ -83,12 +84,15 @@
#include "recent_visibility_coordinator.h"
#include "cover_utils.h"
#include "QsLog.h"
#include "yacreader_http_server.h"
extern YACReaderHttpServer *httpServer;
#ifdef Q_OS_WIN
#include <windows.h>
#include <shellapi.h>
#endif
@ -535,6 +539,10 @@ void LibraryWindow::createMenus()
foldersView->addAction(actions.setFolderAsWesternMangaAction);
foldersView->addAction(actions.setFolderAsWebComicAction);
foldersView->addAction(actions.setFolderAsYonkomaAction);
YACReader::addSperator(foldersView);
foldersView->addAction(actions.setFolderCoverAction);
foldersView->addAction(actions.deleteCustomFolderCoverAction);
selectedLibrary->addAction(actions.updateLibraryAction);
selectedLibrary->addAction(actions.renameLibraryAction);
@ -668,11 +676,14 @@ void LibraryWindow::createMenus()
folderMenu->addAction(actions.setFolderAsReadAction);
folderMenu->addAction(actions.setFolderAsUnreadAction);
folderMenu->addSeparator();
foldersView->addAction(actions.setFolderAsNormalAction);
foldersView->addAction(actions.setFolderAsMangaAction);
foldersView->addAction(actions.setFolderAsWesternMangaAction);
foldersView->addAction(actions.setFolderAsWebComicAction);
foldersView->addAction(actions.setFolderAsYonkomaAction);
folderMenu->addAction(actions.setFolderAsNormalAction);
folderMenu->addAction(actions.setFolderAsMangaAction);
folderMenu->addAction(actions.setFolderAsWesternMangaAction);
folderMenu->addAction(actions.setFolderAsWebComicAction);
folderMenu->addAction(actions.setFolderAsYonkomaAction);
folderMenu->addSeparator();
folderMenu->addAction(actions.setFolderCoverAction);
folderMenu->addAction(actions.deleteCustomFolderCoverAction);
// comic
QMenu *comicMenu = new QMenu(tr("Comic"));
@ -769,19 +780,23 @@ void LibraryWindow::createConnections()
connect(optionsDialog, &YACReaderOptionsDialog::optionsChanged, this, &LibraryWindow::reloadOptions);
connect(optionsDialog, &YACReaderOptionsDialog::editShortcuts, editShortcutsDialog, &QWidget::show);
auto searchDebouncer = new KDToolBox::KDSignalDebouncer(this);
auto searchDebouncer = new KDToolBox::KDStringSignalDebouncer(this);
searchDebouncer->setTimeout(400);
// Search filter
#ifdef Y_MAC_UI
connect(searchEdit, &YACReaderMacOSXSearchLineEdit::filterChanged, searchDebouncer, &KDToolBox::KDSignalThrottler::throttle);
connect(searchDebouncer, &KDToolBox::KDSignalThrottler::triggered, this, [=] {
setSearchFilter(searchEdit->text());
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
connect(libraryToolBar, &YACReaderMacOSXToolbar::filterChanged, searchDebouncer, &KDToolBox::KDStringSignalDebouncer::throttle);
#else
connect(searchEdit, &YACReaderMacOSXSearchLineEdit::filterChanged, searchDebouncer, &KDToolBox::KDStringSignalDebouncer::throttle);
#endif
connect(searchDebouncer, &KDToolBox::KDStringSignalDebouncer::triggered, this, [=](QString filter) {
setSearchFilter(filter);
});
#else
connect(searchEdit, &YACReaderSearchLineEdit::filterChanged, searchDebouncer, &KDToolBox::KDSignalThrottler::throttle);
connect(searchDebouncer, &KDToolBox::KDSignalThrottler::triggered, this, [=] {
setSearchFilter(searchEdit->text());
connect(searchEdit, &YACReaderSearchLineEdit::filterChanged, searchDebouncer, &KDToolBox::KDStringSignalDebouncer::throttle);
connect(searchDebouncer, &KDToolBox::KDStringSignalDebouncer::triggered, this, [=](QString filter) {
setSearchFilter(filter);
});
#endif
connect(&comicQueryResultProcessor, &ComicQueryResultProcessor::newData, this, &LibraryWindow::setComicSearchFilterData);
@ -816,11 +831,17 @@ void LibraryWindow::loadLibrary(const QString &name)
historyController->clear();
showRootWidget();
QString path = libraries.getPath(name) + "/.yacreaderlibrary";
QString rootPath = libraries.getPath(name);
QString path = LibraryPaths::libraryDataPath(rootPath);
QString customFolderCoversPath = LibraryPaths::libraryCustomFoldersCoverPath(rootPath);
QString databasePath = LibraryPaths::libraryDatabasePath(rootPath);
QDir d; // TODO change this by static methods (utils class?? with delTree for example)
QString dbVersion;
if (d.exists(path) && d.exists(path + "/library.ydb") && (dbVersion = DataBaseManagement::checkValidDB(path + "/library.ydb")) != "") // si existe en disco la biblioteca seleccionada, y es válida..
if (d.exists(path) && d.exists(databasePath) && (dbVersion = DataBaseManagement::checkValidDB(databasePath)) != "") // si existe en disco la biblioteca seleccionada, y es válida..
{
// this folde was added in 9.16, it needs to exist before the user starts importing custom covers for folders
d.mkdir(customFolderCoversPath);
int comparation = DataBaseManagement::compareVersions(dbVersion, DB_VERSION);
if (comparation < 0) {
@ -829,8 +850,8 @@ void LibraryWindow::loadLibrary(const QString &name)
importWidget->setUpgradeLook();
showImportingWidget();
upgradeLibraryFuture = std::async(std::launch::async, [this, name, path] {
bool updated = DataBaseManagement::updateToCurrentVersion(path);
upgradeLibraryFuture = std::async(std::launch::async, [this, name, path, rootPath] {
bool updated = DataBaseManagement::updateToCurrentVersion(rootPath);
if (!updated)
emit errorUpgradingLibrary(path);
@ -932,11 +953,9 @@ void LibraryWindow::loadLibrary(const QString &name)
QString currentLibrary = selectedLibrary->currentText();
QString path = libraries.getPath(selectedLibrary->currentText());
if (QMessageBox::question(this, tr("Old library"), tr("Library '%1' has been created with an older version of YACReaderLibrary. It must be created again. Do you want to create the library now?").arg(currentLibrary), QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) {
QDir d(path + "/.yacreaderlibrary");
QDir d(LibraryPaths::libraryDataPath(path));
d.removeRecursively();
// d.rmdir(path+"/.yacreaderlibrary");
createLibraryDialog->setDataAndStart(currentLibrary, path);
// create(path,path+"/.yacreaderlibrary",currentLibrary);
}
// será possible renombrar y borrar estas bibliotecas
actions.renameLibraryAction->setEnabled(true);
@ -1070,9 +1089,9 @@ void LibraryWindow::updateFolder(const QModelIndex &miFolder)
showImportingWidget();
QString currentLibrary = selectedLibrary->currentText();
QString path = libraries.getPath(currentLibrary);
QString path = QDir::cleanPath(libraries.getPath(currentLibrary));
_lastAdded = currentLibrary;
libraryCreator->updateFolder(QDir::cleanPath(path), QDir::cleanPath(path + "/.yacreaderlibrary"), QDir::cleanPath(currentPath() + foldersModel->getFolderPath(miFolder)), miFolder);
libraryCreator->updateFolder(path, LibraryPaths::libraryDataPath(path), QDir::cleanPath(currentPath() + foldersModel->getFolderPath(miFolder)), miFolder);
libraryCreator->start();
}
@ -1436,6 +1455,12 @@ void LibraryWindow::showGridFoldersContextMenu(QPoint point, Folder folder)
auto setFolderAs4KomaAction = new QAction();
setFolderAs4KomaAction->setText(tr("4koma (top to botom)"));
auto setFolderCoverAction = new QAction();
setFolderCoverAction->setText(tr("Set custom cover"));
auto deleteCustomFolderCoverAction = new QAction();
deleteCustomFolderCoverAction->setText(tr("Delete custom cover"));
menu.addAction(openContainingFolderAction);
menu.addAction(updateFolderAction);
menu.addSeparator();
@ -1531,6 +1556,20 @@ void LibraryWindow::showGridFoldersContextMenu(QPoint point, Folder folder)
foldersModel->updateFolderType(QModelIndexList() << foldersModel->getIndexFromFolder(folder), FileType::Yonkoma);
subfolderModel->updateFolderType(QModelIndexList() << foldersModel->getIndexFromFolder(folder), FileType::Yonkoma);
});
connect(setFolderCoverAction, &QAction::triggered, this, [=]() {
setCustomFolderCover(folder);
});
connect(deleteCustomFolderCoverAction, &QAction::triggered, this, [=]() {
resetFolderCover(folder);
});
menu.addSeparator();
menu.addAction(setFolderCoverAction);
if (!folder.customImage.isEmpty()) {
menu.addAction(deleteCustomFolderCoverAction);
}
menu.exec(contentViewsManager->folderContentView->mapToGlobal(point));
}
@ -1773,7 +1812,8 @@ void LibraryWindow::openLibrary(QString path, QString name)
// TODO: fix bug, /a/b/c/.yacreaderlibrary/d/e
path.remove("/.yacreaderlibrary");
QDir d; // TODO change this by static methods (utils class?? with delTree for example)
if (d.exists(path + "/.yacreaderlibrary")) {
auto libraryDataPath = LibraryPaths::libraryDataPath(path);
if (d.exists(libraryDataPath)) {
_lastAdded = name;
_sourceLastAdded = path;
openLastCreated();
@ -1805,7 +1845,7 @@ void LibraryWindow::updateLibrary()
QString currentLibrary = selectedLibrary->currentText();
QString path = libraries.getPath(currentLibrary);
_lastAdded = currentLibrary;
libraryCreator->updateLibrary(path, path + "/.yacreaderlibrary");
libraryCreator->updateLibrary(path, LibraryPaths::libraryDataPath(path));
libraryCreator->start();
}
@ -1814,8 +1854,7 @@ void LibraryWindow::deleteCurrentLibrary()
QString path = libraries.getPath(selectedLibrary->currentText());
libraries.remove(selectedLibrary->currentText());
selectedLibrary->removeItem(selectedLibrary->currentIndex());
// selectedLibrary->setCurrentIndex(0);
path = path + "/.yacreaderlibrary";
path = LibraryPaths::libraryDatabasePath(path);
QDir d(path);
d.removeRecursively();
@ -1895,7 +1934,7 @@ void LibraryWindow::rescanLibraryForXMLInfo()
QString path = libraries.getPath(currentLibrary);
_lastAdded = currentLibrary;
xmlInfoLibraryScanner->scanLibrary(path, path + "/.yacreaderlibrary");
xmlInfoLibraryScanner->scanLibrary(path, LibraryPaths::libraryDataPath(path));
}
void LibraryWindow::showLibraryInfo()
@ -1929,7 +1968,7 @@ void LibraryWindow::rescanFolderForXMLInfo(QModelIndex modelIndex)
QString path = libraries.getPath(currentLibrary);
_lastAdded = currentLibrary;
xmlInfoLibraryScanner->scanFolder(path, path + "/.yacreaderlibrary", QDir::cleanPath(currentPath() + foldersModel->getFolderPath(modelIndex)), modelIndex);
xmlInfoLibraryScanner->scanFolder(path, LibraryPaths::libraryDataPath(path), QDir::cleanPath(currentPath() + foldersModel->getFolderPath(modelIndex)), modelIndex);
}
void LibraryWindow::cancelCreating()
@ -1952,7 +1991,7 @@ void LibraryWindow::stopXMLScanning()
void LibraryWindow::setRootIndex()
{
if (!libraries.isEmpty()) {
QString path = libraries.getPath(selectedLibrary->currentText()) + "/.yacreaderlibrary";
QString path = LibraryPaths::libraryDataPath(libraries.getPath(selectedLibrary->currentText()));
QDir d; // TODO change this by static methods (utils class?? with delTree for example)
if (d.exists(path)) {
navigationController->selectedFolder(QModelIndex());
@ -2267,10 +2306,54 @@ void LibraryWindow::setFolderType(FileType type)
foldersModel->updateFolderType(QModelIndexList() << foldersModelProxy->mapToSource(foldersView->currentIndex()), type);
}
void LibraryWindow::setFolderCover()
{
auto folder = foldersModel->getFolder(foldersModelProxy->mapToSource(foldersView->currentIndex()));
setCustomFolderCover(folder);
}
void LibraryWindow::setCustomFolderCover(Folder folder)
{
auto customCoverPath = YACReader::imageFileLoader(this);
if (!customCoverPath.isEmpty()) {
QImage cover(customCoverPath);
if (cover.isNull()) {
QMessageBox::warning(this, tr("Invalid image"), tr("The selected file is not a valid image."));
return;
}
auto folderCoverPath = LibraryPaths::customFolderCoverPath(libraries.getPath(selectedLibrary->currentText()), QString::number(folder.id));
if (!YACReader::saveCover(folderCoverPath, cover)) {
QMessageBox::warning(this, tr("Error saving cover"), tr("There was an error saving the cover image."));
}
QModelIndex folderIndex = foldersModel->getIndexFromFolder(folder);
auto coversPath = LibraryPaths::libraryCoversFolderPath(libraries.getPath(selectedLibrary->currentText()));
auto relativePath = folderCoverPath.remove(coversPath);
foldersModel->setCustomFolderCover(folderIndex, relativePath);
}
}
void LibraryWindow::deleteCustomFolderCover()
{
auto folder = foldersModel->getFolder(foldersModelProxy->mapToSource(foldersView->currentIndex()));
resetFolderCover(folder);
}
void LibraryWindow::resetFolderCover(Folder folder)
{
auto folderCoverPath = LibraryPaths::customFolderCoverPath(libraries.getPath(selectedLibrary->currentText()), QString::number(folder.id));
if (QFile::exists(folderCoverPath)) {
QFile::remove(folderCoverPath);
}
QModelIndex folderIndex = foldersModel->getIndexFromFolder(folder);
foldersModel->resetFolderCover(folderIndex);
}
void LibraryWindow::exportLibrary(QString destPath)
{
QString currentLibrary = selectedLibrary->currentText();
QString path = libraries.getPath(currentLibrary) + "/.yacreaderlibrary";
QString path = LibraryPaths::libraryDataPath(libraries.getPath(currentLibrary));
packageManager->createPackage(path, destPath + "/" + currentLibrary);
}
@ -2311,13 +2394,13 @@ QString LibraryWindow::currentFolderPath()
void LibraryWindow::showExportComicsInfo()
{
exportComicsInfoDialog->source = currentPath() + "/.yacreaderlibrary/library.ydb";
exportComicsInfoDialog->source = LibraryPaths::libraryDatabasePath(currentPath());
exportComicsInfoDialog->open();
}
void LibraryWindow::showImportComicsInfo()
{
importComicsInfoDialog->dest = currentPath() + "/.yacreaderlibrary/library.ydb";
importComicsInfoDialog->dest = currentPath() + LibraryPaths::libraryDatabasePath(currentPath());
importComicsInfoDialog->open();
}
@ -2508,9 +2591,7 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point)
{
QModelIndex sourceMI = foldersModelProxy->mapToSource(foldersView->indexAt(point));
bool isCompleted = sourceMI.data(FolderModel::CompletedRole).toBool();
bool isRead = sourceMI.data(FolderModel::FinishedRole).toBool();
auto type = sourceMI.data(FolderModel::TypeRole).value<YACReader::FileType>();
auto folder = foldersModel->getFolder(sourceMI);
actions.setFolderAsNormalAction->setCheckable(true);
actions.setFolderAsMangaAction->setCheckable(true);
@ -2524,7 +2605,7 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point)
actions.setFolderAsWebComicAction->setChecked(false);
actions.setFolderAsYonkomaAction->setChecked(false);
switch (type) {
switch (folder.type) {
case FileType::Comic:
actions.setFolderAsNormalAction->setChecked(true);
break;
@ -2549,12 +2630,12 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point)
menu.addSeparator(); //-------------------------------
menu.addAction(actions.rescanXMLFromCurrentFolderAction);
menu.addSeparator(); //-------------------------------
if (isCompleted)
if (folder.completed)
menu.addAction(actions.setFolderAsNotCompletedAction);
else
menu.addAction(actions.setFolderAsCompletedAction);
menu.addSeparator(); //-------------------------------
if (isRead)
if (folder.finished)
menu.addAction(actions.setFolderAsUnreadAction);
else
menu.addAction(actions.setFolderAsReadAction);
@ -2566,6 +2647,11 @@ void LibraryWindow::showFoldersContextMenu(const QPoint &point)
typeMenu->addAction(actions.setFolderAsWesternMangaAction);
typeMenu->addAction(actions.setFolderAsWebComicAction);
typeMenu->addAction(actions.setFolderAsYonkomaAction);
menu.addSeparator(); //-------------------------------
menu.addAction(actions.setFolderCoverAction);
if (!folder.customImage.isEmpty()) {
menu.addAction(actions.deleteCustomFolderCoverAction);
}
menu.exec(foldersView->mapToGlobal(point));
}
@ -2609,7 +2695,7 @@ void LibraryWindow::updateViewsOnComicUpdateWithId(quint64 libraryId, quint64 co
}
QString connectionName = "";
{
QSqlDatabase db = DataBaseManagement::loadDatabase(path + "/.yacreaderlibrary");
QSqlDatabase db = DataBaseManagement::loadDatabase(LibraryPaths::libraryDataPath(path));
bool found;
auto comic = DBHelper::loadComic(comicId, db, found);
if (found) {

View File

@ -246,6 +246,10 @@ public slots:
void setFolderAsRead();
void setFolderAsUnread();
void setFolderType(FileType type);
void setFolderCover();
void setCustomFolderCover(Folder folder);
void deleteCustomFolderCover();
void resetFolderCover(Folder folder);
void openContainingFolderComic();
void deleteCurrentLibrary();
void removeLibrary();

View File

@ -281,6 +281,16 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti
setFolderAsUnreadAction->setData(SET_FOLDER_AS_UNREAD_ACTION_YL);
setFolderAsUnreadAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(SET_FOLDER_AS_UNREAD_ACTION_YL));
setFolderCoverAction = new QAction(window);
setFolderCoverAction->setText(tr("Set custom cover"));
setFolderCoverAction->setData(SET_FOLDER_COVER_ACTION_YL);
setFolderCoverAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(SET_FOLDER_COVER_ACTION_YL));
deleteCustomFolderCoverAction = new QAction(window);
deleteCustomFolderCoverAction->setText(tr("Delete custom cover"));
deleteCustomFolderCoverAction->setData(DELETE_CUSTOM_FOLDER_COVER_ACTION_YL);
deleteCustomFolderCoverAction->setShortcut(ShortcutsManager::getShortcutsManager().getShortcut(DELETE_CUSTOM_FOLDER_COVER_ACTION_YL));
setFolderAsMangaAction = new QAction(window);
setFolderAsMangaAction->setText(tr("manga"));
setFolderAsMangaAction->setData(SET_FOLDER_AS_MANGA_ACTION_YL);
@ -445,6 +455,8 @@ void LibraryWindowActions::createActions(LibraryWindow *window, QSettings *setti
window->addAction(setFolderAsWesternMangaAction);
window->addAction(setFolderAsWebComicAction);
window->addAction(setFolderAsYonkomaAction);
window->addAction(setFolderCoverAction);
window->addAction(deleteCustomFolderCoverAction);
window->addAction(deleteMetadataAction);
window->addAction(rescanXMLFromCurrentFolderAction);
window->addAction(openContainingFolderComicAction);
@ -512,6 +524,8 @@ void LibraryWindowActions::createConnections(
QObject::connect(setFolderAsReadAction, &QAction::triggered, window, &LibraryWindow::setFolderAsRead);
QObject::connect(setFolderAsUnreadAction, &QAction::triggered, window, &LibraryWindow::setFolderAsUnread);
QObject::connect(openContainingFolderAction, &QAction::triggered, window, &LibraryWindow::openContainingFolder);
QObject::connect(setFolderCoverAction, &QAction::triggered, window, &LibraryWindow::setFolderCover);
QObject::connect(deleteCustomFolderCoverAction, &QAction::triggered, window, &LibraryWindow::deleteCustomFolderCover);
QObject::connect(setFolderAsMangaAction, &QAction::triggered, window, [=]() {
window->setFolderType(FileType::Manga);
@ -630,7 +644,9 @@ void LibraryWindowActions::setUpShortcutsManagement(EditShortcutsDialog *editSho
<< setFolderAsMangaAction
<< setFolderAsNormalAction
<< updateCurrentFolderAction
<< rescanXMLFromCurrentFolderAction);
<< rescanXMLFromCurrentFolderAction
<< setFolderCoverAction
<< deleteCustomFolderCoverAction);
allActions << tmpList;
editShortcutsDialog->addActionsGroup("Lists", QIcon(":/images/shortcuts_group_folders.svg"), // TODO change icon

View File

@ -72,6 +72,9 @@ public:
QAction *setFolderAsWesternMangaAction;
QAction *setFolderAsWebComicAction;
QAction *setFolderAsYonkomaAction;
//--
QAction *setFolderCoverAction;
QAction *deleteCustomFolderCoverAction;
QAction *openContainingFolderComicAction;
QAction *setAsReadAction;

View File

@ -17,6 +17,7 @@
#include "yacreader_http_server.h"
#include "yacreader_local_server.h"
#include "comic_db.h"
#include "data_base_management.h"
#include "db_helper.h"
#include "yacreader_libraries.h"
#include "exit_check.h"
@ -85,7 +86,13 @@ void logSystemAndConfig()
OpenGLChecker checker;
QLOG_INFO() << "OpenGL version : " << checker.textVersionDescription();
QLOG_INFO() << "Libraries: " << DBHelper::getLibraries().getLibraries();
auto libraries = DBHelper::getLibraries().getLibraries();
QLOG_INFO() << "Libraries: ";
for (auto library : libraries) {
QLOG_INFO() << " " << library;
auto access = DataBaseManagement::getDatabaseAccess(library.getPath());
QLOG_INFO() << " > STATUS: " << access;
}
QLOG_INFO() << "--------------------------------------------";
}

View File

@ -1,10 +1,14 @@
#include "properties_dialog.h"
#include "yacreader_global_gui.h"
#include "cover_utils.h"
#include "data_base_management.h"
#include "initial_comic_info_extractor.h"
#include "yacreader_field_edit.h"
#include "yacreader_field_plain_text_edit.h"
#include "db_helper.h"
#include "yacreader_cover_label.h"
#include <QHBoxLayout>
#include <QApplication>
@ -36,7 +40,8 @@ PropertiesDialog::PropertiesDialog(QWidget *parent)
createTabBar();
auto rootLayout = new QGridLayout;
cover = new QLabel();
cover = new YACReader::CoverLabel();
connect(cover, &YACReader::CoverLabel::imageDropped, this, &PropertiesDialog::loadCustomCoverImageFromPath);
mainLayout = new QGridLayout;
mainLayout->addWidget(tabBar, 0, 0);
@ -102,11 +107,23 @@ void PropertiesDialog::createCoverBox()
showPreviousCoverPageButton = new QToolButton();
showPreviousCoverPageButton->setIcon(QIcon(":/images/previousCoverPage.png"));
showPreviousCoverPageButton->setToolTip(tr("Load previous page as cover"));
showPreviousCoverPageButton->setStyleSheet("QToolButton {border:none;}");
showNextCoverPageButton = new QToolButton();
showNextCoverPageButton->setIcon(QIcon(":/images/nextCoverPage.png"));
showNextCoverPageButton->setToolTip(tr("Load next page as cover"));
showNextCoverPageButton->setStyleSheet("QToolButton {border:none;}");
resetCoverButton = new QToolButton();
resetCoverButton->setIcon(QIcon(":/images/resetCover.svg"));
resetCoverButton->setToolTip(tr("Reset cover to the default image"));
resetCoverButton->setStyleSheet("QToolButton {border:none;}");
loadCustomCoverImageButton = new QToolButton();
loadCustomCoverImageButton->setIcon(QIcon(":/images/loadCustomCover.svg"));
loadCustomCoverImageButton->setToolTip(tr("Load custom cover image"));
loadCustomCoverImageButton->setStyleSheet("QToolButton {border:none;}");
coverPageNumberLabel = new QLabel("-");
coverPageNumberLabel->setStyleSheet("QLabel {color: white; font-weight:bold; font-size:14px;}");
@ -116,6 +133,10 @@ void PropertiesDialog::createCoverBox()
layout->addWidget(coverPageNumberLabel, 0, Qt::AlignVCenter);
layout->addSpacing(5);
layout->addWidget(showNextCoverPageButton, 0, Qt::AlignVCenter);
layout->addSpacing(5);
layout->addWidget(resetCoverButton, 0, Qt::AlignVCenter);
layout->addSpacing(5);
layout->addWidget(loadCustomCoverImageButton, 0, Qt::AlignVCenter);
coverPageEdit->setStyleSheet("QLineEdit {border:none;}");
layout->setSpacing(0);
@ -132,6 +153,8 @@ void PropertiesDialog::createCoverBox()
connect(showPreviousCoverPageButton, &QAbstractButton::clicked, this, &PropertiesDialog::loadPreviousCover);
connect(showNextCoverPageButton, &QAbstractButton::clicked, this, &PropertiesDialog::loadNextCover);
connect(resetCoverButton, &QAbstractButton::clicked, this, &PropertiesDialog::resetCover);
connect(loadCustomCoverImageButton, &QAbstractButton::clicked, this, &PropertiesDialog::loadCustomCoverImage);
}
void PropertiesDialog::createGeneralInfoBox()
@ -434,6 +457,8 @@ QImage blurred(const QImage &image, const QRect &rect, int radius, bool alphaOnl
void PropertiesDialog::loadComic(ComicDB &comic)
{
customCover = QImage();
if (!comic.info.series.isNull())
series->setText(comic.info.series.toString());
if (!comic.info.title.isNull())
@ -456,22 +481,17 @@ void PropertiesDialog::loadComic(ComicDB &comic)
showPreviousCoverPageButton->setEnabled(true);
showNextCoverPageButton->setEnabled(true);
if (coverPage == 1)
showPreviousCoverPageButton->setDisabled(true);
if (coverPage == comic.info.numPages.toInt())
showNextCoverPageButton->setDisabled(true);
coverChanged = false;
coverBox->show();
updateCoverBoxForSingleComic();
if (!QFileInfo(basePath + comic.path).exists()) {
QMessageBox::warning(this, tr("Not found"), tr("Comic not found. You should update your library."));
showPreviousCoverPageButton->setDisabled(true);
showNextCoverPageButton->setDisabled(true);
}
} else {
coverPageNumberLabel->setText("1");
}
/*if(comic.info.numPages != NULL)
numPagesEdit->setText(QString::number(*comic.info.numPages));*/
if (!comic.info.number.isNull())
numberEdit->setText(comic.info.number.toString());
@ -581,9 +601,13 @@ void PropertiesDialog::updateButtons()
void PropertiesDialog::setComics(QList<ComicDB> comics)
{
updated = false;
sequentialEditing = false;
updated = false;
currentComicIndex = 0;
coverChanged = false;
customCover = QImage();
this->comics = comics;
ComicDB comic = comics[0];
@ -593,7 +617,7 @@ void PropertiesDialog::setComics(QList<ComicDB> comics)
updateButtons();
if (comics.length() > 1) {
coverBox->hide();
updateCoverBoxForMultipleComics();
setDisableUniqueValues(true);
this->setWindowTitle(tr("Edit selected comics information"));
@ -700,9 +724,12 @@ void PropertiesDialog::setComics(QList<ComicDB> comics)
void PropertiesDialog::setComicsForSequentialEditing(int currentComicIndex, QList<ComicDB> comics)
{
updated = false;
sequentialEditing = true;
updated = false;
coverChanged = false;
customCover = QImage();
this->comics = comics;
this->currentComicIndex = currentComicIndex;
@ -726,6 +753,40 @@ void PropertiesDialog::updateComics()
DBHelper::update(&(itr->info), db);
updated = true;
}
if (!sequentialEditing && coverChanged) {
auto coverPath = LibraryPaths::coverPath(basePath, itr->info.hash);
if (customCover.isNull()) { // reseted, we need to restore the default cover
itr->info.coverPage = QVariant();
InitialComicInfoExtractor ie(basePath + itr->path, coverPath, 1);
ie.extract();
if (ie.getOriginalCoverSize().second > 0) {
itr->info.originalCoverSize = QString("%1x%2").arg(ie.getOriginalCoverSize().first).arg(ie.getOriginalCoverSize().second);
itr->info.coverSizeRatio = static_cast<float>(ie.getOriginalCoverSize().first) / ie.getOriginalCoverSize().second;
}
emit coverChangedSignal(*itr);
DBHelper::update(&(itr->info), db);
updated = true;
} else {
itr->info.coverPage = QVariant();
YACReader::saveCover(coverPath, customCover);
auto width = customCover.width();
auto height = customCover.height();
itr->info.originalCoverSize = QString("%1x%2").arg(width).arg(height);
itr->info.coverSizeRatio = static_cast<float>(width) / height;
DBHelper::update(&(itr->info), db);
updated = true;
emit coverChangedSignal(*itr);
}
}
}
db.commit();
connectionName = db.connectionName();
@ -739,14 +800,14 @@ void PropertiesDialog::setMultipleCover()
QPixmap last = lastComic.info.getCover(basePath);
last = last.scaledToHeight(575, Qt::SmoothTransformation);
coverImage = QPixmap::fromImage(blurred(last.toImage(), QRect(0, 0, last.width(), last.height()), 15));
auto coverImage = QPixmap::fromImage(blurred(last.toImage(), QRect(0, 0, last.width(), last.height()), 15));
cover->setPixmap(coverImage);
}
void PropertiesDialog::setCover(const QPixmap &coverI)
{
coverImage = coverI.scaledToHeight(575, Qt::SmoothTransformation);
auto coverImage = coverI.scaledToHeight(575, Qt::SmoothTransformation);
cover->setPixmap(coverImage);
}
@ -755,13 +816,14 @@ void PropertiesDialog::setFilename(const QString &nameString)
{
title->setText(nameString);
}
void PropertiesDialog::setNumpages(int pagesNum)
{
numPagesEdit->setText(QString::number(pagesNum));
}
void PropertiesDialog::setSize(float sizeFloat)
{
size->setText(QString::number(sizeFloat, 'f', 2) + " MB");
}
@ -787,7 +849,11 @@ void PropertiesDialog::save()
if (sequentialEditing)
if (coverChanged) {
itr->info.coverPage = coverPageNumberLabel->text().toInt();
if (customCover.isNull()) {
itr->info.coverPage = coverPageNumberLabel->text().toInt();
} else {
itr->info.coverPage = QVariant();
}
edited = true;
}
@ -954,15 +1020,32 @@ void PropertiesDialog::save()
if (sequentialEditing) {
if (coverChanged) {
InitialComicInfoExtractor ie(basePath + comics[currentComicIndex].path, basePath + "/.yacreaderlibrary/covers/" + comics[currentComicIndex].info.hash + ".jpg", comics[currentComicIndex].info.coverPage.toInt());
ie.extract();
auto coverPath = LibraryPaths::coverPath(basePath, comics[currentComicIndex].info.hash);
if (ie.getOriginalCoverSize().second > 0) {
comics[currentComicIndex].info.originalCoverSize = QString("%1x%2").arg(ie.getOriginalCoverSize().first).arg(ie.getOriginalCoverSize().second);
comics[currentComicIndex].info.coverSizeRatio = static_cast<float>(ie.getOriginalCoverSize().first) / ie.getOriginalCoverSize().second;
if (customCover.isNull()) {
InitialComicInfoExtractor ie(basePath + comics[currentComicIndex].path, coverPath, comics[currentComicIndex].info.coverPage.toInt());
ie.extract();
if (ie.getOriginalCoverSize().second > 0) {
comics[currentComicIndex].info.originalCoverSize = QString("%1x%2").arg(ie.getOriginalCoverSize().first).arg(ie.getOriginalCoverSize().second);
comics[currentComicIndex].info.coverSizeRatio = static_cast<float>(ie.getOriginalCoverSize().first) / ie.getOriginalCoverSize().second;
}
comics[currentComicIndex].info.edited = true;
emit coverChangedSignal(comics[currentComicIndex]);
} else {
auto width = customCover.width();
auto height = customCover.height();
comics[currentComicIndex].info.originalCoverSize = QString("%1x%2").arg(width).arg(height);
comics[currentComicIndex].info.coverSizeRatio = static_cast<float>(width) / height;
comics[currentComicIndex].info.edited = true;
YACReader::saveCover(coverPath, customCover);
emit coverChangedSignal(comics[currentComicIndex]);
}
emit coverChangedSignal(comics[currentComicIndex]);
}
}
}
@ -1092,48 +1175,87 @@ void PropertiesDialog::updateCoverPageNumberLabel(int n)
void PropertiesDialog::loadNextCover()
{
int current = coverPageNumberLabel->text().toInt();
if (current < comics[currentComicIndex].info.numPages.toInt()) {
updateCoverPageNumberLabel(current + 1);
int next;
InitialComicInfoExtractor ie(basePath + comics[currentComicIndex].path, "", current + 1);
ie.extract();
setCover(ie.getCover());
repaint();
if (current == comics[currentComicIndex].info.numPages.toInt())
next = 1;
else
next = current + 1;
if ((current + 1) == comics[currentComicIndex].info.numPages.toInt()) {
showNextCoverPageButton->setDisabled(true);
}
setCoverPage(next);
showPreviousCoverPageButton->setEnabled(true);
if (current + 1 != comics[currentComicIndex].info.coverPage)
coverChanged = true;
else
coverChanged = false;
}
coverChanged = next != comics[currentComicIndex].info.coverPage;
}
void PropertiesDialog::loadPreviousCover()
{
int current = coverPageNumberLabel->text().toInt();
if (current != 1) {
updateCoverPageNumberLabel(current - 1);
InitialComicInfoExtractor ie(basePath + comics[currentComicIndex].path, "", current - 1);
int previous;
if (current == 1)
previous = comics[currentComicIndex].info.numPages.toInt();
else
previous = current - 1;
setCoverPage(previous);
coverChanged = previous != comics[currentComicIndex].info.coverPage.toInt();
}
void PropertiesDialog::resetCover()
{
if (sequentialEditing) {
setCoverPage(1);
coverChanged = true; // it could be that the cover is a custom cover, so we need to always update it
} else {
InitialComicInfoExtractor ie(basePath + comics.last().path, "", 1);
ie.extract();
setCover(ie.getCover());
repaint();
auto cover = ie.getCover();
cover = cover.scaledToHeight(575, Qt::SmoothTransformation);
if ((current - 1) == 1) {
showPreviousCoverPageButton->setDisabled(true);
}
auto coverImage = QPixmap::fromImage(blurred(cover.toImage(), QRect(0, 0, cover.width(), cover.height()), 15));
showNextCoverPageButton->setEnabled(true);
if (current - 1 != comics[currentComicIndex].info.coverPage.toInt())
coverChanged = true;
else
coverChanged = false;
this->cover->setPixmap(coverImage);
customCover = QImage();
coverChanged = true;
}
}
void PropertiesDialog::loadCustomCoverImage()
{
auto path = YACReader::imageFileLoader(this);
loadCustomCoverImageFromPath(path);
}
void PropertiesDialog::loadCustomCoverImageFromPath(const QString &path)
{
if (path.isEmpty()) {
return;
}
customCover = QImage(path);
coverChanged = true;
if (customCover.isNull()) {
QMessageBox::warning(this, tr("Invalid cover"), tr("The image is invalid."));
return;
}
setCover(QPixmap::fromImage(customCover));
}
void PropertiesDialog::setCoverPage(int pageNumber)
{
customCover = QImage();
updateCoverPageNumberLabel(pageNumber);
InitialComicInfoExtractor ie(basePath + comics[currentComicIndex].path, "", pageNumber);
ie.extract();
setCover(ie.getCover());
repaint();
}
bool PropertiesDialog::close()
{
if (updated) {
@ -1142,3 +1264,17 @@ bool PropertiesDialog::close()
return QDialog::close();
}
void PropertiesDialog::updateCoverBoxForMultipleComics()
{
showPreviousCoverPageButton->hide();
showNextCoverPageButton->hide();
coverPageNumberLabel->hide();
}
void PropertiesDialog::updateCoverBoxForSingleComic()
{
showPreviousCoverPageButton->show();
showNextCoverPageButton->show();
coverPageNumberLabel->show();
}

View File

@ -19,6 +19,10 @@ class QComboBox;
// class YACReaderBusyWidget;
class QToolButton;
namespace YACReader {
class CoverLabel;
}
#include "comic_db.h"
class PropertiesDialog : public QDialog
@ -33,7 +37,7 @@ private:
QTabWidget *tabBar;
QWidget *coverBox;
QLabel *cover;
YACReader::CoverLabel *cover;
QScrollArea *sa;
QWidget *generalInfoBox;
@ -113,12 +117,13 @@ private:
QPushButton *previousButton;
QPushButton *restoreButton; //??
QPixmap coverImage;
QToolButton *showPreviousCoverPageButton;
QToolButton *showNextCoverPageButton;
QLabel *coverPageNumberLabel;
QToolButton *resetCoverButton;
QToolButton *loadCustomCoverImageButton;
void createTabBar();
void createCoverBox();
void createGeneralInfoBox();
@ -144,6 +149,8 @@ private:
bool updated;
QString originalCoverSize;
QImage customCover;
public:
PropertiesDialog(QWidget *parent = nullptr);
QString databasePath;
@ -170,7 +177,13 @@ public slots:
void setSize(float size);
void loadNextCover();
void loadPreviousCover();
void resetCover();
void loadCustomCoverImage();
void loadCustomCoverImageFromPath(const QString &path);
void setCoverPage(int pageNumber);
bool close();
void updateCoverBoxForMultipleComics();
void updateCoverBoxForSingleComic();
signals:
void coverChangedSignal(const ComicDB &comic);

View File

@ -1,10 +1,8 @@
#include "covercontroller_v2.h"
#include "db_helper.h" //get libraries
#include <QImage>
#include "yacreader_libraries.h"
#include "yacreader_http_session.h"
#include "template.h"
#include "../static.h"
#include "yacreader_global.h"
using stefanfrings::HttpRequest;
using stefanfrings::HttpResponse;
@ -20,9 +18,10 @@ void CoverControllerV2::service(HttpRequest &request, HttpResponse &response)
QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8();
QStringList pathElements = path.split('/');
QString libraryName = DBHelper::getLibraryName(pathElements.at(3).toInt());
QString fileName = pathElements.at(5);
QStringList remainingPathElements = pathElements.mid(5);
QString fileName = remainingPathElements.join('/');
QImage img(libraries.getPath(libraryName) + "/.yacreaderlibrary/covers/" + fileName);
QImage img(YACReader::LibraryPaths::coverPathWithFileName(libraries.getPath(libraryName), fileName));
if (!img.isNull()) {
QByteArray ba;
QBuffer buffer(&ba);

View File

@ -246,7 +246,7 @@ void RequestMapper::serviceV2(HttpRequest &request, HttpResponse &response)
QRegExp comicFullInfo("/v2/library/.+/comic/[0-9]+/fullinfo/?"); // get comic info
QRegExp comicUpdate("/v2/library/.+/comic/[0-9]+/update/?"); // get comic info
QRegExp comicClose("/v2/library/.+/comic/[0-9]+/close/?"); // the server will close the comic and free memory
QRegExp cover("/v2/library/.+/cover/[0-9a-f]+.jpg"); // get comic cover (navigation)
QRegExp cover("/v2/library/.+/cover/.+"); // get comic cover (navigation)
QRegExp comicPage("/v2/library/.+/comic/[0-9]+/page/[0-9]+/?"); // get comic page
QRegExp comicPageRemote("/v2/library/.+/comic/[0-9]+/page/[0-9]+/remote?"); // get comic page (remote reading)
QRegExp serverVersion("/v2/version/?");

View File

@ -2,9 +2,11 @@
#include "qnaturalsorting.h"
#include "yacreader_global.h"
using namespace YACReader;
void writeIdToLibraryFolder(const QString &path, const QUuid &id)
{
QFile file(path + "/.yacreaderlibrary/id");
QFile file(LibraryPaths::idPath(path));
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream stream(&file);
stream << id.toString(QUuid::WithoutBraces);
@ -14,7 +16,7 @@ void writeIdToLibraryFolder(const QString &path, const QUuid &id)
QUuid readFromLibraryFolder(const QString &path)
{
QFile file(path + "/.yacreaderlibrary/id");
QFile file(LibraryPaths::idPath(path));
if (file.open(QIODevice::ReadOnly | QIODevice::Text)) {
QTextStream stream(&file);
QString id = stream.readLine();
@ -62,7 +64,7 @@ QString YACReaderLibraries::getPath(const QUuid &id)
QString YACReaderLibraries::getDBPath(int id)
{
return getPath(id) + "/.yacreaderlibrary";
return LibraryPaths::libraryDataPath(getPath(id));
}
QString YACReaderLibraries::getName(int id)
@ -228,7 +230,7 @@ QString YACReaderLibrary::getPath() const
QString YACReaderLibrary::getDBPath() const
{
return path + "/.yacreaderlibrary";
return LibraryPaths::libraryDataPath(path);
}
int YACReaderLibrary::getLegacyId() const

View File

@ -57,6 +57,7 @@ HEADERS += ../YACReaderLibrary/library_creator.h \
../common/bookmarks.h \
../common/qnaturalsorting.h \
../common/yacreader_global.h \
../common/cover_utils.h \
../YACReaderLibrary/yacreader_local_server.h \
../YACReaderLibrary/comics_remover.h \
../common/http_worker.h \
@ -87,6 +88,7 @@ SOURCES += ../YACReaderLibrary/library_creator.cpp \
../common/comic.cpp \
../common/bookmarks.cpp \
../common/qnaturalsorting.cpp \
../common/cover_utils.cpp \
../YACReaderLibrary/yacreader_local_server.cpp \
../YACReaderLibrary/comics_remover.cpp \
../common/http_worker.cpp \

View File

@ -33,7 +33,7 @@ void ConsoleUILibraryCreator::createLibrary(const QString &name, const QString &
return;
}
libraryCreator->createLibrary(cleanPath, QDir::cleanPath(pathDir.absolutePath() + "/.yacreaderlibrary"));
libraryCreator->createLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath));
connect(libraryCreator, &LibraryCreator::finished, this, &ConsoleUILibraryCreator::done);
connect(libraryCreator, &LibraryCreator::comicAdded, this, &ConsoleUILibraryCreator::newComic);
@ -62,7 +62,7 @@ void ConsoleUILibraryCreator::updateLibrary(const QString &path)
LibraryCreator *libraryCreator = new LibraryCreator(settings);
QString cleanPath = QDir::cleanPath(pathDir.absolutePath());
libraryCreator->updateLibrary(cleanPath, QDir::cleanPath(pathDir.absolutePath() + "/.yacreaderlibrary"));
libraryCreator->updateLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath));
connect(libraryCreator, &LibraryCreator::finished, this, &ConsoleUILibraryCreator::done);
connect(libraryCreator, &LibraryCreator::comicAdded, this, &ConsoleUILibraryCreator::newComic);
@ -85,8 +85,8 @@ void ConsoleUILibraryCreator::addExistingLibrary(const QString &name, const QStr
}
QString cleanPath = QDir::cleanPath(pathDir.absolutePath());
if (!QDir(cleanPath + "/.yacreaderlibrary").exists()) {
std::cout << "No library database found in directory." << std::endl;
if (!QDir(LibraryPaths::libraryDataPath(cleanPath)).exists()) {
std::cout << "No data folder found in path: " << cleanPath.toStdString() << std::endl;
return;
}
@ -135,7 +135,7 @@ void ConsoleUILibraryCreator::rescanXMLInfoLibrary(const QString &path)
connect(scanner, &XMLInfoLibraryScanner::finished, &eventLoop, &QEventLoop::quit);
std::cout << "Scanning comics";
scanner->scanLibrary(cleanPath, QDir::cleanPath(pathDir.absolutePath() + "/.yacreaderlibrary"));
scanner->scanLibrary(cleanPath, LibraryPaths::libraryDataPath(cleanPath));
eventLoop.exec();
}

View File

@ -14,16 +14,18 @@ void LibrariesUpdater::updateIfNeeded()
libraries.load();
foreach (QString name, libraries.getNames()) {
QString path = libraries.getPath(name) + "/.yacreaderlibrary";
QString libraryPath = libraries.getPath(name);
QString libraryDataPath = YACReader::LibraryPaths::libraryDataPath(libraryPath);
QString databasePath = YACReader::LibraryPaths::libraryDatabasePath(libraryPath);
QDir d;
QString dbVersion;
if (d.exists(path) && d.exists(path + "/library.ydb") && (dbVersion = DataBaseManagement::checkValidDB(path + "/library.ydb")) != "") {
if (d.exists(libraryDataPath) && d.exists(databasePath) && (dbVersion = DataBaseManagement::checkValidDB(databasePath)) != "") {
int comparation = DataBaseManagement::compareVersions(dbVersion, DB_VERSION);
if (comparation < 0) {
bool updated = DataBaseManagement::updateToCurrentVersion(path);
bool updated = DataBaseManagement::updateToCurrentVersion(libraryPath);
if (!updated) {
// TODO log error
}

View File

@ -5,6 +5,7 @@
#include <QImageReader>
#include "comic_db.h"
#include "data_base_management.h"
#include "db_helper.h"
#include "yacreader_http_server.h"
#include "yacreader_global.h"
@ -81,7 +82,7 @@ int main(int argc, char **argv)
QCommandLineParser parser;
parser.setApplicationDescription(QString(QCoreApplication::tr("\nYACReaderLibraryServer is the headless (no gui) version of YACReaderLibrary.\n\n"
"This appplication support persisten settings, to set them up edit this file %1\n"
"This appplication supports persistent settings, to set them up edit this file %1\n"
"To learn about the available settings please check the documentation at https://raw.githubusercontent.com/YACReader/yacreader/develop/YACReaderLibraryServer/SETTINGS_README.md"))
.arg(settingsPath));
parser.addHelpOption();
@ -461,7 +462,14 @@ void logSystemAndConfig()
for (const auto &line : globalInfo.split("\n")) {
QLOG_INFO() << line;
}
QLOG_INFO() << "Libraries: " << DBHelper::getLibraries().getLibraries();
auto libraries = DBHelper::getLibraries().getLibraries();
QLOG_INFO() << "Libraries: ";
for (auto library : libraries) {
QLOG_INFO() << " " << library;
auto access = DataBaseManagement::getDatabaseAccess(library.getPath());
QLOG_INFO() << " > STATUS: " << access;
}
QLOG_INFO() << "--------------------------------------------";
}

View File

@ -18,10 +18,15 @@ jobs:
pool:
vmImage: 'windows-2019'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.10'
architecture: 'x64'
- script: |
pip install -U pip
pip install aqtinstall
mkdir C:\Qt
python -m aqt install -O c:\Qt ${{ parameters.qt_version }} windows desktop ${{ parameters.qt_aqt_spec }} -m qt5compat qtmultimedia qtimageformats
python -m aqt install-qt windows desktop ${{ parameters.qt_version }} ${{ parameters.qt_aqt_spec }} -O c:\Qt -m qt5compat qtmultimedia qtimageformats
dir C:\Qt\${{ parameters.qt_version }}\${{ parameters.qt_spec }}\bin
choco install -y wget
choco install innosetup

View File

@ -18,10 +18,15 @@ jobs:
pool:
vmImage: 'windows-2019'
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: '3.10'
architecture: 'x64'
- script: |
pip install -U pip
pip install aqtinstall
mkdir C:\Qt
python -m aqt install -O c:\Qt ${{ parameters.qt_version }} windows desktop ${{ parameters.qt_aqt_spec }}
python -m aqt install-qt windows desktop ${{ parameters.qt_version }} ${{ parameters.qt_aqt_spec }} -O c:\Qt
dir C:\Qt\${{ parameters.qt_version }}\${{ parameters.qt_spec }}\bin
choco install -y wget
choco install innosetup

View File

@ -404,12 +404,7 @@ ComicInfo &ComicInfo::operator=(const ComicInfo &comicInfo)
QPixmap ComicInfo::getCover(const QString &basePath)
{
if (cover.isNull()) {
cover.load(basePath + "/.yacreaderlibrary/covers/" + hash + ".jpg");
}
QPixmap c;
c.convertFromImage(cover);
return c;
return QPixmap(YACReader::LibraryPaths::coverPath(basePath, hash));
}
QStringList ComicInfo::getWriters()

View File

@ -84,8 +84,6 @@ public:
QVariant comicVineID; // string
QImage cover;
QVariant lastTimeOpened; // integer/date
QVariant coverSizeRatio; // h/w
QVariant originalCoverSize; // string "WxH"
@ -189,8 +187,6 @@ public:
Q_PROPERTY(QVariant comicVineID MEMBER comicVineID CONSTANT)
Q_PROPERTY(QImage cover MEMBER cover CONSTANT)
Q_PROPERTY(QVariant lastTimeOpened MEMBER lastTimeOpened CONSTANT)
Q_PROPERTY(QVariant coverSizeRatio MEMBER coverSizeRatio CONSTANT)

18
common/cover_utils.cpp Normal file
View File

@ -0,0 +1,18 @@
#include "cover_utils.h"
bool YACReader::saveCover(const QString &path, const QImage &cover)
{
QImage scaled;
if (cover.width() > cover.height()) {
scaled = cover.scaledToWidth(640, Qt::SmoothTransformation);
} else {
auto aspectRatio = static_cast<double>(cover.width()) / static_cast<double>(cover.height());
auto maxAllowedAspectRatio = 0.5;
if (aspectRatio < maxAllowedAspectRatio) { // cover is too tall, e.g. webtoon
scaled = cover.scaledToHeight(960, Qt::SmoothTransformation);
} else {
scaled = cover.scaledToWidth(480, Qt::SmoothTransformation);
}
}
return scaled.save(path, 0, 75);
}

9
common/cover_utils.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef COVER_UTILS_H
#define COVER_UTILS_H
#include <QImage>
namespace YACReader {
bool saveCover(const QString &path, const QImage &image);
}
#endif // COVER_UTILS_H

View File

@ -5,10 +5,11 @@
#include <QDataStream>
#include <QMetaType>
#include <QAbstractItemModel>
#include <QDir>
class QLibrary;
#define VERSION "9.15.0"
#define VERSION "9.15.1"
// Used to check if the database needs to be updated, the version is stored in the database.
// This value is only incremented when the database structure changes.
@ -108,7 +109,77 @@ void iterate(const QModelIndex &index,
const QAbstractItemModel *model,
const std::function<bool(const QModelIndex &)> &iteration);
}
// TODO: remove all the dataPath variants and always use the root folder of a library `libraryPath` to get all the paths.
struct LibraryPaths {
LibraryPaths() = delete; // Prevent instantiation
static QString libraryDataPath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary
{
return QDir(libraryPath).filePath(".yacreaderlibrary");
}
static QString libraryDatabasePath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary/library.ydb
{
return QDir(libraryDataPath(libraryPath)).filePath("library.ydb");
}
static QString libraryCoversFolderPath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary/covers
{
return QDir(libraryDataPath(libraryPath)).filePath("covers");
}
static QString libraryCoversPathFromLibraryDataPath(const QString &libraryDataPath) // libraryDataPath + /covers
{
return QDir(libraryDataPath).filePath("covers");
}
static QString coverPath(const QString &libraryPath, const QString &hash) // libraryPath + /.yacreaderlibrary/covers/hash + .jpg
{
return QDir(libraryCoversFolderPath(libraryPath)).filePath(coverFileName(hash));
}
static QString libraryCustomFoldersCoverPath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary/covers/folders
{
return QDir(libraryCoversFolderPath(libraryPath)).filePath("folders");
}
static QString libraryCustomFoldersCoverPathFromLibraryDataPath(const QString &libraryDataPath)
{
return QDir(libraryCoversPathFromLibraryDataPath(libraryDataPath)).filePath("folders");
}
static QString customFolderCoverPath(const QString &libraryPath, const QString &folderId)
{
return QDir(libraryCustomFoldersCoverPath(libraryPath)).filePath(coverFileName(folderId));
}
static QString customFolderCoverPathFromDataPath(const QString &libraryDataPath, const QString &folderId)
{
return QDir(libraryCustomFoldersCoverPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(folderId));
}
static QString coverPathFromLibraryDataPath(const QString &libraryDataPath, const QString &hash) // libraryDataPath + /covers/hash + .jpg
{
return QDir(libraryCoversPathFromLibraryDataPath(libraryDataPath)).filePath(coverFileName(hash));
}
static QString coverFileName(const QString &id) // id + .jpg (it can be a comic hash or a folder id)
{
return id + ".jpg";
}
static QString coverPathWithFileName(const QString &libraryPath, const QString &fileName) // libraryPath + /.yacreaderlibrary/covers/hash + fileName
{
return QDir(libraryCoversFolderPath(libraryPath)).filePath(fileName);
}
static QString idPath(const QString &libraryPath) // libraryPath + /.yacreaderlibrary/id
{
return QDir(libraryDataPath(libraryPath)).filePath("id");
}
};
} // namespace YACReader
Q_DECLARE_METATYPE(YACReader::OpenComicSource::Source)
Q_DECLARE_METATYPE(YACReader::OpenComicSource)

View File

@ -2,6 +2,8 @@
#include <QtCore>
#include <QAction>
#include <QImageReader>
#include <QFileDialog>
using namespace YACReader;
@ -99,3 +101,47 @@ QPixmap YACReader::hdpiPixmap(const QString &file, QSize size)
{
return QIcon(file).pixmap(size);
}
QString YACReader::imageFileLoader(QWidget *parent)
{
QString supportedImageFormatsString;
for (const QByteArray &format : QImageReader::supportedImageFormats()) {
supportedImageFormatsString += QString("*.%1 ").arg(QString(format));
}
return QFileDialog::getOpenFileName(parent, QObject::tr("Select custom cover"), QDir::homePath(), QObject::tr("Images (%1)").arg(supportedImageFormatsString));
}
QString YACReader::imagePathFromMimeData(const QMimeData *mimeData)
{
QString filePath;
if (mimeData->hasUrls()) {
QList<QUrl> urlList = mimeData->urls();
if (!urlList.isEmpty()) {
QUrl url = urlList.first();
if (url.isLocalFile()) {
filePath = url.toLocalFile();
QFileInfo fileInfo(filePath);
QString extension = fileInfo.suffix().toLower();
QList<QByteArray> supportedFormats = QImageReader::supportedImageFormats();
bool isSupported = false;
for (const QByteArray &format : supportedFormats) {
if (extension == QString(format).toLower()) {
isSupported = true;
break;
}
}
if (!isSupported) {
filePath.clear();
}
}
}
}
return filePath;
}

View File

@ -36,6 +36,7 @@
#define DO_NOT_TURN_PAGE_ON_SCROLL "DO_NOT_TURN_PAGE_ON_SCROLL"
#define USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE "USE_SINGLE_SCROLL_STEP_TO_TURN_PAGE"
#define DISABLE_SCROLL_ANIMATION "DISABLE_SCROLL_ANIMATION"
#define MOUSE_MODE "MOUSE_MODE"
#define FLOW_TYPE_GL "FLOW_TYPE_GL"
#define Y_POSITION "Y_POSITION"
@ -97,14 +98,6 @@ enum ComicsViewStatus {
Info
};
enum FitMode {
ToWidth = 0x01,
ToHeight = 0x02,
FullRes = 0x03,
FullPage = 0x04 //,
// Text=0x05
};
enum LibraryUITheme {
Light,
Dark
@ -119,6 +112,7 @@ QString addExtensionToIconPath(const QString &path);
QString addExtensionToIconPathInToolbar(const QString &path);
QAction *actionWithCustomIcon(const QIcon &icon, QAction *action);
QPixmap hdpiPixmap(const QString &file, QSize size);
QString imageFileLoader(QWidget *parent);
QString imagePathFromMimeData(const QMimeData *mimeData);
}
#endif

View File

@ -21,7 +21,8 @@ HEADERS += \
$$PWD/yacreader_library_list_widget.h \
$$PWD/yacreader_library_item_widget.h \
$$PWD/yacreader_treeview.h \
$$PWD/yacreader_busy_widget.h
$$PWD/yacreader_busy_widget.h \
$$PWD/yacreader_cover_label.h
!CONFIG(no_opengl){
HEADERS += $$PWD/yacreader_gl_flow_config_widget.h
}
@ -50,8 +51,8 @@ SOURCES += \
$$PWD/yacreader_library_list_widget.cpp \
$$PWD/yacreader_library_item_widget.cpp \
$$PWD/yacreader_treeview.cpp \
$$PWD/yacreader_busy_widget.cpp
$$PWD/yacreader_busy_widget.cpp \
$$PWD/yacreader_cover_label.cpp
!CONFIG(no_opengl){
SOURCES += $$PWD/yacreader_gl_flow_config_widget.cpp
}

View File

@ -54,6 +54,7 @@ YACReader::WhatsNewDialog::WhatsNewDialog(QWidget *parent)
" &#8226; Bump PDF render size<br/>"
" &#8226; Fix trackpad scrolling, it makes using trackpads more responsive and natural<br/>"
" &#8226; Added more info to Help -> System info<br/>"
" &#8226; Don't use scroll animations on macos by default, it where hdpi scroll is most likely to be used and it causes scroll issues. (new 9.15.1)<br/>"
"<br/>"
"<span style=\"font-weight:600\">YACReaderLibrary</span><br/>"
" &#8226; Fix headers in the table view getting stuck in a non-movable state<br/>"
@ -69,6 +70,8 @@ YACReader::WhatsNewDialog::WhatsNewDialog(QWidget *parent)
" &#8226; Fix occasional crashes when using automatic library updates<br/>"
" &#8226; Add setting to hide the \"Continue Reading...\" banner from the home view<br/>"
" &#8226; Improve Grid and Flow Info comics view scroll performance<br/>"
" &#8226; Improve flexibility of the open comic in third party app setting so more complex commands can be used. e.g. `open -a \"/Applications/My Reader.app"
"{comic_file_path}\"` (new 9.15.1)<br/>"
"<br/>"
"<span style=\"font-weight:600\">YACReaderLibraryServer</span><br/>"
" &#8226; New command --system-info to print information about the execution environment and available resources (including what image formats are supported and what libraries are used by the app).<br/>"

View File

@ -0,0 +1,31 @@
#include "yacreader_cover_label.h"
#include "yacreader_global_gui.h"
YACReader::CoverLabel::CoverLabel(QWidget *parent)
: QLabel(parent)
{
setAcceptDrops(true);
}
void YACReader::CoverLabel::dragEnterEvent(QDragEnterEvent *event)
{
if (event->mimeData()->hasUrls() && !YACReader::imagePathFromMimeData(event->mimeData()).isEmpty()) {
event->acceptProposedAction();
}
}
void YACReader::CoverLabel::dragMoveEvent(QDragMoveEvent *event)
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
}
}
void YACReader::CoverLabel::dropEvent(QDropEvent *event)
{
QString path = YACReader::imagePathFromMimeData(event->mimeData());
if (!path.isEmpty()) {
emit imageDropped(path);
event->acceptProposedAction();
}
}

View File

@ -0,0 +1,29 @@
#ifndef DROP_LABEL_H
#define DROP_LABEL_H
#include <QLabel>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
namespace YACReader {
class CoverLabel : public QLabel
{
Q_OBJECT
public:
explicit CoverLabel(QWidget *parent = nullptr);
protected:
void dragEnterEvent(QDragEnterEvent *event) override;
void dragMoveEvent(QDragMoveEvent *event) override;
void dropEvent(QDropEvent *event) override;
signals:
void imageDropped(const QString &path);
};
}
#endif // DROP_LABEL_H

View File

@ -99,6 +99,7 @@ class YACReaderMacOSXSearchLineEdit : public YACReaderSearchLineEdit
class YACReaderMacOSXToolbar : public YACReaderMainToolBar
{
Q_OBJECT
public:
explicit YACReaderMacOSXToolbar(QWidget *parent = 0);
QSize sizeHint() const override;
@ -109,20 +110,41 @@ public:
void updateViewSelectorIcon(const QIcon &icon);
void attachToWindow(QMainWindow *window);
void *getSearchEditDelegate() { return searchEditDelegate; };
void emitFilterChange(const QString &filter) { emit filterChanged(filter); };
QAction *actionFromIdentifier(const QString &identifier);
signals:
void filterChanged(QString);
private:
void paintEvent(QPaintEvent *) override;
void *searchEditDelegate;
};
#else
#include <QtWidgets>
class YACReaderMacOSXToolbar : public QToolBar
class YACReaderMacOSXToolbar : public QWidget
{
Q_OBJECT
public:
explicit YACReaderMacOSXToolbar(QWidget *parent = 0);
void attachToWindow(QMainWindow *window);
void addStretch();
void setMovable(bool movable) { };
void addSeparator() { };
void setIconSize(const QSize &size) { };
public slots:
void setHidden(bool hidden);
void show();
void hide();
};
#endif

View File

@ -398,8 +398,294 @@ void MacToolBarItemWrapper::updateIcon(bool enabled)
}
#else
#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
NSImage *QIconToNSImage(const QIcon &icon, const QSize &size, const QColor &color = QColor())
{
QPixmap pixmap = icon.pixmap(size);
QImage qImage = pixmap.toImage().convertToFormat(QImage::Format_RGBA8888);
if (color.isValid()) {
QPainter p;
QImage mask(qImage);
p.begin(&mask);
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
QBrush brush(color);
p.fillRect(QRect(0, 0, size.width(), size.height()), brush);
p.end();
p.begin(&qImage);
p.setCompositionMode(QPainter::CompositionMode_Overlay);
p.drawImage(0, 0, mask);
p.end();
}
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
(void *)qImage.bits(),
qImage.width(),
qImage.height(),
8,
qImage.bytesPerLine(),
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGImageRef cgImage = CGBitmapContextCreateImage(context);
NSImage *nsImage = [[NSImage alloc] initWithCGImage:cgImage size:NSMakeSize(qImage.width(), qImage.height())];
// Clean up
CGImageRelease(cgImage);
CGContextRelease(context);
CGColorSpaceRelease(colorSpace);
return nsImage;
}
void bindActionToNSToolbarItem(QAction *action, NSToolbarItem *toolbarItem, const QColor &iconColor = QColor())
{
if (action == nullptr || toolbarItem == nil) {
return;
}
auto update = [=] {
toolbarItem.enabled = action->isEnabled();
QString text = action->text();
QString tooltip = action->toolTip();
toolbarItem.label = text.isEmpty() ? @"" : [NSString stringWithUTF8String:text.toUtf8().constData()];
toolbarItem.paletteLabel = toolbarItem.label;
toolbarItem.toolTip = tooltip.isEmpty() ? @"" : [NSString stringWithUTF8String:tooltip.toUtf8().constData()];
QIcon icon = action->icon();
__auto_type image = QIconToNSImage(icon, { 24, 24 }, iconColor);
if (action->isChecked()) {
NSSize size = image.size;
NSImage *decoratedImage = [[NSImage alloc] initWithSize:size];
[decoratedImage lockFocus];
NSRect rect = NSMakeRect(0, 0, size.width, size.height);
NSBezierPath *path = [NSBezierPath bezierPathWithRoundedRect:rect xRadius:8 yRadius:8];
[[NSColor colorWithCalibratedRed:0.8 green:0.8 blue:0.8 alpha:0.9] setFill];
[path fill];
NSRect imageRect = NSMakeRect(4, 4, size.width - 8, size.height - 8);
[image drawInRect:imageRect
fromRect:NSZeroRect
operation:NSCompositingOperationSourceOver
fraction:1.0];
[decoratedImage unlockFocus];
toolbarItem.image = decoratedImage;
} else {
NSSize size = image.size;
NSImage *decoratedImage = [[NSImage alloc] initWithSize:size];
[decoratedImage lockFocus];
NSRect imageRect = NSMakeRect(4, 4, size.width - 8, size.height - 8);
[image drawInRect:imageRect
fromRect:NSZeroRect
operation:NSCompositingOperationSourceOver
fraction:1.0];
[decoratedImage unlockFocus];
toolbarItem.image = decoratedImage;
}
[image release];
};
if (action->isCheckable()) {
QObject::connect(
action, &QAction::triggered,
[=](bool checked) {
update();
});
}
QObject::connect(
action, &QAction::enabledChanged,
[=](bool enabled) {
toolbarItem.enabled = enabled;
});
QObject::connect(
action, &QAction::changed,
[=]() {
update();
});
toolbarItem.bordered = YES;
update();
}
#ifdef YACREADER_LIBRARY
@interface YACReaderLibraryToolbarDelegate : NSObject <NSToolbarDelegate> {
@public
YACReaderMacOSXToolbar *mytoolbar;
}
- (IBAction)itemClicked:(id)sender;
@end
@implementation YACReaderLibraryToolbarDelegate
- (NSArray<NSToolbarItemIdentifier> *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
Q_UNUSED(toolbar);
return @[
@"Back",
@"Forward",
@"Settings",
@"Server",
@"Help",
NSToolbarSpaceItemIdentifier,
@"ToggleView",
NSToolbarSpaceItemIdentifier,
@"Search",
];
}
- (NSArray<NSToolbarItemIdentifier> *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
Q_UNUSED(toolbar);
return @[
@"Back",
@"Forward",
@"Settings",
@"Server",
@"Help",
@"ToggleView",
@"Search",
NSToolbarSpaceItemIdentifier,
];
}
/*
- (NSArray *)toolbarSelectableItemIdentifiers: (NSToolbar *)toolbar
{
Q_UNUSED(toolbar);
NSMutableArray *array = [[NSMutableArray alloc] init];
QList<QMacToolBarItem *> items = mytoolbar->items();
foreach (const QMacToolBarItem * item, items) {
[array addObject : item->nativeToolBarItem().itemIdentifier];
}
return array;
//NSMutableArray *array = toolbarPrivate->getItemIdentifiers(toolbarPrivate->items, true);
//[array addObjectsFromArray:toolbarPrivate->getItemIdentifiers(toolbarPrivate->allowedItems, true)];
//return array;
}*/
- (IBAction)itemClicked:(id)sender
{
NSToolbarItem *item = reinterpret_cast<NSToolbarItem *>(sender);
QString identifier = QString::fromNSString([item itemIdentifier]);
QAction *action = mytoolbar->actionFromIdentifier(identifier);
;
if (action != nullptr) {
action->trigger();
}
}
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)willBeInserted
{
Q_UNUSED(toolbar);
Q_UNUSED(willBeInserted);
QString identifier = QString::fromNSString(itemIdentifier);
if (identifier == "Search") {
NSSearchToolbarItem *searchItem = [[NSSearchToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
searchItem.resignsFirstResponderWithCancel = true;
searchItem.searchField.delegate = id<NSSearchFieldDelegate>(mytoolbar->getSearchEditDelegate());
searchItem.toolTip = @"Search";
return searchItem;
}
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
toolbarItem.target = self;
toolbarItem.action = @selector(itemClicked:);
QAction *action = mytoolbar->actionFromIdentifier(identifier);
if (identifier == "Back") {
toolbarItem.navigational = YES;
} else if (identifier == "Forward") {
toolbarItem.navigational = YES;
}
bindActionToNSToolbarItem(action, toolbarItem);
return toolbarItem;
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)item
{
QString identifier = QString::fromNSString([item itemIdentifier]);
if (identifier == "Search") {
return YES;
}
QAction *action = mytoolbar->actionFromIdentifier(identifier);
if (action == nullptr) {
return NO;
}
return action->isEnabled();
}
@end
@interface YACReaderLibrarySearchDelegate : NSObject <NSSearchFieldDelegate> {
@public
YACReaderMacOSXToolbar *mytoolbar;
}
@end
@implementation YACReaderLibrarySearchDelegate
- (void)searchFieldDidStartSearching:(NSSearchField *)sender
{
}
- (void)searchFieldDidEndSearching:(NSSearchField *)sender
{
[sender resignFirstResponder];
}
- (void)controlTextDidChange:(NSNotification *)notification
{
NSSearchField *searchField = notification.object;
NSLog(@"Search text changed: %@", searchField.stringValue);
mytoolbar->emitFilterChange(QString::fromNSString(searchField.stringValue));
}
@end
YACReaderMacOSXToolbar::YACReaderMacOSXToolbar(QWidget *parent)
: YACReaderMainToolBar(parent)
{
@ -467,22 +753,159 @@ void YACReaderMacOSXToolbar::updateViewSelectorIcon(const QIcon &icon)
void YACReaderMacOSXToolbar::attachToWindow(QMainWindow *window)
{
auto toolbar = new QToolBar();
NSView *nsview = (NSView *)window->winId();
NSWindow *nswindow = [nsview window];
toolbar->addWidget(this);
toolbar->setMovable(false);
YACReaderLibrarySearchDelegate *searchDelegate = [[YACReaderLibrarySearchDelegate alloc] init];
this->searchEditDelegate = searchDelegate;
searchDelegate->mytoolbar = this;
window->addToolBar(toolbar);
// Create the NSToolbar
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
[toolbar setShowsBaselineSeparator:false];
__auto_type delegate = [[YACReaderLibraryToolbarDelegate alloc] init];
delegate->mytoolbar = this;
[toolbar setDelegate:delegate];
[nswindow setToolbar:toolbar];
}
void YACReaderMacOSXToolbar::paintEvent(QPaintEvent *)
{
}
QAction *YACReaderMacOSXToolbar::actionFromIdentifier(const QString &identifier)
{
if (identifier == "Back") {
return backButton->defaultAction();
} else if (identifier == "Forward") {
return forwardButton->defaultAction();
} else if (identifier == "Settings") {
return settingsButton->defaultAction();
} else if (identifier == "Server") {
return serverButton->defaultAction();
} else if (identifier == "Help") {
return helpButton->defaultAction();
} else if (identifier == "ToggleView") {
return toggleComicsViewButton->defaultAction();
}
return nullptr;
}
#else
@interface YACReaderToolbarDelegate : NSObject <NSToolbarDelegate> {
@public
YACReaderMacOSXToolbar *mytoolbar;
}
- (IBAction)itemClicked:(id)sender;
@end
@implementation YACReaderToolbarDelegate
- (NSArray<NSToolbarItemIdentifier> *)toolbarDefaultItemIdentifiers:(NSToolbar *)toolbar
{
Q_UNUSED(toolbar);
auto actions = mytoolbar->actions();
NSMutableArray<NSToolbarItemIdentifier> *identifiers = [NSMutableArray arrayWithCapacity:actions.size()];
for (QAction *action : actions) {
[identifiers addObject:[NSString stringWithFormat:@"action_%p", action]];
}
return identifiers;
}
- (NSArray<NSToolbarItemIdentifier> *)toolbarAllowedItemIdentifiers:(NSToolbar *)toolbar
{
Q_UNUSED(toolbar);
auto actions = mytoolbar->actions();
NSMutableArray<NSToolbarItemIdentifier> *identifiers = [NSMutableArray arrayWithCapacity:actions.size()];
for (QAction *action : actions) {
[identifiers addObject:[NSString stringWithFormat:@"action_%p", action]];
}
return identifiers;
}
// - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar *)toolbar
// {
// Q_UNUSED(toolbar);
// auto actions = mytoolbar->actions();
// NSMutableArray<NSToolbarItemIdentifier> *identifiers = [NSMutableArray arrayWithCapacity:actions.size()];
// for (QAction *action : actions) {
// if (action->isCheckable()) {
// [identifiers addObject:[NSString stringWithFormat:@"action_%p", action]];
// }
// }
// return identifiers;
// }
- (IBAction)itemClicked:(id)sender
{
NSToolbarItem *item = reinterpret_cast<NSToolbarItem *>(sender);
NSString *itemIdentifier = [item itemIdentifier];
auto actions = mytoolbar->actions();
for (QAction *action : actions) {
if ([itemIdentifier isEqualTo:[NSString stringWithFormat:@"action_%p", action]]) {
action->trigger();
}
}
}
- (NSToolbarItem *)toolbar:(NSToolbar *)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL)willBeInserted
{
Q_UNUSED(toolbar);
Q_UNUSED(willBeInserted);
NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier];
toolbarItem.target = self;
toolbarItem.action = @selector(itemClicked:);
auto actions = mytoolbar->actions();
for (QAction *action : actions) {
if ([itemIdentifier isEqualTo:[NSString stringWithFormat:@"action_%p", action]]) {
bindActionToNSToolbarItem(action, toolbarItem, QColor(200, 200, 200));
}
}
return toolbarItem;
}
- (BOOL)validateToolbarItem:(NSToolbarItem *)item
{
NSString *itemIdentifier = [item itemIdentifier];
auto actions = mytoolbar->actions();
for (QAction *action : actions) {
if ([itemIdentifier isEqualTo:[NSString stringWithFormat:@"action_%p", action]]) {
return action->isEnabled();
}
}
return NO;
}
@end
YACReaderMacOSXToolbar::YACReaderMacOSXToolbar(QWidget *parent)
: QToolBar(parent)
: QWidget(parent)
{
setMovable(false);
setIconSize(QSize(24, 24));
@ -490,14 +913,42 @@ YACReaderMacOSXToolbar::YACReaderMacOSXToolbar(QWidget *parent)
void YACReaderMacOSXToolbar::attachToWindow(QMainWindow *window)
{
window->setUnifiedTitleAndToolBarOnMac(true);
window->addToolBar(this);
NSView *nsview = (NSView *)window->winId();
NSWindow *nswindow = [nsview window];
NSToolbar *toolbar = [[NSToolbar alloc] initWithIdentifier:@"mainToolbar"];
[toolbar setDisplayMode:NSToolbarDisplayModeIconOnly];
[toolbar setShowsBaselineSeparator:false];
__auto_type delegate = [[YACReaderToolbarDelegate alloc] init];
delegate->mytoolbar = this;
[toolbar setDelegate:delegate];
[nswindow setToolbar:toolbar];
}
void YACReaderMacOSXToolbar::addStretch()
{
}
void YACReaderMacOSXToolbar::setHidden(bool hidden)
{
NSView *nsView = reinterpret_cast<NSView *>(this->winId());
NSWindow *window = [nsView window];
if (window && window.toolbar) {
window.toolbar.visible = !hidden;
}
}
void YACReaderMacOSXToolbar::show()
{
setHidden(false);
}
void YACReaderMacOSXToolbar::hide()
{
setHidden(true);
}
#endif
#endif

View File

@ -285,7 +285,7 @@ QWidget *YACReaderRatingDelegate::createEditor(QWidget *parent,
void YACReaderRatingDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int rating = ((ComicItem *)index.internalPointer())->data(11).toInt();
int rating = ((ComicItem *)index.internalPointer())->data(ComicModel::Rating).toInt();
StarRating starRating(rating);

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 B

BIN
images/loadCustomCover.svg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 292 B

BIN
images/resetCover.svg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

View File

@ -74,6 +74,8 @@ public:
#define SET_FOLDER_AS_NORMAL_ACTION_YL "SET_FOLDER_AS_NORMAL_ACTION_YL"
#define SET_FOLDER_AS_WEB_COMIC_ACTION_YL "SET_FOLDER_AS_WEB_COMIC_ACTION_YL"
#define SET_FOLDER_AS_YONKOMA_ACTION_YL "SET_FOLDER_AS_YONKOMA_ACTION_YL"
#define SET_FOLDER_COVER_ACTION_YL "SET_FOLDER_COVER_ACTION_YL"
#define DELETE_CUSTOM_FOLDER_COVER_ACTION_YL "DELETE_CUSTOM_FOLDER_COVER_ACTION_YL"
#define OPEN_CONTAINING_FOLDER_COMIC_ACTION_YL "OPEN_CONTAINING_FOLDER_COMIC_ACTION_YL"
#define RESET_COMIC_RATING_ACTION_YL "RESET_COMIC_RATING_ACTION_YL"
#define SELECT_ALL_COMICS_ACTION_YL "SELECT_ALL_COMICS_ACTION_YL"

View File

@ -209,6 +209,7 @@ void waitAndPrint(const ConcurrentQueue &queue, const QueueControlMessagePrinter
printer.printEndWaitingMessage();
}
#if QT_VERSION < QT_VERSION_CHECK(6, 9, 0)
template<typename T, std::size_t size>
QDebug operator<<(QDebug debug, const std::array<T, size> &array)
{
@ -225,6 +226,7 @@ QDebug operator<<(QDebug debug, const std::array<T, size> &array)
return debug;
}
#endif
using RandomEngine = std::mt19937_64;

View File

@ -180,4 +180,29 @@ KDSignalLeadingDebouncer::KDSignalLeadingDebouncer(QObject *parent)
KDSignalLeadingDebouncer::~KDSignalLeadingDebouncer() = default;
KDStringSignalDebouncer::KDStringSignalDebouncer(QObject *parent)
: QObject(parent), m_debouncer(KDGenericSignalThrottler::Kind::Debouncer,
KDGenericSignalThrottler::EmissionPolicy::Trailing,
parent)
{
connect(&m_debouncer, &KDGenericSignalThrottler::triggered,
this, [=] {
emit triggered(this->value);
});
}
void KDStringSignalDebouncer::setTimeout(int msec) {
m_debouncer.setTimeout(msec);
}
int KDStringSignalDebouncer::timeout() const {
return m_debouncer.timeout();
}
void KDStringSignalDebouncer::throttle(QString value) {
this->value = value;
m_debouncer.throttle();
}
} // namespace KDToolBox

View File

@ -125,6 +125,26 @@ public:
~KDSignalLeadingDebouncer() override;
};
class KDStringSignalDebouncer : public QObject {
Q_OBJECT
public:
explicit KDStringSignalDebouncer(QObject *parent = nullptr);
void setTimeout(int msec);
int timeout() const;
public slots:
void throttle(QString value);
signals:
void triggered(QString value);
private:
QString value;
KDGenericSignalThrottler m_debouncer;
};
} // namespace KDToolBox
#endif // KDSIGNALTHROTTLER_H