From 95817d14eebc0f773be17a87b2e8079e7afbc958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Sun, 20 May 2012 16:12:22 +0200 Subject: [PATCH] commit inicial en mercurial --- COPYING.txt | 674 ++++++++++ README.txt | 4 + YACReader/YACReader.pro | 54 + YACReader/bookmarks.cpp | 159 +++ YACReader/bookmarks.h | 77 ++ YACReader/bookmarks_dialog.cpp | 182 +++ YACReader/bookmarks_dialog.h | 45 + YACReader/comic.cpp | 289 +++++ YACReader/comic.h | 70 ++ YACReader/configuration.cpp | 199 +++ YACReader/configuration.h | 77 ++ YACReader/files.qrc | 15 + YACReader/goto_dialog.cpp | 71 ++ YACReader/goto_dialog.h | 31 + YACReader/goto_flow.cpp | 373 ++++++ YACReader/goto_flow.h | 105 ++ YACReader/icon.ico | Bin 0 -> 99678 bytes YACReader/icon.rc | 1 + YACReader/images.qrc | 67 + YACReader/magnifying_glass.cpp | 267 ++++ YACReader/magnifying_glass.h | 34 + YACReader/main.cpp | 34 + YACReader/main_window_viewer.cpp | 680 ++++++++++ YACReader/main_window_viewer.h | 115 ++ YACReader/options_dialog.cpp | 188 +++ YACReader/options_dialog.h | 57 + YACReader/render.cpp | 746 +++++++++++ YACReader/render.h | 177 +++ YACReader/shortcuts_dialog.cpp | 74 ++ YACReader/shortcuts_dialog.h | 19 + YACReader/translator.cpp | 88 ++ YACReader/translator.h | 32 + YACReader/viewer.cpp | 671 ++++++++++ YACReader/viewer.h | 131 ++ YACReader/yacreader_es.qm | Bin 0 -> 9964 bytes YACReader/yacreader_es.ts | 569 +++++++++ YACReader/yacreader_fr.ts | 548 +++++++++ YACReaderLibrary/YACReaderLibrary.pro | 54 + YACReaderLibrary/add_library_dialog.cpp | 95 ++ YACReaderLibrary/add_library_dialog.h | 33 + YACReaderLibrary/comic_flow.cpp | 266 ++++ YACReaderLibrary/comic_flow.h | 76 ++ YACReaderLibrary/create_library_dialog.cpp | 154 +++ YACReaderLibrary/create_library_dialog.h | 52 + YACReaderLibrary/data_base_management.cpp | 22 + YACReaderLibrary/data_base_management.h | 18 + YACReaderLibrary/export_library_dialog.cpp | 100 ++ YACReaderLibrary/export_library_dialog.h | 36 + YACReaderLibrary/files.qrc | 13 + YACReaderLibrary/icon.ico | Bin 0 -> 99678 bytes YACReaderLibrary/icon.rc | 2 + YACReaderLibrary/icon2.ico | Bin 0 -> 99678 bytes YACReaderLibrary/images.qrc | 33 + YACReaderLibrary/import_library_dialog.cpp | 164 +++ YACReaderLibrary/import_library_dialog.h | 45 + YACReaderLibrary/library_creator.cpp | 323 +++++ YACReaderLibrary/library_creator.h | 64 + YACReaderLibrary/library_window.cpp | 1075 ++++++++++++++++ YACReaderLibrary/library_window.h | 164 +++ YACReaderLibrary/main.cpp | 22 + YACReaderLibrary/options_dialog.cpp | 148 +++ YACReaderLibrary/options_dialog.h | 50 + YACReaderLibrary/package_manager.cpp | 47 + YACReaderLibrary/package_manager.h | 24 + YACReaderLibrary/properties_dialog.cpp | 77 ++ YACReaderLibrary/properties_dialog.h | 29 + YACReaderLibrary/rename_library_dialog.cpp | 68 + YACReaderLibrary/rename_library_dialog.h | 30 + YACReaderLibrary/yacreaderlibrary_es.qm | Bin 0 -> 9449 bytes YACReaderLibrary/yacreaderlibrary_es.ts | 522 ++++++++ common/check_new_version.cpp | 93 ++ common/check_new_version.h | 29 + common/custom_widgets.cpp | 370 ++++++ common/custom_widgets.h | 138 +++ common/pictureflow.cpp | 1298 ++++++++++++++++++++ common/pictureflow.h | 223 ++++ common/qnaturalsorting.cpp | 257 ++++ common/qnaturalsorting.h | 13 + files/about.html | 29 + files/about_es_ES.html | 30 + files/helpYACReader.html | 126 ++ files/helpYACReaderLibrary.html | 75 ++ files/helpYACReaderLibrary_es_ES.html | 73 ++ files/helpYACReader_es_ES.html | 126 ++ files/shortcuts.html | 38 + files/shortcuts2.html | 38 + files/translator.html | 639 ++++++++++ generateVS2010Projects.bat | 31 + images/adjustToFullSize.png | Bin 0 -> 21893 bytes images/alwaysOnTop.png | Bin 0 -> 28230 bytes images/bookmark.png | Bin 0 -> 24352 bytes images/center.png | Bin 0 -> 17966 bytes images/close.png | Bin 0 -> 8350 bytes images/colapse.png | Bin 0 -> 13159 bytes images/comicFolder.png | Bin 0 -> 26436 bytes images/coversPackage.png | Bin 0 -> 29501 bytes images/deleteLibrary.png | Bin 0 -> 21641 bytes images/dictionary.png | Bin 0 -> 23238 bytes images/doublePage.png | Bin 0 -> 35580 bytes images/down.png | Bin 0 -> 689 bytes images/edit.png | Bin 0 -> 15944 bytes images/expand.png | Bin 0 -> 18214 bytes images/exportLibrary.png | Bin 0 -> 29212 bytes images/fit.png | Bin 0 -> 6359 bytes images/flow1.png | Bin 0 -> 9112 bytes images/flow2.png | Bin 0 -> 13316 bytes images/flow3.png | Bin 0 -> 13026 bytes images/folder.png | Bin 0 -> 21534 bytes images/goto.png | Bin 0 -> 15572 bytes images/help.png | Bin 0 -> 16817 bytes images/helpImages/bookmark.png | Bin 0 -> 1801 bytes images/helpImages/center.png | Bin 0 -> 1645 bytes images/helpImages/colapse.png | Bin 0 -> 1379 bytes images/helpImages/comicFolder.png | Bin 0 -> 1826 bytes images/helpImages/coversPackage.png | Bin 0 -> 1670 bytes images/helpImages/deleteLibrary.png | Bin 0 -> 1601 bytes images/helpImages/doublePage.png | Bin 0 -> 1761 bytes images/helpImages/edit.png | Bin 0 -> 1399 bytes images/helpImages/expand.png | Bin 0 -> 1543 bytes images/helpImages/exportLibrary.png | Bin 0 -> 1633 bytes images/helpImages/fit.png | Bin 0 -> 1574 bytes images/helpImages/flow1.png | Bin 0 -> 766 bytes images/helpImages/flow2.png | Bin 0 -> 1039 bytes images/helpImages/flow3.png | Bin 0 -> 1038 bytes images/helpImages/folder.png | Bin 0 -> 1758 bytes images/helpImages/goto.png | Bin 0 -> 1369 bytes images/helpImages/help.png | Bin 0 -> 1615 bytes images/helpImages/icon.png | Bin 0 -> 1654 bytes images/helpImages/importLibrary.png | Bin 0 -> 1631 bytes images/helpImages/keyboard.png | Bin 0 -> 1502 bytes images/helpImages/mouse.png | Bin 0 -> 1550 bytes images/helpImages/new.png | Bin 0 -> 1685 bytes images/helpImages/next.png | Bin 0 -> 1665 bytes images/helpImages/nextComic.png | Bin 0 -> 1574 bytes images/helpImages/notCover.png | Bin 0 -> 583 bytes images/helpImages/open.png | Bin 0 -> 1790 bytes images/helpImages/openFolder.png | Bin 0 -> 1896 bytes images/helpImages/openLibrary.png | Bin 0 -> 1829 bytes images/helpImages/options.png | Bin 0 -> 1573 bytes images/helpImages/prev.png | Bin 0 -> 1694 bytes images/helpImages/previousComic.png | Bin 0 -> 1585 bytes images/helpImages/properties.png | Bin 0 -> 1721 bytes images/helpImages/removeLibrary.png | Bin 0 -> 1612 bytes images/helpImages/rotateL.png | Bin 0 -> 1757 bytes images/helpImages/rotateR.png | Bin 0 -> 1755 bytes images/helpImages/save.png | Bin 0 -> 1642 bytes images/helpImages/setBookmark.png | Bin 0 -> 1532 bytes images/helpImages/setRoot.png | Bin 0 -> 1810 bytes images/helpImages/shortcuts.png | Bin 0 -> 1703 bytes images/helpImages/speaker.png | Bin 0 -> 658 bytes images/helpImages/updateLibrary.png | Bin 0 -> 1589 bytes images/helpImages/zoom.png | Bin 0 -> 1618 bytes images/icon.png | Bin 0 -> 20829 bytes images/importLibrary.png | Bin 0 -> 28417 bytes images/new.png | Bin 0 -> 17139 bytes images/next.png | Bin 0 -> 21672 bytes images/nextComic.png | Bin 0 -> 23796 bytes images/notCover.png | Bin 0 -> 104820 bytes images/open.png | Bin 0 -> 21566 bytes images/openFolder.png | Bin 0 -> 30976 bytes images/openLibrary.png | Bin 0 -> 29748 bytes images/options.png | Bin 0 -> 20699 bytes images/prev.png | Bin 0 -> 24468 bytes images/previousComic.png | Bin 0 -> 23714 bytes images/properties.png | Bin 0 -> 21282 bytes images/removeLibrary.png | Bin 0 -> 19700 bytes images/rotateL.png | Bin 0 -> 27714 bytes images/rotateR.png | Bin 0 -> 27567 bytes images/save.png | Bin 0 -> 23937 bytes images/setAllRead.png | Bin 0 -> 25807 bytes images/setAllUnread.png | Bin 0 -> 26128 bytes images/setBookmark.png | Bin 0 -> 17421 bytes images/setRead.png | Bin 0 -> 18976 bytes images/setRoot.png | Bin 0 -> 25357 bytes images/setUnread.png | Bin 0 -> 22095 bytes images/shortcuts.png | Bin 0 -> 16124 bytes images/showMarks.png | Bin 0 -> 24751 bytes images/up.png | Bin 0 -> 702 bytes images/updateLibrary.png | Bin 0 -> 20542 bytes images/zoom.png | Bin 0 -> 17891 bytes 180 files changed, 14355 insertions(+) create mode 100644 COPYING.txt create mode 100644 README.txt create mode 100644 YACReader/YACReader.pro create mode 100644 YACReader/bookmarks.cpp create mode 100644 YACReader/bookmarks.h create mode 100644 YACReader/bookmarks_dialog.cpp create mode 100644 YACReader/bookmarks_dialog.h create mode 100644 YACReader/comic.cpp create mode 100644 YACReader/comic.h create mode 100644 YACReader/configuration.cpp create mode 100644 YACReader/configuration.h create mode 100644 YACReader/files.qrc create mode 100644 YACReader/goto_dialog.cpp create mode 100644 YACReader/goto_dialog.h create mode 100644 YACReader/goto_flow.cpp create mode 100644 YACReader/goto_flow.h create mode 100644 YACReader/icon.ico create mode 100644 YACReader/icon.rc create mode 100644 YACReader/images.qrc create mode 100644 YACReader/magnifying_glass.cpp create mode 100644 YACReader/magnifying_glass.h create mode 100644 YACReader/main.cpp create mode 100644 YACReader/main_window_viewer.cpp create mode 100644 YACReader/main_window_viewer.h create mode 100644 YACReader/options_dialog.cpp create mode 100644 YACReader/options_dialog.h create mode 100644 YACReader/render.cpp create mode 100644 YACReader/render.h create mode 100644 YACReader/shortcuts_dialog.cpp create mode 100644 YACReader/shortcuts_dialog.h create mode 100644 YACReader/translator.cpp create mode 100644 YACReader/translator.h create mode 100644 YACReader/viewer.cpp create mode 100644 YACReader/viewer.h create mode 100644 YACReader/yacreader_es.qm create mode 100644 YACReader/yacreader_es.ts create mode 100644 YACReader/yacreader_fr.ts create mode 100644 YACReaderLibrary/YACReaderLibrary.pro create mode 100644 YACReaderLibrary/add_library_dialog.cpp create mode 100644 YACReaderLibrary/add_library_dialog.h create mode 100644 YACReaderLibrary/comic_flow.cpp create mode 100644 YACReaderLibrary/comic_flow.h create mode 100644 YACReaderLibrary/create_library_dialog.cpp create mode 100644 YACReaderLibrary/create_library_dialog.h create mode 100644 YACReaderLibrary/data_base_management.cpp create mode 100644 YACReaderLibrary/data_base_management.h create mode 100644 YACReaderLibrary/export_library_dialog.cpp create mode 100644 YACReaderLibrary/export_library_dialog.h create mode 100644 YACReaderLibrary/files.qrc create mode 100644 YACReaderLibrary/icon.ico create mode 100644 YACReaderLibrary/icon.rc create mode 100644 YACReaderLibrary/icon2.ico create mode 100644 YACReaderLibrary/images.qrc create mode 100644 YACReaderLibrary/import_library_dialog.cpp create mode 100644 YACReaderLibrary/import_library_dialog.h create mode 100644 YACReaderLibrary/library_creator.cpp create mode 100644 YACReaderLibrary/library_creator.h create mode 100644 YACReaderLibrary/library_window.cpp create mode 100644 YACReaderLibrary/library_window.h create mode 100644 YACReaderLibrary/main.cpp create mode 100644 YACReaderLibrary/options_dialog.cpp create mode 100644 YACReaderLibrary/options_dialog.h create mode 100644 YACReaderLibrary/package_manager.cpp create mode 100644 YACReaderLibrary/package_manager.h create mode 100644 YACReaderLibrary/properties_dialog.cpp create mode 100644 YACReaderLibrary/properties_dialog.h create mode 100644 YACReaderLibrary/rename_library_dialog.cpp create mode 100644 YACReaderLibrary/rename_library_dialog.h create mode 100644 YACReaderLibrary/yacreaderlibrary_es.qm create mode 100644 YACReaderLibrary/yacreaderlibrary_es.ts create mode 100644 common/check_new_version.cpp create mode 100644 common/check_new_version.h create mode 100644 common/custom_widgets.cpp create mode 100644 common/custom_widgets.h create mode 100644 common/pictureflow.cpp create mode 100644 common/pictureflow.h create mode 100644 common/qnaturalsorting.cpp create mode 100644 common/qnaturalsorting.h create mode 100644 files/about.html create mode 100644 files/about_es_ES.html create mode 100644 files/helpYACReader.html create mode 100644 files/helpYACReaderLibrary.html create mode 100644 files/helpYACReaderLibrary_es_ES.html create mode 100644 files/helpYACReader_es_ES.html create mode 100644 files/shortcuts.html create mode 100644 files/shortcuts2.html create mode 100644 files/translator.html create mode 100644 generateVS2010Projects.bat create mode 100644 images/adjustToFullSize.png create mode 100644 images/alwaysOnTop.png create mode 100644 images/bookmark.png create mode 100644 images/center.png create mode 100644 images/close.png create mode 100644 images/colapse.png create mode 100644 images/comicFolder.png create mode 100644 images/coversPackage.png create mode 100644 images/deleteLibrary.png create mode 100644 images/dictionary.png create mode 100644 images/doublePage.png create mode 100644 images/down.png create mode 100644 images/edit.png create mode 100644 images/expand.png create mode 100644 images/exportLibrary.png create mode 100644 images/fit.png create mode 100644 images/flow1.png create mode 100644 images/flow2.png create mode 100644 images/flow3.png create mode 100644 images/folder.png create mode 100644 images/goto.png create mode 100644 images/help.png create mode 100644 images/helpImages/bookmark.png create mode 100644 images/helpImages/center.png create mode 100644 images/helpImages/colapse.png create mode 100644 images/helpImages/comicFolder.png create mode 100644 images/helpImages/coversPackage.png create mode 100644 images/helpImages/deleteLibrary.png create mode 100644 images/helpImages/doublePage.png create mode 100644 images/helpImages/edit.png create mode 100644 images/helpImages/expand.png create mode 100644 images/helpImages/exportLibrary.png create mode 100644 images/helpImages/fit.png create mode 100644 images/helpImages/flow1.png create mode 100644 images/helpImages/flow2.png create mode 100644 images/helpImages/flow3.png create mode 100644 images/helpImages/folder.png create mode 100644 images/helpImages/goto.png create mode 100644 images/helpImages/help.png create mode 100644 images/helpImages/icon.png create mode 100644 images/helpImages/importLibrary.png create mode 100644 images/helpImages/keyboard.png create mode 100644 images/helpImages/mouse.png create mode 100644 images/helpImages/new.png create mode 100644 images/helpImages/next.png create mode 100644 images/helpImages/nextComic.png create mode 100644 images/helpImages/notCover.png create mode 100644 images/helpImages/open.png create mode 100644 images/helpImages/openFolder.png create mode 100644 images/helpImages/openLibrary.png create mode 100644 images/helpImages/options.png create mode 100644 images/helpImages/prev.png create mode 100644 images/helpImages/previousComic.png create mode 100644 images/helpImages/properties.png create mode 100644 images/helpImages/removeLibrary.png create mode 100644 images/helpImages/rotateL.png create mode 100644 images/helpImages/rotateR.png create mode 100644 images/helpImages/save.png create mode 100644 images/helpImages/setBookmark.png create mode 100644 images/helpImages/setRoot.png create mode 100644 images/helpImages/shortcuts.png create mode 100644 images/helpImages/speaker.png create mode 100644 images/helpImages/updateLibrary.png create mode 100644 images/helpImages/zoom.png create mode 100644 images/icon.png create mode 100644 images/importLibrary.png create mode 100644 images/new.png create mode 100644 images/next.png create mode 100644 images/nextComic.png create mode 100644 images/notCover.png create mode 100644 images/open.png create mode 100644 images/openFolder.png create mode 100644 images/openLibrary.png create mode 100644 images/options.png create mode 100644 images/prev.png create mode 100644 images/previousComic.png create mode 100644 images/properties.png create mode 100644 images/removeLibrary.png create mode 100644 images/rotateL.png create mode 100644 images/rotateR.png create mode 100644 images/save.png create mode 100644 images/setAllRead.png create mode 100644 images/setAllUnread.png create mode 100644 images/setBookmark.png create mode 100644 images/setRead.png create mode 100644 images/setRoot.png create mode 100644 images/setUnread.png create mode 100644 images/shortcuts.png create mode 100644 images/showMarks.png create mode 100644 images/up.png create mode 100644 images/updateLibrary.png create mode 100644 images/zoom.png diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 00000000..94a9ed02 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/README.txt b/README.txt new file mode 100644 index 00000000..f2e8f777 --- /dev/null +++ b/README.txt @@ -0,0 +1,4 @@ +This software was developed by Luis Ángel San Martín Rodríguez (luisangelsm@gmail.com) under GPL v3 license (for more details read COPYING.txt). + +Project home page : http://code.google.com/p/yacreader/ + diff --git a/YACReader/YACReader.pro b/YACReader/YACReader.pro new file mode 100644 index 00000000..d8037321 --- /dev/null +++ b/YACReader/YACReader.pro @@ -0,0 +1,54 @@ +# ##################################################################### +# Automatically generated by qmake (2.01a) mié 8. oct 20:54:05 2008 +# ##################################################################### +TEMPLATE = app +TARGET = +DEPENDPATH += . \ + release +INCLUDEPATH += . +INCLUDEPATH += ../common +QT += network webkit phonon +CONFIG += release +CONFIG -= flat + +# Input +HEADERS += comic.h \ + configuration.h \ + goto_dialog.h \ + magnifying_glass.h \ + main_window_viewer.h \ + viewer.h \ + ../common/pictureflow.h \ + ../common/custom_widgets.h \ + ../common/check_new_version.h \ + ../common/qnaturalsorting.h \ + goto_flow.h \ + options_dialog.h \ + bookmarks.h \ + bookmarks_dialog.h \ + render.h \ + shortcuts_dialog.h \ + translator.h +SOURCES += comic.cpp \ + configuration.cpp \ + goto_dialog.cpp \ + magnifying_glass.cpp \ + main.cpp \ + main_window_viewer.cpp \ + viewer.cpp \ + ../common/pictureflow.cpp \ + ../common/custom_widgets.cpp \ + ../common/check_new_version.cpp \ + ../common/qnaturalsorting.cpp \ + goto_flow.cpp \ + options_dialog.cpp \ + bookmarks.cpp \ + bookmarks_dialog.cpp \ + render.cpp \ + shortcuts_dialog.cpp \ + translator.cpp +RESOURCES += images.qrc \ + files.qrc +RC_FILE = icon.rc +TRANSLATIONS = yacreader_es.ts \ yacreader_fr.ts +FORMS += diff --git a/YACReader/bookmarks.cpp b/YACReader/bookmarks.cpp new file mode 100644 index 00000000..2e5978ec --- /dev/null +++ b/YACReader/bookmarks.cpp @@ -0,0 +1,159 @@ +#include "bookmarks.h" +#include +#include +#include +#include + +#include + +#define NUM_BOOKMARKS 250 + +Bookmarks::Bookmarks() +:lastPageIndex(0) +{ + list.load(); +} +void Bookmarks::setLastPage(int index,const QPixmap & page) +{ + lastPageIndex = index; + lastPage = page; +} +void Bookmarks::setBookmark(int index,const QPixmap & page) +{ + if(!bookmarks.contains(index)) + { + bookmarks.insert(index,page); + latestBookmarks.push_front(index); + if(latestBookmarks.count()>3) + { + bookmarks.remove(latestBookmarks.back()); + latestBookmarks.pop_back(); + } + } + else //udate de pixmap; + { + bookmarks[index]=page; + } +} + +void Bookmarks::removeBookmark(int index) +{ + bookmarks.remove(index); +} + +QList Bookmarks::getBookmarkPages() const +{ + return bookmarks.keys(); +} + +QPixmap Bookmarks::getBookmarkPixmap(int page) const +{ + return bookmarks.value(page); +} + +QPixmap Bookmarks::getLastPagePixmap() const +{ + return lastPage; +} + +int Bookmarks::getLastPage() const +{ + return lastPageIndex; +} + + +bool Bookmarks::isBookmark(int page) +{ + return bookmarks.contains(page); +} + +bool Bookmarks::imageLoaded(int page) +{ + return !bookmarks.value(page).isNull(); +} + +void Bookmarks::newComic(const QString & path) +{ + QFileInfo f(path); + QString comicID = f.fileName().toLower()+QString::number(f.size()); + clear(); + BookmarksList::Bookmark b = list.get(comicID); + comicPath=comicID; + lastPageIndex = b.lastPage; + latestBookmarks = b.bookmarks; + for(int i=0;i> list; + f.close(); + } +} + +void BookmarksList::save() +{ + QFile f(QCoreApplication::applicationDirPath()+"/bookmarks.yacr"); + f.open(QIODevice::WriteOnly); + QDataStream dataS(&f); + if(list.count()>NUM_BOOKMARKS) + deleteOldest(list.count()-NUM_BOOKMARKS); + dataS << list; + f.close(); +} + + +void BookmarksList::deleteOldest(int num) +{ + QString comic; + QDateTime date(QDate(10000,1,1));//TODO MAX_DATE?? + for(QMap::const_iterator itr=list.begin();itr!=list.end();itr++) + { + if(itr->addedadded; + } + } + list.remove(comic); +} + +void BookmarksList::add(const QString & comicID, const Bookmark & b) +{ + list.insert(comicID,b); +} + +BookmarksList::Bookmark BookmarksList::get(const QString & comicID) +{ + //if(list.contains(comicID) + return list.value(comicID); +} \ No newline at end of file diff --git a/YACReader/bookmarks.h b/YACReader/bookmarks.h new file mode 100644 index 00000000..adb0b835 --- /dev/null +++ b/YACReader/bookmarks.h @@ -0,0 +1,77 @@ +#ifndef BOOKMARKS_H +#define BOOKMARKS_H + +#include +#include +#include +#include +#include +#include +class BookmarksList +{ +public: + struct Bookmark { + int lastPage; + QList bookmarks; + QDateTime added; + Bookmark():lastPage(0){}; + friend QDataStream & operator<< ( QDataStream & out, const Bookmark & bm ) + { + out << bm.lastPage; + out << bm.bookmarks; + out << bm.added; + return out; + } + friend QDataStream & operator>> ( QDataStream & in, Bookmark & bm ) + { + in >> bm.lastPage; + in >> bm.bookmarks; + in >> bm.added; + return in; + } + + }; + BookmarksList(){} + void load(); + void save(); + void add(const QString & comicID, const Bookmark & b); + Bookmark get(const QString & comicID); +protected: + QMap list; + void deleteOldest(int num); + +}; + +class Bookmarks : public QObject +{ + Q_OBJECT + + protected: + QString comicPath; + //bookmarks setted by the user + QMap bookmarks; + QList latestBookmarks; + //last page readed + int lastPageIndex; + QPixmap lastPage; + BookmarksList list; + QDateTime added; + + public: + Bookmarks(); + void setLastPage(int index,const QPixmap & page); + void setBookmark(int index,const QPixmap & page); + void removeBookmark(int index); + QList getBookmarkPages() const; + QPixmap getBookmarkPixmap(int page) const; + QPixmap getLastPagePixmap() const; + int getLastPage() const; + bool isBookmark(int page); + bool imageLoaded(int page); + void newComic(const QString & path); + void clear(); + void save(); + +}; + +#endif // BOOKMARKS_H diff --git a/YACReader/bookmarks_dialog.cpp b/YACReader/bookmarks_dialog.cpp new file mode 100644 index 00000000..40d4007d --- /dev/null +++ b/YACReader/bookmarks_dialog.cpp @@ -0,0 +1,182 @@ +#include "bookmarks_dialog.h" + +#include +#include +#include +#include +#include +#include + +#include "bookmarks.h" + +BookmarksDialog::BookmarksDialog(QWidget * parent) + :QDialog(parent,Qt::FramelessWindowHint) +{ + setModal(true); + + animation = new QPropertyAnimation(this,"windowOpacity"); + animation->setDuration(150); + + QHBoxLayout * layout = new QHBoxLayout(); + + //bookmarks + QGridLayout * bookmarksL = new QGridLayout(); + + pages.push_back(new QLabel(tr("Lastest Page"))); + for(int i=0;i<3;i++) + pages.push_back(new QLabel("-")); + + int heightDesktopResolution = QApplication::desktop()->screenGeometry().height(); + int height,width; + height = heightDesktopResolution*0.50; + width = height*0.65; + + coverSize = QSize(width,height); + + for(int i=0;i<4;i++) + { + QLabel * l = new QLabel(); + l->setFixedSize(coverSize); + l->setScaledContents(false); + //l->setPixmap(QPixmap(":/images/notCover.png")); + l->installEventFilter(this); + images.push_back(l); + } + + for(int i=0;i<3;i++) + bookmarksL->addWidget(pages.at(i+1),0,i,Qt::AlignCenter); + + for(int i=0;i<3;i++) + bookmarksL->addWidget(images.at(i+1),1,i,Qt::AlignCenter); + + + //last page + QGridLayout * lp = new QGridLayout(); + lp->addWidget(pages.at(0),0,0,Qt::AlignCenter); + lp->addWidget(images.at(0),1,0,Qt::AlignCenter); + + layout->addLayout(bookmarksL); + QFrame *f = new QFrame( this ); + f->setFrameStyle( QFrame::VLine | QFrame::Sunken ); + layout->addWidget(f); + layout->addLayout(lp); + + QHBoxLayout * buttons = new QHBoxLayout(); + + cancel = new QPushButton(tr("Close")); + cancel->setFlat(true); + connect(cancel,SIGNAL(clicked()),this,SLOT(hide())); + buttons->addStretch(); + buttons->addWidget(cancel); + + QVBoxLayout * l = new QVBoxLayout(); + + l->addWidget(new QLabel(tr("Click on any image to go to the bookmark")),0,Qt::AlignCenter); + l->addLayout(layout); + l->addLayout(buttons); + + this->setPalette(QPalette(QColor(25,25,25))); + //this->setAutoFillBackground(true); + setLayout(l); +} + +void BookmarksDialog::setBookmarks(const Bookmarks & bm) +{ + lastPage = bm.getLastPage(); + if (lastPage > 0) + { + QPixmap p = bm.getLastPagePixmap(); + if(p.isNull()) + { + images.at(0)->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); + images.at(0)->setText(tr("Loading...")); + } + else + { + images.at(0)->setAlignment(Qt::AlignHCenter|Qt::AlignBottom); + images.at(0)->setPixmap(p.scaled(coverSize,Qt::KeepAspectRatio,Qt::SmoothTransformation)); + } + } + else + { + images.at(0)->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); + images.at(0)->setPixmap(QPixmap(":/images/notCover.png").scaled(coverSize,Qt::KeepAspectRatio,Qt::SmoothTransformation)); + + } + + QList l = bm.getBookmarkPages(); + int s = l.count(); + for(int i=0;isetText(QString::number(l.at(i)+1)); + QPixmap p = bm.getBookmarkPixmap(l.at(i)); + if(p.isNull()) + { + images.at(i+1)->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); + images.at(i+1)->setText(tr("Loading...")); + } + else + { + images.at(i+1)->setAlignment(Qt::AlignHCenter|Qt::AlignBottom); + images.at(i+1)->setPixmap(p.scaled(coverSize,Qt::KeepAspectRatio,Qt::SmoothTransformation)); + } + } + for(int i=s;i<3;i++) + { + pages.at(i+1)->setText("-"); + images.at(i+1)->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); + images.at(i+1)->setPixmap(QPixmap(":/images/notCover.png").scaled(coverSize,Qt::KeepAspectRatio,Qt::SmoothTransformation)); + } +} + +bool BookmarksDialog::eventFilter(QObject *obj, QEvent *event) +{ + if(event->type() == QEvent::MouseButtonPress) + { + if (obj == images.at(0)) + { + emit(goToPage(lastPage+1)); + close(); + event->accept(); + } + for(int i=1;i<=3;i++) + { + if(obj == images.at(i)) + { + bool b; + int page = pages.at(i)->text().toInt(&b); + if(b) + { + emit(goToPage(page)); + close(); + } + event->accept(); + } + } + } + // pass the event on to the parent class + return QDialog::eventFilter(obj, event); +} + +void BookmarksDialog::keyPressEvent(QKeyEvent * event) +{ + if(event->key() == Qt::Key_M) + hide(); +} + +void BookmarksDialog::show() +{ + QDialog::show(); + disconnect(animation,SIGNAL(finished()),this,SLOT(close())); + animation->setStartValue(0); + animation->setEndValue(1); + animation->start(); +} + +void BookmarksDialog::hide() +{ + connect(animation,SIGNAL(finished()),this,SLOT(close())); + animation->setStartValue(1); + animation->setEndValue(0); + animation->start(); +} diff --git a/YACReader/bookmarks_dialog.h b/YACReader/bookmarks_dialog.h new file mode 100644 index 00000000..363ed372 --- /dev/null +++ b/YACReader/bookmarks_dialog.h @@ -0,0 +1,45 @@ +#ifndef __BOOKMARKS_DIALOG_H +#define __BOOKMARKS_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +#include "bookmarks.h" + + class BookmarksDialog : public QDialog + { + Q_OBJECT + + protected: + QList pages; + QList images; + + int lastPage; + + QPushButton * accept; + QPushButton * cancel; + + QSize coverSize; + + bool eventFilter(QObject *obj, QEvent *event); + void keyPressEvent(QKeyEvent * event); + QPropertyAnimation * animation; + + public: + BookmarksDialog(QWidget * parent = 0); + + public slots: + void setBookmarks(const Bookmarks & bookmarks); + void show(); + void hide(); + + signals: + void goToPage(unsigned int page); + }; + +#endif // BOOKMARKS_DIALOG_H diff --git a/YACReader/comic.cpp b/YACReader/comic.cpp new file mode 100644 index 00000000..4be17ce1 --- /dev/null +++ b/YACReader/comic.cpp @@ -0,0 +1,289 @@ +#include "comic.h" + +#include +#include +#include +#include +#include +#include +#include "bookmarks.h" +#include "qnaturalsorting.h" + +#define EXTENSIONS << "*.jpg" << "*.jpeg" << "*.png" << "*.gif" << "*.tiff" << "*.tif" << "*.bmp" + +//----------------------------------------------------------------------------- +Comic::Comic() +:_pages(),_index(0),_path(),_loaded(false),bm(new Bookmarks()) +{ + setup(); +} +//----------------------------------------------------------------------------- +Comic::Comic(const QString pathFile) +:_pages(),_index(0),_path(pathFile),_loaded(false),bm(new Bookmarks()) +{ + setup(); + loadFromFile(pathFile); +} +//----------------------------------------------------------------------------- +void Comic::setup() +{ + connect(this,SIGNAL(pageChanged(int)),this,SLOT(checkIsBookmark(int))); + connect(this,SIGNAL(imageLoaded(int)),this,SLOT(updateBookmarkImage(int))); +} +//----------------------------------------------------------------------------- +void Comic::load(const QString & path) +{ + QFileInfo fi(path); + bm->newComic(path); + emit bookmarksLoaded(*bm); + if(fi.isFile()) + { + loadFromFile(path); + } + else + { + if(fi.isDir()) + { + loadFromDir(path); + } + } +} +//----------------------------------------------------------------------------- +void Comic::loadFromFile(const QString & pathFile) +{ + _path = QDir::cleanPath(pathFile); + //load files size + _7z = new QProcess(); + QStringList attributes; + attributes << "l" << "-ssc-" << "-r" << _path EXTENSIONS; + connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(loadSizes(void))); + connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SLOT(openingError(QProcess::ProcessError))); + _7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes); +} +//----------------------------------------------------------------------------- +void Comic::loadFromDir(const QString & pathDir) +{ + _pathDir = pathDir; + start(); +} +//----------------------------------------------------------------------------- +void Comic::run() +{ + QDir d(_pathDir); + QStringList l; + l EXTENSIONS; + d.setNameFilters(l); + d.setFilter(QDir::Files|QDir::NoDotAndDotDot); + //d.setSorting(QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + QFileInfoList list = d.entryInfoList(); + + qSort(list.begin(),list.end(),naturalSortLessThanCIFileInfo); + + int nPages = list.size(); + _pages.clear(); + _pages.resize(nPages); + if(nPages==0) + { + QMessageBox::critical(NULL,tr("No images found"),tr("There are not images on the selected folder")); + emit errorOpening(); + } + else + { + emit pageChanged(0); // this indicates new comic, index=0 + emit numPages(_pages.size()); + _loaded = true; + + for(int i=0;ireadAllStandardOutput(); + QList lines = ba.split('\n'); + QByteArray line; + QString name; + foreach(line,lines) + { + if(rx.indexIn(QString(line))!=-1) + { + _sizes.push_back(rx.cap(1).toInt()); + name = rx.cap(3).trimmed(); + _order.push_back(name); + _fileNames.push_back(name); + } + } + if(_sizes.size()==0) + { + QMessageBox::critical(NULL,tr("File error"),tr("File not found or not images in file")); + emit errorOpening(); + return; + } + _pages.resize(_sizes.size()); + + emit pageChanged(0); // this indicates new comic, index=0 + emit numPages(_pages.size()); + _loaded = true; + + _cfi=0; + qSort(_fileNames.begin(),_fileNames.end(), naturalSortLessThanCI); + int i=0; + foreach(name,_fileNames) + { + _newOrder.insert(name,i); + i++; + } + _7ze = new QProcess(); + QStringList attributes; + attributes << "e" << "-ssc-" << "-so" << "-r" << _path EXTENSIONS; + connect(_7ze,SIGNAL(error(QProcess::ProcessError)),this,SLOT(openingError(QProcess::ProcessError))); + connect(_7ze,SIGNAL(readyReadStandardOutput()),this,SLOT(loadImages(void))); + connect(_7ze,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(loadFinished(void))); + _7ze->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes); +} +//----------------------------------------------------------------------------- +void Comic::loadImages() +{ + + QByteArray ba = _7ze->readAllStandardOutput(); + int s; + int rigthIndex; + while(ba.size()>0) + { + rigthIndex = _newOrder.value(_order[_cfi]); + s = _pages[rigthIndex].size(); + _pages[rigthIndex].append(ba.left(_sizes[_cfi]-s)); + ba.remove(0,_sizes[_cfi]-s); + if(_pages[rigthIndex].size()==static_cast(_sizes[_cfi])) + { + emit imageLoaded(rigthIndex); + emit imageLoaded(rigthIndex,_pages[rigthIndex]); + _cfi++; + } + } +} +//----------------------------------------------------------------------------- +void Comic::openingError(QProcess::ProcessError error) +{ + switch(error) + { + case QProcess::FailedToStart: + QMessageBox::critical(NULL,tr("7z not found"),tr("7z wasn't found in your PATH.")); + break; + case QProcess::Crashed: + QMessageBox::critical(NULL,tr("7z crashed"),tr("7z crashed.")); + break; + case QProcess::ReadError: + QMessageBox::critical(NULL,tr("7z reading"),tr("problem reading from 7z")); + break; + case QProcess::UnknownError: + QMessageBox::critical(NULL,tr("7z problem"),tr("Unknown error 7z")); + break; + default: + //TODO + break; + } + _loaded = false; + emit errorOpening(); +} +int Comic::nextPage() +{ + if(_index<_pages.size()-1) + _index++; + + emit pageChanged(_index); + + return _index; +} +//----------------------------------------------------------------------------- +int Comic::previousPage() +{ + if(_index>0) + _index--; + + emit pageChanged(_index); + + return _index; +} +//----------------------------------------------------------------------------- +void Comic::setIndex(unsigned int index) +{ + if(static_cast(index)<_pages.size()-1) + _index = index; + else + _index = _pages.size()-1; + + emit pageChanged(_index); +} +//----------------------------------------------------------------------------- +QPixmap * Comic::currentPage() +{ + QPixmap * p = new QPixmap(); + p->loadFromData(_pages[_index]); + return p; +} +//----------------------------------------------------------------------------- +QPixmap * Comic::operator[](unsigned int index) +{ + QPixmap * p = new QPixmap(); + p->loadFromData(_pages[index]); + return p; +} +//----------------------------------------------------------------------------- +bool Comic::loaded() +{ + return _loaded; +} +//----------------------------------------------------------------------------- +void Comic::loadFinished() +{ + emit imagesLoaded(); +} +//----------------------------------------------------------------------------- +void Comic::setBookmark() +{ + bm->setBookmark(_index,*operator[](_index)); + emit bookmarksLoaded(*bm); +} +//----------------------------------------------------------------------------- +void Comic::removeBookmark() +{ + bm->removeBookmark(_index); + emit bookmarksLoaded(*bm); +} +//----------------------------------------------------------------------------- +void Comic::saveBookmarks() +{ + bm->setLastPage(_index,*operator[](_index)); + bm->save(); +} +//----------------------------------------------------------------------------- +void Comic::checkIsBookmark(int index) +{ + emit isBookmark(bm->isBookmark(index)); +} +//----------------------------------------------------------------------------- +void Comic::updateBookmarkImage(int index) +{ + if(bm->isBookmark(index)) + { + bm->setBookmark(index,*operator[](index)); + emit bookmarksLoaded(*bm); + } + if(bm->getLastPage() == index) + { + bm->setLastPage(index,*operator[](index)); + emit bookmarksLoaded(*bm); + } + +} diff --git a/YACReader/comic.h b/YACReader/comic.h new file mode 100644 index 00000000..2e5a2d49 --- /dev/null +++ b/YACReader/comic.h @@ -0,0 +1,70 @@ +#ifndef __COMIC_H +#define __COMIC_H +#include +#include +#include +#include +#include + +#include "bookmarks.h" + + class Comic : public QThread + { + Q_OBJECT + private: + //Comic pages, one QPixmap for each file. + QVector _pages; + QVector _sizes; + QStringList _fileNames; + QMap _newOrder; + QVector _order; + int _index; + QString _path; + bool _loaded; + QProcess * _7z; + QProcess * _7ze; + int _cfi; + QString _pathDir; + Bookmarks * bm; + void run(); + public: + //Constructors + Comic(); + Comic(const QString pathFile); + void setup(); + //Load pages from file + void load(const QString & path); + void loadFromFile(const QString & pathFile); + void loadFromDir(const QString & pathDir); + int nextPage(); + int previousPage(); + void setIndex(unsigned int index); + unsigned int getIndex(){return _index;}; + unsigned int numPages(){return _pages.size();} + QPixmap * currentPage(); + bool loaded(); + QPixmap * operator[](unsigned int index); + QVector * getRawData(){return &_pages;}; + public slots: + void loadImages(); + void loadSizes(); + void openingError(QProcess::ProcessError error); + void loadFinished(); + void setBookmark(); + void removeBookmark(); + void saveBookmarks(); + void checkIsBookmark(int index); + void updateBookmarkImage(int); + signals: + void imagesLoaded(); + void imageLoaded(int index); + void imageLoaded(int index,const QByteArray & image); + void pageChanged(int index); + void numPages(unsigned int numPages); + void errorOpening(); + void isBookmark(bool); + void bookmarksLoaded(const Bookmarks &); + + }; + +#endif diff --git a/YACReader/configuration.cpp b/YACReader/configuration.cpp new file mode 100644 index 00000000..1235f06b --- /dev/null +++ b/YACReader/configuration.cpp @@ -0,0 +1,199 @@ +#include "configuration.h" + +#include +#include +#include +#include +#include +#include + + + +#define PATH "PATH" +#define MAG_GLASS_SIZE "MAG_GLASS_SIZE" +#define ZOOM_LEVEL "ZOOM_LEVEL" +#define SLIDE_SIZE "SLIDE_SIZE" +#define FIT "FIT" +#define FLOW_TYPE "FLOW_TYPE" +#define FULLSCREEN "FULLSCREEN" +#define FIT_TO_WIDTH_RATIO "FIT_TO_WIDTH_RATIO" +#define POS "POS" +#define SIZE "SIZE" +#define MAXIMIZED "MAXIMIZED" +#define DOUBLE_PAGE "DOUBLE_PAGE" +#define ADJUST_TO_FULL_SIZE "ADJUST_TO_FULL_SIZE" +#define BACKGROUND_COLOR "BACKGROUND_COLOR" +#define ALWAYS_ON_TOP "ALWAYS_ON_TOP" + +Configuration::Configuration() +{ + //read configuration + load("/YACReader.conf"); +} + +Configuration::Configuration(const Configuration & conf) +{ + //nothing +} + +void Configuration::load(const QString & path) +{ + //load default configuration + defaultPath = "."; + magnifyingGlassSize = QSize(350,175); + gotoSlideSize = QSize(126,200); //normal + //gotoSlideSize = QSize(79,125); //small + //gotoSlideSize = QSize(173,275); //big + //gotoSlideSize = QSize(220,350); //huge + zoomLevel = 0.5; + adjustToWidth = true; + flowType = PictureFlow::Strip; + fullScreen = false; + fitToWidthRatio = 1; + windowSize = QSize(0,0); + maximized = false; + doublePage = false; + adjustToFullSize = false; + backgroundColor = QColor(0,0,0); + alwaysOnTop = false; + + //load from file + QFile f(QCoreApplication::applicationDirPath()+path); + f.open(QIODevice::ReadOnly); + QTextStream txtS(&f); + QString content = txtS.readAll(); + QStringList lines = content.split('\n'); + QString line,name; + int i=0; + foreach(line,lines) + { + if((i%2)==0) + { + name = line.trimmed(); + } + else + { + if(name==PATH) + defaultPath = line.trimmed(); + else + if(name==MAG_GLASS_SIZE) + { + QStringList values = line.split(','); + magnifyingGlassSize = QSize(values[0].toInt(),values[1].toInt()); + } + else + if(name==ZOOM_LEVEL) + zoomLevel = line.toFloat(); + else + if(name==SLIDE_SIZE) + { + int height = line.toInt(); + gotoSlideSize = QSize(static_cast(height/SLIDE_ASPECT_RATIO),height); + } + else + if(name==FIT) + adjustToWidth = line.toInt(); + else + if(name==FLOW_TYPE) + flowType = (PictureFlow::FlowType)line.toInt(); + else + if(name==FULLSCREEN) + fullScreen = line.toInt(); + else + if(name==FIT_TO_WIDTH_RATIO) + fitToWidthRatio = line.toFloat(); + else + if(name==POS) + { + QStringList l = line.split(','); + windowPos = QPoint(l[0].toInt(),l[1].toInt()); + } + else + if(name==SIZE) + { + QStringList l = line.split(','); + windowSize = QSize(l[0].toInt(),l[1].toInt()); + } + else + if(name==MAXIMIZED) + maximized = line.toInt(); + else + if(name==DOUBLE_PAGE) + doublePage = line.toInt(); + else + if(name==ADJUST_TO_FULL_SIZE) + adjustToFullSize = line.toInt(); + else + if(name==BACKGROUND_COLOR) + { + QStringList l = line.split(','); + backgroundColor = QColor(l[0].toInt(),l[1].toInt(),l[2].toInt()); + } + else + if(name==ALWAYS_ON_TOP) + alwaysOnTop = line.toInt(); + + + + } + i++; + } +} + +void Configuration::save() +{ + QFile f(QCoreApplication::applicationDirPath()+"/YACReader.conf"); + if(!f.open(QIODevice::WriteOnly)) + { + QMessageBox::critical(NULL,tr("Saving config file...."),tr("There was a problem saving YACReader configuration. Please, check if you have enough permissions in the YACReader root folder.")); + } + else + { + QTextStream txtS(&f); + + txtS << PATH << "\n"; + txtS << defaultPath << "\n"; + + txtS << MAG_GLASS_SIZE << "\n"; + txtS << magnifyingGlassSize.width() <<","<< magnifyingGlassSize.height() << "\n"; + + txtS << ZOOM_LEVEL << "\n"; + txtS << zoomLevel << "\n"; + + txtS << SLIDE_SIZE << "\n"; + txtS << gotoSlideSize.height() << "\n"; + + txtS << FIT << "\n"; + txtS << (int)adjustToWidth << "\n"; + + txtS << FLOW_TYPE << "\n"; + txtS << (int)flowType << "\n"; + + txtS << FULLSCREEN << "\n"; + txtS << (int)fullScreen << "\n"; + + txtS << FIT_TO_WIDTH_RATIO << "\n"; + txtS << fitToWidthRatio << "\n"; + + txtS << POS << "\n"; + txtS << windowPos.x() << "," << windowPos.y() << "\n"; + + txtS << SIZE << "\n"; + txtS << windowSize.width() << "," << windowSize.height() << "\n"; + + txtS << MAXIMIZED << "\n"; + txtS << (int)maximized << "\n"; + + txtS << DOUBLE_PAGE << "\n"; + txtS << (int)doublePage << "\n"; + + txtS << ADJUST_TO_FULL_SIZE << "\n"; + txtS << (int) adjustToFullSize << "\n"; + + txtS << BACKGROUND_COLOR << "\n"; + txtS << backgroundColor.red() << "," << backgroundColor.green() << "," << backgroundColor.blue() << "\n"; + + txtS << ALWAYS_ON_TOP << "\n"; + txtS << (int)alwaysOnTop << "\n"; + } +} diff --git a/YACReader/configuration.h b/YACReader/configuration.h new file mode 100644 index 00000000..f2644d51 --- /dev/null +++ b/YACReader/configuration.h @@ -0,0 +1,77 @@ +#ifndef __CONFIGURATION_H +#define __CONFIGURATION_H +#include +#include +#include +#include "pictureflow.h" + +#define CONF_FILE_PATH "." +#define SLIDE_ASPECT_RATIO 1.585 + + class Configuration : public QObject + { + Q_OBJECT + + private: + QString defaultPath; + //configuration properties + QSize magnifyingGlassSize; + QSize gotoSlideSize; + float zoomLevel; + bool adjustToWidth; + bool fullScreen; + PictureFlow::FlowType flowType; + float fitToWidthRatio; + QPoint windowPos; + QSize windowSize; + bool maximized; + bool doublePage; + bool alwaysOnTop; + bool adjustToFullSize; + QColor backgroundColor; + + Configuration(); + Configuration(const Configuration & conf); + void load(const QString & path = CONF_FILE_PATH); + + public: + static Configuration & getConfiguration() + { + static Configuration configuration; + return configuration; + }; + QString getDefaultPath() { return defaultPath; }; + void setDefaultPath(QString defaultPath){this->defaultPath = defaultPath;}; + QSize getMagnifyingGlassSize() { return magnifyingGlassSize;}; + void setMagnifyingGlassSize(const QSize & mgs) { magnifyingGlassSize = mgs;}; + QSize getGotoSlideSize() { return gotoSlideSize;}; + void setGotoSlideSize(const QSize & gss) { gotoSlideSize = gss;}; + float getZoomLevel() { return zoomLevel;}; + void setZoomLevel(float zl) { zoomLevel = zl;}; + bool getAdjustToWidth() {return adjustToWidth;}; + void setAdjustToWidth(bool atw=true) {adjustToWidth = atw;}; + PictureFlow::FlowType getFlowType(){return flowType;}; + void setFlowType(PictureFlow::FlowType type){flowType = type;}; + bool getFullScreen(){return fullScreen;}; + void setFullScreen(bool f){fullScreen = f;}; + float getFitToWidthRatio(){return fitToWidthRatio;}; + void setFitToWidthRatio(float r){fitToWidthRatio = r;}; + QPoint getPos(){return windowPos;}; + void setPos(QPoint p){windowPos = p;}; + QSize getSize(){return windowSize;}; + void setSize(QSize s){windowSize = s;}; + bool getMaximized(){return maximized;}; + void setMaximized(bool b){maximized = b;}; + bool getDoublePage(){return doublePage;}; + void setDoublePage(bool b){doublePage = b;}; + void setAdjustToFullSize(bool b){adjustToFullSize = b;}; + bool getAdjustToFullSize(){return adjustToFullSize;}; + void setBackgroundColor(const QColor& color){backgroundColor = color;}; + QColor getBackgroundColor(){return backgroundColor;}; + void setAlwaysOnTop(bool b){alwaysOnTop = b;}; + bool getAlwaysOnTop(){return alwaysOnTop;}; + void save(); + + }; + +#endif diff --git a/YACReader/files.qrc b/YACReader/files.qrc new file mode 100644 index 00000000..9dcfa4f3 --- /dev/null +++ b/YACReader/files.qrc @@ -0,0 +1,15 @@ + + + ../files/about.html + ../files/helpYACReader.html + ../files/shortcuts.html + ../files/shortcuts2.html + ../files/translator.html + ./yacreader_es.qm + + + + ../files/about_es_ES.html + ../files/helpYACReader_es_ES.html + + diff --git a/YACReader/goto_dialog.cpp b/YACReader/goto_dialog.cpp new file mode 100644 index 00000000..42d0bec0 --- /dev/null +++ b/YACReader/goto_dialog.cpp @@ -0,0 +1,71 @@ +#include "goto_dialog.h" + +#include +#include +#include + + + +GoToDialog::GoToDialog(QWidget * parent) +:QDialog(parent) +{ + setupUI(); +} + +void GoToDialog::setupUI() +{ + textLabel = new QLabel(tr("Page : ")); + pageNumber = new QLineEdit; + v = new QIntValidator(this); + v->setBottom(1); + pageNumber->setValidator(v); + textLabel->setBuddy(pageNumber); + textLabel->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + + accept = new QPushButton(tr("Go To")); + connect(accept,SIGNAL(clicked()),this,SLOT(goTo())); + cancel = new QPushButton(tr("Cancel")); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + QHBoxLayout *topLayout = new QHBoxLayout; + + topLayout->addWidget(textLabel); + topLayout->addWidget(pageNumber); + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(accept); + bottomLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget(numPagesLabel = new QLabel(tr("Total pages : "))); + mainLayout->addLayout(topLayout); + mainLayout->addStretch(); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout *imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(); + QPixmap p(":/images/goto.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setWindowTitle(tr("Go to...")); + setModal (true); +} + +void GoToDialog::goTo() +{ + unsigned int page = pageNumber->text().toInt(); + pageNumber->clear(); + emit(goToPage(page)); + close(); +} + +void GoToDialog::setNumPages(unsigned int numPages) +{ + numPagesLabel->setText(tr("Total pages : ")+QString::number(numPages)); + v->setTop(numPages); +} diff --git a/YACReader/goto_dialog.h b/YACReader/goto_dialog.h new file mode 100644 index 00000000..9d13e647 --- /dev/null +++ b/YACReader/goto_dialog.h @@ -0,0 +1,31 @@ +#ifndef __GOTODIALOG_H +#define __GOTODIALOG_H + +#include +#include +#include +#include +#include + + class GoToDialog : public QDialog + { + Q_OBJECT + public: + GoToDialog(QWidget * parent = 0); + private: + QLabel * numPagesLabel; + QLabel * textLabel; + QLineEdit * pageNumber; + QIntValidator * v; + QPushButton * accept; + QPushButton * cancel; + void setupUI(); + public slots: + void goTo(); + void setNumPages(unsigned int numPages); + signals: + void goToPage(unsigned int page); + }; + +#endif + diff --git a/YACReader/goto_flow.cpp b/YACReader/goto_flow.cpp new file mode 100644 index 00000000..ca403783 --- /dev/null +++ b/YACReader/goto_flow.cpp @@ -0,0 +1,373 @@ +#include "goto_flow.h" +#include "configuration.h" +#include "comic.h" +#include "custom_widgets.h" + +#include +#include +#include +#include +#include +#include +#include + +/*#define WIDTH 126 +#define HEIGHT 200*/ + +QMutex mutexGoToFlow; + + + +GoToFlow::GoToFlow(QWidget *parent,PictureFlow::FlowType flowType) +:QWidget(parent),ready(false) +{ + updateTimer = new QTimer; + connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateImageData())); + + worker = new PageLoader; + + QVBoxLayout * layout = new QVBoxLayout(this); + flow = new YACReaderFlow(this,flowType); + flow->setReflectionEffect(PictureFlow::PlainReflection); + imageSize = Configuration::getConfiguration().getGotoSlideSize(); + + flow->setSlideSize(imageSize); + connect(flow,SIGNAL(centerIndexChanged(int)),this,SLOT(setPageNumber(int))); + connect(flow,SIGNAL(selected(unsigned int)),this,SLOT(goTo())); + + QHBoxLayout * bottom = new QHBoxLayout(this); + bottom->addStretch(); + bottom->addWidget(new QLabel(tr("Page : "),this)); + bottom->addWidget(edit = new QLineEdit(this)); + v = new QIntValidator(this); + v->setBottom(1); + edit->setValidator(v); + edit->setAlignment(Qt::AlignRight|Qt::AlignVCenter); + edit->setFixedWidth(40); + edit->setSizePolicy(QSizePolicy(QSizePolicy::Fixed,QSizePolicy::Minimum)); + + centerButton = new QPushButton(this); + centerButton->setIcon(QIcon(":/images/center.png")); + connect(centerButton,SIGNAL(clicked()),this,SLOT(centerSlide())); + bottom->addWidget(centerButton); + + goToButton = new QPushButton(this); + goToButton->setIcon(QIcon(":/images/goto.png")); + connect(goToButton,SIGNAL(clicked()),this,SLOT(goTo())); + bottom->addWidget(goToButton); + + bottom->addStretch(); + + layout->addWidget(flow); + layout->addLayout(bottom); + layout->setStretchFactor(flow,1); + layout->setStretchFactor(bottom,0); + layout->setMargin(0); + layout->setSpacing(0); + setLayout(layout); + this->setAutoFillBackground(true); + resize(static_cast(5*imageSize.width()),static_cast(imageSize.height()*1.7)); + + //install eventFilter + //flow->installEventFilter(this); + edit->installEventFilter(this); + centerButton->installEventFilter(this); + goToButton->installEventFilter(this); + + connect(edit,SIGNAL(returnPressed()),goToButton,SIGNAL(clicked())); + + this->setCursor(QCursor(Qt::ArrowCursor)); +} + +void GoToFlow::goTo() +{ + //emit(goToPage(flow->centerIndex()+1)); + emit(goToPage(edit->text().toInt())); +} + +void GoToFlow::setPageNumber(int page) +{ + edit->setText(QString::number(page+1)); +} + +void GoToFlow::centerSlide() +{ + int page = edit->text().toInt()-1; + int distance = flow->centerIndex()-page; + + if(abs(distance)>10) + { + if(distance<0) + flow->setCenterIndex(flow->centerIndex()+(-distance)-10); + else + flow->setCenterIndex(flow->centerIndex()-distance+10); + } + + flow->showSlide(page); +} + +void GoToFlow::centerSlide(int slide) +{ + if(flow->centerIndex()!=slide) + { + flow->setCenterIndex(slide); + if(ready)// load images if pages are loaded. + { + //worker->reset(); //BUG FIXED : image didn't load if worker was working + preload(); + } + } +} + +void GoToFlow::setNumSlides(unsigned int slides) +{ + // numPagesLabel->setText(tr("Total pages : ")+QString::number(slides)); + // numPagesLabel->adjustSize(); + imagesReady.clear(); + imagesReady.fill(false,slides); + + rawImages.clear(); + rawImages.resize(slides); + + v->setTop(slides); + + SlideInitializer * si = new SlideInitializer(flow,slides); + + imagesLoaded.clear(); + imagesLoaded.fill(false,slides); + + imagesSetted.clear(); + imagesSetted.fill(false,slides); + + numImagesLoaded = 0; + + connect(flow, SIGNAL(centerIndexChanged(int)), this, SLOT(preload())); + + ready = true; + worker->reset(); + + si->start(); +} + +void GoToFlow::reset() +{ + updateTimer->stop(); + /*imagesLoaded.clear(); + numImagesLoaded = 0; + imagesReady.clear(); + rawImages.clear();*/ + ready = false; +} + +void GoToFlow::setImageReady(int index,const QByteArray & image) +{ + rawImages[index]=image; + imagesReady[index]=true; + preload(); +} + +void GoToFlow::preload() +{ + if(numImagesLoaded < imagesLoaded.size()) + updateTimer->start(70); +} + +void GoToFlow::updateImageData() +{ + // can't do anything, wait for the next possibility + if(worker->busy()) + return; + + // set image of last one + int idx = worker->index(); + if( idx >= 0 && !worker->result().isNull()) + { + if(!imagesSetted[idx]) + { + flow->setSlide(idx, worker->result()); + imagesSetted[idx] = true; + numImagesLoaded++; + rawImages[idx].clear();; //release memory + } + + } + + // try to load only few images on the left and right side + // i.e. all visible ones plus some extra +#define COUNT 8 + int indexes[2*COUNT+1]; + int center = flow->centerIndex(); + indexes[0] = center; + for(int j = 0; j < COUNT; j++) + { + indexes[j*2+1] = center+j+1; + indexes[j*2+2] = center-j-1; + } + for(int c = 0; c < 2*COUNT+1; c++) + { + int i = indexes[c]; + if((i >= 0) && (i < flow->slideCount())) + if(!imagesLoaded[i]&&imagesReady[i])//slide(i).isNull()) + { + // schedule thumbnail generation + imagesLoaded[i]=true; + worker->generate(i, flow->slideSize(),rawImages[i]); + return; + } + + } + + // no need to generate anything? stop polling... + updateTimer->stop(); +} + +bool GoToFlow::eventFilter(QObject *target, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) + { + QKeyEvent *keyEvent = static_cast(event); + int key = keyEvent->key(); + if((key==Qt::Key_Return)|| + (key==Qt::Key_Enter)|| + (key==Qt::Key_Space)|| + (key==Qt::Key_Left)|| + (key==Qt::Key_Right)|| + (key==Qt::Key_S)) + this->keyPressEvent(keyEvent); + } + return QWidget::eventFilter(target, event); +} + + +void GoToFlow::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) + { + case Qt::Key_Return: case Qt::Key_Enter: + goTo(); + centerSlide(); + break; + case Qt::Key_Space: + centerSlide(); + break; + case Qt::Key_S: + QCoreApplication::sendEvent(this->parent(),event); + break; + case Qt::Key_Left: case Qt::Key_Right: + QCoreApplication::sendEvent(flow,event); + } + + event->accept(); +} + +void GoToFlow::wheelEvent(QWheelEvent * event) +{ + if(event->delta()<0) + flow->showNext(); + else + flow->showPrevious(); + event->accept(); +} + +void GoToFlow::setFlowType(PictureFlow::FlowType flowType) +{ + flow->setFlowType(flowType); +} + +void GoToFlow::updateSize() //TODO : fix. it doesn't work. +{ + imageSize = Configuration::getConfiguration().getGotoSlideSize(); + flow->setSlideSize(imageSize); + resize(static_cast(5*imageSize.width()),static_cast(imageSize.height()*1.7)); +} +//----------------------------------------------------------------------------- +//SlideInitializer +//----------------------------------------------------------------------------- +SlideInitializer::SlideInitializer(PictureFlow * flow,int slides) +:QThread(),_flow(flow),_slides(slides) +{ + +} +void SlideInitializer::run() +{ + mutexGoToFlow.lock(); + + _flow->clear(); + for(int i=0;i<_slides;i++) + _flow->addSlide(QImage()); + _flow->setCenterIndex(0); + + mutexGoToFlow.unlock(); +} +//----------------------------------------------------------------------------- +//PageLoader +//----------------------------------------------------------------------------- + + +PageLoader::PageLoader(): +QThread(), restart(false), working(false), idx(-1) +{ +} + +PageLoader::~PageLoader() +{ + mutexGoToFlow.lock(); + condition.wakeOne(); + mutexGoToFlow.unlock(); + wait(); +} + +bool PageLoader::busy() const +{ + return isRunning() ? working : false; +} + +void PageLoader::generate(int index, QSize size,const QByteArray & rImage) +{ + mutexGoToFlow.lock(); + this->idx = index; + //this->img = QImage(); + this->size = size; + this->rawImage = rImage; + mutexGoToFlow.unlock(); + + if (!isRunning()) + start(); + else + { + // already running, wake up whenever ready + restart = true; + condition.wakeOne(); + } +} + +void PageLoader::run() +{ + for(;;) + { + // copy necessary data + mutexGoToFlow.lock(); + this->working = true; + //int idx = this->idx; + + + QImage image; + image.loadFromData(this->rawImage); + // let everyone knows it is ready + image = image.scaled(this->size,Qt::KeepAspectRatio,Qt::SmoothTransformation); + + mutexGoToFlow.unlock(); + + mutexGoToFlow.lock(); + this->working = false; + this->img = image; + mutexGoToFlow.unlock(); + + // put to sleep + mutexGoToFlow.lock(); + if (!this->restart) + condition.wait(&mutexGoToFlow); + restart = false; + mutexGoToFlow.unlock(); + } +} diff --git a/YACReader/goto_flow.h b/YACReader/goto_flow.h new file mode 100644 index 00000000..c521f2f4 --- /dev/null +++ b/YACReader/goto_flow.h @@ -0,0 +1,105 @@ +#ifndef __GOTO_FLOW_H +#define __GOTO_FLOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "custom_widgets.h" +class Comic; + +class SlideInitializer; +class PageLoader; + +class GoToFlow : public QWidget +{ + Q_OBJECT +public: + GoToFlow(QWidget* parent = 0,PictureFlow::FlowType flowType = PictureFlow::CoverFlowLike); + bool ready; //comic is ready for read. + void keyPressEvent(QKeyEvent* event); + bool eventFilter(QObject *target, QEvent *event); +private: + YACReaderFlow * flow; + QLineEdit * edit; + QIntValidator * v; + QPushButton * centerButton; + QPushButton * goToButton; + Comic * comic; + QSize imageSize; + + QVector imagesLoaded; + QVector imagesSetted; + uint numImagesLoaded; + QVector imagesReady; + QVector rawImages; + QTimer* updateTimer; + PageLoader* worker; + virtual void wheelEvent(QWheelEvent * event); + + private slots: + void preload(); + void updateImageData(); + + public slots: + void goTo(); + void setPageNumber(int page); + void centerSlide(); + void centerSlide(int slide); + void reset(); + void setNumSlides(unsigned int slides); + void setImageReady(int index,const QByteArray & image); + void setFlowType(PictureFlow::FlowType flowType); + void updateSize(); +signals: + void goToPage(unsigned int page); + + +}; +//----------------------------------------------------------------------------- +//SlideInitializer +//----------------------------------------------------------------------------- +class SlideInitializer : public QThread +{ +public: + SlideInitializer(PictureFlow * flow,int slides); +private: + PictureFlow * _flow; + int _slides; + void run(); +}; +//----------------------------------------------------------------------------- +//PageLoader +//----------------------------------------------------------------------------- +class PageLoader : public QThread +{ +public: + PageLoader(); + ~PageLoader(); + // returns FALSE if worker is still busy and can't take the task + bool busy() const; + void generate(int index, QSize size,const QByteArray & rImage); + void reset(){idx = -1;}; + int index() const { return idx; } + QImage result() const { return img; } +protected: + void run(); +private: + QWaitCondition condition; + + bool restart; + bool working; + int idx; + + QSize size; + QImage img; + QByteArray rawImage; +}; + +#endif diff --git a/YACReader/icon.ico b/YACReader/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..6c31de5a6f6c0c322fe853c1b53fe9c204f6330f GIT binary patch literal 99678 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 Y{QeF6{tf*84gCHM{QeF6zw{0K|6N)}g8%>k literal 0 HcmV?d00001 diff --git a/YACReader/icon.rc b/YACReader/icon.rc new file mode 100644 index 00000000..86e8e9b3 --- /dev/null +++ b/YACReader/icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icon.ico" diff --git a/YACReader/images.qrc b/YACReader/images.qrc new file mode 100644 index 00000000..2aab79e4 --- /dev/null +++ b/YACReader/images.qrc @@ -0,0 +1,67 @@ + + + ../images/open.png + ../images/openFolder.png + ../images/next.png + ../images/prev.png + ../images/icon.png + ../images/zoom.png + ../images/fit.png + ../images/goto.png + ../images/help.png + ../images/center.png + ../images/options.png + ../images/comicFolder.png + ../images/save.png + ../images/rotateL.png + ../images/rotateR.png + ../images/flow1.png + ../images/flow2.png + ../images/flow3.png + ../images/bookmark.png + ../images/setBookmark.png + ../images/notCover.png + ../images/previousComic.png + ../images/nextComic.png + ../images/deleteLibrary.png + ../images/properties.png + ../images/doublePage.png + ../images/shortcuts.png + ../images/close.png + ../images/up.png + ../images/down.png + ../images/dictionary.png + ../images/alwaysOnTop.png + ../images/adjustToFullSize.png + ../images/helpImages/open.png + ../images/helpImages/openFolder.png + ../images/helpImages/next.png + ../images/helpImages/prev.png + ../images/helpImages/icon.png + ../images/helpImages/zoom.png + ../images/helpImages/fit.png + ../images/helpImages/goto.png + ../images/helpImages/help.png + ../images/helpImages/center.png + ../images/helpImages/options.png + ../images/helpImages/comicFolder.png + ../images/helpImages/save.png + ../images/helpImages/rotateL.png + ../images/helpImages/rotateR.png + ../images/helpImages/flow1.png + ../images/helpImages/flow2.png + ../images/helpImages/flow3.png + ../images/helpImages/bookmark.png + ../images/helpImages/setBookmark.png + ../images/helpImages/notCover.png + ../images/helpImages/previousComic.png + ../images/helpImages/nextComic.png + ../images/helpImages/deleteLibrary.png + ../images/helpImages/properties.png + ../images/helpImages/doublePage.png + ../images/helpImages/shortcuts.png + ../images/helpImages/keyboard.png + ../images/helpImages/mouse.png + ../images/helpImages/speaker.png + + diff --git a/YACReader/magnifying_glass.cpp b/YACReader/magnifying_glass.cpp new file mode 100644 index 00000000..766716a3 --- /dev/null +++ b/YACReader/magnifying_glass.cpp @@ -0,0 +1,267 @@ +#include "magnifying_glass.h" +#include "viewer.h" + + + +MagnifyingGlass::MagnifyingGlass(int w, int h, QWidget * parent) +:QLabel(parent),zoomLevel(0.5) +{ + setup(QSize(w,h)); +} + +MagnifyingGlass::MagnifyingGlass(const QSize & size, QWidget * parent) +:QLabel(parent),zoomLevel(0.5) +{ + setup(size); +} + +void MagnifyingGlass::setup(const QSize & size) +{ + resize(size); + setScaledContents(true); + setMouseTracking(true); + setCursor(QCursor(Qt::BlankCursor)); +} + +void MagnifyingGlass::mouseMoveEvent(QMouseEvent * event) +{ + updateImage(); +} + +void MagnifyingGlass::updateImage(int x, int y) +{ + //image section augmented + int zoomWidth = static_cast(width() * zoomLevel); + int zoomHeight = static_cast(height() * zoomLevel); + Viewer * p = (Viewer *)parent(); + int currentPos = p->verticalScrollBar()->sliderPosition(); + const QPixmap * image = p->pixmap(); + int iWidth = image->width(); + int iHeight = image->height(); + float wFactor = static_cast(iWidth) / p->widget()->width(); + float hFactor = static_cast(iHeight) / p->widget()->height(); + zoomWidth *= wFactor; + zoomHeight *= hFactor; + if(p->verticalScrollBar()->minimum()==p->verticalScrollBar()->maximum()) + { + int xp = static_cast(((x-p->widget()->pos().x())*wFactor)-zoomWidth/2); + int yp = static_cast((y-p->widget()->pos().y()+currentPos)*hFactor-zoomHeight/2); + int xOffset=0; + int yOffset=0; + int zw=zoomWidth; + int zh=zoomHeight; + //int wOffset,hOffset=0; + bool outImage = false; + if(xp<0) + { + xOffset = -xp; + xp=0; + zw = zw - xOffset; + outImage = true; + } + if(yp<0) + { + yOffset = -yp; + yp=0; + zh = zh - yOffset; + outImage = true; + } + + if(xp+zoomWidth >= image->width()) + { + zw -= xp+zw - image->width(); + outImage = true; + } + if(yp+zoomHeight >= image->height()) + { + zh -= yp+zh - image->height(); + outImage = true; + } + if(outImage) + { + QImage img(zoomWidth,zoomHeight,QImage::Format_RGB32); + img.fill(0); + if(zw>0&&zh>0) + { + QPainter painter(&img); + painter.drawPixmap(xOffset,yOffset,p->pixmap()->copy(xp,yp,zw,zh)); + } + setPixmap(QPixmap().fromImage(img)); + } + else + setPixmap(p->pixmap()->copy(xp,yp,zoomWidth,zoomHeight)); + } + else + { + int xp = static_cast(((x-p->widget()->pos().x())*wFactor)-zoomWidth/2); + int yp = static_cast((y+currentPos)*hFactor-zoomHeight/2); + int xOffset=0; + int yOffset=0; + int zw=zoomWidth; + int zh=zoomHeight; + //int wOffset,hOffset=0; + bool outImage = false; + if(xp<0) + { + xOffset = -xp; + xp=0; + zw = zw - xOffset; + outImage = true; + } + if(yp<0) + { + yOffset = -yp; + yp=0; + zh = zh - yOffset; + outImage = true; + } + + if(xp+zoomWidth >= image->width()) + { + zw -= xp+zw - image->width(); + outImage = true; + } + if(yp+zoomHeight >= image->height()) + { + zh -= yp+zh - image->height(); + outImage = true; + } + if(outImage) + { + QImage img(zoomWidth,zoomHeight,QImage::Format_RGB32); + img.fill(0); + if(zw>0&&zh>0) + { + QPainter painter(&img); + painter.drawPixmap(xOffset,yOffset,p->pixmap()->copy(xp,yp,zw,zh)); + } + setPixmap(QPixmap().fromImage(img)); + } + else + setPixmap(p->pixmap()->copy(xp,yp,zoomWidth,zoomHeight)); + } + move(static_cast(x-float(width())/2),static_cast(y-float(height())/2)); +} + +void MagnifyingGlass::updateImage() +{ + if(isVisible()) + { + QPoint p = QPoint(cursor().pos().x(),cursor().pos().y()); + p = this->parentWidget()->mapFromGlobal(p); + updateImage(p.x(),p.y()); + } +} +void MagnifyingGlass::wheelEvent(QWheelEvent * event) +{ + switch(event->modifiers()) + { + //size + case Qt::NoModifier: + if(event->delta()<0) + sizeUp(); + else + sizeDown(); + break; + //size height + case Qt::ControlModifier: + if(event->delta()<0) + heightUp(); + else + heightDown(); + break; + //size width + case Qt::AltModifier: + if(event->delta()<0) + widthUp(); + else + widthDown(); + break; + //zoom level + case Qt::ShiftModifier: + if(event->delta()<0) + zoomIn(); + else + zoomOut(); + break; + } + updateImage(); + event->setAccepted(true); +} +void MagnifyingGlass::zoomIn() +{ + if(zoomLevel>0.2) + zoomLevel -= 0.025; +} + +void MagnifyingGlass::zoomOut() +{ + if(zoomLevel<0.9) + zoomLevel += 0.025; +} + +void MagnifyingGlass::sizeUp() +{ + Viewer * p = (Viewer *)parent(); + if(width()<(p->width()*0.90)) + resize(width()+30,height()+15); +} + +void MagnifyingGlass::sizeDown() +{ + if(width()>175) + resize(width()-30,height()-15); +} + +void MagnifyingGlass::heightUp() +{ + Viewer * p = (Viewer *)parent(); + if(height()<(p->height()*0.90)) + resize(width(),height()+15); +} + +void MagnifyingGlass::heightDown() +{ + if(height()>80) + resize(width(),height()-15); +} + +void MagnifyingGlass::widthUp() +{ + Viewer * p = (Viewer *)parent(); + if(width()<(p->width()*0.90)) + resize(width()+30,height()); +} + +void MagnifyingGlass::widthDown() +{ + if(width()>175) + resize(width()-30,height()); +} + +void MagnifyingGlass::keyPressEvent(QKeyEvent *event) +{ + bool validKey = false; + switch (event->key()) + { + case Qt::Key_Plus: + sizeUp(); + validKey = true; + break; + case Qt::Key_Minus: + sizeDown(); + validKey = true; + break; + case Qt::Key_Underscore: + zoomOut(); + validKey = true; + break; + case Qt::Key_Asterisk: + zoomIn(); + validKey = true; + break; + } + updateImage(); + if(validKey) + event->setAccepted(true); +} diff --git a/YACReader/magnifying_glass.h b/YACReader/magnifying_glass.h new file mode 100644 index 00000000..519e3404 --- /dev/null +++ b/YACReader/magnifying_glass.h @@ -0,0 +1,34 @@ +#ifndef __MAGNIFYING_GLASS +#define __MAGNIFYING_GLASS + +#include +#include +#include +#include + + class MagnifyingGlass : public QLabel + { + Q_OBJECT + private: + float zoomLevel; + void setup(const QSize & size); + void keyPressEvent(QKeyEvent * event); + public: + MagnifyingGlass(int width,int height,QWidget * parent); + MagnifyingGlass(const QSize & size, QWidget * parent); + void mouseMoveEvent(QMouseEvent * event); + public slots: + void updateImage(int x, int y); + void updateImage(); + void wheelEvent(QWheelEvent * event); + void zoomIn(); + void zoomOut(); + void sizeUp(); + void sizeDown(); + void heightUp(); + void heightDown(); + void widthUp(); + void widthDown(); + }; + +#endif diff --git a/YACReader/main.cpp b/YACReader/main.cpp new file mode 100644 index 00000000..911aba72 --- /dev/null +++ b/YACReader/main.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include "main_window_viewer.h" +#include "configuration.h" + + +int main(int argc, char * argv[]) +{ + QApplication app(argc, argv); + + QTranslator translator; + QString sufix = QLocale::system().name(); + translator.load(":/yacreader_"+sufix); + app.installTranslator(&translator); + app.setApplicationName("YACReader"); + + MainWindowViewer mwv; + mwv.show(); + + int ret = app.exec(); + + Configuration::getConfiguration().save(); + + return ret; +} diff --git a/YACReader/main_window_viewer.cpp b/YACReader/main_window_viewer.cpp new file mode 100644 index 00000000..7bbfaac0 --- /dev/null +++ b/YACReader/main_window_viewer.cpp @@ -0,0 +1,680 @@ +#include "main_window_viewer.h" +#include "configuration.h" +#include "viewer.h" +#include "goto_dialog.h" +#include "custom_widgets.h" +#include "options_dialog.h" +#include "check_new_version.h" +#include "comic.h" +#include "bookmarks_dialog.h" +#include "shortcuts_dialog.h" + + +MainWindowViewer::MainWindowViewer() +:QMainWindow(),fullscreen(false),toolbars(true),alwaysOnTop(false),currentDirectory("."),currentDirectoryImgDest(".") +{ + loadConfiguration(); + setupUI(); +} + +void MainWindowViewer::loadConfiguration() +{ + Configuration & config = Configuration::getConfiguration(); + currentDirectory = config.getDefaultPath(); + fullscreen = config.getFullScreen(); +} + +void MainWindowViewer::setupUI() +{ + + setWindowIcon(QIcon(":/images/icon.png")); + + viewer = new Viewer(this); + connect(viewer,SIGNAL(reset()),this,SLOT(disableActions())); + + setCentralWidget(viewer); + int heightDesktopResolution = QApplication::desktop()->screenGeometry().height(); + int widthDesktopResolution = QApplication::desktop()->screenGeometry().width(); + int height,width; + height = static_cast(heightDesktopResolution*0.84); + width = static_cast(height*0.70); + Configuration & conf = Configuration::getConfiguration(); + QPoint p = conf.getPos(); + QSize s = conf.getSize(); + if(s.width()!=0) + { + move(p); + resize(s); + } + else + { + move(QPoint((widthDesktopResolution-width)/2,((heightDesktopResolution-height)-40)/2)); + resize(QSize(width,height)); + } + + had = new HelpAboutDialog(this); //TODO load data + + had->loadAboutInformation(":/files/about.html"); + had->loadHelp(":/files/helpYACReader.html"); + + optionsDialog = new OptionsDialog(this); + connect(optionsDialog,SIGNAL(accepted()),viewer,SLOT(updateOptions())); + + shortcutsDialog = new ShortcutsDialog(this); + + createActions(); + createToolBars(); + if(QCoreApplication::argc()>1) + { + //TODO: new method open(QString) + QString pathFile = QCoreApplication::arguments().at(1); + currentDirectory = pathFile; + QFileInfo fi(pathFile); + getSiblingComics(fi.absolutePath(),fi.fileName()); + viewer->open(pathFile); + enableActions(); + } + + versionChecker = new HttpVersionChecker(); + + connect(versionChecker,SIGNAL(newVersionDetected()), + this,SLOT(newVersion())); + + versionChecker->get(); + + viewer->setFocusPolicy(Qt::StrongFocus); + + setWindowTitle("YACReader"); + + if(Configuration::getConfiguration().getAlwaysOnTop()) + { + setWindowFlags(this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint); + } + + if(fullscreen) + toFullScreen(); + if(conf.getMaximized()) + showMaximized(); + + setAcceptDrops(true); +} + +void MainWindowViewer::createActions() +{ + openAction = new QAction(tr("&Open"),this); + openAction->setShortcut(tr("O")); + openAction->setIcon(QIcon(":/images/open.png")); + openAction->setToolTip(tr("Open a comic")); + connect(openAction, SIGNAL(triggered()), this, SLOT(open())); + + openFolderAction = new QAction(tr("Open Folder"),this); + openFolderAction->setShortcut(tr("Ctrl+O")); + openFolderAction->setIcon(QIcon(":/images/openFolder.png")); + openFolderAction->setToolTip(tr("Open image folder")); + connect(openFolderAction, SIGNAL(triggered()), this, SLOT(openFolder())); + + saveImageAction = new QAction(tr("Save"),this); + saveImageAction->setIcon(QIcon(":/images/save.png")); + saveImageAction->setToolTip(tr("Save current page")); + saveImageAction->setDisabled(true); + connect(saveImageAction,SIGNAL(triggered()),this,SLOT(saveImage())); + + openPreviousComicAction = new QAction(tr("Previous Comic"),this); + openPreviousComicAction->setIcon(QIcon(":/images/previousComic.png")); + openPreviousComicAction->setShortcut(Qt::CTRL + Qt::Key_Left); + openPreviousComicAction->setToolTip(tr("Open previous comic")); + openPreviousComicAction->setDisabled(true); + connect(openPreviousComicAction,SIGNAL(triggered()),this,SLOT(openPreviousComic())); + + openNextComicAction = new QAction(tr("Next Comic"),this); + openNextComicAction->setIcon(QIcon(":/images/nextComic.png")); + openNextComicAction->setShortcut(Qt::CTRL + Qt::Key_Right); + openNextComicAction->setToolTip(tr("Open next comic")); + openNextComicAction->setDisabled(true); + connect(openNextComicAction,SIGNAL(triggered()),this,SLOT(openNextComic())); + + prevAction = new QAction(tr("&Previous"),this); + prevAction->setIcon(QIcon(":/images/prev.png")); + prevAction->setShortcut(Qt::Key_Left); + prevAction->setToolTip(tr("Go to previous page")); + prevAction->setDisabled(true); + connect(prevAction, SIGNAL(triggered()),viewer,SLOT(prev())); + + nextAction = new QAction(tr("&Next"),this); + nextAction->setIcon(QIcon(":/images/next.png")); + nextAction->setShortcut(Qt::Key_Right); + nextAction->setToolTip(tr("Go to next page")); + nextAction->setDisabled(true); + connect(nextAction, SIGNAL(triggered()),viewer,SLOT(next())); + + adjustWidth = new QAction(tr("Fit Width"),this); + adjustWidth->setShortcut(tr("A")); + adjustWidth->setIcon(QIcon(":/images/fit.png")); + adjustWidth->setCheckable(true); + adjustWidth->setDisabled(true); + adjustWidth->setChecked(Configuration::getConfiguration().getAdjustToWidth()); + adjustWidth->setToolTip(tr("Fit image to ...")); + //adjustWidth->setIcon(QIcon(":/images/fitWidth.png")); + connect(adjustWidth, SIGNAL(triggered()),this,SLOT(changeFit())); + + leftRotationAction = new QAction(tr("Rotate image to the left"),this); + leftRotationAction->setShortcut(tr("L")); + leftRotationAction->setIcon(QIcon(":/images/rotateL.png")); + leftRotationAction->setDisabled(true); + connect(leftRotationAction, SIGNAL(triggered()),viewer,SLOT(rotateLeft())); + + rightRotationAction = new QAction(tr("Rotate image to the right"),this); + rightRotationAction->setShortcut(tr("R")); + rightRotationAction->setIcon(QIcon(":/images/rotateR.png")); + rightRotationAction->setDisabled(true); + connect(rightRotationAction, SIGNAL(triggered()),viewer,SLOT(rotateRight())); + + doublePageAction = new QAction(tr("Double page mode"),this); + doublePageAction->setToolTip(tr("Switch to double page mode")); + doublePageAction->setShortcut(tr("D")); + doublePageAction->setIcon(QIcon(":/images/doublePage.png")); + doublePageAction->setDisabled(true); + doublePageAction->setCheckable(true); + doublePageAction->setChecked(Configuration::getConfiguration().getDoublePage()); + connect(doublePageAction, SIGNAL(triggered()),viewer,SLOT(doublePageSwitch())); + + goToPage = new QAction(tr("Go To"),this); + goToPage->setShortcut(tr("G")); + goToPage->setIcon(QIcon(":/images/goto.png")); + goToPage->setDisabled(true); + goToPage->setToolTip(tr("Go to page ...")); + connect(goToPage, SIGNAL(triggered()),viewer,SLOT(showGoToDialog())); + + optionsAction = new QAction(tr("Options"),this); + optionsAction->setShortcut(tr("C")); + optionsAction->setToolTip(tr("YACReader options")); + optionsAction->setIcon(QIcon(":/images/options.png")); + + connect(optionsAction, SIGNAL(triggered()),optionsDialog,SLOT(show())); + + helpAboutAction = new QAction(tr("Help"),this); + helpAboutAction->setToolTip(tr("Help, About YACReader")); + helpAboutAction->setShortcut(Qt::Key_F1); + helpAboutAction->setIcon(QIcon(":/images/help.png")); + connect(helpAboutAction, SIGNAL(triggered()),had,SLOT(show())); + + showMagnifyingGlass = new QAction(tr("Magnifying glass"),this); + showMagnifyingGlass->setToolTip(tr("Switch Magnifying glass")); + showMagnifyingGlass->setShortcut(tr("Z")); + showMagnifyingGlass->setIcon(QIcon(":/images/zoom.png")); + showMagnifyingGlass->setDisabled(true); + showMagnifyingGlass->setCheckable(true); + connect(showMagnifyingGlass, SIGNAL(triggered()),viewer,SLOT(magnifyingGlassSwitch())); + + setBookmark = new QAction(tr("Set bookmark"),this); + setBookmark->setToolTip(tr("Set a bookmark on the current page")); + setBookmark->setShortcut(Qt::CTRL+Qt::Key_M); + setBookmark->setIcon(QIcon(":/images/setBookmark.png")); + setBookmark->setDisabled(true); + setBookmark->setCheckable(true); + connect(setBookmark,SIGNAL(triggered (bool)),viewer,SLOT(setBookmark(bool))); + connect(viewer,SIGNAL(pageAvailable(bool)),setBookmark,SLOT(setEnabled(bool))); + connect(viewer,SIGNAL(pageIsBookmark(bool)),setBookmark,SLOT(setChecked(bool))); + + showBookmarks = new QAction(tr("Show bookmarks"),this); + showBookmarks->setToolTip(tr("Show the bookmarks of the current comic")); + showBookmarks->setShortcut(tr("M")); + showBookmarks->setIcon(QIcon(":/images/bookmark.png")); + showBookmarks->setDisabled(true); + connect(showBookmarks, SIGNAL(triggered()),viewer->getBookmarksDialog(),SLOT(show())); + + showShorcutsAction = new QAction(tr("Show keyboard shortcuts"), this ); + showShorcutsAction->setIcon(QIcon(":/images/shortcuts.png")); + connect(showShorcutsAction, SIGNAL(triggered()),shortcutsDialog,SLOT(show())); + + showInfo = new QAction(tr("Show Info"),this); + showInfo->setShortcut(tr("I")); + showInfo->setIcon(QIcon(":/images/properties.png")); + showInfo->setDisabled(true); + connect(showInfo, SIGNAL(triggered()),viewer,SLOT(informationSwitch())); + + closeAction = new QAction(tr("Close"),this); + closeAction->setShortcut(Qt::Key_Escape); + closeAction->setIcon(QIcon(":/images/deleteLibrary.png")); + connect(closeAction,SIGNAL(triggered()),this,SLOT(close())); + + showDictionaryAction = new QAction(tr("Show Dictionary"),this); + showDictionaryAction->setShortcut(Qt::Key_T); + showDictionaryAction->setIcon(QIcon(":/images/dictionary.png")); + showDictionaryAction->setCheckable(true); + showDictionaryAction->setDisabled(true); + connect(showDictionaryAction,SIGNAL(triggered()),viewer,SLOT(translatorSwitch())); + + alwaysOnTopAction = new QAction(tr("Always on top"),this); + alwaysOnTopAction->setShortcut(Qt::Key_Q); + alwaysOnTopAction->setIcon(QIcon(":/images/alwaysOnTop.png")); + alwaysOnTopAction->setCheckable(true); + alwaysOnTopAction->setDisabled(true); + alwaysOnTopAction->setChecked(Configuration::getConfiguration().getAlwaysOnTop()); + connect(alwaysOnTopAction,SIGNAL(triggered()),this,SLOT(alwaysOnTopSwitch())); + + adjustToFullSizeAction = new QAction(tr("Show full size"),this); + adjustToFullSizeAction->setShortcut(Qt::Key_W); + adjustToFullSizeAction->setIcon(QIcon(":/images/adjustToFullSize.png")); + adjustToFullSizeAction->setCheckable(true); + adjustToFullSizeAction->setDisabled(true); + adjustToFullSizeAction->setChecked(Configuration::getConfiguration().getAdjustToFullSize()); + connect(adjustToFullSizeAction,SIGNAL(triggered()),this,SLOT(adjustToFullSizeSwitch())); +} + +void MainWindowViewer::createToolBars() +{ + comicToolBar = addToolBar(tr("&File")); + + QToolButton * tb = new QToolButton(); + tb->addAction(openAction); + tb->addAction(openFolderAction); + tb->setPopupMode(QToolButton::MenuButtonPopup); + tb->setDefaultAction(openAction); + + comicToolBar->addWidget(tb); + comicToolBar->addAction(saveImageAction); + comicToolBar->addAction(openPreviousComicAction); + comicToolBar->addAction(openNextComicAction); + + comicToolBar->addSeparator(); + comicToolBar->addAction(prevAction); + comicToolBar->addAction(nextAction); + comicToolBar->addAction(goToPage); + + comicToolBar->addSeparator(); + comicToolBar->addAction(alwaysOnTopAction); + + comicToolBar->addSeparator(); + comicToolBar->addAction(adjustWidth); + comicToolBar->addAction(adjustToFullSizeAction); + comicToolBar->addAction(leftRotationAction); + comicToolBar->addAction(rightRotationAction); + comicToolBar->addAction(doublePageAction); + + comicToolBar->addSeparator(); + comicToolBar->addAction(showMagnifyingGlass); + + comicToolBar->addSeparator(); + comicToolBar->addAction(setBookmark); + comicToolBar->addAction(showBookmarks); + + comicToolBar->addSeparator(); + comicToolBar->addAction(showDictionaryAction); + + comicToolBar->addWidget(new QToolBarStretch()); + + comicToolBar->addAction(showShorcutsAction); + comicToolBar->addAction(optionsAction); + comicToolBar->addAction(helpAboutAction); + comicToolBar->addAction(closeAction); + + comicToolBar->setMovable(false); + + + viewer->addAction(openAction); + viewer->addAction(openFolderAction); + viewer->addAction(saveImageAction); + viewer->addAction(openPreviousComicAction); + viewer->addAction(openNextComicAction); + QAction * separator = new QAction("",this); + separator->setSeparator(true); + viewer->addAction(separator); + viewer->addAction(prevAction); + viewer->addAction(nextAction); + viewer->addAction(goToPage); + viewer->addAction(adjustWidth); + viewer->addAction(adjustToFullSizeAction); + viewer->addAction(leftRotationAction); + viewer->addAction(rightRotationAction); + viewer->addAction(doublePageAction); + separator = new QAction("",this); + separator->setSeparator(true); + viewer->addAction(separator); + + viewer->addAction(setBookmark); + viewer->addAction(showBookmarks); + separator = new QAction("",this); + separator->setSeparator(true); + viewer->addAction(separator); + + viewer->addAction(showDictionaryAction); + separator = new QAction("",this); + separator->setSeparator(true); + viewer->addAction(separator); + + viewer->addAction(showMagnifyingGlass); + viewer->addAction(optionsAction); + viewer->addAction(helpAboutAction); + viewer->addAction(showInfo); + viewer->addAction(showShorcutsAction); + separator = new QAction("",this); + separator->setSeparator(true); + viewer->addAction(separator); + viewer->addAction(closeAction); + + viewer->setContextMenuPolicy(Qt::ActionsContextMenu); +} + +void MainWindowViewer::open() +{ + QFileDialog openDialog; + QString pathFile = openDialog.getOpenFileName(this,tr("Open Comic"),currentDirectory,tr("Comic files (*.cbr *.cbz *.rar *.zip *.tar *.arj)")); + if (!pathFile.isEmpty()) + { + openComicFromPath(pathFile); + } +} + +void MainWindowViewer::openComicFromPath(QString pathFile) +{ + currentDirectory = pathFile; + QFileInfo fi(pathFile); + getSiblingComics(fi.absolutePath(),fi.fileName()); + viewer->open(pathFile); + enableActions(); +} + +void MainWindowViewer::openFolder() +{ + QFileDialog openDialog; + QString pathDir = openDialog.getExistingDirectory(this,tr("Open folder"),currentDirectory); + if (!pathDir.isEmpty()) + { + openFolderFromPath(pathDir); + } +} + +void MainWindowViewer::openFolderFromPath(QString pathDir) +{ + currentDirectory = pathDir; //TODO ?? + QFileInfo fi(pathDir); + getSiblingComics(fi.absolutePath(),fi.fileName()); + viewer->open(pathDir); + enableActions(); +} + +void MainWindowViewer::saveImage() +{ + QFileDialog saveDialog; + QString pathFile = saveDialog.getSaveFileName(this,tr("Save current page"),currentDirectoryImgDest,tr("Image files (*.jpg)")); + if (!pathFile.isEmpty()) + { + currentDirectoryImgDest = pathFile; + const QPixmap * p = viewer->pixmap(); + if(p!=NULL) + p->save(pathFile); + } +} + +void MainWindowViewer::enableActions() +{ + saveImageAction->setDisabled(false); + prevAction->setDisabled(false); + nextAction->setDisabled(false); + adjustWidth->setDisabled(false); + goToPage->setDisabled(false); + alwaysOnTopAction->setDisabled(false); + leftRotationAction->setDisabled(false); + rightRotationAction->setDisabled(false); + showMagnifyingGlass->setDisabled(false); + doublePageAction->setDisabled(false); + adjustToFullSizeAction->setDisabled(false); + //setBookmark->setDisabled(false); + showBookmarks->setDisabled(false); + showInfo->setDisabled(false); //TODO enable goTo and showInfo (or update) when numPages emited + showDictionaryAction->setDisabled(false); +} +void MainWindowViewer::disableActions() +{ + saveImageAction->setDisabled(true); + prevAction->setDisabled(true); + nextAction->setDisabled(true); + adjustWidth->setDisabled(true); + goToPage->setDisabled(true); + alwaysOnTopAction->setDisabled(true); + leftRotationAction->setDisabled(true); + rightRotationAction->setDisabled(true); + showMagnifyingGlass->setDisabled(true); + doublePageAction->setDisabled(true); + adjustToFullSizeAction->setDisabled(true); + setBookmark->setDisabled(true); + showBookmarks->setDisabled(true); + showInfo->setDisabled(true); //TODO enable goTo and showInfo (or update) when numPages emited + openPreviousComicAction->setDisabled(true); + openNextComicAction->setDisabled(true); + showDictionaryAction->setDisabled(true); +} + +void MainWindowViewer::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) + { + case Qt::Key_Escape: + this->close(); + break; + case Qt::Key_F: + toggleFullScreen(); + break; + case Qt::Key_H: + toggleToolBars(); + break; + case Qt::Key_O: + open(); + break; + default: + QWidget::keyPressEvent(event); + break; + } +} + +void MainWindowViewer::mouseDoubleClickEvent ( QMouseEvent * event ) +{ + toggleFullScreen(); + event->accept(); +} + +void MainWindowViewer::toggleFullScreen() +{ + fullscreen?toNormal():toFullScreen(); + Configuration::getConfiguration().setFullScreen(fullscreen = !fullscreen); +} + +void MainWindowViewer::toFullScreen() +{ + hideToolBars(); + viewer->hide(); + viewer->fullscreen = true;//TODO, change by the right use of windowState(); + showFullScreen(); + viewer->show(); + if(viewer->magnifyingGlassIsVisible()) + viewer->showMagnifyingGlass(); +} + +void MainWindowViewer::toNormal() +{ + //show all + viewer->hide(); + viewer->fullscreen = false;//TODO, change by the right use of windowState(); + //viewer->hideMagnifyingGlass(); + showNormal(); + showToolBars(); + viewer->show(); + if(viewer->magnifyingGlassIsVisible()) + viewer->showMagnifyingGlass(); +} +void MainWindowViewer::toggleToolBars() +{ + toolbars?hideToolBars():showToolBars(); +} +void MainWindowViewer::hideToolBars() +{ + //hide all + this->comicToolBar->hide(); + toolbars = false; +} + +void MainWindowViewer::showToolBars() +{ + this->comicToolBar->show(); + toolbars = true; +} + +void MainWindowViewer::changeFit() +{ + Configuration & conf = Configuration::getConfiguration(); + conf.setAdjustToWidth(!conf.getAdjustToWidth()); + viewer->updatePage(); +} + +void MainWindowViewer::newVersion() +{ + QMessageBox msgBox; + msgBox.setText(tr("There is a new version avaliable")); + msgBox.setInformativeText(tr("Do you want to download the new version?")); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::Yes); + int ret = msgBox.exec(); + + if(ret==QMessageBox::Yes){ + QDesktopServices::openUrl(QUrl("http://code.google.com/p/yacreader/")); + } +} + + + + + +void MainWindowViewer::closeEvent ( QCloseEvent * event ) +{ + viewer->save(); + Configuration & conf = Configuration::getConfiguration(); + if(!fullscreen && !isMaximized()) + { + conf.setPos(pos()); + conf.setSize(size()); + } + conf.setMaximized(isMaximized()); +} + +void MainWindowViewer::openPreviousComic() +{ + if(!previousComicPath.isEmpty()) + { + viewer->open(previousComicPath); + QFileInfo fi(previousComicPath); + getSiblingComics(fi.absolutePath(),fi.fileName()); + } +} + +void MainWindowViewer::openNextComic() +{ + if(!nextComicPath.isEmpty()) + { + viewer->open(nextComicPath); + QFileInfo fi(nextComicPath); + getSiblingComics(fi.absolutePath(),fi.fileName()); + } +} + +void MainWindowViewer::getSiblingComics(QString path,QString currentComic) +{ + QDir d(path); + d.setFilter(QDir::Files|QDir::NoDotAndDotDot); + d.setNameFilters(QStringList() << "*.cbr" << "*.cbz" << "*.rar" << "*.zip" << "*.tar"); + d.setSorting(QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + QStringList list = d.entryList(); + int index = list.indexOf(currentComic); + if(index == -1) //comic not found + { + QFile f(QCoreApplication::applicationDirPath()+"/errorLog.txt"); + if(!f.open(QIODevice::WriteOnly)) + { + QMessageBox::critical(NULL,tr("Saving error log file...."),tr("There was a problem saving YACReader error log file. Please, check if you have enough permissions in the YACReader root folder.")); + } + else + { + QTextStream txtS(&f); + txtS << "METHOD : MainWindowViewer::getSiblingComics" << '\n'; + txtS << "ERROR : current comic not found in its own path" << '\n'; + txtS << path << '\n'; + txtS << currentComic << '\n'; + txtS << "Comic list count : " + list.count() << '\n'; + foreach(QString s, list){ + txtS << s << '\n'; + } + f.close(); + } + } + + previousComicPath = nextComicPath = ""; + if(index>0) + { + previousComicPath = path+"/"+list.at(index-1); + openPreviousComicAction->setDisabled(false); + } + else + openPreviousComicAction->setDisabled(true); + + if(index+1setDisabled(false); + } + else + openNextComicAction->setDisabled(true); +} + +void MainWindowViewer::dropEvent(QDropEvent *event) +{ + QList urlList; + QString fName; + QFileInfo info; + + if (event->mimeData()->hasUrls()) + { + urlList = event->mimeData()->urls(); + + if ( urlList.size() > 0) + { + fName = urlList[0].toLocalFile(); // convert first QUrl to local path + info.setFile( fName ); // information about file + if (info.isFile()) + openComicFromPath(fName); // if is file, setText + else + if(info.isDir()) + openFolderFromPath(fName); + } + } + + event->acceptProposedAction(); +} +void MainWindowViewer::dragEnterEvent(QDragEnterEvent *event) +{ + // accept just text/uri-list mime format + if (event->mimeData()->hasFormat("text/uri-list")) + { + event->acceptProposedAction(); + } +} + +void MainWindowViewer::alwaysOnTopSwitch() +{ + if(!Configuration::getConfiguration().getAlwaysOnTop()) + { + setWindowFlags(this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint); //always on top + show(); + } + else + { + setWindowFlags(this->windowFlags() ^ (Qt::CustomizeWindowHint | Qt::WindowStaysOnTopHint)); + show(); + } + Configuration::getConfiguration().setAlwaysOnTop(!Configuration::getConfiguration().getAlwaysOnTop()); +} + +void MainWindowViewer::adjustToFullSizeSwitch() +{ + Configuration::getConfiguration().setAdjustToFullSize(!Configuration::getConfiguration().getAdjustToFullSize()); + viewer->updatePage(); +} \ No newline at end of file diff --git a/YACReader/main_window_viewer.h b/YACReader/main_window_viewer.h new file mode 100644 index 00000000..c6221510 --- /dev/null +++ b/YACReader/main_window_viewer.h @@ -0,0 +1,115 @@ +#ifndef __MAIN_WINDOW_VIEWER_H +#define __MAIN_WINDOW_VIEWER_H +#include +#include +#include +#include +#include +#include +#include + +class Comic; +class Viewer; +class OptionsDialog; +class HelpAboutDialog; +class HttpVersionChecker; +class ShortcutsDialog; + + + class MainWindowViewer : public QMainWindow + { + Q_OBJECT + + public slots: + void open(); + void openFolder(); + void saveImage(); + void toggleToolBars(); + void hideToolBars(); + void showToolBars(); + void changeFit(); + void enableActions(); + void disableActions(); + void toggleFullScreen(); + void toFullScreen(); + void toNormal(); + void loadConfiguration(); + void newVersion(); + void openPreviousComic(); + void openNextComic(); + void openComicFromPath(QString pathFile); + void openFolderFromPath(QString pathDir); + void alwaysOnTopSwitch(); + void adjustToFullSizeSwitch(); + /*void viewComic(); + void prev(); + void next(); + void updatePage();*/ + + private: + //!State + bool fullscreen; + bool toolbars; + bool alwaysOnTop; + + QString currentDirectory; + QString currentDirectoryImgDest; + //!Widgets + Viewer * viewer; + //GoToDialog * goToDialog; + OptionsDialog * optionsDialog; + HelpAboutDialog * had; + ShortcutsDialog * shortcutsDialog; + + //! ToolBars + QToolBar *comicToolBar; + + //! Actions + QAction *openAction; + QAction *openFolderAction; + QAction *saveImageAction; + QAction *openPreviousComicAction; + QAction *openNextComicAction; + QAction *nextAction; + QAction *prevAction; + QAction *adjustWidth; + QAction *adjustHeight; + QAction *goToPage; + QAction *optionsAction; + QAction *helpAboutAction; + QAction *showMagnifyingGlass; + QAction *setBookmark; + QAction *showBookmarks; + QAction *leftRotationAction; + QAction *rightRotationAction; + QAction *showInfo; + QAction *closeAction; + QAction *doublePageAction; + QAction *showShorcutsAction; + QAction *showDictionaryAction; + QAction *alwaysOnTopAction; + QAction *adjustToFullSizeAction; + + HttpVersionChecker * versionChecker; + QString previousComicPath; + QString nextComicPath; + //! Método que inicializa el interfaz. + void setupUI(); + void createActions(); + void createToolBars(); + void getSiblingComics(QString path,QString currentComic); + + //! Manejadores de evento: + void keyPressEvent(QKeyEvent *event); + //void resizeEvent(QResizeEvent * event); + void mouseDoubleClickEvent ( QMouseEvent * event ); + void dropEvent(QDropEvent *event); + void dragEnterEvent(QDragEnterEvent *event); + + protected: + virtual void closeEvent ( QCloseEvent * event ); + public: + MainWindowViewer(); + + }; +#endif diff --git a/YACReader/options_dialog.cpp b/YACReader/options_dialog.cpp new file mode 100644 index 00000000..e45b124c --- /dev/null +++ b/YACReader/options_dialog.cpp @@ -0,0 +1,188 @@ +#include "options_dialog.h" +#include "configuration.h" +#include +#include +#include +#include +#include + + +OptionsDialog::OptionsDialog(QWidget * parent) +:QDialog(parent) +{ + QVBoxLayout * layout = new QVBoxLayout(this); + + QGroupBox *slideSizeBox = new QGroupBox(tr("\"Go to flow\" size")); + //slideSizeLabel = new QLabel(,this); + slideSize = new QSlider(this); + slideSize->setMinimum(125); + slideSize->setMaximum(350); + slideSize->setPageStep(5); + slideSize->setOrientation(Qt::Horizontal); + QHBoxLayout * slideLayout = new QHBoxLayout(); + slideLayout->addWidget(slideSize); + slideSizeBox->setLayout(slideLayout); + + QGroupBox *pathBox = new QGroupBox(tr("My comics path")); + + QHBoxLayout * path = new QHBoxLayout(); + path->addWidget(pathEdit = new QLineEdit()); + path->addWidget(pathFindButton = new QPushButton(QIcon(":/images/comicFolder.png"),"")); + pathBox->setLayout(path); + + connect(pathFindButton,SIGNAL(clicked()),this,SLOT(findFolder())); + + accept = new QPushButton(tr("Save")); + cancel = new QPushButton(tr("Cancel")); + connect(accept,SIGNAL(clicked()),this,SLOT(saveOptions())); + connect(cancel,SIGNAL(clicked()),this,SLOT(restoreOptions())); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + QGroupBox *groupBox = new QGroupBox(tr("How to show pages in GoToFlow:")); + + radio1 = new QRadioButton(tr("CoverFlow look")); + radio2 = new QRadioButton(tr("Stripe look")); + radio3 = new QRadioButton(tr("Overlapped Stripe look")); + + + QVBoxLayout *vbox = new QVBoxLayout; + QHBoxLayout * opt1 = new QHBoxLayout; + opt1->addWidget(radio1); + QLabel * lOpt1 = new QLabel(); + lOpt1->setPixmap(QPixmap(":/images/flow1.png")); + opt1->addStretch(); + opt1->addWidget(lOpt1); + vbox->addLayout(opt1); + + QHBoxLayout * opt2 = new QHBoxLayout; + opt2->addWidget(radio2); + QLabel * lOpt2 = new QLabel(); + lOpt2->setPixmap(QPixmap(":/images/flow2.png")); + opt2->addStretch(); + opt2->addWidget(lOpt2); + vbox->addLayout(opt2); + + QHBoxLayout * opt3 = new QHBoxLayout; + opt3->addWidget(radio3); + QLabel * lOpt3 = new QLabel(); + lOpt3->setPixmap(QPixmap(":/images/flow3.png")); + opt3->addStretch(); + opt3->addWidget(lOpt3); + vbox->addLayout(opt3); + + + //vbox->addStretch(1); + groupBox->setLayout(vbox); + + //fitToWidthRatioLabel = new QLabel(tr("Page width stretch"),this); + QGroupBox *fitBox = new QGroupBox(tr("Page width stretch")); + fitToWidthRatioS = new QSlider(this); + fitToWidthRatioS->setMinimum(50); + fitToWidthRatioS->setMaximum(100); + fitToWidthRatioS->setPageStep(5); + fitToWidthRatioS->setOrientation(Qt::Horizontal); + QHBoxLayout * fitLayout = new QHBoxLayout; + fitLayout->addWidget(fitToWidthRatioS); + fitBox->setLayout(fitLayout); + + QHBoxLayout * colorSelection = new QHBoxLayout; + backgroundColor = new QLabel(); + QPalette pal = backgroundColor->palette(); + pal.setColor(backgroundColor->backgroundRole(), Qt::black); + backgroundColor->setPalette(pal); + backgroundColor->setAutoFillBackground(true); + + colorDialog = new QColorDialog(Qt::red,this); + connect(colorDialog,SIGNAL(colorSelected(QColor)),this,SLOT(updateColor(QColor))); + + QGroupBox *colorBox = new QGroupBox(tr("Background color")); + //backgroundColor->setMinimumWidth(100); + colorSelection->addWidget(backgroundColor); + colorSelection->addWidget(selectBackgroundColorButton = new QPushButton(tr("Choose"))); + //colorSelection->addStretch(); + connect(selectBackgroundColorButton, SIGNAL(clicked()), colorDialog, SLOT(show())); + colorBox->setLayout(colorSelection); + + QHBoxLayout * buttons = new QHBoxLayout(); + buttons->addStretch(); + buttons->addWidget(new QLabel(tr("Restart is needed"))); + buttons->addWidget(accept); + buttons->addWidget(cancel); + + layout->addWidget(pathBox); + //layout->addLayout(path); + layout->addWidget(slideSizeBox); + //layout->addWidget(slideSize); + layout->addWidget(groupBox); + //layout->addWidget(fitToWidthRatioLabel); + layout->addWidget(fitBox); + layout->addWidget(colorBox); + //layout->addLayout(colorSelection); + layout->addLayout(buttons); + + + setLayout(layout); + + restoreOptions(); //load options + resize(400,0); + setModal (true); + setWindowTitle("Options"); +} + +void OptionsDialog::findFolder() +{ + QString s = QFileDialog::getExistingDirectory(0,tr("Comics directory"),"."); + if(!s.isEmpty()) + { + pathEdit->setText(s); + } +} + +void OptionsDialog::saveOptions() +{ + Configuration & conf = Configuration::getConfiguration(); + conf.setDefaultPath(pathEdit->text()); + conf.setGotoSlideSize(QSize(static_cast(slideSize->sliderPosition()*SLIDE_ASPECT_RATIO),slideSize->sliderPosition())); + if(radio1->isChecked()) + conf.setFlowType(PictureFlow::CoverFlowLike); + if(radio2->isChecked()) + conf.setFlowType(PictureFlow::Strip); + if(radio3->isChecked()) + conf.setFlowType(PictureFlow::StripOverlapped); + conf.setFitToWidthRatio(fitToWidthRatioS->sliderPosition()/100.0); + conf.setBackgroundColor(colorDialog->currentColor()); + conf.save(); + close(); + emit(accepted()); +} + +void OptionsDialog::restoreOptions() +{ + Configuration & conf = Configuration::getConfiguration(); + + slideSize->setSliderPosition(conf.getGotoSlideSize().height()); + fitToWidthRatioS->setSliderPosition(conf.getFitToWidthRatio()*100); + pathEdit->setText(conf.getDefaultPath()); + updateColor(Configuration::getConfiguration().getBackgroundColor()); + switch(conf.getFlowType()){ + case PictureFlow::CoverFlowLike: + radio1->setChecked(true); + break; + case PictureFlow::Strip: + radio2->setChecked(true); + break; + case PictureFlow::StripOverlapped: + radio3->setChecked(true); + break; + } +} + + +void OptionsDialog::updateColor(const QColor & color) +{ + QPalette pal = backgroundColor->palette(); + pal.setColor(backgroundColor->backgroundRole(), color); + backgroundColor->setPalette(pal); + backgroundColor->setAutoFillBackground(true); + colorDialog->setCurrentColor(color); +} \ No newline at end of file diff --git a/YACReader/options_dialog.h b/YACReader/options_dialog.h new file mode 100644 index 00000000..11b92171 --- /dev/null +++ b/YACReader/options_dialog.h @@ -0,0 +1,57 @@ +#ifndef __OPTIONS_DIALOG_H +#define __OPTIONS_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class OptionsDialog : public QDialog +{ +Q_OBJECT + public: + OptionsDialog(QWidget * parent = 0); + private: + //QLabel * pathLabel; + QLineEdit * pathEdit; + QPushButton * pathFindButton; + + QLabel * magGlassSizeLabel; + + QLabel * zoomLevel; + + //QLabel * slideSizeLabel; + QSlider * slideSize; + + QPushButton * accept; + QPushButton * cancel; + + QRadioButton *radio1; + QRadioButton *radio2; + QRadioButton *radio3; + + //QLabel * fitToWidthRatioLabel; + QSlider * fitToWidthRatioS; + + QLabel * backgroundColor; + QPushButton * selectBackgroundColorButton; + + QColorDialog * colorDialog; + + public slots: + void saveOptions(); + void restoreOptions(); + void findFolder(); + void updateColor(const QColor & color); + +signals: + void changedOptions(); + +}; + + +#endif diff --git a/YACReader/render.cpp b/YACReader/render.cpp new file mode 100644 index 00000000..46fe60e2 --- /dev/null +++ b/YACReader/render.cpp @@ -0,0 +1,746 @@ +#include "render.h" +#include +#include +#include +#include +#include + +#define NL 2 +#define NR 2 + +QMutex mutex; + +//----------------------------------------------------------------------------- +// MeanNoiseReductionFilter +//----------------------------------------------------------------------------- + +MeanNoiseReductionFilter::MeanNoiseReductionFilter(enum NeighborghoodSize ns) +:neighborghoodSize(ns) +{ + +} + +QImage MeanNoiseReductionFilter::setFilter(const QImage & image) +{ + int width = image.width(); + int height = image.height(); + QImage result(width,height,image.format()); + int filterSize = sqrt((float)neighborghoodSize); + int bound = filterSize/2; + QRgb pix; + int r,g,b; + for(int j=bound;j redChannel; + QList greenChannel; + QList blueChannel; + for(int j=bound;j hist(256,0); + + for(int j=0;j 1; i--) + { + new_count += hist[i]; + percentage = new_count/count; + next_percentage = (new_count+hist[i-1])/count; + if(fabs (percentage - 0.006) < fabs (next_percentage - 0.006)) + { + max = i-1; + break; + } + } + QColor c; + int range = max - min; + for(int j=0;j f) +:QThread(), +numPage(np), +data(rd), +page(p), +degrees(d), +filters(f) +{ +} + +void PageRender::run() +{ + //QMutexLocker locker(&mutex); + + QImage img; + img.loadFromData(data); + if(degrees > 0) + { + QMatrix m; + m.rotate(degrees); + img = img.transformed(m,Qt::SmoothTransformation); + } + for(int i=0;isetFilter(img); + } + + + *page = img; + + emit pageReady(numPage); +} + +//----------------------------------------------------------------------------- +// DoublePageRender +//----------------------------------------------------------------------------- + +DoublePageRender::DoublePageRender(int np, const QByteArray & rd, const QByteArray & rd2, QImage * p,unsigned int d, QVector f) +:PageRender(), +numPage(np), +data(rd), +data2(rd2), +page(p), +degrees(d), +filters(f) +{ + +} + +void DoublePageRender::run() +{ + //QImage result; + //QMutexLocker locker(&mutex); + QImage img, img2; + if(!data.isEmpty()) + img.loadFromData(data); + if(!data2.isEmpty()) + img2.loadFromData(data2); + /*if(img.isNull()) + img = QPixmap(img2.width(),img2.height()); + if(img2.isNull()) + img2 = QPixmap(img.width(),img.height());*/ + + int x,y; + x = img.width()+img2.width(); + y = qMax(img.height(),img2.height()); + + + QImage auxImg(x,y,QImage::Format_RGB32); + QPainter painter(&auxImg); + painter.drawImage(0,0,img); + painter.drawImage(img.width(),0,img2); + painter.end(); + + if(degrees > 0) + { + QMatrix m; + m.rotate(degrees); + auxImg = auxImg.transformed(m,Qt::SmoothTransformation); + } + for(int i=0;isetFilter(auxImg); + } + + *page = auxImg; + + emit pageReady(numPage); +} + +//----------------------------------------------------------------------------- +// Render +//----------------------------------------------------------------------------- + +Render::Render() +:currentIndex(0),doublePage(false),comic(0),loadedComic(false),imageRotation(0),numLeftPages(NL),numRightPages(NR) +{ + int size = numLeftPages+numRightPages+1; + currentPageBufferedIndex = numLeftPages; + for(int i = 0; iisNull()) + { + if(pagesReady.size()>0) + { + if(doublePage) + { + if(pagesReady[currentIndex] && pagesReady[qMin(currentIndex+1,(int)comic->numPages()-1)]) + if(currentIndex+1 > comic->numPages()-1) + pageRenders[currentPageBufferedIndex] = new DoublePageRender(currentIndex,comic->getRawData()->at(currentIndex),QByteArray(),buffer[currentPageBufferedIndex],imageRotation,filters); + else + pageRenders[currentPageBufferedIndex] = new DoublePageRender(currentIndex,comic->getRawData()->at(currentIndex),comic->getRawData()->at(currentIndex+1),buffer[currentPageBufferedIndex],imageRotation,filters); + else + //las páginas no están listas, y se están cargando en el cómic + emit processingPage(); //para evitar confusiones esta señal debería llamarse de otra forma + } + else + if(pagesReady[currentIndex]) + pageRenders[currentPageBufferedIndex] = new PageRender(currentIndex,comic->getRawData()->at(currentIndex),buffer[currentPageBufferedIndex],imageRotation,filters); + else + //las páginas no están listas, y se están cargando en el cómic + emit processingPage(); //para evitar confusiones esta señal debería llamarse de otra forma + + //si se ha creado un hilo para renderizar la página actual, se arranca + if(pageRenders[currentPageBufferedIndex]!=0) + { + //se conecta la señal pageReady del hilo, con el SLOT prepareAvailablePage + connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int))); + //se emite la señal de procesando, debido a que los hilos se arrancan aquí + if(doublePage || filters.size()>0) + emit processingPage(); + pageRenders[currentPageBufferedIndex]->start(); + pageRenders[currentPageBufferedIndex]->setPriority(QThread::TimeCriticalPriority); + } + else + //en qué caso sería necesario hacer esto??? //TODO: IMPORTANTE, puede que no sea necesario. + emit processingPage(); + } + else + //no hay ninguna página lista para ser renderizada, es necesario esperar. + emit processingPage(); + } + else + // la página actual está lista + emit currentPageReady(); + + //se renderizan las páginas restantes para llenar el buffer. + if(doublePage) + fillBufferDoublePage(); + else + fillBuffer(); +} + +QPixmap * Render::getCurrentPage() +{ + QPixmap * page = new QPixmap(); + *page = page->fromImage(*buffer[currentPageBufferedIndex]); + return page; +} + +void Render::setRotation(int degrees) +{ + +} + +void Render::setComic(Comic * c) +{ + if(comic !=0) + delete comic; + comic = c; +} + +void Render::prepareAvailablePage(int page) +{ + if(currentIndex == page) + emit currentPageReady(); +} + +void Render::update() +{ + render(); +} +//----------------------------------------------------------------------------- +// Comic interface +//----------------------------------------------------------------------------- +void Render::load(const QString & path) +{ + if(comic!=0) + delete comic; + comic = new Comic(); + previousIndex = currentIndex = 0; + + connect(comic,SIGNAL(errorOpening()),this,SIGNAL(errorOpening())); + connect(comic,SIGNAL(imageLoaded(int)),this,SIGNAL(imageLoaded(int))); + connect(comic,SIGNAL(imageLoaded(int)),this,SLOT(pageRawDataReady(int))); + //connect(comic,SIGNAL(pageChanged(int)),this,SIGNAL(pageChanged(int))); + connect(comic,SIGNAL(numPages(unsigned int)),this,SIGNAL(numPages(unsigned int))); + connect(comic,SIGNAL(numPages(unsigned int)),this,SLOT(setNumPages(unsigned int))); + connect(comic,SIGNAL(imageLoaded(int,QByteArray)),this,SIGNAL(imageLoaded(int,QByteArray))); + connect(comic,SIGNAL(isBookmark(bool)),this,SIGNAL(currentPageIsBookmark(bool))); + connect(comic,SIGNAL(bookmarksLoaded(const Bookmarks &)),this,SIGNAL(bookmarksLoaded(const Bookmarks &))); + pagesReady.clear(); + comic->load(path); + invalidate(); + loadedComic = true; //TODO reset if an error occurs while opening +} +//si se solicita la siguiente página, se calcula cuál debe ser en función de si se lee en modo a doble página o no. +//la página sólo se renderiza, si realmente ha cambiado. +void Render::nextPage() +{ + int nextPage; //indica cuál será la próxima página + if(doublePage) + { + nextPage = currentIndex; + if(currentIndex+2numPages()) + { + nextPage = currentIndex+2; + if(currentIndex != nextPage) + comic->setIndex(nextPage); + } + } + else + { + nextPage = comic->nextPage(); + } + + //se fuerza renderizado si la página ha cambiado + if(currentIndex != nextPage) + { + previousIndex = currentIndex; + currentIndex = nextPage; + update(); + } +} +//si se solicita la página anterior, se calcula cuál debe ser en función de si se lee en modo a doble página o no. +//la página sólo se renderiza, si realmente ha cambiado. +void Render::previousPage() +{ + int previousPage; //indica cuál será la próxima página + if(doublePage) + { + if(currentIndex == 1) + invalidate(); + previousPage = qMax(currentIndex-2,0); + if(currentIndex != previousPage) + { + comic->setIndex(previousPage); + } + } + else + { + previousPage = comic->previousPage(); + } + + //se fuerza renderizado si la página ha cambiado + if(currentIndex != previousPage) + { + previousIndex = currentIndex; + currentIndex = previousPage; + update(); + } +} +unsigned int Render::getIndex() +{ + return comic->getIndex(); +} +unsigned int Render::numPages() +{ + return comic->numPages(); +} + +bool Render::hasLoadedComic() +{ + if(comic!=0) + return comic->loaded(); + return false; +} + +void Render::setNumPages(unsigned int numPages) +{ + pagesReady.fill(false,numPages); +} + +void Render::pageRawDataReady(int page) +{ + pagesEmited.push_back(page); + if(pageRenders.size()>0) + { + for(int i=0;i currentIndex-2*numLeftPages)) || + ((pagesEmited.at(i) > currentIndex+1) && (pagesEmited.at(i) < currentIndex+1+2*numRightPages)) ) + { + fillBufferDoublePage(); + } + } + else + { + if ( ((pagesEmited.at(i) < currentIndex) && (pagesEmited.at(i) > currentIndex-numLeftPages)) || + ((pagesEmited.at(i) > currentIndex) && (pagesEmited.at(i) < currentIndex+numRightPages)) ) + { + fillBuffer(); + } + } + } + pagesEmited.clear(); + } +} + +//sólo se renderiza la página, si ha habido un cambio de página +void Render::goTo(int index) +{ + + if(currentIndex != index) + { + comic->setIndex(index); + previousIndex = currentIndex; + currentIndex = index; + + //si cambia la paridad de las página en modo a doble página, se rellena el buffer. + //esto solo debería orcurrir al llegar al principio o al final + if(doublePage && ((previousIndex - index) % 2)!=0) + invalidate(); + + update(); + } +} + +void Render::rotateRight() +{ + imageRotation = (imageRotation+90) % 360; + invalidate(); + update(); +} +void Render::rotateLeft() +{ + if(imageRotation == 0) + imageRotation = 270; + else + imageRotation = imageRotation - 90; + invalidate(); + update(); +} + +//Actualiza el buffer, añadiendo las imágenes (vacías) necesarias para su posterior renderizado y +//eliminado aquellas que ya no sean necesarias. También libera los hilos (no estoy seguro de que sea responsabilidad suya) +//Calcula el número de nuevas páginas que hay que buferear y si debe hacerlo por la izquierda o la derecha (según sea el sentido de la lectura) +void Render::updateBuffer() +{ + //QMutexLocker locker(&mutex); + int windowSize = currentIndex - previousIndex; + if(doublePage) + { + windowSize = windowSize/2; + if(currentIndex == 0 && windowSize == 0 && previousIndex == 1) + windowSize = -1; + + } + if(windowSize > 0)//add pages to right pages and remove on the left + { + windowSize = qMin(windowSize,buffer.size()); + for(int i = 0; i < windowSize; i++) + { + //renders + PageRender * pr = pageRenders.front(); + pageRenders.pop_front(); + if(pr !=0) + { + if(pr->wait()) + delete pr; + } + pageRenders.push_back(0); + + //images + + if(buffer.front()!=0) + delete buffer.front(); + buffer.pop_front(); + buffer.push_back(new QImage()); + } + } + else //add pages to left pages and remove on the right + if(windowSize<0) + { + windowSize = -windowSize; + windowSize = qMin(windowSize,buffer.size()); + for(int i = 0; i < windowSize; i++) + { + //renders + PageRender * pr = pageRenders.back(); + pageRenders.pop_back(); + if(pr !=0) + { + if(pr->wait()) + delete pr; + } + pageRenders.push_front(0); + + //images + buffer.push_front(new QImage()); + QImage * p = buffer.back(); + if(p!=0) + delete p; + buffer.pop_back(); + } + } + previousIndex = currentIndex; +} + +void Render::fillBuffer() +{ + for(int i = 1; i <= qMax(numLeftPages,numRightPages); i++) + { + if ((currentIndex+i < comic->numPages()) && + buffer[currentPageBufferedIndex+i]->isNull() && + i <= numRightPages && + pageRenders[currentPageBufferedIndex+i]==0 && + pagesReady[currentIndex+1]) //preload next pages + { + pageRenders[currentPageBufferedIndex+i] = new PageRender(currentIndex+i,comic->getRawData()->at(currentIndex+i),buffer[currentPageBufferedIndex+i],imageRotation); + connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int))); + pageRenders[currentPageBufferedIndex+i]->start(); + } + + if ((currentIndex-i > 0) && + buffer[currentPageBufferedIndex-i]->isNull() && + i <= numLeftPages && + pageRenders[currentPageBufferedIndex-i]==0 && + pagesReady[currentIndex-1]) //preload previous pages + { + pageRenders[currentPageBufferedIndex-i] = new PageRender(currentIndex-i,comic->getRawData()->at(currentIndex-i),buffer[currentPageBufferedIndex-i],imageRotation); + connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int))); + pageRenders[currentPageBufferedIndex-i]->start(); + } + } +} + +void Render::fillBufferDoublePage() +{ + for(int i = 1; i <= qMax(numLeftPages,numRightPages); i++) + { + if ((currentIndex+2*i < comic->numPages()) && + buffer[currentPageBufferedIndex+i]->isNull() && + i <= numRightPages && + pageRenders[currentPageBufferedIndex+i]==0 && + (pagesReady[currentIndex+2*i] && pagesReady[qMin(currentIndex+(2*i)+1,(int)comic->numPages()-1)])) //preload next pages + { + if(currentIndex+(2*i)+1 > comic->numPages()-1) + pageRenders[currentPageBufferedIndex+i] = new DoublePageRender(currentIndex+2*i,comic->getRawData()->at(currentIndex+(2*i)),QByteArray(),buffer[currentPageBufferedIndex+i],imageRotation); + else + pageRenders[currentPageBufferedIndex+i] = new DoublePageRender(currentIndex+2*i,comic->getRawData()->at(currentIndex+(2*i)),comic->getRawData()->at(currentIndex+(2*i)+1),buffer[currentPageBufferedIndex+i],imageRotation); + connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int))); + pageRenders[currentPageBufferedIndex+i]->start(); + } + + if ((currentIndex-2*i >= -1) && + buffer[currentPageBufferedIndex-i]->isNull() && + i <= numLeftPages && + pageRenders[currentPageBufferedIndex-i]==0 && + (pagesReady[qMax(currentIndex-2*i,0)] && pagesReady[qMin(currentIndex-(2*i)+1,(int)comic->numPages()-1)])) //preload previous pages + { + if(currentIndex-2*i == -1) + pageRenders[currentPageBufferedIndex-i] = new DoublePageRender(0,QByteArray(),comic->getRawData()->at(0),buffer[currentPageBufferedIndex-i],imageRotation); + else + pageRenders[currentPageBufferedIndex-i] = new DoublePageRender(currentIndex-2*i,comic->getRawData()->at(currentIndex-(2*i)),comic->getRawData()->at(currentIndex-(2*i)+1),buffer[currentPageBufferedIndex-i],imageRotation); + connect(pageRenders[currentPageBufferedIndex],SIGNAL(pageReady(int)),this,SLOT(prepareAvailablePage(int))); + pageRenders[currentPageBufferedIndex-i]->start(); + } + } +} + +//Método que debe ser llamado cada vez que la estructura del buffer se vuelve inconsistente con el modo de lectura actual. +//se terminan todos los hilos en ejecución y se libera la memoria (de hilos e imágenes) +void Render::invalidate() +{ + for(int i=0;iterminate(); + delete pageRenders[i]; + pageRenders[i] = 0; + } + } + + for(int i=0;inumPages())) + s += "-"+QString::number(currentIndex+2); + s += "/"+QString::number(comic->numPages()); + return s; +} + +void Render::setBookmark() +{ + comic->setBookmark(); +} + +void Render::removeBookmark() +{ + comic->removeBookmark(); +} + +void Render::save() +{ + comic->saveBookmarks(); +} diff --git a/YACReader/render.h b/YACReader/render.h new file mode 100644 index 00000000..d13d1fee --- /dev/null +++ b/YACReader/render.h @@ -0,0 +1,177 @@ + #ifndef RENDER_H +#define RENDER_H + +#include +#include +#include +#include +#include +#include +#include "comic.h" +//----------------------------------------------------------------------------- +// FILTERS +//----------------------------------------------------------------------------- + +#include + +class Comic; + +class ImageFilter { +public: + ImageFilter(){}; + virtual QImage setFilter(const QImage & image) = 0; +}; + +class MeanNoiseReductionFilter : public ImageFilter { +public: + enum NeighborghoodSize{SMALL=9, LARGE=25 }; + MeanNoiseReductionFilter(enum NeighborghoodSize ns = SMALL); + virtual QImage setFilter(const QImage & image); +private: + enum NeighborghoodSize neighborghoodSize; +}; + +class MedianNoiseReductionFilter : public ImageFilter { +public: + enum NeighborghoodSize{SMALL=9, LARGE=25 }; + MedianNoiseReductionFilter(enum NeighborghoodSize ns = SMALL); + virtual QImage setFilter(const QImage & image); +private: + enum NeighborghoodSize neighborghoodSize; +}; + +class BrightnessFilter : public ImageFilter { +public: + BrightnessFilter(int l=150); + virtual QImage setFilter(const QImage & image); +private: + int level; +}; + +class ContrastFilter : public ImageFilter { +public: + ContrastFilter(); + virtual QImage setFilter(const QImage & image); +private: + int level; +}; + +//----------------------------------------------------------------------------- +// RENDER +//----------------------------------------------------------------------------- + +class PageRender : public QThread +{ + Q_OBJECT +public: + PageRender(); + PageRender(int numPage, const QByteArray & rawData, QImage * page,unsigned int degrees=0, QVector filters = QVector()); + int getNumPage(){return numPage;}; + void setData(const QByteArray & rawData){data = rawData;}; + void setPage(QImage * p){page = p;}; + void setRotation(unsigned int d){degrees = d;}; + void setFilters(QVector f){filters = f;}; +private: + int numPage; + QByteArray data; + QImage * page; + unsigned int degrees; + QVector filters; + void run(); +signals: + void pageReady(int); + +}; +//----------------------------------------------------------------------------- +// RENDER +//----------------------------------------------------------------------------- + +class DoublePageRender : public PageRender +{ + Q_OBJECT +public: + DoublePageRender(int firstPage, const QByteArray & firstPageData,const QByteArray & secondPageData, QImage * page,unsigned int degrees=0, QVector filters = QVector()); +private: + int numPage; + QByteArray data; + QByteArray data2; + QImage * page; + unsigned int degrees; + QVector filters; + void run(); +signals: + void pageReady(int); + +}; + + +class Render : public QObject { +Q_OBJECT +public: + Render(); + +public slots: + void render(); + QPixmap * getCurrentPage(); + void goTo(int index); + void doublePageSwitch(); + void setRotation(int degrees); + void setComic(Comic * c); + void prepareAvailablePage(int page); + void update(); + void setNumPages(unsigned int numPages); + void pageRawDataReady(int page); + //--comic interface + void nextPage(); + void previousPage(); + void load(const QString & path); + void rotateRight(); + void rotateLeft(); + unsigned int getIndex(); + unsigned int numPages(); + bool hasLoadedComic(); + void updateBuffer(); + void fillBuffer(); + void fillBufferDoublePage(); + void invalidate(); + QString getCurrentPagesInformation(); + void setBookmark(); + void removeBookmark(); + void save(); + +signals: + void currentPageReady(); + void processingPage(); + void imagesLoaded(); + void imageLoaded(int index); + void imageLoaded(int index,const QByteArray & image); + void pageChanged(int index); + void numPages(unsigned int numPages); + void errorOpening(); + void currentPageIsBookmark(bool); + void bookmarksLoaded(const Bookmarks &); + +private: + Comic * comic; + bool doublePage; + int previousIndex; + int currentIndex; + //QPixmap * currentPage; + int currentPageBufferedIndex; + int numLeftPages; + int numRightPages; + QList pageRenders; + QList buffer; + void loadAll(); + void updateRightPages(); + void updateLeftPages(); + bool loadedComic; + QList pagesEmited; + QVector pagesReady; + int imageRotation; + QVector filters; + +}; + + +#endif // RENDER_H diff --git a/YACReader/shortcuts_dialog.cpp b/YACReader/shortcuts_dialog.cpp new file mode 100644 index 00000000..cc40b1e4 --- /dev/null +++ b/YACReader/shortcuts_dialog.cpp @@ -0,0 +1,74 @@ +#include "shortcuts_dialog.h" +#include +#include +#include +#include +#include +#include +#include + +ShortcutsDialog::ShortcutsDialog(QWidget * parent) + :QDialog(parent)//,Qt::FramelessWindowHint) +{ + setModal(true); + setWindowIcon(QIcon(":/images/shortcuts.png")); + setWindowTitle(tr("YACReader keyboard shortcuts")); + + QVBoxLayout * mainLayout = new QVBoxLayout; + + close = new QPushButton(tr("Close")); + connect(close,SIGNAL(clicked()),this,SLOT(close())); + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(close); + + QHBoxLayout * shortcutsLayout = new QHBoxLayout; + + shortcuts = new QTextEdit(); + QTextEdit * shortcuts2 = new QTextEdit(); + shortcuts->setFrameStyle(QFrame::NoFrame); + shortcuts2->setFrameStyle(QFrame::NoFrame); + //"

General functions:


O : Open comic
Esc : Exit

" + shortcuts->setReadOnly(true); + shortcuts2->setReadOnly(true); + shortcutsLayout->addWidget(shortcuts); + shortcutsLayout->addWidget(shortcuts2); + shortcutsLayout->setSpacing(0); + mainLayout->addLayout(shortcutsLayout); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout *imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(); + QPixmap p(":/images/shortcuts.png"); + imgLabel->setPixmap(p); + + QVBoxLayout * imgLayout = new QVBoxLayout; + imgLayout->addWidget(imgLabel); + imgLayout->addStretch(); + + imgMainLayout->addLayout(imgLayout); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setFixedSize(QSize(700,500)); + + QFile f(":/files/shortcuts.html"); + f.open(QIODevice::ReadOnly); + QTextStream txtS(&f); + QString content = txtS.readAll(); + f.close(); + + shortcuts->setHtml(content); + + QFile f2(":/files/shortcuts2.html"); + f2.open(QIODevice::ReadOnly); + QTextStream txtS2(&f2); + content = txtS2.readAll(); + f2.close(); + + shortcuts2->setHtml(content); + + setWindowTitle(tr("Keyboard Shortcuts")); +} diff --git a/YACReader/shortcuts_dialog.h b/YACReader/shortcuts_dialog.h new file mode 100644 index 00000000..8f1b66b6 --- /dev/null +++ b/YACReader/shortcuts_dialog.h @@ -0,0 +1,19 @@ +#ifndef SHORTCUTS_DIALOG_H +#define SHORTCUTS_DIALOG_H + +#include +#include +#include + +class ShortcutsDialog : public QDialog +{ +Q_OBJECT + public: + ShortcutsDialog(QWidget * parent = 0); + private: + QTextEdit * shortcuts; + QPushButton * close; + public slots: +}; + +#endif // SHORTCUTS_DIALOG_H diff --git a/YACReader/translator.cpp b/YACReader/translator.cpp new file mode 100644 index 00000000..fce5832d --- /dev/null +++ b/YACReader/translator.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "translator.h" + +YACReaderTranslator::YACReaderTranslator(QWidget * parent) +:QWidget(parent) +{ + this->setCursor(QCursor(Qt::ArrowCursor)); + this->setAutoFillBackground(true); + this->setBackgroundRole(QPalette::Window); + QPalette p(this->palette()); + p.setColor(QPalette::Window, QColor(96,96,96)); + this->setPalette(p); + + QVBoxLayout *layout = new QVBoxLayout(this); + QWebView * view = new QWebView(); + QFile f(":/files/translator.html"); + f.open(QIODevice::ReadOnly); + QTextStream txtS(&f); + QString contentHTML = txtS.readAll(); + view->setHtml(contentHTML); + view->page()->setLinkDelegationPolicy(QWebPage::DelegateExternalLinks); + connect(view->page(),SIGNAL(linkClicked(QUrl)),this,SLOT(play(QUrl))); + + QHBoxLayout * buttonBar = new QHBoxLayout(); + QPushButton * close = new QPushButton(QIcon(QPixmap(":/images/close.png")),""); + close->setFlat(true); + buttonBar->addStretch(); + close->resize(18,18); + buttonBar->addWidget(close); + buttonBar->setMargin(0); + connect(close,SIGNAL(clicked()),this->parent(),SLOT(animateHideTranslator())); + + layout->setMargin(0); + layout->setSpacing(0); + + layout->addLayout(buttonBar); + layout->addWidget(view); + + resize(view->size().width()/1.60,view->size().height()); + + music = createPlayer(MusicCategory); + + show(); +} + +void YACReaderTranslator::play(const QUrl & url) +{ + MediaSource src(url); + src.setAutoDelete(true); + music->setCurrentSource(src); + music->play(); +} + +YACReaderTranslator::~YACReaderTranslator() +{ + delete music; +} + +void YACReaderTranslator::mousePressEvent(QMouseEvent *event) +{ + drag = true; + click = event->pos(); +} + +void YACReaderTranslator::mouseReleaseEvent(QMouseEvent *event) +{ + drag = false; +} + +void YACReaderTranslator::mouseMoveEvent(QMouseEvent * event) +{ + if(drag) + this->move(QPoint(mapToParent(event->pos())-click)); + +} + diff --git a/YACReader/translator.h b/YACReader/translator.h new file mode 100644 index 00000000..759ce22c --- /dev/null +++ b/YACReader/translator.h @@ -0,0 +1,32 @@ +#ifndef __TRANSLATOR_H +#define __TRANSLATOR_H + +class QUrl; +class QMouseEvent; +class QPoint; +#include +#include + +using namespace Phonon; + +class YACReaderTranslator : public QWidget +{ + Q_OBJECT + public: + YACReaderTranslator(QWidget * parent = 0); + ~YACReaderTranslator(); + + public slots: + void play(const QUrl & url); + +protected: + void mousePressEvent(QMouseEvent *event); + void mouseReleaseEvent(QMouseEvent *event); + void mouseMoveEvent ( QMouseEvent * event ); + bool drag; + QPoint click; +private: + MediaObject * music; +}; + +#endif \ No newline at end of file diff --git a/YACReader/viewer.cpp b/YACReader/viewer.cpp new file mode 100644 index 00000000..a107d394 --- /dev/null +++ b/YACReader/viewer.cpp @@ -0,0 +1,671 @@ +#include "viewer.h" +#include "magnifying_glass.h" +#include "configuration.h" +#include "magnifying_glass.h" +#include "goto_flow.h" +#include "bookmarks_dialog.h" +#include "render.h" +#include "goto_dialog.h" +#include "translator.h" + +#include +#include +#define STEPS 22 + + + +Viewer::Viewer(QWidget * parent) +:QScrollArea(parent), +currentPage(0), +magnifyingGlassShowed(false), +fullscreen(false), +information(false), +adjustToWidthRatio(1), +doublePage(false), +wheelStop(false), +direction(1), +restoreMagnifyingGlass(false), +drag(false) + +{ + translator = new YACReaderTranslator(this); + translator->hide(); + translatorAnimation = new QPropertyAnimation(translator,"pos"); + translatorAnimation->setDuration(150); + translatorXPos = -10000; + translator->move(-translator->width(),10); + //current comic page + content = new QLabel(this); + configureContent(tr("Press 'O' to open comic.")); + //scroll area configuration + setBackgroundRole(QPalette::Dark); + setWidget(content); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFrameStyle(QFrame::NoFrame); + setAlignment(Qt::AlignCenter); + + QPalette palette; + palette.setColor(backgroundRole(), Configuration::getConfiguration().getBackgroundColor()); + setPalette(palette); + //--------------------------------------- + mglass = new MagnifyingGlass(Configuration::getConfiguration().getMagnifyingGlassSize(),this); + mglass->hide(); + content->setMouseTracking(true); + setMouseTracking(true); + + informationLabel = new QLabel(this); + informationLabel->setAlignment(Qt::AlignVCenter|Qt::AlignHCenter); + informationLabel->setAutoFillBackground(true); + informationLabel->setFont(QFont("courier new", 12)); + informationLabel->hide(); + informationLabel->resize(100,25); + + showCursor(); + + goToDialog = new GoToDialog(this); + + goToFlow = new GoToFlow(this,Configuration::getConfiguration().getFlowType()); + goToFlow->hide(); + showGoToFlowAnimation = new QPropertyAnimation(goToFlow,"pos"); + showGoToFlowAnimation->setDuration(150); + + bd = new BookmarksDialog(this->parentWidget()); + + render = new Render(); + + scroller = new QTimer(this); + + hideCursorTimer = new QTimer(); + hideCursorTimer->setSingleShot(true); + + if(Configuration::getConfiguration().getDoublePage()) + doublePageSwitch(); + + createConnections(); + + hideCursorTimer->start(2500); + + setMouseTracking(true); +} + +void Viewer::createConnections() +{ + //magnifyingGlass (update mg after a background change + connect(this,SIGNAL(backgroundChanges()),mglass,SLOT(updateImage())); + + //goToDialog + connect(goToDialog,SIGNAL(goToPage(unsigned int)),this,SLOT(goTo(unsigned int))); + + //goToFlow goTo + connect(goToFlow,SIGNAL(goToPage(unsigned int)),this,SLOT(goTo(unsigned int))); + + //current time + QTimer * t = new QTimer(); + connect(t,SIGNAL(timeout()),this,SLOT(updateInformation())); + t->start(1000); + + //hide cursor + connect(hideCursorTimer,SIGNAL(timeout()),this,SLOT(hideCursor())); + + //bookmarks + connect(bd,SIGNAL(goToPage(unsigned int)),this,SLOT(goTo(unsigned int))); + + //render + connect(render,SIGNAL(errorOpening()),this,SLOT(resetContent())); + connect(render,SIGNAL(numPages(unsigned int)),goToFlow,SLOT(setNumSlides(unsigned int))); + connect(render,SIGNAL(numPages(unsigned int)),goToDialog,SLOT(setNumPages(unsigned int))); + connect(render,SIGNAL(imageLoaded(int,QByteArray)),goToFlow,SLOT(setImageReady(int,QByteArray))); + connect(render,SIGNAL(currentPageReady()),this,SLOT(updatePage())); + connect(render,SIGNAL(processingPage()),this,SLOT(setLoadingMessage())); + connect(render,SIGNAL(currentPageIsBookmark(bool)),this,SIGNAL(pageIsBookmark(bool))); + connect(render,SIGNAL(bookmarksLoaded(const Bookmarks &)),bd,SLOT(setBookmarks(const Bookmarks &))); +} + +void Viewer::open(QString pathFile) +{ + if(render->hasLoadedComic()) + save(); + //bd->setBookmarks(*bm); + + goToFlow->reset(); + render->load(pathFile); + render->update(); + + verticalScrollBar()->setSliderPosition(verticalScrollBar()->minimum()); +} + +void Viewer::next() +{ + direction = 1; + render->nextPage(); +} + +void Viewer::prev() +{ + direction = -1; + render->previousPage(); +} +void Viewer::showGoToDialog() +{ + goToDialog->show(); +} +void Viewer::goTo(unsigned int page) +{ + direction = 1; //in "go to" direction is always fordward + render->goTo(page-1); +} + +void Viewer::updatePage() +{ + QPixmap * previousPage = currentPage; + currentPage = render->getCurrentPage(); + content->setPixmap(*currentPage); + updateContentSize(); + updateVerticalScrollBar(); + emit backgroundChanges(); + emit(pageAvailable(true)); + //TODO -> update bookmark action + setFocus(Qt::ShortcutFocusReason); + delete previousPage; + if(restoreMagnifyingGlass) + { + restoreMagnifyingGlass = false; + showMagnifyingGlass(); + } + +} + +void Viewer::updateContentSize() +{ + //there is an image to resize + if(currentPage !=0 && !currentPage->isNull()) + { + if(Configuration::getConfiguration().getAdjustToFullSize()) + { + content->resize(currentPage->width(),currentPage->height()); + } + else + { + float aspectRatio = (float)currentPage->width()/currentPage->height(); + //Fit to width + if(Configuration::getConfiguration().getAdjustToWidth()) + { + adjustToWidthRatio = Configuration::getConfiguration().getFitToWidthRatio(); + if(static_cast(width()*adjustToWidthRatio/aspectRatio)(height()*aspectRatio)>width()) + content->resize(width(),static_cast(width()/aspectRatio)); + else + content->resize(static_cast(height()*aspectRatio),height()); + else + content->resize(width()*adjustToWidthRatio,static_cast(width()*adjustToWidthRatio/aspectRatio)); + } + //Fit to height or fullsize/custom size + else + { + if(static_cast(height()*aspectRatio)>width()) //page width exceeds window width + content->resize(width(),static_cast(width()/aspectRatio)); + else + content->resize(static_cast(height()*aspectRatio),height()); + } + } + emit backgroundChanges(); + } + content->update(); //TODO, it shouldn't be neccesary +} + +void Viewer::updateVerticalScrollBar() +{ + if(direction > 0) + verticalScrollBar()->setSliderPosition(verticalScrollBar()->minimum()); + else + verticalScrollBar()->setSliderPosition(verticalScrollBar()->maximum()); +} + +void Viewer::scrollDown() +{ + if(verticalScrollBar()->sliderPosition()==verticalScrollBar()->maximum()) + { + next(); + scroller->stop(); + } + else + { + int currentPos = verticalScrollBar()->sliderPosition(); + verticalScrollBar()->setSliderPosition(currentPos=currentPos+posByStep); + if((verticalScrollBar()->sliderPosition()==verticalScrollBar()->maximum()) + ||(verticalScrollBar()->sliderPosition()>=nextPos)) + scroller->stop(); + emit backgroundChanges(); + } +} + +void Viewer::scrollUp() +{ + if(verticalScrollBar()->sliderPosition()==verticalScrollBar()->minimum()) + { + prev(); + scroller->stop(); + } + else + { + int currentPos = verticalScrollBar()->sliderPosition(); + verticalScrollBar()->setSliderPosition(currentPos=currentPos-posByStep); + if((verticalScrollBar()->sliderPosition()==verticalScrollBar()->minimum()) + ||(verticalScrollBar()->sliderPosition()<=nextPos)) + scroller->stop(); + emit backgroundChanges(); + } +} + +void Viewer::keyPressEvent(QKeyEvent *event) +{ + if(render->hasLoadedComic()) + { + if(goToFlow->isVisible() && event->key()!=Qt::Key_S) + QCoreApplication::sendEvent(goToFlow,event); + else + switch (event->key()) + { + case Qt::Key_Space: + disconnect(scroller,SIGNAL(timeout()),this,0); + connect(scroller,SIGNAL(timeout()),this,SLOT(scrollDown())); + posByStep = height()/STEPS; + nextPos=verticalScrollBar()->sliderPosition()+static_cast((height()*0.80)); + scroller->start(20); + break; + case Qt::Key_B: + disconnect(scroller,SIGNAL(timeout()),this,0); + connect(scroller,SIGNAL(timeout()),this,SLOT(scrollUp())); + posByStep = height()/STEPS; + nextPos=verticalScrollBar()->sliderPosition()-static_cast((height()*0.80)); + scroller->start(20); + break; + case Qt::Key_S: + goToFlowSwitch(); + break; + case Qt::Key_T: + translatorSwitch(); + break; + case Qt::Key_Down: + /*if(verticalScrollBar()->sliderPosition()==verticalScrollBar()->maximum()) + next(); + else*/ + QAbstractScrollArea::keyPressEvent(event); + emit backgroundChanges(); + break; + case Qt::Key_Up: + /*if(verticalScrollBar()->sliderPosition()==verticalScrollBar()->minimum()) + prev(); + else*/ + QAbstractScrollArea::keyPressEvent(event); + emit backgroundChanges(); + break; + case Qt::Key_Home: + goTo(1); + break; + case Qt::Key_End: + goTo(this->render->numPages()); + break; + default: + QAbstractScrollArea::keyPressEvent(event); + break; + } + if(mglass->isVisible()) + switch(event->key()) + { + case Qt::Key_Plus: case Qt::Key_Minus: case Qt::Key_Underscore: case Qt::Key_Asterisk: + QCoreApplication::sendEvent(mglass,event); + } + } +} + +void Viewer::wheelEvent(QWheelEvent * event) +{ + if(render->hasLoadedComic()) + { + if((event->delta()<0)&&(verticalScrollBar()->sliderPosition()==verticalScrollBar()->maximum())) + { + if(wheelStop) + { + next(); + event->accept(); + wheelStop = false; + return; + } + else + wheelStop = true; + } + else + if((event->delta()>0)&&(verticalScrollBar()->sliderPosition()==verticalScrollBar()->minimum())) + { + if(wheelStop) + { + prev(); + event->accept(); + wheelStop = false; + return; + } + else + wheelStop = true; + } + + QAbstractScrollArea::wheelEvent(event); + emit backgroundChanges(); + } +} + +void Viewer::resizeEvent(QResizeEvent * event) +{ + updateContentSize(); + goToFlow->move(QPoint((width()-goToFlow->width())/2,height()-goToFlow->height())); + informationLabel->move(QPoint((width()-informationLabel->width())/2,0)); + QScrollArea::resizeEvent(event); +} + +void Viewer::mouseMoveEvent(QMouseEvent * event) +{ + showCursor(); + hideCursorTimer->start(2500); + + if(magnifyingGlassShowed) + mglass->move(static_cast(event->x()-float(mglass->width())/2),static_cast(event->y()-float(mglass->height())/2)); + + if(render->hasLoadedComic()) + { + if(showGoToFlowAnimation->state()!=QPropertyAnimation::Running) + { + if(goToFlow->isVisible()) + { + animateHideGoToFlow(); + //goToFlow->hide(); + } + else + { + int umbral = (width()-goToFlow->width())/2; + if((event->y()>height()-15)&&(event->x()>umbral)&&(event->x()stop(); + } + } + } + + if(drag) + { + int currentPosY = verticalScrollBar()->sliderPosition(); + int currentPosX = horizontalScrollBar()->sliderPosition(); + verticalScrollBar()->setSliderPosition(currentPosY=currentPosY+(yDragOrigin-event->y())); + horizontalScrollBar()->setSliderPosition(currentPosX=currentPosX+(xDragOrigin-event->x())); + yDragOrigin = event->y(); + xDragOrigin = event->x(); + } + } + + +} + +const QPixmap * Viewer::pixmap() +{ + return content->pixmap(); +} + +void Viewer::magnifyingGlassSwitch() +{ + magnifyingGlassShowed?hideMagnifyingGlass():showMagnifyingGlass(); +} + +void Viewer::showMagnifyingGlass() +{ + if(render->hasLoadedComic()) + { + QPoint p = QPoint(cursor().pos().x(),cursor().pos().y()); + p = this->parentWidget()->mapFromGlobal(p); + mglass->move(static_cast(p.x()-float(mglass->width())/2) + ,static_cast(p.y()-float(mglass->height())/2)); + mglass->show(); + mglass->updateImage(mglass->x()+mglass->width()/2,mglass->y()+mglass->height()/2); + magnifyingGlassShowed = true; + } +} + +void Viewer::hideMagnifyingGlass() +{ + mglass->hide(); + magnifyingGlassShowed = false; +} + +void Viewer::informationSwitch() +{ + information?informationLabel->hide():informationLabel->show(); + informationLabel->move(QPoint((width()-informationLabel->width())/2,0)); + information=!information; + //TODO it shouldn't be neccesary + informationLabel->adjustSize(); + informationLabel->update(); +} + +void Viewer::updateInformation() +{ + if(render->hasLoadedComic()) + { + informationLabel->setText(render->getCurrentPagesInformation()+" - "+QTime::currentTime().toString("HH:mm")); + informationLabel->adjustSize(); + informationLabel->update(); //TODO it shouldn't be neccesary + } +} + +void Viewer::goToFlowSwitch() +{ + goToFlow->isVisible()?animateHideGoToFlow():showGoToFlow(); +} + +void Viewer::translatorSwitch() +{ + translator->isVisible()?animateHideTranslator():animateShowTranslator(); +} + +void Viewer::showGoToFlow() +{ + if(render->hasLoadedComic()) + { + animateShowGoToFlow(); + } +} + +void Viewer::animateShowGoToFlow() +{ + if(goToFlow->isHidden() && showGoToFlowAnimation->state()!=QPropertyAnimation::Running) + { + disconnect(showGoToFlowAnimation,SIGNAL(finished()),goToFlow,SLOT(hide())); + connect(showGoToFlowAnimation,SIGNAL(finished()),this,SLOT(moveCursoToGoToFlow())); + showGoToFlowAnimation->setStartValue(QPoint((width()-goToFlow->width())/2,height()-10)); + showGoToFlowAnimation->setEndValue(QPoint((width()-goToFlow->width())/2,height()-goToFlow->height())); + showGoToFlowAnimation->start(); + goToFlow->centerSlide(render->getIndex()); + goToFlow->setPageNumber(render->getIndex()); + goToFlow->show(); + goToFlow->setFocus(Qt::OtherFocusReason); + } +} + +void Viewer::animateHideGoToFlow() +{ + if(goToFlow->isVisible() && showGoToFlowAnimation->state()!=QPropertyAnimation::Running) + { + connect(showGoToFlowAnimation,SIGNAL(finished()),goToFlow,SLOT(hide())); + disconnect(showGoToFlowAnimation,SIGNAL(finished()),this,SLOT(moveCursoToGoToFlow())); + showGoToFlowAnimation->setStartValue(QPoint((width()-goToFlow->width())/2,height()-goToFlow->height())); + showGoToFlowAnimation->setEndValue(QPoint((width()-goToFlow->width())/2,height())); + showGoToFlowAnimation->start(); + goToFlow->centerSlide(render->getIndex()); + goToFlow->setPageNumber(render->getIndex()); + this->setFocus(Qt::OtherFocusReason); + } +} + +void Viewer::moveCursoToGoToFlow() +{ + //Move cursor to goToFlow widget on show (this avoid hide when mouse is moved) + int y = goToFlow->pos().y(); + int x1 = goToFlow->pos().x(); + int x2 = x1 + goToFlow->width(); + QPoint cursorPos = mapFromGlobal(cursor().pos()); + int cursorX = cursorPos.x(); + int cursorY = cursorPos.y(); + + if(cursorY <= y) + cursorY = y + 10; + if(cursorX <= x1) + cursorX = x1 + 10; + if(cursorX >= x2) + cursorX = x2 - 10; + cursor().setPos(mapToGlobal(QPoint(cursorX,cursorY))); + hideCursorTimer->stop(); + showCursor(); +} + +void Viewer::rotateLeft() +{ + render->rotateLeft(); +} +void Viewer::rotateRight() +{ + render->rotateRight(); +} + +//TODO +void Viewer::setBookmark(bool set) +{ + render->setBookmark(); + if(set) //add bookmark + { + render->setBookmark(); + } + else //remove bookmark + { + render->removeBookmark(); + } +} + +void Viewer::save () +{ + if(render->hasLoadedComic()) + render->save(); +} + +void Viewer::doublePageSwitch() +{ + doublePage = !doublePage; + render->doublePageSwitch(); + Configuration::getConfiguration().setDoublePage(doublePage); +} + +void Viewer::resetContent() +{ + configureContent(tr("Press 'O' to open comic.")); + goToFlow->reset(); + emit reset(); +} + +void Viewer::setLoadingMessage() +{ + if(magnifyingGlassShowed) + { + hideMagnifyingGlass(); + restoreMagnifyingGlass = true; + } + emit(pageAvailable(false)); + configureContent(tr("Loading...please wait!")); +} + +void Viewer::configureContent(QString msg) +{ + content->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + content->setScaledContents(true); + content->setAlignment(Qt::AlignTop|Qt::AlignHCenter); + content->setText(msg); + content->setFont(QFont("courier new", 12)); + content->adjustSize(); + setFocus(Qt::ShortcutFocusReason); + //emit showingText(); +} + +void Viewer::hideCursor() +{ + setCursor(Qt::BlankCursor); +} +void Viewer::showCursor() +{ + if(drag) + setCursor(Qt::ClosedHandCursor); + else + setCursor(Qt::OpenHandCursor); +} + +void Viewer::updateOptions() +{ + + goToFlow->setFlowType(Configuration::getConfiguration().getFlowType()); + updateBackgroundColor(Configuration::getConfiguration().getBackgroundColor()); + updateContentSize(); + //goToFlow->updateSize(); +} + +void Viewer::updateBackgroundColor(const QColor & color) +{ + QPalette palette; + palette.setColor(backgroundRole(), color); + setPalette(palette); +} + +void Viewer::animateShowTranslator() +{ + if(translator->isHidden() && translatorAnimation->state()!=QPropertyAnimation::Running) + { + disconnect(translatorAnimation,SIGNAL(finished()),translator,SLOT(hide())); + if(translatorXPos == -10000) + translatorXPos = (width()-translator->width())/2; + int x = qMax(0,qMin(translatorXPos,width()-translator->width())); + if(translator->pos().x()<0) + { + translatorAnimation->setStartValue(QPoint(-translator->width(),translator->pos().y())); + } + else + { + translatorAnimation->setStartValue(QPoint(width()+translator->width(),translator->pos().y())); + } + translatorAnimation->setEndValue(QPoint(x,translator->pos().y())); + translatorAnimation->start(); + translator->show(); + translator->setFocus(Qt::OtherFocusReason); + } +} +void Viewer::animateHideTranslator() +{ + if(translator->isVisible() && translatorAnimation->state()!=QPropertyAnimation::Running) + { + connect(translatorAnimation,SIGNAL(finished()),translator,SLOT(hide())); + translatorAnimation->setStartValue(QPoint(translatorXPos = translator->pos().x(),translator->pos().y())); + if((translator->width()/2)+translator->pos().x() <= width()/2) + translatorAnimation->setEndValue(QPoint(-translator->width(),translator->pos().y())); + else + translatorAnimation->setEndValue(QPoint(width()+translator->width(),translator->pos().y())); + translatorAnimation->start(); + this->setFocus(Qt::OtherFocusReason); + } +} + +void Viewer::mousePressEvent ( QMouseEvent * event ) +{ + drag = true; + yDragOrigin = event->y(); + xDragOrigin = event->x(); + setCursor(Qt::ClosedHandCursor); +} +void Viewer::mouseReleaseEvent ( QMouseEvent * event ) +{ + drag = false; + setCursor(Qt::OpenHandCursor); +} \ No newline at end of file diff --git a/YACReader/viewer.h b/YACReader/viewer.h new file mode 100644 index 00000000..6da04035 --- /dev/null +++ b/YACReader/viewer.h @@ -0,0 +1,131 @@ +#ifndef __VIEWER_H +#define __VIEWER_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +class Comic; +class MagnifyingGlass; +class GoToFlow; +class BookmarksDialog; +class Render; +class GoToDialog; +class YACReaderTranslator; + + class Viewer : public QScrollArea + { + Q_OBJECT + public: + bool fullscreen; //TODO, change by the right use of windowState(); + public slots: + void open(QString pathFile); + void prev(); + void next(); + void showGoToDialog(); + void goTo(unsigned int page); + void updatePage(); + void updateContentSize(); + void updateVerticalScrollBar(); + void updateOptions(); + void scrollDown(); + void scrollUp(); + void magnifyingGlassSwitch(); + void showMagnifyingGlass(); + void hideMagnifyingGlass(); + void informationSwitch(); + void updateInformation(); + void goToFlowSwitch(); + void showGoToFlow(); + void moveCursoToGoToFlow(); + void animateShowGoToFlow(); + void animateHideGoToFlow(); + void rotateLeft(); + void rotateRight(); + bool magnifyingGlassIsVisible() {return magnifyingGlassShowed;} + void setBookmark(bool); + void save(); + void doublePageSwitch(); + void resetContent(); + void setLoadingMessage(); + void configureContent(QString msg); + void hideCursor(); + void showCursor(); + void createConnections(); + void translatorSwitch(); + void animateShowTranslator(); + void animateHideTranslator(); +virtual void mousePressEvent ( QMouseEvent * event ); +virtual void mouseReleaseEvent ( QMouseEvent * event ); + void updateBackgroundColor(const QColor & color); + + private: + bool information; + bool doublePage; + QLabel * informationLabel; + QTimer * scroller; + int posByStep; + int nextPos; + GoToFlow * goToFlow; + QPropertyAnimation * showGoToFlowAnimation; + GoToDialog * goToDialog; + //!Image properties + float adjustToWidthRatio; + //! Comic + //Comic * comic; + int index; + QPixmap *currentPage; + BookmarksDialog * bd; + bool wheelStop; + Render * render; + QTimer * hideCursorTimer; + int direction; + bool drag; + + //!Widgets + QLabel *content; + + YACReaderTranslator * translator; + int translatorXPos; + QPropertyAnimation * translatorAnimation; + + int yDragOrigin; + int xDragOrigin; + private: + //!Magnifying glass + MagnifyingGlass *mglass; + bool magnifyingGlassShowed; + bool restoreMagnifyingGlass; + + //! Manejadores de evento: + void keyPressEvent(QKeyEvent * event); + void resizeEvent(QResizeEvent * event); + void wheelEvent(QWheelEvent * event); + void mouseMoveEvent(QMouseEvent * event); + + public: + Viewer(QWidget * parent = 0); + void toggleFullScreen(); + const QPixmap * pixmap(); + //Comic * getComic(){return comic;} + const BookmarksDialog * getBookmarksDialog(){return bd;} + signals: + void backgroundChanges(); + void pageAvailable(bool); + void pageIsBookmark(bool); + void reset(); + }; + +#endif diff --git a/YACReader/yacreader_es.qm b/YACReader/yacreader_es.qm new file mode 100644 index 0000000000000000000000000000000000000000..acfc1ae4982a08683571e62be9029a68906d1085 GIT binary patch literal 9964 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 + + + + BookmarksDialog + + + Lastest Page + Última página + + + + Close + Cerrar + + + + Click on any image to go to the bookmark + Pulsa en cualquier imagen para ir al marcador + + + + + Loading... + Cargando... + + + + Comic + + + No images found + No se han encontrado imágenes + + + + There are not images on the selected folder + No hay imágenes en el directorio seleccionado + + + + File error + Error en archivo + + + + File not found or not images in file + Archivo no encontrado o no hay imágenes en él + + + + 7z not found + 7z no encontrado + + + + 7z wasn't found in your PATH. + 7z no se ha encontrado en el PATH. + + + + 7z crashed + 7z falló + + + + 7z crashed. + 7z falló. + + + + 7z reading + 7z leyendo + + + + problem reading from 7z + Problema leyendo desde 7z + + + + 7z problem + 7z problema + + + + Unknown error 7z + Error desconocido 7z + + + + Configuration + + + Saving config file.... + Guardando el archivo de configuración... + + + + There was a problem saving YACReader configuration. Please, check if you have enough permissions in the YACReader root folder. + Hubo un problema al guardar la configuración de YACReader. Por favor, comprueba si tienes suficientes permisos en el directorio raíz de YACReader. + + + + GoToDialog + + + Page : + Página : + + + + Go To + Ir a + + + + Cancel + Cancelar + + + + + Total pages : + Páginas totales: + + + + Go to... + Ir a... + + + + GoToFlow + + + Page : + Página: + + + Total pages : + Page: + + + + HelpAboutDialog + + + About + Acerca de + + + + Help + Ayuda + + + + MainWindowViewer + + + &Open + &Abrir + + + + O + O + + + + Open a comic + Abrir cómic + + + + Open Folder + Abrir carpeta + + + + Ctrl+O + Ctrl+O + + + + Open image folder + Open images in a folder + Abrir carpeta de imágenes + + + + Save + Guardar + + + + + Save current page + Guardar la página actual + + + + Previous Comic + Cómic anterior + + + + Open previous comic + Abrir cómic anterior + + + + Next Comic + Siguiente Cómic + + + + Open next comic + Abrir siguiente cómic + + + + &Previous + A&nterior + + + + Go to previous page + Ir a la página anterior + + + + &Next + Siguie&nte + + + + Go to next page + Ir a la página siguiente + + + + Fit Width + Ajustar anchura + + + + A + A + + + + Fit image to ... + Ajustar imagen a... + + + + Rotate image to the left + Rotar imagen a la izquierda + + + + L + L + + + + Rotate image to the right + Rotar imagen a la derecha + + + + R + R + + + + Double page mode + Modo a doble página + + + + Switch to double page mode + Cambiar a modo de doble página + + + + D + D + + + + Go To + Ir a + + + + G + G + + + + Go to page ... + Ir a página... + + + + Options + Opciones + + + + C + C + + + + YACReader options + Opciones de YACReader + + + + Help + Ayuda + + + + Help, About YACReader + Ayuda, Sobre YACReader + + + + Magnifying glass + Lupa + + + + Switch Magnifying glass + Lupa On/Off + + + + Z + Z + + + + Set bookmark + Añadir marcador + + + + Set a bookmark on the current page + Añadir un marcador en la página actual + + + + Show bookmarks + Mostrar marcadores + + + + Show the bookmarks of the current comic + Mostrar los marcadores del cómic actual + + + + M + M + + + + Show keyboard shortcuts + Mostrar atajos de teclado + + + + Show Info + Mostrar información + + + + I + I + + + + Close + Cerrar + + + + Show Dictionary + Mostrar diccionario + + + + Always on top + Siempre visible + + + + Show full size + Mostrar a tamaño original + + + + &File + &Archivo + + + + Open Comic + Abrir cómic + + + + Comic files (*.cbr *.cbz *.rar *.zip *.tar *.arj) + Archivos de cómic (*.cbr *.cbz *.rar *.zip *.tar *.arj) + + + + Open folder + Abrir carpeta + + + + Image files (*.jpg) + Archivos de imagen (*.jpg) + + + + There is a new version avaliable + Hay una nueva versión disponible + + + + Do you want to download the new version? + ¿Desea descargar la nueva versión? + + + + Saving error log file.... + Guardando el archivo de log... + + + + There was a problem saving YACReader error log file. Please, check if you have enough permissions in the YACReader root folder. + Hubo un problema al guardar el archivo de log de YACReader. Por favor, comprueba si tienes suficientes permisos en el directorio raíz de YACReader. + + + + OptionsDialog + + + "Go to flow" size + Tamaño de "Go to flow" + + + + My comics path + Ruta a mis cómics + + + + Save + Guardar + + + + Cancel + Cancelar + + + + How to show pages in GoToFlow: + ¿Cómo deseas que se muestren las páginas en "Go To Flow": + + + + CoverFlow look + + + + + Stripe look + + + + + Overlapped Stripe look + Overlaped Stripe look + + + + + Page width stretch + Ajuste en anchura de la página + + + + Background color + Color de fondo + + + + Choose + Elegir + + + + Restart is needed + Es necesario reiniciar + + + + Comics directory + Directorio de cómics + + + + QPushButton + + Hello world! + Hola mundo! + + + + ShortcutsDialog + + + YACReader keyboard shortcuts + Atajos de teclado de YACReader + + + + Close + Cerrar + + + + Keyboard Shortcuts + Atajos de teclado + + + + Viewer + + + + Press 'O' to open comic. + Pulsa 'O' para abrir un fichero. + + + Show Info + Mostrar información + + + I + I + + + + Loading...please wait! + Cargando...espere, por favor! + + + diff --git a/YACReader/yacreader_fr.ts b/YACReader/yacreader_fr.ts new file mode 100644 index 00000000..fd42ce28 --- /dev/null +++ b/YACReader/yacreader_fr.ts @@ -0,0 +1,548 @@ + + + + + BookmarksDialog + + + Lastest Page + + + + + Close + + + + + Click on any image to go to the bookmark + + + + + + Loading... + + + + + Comic + + + No images found + + + + + There are not images on the selected folder + + + + + File error + + + + + File not found or not images in file + + + + + 7z not found + + + + + 7z wasn't found in your PATH. + + + + + 7z crashed + + + + + 7z crashed. + + + + + 7z reading + + + + + problem reading from 7z + + + + + 7z problem + + + + + Unknown error 7z + + + + + Configuration + + + Saving config file.... + + + + + There was a problem saving YACReader configuration. Please, check if you have enough permissions in the YACReader root folder. + + + + + GoToDialog + + + Page : + + + + + Go To + + + + + Cancel + + + + + + Total pages : + + + + + Go to... + + + + + GoToFlow + + + Page : + + + + + HelpAboutDialog + + + About + + + + + Help + + + + + MainWindowViewer + + + &Open + + + + + O + + + + + Open a comic + + + + + Open Folder + + + + + Ctrl+O + + + + + Open image folder + + + + + Save + + + + + + Save current page + + + + + Previous Comic + + + + + Open previous comic + + + + + Next Comic + + + + + Open next comic + + + + + &Previous + + + + + Go to previous page + + + + + &Next + + + + + Go to next page + + + + + Fit Width + + + + + A + + + + + Fit image to ... + + + + + Rotate image to the left + + + + + L + + + + + Rotate image to the right + + + + + R + + + + + Double page mode + + + + + Switch to double page mode + + + + + D + + + + + Go To + + + + + G + + + + + Go to page ... + + + + + Options + + + + + C + + + + + YACReader options + + + + + Help + + + + + Help, About YACReader + + + + + Magnifying glass + + + + + Switch Magnifying glass + + + + + Z + + + + + Set bookmark + + + + + Set a bookmark on the current page + + + + + Show bookmarks + + + + + Show the bookmarks of the current comic + + + + + M + + + + + Show keyboard shortcuts + + + + + Show Info + + + + + I + + + + + Close + + + + + Show Dictionary + + + + + Always on top + + + + + Show full size + + + + + &File + + + + + Open Comic + + + + + Comic files (*.cbr *.cbz *.rar *.zip *.tar *.arj) + + + + + Open folder + + + + + Image files (*.jpg) + + + + + There is a new version avaliable + + + + + Do you want to download the new version? + + + + + Saving error log file.... + + + + + There was a problem saving YACReader error log file. Please, check if you have enough permissions in the YACReader root folder. + + + + + OptionsDialog + + + "Go to flow" size + + + + + My comics path + + + + + Save + + + + + Cancel + + + + + How to show pages in GoToFlow: + + + + + CoverFlow look + + + + + Stripe look + + + + + Overlapped Stripe look + + + + + Page width stretch + + + + + Background color + + + + + Choose + + + + + Restart is needed + + + + + Comics directory + + + + + ShortcutsDialog + + + YACReader keyboard shortcuts + + + + + Close + + + + + Keyboard Shortcuts + + + + + Viewer + + + + Press 'O' to open comic. + + + + + Loading...please wait! + + + + diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro new file mode 100644 index 00000000..68e6c339 --- /dev/null +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -0,0 +1,54 @@ +###################################################################### +# Automatically generated by qmake (2.01a) dom 12. oct 20:47:48 2008 +###################################################################### + +TEMPLATE = app +TARGET = +DEPENDPATH += . +INCLUDEPATH += . +INCLUDEPATH += ../common +CONFIG += release +CONFIG -= flat +QT += sql + +# Input +HEADERS += comic_flow.h \ + create_library_dialog.h \ + library_creator.h \ + library_window.h \ + ../common/pictureflow.h \ + add_library_dialog.h \ + ../common/custom_widgets.h \ + rename_library_dialog.h \ + properties_dialog.h \ + options_dialog.h \ + export_library_dialog.h \ + import_library_dialog.h \ + package_manager.h \ + ../common/qnaturalsorting.h \ + data_base_management.h \ + bundle_creator.h +SOURCES += comic_flow.cpp \ + create_library_dialog.cpp \ + library_creator.cpp \ + library_window.cpp \ + main.cpp \ + ../common/pictureflow.cpp \ + add_library_dialog.cpp \ + ../common/custom_widgets.cpp \ + rename_library_dialog.cpp \ + properties_dialog.cpp \ + options_dialog.cpp \ + export_library_dialog.cpp \ + import_library_dialog.cpp \ + package_manager.cpp \ + ../common/qnaturalsorting.cpp \ + data_base_management.cpp \ + bundle_creator.cpp +RESOURCES += images.qrc files.qrc +RC_FILE = icon.rc + +TRANSLATIONS = yacreaderlibrary_es.ts + +Release:DESTDIR = ../release +Debug:DESTDIR = ../debug diff --git a/YACReaderLibrary/add_library_dialog.cpp b/YACReaderLibrary/add_library_dialog.cpp new file mode 100644 index 00000000..072b47f0 --- /dev/null +++ b/YACReaderLibrary/add_library_dialog.cpp @@ -0,0 +1,95 @@ +#include "add_library_dialog.h" + +#include +#include +#include + + + +AddLibraryDialog::AddLibraryDialog(QWidget * parent) +:QDialog(parent) +{ + setupUI(); +} + +void AddLibraryDialog::setupUI() +{ + textLabel = new QLabel(tr("Comics folder : ")); + path = new QLineEdit; + textLabel->setBuddy(path); + + nameLabel = new QLabel(tr("Library Name : ")); + nameEdit = new QLineEdit; + nameLabel->setBuddy(nameEdit); + + accept = new QPushButton(tr("Add")); + accept->setDisabled(true); + connect(accept,SIGNAL(clicked()),this,SLOT(add())); + + cancel = new QPushButton(tr("Cancel")); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + find = new QPushButton(QIcon(":/images/comicFolder.png"),""); + connect(find,SIGNAL(clicked()),this,SLOT(findPath())); + + QHBoxLayout *nameLayout = new QHBoxLayout; + + nameLayout->addWidget(nameLabel); + nameLayout->addWidget(nameEdit); + + QHBoxLayout *libraryLayout = new QHBoxLayout; + + libraryLayout->addWidget(textLabel); + libraryLayout->addWidget(path); + libraryLayout->addWidget(find); + libraryLayout->setStretchFactor(find,0); //TODO + + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(accept); + bottomLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(nameLayout); + mainLayout->addLayout(libraryLayout); + mainLayout->addStretch(); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout * imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(this); + QPixmap p(":/images/openLibrary.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setModal(true); + setWindowTitle(tr("Add an existing library")); +} + +void AddLibraryDialog::add() +{ + //accept->setEnabled(false); + emit(addLibrary(QDir::cleanPath(path->text()),nameEdit->text())); + close(); +} + +void AddLibraryDialog::findPath() +{ + QString s = QFileDialog::getExistingDirectory(0,"Comics directory","."); + if(!s.isEmpty()) + { + path->setText(s); + accept->setEnabled(true); + } +} + +void AddLibraryDialog::close() +{ + path->clear(); + nameEdit->clear(); + accept->setEnabled(false); + QDialog::close(); +} \ No newline at end of file diff --git a/YACReaderLibrary/add_library_dialog.h b/YACReaderLibrary/add_library_dialog.h new file mode 100644 index 00000000..f6274270 --- /dev/null +++ b/YACReaderLibrary/add_library_dialog.h @@ -0,0 +1,33 @@ +#ifndef __ADD_LIBRARY_DIALOG_H +#define __ADD_LIBRARY_DIALOG_H + +#include +#include +#include +#include +#include + + class AddLibraryDialog : public QDialog + { + Q_OBJECT + public: + AddLibraryDialog(QWidget * parent = 0); + private: + QLabel * nameLabel; + QLabel * textLabel; + QLineEdit * path; + QLineEdit * nameEdit; + QPushButton * find; + QPushButton * accept; + QPushButton * cancel; + void setupUI(); + public slots: + void add(); + void findPath(); + void close(); + signals: + void addLibrary(QString target, QString name); + }; + +#endif + diff --git a/YACReaderLibrary/comic_flow.cpp b/YACReaderLibrary/comic_flow.cpp new file mode 100644 index 00000000..f6e699d0 --- /dev/null +++ b/YACReaderLibrary/comic_flow.cpp @@ -0,0 +1,266 @@ +#include "comic_flow.h" +#include "qnaturalsorting.h" + +#include +#include +#include + + + + + +ComicFlow::ComicFlow(QWidget* parent,FlowType flowType) +:YACReaderFlow(parent,flowType) +{ + updateTimer = new QTimer; + connect(updateTimer, SIGNAL(timeout()), this, SLOT(updateImageData())); + + worker = new ImageLoader; + connect(this, SIGNAL(centerIndexChanged(int)), this, SLOT(preload())); + + setReflectionEffect(PlainReflection); +} + +ComicFlow::~ComicFlow() +{ + delete worker; + delete updateTimer; +} + +QString ComicFlow::getImagePath() const +{ + return imagePath; +} + +QStringList ComicFlow::getImageFiles() const +{ + return imageFiles; +} + +// get list of all files in a directory (will be filtered later) +// this is usually very fast so no need to put it in a separate thread +static QStringList findFiles(const QString& path = QString()) +{ + //list files; + QStringList files; + + QDir dir = QDir::current(); + if(!path.isEmpty()) + dir = QDir(path); + + dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks); + dir.setNameFilters(QStringList() << "*.jpg"); + //dir.setSorting(QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + QFileInfoList list = dir.entryInfoList(); + + qSort(list.begin(),list.end(),naturalSortLessThanCIFileInfo); + + for (int i = 0; i < list.size(); ++i) + { + QFileInfo fileInfo = list.at(i); + files.append(dir.absoluteFilePath(fileInfo.fileName())); + } + + //std::sort(files.begin(), files.end(), naturalSortLessThanCI); + + return files; +} + +// take only files which are readable (as images) +// also seems to be fast as it does a quick check only +static QStringList filterImages(const QStringList& files) +{ + QStringList imageFiles; + + QImageReader reader; + foreach(QString fname, files) + { + reader.setFileName(fname); + if(reader.canRead()) + imageFiles += fname; + } + + return imageFiles; +} + +void ComicFlow::setImagePath(const QString& path) +{ + clear(); + + imagePath = path; + imageFiles = findFiles(path); + imagesLoaded.clear(); + imagesLoaded.fill(false,imageFiles.size()); + numImagesLoaded = 0; + + imagesSetted.clear(); + imagesSetted.fill(false,imageFiles.size()); + + // populate with empty images + QImage img; //TODO remove + QString s; + for(int i = 0; i < (int)imageFiles.size(); i++) + { + addSlide(img); + s = imageFiles.at(i); + s.remove(s.size()-4,4); + if(QFileInfo(s+".r").exists()) + markSlide(i); + } + + setCenterIndex(0); + worker->reset(); + preload(); +} + +void ComicFlow::preload() +{ + if(numImagesLoaded < imagesLoaded.size()) + updateTimer->start(70); +} + +void ComicFlow::updateImageData() +{ + // can't do anything, wait for the next possibility + if(worker->busy()) + return; + + // set image of last one + int idx = worker->index(); + if( idx >= 0 && !worker->result().isNull()) + { + if(!imagesSetted[idx]) + { + setSlide(idx, worker->result()); + imagesSetted[idx] = true; + numImagesLoaded++; + } + + } + + // try to load only few images on the left and right side + // i.e. all visible ones plus some extra +#define COUNT 8 + int indexes[2*COUNT+1]; + int center = centerIndex(); + indexes[0] = center; + for(int j = 0; j < COUNT; j++) + { + indexes[j*2+1] = center+j+1; + indexes[j*2+2] = center-j-1; + } + for(int c = 0; c < 2*COUNT+1; c++) + { + int i = indexes[c]; + if((i >= 0) && (i < slideCount())) + if(!imagesLoaded[i])//slide(i).isNull()) + { + // schedule thumbnail generation + QString fname = imageFiles[i]; + imagesLoaded[i]=true; + + worker->generate(i, fname, slideSize()); + return; + } + } + + // no need to generate anything? stop polling... + updateTimer->stop(); +} + +void ComicFlow::keyPressEvent(QKeyEvent* event) +{ + PictureFlow::keyPressEvent(event); +} + +void ComicFlow::wheelEvent(QWheelEvent * event) +{ + if(event->delta()<0) + showNext(); + else + showPrevious(); + event->accept(); +} + +//----------------------------------------------------------------------------- +//ImageLoader +//----------------------------------------------------------------------------- +static QImage loadImage(const QString& fileName) +{ + QImage image; + bool result = image.load(fileName); + + if(!result) + return QImage(); + + return image; +} + +ImageLoader::ImageLoader(): +QThread(), restart(false), working(false), idx(-1) +{ +} + +ImageLoader::~ImageLoader() +{ + mutex.lock(); + condition.wakeOne(); + mutex.unlock(); + wait(); +} + +bool ImageLoader::busy() const +{ + return isRunning() ? working : false; +} + +void ImageLoader::generate(int index, const QString& fileName, QSize size) +{ + mutex.lock(); + this->idx = index; + this->fileName = fileName; + this->size = size; + this->img = QImage(); + mutex.unlock(); + + if (!isRunning()) + start(); + else + { + // already running, wake up whenever ready + restart = true; + condition.wakeOne(); + } +} + +void ImageLoader::run() +{ + for(;;) + { + // copy necessary data + mutex.lock(); + this->working = true; + QString fileName = this->fileName; + mutex.unlock(); + + QImage image = loadImage(fileName); + + // let everyone knows it is ready + mutex.lock(); + this->working = false; + this->img = image; + mutex.unlock(); + + // put to sleep + mutex.lock(); + if (!this->restart) + condition.wait(&mutex); + restart = false; + mutex.unlock(); + } +} + +QImage ImageLoader::result() +{ + return img; +} \ No newline at end of file diff --git a/YACReaderLibrary/comic_flow.h b/YACReaderLibrary/comic_flow.h new file mode 100644 index 00000000..ae9c5b9a --- /dev/null +++ b/YACReaderLibrary/comic_flow.h @@ -0,0 +1,76 @@ +#ifndef __COMICFLOW_H +#define __COMICFLOW_H + +#include "custom_widgets.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class ImageLoader; +class ComicFlow : public YACReaderFlow +{ + Q_OBJECT +public: + ComicFlow(QWidget* parent = 0,FlowType flowType = CoverFlowLike); + virtual ~ComicFlow(); + + //void render(); + QString getImagePath() const; + QStringList getImageFiles() const; + void setImagePath(const QString& path); + //bool eventFilter(QObject *target, QEvent *event); + void keyPressEvent(QKeyEvent* event); + +private slots: + void preload(); + void updateImageData(); + +private: + QString imagePath; + QStringList imageFiles; + QVector imagesLoaded; + QVector imagesSetted; + uint numImagesLoaded; + QTimer* updateTimer; + ImageLoader* worker; + virtual void wheelEvent(QWheelEvent * event); +}; + + +//----------------------------------------------------------------------------- +// Source code of ImageLoader class was modified from http://code.google.com/p/photoflow/ +//------------------------------------------------------------------------------ +class ImageLoader : public QThread +{ +public: + ImageLoader(); + ~ImageLoader(); + // returns FALSE if worker is still busy and can't take the task + bool busy() const; + void generate(int index, const QString& fileName, QSize size); + void reset(){idx = -1;}; + int index() const { return idx; }; + QImage result(); + +protected: + void run(); + +private: + QMutex mutex; + QWaitCondition condition; + + bool restart; + bool working; + int idx; + QString fileName; + QSize size; + QImage img; +}; + + +#endif diff --git a/YACReaderLibrary/create_library_dialog.cpp b/YACReaderLibrary/create_library_dialog.cpp new file mode 100644 index 00000000..d13e991c --- /dev/null +++ b/YACReaderLibrary/create_library_dialog.cpp @@ -0,0 +1,154 @@ +#include "create_library_dialog.h" + +#include +#include +#include + + + +CreateLibraryDialog::CreateLibraryDialog(QWidget * parent) +:QDialog(parent) +{ + setupUI(); +} + +void CreateLibraryDialog::setupUI() +{ + textLabel = new QLabel(tr("Comics folder : ")); + path = new QLineEdit; + textLabel->setBuddy(path); + + nameLabel = new QLabel(tr("Library Name : ")); + nameEdit = new QLineEdit; + nameLabel->setBuddy(nameEdit); + + accept = new QPushButton(tr("Create")); + accept->setDisabled(true); + connect(accept,SIGNAL(clicked()),this,SLOT(create())); + + cancel = new QPushButton(tr("Cancel")); + connect(cancel,SIGNAL(clicked()),this,SIGNAL(cancelCreate())); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + find = new QPushButton(QIcon(":/images/comicFolder.png"),""); + connect(find,SIGNAL(clicked()),this,SLOT(findPath())); + + QHBoxLayout *nameLayout = new QHBoxLayout; + + nameLayout->addWidget(nameLabel); + nameLayout->addWidget(nameEdit); + + QHBoxLayout *libraryLayout = new QHBoxLayout; + + libraryLayout->addWidget(textLabel); + libraryLayout->addWidget(path); + libraryLayout->addWidget(find); + libraryLayout->setStretchFactor(find,0); //TODO + + QHBoxLayout *middleLayout = new QHBoxLayout; + + processLabel = new QLabel("Procesing : "); + currentFileLabel = new QLabel(""); + middleLayout->addWidget(processLabel); + middleLayout->addWidget(currentFileLabel); + middleLayout->addStretch(); + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(accept); + bottomLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(nameLayout); + mainLayout->addLayout(libraryLayout); + mainLayout->addLayout(middleLayout); + mainLayout->addStretch(); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout * imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(this); + QPixmap p(":/images/new.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setModal(true); + setWindowTitle(tr("Create new library")); +} + +void CreateLibraryDialog::create() +{ + accept->setEnabled(false); + emit(createLibrary(QDir::cleanPath(path->text()),QDir::cleanPath(path->text())+"/.yacreaderlibrary",nameEdit->text())); +} + +void CreateLibraryDialog::findPath() +{ + QString s = QFileDialog::getExistingDirectory(0,"Comics directory","."); + if(!s.isEmpty()) + { + path->setText(s); + accept->setEnabled(true); + } +} + +void CreateLibraryDialog::showCurrentFile(QString file) +{ + currentFileLabel->setText(file); + currentFileLabel->update(); + this->update(); +} +void CreateLibraryDialog::close() +{ + path->clear(); + nameEdit->clear(); + currentFileLabel->setText(""); + accept->setEnabled(true); + QDialog::close(); +} +//----------------------------------------------------------------------------- +// UpdateLibraryDialog +//----------------------------------------------------------------------------- +UpdateLibraryDialog::UpdateLibraryDialog(QWidget * parent) +:QDialog(parent) +{ + QVBoxLayout * mainLayout = new QVBoxLayout; + mainLayout->addWidget(message = new QLabel(tr("Updating...."))); + mainLayout->addWidget(currentFileLabel = new QLabel("")); + + QHBoxLayout * bottom = new QHBoxLayout; + bottom->addStretch(); + bottom->addWidget(cancel = new QPushButton(tr("Cancel"))); + + connect(cancel,SIGNAL(clicked()),this,SIGNAL(cancelUpdate())); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + mainLayout->addStretch(); + mainLayout->addLayout(bottom); + + QHBoxLayout * imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(this); + QPixmap p(":/images/updateLibrary.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setModal(true); +} + +void UpdateLibraryDialog::showCurrentFile(QString file) +{ + currentFileLabel->setText(file); + currentFileLabel->update(); + this->update(); +} + +void UpdateLibraryDialog::close() +{ + currentFileLabel->setText(""); + QDialog::close(); +} diff --git a/YACReaderLibrary/create_library_dialog.h b/YACReaderLibrary/create_library_dialog.h new file mode 100644 index 00000000..424234f2 --- /dev/null +++ b/YACReaderLibrary/create_library_dialog.h @@ -0,0 +1,52 @@ +#ifndef __CREATE_LIBRARY_DIALOG_H +#define __CREATE_LIBRARY_DIALOG_H + +#include +#include +#include +#include +#include + + class CreateLibraryDialog : public QDialog + { + Q_OBJECT + public: + CreateLibraryDialog(QWidget * parent = 0); + private: + QLabel * nameLabel; + QLabel * textLabel; + QLabel * processLabel; + QLabel * currentFileLabel; + QLineEdit * path; + QLineEdit * nameEdit; + QPushButton * find; + QPushButton * accept; + QPushButton * cancel; + void setupUI(); + public slots: + void create(); + void findPath(); + void showCurrentFile(QString file); + void close(); + signals: + void createLibrary(QString source, QString target, QString name); + void cancelCreate(); + }; + + class UpdateLibraryDialog : public QDialog + { + Q_OBJECT + public: + UpdateLibraryDialog(QWidget * parent = 0); + private: + QLabel * message; + QLabel * currentFileLabel; + QPushButton * cancel; + public slots: + void showCurrentFile(QString file); + void close(); + signals: + void cancelUpdate(); + }; + +#endif diff --git a/YACReaderLibrary/data_base_management.cpp b/YACReaderLibrary/data_base_management.cpp new file mode 100644 index 00000000..98d0bb8f --- /dev/null +++ b/YACReaderLibrary/data_base_management.cpp @@ -0,0 +1,22 @@ +#include "data_base_management.h" + +#include + +DataBaseManagement::DataBaseManagement() + :QObject(),dataBasesList() +{ + +} + +bool DataBaseManagement::createDataBase(QString name, QString path) +{ + QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE"); + db.setDatabaseName(name + ".ydb"); + if (!db.open()) + qDebug() << db.lastError(); + else { + qDebug() << db.tables(); + db.close(); + } + return true; +} \ No newline at end of file diff --git a/YACReaderLibrary/data_base_management.h b/YACReaderLibrary/data_base_management.h new file mode 100644 index 00000000..52410b2b --- /dev/null +++ b/YACReaderLibrary/data_base_management.h @@ -0,0 +1,18 @@ +#ifndef __DATA_BASE_MANAGEMENT_H +#define __DATA_BASE_MANAGEMENT_H + +#include +#include + +class DataBaseManagement : public QObject +{ + Q_OBJECT +private: + QList dataBasesList; +public: + DataBaseManagement(); + bool createDataBase(QString name, QString path); + +}; + +#endif \ No newline at end of file diff --git a/YACReaderLibrary/export_library_dialog.cpp b/YACReaderLibrary/export_library_dialog.cpp new file mode 100644 index 00000000..7b127d04 --- /dev/null +++ b/YACReaderLibrary/export_library_dialog.cpp @@ -0,0 +1,100 @@ +#include "export_library_dialog.h" +#include +#include +#include +#include + +ExportLibraryDialog::ExportLibraryDialog(QWidget * parent) +:QDialog(parent),progressCount(0) +{ + textLabel = new QLabel(tr("Output folder : ")); + path = new QLineEdit; + textLabel->setBuddy(path); + + accept = new QPushButton(tr("Create")); + accept->setDisabled(true); + connect(accept,SIGNAL(clicked()),this,SLOT(exportLibrary())); + + cancel = new QPushButton(tr("Cancel")); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + connect(cancel,SIGNAL(clicked()),this,SIGNAL(rejected())); + + find = new QPushButton(QIcon(":/images/comicFolder.png"),""); + connect(find,SIGNAL(clicked()),this,SLOT(findPath())); + + QHBoxLayout *libraryLayout = new QHBoxLayout; + + libraryLayout->addWidget(textLabel); + libraryLayout->addWidget(path); + libraryLayout->addWidget(find); + libraryLayout->setStretchFactor(find,0); //TODO + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(accept); + bottomLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(libraryLayout); + mainLayout->addWidget(progress=new QLabel()); + mainLayout->addStretch(); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout * imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(this); + QPixmap p(":/images/exportLibrary.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setModal(true); + setWindowTitle(tr("Create covers package")); + t.setInterval(500); + connect(&t,SIGNAL(timeout()),this,SLOT(updateProgress())); +} + +void ExportLibraryDialog::exportLibrary() +{ + accept->setEnabled(false); + emit exportPath(QDir::cleanPath(path->text())); + t.start(); +} + +void ExportLibraryDialog::findPath() +{ + QString s = QFileDialog::getExistingDirectory(0,tr("Destination directory"),"."); + if(!s.isEmpty()) + { + path->setText(s); + accept->setEnabled(true); + } +} + +void ExportLibraryDialog::close() +{ + path->clear(); + accept->setEnabled(false); + t.stop(); + progressCount=0; + progress->setText(""); + QDialog::close(); +} + +void ExportLibraryDialog::run() +{ + +} + +void ExportLibraryDialog::updateProgress() +{ + if(progressCount == 0) + progress->setText(tr("Creating package .")); + else + progress->setText(progress->text()+" ."); + progressCount++; + if(progressCount == 15) + progressCount = 0; + +} diff --git a/YACReaderLibrary/export_library_dialog.h b/YACReaderLibrary/export_library_dialog.h new file mode 100644 index 00000000..538abed3 --- /dev/null +++ b/YACReaderLibrary/export_library_dialog.h @@ -0,0 +1,36 @@ +#ifndef EXPORT_LIBRARY_DIALOG_H +#define EXPORT_LIBRARY_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +class ExportLibraryDialog : public QDialog +{ + Q_OBJECT +public: + ExportLibraryDialog(QWidget * parent = 0); +public slots: + void exportLibrary(); + void findPath(); + void close(); + void updateProgress(); +private: + int progressCount; + QLabel * progress; + QLabel * textLabel; + QLineEdit * path; + QPushButton * find; + QPushButton * accept; + QPushButton * cancel; + void run(); + QTimer t; +signals: + void exportPath(QString); +}; + +#endif \ No newline at end of file diff --git a/YACReaderLibrary/files.qrc b/YACReaderLibrary/files.qrc new file mode 100644 index 00000000..42bce93f --- /dev/null +++ b/YACReaderLibrary/files.qrc @@ -0,0 +1,13 @@ + + + ../files/about.html + ../files/helpYACReaderLibrary.html + ./yacreaderlibrary_es.qm + + + + ../files/about_es_ES.html + ../files/helpYACReaderLibrary_es_ES.html + + + diff --git a/YACReaderLibrary/icon.ico b/YACReaderLibrary/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..b2232648330a45c5639a6c13790191bbf0dd1a9a GIT binary patch literal 99678 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 bSt|Z2dZ3~QDte%z2P%4?q6hw$df@*7sMu_a literal 0 HcmV?d00001 diff --git a/YACReaderLibrary/icon.rc b/YACReaderLibrary/icon.rc new file mode 100644 index 00000000..afe5a14f --- /dev/null +++ b/YACReaderLibrary/icon.rc @@ -0,0 +1,2 @@ +IDI_ICON1 ICON DISCARDABLE "icon.ico" +IDI_ICON2 ICON DISCARDABLE "icon2.ico" \ No newline at end of file diff --git a/YACReaderLibrary/icon2.ico b/YACReaderLibrary/icon2.ico new file mode 100644 index 0000000000000000000000000000000000000000..e87778428481086fb0b280ade6be220b1425b6a4 GIT binary patch literal 99678 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 N`wG}s;3->y{{v~kuC)LF literal 0 HcmV?d00001 diff --git a/YACReaderLibrary/images.qrc b/YACReaderLibrary/images.qrc new file mode 100644 index 00000000..b3f25463 --- /dev/null +++ b/YACReaderLibrary/images.qrc @@ -0,0 +1,33 @@ + + + ../images/deleteLibrary.png + ../images/folder.png + ../images/help.png + ../images/icon.png + ../images/new.png + ../images/openLibrary.png + ../images/removeLibrary.png + ../images/updateLibrary.png + ../images/setRoot.png + ../images/expand.png + ../images/colapse.png + ../images/comicFolder.png + ../images/notCover.png + ../images/edit.png + ../images/fit.png + ../images/properties.png + ../images/options.png + ../images/flow1.png + ../images/flow2.png + ../images/flow3.png + ../images/importLibrary.png + ../images/exportLibrary.png + ../images/open.png + ../images/coversPackage.png + ../images/setRead.png + ../images/setAllRead.png + ../images/setUnread.png + ../images/setAllUnread.png + ../images/showMarks.png + + diff --git a/YACReaderLibrary/import_library_dialog.cpp b/YACReaderLibrary/import_library_dialog.cpp new file mode 100644 index 00000000..cfc5563d --- /dev/null +++ b/YACReaderLibrary/import_library_dialog.cpp @@ -0,0 +1,164 @@ +#include "import_library_dialog.h" + +#include +#include +#include + + + +ImportLibraryDialog::ImportLibraryDialog(QWidget * parent) +:QDialog(parent),progressCount(0) +{ + setupUI(); +} + +void ImportLibraryDialog::setupUI() +{ + nameLabel = new QLabel(tr("Library Name : ")); + nameEdit = new QLineEdit; + nameLabel->setBuddy(nameEdit); + connect(nameEdit,SIGNAL(textChanged(QString)),this,SLOT(nameEntered())); + + textLabel = new QLabel(tr("Package location : ")); + path = new QLineEdit; + textLabel->setBuddy(path); + + destLabel = new QLabel(tr("Destination folder : ")); + destPath = new QLineEdit; + textLabel->setBuddy(destPath); + + accept = new QPushButton(tr("Unpack")); + accept->setDisabled(true); + connect(accept,SIGNAL(clicked()),this,SLOT(add())); + + cancel = new QPushButton(tr("Cancel")); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + //connect(cancel,SIGNAL(clicked()),this,SIGNAL(rejected())); + + find = new QPushButton(QIcon(":/images/coversPackage.png"),""); + connect(find,SIGNAL(clicked()),this,SLOT(findPath())); + + findDest = new QPushButton(QIcon(":/images/open.png"),""); + connect(findDest,SIGNAL(clicked()),this,SLOT(findDestination())); + + QHBoxLayout *nameLayout = new QHBoxLayout; + + nameLayout->addWidget(nameLabel); + nameLayout->addWidget(nameEdit); + + QHBoxLayout *libraryLayout = new QHBoxLayout; + + libraryLayout->addWidget(textLabel); + libraryLayout->addWidget(path); + libraryLayout->addWidget(find); + libraryLayout->setStretchFactor(find,0); //TODO + + QHBoxLayout *destLayout = new QHBoxLayout; + + destLayout->addWidget(destLabel); + destLayout->addWidget(destPath); + destLayout->addWidget(findDest); + destLayout->setStretchFactor(findDest,0); //TODO + + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(accept); + bottomLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(nameLayout); + mainLayout->addLayout(libraryLayout); + mainLayout->addLayout(destLayout); + mainLayout->addWidget(progress = new QLabel()); + mainLayout->addStretch(); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout * imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(this); + QPixmap p(":/images/importLibrary.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setModal(true); + setWindowTitle(tr("Extract a catalog")); + + t.setInterval(500); + t.stop(); + connect(&t,SIGNAL(timeout()),this,SLOT(updateProgress())); +} + +void ImportLibraryDialog::add() +{ + accept->setEnabled(false); + t.start(); + emit(unpackCLC(QDir::cleanPath(path->text()),QDir::cleanPath(destPath->text()),nameEdit->text())); +} + +void ImportLibraryDialog::findPath() +{ + QString s = QFileDialog::getOpenFileName(0,"Covers Package",".",tr("Compresed library covers (*.clc)")); + if(!s.isEmpty()) + { + path->setText(s); + if(!destPath->text().isEmpty() && !nameEdit->text().isEmpty()) + accept->setEnabled(true); + } +} + + +void ImportLibraryDialog::findDestination() +{ + QString s = QFileDialog::getExistingDirectory(0,"Folder",".",QFileDialog::ShowDirsOnly); + if(!s.isEmpty()) + { + destPath->setText(s); + if(!path->text().isEmpty() && !nameEdit->text().isEmpty()) + accept->setEnabled(true); + } +} + +void ImportLibraryDialog::nameEntered() +{ + if(!nameEdit->text().isEmpty()) + { + if(!path->text().isEmpty() && !destPath->text().isEmpty()) + accept->setEnabled(true); + } + else + accept->setEnabled(false); +} + +void ImportLibraryDialog::close() +{ + path->clear(); + destPath->clear(); + nameEdit->clear(); + accept->setEnabled(false); + if(t.isActive()) + { + t.stop(); + emit rejected(); + } + progress->setText(""); + QDialog::hide(); +} + +void ImportLibraryDialog::updateProgress() +{ + if(progressCount == 0) + progress->setText(tr("Importing package .")); + else + progress->setText(progress->text()+" ."); + progressCount++; + if(progressCount == 15) + progressCount = 0; +} + +void ImportLibraryDialog::closeEvent ( QCloseEvent * e ) +{ + close(); +} diff --git a/YACReaderLibrary/import_library_dialog.h b/YACReaderLibrary/import_library_dialog.h new file mode 100644 index 00000000..1d44a513 --- /dev/null +++ b/YACReaderLibrary/import_library_dialog.h @@ -0,0 +1,45 @@ +#ifndef IMPORT_LIBRARY_DIALOG_H +#define IMPORT_LIBRARY_DIALOG_H + + +#include +#include +#include +#include +#include +#include + + class ImportLibraryDialog : public QDialog + { + Q_OBJECT + public: + ImportLibraryDialog(QWidget * parent = 0); + private: + QLabel * nameLabel; + QLabel * textLabel; + QLabel * destLabel; + QLineEdit * path; + QLineEdit * destPath; + QLineEdit * nameEdit; + QPushButton * find; + QPushButton * findDest; + QPushButton * accept; + QPushButton * cancel; + QLabel * progress; + void setupUI(); + QTimer t; + int progressCount; + void closeEvent ( QCloseEvent * e ); + public slots: + void add(); + void findPath(); + void findDestination(); + void close(); + void updateProgress(); + void nameEntered(); + + signals: + void unpackCLC(QString clc,QString targetFolder, QString name); + }; + +#endif diff --git a/YACReaderLibrary/library_creator.cpp b/YACReaderLibrary/library_creator.cpp new file mode 100644 index 00000000..97a33a3d --- /dev/null +++ b/YACReaderLibrary/library_creator.cpp @@ -0,0 +1,323 @@ +#include "library_creator.h" +#include "custom_widgets.h" + +#include +#include + +//QMutex mutex; + + + +/*int numThreads = 0; +QWaitCondition waitCondition; +QMutex mutex; +*/ +LibraryCreator::LibraryCreator() +{ + _nameFilter << "*.cbr" << "*.cbz" << "*.rar" << "*.zip" << "*.tar"; +} + +void LibraryCreator::createLibrary(const QString &source, const QString &target) +{ + _source = source; + _target = target; + if(!QDir(target).exists()) + _mode = CREATOR; + else + _mode = UPDATER; +} + +void LibraryCreator::updateLibrary(const QString &source, const QString &target) +{ + _source = source; + _target = target; + _mode = UPDATER; +} + +void LibraryCreator::run() +{ + stopRunning = false; + if(_mode == CREATOR) + create(QDir(_source)); + else + update(QDir(_source),QDir(_target)); + emit(finished()); +} + +void LibraryCreator::stop() +{ + stopRunning = true; +} + +void LibraryCreator::create(QDir dir) +{ + dir.setNameFilters(_nameFilter); + dir.setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot); + QFileInfoList list = dir.entryInfoList(); + for (int i = 0; i < list.size(); ++i) + { + if(stopRunning) + return; + QFileInfo fileInfo = list.at(i); + if(fileInfo.isDir()) + { + create(QDir(fileInfo.absoluteFilePath())); + } + else + { + dir.mkpath(_target+(QDir::cleanPath(fileInfo.absolutePath()).remove(_source))); + emit(coverExtracted(QDir::cleanPath(fileInfo.absoluteFilePath()).remove(_source))); + + ThumbnailCreator tc(QDir::cleanPath(fileInfo.absoluteFilePath()),_target+(QDir::cleanPath(fileInfo.absoluteFilePath()).remove(_source))+".jpg"); + tc.create(); + } + } +} + +void LibraryCreator::update(QDir dirS,QDir dirD) +{ + dirS.setNameFilters(_nameFilter); + dirS.setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot); + dirS.setSorting(QDir::Name|QDir::IgnoreCase|QDir::LocaleAware|QDir::DirsFirst); + QFileInfoList listS = dirS.entryInfoList(); + + dirD.setNameFilters(QStringList()<<"*.jpg"); + dirD.setFilter(QDir::AllDirs|QDir::Files|QDir::NoDotAndDotDot); + dirD.setSorting(QDir::Name|QDir::IgnoreCase|QDir::LocaleAware|QDir::DirsFirst); + QFileInfoList listD = dirD.entryInfoList(); + + int lenghtS = listS.size(); + int lenghtD = listD.size(); + int maxLenght = qMax(lenghtS,lenghtD); + + bool updated; + int i,j; + for (i=0,j=0; (i < lenghtS)||(j < lenghtD);) + { + if(stopRunning) + return; + updated = false; + if(i>=lenghtS) //finished source files/dirs + { + //delete listD //from j + for(;j=lenghtD) //finished library files/dirs + { + //create listS //from i + for(;i 0) //delete thumbnail + { + dirD.remove(fileInfoD.absoluteFilePath()); + QString tick = fileInfoD.absoluteFilePath(); + dirD.remove(tick.remove(tick.size()-3,3)); + dirD.remove(tick+"r"); + j++; + } + else //same file + { + if(fileInfoS.isFile() && fileInfoD.isFile()) + { + if(fileInfoS.lastModified()>fileInfoD.lastModified()) + { + dirD.mkpath(_target+(QDir::cleanPath(fileInfoS.absolutePath()).remove(_source))); + emit(coverExtracted(QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source))); + ThumbnailCreator tc(QDir::cleanPath(fileInfoS.absoluteFilePath()),_target+(QDir::cleanPath(fileInfoS.absoluteFilePath()).remove(_source))+".jpg"); + tc.create(); + } + } + i++;j++; + } + } + } + } + } +} +ThumbnailCreator::ThumbnailCreator(QString fileSource, QString target="") +:_fileSource(fileSource),_target(target),_numPages(0) +{ +} + +void ThumbnailCreator::create() +{ + _7z = new QProcess(); + QStringList attributes; + attributes << "l" << "-ssc-" << "-r" << _fileSource << "*.jpg" << "*.jpeg" << "*.png" << "*.gif" << "*.tiff" << "*.tif" << "*.bmp"; + //connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(loadFirstImage(void))); + connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SIGNAL(openingError(QProcess::ProcessError))); + _7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes); + _7z->waitForFinished(60000); + + QRegExp rx("[0-9]{4}-[0-9]{2}-[0-9]{2}[ ]+[0-9]{2}:[0-9]{2}:[0-9]{2}[ ]+.{5}[ ]+([0-9]+)[ ]+([0-9]+)[ ]+(.+)"); + + QString ba = QString::fromUtf8(_7z->readAllStandardOutput()); + QList lines = ba.split('\n'); + QString line; + _currentName = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"; //TODO + QString name; + foreach(line,lines) + { + if(rx.indexIn(line)!=-1) + { + name = rx.cap(3).trimmed(); + if(0 > QString::localeAwareCompare(name,_currentName)) + _currentName = name; + _numPages++; + } + } + delete _7z; + attributes.clear(); + _currentName = QDir::fromNativeSeparators(_currentName).split('/').last(); //separator fixed. +#ifdef Q_WS_WIN + attributes << "e" << "-so" << "-r" << _fileSource << QString(_currentName.toLocal8Bit().constData()); //TODO platform dependency?? OEM 437 +#else + attributes << "e" << "-so" << "-r" << _fileSource << _currentName; //TODO platform dependency?? OEM 437 +#endif + _7z = new QProcess(); + connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SIGNAL(openingError(QProcess::ProcessError))); + //connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SLOT(writeThumbnail(void))); + _7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes); + _7z->waitForFinished(60000); + + QByteArray image = _7z->readAllStandardOutput(); + QString error = _7z->readAllStandardError(); + QImage p; + if(_target=="") + { + if(!_cover.loadFromData(image)) + _cover.load(":/images/notCover.png"); + } + else + { + if(p.loadFromData(image)) + { + //TODO calculate aspect ratio + QImage scaled; + if(p.width()>p.height()) //landscape?? + scaled = p.scaledToWidth(640,Qt::SmoothTransformation); + else + scaled = p.scaledToWidth(480,Qt::SmoothTransformation); + scaled.save(_target,0,75); + } + else + { + p.load(":/images/notCover.png"); + p.save(_target); + //TODO save a default image. + } + } + delete _7z; +} + +/*void ThumbnailCreator::openingError(QProcess::ProcessError error) +{ + //TODO : move to the gui thread + switch(error) + { + case QProcess::FailedToStart: + QMessageBox::critical(NULL,tr("7z not found"),tr("7z wasn't found in your PATH.")); + break; + case QProcess::Crashed: + QMessageBox::critical(NULL,tr("7z crashed"),tr("7z crashed.")); + break; + case QProcess::ReadError: + QMessageBox::critical(NULL,tr("7z reading"),tr("problem reading from 7z")); + break; + case QProcess::UnknownError: + QMessageBox::critical(NULL,tr("7z problem"),tr("Unknown error 7z")); + break; + default: + //TODO + break; + } +}*/ diff --git a/YACReaderLibrary/library_creator.h b/YACReaderLibrary/library_creator.h new file mode 100644 index 00000000..5a970adb --- /dev/null +++ b/YACReaderLibrary/library_creator.h @@ -0,0 +1,64 @@ +#ifndef __LIBRARY_CREATOR_H +#define __LIBRARY_CREATOR_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + class LibraryCreator : public QThread + { + Q_OBJECT + public: + LibraryCreator(); + void createLibrary(const QString & source, const QString & target); + void updateLibrary(const QString & source, const QString & target); + void stop(); + private: + enum Mode {CREATOR,UPDATER}; + enum Mode _mode; + QString _source; + QString _target; + QStringList _nameFilter; + //recursive method + void create(QDir currentDirectory); + void update(QDir currentDirectory,QDir libraryCurrentDirectory); + void run(); + bool stopRunning; + signals: + void finished(); + void coverExtracted(QString); + void folderUpdated(QString); + }; + + class ThumbnailCreator : public QObject + { + Q_OBJECT + + public: + ThumbnailCreator(QString fileSource, QString target); + private: + QProcess * _7z; + QString _fileSource; + QString _target; + QString _currentName; + int _numPages; + QPixmap _cover; + + public slots: + void create(); + int getNumPages(){return _numPages;}; + QPixmap getCover(){return _cover;}; + signals: + void openingError(QProcess::ProcessError error); + + }; + +#endif diff --git a/YACReaderLibrary/library_window.cpp b/YACReaderLibrary/library_window.cpp new file mode 100644 index 00000000..5efbfc70 --- /dev/null +++ b/YACReaderLibrary/library_window.cpp @@ -0,0 +1,1075 @@ +#include "library_window.h" +#include "custom_widgets.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// + +LibraryWindow::LibraryWindow() +:QMainWindow(),skip(0),fullscreen(false),fetching(false) +{ + setupUI(); + loadLibraries(); +} + +void LibraryWindow::setupUI() +{ + createLibraryDialog = new CreateLibraryDialog(this); + updateLibraryDialog = new UpdateLibraryDialog(this); + renameLibraryDialog = new RenameLibraryDialog(this); + propertiesDialog = new PropertiesDialog(this); + exportLibraryDialog = new ExportLibraryDialog(this); + importLibraryDialog = new ImportLibraryDialog(this); + + libraryCreator = new LibraryCreator(); + packageManager = new PackageManager(); + + addLibraryDialog = new AddLibraryDialog(this); + + QSplitter * sVertical = new QSplitter(Qt::Vertical); + QSplitter * sHorizontal = new QSplitter(Qt::Horizontal); + //TODO: flowType is a global variable + optionsDialog = new OptionsDialog(this); + optionsDialog->restoreOptions(); + comicFlow = new ComicFlow(0,flowType); + + comicFlow->setFocusPolicy(Qt::StrongFocus); + comicFlow->setShowMarks(true); + QMatrix m; + m.rotate(-90); + m.scale(-1,1); + comicFlow->setMarkImage(QImage(":/images/setRead.png").transformed(m,Qt::SmoothTransformation)); + int heightDesktopResolution = QApplication::desktop()->screenGeometry().height(); + int height,width; + height = heightDesktopResolution*0.39; + width = height*0.65; + slideSizeW = QSize(width,height); + height = heightDesktopResolution*0.55; + width = height*0.70; + slideSizeF = QSize(width,height); + comicFlow->setSlideSize(slideSizeW); + setFocusProxy(comicFlow); + + comicView = new QListView; + foldersView = new QTreeView; + + + sVertical->addWidget(comicFlow); + sVertical->addWidget(comicView); + /*sVertical->setStretchFactor(0,1); + sVertical->setStretchFactor(1,0); + */ + + left = new QWidget; + QVBoxLayout * l = new QVBoxLayout; + selectedLibrary = new QComboBox; + l->setContentsMargins(2,2,0,0); + l->addWidget(new QLabel(tr("Select a library:"))); + l->addWidget(selectedLibrary); + treeActions = new QToolBar(left); + treeActions->setIconSize(QSize(16,16)); + l->addWidget(treeActions); + l->addWidget(foldersView); + + QVBoxLayout * searchLayout = new QVBoxLayout; + + QHBoxLayout * filter = new QHBoxLayout; + filter->addWidget(foldersFilter = new QLineEdit()); + previousFilter = ""; + filter->addWidget(clearFoldersFilter = new QPushButton(tr("Clear"))); + + searchLayout->addWidget(new QLabel(tr("Search folders/comics"),this)); + + searchLayout->addLayout(filter); + searchLayout->addWidget(includeComicsCheckBox = new QCheckBox(tr("Include files (slower)"),this)); + + l->addLayout(searchLayout); + l->setSpacing(1); + left->setLayout(l); + + sHorizontal->addWidget(left); + sHorizontal->addWidget(sVertical); + sHorizontal->setStretchFactor(0,0); + sHorizontal->setStretchFactor(1,1); + setCentralWidget(sHorizontal); + + //dirmodels + dm = new QFileSystemModel(); + dm->setFilter(QDir::Dirs|QDir::NoDotAndDotDot); + dm->sort(0);//,QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + dmCV = new QFileSystemModel(); + dmCV->setNameFilters(QStringList() << "*jpg"); + dmCV->setFilter(QDir::Files|QDir::CaseSensitive|QDir::NoDotAndDotDot);//,QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + dmCV->setNameFilterDisables(true); + + dm->setReadOnly(true); + //dm->setLazyChildCount(false); + dmCV->setReadOnly(true); + + dm->setIconProvider(&fip); + dmCV->setIconProvider(&fip); + //dmCV->sort(1); + + proxyFilter = new YACReaderTreeSearch(); + proxyFilter->setSourceModel(dm); + proxyFilter->setFilterRole(Qt::DisplayRole); + + /*proxySort = new YACReaderSortComics(); + proxySort->setSourceModel(dmCV); + proxySort->setFilterRole(Qt::DisplayRole);*/ + + //views + foldersView->header()->hideSection(1); + foldersView->header()->hideSection(2); + foldersView->header()->hideSection(3); + foldersView->header()->adjustSize(); + foldersView->header()->hide(); + foldersView->setAnimated(true); + foldersView->setContextMenuPolicy(Qt::ActionsContextMenu); + foldersView->setContextMenuPolicy(Qt::ActionsContextMenu); + + comicView->setAlternatingRowColors(true); + comicView->setItemDelegate(new YACReaderComicViewDelegate()); + comicView->setContextMenuPolicy(Qt::ActionsContextMenu); + + had = new HelpAboutDialog(this); //TODO load data. + QString sufix = QLocale::system().name(); + if(QFile(":/files/about_"+sufix+".html").exists()) + had->loadAboutInformation(":/files/about_"+sufix+".html"); + else + had->loadAboutInformation(":/files/about.html"); + + if(QFile(":/files/helpYACReaderLibrary_"+sufix+".html").exists()) + had->loadHelp(":/files/helpYACReaderLibrary_"+sufix+".html"); + else + had->loadHelp(":/files/helpYACReaderLibrary.html"); + + fullScreenToolTip = new QLabel(this); + fullScreenToolTip->setText(tr(" press 'F' to close fullscreen mode ")); + fullScreenToolTip->setPalette(QPalette(QColor(0,0,0))); + fullScreenToolTip->setFont(QFont("courier new",15,234)); + fullScreenToolTip->setAutoFillBackground(true); + fullScreenToolTip->hide(); + fullScreenToolTip->adjustSize(); + + createActions(); + createToolBars(); + createMenus(); + createConnections(); + + setFoldersFilter(""); + + comicFlow->setFocus(Qt::OtherFocusReason); + + setWindowTitle(tr("YACReader Library")); +} + +void LibraryWindow::createActions() +{ + createLibraryAction = new QAction(this); + createLibraryAction->setToolTip(tr("Create a new library")); + createLibraryAction->setShortcut(Qt::Key_C); + createLibraryAction->setIcon(QIcon(":/images/new.png")); + + openLibraryAction = new QAction(this); + openLibraryAction->setToolTip(tr("Open an existing library")); + openLibraryAction->setShortcut(Qt::Key_O); + openLibraryAction->setIcon(QIcon(":/images/openLibrary.png")); + + exportLibraryAction = new QAction(this); + exportLibraryAction->setToolTip(tr("Pack the covers of the selected library")); + exportLibraryAction->setIcon(QIcon(":/images/exportLibrary.png")); + + importLibraryAction = new QAction(this); + importLibraryAction->setToolTip(tr("Unpack a catalog")); + importLibraryAction->setIcon(QIcon(":/images/importLibrary.png")); + + updateLibraryAction = new QAction(this); + updateLibraryAction->setToolTip(tr("Update current library")); + updateLibraryAction->setShortcut(Qt::Key_U); + updateLibraryAction->setIcon(QIcon(":/images/updateLibrary.png")); + + renameLibraryAction = new QAction(this); + renameLibraryAction->setToolTip(tr("Rename current library")); + renameLibraryAction->setShortcut(Qt::Key_R); + renameLibraryAction->setIcon(QIcon(":/images/edit.png")); + + deleteLibraryAction = new QAction(this); + deleteLibraryAction->setToolTip(tr("Delete current library from disk")); + deleteLibraryAction->setIcon(QIcon(":/images/deleteLibrary.png")); + + removeLibraryAction = new QAction(this); + removeLibraryAction->setToolTip(tr("Remove current library from your collection")); + removeLibraryAction->setIcon(QIcon(":/images/removeLibrary.png")); + + openComicAction = new QAction(this); + openComicAction->setToolTip(tr("Open current comic on YACReader")); + openComicAction->setShortcut(Qt::Key_Return); + openComicAction->setIcon(QIcon(":/images/icon.png")); + + setAsReadAction = new QAction(tr("Set as read"),this); + setAsReadAction->setToolTip(tr("Set comic as read")); + setAsReadAction->setIcon(QIcon(":/images/setRead.png")); + + setAsNonReadAction = new QAction(tr("Set as unread"),this); + setAsNonReadAction->setToolTip(tr("Set comic as unread")); + setAsNonReadAction->setIcon(QIcon(":/images/setUnread.png")); + + setAllAsReadAction = new QAction(tr("Set all as read"),this); + setAllAsReadAction->setToolTip(tr("Set all comics as read")); + setAllAsReadAction->setIcon(QIcon(":/images/setAllRead.png")); + + setAllAsNonReadAction = new QAction(tr("Set all as unread"),this); + setAllAsNonReadAction->setToolTip(tr("Set all comics as unread")); + setAllAsNonReadAction->setIcon(QIcon(":/images/setAllUnread.png")); + + showHideMarksAction = new QAction(tr("Show/Hide marks"),this); + showHideMarksAction->setToolTip(tr("Show or hide readed marks")); + showHideMarksAction->setShortcut(Qt::Key_M); + showHideMarksAction->setCheckable(true); + showHideMarksAction->setIcon(QIcon(":/images/showMarks.png")); + showHideMarksAction->setChecked(true); + + + showPropertiesAction = new QAction(this); + showPropertiesAction->setToolTip(tr("Show properties of current comic")); + showPropertiesAction->setShortcut(Qt::Key_P); + showPropertiesAction->setIcon(QIcon(":/images/properties.png")); + + toggleFullScreenAction = new QAction(this); + toggleFullScreenAction->setToolTip(tr("Fullscreen mode on/off (F)")); + toggleFullScreenAction->setShortcut(Qt::Key_F); + toggleFullScreenAction->setIcon(QIcon(":/images/fit.png")); + + helpAboutAction = new QAction(this); + helpAboutAction->setToolTip(tr("Help, About YACReader")); + helpAboutAction->setShortcut(Qt::Key_F1); + helpAboutAction->setIcon(QIcon(":/images/help.png")); + + setRootIndexAction = new QAction(this); + setRootIndexAction->setToolTip(tr("Select root node")); + setRootIndexAction->setIcon(QIcon(":/images/setRoot.png")); + + expandAllNodesAction = new QAction(this); + expandAllNodesAction->setToolTip(tr("Expand all nodes")); + expandAllNodesAction->setIcon(QIcon(":/images/expand.png")); + + colapseAllNodesAction = new QAction(this); + colapseAllNodesAction->setToolTip(tr("Colapse all nodes")); + colapseAllNodesAction->setIcon(QIcon(":/images/colapse.png")); + + optionsAction = new QAction(this); + optionsAction->setToolTip(tr("Show options dialog")); + optionsAction->setIcon(QIcon(":/images/options.png")); + + //disable actions + updateLibraryAction->setEnabled(false); + renameLibraryAction->setEnabled(false); + deleteLibraryAction->setEnabled(false); + removeLibraryAction->setEnabled(false); + openComicAction->setEnabled(false); + showPropertiesAction->setEnabled(false); + setAsReadAction->setEnabled(false); + setAsNonReadAction->setEnabled(false); + setAllAsReadAction->setEnabled(false); + setAllAsNonReadAction->setEnabled(false); + + openContainingFolderAction = new QAction(this); + openContainingFolderAction->setText(tr("Open folder...")); + openContainingFolderAction->setIcon(QIcon(":/images/open.png")); + + openContainingFolderComicAction = new QAction(this); + openContainingFolderComicAction->setText(tr("Open containing folder...")); + openContainingFolderComicAction->setIcon(QIcon(":/images/open.png")); +} + +void LibraryWindow::createToolBars() +{ + libraryToolBar = addToolBar(tr("Library")); + libraryToolBar->setIconSize(QSize(32,32)); //TODO make icon size dynamic + libraryToolBar->addAction(createLibraryAction); + libraryToolBar->addAction(openLibraryAction); + libraryToolBar->addAction(exportLibraryAction); + libraryToolBar->addAction(importLibraryAction); + + libraryToolBar->addSeparator(); + libraryToolBar->addAction(updateLibraryAction); + libraryToolBar->addAction(renameLibraryAction); + libraryToolBar->addAction(removeLibraryAction); + libraryToolBar->addAction(deleteLibraryAction); + + libraryToolBar->addSeparator(); + libraryToolBar->addAction(openComicAction); + libraryToolBar->addAction(showPropertiesAction); + + QToolButton * tb = new QToolButton(); + tb->addAction(setAsReadAction); + tb->addAction(setAllAsReadAction); + tb->setPopupMode(QToolButton::MenuButtonPopup); + tb->setDefaultAction(setAsReadAction); + + QToolButton * tb2 = new QToolButton(); + tb2->addAction(setAsNonReadAction); + tb2->addAction(setAllAsNonReadAction); + tb2->setPopupMode(QToolButton::MenuButtonPopup); + tb2->setDefaultAction(setAsNonReadAction); + + libraryToolBar->addWidget(tb); + libraryToolBar->addWidget(tb2); + + libraryToolBar->addAction(showHideMarksAction); + + libraryToolBar->addSeparator(); + libraryToolBar->addAction(toggleFullScreenAction); + + libraryToolBar->addWidget(new QToolBarStretch()); + libraryToolBar->addAction(optionsAction); + libraryToolBar->addAction(helpAboutAction); + + + libraryToolBar->setMovable(false); + + treeActions->addAction(setRootIndexAction); + treeActions->addAction(expandAllNodesAction); + treeActions->addAction(colapseAllNodesAction); + + comicFlow->addAction(toggleFullScreenAction); + comicFlow->addAction(openComicAction); +} + +void LibraryWindow::createMenus() +{ + comicView->addAction(openContainingFolderComicAction); + foldersView->addAction(openContainingFolderAction); +} + +void LibraryWindow::createConnections() +{ + //libraryCreator connections + connect(createLibraryDialog,SIGNAL(createLibrary(QString,QString,QString)),this,SLOT(create(QString,QString,QString))); + connect(libraryCreator,SIGNAL(coverExtracted(QString)),createLibraryDialog,SLOT(showCurrentFile(QString))); + connect(libraryCreator,SIGNAL(finished()),createLibraryDialog,SLOT(close())); + connect(libraryCreator,SIGNAL(coverExtracted(QString)),updateLibraryDialog,SLOT(showCurrentFile(QString))); + connect(libraryCreator,SIGNAL(finished()),updateLibraryDialog,SLOT(close())); + connect(libraryCreator,SIGNAL(finished()),this,SLOT(openLastCreated())); + + //packageManager connections + connect(exportLibraryDialog,SIGNAL(exportPath(QString)),this,SLOT(exportLibrary(QString))); + connect(exportLibraryDialog,SIGNAL(rejected()),packageManager,SLOT(cancel())); + connect(packageManager,SIGNAL(exported()),exportLibraryDialog,SLOT(close())); + connect(importLibraryDialog,SIGNAL(unpackCLC(QString,QString,QString)),this,SLOT(importLibrary(QString,QString,QString))); + connect(importLibraryDialog,SIGNAL(rejected()),packageManager,SLOT(cancel())); + connect(importLibraryDialog,SIGNAL(rejected()),this,SLOT(deleteCurrentLibrary())); + connect(packageManager,SIGNAL(imported()),importLibraryDialog,SLOT(hide())); + connect(packageManager,SIGNAL(imported()),this,SLOT(openLastCreated())); + + + //create and update dialogs + connect(createLibraryDialog,SIGNAL(cancelCreate()),this,SLOT(cancelCreating())); + connect(updateLibraryDialog,SIGNAL(cancelUpdate()),this,SLOT(stopLibraryCreator())); + + //open existing library from dialog. + connect(addLibraryDialog,SIGNAL(addLibrary(QString,QString)),this,SLOT(openLibrary(QString,QString))); + + //load library when selected library changes + connect(selectedLibrary,SIGNAL(currentIndexChanged(QString)),this,SLOT(loadLibrary(QString))); + + //rename library dialog + connect(renameLibraryDialog,SIGNAL(renameLibrary(QString)),this,SLOT(rename(QString))); + + //navigations between view modes (tree,list and flow) + connect(foldersView, SIGNAL(pressed(QModelIndex)), this, SLOT(loadCovers(QModelIndex))); + connect(comicView, SIGNAL(pressed(QModelIndex)), this, SLOT(centerComicFlow(QModelIndex))); + connect(comicFlow, SIGNAL(centerIndexChanged(int)), this, SLOT(updateComicView(int))); + + //actions + connect(createLibraryAction,SIGNAL(triggered()),this,SLOT(createLibrary())); + connect(exportLibraryAction,SIGNAL(triggered()),exportLibraryDialog,SLOT(show())); + connect(importLibraryAction,SIGNAL(triggered()),importLibraryDialog,SLOT(show())); + + connect(openLibraryAction,SIGNAL(triggered()),this,SLOT(showAddLibrary())); + connect(showPropertiesAction,SIGNAL(triggered()),this,SLOT(showProperties())); + connect(setAsReadAction,SIGNAL(triggered()),this,SLOT(setCurrentComicReaded())); + connect(setAsNonReadAction,SIGNAL(triggered()),this,SLOT(setCurrentComicUnreaded())); + connect(setAllAsReadAction,SIGNAL(triggered()),this,SLOT(setComicsReaded())); + connect(setAllAsNonReadAction,SIGNAL(triggered()),this,SLOT(setComicsUnreaded())); + + connect(showHideMarksAction,SIGNAL(toggled(bool)),comicFlow,SLOT(setShowMarks(bool))); + + + connect(updateLibraryAction,SIGNAL(triggered()),this,SLOT(updateLibrary())); + connect(renameLibraryAction,SIGNAL(triggered()),this,SLOT(renameLibrary())); + connect(deleteLibraryAction,SIGNAL(triggered()),this,SLOT(deleteLibrary())); + connect(removeLibraryAction,SIGNAL(triggered()),this,SLOT(removeLibrary())); + connect(openComicAction,SIGNAL(triggered()),this,SLOT(openComic())); + connect(helpAboutAction,SIGNAL(triggered()),had,SLOT(show())); + connect(setRootIndexAction,SIGNAL(triggered()),this,SLOT(setRootIndex())); + connect(expandAllNodesAction,SIGNAL(triggered()),foldersView,SLOT(expandAll())); + connect(colapseAllNodesAction,SIGNAL(triggered()),foldersView,SLOT(collapseAll())); + connect(toggleFullScreenAction,SIGNAL(triggered()),this,SLOT(toggleFullScreen())); + connect(optionsAction, SIGNAL(triggered()),optionsDialog,SLOT(show())); + connect(optionsDialog, SIGNAL(optionsChanged()),this,SLOT(reloadOptions())); + //ComicFlow + connect(comicFlow,SIGNAL(selected(unsigned int)),this,SLOT(openComic())); + connect(comicView,SIGNAL(doubleClicked(QModelIndex)),this,SLOT(openComic())); + //Folders filter + connect(clearFoldersFilter,SIGNAL(clicked()),foldersFilter,SLOT(clear())); + connect(foldersFilter,SIGNAL(textChanged(QString)),this,SLOT(setFoldersFilter(QString))); + connect(includeComicsCheckBox,SIGNAL(stateChanged(int)),this,SLOT(searchInFiles(int))); + + //ContextMenus + connect(openContainingFolderComicAction,SIGNAL(triggered()),this,SLOT(openContainingFolderComic())); + connect(openContainingFolderAction,SIGNAL(triggered()),this,SLOT(openContainingFolder())); + + //connect(dm,SIGNAL(directoryLoaded(QString)),foldersView,SLOT(expandAll())); + connect(dm,SIGNAL(directoryLoaded(QString)),this,SLOT(updateFoldersView(QString))); +} + +void LibraryWindow::loadLibrary(const QString & name) +{ + if(libraries.size()>0) + { + QString path=libraries.value(name)+"/.yacreaderlibrary"; + QDir d; //TODO change this by static methods (utils class?? with delTree for example) + if(d.exists(path)) + { + //renew dirmodels (because QFileSystemModel is.......crap ;) ) + QFileSystemModel * oldDm = dm; + QFileSystemModel * oldDmCV = dmCV; + YACReaderTreeSearch * oldTreeSearch = proxyFilter; + //YACReaderSortComics * oldSortProxy = proxySort; + + dm = new QFileSystemModel(); + dm->setFilter(QDir::Dirs|QDir::NoDotAndDotDot); + dm->sort(0);//,QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + dmCV = new QFileSystemModel(); + dmCV->setNameFilters(QStringList() << "*jpg"); + dmCV->setFilter(QDir::Files|QDir::CaseSensitive|QDir::NoDotAndDotDot);//,QDir::Name|QDir::IgnoreCase|QDir::LocaleAware); + dmCV->setNameFilterDisables(true); + + dm->setReadOnly(true); + //dm->setLazyChildCount(false); + dmCV->setReadOnly(true); + + dm->setIconProvider(&fip); + dmCV->setIconProvider(&fip); + //dmCV->sort(1); + + proxyFilter = new YACReaderTreeSearch(); + proxyFilter->setSourceModel(dm); + proxyFilter->setFilterRole(Qt::DisplayRole); + + /*proxySort = new YACReaderSortComics(); + proxySort->setSourceModel(dmCV); + proxySort->setFilterRole(Qt::DisplayRole);*/ + + connect(dm,SIGNAL(directoryLoaded(QString)),this,SLOT(updateFoldersView(QString))); + + includeComicsCheckBox->setCheckState(Qt::Unchecked); + + if(oldDm !=0) + delete oldDm; + if(oldDmCV !=0) + delete oldDmCV; + if(oldTreeSearch !=0) + delete oldTreeSearch; + /*if(oldSortProxy !=0) + delete oldSortProxy;*/ + //end renew dirmodels + //TODO review this code, some sentences could be not necessary + foldersView->setModel(proxyFilter); + comicView->setModel(dmCV); + _rootIndex = dm->setRootPath(path); + i=0; + + comicView->setRootIndex(dmCV->setRootPath(path)); + foldersView->setRootIndex(proxyFilter->mapFromSource(_rootIndex));//dm->setRootPath(path))); + + foldersView->header()->hideSection(1); + foldersView->header()->hideSection(2); + foldersView->header()->hideSection(3); + foldersView->header()->adjustSize(); + foldersView->header()->hide(); + + loadCovers(proxyFilter->mapFromSource(dm->index(path))); + //foldersView->expandAll(); + + } + else + { + comicView->setModel(NULL); + foldersView->setModel(NULL); + comicFlow->clear(); + } + d.setCurrent(libraries.value(name)); + d.setFilter(QDir::AllDirs | QDir::Files | QDir::Hidden | QDir::NoSymLinks | QDir::NoDotAndDotDot); + if(d.count()<=1) + { + //QMessageBox::critical(NULL,QString::number(d.count()),QString::number(d.count())); + updateLibraryAction->setEnabled(false); + openComicAction->setEnabled(false); + showPropertiesAction->setEnabled(false); + openContainingFolderAction->setEnabled(false); + openContainingFolderComicAction->setEnabled(false); + setAsReadAction->setEnabled(false); + setAsNonReadAction->setEnabled(false); + setAllAsReadAction->setEnabled(false); + setAllAsNonReadAction->setEnabled(false); + importedCovers = true; + } + else + { + updateLibraryAction->setEnabled(true); + openComicAction->setEnabled(true); + showPropertiesAction->setEnabled(true); + openContainingFolderAction->setEnabled(true); + openContainingFolderComicAction->setEnabled(true); + setAsReadAction->setEnabled(true); + setAsNonReadAction->setEnabled(true); + setAllAsReadAction->setEnabled(true); + setAllAsNonReadAction->setEnabled(true); + importedCovers = false; + } + renameLibraryAction->setEnabled(true); + deleteLibraryAction->setEnabled(true); + removeLibraryAction->setEnabled(true); + foldersFilter->setEnabled(true); + clearFoldersFilter->setEnabled(true); + } + else + { + updateLibraryAction->setEnabled(false); + renameLibraryAction->setEnabled(false); + deleteLibraryAction->setEnabled(false); + removeLibraryAction->setEnabled(false); + foldersFilter->setEnabled(false); + clearFoldersFilter->setEnabled(false); + setAsReadAction->setEnabled(false); + setAsNonReadAction->setEnabled(false); + setAllAsReadAction->setEnabled(false); + setAllAsNonReadAction->setEnabled(false); + } +} + +void LibraryWindow::loadCovers(const QModelIndex & mi) +{ +if(foldersFilter->text()!="") +{ + setFoldersFilter(""); + foldersFilter->clear(); +} + QString path = dm->filePath(proxyFilter->mapToSource(mi)); + delete dmCV; + dmCV = new QFileSystemModel(); + dmCV->setFilter(QDir::Files|QDir::CaseSensitive|QDir::NoDotAndDotDot); + dmCV->setNameFilters(QStringList() << "*jpg"); + dmCV->setNameFilterDisables(false); + dmCV->setReadOnly(true); + + dmCV->setIconProvider(&fip); + //dmCV->sort(1); + //delete proxySort; + + //TODO + /*proxySort = new YACReaderSortComics(); + proxySort->setSourceModel(dmCV); + proxySort->setFilterRole(Qt::DisplayRole);*/ + + comicView->setModel(dmCV); + comicView->setRootIndex(dmCV->setRootPath(path)); + + comicFlow->setImagePath(path); + comicFlow->setFocus(Qt::OtherFocusReason); + paths = comicFlow->getImageFiles(); + if(paths.size()>0 && !importedCovers) + { + openComicAction->setEnabled(true); + showPropertiesAction->setEnabled(true); + setAsReadAction->setEnabled(true); + setAsNonReadAction->setEnabled(true); + setAllAsReadAction->setEnabled(true); + setAllAsNonReadAction->setEnabled(true); + } + else + { + openComicAction->setEnabled(false); + showPropertiesAction->setEnabled(false); + setAsReadAction->setEnabled(false); + setAsNonReadAction->setEnabled(false); + setAllAsReadAction->setEnabled(false); + setAllAsNonReadAction->setEnabled(false); + } + if(paths.size()>0) + comicView->setCurrentIndex(dmCV->index(paths[0])); + +} + +void LibraryWindow::centerComicFlow(const QModelIndex & mi) +{ + int distance = comicFlow->centerIndex()-mi.row(); + if(abs(distance)>10) + { + if(distance<0) + comicFlow->setCenterIndex(comicFlow->centerIndex()+(-distance)-10); + else + comicFlow->setCenterIndex(comicFlow->centerIndex()-distance+10); + skip = 10; + } + else + skip = abs(comicFlow->centerIndex()-mi.row()); + comicFlow->showSlide(mi.row()); + comicFlow->setFocus(Qt::OtherFocusReason); +} + +void LibraryWindow::updateComicView(int i) +{ + + if((paths.size()>0)&&skip==0) + comicView->setCurrentIndex(dmCV->index(paths[i])); + skip?(--skip):0; +} + +void LibraryWindow::openComic() +{ + //int index = comicFlow->centerIndex(); + if(!importedCovers) + { + QModelIndex mi = comicView->currentIndex(); + QString path = QDir::cleanPath(dmCV->filePath(mi)); + + path.remove("/.yacreaderlibrary"); + path.remove(path.size()-4,4); + QProcess::startDetached(QDir::cleanPath(QCoreApplication::applicationDirPath())+"/YACReader",QStringList() << path); + //Comic is readed + setCurrentComicReaded(); + + + } +} + +void LibraryWindow::setCurrentComicReaded() +{ + comicFlow->markSlide(comicFlow->centerIndex()); + QModelIndex mi = comicView->currentIndex(); + QString path = QDir::cleanPath(dmCV->filePath(mi)); + QFile f(path.remove(path.size()-3,3)+"r"); + f.open(QIODevice::WriteOnly); + f.close(); + + comicFlow->updateMarks(); +} + +void LibraryWindow::setComicsReaded() +{ + QModelIndex mi = proxyFilter->mapToSource(foldersView->currentIndex()); + QString path; + + if(mi.isValid()) + path = QDir::cleanPath(dm->filePath(mi)); + else + path = dm->rootPath(); + + QDir d(path,"*.jpg"); + d.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); + QFileInfoList list = d.entryInfoList(); + int nFiles = list.size(); + for(int i=0;imarkSlide(i); + } + + comicFlow->updateMarks(); +} + +void LibraryWindow::setCurrentComicUnreaded() +{ + comicFlow->unmarkSlide(comicFlow->centerIndex()); + QModelIndex mi = comicView->currentIndex(); + QString path = QDir::cleanPath(dmCV->filePath(mi)); + QFile f(path.remove(path.size()-3,3)+"r"); + f.open(QIODevice::WriteOnly); + f.remove(); + f.close(); + + comicFlow->updateMarks(); +} + +void LibraryWindow::setComicsUnreaded() +{ + QModelIndex mi = proxyFilter->mapToSource(foldersView->currentIndex()); + QString path; + + if(mi.isValid()) + path = QDir::cleanPath(dm->filePath(mi)); + else + path = dm->rootPath(); + + QDir d(path,"*.jpg"); + d.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot); + QFileInfoList list = d.entryInfoList(); + int nFiles = list.size(); + for(int i=0;iunmarkSlide(i); + } + + comicFlow->updateMarks(); +} + +void LibraryWindow::createLibrary() +{ + createLibraryDialog->show(); +} + +void LibraryWindow::create(QString source, QString dest, QString name) +{ + _lastAdded = name; + libraries.insert(name,source); + selectedLibrary->addItem(name,source); + libraryCreator->createLibrary(source,dest); + libraryCreator->start(); + saveLibraries(); +} + +void LibraryWindow::openLastCreated() +{ + loadLibrary(_lastAdded); + selectedLibrary->setCurrentIndex(selectedLibrary->findText(_lastAdded)); +} + +void LibraryWindow::showAddLibrary() +{ + addLibraryDialog->show(); +} + +void LibraryWindow::openLibrary(QString path, QString name) +{ + _lastAdded = name; + libraries.insert(name,path); + selectedLibrary->addItem(name,path); + openLastCreated(); + saveLibraries(); +} + +void LibraryWindow::loadLibraries() +{ + QFile f(QCoreApplication::applicationDirPath()+"/libraries.yacr"); + f.open(QIODevice::ReadOnly); + QTextStream txtS(&f); + QString content = txtS.readAll(); + QStringList lines = content.split('\n'); + QString line,name; + int i=0; + foreach(line,lines) + { + if((i%2)==0) + { + name = line; + } + else + { + libraries.insert(name.trimmed(),line.trimmed()); + selectedLibrary->addItem(name.trimmed(),line.trimmed()); + } + i++; + } +} + +void LibraryWindow::saveLibraries() +{ + QFile f(QCoreApplication::applicationDirPath()+"/libraries.yacr"); + if(!f.open(QIODevice::WriteOnly)) + { + QMessageBox::critical(NULL,tr("Saving libraries file...."),tr("There was a problem saving YACReaderLibrary libraries file. Please, check if you have enough permissions in the YACReader root folder.")); + } + else + { + QTextStream txtS(&f); + for(QMap::iterator i = libraries.begin();i!=libraries.end();i++) + { + txtS << i.key() << "\n"; + txtS << i.value() << "\n"; + } + } +} + +void LibraryWindow::updateLibrary() +{ + delete dm; + delete dmCV; + delete proxyFilter; + dm = 0; + dmCV = 0; + proxyFilter = 0; + + QString currentLibrary = selectedLibrary->currentText(); + QString path = libraries.value(currentLibrary); + _lastAdded = currentLibrary; + libraryCreator->updateLibrary(path,path+"/.yacreaderlibrary"); + libraryCreator->start(); + updateLibraryDialog->show(); +} + +void LibraryWindow::deleteLibrary() +{ + QString currentLibrary = selectedLibrary->currentText(); + if(QMessageBox::question(this,tr("Are you sure?"),tr("Do you want delete ")+currentLibrary+" library?",QMessageBox::Yes,QMessageBox::No)==QMessageBox::Yes) + { + deleteCurrentLibrary(); + } +} + +void LibraryWindow::deleteCurrentLibrary() +{ + QString path = libraries.value(selectedLibrary->currentText()); + libraries.remove(selectedLibrary->currentText()); + selectedLibrary->removeItem(selectedLibrary->currentIndex()); + selectedLibrary->setCurrentIndex(0); + path = path+"/.yacreaderlibrary"; + QDir d(path); + delTree(d); + d.rmdir(path); + if(libraries.size()==0)//no more libraries avaliable. + { + comicView->setModel(NULL); + foldersView->setModel(NULL); + comicFlow->clear(); + } + saveLibraries(); +} + +void LibraryWindow::removeLibrary() +{ + QString currentLibrary = selectedLibrary->currentText(); + if(QMessageBox::question(this,tr("Are you sure?"),tr("Do you want remove ")+currentLibrary+tr(" library?\nFiles won't be erased from disk."),QMessageBox::Yes,QMessageBox::No)==QMessageBox::Yes) + { + libraries.remove(currentLibrary); + selectedLibrary->removeItem(selectedLibrary->currentIndex()); + selectedLibrary->setCurrentIndex(0); + if(libraries.size()==0)//no more libraries avaliable. + { + comicView->setModel(NULL); + foldersView->setModel(NULL); + comicFlow->clear(); + } + saveLibraries(); + } +} + +void LibraryWindow::renameLibrary() +{ + renameLibraryDialog->show(); +} + +void LibraryWindow::rename(QString newName) +{ + QString currentLibrary = selectedLibrary->currentText(); + QString path = libraries.value(currentLibrary); + libraries.remove(currentLibrary); + selectedLibrary->removeItem(selectedLibrary->currentIndex()); + libraries.insert(newName,path); + selectedLibrary->addItem(newName,path); + saveLibraries(); + selectedLibrary->setCurrentIndex(selectedLibrary->findText(newName)); +} + +void LibraryWindow::cancelCreating() +{ + stopLibraryCreator(); + //TODO delete library. +} + +void LibraryWindow::stopLibraryCreator() +{ + libraryCreator->stop(); + libraryCreator->wait(); +} + +void LibraryWindow::setRootIndex() +{ + if(libraries.size()>0) + { + QString path=libraries.value(selectedLibrary->currentText())+"/.yacreaderlibrary"; + QDir d; //TODO change this by static methods (utils class?? with delTree for example) + if(d.exists(path)) + { + //dmCV->refresh(dmCV->index(path)); + comicView->setRootIndex(dmCV->index(path)); + loadCovers(proxyFilter->mapFromSource(dm->index(path))); + } + else + { + comicView->setModel(NULL); + comicFlow->clear(); + } + } +} + + +void LibraryWindow::toggleFullScreen() +{ + fullscreen?toNormal():toFullScreen(); + fullscreen = !fullscreen; +} + +void LibraryWindow::toFullScreen() +{ + comicFlow->hide(); + comicFlow->setSlideSize(slideSizeF); + comicFlow->setCenterIndex(comicFlow->centerIndex()); + comicView->hide(); + left->hide(); + libraryToolBar->hide(); + + showFullScreen(); + + comicFlow->show(); + comicFlow->setFocus(Qt::OtherFocusReason); + fullScreenToolTip->move((width()-fullScreenToolTip->width())/2,0); + fullScreenToolTip->adjustSize(); + fullScreenToolTip->show(); +} + +void LibraryWindow::toNormal() +{ + comicFlow->hide(); + comicFlow->setSlideSize(slideSizeW); + comicFlow->setCenterIndex(comicFlow->centerIndex()); + comicFlow->render(); + comicView->show(); + left->show(); + fullScreenToolTip->hide(); + libraryToolBar->show(); + comicFlow->show(); + + showMaximized(); +} + +void LibraryWindow::setFoldersFilter(QString filter) +{ + if(filter.contains(previousFilter)) + proxyFilter->softReset(); + else + proxyFilter->reset(); + previousFilter = filter; + if(!filter.isEmpty()) + { + proxyFilter->setFilterRegExp(QRegExp(filter,Qt::CaseInsensitive,QRegExp::FixedString)); + foldersView->expandAll(); + } + else + { + proxyFilter->setFilterRegExp(QRegExp()); + foldersView->scrollTo(foldersView->currentIndex(),QAbstractItemView::PositionAtTop); + foldersView->collapseAll(); + } +} + +void LibraryWindow::showProperties() +{ + //TODO create a new method for this + QModelIndex mi = comicView->currentIndex(); + QString path = QDir::cleanPath(dmCV->filePath(mi)).remove("/.yacreaderlibrary"); + path.remove(path.size()-4,4); + + ThumbnailCreator tc(path,""); + tc.create(); + propertiesDialog->setCover(tc.getCover()); + propertiesDialog->setFilename(path.split("/").last()); + propertiesDialog->setNumpages(tc.getNumPages()); + QFile file(path); + propertiesDialog->setSize(file.size()/(1024.0*1024)); + file.close(); + propertiesDialog->show(); +} + +void LibraryWindow::openContainingFolderComic() +{ + QModelIndex modelIndex = comicView->currentIndex(); + QString path = QDir::cleanPath(dmCV->fileInfo(modelIndex).absolutePath()).remove("/.yacreaderlibrary"); + QDesktopServices::openUrl(QUrl("file:///"+path, QUrl::TolerantMode)); +} + +void LibraryWindow::openContainingFolder() +{ + QModelIndex modelIndex = foldersView->currentIndex(); + QString path = QDir::cleanPath(dm->filePath(proxyFilter->mapToSource(modelIndex))).remove("/.yacreaderlibrary"); + QDesktopServices::openUrl(QUrl("file:///"+path, QUrl::TolerantMode)); +} + +void LibraryWindow::exportLibrary(QString destPath) +{ + QString currentLibrary = selectedLibrary->currentText(); + QString path = libraries.value(currentLibrary)+"/.yacreaderlibrary"; + packageManager->createPackage(path,destPath+"/"+currentLibrary); +} + +void LibraryWindow::importLibrary(QString clc,QString destPath,QString name) +{ + packageManager->extractPackage(clc,destPath+"/"+name); + openLibrary(destPath+"/"+name,name); +} + +void LibraryWindow::reloadOptions() +{ + comicFlow->setFlowType(flowType); +} + +void LibraryWindow::updateFoldersView(QString path) +{ + QModelIndex mi = dm->index(path); + int rowCount = dm->rowCount(mi); + if(!fetching) + { + //fetching = true; + for(int i=0;ifetchMore(dm->index(i,0,mi)); + //int childCount = dm->rowCount(dm->index(i,0,mi)); + //if(childCount>0) + // QMessageBox::critical(NULL,tr("..."),tr("-----")); + fetching = false; + } + } + +} + +void LibraryWindow::searchInFiles(int state) +{ + + if(state == Qt::Checked) + { + dm->setFilter(QDir::Dirs|QDir::Files|QDir::NoDotAndDotDot); //crash, after update proxy filter + } + else + { + dm->setFilter(QDir::Dirs|QDir::NoDotAndDotDot); //crash + } +} + + + diff --git a/YACReaderLibrary/library_window.h b/YACReaderLibrary/library_window.h new file mode 100644 index 00000000..9df1373e --- /dev/null +++ b/YACReaderLibrary/library_window.h @@ -0,0 +1,164 @@ +#ifndef __LIBRARYWINDOW_H +#define __LIBRARYWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "create_library_dialog.h" +#include "add_library_dialog.h" +#include "library_creator.h" +#include "comic_flow.h" +#include "custom_widgets.h" +#include "rename_library_dialog.h" +#include "properties_dialog.h" +#include "options_dialog.h" +#include "export_library_dialog.h" +#include "import_library_dialog.h" +#include "package_manager.h" + + +class LibraryWindow : public QMainWindow +{ + Q_OBJECT +private: + QWidget * left; + CreateLibraryDialog * createLibraryDialog; + UpdateLibraryDialog * updateLibraryDialog; + ExportLibraryDialog * exportLibraryDialog; + ImportLibraryDialog * importLibraryDialog; + AddLibraryDialog * addLibraryDialog; + LibraryCreator * libraryCreator; + HelpAboutDialog * had; + RenameLibraryDialog * renameLibraryDialog; + PropertiesDialog * propertiesDialog; + bool fullscreen; + bool importedCovers; //if true, the library is read only (not updates,open comic or properties) + YACReaderTreeSearch * proxyFilter; + YACReaderSortComics * proxySort; + PackageManager * packageManager; + + ComicFlow * comicFlow; + QSize slideSizeW; + QSize slideSizeF; + //search filter + QLineEdit * foldersFilter; + QString previousFilter; + QPushButton * clearFoldersFilter; + QCheckBox * includeComicsCheckBox; + //------------- + QListView * comicView; + QTreeView * foldersView; + QComboBox * selectedLibrary; + QFileSystemModel * dm; + QFileSystemModel * dmCV; + QStringList paths; + QMap libraries; + QLabel * fullScreenToolTip; + YACReaderIconProvider fip; + + bool fetching; + + int i; + unsigned int skip; + + QAction * openComicAction; + QAction * showPropertiesAction; + QAction * createLibraryAction; + QAction * openLibraryAction; + QAction * exportLibraryAction; + QAction * importLibraryAction; + QAction * updateLibraryAction; + QAction * deleteLibraryAction; + QAction * removeLibraryAction; + QAction * helpAboutAction; + QAction * renameLibraryAction; + QAction * toggleFullScreenAction; + QAction * optionsAction; + + QAction * setRootIndexAction; + QAction * expandAllNodesAction; + QAction * colapseAllNodesAction; + + QAction * openContainingFolderAction; + QAction * openContainingFolderComicAction; + QAction * setAsReadAction; + QAction * setAsNonReadAction; + QAction * setAllAsReadAction; + QAction * setAllAsNonReadAction; + QAction * showHideMarksAction; + + + QToolBar * libraryToolBar; + QToolBar * treeActions; + QToolBar * comicsToolBar; + + OptionsDialog * optionsDialog; + + QString libraryPath; + QString comicsPath; + + QString _lastAdded; + + QModelIndex _rootIndex; + QModelIndex _rootIndexCV; + + void setupUI(); + void createActions(); + void createToolBars(); + void createMenus(); + void createConnections(); +public: + LibraryWindow(); +public slots: + void loadLibrary(const QString & path); + void loadCovers(const QModelIndex & mi); + void centerComicFlow(const QModelIndex & mi); + void updateComicView(int i); + void openComic(); + void createLibrary(); + void create(QString source,QString dest, QString name); + void showAddLibrary(); + void openLibrary(QString path, QString name); + void loadLibraries(); + void saveLibraries(); + void openLastCreated(); + void updateLibrary(); + void deleteLibrary(); + void openContainingFolder(); + void openContainingFolderComic(); + void deleteCurrentLibrary(); + void removeLibrary(); + void renameLibrary(); + void rename(QString newName); + void cancelCreating(); + void stopLibraryCreator(); + void setRootIndex(); + void toggleFullScreen(); + void toNormal(); + void toFullScreen(); + void setFoldersFilter(QString filter); + void showProperties(); + void exportLibrary(QString destPath); + void importLibrary(QString clc,QString destPath,QString name); + void reloadOptions(); + void updateFoldersView(QString); + void setCurrentComicReaded(); + void setCurrentComicUnreaded(); + void setComicsReaded(); + void setComicsUnreaded(); + void searchInFiles(int); +}; + +#endif + + + diff --git a/YACReaderLibrary/main.cpp b/YACReaderLibrary/main.cpp new file mode 100644 index 00000000..905c0cea --- /dev/null +++ b/YACReaderLibrary/main.cpp @@ -0,0 +1,22 @@ +#include "library_window.h" + +#include + +#define PICTUREFLOW_QT4 1 + +int main( int argc, char ** argv ) +{ + QApplication app( argc, argv ); + + QTranslator translator; + QString sufix = QLocale::system().name(); + translator.load(":/yacreaderlibrary_"+sufix); + app.installTranslator(&translator); + app.setApplicationName("YACReaderLibrary"); + + QMainWindow * mw = new LibraryWindow(); + mw->resize(800,480); + mw->showMaximized(); + + return app.exec(); +} diff --git a/YACReaderLibrary/options_dialog.cpp b/YACReaderLibrary/options_dialog.cpp new file mode 100644 index 00000000..0744b59f --- /dev/null +++ b/YACReaderLibrary/options_dialog.cpp @@ -0,0 +1,148 @@ +#include "options_dialog.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +PictureFlow::FlowType flowType = PictureFlow::Strip; + +OptionsDialog::OptionsDialog(QWidget * parent) +:QDialog() +{ + QVBoxLayout * layout = new QVBoxLayout(this); + + accept = new QPushButton(tr("Save")); + cancel = new QPushButton(tr("Cancel")); + connect(accept,SIGNAL(clicked()),this,SLOT(saveOptions())); + connect(cancel,SIGNAL(clicked()),this,SLOT(restoreOptions())); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + QGroupBox *groupBox = new QGroupBox(tr("How to show covers:")); + + radio1 = new QRadioButton(tr("CoverFlow look")); + radio2 = new QRadioButton(tr("Stripe look")); + radio3 = new QRadioButton(tr("Overlapped Stripe look")); + + + QVBoxLayout *vbox = new QVBoxLayout; + QHBoxLayout * opt1 = new QHBoxLayout; + opt1->addWidget(radio1); + QLabel * lOpt1 = new QLabel(); + lOpt1->setPixmap(QPixmap(":/images/flow1.png")); + opt1->addStretch(); + opt1->addWidget(lOpt1); + vbox->addLayout(opt1); + + QHBoxLayout * opt2 = new QHBoxLayout; + opt2->addWidget(radio2); + QLabel * lOpt2 = new QLabel(); + lOpt2->setPixmap(QPixmap(":/images/flow2.png")); + opt2->addStretch(); + opt2->addWidget(lOpt2); + vbox->addLayout(opt2); + + QHBoxLayout * opt3 = new QHBoxLayout; + opt3->addWidget(radio3); + QLabel * lOpt3 = new QLabel(); + lOpt3->setPixmap(QPixmap(":/images/flow3.png")); + opt3->addStretch(); + opt3->addWidget(lOpt3); + vbox->addLayout(opt3); + + + //vbox->addStretch(1); + groupBox->setLayout(vbox); + + QHBoxLayout * buttons = new QHBoxLayout(); + buttons->addStretch(); + buttons->addWidget(accept); + buttons->addWidget(cancel); + + layout->addWidget(groupBox); + layout->addLayout(buttons); + + setLayout(layout); + + restoreOptions(); //load options + resize(200,0); + setModal (true); + setWindowTitle("Options"); +} + +void OptionsDialog::findFolder() +{ + QString s = QFileDialog::getExistingDirectory(0,tr("Comics directory"),"."); + if(!s.isEmpty()) + { + pathEdit->setText(s); + } +} + +void OptionsDialog::saveOptions() +{ + QFile f(QCoreApplication::applicationDirPath()+"/YACReaderLibrary.conf"); + if(!f.open(QIODevice::WriteOnly)) + { + QMessageBox::critical(NULL,tr("Saving config file...."),tr("There was a problem saving YACReaderLibrary configuration. Please, check if you have enough permissions in the YACReader root folder.")); + } + else + { + QTextStream txtS(&f); + if(radio1->isChecked()) + { + txtS << "FLOW_TYPE" << "\n" << (int)PictureFlow::CoverFlowLike << "\n"; + flowType = PictureFlow::CoverFlowLike; + } + if(radio2->isChecked()) + { + txtS << "FLOW_TYPE" << "\n" << (int)PictureFlow::Strip << "\n"; + flowType = PictureFlow::Strip; + } + if(radio3->isChecked()) + { + txtS << "FLOW_TYPE" << "\n" << (int)PictureFlow::StripOverlapped << "\n"; + flowType = PictureFlow::StripOverlapped; + } + f.close(); + close(); + emit(optionsChanged()); + } +} + +void OptionsDialog::restoreOptions() +{ + QFile f(QCoreApplication::applicationDirPath()+"/YACReaderLibrary.conf"); + if(f.exists()) + { + f.open(QIODevice::ReadOnly); + QTextStream txtS(&f); + QString content = txtS.readAll(); + + QStringList lines = content.split('\n'); + if(lines.count()>0){ + QString name = lines.at(1); + + switch(flowType=(PictureFlow::FlowType)name.toInt()){ + case PictureFlow::CoverFlowLike: + radio1->setChecked(true); + break; + case PictureFlow::Strip: + radio2->setChecked(true); + break; + case PictureFlow::StripOverlapped: + radio3->setChecked(true); + break; + } + } + else + flowType=PictureFlow::Strip; + } + else + flowType=PictureFlow::Strip; + +} diff --git a/YACReaderLibrary/options_dialog.h b/YACReaderLibrary/options_dialog.h new file mode 100644 index 00000000..36fe2d6a --- /dev/null +++ b/YACReaderLibrary/options_dialog.h @@ -0,0 +1,50 @@ +#ifndef __OPTIONS_DIALOG_H +#define __OPTIONS_DIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include "pictureflow.h" + +extern PictureFlow::FlowType flowType; + +class OptionsDialog : public QDialog +{ +Q_OBJECT + public: + OptionsDialog(QWidget * parent = 0); + private: + QLabel * pathLabel; + QLineEdit * pathEdit; + QPushButton * pathFindButton; + + QLabel * magGlassSizeLabel; + + QLabel * zoomLevel; + + QLabel * slideSizeLabel; + QSlider * slideSize; + + QPushButton * accept; + QPushButton * cancel; + + QRadioButton *radio1; + QRadioButton *radio2; + QRadioButton *radio3; + + public slots: + void saveOptions(); + void restoreOptions(); + void findFolder(); + +signals: + void optionsChanged(); + +}; + + +#endif diff --git a/YACReaderLibrary/package_manager.cpp b/YACReaderLibrary/package_manager.cpp new file mode 100644 index 00000000..44066d54 --- /dev/null +++ b/YACReaderLibrary/package_manager.cpp @@ -0,0 +1,47 @@ +#include "package_manager.h" +#include + +PackageManager::PackageManager() +:_7z(0) +{ + +} + +void PackageManager::createPackage(const QString & libraryPath,const QString & dest) +{ + QStringList attributes; + attributes << "a" << "-y" << "-ttar" << dest+".clc" << libraryPath ; + _7z = new QProcess(); + connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SLOT(openingError(QProcess::ProcessError))); + connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SIGNAL(exported())); + _7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes); +} + +void PackageManager::extractPackage(const QString & packagePath,const QString & destDir) +{ + QStringList attributes; + QString output = "-o"; + output += destDir; + attributes << "x" << "-y" << output << packagePath; + _7z = new QProcess(); + connect(_7z,SIGNAL(error(QProcess::ProcessError)),this,SLOT(openingError(QProcess::ProcessError))); + connect(_7z,SIGNAL(finished(int,QProcess::ExitStatus)),this,SIGNAL(imported())); + _7z->start(QCoreApplication::applicationDirPath()+"/utils/7z",attributes); +} + +void PackageManager::cancel() +{ + if(_7z!=0) + { + _7z->disconnect(); + _7z->kill(); + if(creating) + { + //TODO remove dest+".clc" + } + else + { + //TODO fixed: is done by libraryWindow + } + } +} diff --git a/YACReaderLibrary/package_manager.h b/YACReaderLibrary/package_manager.h new file mode 100644 index 00000000..497fc826 --- /dev/null +++ b/YACReaderLibrary/package_manager.h @@ -0,0 +1,24 @@ +#ifndef PACKAGE_MANAGER_H +#define PACKAGE_MANAGER_H + +#include + +class PackageManager : public QObject +{ + Q_OBJECT +public: + PackageManager(); + void createPackage(const QString & libraryPath,const QString & dest); + void extractPackage(const QString & packagePath,const QString & destDir); + public slots: + void cancel(); +private: + bool creating; + QProcess * _7z; + +signals: + void exported(); + void imported(); +}; + +#endif diff --git a/YACReaderLibrary/properties_dialog.cpp b/YACReaderLibrary/properties_dialog.cpp new file mode 100644 index 00000000..8853f496 --- /dev/null +++ b/YACReaderLibrary/properties_dialog.cpp @@ -0,0 +1,77 @@ +#include "properties_dialog.h" +#include +#include +#include + + + +PropertiesDialog::PropertiesDialog(QWidget * parent) +:QDialog(parent) +{ + QVBoxLayout * l = new QVBoxLayout(); + + sa = new QScrollArea(this); + _cover = new QLabel(sa); + _cover->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + _cover->setScaledContents(true); + _cover->setAlignment(Qt::AlignTop|Qt::AlignHCenter); + + + sa->setWidget(_cover); + sa->setBackgroundRole(QPalette::Dark); + sa->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + sa->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + sa->setFrameStyle(QFrame::NoFrame); + sa->setAlignment(Qt::AlignCenter); + + l->addWidget(sa); + + QVBoxLayout * info = new QVBoxLayout(); + info->addWidget(_name = new QLabel("name")); + info->addWidget(_pages = new QLabel("pages")); + info->addWidget(_size = new QLabel("size")); + info->setSizeConstraint(QLayout::SetMaximumSize); + l->addLayout(info); + this->setLayout(l); + this->setWindowTitle(tr("Comic properties")); +} + +void PropertiesDialog::setCover(const QPixmap & cover) +{ + _cover->setPixmap(cover); + float aspectRatio = (float)cover.width()/cover.height(); + int heightDesktopResolution = QApplication::desktop()->screenGeometry().height(); + int widthDesktopResolution = QApplication::desktop()->screenGeometry().width(); + int sHeight,sWidth; + sHeight = static_cast(heightDesktopResolution*0.84); + + if(aspectRatio<1) + { + sWidth = static_cast(sHeight*0.70); + this->resize(sWidth,sHeight); + this->move(QPoint((widthDesktopResolution-sWidth)/2,((heightDesktopResolution-sHeight)-40)/2)); + _cover->resize(static_cast((height()-90)*aspectRatio), + (height()-90)); + } + else + { + sWidth = static_cast(sHeight*1.10); + this->resize(sWidth,sHeight); + this->move(QPoint((widthDesktopResolution-sWidth)/2,((heightDesktopResolution-sHeight)-40)/2)); + _cover->resize((width()-25), + static_cast((width()-25)/aspectRatio)); + } +} +void PropertiesDialog::setFilename(const QString & name) +{ + _name->setText(tr("Name : ") + name); +} +void PropertiesDialog::setNumpages(int pages) +{ + _pages->setText(tr("Number of pages : ") + QString::number(pages)); +} +void PropertiesDialog::setSize(float size) +{ + + _size->setText(tr("Size : ") + QString::number(size,'f',2) + " MB"); +} \ No newline at end of file diff --git a/YACReaderLibrary/properties_dialog.h b/YACReaderLibrary/properties_dialog.h new file mode 100644 index 00000000..2454680a --- /dev/null +++ b/YACReaderLibrary/properties_dialog.h @@ -0,0 +1,29 @@ +#ifndef __PROPERTIES_DIALOG_H +#define __PROPERTIES_DIALOG_H + +#include +#include +#include +#include +#include + + class PropertiesDialog : public QDialog + { + Q_OBJECT + private: + QLabel * _cover; + QLabel * _name; + QLabel * _pages; + QLabel * _size; + QScrollArea * sa; + + public: + PropertiesDialog(QWidget * parent = 0); + public slots: + void setCover(const QPixmap & cover); + void setFilename(const QString & name); + void setNumpages(int pages); + void setSize(float size); + }; +#endif + diff --git a/YACReaderLibrary/rename_library_dialog.cpp b/YACReaderLibrary/rename_library_dialog.cpp new file mode 100644 index 00000000..64c84f52 --- /dev/null +++ b/YACReaderLibrary/rename_library_dialog.cpp @@ -0,0 +1,68 @@ +#include "rename_library_dialog.h" + +#include +#include +#include + + + +RenameLibraryDialog::RenameLibraryDialog(QWidget * parent) +:QDialog(parent) +{ + setupUI(); +} + +void RenameLibraryDialog::setupUI() +{ + newNameLabel = new QLabel(tr("New Library Name : ")); + newNameEdit = new QLineEdit; + newNameLabel->setBuddy(newNameEdit); + + accept = new QPushButton(tr("Rename")); + //accept->setDisabled(true); + connect(accept,SIGNAL(clicked()),this,SLOT(rename())); + + cancel = new QPushButton(tr("Cancel")); + connect(cancel,SIGNAL(clicked()),this,SLOT(close())); + + QHBoxLayout *nameLayout = new QHBoxLayout; + + nameLayout->addWidget(newNameLabel); + nameLayout->addWidget(newNameEdit); + + QHBoxLayout *bottomLayout = new QHBoxLayout; + bottomLayout->addStretch(); + bottomLayout->addWidget(accept); + bottomLayout->addWidget(cancel); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addLayout(nameLayout); + mainLayout->addStretch(); + mainLayout->addLayout(bottomLayout); + + QHBoxLayout * imgMainLayout = new QHBoxLayout; + QLabel * imgLabel = new QLabel(this); + QPixmap p(":/images/edit.png"); + imgLabel->setPixmap(p); + imgMainLayout->addWidget(imgLabel); + imgMainLayout->addLayout(mainLayout); + + setLayout(imgMainLayout); + + setModal(true); + setWindowTitle(tr("Rename current library")); +} + +void RenameLibraryDialog::rename() +{ + //accept->setEnabled(false); + emit(renameLibrary(newNameEdit->text())); + close(); +} + +void RenameLibraryDialog::close() +{ + newNameEdit->clear(); + //accept->setEnabled(false); + QDialog::close(); +} \ No newline at end of file diff --git a/YACReaderLibrary/rename_library_dialog.h b/YACReaderLibrary/rename_library_dialog.h new file mode 100644 index 00000000..aa5eaccf --- /dev/null +++ b/YACReaderLibrary/rename_library_dialog.h @@ -0,0 +1,30 @@ +#ifndef __RENAME_LIBRARY_DIALOG_H +#define __RENAME_LIBRARY_DIALOG_H + +#include +#include +#include +#include + + + class RenameLibraryDialog : public QDialog + { + Q_OBJECT + public: + RenameLibraryDialog(QWidget * parent = 0); + private: + QLabel * newNameLabel; + QLineEdit * newNameEdit; + QPushButton * accept; + QPushButton * cancel; + void setupUI(); + public slots: + void rename(); + void close(); +signals: + void renameLibrary(QString newName); + }; + + +#endif + diff --git a/YACReaderLibrary/yacreaderlibrary_es.qm b/YACReaderLibrary/yacreaderlibrary_es.qm new file mode 100644 index 0000000000000000000000000000000000000000..92ed41fc9d9dce6f5d59d11efddbc2e7069f208a GIT binary patch literal 9449 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 literal 0 HcmV?d00001 diff --git a/YACReaderLibrary/yacreaderlibrary_es.ts b/YACReaderLibrary/yacreaderlibrary_es.ts new file mode 100644 index 00000000..8fc05d28 --- /dev/null +++ b/YACReaderLibrary/yacreaderlibrary_es.ts @@ -0,0 +1,522 @@ + + + + + AddLibraryDialog + + + Comics folder : + Carpeta de cómics: + + + + Library Name : + Nombre de la librería: + + + + Add + Añadir + + + + Cancel + Cancelar + + + + Add an existing library + Añadir la librería existente + + + + CreateLibraryDialog + + + Comics folder : + Carpeta de cómics: + + + + Library Name : + Nombre de la libreria: + + + + Create + Crear + + + + Cancel + Cancelar + + + + Create new library + Crear la nueva librería + + + + ExportLibraryDialog + + + Output folder : + Carpeta de destino: + + + + Create + Crear + + + + Cancel + Cancelar + + + + Create covers package + Crear paquete de portadas + + + + Destination directory + Carpeta de destino + + + + Creating package . + Creando paquete . + + + + HelpAboutDialog + + + About + Acerca de + + + + Help + Ayuda + + + + ImportLibraryDialog + + Comics folder : + Carpeta de cómics: + + + + Library Name : + Nombre de la biblioteca : + + + Add + Añadir + + + + Package location : + Ubicación del paquete: + + + + Destination folder : + Directorio de destino: + + + + Unpack + Desempaquetar + + + + Cancel + Cancelar + + + + Extract a catalog + Extraer un catálogo + + + + Compresed library covers (*.clc) + Compresed library covers (*.clc) + + + + Importing package . + Importando paquete. + + + + LibraryWindow + + + Select a library: + Seleciona una biblioteca: + + + + Clear + Borrar + + + + Search folders/comics + Buscar directorios/cómics + + + + Include files (slower) + Incluir archivos (lento) + + + + <font color='white'> press 'F' to close fullscreen mode </font> + <font color='white'> presiona 'F' para salir de pantalla completa </font> + + + + YACReader Library + YACReader Library + + + + Create a new library + Crear una nueva biblioteca + + + + Open an existing library + Abrir una biblioteca existente + + + + Pack the covers of the selected library + Empaquetar las portadas de la biblioteca seleccionada + + + + Unpack a catalog + Desempaquetar un catálogo + + + + Update current library + Actualizar la librería seleccionada + + + + Rename current library + Renombrar la libreria seleccionada + + + + Delete current library from disk + Borrar la libería seleccionada del disco + + + + Remove current library from your collection + Eliminar librería de la colección + + + + Open current comic on YACReader + Abrir el cómic actual en YACReader + + + + Set as read + Marcar como leído + + + + Set comic as read + Marcar cómic como leído + + + + Set as unread + Marcar como no leído + + + + Set comic as unread + Marcar cómic como no leído + + + + Set all as read + Marcar todos como leídos + + + + Set all comics as read + Marcar todos los cómics como leídos + + + + Set all as unread + Marcar todos como no leídos + + + + Set all comics as unread + Marcar todos los cómics como no leídos + + + + Show/Hide marks + Mostrar/Ocultar marcas + + + + Show or hide readed marks + Mostrar u ocultar marcas + + + + Show properties of current comic + Mostrar las propiedades del cómic actual + + + + Fullscreen mode on/off (F) + Activar/desactivar modo a pantalla completa (F) + + + + Help, About YACReader + Ayuda, A cerca de... YACReader + + + + Select root node + Seleccionar el nodo raíz + + + + Expand all nodes + Expandir todos los nodos + + + + Colapse all nodes + Contraer todos los nodos + + + + Show options dialog + Mostrar opciones + + + + Open folder... + Abrir carpeta... + + + + Open containing folder... + Abrir carpeta contenedora... + + + + Library + Librería + + + + Saving libraries file.... + Guardando bibliotecas... + + + + There was a problem saving YACReaderLibrary libraries file. Please, check if you have enough permissions in the YACReader root folder. + Hubo un problema al guardar las bibliotecas de YACReaderLibrary. Por favor, comprueba si tienes suficientes permisos en el directorio raíz de YACReader. + + + + + Are you sure? + ¿Estás seguro? + + + + Do you want delete + ¿Deseas borrar + + + + Do you want remove + ¿Deseas eliminar + + + + library? +Files won't be erased from disk. + ? Los archivos no serán borrados del disco. + + + + OptionsDialog + + + Save + Guardar + + + + Cancel + Cancelar + + + + How to show covers: + Cómo mostrar las portadas: + + + + CoverFlow look + + + + + Stripe look + + + + + Overlapped Stripe look + Overlaped Stripe look + + + + Restart is needed + Es necesario reiniciar + + + + Comics directory + Directorio de cómics + + + + Saving config file.... + Hubo un problema al guardar la configuración de YACReaderLibrary. Por favor, comprueba si tienes suficientes permisos en el directorio raíz de YACReader. + Guardando archivo de configuración... + + + + There was a problem saving YACReaderLibrary configuration. Please, check if you have enough permissions in the YACReader root folder. + + + + + PropertiesDialog + + + Comic properties + Propiedades del cómic + + + + Name : + Nombre : + + + + Number of pages : + Número de páginas : + + + + Size : + Tamaño : + + + + QPushButton + + Hello world! + Hola mundo! + + + + RenameLibraryDialog + + + New Library Name : + Nombre de la nueva librería : + + + + Rename + Renombrar + + + + Cancel + Cancelar + + + + Rename current library + Renombrar la libreria seleccionada + + + + ThumbnailCreator + + 7z not found + 7z no encontrado + + + 7z wasn't found in your PATH. + 7z no ha sido encontrado en tu PATH. + + + 7z crashed + fallo en 7z + + + 7z crashed. + fallo en 7z. + + + 7z reading + Leyendo de 7z + + + problem reading from 7z + Problema lenyendo de 7z + + + 7z problem + Problema en 7z + + + Unknown error 7z + Error desconocido en 7z + + + + UpdateLibraryDialog + + + Updating.... + Actualizado... + + + + Cancel + Cancelar + + + diff --git a/common/check_new_version.cpp b/common/check_new_version.cpp new file mode 100644 index 00000000..d474be89 --- /dev/null +++ b/common/check_new_version.cpp @@ -0,0 +1,93 @@ +#include "check_new_version.h" +#include +#include +#include +#include + +#define VERSION "0.4.5" + +HttpVersionChecker::HttpVersionChecker() + :QWidget() +{ + http = new QHttp(this); + + connect(http, SIGNAL(requestFinished(int, bool)), + this, SLOT(httpRequestFinished(int, bool))); + + connect(http, SIGNAL(responseHeaderReceived(const QHttpResponseHeader &)), + this, SLOT(readResponseHeader(const QHttpResponseHeader &))); + + connect(http, SIGNAL(readyRead(const QHttpResponseHeader &)), + this, SLOT(read(const QHttpResponseHeader &))); +} + +void HttpVersionChecker::get() +{ + QUrl url("http://code.google.com/p/yacreader/downloads/list"); + QHttp::ConnectionMode mode = QHttp::ConnectionModeHttp; + http->setHost(url.host(), mode, url.port() == -1 ? 0 : url.port()); + QByteArray path = QUrl::toPercentEncoding(url.path(), "!$&'()*+,;=:@/"); + if (path.isEmpty()) + path = "/"; + httpGetId = http->get(path, 0); +} +void HttpVersionChecker::readResponseHeader(const QHttpResponseHeader &responseHeader) +{ + +} + +void HttpVersionChecker::read(const QHttpResponseHeader &){ + content.append(http->readAll()); +} + +void HttpVersionChecker::httpRequestFinished(int requestId, bool error) +{ + + QString response(content); +#ifdef Q_WS_WIN + QRegExp rx(".*YACReader\\-([0-9]+).([0-9]+).([0-9]+)\\.?([0-9]+)?.{0,5}win32.*"); +#endif + +#ifdef Q_WS_X11 + QRegExp rx(".*YACReader\\-([0-9]+).([0-9]+).([0-9]+)\\.?([0-9]+)?.{0,5}X11.*"); +#endif + +#ifdef Q_WS_MAC + QRegExp rx(".*YACReader\\-([0-9]+).([0-9]+).([0-9]+)\\.?([0-9]+)?.{0,5}Mac.*"); +#endif + + int index = 0; + bool newVersion = false; + bool sameVersion = true; + //bool currentVersionIsNewer = false; + + QString version(VERSION); + QStringList sl = version.split("."); + if((index = rx.indexIn(response))!=-1) + { + int length = qMin(sl.size(),(rx.cap(4)!="")?4:3); + for(int i=0;isl.at(i).toInt()){ + newVersion=true; + break; + } + else + sameVersion = sameVersion && rx.cap(i+1).toInt()==sl.at(i).toInt(); + } + if(!newVersion && sameVersion) +{ + if((sl.size()==3)&&(rx.cap(4)!="")) + newVersion = true; +} + + + } + + if(newVersion == true) + emit newVersionDetected(); +} diff --git a/common/check_new_version.h b/common/check_new_version.h new file mode 100644 index 00000000..e9f8ab3e --- /dev/null +++ b/common/check_new_version.h @@ -0,0 +1,29 @@ +#ifndef __CHECKUPDATE_H +#define __CHECKUPDATE_H + +#include +#include +#include +#include + + class HttpVersionChecker : public QWidget + { + Q_OBJECT + public: + HttpVersionChecker(); + bool thereIsNewVersion(); + public slots: + void httpRequestFinished(int requestId, bool error); + void readResponseHeader(const QHttpResponseHeader &); + void read(const QHttpResponseHeader &); + void get(); + private: + QHttp *http; + int httpGetId; + QByteArray content; + bool found; + signals: + void newVersionDetected(); + }; + +#endif diff --git a/common/custom_widgets.cpp b/common/custom_widgets.cpp new file mode 100644 index 00000000..d749a1b9 --- /dev/null +++ b/common/custom_widgets.cpp @@ -0,0 +1,370 @@ +#include "custom_widgets.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qnaturalsorting.h" + + + + +HelpAboutDialog::HelpAboutDialog(QWidget * parent) +:QDialog(parent) +{ + QVBoxLayout * layout = new QVBoxLayout(); + + tabWidget = new QTabWidget(); + + tabWidget->addTab(aboutText = new QTextBrowser(), tr("About")); + aboutText->setOpenExternalLinks(true); + aboutText->setFont(QFont("Comic Sans MS", 10)); //purisa + tabWidget->addTab(helpText = new QTextBrowser(), tr("Help")); + helpText->setOpenExternalLinks(true); + helpText->setFont(QFont("Comic Sans MS", 10)); + //helpText->setDisabled(true); + //tabWidget->addTab(,"About Qt"); + + layout->addWidget(tabWidget); + layout->setContentsMargins(1,3,1,1); + + setLayout(layout); + resize(500, QApplication::desktop()->availableGeometry().height()*0.83); +} + +HelpAboutDialog::HelpAboutDialog(const QString & pathAbout,const QString & pathHelp,QWidget * parent) +:QDialog(parent) +{ + loadAboutInformation(pathAbout); + loadHelp(pathHelp); +} + +void HelpAboutDialog::loadAboutInformation(const QString & path) +{ + aboutText->insertHtml(fileToString(path)); + aboutText->moveCursor(QTextCursor::Start); +} + +void HelpAboutDialog::loadHelp(const QString & path) +{ + helpText->insertHtml(fileToString(path)); + helpText->moveCursor(QTextCursor::Start); +} + +QString HelpAboutDialog::fileToString(const QString & path) +{ + QFile f(path); + f.open(QIODevice::ReadOnly); + QTextStream txtS(&f); + QString content = txtS.readAll(); + f.close(); + + return content; +} + +void delTree(QDir dir) +{ + dir.setFilter(QDir::AllDirs|QDir::Files|QDir::Hidden|QDir::NoDotAndDotDot); + QFileInfoList list = dir.entryInfoList(); + for (int i = 0; i < list.size(); ++i) + { + QFileInfo fileInfo = list.at(i); + if(fileInfo.isDir()) + { + delTree(QDir(fileInfo.absoluteFilePath())); + dir.rmdir(fileInfo.absoluteFilePath()); + } + else + { + dir.remove(fileInfo.absoluteFilePath()); + } + } +} + +YACReaderIconProvider::YACReaderIconProvider() +:QFileIconProvider() +{ +} + +QIcon YACReaderIconProvider::icon(IconType type) const +{ + switch(type) + { + case Folder: + return QIcon(":/images/folder.png"); + break; + case File: + return QIcon(":/images/icon.png"); + break; + default: + return QFileIconProvider::icon(type); + } +} +QIcon YACReaderIconProvider::icon(const QFileInfo & info) const +{ + if(info.isDir()) + return QIcon(":/images/folder.png"); + if(info.isFile()) + return QIcon(":/images/icon.png"); +} +QString YACReaderIconProvider::type(const QFileInfo & info) const +{ + return QFileIconProvider::type(info); +} + +YACReaderFlow::YACReaderFlow(QWidget * parent,FlowType flowType) : PictureFlow(parent,flowType) {} + +void YACReaderFlow::mousePressEvent(QMouseEvent* event) +{ + if(event->x() > (width()+slideSize().width())/2) + showNext(); + else + if(event->x() < (width()-slideSize().width())/2) + showPrevious(); + //else (centered cover space) +} + +void YACReaderFlow::mouseDoubleClickEvent(QMouseEvent* event) +{ + if((event->x() > (width()-slideSize().width())/2)&&(event->x() < (width()+slideSize().width())/2)) + emit selected(centerIndex()); +} + +YACReaderComicDirModel::YACReaderComicDirModel( const QStringList & nameFilters, QDir::Filters filters, QDir::SortFlags sort, QObject * parent ) +:QDirModel(nameFilters,filters,sort,parent) +{ + +} + +//this method isn't used to show fileName on QListView +QString YACReaderComicDirModel::fileName ( const QModelIndex & index ) const +{ + QString fileName = QDirModel::fileName(index); + return fileName.remove(fileName.size()-4,4); +} + +QFileInfo YACReaderComicDirModel::fileInfo ( const QModelIndex & index ) const +{ + QFileInfo fileInfo = QDirModel::fileInfo(index); + QString path = QDir::cleanPath(filePath(index)).remove("/.yacreaderlibrary"); + path.remove(path.size()-4,4); + fileInfo.setFile(path); + return fileInfo; +} + + +YACReaderComicViewDelegate::YACReaderComicViewDelegate(QObject * parent) +:QItemDelegate(parent) +{ +} + +void YACReaderComicViewDelegate::paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const +{ + QStyleOptionViewItemV4 opt = setOptions(index, option); + + const QStyleOptionViewItemV2 *v2 = qstyleoption_cast(&option); + opt.features = v2 ? v2->features + : QStyleOptionViewItemV2::ViewItemFeatures(QStyleOptionViewItemV2::None); + const QStyleOptionViewItemV3 *v3 = qstyleoption_cast(&option); + opt.locale = v3 ? v3->locale : QLocale(); + opt.widget = v3 ? v3->widget : 0; + + // prepare + painter->save(); + painter->setClipRect(opt.rect); + + // get the data and the rectangles + + QVariant value; + + QPixmap pixmap; + QRect decorationRect; + QIcon icon; + value = index.data(Qt::DecorationRole); + if (value.isValid()) { + // ### we need the pixmap to call the virtual function + pixmap = decoration(opt, value); + if (value.type() == QVariant::Icon) { + icon = qvariant_cast(value); + const QSize size = icon.actualSize(option.decorationSize); + decorationRect = QRect(QPoint(0, 0), size); + } else { + icon = QIcon(); + decorationRect = QRect(QPoint(0, 0), pixmap.size()); + } + } else { + icon = QIcon(); + decorationRect = QRect(); + } + + QString text; + QRect displayRect; + value = index.data(Qt::DisplayRole); + if (value.isValid() && !value.isNull()) { + text = value.toString(); + text.remove(text.size()-4,4); + displayRect = textRectangle(painter, textLayoutBounds(opt), opt.font, text); + } + + QRect checkRect; + Qt::CheckState checkState = Qt::Unchecked; + value = index.data(Qt::CheckStateRole); + if (value.isValid()) { + checkState = static_cast(value.toInt()); + checkRect = check(opt, opt.rect, value); + } + + // do the layout + + doLayout(opt, &checkRect, &decorationRect, &displayRect, false); + + // draw the item + + drawBackground(painter, opt, index); + drawCheck(painter, opt, checkRect, checkState); + drawDecoration(painter, opt, decorationRect, pixmap); + drawDisplay(painter, opt, displayRect, text); + drawFocus(painter, opt, displayRect); + + // done + painter->restore(); +} + +QRect YACReaderComicViewDelegate::textLayoutBounds(const QStyleOptionViewItemV2 &option) const +{ + QRect rect = option.rect; + const bool wrapText = option.features & QStyleOptionViewItemV2::WrapText; + switch (option.decorationPosition) { + case QStyleOptionViewItem::Left: + case QStyleOptionViewItem::Right: + rect.setWidth(wrapText && rect.isValid() ? rect.width() : (1000)); + break; + case QStyleOptionViewItem::Top: + case QStyleOptionViewItem::Bottom: + rect.setWidth(wrapText ? option.decorationSize.width() : (1000)); + break; + } + + return rect; +} + +YACReaderTreeSearch::YACReaderTreeSearch(QObject * parent) +:QSortFilterProxyModel(parent),cache(new ModelIndexCache()) +{ + this->setFilterCaseSensitivity(Qt::CaseInsensitive); +} + +bool YACReaderTreeSearch::containsFiles(QString path,const QRegExp &exp) const +{ + QDir dir(path); + QStringList list = dir.entryList(QStringList() << "*"+exp.pattern()+"*",QDir::Files | QDir::NoDotAndDotDot); + return list.size()>0; +} + +bool YACReaderTreeSearch::itemMatchesExpression(const QModelIndex &index, const QRegExp &exp) const +{ + + QString name = ((QFileSystemModel *)sourceModel())->filePath(index); + ModelIndexCache::CacheData cd = cache->getCacheData(name); + bool v = false; + if(!cd.visited || cd.acepted) + { + v = name.contains(exp);// || containsFiles(name,exp); // TODO : complete path? + int numChildren = sourceModel()->rowCount(index); + for(int i=0; iindex(i,0,index), exp); + } + cd.visited = true; + cd.acepted = v; + cache->setModelIndex(name,cd); + } + + return cd.acepted; +} + +bool YACReaderTreeSearch::filterAcceptsRow ( int sourceRow, const QModelIndex & sourceParent ) const +{ + QString name = sourceModel()->data(sourceModel()->index(sourceRow, 0, sourceParent),Qt::DisplayRole ).toString(); + QFileSystemModel * dm = (QFileSystemModel *)sourceModel(); + if(!dm->isDir(dm->index(sourceRow, 0, sourceParent))) + //if(name.endsWith(".jpg")||name.endsWith(".db")) //TODO: if is not a dir + return false; + if(filterRegExp().isEmpty()) + return true; + if(name.contains("yacreader")) + return true; + QString path = dm->filePath(dm->index(sourceRow, 0, sourceParent)); + if(path.contains("yacreaderlibrary")) + return itemMatchesExpression(sourceModel()->index(sourceRow, 0, sourceParent), filterRegExp()); + else + return true; +} + +void YACReaderTreeSearch::reset() +{ + //invalidateFilter(); + cache->clear(); +} + +void YACReaderTreeSearch::softReset() +{ + //invalidateFilter(); +} + +ModelIndexCache::ModelIndexCache() +:cache() +{ +} + +void ModelIndexCache::setModelIndex(const QString & index, const CacheData & cd) +{ + cache.insert(index,cd); +} +ModelIndexCache::CacheData ModelIndexCache::getCacheData(const QString & index) const +{ + if(cache.contains(index)) + return cache.value(index); + else + { + CacheData cd; + cd.visited = false; + cd.acepted = true; + return cd; + } +} + +void ModelIndexCache::clear() +{ + cache.clear(); +} + +YACReaderSortComics::YACReaderSortComics(QObject * parent) +:QSortFilterProxyModel(parent) +{ + +} + +bool YACReaderSortComics::lessThan(const QModelIndex &left, const QModelIndex &right) const +{ + QVariant leftData = sourceModel()->data(left); + QVariant rightData = sourceModel()->data(right); + + QString leftString = leftData.toString(); + QString rightString = rightData.toString(); + + return naturalSortLessThanCI(leftString,rightString); +} diff --git a/common/custom_widgets.h b/common/custom_widgets.h new file mode 100644 index 00000000..404d040a --- /dev/null +++ b/common/custom_widgets.h @@ -0,0 +1,138 @@ +#ifndef __CUSTOM_WIDGETS_H +#define __CUSTOM_WIDGETS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include "pictureflow.h" + +class QToolBarStretch : public QWidget +{ +public: + QToolBarStretch(QWidget * parent=0):QWidget(parent) + { + QHBoxLayout * l= new QHBoxLayout(); + l->addStretch(); + setLayout(l); + } +}; + +class HelpAboutDialog : public QDialog +{ +Q_OBJECT +public: + HelpAboutDialog(QWidget * parent=0); + HelpAboutDialog(const QString & pathAbout,const QString & pathHelp,QWidget * parent =0); +public slots: + void loadAboutInformation(const QString & path); + void loadHelp(const QString & path); + +private: + QTabWidget *tabWidget; + QTextBrowser *aboutText; + QTextBrowser *helpText; + QString fileToString(const QString & path); +}; + +class YACReaderIconProvider : public QFileIconProvider +{ +public: + YACReaderIconProvider(); + virtual QIcon icon ( IconType type ) const; + virtual QIcon icon ( const QFileInfo & info ) const; + virtual QString type ( const QFileInfo & info ) const; +}; + +class YACReaderFlow : public PictureFlow +{ +Q_OBJECT +public: + YACReaderFlow(QWidget * parent,FlowType flowType = CoverFlowLike); +protected: + void mousePressEvent(QMouseEvent* event); + void mouseDoubleClickEvent(QMouseEvent* event); + +signals: + void selected(unsigned int centerIndex); +}; + +class YACReaderComicDirModel : public QDirModel +{ +Q_OBJECT + public: + YACReaderComicDirModel( const QStringList & nameFilters, QDir::Filters filters, QDir::SortFlags sort, QObject * parent = 0 ); + QString fileName ( const QModelIndex & index ) const; + QFileInfo fileInfo ( const QModelIndex & index ) const; +}; + +class YACReaderComicViewDelegate : public QItemDelegate +{ + Q_OBJECT + public: + YACReaderComicViewDelegate(QObject * parent = 0); + virtual void paint ( QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index ) const; + QRect textLayoutBounds(const QStyleOptionViewItemV2 &option) const; +}; + +class ModelIndexCache +{ + +public: + struct CacheData{ + bool visited; + bool acepted; + }; + ModelIndexCache(); + void setModelIndex(const QString & index, const CacheData & cd); + CacheData getCacheData(const QString & index) const; + void clear(); + + +private: + QHash cache; +}; + + +class YACReaderTreeSearch : public QSortFilterProxyModel +{ + Q_OBJECT + public: + YACReaderTreeSearch(QObject * parent = 0); +protected: + virtual bool filterAcceptsRow ( int sourceRow, const QModelIndex & source_parent ) const; + bool itemMatchesExpression(const QModelIndex &index, const QRegExp &exp) const; + bool containsFiles(QString path,const QRegExp &exp) const; + public slots: + void reset(); + void softReset(); +private: + ModelIndexCache * cache; +}; + +class YACReaderSortComics : public QSortFilterProxyModel +{ + Q_OBJECT + public: + YACReaderSortComics(QObject * parent = 0); + protected: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; +}; + +void delTree(QDir dir); +#endif + diff --git a/common/pictureflow.cpp b/common/pictureflow.cpp new file mode 100644 index 00000000..21bf4dcf --- /dev/null +++ b/common/pictureflow.cpp @@ -0,0 +1,1298 @@ +/* + PictureFlow - animated image show widget + http://pictureflow.googlecode.com + + Copyright (C) 2008 Ariya Hidayat (ariya@kde.org) + Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#include "pictureflow.h" + +// detect Qt version +#if QT_VERSION >= 0x040000 +#define PICTUREFLOW_QT4 +#elif QT_VERSION >= 0x030000 +#define PICTUREFLOW_QT3 +#elif QT_VERSION >= 235 +#define PICTUREFLOW_QT2 +#else +#error PictureFlow widgets need Qt 2, Qt 3 or Qt 4 +#endif + +#ifdef PICTUREFLOW_QT4 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef PICTUREFLOW_QT3 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define qMax(x,y) ((x) > (y)) ? (x) : (y) +#define qMin(x,y) ((x) < (y)) ? (x) : (y) + +#define QVector QValueVector + +#define toImage convertToImage +#define contains find +#define modifiers state +#define ControlModifier ControlButton +#endif + +#ifdef PICTUREFLOW_QT2 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define qMax(x,y) ((x) > (y)) ? (x) : (y) +#define qMin(x,y) ((x) < (y)) ? (x) : (y) + +#define QVector QArray + +#define toImage convertToImage +#define contains find +#define modifiers state +#define ControlModifier ControlButton +#define flush flushX +#endif + +// for fixed-point arithmetic, we need minimum 32-bit long +// long long (64-bit) might be useful for multiplication and division +typedef long PFreal; +#define PFREAL_SHIFT 10 +#define PFREAL_ONE (1 << PFREAL_SHIFT) + +#define IANGLE_MAX 1024 +#define IANGLE_MASK 1023 + +inline PFreal fmul(PFreal a, PFreal b) +{ + return ((long long)(a))*((long long)(b)) >> PFREAL_SHIFT; +} + +inline PFreal fdiv(PFreal num, PFreal den) +{ + long long p = (long long)(num) << (PFREAL_SHIFT*2); + long long q = p / (long long)den; + long long r = q >> PFREAL_SHIFT; + + return r; +} + +inline PFreal fsin(int iangle) +{ + // warning: regenerate the table if IANGLE_MAX and PFREAL_SHIFT are changed! + static const PFreal tab[] = { + 3, 103, 202, 300, 394, 485, 571, 652, + 726, 793, 853, 904, 947, 980, 1004, 1019, + 1023, 1018, 1003, 978, 944, 901, 849, 789, + 721, 647, 566, 479, 388, 294, 196, 97, + -4, -104, -203, -301, -395, -486, -572, -653, + -727, -794, -854, -905, -948, -981, -1005, -1020, + -1024, -1019, -1004, -979, -945, -902, -850, -790, + -722, -648, -567, -480, -389, -295, -197, -98, + 3 + }; + + while(iangle < 0) + iangle += IANGLE_MAX; + iangle &= IANGLE_MASK; + + int i = (iangle >> 4); + PFreal p = tab[i]; + PFreal q = tab[(i+1)]; + PFreal g = (q - p); + return p + g * (iangle-i*16)/16; +} + +inline PFreal fcos(int iangle) +{ + return fsin(iangle + (IANGLE_MAX >> 2)); +} + +/* ---------------------------------------------------------- + +PictureFlowState stores the state of all slides, i.e. all the necessary +information to be able to render them. + +PictureFlowAnimator is responsible to move the slides during the +transition between slides, to achieve the effect similar to Cover Flow, +by changing the state. + +PictureFlowSoftwareRenderer (or PictureFlowOpenGLRenderer) is +the actual 3-d renderer. It should render all slides given the state +(an instance of PictureFlowState). + +Instances of all the above three classes are stored in +PictureFlowPrivate. + +------------------------------------------------------- */ + +struct SlideInfo +{ + int slideIndex; + int angle; + PFreal cx; + PFreal cy; + int blend; +}; + +class PictureFlowState +{ +public: + PictureFlowState(int angle=50, float spacingRatio=0); + ~PictureFlowState(); + + void reposition(); + void reset(); + + QRgb backgroundColor; + int slideWidth; + int slideHeight; + PictureFlow::ReflectionEffect reflectionEffect; + QVector slideImages; + + QVector marks; + bool showMarks; + QImage mark; + + int angle; + int rawAngle; + int spacing; + float spacingRatio; + PFreal offsetX; + PFreal offsetY; + + SlideInfo centerSlide; + QVector leftSlides; + QVector rightSlides; + int centerIndex; +}; + +class PictureFlowAnimator +{ +public: + PictureFlowAnimator(); + PictureFlowState* state; + + void start(int slide); + void stop(int slide); + void update(); + + int target; + int step; + int frame; + QTimer animateTimer; +}; + +class PictureFlowAbstractRenderer +{ +public: + PictureFlowAbstractRenderer(): state(0), dirty(false), widget(0) {} + virtual ~PictureFlowAbstractRenderer() {} + + PictureFlowState* state; + bool dirty; + QWidget* widget; + + virtual void init() = 0; + virtual void paint() = 0; +}; + +class PictureFlowSoftwareRenderer: public PictureFlowAbstractRenderer +{ +public: + PictureFlowSoftwareRenderer(); + ~PictureFlowSoftwareRenderer(); + + virtual void init(); + virtual void paint(); + void render(); + + +private: + QSize size; + QRgb bgcolor; + int effect; + QImage buffer; + QVector rays; + QImage* blankSurface; +#ifdef PICTUREFLOW_QT4 + QCache surfaceCache; + QHash imageHash; +#endif +#ifdef PICTUREFLOW_QT3 + QCache surfaceCache; + QMap imageHash; +#endif +#ifdef PICTUREFLOW_QT2 + QCache surfaceCache; + QIntDict imageHash; +#endif + + + void renderSlides(); + QRect renderSlide(const SlideInfo &slide, int col1 = -1, int col2 = -1); + QImage* surface(int slideIndex); +}; + +// ------------- PictureFlowState --------------------------------------- + +PictureFlowState::PictureFlowState(int a, float sr): +backgroundColor(0), slideWidth(150), slideHeight(200), +reflectionEffect(PictureFlow::BlurredReflection), centerIndex(0) , rawAngle(a), spacingRatio(sr) +{ +} + +PictureFlowState::~PictureFlowState() +{ + for(int i = 0; i < (int)slideImages.count(); i++) + delete slideImages[i]; +} + +// readjust the settings, call this when slide dimension is changed +void PictureFlowState::reposition() +{ + // angle = 70 * IANGLE_MAX / 360; // approx. 70 degrees tilted + angle = rawAngle * IANGLE_MAX / 360; + offsetX = slideWidth/2 * (PFREAL_ONE-fcos(angle)); + offsetY = slideWidth/2 * fsin(angle); + offsetX += slideWidth * PFREAL_ONE; + offsetY += slideWidth * PFREAL_ONE / 3; + if(rawAngle < 45) + offsetX += offsetX/4; + if(angle>0) + spacing = slideWidth * 0.35; + else + spacing = slideWidth*spacingRatio + slideWidth*(spacingRatio?0.10:0.2); +} + +// adjust slides so that they are in "steady state" position +void PictureFlowState::reset() +{ + centerSlide.angle = 0; + centerSlide.cx = 0; + centerSlide.cy = 0; + centerSlide.slideIndex = centerIndex; + centerSlide.blend = 256; + + if(angle == 0 && spacingRatio) + leftSlides.resize(4); + else + leftSlides.resize(6); + for(int i = 0; i < (int)leftSlides.count(); i++) + { + SlideInfo& si = leftSlides[i]; + si.angle = angle; + si.cx = -(offsetX + spacing*(i)*PFREAL_ONE); + si.cy = offsetY; + si.slideIndex = centerIndex-1-i; + si.blend = 200; + if(i == (int)leftSlides.count()-2) + si.blend = 128; + if(i == (int)leftSlides.count()-1) + si.blend = 0; + } + if(angle==0 && spacingRatio) + rightSlides.resize(4); + else + rightSlides.resize(6); + for(int i = 0; i < (int)rightSlides.count(); i++) + { + SlideInfo& si = rightSlides[i]; + si.angle = -angle; + si.cx = offsetX + spacing*(i)*PFREAL_ONE; + si.cy = offsetY; + si.slideIndex = centerIndex+1+i; + si.blend = 200; + if(i == (int)rightSlides.count()-2) + si.blend = 128; + if(i == (int)rightSlides.count()-1) + si.blend = 0; + } +} + +// ------------- PictureFlowAnimator --------------------------------------- + +PictureFlowAnimator::PictureFlowAnimator(): +state(0), target(0), step(0), frame(0) +{ +} + +void PictureFlowAnimator::start(int slide) +{ + target = slide; + if(!animateTimer.isActive() && state) + { + step = (target < state->centerSlide.slideIndex) ? -1 : 1; + animateTimer.start(30); + } +} + +void PictureFlowAnimator::stop(int slide) +{ + step = 0; + target = slide; + frame = slide << 16; + animateTimer.stop(); +} + +void PictureFlowAnimator::update() +{ + if(!animateTimer.isActive()) + return; + if(step == 0) + return; + if(!state) + return; + + int speed = 16384/4; + +#if 1 + // deaccelerate when approaching the target + const int max = 2 * 65536; + + int fi = frame; + fi -= (target << 16); + if(fi < 0) + fi = -fi; + fi = qMin(fi, max); + + int ia = IANGLE_MAX * (fi-max/2) / (max*2); + speed = 512 + 16384 * (PFREAL_ONE+fsin(ia))/PFREAL_ONE; +#endif + + frame += speed*step; + + int index = frame >> 16; + int pos = frame & 0xffff; + int neg = 65536 - pos; + int tick = (step < 0) ? neg : pos; + PFreal ftick = (tick * PFREAL_ONE) >> 16; + + if(step < 0) + index++; + + if(state->centerIndex != index) + { + state->centerIndex = index; + frame = index << 16; + state->centerSlide.slideIndex = state->centerIndex; + for(int i = 0; i < (int)state->leftSlides.count(); i++) + state->leftSlides[i].slideIndex = state->centerIndex-1-i; + for(int i = 0; i < (int)state->rightSlides.count(); i++) + state->rightSlides[i].slideIndex = state->centerIndex+1+i; + } + + state->centerSlide.angle = (step * tick * state->angle) >> 16; + state->centerSlide.cx = -step * fmul(state->offsetX, ftick); + state->centerSlide.cy = fmul(state->offsetY, ftick); + + if(state->centerIndex == target) + { + stop(target); + state->reset(); + return; + } + + for(int i = 0; i < (int)state->leftSlides.count(); i++) + { + SlideInfo& si = state->leftSlides[i]; + si.angle = state->angle; + si.cx = -(state->offsetX + state->spacing*(i)*PFREAL_ONE + step*state->spacing*ftick); + si.cy = state->offsetY; + } + + for(int i = 0; i < (int)state->rightSlides.count(); i++) + { + SlideInfo& si = state->rightSlides[i]; + si.angle = -state->angle; + si.cx = state->offsetX + state->spacing*(i)*PFREAL_ONE - step*state->spacing*ftick; + si.cy = state->offsetY; + } + + if(step > 0) + { + PFreal ftick = (neg * PFREAL_ONE) >> 16; + state->rightSlides[0].angle = -(neg * state->angle) >> 16; + state->rightSlides[0].cx = fmul(state->offsetX, ftick); + state->rightSlides[0].cy = fmul(state->offsetY, ftick); + } + else + { + PFreal ftick = (pos * PFREAL_ONE) >> 16; + state->leftSlides[0].angle = (pos * state->angle) >> 16; + state->leftSlides[0].cx = -fmul(state->offsetX, ftick); + state->leftSlides[0].cy = fmul(state->offsetY, ftick); + } + + // must change direction ? + if(target < index) if(step > 0) + step = -1; + if(target > index) if(step < 0) + step = 1; + + // the first and last slide must fade in/fade out + int nleft = state->leftSlides.count(); + int nright = state->rightSlides.count(); + int fade = pos / 256; + + for(int index = 0; index < nleft; index++) + { + int blend = 200; + if(index == nleft-1) + blend = (step > 0) ? 0 : 128-fade/2; + if(index == nleft-2) + blend = (step > 0) ? 128-fade/2 : 200-(0.5625*fade/2); + if(index == nleft-3) + blend = (step > 0) ? 200-(0.5625*fade/2) : 200; + if(index == 0) + blend = (step > 0) ? 200 : 200 + 56-(0.4375*fade/2) ; + state->leftSlides[index].blend = blend; + } + for(int index = 0; index < nright; index++) + { + int blend = (index < nright-2) ? 200 : 128; + if(index == nright-1) + blend = (step > 0) ? fade/2 : 0; + if(index == nright-2) + blend = (step > 0) ? 128+(0.5625*fade/2) : (0.5625*fade/2); + if(index == nright-3) + blend = (step > 0) ? 200 : 128+(0.5625*fade/2); + if(index == 0) + blend = (step > 0) ? 200 + (0.4375*fade/2) : 200; + state->rightSlides[index].blend = blend; + } + + state->centerSlide.blend = (step > 0) ? 256 - (0.4375*fade/2) : 200 + (0.4375*fade/2); + +} + +// ------------- PictureFlowSoftwareRenderer --------------------------------------- + +PictureFlowSoftwareRenderer::PictureFlowSoftwareRenderer(): +PictureFlowAbstractRenderer(), size(0,0), bgcolor(0), effect(-1), blankSurface(0) +{ +#ifdef PICTUREFLOW_QT3 + surfaceCache.setAutoDelete(true); +#endif +} + +PictureFlowSoftwareRenderer::~PictureFlowSoftwareRenderer() +{ + surfaceCache.clear(); + buffer = QImage(); + delete blankSurface; +} + +void PictureFlowSoftwareRenderer::paint() +{ + if(!widget) + return; + + if(widget->size() != size) + init(); + + if(state->backgroundColor != bgcolor) + { + bgcolor = state->backgroundColor; + surfaceCache.clear(); + } + + if((int)(state->reflectionEffect) != effect) + { + effect = (int)state->reflectionEffect; + surfaceCache.clear(); + } + + if(dirty) + render(); + + QPainter painter(widget); + painter.drawImage(QPoint(0,0), buffer); +} + +void PictureFlowSoftwareRenderer::init() +{ + if(!widget) + return; + + surfaceCache.clear(); + blankSurface = 0; + + size = widget->size(); + int ww = size.width(); + int wh = size.height(); + int w = (ww+1)/2; + int h = (wh+1)/2; + +#ifdef PICTUREFLOW_QT4 + buffer = QImage(ww, wh, QImage::Format_RGB32); +#endif +#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) + buffer.create(ww, wh, 32); +#endif + buffer.fill(bgcolor); + + rays.resize(w*2); + for(int i = 0; i < w; i++) + { + PFreal gg = ((PFREAL_ONE >> 1) + i * PFREAL_ONE) / (2*h); + rays[w-i-1] = -gg; + rays[w+i] = gg; + } + + dirty = true; +} + +// TODO: optimize this with lookup tables +static QRgb blendColor(QRgb c1, QRgb c2, int blend) +{ + int r = qRed(c1) * blend/256 + qRed(c2)*(256-blend)/256; + int g = qGreen(c1) * blend/256 + qGreen(c2)*(256-blend)/256; + int b = qBlue(c1) * blend/256 + qBlue(c2)*(256-blend)/256; + return qRgb(r, g, b); +} + + +static QImage* prepareSurface(const QImage* slideImage, int w, int h, QRgb bgcolor, +PictureFlow::ReflectionEffect reflectionEffect) +{ + + int iw,ih; + iw = slideImage->width(); + ih = slideImage->height(); + int psw,psh; + if(iw>ih) + { + psw = w; + psh = w * (1.0*ih/iw); + } + else + { + int h1=h; + psw = h1 * (1.0*iw/ih); + psh = h1; + + while(psw>w) + { + h1-=2; + psw = h1 * (1.0*iw/ih); + psh = h1; + } + } + w = psw; + +#ifdef PICTUREFLOW_QT4 + Qt::TransformationMode mode = Qt::SmoothTransformation; + QImage img = slideImage->scaled(psw, psh, Qt::IgnoreAspectRatio, mode); +#endif +#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) + QImage img = slideImage->smoothScale(w, h); +#endif + + // slightly larger, to accomodate for the reflection + int hs = h * 2; + int hofs = h / 3; + + // offscreen buffer: black is sweet +#ifdef PICTUREFLOW_QT4 + QImage* result = new QImage(hs, w, QImage::Format_RGB32); +#endif +#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) + QImage* result = new QImage; + result->create(hs, w, 32); +#endif + result->fill(bgcolor); + + // transpose the image, this is to speed-up the rendering + // because we process one column at a time + // (and much better and faster to work row-wise, i.e in one scanline) + int lhof = (h-psh); + //int lwof = (w-psw)/2; + for(int x = 0; x < psw; x++) + for(int y = 0; y < psh; y++) + + result->setPixel(hofs + y + lhof , x, img.pixel(x, y)); + + if(reflectionEffect != PictureFlow::NoReflection) + { + // create the reflection + int ht = hs - (h+hofs); + int hte = ht; + for(int x = 0; x < psw; x++) + for(int y = 0; y < ht; y++) + { + QRgb color; + if(ysetPixel(h+hofs + y, x,blendColor(color,bgcolor,80*(hte-y)/hte)); + } + } + + return result; +} + +QImage* PictureFlowSoftwareRenderer::surface(int slideIndex) +{ + if(!state) + return 0; + if(slideIndex < 0) + return 0; + if(slideIndex >= (int)state->slideImages.count()) + return 0; + +#ifdef PICTUREFLOW_QT4 + int key = slideIndex; +#endif +#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) + QString key = QString::number(slideIndex); +#endif + + QImage* img = state->slideImages.at(slideIndex); + + bool empty = img ? img->isNull() : true; + if(empty) + { + surfaceCache.remove(key); + imageHash.remove(slideIndex); + if(!blankSurface) + { + int sw = state->slideWidth; + int sh = state->slideHeight; + +#ifdef PICTUREFLOW_QT4 + QImage img = QImage(sw, sh, QImage::Format_RGB32); + + QPainter painter(&img); + QPoint p1(sw*4/10, 0); + QPoint p2(sw*6/10, sh); + QLinearGradient linearGrad(p1, p2); + linearGrad.setColorAt(0, Qt::black); + linearGrad.setColorAt(1, Qt::white); + painter.setBrush(linearGrad); + painter.fillRect(0, 0, sw, sh, QBrush(linearGrad)); + + + painter.end(); +#endif +#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) + QPixmap pixmap(sw, sh, 32); + QPainter painter(&pixmap); + painter.fillRect(pixmap.rect(), QColor(192,192,192)); + painter.fillRect(5, 5, sw-10, sh-10, QColor(64,64,64)); + painter.end(); + QImage img = pixmap.convertToImage(); +#endif + + blankSurface = prepareSurface(&img, sw, sh, bgcolor, state->reflectionEffect); + } + return blankSurface; + } + +#ifdef PICTUREFLOW_QT4 + bool exist = imageHash.contains(slideIndex); + if(exist) + if(img == imageHash.find(slideIndex).value()) +#endif +#ifdef PICTUREFLOW_QT3 + bool exist = imageHash.find(slideIndex) != imageHash.end(); + if(exist) + if(img == imageHash.find(slideIndex).data()) +#endif +#ifdef PICTUREFLOW_QT2 + if(img == imageHash[slideIndex]) +#endif + if(surfaceCache.contains(key)) + return surfaceCache[key]; + + + QImage* sr = prepareSurface(img, state->slideWidth, state->slideHeight, bgcolor, state->reflectionEffect); + //check if this slide must be marked + //if(marks[slideIndex]) + if(state->showMarks) + { + if(state->marks[slideIndex]) + { + QPainter painter(sr); + painter.setPen(QColor(255,0,0).rgb()); + int sh = sr->height(); + int jInit = sh*4/5; + int iInit = state->slideHeight+state->slideHeight/3; + /*for(int j = jInit; j < sh; j ++) + { + for(int i = iInit-(j-jInit); i < iInit; i ++) + { + + painter.drawPoint(i,j); + } + }*/ + painter.drawImage(QRect(iInit-(sh-jInit),jInit,sh-jInit,sh-jInit),state->mark); + } + } + surfaceCache.insert(key, sr); + imageHash.insert(slideIndex, img); + + return sr; +} + +// Renders a slide to offscreen buffer. Returns a rect of the rendered area. +// col1 and col2 limit the column for rendering. +QRect PictureFlowSoftwareRenderer::renderSlide(const SlideInfo &slide, int col1, int col2) +{ + int blend = slide.blend; + if(!blend) + return QRect(); + + QImage* src = surface(slide.slideIndex); + if(!src) + return QRect(); + + QRect rect(0, 0, 0, 0); + + int sw = src->height(); + int sh = src->width(); + int h = buffer.height(); + int w = buffer.width(); + + if(col1 > col2) + { + int c = col2; + col2 = col1; + col1 = c; + } + + col1 = (col1 >= 0) ? col1 : 0; + col2 = (col2 >= 0) ? col2 : w-1; + col1 = qMin(col1, w-1); + col2 = qMin(col2, w-1); + + int zoom = 100; + int distance = h * 100 / zoom; + PFreal sdx = fcos(slide.angle); + PFreal sdy = fsin(slide.angle); + PFreal xs = slide.cx - state->slideWidth * sdx/2; + PFreal ys = slide.cy - state->slideWidth * sdy/2; + PFreal dist = distance * PFREAL_ONE; + + int xi = qMax((PFreal)0, (w*PFREAL_ONE/2) + fdiv(xs*h, dist+ys) >> PFREAL_SHIFT); + if(xi >= w) + return rect; + + bool flag = false; + rect.setLeft(xi); + for(int x = qMax(xi, col1); x <= col2; x++) + { + PFreal hity = 0; + PFreal fk = rays[x]; + if(sdy) + { + fk = fk - fdiv(sdx,sdy); + hity = -fdiv((rays[x]*distance - slide.cx + slide.cy*sdx/sdy), fk); + } + + dist = distance*PFREAL_ONE + hity; + if(dist < 0) + continue; + + PFreal hitx = fmul(dist, rays[x]); + PFreal hitdist = fdiv(hitx - slide.cx, sdx); + + int column = sw/2 + (hitdist >> PFREAL_SHIFT); + if(column >= sw) + break; + if(column < 0) + continue; + + rect.setRight(x); + if(!flag) + rect.setLeft(x); + flag = true; + + int y1 = h/2; + int y2 = y1+ 1; + QRgb* pixel1 = (QRgb*)(buffer.scanLine(y1)) + x; + QRgb* pixel2 = (QRgb*)(buffer.scanLine(y2)) + x; + QRgb pixelstep = pixel2 - pixel1; + + int center = (sh/2); + int dy = dist / h; + int p1 = center*PFREAL_ONE - dy/2; + int p2 = center*PFREAL_ONE + dy/2; + + const QRgb *ptr = (const QRgb*)(src->scanLine(column)); + if(blend == 256) + while((y1 >= 0) && (y2 < h) && (p1 >= 0)) + { + *pixel1 = ptr[p1 >> PFREAL_SHIFT]; + *pixel2 = ptr[p2 >> PFREAL_SHIFT]; + p1 -= dy; + p2 += dy; + y1--; + y2++; + pixel1 -= pixelstep; + pixel2 += pixelstep; + } + else + while((y1 >= 0) && (y2 < h) && (p1 >= 0)) + { + QRgb c1 = ptr[p1 >> PFREAL_SHIFT]; + QRgb c2 = ptr[p2 >> PFREAL_SHIFT]; + *pixel1 = blendColor(c1, bgcolor, blend); + *pixel2 = blendColor(c2, bgcolor, blend); + p1 -= dy; + p2 += dy; + y1--; + y2++; + pixel1 -= pixelstep; + pixel2 += pixelstep; + } + } + + rect.setTop(0); + rect.setBottom(h-1); + return rect; +} + +void PictureFlowSoftwareRenderer::renderSlides() +{ + int nleft = state->leftSlides.count(); + int nright = state->rightSlides.count(); + + QRect r = renderSlide(state->centerSlide); + int c1 = r.left(); + int c2 = r.right(); + + for(int index = 0; index < nleft; index++) + { + QRect rs = renderSlide(state->leftSlides[index], 0, c1-1); + if(!rs.isEmpty()) + c1 = rs.left(); + } + for(int index = 0; index < nright; index++) + { + QRect rs = renderSlide(state->rightSlides[index], c2+1, buffer.width()); + if(!rs.isEmpty()) + c2 = rs.right(); + } +} + +// Render the slides. Updates only the offscreen buffer. +void PictureFlowSoftwareRenderer::render() +{ + buffer.fill(state->backgroundColor); + renderSlides(); + if(state->slideImages.size()>0) + { + int x = buffer.width()/2; + int size = buffer.width() * 0.021; + int start = buffer.width() * 0.010; + for(int j = start; jbackgroundColor); + for(int i = 0; ibackgroundColor); + buffer.setPixel(QPoint(x+i,j),QColor(255,255,255).rgb()-state->backgroundColor); + } + } + + QPainter painter(&buffer); + painter.setPen(QColor(255,255,255).rgb()-state->backgroundColor); + painter.setFont(QFont("Arial", start+size*0.5)); + painter.drawText(start , start+size, QString().setNum(state->centerIndex+1)+"/"+QString().setNum(state->slideImages.size())); + } + dirty = false; +} + +// ----------------------------------------- + +class PictureFlowPrivate +{ +public: + PictureFlowState* state; + PictureFlowAnimator* animator; + PictureFlowAbstractRenderer* renderer; + QTimer triggerTimer; +}; + + +PictureFlow::PictureFlow(QWidget* parent,FlowType flowType): QWidget(parent) +{ + d = new PictureFlowPrivate; + + switch(flowType){ + case CoverFlowLike: + d->state = new PictureFlowState(50,0); + break; + case Strip: + d->state = new PictureFlowState(0,1); + break; + case StripOverlapped: + d->state = new PictureFlowState(0,0); + break; + } + + d->state->reset(); + d->state->reposition(); + + d->renderer = new PictureFlowSoftwareRenderer; + d->renderer->state = d->state; + d->renderer->widget = this; + d->renderer->init(); + + d->animator = new PictureFlowAnimator; + d->animator->state = d->state; + QObject::connect(&d->animator->animateTimer, SIGNAL(timeout()), this, SLOT(updateAnimation())); + + QObject::connect(&d->triggerTimer, SIGNAL(timeout()), this, SLOT(render())); + +#ifdef PICTUREFLOW_QT4 + setAttribute(Qt::WA_StaticContents, true); + setAttribute(Qt::WA_OpaquePaintEvent, true); + setAttribute(Qt::WA_NoSystemBackground, true); +#endif +#ifdef PICTUREFLOW_QT3 + setWFlags(getWFlags() | Qt::WStaticContents); + setWFlags(getWFlags() | Qt::WNoAutoErase); +#endif +#ifdef PICTUREFLOW_QT2 + setWFlags(getWFlags() | Qt::WPaintClever); + setWFlags(getWFlags() | Qt::WRepaintNoErase); + setWFlags(getWFlags() | Qt::WResizeNoErase); +#endif +} + +PictureFlow::~PictureFlow() +{ + delete d->renderer; + delete d->animator; + delete d->state; + delete d; +} + +int PictureFlow::slideCount() const +{ + return d->state->slideImages.count(); +} + +QColor PictureFlow::backgroundColor() const +{ + return QColor(d->state->backgroundColor); +} + +void PictureFlow::setBackgroundColor(const QColor& c) +{ + d->state->backgroundColor = c.rgb(); + triggerRender(); +} + +QSize PictureFlow::slideSize() const +{ + return QSize(d->state->slideWidth, d->state->slideHeight); +} + +void PictureFlow::setSlideSize(QSize size) +{ + d->state->slideWidth = size.width(); + d->state->slideHeight = size.height(); + d->state->reposition(); + triggerRender(); +} + +PictureFlow::ReflectionEffect PictureFlow::reflectionEffect() const +{ + return d->state->reflectionEffect; +} + +void PictureFlow::setReflectionEffect(ReflectionEffect effect) +{ + d->state->reflectionEffect = effect; + triggerRender(); +} + +QImage PictureFlow::slide(int index) const +{ + QImage* i = 0; + if((index >= 0) && (index < slideCount())) + i = d->state->slideImages[index]; + return i ? QImage(*i) : QImage(); +} + +void PictureFlow::addSlide(const QImage& image) +{ + int c = d->state->slideImages.count(); + d->state->slideImages.resize(c+1); + d->state->slideImages[c] = new QImage(image); + d->state->marks.resize(c+1); + d->state->marks[c] = false; + triggerRender(); +} + +void PictureFlow::addSlide(const QPixmap& pixmap) +{ + addSlide(pixmap.toImage()); +} + +void PictureFlow::setSlide(int index, const QImage& image) +{ + if((index >= 0) && (index < slideCount())) + { + QImage* i = image.isNull() ? 0 : new QImage(image); + delete d->state->slideImages[index]; + d->state->slideImages[index] = i; + triggerRender(); + } +} + +void PictureFlow::setSlide(int index, const QPixmap& pixmap) +{ + setSlide(index, pixmap.toImage()); +} + +int PictureFlow::centerIndex() const +{ + return d->state->centerIndex; +} + +void PictureFlow::setCenterIndex(int index) +{ + index = qMin(index, slideCount()-1); + index = qMax(index, 0); + d->state->centerIndex = index; + d->state->reset(); + d->animator->stop(index); + triggerRender(); +} + +void PictureFlow::clear() +{ + int c = d->state->slideImages.count(); + for(int i = 0; i < c; i++) + delete d->state->slideImages[i]; + d->state->slideImages.resize(0); + + d->state->marks.resize(0); + + d->state->reset(); + triggerRender(); +} + +void PictureFlow::render() +{ + d->renderer->dirty = true; + update(); +} + +void PictureFlow::triggerRender() +{ +#ifdef PICTUREFLOW_QT4 + d->triggerTimer.setSingleShot(true); + d->triggerTimer.start(0); +#endif +#if defined(PICTUREFLOW_QT3) || defined(PICTUREFLOW_QT2) + d->triggerTimer.start(0, true); +#endif +} + +void PictureFlow::showPrevious() +{ + int step = d->animator->step; + int center = d->state->centerIndex; + + if(step > 0) + d->animator->start(center); + + if(step == 0) + if(center > 0) + d->animator->start(center - 1); + + if(step < 0) + d->animator->target = qMax(0, center - 2); +} + +void PictureFlow::showNext() +{ + int step = d->animator->step; + int center = d->state->centerIndex; + + if(step < 0) + d->animator->start(center); + + if(step == 0) + if(center < slideCount()-1) + d->animator->start(center + 1); + + if(step > 0) + d->animator->target = qMin(center + 2, slideCount()-1); +} + +void PictureFlow::showSlide(int index) +{ + index = qMax(index, 0); + index = qMin(slideCount()-1, index); + if(index == d->state->centerSlide.slideIndex) + return; + + d->animator->start(index); +} + +void PictureFlow::keyPressEvent(QKeyEvent* event) +{ + if(event->key() == Qt::Key_Left) + { + if(event->modifiers() == Qt::ControlModifier) + showSlide(centerIndex()-10); + else + showPrevious(); + event->accept(); + return; + } + + if(event->key() == Qt::Key_Right) + { + if(event->modifiers() == Qt::ControlModifier) + showSlide(centerIndex()+10); + else + showNext(); + event->accept(); + return; + } + + event->ignore(); +} + +void PictureFlow::mousePressEvent(QMouseEvent* event) +{ + if(event->x() > width()/2) + showNext(); + else + showPrevious(); +} + +void PictureFlow::paintEvent(QPaintEvent* event) +{ + Q_UNUSED(event); + d->renderer->paint(); +} + +void PictureFlow::resizeEvent(QResizeEvent* event) +{ + triggerRender(); + QWidget::resizeEvent(event); +} + +void PictureFlow::updateAnimation() +{ + int old_center = d->state->centerIndex; + d->animator->update(); + triggerRender(); + if(d->state->centerIndex != old_center) + emit centerIndexChanged(d->state->centerIndex); +} + +void PictureFlow::setFlowType(FlowType flowType) +{ + switch(flowType){ + case CoverFlowLike: + d->state->rawAngle = 50; + d->state->spacingRatio = 0, + d->state->reposition(); + break; + case Strip: + d->state->rawAngle = 0; + d->state->spacingRatio = 1; + d->state->reposition(); + break; + case StripOverlapped: + d->state->rawAngle = 0; + d->state->spacingRatio = 0; + d->state->reposition(); + break; + } + d->state->reset(); + d->renderer->init(); +} + +void PictureFlow::setMarkImage(const QImage & m) +{ + d->state->mark = m; +} + +void PictureFlow::markSlide(int index) +{ + if(indexstate->marks.size()) + d->state->marks[index] = true; +} + +void PictureFlow::updateMarks() +{ + d->renderer->init(); + d->renderer->paint(); + repaint(); +} + +void PictureFlow::unmarkSlide(int index) +{ + if(indexstate->marks.size()) + d->state->marks[index] = false; +} + +void PictureFlow::setMarks(const QVector & m) +{ + d->state->marks = m; + updateMarks(); +} + +void PictureFlow::setShowMarks(bool enable) +{ + d->state->showMarks = enable; + updateMarks(); +} + +QVector PictureFlow::getMarks() +{ + return d->state->marks; +} + diff --git a/common/pictureflow.h b/common/pictureflow.h new file mode 100644 index 00000000..f1c75db0 --- /dev/null +++ b/common/pictureflow.h @@ -0,0 +1,223 @@ +/* + PictureFlow - animated image show widget + http://pictureflow.googlecode.com + + Copyright (C) 2008 Ariya Hidayat (ariya@kde.org) + Copyright (C) 2007 Ariya Hidayat (ariya@kde.org) + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef PICTUREFLOW_H +#define PICTUREFLOW_H + +#include + +class PictureFlowPrivate; + +/*! + Class PictureFlow implements an image show widget with animation effect + like Apple's CoverFlow (in iTunes and iPod). Images are arranged in form + of slides, one main slide is shown at the center with few slides on + the left and right sides of the center slide. When the next or previous + slide is brought to the front, the whole slides flow to the right or + the right with smooth animation effect; until the new slide is finally + placed at the center. + + */ +class PictureFlow : public QWidget +{ +Q_OBJECT + + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor) + Q_PROPERTY(QSize slideSize READ slideSize WRITE setSlideSize) + Q_PROPERTY(int slideCount READ slideCount) + Q_PROPERTY(int centerIndex READ centerIndex WRITE setCenterIndex) + +public: + + enum ReflectionEffect + { + NoReflection, + PlainReflection, + BlurredReflection + }; + + enum FlowType + { + CoverFlowLike, + Strip, + StripOverlapped + }; + + /*! + Creates a new PictureFlow widget. + */ + PictureFlow(QWidget* parent = 0, FlowType flowType = CoverFlowLike); + + /*! + Destroys the widget. + */ + ~PictureFlow(); + + /*! + Returns the background color. + */ + QColor backgroundColor() const; + + /*! + Sets the background color. By default it is black. + */ + void setBackgroundColor(const QColor& c); + + /*! + Returns the dimension of each slide (in pixels). + */ + QSize slideSize() const; + + /*! + Sets the dimension of each slide (in pixels). + */ + void setSlideSize(QSize size); + + /*! + Returns the total number of slides. + */ + int slideCount() const; + + /*! + Returns QImage of specified slide. + */ + QImage slide(int index) const; + + /*! + Returns the index of slide currently shown in the middle of the viewport. + */ + int centerIndex() const; + + /*! + Returns the effect applied to the reflection. + */ + ReflectionEffect reflectionEffect() const; + + /*! + Sets the effect applied to the reflection. The default is PlainReflection. + */ + void setReflectionEffect(ReflectionEffect effect); + + +public slots: + + /*! + Adds a new slide. + */ + void addSlide(const QImage& image); + + /*! + Adds a new slide. + */ + void addSlide(const QPixmap& pixmap); + + /*! + Sets an image for specified slide. If the slide already exists, + it will be replaced. + */ + void setSlide(int index, const QImage& image); + + /*! + Sets a pixmap for specified slide. If the slide already exists, + it will be replaced. + */ + void setSlide(int index, const QPixmap& pixmap); + + /*! + Sets slide to be shown in the middle of the viewport. No animation + effect will be produced, unlike using showSlide. + */ + void setCenterIndex(int index); + + /*! + Clears all slides. + */ + void clear(); + + /*! + Shows previous slide using animation effect. + */ + void showPrevious(); + + /*! + Shows next slide using animation effect. + */ + void showNext(); + + /*! + Go to specified slide using animation effect. + */ + void showSlide(int index); + + /*! + Rerender the widget. Normally this function will be automatically invoked + whenever necessary, e.g. during the transition animation. + */ + void render(); + + /*! + Schedules a rendering update. Unlike render(), this function does not cause + immediate rendering. + */ + void triggerRender(); + + void setFlowType(FlowType flowType); + + void setMarkImage(const QImage & mark); + + void markSlide(int index); + + void updateMarks(); + + void unmarkSlide(int index); + + void setMarks(const QVector & marks); + + void setShowMarks(bool enable); + + QVector getMarks(); + + +signals: + void centerIndexChanged(int index); + +protected: + void paintEvent(QPaintEvent *event); + void keyPressEvent(QKeyEvent* event); + void mousePressEvent(QMouseEvent* event); + void resizeEvent(QResizeEvent* event); + +private slots: + void updateAnimation(); + +private: + PictureFlowPrivate* d; + QImage mark; + QVector marks; +}; + +#endif // PICTUREFLOW_H + diff --git a/common/qnaturalsorting.cpp b/common/qnaturalsorting.cpp new file mode 100644 index 00000000..0c60d846 --- /dev/null +++ b/common/qnaturalsorting.cpp @@ -0,0 +1,257 @@ +/* This file contains parts of the KDE libraries + Copyright (C) 1999 Ian Zepp (icszepp@islc.net) + Copyright (C) 2006 by Dominic Battre + Copyright (C) 2006 by Martin Pool + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include "qnaturalsorting.h" + +//from KDE +/* +int naturalCompare(const QString &_a, const QString &_b, Qt::CaseSensitivity caseSensitivity) +{ + // This method chops the input a and b into pieces of + // digits and non-digits (a1.05 becomes a | 1 | . | 05) + // and compares these pieces of a and b to each other + // (first with first, second with second, ...). + // + // This is based on the natural sort order code code by Martin Pool + // http://sourcefrog.net/projects/natsort/ + // Martin Pool agreed to license this under LGPL or GPL. + + // FIXME: Using toLower() to implement case insensitive comparison is + // sub-optimal, but is needed because we compare strings with + // localeAwareCompare(), which does not know about case sensitivity. + // A task has been filled for this in Qt Task Tracker with ID 205990. + // http://trolltech.com/developer/task-tracker/index_html?method=entry&id=205990 + QString a; + QString b; + if (caseSensitivity == Qt::CaseSensitive) { + a = _a; + b = _b; + } else { + a = _a.toLower(); + b = _b.toLower(); + } + + const QChar* currA = a.unicode(); // iterator over a + const QChar* currB = b.unicode(); // iterator over b + + if (currA == currB) { + return 0; + } + + const QChar* begSeqA = currA; // beginning of a new character sequence of a + const QChar* begSeqB = currB; + + while (!currA->isNull() && !currB->isNull()) { + if (currA->unicode() == QChar::ObjectReplacementCharacter) { + return 1; + } + + if (currB->unicode() == QChar::ObjectReplacementCharacter) { + return -1; + } + + if (currA->unicode() == QChar::ReplacementCharacter) { + return 1; + } + + if (currB->unicode() == QChar::ReplacementCharacter) { + return -1; + } + + // find sequence of characters ending at the first non-character + while (!currA->isNull() && !currA->isDigit() && !currA->isPunct() && !currA->isSpace()) { + ++currA; + } + + while (!currB->isNull() && !currB->isDigit() && !currB->isPunct() && !currB->isSpace()) { + ++currB; + } + + // compare these sequences + const QStringRef& subA(a.midRef(begSeqA - a.unicode(), currA - begSeqA)); + const QStringRef& subB(b.midRef(begSeqB - b.unicode(), currB - begSeqB)); + const int cmp = QStringRef::localeAwareCompare(subA, subB); + if (cmp != 0) { + return cmp < 0 ? -1 : +1; + } + + if (currA->isNull() || currB->isNull()) { + break; + } + + // find sequence of characters ending at the first non-character + while (currA->isPunct() || currA->isSpace() || currB->isPunct() || currB->isSpace()) { + if (*currA != *currB) { + return (*currA < *currB) ? -1 : +1; + } + ++currA; + ++currB; + } + + // now some digits follow... + if ((*currA == '0') || (*currB == '0')) { + // one digit-sequence starts with 0 -> assume we are in a fraction part + // do left aligned comparison (numbers are considered left aligned) + while (1) { + if (!currA->isDigit() && !currB->isDigit()) { + break; + } else if (!currA->isDigit()) { + return +1; + } else if (!currB->isDigit()) { + return -1; + } else if (*currA < *currB) { + return -1; + } else if (*currA > *currB) { + return + 1; + } + ++currA; + ++currB; + } + } else { + // No digit-sequence starts with 0 -> assume we are looking at some integer + // do right aligned comparison. + // + // The longest run of digits wins. That aside, the greatest + // value wins, but we can't know that it will until we've scanned + // both numbers to know that they have the same magnitude. + + bool isFirstRun = true; + int weight = 0; + while (1) { + if (!currA->isDigit() && !currB->isDigit()) { + if (weight != 0) { + return weight; + } + break; + } else if (!currA->isDigit()) { + if (isFirstRun) { + return *currA < *currB ? -1 : +1; + } else { + return -1; + } + } else if (!currB->isDigit()) { + if (isFirstRun) { + return *currA < *currB ? -1 : +1; + } else { + return +1; + } + } else if ((*currA < *currB) && (weight == 0)) { + weight = -1; + } else if ((*currA > *currB) && (weight == 0)) { + weight = + 1; + } + ++currA; + ++currB; + isFirstRun = false; + } + } + + begSeqA = currA; + begSeqB = currB; + } + + if (currA->isNull() && currB->isNull()) { + return 0; + } + + return currA->isNull() ? -1 : + 1; +} + +*/ +static inline QChar getNextChar(const QString &s, int location) +{ + return (location < s.length()) ? s.at(location) : QChar(); +} + +int naturalCompare(const QString &s1, const QString &s2, Qt::CaseSensitivity cs) +{ + for (int l1 = 0, l2 = 0; l1 <= s1.count() && l2 <= s2.count(); ++l1, ++l2) { + // skip spaces, tabs and 0's + QChar c1 = getNextChar(s1, l1); + while (c1.isSpace()) + c1 = getNextChar(s1, ++l1); + QChar c2 = getNextChar(s2, l2); + while (c2.isSpace()) + c2 = getNextChar(s2, ++l2); + + if (c1.isDigit() && c2.isDigit()) { + while (c1.digitValue() == 0) + c1 = getNextChar(s1, ++l1); + while (c2.digitValue() == 0) + c2 = getNextChar(s2, ++l2); + + int lookAheadLocation1 = l1; + int lookAheadLocation2 = l2; + int currentReturnValue = 0; + // find the last digit, setting currentReturnValue as we go if it isn't equal + for ( + QChar lookAhead1 = c1, lookAhead2 = c2; + (lookAheadLocation1 <= s1.length() && lookAheadLocation2 <= s2.length()); + lookAhead1 = getNextChar(s1, ++lookAheadLocation1), + lookAhead2 = getNextChar(s2, ++lookAheadLocation2) + ) { + bool is1ADigit = !lookAhead1.isNull() && lookAhead1.isDigit(); + bool is2ADigit = !lookAhead2.isNull() && lookAhead2.isDigit(); + if (!is1ADigit && !is2ADigit) + break; + if (!is1ADigit) + return -1; + if (!is2ADigit) + return 1; + if (currentReturnValue == 0) { + if (lookAhead1 < lookAhead2) { + currentReturnValue = -1; + } else if (lookAhead1 > lookAhead2) { + currentReturnValue = 1; + } + } + } + if (currentReturnValue != 0) + return currentReturnValue; + } + + if (cs == Qt::CaseInsensitive) { + if (!c1.isLower()) c1 = c1.toLower(); + if (!c2.isLower()) c2 = c2.toLower(); + } + int r = QString::localeAwareCompare(c1, c2); + if (r < 0) + return -1; + if (r > 0) + return 1; + } + // The two strings are the same (02 == 2) so fall back to the normal sort + return QString::compare(s1, s2, cs); +} +bool naturalSortLessThanCS( const QString &left, const QString &right ) +{ + return (naturalCompare( left, right, Qt::CaseSensitive ) < 0); +} + +bool naturalSortLessThanCI( const QString &left, const QString &right ) +{ + return (naturalCompare( left, right, Qt::CaseInsensitive ) < 0); +} + +bool naturalSortLessThanCIFileInfo(const QFileInfo & left,const QFileInfo & right) +{ + return naturalSortLessThanCI(left.fileName(),right.fileName()); +} diff --git a/common/qnaturalsorting.h b/common/qnaturalsorting.h new file mode 100644 index 00000000..d1eb0163 --- /dev/null +++ b/common/qnaturalsorting.h @@ -0,0 +1,13 @@ + + +#ifndef __QNATURALSORTING_H +#define __QNATURALSORTING_H + +#include +#include + +bool naturalSortLessThanCS( const QString &left, const QString &right ); +bool naturalSortLessThanCI( const QString &left, const QString &right ); +bool naturalSortLessThanCIFileInfo(const QFileInfo & left,const QFileInfo & right); + +#endif \ No newline at end of file diff --git a/files/about.html b/files/about.html new file mode 100644 index 00000000..83d38825 --- /dev/null +++ b/files/about.html @@ -0,0 +1,29 @@ + + + + +

+YACReader - Yet Another Comic Reader - version 0.4.5
+by Luis Ángel San Martín Rodríguez
+e-mail: luisangelsm@gmail.com
+web site: http://code.google.com/p/yacreader/
+Published under GPL v3 license. +


+

+ +

+If you like YACReader, please, consider to make a donation Donate! +


+

+ +

+Compressed files are loaded using 7zip (Windows version) and p7zip (Linux/MacOS X versions) +

+

+Flow effect uses PictureFlow. +

+

+Icons were desinged by Mattahan. +

+ + \ No newline at end of file diff --git a/files/about_es_ES.html b/files/about_es_ES.html new file mode 100644 index 00000000..fb983a8a --- /dev/null +++ b/files/about_es_ES.html @@ -0,0 +1,30 @@ + + + + +

+
+YACReader - Yet Another Comic Reader - versión 0.4.5
+por Luis Ángel San Martín Rodríguez
+e-mail: luisangelsm@gmail.com
+Página web: http://code.google.com/p/yacreader/
+Publicado bajo licencia GPL v3. +


+

+ +

+Si te gusta YACReader, por favor, considera realizar una donación. ¡Dona! +


+

+ +

+Los archivos comprimidos son cargados mediante 7zip (en Windows) y p7zip (en las versiones Linux/MacOS X) +

+

+Los efectos de animación 'flow' usan PictureFlow. +

+

+Los iconos han sido diseñados por Mattahan. +

+ + \ No newline at end of file diff --git a/files/helpYACReader.html b/files/helpYACReader.html new file mode 100644 index 00000000..1b71eac4 --- /dev/null +++ b/files/helpYACReader.html @@ -0,0 +1,126 @@ + + + + +

Quick start guide

+

Features

+

+ YACReader is a fast and simple comic reader with the following features: +

    +
  • Multiplatform, there are Windows, Linux and MacOS X versions.
  • +
  • cbr, cbz, rar, zip, tar and folders support
  • +
  • jpeg, gif, png, tiff and bmp image support
  • +
  • comic reading using keyboard and mouse
  • +
  • fast open and comic navigation
  • +
  • fullscreen mode
  • +
  • configurable magnifying glass for improved reading, since 0.2.0 version is avaliable in windowed mode
  • +
  • fit width (also adjust to an specific width) and fit height modes.
  • +
  • configurable CoverFlow like effect for "go to page" function.
  • +
  • image rotation for comfortable reading even in tablet PCs
  • +
  • double page reading
  • +
  • Comic bookmarks
  • +
+

+

Functions and sortcuts

+

+ General functions: +

    +
  • Open comic : 'O' key
  • +
  • Open folder : 'Ctrl' + 'O' key
  • +
  • Open next cómic : 'Ctrl' + 'Right'
  • +
  • Open previous comic : 'Ctrl' + 'Left' key
  • +
  • Exit : 'Esc' key
  • +
  • Go to previous page : 'Left' key
  • +
  • Go to next page : 'Right' key
  • +
  • Scroll up : 'Wheel mouse up' or 'Up' key
  • +
  • Scroll down : 'Wheel mouse down' or 'Down' key
  • +
  • Auto Scroll down : 'Space Bar'
  • +
  • Auto Scroll up : 'B' key
  • +
  • Rotate to the left : 'L' key
  • +
  • Rotate to the rigth : 'R' key
  • +
  • Open "Go to" dialog : 'G' key
  • +
  • Change between fit width/height modes : 'A' key
  • +
  • Double page mode : 'D' key
  • +
  • Toggle Full Screen mode : 'F' key or double click
  • +
  • For "Go to flow" mode aproximate mouse cursor to bottom border or press 'S' key (show/hide switch).
  • +
  • Show magnifying glass (only in fullscreen mode) : 'Z' key
  • +
  • Show options : 'C' key
  • +
  • Show/hide tool bar : 'H' key
  • +
  • Show information ("current page/number of pages - current time"): 'I' key
  • +
  • Show bookmarks dialog : 'M' key
  • +
  • Set bookmark: 'Ctrl' + 'M' key
  • +
+

+

+ "Go to flow" functions: +

    +
  • Hide / show : 'S' key.
  • + +
  • Go to current central page on the flow : 'Return' or 'Enter'
  • +
  • Next flow page : 'Right' key or left mouse click on the right area of the flow
  • +
  • Previous flow page : 'Left' key or left mouse click on the left area of the flow
  • +
  • Fast forward : 'Ctrl' + 'Right'
  • +
  • Fast backward : 'Ctrl' + 'Left'
  • +
  • Scroll flow : Wheel mouse
  • +
+

+

+ Magnifying glass functions: +

    +
  • Show/hide : 'Z' key.
  • +
  • Resize : Wheel mouse or '+'/'-' keys.
  • +
  • Resize height : 'Ctrl' + Wheel mouse.
  • +
  • Resize width : 'Alt' + Wheel mouse.
  • +
  • Adjust zoom level : 'Shift' + Wheel mouse or 'Shift' + '+'/'-' keys.
  • +
+

+

+ Configuration dialog +