diff --git a/CHANGELOG.md b/CHANGELOG.md index a49e8047b..7c1a5b863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,22 @@ Version counting is based on semantic versioning (Major.Feature.Patch) +## WIP + +### YACReader +* Fix segfault (or worse) when exiting YACReader while processing a comic. +* Fix last read page calculation in double page mode. + +### YACReaderLibrary +* Fix drag&drop in the comics grid view. +* Detect back/forward mouse buttons to move back and forward through the browsing history. +* Fix crash when disabling the server. + +### All apps +* Run logger in a dedicated thread to avoid segfaults at application shutdown +* Add support for poppler-qt6 pdf backend +* Remove image allocation limit. + ## 9.10 ### YACReader diff --git a/YACReader/YACReader.pro b/YACReader/YACReader.pro index 855de657d..49684d372 100644 --- a/YACReader/YACReader.pro +++ b/YACReader/YACReader.pro @@ -214,28 +214,16 @@ win32 { $(COPY) $$shell_path($${SOURCE_QM_DIR}) $$shell_path($${DEPLOYMENT_OUT_QM_DIR}) } else { LRELEASE_DIR = ../release/languages/ + QM_FILES_INSTALL_PATH = $$DATADIR/yacreader/languages } unix:!macx { -# set install prefix if it's empty -isEmpty(PREFIX) { - PREFIX = /usr -} -isEmpty(BINDIR) { - BINDIR = $$PREFIX/bin -} -isEmpty(LIBDIR) { - LIBDIR = $$PREFIX/lib -} -isEmpty(DATADIR) { - DATADIR = $$PREFIX/share -} DEFINES += "LIBDIR=\\\"$$LIBDIR\\\"" "DATADIR=\\\"$$DATADIR\\\"" #MAKE INSTALL -INSTALLS += bin docs icon desktop translation manpage +INSTALLS += bin docs icon desktop manpage bin.path = $$BINDIR isEmpty(DESTDIR) { @@ -256,9 +244,6 @@ icon.files = ../YACReader.svg desktop.path = $$DATADIR/applications desktop.files = ../YACReader.desktop -translation.path = $$DATADIR/yacreader/languages -translation.files = ../release/languages/yacreader_* - manpage.path = $$DATADIR/man/man1 manpage.files = ../YACReader.1 diff --git a/YACReader/main.cpp b/YACReader/main.cpp index d7a2b48c1..521c9dbfd 100644 --- a/YACReader/main.cpp +++ b/YACReader/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include "main_window_viewer.h" #include "configuration.h" @@ -97,6 +98,10 @@ int main(int argc, char *argv[]) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QImageReader::setAllocationLimit(0); +#endif + #if defined(_MSC_VER) && defined(_DEBUG) _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif diff --git a/YACReader/render.cpp b/YACReader/render.cpp index a0218a687..fee12b11d 100644 --- a/YACReader/render.cpp +++ b/YACReader/render.cpp @@ -382,20 +382,23 @@ Render::Render() Render::~Render() { - if (comic != nullptr) { - comic->moveToThread(QApplication::instance()->thread()); - comic->deleteLater(); - } - - foreach (PageRender *pr, pageRenders) - if (pr != nullptr) { - if (pr->wait()) - delete pr; + for (auto *pr : pageRenders) { + if (pr != nullptr && pr->wait()) { + delete pr; } + } // TODO move to share_ptr - foreach (ImageFilter *filter, filters) + for (auto *filter : filters) { delete filter; + } + + if (comic != nullptr) { + comic->invalidate(); + comic->deleteLater(); + comic->thread()->quit(); + comic->thread()->wait(); + } } // Este método se encarga de forzar el renderizado de las páginas. // Actualiza el buffer según es necesario. diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp index 4894f25bd..34598e473 100644 --- a/YACReader/viewer.cpp +++ b/YACReader/viewer.cpp @@ -1128,7 +1128,9 @@ void Viewer::updateComic(ComicDB &comic) if (!doublePage || (doublePage && render->currentPageIsDoublePage() == false)) { comic.info.currentPage = render->getIndex() + 1; } else { - if (!(render->getIndex() + 1 == comic.info.currentPage || render->getIndex() + 2 == comic.info.currentPage)) { + if (doublePage && render->currentPageIsDoublePage() && (render->getIndex() + 2 >= render->numPages())) { + comic.info.currentPage = std::min(render->numPages(), render->getIndex() + 2); + } else { comic.info.currentPage = std::min(render->numPages(), render->getIndex() + 1); } } diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index b66127bff..41edbde50 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -307,6 +307,7 @@ win32 { $(COPY) $$shell_path($${SOURCE_QM_DIR}) $$shell_path($${DEPLOYMENT_OUT_QM_DIR}) } else { LRELEASE_DIR = ../release/languages/ + QM_FILES_INSTALL_PATH = $$DATADIR/yacreader/languages } #QML/GridView @@ -328,24 +329,11 @@ unix:!macx:RESOURCES += qml_win.qrc macx:RESOURCES += qml_osx.qrc unix:!macx { -#set install prefix if it's empty -isEmpty(PREFIX) { - PREFIX = /usr -} -isEmpty(BINDIR) { - BINDIR = $$PREFIX/bin -} -isEmpty(LIBDIR) { - LIBDIR = $$PREFIX/lib -} -isEmpty(DATADIR) { - DATADIR = $$PREFIX/share -} DEFINES += "LIBDIR=\\\"$$LIBDIR\\\"" "DATADIR=\\\"$$DATADIR\\\"" "BINDIR=\\\"$$BINDIR\\\"" #MAKE INSTALL -INSTALLS += bin icon desktop server translation manpage +INSTALLS += bin icon desktop server manpage bin.path = $$BINDIR isEmpty(DESTDIR) { @@ -363,9 +351,6 @@ icon.files = ../YACReaderLibrary.svg desktop.path = $$DATADIR/applications desktop.files = ../YACReaderLibrary.desktop -translation.path = $$DATADIR/yacreader/languages -translation.files = ../release/languages/yacreaderlibrary_* - manpage.path = $$DATADIR/man/man1 manpage.files = ../YACReaderLibrary.1 } diff --git a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp index bf26fe582..28781adab 100644 --- a/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp +++ b/YACReaderLibrary/comic_vine/comic_vine_dialog.cpp @@ -33,6 +33,7 @@ ComicVineDialog::ComicVineDialog(QWidget *parent) : QDialog(parent) { setWindowFlags(Qt::Window); + setModal(true); doLayout(); doStackedWidgets(); diff --git a/YACReaderLibrary/import_widget.cpp b/YACReaderLibrary/import_widget.cpp index 4a266268c..7d721b48e 100644 --- a/YACReaderLibrary/import_widget.cpp +++ b/YACReaderLibrary/import_widget.cpp @@ -11,7 +11,7 @@ #include #include // TODO: is QGLWidget needed here??? -//#include +// #include #include #include #include diff --git a/YACReaderLibrary/initial_comic_info_extractor.cpp b/YACReaderLibrary/initial_comic_info_extractor.cpp index 2d889f4c7..763907f54 100644 --- a/YACReaderLibrary/initial_comic_info_extractor.cpp +++ b/YACReaderLibrary/initial_comic_info_extractor.cpp @@ -27,38 +27,31 @@ void InitialComicInfoExtractor::extract() #ifndef NO_PDF if (fi.suffix().compare("pdf", Qt::CaseInsensitive) == 0) { #if defined Q_OS_MAC && defined USE_PDFKIT - MacOSXPDFComic *pdfComic = new MacOSXPDFComic(); + auto pdfComic = std::make_unique(); if (!pdfComic->openComic(_fileSource)) { - delete pdfComic; - // QImage p; - // p.load(":/images/notCover.png"); - // p.save(_target); return; } #elif defined USE_PDFIUM - auto pdfComic = new PdfiumComic(); + auto pdfComic = std::make_unique(); if (!pdfComic->openComic(_fileSource)) { - delete pdfComic; return; } #else - Poppler::Document *pdfComic = Poppler::Document::load(_fileSource); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + auto pdfComic = Poppler::Document::load(_fileSource); +#else + auto _pdfComic = Poppler::Document::load(_fileSource); + auto pdfComic = std::unique_ptr(_pdfComic); +#endif #endif - if (!pdfComic) { QLOG_WARN() << "Extracting cover: unable to open PDF file " << _fileSource; - // delete pdfComic; //TODO check if the delete is needed - pdfComic = 0; - // QImage p; - // p.load(":/images/notCover.png"); - // p.save(_target); return; } #if !defined USE_PDFKIT && !defined USE_PDFIUM // poppler only, not mac if (pdfComic->isLocked()) { QLOG_WARN() << "Extracting cover: unable to open PDF file " << _fileSource; - delete pdfComic; return; } #endif @@ -75,11 +68,7 @@ void InitialComicInfoExtractor::extract() saveCover(_target, p); } else if (_target != "") { QLOG_WARN() << "Extracting cover: requested cover index greater than numPages " << _fileSource; - // QImage p; - // p.load(":/images/notCover.png"); - // p.save(_target); } - delete pdfComic; } return; } diff --git a/YACReaderLibrary/library_window.cpp b/YACReaderLibrary/library_window.cpp index 2ce6ab4e0..0ac891e6e 100644 --- a/YACReaderLibrary/library_window.cpp +++ b/YACReaderLibrary/library_window.cpp @@ -59,7 +59,7 @@ #include "comic_vine_dialog.h" #include "api_key_dialog.h" -//#include "yacreader_social_dialog.h" +// #include "yacreader_social_dialog.h" #include "comics_view.h" @@ -139,6 +139,29 @@ void LibraryWindow::afterLaunchTasks() } } +bool LibraryWindow::eventFilter(QObject *object, QEvent *event) +{ + if (this->isActiveWindow()) { + if (event->type() == QEvent::MouseButtonRelease) { + auto mouseEvent = static_cast(event); + + if (mouseEvent->button() == Qt::ForwardButton) { + forwardAction->trigger(); + event->accept(); + return true; + } + + if (mouseEvent->button() == Qt::BackButton) { + backAction->trigger(); + event->accept(); + return true; + } + } + } + + return QMainWindow::eventFilter(object, event); +} + void LibraryWindow::createSettings() { settings = new QSettings(YACReader::getSettingsPath() + "/YACReaderLibrary.ini", QSettings::IniFormat); // TODO unificar la creación del fichero de config con el servidor diff --git a/YACReaderLibrary/library_window.h b/YACReaderLibrary/library_window.h index fc12dad1b..acbf4ee2c 100644 --- a/YACReaderLibrary/library_window.h +++ b/YACReaderLibrary/library_window.h @@ -434,6 +434,8 @@ public slots: void afterLaunchTasks(); + bool eventFilter(QObject *object, QEvent *event) override; + private: //! @brief Exits search mode if it is active. //! @return true If the search mode was active when this function was called. diff --git a/YACReaderLibrary/main.cpp b/YACReaderLibrary/main.cpp index 170d71a96..70e01b25c 100644 --- a/YACReaderLibrary/main.cpp +++ b/YACReaderLibrary/main.cpp @@ -11,6 +11,7 @@ #include #endif #include +#include #include "yacreader_global.h" #include "yacreader_http_server.h" @@ -130,6 +131,10 @@ int main(int argc, char **argv) QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QImageReader::setAllocationLimit(0); +#endif + QApplication app(argc, argv); #ifdef FORCE_ANGLE @@ -269,6 +274,8 @@ int main(int argc, char **argv) } #endif + app.installEventFilter(mw); + int ret = app.exec(); QLOG_INFO() << "YACReaderLibrary closed with exit code :" << ret; diff --git a/YACReaderLibrary/qml/GridComicsView.qml b/YACReaderLibrary/qml/GridComicsView.qml index d5366df51..1786fca00 100644 --- a/YACReaderLibrary/qml/GridComicsView.qml +++ b/YACReaderLibrary/qml/GridComicsView.qml @@ -18,640 +18,600 @@ SplitView { color: info_container.color } -Rectangle { - id: main - clip: true - - Image { - id: backgroundImg - anchors.fill: parent - source: backgroundImage - fillMode: Image.PreserveAspectCrop - smooth: true - mipmap: true - asynchronous : true - cache: false //TODO clear cache only when it is needed - opacity: 0 - visible: false - } - - FastBlur { - anchors.fill: backgroundImg - source: backgroundImg - radius: backgroundBlurRadius - opacity: backgroundBlurOpacity - visible: backgroundBlurVisible - } - - color: backgroundColor - width: parent.width - (info_container.visible ? info_container.width : 0) - SplitView.fillWidth: true - SplitView.minimumWidth: coverWidth + 100 - height: parent.height - anchors.margins: 0 - - Component { - id: appDelegate - Rectangle - { - id: cell - width: grid.cellWidth - height: grid.cellHeight - color: "#00000000" - - scale: mouseArea.containsMouse ? 1.025 : 1 - - Behavior on scale { - NumberAnimation { duration: 90 } - } + Rectangle { + id: main + clip: true - DropShadow { - anchors.fill: realCell - horizontalOffset: 0 - verticalOffset: 0 - radius: 8.0 - samples: 17 - color: "#FF000000" - source: realCell - visible: (Qt.platform.os === "osx") ? false : true; - } + Image { + id: backgroundImg + anchors.fill: parent + source: backgroundImage + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: false //TODO clear cache only when it is needed + opacity: 0 + visible: false + } - Rectangle { - id: realCell - - property int position : 0 - property bool dragging: false; - Drag.active: mouseArea.drag.active - Drag.hotSpot.x: 32 - Drag.hotSpot.y: 32 - Drag.dragType: Drag.Automatic - //Drag.mimeData: { "x": 1 } - Drag.proposedAction: Qt.CopyAction - Drag.onActiveChanged: { - if(!dragging) - { - dragManager.startDrag(); - dragging = true; - }else - dragging = false; - } + FastBlur { + anchors.fill: backgroundImg + source: backgroundImg + radius: backgroundBlurRadius + opacity: backgroundBlurOpacity + visible: backgroundBlurVisible + } - width: itemWidth - height: itemHeight + color: backgroundColor + width: parent.width - (info_container.visible ? info_container.width : 0) + SplitView.fillWidth: true + SplitView.minimumWidth: coverWidth + 100 + height: parent.height + anchors.margins: 0 - color: ((dummyValue || !dummyValue) && comicsSelectionHelper.isSelectedIndex(index))?selectedColor:cellColor; - //border.color: ((dummyValue || !dummyValue) && comicsSelectionHelper.isSelectedIndex(index))?selectedBorderColor:borderColor; - //border.width: ?1:0; - anchors.horizontalCenter: parent.horizontalCenter + Component { + id: appDelegate + Rectangle + { + id: cell + width: grid.cellWidth + height: grid.cellHeight + color: "#00000000" - Rectangle - { - id: mouseOverBorder + scale: mouseArea.containsMouse ? 1.025 : 1 - property bool commonBorder : false + Behavior on scale { + NumberAnimation { duration: 90 } + } - property int lBorderwidth : 2 - property int rBorderwidth : 2 - property int tBorderwidth : 2 - property int bBorderwidth : 2 + DropShadow { + anchors.fill: realCell + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + samples: 17 + color: "#FF000000" + source: realCell + visible: (Qt.platform.os === "osx") ? false : true; + } - property int commonBorderWidth : 1 + Rectangle { + id: realCell + + property int position : 0 + property bool dragging: false; + Drag.active: mouseArea.drag.active + Drag.hotSpot.x: 32 + Drag.hotSpot.y: 32 + Drag.dragType: Drag.Automatic + //Drag.mimeData: { "x": 1 } + Drag.proposedAction: Qt.CopyAction + Drag.onActiveChanged: { + if(!dragging) + { + dragManager.startDrag(); + dragging = true; + }else + dragging = false; + } - z : -1 + width: itemWidth + height: itemHeight - color: "#00000000" + color: ((dummyValue || !dummyValue) && comicsSelectionHelper.isSelectedIndex(index))?selectedColor:cellColor; + //border.color: ((dummyValue || !dummyValue) && comicsSelectionHelper.isSelectedIndex(index))?selectedBorderColor:borderColor; + //border.width: ?1:0; + anchors.horizontalCenter: parent.horizontalCenter - anchors + Rectangle { - left: parent.left - right: parent.right - top: parent.top - bottom: parent.bottom - - topMargin : commonBorder ? -commonBorderWidth : -tBorderwidth - bottomMargin : commonBorder ? -commonBorderWidth : -bBorderwidth - leftMargin : commonBorder ? -commonBorderWidth : -lBorderwidth - rightMargin : commonBorder ? -commonBorderWidth : -rBorderwidth - } + id: mouseOverBorder - border.color: (Qt.platform.os === "osx") ? selectedBorderColor : "#ffcc00" - border.width: 3 + property bool commonBorder : false - opacity: (dummyValue || !dummyValue) && comicsSelectionHelper.isSelectedIndex(index) ? 1 : 0 + property int lBorderwidth : 2 + property int rBorderwidth : 2 + property int tBorderwidth : 2 + property int bBorderwidth : 2 - Behavior on opacity { - NumberAnimation { duration: 300 } - } - - radius : 2 - } + property int commonBorderWidth : 1 + z : -1 - MouseArea { - id: mouseArea - drag.target: realCell + color: "#00000000" - drag.minimumX: 0 - drag.maximumX: 0 - drag.minimumY: 0 - drag.maximumY: 0 + anchors + { + left: parent.left + right: parent.right + top: parent.top + bottom: parent.bottom + + topMargin : commonBorder ? -commonBorderWidth : -tBorderwidth + bottomMargin : commonBorder ? -commonBorderWidth : -bBorderwidth + leftMargin : commonBorder ? -commonBorderWidth : -lBorderwidth + rightMargin : commonBorder ? -commonBorderWidth : -rBorderwidth + } - anchors.fill: parent - acceptedButtons: Qt.LeftButton | Qt.RightButton + border.color: (Qt.platform.os === "osx") ? selectedBorderColor : "#ffcc00" + border.width: 3 - hoverEnabled: true + opacity: (dummyValue || !dummyValue) && comicsSelectionHelper.isSelectedIndex(index) ? 1 : 0 - onDoubleClicked: { - comicsSelectionHelper.clear(); + Behavior on opacity { + NumberAnimation { duration: 300 } + } - comicsSelectionHelper.selectIndex(index); - grid.currentIndex = index; - currentIndexHelper.selectedItem(index); + radius : 2 } - function selectAll(from,to) - { - for(var i = from;i<=to;i++) - { - comicsSelectionHelper.selectIndex(i); - } - } - onPressed: mouse => { - var ci = grid.currentIndex; //save current index + MouseArea { + id: mouseArea + drag.target: realCell - /*if(mouse.button != Qt.RightButton && !(mouse.modifiers & Qt.ControlModifier || mouse.modifiers & Qt.ShiftModifier)) - { - if(!comicsSelectionHelper.isSelectedIndex(index)) - comicsSelectionHelper.clear(); - }*/ + drag.minimumX: 0 + drag.maximumX: 0 + drag.minimumY: 0 + drag.maximumY: 0 - if(mouse.modifiers & Qt.ShiftModifier) - if(index < ci) - { - selectAll(index,ci); - grid.currentIndex = index; - } - else if (index > ci) - { - selectAll(ci,index); - grid.currentIndex = index; - } + anchors.fill: parent + acceptedButtons: Qt.LeftButton | Qt.RightButton - mouse.accepted = true; + hoverEnabled: true - if(mouse.button === Qt.RightButton) // context menu is requested - { - if(!comicsSelectionHelper.isSelectedIndex(index)) //the context menu is requested outside the current selection, the selection will be - { - currentIndexHelper.setCurrentIndex(index) - grid.currentIndex = index; - } + onDoubleClicked: { + comicsSelectionHelper.clear(); - var coordinates = main.mapFromItem(realCell,mouseX,mouseY) - contextMenuHelper.requestedContextMenu(Qt.point(coordinates.x,coordinates.y)); - mouse.accepted = false; + comicsSelectionHelper.selectIndex(index); + grid.currentIndex = index; + currentIndexHelper.selectedItem(index); + } - } else //left button + function selectAll(from,to) { - - if(mouse.modifiers & Qt.ControlModifier) + for(var i = from;i<=to;i++) { - if(comicsSelectionHelper.isSelectedIndex(index)) - { - if(comicsSelectionHelper.numItemsSelected()>1) - { - comicsSelectionHelper.deselectIndex(index); - if(grid.currentIndex === index) - grid.currentIndex = comicsSelectionHelper.lastSelectedIndex(); - } - } - else - { - comicsSelectionHelper.selectIndex(index); - grid.currentIndex = index; - } - } - - if(mouse.button !== Qt.RightButton && !(mouse.modifiers & Qt.ControlModifier || mouse.modifiers & Qt.ShiftModifier)) //just left button click - { - if(comicsSelectionHelper.isSelectedIndex(index)) //the context menu is requested outside the current selection, the selection will be - { - - } - else - { - currentIndexHelper.setCurrentIndex(index) - } - - grid.currentIndex = index; + comicsSelectionHelper.selectIndex(i); } } - } + onPressed: mouse => { + var ci = grid.currentIndex; //save current index - onReleased: mouse => { - if(mouse.button === Qt.LeftButton && !(mouse.modifiers & Qt.ControlModifier || mouse.modifiers & Qt.ShiftModifier)) + /*if(mouse.button != Qt.RightButton && !(mouse.modifiers & Qt.ControlModifier || mouse.modifiers & Qt.ShiftModifier)) { - if(comicsSelectionHelper.isSelectedIndex(index)) - { - currentIndexHelper.setCurrentIndex(index) - grid.currentIndex = index; - } - } + if(!comicsSelectionHelper.isSelectedIndex(index)) + comicsSelectionHelper.clear(); + }*/ + + if(mouse.modifiers & Qt.ShiftModifier) + if(index < ci) + { + selectAll(index,ci); + grid.currentIndex = index; + } + else if (index > ci) + { + selectAll(ci,index); + grid.currentIndex = index; + } + + mouse.accepted = true; + + if(mouse.button === Qt.RightButton) // context menu is requested + { + if(!comicsSelectionHelper.isSelectedIndex(index)) //the context menu is requested outside the current selection, the selection will be + { + currentIndexHelper.setCurrentIndex(index) + grid.currentIndex = index; + } + + var coordinates = main.mapFromItem(realCell,mouseX,mouseY) + contextMenuHelper.requestedContextMenu(Qt.point(coordinates.x,coordinates.y)); + mouse.accepted = false; + + } else //left button + { + + if(mouse.modifiers & Qt.ControlModifier) + { + if(comicsSelectionHelper.isSelectedIndex(index)) + { + if(comicsSelectionHelper.numItemsSelected()>1) + { + comicsSelectionHelper.deselectIndex(index); + if(grid.currentIndex === index) + grid.currentIndex = comicsSelectionHelper.lastSelectedIndex(); + } + } + else + { + comicsSelectionHelper.selectIndex(index); + grid.currentIndex = index; + } + } + + if(mouse.button !== Qt.RightButton && !(mouse.modifiers & Qt.ControlModifier || mouse.modifiers & Qt.ShiftModifier)) //just left button click + { + if(comicsSelectionHelper.isSelectedIndex(index)) //the context menu is requested outside the current selection, the selection will be + { + + } + else + { + currentIndexHelper.setCurrentIndex(index) + } + + grid.currentIndex = index; + } + } + + } + + onReleased: mouse => { + if(mouse.button === Qt.LeftButton && !(mouse.modifiers & Qt.ControlModifier || mouse.modifiers & Qt.ShiftModifier)) + { + if(comicsSelectionHelper.isSelectedIndex(index)) + { + currentIndexHelper.setCurrentIndex(index) + grid.currentIndex = index; + } + } + } } } - } - /**/ - - //cover - Image { - id: coverElement - width: coverWidth - height: coverHeight - anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} - source: cover_path - fillMode: Image.PreserveAspectCrop - smooth: true - mipmap: true - asynchronous : true - cache: false //TODO clear cache only when it is needed + /**/ + + //cover + Image { + id: coverElement + width: coverWidth + height: coverHeight + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + source: cover_path + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: false //TODO clear cache only when it is needed - } + } - //border - Rectangle { - width: coverElement.width - height: coverElement.height - anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} - color: "transparent" - border { - color: "#20FFFFFF" - width: 1 + //border + Rectangle { + width: coverElement.width + height: coverElement.height + anchors {horizontalCenter: parent.horizontalCenter; top: realCell.top; topMargin: 0} + color: "transparent" + border { + color: "#20FFFFFF" + width: 1 + } } - } - //mark - Image { - id: mark - width: 23 - height: 23 - source: read_column&&show_marks?"tick.png":has_been_opened&&show_marks?"reading.png":"" - anchors {right: coverElement.right; top: coverElement.top; topMargin: 9; rightMargin: 9} - asynchronous : true - } + //mark + Image { + id: mark + width: 23 + height: 23 + source: read_column&&show_marks?"tick.png":has_been_opened&&show_marks?"reading.png":"" + anchors {right: coverElement.right; top: coverElement.top; topMargin: 9; rightMargin: 9} + asynchronous : true + } - //title - Text { - id : titleText - anchors { top: coverElement.bottom; left: realCell.left; leftMargin: 4; rightMargin: 4; topMargin: 4; } - width: itemWidth - 8 - maximumLineCount: 2 - wrapMode: Text.WordWrap - text: title - elide: Text.ElideRight - color: titleColor - clip: true - font.letterSpacing: fontSpacing - font.pointSize: fontSize - font.family: fontFamily - } + //title + Text { + id : titleText + anchors { top: coverElement.bottom; left: realCell.left; leftMargin: 4; rightMargin: 4; topMargin: 4; } + width: itemWidth - 8 + maximumLineCount: 2 + wrapMode: Text.WordWrap + text: title + elide: Text.ElideRight + color: titleColor + clip: true + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } - //number - Text { - anchors {bottom: realCell.bottom; left: realCell.left; margins: 4} - text: number?"#"+number:"" - color: textColor - font.letterSpacing: fontSpacing - font.pointSize: fontSize - font.family: fontFamily - } + //number + Text { + anchors {bottom: realCell.bottom; left: realCell.left; margins: 4} + text: number?"#"+number:"" + color: textColor + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } - //page icon - Image { - id: pageImage - anchors {bottom: realCell.bottom; right: realCell.right; bottomMargin: 5; rightMargin: 4; leftMargin: 4} - source: "page.png" - width: 8 - height: 10 - } + //page icon + Image { + id: pageImage + anchors {bottom: realCell.bottom; right: realCell.right; bottomMargin: 5; rightMargin: 4; leftMargin: 4} + source: "page.png" + width: 8 + height: 10 + } - //numPages - Text { - id: pages - anchors {bottom: realCell.bottom; right: pageImage.left; margins: 4} - text: has_been_opened?current_page+"/"+num_pages:num_pages - color: textColor - font.letterSpacing: fontSpacing - font.pointSize: fontSize - font.family: fontFamily - } + //numPages + Text { + id: pages + anchors {bottom: realCell.bottom; right: pageImage.left; margins: 4} + text: has_been_opened?current_page+"/"+num_pages:num_pages + color: textColor + font.letterSpacing: fontSpacing + font.pointSize: fontSize + font.family: fontFamily + } - //rating icon - Image { - id: ratingImage - anchors {bottom: realCell.bottom; right: pageImage.left; bottomMargin: 5; rightMargin: Math.floor(pages.width)+12} - source: "star.png" - width: 13 - height: 11 + //rating icon + Image { + id: ratingImage + anchors {bottom: realCell.bottom; right: pageImage.left; bottomMargin: 5; rightMargin: Math.floor(pages.width)+12} + source: "star.png" + width: 13 + height: 11 - MouseArea { - anchors.fill: parent - onPressed: { - console.log("rating"); - comicsSelectionHelper.clear(); - comicsSelectionHelper.selectIndex(index); - grid.currentIndex = index; - ratingConextMenu.popup(); + MouseArea { + anchors.fill: parent + onPressed: { + console.log("rating"); + comicsSelectionHelper.clear(); + comicsSelectionHelper.selectIndex(index); + grid.currentIndex = index; + ratingConextMenu.popup(); + } } - } - Menu { - background: Rectangle { - implicitWidth: 42 - implicitHeight: 100 - //border.color: "#222" - //color: "#444" - } + Menu { + background: Rectangle { + implicitWidth: 42 + implicitHeight: 100 + //border.color: "#222" + //color: "#444" + } - id: ratingConextMenu + id: ratingConextMenu - Action { text: "1"; enabled: true; onTriggered: comicRatingHelper.rate(index,1) } - Action { text: "2"; enabled: true; onTriggered: comicRatingHelper.rate(index,2) } - Action { text: "3"; enabled: true; onTriggered: comicRatingHelper.rate(index,3) } - Action { text: "4"; enabled: true; onTriggered: comicRatingHelper.rate(index,4) } - Action { text: "5"; enabled: true; onTriggered: comicRatingHelper.rate(index,5) } + Action { text: "1"; enabled: true; onTriggered: comicRatingHelper.rate(index,1) } + Action { text: "2"; enabled: true; onTriggered: comicRatingHelper.rate(index,2) } + Action { text: "3"; enabled: true; onTriggered: comicRatingHelper.rate(index,3) } + Action { text: "4"; enabled: true; onTriggered: comicRatingHelper.rate(index,4) } + Action { text: "5"; enabled: true; onTriggered: comicRatingHelper.rate(index,5) } - delegate: MenuItem { - implicitHeight: 30 + delegate: MenuItem { + implicitHeight: 30 + } } } - } - //comic rating - Text { - id: comicRating - anchors {bottom: realCell.bottom; right: ratingImage.left; margins: 4} - text: rating>0?rating:"-" - color: textColor + //comic rating + Text { + id: comicRating + anchors {bottom: realCell.bottom; right: ratingImage.left; margins: 4} + text: rating>0?rating:"-" + color: textColor + } } } - } - - Rectangle { - id: scrollView - objectName: "topScrollView" - anchors.fill: parent - anchors.margins: 0 - children: grid - - color: "transparent" - - function scrollToOrigin() { - grid.contentY = grid.originY - grid.contentX = grid.originX - } - DropArea { + Rectangle { + id: scrollView + objectName: "topScrollView" anchors.fill: parent + anchors.margins: 0 + children: grid - onEntered: { - if(drag.hasUrls) - { - if(dropManager.canDropUrls(drag.urls, drag.action)) - { - drag.accepted = true; - }else - drag.accepted = false; - } - else if (dropManager.canDropFormats(drag.formats)) { - drag.accepted = true; - } else - drag.accepted = false; - } + color: "transparent" - onDropped: { - if(drop.hasUrls && dropManager.canDropUrls(drop.urls, drop.action)) - { - dropManager.droppedFiles(drop.urls, drop.action); - } - else{ - if (dropManager.canDropFormats(drop.formats)) - { - var destItem = grid.itemAt(drop.x,drop.y + grid.contentY); - var destLocalX = grid.mapToItem(destItem,drop.x,drop.y + grid.contentY).x - var realIndex = grid.indexAt(drop.x,drop.y + grid.contentY); - - if(realIndex === -1) - realIndex = grid.count - 1; - - var destIndex = destLocalX < (grid.cellWidth / 2) ? realIndex : realIndex + 1; - dropManager.droppedComicsForResortingAt(drop.getDataAsString(), destIndex); - } - } + function scrollToOrigin() { + grid.contentY = grid.originY + grid.contentX = grid.originX } - } - - property Component currentComicView: Component { - id: currentComicView - Rectangle { - id: currentComicViewTopView - color: "#00000000" - - height: showCurrentComic ? 270 : 20 + property Component currentComicView: Component { + id: currentComicView Rectangle { - color: (Qt.platform.os === "osx") ? "#88FFFFFF" : "#88000000" + id: currentComicViewTopView + color: "#00000000" - id: currentComicVisualView + height: showCurrentComic ? 270 : 20 - width: main.width - height: 250 + Rectangle { + color: (Qt.platform.os === "osx") ? "#88FFFFFF" : "#88000000" - visible: showCurrentComic + id: currentComicVisualView - //cover - Image { - id: currentCoverElement - anchors.fill: parent + width: main.width + height: 250 - anchors.leftMargin: 15 - anchors.topMargin: 15 - anchors.bottomMargin: 15 - anchors.rightMargin: 15 - horizontalAlignment: Image.AlignLeft - anchors {horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 0} - source: comicsList.getCoverUrlPathForComicHash(currentComicInfo.hash.toString()) - fillMode: Image.PreserveAspectFit - smooth: true - mipmap: true - asynchronous : true - cache: false //TODO clear cache only when it is needed - } + visible: showCurrentComic - DropShadow { - anchors.fill: currentCoverElement - horizontalOffset: 0 - verticalOffset: 0 - radius: 8.0 - samples: 17 - color: "#FF000000" - source: currentCoverElement - visible: (Qt.platform.os === "osx") ? false : true; - } + //cover + Image { + id: currentCoverElement + anchors.fill: parent - ColumnLayout - { - id: currentComicInfoView + anchors.leftMargin: 15 + anchors.topMargin: 15 + anchors.bottomMargin: 15 + anchors.rightMargin: 15 + horizontalAlignment: Image.AlignLeft + anchors {horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 0} + source: comicsList.getCoverUrlPathForComicHash(currentComicInfo.hash.toString()) + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous : true + cache: false //TODO clear cache only when it is needed + } - x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin - //y: currentCoverElement.anchors.topMargin + DropShadow { + anchors.fill: currentCoverElement + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + samples: 17 + color: "#FF000000" + source: currentCoverElement + visible: (Qt.platform.os === "osx") ? false : true; + } - anchors.top: currentCoverElement.top - anchors.right: parent.right - anchors.left: readButton.left + ColumnLayout + { + id: currentComicInfoView - spacing: 9 + x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin + //y: currentCoverElement.anchors.topMargin - Text { - Layout.topMargin: 7 - Layout.fillWidth: true - Layout.rightMargin: 20 + anchors.top: currentCoverElement.top + anchors.right: parent.right + anchors.left: readButton.left - Layout.alignment: Qt.AlignTop | Qt.AlignLeft + spacing: 9 - id: currentComicInfoTitleView + Text { + Layout.topMargin: 7 + Layout.fillWidth: true + Layout.rightMargin: 20 - color: infoTitleColor - font.family: "Arial" - font.bold: true - font.pixelSize: 21 - wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignTop | Qt.AlignLeft - text: currentComic.getTitleIncludingNumber() - } + id: currentComicInfoTitleView - Flow { - spacing: 0 - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.fillWidth: true - Layout.fillHeight: false - - id: currentComicDetailsFlowView - property font infoFont: Qt.font({ - family: "Arial", - pixelSize: 14 - }); - property string infoFlowTextColor: infoTextColor + color: infoTitleColor + font.family: "Arial" + font.bold: true + font.pixelSize: 21 + wrapMode: Text.WordWrap - Text { - id: currentComicInfoVolume - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.volume ? currentComicInfo.volume : "" - rightPadding: 20 - visible: currentComicInfo.volume ? true : false + text: currentComic.getTitleIncludingNumber() } - Text { - id: currentComicInfoNumbering - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.number + "/" + currentComicInfo.count - rightPadding: 20 - visible : currentComicInfo.number ? true : false - } + Flow { + spacing: 0 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.fillWidth: true + Layout.fillHeight: false + + id: currentComicDetailsFlowView + property font infoFont: Qt.font({ + family: "Arial", + pixelSize: 14 + }); + property string infoFlowTextColor: infoTextColor + + Text { + id: currentComicInfoVolume + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.volume ? currentComicInfo.volume : "" + rightPadding: 20 + visible: currentComicInfo.volume ? true : false + } - Text { - id: currentComicInfoGenre - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.genere ? currentComicInfo.genere : "" - rightPadding: 20 - visible: currentComicInfo.genere ? true : false - } + Text { + id: currentComicInfoNumbering + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.number + "/" + currentComicInfo.count + rightPadding: 20 + visible : currentComicInfo.number ? true : false + } - Text { - id: currentComicInfoDate - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.date ? currentComicInfo.date : "" - rightPadding: 20 - visible: currentComicInfo.date ? true : false - } + Text { + id: currentComicInfoGenre + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.genere ? currentComicInfo.genere : "" + rightPadding: 20 + visible: currentComicInfo.genere ? true : false + } - Text { - id: currentComicInfoPages - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: (currentComicInfo.numPages ? currentComicInfo.numPages : "") + " pages" - rightPadding: 20 - visible: currentComicInfo.numPages ? true : false - } + Text { + id: currentComicInfoDate + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.date ? currentComicInfo.date : "" + rightPadding: 20 + visible: currentComicInfo.date ? true : false + } - Text { - id: currentComicInfoShowInComicVine - font: currentComicDetailsFlowView.infoFont - color: "#ffcc00" - text: "Show in Comic Vine" - visible: currentComicInfo.comicVineID ? true : false - MouseArea { - anchors.fill: parent - onClicked: { - Qt.openUrlExternally("http://www.comicvine.com/comic/4000-%1/".arg(comicInfo.comicVineID)); + Text { + id: currentComicInfoPages + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: (currentComicInfo.numPages ? currentComicInfo.numPages : "") + " pages" + rightPadding: 20 + visible: currentComicInfo.numPages ? true : false + } + + Text { + id: currentComicInfoShowInComicVine + font: currentComicDetailsFlowView.infoFont + color: "#ffcc00" + text: "Show in Comic Vine" + visible: currentComicInfo.comicVineID ? true : false + MouseArea { + anchors.fill: parent + onClicked: { + Qt.openUrlExternally("http://www.comicvine.com/comic/4000-%1/".arg(comicInfo.comicVineID)); + } } } } - } - ScrollView { - Layout.topMargin: 6 - Layout.rightMargin: 30 - Layout.bottomMargin: 5 - Layout.fillWidth: true - Layout.maximumHeight: (currentComicVisualView.height * 0.32) - Layout.maximumWidth: 960 + ScrollView { + Layout.topMargin: 6 + Layout.rightMargin: 30 + Layout.bottomMargin: 5 + Layout.fillWidth: true + Layout.maximumHeight: (currentComicVisualView.height * 0.32) + Layout.maximumWidth: 960 - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - contentItem: currentComicInfoSinopsis + contentItem: currentComicInfoSinopsis - id: synopsisScroller + id: synopsisScroller - clip: true + clip: true - Text { - Layout.maximumWidth: 960 + Text { + Layout.maximumWidth: 960 - width: synopsisScroller.width + width: synopsisScroller.width - id: currentComicInfoSinopsis - color: infoTitleColor - font.family: "Arial" - font.pixelSize: 14 - wrapMode: Text.WordWrap + id: currentComicInfoSinopsis + color: infoTitleColor + font.family: "Arial" + font.pixelSize: 14 + wrapMode: Text.WordWrap - text: '' + currentComicInfo.synopsis ?? "" + '' - visible: currentComicInfo.synopsis ?? false - textFormat: Text.RichText + visible: currentComicInfo.synopsis ?? false + textFormat: Text.RichText + } } } - } - Button { - text: "Read" - id: readButton - x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin - anchors.bottom: currentCoverElement.bottom - anchors.bottomMargin: 15 + Button { + text: "Read" + id: readButton + x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin + anchors.bottom: currentCoverElement.bottom + anchors.bottomMargin: 15 - onClicked: comicOpener.triggerOpenCurrentComic() + onClicked: comicOpener.triggerOpenCurrentComic() background: Rectangle { implicitWidth: 100 implicitHeight: 30 @@ -671,202 +631,242 @@ Rectangle { color: "white" text: readButton.text } - } + } - DropShadow { - anchors.fill: readButton - horizontalOffset: 0 - verticalOffset: 0 - radius: 8.0 - samples: 17 - color: "#AA000000" - source: readButton - visible: ((Qt.platform.os === "osx") ? false : true) && !readButton.pressed + DropShadow { + anchors.fill: readButton + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + samples: 17 + color: "#AA000000" + source: readButton + visible: ((Qt.platform.os === "osx") ? false : true) && !readButton.pressed + } } } } - } - GridView { - id:grid - objectName: "grid" - anchors.fill: parent - cellHeight: cellCustomHeight - header: currentComicView - focus: true - model: comicsList - delegate: appDelegate - anchors.topMargin: 0 - anchors.bottomMargin: 10 - anchors.leftMargin: 0 - anchors.rightMargin: 0 - pixelAligned: true - highlightFollowsCurrentItem: true - - currentIndex: 0 - cacheBuffer: 0 - - interactive: true - - move: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + GridView { + id:grid + objectName: "grid" + anchors.fill: parent + cellHeight: cellCustomHeight + header: currentComicView + focus: true + model: comicsList + delegate: appDelegate + anchors.topMargin: 0 + anchors.bottomMargin: 10 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + pixelAligned: true + highlightFollowsCurrentItem: true + + currentIndex: 0 + cacheBuffer: 0 + + interactive: true + + move: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - moveDisplaced: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + moveDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - remove: Transition { - ParallelAnimation { - NumberAnimation { property: "opacity"; to: 0; duration: 250 } + remove: Transition { + ParallelAnimation { + NumberAnimation { property: "opacity"; to: 0; duration: 250 } + } } - } - removeDisplaced: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + removeDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - displaced: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + displaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - function numCellsPerRow() { - return Math.floor(width / cellCustomWidth); - } + function numCellsPerRow() { + return Math.floor(width / cellCustomWidth); + } - onWidthChanged: { - calculateCellWidths(cellCustomWidth); - } + onWidthChanged: { + calculateCellWidths(cellCustomWidth); + } - function calculateCellWidths(cWidth) { - var wholeCells = Math.floor(width / cWidth); - var rest = width - (cWidth * wholeCells) - - grid.cellWidth = cWidth + Math.floor(rest / wholeCells); - } - - WheelHandler { - onWheel: { - if (grid.contentHeight <= grid.height) { - return; - } - - var newValue = Math.min((grid.contentHeight - grid.height - (showCurrentComic ? 270 : 20)), (Math.max(grid.originY , grid.contentY - event.angleDelta.y))); - grid.contentY = newValue; - } - } - - ScrollBar.vertical: ScrollBar { - visible: grid.contentHeight > grid.height - - contentItem: Item { - implicitWidth: 12 - implicitHeight: 26 - Rectangle { - color: "#88424242" - anchors.fill: parent - anchors.topMargin: 6 - anchors.leftMargin: 3 - anchors.rightMargin: 2 - anchors.bottomMargin: 6 - border.color: "#AA313131" - border.width: 1 - radius: 3.5 - } - } - } - - Keys.onPressed: { - if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier) { - event.accepted = true - return; - } - - var numCells = grid.numCellsPerRow(); - var ci = 0; - if (event.key === Qt.Key_Right) { - ci = Math.min(grid.currentIndex+1,grid.count - 1); - } - else if (event.key === Qt.Key_Left) { - ci = Math.max(0,grid.currentIndex-1); - } - else if (event.key === Qt.Key_Up) { - ci = Math.max(0,grid.currentIndex-numCells); - } - else if (event.key === Qt.Key_Down) { - ci = Math.min(grid.currentIndex+numCells,grid.count - 1); - } else { - return; - } - - event.accepted = true; - grid.currentIndex = -1 - comicsSelectionHelper.clear(); - currentIndexHelper.setCurrentIndex(ci); - grid.currentIndex = ci; - } - } - } -} + function calculateCellWidths(cWidth) { + var wholeCells = Math.floor(width / cWidth); + var rest = width - (cWidth * wholeCells) -Rectangle { - id: info_container - objectName: "infoContainer" - SplitView.preferredWidth: 350 - SplitView.minimumWidth: 350 - SplitView.maximumWidth: 960 - height: parent.height + grid.cellWidth = cWidth + Math.floor(rest / wholeCells); + } - color: infoBackgroundColor + WheelHandler { + onWheel: { + if (grid.contentHeight <= grid.height) { + return; + } - visible: showInfo + var newValue = Math.min((grid.contentHeight - grid.height - (showCurrentComic ? 270 : 20)), (Math.max(grid.originY , grid.contentY - event.angleDelta.y))); + grid.contentY = newValue; + } + } - Flickable{ - id: infoFlickable - anchors.fill: parent - anchors.margins: 0 + ScrollBar.vertical: ScrollBar { + visible: grid.contentHeight > grid.height + + contentItem: Item { + implicitWidth: 12 + implicitHeight: 26 + Rectangle { + color: "#88424242" + anchors.fill: parent + anchors.topMargin: 6 + anchors.leftMargin: 3 + anchors.rightMargin: 2 + anchors.bottomMargin: 6 + border.color: "#AA313131" + border.width: 1 + radius: 3.5 + } + } + } - contentWidth: infoView.width - contentHeight: infoView.height + Keys.onPressed: { + if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier) { + event.accepted = true + return; + } - ComicInfoView { - id: infoView - width: info_container.width - } + var numCells = grid.numCellsPerRow(); + var ci = 0; + if (event.key === Qt.Key_Right) { + ci = Math.min(grid.currentIndex+1,grid.count - 1); + } + else if (event.key === Qt.Key_Left) { + ci = Math.max(0,grid.currentIndex-1); + } + else if (event.key === Qt.Key_Up) { + ci = Math.max(0,grid.currentIndex-numCells); + } + else if (event.key === Qt.Key_Down) { + ci = Math.min(grid.currentIndex+numCells,grid.count - 1); + } else { + return; + } - WheelHandler { - onWheel: { - if (infoFlickable.contentHeight <= infoFlickable.height) { - return; + event.accepted = true; + grid.currentIndex = -1 + comicsSelectionHelper.clear(); + currentIndexHelper.setCurrentIndex(ci); + grid.currentIndex = ci; } - var newValue = Math.min((infoFlickable.contentHeight - infoFlickable.height), (Math.max(infoFlickable.originY , infoFlickable.contentY - event.angleDelta.y))); - infoFlickable.contentY = newValue; + DropArea { + anchors.fill: parent + + onEntered: drag => { + if(drag.hasUrls) + { + if(dropManager.canDropUrls(drag.urls, drag.action)) + { + drag.accepted = true; + }else + drag.accepted = false; + } + else if (dropManager.canDropFormats(drag.formats)) { + drag.accepted = true; + } else + drag.accepted = false; + } + + onDropped: drop => { + if(drop.hasUrls && dropManager.canDropUrls(drop.urls, drop.action)) + { + dropManager.droppedFiles(drop.urls, drop.action); + } + else{ + if (dropManager.canDropFormats(drop.formats)) + { + var destItem = grid.itemAt(drop.x,drop.y + grid.contentY); + var destLocalX = grid.mapToItem(destItem,drop.x,drop.y + grid.contentY).x + var realIndex = grid.indexAt(drop.x,drop.y + grid.contentY); + + if(realIndex === -1) + realIndex = grid.count - 1; + + var destIndex = destLocalX < (grid.cellWidth / 2) ? realIndex : realIndex + 1; + dropManager.droppedComicsForResortingAt("", destIndex); + } + } + } + } } } + } - ScrollBar.vertical: ScrollBar { - visible: infoFlickable.contentHeight > infoFlickable.height + Rectangle { + id: info_container + objectName: "infoContainer" + SplitView.preferredWidth: 350 + SplitView.minimumWidth: 350 + SplitView.maximumWidth: 960 + height: parent.height - contentItem: Item { - implicitWidth: 12 - implicitHeight: 26 - Rectangle { - color: "#424246" - anchors.fill: parent - anchors.topMargin: 6 - anchors.leftMargin: 5 - anchors.rightMargin: 4 - anchors.bottomMargin: 6 - radius: 2 + color: infoBackgroundColor + + visible: showInfo + + Flickable{ + id: infoFlickable + anchors.fill: parent + anchors.margins: 0 + + contentWidth: infoView.width + contentHeight: infoView.height + + ComicInfoView { + id: infoView + width: info_container.width + } + + WheelHandler { + onWheel: { + if (infoFlickable.contentHeight <= infoFlickable.height) { + return; + } + + var newValue = Math.min((infoFlickable.contentHeight - infoFlickable.height), (Math.max(infoFlickable.originY , infoFlickable.contentY - event.angleDelta.y))); + infoFlickable.contentY = newValue; + } + } + + ScrollBar.vertical: ScrollBar { + visible: infoFlickable.contentHeight > infoFlickable.height + + contentItem: Item { + implicitWidth: 12 + implicitHeight: 26 + Rectangle { + color: "#424246" + anchors.fill: parent + anchors.topMargin: 6 + anchors.leftMargin: 5 + anchors.rightMargin: 4 + anchors.bottomMargin: 6 + radius: 2 + } } } } - } -} + } } diff --git a/YACReaderLibrary/qml/GridComicsView6.qml b/YACReaderLibrary/qml/GridComicsView6.qml index 86f414945..10294fc05 100644 --- a/YACReaderLibrary/qml/GridComicsView6.qml +++ b/YACReaderLibrary/qml/GridComicsView6.qml @@ -20,73 +20,73 @@ SplitView { color: info_container.color } -Rectangle { - id: main - clip: true - - Image { - id: backgroundImg - anchors.fill: parent - source: backgroundImage - fillMode: Image.PreserveAspectCrop - smooth: true - mipmap: true - asynchronous : true - cache: false //TODO clear cache only when it is needed - opacity: 0 - visible: false - } + Rectangle { + id: main + clip: true - FastBlur { - anchors.fill: backgroundImg - source: backgroundImg - radius: backgroundBlurRadius - opacity: backgroundBlurOpacity - visible: backgroundBlurVisible - } + Image { + id: backgroundImg + anchors.fill: parent + source: backgroundImage + fillMode: Image.PreserveAspectCrop + smooth: true + mipmap: true + asynchronous : true + cache: false //TODO clear cache only when it is needed + opacity: 0 + visible: false + } - color: backgroundColor - width: parent.width - (info_container.visible ? info_container.width : 0) - SplitView.fillWidth: true - SplitView.minimumWidth: coverWidth + 100 - height: parent.height - anchors.margins: 0 - - Component { - id: appDelegate - Rectangle - { - id: cell - width: grid.cellWidth - height: grid.cellHeight - color: "#00000000" - - scale: mouseArea.containsMouse ? 1.025 : 1 - - Behavior on scale { - NumberAnimation { duration: 90 } - } + FastBlur { + anchors.fill: backgroundImg + source: backgroundImg + radius: backgroundBlurRadius + opacity: backgroundBlurOpacity + visible: backgroundBlurVisible + } - DropShadow { - anchors.fill: realCell - transparentBorder: true - horizontalOffset: 0 - verticalOffset: 0 - radius: 10.0 - //samples: 17 - color: "#FF000000" - source: realCell - visible: (Qt.platform.os === "osx") ? false : true; - } + color: backgroundColor + width: parent.width - (info_container.visible ? info_container.width : 0) + SplitView.fillWidth: true + SplitView.minimumWidth: coverWidth + 100 + height: parent.height + anchors.margins: 0 + + Component { + id: appDelegate + Rectangle + { + id: cell + width: grid.cellWidth + height: grid.cellHeight + color: "#00000000" + + scale: mouseArea.containsMouse ? 1.025 : 1 + + Behavior on scale { + NumberAnimation { duration: 90 } + } + + DropShadow { + anchors.fill: realCell + transparentBorder: true + horizontalOffset: 0 + verticalOffset: 0 + radius: 10.0 + //samples: 17 + color: "#FF000000" + source: realCell + visible: (Qt.platform.os === "osx") ? false : true; + } - Rectangle { - id: realCell + Rectangle { + id: realCell - property int position : 0 - property bool dragging: false; - Drag.active: mouseArea.drag.active - Drag.hotSpot.x: 32 - Drag.hotSpot.y: 32 + property int position : 0 + property bool dragging: false; + Drag.active: mouseArea.drag.active + Drag.hotSpot.x: 32 + Drag.hotSpot.y: 32 Drag.dragType: Drag.Automatic //Drag.mimeData: { "x": 1 } Drag.proposedAction: Qt.CopyAction @@ -397,267 +397,227 @@ Rectangle { id: comicRating anchors {bottom: realCell.bottom; right: ratingImage.left; margins: 4} text: rating>0?rating:"-" - color: textColor + color: textColor + } } } - } - Rectangle { - id: scrollView - objectName: "topScrollView" - anchors.fill: parent - anchors.margins: 0 - children: grid - - color: "transparent" - - function scrollToOrigin() { - grid.contentY = grid.originY - grid.contentX = grid.originX - } - - DropArea { + Rectangle { + id: scrollView + objectName: "topScrollView" anchors.fill: parent + anchors.margins: 0 + children: grid - onEntered: { - if(drag.hasUrls) - { - if(dropManager.canDropUrls(drag.urls, drag.action)) - { - drag.accepted = true; - }else - drag.accepted = false; - } - else if (dropManager.canDropFormats(drag.formats)) { - drag.accepted = true; - } else - drag.accepted = false; - } + color: "transparent" - onDropped: { - if(drop.hasUrls && dropManager.canDropUrls(drop.urls, drop.action)) - { - dropManager.droppedFiles(drop.urls, drop.action); - } - else{ - if (dropManager.canDropFormats(drop.formats)) - { - var destItem = grid.itemAt(drop.x,drop.y + grid.contentY); - var destLocalX = grid.mapToItem(destItem,drop.x,drop.y + grid.contentY).x - var realIndex = grid.indexAt(drop.x,drop.y + grid.contentY); - - if(realIndex === -1) - realIndex = grid.count - 1; - - var destIndex = destLocalX < (grid.cellWidth / 2) ? realIndex : realIndex + 1; - dropManager.droppedComicsForResortingAt(drop.getDataAsString(), destIndex); - } - } + function scrollToOrigin() { + grid.contentY = grid.originY + grid.contentX = grid.originX } - } - - property Component currentComicView: Component { - id: currentComicView - Rectangle { - id: currentComicViewTopView - color: "#00000000" - - height: showCurrentComic ? 270 : 20 + property Component currentComicView: Component { + id: currentComicView Rectangle { - color: (Qt.platform.os === "osx") ? "#88FFFFFF" : "#88000000" - - id: currentComicVisualView - - width: main.width - height: 250 - - visible: showCurrentComic - - //cover - Image { - id: currentCoverElement - anchors.fill: parent - - anchors.leftMargin: 15 - anchors.topMargin: 15 - anchors.bottomMargin: 15 - anchors.rightMargin: 15 - horizontalAlignment: Image.AlignLeft - anchors {horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 0} - source: comicsList.getCoverUrlPathForComicHash(currentComicInfo.hash.toString()) - fillMode: Image.PreserveAspectFit - smooth: true - mipmap: true - asynchronous : true - cache: false //TODO clear cache only when it is needed - } - - DropShadow { - anchors.fill: currentCoverElement - horizontalOffset: 0 - verticalOffset: 0 - radius: 8.0 - transparentBorder: true - //samples: 17 - color: "#FF000000" - source: currentCoverElement - visible: (Qt.platform.os === "osx") ? false : true; - } - - ColumnLayout - { - id: currentComicInfoView - - x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin - //y: currentCoverElement.anchors.topMargin + id: currentComicViewTopView + color: "#00000000" + + height: showCurrentComic ? 270 : 20 + + Rectangle { + color: (Qt.platform.os === "osx") ? "#88FFFFFF" : "#88000000" + + id: currentComicVisualView + + width: main.width + height: 250 + + visible: showCurrentComic + + //cover + Image { + id: currentCoverElement + anchors.fill: parent + + anchors.leftMargin: 15 + anchors.topMargin: 15 + anchors.bottomMargin: 15 + anchors.rightMargin: 15 + horizontalAlignment: Image.AlignLeft + anchors {horizontalCenter: parent.horizontalCenter; top: parent.top; topMargin: 0} + source: comicsList.getCoverUrlPathForComicHash(currentComicInfo.hash.toString()) + fillMode: Image.PreserveAspectFit + smooth: true + mipmap: true + asynchronous : true + cache: false //TODO clear cache only when it is needed + } - anchors.top: currentCoverElement.top - anchors.right: parent.right - anchors.left: readButton.left + DropShadow { + anchors.fill: currentCoverElement + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + transparentBorder: true + //samples: 17 + color: "#FF000000" + source: currentCoverElement + visible: (Qt.platform.os === "osx") ? false : true; + } - spacing: 9 + ColumnLayout + { + id: currentComicInfoView - Text { - Layout.topMargin: 7 - Layout.fillWidth: true - Layout.rightMargin: 20 + x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin + //y: currentCoverElement.anchors.topMargin - Layout.alignment: Qt.AlignTop | Qt.AlignLeft + anchors.top: currentCoverElement.top + anchors.right: parent.right + anchors.left: readButton.left - id: currentComicInfoTitleView + spacing: 9 - color: infoTitleColor - font.family: "Arial" - font.bold: true - font.pixelSize: 21 - wrapMode: Text.WordWrap + Text { + Layout.topMargin: 7 + Layout.fillWidth: true + Layout.rightMargin: 20 - text: currentComic.getTitleIncludingNumber() - } + Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Flow { - spacing: 0 - Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.fillWidth: true - Layout.fillHeight: false + id: currentComicInfoTitleView - id: currentComicDetailsFlowView - property font infoFont: Qt.font({ - family: "Arial", - pixelSize: 14 - }); - property string infoFlowTextColor: infoTextColor + color: infoTitleColor + font.family: "Arial" + font.bold: true + font.pixelSize: 21 + wrapMode: Text.WordWrap - Text { - id: currentComicInfoVolume - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.volume ? currentComicInfo.volume : "" - rightPadding: 20 - visible: currentComicInfo.volume ? true : false + text: currentComic.getTitleIncludingNumber() } - Text { - id: currentComicInfoNumbering - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.number + "/" + currentComicInfo.count - rightPadding: 20 - visible : currentComicInfo.number ? true : false - } + Flow { + spacing: 0 + Layout.alignment: Qt.AlignTop | Qt.AlignLeft + Layout.fillWidth: true + Layout.fillHeight: false + + id: currentComicDetailsFlowView + property font infoFont: Qt.font({ + family: "Arial", + pixelSize: 14 + }); + property string infoFlowTextColor: infoTextColor + + Text { + id: currentComicInfoVolume + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.volume ? currentComicInfo.volume : "" + rightPadding: 20 + visible: currentComicInfo.volume ? true : false + } - Text { - id: currentComicInfoGenre - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.genere ? currentComicInfo.genere : "" - rightPadding: 20 - visible: currentComicInfo.genere ? true : false - } + Text { + id: currentComicInfoNumbering + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.number + "/" + currentComicInfo.count + rightPadding: 20 + visible : currentComicInfo.number ? true : false + } - Text { - id: currentComicInfoDate - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: currentComicInfo.date ? currentComicInfo.date : "" - rightPadding: 20 - visible: currentComicInfo.date ? true : false - } + Text { + id: currentComicInfoGenre + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.genere ? currentComicInfo.genere : "" + rightPadding: 20 + visible: currentComicInfo.genere ? true : false + } - Text { - id: currentComicInfoPages - color: currentComicDetailsFlowView.infoFlowTextColor - font: currentComicDetailsFlowView.infoFont - text: (currentComicInfo.numPages ? currentComicInfo.numPages : "") + " pages" - rightPadding: 20 - visible: currentComicInfo.numPages ? true : false - } + Text { + id: currentComicInfoDate + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: currentComicInfo.date ? currentComicInfo.date : "" + rightPadding: 20 + visible: currentComicInfo.date ? true : false + } - Text { - id: currentComicInfoShowInComicVine - font: currentComicDetailsFlowView.infoFont - color: "#ffcc00" - text: "Show in Comic Vine" - visible: currentComicInfo.comicVineID ? true : false - MouseArea { - anchors.fill: parent - onClicked: { - Qt.openUrlExternally("http://www.comicvine.com/comic/4000-%1/".arg(comicInfo.comicVineID)); + Text { + id: currentComicInfoPages + color: currentComicDetailsFlowView.infoFlowTextColor + font: currentComicDetailsFlowView.infoFont + text: (currentComicInfo.numPages ? currentComicInfo.numPages : "") + " pages" + rightPadding: 20 + visible: currentComicInfo.numPages ? true : false + } + + Text { + id: currentComicInfoShowInComicVine + font: currentComicDetailsFlowView.infoFont + color: "#ffcc00" + text: "Show in Comic Vine" + visible: currentComicInfo.comicVineID ? true : false + MouseArea { + anchors.fill: parent + onClicked: { + Qt.openUrlExternally("http://www.comicvine.com/comic/4000-%1/".arg(comicInfo.comicVineID)); + } } } } - } - ScrollView { - Layout.topMargin: 6 - Layout.rightMargin: 30 - Layout.bottomMargin: 5 - Layout.fillWidth: true - Layout.maximumHeight: (currentComicVisualView.height * 0.32) - Layout.maximumWidth: 960 + ScrollView { + Layout.topMargin: 6 + Layout.rightMargin: 30 + Layout.bottomMargin: 5 + Layout.fillWidth: true + Layout.maximumHeight: (currentComicVisualView.height * 0.32) + Layout.maximumWidth: 960 - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - contentWidth: -1 - contentItem: currentComicInfoSinopsis + contentWidth: -1 + contentItem: currentComicInfoSinopsis - id: synopsisScroller + id: synopsisScroller - clip: true + clip: true - Text { - Layout.maximumWidth: 960 + Text { + Layout.maximumWidth: 960 - width: synopsisScroller.width + width: synopsisScroller.width - id: currentComicInfoSinopsis - color: infoTitleColor - font.family: "Arial" - font.pixelSize: 14 - wrapMode: Text.WordWrap + id: currentComicInfoSinopsis + color: infoTitleColor + font.family: "Arial" + font.pixelSize: 14 + wrapMode: Text.WordWrap - text: '' + currentComicInfo.synopsis ?? "" + '' - visible: currentComicInfo.synopsis ?? false - textFormat: Text.RichText + visible: currentComicInfo.synopsis ?? false + textFormat: Text.RichText + } } } - } - Button { - containmentMask: null - text: "Read" - id: readButton - x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin - anchors.bottom: currentCoverElement.bottom - anchors.bottomMargin: 15 + Button { + containmentMask: null + text: "Read" + id: readButton + x: currentCoverElement.anchors.rightMargin + currentCoverElement.paintedWidth + currentCoverElement.anchors.rightMargin + anchors.bottom: currentCoverElement.bottom + anchors.bottomMargin: 15 - onClicked: comicOpener.triggerOpenCurrentComic() + onClicked: comicOpener.triggerOpenCurrentComic() background: Rectangle { implicitWidth: 100 implicitHeight: 30 @@ -677,203 +637,243 @@ Rectangle { color: "white" text: readButton.text } - } + } - DropShadow { - anchors.fill: readButton - transparentBorder: true - horizontalOffset: 0 - verticalOffset: 0 - radius: 8.0 - //samples: 17 - color: "#AA000000" - source: readButton - visible: ((Qt.platform.os === "osx") ? false : true) && !readButton.pressed + DropShadow { + anchors.fill: readButton + transparentBorder: true + horizontalOffset: 0 + verticalOffset: 0 + radius: 8.0 + //samples: 17 + color: "#AA000000" + source: readButton + visible: ((Qt.platform.os === "osx") ? false : true) && !readButton.pressed + } } } } - } - GridView { - id:grid - objectName: "grid" - anchors.fill: parent - cellHeight: cellCustomHeight - header: currentComicView - focus: true - model: comicsList - delegate: appDelegate - anchors.topMargin: 0 - anchors.bottomMargin: 10 - anchors.leftMargin: 0 - anchors.rightMargin: 0 - pixelAligned: true - highlightFollowsCurrentItem: true - - currentIndex: 0 - cacheBuffer: 0 - - interactive: true - - move: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + GridView { + id:grid + objectName: "grid" + anchors.fill: parent + cellHeight: cellCustomHeight + header: currentComicView + focus: true + model: comicsList + delegate: appDelegate + anchors.topMargin: 0 + anchors.bottomMargin: 10 + anchors.leftMargin: 0 + anchors.rightMargin: 0 + pixelAligned: true + highlightFollowsCurrentItem: true + + currentIndex: 0 + cacheBuffer: 0 + + interactive: true + + move: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - moveDisplaced: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + moveDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - remove: Transition { - ParallelAnimation { - NumberAnimation { property: "opacity"; to: 0; duration: 250 } + remove: Transition { + ParallelAnimation { + NumberAnimation { property: "opacity"; to: 0; duration: 250 } + } } - } - removeDisplaced: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + removeDisplaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - displaced: Transition { - NumberAnimation { properties: "x,y"; duration: 250 } - } + displaced: Transition { + NumberAnimation { properties: "x,y"; duration: 250 } + } - function numCellsPerRow() { - return Math.floor(width / cellCustomWidth); - } + function numCellsPerRow() { + return Math.floor(width / cellCustomWidth); + } - onWidthChanged: { - calculateCellWidths(cellCustomWidth); - } + onWidthChanged: { + calculateCellWidths(cellCustomWidth); + } - function calculateCellWidths(cWidth) { - var wholeCells = Math.floor(width / cWidth); - var rest = width - (cWidth * wholeCells) - - grid.cellWidth = cWidth + Math.floor(rest / wholeCells); - } - - WheelHandler { - onWheel: { - if (grid.contentHeight <= grid.height) { - return; - } - - var newValue = Math.min((grid.contentHeight - grid.height - (showCurrentComic ? 270 : 20)), (Math.max(grid.originY , grid.contentY - event.angleDelta.y))); - grid.contentY = newValue; - } - } - - ScrollBar.vertical: ScrollBar { - visible: grid.contentHeight > grid.height - - contentItem: Item { - implicitWidth: 12 - implicitHeight: 26 - Rectangle { - color: "#88424242" - anchors.fill: parent - anchors.topMargin: 6 - anchors.leftMargin: 3 - anchors.rightMargin: 2 - anchors.bottomMargin: 6 - border.color: "#AA313131" - border.width: 1 - radius: 3.5 - } - } - } - - Keys.onPressed: { - if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier) { - event.accepted = true - return; - } - - var numCells = grid.numCellsPerRow(); - var ci = 0; - if (event.key === Qt.Key_Right) { - ci = Math.min(grid.currentIndex+1,grid.count - 1); - } - else if (event.key === Qt.Key_Left) { - ci = Math.max(0,grid.currentIndex-1); - } - else if (event.key === Qt.Key_Up) { - ci = Math.max(0,grid.currentIndex-numCells); - } - else if (event.key === Qt.Key_Down) { - ci = Math.min(grid.currentIndex+numCells,grid.count - 1); - } else { - return; - } - - event.accepted = true; - grid.currentIndex = -1 - comicsSelectionHelper.clear(); - currentIndexHelper.setCurrentIndex(ci); - grid.currentIndex = ci; - } - } - } -} + function calculateCellWidths(cWidth) { + var wholeCells = Math.floor(width / cWidth); + var rest = width - (cWidth * wholeCells) -Rectangle { - id: info_container - objectName: "infoContainer" - SplitView.preferredWidth: 350 - SplitView.minimumWidth: 350 - SplitView.maximumWidth: 960 - height: parent.height + grid.cellWidth = cWidth + Math.floor(rest / wholeCells); + } - color: infoBackgroundColor + WheelHandler { + onWheel: { + if (grid.contentHeight <= grid.height) { + return; + } - visible: showInfo + var newValue = Math.min((grid.contentHeight - grid.height - (showCurrentComic ? 270 : 20)), (Math.max(grid.originY , grid.contentY - event.angleDelta.y))); + grid.contentY = newValue; + } + } - Flickable{ - id: infoFlickable - anchors.fill: parent - anchors.margins: 0 + ScrollBar.vertical: ScrollBar { + visible: grid.contentHeight > grid.height + + contentItem: Item { + implicitWidth: 12 + implicitHeight: 26 + Rectangle { + color: "#88424242" + anchors.fill: parent + anchors.topMargin: 6 + anchors.leftMargin: 3 + anchors.rightMargin: 2 + anchors.bottomMargin: 6 + border.color: "#AA313131" + border.width: 1 + radius: 3.5 + } + } + } - contentWidth: infoView.width - contentHeight: infoView.height + Keys.onPressed: { + if (event.modifiers & Qt.ControlModifier || event.modifiers & Qt.ShiftModifier) { + event.accepted = true + return; + } - ComicInfoView { - id: infoView - width: info_container.width - } + var numCells = grid.numCellsPerRow(); + var ci = 0; + if (event.key === Qt.Key_Right) { + ci = Math.min(grid.currentIndex+1,grid.count - 1); + } + else if (event.key === Qt.Key_Left) { + ci = Math.max(0,grid.currentIndex-1); + } + else if (event.key === Qt.Key_Up) { + ci = Math.max(0,grid.currentIndex-numCells); + } + else if (event.key === Qt.Key_Down) { + ci = Math.min(grid.currentIndex+numCells,grid.count - 1); + } else { + return; + } - WheelHandler { - onWheel: { - if (infoFlickable.contentHeight <= infoFlickable.height) { - return; + event.accepted = true; + grid.currentIndex = -1 + comicsSelectionHelper.clear(); + currentIndexHelper.setCurrentIndex(ci); + grid.currentIndex = ci; } - var newValue = Math.min((infoFlickable.contentHeight - infoFlickable.height), (Math.max(infoFlickable.originY , infoFlickable.contentY - event.angleDelta.y))); - infoFlickable.contentY = newValue; + DropArea { + anchors.fill: parent + + onEntered: drag => { + if(drag.hasUrls) + { + if(dropManager.canDropUrls(drag.urls, drag.action)) + { + drag.accepted = true; + }else + drag.accepted = false; + } + else if (dropManager.canDropFormats(drag.formats)) { + drag.accepted = true; + } else + drag.accepted = false; + } + + onDropped: drop => { + if(drop.hasUrls && dropManager.canDropUrls(drop.urls, drop.action)) + { + dropManager.droppedFiles(drop.urls, drop.action); + } + else{ + if (dropManager.canDropFormats(drop.formats)) + { + var destItem = grid.itemAt(drop.x,drop.y + grid.contentY); + var destLocalX = grid.mapToItem(destItem,drop.x,drop.y + grid.contentY).x + var realIndex = grid.indexAt(drop.x,drop.y + grid.contentY); + + if(realIndex === -1) + realIndex = grid.count - 1; + + var destIndex = destLocalX < (grid.cellWidth / 2) ? realIndex : realIndex + 1; + dropManager.droppedComicsForResortingAt("", destIndex); + } + } + } + } } } + } - ScrollBar.vertical: ScrollBar { - visible: infoFlickable.contentHeight > infoFlickable.height + Rectangle { + id: info_container + objectName: "infoContainer" + SplitView.preferredWidth: 350 + SplitView.minimumWidth: 350 + SplitView.maximumWidth: 960 + height: parent.height - contentItem: Item { - implicitWidth: 12 - implicitHeight: 26 - Rectangle { - color: "#424246" - anchors.fill: parent - anchors.topMargin: 6 - anchors.leftMargin: 5 - anchors.rightMargin: 4 - anchors.bottomMargin: 6 - radius: 2 + color: infoBackgroundColor + + visible: showInfo + + Flickable{ + id: infoFlickable + anchors.fill: parent + anchors.margins: 0 + + contentWidth: infoView.width + contentHeight: infoView.height + + ComicInfoView { + id: infoView + width: info_container.width + } + + WheelHandler { + onWheel: { + if (infoFlickable.contentHeight <= infoFlickable.height) { + return; + } + + var newValue = Math.min((infoFlickable.contentHeight - infoFlickable.height), (Math.max(infoFlickable.originY , infoFlickable.contentY - event.angleDelta.y))); + infoFlickable.contentY = newValue; + } + } + + ScrollBar.vertical: ScrollBar { + visible: infoFlickable.contentHeight > infoFlickable.height + + contentItem: Item { + implicitWidth: 12 + implicitHeight: 26 + Rectangle { + color: "#424246" + anchors.fill: parent + anchors.topMargin: 6 + anchors.leftMargin: 5 + anchors.rightMargin: 4 + anchors.bottomMargin: 6 + radius: 2 + } } } } - } -} + } } diff --git a/YACReaderLibrary/server/yacreader_http_server.cpp b/YACReaderLibrary/server/yacreader_http_server.cpp index 40ba799d2..3dd61cf73 100644 --- a/YACReaderLibrary/server/yacreader_http_server.cpp +++ b/YACReaderLibrary/server/yacreader_http_server.cpp @@ -5,7 +5,7 @@ #include "static.h" #include "yacreader_http_server.h" -//#include "dualfilelogger.h" +// #include "dualfilelogger.h" #include "httplistener.h" #include "requestmapper.h" #include "staticfilecontroller.h" diff --git a/YACReaderLibrary/server_config_dialog.cpp b/YACReaderLibrary/server_config_dialog.cpp index d16211eba..e27998b11 100644 --- a/YACReaderLibrary/server_config_dialog.cpp +++ b/YACReaderLibrary/server_config_dialog.cpp @@ -167,8 +167,13 @@ ServerConfigDialog::ServerConfigDialog(QWidget *parent) if (settings->value(SERVER_ON, true).toBool()) { check->setChecked(true); generateQR(); - } else + } else { + ip->setDisabled(true); + port->setDisabled(true); + accept->setDisabled(true); + check->setChecked(false); + } performanceWorkaroundCheck->setChecked(settings->value(REMOTE_BROWSE_PERFORMANCE_WORKAROUND, false).toBool()); @@ -184,14 +189,18 @@ void ServerConfigDialog::enableServer(int status) settings->beginGroup("libraryConfig"); if (status == Qt::Checked) { + ip->setDisabled(false); + port->setDisabled(false); + accept->setDisabled(false); httpServer->start(); this->generateQR(); settings->setValue(SERVER_ON, true); } else { httpServer->stop(); qrCode->setPixmap(QPixmap()); - ip->clear(); - port->setText(""); + ip->setDisabled(true); + port->setDisabled(true); + accept->setDisabled(true); settings->setValue(SERVER_ON, false); } settings->endGroup(); diff --git a/YACReaderLibraryServer/YACReaderLibraryServer.pro b/YACReaderLibraryServer/YACReaderLibraryServer.pro index 311c1764a..b84c20573 100644 --- a/YACReaderLibraryServer/YACReaderLibraryServer.pro +++ b/YACReaderLibraryServer/YACReaderLibraryServer.pro @@ -130,13 +130,6 @@ contains(QMAKE_TARGET.arch, x86_64) { unix:!macx { #set install prefix if it's empty -isEmpty(PREFIX) { - PREFIX = /usr -} - -BINDIR = $$PREFIX/bin -LIBDIR = $$PREFIX/lib -DATADIR = $$PREFIX/share DEFINES += "LIBDIR=\\\"$$LIBDIR\\\"" "DATADIR=\\\"$$DATADIR\\\"" "BINDIR=\\\"$$BINDIR\\\"" @@ -150,7 +143,7 @@ DEFINES += "LIBDIR=\\\"$$LIBDIR\\\"" "DATADIR=\\\"$$DATADIR\\\"" "BINDIR=\\\"$$ } CONFIG(server_standalone) { - INSTALLS += bin server translation systemd + INSTALLS += bin server systemd } else:CONFIG(server_bundled) { INSTALLS += bin systemd @@ -169,9 +162,6 @@ server.files = ../release/server systemd.path = $$LIBDIR/systemd/user systemd.files = yacreaderlibraryserver.service -translation.path = $$DATADIR/yacreader/languages -translation.files = ../release/languages/yacreaderlibrary_* - # TODO: We need a manpage for yaclibserver #manpage.path = $$DATADIR/man/man1 #manpage.files = ../YACReaderLibrary.1 diff --git a/YACReaderLibraryServer/main.cpp b/YACReaderLibraryServer/main.cpp index df14beaae..bda5e416b 100644 --- a/YACReaderLibraryServer/main.cpp +++ b/YACReaderLibraryServer/main.cpp @@ -1,8 +1,9 @@ -//#include +// #include #include #include #include #include +#include #include "comic_db.h" #include "db_helper.h" @@ -81,6 +82,10 @@ int main(int argc, char **argv) QCoreApplication app(argc, argv); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + QImageReader::setAllocationLimit(0); +#endif + app.setApplicationName("YACReaderLibrary"); app.setOrganizationName("YACReader"); diff --git a/azure-pipelines-windows-template-qt6.yml b/azure-pipelines-windows-template-qt6.yml index c0621956c..af4f09051 100644 --- a/azure-pipelines-windows-template-qt6.yml +++ b/azure-pipelines-windows-template-qt6.yml @@ -4,7 +4,7 @@ parameters: qt_version: '6.2.2' qt_spec: 'msvc2019_64' qt_aqt_spec: 'win64_msvc2019_64' - vc_redist_url: 'https://aka.ms/vs/16/release/vc_redist.x64.exe' + vc_redist_url: 'https://aka.ms/vs/17/release/vc_redist.x64.exe' vc_redist_file_name: 'vc_redist.x64.exe' vc_vars: 'vcvars64.bat' diff --git a/azure-pipelines-windows-template.yml b/azure-pipelines-windows-template.yml index 550eeaecb..c39b000a1 100644 --- a/azure-pipelines-windows-template.yml +++ b/azure-pipelines-windows-template.yml @@ -4,7 +4,7 @@ parameters: qt_version: '5.15.2' qt_spec: 'msvc2019_64' qt_aqt_spec: 'win64_msvc2019_64' - vc_redist_url: 'https://aka.ms/vs/16/release/vc_redist.x64.exe' + vc_redist_url: 'https://aka.ms/vs/17/release/vc_redist.x64.exe' vc_redist_file_name: 'vc_redist.x64.exe' vc_vars: 'vcvars64.bat' diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a2be17867..248acbc1d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -234,7 +234,7 @@ jobs: qt_version: '5.15.2' qt_spec: 'msvc2019_64' qt_aqt_spec: 'win64_msvc2019_64' - vc_redist_url: 'https://aka.ms/vs/16/release/vc_redist.x64.exe' + vc_redist_url: 'https://aka.ms/vs/17/release/vc_redist.x64.exe' vc_redist_file_name: 'vc_redist.x64.exe' vc_vars: 'vcvars64.bat' @@ -249,7 +249,7 @@ jobs: qt_version: '6.3.1' qt_spec: 'msvc2019_64' qt_aqt_spec: 'win64_msvc2019_64' - vc_redist_url: 'https://aka.ms/vs/16/release/vc_redist.x64.exe' + vc_redist_url: 'https://aka.ms/vs/17/release/vc_redist.x64.exe' vc_redist_file_name: 'vc_redist.x64.exe' vc_vars: 'vcvars64.bat' @@ -264,7 +264,7 @@ jobs: qt_version: '5.15.2' qt_spec: 'msvc2019' qt_aqt_spec: 'win32_msvc2019' - vc_redist_url: 'https://aka.ms/vs/16/release/vc_redist.x86.exe' + vc_redist_url: 'https://aka.ms/vs/17/release/vc_redist.x86.exe' vc_redist_file_name: 'vc_redist.x86.exe' vc_vars: 'vcvars32.bat' diff --git a/ci/win/build_installer.iss b/ci/win/build_installer.iss index 45f601106..88a7e67f6 100644 --- a/ci/win/build_installer.iss +++ b/ci/win/build_installer.iss @@ -115,10 +115,6 @@ LaunchYACReaderLibrary=Start YACreaderLibrary after finishing installation LaunchYACReader=Start YACreader after finishing installation [Run] -Filename: {tmp}\vc_redist.{#PLATFORM}.exe; \ -Parameters: "/uninstall /quiet /norestart"; \ -StatusMsg: "Uninstalling VC++ Redistributables..." - Filename: {tmp}\vc_redist.{#PLATFORM}.exe; \ Parameters: "/install /quiet /norestart"; \ StatusMsg: "Installing VC++ Redistributables..." diff --git a/ci/win/build_installer_qt6.iss b/ci/win/build_installer_qt6.iss index 05534151d..6ca80f80d 100644 --- a/ci/win/build_installer_qt6.iss +++ b/ci/win/build_installer_qt6.iss @@ -124,10 +124,6 @@ LaunchYACReaderLibrary=Start YACreaderLibrary after finishing installation LaunchYACReader=Start YACreader after finishing installation [Run] -Filename: {tmp}\vc_redist.{#PLATFORM}.exe; \ -Parameters: "/uninstall /quiet /norestart"; \ -StatusMsg: "Uninstalling VC++ Redistributables..." - Filename: {tmp}\vc_redist.{#PLATFORM}.exe; \ Parameters: "/install /quiet /norestart"; \ StatusMsg: "Installing VC++ Redistributables..." diff --git a/common/comic.cpp b/common/comic.cpp index 0349bf93d..059c62038 100644 --- a/common/comic.cpp +++ b/common/comic.cpp @@ -796,24 +796,27 @@ bool PDFComic::load(const QString &path, const ComicDB &comic) void PDFComic::process() { #if defined Q_OS_MAC && defined USE_PDFKIT - pdfComic = new MacOSXPDFComic(); + pdfComic = std::make_unique(); if (!pdfComic->openComic(_path)) { - delete pdfComic; emit errorOpening(); return; } #elif defined USE_PDFIUM - pdfComic = new PdfiumComic(); + pdfComic = std::make_unique(); if (!pdfComic->openComic(_path)) { - delete pdfComic; emit errorOpening(); return; } #else + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) pdfComic = Poppler::Document::load(_path); +#else + auto _pdfComic = Poppler::Document::load(_path); + pdfComic = std::unique_ptr(_pdfComic); +#endif + if (!pdfComic) { - // delete pdfComic; - // pdfComic = 0; moveToThread(QCoreApplication::instance()->thread()); emit errorOpening(); return; @@ -824,7 +827,6 @@ void PDFComic::process() return; } - // pdfComic->setRenderHint(Poppler::Document::Antialiasing, true); pdfComic->setRenderHint(Poppler::Document::TextAntialiasing, true); #endif @@ -853,7 +855,6 @@ void PDFComic::process() int buffered_index = _index; for (int i = buffered_index; i < nPages; i++) { if (_invalidated) { - delete pdfComic; moveToThread(QCoreApplication::instance()->thread()); return; } @@ -862,14 +863,12 @@ void PDFComic::process() } for (int i = 0; i < buffered_index; i++) { if (_invalidated) { - delete pdfComic; moveToThread(QCoreApplication::instance()->thread()); return; } renderPage(i); } - delete pdfComic; moveToThread(QCoreApplication::instance()->thread()); emit imagesLoaded(); } @@ -883,15 +882,16 @@ void PDFComic::renderPage(int page) QImage img = pdfComic->getPage(page); if (!img.isNull()) { #else - Poppler::Page *pdfpage = pdfComic->page(page); + std::unique_ptr pdfpage(pdfComic->page(page)); if (pdfpage) { QImage img = pdfpage->renderToImage(150, 150); - delete pdfpage; #endif QByteArray ba; QBuffer buf(&ba); + buf.open(QIODevice::WriteOnly); img.save(&buf, "jpg", 96); _pages[page] = ba; + buf.close(); emit imageLoaded(page); emit imageLoaded(page, _pages[page]); } diff --git a/common/comic.h b/common/comic.h index a64da6f54..170cc8852 100644 --- a/common/comic.h +++ b/common/comic.h @@ -12,8 +12,8 @@ #include "pdf_comic.h" #endif // NO_PDF class ComicDB; -//#define EXTENSIONS << "*.jpg" << "*.jpeg" << "*.png" << "*.gif" << "*.tiff" << "*.tif" << "*.bmp" Comic::getSupportedImageFormats() -//#define EXTENSIONS_LITERAL << ".jpg" << ".jpeg" << ".png" << ".gif" << ".tiff" << ".tif" << ".bmp" //Comic::getSupportedImageLiteralFormats() + +// #define EXTENSIONS_LITERAL << ".jpg" << ".jpeg" << ".png" << ".gif" << ".tiff" << ".tif" << ".bmp" //Comic::getSupportedImageLiteralFormats() class Comic : public QObject { Q_OBJECT @@ -165,13 +165,14 @@ class PDFComic : public Comic private: // pdf #if defined Q_OS_MAC && defined USE_PDFKIT - MacOSXPDFComic *pdfComic; + std::unique_ptr pdfComic; #elif defined USE_PDFIUM - PdfiumComic *pdfComic; + std::unique_ptr pdfComic; #else - Poppler::Document *pdfComic; + std::unique_ptr pdfComic; #endif void renderPage(int page); + // void run(); public: diff --git a/common/pdf_comic.h b/common/pdf_comic.h index 13febf5cb..f2d8ff8c1 100644 --- a/common/pdf_comic.h +++ b/common/pdf_comic.h @@ -5,6 +5,7 @@ #include #include #include +#include #if defined Q_OS_MAC && defined USE_PDFKIT class MacOSXPDFComic @@ -45,6 +46,10 @@ class PdfiumComic QFile pdfFile; }; #else +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else #include "poppler-qt5.h" +#endif // QT_VERSION #endif // Q_OS_MAC #endif // PDF_COMIC_H diff --git a/common/yacreader_global.h b/common/yacreader_global.h index d2f35f97d..8bddd9a7d 100644 --- a/common/yacreader_global.h +++ b/common/yacreader_global.h @@ -6,7 +6,7 @@ #include #include -#define VERSION "9.10.0" +#define VERSION "9.11.0" #define REMOTE_BROWSE_PERFORMANCE_WORKAROUND "REMOTE_BROWSE_PERFORMANCE_WORKAROUND" #define IMPORT_COMIC_INFO_XML_METADATA "IMPORT_COMIC_INFO_XML_METADATA" diff --git a/config.pri b/config.pri index 033c486de..a5d82f03b 100644 --- a/config.pri +++ b/config.pri @@ -93,4 +93,20 @@ macx:!CONFIG(pdfkit):!CONFIG(pdfium):!CONFIG(no_pdf) { DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050900 } +unix:!macx { +# set install prefix if it's empty +isEmpty(PREFIX) { + PREFIX = /usr +} +isEmpty(BINDIR) { + BINDIR = $$PREFIX/bin +} +isEmpty(LIBDIR) { + LIBDIR = $$PREFIX/lib +} +isEmpty(DATADIR) { + DATADIR = $$PREFIX/share +} +} + DEFINES += QT_DEPRECATED_WARNINGS diff --git a/custom_widgets/whats_new_dialog.cpp b/custom_widgets/whats_new_dialog.cpp index 42e36dcfd..5d1e57a23 100644 --- a/custom_widgets/whats_new_dialog.cpp +++ b/custom_widgets/whats_new_dialog.cpp @@ -46,25 +46,20 @@ YACReader::WhatsNewDialog::WhatsNewDialog(QWidget *parent) "color:#858585;"); auto text = new QLabel(); - text->setText("New release with the following updates:
" + text->setText("A small update with a bunch of fixes:
" "
" "YACReader
" - " • Fixed color selection dialog appearing as a subwindow in macos.
" - " • Better support for HDPI screens (SVG icons).
" + " • Fix crash when exiting YACReader while it is processing a comic.
" + " • Fix last read page calculation in double page mode.
" "
" "YACReaderLibrary
" - " • New folder content view that replaces the old `subfolders in this folder` view shown when folders don't have direct comics. You may need to update your libraries to make folders display properly.
" - " • Continue Reading view that it is shown for the root folder.
" - " • UI gets updated when YACReaderLibrary gets updates from YACReader or YACReader for iOS.
" - " • Fixed going forward history navigation.
" - " • Fixed selected folder restoration after folder updates.
" - " • Add option to delete metadata from comics.
" - " • Better support for HDPI screens (SVG icons).
" - " • Importing ComicInfo.XML is now optional, you can change the behavior in Settings -> General.
" - " • Add option to scan XML metadata from all the comics in a folder.
" + " • Fix drag&drop in the comics grid view.
" + " • Detect back/forward mouse buttons to move back and forward through the browsing history.
" + " • Fix crash when disabling the server.
" "
" - "Server
" - " • New webui status page (reachable by navigating to server::port/webui).
" + "All apps
" + " • Add support for poppler-qt6 pdf backend (only relevat if you are building YACReader yourself).
" + " • Remove image allocation limit in Qt6.
" "
" "NOTE: Importing metadata from ComicInfo.XML in now disabled by default, if you want you can enable it Settings -> General.
" "
" diff --git a/dependencies/pdf_backend.pri b/dependencies/pdf_backend.pri index a14885bb1..0905ede61 100644 --- a/dependencies/pdf_backend.pri +++ b/dependencies/pdf_backend.pri @@ -55,18 +55,31 @@ CONFIG(poppler) { LIBS += -L$$PWD/poppler/dependencies/bin } if(unix|mingw):!macx { - !contains(QT_CONFIG, no-pkg-config):packagesExist(poppler-qt5) { - message("Using system provided installation of poppler-qt5 found by pkg-config.") - CONFIG += link_pkgconfig - PKGCONFIG += poppler-qt5 - } else:!macx:exists(/usr/include/poppler/qt5) { - message("Using system provided installation of poppler-qt5.") - INCLUDEPATH += /usr/include/poppler/qt5 - LIBS += -lpoppler-qt5 + greaterThan (QT_MAJOR_VERSION, 5) { + !contains(QT_CONFIG, no-pkg-config):packagesExist(poppler-qt6) { + message("Using system provided installation of poppler-qt6 found by pkg-config.") + CONFIG += link_pkgconfig + PKGCONFIG += poppler-qt6 + } else:!macx:exists(/usr/include/poppler/qt6) { + message("Using system provided installation of poppler-qt6.") + INCLUDEPATH += /usr/include/poppler/qt6 + LIBS += -lpoppler-qt6 + } else { + error("Could not find poppler-qt6") + } } else { - error("Could not find poppler-qt5") + !contains(QT_CONFIG, no-pkg-config):packagesExist(poppler-qt5) { + message("Using system provided installation of poppler-qt5 found by pkg-config.") + CONFIG += link_pkgconfig + PKGCONFIG += poppler-qt5 + } else:!macx:exists(/usr/include/poppler/qt5) { + message("Using system provided installation of poppler-qt5.") + INCLUDEPATH += /usr/include/poppler/qt5 + LIBS += -lpoppler-qt5 + } else { + error("Could not find poppler-qt5") + } } - } unix:macx { error (Poppler backend is currently not supported on macOS) diff --git a/third_party/QsLog/QsLog.pri b/third_party/QsLog/QsLog.pri index baf9d1f94..20363e1c9 100644 --- a/third_party/QsLog/QsLog.pri +++ b/third_party/QsLog/QsLog.pri @@ -1,7 +1,7 @@ INCLUDEPATH += $$PWD #DEFINES += QS_LOG_LINE_NUMBERS # automatically writes the file and line for each log message #DEFINES += QS_LOG_DISABLE # logging code is replaced with a no-op -#DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread +DEFINES += QS_LOG_SEPARATE_THREAD # messages are queued and written from a separate thread #DEFINES += QS_LOG_WIN_PRINTF_CONSOLE # Use fprintf instead of OutputDebugString on Windows #DEFINES += QS_LOG_WINDOW # allows easily showing log messages in a UI