diff --git a/CMakeLists.txt b/CMakeLists.txt index b27fe9a07..313bfecdb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,8 +65,8 @@ project(rssguard) set(APP_NAME "RSS Guard") set(APP_LOW_NAME "rssguard") -set(APP_VERSION "2.5.3") -set(FILE_VERSION "2,5,3,0") +set(APP_VERSION "3.0.0") +set(FILE_VERSION "3,0,0,0") set(APP_AUTHOR "Martin Rotter") set(APP_URL "http://bitbucket.org/skunkos/rssguard") set(APP_URL_ISSUES "http://bitbucket.org/skunkos/rssguard/issues") @@ -343,17 +343,18 @@ set(APP_SOURCES src/qtsingleapplication/qtsinglecoreapplication.cpp src/qtsingleapplication/qtsingleapplication.cpp + # QT-JSON sources. + src/qt-json/json.cpp + # GUI sources. src/gui/dialogs/formmain.cpp src/gui/dialogs/formsettings.cpp src/gui/dialogs/formabout.cpp - src/gui/dialogs/formcategorydetails.cpp - src/gui/dialogs/formfeeddetails.cpp src/gui/dialogs/formupdate.cpp - src/gui/dialogs/formimportexport.cpp src/gui/dialogs/formdatabasecleanup.cpp src/gui/dialogs/formbackupdatabasesettings.cpp src/gui/dialogs/formrestoredatabasesettings.cpp + src/gui/dialogs/formaddaccount.cpp src/gui/notifications/notification.cpp src/gui/systemtrayicon.cpp src/gui/baselineedit.cpp @@ -412,14 +413,37 @@ set(APP_SOURCES src/core/messagesproxymodel.cpp src/core/feedsmodel.cpp src/core/feedsproxymodel.cpp - src/core/category.cpp - src/core/rootitem.cpp - src/core/feed.cpp src/core/parsingfactory.cpp src/core/feeddownloader.cpp - src/core/feedsimportexportmodel.cpp - src/core/recyclebin.cpp - src/core/feedsselection.cpp + src/core/message.cpp + + # ABSTRACT service sources. + src/services/abstract/rootitem.cpp + src/services/abstract/serviceentrypoint.cpp + src/services/abstract/feed.cpp + src/services/abstract/category.cpp + src/services/abstract/serviceroot.cpp + src/services/abstract/recyclebin.cpp + + # STANDARD feed service sources. + src/services/standard/gui/formstandardcategorydetails.cpp + src/services/standard/gui/formstandardfeeddetails.cpp + src/services/standard/gui/formstandardimportexport.cpp + src/services/standard/standardfeedsimportexportmodel.cpp + src/services/standard/standardserviceentrypoint.cpp + src/services/standard/standardcategory.cpp + src/services/standard/standardfeed.cpp + src/services/standard/standardserviceroot.cpp + src/services/standard/standardrecyclebin.cpp + + # TT-RSS feed service sources. + src/services/tt-rss/ttrssserviceentrypoint.cpp + src/services/tt-rss/ttrssserviceroot.cpp + src/services/tt-rss/ttrssfeed.cpp + src/services/tt-rss/ttrsscategory.cpp + src/services/tt-rss/ttrssrecyclebin.cpp + src/services/tt-rss/gui/formeditaccount.cpp + src/services/tt-rss/network/ttrssnetworkfactory.cpp # NETWORK-WEB sources. src/network-web/basenetworkaccessmanager.cpp @@ -462,13 +486,11 @@ set(APP_HEADERS src/gui/dialogs/formmain.h src/gui/dialogs/formsettings.h src/gui/dialogs/formabout.h - src/gui/dialogs/formcategorydetails.h - src/gui/dialogs/formfeeddetails.h - src/gui/dialogs/formimportexport.h src/gui/dialogs/formbackupdatabasesettings.h src/gui/dialogs/formrestoredatabasesettings.h src/gui/dialogs/formdatabasecleanup.h src/gui/dialogs/formupdate.h + src/gui/dialogs/formaddaccount.h src/gui/notifications/notification.h src/gui/systemtrayicon.h src/gui/baselineedit.h @@ -518,7 +540,30 @@ set(APP_HEADERS src/core/feedsmodel.h src/core/feedsproxymodel.h src/core/feeddownloader.h - src/core/feedsimportexportmodel.h + + # ABSTRACT service headers. + src/services/abstract/rootitem.h + src/services/abstract/feed.h + src/services/abstract/category.h + src/services/abstract/serviceroot.h + src/services/abstract/recyclebin.h + + # STANDARD service headers. + src/services/standard/standardfeedsimportexportmodel.h + src/services/standard/gui/formstandardcategorydetails.h + src/services/standard/gui/formstandardfeeddetails.h + src/services/standard/gui/formstandardimportexport.h + src/services/standard/standardcategory.h + src/services/standard/standardfeed.h + src/services/standard/standardserviceroot.h + src/services/standard/standardrecyclebin.h + + # TT-RSS service headers. + src/services/tt-rss/ttrssserviceroot.h + src/services/tt-rss/ttrssrecyclebin.h; + src/services/tt-rss/ttrssfeed.h + src/services/tt-rss/ttrsscategory.h + src/services/tt-rss/gui/formeditaccount.h # NETWORK-WEB headers. src/network-web/webpage.h @@ -548,13 +593,21 @@ set(APP_FORMS src/gui/dialogs/formmain.ui src/gui/dialogs/formsettings.ui src/gui/dialogs/formabout.ui - src/gui/dialogs/formcategorydetails.ui - src/gui/dialogs/formfeeddetails.ui - src/gui/toolbareditor.ui - src/gui/dialogs/formimportexport.ui src/gui/dialogs/formbackupdatabasesettings.ui src/gui/dialogs/formrestoredatabasesettings.ui src/gui/dialogs/formdatabasecleanup.ui + src/gui/dialogs/formaddaccount.ui + src/gui/toolbareditor.ui + + # STANDARD service forms. + src/services/standard/gui/formstandardcategorydetails.ui + src/services/standard/gui/formstandardfeeddetails.ui + src/services/standard/gui/formstandardimportexport.ui + + # TT-RSS service forms. + src/services/tt-rss/gui/formeditaccount.ui + + # NETWORK forms. src/network-web/downloadmanager.ui src/network-web/downloaditem.ui diff --git a/README.md b/README.md index 0ab270a00..5b12953bb 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ RSS Guard ========= Welcome to RSS Guard website. You can find here basic information. -RSS Guard is simple and easy-to-use RSS/ATOM feed aggregator developed using Qt framework. +RSS Guard is simple and easy-to-use RSS/ATOM feed aggregator developed using Qt framework which supports online feed synchronization. - - - Contacts @@ -66,6 +66,8 @@ RSS Guard is simple (yet powerful) feed reader. It is able to fetch the most kno RSS Guard is written in C++. It is pretty fast even with tons of messages loaded. The core features are: +* **support for online feed synchronization via plugins**, + * Tiny Tiny RSS (from RSS Guard 3.0.0). * multiplatformity, * support for all feed formats, * simplicity, @@ -113,10 +115,9 @@ RSS Guard is written in C++. It is pretty fast even with tons of messages loaded * Qt library is the only dependency, * open-source development model and friendly author waiting for your feedback, * no ads, no hidden costs. + - - - Philosophy ---------- -RSS Guard tends to be independent software. It's free, it's open-source. RSS Guard will never depend on other services - this includes online news aggregators like Feedly, The Old Reader and others. - -That's why RSS Guard will never integrate those services unless someone else codes support for them on his own. Remember, RSS Guard supports online synchronization via MySQL/MariaDB or you can use Dropbox to synchronize SQLite data storage. \ No newline at end of file +RSS Guard tends to be independent software. It's free, it's open-source. RSS Guard accepts donations but only to SUPPORT its development. \ No newline at end of file diff --git a/localization/rssguard-cs_CZ.ts b/localization/rssguard-cs_CZ.ts index b518b6ae4..a089881e9 100644 --- a/localization/rssguard-cs_CZ.ts +++ b/localization/rssguard-cs_CZ.ts @@ -1,4 +1,6 @@ - + + + AdBlockAddSubscriptionDialog @@ -100,7 +102,7 @@ Berte také na paměti, že některé prostředky webových stránek jsou intern AdBlockIcon Adblock - + Show Adblock &settings @@ -196,25 +198,6 @@ Berte také na paměti, že některé prostředky webových stránek jsou intern Obnovení nastavení nebylo spuštěno. Ujistěte se, že cílový adresář je zapisovatelný. - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - %1 (kategorie)%2%3 - - - -This category does not contain any nested items. - -Tato kategorie neobsahuje žádné položky. - - - %n unread message(s). - Tooltip for "unread" column of feed list. - %n nepřečtená zpráva.%n nepřečtené zprávy.%n nepřečtených zpráv. - - DatabaseCleaner @@ -292,10 +275,14 @@ Tato kategorie neobsahuje žádné položky. Click me to add feeds from this website. This website contains %n feed(s). - Pro přidání kanálů z této stránky na mě klikni. -Tato stránka obsahuje %n kanál.Pro přidání kanálů z této stránky na mě klikni. -Tato stránka obsahuje %n kanály.Pro přidání kanálů z této stránky na mě klikni. -Tato stránka obsahuje %n kanálů. + + Pro přidání kanálů z této stránky na mě klikni. +Tato stránka obsahuje %n kanál. + Pro přidání kanálů z této stránky na mě klikni. +Tato stránka obsahuje %n kanály. + Pro přidání kanálů z této stránky na mě klikni. +Tato stránka obsahuje %n kanálů. + @@ -373,14 +360,14 @@ Tato stránka obsahuje %n kanálů. Stahování dokončeno - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. Soubor '%1' je stažen. Klikněte sem pro otevření nadřazeného adresáře. URL: %1 - + Local file: %1 @@ -399,11 +386,19 @@ Klikněte sem pro otevření nadřazeného adresáře. %n minutes remaining - %n minuta do konce%n minuty do konce%n minut do konce + + %n minuta do konce + %n minuty do konce + %n minut do konce + %n seconds remaining - %n vteřina do konce%n vteřiny do konce%n vteřin do konce + + %n vteřina do konce + %n vteřiny do konce + %n vteřin do konce + bytes @@ -411,63 +406,23 @@ Klikněte sem pro otevření nadřazeného adresáře. kB - + MB - + GB - + Downloading %n file(s)... - Stahuji %n soubor...Stahuji %n soubory...Stahuji %n souborů... - - - - Feed - - does not use auto-update - Describes feed auto-update status. - nepoužívá auto-aktualizace - - - uses global settings - Describes feed auto-update status. - používá globální nastavení - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - používá specifické nastavení (%n minuta do další aktualizace)používá specifické nastavení (%n minuty do další aktualizace)používá specifické nastavení (%n minut do další aktualizace) - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - %1 (%2)%3 - -Síťový status: %6 -Kódování: %4 -Automatický update: %5 - - - %n unread message(s). - Tooltip for "unread" column of feed list. - %n nepřečtená zpráva.%n nepřečtené zprávy.%n nepřečtených zpráv. - - - Metadata not fetched - Metadata nezískána - - - Metadata was not fetched because: %1 - Metadata nezískána, protože: %1 + + Stahuji %n soubor... + Stahuji %n soubory... + Stahuji %n souborů... + @@ -476,24 +431,10 @@ Automatický update: %5 Toolbar for messages Panel zpráv - - Feed update started - Text display in status bar when feed update is started. - Spuštěn update kanálů - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - Aktualizován kanál '%1' - Toolbar for feeds Panel kanálů - - Error when loading initial feeds - Chyba při načítání úvodních kanálů - Cannot cleanup database Nelze vyčistit databázi @@ -502,18 +443,6 @@ Automatický update: %5 Cannot cleanup database, because another critical action is running. Databázi nelze v současné době vyčistit, protože běží jiná kritická akce. Zkuste to později. - - Cannot update all items - Nelze aktualizovat všechny položky - - - You cannot update all items because another another critical operation is ongoing. - Nelze aktualizovat všechny položky, protože už běží jiná kritická operace. - - - New messages downloaded - Staženy nové zprávy - FeedsImportExportModel @@ -550,25 +479,47 @@ Automatický update: %5 Name of root item of feed list which can be seen in feed add/edit dialog. Kořen - - Invalid tree data. - Chybná data stromu. - - - Import successfull, but some feeds/categories were not imported due to error. - Import byl úspěšný, ale některé kanály či kategorie nebyly importovány kvůli chybě. - - - Import was completely successfull. - Import byl zcela úspěšný. - Starting auto-update of some feeds Zahajuji auto-update některých kanálů I will auto-update %n feed(s). - Budu aktualizovat %n kanál.Budu aktualizovat %n kanály.Budu aktualizovat %n kanálů. + + Budu aktualizovat %n kanál. + Budu aktualizovat %n kanály. + Budu aktualizovat %n kanálů. + + + + Cannot update all items + Nelze aktualizovat všechny položky + + + You cannot update all items because another another critical operation is ongoing. + Nelze aktualizovat všechny položky, protože už běží jiná kritická operace. + + + Feed update started + Text display in status bar when feed update is started. + Spuštěn update kanálů + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + Aktualizován kanál '%1' + + + New messages downloaded + Staženy nové zprávy + + + You can't transfer dragged item into different account, this is not supported. + Tažené položky nelze přesouvat mezi účty, toto není podporováno. + + + Cannot perform drag & drop operation + Operaci drag & drop nelze vykonat @@ -580,14 +531,6 @@ Automatický update: %5 FeedsView - - Cannot add standard category - Nelze přidat standardní kategorii - - - Cannot add standard feed - Nelze přidat standardní kanál - Cannot edit item Nelze upravit položku @@ -596,50 +539,10 @@ Automatický update: %5 Cannot delete item Nelze smazat položku - - You are about to delete selected feed or category. - Právě se chystáte smazat vybraný kanál či kategorii. - - - Deletion of item failed. - Mazání položky selhalo. - - - Selected item was not deleted due to error. - Vybraná položka nebyla smazána kvůli chybě. - - - Do you really want to delete selected item? - Opravdu chcete vybranou položku smazat? - - - Permanently delete messages - Trvalé smazání zpráv - - - You are about to permanenty delete all messages from your recycle bin. - Chystáte se vysypat koš. - - - Do you really want to empty your recycle bin? - Opravdu chcete koš vysypat? - Context menu for empty space Kontextové menu pro prázdný prostor - - Context menu for recycle bin - Kontextové menu pro koš - - - You cannot add new standard category now because another critical operation is ongoing. - Nelze přidat novou kategorii, protože už běží jiná kritická operace. - - - You cannot add new standard feed now because another critical operation is ongoing. - Nelze přidat nový kanál, protože už běží jiná kritická operace. - Selected item cannot be edited because another critical operation is ongoing. Nelze editovat vybranou položku, protože už běží jiná kritická operace. @@ -648,14 +551,44 @@ Automatický update: %5 Selected item cannot be deleted because another critical operation is ongoing. Nelze smazat vybranou položku, protože už běží jiná kritická operace. - - Delete feed/category - Smazat kanál/kategorii - Context menu for categories Kontextové menu pro kategorie + + Selected item cannot be edited, this is not (yet?) supported. + Vybraná položka nemůže být upravena, toto není podporováno. + + + Deleting "%1" + Maži "%1" + + + You are about to completely delete item "%1". + Chystáte se zcela vymazat položku "%1". + + + Are you sure? + Jste si jistý? + + + Cannot delete "%1" + Nelze smazat "%1" + + + This item cannot be deleted because something critically failed. Submit bug report. + Tuto položku nelze smazat, protože se něco kriticky porouchalo, nahlašte tuto chybu. + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + Tuto položku nelze smazat, protože to nepodporuje +nebo tato funkcionality dosud není implementována. + + + Context menu for other items + Kontextové menu pro ostatní položky + FormAbout @@ -703,10 +636,6 @@ Automatický update: %5 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> <b>%8</b><br><b>Verze:</b> %1 (při sestavování použit OS %2 a CMake %3)<br><b>Revize:</b> %4<br><b>Datum sestavení:</b> %5<br><b>Qt:</b> %6 (při kompilaci %7)<br> - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - <body>%5 je (velmi) jednoduduchá čtečka kanálů.<br><br>Tato aplikace je šířena pod podmínkami licence GNU General Public License, verze 3.<br><br>Kontakty:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~web</li></ul>Zdrojové kódy pro %5 je možné získat z jeho webu.<br><br><br>Copyright (C) 2011-%3 %4</body> - About %1 About RSS Guard dialog title. @@ -736,6 +665,49 @@ Automatický update: %5 Resources Zdroje + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + <body>%5 je (velmi) lehkotonážní prohlížeč kanálů.<br><br>Tento software je distribuován pod licencí GNU General Public License, verze 3.<br><br>Kontakty:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~web</li></ul>Zdrojový kór pro %5 lze získat na jeho webu.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + FormAddAccount + + Add new account + Přidat nový účet + + + Details + Detaily + + + Name + Název + + + Version + Verze + + + Author + Autor + + + Description + Popis + + + Cannot add account + Účet nelze přidat + + + Some critical error occurred, report this to developers. + Vyskytla se kritická chyba, nahlaště problém vývojářům. + + + This account can be added only once. + Tento účet může být přidán pouze jednou. + FormBackupDatabaseSettings @@ -812,134 +784,6 @@ Automatický update: %5 Je zvolen vhodný výstupní adresář. - - FormCategoryDetails - - Parent category - Nadřazená kategorie - - - Select parent item for your category. - Zvolte nadřazenou kategorii pro Vaši kategorii. - - - Title - Nadpis - - - Description - Popis - - - Icon - Ikona - - - Select icon for your category. - Zvolte ikonu pro Vaši kategorii. - - - Add new category - Přidat novou kategorii - - - Edit existing category - Upravit existující kategorii - - - Cannot add category - Nelze přidat kategorii - - - Category was not added due to error. - Kategorie nebyla přidána kvůli chybě. - - - Cannot edit category - Nelze upravit kategorii - - - Category was not edited due to error. - Kategorie nebyla upravena kvůli chybě. - - - Category name is ok. - Název kategorie je v pořádku. - - - Category name is too short. - Název kategorie je příliš krátký. - - - Description is empty. - Popis je prázdný. - - - Select icon file for the category - Zvolte ikonu pro Vaši kategorii - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Obrázky (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Vybrat ikonu - - - Cancel - Zrušit - - - Look in: - Label to describe the folder for icon file selection dialog. - Hledat v: - - - Icon name: - Název ikony: - - - Icon type: - Typ ikony: - - - Category title - Název kategorie - - - Set title for your category. - Zvolte název pro Vaši kategorii. - - - Category description - Popis kategorie - - - Set description for your category. - Zvolte popis Vaší kategorie. - - - Icon selection - Vybrat ikonu - - - Load icon from file... - Načíst ikonu ze souboru... - - - Do not use icon - Nepoužít ikonu - - - Use default icon - Použít výchozí ikonu - - - The description is ok. - Popis je v pořádku. - - FormDatabaseCleanup @@ -952,7 +796,11 @@ Automatický update: %5 day(s) - den dny dnů + + den + dny + dnů + Shrink database file @@ -1007,389 +855,6 @@ Automatický update: %5 Vymazat všechny důležité zprávy (včetně těch z koše) - - FormFeedDetails - - Parent category - Nadřazená kategorie - - - Select parent item for your feed. - Zvolte nadřazenou kategorii pro Váš kanál. - - - Type - Typ - - - Select type of the standard feed. - Zvolte typ standardního kanálu. - - - Encoding - Kódování - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - Zvolte kódování kanálu. Pokud si nejste jisti, tak zvolte kódování "UTF-8". - - - Auto-update - Auto-aktualizace - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - Zvolte strategii auto-aktualizací tohoto kanálu. Výchozí strategorie auto-aktualizace znamená, že kanál bude aktualizován v intervalech udaných v nastavení aplikace. - - - minutes - minut - - - Title - Nadpis - - - Description - Popis - - - URL - - - - Fetch it now - Načíst nyní - - - Icon - Ikona - - - Select icon for your feed. - Zvolte ikonu pro Váš kanál. - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - Některé kanály vyžaduje autentizaci, a to včetně kanálů pro GMail. Je podporována autentizace BASIC, NTLM-2 a DIGEST-MD5. - - - Requires authentication - Vyžaduje autentizaci - - - Username - Uživatelské jméno - - - Password - Heslo - - - Fetch metadata - Načíst metadata - - - Add new feed - Přidat nový kanál - - - Edit existing feed - Upravit existující kanál - - - Feed name is ok. - Název kanálu je v pořádku. - - - Feed name is too short. - Název kanálu je příliš krátký. - - - Description is empty. - Popis je prázdný. - - - The url is ok. - Url je v pořádku. - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - Url neobsahuje standardní schéma. Začíná Vaše url schématem "http://" nebo "https://". - - - The url is empty. - Url je prázdné. - - - Username is ok or it is not needed. - Uživatelské jméno je v pořádku nebo není třeba. - - - Username is empty. - Uživatelské jméno je prázdné. - - - Password is ok or it is not needed. - Heslo je v pořádku nebo není třeba. - - - Password is empty. - Heslo je prázdné. - - - Select icon file for the feed - Vybrat ikonu pro kanál - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Obrázky (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Vybrat ikonu - - - Cancel - Zrušit - - - Look in: - Label for field with icon file name textbox for selection dialog. - Hledat v: - - - Icon name: - Název ikony: - - - Icon type: - Typ ikony: - - - Cannot add feed - Nelze přidat kanál - - - Feed was not added due to error. - Kanál nepřidán kvůli chybě. - - - Cannot edit feed - Nelze upravit kanál - - - All metadata fetched successfully. - Metadata stažena úspěšně. - - - Feed and icon metadata fetched. - Metadata a ikona staženy. - - - Result: %1. - Výsledek: %1. - - - Feed or icon metatada not fetched. - Metadata nebo ikona nestaženy. - - - Error: %1. - Chyba: %1. - - - No metadata fetched. - Žádná metadata nestažena. - - - Feed title - Název kanálu - - - Set title for your feed. - Zvolte název pro Váš kanál. - - - Feed description - Popis kanálu - - - Set description for your feed. - Zvolte popis Vašeho kanálu. - - - Full feed url including scheme - Plné url kanálu včetně schématu - - - Set url for your feed. - Zvolte url Vašeho kanálu. - - - Set username to access the feed. - Nastavte uživatelské jméno pro tento kanál. - - - Set password to access the feed. - Nastavte heslo pro tento kanál. - - - Icon selection - Vybrat ikonu - - - Load icon from file... - Načíst ikonu ze souboru... - - - Do not use icon - Nepoužít ikonu - - - Use default icon - Použít výchozí ikonu - - - No metadata fetched so far. - Metadata doposud nenačtena. - - - Auto-update using global interval - Auto-aktualizovat dle hlavního nastavení - - - Auto-update every - Auto-aktualizovat každých - - - Do not auto-update at all - Zakázat auto-aktualizace - - - The description is ok. - Popis je v pořádku. - - - Feed was not edited due to error. - Kanál nebyl upraven kvůli chybě. - - - Icon fetched successfully. - Ikona úspěšně stažena. - - - Icon metadata fetched. - Metadata ikony načtena. - - - Icon metatada not fetched. - Metadata ikony nenačtena. - - - No icon fetched. - Ikona nestažena. - - - Fetch icon from feed - Stáhnout ikonu online z kanálu - - - - FormImportExport - - &Select file - &Zvolit soubor - - - Operation results - Výsledky operací - - - No file is selected. - Nevybrán žádný soubor. - - - No operation executed yet. - Doposud neprovedena žádná operace. - - - Export feeds - Exportovat kanály - - - Destination file - Cílový soubor - - - Source feeds && categories - Zdrojové kanály && kategorie - - - Source file - Zdrojový soubor - - - Target feeds && categories - Cílové kanály && kategorie - - - Import feeds - Importovat kanály - - - OPML 2.0 files (*.opml) - soubory OPML 2.0 (*.opml) - - - Select file for feeds export - Zvolit soubor pro export kanálů - - - File is selected. - Soubor je vybrán. - - - Select file for feeds import - Zvolit soubot pro import kanálů - - - Cannot open source file. - Zdrojový soubor nelze otevřít. - - - Feeds were loaded. - Kanály načteny. - - - Error, file is not well-formed. Select another file. - Chyba, soubor nemá správný formát, zvolte jiný. - - - Error occurred. File is not well-formed. Select another file. - Chyba, soubor nemá správný formát, zvolte jiný. - - - Feeds were exported successfully. - Kanály byly úspěšně exportovány. - - - Cannot write into destination file. - Do cílového souboru nelze zapisovat. - - - Critical error occurred. - Vyskytla se kritická chyba. - - - &Check all items - &Označit vše - - - &Uncheck all items - O&dznačit vše - - FormMain @@ -1464,22 +929,6 @@ Automatický update: %5 No actions are available right now. Žádná akce není právě dostupná. - - Fee&ds && categories - Kanály && ka&tegorie - - - Mark all messages (without message filters) from selected feeds as read. - Označit všechny zprávy (i přes filtry zpráv) z vybraných kanálů jako přečtené. - - - Mark all messages (without message filters) from selected feeds as unread. - Označit všechny zprávy (i přes filtry zpráv) z vybraných kanálů jako nepřečtené. - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - Zobrazí všechny zprávy z vybraných kanálů/kategorií v "novinovém" náhledu. Všechny zprávy budou automaticky označeny jako přečtené. - Hides main window if it is visible and shows it if it is hidden. Skryje hlavní ikno, je-li aktuálně viditelné. Jinak jej zobrazí. @@ -1504,34 +953,6 @@ Automatický update: %5 &Delete selected messages Sma&zat vybrané zprávy - - Deletes all messages from selected feeds. - Smaže všechny zprávy z vybraných kanálů. - - - Marks all messages in all feeds read. This does not take message filters into account. - Označí všechny zprávy ve všech kanálech jako přečtené. Tato funkce nemusí brát v potaz případně filtry zpráv. - - - Deletes all messages from all feeds. - Smaže všechny zprávy ze všech kanálů. - - - Update &all feeds - Aktualizovat všechny k&anály - - - Update &selected feeds - Aktualizovat vy&brané kanály - - - &Edit selected feed/category - Up&ravit vybraný kanál/kategorii - - - &Delete selected feed/category - Smazat vybraný kaná&l/kategorii - Settings Nastavení @@ -1540,10 +961,6 @@ Automatický update: %5 Hides or displays the main menu. Skryje či zobrazí hlavní menu. - - Add &new feed/category - &Přidat novou položku - &Close all tabs except current one &Zavřít všechny taby až na ten aktivní @@ -1560,18 +977,6 @@ Automatický update: %5 Mark &selected messages as &unread Označit vybrané zprávy jako &nepřečtené - - &Mark selected feeds as read - Označit vybrané kanály jako &přečtené - - - &Mark selected feeds as unread - Označit vybrané kanály jako &nepřečtené - - - &Clean selected feeds - &Vyčistit vybrané kanály - Open selected source articles in &external browser &Otevřít vybrané zdrojové články v externím prohlížeči @@ -1584,26 +989,6 @@ Automatický update: %5 Open selected source articles in &internal browser &Otevřít vybrané zdrojové články v interním prohlížeči - - &Mark all feeds as &read - Označit všechny kanály jako &přečtené - - - View selected feeds in &newspaper mode - Zobrazit vybrané kanály v &novinovém náhledu - - - &Clean all feeds - &Vyčistit všechny kanály - - - Select &next feed/category - Vybrat &další kanál/kategorii - - - Select &previous feed/category - Vybrat &předchozí kanál/kategorii - Select &next message Vybrat &další zprávu @@ -1656,22 +1041,6 @@ Automatický update: %5 Cannot open external browser. Navigate to application website manually. Externí webový prohlížeč nelze otevřít. Zkontrolujte aktualizace ručně na webu programu. - - New &feed - Nový &kanál - - - Add new feed. - Přidat nový kanál. - - - New &category - No&vá kategorie - - - Add new category. - Přidat novou kategorii. - &Toolbars &Nástrojové lišty @@ -1684,30 +1053,10 @@ Automatický update: %5 &Feed/message list headers &Hlavičky seznamů zpráv/kanálů - - &Import feeds - &Importovat kanály - - - Imports feeds you want from selected file. - Importuje kanály ze souboru. - - - &Export feeds - &Exportovat kanály - - - Exports feeds you want to selected file. - Exportuje kanály do souboru. - Close all tabs except current one. Zavřít všechny taby kromě aktivního. - - &Recycle bin - &Koš - Report a &bug (GitHub)... Nahlásit &chybu (GitHub)... @@ -1724,18 +1073,6 @@ Automatický update: %5 Display &wiki Zobrazit &wiki - - &Empty recycle bin - &Vysypat koš - - - &Restore all messages - &Obnovit všechny zprávy z koše - - - Restore &selected messages - Obnovit &vybrané zprávy z koše - &Restart &Restartovat @@ -1765,16 +1102,132 @@ Automatický update: %5 &Vyčistit databázi - Show only unread feeds/categories - Zobrazit pouze nepřečtené kanály/kategorie + Add &new item + Přidat &novou položku - &Fetch feed metadata - &Získat metadata kanálu + Update &all items + Aktualizovat &všechny položky - &Expand/collapse selected feed/category - &Expandovat/složit vybraný kanál/kategorii + Update &selected items + Aktualizovat &vybrané položky + + + &Edit selected item + &Upravit vybranou položku + + + &Delete selected item + Smazat &vybranou položku + + + &Mark selected items as read + Označit vybrané položky jako &přečtené + + + Mark all messages (without message filters) from selected items as read. + Označí všechny zprávy (vyjma zprávových filtrů) z vybraných položek jako přečtené. + + + &Mark selected items as unread + Označit vybrané položky jako &nepřečtené + + + Mark all messages (without message filters) from selected items as unread. + Označí všechny zprávy (vyjma zprávových filtrů) z vybraných položek jako nepřečtené. + + + &Clean selected items + &Vyčistit vybrané položky + + + Deletes all messages from selected items. + Smaže všechny zprávy z vybraných položek (umístí je do koše). + + + &Mark all items as &read + Označit všechno jako &přečtené + + + Marks all messages in all items read. This does not take message filters into account. + Označí zcela všechny zprávy (vyjma odpadkových košů) jako přečtené. + + + View selected items in &newspaper mode + Zobrazit vybrané položky v &novinovém náhledu + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + Zobrazí všechny zprávy ze vybraných položek v "novinovém náhledu". Zprávy jsou automaticky označeny jako přečtené. + + + &Clean all items + &Vyčistit všechny položky + + + Deletes all messages from all items. + Smaže zprávy ze všech položek (vyjma odpadkových košů). + + + Select &next item + Vybrat &další položku + + + Select &previous item + Vybrat &předchozí položku + + + Show only unread items + Zobrazit pouze položky s nepřečtenými zprávami + + + &Expand/collapse selected item + &Rozbalit/sbalit položku + + + &Add new service account + &Přidat nový účet + + + &Restore selected messages + &Obnovit vybrané zprávy + + + No possible actions + Žádná možná akce + + + Feeds && categories && accounts + Kanály && zprávy && účty + + + &Recycle bin(s) + &Odpadkové koše + + + &Restore all recycle bins + &Obnovit všechn ze všech odp. košů + + + &Empty all recycle bins + &Vyprázdnit všechny odp. koše + + + Select next &unread message + Vybrat další &nepřečtenou zprávu + + + No recycle bin + Žádný odpadkový koš + + + Restore recycle bin + Obnovit odp. koš + + + Empty recycle bin + Vyprázdnit odp. koš @@ -1862,7 +1315,7 @@ Automatický update: %5 Proxy - + Icons && skins @@ -1891,7 +1344,7 @@ Automatický update: %5 Port - + Username @@ -1925,17 +1378,13 @@ Automatický update: %5 Author Autor - - Email - - Socks5 - + Http - + (not supported on this platform) @@ -2160,7 +1609,7 @@ Autoři této aplikace nenesou žádnou odpovědnost za ztrátu Vašich dat. ms - + Update all feed on application startup @@ -2451,7 +1900,7 @@ File filter for external e-mail selection dialog. Mozilla Thunderbird - + Working database which you have full access to. @@ -2505,6 +1954,525 @@ File filter for external e-mail selection dialog. Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) Moderní notifikace (Toto používá nativní notifikace přes D-Bus, jsou-li dostupné.) + + E-mail + + + + Enable notifications + Povolit notifikace + + + + FormStandardCategoryDetails + + Parent category + Nadřazená kategorie + + + Select parent item for your category. + Zvolte nadřazenou kategorii pro Vaši kategorii. + + + Title + Nadpis + + + Description + Popis + + + Icon + Ikona + + + Select icon for your category. + Zvolte ikonu pro Vaši kategorii. + + + Add new category + Přidat novou kategorii + + + Edit existing category + Upravit existující kategorii + + + Cannot add category + Nelze přidat kategorii + + + Category was not added due to error. + Kategorie nebyla přidána kvůli chybě. + + + Cannot edit category + Nelze upravit kategorii + + + Category was not edited due to error. + Kategorie nebyla upravena kvůli chybě. + + + Category name is ok. + Název kategorie je v pořádku. + + + Category name is too short. + Název kategorie je příliš krátký. + + + Description is empty. + Popis je prázdný. + + + The description is ok. + Popis je v pořádku. + + + Select icon file for the category + Zvolte ikonu pro Vaši kategorii + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Obrázky (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Vybrat ikonu + + + Cancel + Zrušit + + + Look in: + Label to describe the folder for icon file selection dialog. + Hledat v: + + + Icon name: + Název ikony: + + + Icon type: + Typ ikony: + + + Category title + Název kategorie + + + Set title for your category. + Zvolte název pro Vaši kategorii. + + + Category description + Popis kategorie + + + Set description for your category. + Zvolte popis Vaší kategorie. + + + Icon selection + Vybrat ikonu + + + Load icon from file... + Načíst ikonu ze souboru... + + + Do not use icon + Nepoužít ikonu + + + Use default icon + Použít výchozí ikonu + + + + FormStandardFeedDetails + + Parent category + Nadřazená kategorie + + + Select parent item for your feed. + Zvolte nadřazenou kategorii pro Váš kanál. + + + Type + Typ + + + Select type of the standard feed. + Zvolte typ standardního kanálu. + + + Encoding + Kódování + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + Zvolte kódování kanálu. Pokud si nejste jisti, tak zvolte kódování "UTF-8". + + + Auto-update + Auto-aktualizace + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + Zvolte strategii auto-aktualizací tohoto kanálu. Výchozí strategorie auto-aktualizace znamená, že kanál bude aktualizován v intervalech udaných v nastavení aplikace. + + + minutes + minut + + + Title + Nadpis + + + Description + Popis + + + URL + + + + Fetch it now + Načíst nyní + + + Icon + Ikona + + + Select icon for your feed. + Zvolte ikonu pro Váš kanál. + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + Některé kanály vyžaduje autentizaci, a to včetně kanálů pro GMail. Je podporována autentizace BASIC, NTLM-2 a DIGEST-MD5. + + + Requires authentication + Vyžaduje autentizaci + + + Username + Uživatelské jméno + + + Password + Heslo + + + Fetch metadata + Načíst metadata + + + Add new feed + Přidat nový kanál + + + Edit existing feed + Upravit existující kanál + + + Feed name is ok. + Název kanálu je v pořádku. + + + Feed name is too short. + Název kanálu je příliš krátký. + + + Description is empty. + Popis je prázdný. + + + The description is ok. + Popis je v pořádku. + + + The url is ok. + Url je v pořádku. + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + Url neobsahuje standardní schéma. Začíná Vaše url schématem "http://" nebo "https://". + + + The url is empty. + Url je prázdné. + + + Username is ok or it is not needed. + Uživatelské jméno je v pořádku nebo není třeba. + + + Username is empty. + Uživatelské jméno je prázdné. + + + Password is ok or it is not needed. + Heslo je v pořádku nebo není třeba. + + + Password is empty. + Heslo je prázdné. + + + Select icon file for the feed + Vybrat ikonu pro kanál + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Obrázky (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Vybrat ikonu + + + Cancel + Zrušit + + + Look in: + Label for field with icon file name textbox for selection dialog. + Hledat v: + + + Icon name: + Název ikony: + + + Icon type: + Typ ikony: + + + Cannot add feed + Nelze přidat kanál + + + Feed was not added due to error. + Kanál nepřidán kvůli chybě. + + + Cannot edit feed + Nelze upravit kanál + + + Feed was not edited due to error. + Kanál nebyl upraven kvůli chybě. + + + All metadata fetched successfully. + Metadata stažena úspěšně. + + + Feed and icon metadata fetched. + Metadata a ikona staženy. + + + Result: %1. + Výsledek: %1. + + + Feed or icon metatada not fetched. + Metadata nebo ikona nestaženy. + + + Error: %1. + Chyba: %1. + + + No metadata fetched. + Žádná metadata nestažena. + + + Icon fetched successfully. + Ikona úspěšně stažena. + + + Icon metadata fetched. + Metadata ikony načtena. + + + Icon metatada not fetched. + Metadata ikony nenačtena. + + + No icon fetched. + Ikona nestažena. + + + Feed title + Název kanálu + + + Set title for your feed. + Zvolte název pro Váš kanál. + + + Feed description + Popis kanálu + + + Set description for your feed. + Zvolte popis Vašeho kanálu. + + + Full feed url including scheme + Plné url kanálu včetně schématu + + + Set url for your feed. + Zvolte url Vašeho kanálu. + + + Set username to access the feed. + Nastavte uživatelské jméno pro tento kanál. + + + Set password to access the feed. + Nastavte heslo pro tento kanál. + + + Icon selection + Vybrat ikonu + + + Load icon from file... + Načíst ikonu ze souboru... + + + Do not use icon + Nepoužít ikonu + + + Use default icon + Použít výchozí ikonu + + + Fetch icon from feed + Stáhnout ikonu online z kanálu + + + No metadata fetched so far. + Metadata doposud nenačtena. + + + Auto-update using global interval + Auto-aktualizovat dle hlavního nastavení + + + Auto-update every + Auto-aktualizovat každých + + + Do not auto-update at all + Zakázat auto-aktualizace + + + + FormStandardImportExport + + &Select file + &Zvolit soubor + + + &Check all items + &Označit vše + + + &Uncheck all items + O&dznačit vše + + + Operation results + Výsledky operací + + + No file is selected. + Nevybrán žádný soubor. + + + No operation executed yet. + Doposud neprovedena žádná operace. + + + Destination file + Cílový soubor + + + Source feeds && categories + Zdrojové kanály && kategorie + + + Export feeds + Exportovat kanály + + + Source file + Zdrojový soubor + + + Target feeds && categories + Cílové kanály && kategorie + + + Import feeds + Importovat kanály + + + OPML 2.0 files (*.opml) + soubory OPML 2.0 (*.opml) + + + Select file for feeds export + Zvolit soubor pro export kanálů + + + File is selected. + Soubor je vybrán. + + + Select file for feeds import + Zvolit soubot pro import kanálů + + + Cannot open source file. + Zdrojový soubor nelze otevřít. + + + Feeds were loaded. + Kanály načteny. + + + Error, file is not well-formed. Select another file. + Chyba, soubor nemá správný formát, zvolte jiný. + + + Error occurred. File is not well-formed. Select another file. + Chyba, soubor nemá správný formát, zvolte jiný. + + + Feeds were exported successfully. + Kanály byly úspěšně exportovány. + + + Cannot write into destination file. + Do cílového souboru nelze zapisovat. + + + Critical error occurred. + Vyskytla se kritická chyba. + FormUpdate @@ -2657,7 +2625,7 @@ Přejít na web aplikace a stáhnout jej ručně. MessagesModel Id - + Read @@ -2681,7 +2649,7 @@ Přejít na web aplikace a stáhnout jej ručně. Url - + Author @@ -2751,6 +2719,14 @@ Přejít na web aplikace a stáhnout jej ručně. List of attachments. Seznam příloh. + + Loading of messages from item '%s' failed. + Načítání zpráv z položky '%s' selhalo. + + + Loading of messages failed, maybe messages could not be downloaded. + Načítání zpráv selhalo, možná zprávy nemohly být staženy. + MessagesToolBar @@ -2924,26 +2900,44 @@ Přejít na web aplikace a stáhnout jej ručně. LANG_EMAIL rotter.martinos@gmail.com - - Load initial feeds - Načíst úvodní kanály - - - Do you want to load initial set of feeds? - Chcete načíst úvodní set kanálů? - LANG_NAME Name of language, e.g. English. Čeština - - You started %1 for the first time, now you can load initial set of feeds. - Spustili jste %1 poprvé, nyní si můžete zvolit, zda chcete nahrát výchozí sadu kanálů. + + + ++ %n other feeds. + + + ++ %n další kanál. + + ++ %n dalších kanálů. + + ++ %n kanály. + - Welcome to %1 %2. - Vítá vás %1 %2. + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + Vítá Vás %1. + +Prosím, zkontrolujte novinky +klknutím na tuto notifikaci. + + + Welcome to %1. + Vítá vás %1. + + + Load initial set of feeds + Načíst úvodní sadu kanálů @@ -2964,7 +2958,11 @@ Přejít na web aplikace a stáhnout jej ručně. %n deleted message(s). - %n smazaná zpráva.%n smazané zprávy.%n smazaných zpráv. + + %n smazaná zpráva. + %n smazané zprávy. + %n smazaných zpráv. + @@ -2982,6 +2980,141 @@ Přejít na web aplikace a stáhnout jej ručně. Klikněte a stiskněte novou zkratku. + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + %1 (kategorie)%2%3 + + + +This category does not contain any nested items. + +Tato kategorie neobsahuje žádné položky. + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n nepřečtená zpráva. + %n nepřečtené zprávy. + %n nepřečtených zpráv. + + + + + StandardFeed + + Metadata not fetched + Metadata nezískána + + + Metadata was not fetched because: %1. + Metadata nezískána, protože: %1. + + + does not use auto-update + Describes feed auto-update status. + nepoužívá auto-aktualizace + + + uses global settings + Describes feed auto-update status. + používá globální nastavení + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + používá specifické nastavení (%n minuta do další aktualizace) + používá specifické nastavení (%n minuty do další aktualizace) + používá specifické nastavení (%n minut do další aktualizace) + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + %1 (%2)%3 + +Síťový status: %6 +Kódování: %4 +Automatický update: %5 + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n nepřečtená zpráva. + %n nepřečtené zprávy. + %n nepřečtených zpráv. + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + Toto je účet pro standardní RSS/RDS/ATOM kanály. + + + You started %1 for the first time, now you can load initial set of feeds. + Spustili jste %1 poprvé, nyní si můžete zvolit, zda chcete nahrát výchozí sadu kanálů. + + + Do you want to load initial set of feeds? + Chcete načíst úvodní set kanálů? + + + Error when loading initial feeds + Chyba při načítání úvodních kanálů + + + This is service account for standard RSS/RDF/ATOM feeds. + Toto je účet pro standardní RSS/RDS/ATOM kanály. + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n nepřečtená zpráva. + %n nepřečtené zprávy. + %n nepřečtených zpráv. + + + + Fetch metadata + Načíst metadata + + + Import successfull, but some feeds/categories were not imported due to error. + Import byl úspěšný, ale některé kanály či kategorie nebyly importovány kvůli chybě. + + + Import was completely successfull. + Import byl zcela úspěšný. + + + Add new category + Přidat novou kategorii + + + Add new feed + Přidat nový kanál + + + Export feeds + Exportovat kanály + + + Import feeds + Importovat kanály + + StatusBar @@ -3003,6 +3136,10 @@ Přejít na web aplikace a stáhnout jej ručně. Click the bubble for more information. Klikněte na bublinu pro více informací. + + anonymous + anonym + SystemTrayIcon @@ -3110,6 +3247,22 @@ Nepřečtené zprávy: %2 Nejdříve ukončete otevřené modální dialogy. + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n nepřečtená zpráva. + %n nepřečtené zprávy. + %n nepřečtených zpráv. + + + WebBrowser @@ -3181,6 +3334,14 @@ Nepřečtené zprávy: %2 Stop web page loading. Zastavit načítání aktuální webové stránky. + + Cannot add feed + Nelze přidat kanál + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + Tento kanál neumí %1 přidat, protože standardní RSS/ATOM účet není aktivován. Je třeba jej aktivovat. + WebView @@ -3321,4 +3482,4 @@ Nepřečtené zprávy: %2 Hledat "%1" přes Google... - \ No newline at end of file + diff --git a/localization/rssguard-de_DE.ts b/localization/rssguard-de_DE.ts index 78ab710be..15aa182c5 100644 --- a/localization/rssguard-de_DE.ts +++ b/localization/rssguard-de_DE.ts @@ -1,170 +1,172 @@ - + + + AdBlockAddSubscriptionDialog Add subscription - + Another subscription - + Entered title is okay. - + Entered title is empty. - + Entered url is okay. - + Entered url is empty. - + Title - + Titel Address - + AdBlockCustomList Custom rules - + AdBlockDialog Adblock settings - + Enable Adblock - + Note that Adblock may significantly slow this application down once you activate huge subscriptions. Too many rules is not good for performance. Also, make sure you restart application after you disable Adblock if you wish to have low memory footprint. Adblock is known to use much system memory. Also note that some resources are cached by internal web browser. Thus, after changing some rules or subscriptions they will fully apply only for new application instances. Make sure you restart RSS Guard for best Adblock experience. - + Options - + Filter rules - + Use only essential part of EasyList (for performance reasons) - + Add rule - + Remove rule - + Add subscription - + Remove subscription - + Update subscriptions - + Rules writing guide - + AdBlockIcon Adblock - + Show Adblock &settings - + Disable on %1 - + Disable only on this page - + Blocked popup windows - + %1 with (%2) - + No content blocked - + Blocked some content - click to edit rule - + Adblock - up and running - + Adblock - not running - + AdBlockSubscription Cannot load subscription! - + AdBlockTreeWidget Please write your rule here - + %1 (recently updated) - + %1 (error: %2) - + Add rule - + Remove rule - + @@ -175,92 +177,74 @@ Also note that some resources are cached by internal web browser. Thus, after ch Output directory is not writable. - + Settings file not copied to output directory successfully. - + Database file not copied to output directory successfully. - + Database restoration was not initiated. Make sure that output directory is writable. - + Settings restoration was not initiated. Make sure that output directory is writable. - - - - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - - - - -This category does not contain any nested items. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - + DatabaseCleaner Shrinking database file... - + Database file shrinked... - + Removing read messages... - + Read messages purged... - + Recycle bin purged... - + Removing old messages... - + Purging recycle bin... - + Old messages purged... - + DatabaseFactory MySQL server works as expected. - + No MySQL server is running in the target destination. - + Access denied. Invalid username or password used. Access to MySQL server was denied. - + Unknown error. @@ -269,15 +253,15 @@ This category does not contain any nested items. Selected database does not exist (yet). - + MySQL/MariaDB (dedicated database) - + SQLite (embedded database) - + @@ -289,14 +273,17 @@ This category does not contain any nested items. Click me to add feeds from this website. This website contains %n feed(s). - + + + + DownloadItem Ico - + Filename @@ -304,103 +291,109 @@ This website contains %n feed(s). Error opening output file: %1 - + &Try again - + &Stop - + &Open file - + Select destination for downloaded file - + Error: %1 - + Fehler: %1. {1?} Download directory couldn't be created - + Error when saving file: %1 - + %1 of %2 (%3 per second) - %4 - + %1 of %2 - download completed - + Open &directory - + Cannot open file - + Cannot open output file. Open it manually. - + Cannot open directory - + Cannot open output directory. Open it manually. - + Download finished - + - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. - + URL: %1 - + Local file: %1 - + Selection of local file cancelled. - + DownloadManager Clean up - + %n minutes remaining - + + + + %n seconds remaining - + + + + bytes - + kB @@ -416,47 +409,10 @@ Click here to open parent directory. Downloading %n file(s)... - - - - - Feed - - does not use auto-update - Describes feed auto-update status. - - - - uses global settings - Describes feed auto-update status. - - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - - - - Metadata not fetched - - - - Metadata was not fetched because: %1 - + + + + @@ -465,58 +421,32 @@ Auto-update status: %5 Toolbar for messages Toolbar für Nachrichten - - Feed update started - Text display in status bar when feed update is started. - Feed update begonnen - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - Upgedateter feed '%1' - Toolbar for feeds Toolbar für Feeds - - Error when loading initial feeds - - Cannot cleanup database - + Cannot cleanup database, because another critical action is running. - - - - Cannot update all items - - - - You cannot update all items because another another critical operation is ongoing. - - - - New messages downloaded - + FeedsImportExportModel (category) - + (feed) - + Category - + @@ -539,25 +469,46 @@ Auto-update status: %5 Name of root item of feed list which can be seen in feed add/edit dialog. Wurzel - - Invalid tree data. - - - - Import successfull, but some feeds/categories were not imported due to error. - - - - Import was completely successfull. - - Starting auto-update of some feeds - + I will auto-update %n feed(s). - + + + + + + + Cannot update all items + + + + You cannot update all items because another another critical operation is ongoing. + + + + Feed update started + Text display in status bar when feed update is started. + Feed update begonnen + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + Upgedateter feed '%1' + + + New messages downloaded + + + + You can't transfer dragged item into different account, this is not supported. + + + + Cannot perform drag & drop operation + @@ -569,14 +520,6 @@ Auto-update status: %5 FeedsView - - Cannot add standard category - Kann die Standardkategorie nicht hinzufügen - - - Cannot add standard feed - Kann den Standard-Feed nicht hinzufügen - Cannot edit item Kann das Item nicht editieren @@ -585,65 +528,54 @@ Auto-update status: %5 Cannot delete item Item kann nicht gelöscht werden - - You are about to delete selected feed or category. - - - - Deletion of item failed. - - - - Selected item was not deleted due to error. - - - - Do you really want to delete selected item? - Möchtest du dieses Item wirklich löschen? - - - Permanently delete messages - Lösche diese Nachrichten dauerhaft - - - You are about to permanenty delete all messages from your recycle bin. - - - - Do you really want to empty your recycle bin? - Möchtest du den Mülleimer wirklich leeren? - Context menu for empty space - - - - Context menu for recycle bin - - - - You cannot add new standard category now because another critical operation is ongoing. - - - - You cannot add new standard feed now because another critical operation is ongoing. - + Selected item cannot be edited because another critical operation is ongoing. - + Selected item cannot be deleted because another critical operation is ongoing. - - - - Delete feed/category - + Context menu for categories - + + + + Selected item cannot be edited, this is not (yet?) supported. + + + + Deleting "%1" + + + + You are about to completely delete item "%1". + + + + Are you sure? + + + + Cannot delete "%1" + + + + This item cannot be deleted because something critically failed. Submit bug report. + + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + + + + Context menu for other items + @@ -692,45 +624,84 @@ Auto-update status: %5 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> <b>%8</b><br><b>Version:</b> %1 (gebildet am %2 mit CMake %3)<br><b>Revision:</b> %4<br><b>Bildungsdatum:</b> %5<br><b>Qt:</b> %6 (kompiliert unter %7)<br> - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - - About %1 About RSS Guard dialog title. - + Settings type - + Settings file - + Database root path - + FULLY portable - + PARTIALLY portable - + Resources - + + + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + + FormAddAccount + + Add new account + + + + Details + + + + Name + Name + + + Version + Version + + + Author + Author + + + Description + Beschreibung + + + Cannot add account + + + + Some critical error occurred, report this to developers. + + + + This account can be added only once. + FormBackupDatabaseSettings Backup database/settings - + Backup properties @@ -750,633 +721,125 @@ Auto-update status: %5 Backup name - + Operation results - + Common name for backup files - + No operation executed yet. - + Backup was created successfully. - + Backup name cannot be empty. - + Backup name looks okay. - + Backup failed. - + Output directory - + &Select directory - + Backup was created successfully and stored in target directory. - + Select destination directory - + Good destination directory is specified. - - - - - FormCategoryDetails - - Parent category - Stammkategorie - - - Select parent item for your category. - Selektieren Sie das Stamm-Item für Ihre Kategorie - - - Title - Titel - - - Description - Beschreibung - - - Icon - Icon - - - Select icon for your category. - Selektieren Sie das Icon für Ihre Kategorie - - - Add new category - Füge neue Kategorie hinzu - - - Edit existing category - Bearbeite bestehende Kategorie - - - Cannot add category - Kategorie kann nicht hinzugefügt werden - - - Category was not added due to error. - Kategorie wurde nicht hinzugefügt aufgrund eines Fehler. - - - Cannot edit category - Kategorie kann nicht editiert werden - - - Category was not edited due to error. - Kategorie wurde nicht editiert aufgrund eines Fehler. - - - Category name is ok. - Kategoriename ist okay. - - - Category name is too short. - Kategoriename ist zu kurz. - - - Description is empty. - Beschreibung ist leer. - - - Select icon file for the category - Selektiere die Icon-Datei für die Kategorie - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Bilder (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Selektiere Icon - - - Cancel - Abbrechen - - - Look in: - Label to describe the folder for icon file selection dialog. - Schauen Sie in: - - - Icon name: - Icon-Name: - - - Icon type: - Icon-Typ: - - - Category title - Kategorietitel - - - Set title for your category. - Setzen Sie den Titel für die Kategorie. - - - Category description - Kategoriebeschreibung - - - Set description for your category. - Setzen Sie die Beschreibung für die Kategorie. - - - Icon selection - Icon-Selektion - - - Load icon from file... - Lade Icon aus Datei... - - - Do not use icon - Icon nicht verwenden - - - Use default icon - Standard-Icon verwenden - - - The description is ok. - Die Beschreibung ist OK. + FormDatabaseCleanup Cleanup database - + Remove all messages older than - + day(s) - + + + + Shrink database file - + Database information - + Database file size - + Database type - + Progress - + I am ready. - + Database cleanup is running. - + Database cleanup is completed. - + Database cleanup failed. - + Cleanup settings (all checked items are completely erased from database) - + Remove all read messages (not those from recycle bin) - + Remove all messages from recycle bin - + Remove all starred messages (including those from recycle bin) - - - - - FormFeedDetails - - Parent category - Stammkategorie - - - Select parent item for your feed. - Selektieren Sie das Stamm-Item für Ihren Feed. - - - Type - Typ - - - Select type of the standard feed. - Selektiere den Typ des Standard-Feeds. - - - Encoding - Enkodierung - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - Selektiere die Enkodierung des Standard-Feeds. Falls Sie unsicher sind wählen einfach die "UTF-8" Enkodierung. - - - Auto-update - Auto-Update - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - - - - minutes - Minuten - - - Title - Titel - - - Description - Beschreibung - - - URL - URL - - - Fetch it now - Jetzt abrufen - - - Icon - Icon - - - Select icon for your feed. - Selektieren Sie das Icon für Ihren Feed. - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - Gewisse Feeds brauchen eine Authentifizierung, wie z.b. Gmail-Feeds. BASIC, NTLM-2 und DIGEST-MD5 Authentifizierungsmodelle werden unterstützt. - - - Requires authentication - Benötigt Authentifizierung - - - Username - Benutzername - - - Password - Passwort - - - Fetch metadata - Metadaten abrufen - - - Add new feed - - - - Edit existing feed - - - - Feed name is ok. - Feed-Name ist okay. - - - Feed name is too short. - Feed-Name ist zu kurz. - - - Description is empty. - Beschreibung ist leer. - - - The url is ok. - Die URL ist okay. - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - Die URL entspricht nicht dem Standardmuster. Beginnt Ihre URL mit "http://" oder "https://"? - - - The url is empty. - Die URL ist leer. - - - Username is ok or it is not needed. - Benutzername ist okay oder wird nicht benötigt. - - - Username is empty. - Benutzername ist leer. - - - Password is ok or it is not needed. - Passwort ist okay oder wird nicht benötigt. - - - Password is empty. - Passwort ist leer. - - - Select icon file for the feed - Selektiere die Icon-Datei für den Feed. - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Bilder (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Selektiere Icon - - - Cancel - Abbrechen - - - Look in: - Label for field with icon file name textbox for selection dialog. - Schauen Sie in: - - - Icon name: - Icon-Name: - - - Icon type: - Icon-Typ: - - - Cannot add feed - Kann Feed nicht hinzufügen - - - Feed was not added due to error. - Feed wurde nicht hinzugefügt aufgrund eines Fehler. - - - Cannot edit feed - Feed kann nicht editiert werden - - - All metadata fetched successfully. - Alle Metadaten wurden erfolgreich abgerufen. - - - Feed and icon metadata fetched. - Feed- und Icon-Metadaten abgerufen. - - - Result: %1. - Resultat: %1. - - - Feed or icon metatada not fetched. - Feed- oder Icon-Metadaten nicht abgerufen. - - - Error: %1. - Fehler: %1. - - - No metadata fetched. - Keine Metadaten abgerufen. - - - Feed title - Feed-Titel - - - Set title for your feed. - Setzen Sie den Titel für Ihren Feed. - - - Feed description - Feed-Beschreibung - - - Set description for your feed. - Setzen Sie die Beschreibung für Ihren Feed. - - - Full feed url including scheme - - - - Set url for your feed. - Setze die URL für Ihren Feed. - - - Set username to access the feed. - Setzen Sie den Benutzernamen um zum Feed zu gelangen. - - - Set password to access the feed. - Setzen Sie das Passwort um zum Feed zu gelangen. - - - Icon selection - Icon-Selektion - - - Load icon from file... - Lade Icon aus Datei... - - - Do not use icon - Icon nicht verwenden - - - Use default icon - Standard-Icon verwenden - - - No metadata fetched so far. - Bisher keine Metadaten abgerufen. - - - Auto-update using global interval - Auto-Update benutzt globales Intervall - - - Auto-update every - Auto-Update alle - - - Do not auto-update at all - Kein Auto-Update ausführen - - - The description is ok. - Die Beschreibung ist OK. - - - Feed was not edited due to error. - Feed wurde nicht editiert aufgrund eines Fehler. - - - Icon fetched successfully. - - - - Icon metadata fetched. - - - - Icon metatada not fetched. - - - - No icon fetched. - - - - Fetch icon from feed - - - - - FormImportExport - - &Select file - - - - Operation results - - - - No file is selected. - Keine Datei ausgewählt - - - No operation executed yet. - - - - Export feeds - - - - Destination file - - - - Source feeds && categories - - - - Source file - - - - Target feeds && categories - - - - Import feeds - - - - OPML 2.0 files (*.opml) - - - - Select file for feeds export - - - - File is selected. - - - - Select file for feeds import - - - - Cannot open source file. - - - - Feeds were loaded. - - - - Error, file is not well-formed. Select another file. - - - - Error occurred. File is not well-formed. Select another file. - - - - Feeds were exported successfully. - - - - Cannot write into destination file. - - - - Critical error occurred. - - - - &Check all items - - - - &Uncheck all items - + @@ -1453,22 +916,6 @@ Auto-update status: %5 No actions are available right now. Keine Funktionen verfügbar. - - Fee&ds && categories - Fee&ds && Kategorien - - - Mark all messages (without message filters) from selected feeds as read. - Markiere alle Nachrichten (ohne Nachrichtenfilter) der selektierten Feeds als gelesen. - - - Mark all messages (without message filters) from selected feeds as unread. - Markiere alle Nachrichten (ohne Nachrichtenfilter) der selektierten Feeds als ungelesen. - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - Zeige alle Nachrichten der selektierten Feeds/Kategorien in einem neuen Zeitungsmodusreiter an. Bemerke, dass Nachrichten nicht automatisch als gelesen gesetzt werden. - Hides main window if it is visible and shows it if it is hidden. Hauptfenster verstecken falls es sichtbar was oder sichtbar falls es versteckt war. @@ -1483,43 +930,15 @@ Auto-update status: %5 &About application - + Displays extra info about this application. - + &Delete selected messages - - - - Deletes all messages from selected feeds. - - - - Marks all messages in all feeds read. This does not take message filters into account. - - - - Deletes all messages from all feeds. - - - - Update &all feeds - - - - Update &selected feeds - - - - &Edit selected feed/category - - - - &Delete selected feed/category - + Settings @@ -1527,298 +946,330 @@ Auto-update status: %5 Hides or displays the main menu. - - - - Add &new feed/category - + &Close all tabs except current one - + &Close current tab - + Mark &selected messages as &read - + Mark &selected messages as &unread - - - - &Mark selected feeds as read - - - - &Mark selected feeds as unread - - - - &Clean selected feeds - + Open selected source articles in &external browser - + Open selected messages in &internal browser - + Open selected source articles in &internal browser - - - - &Mark all feeds as &read - - - - View selected feeds in &newspaper mode - - - - &Clean all feeds - - - - Select &next feed/category - - - - Select &previous feed/category - + Select &next message - + Select &previous message - + Check for &updates - + Enable &JavaScript - + Enable external &plugins - + Auto-load &images - + Show/hide - + &Fullscreen - + &Feed list - + &Main menu - + Switch visibility of main &window - + Cannot open external browser - + Cannot open external browser. Navigate to application website manually. - - - - New &feed - - - - Add new feed. - - - - New &category - - - - Add new category. - + &Toolbars - + Switch visibility of main toolbars. - + &Feed/message list headers - - - - &Import feeds - - - - Imports feeds you want from selected file. - - - - &Export feeds - - - - Exports feeds you want to selected file. - + Close all tabs except current one. - - - - &Recycle bin - + Report a &bug (GitHub)... - + Report a bug (BitBucket)... - + &Donate via PayPal - + Display &wiki - - - - &Empty recycle bin - - - - &Restore all messages - - - - Restore &selected messages - + &Restart - + &Restore database/settings - + &Backup database/settings - + Switch message list layout orientation - + &Downloads - + Send selected message via e-mail - + &Cleanup database - + - Show only unread feeds/categories - + Add &new item + - &Fetch feed metadata - + Update &all items + - &Expand/collapse selected feed/category - + Update &selected items + + + + &Edit selected item + + + + &Delete selected item + + + + &Mark selected items as read + + + + Mark all messages (without message filters) from selected items as read. + + + + &Mark selected items as unread + + + + Mark all messages (without message filters) from selected items as unread. + + + + &Clean selected items + + + + Deletes all messages from selected items. + + + + &Mark all items as &read + + + + Marks all messages in all items read. This does not take message filters into account. + + + + View selected items in &newspaper mode + + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + + &Clean all items + + + + Deletes all messages from all items. + + + + Select &next item + + + + Select &previous item + + + + Show only unread items + + + + &Expand/collapse selected item + + + + &Add new service account + + + + &Restore selected messages + + + + No possible actions + + + + Feeds && categories && accounts + + + + &Recycle bin(s) + + + + &Restore all recycle bins + + + + &Empty all recycle bins + + + + Select next &unread message + + + + No recycle bin + + + + Restore recycle bin + + + + Empty recycle bin + FormRestoreDatabaseSettings Restore database/settings - + Operation results - + Restore database - + Restore settings - + Restart - + No operation executed yet. - + Restoration was initiated. Restart to proceed. - + You need to restart application for restoration process to finish. - + Source directory - + &Select directory - + Database and/or settings were not copied to restoration directory successully. - + Select source directory - + Good source directory is specified. - + @@ -1914,10 +1365,6 @@ Auto-update status: %5 Author Author - - Email - Email - Socks5 Socks5 @@ -2037,7 +1484,7 @@ Auto-update status: %5 Parameters to executable - + some keyboard shortcuts are not unique @@ -2087,7 +1534,7 @@ Disadvantages: <li>application startup and shutdown can take little longer (max. 2 seconds).</li> </ul> Authors of this application are NOT responsible for lost data. - + in-memory database switched @@ -2243,65 +1690,65 @@ Authors of this application are NOT responsible for lost data. You did not executed any connection test yet. - + Launch %1 on operating system startup - + Enable JavaScript - + Enable external plugins based on NPAPI - + Auto-load images - + <html><head/><body><p>If unchecked, then default system-wide web browser is used.</p></body></html> - + Feeds && categories - + Message count format in feed list - + Enter format for count of messages displayed next to each feed/category in feed list. Use "%all" and "%unread" strings which are placeholders for the actual count of all (or unread) messages. - + custom external browser is not set correctly - + Toolbars - + Toolbar for feeds list - + Toolbar for messages list - + Select toolbar to edit - + Some critical settings were changed and will be applied after the application gets restarted. You have to restart manually. - + Do you want to restart now? @@ -2309,70 +1756,70 @@ You have to restart manually. Check for updates on application startup - + Use custom date/time format (overrides format loaded from active localization) - + Executables (*) File filter for external browser selection dialog. ---------- File filter for external e-mail selection dialog. - + Remove all read messages from all feeds on application exit - + When new message arrives from feed and duplicate exists, then its content is updated and new message is dropped. - + Remove duplicate messages - + Downloads - + Target directory for downloaded files - + Ask for each individual downloaded file - + Target directory where all downloaded files are saved - + &Browse - + Select downloads target directory - + &Show password - + Web browser & e-mail & proxy - + Remove junk Trolltech registry key (HKCU\Software\Trolltech) when application quits (Use at your own risk!) - + Working database - + Mouse gestures work with middle mouse button. Possible gestures are: @@ -2380,101 +1827,620 @@ File filter for external e-mail selection dialog. • next web page (drag mouse right), • reload current web page (drag mouse up), • open new web browser tab (drag mouse down). - + Use custom external web browser - + External e-mail client - + Use custom external e-mail client - + E-mail client executable - + Executable file of e-mail client - + Select client - + Placeholders: • %1 - title of selected message, • %2 - body of selected message. - + Save all downloaded files to - + Select e-mail executable - + Mozilla Thunderbird - + Working database which you have full access to. - + Working database is empty. - + Working database is ok. - + Notification position - + (Tray icon is not available.) - + Bottom-left corner - + Top-left corner - + Bottom-right corner - + Top-right corner - + Internal message browser fonts - + Standard font - + Note that speed of used MySQL server and latency of used connection medium HEAVILY influences the final performance of this application. Using slow database connections leads to bad performance when browsing feeds or messages. - + Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) - + + + + E-mail + + + + Enable notifications + + + + + FormStandardCategoryDetails + + Parent category + Stammkategorie + + + Select parent item for your category. + Selektieren Sie das Stamm-Item für Ihre Kategorie + + + Title + Titel + + + Description + Beschreibung + + + Icon + Icon + + + Select icon for your category. + Selektieren Sie das Icon für Ihre Kategorie + + + Add new category + Füge neue Kategorie hinzu + + + Edit existing category + Bearbeite bestehende Kategorie + + + Cannot add category + Kategorie kann nicht hinzugefügt werden + + + Category was not added due to error. + Kategorie wurde nicht hinzugefügt aufgrund eines Fehler. + + + Cannot edit category + Kategorie kann nicht editiert werden + + + Category was not edited due to error. + Kategorie wurde nicht editiert aufgrund eines Fehler. + + + Category name is ok. + Kategoriename ist okay. + + + Category name is too short. + Kategoriename ist zu kurz. + + + Description is empty. + Beschreibung ist leer. + + + The description is ok. + Die Beschreibung ist OK. + + + Select icon file for the category + Selektiere die Icon-Datei für die Kategorie + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Bilder (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Selektiere Icon + + + Cancel + Abbrechen + + + Look in: + Label to describe the folder for icon file selection dialog. + Schauen Sie in: + + + Icon name: + Icon-Name: + + + Icon type: + Icon-Typ: + + + Category title + Kategorietitel + + + Set title for your category. + Setzen Sie den Titel für die Kategorie. + + + Category description + Kategoriebeschreibung + + + Set description for your category. + Setzen Sie die Beschreibung für die Kategorie. + + + Icon selection + Icon-Selektion + + + Load icon from file... + Lade Icon aus Datei... + + + Do not use icon + Icon nicht verwenden + + + Use default icon + Standard-Icon verwenden + + + + FormStandardFeedDetails + + Parent category + Stammkategorie + + + Select parent item for your feed. + Selektieren Sie das Stamm-Item für Ihren Feed. + + + Type + Typ + + + Select type of the standard feed. + Selektiere den Typ des Standard-Feeds. + + + Encoding + Enkodierung + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + Selektiere die Enkodierung des Standard-Feeds. Falls Sie unsicher sind wählen einfach die "UTF-8" Enkodierung. + + + Auto-update + Auto-Update + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + + + + minutes + Minuten + + + Title + Titel + + + Description + Beschreibung + + + URL + URL + + + Fetch it now + Jetzt abrufen + + + Icon + Icon + + + Select icon for your feed. + Selektieren Sie das Icon für Ihren Feed. + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + Gewisse Feeds brauchen eine Authentifizierung, wie z.b. Gmail-Feeds. BASIC, NTLM-2 und DIGEST-MD5 Authentifizierungsmodelle werden unterstützt. + + + Requires authentication + Benötigt Authentifizierung + + + Username + Benutzername + + + Password + Passwort + + + Fetch metadata + Metadaten abrufen + + + Add new feed + + + + Edit existing feed + + + + Feed name is ok. + Feed-Name ist okay. + + + Feed name is too short. + Feed-Name ist zu kurz. + + + Description is empty. + Beschreibung ist leer. + + + The description is ok. + Die Beschreibung ist OK. + + + The url is ok. + Die URL ist okay. + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + Die URL entspricht nicht dem Standardmuster. Beginnt Ihre URL mit "http://" oder "https://"? + + + The url is empty. + Die URL ist leer. + + + Username is ok or it is not needed. + Benutzername ist okay oder wird nicht benötigt. + + + Username is empty. + Benutzername ist leer. + + + Password is ok or it is not needed. + Passwort ist okay oder wird nicht benötigt. + + + Password is empty. + Passwort ist leer. + + + Select icon file for the feed + Selektiere die Icon-Datei für den Feed. + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Bilder (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Selektiere Icon + + + Cancel + Abbrechen + + + Look in: + Label for field with icon file name textbox for selection dialog. + Schauen Sie in: + + + Icon name: + Icon-Name: + + + Icon type: + Icon-Typ: + + + Cannot add feed + Kann Feed nicht hinzufügen + + + Feed was not added due to error. + Feed wurde nicht hinzugefügt aufgrund eines Fehler. + + + Cannot edit feed + Feed kann nicht editiert werden + + + Feed was not edited due to error. + Feed wurde nicht editiert aufgrund eines Fehler. + + + All metadata fetched successfully. + Alle Metadaten wurden erfolgreich abgerufen. + + + Feed and icon metadata fetched. + Feed- und Icon-Metadaten abgerufen. + + + Result: %1. + Resultat: %1. + + + Feed or icon metatada not fetched. + Feed- oder Icon-Metadaten nicht abgerufen. + + + Error: %1. + Fehler: %1. + + + No metadata fetched. + Keine Metadaten abgerufen. + + + Icon fetched successfully. + + + + Icon metadata fetched. + + + + Icon metatada not fetched. + + + + No icon fetched. + + + + Feed title + Feed-Titel + + + Set title for your feed. + Setzen Sie den Titel für Ihren Feed. + + + Feed description + Feed-Beschreibung + + + Set description for your feed. + Setzen Sie die Beschreibung für Ihren Feed. + + + Full feed url including scheme + + + + Set url for your feed. + Setze die URL für Ihren Feed. + + + Set username to access the feed. + Setzen Sie den Benutzernamen um zum Feed zu gelangen. + + + Set password to access the feed. + Setzen Sie das Passwort um zum Feed zu gelangen. + + + Icon selection + Icon-Selektion + + + Load icon from file... + Lade Icon aus Datei... + + + Do not use icon + Icon nicht verwenden + + + Use default icon + Standard-Icon verwenden + + + Fetch icon from feed + + + + No metadata fetched so far. + Bisher keine Metadaten abgerufen. + + + Auto-update using global interval + Auto-Update benutzt globales Intervall + + + Auto-update every + Auto-Update alle + + + Do not auto-update at all + Kein Auto-Update ausführen + + + + FormStandardImportExport + + &Select file + + + + &Check all items + + + + &Uncheck all items + + + + Operation results + + + + No file is selected. + Keine Datei ausgewählt + + + No operation executed yet. + + + + Destination file + + + + Source feeds && categories + + + + Export feeds + + + + Source file + + + + Target feeds && categories + + + + Import feeds + + + + OPML 2.0 files (*.opml) + + + + Select file for feeds export + + + + File is selected. + + + + Select file for feeds import + + + + Cannot open source file. + + + + Feeds were loaded. + + + + Error, file is not well-formed. Select another file. + + + + Error occurred. File is not well-formed. Select another file. + + + + Feeds were exported successfully. + + + + Cannot write into destination file. + + + + Critical error occurred. + @@ -2536,87 +2502,87 @@ die aktuell installierte. Update - + Download new installation files. - + Checking for updates failed. - + Download installation file for your OS. - + Installation file is not available directly. Go to application website to obtain it manually. - + No new update available. - + Cannot update application - + Cannot navigate to installation file. Check new installation downloads manually on project website. - + Download update - + Downloaded %1% (update size is %2 kB). - + Downloading update... - + Downloaded successfully - + Package was downloaded successfully. - + Install update - + Error occured - + Error occured during downloading of the package. - + Cannot launch external updater. Update application manually. - + Go to application website - + IOFactory Cannot open file '%1' for reading. - + Cannot open file '%1' for writting. - + @@ -2710,54 +2676,62 @@ Go to application website to obtain it manually. Permanently deleted - + Is message permanently deleted from recycle bin? - + Attachments - + List of attachments. - + + + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + MessagesToolBar Search messages - + Message search box - + Menu for highlighting messages - + No extra highlighting - + Highlight unread messages - + Highlight important messages - + Display all messages - + Message highlighter - + Toolbar spacer @@ -2788,11 +2762,11 @@ Go to application website to obtain it manually. Problem with starting external e-mail client - + External e-mail client could not be started. - + @@ -2810,22 +2784,22 @@ Go to application website to obtain it manually. connection refused Network status. - + connection timed out Network status. - + SSL handshake failed Network status. - + proxy server connection refused Network status. - + temporary failure @@ -2835,17 +2809,17 @@ Go to application website to obtain it manually. authentication failed Network status. - + proxy authentication required Network status. - + proxy server not found Network status. - + uknown content @@ -2865,15 +2839,15 @@ Go to application website to obtain it manually. no errors Network status. - + access to content was denied - + connection timed out or was cancelled - + @@ -2897,61 +2871,198 @@ Go to application website to obtain it manually. LANG_EMAIL patlecat@gmail.com - - Load initial feeds - - - - Do you want to load initial set of feeds? - - LANG_NAME Name of language, e.g. English. Deutsch - - You started %1 for the first time, now you can load initial set of feeds. - + + + ++ %n other feeds. + + + + - Welcome to %1 %2. - + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + + + + Welcome to %1. + + + + Load initial set of feeds + RecycleBin Recycle bin - + Recycle bin contains all deleted messages from all feeds. - + Recycle bin %1 - + %n deleted message(s). - + + + + ShortcutCatcher Reset to original shortcut. - + Clear current shortcut. - + Click and hit new shortcut. - + + + + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + + + + +This category does not contain any nested items. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardFeed + + Metadata not fetched + + + + Metadata was not fetched because: %1. + + + + does not use auto-update + Describes feed auto-update status. + + + + uses global settings + Describes feed auto-update status. + + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + + + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + + + + You started %1 for the first time, now you can load initial set of feeds. + + + + Do you want to load initial set of feeds? + + + + Error when loading initial feeds + + + + This is service account for standard RSS/RDF/ATOM feeds. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + Fetch metadata + Metadaten abrufen + + + Import successfull, but some feeds/categories were not imported due to error. + + + + Import was completely successfull. + + + + Add new category + Füge neue Kategorie hinzu + + + Add new feed + + + + Export feeds + + + + Import feeds + @@ -2962,18 +3073,22 @@ Go to application website to obtain it manually. Switch application between fulscreen/normal states right from this status bar icon. - + SystemFactory New version available - + Click the bubble for more information. - + + + + anonymous + @@ -2981,7 +3096,7 @@ Go to application website to obtain it manually. %1 Unread news: %2 - + @@ -3012,42 +3127,42 @@ Unread news: %2 Displays main menu. - + Main menu - + Open new web browser tab. - + Downloads - + ToolBarEditor Activated actions - + Available actions - + Insert separator - + Insert spacer - + Separator - + Toolbar spacer @@ -3055,23 +3170,23 @@ Unread news: %2 Move action up - + Move action down - + Add selected action - + Delete selected action - + Delete all actions - + @@ -3081,6 +3196,21 @@ Unread news: %2 Schliessen Sie zuerst alle modalen Fenster. + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + WebBrowser @@ -3152,6 +3282,14 @@ Unread news: %2 Stop web page loading. Stoppe das laden der Webseite. + + Cannot add feed + Kann Feed nicht hinzufügen + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + + WebView @@ -3205,91 +3343,91 @@ Unread news: %2 Copies current selection into the clipboard. - + Copy link url to clipboard. - + Copy image to clipboard. - + Copy image url to clipboard. - + Open this hyperlink in new tab. - + Open the hyperlink in this tab. - + Open this image in this tab. - + Open link in external browser - + Open the hyperlink in external browser. - + Print - + Print current web page. - + HTML web pages (*.html) - + Select destination file for web page - + Cannot save web page - + Web page cannot be saved because destination file is not writtable. - + Save target as... - + Download content from the hyperlink. - + Save page as... - + Save image to disk. - + Save image as... - + source_page - + Search "%1" via Google... - + - \ No newline at end of file + diff --git a/localization/rssguard-en_GB.ts b/localization/rssguard-en_GB.ts index a85ae6343..e2ca47af6 100644 --- a/localization/rssguard-en_GB.ts +++ b/localization/rssguard-en_GB.ts @@ -196,27 +196,6 @@ Also note that some resources are cached by internal web browser. Thus, after ch - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - - - - -This category does not contain any nested items. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - - - - - - DatabaseCleaner @@ -436,76 +415,16 @@ Click here to open parent directory. - - Feed - - does not use auto-update - Describes feed auto-update status. - - - - uses global settings - Describes feed auto-update status. - - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - - - - - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - - - - - - - Metadata not fetched - - - - Metadata was not fetched because: %1 - - - FeedMessageViewer Toolbar for messages - - Feed update started - Text display in status bar when feed update is started. - - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - - Toolbar for feeds - - Error when loading initial feeds - - Cannot cleanup database @@ -514,18 +433,6 @@ Auto-update status: %5 Cannot cleanup database, because another critical action is running. - - Cannot update all items - - - - You cannot update all items because another another critical operation is ongoing. - - - - New messages downloaded - - FeedsImportExportModel @@ -562,18 +469,6 @@ Auto-update status: %5 Name of root item of feed list which can be seen in feed add/edit dialog. - - Invalid tree data. - - - - Import successfull, but some feeds/categories were not imported due to error. - - - - Import was completely successfull. - - Starting auto-update of some feeds @@ -585,6 +480,36 @@ Auto-update status: %5 + + Cannot update all items + + + + You cannot update all items because another another critical operation is ongoing. + + + + Feed update started + Text display in status bar when feed update is started. + + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + + + + New messages downloaded + + + + You can't transfer dragged item into different account, this is not supported. + + + + Cannot perform drag & drop operation + + FeedsToolBar @@ -595,14 +520,6 @@ Auto-update status: %5 FeedsView - - Cannot add standard category - - - - Cannot add standard feed - - Cannot edit item @@ -611,50 +528,10 @@ Auto-update status: %5 Cannot delete item - - You are about to delete selected feed or category. - - - - Deletion of item failed. - - - - Selected item was not deleted due to error. - - - - Do you really want to delete selected item? - - - - Permanently delete messages - - - - You are about to permanenty delete all messages from your recycle bin. - - - - Do you really want to empty your recycle bin? - - Context menu for empty space - - Context menu for recycle bin - - - - You cannot add new standard category now because another critical operation is ongoing. - - - - You cannot add new standard feed now because another critical operation is ongoing. - - Selected item cannot be edited because another critical operation is ongoing. @@ -664,11 +541,40 @@ Auto-update status: %5 - Delete feed/category + Context menu for categories - Context menu for categories + Selected item cannot be edited, this is not (yet?) supported. + + + + Deleting "%1" + + + + You are about to completely delete item "%1". + + + + Are you sure? + + + + Cannot delete "%1" + + + + This item cannot be deleted because something critically failed. Submit bug report. + + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + + + + Context menu for other items @@ -718,10 +624,6 @@ Auto-update status: %5 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - - About %1 About RSS Guard dialog title. @@ -751,6 +653,49 @@ Auto-update status: %5 Resources + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + + FormAddAccount + + Add new account + + + + Details + + + + Name + + + + Version + + + + Author + + + + Description + + + + Cannot add account + + + + Some critical error occurred, report this to developers. + + + + This account can be added only once. + + FormBackupDatabaseSettings @@ -827,134 +772,6 @@ Auto-update status: %5 - - FormCategoryDetails - - Parent category - - - - Select parent item for your category. - - - - Title - - - - Description - - - - Icon - - - - Select icon for your category. - - - - Add new category - - - - Edit existing category - - - - Cannot add category - - - - Category was not added due to error. - - - - Cannot edit category - - - - Category was not edited due to error. - - - - Category name is ok. - - - - Category name is too short. - - - - Description is empty. - - - - Select icon file for the category - - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - - Select icon - - - - Cancel - - - - Look in: - Label to describe the folder for icon file selection dialog. - - - - Icon name: - - - - Icon type: - - - - Category title - - - - Set title for your category. - - - - Category description - - - - Set description for your category. - - - - Icon selection - - - - Load icon from file... - - - - Do not use icon - - - - Use default icon - - - - The description is ok. - - - FormDatabaseCleanup @@ -1025,389 +842,6 @@ Auto-update status: %5 - - FormFeedDetails - - Parent category - - - - Select parent item for your feed. - - - - Type - - - - Select type of the standard feed. - - - - Encoding - - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - - - - Auto-update - - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - - - - minutes - - - - Title - - - - Description - - - - URL - - - - Fetch it now - - - - Icon - - - - Select icon for your feed. - - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - - - - Requires authentication - - - - Username - - - - Password - - - - Fetch metadata - - - - Add new feed - - - - Edit existing feed - - - - Feed name is ok. - - - - Feed name is too short. - - - - Description is empty. - - - - The url is ok. - - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - - - - The url is empty. - - - - Username is ok or it is not needed. - - - - Username is empty. - - - - Password is ok or it is not needed. - - - - Password is empty. - - - - Select icon file for the feed - - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - - Select icon - - - - Cancel - - - - Look in: - Label for field with icon file name textbox for selection dialog. - - - - Icon name: - - - - Icon type: - - - - Cannot add feed - - - - Feed was not added due to error. - - - - Cannot edit feed - - - - All metadata fetched successfully. - - - - Feed and icon metadata fetched. - - - - Result: %1. - - - - Feed or icon metatada not fetched. - - - - Error: %1. - - - - No metadata fetched. - - - - Feed title - - - - Set title for your feed. - - - - Feed description - - - - Set description for your feed. - - - - Full feed url including scheme - - - - Set url for your feed. - - - - Set username to access the feed. - - - - Set password to access the feed. - - - - Icon selection - - - - Load icon from file... - - - - Do not use icon - - - - Use default icon - - - - No metadata fetched so far. - - - - Auto-update using global interval - - - - Auto-update every - - - - Do not auto-update at all - - - - The description is ok. - - - - Feed was not edited due to error. - - - - Icon fetched successfully. - - - - Icon metadata fetched. - - - - Icon metatada not fetched. - - - - No icon fetched. - - - - Fetch icon from feed - - - - - FormImportExport - - &Select file - - - - Operation results - - - - No file is selected. - - - - No operation executed yet. - - - - Export feeds - - - - Destination file - - - - Source feeds && categories - - - - Source file - - - - Target feeds && categories - - - - Import feeds - - - - OPML 2.0 files (*.opml) - - - - Select file for feeds export - - - - File is selected. - - - - Select file for feeds import - - - - Cannot open source file. - - - - Feeds were loaded. - - - - Error, file is not well-formed. Select another file. - - - - Error occurred. File is not well-formed. Select another file. - - - - Feeds were exported successfully. - - - - Cannot write into destination file. - - - - Critical error occurred. - - - - &Check all items - - - - &Uncheck all items - - - FormMain @@ -1482,22 +916,6 @@ Auto-update status: %5 No actions are available right now. - - Fee&ds && categories - - - - Mark all messages (without message filters) from selected feeds as read. - - - - Mark all messages (without message filters) from selected feeds as unread. - - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - - Hides main window if it is visible and shows it if it is hidden. @@ -1522,34 +940,6 @@ Auto-update status: %5 &Delete selected messages - - Deletes all messages from selected feeds. - - - - Marks all messages in all feeds read. This does not take message filters into account. - - - - Deletes all messages from all feeds. - - - - Update &all feeds - - - - Update &selected feeds - - - - &Edit selected feed/category - - - - &Delete selected feed/category - - Settings @@ -1558,10 +948,6 @@ Auto-update status: %5 Hides or displays the main menu. - - Add &new feed/category - - &Close all tabs except current one @@ -1578,18 +964,6 @@ Auto-update status: %5 Mark &selected messages as &unread - - &Mark selected feeds as read - - - - &Mark selected feeds as unread - - - - &Clean selected feeds - - Open selected source articles in &external browser @@ -1602,26 +976,6 @@ Auto-update status: %5 Open selected source articles in &internal browser - - &Mark all feeds as &read - - - - View selected feeds in &newspaper mode - - - - &Clean all feeds - - - - Select &next feed/category - - - - Select &previous feed/category - - Select &next message @@ -1674,22 +1028,6 @@ Auto-update status: %5 Cannot open external browser. Navigate to application website manually. - - New &feed - - - - Add new feed. - - - - New &category - - - - Add new category. - - &Toolbars @@ -1702,30 +1040,10 @@ Auto-update status: %5 &Feed/message list headers - - &Import feeds - - - - Imports feeds you want from selected file. - - - - &Export feeds - - - - Exports feeds you want to selected file. - - Close all tabs except current one. - - &Recycle bin - - Report a &bug (GitHub)... @@ -1742,18 +1060,6 @@ Auto-update status: %5 Display &wiki - - &Empty recycle bin - - - - &Restore all messages - - - - Restore &selected messages - - &Restart @@ -1783,15 +1089,131 @@ Auto-update status: %5 - Show only unread feeds/categories + Add &new item - &Fetch feed metadata + Update &all items - &Expand/collapse selected feed/category + Update &selected items + + + + &Edit selected item + + + + &Delete selected item + + + + &Mark selected items as read + + + + Mark all messages (without message filters) from selected items as read. + + + + &Mark selected items as unread + + + + Mark all messages (without message filters) from selected items as unread. + + + + &Clean selected items + + + + Deletes all messages from selected items. + + + + &Mark all items as &read + + + + Marks all messages in all items read. This does not take message filters into account. + + + + View selected items in &newspaper mode + + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + + &Clean all items + + + + Deletes all messages from all items. + + + + Select &next item + + + + Select &previous item + + + + Show only unread items + + + + &Expand/collapse selected item + + + + &Add new service account + + + + &Restore selected messages + + + + No possible actions + + + + Feeds && categories && accounts + + + + &Recycle bin(s) + + + + &Restore all recycle bins + + + + &Empty all recycle bins + + + + Select next &unread message + + + + No recycle bin + + + + Restore recycle bin + + + + Empty recycle bin @@ -1943,10 +1365,6 @@ Auto-update status: %5 Author - - Email - - Socks5 @@ -2503,6 +1921,525 @@ File filter for external e-mail selection dialog. Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) + + E-mail + + + + Enable notifications + + + + + FormStandardCategoryDetails + + Parent category + + + + Select parent item for your category. + + + + Title + + + + Description + + + + Icon + + + + Select icon for your category. + + + + Add new category + + + + Edit existing category + + + + Cannot add category + + + + Category was not added due to error. + + + + Cannot edit category + + + + Category was not edited due to error. + + + + Category name is ok. + + + + Category name is too short. + + + + Description is empty. + + + + The description is ok. + + + + Select icon file for the category + + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + + Select icon + + + + Cancel + + + + Look in: + Label to describe the folder for icon file selection dialog. + + + + Icon name: + + + + Icon type: + + + + Category title + + + + Set title for your category. + + + + Category description + + + + Set description for your category. + + + + Icon selection + + + + Load icon from file... + + + + Do not use icon + + + + Use default icon + + + + + FormStandardFeedDetails + + Parent category + + + + Select parent item for your feed. + + + + Type + + + + Select type of the standard feed. + + + + Encoding + + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + + + + Auto-update + + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + + + + minutes + + + + Title + + + + Description + + + + URL + + + + Fetch it now + + + + Icon + + + + Select icon for your feed. + + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + + + + Requires authentication + + + + Username + + + + Password + + + + Fetch metadata + + + + Add new feed + + + + Edit existing feed + + + + Feed name is ok. + + + + Feed name is too short. + + + + Description is empty. + + + + The description is ok. + + + + The url is ok. + + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + + + + The url is empty. + + + + Username is ok or it is not needed. + + + + Username is empty. + + + + Password is ok or it is not needed. + + + + Password is empty. + + + + Select icon file for the feed + + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + + Select icon + + + + Cancel + + + + Look in: + Label for field with icon file name textbox for selection dialog. + + + + Icon name: + + + + Icon type: + + + + Cannot add feed + + + + Feed was not added due to error. + + + + Cannot edit feed + + + + Feed was not edited due to error. + + + + All metadata fetched successfully. + + + + Feed and icon metadata fetched. + + + + Result: %1. + + + + Feed or icon metatada not fetched. + + + + Error: %1. + + + + No metadata fetched. + + + + Icon fetched successfully. + + + + Icon metadata fetched. + + + + Icon metatada not fetched. + + + + No icon fetched. + + + + Feed title + + + + Set title for your feed. + + + + Feed description + + + + Set description for your feed. + + + + Full feed url including scheme + + + + Set url for your feed. + + + + Set username to access the feed. + + + + Set password to access the feed. + + + + Icon selection + + + + Load icon from file... + + + + Do not use icon + + + + Use default icon + + + + Fetch icon from feed + + + + No metadata fetched so far. + + + + Auto-update using global interval + + + + Auto-update every + + + + Do not auto-update at all + + + + + FormStandardImportExport + + &Select file + + + + &Check all items + + + + &Uncheck all items + + + + Operation results + + + + No file is selected. + + + + No operation executed yet. + + + + Destination file + + + + Source feeds && categories + + + + Export feeds + + + + Source file + + + + Target feeds && categories + + + + Import feeds + + + + OPML 2.0 files (*.opml) + + + + Select file for feeds export + + + + File is selected. + + + + Select file for feeds import + + + + Cannot open source file. + + + + Feeds were loaded. + + + + Error, file is not well-formed. Select another file. + + + + Error occurred. File is not well-formed. Select another file. + + + + Feeds were exported successfully. + + + + Cannot write into destination file. + + + + Critical error occurred. + + FormUpdate @@ -2748,6 +2685,14 @@ Go to application website to obtain it manually. List of attachments. + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + + MessagesToolBar @@ -2921,25 +2866,33 @@ Go to application website to obtain it manually. LANG_EMAIL rotter.martinos@gmail.com - - Load initial feeds - - - - Do you want to load initial set of feeds? - - LANG_NAME Name of language, e.g. English. + + + ++ %n other feeds. + + + + + - You started %1 for the first time, now you can load initial set of feeds. + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. - Welcome to %1 %2. + Welcome to %1. + + + + Load initial set of feeds @@ -2981,6 +2934,132 @@ Go to application website to obtain it manually. + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + + + + +This category does not contain any nested items. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardFeed + + Metadata not fetched + + + + Metadata was not fetched because: %1. + + + + does not use auto-update + Describes feed auto-update status. + + + + uses global settings + Describes feed auto-update status. + + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + + + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + + + + You started %1 for the first time, now you can load initial set of feeds. + + + + Do you want to load initial set of feeds? + + + + Error when loading initial feeds + + + + This is service account for standard RSS/RDF/ATOM feeds. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + Fetch metadata + + + + Import successfull, but some feeds/categories were not imported due to error. + + + + Import was completely successfull. + + + + Add new category + + + + Add new feed + + + + Export feeds + + + + Import feeds + + + StatusBar @@ -3002,6 +3081,10 @@ Go to application website to obtain it manually. Click the bubble for more information. + + anonymous + + SystemTrayIcon @@ -3108,6 +3191,21 @@ Unread news: %2 + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + WebBrowser @@ -3179,6 +3277,14 @@ Unread news: %2 Stop web page loading. + + Cannot add feed + + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + + WebView diff --git a/localization/rssguard-en_US.ts b/localization/rssguard-en_US.ts index 4ac459cd1..807e83f9a 100644 --- a/localization/rssguard-en_US.ts +++ b/localization/rssguard-en_US.ts @@ -1,522 +1,452 @@ - + + + AdBlockAddSubscriptionDialog Add subscription - + Another subscription - + Entered title is okay. - + Entered title is empty. - + Entered url is okay. - + Entered url is empty. - + Title - + Address - + AdBlockCustomList Custom rules - + AdBlockDialog Adblock settings - + Enable Adblock - + Note that Adblock may significantly slow this application down once you activate huge subscriptions. Too many rules is not good for performance. Also, make sure you restart application after you disable Adblock if you wish to have low memory footprint. Adblock is known to use much system memory. Also note that some resources are cached by internal web browser. Thus, after changing some rules or subscriptions they will fully apply only for new application instances. Make sure you restart RSS Guard for best Adblock experience. - + Options - + Filter rules - + Use only essential part of EasyList (for performance reasons) - + Add rule - + Remove rule - + Add subscription - + Remove subscription - + Update subscriptions - + Rules writing guide - + AdBlockIcon Adblock - + Show Adblock &settings - + Disable on %1 - + Disable only on this page - + Blocked popup windows - + %1 with (%2) - + No content blocked - + Blocked some content - click to edit rule - + Adblock - up and running - + Adblock - not running - + AdBlockSubscription Cannot load subscription! - + AdBlockTreeWidget Please write your rule here - + %1 (recently updated) - + %1 (error: %2) - + Add rule - + Remove rule - + Application Application is already running. - + Output directory is not writable. - + Settings file not copied to output directory successfully. - + Database file not copied to output directory successfully. - + Database restoration was not initiated. Make sure that output directory is writable. - + Settings restoration was not initiated. Make sure that output directory is writable. - - - - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - - - - -This category does not contain any nested items. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - + DatabaseCleaner Shrinking database file... - + Database file shrinked... - + Removing read messages... - + Read messages purged... - + Recycle bin purged... - + Removing old messages... - + Purging recycle bin... - + Old messages purged... - + DatabaseFactory MySQL server works as expected. - + No MySQL server is running in the target destination. - + Access denied. Invalid username or password used. Access to MySQL server was denied. - + Unknown error. Unknown MySQL error arised. - + Selected database does not exist (yet). - + MySQL/MariaDB (dedicated database) - + SQLite (embedded database) - + DiscoverFeedsButton This website does not contain any feeds. - + Click me to add feeds from this website. This website contains %n feed(s). - + + + + DownloadItem Ico - + Filename - + Error opening output file: %1 - + &Try again - + &Stop - + &Open file - + Select destination for downloaded file - + Error: %1 - + Download directory couldn't be created - + Error when saving file: %1 - + %1 of %2 (%3 per second) - %4 - + %1 of %2 - download completed - + Open &directory - + Cannot open file - + Cannot open output file. Open it manually. - + Cannot open directory - + Cannot open output directory. Open it manually. - + Download finished - + - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. - + URL: %1 - + Local file: %1 - + Selection of local file cancelled. - + DownloadManager Clean up - + %n minutes remaining - + + + + %n seconds remaining - + + + + bytes - + kB - + MB - + GB - + Downloading %n file(s)... - - - - - Feed - - does not use auto-update - Describes feed auto-update status. - - - - uses global settings - Describes feed auto-update status. - - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - - - - Metadata not fetched - - - - Metadata was not fetched because: %1 - + + + + FeedMessageViewer Toolbar for messages - - - - Feed update started - Text display in status bar when feed update is started. - - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - + Toolbar for feeds - - - - Error when loading initial feeds - + Cannot cleanup database - + Cannot cleanup database, because another critical action is running. - - - - Cannot update all items - - - - You cannot update all items because another another critical operation is ongoing. - - - - New messages downloaded - + FeedsImportExportModel (category) - + (feed) - + Category - + @@ -524,1301 +454,822 @@ Auto-update status: %5 Title Title text in the feed list header. - + Titles of feeds/categories. - + Counts of unread/all meesages. - + Root Name of root item of feed list which can be seen in feed add/edit dialog. - - - - Invalid tree data. - - - - Import successfull, but some feeds/categories were not imported due to error. - - - - Import was completely successfull. - + Starting auto-update of some feeds - + I will auto-update %n feed(s). - + + + + + + + Cannot update all items + + + + You cannot update all items because another another critical operation is ongoing. + + + + Feed update started + Text display in status bar when feed update is started. + + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + + + + New messages downloaded + + + + You can't transfer dragged item into different account, this is not supported. + + + + Cannot perform drag & drop operation + FeedsToolBar Toolbar spacer - + FeedsView - - Cannot add standard category - - - - Cannot add standard feed - - Cannot edit item - + Cannot delete item - - - - You are about to delete selected feed or category. - - - - Deletion of item failed. - - - - Selected item was not deleted due to error. - - - - Do you really want to delete selected item? - - - - Permanently delete messages - - - - You are about to permanenty delete all messages from your recycle bin. - - - - Do you really want to empty your recycle bin? - + Context menu for empty space - - - - Context menu for recycle bin - - - - You cannot add new standard category now because another critical operation is ongoing. - - - - You cannot add new standard feed now because another critical operation is ongoing. - + Selected item cannot be edited because another critical operation is ongoing. - + Selected item cannot be deleted because another critical operation is ongoing. - - - - Delete feed/category - + Context menu for categories - + + + + Selected item cannot be edited, this is not (yet?) supported. + + + + Deleting "%1" + + + + You are about to completely delete item "%1". + + + + Are you sure? + + + + Cannot delete "%1" + + + + This item cannot be deleted because something critically failed. Submit bug report. + + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + + + + Context menu for other items + FormAbout Information - + Licenses - + GNU GPL License (applies to RSS Guard source code) - + GNU GPL License - + BSD License (applies to QtSingleApplication source code) - + Licenses page is available only in English language. - + Changelog - + Changelog page is available only in English language. - + License not found. - + Changelog not found. - + <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> - - - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - + About %1 About RSS Guard dialog title. - + Settings type - + Settings file - + Database root path - + FULLY portable - + PARTIALLY portable - + Resources - + + + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + + FormAddAccount + + Add new account + + + + Details + + + + Name + + + + Version + + + + Author + + + + Description + + + + Cannot add account + + + + Some critical error occurred, report this to developers. + + + + This account can be added only once. + FormBackupDatabaseSettings Backup database/settings - + Backup properties - + Items to backup - + Database - + Settings - + Backup name - + Operation results - + Common name for backup files - + No operation executed yet. - + Backup was created successfully. - + Backup name cannot be empty. - + Backup name looks okay. - + Backup failed. - + Output directory - + &Select directory - + Backup was created successfully and stored in target directory. - + Select destination directory - + Good destination directory is specified. - - - - - FormCategoryDetails - - Parent category - - - - Select parent item for your category. - - - - Title - - - - Description - - - - Icon - - - - Select icon for your category. - - - - Add new category - - - - Edit existing category - - - - Cannot add category - - - - Category was not added due to error. - - - - Cannot edit category - - - - Category was not edited due to error. - - - - Category name is ok. - - - - Category name is too short. - - - - Description is empty. - - - - Select icon file for the category - - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - - Select icon - - - - Cancel - - - - Look in: - Label to describe the folder for icon file selection dialog. - - - - Icon name: - - - - Icon type: - - - - Category title - - - - Set title for your category. - - - - Category description - - - - Set description for your category. - - - - Icon selection - - - - Load icon from file... - - - - Do not use icon - - - - Use default icon - - - - The description is ok. - + FormDatabaseCleanup Cleanup database - + Remove all messages older than - + day(s) - + + + + Shrink database file - + Database information - + Database file size - + Database type - + Progress - + I am ready. - + Database cleanup is running. - + Database cleanup is completed. - + Database cleanup failed. - + Cleanup settings (all checked items are completely erased from database) - + Remove all read messages (not those from recycle bin) - + Remove all messages from recycle bin - + Remove all starred messages (including those from recycle bin) - - - - - FormFeedDetails - - Parent category - - - - Select parent item for your feed. - - - - Type - - - - Select type of the standard feed. - - - - Encoding - - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - - - - Auto-update - - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - - - - minutes - - - - Title - - - - Description - - - - URL - - - - Fetch it now - - - - Icon - - - - Select icon for your feed. - - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - - - - Requires authentication - - - - Username - - - - Password - - - - Fetch metadata - - - - Add new feed - - - - Edit existing feed - - - - Feed name is ok. - - - - Feed name is too short. - - - - Description is empty. - - - - The url is ok. - - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - - - - The url is empty. - - - - Username is ok or it is not needed. - - - - Username is empty. - - - - Password is ok or it is not needed. - - - - Password is empty. - - - - Select icon file for the feed - - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - - Select icon - - - - Cancel - - - - Look in: - Label for field with icon file name textbox for selection dialog. - - - - Icon name: - - - - Icon type: - - - - Cannot add feed - - - - Feed was not added due to error. - - - - Cannot edit feed - - - - All metadata fetched successfully. - - - - Feed and icon metadata fetched. - - - - Result: %1. - - - - Feed or icon metatada not fetched. - - - - Error: %1. - - - - No metadata fetched. - - - - Feed title - - - - Set title for your feed. - - - - Feed description - - - - Set description for your feed. - - - - Full feed url including scheme - - - - Set url for your feed. - - - - Set username to access the feed. - - - - Set password to access the feed. - - - - Icon selection - - - - Load icon from file... - - - - Do not use icon - - - - Use default icon - - - - No metadata fetched so far. - - - - Auto-update using global interval - - - - Auto-update every - - - - Do not auto-update at all - - - - The description is ok. - - - - Feed was not edited due to error. - - - - Icon fetched successfully. - - - - Icon metadata fetched. - - - - Icon metatada not fetched. - - - - No icon fetched. - - - - Fetch icon from feed - - - - - FormImportExport - - &Select file - - - - Operation results - - - - No file is selected. - - - - No operation executed yet. - - - - Export feeds - - - - Destination file - - - - Source feeds && categories - - - - Source file - - - - Target feeds && categories - - - - Import feeds - - - - OPML 2.0 files (*.opml) - - - - Select file for feeds export - - - - File is selected. - - - - Select file for feeds import - - - - Cannot open source file. - - - - Feeds were loaded. - - - - Error, file is not well-formed. Select another file. - - - - Error occurred. File is not well-formed. Select another file. - - - - Feeds were exported successfully. - - - - Cannot write into destination file. - - - - Critical error occurred. - - - - &Check all items - - - - &Uncheck all items - + FormMain &File - + &Help - + &View - + &Tools - + &Quit - + &Settings - + &Current tab - + &Add tab - + &Messages - + &Web browser - + Switch &importance of selected messages - + Quit the application. - + Display settings of the application. - + Switch fullscreen mode. - + Add new web browser tab. - + Close current web browser tab. - + No actions available - + No actions are available right now. - - - - Fee&ds && categories - - - - Mark all messages (without message filters) from selected feeds as read. - - - - Mark all messages (without message filters) from selected feeds as unread. - - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - + Hides main window if it is visible and shows it if it is hidden. - + Hides or shows the list of feeds/categories. - + Check if new update for the application is available for download. - + &About application - + Displays extra info about this application. - + &Delete selected messages - - - - Deletes all messages from selected feeds. - - - - Marks all messages in all feeds read. This does not take message filters into account. - - - - Deletes all messages from all feeds. - - - - Update &all feeds - - - - Update &selected feeds - - - - &Edit selected feed/category - - - - &Delete selected feed/category - + Settings - + Hides or displays the main menu. - - - - Add &new feed/category - + &Close all tabs except current one - + &Close current tab - + Mark &selected messages as &read - + Mark &selected messages as &unread - - - - &Mark selected feeds as read - - - - &Mark selected feeds as unread - - - - &Clean selected feeds - + Open selected source articles in &external browser - + Open selected messages in &internal browser - + Open selected source articles in &internal browser - - - - &Mark all feeds as &read - - - - View selected feeds in &newspaper mode - - - - &Clean all feeds - - - - Select &next feed/category - - - - Select &previous feed/category - + Select &next message - + Select &previous message - + Check for &updates - + Enable &JavaScript - + Enable external &plugins - + Auto-load &images - + Show/hide - + &Fullscreen - + &Feed list - + &Main menu - + Switch visibility of main &window - + Cannot open external browser - + Cannot open external browser. Navigate to application website manually. - - - - New &feed - - - - Add new feed. - - - - New &category - - - - Add new category. - + &Toolbars - + Switch visibility of main toolbars. - + &Feed/message list headers - - - - &Import feeds - - - - Imports feeds you want from selected file. - - - - &Export feeds - - - - Exports feeds you want to selected file. - + Close all tabs except current one. - - - - &Recycle bin - + Report a &bug (GitHub)... - + Report a bug (BitBucket)... - + &Donate via PayPal - + Display &wiki - - - - &Empty recycle bin - - - - &Restore all messages - - - - Restore &selected messages - + &Restart - + &Restore database/settings - + &Backup database/settings - + Switch message list layout orientation - + &Downloads - + Send selected message via e-mail - + &Cleanup database - + - Show only unread feeds/categories - + Add &new item + - &Fetch feed metadata - + Update &all items + - &Expand/collapse selected feed/category - + Update &selected items + + + + &Edit selected item + + + + &Delete selected item + + + + &Mark selected items as read + + + + Mark all messages (without message filters) from selected items as read. + + + + &Mark selected items as unread + + + + Mark all messages (without message filters) from selected items as unread. + + + + &Clean selected items + + + + Deletes all messages from selected items. + + + + &Mark all items as &read + + + + Marks all messages in all items read. This does not take message filters into account. + + + + View selected items in &newspaper mode + + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + + &Clean all items + + + + Deletes all messages from all items. + + + + Select &next item + + + + Select &previous item + + + + Show only unread items + + + + &Expand/collapse selected item + + + + &Add new service account + + + + &Restore selected messages + + + + No possible actions + + + + Feeds && categories && accounts + + + + &Recycle bin(s) + + + + &Restore all recycle bins + + + + &Empty all recycle bins + + + + Select next &unread message + + + + No recycle bin + + + + Restore recycle bin + + + + Empty recycle bin + FormRestoreDatabaseSettings Restore database/settings - + Operation results - + Restore database - + Restore settings - + Restart - + No operation executed yet. - + Restoration was initiated. Restart to proceed. - + You need to restart application for restoration process to finish. - + Source directory - + &Select directory - + Database and/or settings were not copied to restoration directory successully. - + Select source directory - + Good source directory is specified. - + @@ -1826,252 +1277,248 @@ Auto-update status: %5 General General settings section. - + User interface - + Icon theme - + Settings - + Keyboard shortcuts - + Language Language settings section. - + Proxy - + Icons && skins - + Tray icon - + Start application hidden - + Type Proxy server type. - + Host - + Hostname or IP of your proxy server - + Port - + Username - + Your username for proxy server authentication - + Password - + Your password for proxy server authentication - + Display password - + Code - + Version - + Author - - - - Email - + Socks5 - + Http - + (not supported on this platform) - + Tray area && notifications - + Tabs - + Close tabs with - + Middle mouse button single-click - + Open new tabs with left mouse button double-click on tab bar - + Enable mouse gestures - + Queue new tabs (with hyperlinks) after the active tab - + no icon theme Label for disabling icon theme. - + Cannot save settings - + Name - + Icons - + Skins - + Active skin: - + Selected skin: - + Hide tab bar if just one tab is visible - + Critical settings were changed - + Feeds & messages - + Some critical settings are not set. You must fix these settings in order confirm new settings. - + Messages - + Web browser executable - + Executable parameters - + Note that "%1" (without quotation marks) is placeholder for URL of selected message. - + Select web browser executable - + Executables (*.*) - + Opera 12 or older - + Executable file of web browser - + Parameters to executable - + some keyboard shortcuts are not unique - + List of errors: %1. - + List of changes: %1. - + language changed - + icon theme changed - + skin changed - + Use sample arguments for - + Use in-memory database as the working database - + Usage of in-memory working database has several advantages and pitfalls. Make sure that you are familiar with these before you turn this feature on. Advantages: @@ -2085,292 +1532,292 @@ Disadvantages: <li>application startup and shutdown can take little longer (max. 2 seconds).</li> </ul> Authors of this application are NOT responsible for lost data. - + in-memory database switched - + Internal web browser - + External web browser - + WARNING: Note that switching to another data storage type will NOT copy existing your data from currently active data storage to newly selected one. - + Database driver - + Hostname - + Test setup - + Right mouse button double-click - + Auto-update all feeds every - + minutes - + Feed connection timeout - + Connection timeout is time interval which is reserved for downloading new messages for the feed. If this time interval elapses, then download process is aborted. - + ms - + Update all feed on application startup - + Data storage - + Hostname of your MySQL server - + Username to login with - + Password for your username - + data storage backend changed - + Hostname is empty. - + Hostname looks ok. - + Username is empty. - + Username looks ok. - + Password is empty. - + Password looks ok. - + Toolbar button style - + Hide main window when it is minimized - + No connection test triggered so far. - + Note that these settings are applied only on newly established connections. - + Select browser - + No proxy - + System proxy - + Icon only - + Text only - + Text beside icon - + Text under icon - + Follow OS style - + Keep message selection in the middle of the message list viewport - + You did not executed any connection test yet. - + Launch %1 on operating system startup - + Enable JavaScript - + Enable external plugins based on NPAPI - + Auto-load images - + <html><head/><body><p>If unchecked, then default system-wide web browser is used.</p></body></html> - + Feeds && categories - + Message count format in feed list - + Enter format for count of messages displayed next to each feed/category in feed list. Use "%all" and "%unread" strings which are placeholders for the actual count of all (or unread) messages. - + custom external browser is not set correctly - + Toolbars - + Toolbar for feeds list - + Toolbar for messages list - + Select toolbar to edit - + Some critical settings were changed and will be applied after the application gets restarted. You have to restart manually. - + Do you want to restart now? - + Check for updates on application startup - + Use custom date/time format (overrides format loaded from active localization) - + Executables (*) File filter for external browser selection dialog. ---------- File filter for external e-mail selection dialog. - + Remove all read messages from all feeds on application exit - + When new message arrives from feed and duplicate exists, then its content is updated and new message is dropped. - + Remove duplicate messages - + Downloads - + Target directory for downloaded files - + Ask for each individual downloaded file - + Target directory where all downloaded files are saved - + &Browse - + Select downloads target directory - + &Show password - + Web browser & e-mail & proxy - + Remove junk Trolltech registry key (HKCU\Software\Trolltech) when application quits (Use at your own risk!) - + Working database - + Mouse gestures work with middle mouse button. Possible gestures are: @@ -2378,416 +1825,943 @@ File filter for external e-mail selection dialog. • next web page (drag mouse right), • reload current web page (drag mouse up), • open new web browser tab (drag mouse down). - + Use custom external web browser - + External e-mail client - + Use custom external e-mail client - + E-mail client executable - + Executable file of e-mail client - + Select client - + Placeholders: • %1 - title of selected message, • %2 - body of selected message. - + Save all downloaded files to - + Select e-mail executable - + Mozilla Thunderbird - + Working database which you have full access to. - + Working database is empty. - + Working database is ok. - + Notification position - + (Tray icon is not available.) - + Bottom-left corner - + Top-left corner - + Bottom-right corner - + Top-right corner - + Internal message browser fonts - + Standard font - + Note that speed of used MySQL server and latency of used connection medium HEAVILY influences the final performance of this application. Using slow database connections leads to bad performance when browsing feeds or messages. - + Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) - + + + + E-mail + + + + Enable notifications + + + + + FormStandardCategoryDetails + + Parent category + + + + Select parent item for your category. + + + + Title + + + + Description + + + + Icon + + + + Select icon for your category. + + + + Add new category + + + + Edit existing category + + + + Cannot add category + + + + Category was not added due to error. + + + + Cannot edit category + + + + Category was not edited due to error. + + + + Category name is ok. + + + + Category name is too short. + + + + Description is empty. + + + + The description is ok. + + + + Select icon file for the category + + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + + Select icon + + + + Cancel + + + + Look in: + Label to describe the folder for icon file selection dialog. + + + + Icon name: + + + + Icon type: + + + + Category title + + + + Set title for your category. + + + + Category description + + + + Set description for your category. + + + + Icon selection + + + + Load icon from file... + + + + Do not use icon + + + + Use default icon + + + + + FormStandardFeedDetails + + Parent category + + + + Select parent item for your feed. + + + + Type + + + + Select type of the standard feed. + + + + Encoding + + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + + + + Auto-update + + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + + + + minutes + + + + Title + + + + Description + + + + URL + + + + Fetch it now + + + + Icon + + + + Select icon for your feed. + + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + + + + Requires authentication + + + + Username + + + + Password + + + + Fetch metadata + + + + Add new feed + + + + Edit existing feed + + + + Feed name is ok. + + + + Feed name is too short. + + + + Description is empty. + + + + The description is ok. + + + + The url is ok. + + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + + + + The url is empty. + + + + Username is ok or it is not needed. + + + + Username is empty. + + + + Password is ok or it is not needed. + + + + Password is empty. + + + + Select icon file for the feed + + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + + Select icon + + + + Cancel + + + + Look in: + Label for field with icon file name textbox for selection dialog. + + + + Icon name: + + + + Icon type: + + + + Cannot add feed + + + + Feed was not added due to error. + + + + Cannot edit feed + + + + Feed was not edited due to error. + + + + All metadata fetched successfully. + + + + Feed and icon metadata fetched. + + + + Result: %1. + + + + Feed or icon metatada not fetched. + + + + Error: %1. + + + + No metadata fetched. + + + + Icon fetched successfully. + + + + Icon metadata fetched. + + + + Icon metatada not fetched. + + + + No icon fetched. + + + + Feed title + + + + Set title for your feed. + + + + Feed description + + + + Set description for your feed. + + + + Full feed url including scheme + + + + Set url for your feed. + + + + Set username to access the feed. + + + + Set password to access the feed. + + + + Icon selection + + + + Load icon from file... + + + + Do not use icon + + + + Use default icon + + + + Fetch icon from feed + + + + No metadata fetched so far. + + + + Auto-update using global interval + + + + Auto-update every + + + + Do not auto-update at all + + + + + FormStandardImportExport + + &Select file + + + + &Check all items + + + + &Uncheck all items + + + + Operation results + + + + No file is selected. + + + + No operation executed yet. + + + + Destination file + + + + Source feeds && categories + + + + Export feeds + + + + Source file + + + + Target feeds && categories + + + + Import feeds + + + + OPML 2.0 files (*.opml) + + + + Select file for feeds export + + + + File is selected. + + + + Select file for feeds import + + + + Cannot open source file. + + + + Feeds were loaded. + + + + Error, file is not well-formed. Select another file. + + + + Error occurred. File is not well-formed. Select another file. + + + + Feeds were exported successfully. + + + + Cannot write into destination file. + + + + Critical error occurred. + FormUpdate Current release - + Available release - + Changes - + Status - + unknown Unknown release. - + List with updates was not downloaded successfully. - + New release available. - + This is new version which can be downloaded and installed. - + Error: '%1'. - + No new release available. - + This release is not newer than currently installed one. - + Check for updates - + Update - + Download new installation files. - + Checking for updates failed. - + Download installation file for your OS. - + Installation file is not available directly. Go to application website to obtain it manually. - + No new update available. - + Cannot update application - + Cannot navigate to installation file. Check new installation downloads manually on project website. - + Download update - + Downloaded %1% (update size is %2 kB). - + Downloading update... - + Downloaded successfully - + Package was downloaded successfully. - + Install update - + Error occured - + Error occured during downloading of the package. - + Cannot launch external updater. Update application manually. - + Go to application website - + IOFactory Cannot open file '%1' for reading. - + Cannot open file '%1' for writting. - + LocationLineEdit Website address goes here - + MessagesModel Id - + Read - + Deleted - + Important - + Feed - + Title - + Url - + Author - + Created on - + Contents - + Id of the message. - + Is message read? - + Is message deleted? - + Is message important? - + Id of feed which this message belongs to. - + Title of the message. - + Url of the message. - + Author of the message. - + Creation date of the message. - + Contents of the message. - + Permanently deleted - + Is message permanently deleted from recycle bin? - + Attachments - + List of attachments. - + + + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + MessagesToolBar Search messages - + Message search box - + Menu for highlighting messages - + No extra highlighting - + Highlight unread messages - + Highlight important messages - + Display all messages - + Message highlighter - + Toolbar spacer - + MessagesView Context menu for messages - + Meesage without URL - + Message '%s' does not contain URL. - + Problem with starting external web browser - + External web browser could not be started. - + Problem with starting external e-mail client - + External e-mail client could not be started. - + @@ -2795,80 +2769,80 @@ Go to application website to obtain it manually. protocol error Network status. - + host not found Network status. - + connection refused Network status. - + connection timed out Network status. - + SSL handshake failed Network status. - + proxy server connection refused Network status. - + temporary failure Network status. - + authentication failed Network status. - + proxy authentication required Network status. - + proxy server not found Network status. - + uknown content Network status. - + content not found Network status. - + unknown error Network status. - + no errors Network status. - + access to content was denied - + connection timed out or was cancelled - + @@ -2886,89 +2860,230 @@ Go to application website to obtain it manually. LANG_AUTHOR Name of translator - optional. - + LANG_EMAIL rotter.martinos@gmail.com - - Load initial feeds - - - - Do you want to load initial set of feeds? - - LANG_NAME Name of language, e.g. English. English (USA) - - You started %1 for the first time, now you can load initial set of feeds. - + + + ++ %n other feeds. + + + + - Welcome to %1 %2. - + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + + + + Welcome to %1. + + + + Load initial set of feeds + RecycleBin Recycle bin - + Recycle bin contains all deleted messages from all feeds. - + Recycle bin %1 - + %n deleted message(s). - + + + + ShortcutCatcher Reset to original shortcut. - + Clear current shortcut. - + Click and hit new shortcut. - + + + + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + + + + +This category does not contain any nested items. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardFeed + + Metadata not fetched + + + + Metadata was not fetched because: %1. + + + + does not use auto-update + Describes feed auto-update status. + + + + uses global settings + Describes feed auto-update status. + + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + + + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + + + + You started %1 for the first time, now you can load initial set of feeds. + + + + Do you want to load initial set of feeds? + + + + Error when loading initial feeds + + + + This is service account for standard RSS/RDF/ATOM feeds. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + Fetch metadata + + + + Import successfull, but some feeds/categories were not imported due to error. + + + + Import was completely successfull. + + + + Add new category + + + + Add new feed + + + + Export feeds + + + + Import feeds + StatusBar Fullscreen mode - + Switch application between fulscreen/normal states right from this status bar icon. - + SystemFactory New version available - + Click the bubble for more information. - + + + + anonymous + @@ -2976,315 +3091,338 @@ Go to application website to obtain it manually. %1 Unread news: %2 - + TabBar Close this tab. - + Close tab - + TabWidget Feeds - + Browse your feeds and messages - + Web browser Web browser default tab title. - + Displays main menu. - + Main menu - + Open new web browser tab. - + Downloads - + ToolBarEditor Activated actions - + Available actions - + Insert separator - + Insert spacer - + Separator - + Toolbar spacer - + Move action up - + Move action down - + Add selected action - + Delete selected action - + Delete all actions - + TrayIconMenu Close opened modal dialogs first. - + + + + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + WebBrowser Navigation panel - + Back - + Forward - + Reload - + Stop - + Zoom - + No title Webbrowser tab title when no title is available. - + Decrease zoom. - + Reset zoom to default. - + Increase zoom. - + Written by - + uknown author - + Newspaper view - + Go back. - + Go forward. - + Reload current web page. - + Stop web page loading. - + + + + Cannot add feed + + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + WebView Reload web page - + Copy link url - + Copy image - + Copy image url - + Open link in new tab - + Follow link - + Open image in new tab - + Web browser - + Image - + Hyperlink - + Reload current web page. - + Copy selection - + Copies current selection into the clipboard. - + Copy link url to clipboard. - + Copy image to clipboard. - + Copy image url to clipboard. - + Open this hyperlink in new tab. - + Open the hyperlink in this tab. - + Open this image in this tab. - + Open link in external browser - + Open the hyperlink in external browser. - + Print - + Print current web page. - + HTML web pages (*.html) - + Select destination file for web page - + Cannot save web page - + Web page cannot be saved because destination file is not writtable. - + Save target as... - + Download content from the hyperlink. - + Save page as... - + Save image to disk. - + Save image as... - + source_page - + Search "%1" via Google... - + - \ No newline at end of file + diff --git a/localization/rssguard-fr_FR.ts b/localization/rssguard-fr_FR.ts index d82cc5f24..9d64d2a36 100644 --- a/localization/rssguard-fr_FR.ts +++ b/localization/rssguard-fr_FR.ts @@ -1,170 +1,172 @@ - + + + AdBlockAddSubscriptionDialog Add subscription - + Another subscription - + Entered title is okay. - + Entered title is empty. - + Entered url is okay. - + Entered url is empty. - + Title - + Titre Address - + AdBlockCustomList Custom rules - + AdBlockDialog Adblock settings - + Enable Adblock - + Note that Adblock may significantly slow this application down once you activate huge subscriptions. Too many rules is not good for performance. Also, make sure you restart application after you disable Adblock if you wish to have low memory footprint. Adblock is known to use much system memory. Also note that some resources are cached by internal web browser. Thus, after changing some rules or subscriptions they will fully apply only for new application instances. Make sure you restart RSS Guard for best Adblock experience. - + Options - + Filter rules - + Use only essential part of EasyList (for performance reasons) - + Add rule - + Remove rule - + Add subscription - + Remove subscription - + Update subscriptions - + Rules writing guide - + AdBlockIcon Adblock - + Show Adblock &settings - + Disable on %1 - + Disable only on this page - + Blocked popup windows - + %1 with (%2) - + No content blocked - + Blocked some content - click to edit rule - + Adblock - up and running - + Adblock - not running - + AdBlockSubscription Cannot load subscription! - + AdBlockTreeWidget Please write your rule here - + %1 (recently updated) - + %1 (error: %2) - + Add rule - + Remove rule - + @@ -175,76 +177,58 @@ Also note that some resources are cached by internal web browser. Thus, after ch Output directory is not writable. - + Settings file not copied to output directory successfully. - + Database file not copied to output directory successfully. - + Database restoration was not initiated. Make sure that output directory is writable. - + Settings restoration was not initiated. Make sure that output directory is writable. - - - - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - - - - -This category does not contain any nested items. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - + DatabaseCleaner Shrinking database file... - + Database file shrinked... - + Removing read messages... - + Read messages purged... - + Recycle bin purged... - + Removing old messages... - + Purging recycle bin... - + Old messages purged... - + @@ -269,194 +253,166 @@ This category does not contain any nested items. Selected database does not exist (yet). - + MySQL/MariaDB (dedicated database) - + SQLite (embedded database) - + DiscoverFeedsButton This website does not contain any feeds. - + Click me to add feeds from this website. This website contains %n feed(s). - + + + + DownloadItem Ico - + Filename - + Error opening output file: %1 - + &Try again - + &Stop - + &Open file - + Select destination for downloaded file - + Error: %1 - + Erreur : %1. {1?} Download directory couldn't be created - + Error when saving file: %1 - + %1 of %2 (%3 per second) - %4 - + %1 of %2 - download completed - + Open &directory - + Cannot open file - + Cannot open output file. Open it manually. - + Cannot open directory - + Cannot open output directory. Open it manually. - + Download finished - + - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. - + URL: %1 - + Local file: %1 - + Selection of local file cancelled. - + DownloadManager Clean up - + %n minutes remaining - + + + + %n seconds remaining - + + + + bytes - + kB - + MB - + GB - + Downloading %n file(s)... - - - - - Feed - - does not use auto-update - Describes feed auto-update status. - - - - uses global settings - Describes feed auto-update status. - - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - - - - Metadata not fetched - - - - Metadata was not fetched because: %1 - + + + + @@ -465,43 +421,17 @@ Auto-update status: %5 Toolbar for messages Barre d'outils pour les messages - - Feed update started - Text display in status bar when feed update is started. - Mise à jour des flux démarrée - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - Flux mis à jour '%1' - Toolbar for feeds Barre d'outils pour les flux - - Error when loading initial feeds - - Cannot cleanup database - + Cannot cleanup database, because another critical action is running. - - - - Cannot update all items - - - - You cannot update all items because another another critical operation is ongoing. - - - - New messages downloaded - + @@ -516,7 +446,7 @@ Auto-update status: %5 Category - + @@ -539,44 +469,57 @@ Auto-update status: %5 Name of root item of feed list which can be seen in feed add/edit dialog. Racine - - Invalid tree data. - - - - Import successfull, but some feeds/categories were not imported due to error. - - - - Import was completely successfull. - - Starting auto-update of some feeds - + I will auto-update %n feed(s). - + + + + + + + Cannot update all items + + + + You cannot update all items because another another critical operation is ongoing. + + + + Feed update started + Text display in status bar when feed update is started. + Mise à jour des flux démarrée + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + Flux mis à jour '%1' + + + New messages downloaded + + + + You can't transfer dragged item into different account, this is not supported. + + + + Cannot perform drag & drop operation + FeedsToolBar Toolbar spacer - + FeedsView - - Cannot add standard category - Impossible d'ajouter une catégorie standard - - - Cannot add standard feed - Impossible d'ajouter un flux standard - Cannot edit item Impossible d'éditer l'article @@ -585,65 +528,54 @@ Auto-update status: %5 Cannot delete item Impossible de supprimer l'article - - You are about to delete selected feed or category. - - - - Deletion of item failed. - - - - Selected item was not deleted due to error. - - - - Do you really want to delete selected item? - - - - Permanently delete messages - - - - You are about to permanenty delete all messages from your recycle bin. - - - - Do you really want to empty your recycle bin? - - Context menu for empty space - - - - Context menu for recycle bin - - - - You cannot add new standard category now because another critical operation is ongoing. - - - - You cannot add new standard feed now because another critical operation is ongoing. - + Selected item cannot be edited because another critical operation is ongoing. - + Selected item cannot be deleted because another critical operation is ongoing. - - - - Delete feed/category - + Context menu for categories - + + + + Selected item cannot be edited, this is not (yet?) supported. + + + + Deleting "%1" + + + + You are about to completely delete item "%1". + + + + Are you sure? + + + + Cannot delete "%1" + + + + This item cannot be deleted because something critically failed. Submit bug report. + + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + + + + Context menu for other items + @@ -692,10 +624,6 @@ Auto-update status: %5 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> <b>%8</b><br><b>Version :</b> %1 (construit sur %2 avec CMake %3)<br><b>Révision :</b> %4<br><b>Date de création :</b> %5<br><b>Qt:</b> %6 (compilé avec %7)<br> - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - <body>%5 est un lecteur de flux (très) petit.<br><br>Ce logiciel est distribué sous les termes de la licence GNU General Public License, version 3.<br><br>Contacts : <ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~site internet</li></ul>Vous pouvez obtenir le code source de %5 depuis le site internet.<br><br><br>Copyright (C) 2011-%3 %4</body> - About %1 About RSS Guard dialog title. @@ -703,46 +631,89 @@ Auto-update status: %5 Settings type - + Settings file - + Database root path - + FULLY portable - + PARTIALLY portable - + Resources - + + + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + + FormAddAccount + + Add new account + + + + Details + + + + Name + Nom + + + Version + Version + + + Author + Auteur + + + Description + Description + + + Cannot add account + + + + Some critical error occurred, report this to developers. + + + + This account can be added only once. + FormBackupDatabaseSettings Backup database/settings - + Backup properties - + Items to backup - + Database - + Settings @@ -750,633 +721,125 @@ Auto-update status: %5 Backup name - + Operation results - + Common name for backup files - + No operation executed yet. - + Backup was created successfully. - + Backup name cannot be empty. - + Backup name looks okay. - + Backup failed. - + Output directory - + &Select directory - + Backup was created successfully and stored in target directory. - + Select destination directory - + Good destination directory is specified. - - - - - FormCategoryDetails - - Parent category - Catégorie parente - - - Select parent item for your category. - Sélectionner l'article parent pour votre catégorie. - - - Title - Titre - - - Description - Description - - - Icon - Icône - - - Select icon for your category. - Sélectionner un icône pour votre catégorie - - - Add new category - Ajouter une nouvelle catégorie - - - Edit existing category - - - - Cannot add category - Impossible d'ajouter une catégorie - - - Category was not added due to error. - La catégorie n'a pas été ajoutée dû à une erreur - - - Cannot edit category - Impossible d'éditer la catégori - - - Category was not edited due to error. - La catégorie n'a pas été éditée dû à une erreur. - - - Category name is ok. - Le nom de la catégorie est correct. - - - Category name is too short. - Le nom de la catégorie est trop court. - - - Description is empty. - La description est vide. - - - Select icon file for the category - Sélectionner un icône pour la catégorie - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Sélectionner l'icône - - - Cancel - Annuler - - - Look in: - Label to describe the folder for icon file selection dialog. - Rechercher dans : - - - Icon name: - Nom de l'icône : - - - Icon type: - Type d'icône : - - - Category title - Titre de la catégorie - - - Set title for your category. - Définir un titre pour votre catégorie - - - Category description - Description de la catégorie - - - Set description for your category. - Définir une description pour votre catégorie. - - - Icon selection - Sélection de l'icône - - - Load icon from file... - Charger l'icône depuis un fichier... - - - Do not use icon - Ne pas utiliser les icônes - - - Use default icon - Utiliser les icônes par défaut - - - The description is ok. - + FormDatabaseCleanup Cleanup database - + Remove all messages older than - + day(s) - + + + + Shrink database file - + Database information - + Database file size - + Database type - + Progress - + I am ready. - + Database cleanup is running. - + Database cleanup is completed. - + Database cleanup failed. - + Cleanup settings (all checked items are completely erased from database) - + Remove all read messages (not those from recycle bin) - + Remove all messages from recycle bin - + Remove all starred messages (including those from recycle bin) - - - - - FormFeedDetails - - Parent category - Catégorie parente - - - Select parent item for your feed. - Sélectionner l'article parent pour votre flux. - - - Type - Type - - - Select type of the standard feed. - Sélectionner un type pour le flux standard. - - - Encoding - Encodage - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - Sélectionner un encodage pour le flux standard. Si vous n'êtes pas sûr à propos de l'encodage, sélectionner alors l'encodage "UTF-8". - - - Auto-update - Mise à jour automatique - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - Sélectionner la stratégie des mises à jour automatique pour ce flux. Par défaut, cette stratégie signifie que le flux sera mis à jour par intervalle de temps défini dans les paramètres de l'application. - - - minutes - minutes - - - Title - Titre - - - Description - Description - - - URL - URL - - - Fetch it now - Le chercher maintenant - - - Icon - Icône - - - Select icon for your feed. - Sélectionner un icône pour votre flux. - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - Certain flux requière une authentification, incluant les flux GMail. Les schémas d'authentification BASIC, NTLM-2 et DIGEST-MD5 sont supportés. - - - Requires authentication - Authentification requise - - - Username - Nom d'utilisateur - - - Password - Mot de passe - - - Fetch metadata - Chercher les métadonnées - - - Add new feed - Ajouter un nouveau flux - - - Edit existing feed - - - - Feed name is ok. - Le nom du flux est correct. - - - Feed name is too short. - Le nom du flux est trop court. - - - Description is empty. - La description est vide. - - - The url is ok. - L'URL est correct. - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - L'URL ne respecte pas le pattern standard. Votre URL doit commencer avec les préfixe "http://" ou "https://". - - - The url is empty. - L'URL est vide. - - - Username is ok or it is not needed. - Le nom d'utilisateur est correct ou non nécessaire. - - - Username is empty. - Le nom d'utilisateur est vide. - - - Password is ok or it is not needed. - Le mot de passe est correct ou non nécessaire. - - - Password is empty. - Le mot de passe est vide. - - - Select icon file for the feed - Sélectionner un icône pour le flux - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Sélectionner l'icône - - - Cancel - Annuler - - - Look in: - Label for field with icon file name textbox for selection dialog. - Rechercher dans : - - - Icon name: - Nom de l'icône : - - - Icon type: - Type d'icône : - - - Cannot add feed - Impossible d'ajouter le flux - - - Feed was not added due to error. - Le flux n'a pas été ajouté dû à une erreur. - - - Cannot edit feed - Impossible d'éditer le flux - - - All metadata fetched successfully. - Tout les méta-datas ont été extraites avec succès. - - - Feed and icon metadata fetched. - Flux et icône extraits. - - - Result: %1. - Résultat : %1. - - - Feed or icon metatada not fetched. - Flux ou icône non extrait. - - - Error: %1. - Erreur : %1. - - - No metadata fetched. - Aucune méta-donnée extraite. - - - Feed title - Titre du flux - - - Set title for your feed. - Définir un titre pour votre flux. - - - Feed description - Description du flux - - - Set description for your feed. - Définir une description pour votre flux. - - - Full feed url including scheme - URL du flux complet incluant le préfixe - - - Set url for your feed. - Définir l'URL pour votre flux. - - - Set username to access the feed. - Définir le nom d'utilisateur pour accéder au flux. - - - Set password to access the feed. - Définir le mot de passe pour accéder au flux. - - - Icon selection - Sélection de l'icône - - - Load icon from file... - Charger l'icône depuis un fichier... - - - Do not use icon - Ne pas utiliser les icônes - - - Use default icon - Utiliser les icônes par défaut - - - No metadata fetched so far. - Pas de métadonnées trouvé aussi loin. - - - Auto-update using global interval - Mise à jour automatique utilisant l'intervalle global - - - Auto-update every - Tout mettre à jour - - - Do not auto-update at all - Ne pas mettre tout à jour automatiquement - - - The description is ok. - - - - Feed was not edited due to error. - Le flux n'a pas été édité dû à une erreur. - - - Icon fetched successfully. - - - - Icon metadata fetched. - - - - Icon metatada not fetched. - - - - No icon fetched. - - - - Fetch icon from feed - - - - - FormImportExport - - &Select file - - - - Operation results - - - - No file is selected. - - - - No operation executed yet. - - - - Export feeds - - - - Destination file - - - - Source feeds && categories - - - - Source file - Fichier source - - - Target feeds && categories - - - - Import feeds - Importer des flux - - - OPML 2.0 files (*.opml) - Fichier OPML 2.0 (*.opml) - - - Select file for feeds export - - - - File is selected. - Le fichier est sélectionné - - - Select file for feeds import - - - - Cannot open source file. - - - - Feeds were loaded. - - - - Error, file is not well-formed. Select another file. - - - - Error occurred. File is not well-formed. Select another file. - - - - Feeds were exported successfully. - - - - Cannot write into destination file. - - - - Critical error occurred. - Erreur critique rencontrée - - - &Check all items - - - - &Uncheck all items - + @@ -1453,22 +916,6 @@ Auto-update status: %5 No actions are available right now. Aucune actions disponibles pour le moment. - - Fee&ds && categories - Flux && catégories - - - Mark all messages (without message filters) from selected feeds as read. - Marquer tout les messages (sans les filtres) de la sélection comme lus. - - - Mark all messages (without message filters) from selected feeds as unread. - Marquer tout les messages (sans les filtres) de la sélection comme non-lus. - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - Afficher tout les messages depuis les flux/catégories sélectionnées dans un nouvel onglet en mode "journal". Notez que les messages ne sont pas marqué lus automatiquement. - Hides main window if it is visible and shows it if it is hidden. Cacher la fenêtre principale si il est visible et la montrer si il est cacher. @@ -1493,34 +940,6 @@ Auto-update status: %5 &Delete selected messages &Supprimer les messages sélectionnés - - Deletes all messages from selected feeds. - Supprimer tout les messages des flux sélectionnés. - - - Marks all messages in all feeds read. This does not take message filters into account. - Marquer tout les messages dans tout les flux lus. Cela ne tient pas en compte les filtres de messages. - - - Deletes all messages from all feeds. - Supprimer tout les messages de tout les flux. - - - Update &all feeds - Mettre à jour &tout les flux - - - Update &selected feeds - Mettre à jour les flux &sélectionnés - - - &Edit selected feed/category - &Éditer le flux/catégorie sélectionné - - - &Delete selected feed/category - &Supprimer le flux/catégorie sélectionné - Settings Paramètres @@ -1529,10 +948,6 @@ Auto-update status: %5 Hides or displays the main menu. Cacher ou montrer le menu principal. - - Add &new feed/category - Ajout un &nouveau flux/catégorie - &Close all tabs except current one &Fermer tout les onglets sauf le courant @@ -1549,18 +964,6 @@ Auto-update status: %5 Mark &selected messages as &unread Marquer les messages &sélectionner comme &non lu - - &Mark selected feeds as read - &Marquer les flux sélectionnés comme lus - - - &Mark selected feeds as unread - &Marquer les flux sélectionnés comme non lus - - - &Clean selected feeds - &Nettoyer les flux sélectionnés - Open selected source articles in &external browser Ouvrir les sources de l'article sélectionnées dans le navigateur &externe @@ -1573,26 +976,6 @@ Auto-update status: %5 Open selected source articles in &internal browser Ouvrir les sources de l'article sélectionnées dans le navigateur &interne - - &Mark all feeds as &read - &Marquer tout les flux comme &lus - - - View selected feeds in &newspaper mode - Voir les flux sélectionnés dans le mode &journal - - - &Clean all feeds - &Nettoyer tout les flux - - - Select &next feed/category - Sélectionner le flux/catégorie &suivant - - - Select &previous feed/category - Sélection le flux/catégorie &précédent - Select &next message Sélectionner le message &suivant @@ -1635,75 +1018,39 @@ Auto-update status: %5 Switch visibility of main &window - + Cannot open external browser - + Cannot open external browser. Navigate to application website manually. - - - - New &feed - Nouveau &flux - - - Add new feed. - Ajouter un nouveau flux - - - New &category - Nouvelle &catégorie - - - Add new category. - Ajouter une nouvelle catégorie + &Toolbars - + Switch visibility of main toolbars. - + &Feed/message list headers - - - - &Import feeds - - - - Imports feeds you want from selected file. - - - - &Export feeds - - - - Exports feeds you want to selected file. - + Close all tabs except current one. - - - - &Recycle bin - &Corbeille + Report a &bug (GitHub)... - + Report a bug (BitBucket)... - + &Donate via PayPal @@ -1711,19 +1058,7 @@ Auto-update status: %5 Display &wiki - - - - &Empty recycle bin - - - - &Restore all messages - - - - Restore &selected messages - + &Restart @@ -1731,94 +1066,210 @@ Auto-update status: %5 &Restore database/settings - + &Backup database/settings - + Switch message list layout orientation - + &Downloads - + Send selected message via e-mail - + &Cleanup database - + - Show only unread feeds/categories - + Add &new item + - &Fetch feed metadata - + Update &all items + - &Expand/collapse selected feed/category - + Update &selected items + + + + &Edit selected item + + + + &Delete selected item + + + + &Mark selected items as read + + + + Mark all messages (without message filters) from selected items as read. + + + + &Mark selected items as unread + + + + Mark all messages (without message filters) from selected items as unread. + + + + &Clean selected items + + + + Deletes all messages from selected items. + + + + &Mark all items as &read + + + + Marks all messages in all items read. This does not take message filters into account. + + + + View selected items in &newspaper mode + + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + + &Clean all items + + + + Deletes all messages from all items. + + + + Select &next item + + + + Select &previous item + + + + Show only unread items + + + + &Expand/collapse selected item + + + + &Add new service account + + + + &Restore selected messages + + + + No possible actions + + + + Feeds && categories && accounts + + + + &Recycle bin(s) + + + + &Restore all recycle bins + + + + &Empty all recycle bins + + + + Select next &unread message + + + + No recycle bin + + + + Restore recycle bin + + + + Empty recycle bin + FormRestoreDatabaseSettings Restore database/settings - + Operation results - + Restore database - + Restore settings - + Restart - + No operation executed yet. - + Restoration was initiated. Restart to proceed. - + You need to restart application for restoration process to finish. - + Source directory - + &Select directory - + Database and/or settings were not copied to restoration directory successully. - + Select source directory - + Good source directory is specified. - + @@ -1914,10 +1365,6 @@ Auto-update status: %5 Author Auteur - - Email - Email - Socks5 Socks5 @@ -2293,25 +1740,25 @@ Les auteurs de cette application NE sont PAS responsable de la perte de données Toolbars - + Toolbar for feeds list - + Toolbar for messages list - + Select toolbar to edit - + Some critical settings were changed and will be applied after the application gets restarted. You have to restart manually. - + Do you want to restart now? @@ -2319,70 +1766,70 @@ You have to restart manually. Check for updates on application startup - + Use custom date/time format (overrides format loaded from active localization) - + Executables (*) File filter for external browser selection dialog. ---------- File filter for external e-mail selection dialog. - + Remove all read messages from all feeds on application exit - + When new message arrives from feed and duplicate exists, then its content is updated and new message is dropped. - + Remove duplicate messages - + Downloads - + Target directory for downloaded files - + Ask for each individual downloaded file - + Target directory where all downloaded files are saved - + &Browse - + Select downloads target directory - + &Show password - + Web browser & e-mail & proxy - + Remove junk Trolltech registry key (HKCU\Software\Trolltech) when application quits (Use at your own risk!) - + Working database - + Mouse gestures work with middle mouse button. Possible gestures are: @@ -2390,101 +1837,620 @@ File filter for external e-mail selection dialog. • next web page (drag mouse right), • reload current web page (drag mouse up), • open new web browser tab (drag mouse down). - + Use custom external web browser - + External e-mail client - + Use custom external e-mail client - + E-mail client executable - + Executable file of e-mail client - + Select client - + Placeholders: • %1 - title of selected message, • %2 - body of selected message. - + Save all downloaded files to - + Select e-mail executable - + Mozilla Thunderbird - + Working database which you have full access to. - + Working database is empty. - + Working database is ok. - + Notification position - + (Tray icon is not available.) - + Bottom-left corner - + Top-left corner - + Bottom-right corner - + Top-right corner - + Internal message browser fonts - + Standard font - + Note that speed of used MySQL server and latency of used connection medium HEAVILY influences the final performance of this application. Using slow database connections leads to bad performance when browsing feeds or messages. - + Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) - + + + + E-mail + + + + Enable notifications + + + + + FormStandardCategoryDetails + + Parent category + Catégorie parente + + + Select parent item for your category. + Sélectionner l'article parent pour votre catégorie. + + + Title + Titre + + + Description + Description + + + Icon + Icône + + + Select icon for your category. + Sélectionner un icône pour votre catégorie + + + Add new category + Ajouter une nouvelle catégorie + + + Edit existing category + + + + Cannot add category + Impossible d'ajouter une catégorie + + + Category was not added due to error. + La catégorie n'a pas été ajoutée dû à une erreur + + + Cannot edit category + Impossible d'éditer la catégori + + + Category was not edited due to error. + La catégorie n'a pas été éditée dû à une erreur. + + + Category name is ok. + Le nom de la catégorie est correct. + + + Category name is too short. + Le nom de la catégorie est trop court. + + + Description is empty. + La description est vide. + + + The description is ok. + + + + Select icon file for the category + Sélectionner un icône pour la catégorie + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Sélectionner l'icône + + + Cancel + Annuler + + + Look in: + Label to describe the folder for icon file selection dialog. + Rechercher dans : + + + Icon name: + Nom de l'icône : + + + Icon type: + Type d'icône : + + + Category title + Titre de la catégorie + + + Set title for your category. + Définir un titre pour votre catégorie + + + Category description + Description de la catégorie + + + Set description for your category. + Définir une description pour votre catégorie. + + + Icon selection + Sélection de l'icône + + + Load icon from file... + Charger l'icône depuis un fichier... + + + Do not use icon + Ne pas utiliser les icônes + + + Use default icon + Utiliser les icônes par défaut + + + + FormStandardFeedDetails + + Parent category + Catégorie parente + + + Select parent item for your feed. + Sélectionner l'article parent pour votre flux. + + + Type + Type + + + Select type of the standard feed. + Sélectionner un type pour le flux standard. + + + Encoding + Encodage + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + Sélectionner un encodage pour le flux standard. Si vous n'êtes pas sûr à propos de l'encodage, sélectionner alors l'encodage "UTF-8". + + + Auto-update + Mise à jour automatique + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + Sélectionner la stratégie des mises à jour automatique pour ce flux. Par défaut, cette stratégie signifie que le flux sera mis à jour par intervalle de temps défini dans les paramètres de l'application. + + + minutes + minutes + + + Title + Titre + + + Description + Description + + + URL + URL + + + Fetch it now + Le chercher maintenant + + + Icon + Icône + + + Select icon for your feed. + Sélectionner un icône pour votre flux. + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + Certain flux requière une authentification, incluant les flux GMail. Les schémas d'authentification BASIC, NTLM-2 et DIGEST-MD5 sont supportés. + + + Requires authentication + Authentification requise + + + Username + Nom d'utilisateur + + + Password + Mot de passe + + + Fetch metadata + Chercher les métadonnées + + + Add new feed + Ajouter un nouveau flux + + + Edit existing feed + + + + Feed name is ok. + Le nom du flux est correct. + + + Feed name is too short. + Le nom du flux est trop court. + + + Description is empty. + La description est vide. + + + The description is ok. + + + + The url is ok. + L'URL est correct. + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + L'URL ne respecte pas le pattern standard. Votre URL doit commencer avec les préfixe "http://" ou "https://". + + + The url is empty. + L'URL est vide. + + + Username is ok or it is not needed. + Le nom d'utilisateur est correct ou non nécessaire. + + + Username is empty. + Le nom d'utilisateur est vide. + + + Password is ok or it is not needed. + Le mot de passe est correct ou non nécessaire. + + + Password is empty. + Le mot de passe est vide. + + + Select icon file for the feed + Sélectionner un icône pour le flux + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Sélectionner l'icône + + + Cancel + Annuler + + + Look in: + Label for field with icon file name textbox for selection dialog. + Rechercher dans : + + + Icon name: + Nom de l'icône : + + + Icon type: + Type d'icône : + + + Cannot add feed + Impossible d'ajouter le flux + + + Feed was not added due to error. + Le flux n'a pas été ajouté dû à une erreur. + + + Cannot edit feed + Impossible d'éditer le flux + + + Feed was not edited due to error. + Le flux n'a pas été édité dû à une erreur. + + + All metadata fetched successfully. + Tout les méta-datas ont été extraites avec succès. + + + Feed and icon metadata fetched. + Flux et icône extraits. + + + Result: %1. + Résultat : %1. + + + Feed or icon metatada not fetched. + Flux ou icône non extrait. + + + Error: %1. + Erreur : %1. + + + No metadata fetched. + Aucune méta-donnée extraite. + + + Icon fetched successfully. + + + + Icon metadata fetched. + + + + Icon metatada not fetched. + + + + No icon fetched. + + + + Feed title + Titre du flux + + + Set title for your feed. + Définir un titre pour votre flux. + + + Feed description + Description du flux + + + Set description for your feed. + Définir une description pour votre flux. + + + Full feed url including scheme + URL du flux complet incluant le préfixe + + + Set url for your feed. + Définir l'URL pour votre flux. + + + Set username to access the feed. + Définir le nom d'utilisateur pour accéder au flux. + + + Set password to access the feed. + Définir le mot de passe pour accéder au flux. + + + Icon selection + Sélection de l'icône + + + Load icon from file... + Charger l'icône depuis un fichier... + + + Do not use icon + Ne pas utiliser les icônes + + + Use default icon + Utiliser les icônes par défaut + + + Fetch icon from feed + + + + No metadata fetched so far. + Pas de métadonnées trouvé aussi loin. + + + Auto-update using global interval + Mise à jour automatique utilisant l'intervalle global + + + Auto-update every + Tout mettre à jour + + + Do not auto-update at all + Ne pas mettre tout à jour automatiquement + + + + FormStandardImportExport + + &Select file + + + + &Check all items + + + + &Uncheck all items + + + + Operation results + + + + No file is selected. + + + + No operation executed yet. + + + + Destination file + + + + Source feeds && categories + + + + Export feeds + + + + Source file + Fichier source + + + Target feeds && categories + + + + Import feeds + Importer des flux + + + OPML 2.0 files (*.opml) + Fichier OPML 2.0 (*.opml) + + + Select file for feeds export + + + + File is selected. + Le fichier est sélectionné + + + Select file for feeds import + + + + Cannot open source file. + + + + Feeds were loaded. + + + + Error, file is not well-formed. Select another file. + + + + Error occurred. File is not well-formed. Select another file. + + + + Feeds were exported successfully. + + + + Cannot write into destination file. + + + + Critical error occurred. + Erreur critique rencontrée @@ -2580,23 +2546,23 @@ Aller sur le site de l'application pour les obtenir manuellement. Download update - + Downloaded %1% (update size is %2 kB). - + Downloading update... - + Downloaded successfully - + Package was downloaded successfully. - + Install update @@ -2608,11 +2574,11 @@ Aller sur le site de l'application pour les obtenir manuellement. Error occured during downloading of the package. - + Cannot launch external updater. Update application manually. - + Go to application website @@ -2623,11 +2589,11 @@ Aller sur le site de l'application pour les obtenir manuellement.IOFactory Cannot open file '%1' for reading. - + Cannot open file '%1' for writting. - + @@ -2721,46 +2687,54 @@ Aller sur le site de l'application pour les obtenir manuellement. Permanently deleted - + Is message permanently deleted from recycle bin? - + Attachments - + List of attachments. - + + + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + MessagesToolBar Search messages - + Message search box - + Menu for highlighting messages - + No extra highlighting - + Highlight unread messages - + Highlight important messages - + Display all messages @@ -2768,11 +2742,11 @@ Aller sur le site de l'application pour les obtenir manuellement. Message highlighter - + Toolbar spacer - + @@ -2799,11 +2773,11 @@ Aller sur le site de l'application pour les obtenir manuellement. Problem with starting external e-mail client - + External e-mail client could not be started. - + @@ -2880,11 +2854,11 @@ Aller sur le site de l'application pour les obtenir manuellement. access to content was denied - + connection timed out or was cancelled - + @@ -2908,61 +2882,198 @@ Aller sur le site de l'application pour les obtenir manuellement.LANG_EMAIL nicolaslegall34@gmail.com - - Load initial feeds - - - - Do you want to load initial set of feeds? - - LANG_NAME Name of language, e.g. English. French - - You started %1 for the first time, now you can load initial set of feeds. - + + + ++ %n other feeds. + + + + - Welcome to %1 %2. - + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + + + + Welcome to %1. + + + + Load initial set of feeds + RecycleBin Recycle bin - + Recycle bin contains all deleted messages from all feeds. - + Recycle bin %1 - + %n deleted message(s). - + + + + ShortcutCatcher Reset to original shortcut. - + Clear current shortcut. - + Click and hit new shortcut. - + + + + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + + + + +This category does not contain any nested items. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardFeed + + Metadata not fetched + + + + Metadata was not fetched because: %1. + + + + does not use auto-update + Describes feed auto-update status. + + + + uses global settings + Describes feed auto-update status. + + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + + + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + + + + You started %1 for the first time, now you can load initial set of feeds. + + + + Do you want to load initial set of feeds? + + + + Error when loading initial feeds + + + + This is service account for standard RSS/RDF/ATOM feeds. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + Fetch metadata + Chercher les métadonnées + + + Import successfull, but some feeds/categories were not imported due to error. + + + + Import was completely successfull. + + + + Add new category + Ajouter une nouvelle catégorie + + + Add new feed + Ajouter un nouveau flux + + + Export feeds + + + + Import feeds + Importer des flux @@ -2980,11 +3091,15 @@ Aller sur le site de l'application pour les obtenir manuellement.SystemFactory New version available - + Click the bubble for more information. - + + + + anonymous + @@ -2992,7 +3107,7 @@ Aller sur le site de l'application pour les obtenir manuellement. %1 Unread news: %2 - + @@ -3023,7 +3138,7 @@ Unread news: %2 Displays main menu. - + Main menu @@ -3035,14 +3150,14 @@ Unread news: %2 Downloads - + ToolBarEditor Activated actions - + Available actions @@ -3050,39 +3165,39 @@ Unread news: %2 Insert separator - + Insert spacer - + Separator - + Toolbar spacer - + Move action up - + Move action down - + Add selected action - + Delete selected action - + Delete all actions - + @@ -3092,6 +3207,21 @@ Unread news: %2 Fermer en premier les fenêtres modales ouvertes. + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + WebBrowser @@ -3163,6 +3293,14 @@ Unread news: %2 Stop web page loading. Arrêter le chargement de la page + + Cannot add feed + Impossible d'ajouter le flux + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + + WebView @@ -3244,63 +3382,63 @@ Unread news: %2 Open link in external browser - + Open the hyperlink in external browser. - + Print - + Print current web page. - + HTML web pages (*.html) - + Select destination file for web page - + Cannot save web page - + Web page cannot be saved because destination file is not writtable. - + Save target as... - + Download content from the hyperlink. - + Save page as... - + Save image to disk. - + Save image as... - + source_page - + Search "%1" via Google... - + - \ No newline at end of file + diff --git a/localization/rssguard-it_IT.ts b/localization/rssguard-it_IT.ts index 4813b3b44..00592534c 100644 --- a/localization/rssguard-it_IT.ts +++ b/localization/rssguard-it_IT.ts @@ -1,4 +1,6 @@ - + + + AdBlockAddSubscriptionDialog @@ -15,15 +17,15 @@ Entered title is empty. - + Entered url is okay. - + Entered url is empty. - + Title @@ -55,7 +57,7 @@ Note that Adblock may significantly slow this application down once you activate huge subscriptions. Too many rules is not good for performance. Also, make sure you restart application after you disable Adblock if you wish to have low memory footprint. Adblock is known to use much system memory. Also note that some resources are cached by internal web browser. Thus, after changing some rules or subscriptions they will fully apply only for new application instances. Make sure you restart RSS Guard for best Adblock experience. - + Options @@ -63,11 +65,11 @@ Also note that some resources are cached by internal web browser. Thus, after ch Filter rules - + Use only essential part of EasyList (for performance reasons) - + Add rule @@ -91,7 +93,7 @@ Also note that some resources are cached by internal web browser. Thus, after ch Rules writing guide - + @@ -102,11 +104,11 @@ Also note that some resources are cached by internal web browser. Thus, after ch Show Adblock &settings - + Disable on %1 - + Disable only on this page @@ -114,11 +116,11 @@ Also note that some resources are cached by internal web browser. Thus, after ch Blocked popup windows - + %1 with (%2) - + No content blocked @@ -126,37 +128,37 @@ Also note that some resources are cached by internal web browser. Thus, after ch Blocked some content - click to edit rule - + Adblock - up and running - + Adblock - not running - + AdBlockSubscription Cannot load subscription! - + AdBlockTreeWidget Please write your rule here - + %1 (recently updated) - + %1 (error: %2) - + Add rule @@ -175,92 +177,74 @@ Also note that some resources are cached by internal web browser. Thus, after ch Output directory is not writable. - + Settings file not copied to output directory successfully. - + Database file not copied to output directory successfully. - + Database restoration was not initiated. Make sure that output directory is writable. - + Settings restoration was not initiated. Make sure that output directory is writable. - - - - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - - - - -This category does not contain any nested items. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - + DatabaseCleaner Shrinking database file... - + Database file shrinked... - + Removing read messages... - + Read messages purged... - + Recycle bin purged... - + Removing old messages... - + Purging recycle bin... - + Old messages purged... - + DatabaseFactory MySQL server works as expected. - + No MySQL server is running in the target destination. - + Access denied. Invalid username or password used. Access to MySQL server was denied. - + Unknown error. @@ -269,27 +253,30 @@ This category does not contain any nested items. Selected database does not exist (yet). - + MySQL/MariaDB (dedicated database) - + SQLite (embedded database) - + DiscoverFeedsButton This website does not contain any feeds. - + Click me to add feeds from this website. This website contains %n feed(s). - + + + + @@ -304,72 +291,72 @@ This website contains %n feed(s). Error opening output file: %1 - + &Try again - + &Stop - + &Open file - + Select destination for downloaded file - + Error: %1 - + Errore: %1. {1?} Download directory couldn't be created - + Error when saving file: %1 - + %1 of %2 (%3 per second) - %4 - + %1 of %2 - download completed - + Open &directory - + Cannot open file - + Cannot open output file. Open it manually. - + Cannot open directory - + Cannot open output directory. Open it manually. - + Download finished - + - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. - + URL: %1 @@ -377,11 +364,11 @@ Click here to open parent directory. Local file: %1 - + Selection of local file cancelled. - + @@ -392,11 +379,17 @@ Click here to open parent directory. %n minutes remaining - + + + + %n seconds remaining - + + + + bytes @@ -416,47 +409,10 @@ Click here to open parent directory. Downloading %n file(s)... - - - - - Feed - - does not use auto-update - Describes feed auto-update status. - - - - uses global settings - Describes feed auto-update status. - - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - - - - %n unread message(s). - Tooltip for "unread" column of feed list. - - - - Metadata not fetched - - - - Metadata was not fetched because: %1 - + + + + @@ -465,58 +421,32 @@ Auto-update status: %5 Toolbar for messages Toolbar per messaggi - - Feed update started - Text display in status bar when feed update is started. - Aggiornamento feed iniziato - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - Feed '%1' aggiornato - Toolbar for feeds Toolbar per i feed - - Error when loading initial feeds - - Cannot cleanup database - + Cannot cleanup database, because another critical action is running. - - - - Cannot update all items - - - - You cannot update all items because another another critical operation is ongoing. - - - - New messages downloaded - + FeedsImportExportModel (category) - + (feed) - + Category - + @@ -539,25 +469,46 @@ Auto-update status: %5 Name of root item of feed list which can be seen in feed add/edit dialog. Root - - Invalid tree data. - - - - Import successfull, but some feeds/categories were not imported due to error. - - - - Import was completely successfull. - - Starting auto-update of some feeds - + I will auto-update %n feed(s). - + + + + + + + Cannot update all items + + + + You cannot update all items because another another critical operation is ongoing. + + + + Feed update started + Text display in status bar when feed update is started. + Aggiornamento feed iniziato + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + Feed '%1' aggiornato + + + New messages downloaded + + + + You can't transfer dragged item into different account, this is not supported. + + + + Cannot perform drag & drop operation + @@ -569,14 +520,6 @@ Auto-update status: %5 FeedsView - - Cannot add standard category - Impossibile aggiungere categoria standard - - - Cannot add standard feed - Impossibile aggiungere feed standard - Cannot edit item Impossibile modificare elemento @@ -585,65 +528,54 @@ Auto-update status: %5 Cannot delete item Impossibile eliminare l'elemento - - You are about to delete selected feed or category. - - - - Deletion of item failed. - - - - Selected item was not deleted due to error. - - - - Do you really want to delete selected item? - - - - Permanently delete messages - - - - You are about to permanenty delete all messages from your recycle bin. - - - - Do you really want to empty your recycle bin? - - Context menu for empty space - - - - Context menu for recycle bin - - - - You cannot add new standard category now because another critical operation is ongoing. - - - - You cannot add new standard feed now because another critical operation is ongoing. - + Selected item cannot be edited because another critical operation is ongoing. - + Selected item cannot be deleted because another critical operation is ongoing. - - - - Delete feed/category - + Context menu for categories - + + + + Selected item cannot be edited, this is not (yet?) supported. + + + + Deleting "%1" + + + + You are about to completely delete item "%1". + + + + Are you sure? + + + + Cannot delete "%1" + + + + This item cannot be deleted because something critically failed. Submit bug report. + + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + + + + Context menu for other items + @@ -690,11 +622,7 @@ Auto-update status: %5 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> - - - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - <body>%5 è un (davvero) piccolo lettore di feed.<br><br>Questo software viene distribuito sotto i termini della GNU General Public License, version 3.<br><br>Contatti:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>Puoi ottonere il codice sorgente di %5 dal suo sito web.<br><br><br>Copyright (C) 2011-%3 %4</body> + About %1 @@ -703,42 +631,85 @@ Auto-update status: %5 Settings type - + Settings file - + Database root path - + FULLY portable - + PARTIALLY portable - + Resources Risorse + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + + FormAddAccount + + Add new account + + + + Details + + + + Name + Nome + + + Version + Versione + + + Author + Autore + + + Description + Descrizione + + + Cannot add account + + + + Some critical error occurred, report this to developers. + + + + This account can be added only once. + + FormBackupDatabaseSettings Backup database/settings - + Backup properties - + Items to backup - + Database @@ -750,218 +721,93 @@ Auto-update status: %5 Backup name - + Operation results - + Common name for backup files - + No operation executed yet. - + Backup was created successfully. - + Backup name cannot be empty. - + Backup name looks okay. - + Backup failed. - + Output directory - + &Select directory - + Backup was created successfully and stored in target directory. - + Select destination directory - + Good destination directory is specified. - - - - - FormCategoryDetails - - Parent category - - - - Select parent item for your category. - - - - Title - Titolo - - - Description - Descrizione - - - Icon - Icona - - - Select icon for your category. - Seleziona icona per la tua categoria. - - - Add new category - Aggiungi nuova categoria - - - Edit existing category - Modifica categoria esistente - - - Cannot add category - Impossibile aggiungere categria - - - Category was not added due to error. - - - - Cannot edit category - Impossibile modificare categoria - - - Category was not edited due to error. - - - - Category name is ok. - Il nome della categoria è ok. - - - Category name is too short. - Il nome della categoria è troppo corto. - - - Description is empty. - La descrizione è vuota. - - - Select icon file for the category - Seleziona icona per la categoria - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Immagini (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Seleziona icona - - - Cancel - Annulla - - - Look in: - Label to describe the folder for icon file selection dialog. - - - - Icon name: - Nome icona: - - - Icon type: - Tipo icona: - - - Category title - Titolo categoria - - - Set title for your category. - Imposta titolo per la tua categoria. - - - Category description - Descrizione categoria - - - Set description for your category. - Imposta descrizione per la tua categoria. - - - Icon selection - Selezione icona - - - Load icon from file... - Carica icona dal file... - - - Do not use icon - Non usare icona - - - Use default icon - Usa icona di default - - - The description is ok. - + FormDatabaseCleanup Cleanup database - + Remove all messages older than - + day(s) - + + + + Shrink database file - + Database information - + Database file size - + Database type - + Progress - + I am ready. @@ -969,414 +815,31 @@ Auto-update status: %5 Database cleanup is running. - + Database cleanup is completed. - + Database cleanup failed. - + Cleanup settings (all checked items are completely erased from database) - + Remove all read messages (not those from recycle bin) - + Remove all messages from recycle bin - + Remove all starred messages (including those from recycle bin) - - - - - FormFeedDetails - - Parent category - - - - Select parent item for your feed. - - - - Type - Tipo - - - Select type of the standard feed. - Seleziona tipo di feed standard. - - - Encoding - - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - - - - Auto-update - Auto-aggiorna - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - - - - minutes - minuti - - - Title - Titolo - - - Description - Descrizione - - - URL - URL - - - Fetch it now - Recupera adesso - - - Icon - Icona - - - Select icon for your feed. - Seleziona icona per il tuo feed. - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - - - - Requires authentication - Richiede autenticazione - - - Username - Nome utente - - - Password - Password - - - Fetch metadata - Recupera metadata - - - Add new feed - Aggiungi nuovo feed - - - Edit existing feed - Modifica feed esistente - - - Feed name is ok. - Il nome feed è ok. - - - Feed name is too short. - Il nome feed è troppo corto. - - - Description is empty. - La descrizione è vuota. - - - The url is ok. - L'url è ok. - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - - - - The url is empty. - L'url è vuoto. - - - Username is ok or it is not needed. - - - - Username is empty. - Nome utente vuoto. - - - Password is ok or it is not needed. - - - - Password is empty. - La password è vuota. - - - Select icon file for the feed - Seleziona icona per il feed - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Immagini (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Seleziona icona - - - Cancel - Annulla - - - Look in: - Label for field with icon file name textbox for selection dialog. - - - - Icon name: - Nome icona: - - - Icon type: - Tipo icona: - - - Cannot add feed - Impossibile aggiungere feed - - - Feed was not added due to error. - Feed non aggiunto a causa di un errore. - - - Cannot edit feed - Impossibile modificare il feed - - - All metadata fetched successfully. - Tutti i metadata recuperati con successo. - - - Feed and icon metadata fetched. - Feed e icona metadata recuperati. - - - Result: %1. - Risultato: %1. - - - Feed or icon metatada not fetched. - Feed o icona metadata recuperati. - - - Error: %1. - Errore: %1. - - - No metadata fetched. - Nessun metadata recuperato. - - - Feed title - Titolo feed - - - Set title for your feed. - Imposta titolo per il tuo feed. - - - Feed description - Descrizione feed - - - Set description for your feed. - Imposta descrizione del tuo feed. - - - Full feed url including scheme - - - - Set url for your feed. - Imposta url per il tuo feed. - - - Set username to access the feed. - Imposta nome utente per accedere al feed. - - - Set password to access the feed. - Imposta password per accedere al feed. - - - Icon selection - Selezione icona - - - Load icon from file... - Carica icona dal file... - - - Do not use icon - Non usare icona - - - Use default icon - Usa icona di default - - - No metadata fetched so far. - - - - Auto-update using global interval - - - - Auto-update every - Auto-aggiorna ogni - - - Do not auto-update at all - - - - The description is ok. - - - - Feed was not edited due to error. - Feed non modificato a coausa di un errore. - - - Icon fetched successfully. - - - - Icon metadata fetched. - - - - Icon metatada not fetched. - - - - No icon fetched. - - - - Fetch icon from feed - - - - - FormImportExport - - &Select file - - - - Operation results - - - - No file is selected. - - - - No operation executed yet. - - - - Export feeds - - - - Destination file - - - - Source feeds && categories - - - - Source file - - - - Target feeds && categories - - - - Import feeds - - - - OPML 2.0 files (*.opml) - - - - Select file for feeds export - - - - File is selected. - - - - Select file for feeds import - - - - Cannot open source file. - - - - Feeds were loaded. - - - - Error, file is not well-formed. Select another file. - - - - Error occurred. File is not well-formed. Select another file. - - - - Feeds were exported successfully. - - - - Cannot write into destination file. - - - - Critical error occurred. - - - - &Check all items - - - - &Uncheck all items - + @@ -1423,7 +886,7 @@ Auto-update status: %5 Switch &importance of selected messages - + Quit the application. @@ -1453,22 +916,6 @@ Auto-update status: %5 No actions are available right now. Non è disponibile nessuna azione adesso. - - Fee&ds && categories - - - - Mark all messages (without message filters) from selected feeds as read. - - - - Mark all messages (without message filters) from selected feeds as unread. - - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - - Hides main window if it is visible and shows it if it is hidden. Nasconde la finestra principale se è visibile e la mostra se è nascosta. @@ -1483,7 +930,7 @@ Auto-update status: %5 &About application - + Displays extra info about this application. @@ -1493,34 +940,6 @@ Auto-update status: %5 &Delete selected messages &Elimina i messaggi selezionati - - Deletes all messages from selected feeds. - Elimina tutti i messaggi dai feed selezionati. - - - Marks all messages in all feeds read. This does not take message filters into account. - - - - Deletes all messages from all feeds. - Elimina tutti i messaggi da tutti i feed. - - - Update &all feeds - - - - Update &selected feeds - - - - &Edit selected feed/category - - - - &Delete selected feed/category - - Settings Impostazioni @@ -1529,13 +948,9 @@ Auto-update status: %5 Hides or displays the main menu. Nascondi o visualizza il menu principale. - - Add &new feed/category - - &Close all tabs except current one - + &Close current tab @@ -1543,67 +958,35 @@ Auto-update status: %5 Mark &selected messages as &read - + Mark &selected messages as &unread - - - - &Mark selected feeds as read - - - - &Mark selected feeds as unread - - - - &Clean selected feeds - + Open selected source articles in &external browser - + Open selected messages in &internal browser - + Open selected source articles in &internal browser - - - - &Mark all feeds as &read - - - - View selected feeds in &newspaper mode - - - - &Clean all feeds - - - - Select &next feed/category - - - - Select &previous feed/category - + Select &next message - + Select &previous message - + Check for &updates - + Enable &JavaScript @@ -1623,11 +1006,11 @@ Auto-update status: %5 &Fullscreen - + &Feed list - + &Main menu @@ -1635,7 +1018,7 @@ Auto-update status: %5 Switch visibility of main &window - + Cannot open external browser @@ -1643,23 +1026,7 @@ Auto-update status: %5 Cannot open external browser. Navigate to application website manually. - - - - New &feed - Nuovo &feed - - - Add new feed. - Aggiungi nuovo feed. - - - New &category - Nuova &categoria - - - Add new category. - Aggiungi nuova categoria. + &Toolbars @@ -1671,154 +1038,238 @@ Auto-update status: %5 &Feed/message list headers - - - - &Import feeds - - - - Imports feeds you want from selected file. - - - - &Export feeds - - - - Exports feeds you want to selected file. - + Close all tabs except current one. - - - - &Recycle bin - + Report a &bug (GitHub)... - + Report a bug (BitBucket)... - + &Donate via PayPal - + Display &wiki - - - - &Empty recycle bin - - - - &Restore all messages - - - - Restore &selected messages - + &Restart - + &Restore database/settings - + &Backup database/settings - + Switch message list layout orientation - + &Downloads - + Send selected message via e-mail - + &Cleanup database - + - Show only unread feeds/categories - + Add &new item + - &Fetch feed metadata - + Update &all items + - &Expand/collapse selected feed/category - + Update &selected items + + + + &Edit selected item + + + + &Delete selected item + + + + &Mark selected items as read + + + + Mark all messages (without message filters) from selected items as read. + + + + &Mark selected items as unread + + + + Mark all messages (without message filters) from selected items as unread. + + + + &Clean selected items + + + + Deletes all messages from selected items. + + + + &Mark all items as &read + + + + Marks all messages in all items read. This does not take message filters into account. + + + + View selected items in &newspaper mode + + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + + &Clean all items + + + + Deletes all messages from all items. + + + + Select &next item + + + + Select &previous item + + + + Show only unread items + + + + &Expand/collapse selected item + + + + &Add new service account + + + + &Restore selected messages + + + + No possible actions + + + + Feeds && categories && accounts + + + + &Recycle bin(s) + + + + &Restore all recycle bins + + + + &Empty all recycle bins + + + + Select next &unread message + + + + No recycle bin + + + + Restore recycle bin + + + + Empty recycle bin + FormRestoreDatabaseSettings Restore database/settings - + Operation results - + Restore database - + Restore settings - + Restart - + No operation executed yet. - + Restoration was initiated. Restart to proceed. - + You need to restart application for restoration process to finish. - + Source directory - + &Select directory - + Database and/or settings were not copied to restoration directory successully. - + Select source directory - + Good source directory is specified. - + @@ -1914,10 +1365,6 @@ Auto-update status: %5 Author Autore - - Email - Email - Socks5 Socks5 @@ -1932,7 +1379,7 @@ Auto-update status: %5 Tray area && notifications - + Tabs @@ -1944,11 +1391,11 @@ Auto-update status: %5 Middle mouse button single-click - + Open new tabs with left mouse button double-click on tab bar - + Enable mouse gestures @@ -1956,7 +1403,7 @@ Auto-update status: %5 Queue new tabs (with hyperlinks) after the active tab - + no icon theme @@ -2001,7 +1448,7 @@ Auto-update status: %5 Some critical settings are not set. You must fix these settings in order confirm new settings. - + Messages @@ -2017,7 +1464,7 @@ Auto-update status: %5 Note that "%1" (without quotation marks) is placeholder for URL of selected message. - + Select web browser executable @@ -2073,7 +1520,7 @@ Auto-update status: %5 Use in-memory database as the working database - + Usage of in-memory working database has several advantages and pitfalls. Make sure that you are familiar with these before you turn this feature on. Advantages: @@ -2087,11 +1534,11 @@ Disadvantages: <li>application startup and shutdown can take little longer (max. 2 seconds).</li> </ul> Authors of this application are NOT responsible for lost data. - + in-memory database switched - + Internal web browser @@ -2103,7 +1550,7 @@ Authors of this application are NOT responsible for lost data. WARNING: Note that switching to another data storage type will NOT copy existing your data from currently active data storage to newly selected one. - + Database driver @@ -2115,11 +1562,11 @@ Authors of this application are NOT responsible for lost data. Test setup - + Right mouse button double-click - + Auto-update all feeds every @@ -2135,7 +1582,7 @@ Authors of this application are NOT responsible for lost data. Connection timeout is time interval which is reserved for downloading new messages for the feed. If this time interval elapses, then download process is aborted. - + ms @@ -2163,7 +1610,7 @@ Authors of this application are NOT responsible for lost data. data storage backend changed - + Hostname is empty. @@ -2199,7 +1646,7 @@ Authors of this application are NOT responsible for lost data. No connection test triggered so far. - + Note that these settings are applied only on newly established connections. @@ -2239,11 +1686,11 @@ Authors of this application are NOT responsible for lost data. Keep message selection in the middle of the message list viewport - + You did not executed any connection test yet. - + Launch %1 on operating system startup @@ -2263,7 +1710,7 @@ Authors of this application are NOT responsible for lost data. <html><head/><body><p>If unchecked, then default system-wide web browser is used.</p></body></html> - + Feeds && categories @@ -2275,7 +1722,7 @@ Authors of this application are NOT responsible for lost data. Enter format for count of messages displayed next to each feed/category in feed list. Use "%all" and "%unread" strings which are placeholders for the actual count of all (or unread) messages. - + custom external browser is not set correctly @@ -2301,7 +1748,7 @@ Authors of this application are NOT responsible for lost data. Some critical settings were changed and will be applied after the application gets restarted. You have to restart manually. - + Do you want to restart now? @@ -2309,70 +1756,70 @@ You have to restart manually. Check for updates on application startup - + Use custom date/time format (overrides format loaded from active localization) - + Executables (*) File filter for external browser selection dialog. ---------- File filter for external e-mail selection dialog. - + Remove all read messages from all feeds on application exit - + When new message arrives from feed and duplicate exists, then its content is updated and new message is dropped. - + Remove duplicate messages - + Downloads - + Target directory for downloaded files - + Ask for each individual downloaded file - + Target directory where all downloaded files are saved - + &Browse - + Select downloads target directory - + &Show password - + Web browser & e-mail & proxy - + Remove junk Trolltech registry key (HKCU\Software\Trolltech) when application quits (Use at your own risk!) - + Working database - + Mouse gestures work with middle mouse button. Possible gestures are: @@ -2380,101 +1827,620 @@ File filter for external e-mail selection dialog. • next web page (drag mouse right), • reload current web page (drag mouse up), • open new web browser tab (drag mouse down). - + Use custom external web browser - + External e-mail client - + Use custom external e-mail client - + E-mail client executable - + Executable file of e-mail client - + Select client - + Placeholders: • %1 - title of selected message, • %2 - body of selected message. - + Save all downloaded files to - + Select e-mail executable - + Mozilla Thunderbird - + Working database which you have full access to. - + Working database is empty. - + Working database is ok. - + Notification position - + (Tray icon is not available.) - + Bottom-left corner - + Top-left corner - + Bottom-right corner - + Top-right corner - + Internal message browser fonts - + Standard font - + Note that speed of used MySQL server and latency of used connection medium HEAVILY influences the final performance of this application. Using slow database connections leads to bad performance when browsing feeds or messages. - + Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) - + + + + E-mail + + + + Enable notifications + + + + + FormStandardCategoryDetails + + Parent category + + + + Select parent item for your category. + + + + Title + Titolo + + + Description + Descrizione + + + Icon + Icona + + + Select icon for your category. + Seleziona icona per la tua categoria. + + + Add new category + Aggiungi nuova categoria + + + Edit existing category + Modifica categoria esistente + + + Cannot add category + Impossibile aggiungere categria + + + Category was not added due to error. + + + + Cannot edit category + Impossibile modificare categoria + + + Category was not edited due to error. + + + + Category name is ok. + Il nome della categoria è ok. + + + Category name is too short. + Il nome della categoria è troppo corto. + + + Description is empty. + La descrizione è vuota. + + + The description is ok. + + + + Select icon file for the category + Seleziona icona per la categoria + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Immagini (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Seleziona icona + + + Cancel + Annulla + + + Look in: + Label to describe the folder for icon file selection dialog. + + + + Icon name: + Nome icona: + + + Icon type: + Tipo icona: + + + Category title + Titolo categoria + + + Set title for your category. + Imposta titolo per la tua categoria. + + + Category description + Descrizione categoria + + + Set description for your category. + Imposta descrizione per la tua categoria. + + + Icon selection + Selezione icona + + + Load icon from file... + Carica icona dal file... + + + Do not use icon + Non usare icona + + + Use default icon + Usa icona di default + + + + FormStandardFeedDetails + + Parent category + + + + Select parent item for your feed. + + + + Type + Tipo + + + Select type of the standard feed. + Seleziona tipo di feed standard. + + + Encoding + + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + + + + Auto-update + Auto-aggiorna + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + + + + minutes + minuti + + + Title + Titolo + + + Description + Descrizione + + + URL + URL + + + Fetch it now + Recupera adesso + + + Icon + Icona + + + Select icon for your feed. + Seleziona icona per il tuo feed. + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + + + + Requires authentication + Richiede autenticazione + + + Username + Nome utente + + + Password + Password + + + Fetch metadata + Recupera metadata + + + Add new feed + Aggiungi nuovo feed + + + Edit existing feed + Modifica feed esistente + + + Feed name is ok. + Il nome feed è ok. + + + Feed name is too short. + Il nome feed è troppo corto. + + + Description is empty. + La descrizione è vuota. + + + The description is ok. + + + + The url is ok. + L'url è ok. + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + + + + The url is empty. + L'url è vuoto. + + + Username is ok or it is not needed. + + + + Username is empty. + Nome utente vuoto. + + + Password is ok or it is not needed. + + + + Password is empty. + La password è vuota. + + + Select icon file for the feed + Seleziona icona per il feed + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Immagini (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Seleziona icona + + + Cancel + Annulla + + + Look in: + Label for field with icon file name textbox for selection dialog. + + + + Icon name: + Nome icona: + + + Icon type: + Tipo icona: + + + Cannot add feed + Impossibile aggiungere feed + + + Feed was not added due to error. + Feed non aggiunto a causa di un errore. + + + Cannot edit feed + Impossibile modificare il feed + + + Feed was not edited due to error. + Feed non modificato a coausa di un errore. + + + All metadata fetched successfully. + Tutti i metadata recuperati con successo. + + + Feed and icon metadata fetched. + Feed e icona metadata recuperati. + + + Result: %1. + Risultato: %1. + + + Feed or icon metatada not fetched. + Feed o icona metadata recuperati. + + + Error: %1. + Errore: %1. + + + No metadata fetched. + Nessun metadata recuperato. + + + Icon fetched successfully. + + + + Icon metadata fetched. + + + + Icon metatada not fetched. + + + + No icon fetched. + + + + Feed title + Titolo feed + + + Set title for your feed. + Imposta titolo per il tuo feed. + + + Feed description + Descrizione feed + + + Set description for your feed. + Imposta descrizione del tuo feed. + + + Full feed url including scheme + + + + Set url for your feed. + Imposta url per il tuo feed. + + + Set username to access the feed. + Imposta nome utente per accedere al feed. + + + Set password to access the feed. + Imposta password per accedere al feed. + + + Icon selection + Selezione icona + + + Load icon from file... + Carica icona dal file... + + + Do not use icon + Non usare icona + + + Use default icon + Usa icona di default + + + Fetch icon from feed + + + + No metadata fetched so far. + + + + Auto-update using global interval + + + + Auto-update every + Auto-aggiorna ogni + + + Do not auto-update at all + + + + + FormStandardImportExport + + &Select file + + + + &Check all items + + + + &Uncheck all items + + + + Operation results + + + + No file is selected. + + + + No operation executed yet. + + + + Destination file + + + + Source feeds && categories + + + + Export feeds + + + + Source file + + + + Target feeds && categories + + + + Import feeds + + + + OPML 2.0 files (*.opml) + + + + Select file for feeds export + + + + File is selected. + + + + Select file for feeds import + + + + Cannot open source file. + + + + Feeds were loaded. + + + + Error, file is not well-formed. Select another file. + + + + Error occurred. File is not well-formed. Select another file. + + + + Feeds were exported successfully. + + + + Cannot write into destination file. + + + + Critical error occurred. + @@ -2553,7 +2519,7 @@ correntemente installato. Installation file is not available directly. Go to application website to obtain it manually. - + No new update available. @@ -2565,7 +2531,7 @@ Go to application website to obtain it manually. Cannot navigate to installation file. Check new installation downloads manually on project website. - + Download update @@ -2573,7 +2539,7 @@ Go to application website to obtain it manually. Downloaded %1% (update size is %2 kB). - + Downloading update... @@ -2593,30 +2559,30 @@ Go to application website to obtain it manually. Error occured - + Error occured during downloading of the package. - + Cannot launch external updater. Update application manually. - + Go to application website - + IOFactory Cannot open file '%1' for reading. - + Cannot open file '%1' for writting. - + @@ -2710,19 +2676,27 @@ Go to application website to obtain it manually. Permanently deleted - + Is message permanently deleted from recycle bin? - + Attachments - + List of attachments. - + + + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + @@ -2737,19 +2711,19 @@ Go to application website to obtain it manually. Menu for highlighting messages - + No extra highlighting - + Highlight unread messages - + Highlight important messages - + Display all messages @@ -2757,7 +2731,7 @@ Go to application website to obtain it manually. Message highlighter - + Toolbar spacer @@ -2788,11 +2762,11 @@ Go to application website to obtain it manually. Problem with starting external e-mail client - + External e-mail client could not be started. - + @@ -2865,15 +2839,15 @@ Go to application website to obtain it manually. no errors Network status. - + access to content was denied - + connection timed out or was cancelled - + @@ -2897,46 +2871,57 @@ Go to application website to obtain it manually. LANG_EMAIL rotter.martinos@gmail.com - - Load initial feeds - - - - Do you want to load initial set of feeds? - - LANG_NAME Name of language, e.g. English. Italiano - - You started %1 for the first time, now you can load initial set of feeds. - + + + ++ %n other feeds. + + + + - Welcome to %1 %2. - + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + + + + Welcome to %1. + + + + Load initial set of feeds + RecycleBin Recycle bin - + Recycle bin contains all deleted messages from all feeds. - + Recycle bin %1 - + %n deleted message(s). - + + + + @@ -2951,7 +2936,133 @@ Go to application website to obtain it manually. Click and hit new shortcut. - + + + + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + + + + +This category does not contain any nested items. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardFeed + + Metadata not fetched + + + + Metadata was not fetched because: %1. + + + + does not use auto-update + Describes feed auto-update status. + + + + uses global settings + Describes feed auto-update status. + + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + + + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + + + + You started %1 for the first time, now you can load initial set of feeds. + + + + Do you want to load initial set of feeds? + + + + Error when loading initial feeds + + + + This is service account for standard RSS/RDF/ATOM feeds. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + + + + Fetch metadata + Recupera metadata + + + Import successfull, but some feeds/categories were not imported due to error. + + + + Import was completely successfull. + + + + Add new category + Aggiungi nuova categoria + + + Add new feed + Aggiungi nuovo feed + + + Export feeds + + + + Import feeds + @@ -2962,18 +3073,22 @@ Go to application website to obtain it manually. Switch application between fulscreen/normal states right from this status bar icon. - + SystemFactory New version available - + Click the bubble for more information. - + + + + anonymous + @@ -2981,7 +3096,7 @@ Go to application website to obtain it manually. %1 Unread news: %2 - + @@ -3020,11 +3135,11 @@ Unread news: %2 Open new web browser tab. - + Downloads - + @@ -3055,30 +3170,45 @@ Unread news: %2 Move action up - + Move action down - + Add selected action - + Delete selected action - + Delete all actions - + TrayIconMenu Close opened modal dialogs first. - + + + + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + + + @@ -3152,6 +3282,14 @@ Unread news: %2 Stop web page loading. Ferma caricamento pagina web. + + Cannot add feed + Impossibile aggiungere feed + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + + WebView @@ -3205,19 +3343,19 @@ Unread news: %2 Copies current selection into the clipboard. - + Copy link url to clipboard. - + Copy image to clipboard. - + Copy image url to clipboard. - + Open this hyperlink in new tab. @@ -3233,63 +3371,63 @@ Unread news: %2 Open link in external browser - + Open the hyperlink in external browser. - + Print - + Print current web page. - + HTML web pages (*.html) - + Select destination file for web page - + Cannot save web page - + Web page cannot be saved because destination file is not writtable. - + Save target as... - + Download content from the hyperlink. - + Save page as... - + Save image to disk. - + Save image as... - + source_page - + Search "%1" via Google... - + - \ No newline at end of file + diff --git a/localization/rssguard-nl_NL.ts b/localization/rssguard-nl_NL.ts index f17dcba60..94cb3b1c1 100644 --- a/localization/rssguard-nl_NL.ts +++ b/localization/rssguard-nl_NL.ts @@ -1,4 +1,6 @@ - + + + AdBlockAddSubscriptionDialog @@ -31,7 +33,7 @@ Address - Adres: + Adres @@ -181,11 +183,11 @@ Merk ook op dat sommige hulpbronnen worden gecached door de interne web browser. Settings file not copied to output directory successfully. - Instellingen van bestand niet succesvol gekopieerd naar uitvoermap + Instellingen van bestand niet succesvol gekopieerd naar uitvoermap. Database file not copied to output directory successfully. - Databasebestand niet succesvol gekopieerd naar uitvoermap + Databasebestand niet succesvol gekopieerd naar uitvoermap. Database restoration was not initiated. Make sure that output directory is writable. @@ -196,25 +198,6 @@ Merk ook op dat sommige hulpbronnen worden gecached door de interne web browser. Herstel van de instellingen werd niet gestart. Zorg ervoor dat de uitvoermap beschrijfbaar is. - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - %1 (categorie)%2%3 - - - -This category does not contain any nested items. - -Deze categorie bevat geen nested items. - - - %n unread message(s). - Tooltip for "unread" column of feed list. - %n ongelezen bericht%n ongelezen berichten. - - DatabaseCleaner @@ -236,7 +219,7 @@ Gelezen berichten gewist... Recycle bin purged... - Prullenbak gewist.. + Prullenbak gewist... Removing old messages... @@ -293,9 +276,12 @@ Gelezen berichten gewist... Click me to add feeds from this website. This website contains %n feed(s). - Klik hier om feeds van deze website toe tevoegen -Deze website bevat % n feed.Klik hier om feeds van deze website toe tevoegen -Deze website bevat % n feed(s). + + Klik hier om feeds van deze website toe tevoegen. +Deze website bevat %n feed. + Klik hier om feeds van deze website toe tevoegen. +Deze website bevat %n feeds. + @@ -373,7 +359,7 @@ Deze website bevat % n feed(s). Download klaar - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. Bestand '%1' is klaar Klik hier om map te openen. @@ -399,11 +385,17 @@ Klik hier om map te openen. %n minutes remaining - %n resterende minuut%n resterende minuten + + %n resterende minuut + %n resterende minuten + %n seconds remaining - %n resterende seconde%n resterende seconden + + %n resterende seconde + %n resterende seconden + bytes @@ -423,51 +415,10 @@ Klik hier om map te openen. Downloading %n file(s)... - Dowloading %1 bestand...Downloading %n bestanden... - - - - Feed - - does not use auto-update - Describes feed auto-update status. - automatisch bijwerken niet gebruiken - - - uses global settings - Describes feed auto-update status. - gebruik algemene instellingen - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - gebruik specifieke instellingen (%n minuut voor volgende automatische update)gebruik specifieke instellingen (%n minuten voor volgende automatische update) - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - %1 (%2)%3 - -Netwerk status: %6 -Coderen: %4 -Auto-update status: 55 - - - %n unread message(s). - Tooltip for "unread" column of feed list. - %n ongelezen bericht.%n ongelezen berichten. - - - Metadata not fetched - Metadata niet opgehaald - - - Metadata was not fetched because: %1 - Metadate niet opgehaald omdat: %1 + + Dowloading %n bestand... + Downloading %n bestanden... + @@ -476,24 +427,10 @@ Auto-update status: 55 Toolbar for messages Werkbalk voor berichten - - Feed update started - Text display in status bar when feed update is started. - Bijwerken feed is gestart - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - Feed bijwerken '%1' - Toolbar for feeds Werkbalk voor feeds - - Error when loading initial feeds - Fout bij het laden van de eerste feeds - Cannot cleanup database Kan database niet opschonen @@ -502,18 +439,6 @@ Auto-update status: 55 Cannot cleanup database, because another critical action is running. Je kunt database niet opschonenen omdat een andere kritische operatie gaande is. - - Cannot update all items - Kan alle items niet bijwerken - - - You cannot update all items because another another critical operation is ongoing. - U kunt niet alle items updaten omdat er een andere bewerking plaats vind. - - - New messages downloaded - Nieuw bericht gedownload - FeedsImportExportModel @@ -550,25 +475,46 @@ Auto-update status: 55 Name of root item of feed list which can be seen in feed add/edit dialog. Root - - Invalid tree data. - Ongeldige structuur gegevens - - - Import successfull, but some feeds/categories were not imported due to error. - Importeren succesvol, maar sommige feeds / categorieën waren niet goed geïmporteerd door fouten. - - - Import was completely successfull. - Importeren is helemaal geslaagd. - Starting auto-update of some feeds Begint met auto-update van sommige feeds I will auto-update %n feed(s). - Auto-update van %n feedAuto-update van %n feed(s) + + Auto-update van %n feed. + Auto-update van %n feeds. + + + + Cannot update all items + Kan alle items niet bijwerken + + + You cannot update all items because another another critical operation is ongoing. + U kunt niet alle items updaten omdat er een andere bewerking plaats vind. + + + Feed update started + Text display in status bar when feed update is started. + Bijwerken feed is gestart + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + Feed bijwerken '%1' + + + New messages downloaded + Nieuw bericht gedownload + + + You can't transfer dragged item into different account, this is not supported. + Je kan geen gesleepte item overdragen naar een ander account, dir wordt niet ondersteunt. + + + Cannot perform drag & drop operation + Kan de drag & drop bewerking niet uitvoeren @@ -580,14 +526,6 @@ Auto-update status: 55 FeedsView - - Cannot add standard category - Kan geen standaard categorie toevoegen - - - Cannot add standard feed - Kan geen standaard feed toevoegen - Cannot edit item Kan item niet bewerken @@ -596,50 +534,10 @@ Auto-update status: 55 Cannot delete item Kan item niet verwijderen - - You are about to delete selected feed or category. - Je gaat geselecteerde feed of categorie verwijderen. - - - Deletion of item failed. - Verwijdering van Item is mislukt. - - - Selected item was not deleted due to error. - Geselecteerde item is niet verwijderd door een fout. - - - Do you really want to delete selected item? - Wil je het geselecteerde item echt verwijderen? - - - Permanently delete messages - Definitief berichten verwijderen - - - You are about to permanenty delete all messages from your recycle bin. - Je gaat definitief alle berichten uit de prullenbak verwijderen. - - - Do you really want to empty your recycle bin? - Wil je de prullenbak echt legen? - Context menu for empty space Contextmenu voor lege regels - - Context menu for recycle bin - Contextmenu voor prullenbak - - - You cannot add new standard category now because another critical operation is ongoing. - U kunt geen nieuwe standaard categorie toevoegen omdat een andere kritieke operatie aan de gang is. - - - You cannot add new standard feed now because another critical operation is ongoing. - U kunt geen nieuwe standaard feed toevoegen omdat een andere kritieke operatie aan de gang is. - Selected item cannot be edited because another critical operation is ongoing. Geselecteerde item kunt u niet bewerken omdat een andere kritieke operatie aan de gang is. @@ -648,14 +546,44 @@ Auto-update status: 55 Selected item cannot be deleted because another critical operation is ongoing. Geselecteerde item kunt u niet verwijderen omdat een andere kritieke operatie aan de gang is. - - Delete feed/category - Verwijder feed/categorie - Context menu for categories Contextmenu voor categorieën + + Selected item cannot be edited, this is not (yet?) supported. + Geselecteerde item kun je niet bewerken,dit wordt nog (niet) ondersteunt. + + + Deleting "%1" + Verwijder "%1" + + + You are about to completely delete item "%1". + Je staat op het punt om gehele item "%1" te verwijderen. + + + Are you sure? + Weet je het zeker? + + + Cannot delete "%1" + Kan "%1" niet verwijderen + + + This item cannot be deleted because something critically failed. Submit bug report. + Deze item kan niet worden verwijderd omdat het ergens fout ging. Meld deze bug. + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + Deze item kan niet worden verwijderd omdat het niet wordt ondersteunt +of deze functie bestaat nog niet. + + + Context menu for other items + Invoegen van andere items + FormAbout @@ -703,10 +631,6 @@ Auto-update status: 55 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> <b>%8</b><br><b>Versie:</b> %1 (Gecompileerd onder %2 en Cmake %3)<br><b> Revisie:</b> %4<br><b>Gecompileerd op:</b> %5<br><b>QT versie:</b> %6 (Gecompileerd met %7)<br> - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - <body>%5 is een (zeer) makelijk te gebruiken feed lezer<br><br>Dit programma is beschikbaar onder te termen van de GNU General Public License versie 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>U kunt de broncode voor %5 downloaden van de website..<br><br><br>Auteursrecht (C) 2011-%3 %4</body> - About %1 About RSS Guard dialog title. @@ -736,6 +660,49 @@ Auto-update status: 55 Resources Hulpbronnen + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + <body>%5 is een (zeer) makelijk te gebruiken feed lezer.<br><br>Dit programma is beschikbaar onder te termen van de GNU General Public License, versie 3.<br><br>Contact:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~Website</li><li>U kunt de broncode voor %5 downloaden van de website.<br><br><br>Auteursrecht (C) 2011-%3 %4</body> + + + + FormAddAccount + + Add new account + Voeg nieuw account toe + + + Details + Gegevens + + + Name + Naam + + + Version + Versie + + + Author + Auteur + + + Description + Omschrijving + + + Cannot add account + Kan account niet toevoegen + + + Some critical error occurred, report this to developers. + Enkele kritieke fouten opgetreden, vermeld dit bij de ontwikkelaars. + + + This account can be added only once. + Dit account kan maar 1 maal worden toegevoegd. + FormBackupDatabaseSettings @@ -809,135 +776,7 @@ Auto-update status: 55 Good destination directory is specified. - Juiste bestemmingsmap is opgegeven - - - - FormCategoryDetails - - Parent category - Oudere categorie - - - Select parent item for your category. - Kies hoofd item voor je categorie. - - - Title - Titel - - - Description - Omschrijving - - - Icon - Pictogram - - - Select icon for your category. - Selecteer pictogram voor je categorie. - - - Add new category - Voeg nieuwe categorie toe - - - Edit existing category - Bewerk bestaande categorie - - - Cannot add category - Kan geen categorie toevoegen - - - Category was not added due to error. - Door een fout is de categorie niet toegevoegd. - - - Cannot edit category - Kan categorie niet bewerken - - - Category was not edited due to error. - Door een fout is de categorie niet bewerkt. - - - Category name is ok. - Categorie naam is ok. - - - Category name is too short. - Categorie naam is te kort. - - - Description is empty. - Omschrijving is leeg. - - - Select icon file for the category - Kies pictogram bestand voor de categorie - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Afbeeldingen (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Selecteer pictogram - - - Cancel - Annuleer - - - Look in: - Label to describe the folder for icon file selection dialog. - Kijk in: - - - Icon name: - Pictogram naam: - - - Icon type: - Type pictogram: - - - Category title - Titel categorie - - - Set title for your category. - Stel titel in voor je categorie. - - - Category description - Categorie omschrijving - - - Set description for your category. - Stel omschrijving in voor je categorie. - - - Icon selection - Pictogram selectie - - - Load icon from file... - Laad pictogram uit een bestand... - - - Do not use icon - Gebruik geen pictogram - - - Use default icon - Gebruik standaard pictogram - - - The description is ok. - Omschrijving is ok. + Juiste bestemmingsmap is opgegeven. @@ -952,7 +791,10 @@ Auto-update status: 55 day(s) - dagdagen + + dag + dagen + Shrink database file @@ -976,7 +818,7 @@ Auto-update status: 55 I am ready. - Ik ben klaar + Het is klaar. Database cleanup is running. @@ -1007,389 +849,6 @@ Auto-update status: 55 Verwijder alle berichten met ster(niet die van de prullenbak) - - FormFeedDetails - - Parent category - Oudere categorie - - - Select parent item for your feed. - Kies hoofd item voor je feed. - - - Type - Type - - - Select type of the standard feed. - Selecteer type van de standaard feed. - - - Encoding - Coderen - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - Kies codering van de standaard feed. Als je niet zeker bent van de codering, selecteer dan "UTF-8" codering. - - - Auto-update - Automatische-update - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - Selekteer de automatische bijwerk strategie voor deze feed.Standaard automatische bijwerken strategie betekent dat de feed zal worden bijgewerkt in tijd tussenpauzes ingesteld in RSSguard instelling. - - - minutes - minuten - - - Title - Titel - - - Description - Omschrijving - - - URL - URL - - - Fetch it now - Nu ophalen - - - Icon - Pictogram - - - Select icon for your feed. - Selecteer pictogram voor je feed. - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - Sommige feeds vereisen verificatie,inclusief GMail feeds, BASIC, NTLM-2 en DIGEST-MD5 verificaties schema's worden ondersteund. - - - Requires authentication - Vereist verificatie - - - Username - Gebruikersnaam - - - Password - Paswoord - - - Fetch metadata - Ophalen van metadata - - - Add new feed - Voeg nieuw feed toe - - - Edit existing feed - Bewerk bestaande feed - - - Feed name is ok. - Feed naam is ok. - - - Feed name is too short. - Feed naam is te kort. - - - Description is empty. - Omschrijving is leeg. - - - The url is ok. - De url is ok. - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - De URL voldoet niet aan het standaard patroon. Start je url met "http://" of "https://" prefix. - - - The url is empty. - De url is leeg. - - - Username is ok or it is not needed. - Gebruikersnaam is ok of het is niet nodig. - - - Username is empty. - Gebruikersnaam is leeg. - - - Password is ok or it is not needed. - Paswoord is ok of het is niet nodig. - - - Password is empty. - Paswoord is leeg. - - - Select icon file for the feed - Selecteer pictogram bestand voor je feed - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Afbeeldingen (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Selecteer pictogram - - - Cancel - Annuleer - - - Look in: - Label for field with icon file name textbox for selection dialog. - Kijk in: - - - Icon name: - Pictogram naam: - - - Icon type: - Type pictogram: - - - Cannot add feed - Kan geen feed toevoegen - - - Feed was not added due to error. - Door een fout is de feed niet toegevoegd. - - - Cannot edit feed - Kan feed niet bewerken - - - All metadata fetched successfully. - Alle metadata is succesvol opgehaald. - - - Feed and icon metadata fetched. - Metadata opgehaald voor feed en pictogram. - - - Result: %1. - Resultaat: %1. - - - Feed or icon metatada not fetched. - Metadata voor feed en pictogram niet opgehaald. - - - Error: %1. - Fout: %1. - - - No metadata fetched. - Geen metadata opgehaald. - - - Feed title - Feed naam - - - Set title for your feed. - Stel titel in voor je feed. - - - Feed description - Feed omschrijving - - - Set description for your feed. - Stel omschrijving voor feed in. - - - Full feed url including scheme - Volledige feed url inclusief schema - - - Set url for your feed. - Stel url in voor je feed. - - - Set username to access the feed. - Stel gebruikersnaam in voor toegang tot feed. - - - Set password to access the feed. - Stel paswoord in voor toegang tot feed. - - - Icon selection - Pictogram selectie - - - Load icon from file... - Laad pictogram uit een bestand... - - - Do not use icon - Gebruik geen pictogram - - - Use default icon - Gebruik standaard pictogram - - - No metadata fetched so far. - Nog geen metadata opgehaald. - - - Auto-update using global interval - Automatisch bijwerken met behulp van globale interval - - - Auto-update every - Automatisch bijwerken elke - - - Do not auto-update at all - Niet automatisch bijwerken - - - The description is ok. - Omschrijving is ok. - - - Feed was not edited due to error. - Door een fout is de feed niet bewerkt. - - - Icon fetched successfully. - Pictogram met succes opgehaald. - - - Icon metadata fetched. - Metadata pictogram opgehaald. - - - Icon metatada not fetched. - Metadata pictogram niet opgehaald. - - - No icon fetched. - Geen pictogram opgehaald. - - - Fetch icon from feed - Pictogram opgehaald van feed - - - - FormImportExport - - &Select file - &Selecteer bestand - - - Operation results - Resultaten - - - No file is selected. - Geen bestand geselecteerd. - - - No operation executed yet. - Nog geen handeling uitgevoerd. - - - Export feeds - Exporteer feeds - - - Destination file - Doelbestand - - - Source feeds && categories - Source Feeds && categorieën - - - Source file - Source bestand - - - Target feeds && categories - Doelgroep feeds && categorieën - - - Import feeds - Importeer feeds. - - - OPML 2.0 files (*.opml) - OPML 2.0 bestanden (*.opml) - - - Select file for feeds export - Selecteer bestand voor feed export - - - File is selected. - Bestand is geselecteerd. - - - Select file for feeds import - Selecteer bestand voor feed import - - - Cannot open source file. - Kan source bestand niet openen. - - - Feeds were loaded. - Feeds zijn geladen. - - - Error, file is not well-formed. Select another file. - Fout, het bestand is niet goed gevormd. Selecteer een ander bestand. - - - Error occurred. File is not well-formed. Select another file. - Fout opgetreden. Bestand is niet goed gevormd. Selecteer een ander bestand. - - - Feeds were exported successfully. - Feeds zijn met succes geëxporteerd. - - - Cannot write into destination file. - Kan niet schrijven naar doelbestand. - - - Critical error occurred. - Kritieke fout opgetreden. - - - &Check all items - &Controleer alle items - - - &Uncheck all items - &Vinkje bij alle items - - FormMain @@ -1464,22 +923,6 @@ Auto-update status: 55 No actions are available right now. Er zijn geen acties beschikbaar op dit moment. - - Fee&ds && categories - Fee&ds && categorieën - - - Mark all messages (without message filters) from selected feeds as read. - Markeer alle berichten (zonder berichten filters) van geselecteerde feeds als gelezen. - - - Mark all messages (without message filters) from selected feeds as unread. - Markeer alle berichten (zonder berichten filters) van geselecteerde feeds als ongelezen. - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - Toon alle berichten van geselecteerde feeds/categorieën in een nieuwe "Krantweergave modus" tabblad. Onthoud dat de berichten niet zijn ingesteld als automatisch gelezen. - Hides main window if it is visible and shows it if it is hidden. Verberg hoofdvenster als het zichtbaar is en toon het als het verborgen is. @@ -1504,34 +947,6 @@ Auto-update status: 55 &Delete selected messages Verwij&der geselecteerde berichten - - Deletes all messages from selected feeds. - Verwijder alle berichten van geselecteerde feeds. - - - Marks all messages in all feeds read. This does not take message filters into account. - Markeer alle berichten van alle feeds als gelezen. Dit is niet van toepassing op berichten filters in account. - - - Deletes all messages from all feeds. - Verwijder alle berichten van alle feeds. - - - Update &all feeds - &Alle feeds bijwerken - - - Update &selected feeds - Update ge&selecteerde feeds - - - &Edit selected feed/category - B&ewerk geselecteerde feed/categorie - - - &Delete selected feed/category - Verwij&der geselecteerde feed/categorie - Settings Instellingen @@ -1540,10 +955,6 @@ Auto-update status: 55 Hides or displays the main menu. Verberg of toon het hoofdmenu. - - Add &new feed/category - Voeg &nieuwe feeds/categorieën toe - &Close all tabs except current one &Sluit alle tabbladen behalve deze @@ -1560,18 +971,6 @@ Auto-update status: 55 Mark &selected messages as &unread Markeer ge&selecteerde berichten als &ongelezen - - &Mark selected feeds as read - &Markeer geselecteerde feeds als gelezen - - - &Mark selected feeds as unread - &Markeer geselecteerde bericht als ongelezen - - - &Clean selected feeds - &Wis geselecteerde feeds - Open selected source articles in &external browser Open geselecteerde bron artikelen met &externe webbrowser @@ -1584,26 +983,6 @@ Auto-update status: 55 Open selected source articles in &internal browser Open geselecteerde bron artikelen met &ingebouwde webbrowser - - &Mark all feeds as &read - &Markeer alle feeds als &gelezen - - - View selected feeds in &newspaper mode - Bekijk de geselecteerde items in de kra&ntweergave modus - - - &Clean all feeds - Alle feeds ops&chonen - - - Select &next feed/category - Selecteer volge&nde feeds/categorieën - - - Select &previous feed/category - Selecteer &vorige feeds/categorieën - Select &next message Selecteer volge&nd bericht @@ -1656,22 +1035,6 @@ Auto-update status: 55 Cannot open external browser. Navigate to application website manually. Kan externe webbrowser niet starten, Navigeer handmatig naar RSSguard website. - - New &feed - Nieuw &feed - - - Add new feed. - Voeg nieuw feed toe. - - - New &category - Nieuw &categorie - - - Add new category. - Voeg nieuwe categorie toe. - &Toolbars &Werkbalk @@ -1684,38 +1047,17 @@ Auto-update status: 55 &Feed/message list headers &Feed/bericht kopteksten - - &Import feeds - &Importeer feeds - - - Imports feeds you want from selected file. - Importeer feeds die je wilt van het geselecteerde bestand. - - - &Export feeds - &Exporteer feeds - - - Exports feeds you want to selected file. - -Exporteer feeds die je wilt van het geselecteerde bestand. - Close all tabs except current one. Sluit alle tabbladen behalve deze. - - &Recycle bin - &Prullenbak - Report a &bug (GitHub)... Rapporteer een &bug (Github)... Report a bug (BitBucket)... - Rapporteer een &bug (Bitbucket)... + Rapporteer een bug (Bitbucket)... &Donate via PayPal @@ -1725,18 +1067,6 @@ Exporteer feeds die je wilt van het geselecteerde bestand. Display &wiki Toon &wiki - - &Empty recycle bin - &Prullenbak legen - - - &Restore all messages - &Herstel alle berichten - - - Restore &selected messages - Herstel &geselecteerde berichten - &Restart &Herstart @@ -1747,7 +1077,7 @@ Exporteer feeds die je wilt van het geselecteerde bestand. &Backup database/settings - Backup database/instellingen + &Backup database/instellingen Switch message list layout orientation @@ -1766,16 +1096,132 @@ Exporteer feeds die je wilt van het geselecteerde bestand. &database opschonen - Show only unread feeds/categories - Toon alleen de ongelezen feeds/categorieën + Add &new item + Voeg &nieuw item toe - &Fetch feed metadata - &Ophalen van de feed metadata + Update &all items + &Alle items bijwerken - &Expand/collapse selected feed/category - &Uitklappen/Inklappen van geselcteerde feed/categorie + Update &selected items + &Geselecteerde items bijwerken + + + &Edit selected item + &Bewerk geselecteerde item + + + &Delete selected item + &Verwijder geselecteerde item + + + &Mark selected items as read + &Markeer geselecteerde berichten als gelezen + + + Mark all messages (without message filters) from selected items as read. + Markeer alle berichten (zonder berichtenfilters) van geselecteerde items als gelezen. + + + &Mark selected items as unread + &Markeer geselecteerde item als ongelezen + + + Mark all messages (without message filters) from selected items as unread. + Markeer alle berichten (zonder berichtenfilters) van geselecteerde items als ongelezen. + + + &Clean selected items + Geselecteerde items &opschonen + + + Deletes all messages from selected items. + Verwijder alle berichten van geselecteerde items. + + + &Mark all items as &read + &Markeer alle items als &gelezen + + + Marks all messages in all items read. This does not take message filters into account. + Markeer alle berichten in alle items als gelezen. Dit neemt geen berichtenfilters mee in account. + + + View selected items in &newspaper mode + Bekijk geselecteerde items in kra&ntweergave + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + Toon alle berichten van geselecteerde item in een nieuwe "krantweergave" tabblad.Let op dat berichten niet automatisch als gelezen zijn ingesteld. + + + &Clean all items + Alle items &opschonen + + + Deletes all messages from all items. + Verwijder alle berichten van alle items. + + + Select &next item + Selecteer &volgende item + + + Select &previous item + Selecteer &vorige item + + + Show only unread items + Toon alleen ongelezen items + + + &Expand/collapse selected item + &Uitklappen/inklappen geselecteerde item + + + &Add new service account + &Voeg nieuwe service account toe + + + &Restore selected messages + &Herstel geselecteerde berichten + + + No possible actions + Geen mogelijke acties + + + Feeds && categories && accounts + Feeds && categorieën && accounts + + + &Recycle bin(s) + &Prullenbak + + + &Restore all recycle bins + &Hestel alle prullenbakken + + + &Empty all recycle bins + &Leeg alle prullenbakken + + + Select next &unread message + Selecteer volgende &ongelezen bericht + + + No recycle bin + Geeen vuilnisbak + + + Restore recycle bin + Hestel vuilnisbak + + + Empty recycle bin + Leeg vuilnisbak @@ -1926,10 +1372,6 @@ Exporteer feeds die je wilt van het geselecteerde bestand. Author Auteur - - Email - Email - Socks5 Socks5 @@ -2445,10 +1887,8 @@ Open nieuw webbrowser pagina(sleep muis omlaag). • %1 - title of selected message, • %2 - body of selected message. Plaatshouders: - -•% 1 - titel van het geselecteerde bericht, - -•% 2 - body van geselecteerde bericht. + •.%1 - titel van het geselecteerde bericht, + • %2 - body van geselecteerde bericht. Save all downloaded files to @@ -2472,7 +1912,7 @@ Open nieuw webbrowser pagina(sleep muis omlaag). Working database is ok. - Werkende database is ok + Werkende database is ok. Notification position @@ -2514,6 +1954,525 @@ Open nieuw webbrowser pagina(sleep muis omlaag). Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) Fancy && moderne popup meldingen (Dit gebruikt OS natieve meldingen via D-Bus indien beschikbaar.) + + E-mail + E-mail + + + Enable notifications + Meldingen aanzetten + + + + FormStandardCategoryDetails + + Parent category + Oudere categorie + + + Select parent item for your category. + Kies hoofd item voor je categorie. + + + Title + Titel + + + Description + Omschrijving + + + Icon + Pictogram + + + Select icon for your category. + Selecteer pictogram voor je categorie. + + + Add new category + Voeg nieuwe categorie toe + + + Edit existing category + Bewerk bestaande categorie + + + Cannot add category + Kan geen categorie toevoegen + + + Category was not added due to error. + Door een fout is de categorie niet toegevoegd. + + + Cannot edit category + Kan categorie niet bewerken + + + Category was not edited due to error. + Door een fout is de categorie niet bewerkt. + + + Category name is ok. + Categorie naam is ok. + + + Category name is too short. + Categorie naam is te kort. + + + Description is empty. + Omschrijving is leeg. + + + The description is ok. + Omschrijving is ok. + + + Select icon file for the category + Kies pictogram bestand voor de categorie + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Afbeeldingen (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Selecteer pictogram + + + Cancel + Annuleer + + + Look in: + Label to describe the folder for icon file selection dialog. + Kijk in: + + + Icon name: + Pictogram naam: + + + Icon type: + Type pictogram: + + + Category title + Titel categorie + + + Set title for your category. + Stel titel in voor je categorie. + + + Category description + Categorie omschrijving + + + Set description for your category. + Stel omschrijving in voor je categorie. + + + Icon selection + Pictogram selectie + + + Load icon from file... + Laad pictogram uit een bestand... + + + Do not use icon + Gebruik geen pictogram + + + Use default icon + Gebruik standaard pictogram + + + + FormStandardFeedDetails + + Parent category + Oudere categorie + + + Select parent item for your feed. + Kies hoofd item voor je feed. + + + Type + Type + + + Select type of the standard feed. + Selecteer type van de standaard feed. + + + Encoding + Coderen + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + Kies codering van de standaard feed. Als je niet zeker bent van de codering, selecteer dan "UTF-8" codering. + + + Auto-update + Automatische-update + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + Selekteer de automatische bijwerk strategie voor deze feed.Standaard automatische bijwerken strategie betekent dat de feed zal worden bijgewerkt in tijd tussenpauzes ingesteld in RSSguard instelling. + + + minutes + minuten + + + Title + Titel + + + Description + Omschrijving + + + URL + URL + + + Fetch it now + Nu ophalen + + + Icon + Pictogram + + + Select icon for your feed. + Selecteer pictogram voor je feed. + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + Sommige feeds vereisen verificatie,inclusief GMail feeds, BASIC, NTLM-2 en DIGEST-MD5 verificaties schema's worden ondersteund. + + + Requires authentication + Vereist verificatie + + + Username + Gebruikersnaam + + + Password + Paswoord + + + Fetch metadata + Ophalen van metadata + + + Add new feed + Voeg nieuw feed toe + + + Edit existing feed + Bewerk bestaande feed + + + Feed name is ok. + Feed naam is ok. + + + Feed name is too short. + Feed naam is te kort. + + + Description is empty. + Omschrijving is leeg. + + + The description is ok. + Omschrijving is ok. + + + The url is ok. + De url is ok. + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + De URL voldoet niet aan het standaard patroon. Start je url met "http://" of "https://" prefix. + + + The url is empty. + De url is leeg. + + + Username is ok or it is not needed. + Gebruikersnaam is ok of het is niet nodig. + + + Username is empty. + Gebruikersnaam is leeg. + + + Password is ok or it is not needed. + Paswoord is ok of het is niet nodig. + + + Password is empty. + Paswoord is leeg. + + + Select icon file for the feed + Selecteer pictogram bestand voor je feed + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + Afbeeldingen (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + Select icon + Selecteer pictogram + + + Cancel + Annuleer + + + Look in: + Label for field with icon file name textbox for selection dialog. + Kijk in: + + + Icon name: + Pictogram naam: + + + Icon type: + Type pictogram: + + + Cannot add feed + Kan geen feed toevoegen + + + Feed was not added due to error. + Door een fout is de feed niet toegevoegd. + + + Cannot edit feed + Kan feed niet bewerken + + + Feed was not edited due to error. + Door een fout is de feed niet bewerkt. + + + All metadata fetched successfully. + Alle metadata is succesvol opgehaald. + + + Feed and icon metadata fetched. + Metadata opgehaald voor feed en pictogram. + + + Result: %1. + Resultaat: %1. + + + Feed or icon metatada not fetched. + Metadata voor feed en pictogram niet opgehaald. + + + Error: %1. + Fout: %1. + + + No metadata fetched. + Geen metadata opgehaald. + + + Icon fetched successfully. + Pictogram met succes opgehaald. + + + Icon metadata fetched. + Metadata pictogram opgehaald. + + + Icon metatada not fetched. + Metadata pictogram niet opgehaald. + + + No icon fetched. + Geen pictogram opgehaald. + + + Feed title + Feed naam + + + Set title for your feed. + Stel titel in voor je feed. + + + Feed description + Feed omschrijving + + + Set description for your feed. + Stel omschrijving voor feed in. + + + Full feed url including scheme + Volledige feed url inclusief schema + + + Set url for your feed. + Stel url in voor je feed. + + + Set username to access the feed. + Stel gebruikersnaam in voor toegang tot feed. + + + Set password to access the feed. + Stel paswoord in voor toegang tot feed. + + + Icon selection + Pictogram selectie + + + Load icon from file... + Laad pictogram uit een bestand... + + + Do not use icon + Gebruik geen pictogram + + + Use default icon + Gebruik standaard pictogram + + + Fetch icon from feed + Pictogram opgehaald van feed + + + No metadata fetched so far. + Nog geen metadata opgehaald. + + + Auto-update using global interval + Automatisch bijwerken met behulp van globale interval + + + Auto-update every + Automatisch bijwerken elke + + + Do not auto-update at all + Niet automatisch bijwerken + + + + FormStandardImportExport + + &Select file + &Selecteer bestand + + + &Check all items + &Controleer alle items + + + &Uncheck all items + &Vinkje bij alle items + + + Operation results + Resultaten + + + No file is selected. + Geen bestand geselecteerd. + + + No operation executed yet. + Nog geen handeling uitgevoerd. + + + Destination file + Doelbestand + + + Source feeds && categories + Source Feeds && categorieën + + + Export feeds + Exporteer feeds + + + Source file + Source bestand + + + Target feeds && categories + Doelgroep feeds && categorieën + + + Import feeds + Importeer feeds + + + OPML 2.0 files (*.opml) + OPML 2.0 bestanden (*.opml) + + + Select file for feeds export + Selecteer bestand voor feed export + + + File is selected. + Bestand is geselecteerd. + + + Select file for feeds import + Selecteer bestand voor feed import + + + Cannot open source file. + Kan source bestand niet openen. + + + Feeds were loaded. + Feeds zijn geladen. + + + Error, file is not well-formed. Select another file. + Fout, het bestand is niet goed gevormd. Selecteer een ander bestand. + + + Error occurred. File is not well-formed. Select another file. + Fout opgetreden. Bestand is niet goed gevormd. Selecteer een ander bestand. + + + Feeds were exported successfully. + Feeds zijn met succes geëxporteerd. + + + Cannot write into destination file. + Kan niet schrijven naar doelbestand. + + + Critical error occurred. + Kritieke fout opgetreden. + FormUpdate @@ -2763,6 +2722,14 @@ Ga naar RRSguard website en download het handmatig. List of attachments. Bijlagen lijst. + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + + MessagesToolBar @@ -2936,26 +2903,41 @@ Ga naar RRSguard website en download het handmatig. LANG_EMAIL elbert.pol@gmail.com - - Load initial feeds - Laad eerste feeds - - - Do you want to load initial set of feeds? - Wil je de eerste set van feeds laden? - LANG_NAME Name of language, e.g. English. Nederlands - - You started %1 for the first time, now you can load initial set of feeds. - Je startte %1 voor de eerste keer, nu kun je de eerste set van de feeds laden + + + ++ %n other feeds. + + + ++ %n andere feed. + + ++ %n andere feeds. + - Welcome to %1 %2. - Welkom bij %1 %2. + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + Welkom bij %1. + +Check voor NIEUW materiaal in deze versie +door te clicken op deze popup melding. + + + Welcome to %1. + Welkom bij %1. + + + Load initial set of feeds + Laad eerste set van de feeds @@ -2976,7 +2958,10 @@ Ga naar RRSguard website en download het handmatig. %n deleted message(s). - %n verwijderde bericht(en).%n verwijderde bericht(en). + + %n verwijderde bericht(en). + %n verwijderde bericht(en). + @@ -2994,6 +2979,137 @@ Ga naar RRSguard website en download het handmatig. Klik en raak nieuwe sneltoets. + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + %1 (categorie)%2%3 + + + +This category does not contain any nested items. + +Deze categorie bevat geen nested items. + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n ongelezen bericht. + %n ongelezen berichten. + + + + + StandardFeed + + Metadata not fetched + Metadata niet opgehaald + + + Metadata was not fetched because: %1. + Metadate niet opgehaald omdat: %1. + + + does not use auto-update + Describes feed auto-update status. + automatisch bijwerken niet gebruiken + + + uses global settings + Describes feed auto-update status. + gebruik algemene instellingen + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + gebruik specifieke instellingen (%n minuut voor volgende automatische update) + gebruik specifieke instellingen (%n minuten voor volgende automatische update) + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + %1 (%2)%3 + +Netwerk status: %6 +Coderen: %4 +Auto-update status: %5 + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n ongelezen bericht. + %n ongelezen berichten. + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + Dit is verplichte service account voor standaard RSS/RDF/ATOM feeds. + + + You started %1 for the first time, now you can load initial set of feeds. + Je start %1 voor de eerste keer, nu kun je de eerste set van de feeds laden. + + + Do you want to load initial set of feeds? + Wil je de eerste set van feeds laden? + + + Error when loading initial feeds + Fout bij het laden van de eerste feeds + + + This is service account for standard RSS/RDF/ATOM feeds. + Dit is verplichte service account voor standaard RSS/RDF/ATOM feeds. + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n ongelezen bericht. + %n ongelezen berichten. + + + + Fetch metadata + Ophalen van metadata + + + Import successfull, but some feeds/categories were not imported due to error. + Importeren succesvol, maar sommige feeds / categorieën waren niet goed geïmporteerd door fouten. + + + Import was completely successfull. + Importeren is helemaal geslaagd. + + + Add new category + Voeg nieuwe categorie toe + + + Add new feed + Voeg nieuw feed toe + + + Export feeds + Exporteer feeds + + + Import feeds + Importeer feeds + + StatusBar @@ -3015,6 +3131,10 @@ Ga naar RRSguard website en download het handmatig. Click the bubble for more information. Klik op luchtbel voor meer informatie. + + anonymous + Anoniem + SystemTrayIcon @@ -3123,6 +3243,21 @@ Ongelezen nieuws: %2 Sluit geopende modaal vensters eerst. + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + Dit is een service account van TT-RSS (TinyTiny RSS) server. + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n ongelezen bericht. + %n ongelezen berichten. + + + WebBrowser @@ -3194,6 +3329,14 @@ Ongelezen nieuws: %2 Stop web page loading. Stop het laden van pagina. + + Cannot add feed + Kan geen feed toevoegen + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + Kan geen feed toevoegen aan %1 omdat de standaard RSS/ATOM account niet aanstaat. Zit die eerst aan. + WebView @@ -3279,7 +3422,7 @@ Ongelezen nieuws: %2 Open the hyperlink in external browser. - Open de hyperlink in externe browser + Open de hyperlink in externe browser. Print @@ -3307,7 +3450,7 @@ Ongelezen nieuws: %2 Save target as... - Doel opslaan als.. + Doel opslaan als... Download content from the hyperlink. @@ -3334,4 +3477,4 @@ Ongelezen nieuws: %2 Zoek "%1" met google... - \ No newline at end of file + diff --git a/localization/rssguard-sv_SE.ts b/localization/rssguard-sv_SE.ts index 26e1cc170..0eff502bb 100644 --- a/localization/rssguard-sv_SE.ts +++ b/localization/rssguard-sv_SE.ts @@ -1,4 +1,6 @@ - + + + AdBlockAddSubscriptionDialog @@ -196,25 +198,6 @@ Notera också att vissa resurser cachelagras av den interna webbläsaren. Om du Inställningsåterställning startades inte. Tillse att utdatamappen är skrivbar. - - Category - - %1 (category)%2%3 - Tooltip for standard feed. - %1 (kategori)%2%3 - - - -This category does not contain any nested items. - -Denna kategori innehåller inga objekt. - - - %n unread message(s). - Tooltip for "unread" column of feed list. - %n oläst meddelande.%n olästa meddelanden. - - DatabaseCleaner @@ -292,9 +275,12 @@ Denna kategori innehåller inga objekt. Click me to add feeds from this website. This website contains %n feed(s). - Klicka för att lägga till flöden från webbsidan. -Denna webbsida innehåller %1 flöde.Klicka för att lägga till flöden från webbsidan. -Denna webbsida innehåller %1 flöden. + + Klicka för att lägga till flöden från webbsidan. +Denna webbsida innehåller %1 flöde. + Klicka för att lägga till flöden från webbsidan. +Denna webbsida innehåller %1 flöden. + @@ -372,7 +358,7 @@ Denna webbsida innehåller %1 flöden. Nedladdning slutförd - File '%1' is downloaded. + File '%1' is downloaded. Click here to open parent directory. Filen '%1' är nedlladdad. Klicka här för att öppna målmappen. @@ -398,11 +384,17 @@ Klicka här för att öppna målmappen. %n minutes remaining - %n minut kvar%n minuter kvar + + %n minut kvar + %n minuter kvar + %n seconds remaining - %n sekund kvar%n sekunder kvar + + %n sekund kvar + %n sekunder kvar + bytes @@ -422,51 +414,10 @@ Klicka här för att öppna målmappen. Downloading %n file(s)... - Laddar ner %n fil...Laddar ner %n filer... - - - - Feed - - does not use auto-update - Describes feed auto-update status. - uppdateras inte automatiskt - - - uses global settings - Describes feed auto-update status. - Globala inställningar - - - uses specific settings (%n minute(s) to next auto-update) - Describes feed auto-update status. - Anpassade inställningar. (%n minut till nästa auto-uppdatering)Anpassade inställningar. (%n minuter till nästa auto-uppdatering) - - - %1 (%2)%3 - -Network status: %6 -Encoding: %4 -Auto-update status: %5 - Tooltip for feed. - %1 (%2)%3 - -Nätverksstatus: %6 -Kodning: %4 -Uppdateringsstatus: %5 - - - %n unread message(s). - Tooltip for "unread" column of feed list. - %n oläst meddelande.%n olästa meddelanden. - - - Metadata not fetched - Metadata hämtades inte - - - Metadata was not fetched because: %1 - Metadata hämtades inte på grund av: %1 + + Laddar ner %n fil... + Laddar ner %n filer... + @@ -475,24 +426,10 @@ Uppdateringsstatus: %5 Toolbar for messages Verktygsfält för meddelanden - - Feed update started - Text display in status bar when feed update is started. - Flödesuppdatering startad - - - Updated feed '%1' - Text display in status bar when particular feed is updated. - Uppdaterade flödet '%1' - Toolbar for feeds Verktygsfält för flöden - - Error when loading initial feeds - Fel vid inläsning av flöden - Cannot cleanup database Kan inte rensa databasen @@ -501,18 +438,6 @@ Uppdateringsstatus: %5 Cannot cleanup database, because another critical action is running. Kan inte rensa databasen, eftersom en annan kritisk åtgärd pågår. - - Cannot update all items - Kan inte uppdatera alla objekt - - - You cannot update all items because another another critical operation is ongoing. - Du kan inte uppdatera alla objekt, eftersom en annan kritisk åtgärd pågår. - - - New messages downloaded - Nya meddelanden nedladdade - FeedsImportExportModel @@ -549,25 +474,46 @@ Uppdateringsstatus: %5 Name of root item of feed list which can be seen in feed add/edit dialog. Root - - Invalid tree data. - Ogiltig träddata. - - - Import successfull, but some feeds/categories were not imported due to error. - Importen slutfördes, men vissa flöden/kategorier importerades inte på grund av något fel. - - - Import was completely successfull. - Importen slutfördes korrekt. - Starting auto-update of some feeds Uppdaterar flöden automatiskt I will auto-update %n feed(s). - Jag uppdaterar %n flöde automatisktJag uppdaterar %n flöden automatiskt + + Jag uppdaterar %n flöde automatiskt + Jag uppdaterar %n flöden automatiskt + + + + Cannot update all items + Kan inte uppdatera alla objekt + + + You cannot update all items because another another critical operation is ongoing. + Du kan inte uppdatera alla objekt, eftersom en annan kritisk åtgärd pågår. + + + Feed update started + Text display in status bar when feed update is started. + Flödesuppdatering startad + + + Updated feed '%1' + Text display in status bar when particular feed is updated. + Uppdaterade flödet '%1' + + + New messages downloaded + Nya meddelanden nedladdade + + + You can't transfer dragged item into different account, this is not supported. + + + + Cannot perform drag & drop operation + @@ -579,14 +525,6 @@ Uppdateringsstatus: %5 FeedsView - - Cannot add standard category - Kan inte lägga till kategori - - - Cannot add standard feed - Kan inte lägga till flöde - Cannot edit item Kan inte redigera objektet @@ -595,50 +533,10 @@ Uppdateringsstatus: %5 Cannot delete item Kan inte bort objektet - - You are about to delete selected feed or category. - Du är på väg att ta bort markerat flöde eller kategori. - - - Deletion of item failed. - Borttagningen misslyckades. - - - Selected item was not deleted due to error. - Objektet togs inte bort, på grund av ett fel. - - - Do you really want to delete selected item? - Vill du verkligen ta bort markerat objekt? - - - Permanently delete messages - Ta bort meddelanden permanent - - - You are about to permanenty delete all messages from your recycle bin. - Du är på väg att permanent ta bort alla meddelanden från papperskorgen. - - - Do you really want to empty your recycle bin? - Vill du verkligen tömma papperskorgen? - Context menu for empty space Kontextmeny för tomt utrymme - - Context menu for recycle bin - Kontextmeny för papperskorgen - - - You cannot add new standard category now because another critical operation is ongoing. - Du kan inte lägga till ny standardkategori nu, eftersom en annan kritisk åtgärd pågår. - - - You cannot add new standard feed now because another critical operation is ongoing. - Du kan inte lägga till nytt standardflöde nu, eftersom en annan kritisk åtgärd pågår. - Selected item cannot be edited because another critical operation is ongoing. Markerat objekt kan inte redigeras, eftersom en annan kritisk åtgärd pågår. @@ -647,14 +545,43 @@ Uppdateringsstatus: %5 Selected item cannot be deleted because another critical operation is ongoing. Markerat objekt kan inte tas bort, eftersom en annan kritisk åtgärd pågår. - - Delete feed/category - Ta bort flöde/kategori - Context menu for categories Kontextmeny för kategorier + + Selected item cannot be edited, this is not (yet?) supported. + + + + Deleting "%1" + + + + You are about to completely delete item "%1". + + + + Are you sure? + + + + Cannot delete "%1" + + + + This item cannot be deleted because something critically failed. Submit bug report. + + + + This item cannot be deleted, because it does not support it +or this functionality is not implemented yet. + + + + Context menu for other items + + FormAbout @@ -702,10 +629,6 @@ Uppdateringsstatus: %5 <b>%8</b><br><b>Version:</b> %1 (build on %2 with CMake %3)<br><b>Revision:</b> %4<br><b>Build date:</b> %5<br><b>Qt:</b> %6 (compiled against %7)<br> <b>%8</b><br><b>Version:</b> %1 (byggd på %2 med CMake %3)<br><b>Revision:</b> %4<br><b>Byggdatum:</b> %5<br><b>Qt:</b> %6 (kompilerad mot %7)<br> - - <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~email</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> - <body>%5 är en (mycket) lätt flödesläsare.<br><br>Mjukvaran distribueras under villkoren för GNU General Public Licens, version 3.<br><br>Kontakt:<ul><li><a href="mailto://%1">%1</a> ~e-post</li><li><a href="%2">%2</a> ~webbsida</li></ul>Du kan hämmta källkoden för %5 från webbsidan.<br><br><br>Copyright (C) 2011-%3 %4</body> - About %1 About RSS Guard dialog title. @@ -735,6 +658,49 @@ Uppdateringsstatus: %5 Resources Resurser + + <body>%5 is a (very) tiny feed reader.<br><br>This software is distributed under the terms of GNU General Public License, version 3.<br><br>Contacts:<ul><li><a href="mailto://%1">%1</a> ~e-mail</li><li><a href="%2">%2</a> ~website</li></ul>You can obtain source code for %5 from its website.<br><br><br>Copyright (C) 2011-%3 %4</body> + + + + + FormAddAccount + + Add new account + + + + Details + + + + Name + Namn + + + Version + Version + + + Author + + + + Description + Beskrivning + + + Cannot add account + + + + Some critical error occurred, report this to developers. + + + + This account can be added only once. + + FormBackupDatabaseSettings @@ -811,134 +777,6 @@ Uppdateringsstatus: %5 Målmappen är godkänd. - - FormCategoryDetails - - Parent category - Överordnad kategori - - - Select parent item for your category. - Välj överordnad mapp för kategorin. - - - Title - Namn - - - Description - Beskrivning - - - Icon - Ikon - - - Select icon for your category. - Välj ikon för kategorin. - - - Add new category - Lägg till ny kategori - - - Edit existing category - Redigera befintlig kategori - - - Cannot add category - Kan inte lägga till kategori - - - Category was not added due to error. - Kategorin lades inte till, på grund av något fel. - - - Cannot edit category - Kan inte redigera kategorin - - - Category was not edited due to error. - Kategorin kan inte redigeras, på grund av något fel. - - - Category name is ok. - Kategorinamnet är ok. - - - Category name is too short. - Kategorinamnet är för kort. - - - Description is empty. - Beskrivning saknas. - - - Select icon file for the category - Välj ikonfil för kategorin - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - Bilder (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Välj ikon - - - Cancel - Avbryt - - - Look in: - Label to describe the folder for icon file selection dialog. - Sök i: - - - Icon name: - Ikonnamn: - - - Icon type: - Ikontyp: - - - Category title - Kategorinamn - - - Set title for your category. - Ange namnet på din kategori. - - - Category description - Kategoribeskrivning - - - Set description for your category. - Beskriv din kategori. - - - Icon selection - Ikonval - - - Load icon from file... - Hämta ikon från fil... - - - Do not use icon - Använd ingen ikon - - - Use default icon - Använd standardikon - - - The description is ok. - Beskrivningen är ok. - - FormDatabaseCleanup @@ -951,7 +789,10 @@ Uppdateringsstatus: %5 day(s) - dagdagar + + dag + dagar + Shrink database file @@ -1006,389 +847,6 @@ Uppdateringsstatus: %5 Ta bort alla stjärnmärkta meddelanden (inklusive dem i papperskorgen) - - FormFeedDetails - - Parent category - Överordnad kategori - - - Select parent item for your feed. - Välj överordnad mapp för flödet. - - - Type - Typ - - - Select type of the standard feed. - Välj flödestyp. - - - Encoding - Kodning - - - Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. - Välj flödeskodning. Välj "UTF-8" om du är osäker på kodningen. - - - Auto-update - Auto-uppdatering - - - Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. - Välj uppdateringsstrategi för flödet. Global auto-uppdatering, innebär att flödet kommer att uppdateras med tidsintervall angivna i programinställningarna. - - - minutes - minuter - - - Title - Namn - - - Description - Beskrivning - - - URL - URL - - - Fetch it now - Hämta nu - - - Icon - Ikon - - - Select icon for your feed. - Välj ikon för flödet. - - - Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - Vissa flöden kräver autentisering, inklusive Gmail-flöden. BASIC, NTLM-2 och DIGEST-MD5 autentisering stöds. - - - Requires authentication - Kräver autentisering - - - Username - Användarnamn - - - Password - Lösenord - - - Fetch metadata - Hämta metadata - - - Add new feed - Lägg till nytt flöde - - - Edit existing feed - Redigera befintligt flöde - - - Feed name is ok. - Flödesnamnet är ok. - - - Feed name is too short. - Flödesnamnet är för kort. - - - Description is empty. - Beskrivning saknas. - - - The url is ok. - Webbadressen är ok. - - - The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. - Webbadressen liknar inte standardmönstret. Börjar din URL med prefixet "http://" eller "https://"?. - - - The url is empty. - URL saknas. - - - Username is ok or it is not needed. - Användarnamnet är ok, eller behövs inte. - - - Username is empty. - Användarnamn saknas. - - - Password is ok or it is not needed. - Lösenordet är ok, eller behövs inte. - - - Password is empty. - Lösenord saknas. - - - Select icon file for the feed - Välj ikonfil för flödet - - - Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - bilder (*.bmp *.jpg *.jpeg *.png *.svg *.tga) - - - Select icon - Välj ikon - - - Cancel - Avbryt - - - Look in: - Label for field with icon file name textbox for selection dialog. - Sök i: - - - Icon name: - Ikonnamn: - - - Icon type: - Ikontyp: - - - Cannot add feed - Kan inte lägga till flöde - - - Feed was not added due to error. - Flödet lades inte till, på grund av något fel. - - - Cannot edit feed - Kan inte redigera flödet - - - All metadata fetched successfully. - All metadata hämtades korrekt. - - - Feed and icon metadata fetched. - Flödes- och ikonmetadata hämtad. - - - Result: %1. - Resultat: %1. - - - Feed or icon metatada not fetched. - Flödes- eller ikonmetadata hämtades inte. - - - Error: %1. - Fel: %1. - - - No metadata fetched. - Ingen metadata hämtades. - - - Feed title - Flödesnamn - - - Set title for your feed. - Ange flödets namn. - - - Feed description - Flödesbeskrivning - - - Set description for your feed. - Beskriv flödet. - - - Full feed url including scheme - Flödets fullständiga webbadress (URL) - - - Set url for your feed. - Ange flödets URL. - - - Set username to access the feed. - Ange användarnamn för att få åtkomst till flödet. - - - Set password to access the feed. - Ange lösenord för att få åtkomst till flödet. - - - Icon selection - Ikonval - - - Load icon from file... - Hämta ikon från fil... - - - Do not use icon - Använd ingen ikon - - - Use default icon - Använd standardikon - - - No metadata fetched so far. - Ingen metadata hämtad. - - - Auto-update using global interval - Global auto-uppdatering - - - Auto-update every - Auto-uppdatera varje - - - Do not auto-update at all - Ingen auto-uppdatering - - - The description is ok. - Beskrivningen är ok. - - - Feed was not edited due to error. - Flödet redigerades inte, på grund av något fel. - - - Icon fetched successfully. - Ikon hämtades. - - - Icon metadata fetched. - Ikonmetadata hämtad. - - - Icon metatada not fetched. - Ikonmetadata hämtades inte. - - - No icon fetched. - Ikon hämtades inte. - - - Fetch icon from feed - Hämta ikon från flödet - - - - FormImportExport - - &Select file - &Välj fil - - - Operation results - Åtgärdsresultat - - - No file is selected. - Ingen fil har valts. - - - No operation executed yet. - Ingen åtgärd slutförd än. - - - Export feeds - Exportera flöden - - - Destination file - Målfil - - - Source feeds && categories - Källflöden && -kategorier - - - Source file - Källfil - - - Target feeds && categories - Målflöden && -kategorier - - - Import feeds - Importera flöden - - - OPML 2.0 files (*.opml) - OPML 2.0-filer (*.opml) - - - Select file for feeds export - Välj fil för flödesexport - - - File is selected. - Fil är vald. - - - Select file for feeds import - Välj fil för flödesimport - - - Cannot open source file. - Kan inte öppna källfil. - - - Feeds were loaded. - Flöden lästes in. - - - Error, file is not well-formed. Select another file. - Fel! Filen är inte rätt formaterad. Välj en annan fil. - - - Error occurred. File is not well-formed. Select another file. - Ett fel uppstod. Filen är felformaterad. Välj en annan fil. - - - Feeds were exported successfully. - Flöden exporterades korrekt. - - - Cannot write into destination file. - Kan inte skriva till målfilen. - - - Critical error occurred. - Ett allvarligt fel uppstod. - - - &Check all items - &Markera alla - - - &Uncheck all items - &Avmarkera alla - - FormMain @@ -1463,22 +921,6 @@ Uppdateringsstatus: %5 No actions are available right now. Inga åtgärder tillgängliga just nu. - - Fee&ds && categories - &Flöden && kategorier - - - Mark all messages (without message filters) from selected feeds as read. - Markera alla meddelanden från valda flöden, som lästa. - - - Mark all messages (without message filters) from selected feeds as unread. - Markera alla meddelanden från valda flöden, som olästa. - - - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. - Visa alla meddelanden från markerade flöden/kategorier i en ny flik, som "tidningsvy". Notera att meddelandena inte automatiskt markeras som lästa. - Hides main window if it is visible and shows it if it is hidden. Dölj programfönstret om det är synligt, och visa det om det är dolt. @@ -1503,34 +945,6 @@ Uppdateringsstatus: %5 &Delete selected messages &Ta bort markerade meddelanden - - Deletes all messages from selected feeds. - Ta bort alla meddelanden från markerade flöden. - - - Marks all messages in all feeds read. This does not take message filters into account. - Markera alla meddelanden i samtliga flöden som lästa. Detta åsidosätter eventuella meddelandefilter. - - - Deletes all messages from all feeds. - Ta bort alla meddelanden från samtliga flöden. - - - Update &all feeds - Uppdatera &alla flöden - - - Update &selected feeds - Uppdatera &markerade flöden - - - &Edit selected feed/category - &Redigera markerat flöde/kategori - - - &Delete selected feed/category - &Ta bort markerat flöde/kategori - Settings Inställningar @@ -1539,10 +953,6 @@ Uppdateringsstatus: %5 Hides or displays the main menu. Dölj/Visa huvudmenyn. - - Add &new feed/category - Lägg till &nytt flöde/kategori - &Close all tabs except current one &Stäng alla flikar utom den aktuella @@ -1559,18 +969,6 @@ Uppdateringsstatus: %5 Mark &selected messages as &unread Märk markerade &meddelanden som &olästa - - &Mark selected feeds as read - &Märk markerade meddelanden som lästa - - - &Mark selected feeds as unread - &Märk markerade meddelanden som olästa - - - &Clean selected feeds - &Rensa markerade flöden - Open selected source articles in &external browser Öppna markerade källartiklar i &extern webbläsare @@ -1583,26 +981,6 @@ Uppdateringsstatus: %5 Open selected source articles in &internal browser Öppna markerade källartiklar i &intern webbläsare - - &Mark all feeds as &read - &Markera samtliga flöden som &lästa - - - View selected feeds in &newspaper mode - Visa markerade flöden som &tidningsvy - - - &Clean all feeds - &Rensa alla flöden - - - Select &next feed/category - Gå till &nästa flöde/kategori - - - Select &previous feed/category - Gå till &föregående flöde/kategori - Select &next message Gå till &nästa meddelande @@ -1655,22 +1033,6 @@ Uppdateringsstatus: %5 Cannot open external browser. Navigate to application website manually. Kan inte öppna extern webbläsare. Navigera manuellt till programmets webbsida. - - New &feed - Nytt &flöde - - - Add new feed. - Lägg till nytt flöde. - - - New &category - Ny &kategori - - - Add new category. - Lägg till ny kategori. - &Toolbars &Verktygsfält @@ -1683,30 +1045,10 @@ Uppdateringsstatus: %5 &Feed/message list headers &Kolumnrubriker - - &Import feeds - &Importera flöden - - - Imports feeds you want from selected file. - Importera flöden från fil. - - - &Export feeds - &Exportera flöden - - - Exports feeds you want to selected file. - Exportera flöden till fil. - Close all tabs except current one. Stäng alla flikar utom aktuell. - - &Recycle bin - &Papperskorgen - Report a &bug (GitHub)... Rapportera ett &fel (GitHub)... @@ -1723,18 +1065,6 @@ Uppdateringsstatus: %5 Display &wiki Visa &wiki - - &Empty recycle bin - &Töm papperskorgen - - - &Restore all messages - &Återställ alla meddelanden - - - Restore &selected messages - Återställ &markerade meddelanden - &Restart &Starta om @@ -1764,16 +1094,132 @@ Uppdateringsstatus: %5 &Rensa databasen - Show only unread feeds/categories - Visa endast olästa flöden/kategorier + Add &new item + - &Fetch feed metadata - &Hämta flödesmetadata + Update &all items + - &Expand/collapse selected feed/category - &Expandera/Komprimera markerat flöde/kategori + Update &selected items + + + + &Edit selected item + + + + &Delete selected item + + + + &Mark selected items as read + + + + Mark all messages (without message filters) from selected items as read. + + + + &Mark selected items as unread + + + + Mark all messages (without message filters) from selected items as unread. + + + + &Clean selected items + + + + Deletes all messages from selected items. + + + + &Mark all items as &read + + + + Marks all messages in all items read. This does not take message filters into account. + + + + View selected items in &newspaper mode + + + + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + + &Clean all items + + + + Deletes all messages from all items. + + + + Select &next item + + + + Select &previous item + + + + Show only unread items + + + + &Expand/collapse selected item + + + + &Add new service account + + + + &Restore selected messages + + + + No possible actions + + + + Feeds && categories && accounts + + + + &Recycle bin(s) + + + + &Restore all recycle bins + + + + &Empty all recycle bins + + + + Select next &unread message + + + + No recycle bin + + + + Restore recycle bin + + + + Empty recycle bin + @@ -1924,10 +1370,6 @@ Uppdateringsstatus: %5 Author Översättare - - Email - E-post - Socks5 Socks5 @@ -2505,6 +1947,525 @@ File filter for external e-mail selection dialog. Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) Tjusiga && moderna popup-aviseringar (Använder systemets integrerade aviseringar via D-Bus, om tillgängligt.) + + E-mail + + + + Enable notifications + + + + + FormStandardCategoryDetails + + Parent category + Överordnad kategori + + + Select parent item for your category. + Välj överordnad mapp för kategorin. + + + Title + + + + Description + Beskrivning + + + Icon + Ikon + + + Select icon for your category. + Välj ikon för kategorin. + + + Add new category + Lägg till ny kategori + + + Edit existing category + Redigera befintlig kategori + + + Cannot add category + Kan inte lägga till kategori + + + Category was not added due to error. + Kategorin lades inte till, på grund av något fel. + + + Cannot edit category + Kan inte redigera kategorin + + + Category was not edited due to error. + Kategorin kan inte redigeras, på grund av något fel. + + + Category name is ok. + Kategorinamnet är ok. + + + Category name is too short. + Kategorinamnet är för kort. + + + Description is empty. + Beskrivning saknas. + + + The description is ok. + Beskrivningen är ok. + + + Select icon file for the category + Välj ikonfil för kategorin + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + + Select icon + Välj ikon + + + Cancel + Avbryt + + + Look in: + Label to describe the folder for icon file selection dialog. + Sök i: + + + Icon name: + Ikonnamn: + + + Icon type: + Ikontyp: + + + Category title + Kategorinamn + + + Set title for your category. + Ange namnet på din kategori. + + + Category description + Kategoribeskrivning + + + Set description for your category. + Beskriv din kategori. + + + Icon selection + Ikonval + + + Load icon from file... + Hämta ikon från fil... + + + Do not use icon + Använd ingen ikon + + + Use default icon + Använd standardikon + + + + FormStandardFeedDetails + + Parent category + Överordnad kategori + + + Select parent item for your feed. + Välj överordnad mapp för flödet. + + + Type + Typ + + + Select type of the standard feed. + Välj flödestyp. + + + Encoding + Kodning + + + Select encoding of the standard feed. If you are unsure about the encoding, then select "UTF-8" encoding. + Välj flödeskodning. Välj "UTF-8" om du är osäker på kodningen. + + + Auto-update + Auto-uppdatering + + + Select the auto-update strategy for this feed. Default auto-update strategy means that the feed will be update in time intervals set in application settings. + Välj uppdateringsstrategi för flödet. Global auto-uppdatering, innebär att flödet kommer att uppdateras med tidsintervall angivna i programinställningarna. + + + minutes + minuter + + + Title + + + + Description + Beskrivning + + + URL + URL + + + Fetch it now + Hämta nu + + + Icon + Ikon + + + Select icon for your feed. + Välj ikon för flödet. + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + Vissa flöden kräver autentisering, inklusive Gmail-flöden. BASIC, NTLM-2 och DIGEST-MD5 autentisering stöds. + + + Requires authentication + Kräver autentisering + + + Username + Användarnamn + + + Password + Lösenord + + + Fetch metadata + Hämta metadata + + + Add new feed + Lägg till nytt flöde + + + Edit existing feed + Redigera befintligt flöde + + + Feed name is ok. + Flödesnamnet är ok. + + + Feed name is too short. + Flödesnamnet är för kort. + + + Description is empty. + Beskrivning saknas. + + + The description is ok. + Beskrivningen är ok. + + + The url is ok. + Webbadressen är ok. + + + The url does not meet standard pattern. Does your url start with "http://" or "https://" prefix. + Webbadressen liknar inte standardmönstret. Börjar din URL med prefixet "http://" eller "https://"?. + + + The url is empty. + URL saknas. + + + Username is ok or it is not needed. + Användarnamnet är ok, eller behövs inte. + + + Username is empty. + Användarnamn saknas. + + + Password is ok or it is not needed. + Lösenordet är ok, eller behövs inte. + + + Password is empty. + Lösenord saknas. + + + Select icon file for the feed + Välj ikonfil för flödet + + + Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga) + + + + Select icon + Välj ikon + + + Cancel + Avbryt + + + Look in: + Label for field with icon file name textbox for selection dialog. + Sök i: + + + Icon name: + Ikonnamn: + + + Icon type: + Ikontyp: + + + Cannot add feed + Kan inte lägga till flöde + + + Feed was not added due to error. + Flödet lades inte till, på grund av något fel. + + + Cannot edit feed + Kan inte redigera flödet + + + Feed was not edited due to error. + Flödet redigerades inte, på grund av något fel. + + + All metadata fetched successfully. + All metadata hämtades korrekt. + + + Feed and icon metadata fetched. + Flödes- och ikonmetadata hämtad. + + + Result: %1. + Resultat: %1. + + + Feed or icon metatada not fetched. + Flödes- eller ikonmetadata hämtades inte. + + + Error: %1. + Fel: %1. + + + No metadata fetched. + Ingen metadata hämtades. + + + Icon fetched successfully. + Ikon hämtades. + + + Icon metadata fetched. + Ikonmetadata hämtad. + + + Icon metatada not fetched. + Ikonmetadata hämtades inte. + + + No icon fetched. + Ikon hämtades inte. + + + Feed title + Flödesnamn + + + Set title for your feed. + Ange flödets namn. + + + Feed description + Flödesbeskrivning + + + Set description for your feed. + Beskriv flödet. + + + Full feed url including scheme + Flödets fullständiga webbadress (URL) + + + Set url for your feed. + Ange flödets URL. + + + Set username to access the feed. + Ange användarnamn för att få åtkomst till flödet. + + + Set password to access the feed. + Ange lösenord för att få åtkomst till flödet. + + + Icon selection + Ikonval + + + Load icon from file... + Hämta ikon från fil... + + + Do not use icon + Använd ingen ikon + + + Use default icon + Använd standardikon + + + Fetch icon from feed + Hämta ikon från flödet + + + No metadata fetched so far. + Ingen metadata hämtad. + + + Auto-update using global interval + Global auto-uppdatering + + + Auto-update every + Auto-uppdatera varje + + + Do not auto-update at all + Ingen auto-uppdatering + + + + FormStandardImportExport + + &Select file + &Välj fil + + + &Check all items + &Markera alla + + + &Uncheck all items + &Avmarkera alla + + + Operation results + Åtgärdsresultat + + + No file is selected. + Ingen fil har valts. + + + No operation executed yet. + Ingen åtgärd slutförd än. + + + Destination file + Målfil + + + Source feeds && categories + Källflöden && -kategorier + + + Export feeds + Exportera flöden + + + Source file + Källfil + + + Target feeds && categories + Målflöden && -kategorier + + + Import feeds + Importera flöden + + + OPML 2.0 files (*.opml) + OPML 2.0-filer (*.opml) + + + Select file for feeds export + Välj fil för flödesexport + + + File is selected. + Fil är vald. + + + Select file for feeds import + Välj fil för flödesimport + + + Cannot open source file. + Kan inte öppna källfil. + + + Feeds were loaded. + Flöden lästes in. + + + Error, file is not well-formed. Select another file. + Fel! Filen är inte rätt formaterad. Välj en annan fil. + + + Error occurred. File is not well-formed. Select another file. + Ett fel uppstod. Filen är felformaterad. Välj en annan fil. + + + Feeds were exported successfully. + Flöden exporterades korrekt. + + + Cannot write into destination file. + Kan inte skriva till målfilen. + + + Critical error occurred. + Ett allvarligt fel uppstod. + FormUpdate @@ -2753,6 +2714,14 @@ Gå till programmets hemsida för att hämta den manuellt. List of attachments. Lista över bilagor. + + Loading of messages from item '%s' failed. + + + + Loading of messages failed, maybe messages could not be downloaded. + + MessagesToolBar @@ -2926,26 +2895,34 @@ Gå till programmets hemsida för att hämta den manuellt. LANG_EMAIL eson57@gmail.com - - Load initial feeds - Läs in flöden - - - Do you want to load initial set of feeds? - Vill du läsa in flödesuppsättningen? - LANG_NAME Name of language, e.g. English. Swedish - - You started %1 for the first time, now you can load initial set of feeds. - Du har startat %1 för första gången. Nu kan du läsa in inledande flödesuppsättning. + + + ++ %n other feeds. + + + + - Welcome to %1 %2. - Välkommen till %1 %2. + Welcome to %1. + +Please, check NEW stuff included in this +version by clicking this popup notification. + + + + Welcome to %1. + Välkommen till %1 %2. {1.?} + + + Load initial set of feeds + @@ -2966,7 +2943,10 @@ Gå till programmets hemsida för att hämta den manuellt. %n deleted message(s). - %n borttaget meddelande.%n borttagna meddelanden. + + %n borttaget meddelande. + %n borttagna meddelanden. + @@ -2984,6 +2964,137 @@ Gå till programmets hemsida för att hämta den manuellt. Klicka och välj ny snabbtangent. + + StandardCategory + + %1 (category)%2%3 + Tooltip for standard feed. + %1 (kategori)%2%3 + + + +This category does not contain any nested items. + +Denna kategori innehåller inga objekt. + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n oläst meddelande. + %n olästa meddelanden. + + + + + StandardFeed + + Metadata not fetched + Metadata hämtades inte + + + Metadata was not fetched because: %1. + Metadata hämtades inte på grund av: %1. + + + does not use auto-update + Describes feed auto-update status. + uppdateras inte automatiskt + + + uses global settings + Describes feed auto-update status. + Globala inställningar + + + uses specific settings (%n minute(s) to next auto-update) + Describes feed auto-update status. + + Anpassade inställningar. (%n minut till nästa auto-uppdatering) + Anpassade inställningar. (%n minuter till nästa auto-uppdatering) + + + + %1 (%2)%3 + +Network status: %6 +Encoding: %4 +Auto-update status: %5 + Tooltip for feed. + %1 (%2)%3 + +Nätverksstatus: %6 +Kodning: %4 +Uppdateringsstatus: %5 + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n oläst meddelande. + %n olästa meddelanden. + + + + + StandardServiceRoot + + This is obligatory service account for standard RSS/RDF/ATOM feeds. + + + + You started %1 for the first time, now you can load initial set of feeds. + Du har startat %1 för första gången. Nu kan du läsa in inledande flödesuppsättning. + + + Do you want to load initial set of feeds? + Vill du läsa in flödesuppsättningen? + + + Error when loading initial feeds + Fel vid inläsning av flöden + + + This is service account for standard RSS/RDF/ATOM feeds. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n oläst meddelande. + %n olästa meddelanden. + + + + Fetch metadata + Hämta metadata + + + Import successfull, but some feeds/categories were not imported due to error. + Importen slutfördes, men vissa flöden/kategorier importerades inte på grund av något fel. + + + Import was completely successfull. + Importen slutfördes korrekt. + + + Add new category + Lägg till ny kategori + + + Add new feed + Lägg till nytt flöde + + + Export feeds + Exportera flöden + + + Import feeds + Importera flöden + + StatusBar @@ -3005,6 +3116,10 @@ Gå till programmets hemsida för att hämta den manuellt. Click the bubble for more information. Klicka på detta meddelande för mer information. + + anonymous + + SystemTrayIcon @@ -3112,6 +3227,21 @@ Olästa nyheter: %2 Stäng öppna dialogrutor först. + + TtRssServiceRoot + + This is service account TT-RSS (TinyTiny RSS) server. + + + + %n unread message(s). + Tooltip for "unread" column of feed list. + + %n oläst meddelande. + %n olästa meddelanden. + + + WebBrowser @@ -3183,6 +3313,14 @@ Olästa nyheter: %2 Stop web page loading. Stoppa inläsning av webbsidan. + + Cannot add feed + Kan inte lägga till flöde + + + You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first. + + WebView @@ -3323,4 +3461,4 @@ Olästa nyheter: %2 Sök "%1" via Google... - \ No newline at end of file + diff --git a/resources/graphics/icons/mini-kfaenza/application-ttrss.png b/resources/graphics/icons/mini-kfaenza/application-ttrss.png new file mode 100755 index 000000000..15c2ac4d4 Binary files /dev/null and b/resources/graphics/icons/mini-kfaenza/application-ttrss.png differ diff --git a/resources/graphics/icons/mini-kfaenza/item-sync.png b/resources/graphics/icons/mini-kfaenza/item-sync.png new file mode 100755 index 000000000..4d025c236 Binary files /dev/null and b/resources/graphics/icons/mini-kfaenza/item-sync.png differ diff --git a/resources/graphics/icons/numix/application-ttrss.png b/resources/graphics/icons/numix/application-ttrss.png new file mode 100755 index 000000000..15c2ac4d4 Binary files /dev/null and b/resources/graphics/icons/numix/application-ttrss.png differ diff --git a/resources/misc/db_init_mysql.sql b/resources/misc/db_init_mysql.sql index fda045d7c..68e397b7c 100644 --- a/resources/misc/db_init_mysql.sql +++ b/resources/misc/db_init_mysql.sql @@ -12,17 +12,40 @@ CREATE TABLE IF NOT EXISTS Information ( inf_value TEXT NOT NULL ); -- ! -INSERT INTO Information VALUES (1, 'schema_version', '3'); +INSERT INTO Information VALUES (1, 'schema_version', '4'); -- ! +CREATE TABLE IF NOT EXISTS Accounts ( + id INTEGER PRIMARY KEY, + type TEXT NOT NULL +); +-- ! +INSERT INTO Accounts (type) VALUES ('std-rss'); +-- ! +CREATE TABLE IF NOT EXISTS TtRssAccounts ( + id INTEGER, + username TEXT NOT NULL, + password TEXT, + auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0, + auth_username TEXT, + auth_password TEXT, + url TEXT NOT NULL, + force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0, + + FOREIGN KEY (id) REFERENCES Accounts (id) +); DROP TABLE IF EXISTS Categories; -- ! CREATE TABLE IF NOT EXISTS Categories ( id INTEGER AUTO_INCREMENT PRIMARY KEY, parent_id INTEGER NOT NULL, - title VARCHAR(100) NOT NULL UNIQUE CHECK (title != ''), + title VARCHAR(100) NOT NULL CHECK (title != ''), description TEXT, - date_created BIGINT NOT NULL CHECK (date_created != 0), - icon BLOB + date_created BIGINT, + icon BLOB, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) ); -- ! DROP TABLE IF EXISTS Feeds; @@ -31,45 +54,40 @@ CREATE TABLE IF NOT EXISTS Feeds ( id INTEGER AUTO_INCREMENT PRIMARY KEY, title TEXT NOT NULL CHECK (title != ''), description TEXT, - date_created BIGINT NOT NULL CHECK (date_created != 0), + date_created BIGINT, icon BLOB, category INTEGER NOT NULL CHECK (category >= -1), - encoding TEXT NOT NULL CHECK (encoding != ''), - url VARCHAR(100) NOT NULL UNIQUE CHECK (url != ''), + encoding TEXT, + url VARCHAR(100), protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1), username TEXT, password TEXT, update_type INTEGER(1) NOT NULL CHECK (update_type >= 0), - update_interval INTEGER NOT NULL DEFAULT 15 CHECK (update_interval >= 5), - type INTEGER NOT NULL CHECK (type >= 0) -); --- ! -DROP TABLE IF EXISTS FeedsData; --- ! -CREATE TABLE IF NOT EXISTS FeedsData ( - feed_id INTEGER NOT NULL, - feed_key VARCHAR(100) NOT NULL, - feed_value TEXT, + update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15, + type INTEGER, + account_id INTEGER NOT NULL, + custom_id TEXT, - PRIMARY KEY (feed_id, feed_key), - FOREIGN KEY (feed_id) REFERENCES Feeds (id) + FOREIGN KEY (account_id) REFERENCES Accounts (id) ); -- ! DROP TABLE IF EXISTS Messages; -- ! CREATE TABLE IF NOT EXISTS Messages ( id INTEGER AUTO_INCREMENT PRIMARY KEY, - is_read INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_read >= 0 AND is_read <= 1), - is_deleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_deleted >= 0 AND is_deleted <= 1), - is_important INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_important >= 0 AND is_important <= 1), - feed INTEGER NOT NULL, + is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0, + is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0, + is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0 , + feed TEXT NOT NULL, title TEXT NOT NULL CHECK (title != ''), - url TEXT NOT NULL, - author TEXT NOT NULL, + url TEXT, + author TEXT, date_created BIGINT NOT NULL CHECK (date_created != 0), contents TEXT, - is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1), + is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0 , enclosures TEXT, + account_id INTEGER NOT NULL, + custom_id TEXT, - FOREIGN KEY (feed) REFERENCES Feeds (id) + FOREIGN KEY (account_id) REFERENCES Accounts (id) ); \ No newline at end of file diff --git a/resources/misc/db_init_sqlite.sql b/resources/misc/db_init_sqlite.sql index d02ba1a99..d4c20de99 100644 --- a/resources/misc/db_init_sqlite.sql +++ b/resources/misc/db_init_sqlite.sql @@ -6,17 +6,41 @@ CREATE TABLE IF NOT EXISTS Information ( inf_value TEXT NOT NULL ); -- ! -INSERT INTO Information VALUES (1, 'schema_version', '3'); +INSERT INTO Information VALUES (1, 'schema_version', '4'); +-- ! +CREATE TABLE IF NOT EXISTS Accounts ( + id INTEGER PRIMARY KEY, + type TEXT NOT NULL +); +-- ! +INSERT INTO Accounts (type) VALUES ('std-rss'); +-- ! +CREATE TABLE IF NOT EXISTS TtRssAccounts ( + id INTEGER, + username TEXT NOT NULL, + password TEXT, + auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0, + auth_username TEXT, + auth_password TEXT, + url TEXT NOT NULL, + force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0, + + FOREIGN KEY (id) REFERENCES Accounts (id) +); -- ! DROP TABLE IF EXISTS Categories; -- ! CREATE TABLE IF NOT EXISTS Categories ( id INTEGER PRIMARY KEY, parent_id INTEGER NOT NULL, - title TEXT NOT NULL UNIQUE CHECK (title != ''), + title TEXT NOT NULL CHECK (title != ''), description TEXT, - date_created INTEGER NOT NULL CHECK (date_created != 0), - icon BLOB + date_created INTEGER, + icon BLOB, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) ); -- ! DROP TABLE IF EXISTS Feeds; @@ -25,45 +49,40 @@ CREATE TABLE IF NOT EXISTS Feeds ( id INTEGER PRIMARY KEY, title TEXT NOT NULL CHECK (title != ''), description TEXT, - date_created INTEGER NOT NULL CHECK (date_created != 0), + date_created INTEGER, icon BLOB, category INTEGER NOT NULL CHECK (category >= -1), - encoding TEXT NOT NULL CHECK (encoding != ''), - url TEXT NOT NULL UNIQUE CHECK (url != ''), + encoding TEXT, + url TEXT, protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1), username TEXT, password TEXT, update_type INTEGER(1) NOT NULL CHECK (update_type >= 0), update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15, - type INTEGER NOT NULL CHECK (type >= 0) -); --- ! -DROP TABLE IF EXISTS FeedsData; --- ! -CREATE TABLE IF NOT EXISTS FeedsData ( - feed_id INTEGER NOT NULL, - feed_key TEXT NOT NULL, - feed_value TEXT, + type INTEGER, + account_id INTEGER NOT NULL, + custom_id TEXT, - PRIMARY KEY (feed_id, feed_key), - FOREIGN KEY (feed_id) REFERENCES Feeds (id) + FOREIGN KEY (account_id) REFERENCES Accounts (id) ); -- ! DROP TABLE IF EXISTS Messages; -- ! CREATE TABLE IF NOT EXISTS Messages ( id INTEGER PRIMARY KEY, - is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT (0), - is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT (0), - is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT (0), - feed INTEGER NOT NULL, + is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0, + is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0, + is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0, + feed TEXT NOT NULL, title TEXT NOT NULL CHECK (title != ''), - url TEXT NOT NULL, - author TEXT NOT NULL, + url TEXT, + author TEXT, date_created INTEGER NOT NULL CHECK (date_created != 0), contents TEXT, - is_pdeleted INTEGER(1) NOT NULL DEFAULT 0 CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1), + is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0, enclosures TEXT, + account_id INTEGER NOT NULL, + custom_id TEXT, - FOREIGN KEY (feed) REFERENCES Feeds (id) + FOREIGN KEY (account_id) REFERENCES Accounts (id) ); \ No newline at end of file diff --git a/resources/misc/db_update_mysql_3_4.sql b/resources/misc/db_update_mysql_3_4.sql new file mode 100644 index 000000000..f2c27f644 --- /dev/null +++ b/resources/misc/db_update_mysql_3_4.sql @@ -0,0 +1,68 @@ +CREATE TABLE Accounts ( + id INTEGER PRIMARY KEY, + type TEXT NOT NULL +); +-- ! +INSERT INTO Accounts (type) VALUES ('std-rss'); +-- ! +DROP TABLE IF EXISTS FeedsData; +-- ! +CREATE TABLE TtRssAccounts ( + id INTEGER, + username TEXT NOT NULL, + password TEXT, + auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0, + auth_username TEXT, + auth_password TEXT, + url TEXT NOT NULL, + force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0, + + FOREIGN KEY (id) REFERENCES Accounts (id) +); +-- ! +ALTER TABLE Messages +ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1; +-- ! +ALTER TABLE Messages +ADD COLUMN custom_id TEXT; +-- ! +ALTER TABLE Messages +DROP FOREIGN KEY feed; +-- ! +ALTER TABLE Messages +MODIFY feed TEXT NOT NULL; +-- ! +ALTER TABLE Messages +MODIFY author TEXT; +-- ! +ALTER TABLE Messages +MODIFY url TEXT; +-- ! +ALTER TABLE Feeds +ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1; +-- ! +ALTER TABLE Feeds +ADD COLUMN custom_id TEXT; +-- ! +ALTER TABLE Feeds +MODIFY date_created BIGINT; +-- ! +ALTER TABLE Feeds +MODIFY encoding TEXT; +-- ! +ALTER TABLE Feeds +MODIFY url VARCHAR(100); +-- ! +ALTER TABLE Feeds +MODIFY type INTEGER; +-- ! +ALTER TABLE Categories +ADD COLUMN account_id INTEGER NOT NULL DEFAULT 1; +-- ! +ALTER TABLE Categories +ADD COLUMN custom_id TEXT; +-- ! +ALTER TABLE Categories +MODIFY date_created BIGINT; +-- ! +UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version'; \ No newline at end of file diff --git a/resources/misc/db_update_sqlite_3_4.sql b/resources/misc/db_update_sqlite_3_4.sql new file mode 100644 index 000000000..7366f1b5e --- /dev/null +++ b/resources/misc/db_update_sqlite_3_4.sql @@ -0,0 +1,103 @@ +CREATE TABLE Accounts ( + id INTEGER PRIMARY KEY, + type TEXT NOT NULL +); +-- ! +INSERT INTO Accounts (type) VALUES ('std-rss'); +-- ! +DROP TABLE IF EXISTS FeedsData; +-- ! +CREATE TABLE TtRssAccounts ( + id INTEGER, + username TEXT NOT NULL, + password TEXT, + auth_protected INTEGER(1) NOT NULL CHECK (auth_protected >= 0 AND auth_protected <= 1) DEFAULT 0, + auth_username TEXT, + auth_password TEXT, + url TEXT NOT NULL, + force_update INTEGER(1) NOT NULL CHECK (force_update >= 0 AND force_update <= 1) DEFAULT 0, + + FOREIGN KEY (id) REFERENCES Accounts (id) +); +-- ! +CREATE TABLE backup_Messages AS SELECT * FROM Messages; +-- ! +DROP TABLE Messages; +-- ! +CREATE TABLE Messages ( + id INTEGER PRIMARY KEY, + is_read INTEGER(1) NOT NULL CHECK (is_read >= 0 AND is_read <= 1) DEFAULT 0, + is_deleted INTEGER(1) NOT NULL CHECK (is_deleted >= 0 AND is_deleted <= 1) DEFAULT 0, + is_important INTEGER(1) NOT NULL CHECK (is_important >= 0 AND is_important <= 1) DEFAULT 0, + feed TEXT NOT NULL, + title TEXT NOT NULL CHECK (title != ''), + url TEXT, + author TEXT, + date_created INTEGER NOT NULL CHECK (date_created != 0), + contents TEXT, + is_pdeleted INTEGER(1) NOT NULL CHECK (is_pdeleted >= 0 AND is_pdeleted <= 1) DEFAULT 0, + enclosures TEXT, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) +); +-- ! +INSERT INTO Messages (id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, account_id) +SELECT id, is_read, is_deleted, is_important, feed, title, url, author, date_created, contents, is_pdeleted, enclosures, 1 FROM backup_Messages; +-- ! +DROP TABLE backup_Messages; +-- ! +CREATE TABLE backup_Feeds AS SELECT * FROM Feeds; +-- ! +DROP TABLE Feeds; +-- ! +CREATE TABLE Feeds ( + id INTEGER PRIMARY KEY, + title TEXT NOT NULL CHECK (title != ''), + description TEXT, + date_created INTEGER, + icon BLOB, + category INTEGER NOT NULL CHECK (category >= -1), + encoding TEXT, + url TEXT, + protected INTEGER(1) NOT NULL CHECK (protected >= 0 AND protected <= 1), + username TEXT, + password TEXT, + update_type INTEGER(1) NOT NULL CHECK (update_type >= 0), + update_interval INTEGER NOT NULL CHECK (update_interval >= 5) DEFAULT 15, + type INTEGER, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) +); +-- ! +INSERT INTO Feeds (id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_type, type, account_id) +SELECT id, title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_type, type, 1 FROM backup_Feeds; +-- ! +DROP TABLE backup_Feeds; +-- ! +CREATE TABLE backup_Categories AS SELECT * FROM Categories; +-- ! +DROP TABLE Categories; +-- ! +CREATE TABLE Categories ( + id INTEGER PRIMARY KEY, + parent_id INTEGER NOT NULL, + title TEXT NOT NULL CHECK (title != ''), + description TEXT, + date_created INTEGER, + icon BLOB, + account_id INTEGER NOT NULL, + custom_id TEXT, + + FOREIGN KEY (account_id) REFERENCES Accounts (id) +); +-- ! +INSERT INTO Categories (id, parent_id, title, description, date_created, icon, account_id) +SELECT id, parent_id, title, description, date_created, icon, 1 FROM backup_Categories; +-- ! +DROP TABLE backup_Categories; +-- ! +UPDATE Information SET inf_value = '4' WHERE inf_key = 'schema_version'; \ No newline at end of file diff --git a/resources/text/CHANGELOG b/resources/text/CHANGELOG index 936cec063..dbf432f47 100644 --- a/resources/text/CHANGELOG +++ b/resources/text/CHANGELOG @@ -12,6 +12,30 @@ +

3.0.0

+ + Added: + + + Fixed: + + +

2.5.2

Added: diff --git a/src/core/feeddownloader.cpp b/src/core/feeddownloader.cpp old mode 100644 new mode 100755 index 8482e11a6..22d331a31 --- a/src/core/feeddownloader.cpp +++ b/src/core/feeddownloader.cpp @@ -17,11 +17,12 @@ #include "core/feeddownloader.h" -#include "core/feed.h" +#include "services/abstract/feed.h" #include "definitions/definitions.h" #include #include +#include FeedDownloader::FeedDownloader(QObject *parent) : QObject(parent) { @@ -32,7 +33,7 @@ FeedDownloader::~FeedDownloader() { qDebug("Destroying FeedDownloader instance."); } -void FeedDownloader::updateFeeds(const QList &feeds) { +void FeedDownloader::updateFeeds(const QList &feeds) { qDebug().nospace() << "Performing feed updates in thread: \'" << QThread::currentThreadId() << "\'."; // Job starts now. @@ -66,10 +67,15 @@ QString FeedDownloadResults::getOverview(int how_many_feeds) { QStringList result; - // TODO: Maybe enhance the formatting of this output. for (int i = 0, number_items_output = qMin(how_many_feeds, m_updatedFeeds.size()); i < number_items_output; i++) { result.append(m_updatedFeeds.at(i).first + QSL(": ") + QString::number(m_updatedFeeds.at(i).second)); } - return result.join(QSL("\n")); + QString res_str = result.join(QSL("\n")); + + if (m_updatedFeeds.size() > how_many_feeds) { + res_str += QObject::tr("\n\n+ %n other feeds.", 0, m_updatedFeeds.size() - how_many_feeds); + } + + return res_str; } diff --git a/src/core/feeddownloader.h b/src/core/feeddownloader.h old mode 100644 new mode 100755 diff --git a/src/core/feedsmodel.cpp b/src/core/feedsmodel.cpp index f78b4dd23..2a2a3164d 100755 --- a/src/core/feedsmodel.cpp +++ b/src/core/feedsmodel.cpp @@ -18,16 +18,22 @@ #include "core/feedsmodel.h" #include "definitions/definitions.h" -#include "core/category.h" -#include "core/feed.h" -#include "core/recyclebin.h" -#include "core/feedsimportexportmodel.h" +#include "services/abstract/feed.h" +#include "services/abstract/category.h" +#include "services/abstract/serviceroot.h" +#include "services/abstract/recyclebin.h" +#include "services/standard/standardserviceroot.h" #include "miscellaneous/textfactory.h" #include "miscellaneous/databasefactory.h" +#include "miscellaneous/databasecleaner.h" #include "miscellaneous/iconfactory.h" #include "miscellaneous/mutex.h" #include "gui/messagebox.h" +#include "gui/statusbar.h" +#include "gui/dialogs/formmain.h" +#include "core/feeddownloader.h" +#include #include #include #include @@ -40,7 +46,9 @@ FeedsModel::FeedsModel(QObject *parent) - : QAbstractItemModel(parent), m_recycleBin(new RecycleBin()), m_autoUpdateTimer(new QTimer(this)) { + : QAbstractItemModel(parent), m_autoUpdateTimer(new QTimer(this)), + m_feedDownloaderThread(NULL), m_feedDownloader(NULL), + m_dbCleanerThread(NULL), m_dbCleaner(NULL) { setObjectName(QSL("FeedsModel")); // Create root item. @@ -62,15 +70,17 @@ FeedsModel::FeedsModel(QObject *parent) connect(m_autoUpdateTimer, SIGNAL(timeout()), this, SLOT(executeNextAutoUpdate())); - loadFromDatabase(); - - // Setup the timer. + //loadActivatedServiceAccounts(); updateAutoUpdateStatus(); } FeedsModel::~FeedsModel() { qDebug("Destroying FeedsModel instance."); + foreach (ServiceRoot *account, serviceRoots()) { + account->stop(); + } + // Delete all model items. delete m_rootItem; } @@ -79,6 +89,214 @@ void FeedsModel::quit() { if (m_autoUpdateTimer->isActive()) { m_autoUpdateTimer->stop(); } + + // Close worker threads. + if (m_feedDownloaderThread != NULL && m_feedDownloaderThread->isRunning()) { + qDebug("Quitting feed downloader thread."); + m_feedDownloaderThread->quit(); + + if (!m_feedDownloaderThread->wait(CLOSE_LOCK_TIMEOUT)) { + qCritical("Feed downloader thread is running despite it was told to quit. Terminating it."); + m_feedDownloaderThread->terminate(); + } + } + + if (m_dbCleanerThread != NULL && m_dbCleanerThread->isRunning()) { + qDebug("Quitting database cleaner thread."); + m_dbCleanerThread->quit(); + + if (!m_dbCleanerThread->wait(CLOSE_LOCK_TIMEOUT)) { + qCritical("Database cleaner thread is running despite it was told to quit. Terminating it."); + m_dbCleanerThread->terminate(); + } + } + + // Close workers. + if (m_feedDownloader != NULL) { + qDebug("Feed downloader exists. Deleting it from memory."); + m_feedDownloader->deleteLater(); + } + + if (m_dbCleaner != NULL) { + qDebug("Database cleaner exists. Deleting it from memory."); + m_dbCleaner->deleteLater(); + } + + if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::ClearReadOnExit)).toBool()) { + markItemCleared(m_rootItem, true); + } +} + +void FeedsModel::updateFeeds(const QList &feeds) { + if (!qApp->feedUpdateLock()->tryLock()) { + qApp->showGuiMessage(tr("Cannot update all items"), + tr("You cannot update all items because another another critical operation is ongoing."), + QSystemTrayIcon::Warning, qApp->mainForm(), true); + return; + } + + if (m_feedDownloader == NULL) { + m_feedDownloader = new FeedDownloader(); + m_feedDownloaderThread = new QThread(); + + // Downloader setup. + qRegisterMetaType >("QList"); + m_feedDownloader->moveToThread(m_feedDownloaderThread); + + connect(this, SIGNAL(feedsUpdateRequested(QList)), m_feedDownloader, SLOT(updateFeeds(QList))); + connect(m_feedDownloaderThread, SIGNAL(finished()), m_feedDownloaderThread, SLOT(deleteLater())); + connect(m_feedDownloader, SIGNAL(finished(FeedDownloadResults)), this, SLOT(onFeedUpdatesFinished(FeedDownloadResults))); + connect(m_feedDownloader, SIGNAL(started()), this, SLOT(onFeedUpdatesStarted())); + connect(m_feedDownloader, SIGNAL(progress(Feed*,int,int)), this, SLOT(onFeedUpdatesProgress(Feed*,int,int))); + + // Connections are made, start the feed downloader thread. + m_feedDownloaderThread->start(); + } + + emit feedsUpdateRequested(feeds); +} + +void FeedsModel::onFeedUpdatesStarted() { + //: Text display in status bar when feed update is started. + qApp->mainForm()->statusBar()->showProgressFeeds(0, tr("Feed update started")); +} + +void FeedsModel::onFeedUpdatesProgress(Feed *feed, int current, int total) { + // Some feed got updated. + qApp->mainForm()->statusBar()->showProgressFeeds((current * 100.0) / total, + //: Text display in status bar when particular feed is updated. + tr("Updated feed '%1'").arg(feed->title())); +} + +void FeedsModel::onFeedUpdatesFinished(FeedDownloadResults results) { + qApp->feedUpdateLock()->unlock(); + qApp->mainForm()->statusBar()->clearProgressFeeds(); + + if (!results.m_updatedFeeds.isEmpty()) { + // Now, inform about results via GUI message/notification. + qApp->showGuiMessage(tr("New messages downloaded"), results.getOverview(10), QSystemTrayIcon::NoIcon, + 0, false, qApp->icons()->fromTheme(QSL("item-update-all"))); + } + + emit feedsUpdateFinished(); +} + +void FeedsModel::updateAllFeeds() { + updateFeeds(m_rootItem->getSubTreeFeeds()); +} + + +DatabaseCleaner *FeedsModel::databaseCleaner() { + if (m_dbCleaner == NULL) { + m_dbCleaner = new DatabaseCleaner(); + m_dbCleanerThread = new QThread(); + + // Downloader setup. + qRegisterMetaType("CleanerOrders"); + m_dbCleaner->moveToThread(m_dbCleanerThread); + connect(m_dbCleanerThread, SIGNAL(finished()), m_dbCleanerThread, SLOT(deleteLater())); + + // Connections are made, start the feed downloader thread. + m_dbCleanerThread->start(); + } + + return m_dbCleaner; +} + +QMimeData *FeedsModel::mimeData(const QModelIndexList &indexes) const { + QMimeData *mime_data = new QMimeData(); + QByteArray encoded_data; + QDataStream stream(&encoded_data, QIODevice::WriteOnly); + + foreach (const QModelIndex &index, indexes) { + if (index.column() != 0) { + continue; + } + + RootItem *item_for_index = itemForIndex(index); + + if (item_for_index->kind() != RootItemKind::Root) { + stream << (quintptr) item_for_index; + } + } + + mime_data->setData(MIME_TYPE_ITEM_POINTER, encoded_data); + return mime_data; +} + +QStringList FeedsModel::mimeTypes() const { + return QStringList() << MIME_TYPE_ITEM_POINTER; +} + +bool FeedsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { + Q_UNUSED(row) + Q_UNUSED(column) + + if (action == Qt::IgnoreAction) { + return true; + } + else if (action != Qt::MoveAction) { + return false; + } + + QByteArray dragged_items_data = data->data(MIME_TYPE_ITEM_POINTER); + + if (dragged_items_data.isEmpty()) { + return false; + } + else { + QDataStream stream(&dragged_items_data, QIODevice::ReadOnly); + + while (!stream.atEnd()) { + quintptr pointer_to_item; + stream >> pointer_to_item; + + // We have item we want to drag, we also determine the target item. + RootItem *dragged_item = (RootItem*) pointer_to_item; + RootItem *target_item = itemForIndex(parent); + ServiceRoot *dragged_item_root = dragged_item->getParentServiceRoot(); + ServiceRoot *target_item_root = target_item->getParentServiceRoot(); + + if (dragged_item == target_item || dragged_item->parent() == target_item) { + qDebug("Dragged item is equal to target item or its parent is equal to target item. Cancelling drag-drop action."); + return false; + } + + if (dragged_item_root != target_item_root) { + // Transferring of items between different accounts is not possible. + qApp->showGuiMessage(tr("Cannot perform drag & drop operation"), + tr("You can't transfer dragged item into different account, this is not supported."), + QSystemTrayIcon::Warning, + qApp->mainForm(), + true); + + qDebug("Dragged item cannot be dragged into different account. Cancelling drag-drop action."); + return false; + } + + if (dragged_item->performDragDropChange(target_item)) { + // Drag & drop is supported by the dragged item and was + // completed on data level and in item hierarchy. + emit requireItemValidationAfterDragDrop(indexForItem(dragged_item)); + } + } + + return true; + } + + return false; +} + +Qt::DropActions FeedsModel::supportedDropActions() const { + return Qt::MoveAction; +} + +Qt::ItemFlags FeedsModel::flags(const QModelIndex &index) const { + Qt::ItemFlags base_flags = QAbstractItemModel::flags(index); + RootItem *item_for_index = itemForIndex(index); + Qt::ItemFlags additional_flags = item_for_index->additionalFlags(); + + return base_flags | additional_flags; } void FeedsModel::executeNextAutoUpdate() { @@ -137,116 +355,6 @@ void FeedsModel::updateAutoUpdateStatus() { } } -QMimeData *FeedsModel::mimeData(const QModelIndexList &indexes) const { - QMimeData *mime_data = new QMimeData(); - QByteArray encoded_data; - QDataStream stream(&encoded_data, QIODevice::WriteOnly); - - foreach (const QModelIndex &index, indexes) { - if (index.column() != 0) { - continue; - } - - RootItem *item_for_index = itemForIndex(index); - - if (item_for_index->kind() != RootItem::Root) { - stream << (quintptr) item_for_index; - } - } - - mime_data->setData(MIME_TYPE_ITEM_POINTER, encoded_data); - return mime_data; -} - -QStringList FeedsModel::mimeTypes() const { - return QStringList() << MIME_TYPE_ITEM_POINTER; -} - -bool FeedsModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { - Q_UNUSED(row) - Q_UNUSED(column) - - if (action == Qt::IgnoreAction) { - return true; - } - else if (action != Qt::MoveAction) { - return false; - } - - QByteArray dragged_items_data = data->data(MIME_TYPE_ITEM_POINTER); - - if (dragged_items_data.isEmpty()) { - return false; - } - else { - QDataStream stream(&dragged_items_data, QIODevice::ReadOnly); - - while (!stream.atEnd()) { - quintptr pointer_to_item; - stream >> pointer_to_item; - - // We have item we want to drag, we also determine the target item. - RootItem *dragged_item = (RootItem*) pointer_to_item; - RootItem *target_item = itemForIndex(parent); - - if (dragged_item == target_item || dragged_item->parent() == target_item) { - qDebug("Dragged item is equal to target item or its parent is equal to target item. Cancelling drag-drop action."); - return false; - } - - if (dragged_item->kind() == RootItem::Feeed) { - qDebug("Drag-drop action for feed '%s' detected, editing the feed.", qPrintable(dragged_item->title())); - - Feed *actual_feed = dragged_item->toFeed(); - Feed *feed_new = new Feed(*actual_feed); - - feed_new->setParent(target_item); - editFeed(actual_feed, feed_new); - - emit requireItemValidationAfterDragDrop(indexForItem(actual_feed)); - } - else if (dragged_item->kind() == RootItem::Cattegory) { - qDebug("Drag-drop action for category '%s' detected, editing the feed.", qPrintable(dragged_item->title())); - - Category *actual_category = dragged_item->toCategory(); - Category *category_new = new Category(*actual_category); - - category_new->clearChildren(); - category_new->setParent(target_item); - editCategory(actual_category, category_new); - - emit requireItemValidationAfterDragDrop(indexForItem(actual_category)); - } - } - - return true; - } -} - -Qt::DropActions FeedsModel::supportedDropActions() const { - return Qt::MoveAction; -} - -Qt::ItemFlags FeedsModel::flags(const QModelIndex &index) const { - Qt::ItemFlags base_flags = QAbstractItemModel::flags(index); - RootItem *item_for_index = itemForIndex(index); - - switch (item_for_index->kind()) { - case RootItem::Bin: - return base_flags; - - case RootItem::Cattegory: - return base_flags | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; - - case RootItem::Feeed: - return base_flags | Qt::ItemIsDragEnabled; - - case RootItem::Root: - default: - return base_flags | Qt::ItemIsDropEnabled; - } -} - QVariant FeedsModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation != Qt::Horizontal) { return QVariant(); @@ -318,118 +426,98 @@ int FeedsModel::rowCount(const QModelIndex &parent) const { } } -bool FeedsModel::removeItem(const QModelIndex &index) { +void FeedsModel::reloadCountsOfWholeModel() { + m_rootItem->updateCounts(true); + reloadWholeLayout(); + notifyWithCounts(); +} + +void FeedsModel::removeItem(const QModelIndex &index) { if (index.isValid()) { - QModelIndex parent_index = index.parent(); RootItem *deleting_item = itemForIndex(index); + QModelIndex parent_index = index.parent(); RootItem *parent_item = deleting_item->parent(); - // Try to persistently remove the item. - if (deleting_item->removeItself()) { - // Item was persistently removed. - // Remove it from the model. - beginRemoveRows(parent_index, index.row(), index.row()); - parent_item->removeChild(deleting_item); - endRemoveRows(); + beginRemoveRows(parent_index, index.row(), index.row()); + parent_item->removeChild(deleting_item); + endRemoveRows(); - delete deleting_item; + deleting_item->deleteLater(); + notifyWithCounts(); + } +} + +void FeedsModel::removeItem(RootItem *deleting_item) { + if (deleting_item != NULL) { + QModelIndex index = indexForItem(deleting_item); + QModelIndex parent_index = index.parent(); + RootItem *parent_item = deleting_item->parent(); + + beginRemoveRows(parent_index, index.row(), index.row()); + parent_item->removeChild(deleting_item); + endRemoveRows(); + + deleting_item->deleteLater(); + notifyWithCounts(); + } +} + +void FeedsModel::reassignNodeToNewParent(RootItem *original_node, RootItem *new_parent) { + RootItem *original_parent = original_node->parent(); + + if (original_parent != new_parent) { + if (original_parent != NULL) { + int original_index_of_item = original_parent->childItems().indexOf(original_node); + + if (original_index_of_item >= 0) { + // Remove the original item from the model... + beginRemoveRows(indexForItem(original_parent), original_index_of_item, original_index_of_item); + original_parent->removeChild(original_node); + endRemoveRows(); + } + } + + int new_index_of_item = new_parent->childCount(); + + // ... and insert it under the new parent. + beginInsertRows(indexForItem(new_parent), new_index_of_item, new_index_of_item); + new_parent->appendChild(original_node); + endInsertRows(); + } +} + +QList FeedsModel::serviceRoots() { + QList roots; + + foreach (RootItem *root, m_rootItem->childItems()) { + if (root->kind() == RootItemKind::ServiceRoot) { + roots.append(root->toServiceRoot()); + } + } + + return roots; +} + +bool FeedsModel::containsServiceRootFromEntryPoint(ServiceEntryPoint *point) { + foreach (RootItem *root, serviceRoots()) { + if (root->toServiceRoot()->code() == point->code()) { return true; } } - // Item was not removed successfully. return false; } -bool FeedsModel::addCategory(Category *category, RootItem *parent) { - // Get index of parent item (parent standard category). - QModelIndex parent_index = indexForItem(parent); - bool result = category->addItself(parent); +StandardServiceRoot *FeedsModel::standardServiceRoot() { + foreach (RootItem *root, serviceRoots()) { + StandardServiceRoot *std_service_root; - if (result) { - // Category was added to the persistent storage, - // so add it to the model. - beginInsertRows(parent_index, parent->childCount(), parent->childCount()); - parent->appendChild(category); - endInsertRows(); - } - else { - // We cannot delete (*this) in its method, thus delete it here. - delete category; + if ((std_service_root = dynamic_cast(root)) != NULL) { + return std_service_root; + } } - return result; -} - -bool FeedsModel::editCategory(Category *original_category, Category *new_category_data) { - RootItem *original_parent = original_category->parent(); - RootItem *new_parent = new_category_data->parent(); - bool result = original_category->editItself(new_category_data); - - if (result && original_parent != new_parent) { - // User edited category and set it new parent item, - // se we need to move the item in the model too. - int original_index_of_category = original_parent->childItems().indexOf(original_category); - int new_index_of_category = new_parent->childCount(); - - // Remove the original item from the model... - beginRemoveRows(indexForItem(original_parent), original_index_of_category, original_index_of_category); - original_parent->removeChild(original_category); - endRemoveRows(); - - // ...and insert it under the new parent. - beginInsertRows(indexForItem(new_parent), new_index_of_category, new_index_of_category); - new_parent->appendChild(original_category); - endInsertRows(); - } - - // Cleanup temporary new category data. - delete new_category_data; - return result; -} - -bool FeedsModel::addFeed(Feed *feed, RootItem *parent) { - // Get index of parent item (parent standard category or root item). - QModelIndex parent_index = indexForItem(parent); - bool result = feed->addItself(parent); - - if (result) { - // Feed was added to the persistent storage so add it to the model. - beginInsertRows(parent_index, parent->childCount(), parent->childCount()); - parent->appendChild(feed); - endInsertRows(); - } - else { - delete feed; - } - - return result; -} - -bool FeedsModel::editFeed(Feed *original_feed, Feed *new_feed_data) { - RootItem *original_parent = original_feed->parent(); - RootItem *new_parent = new_feed_data->parent(); - bool result = original_feed->editItself(new_feed_data); - - if (result && original_parent != new_parent) { - // User edited category and set it new parent item, - // se we need to move the item in the model too. - int original_index_of_feed = original_parent->childItems().indexOf(original_feed); - int new_index_of_feed = new_parent->childCount(); - - // Remove the original item from the model... - beginRemoveRows(indexForItem(original_parent), original_index_of_feed, original_index_of_feed); - original_parent->removeChild(original_feed); - endRemoveRows(); - - // ... and insert it under the new parent. - beginInsertRows(indexForItem(new_parent), new_index_of_feed, new_index_of_feed); - new_parent->appendChild(original_feed); - endInsertRows(); - } - - delete new_feed_data; - return result; + return NULL; } QList FeedsModel::feedsForScheduledUpdate(bool auto_update_now) { @@ -471,37 +559,8 @@ QList FeedsModel::feedsForScheduledUpdate(bool auto_update_now) { return feeds_for_update; } -QList FeedsModel::messagesForFeeds(const QList &feeds) { - QList messages; - - QSqlDatabase database = qApp->database()->connection(objectName(), - DatabaseFactory::FromSettings); - QSqlQuery query_read_msg(database); - query_read_msg.setForwardOnly(true); - query_read_msg.prepare("SELECT title, url, author, date_created, contents " - "FROM Messages " - "WHERE is_deleted = 0 AND feed = :feed;"); - - foreach (Feed *feed, feeds) { - query_read_msg.bindValue(QSL(":feed"), feed->id()); - - if (query_read_msg.exec()) { - while (query_read_msg.next()) { - Message message; - - message.m_feedId = feed->id(); - message.m_title = query_read_msg.value(0).toString(); - message.m_url = query_read_msg.value(1).toString(); - message.m_author = query_read_msg.value(2).toString(); - message.m_created = TextFactory::parseDateTime(query_read_msg.value(3).value()); - message.m_contents = query_read_msg.value(4).toString(); - - messages.append(message); - } - } - } - - return messages; +QList FeedsModel::messagesForItem(RootItem *item) { + return item->undeletedMessages(); } int FeedsModel::columnCount(const QModelIndex &parent) const { @@ -522,7 +581,7 @@ RootItem *FeedsModel::itemForIndex(const QModelIndex &index) const { Category *FeedsModel::categoryForIndex(const QModelIndex &index) const { RootItem *item = itemForIndex(index); - if (item->kind() == RootItem::Cattegory) { + if (item->kind() == RootItemKind::Category) { return item->toCategory(); } else { @@ -530,26 +589,15 @@ Category *FeedsModel::categoryForIndex(const QModelIndex &index) const { } } -RecycleBin *FeedsModel::recycleBinForIndex(const QModelIndex &index) const { - RootItem *item = itemForIndex(index); - - if (item->kind() == RootItem::Bin) { - return item->toRecycleBin(); - } - else { - return NULL; - } -} - QModelIndex FeedsModel::indexForItem(RootItem *item) const { - if (item == NULL || item->kind() == RootItem::Root) { + if (item == NULL || item->kind() == RootItemKind::Root) { // Root item lies on invalid index. return QModelIndex(); } QStack chain; - while (item->kind() != RootItem::Root) { + while (item->kind() != RootItemKind::Root) { chain.push(item); item = item->parent(); } @@ -576,84 +624,6 @@ bool FeedsModel::hasAnyFeedNewMessages() { return false; } -bool FeedsModel::mergeModel(FeedsImportExportModel *model, QString &output_message) { - if (model == NULL || model->rootItem() == NULL) { - output_message = tr("Invalid tree data."); - qDebug("Root item for merging two models is null."); - return false; - } - - QStack original_parents; original_parents.push(m_rootItem); - QStack new_parents; new_parents.push(model->rootItem()); - bool some_feed_category_error = false; - - // We are definitely about to add some new items into the model. - //emit layoutAboutToBeChanged(); - - // Iterate all new items we would like to merge into current model. - while (!new_parents.isEmpty()) { - RootItem *target_parent = original_parents.pop(); - RootItem *source_parent = new_parents.pop(); - - foreach (RootItem *source_item, source_parent->childItems()) { - if (!model->isItemChecked(source_item)) { - // We can skip this item, because it is not checked and should not be imported. - // NOTE: All descendants are thus skipped too. - continue; - } - - if (source_item->kind() == RootItem::Cattegory) { - Category *source_category = source_item->toCategory(); - Category *new_category = new Category(*source_category); - - // Add category to model. - new_category->clearChildren(); - - if (addCategory(new_category, target_parent)) { - // Process all children of this category. - original_parents.push(new_category); - new_parents.push(source_category); - } - else { - // Add category failed, but this can mean that the same category (with same title) - // already exists. If such a category exists in current parent, then find it and - // add descendants to it. - RootItem *existing_category = target_parent->child(RootItem::Cattegory, new_category->title()); - - if (existing_category != NULL) { - original_parents.push(existing_category); - new_parents.push(source_category); - } - else { - some_feed_category_error = true; - } - } - } - else if (source_item->kind() == RootItem::Feeed) { - Feed *source_feed = source_item->toFeed(); - Feed *new_feed = new Feed(*source_feed); - - // Append this feed and end this iteration. - if (!addFeed(new_feed, target_parent)) { - some_feed_category_error = true; - } - } - } - } - - // Changes are done now. Finalize the new model. - //emit layoutChanged(); - - if (some_feed_category_error) { - output_message = tr("Import successfull, but some feeds/categories were not imported due to error."); - } - else { - output_message = tr("Import was completely successfull."); - } - - return !some_feed_category_error; -} - void FeedsModel::reloadChangedLayout(QModelIndexList list) { while (!list.isEmpty()) { QModelIndex indx = list.takeFirst(); @@ -664,6 +634,33 @@ void FeedsModel::reloadChangedLayout(QModelIndexList list) { } } +void FeedsModel::reloadChangedItem(RootItem *item) { + QModelIndex index_item = indexForItem(item); + reloadChangedLayout(QModelIndexList() << index_item); +} + +void FeedsModel::notifyWithCounts() { + if (SystemTrayIcon::isSystemTrayActivated()) { + qApp->trayIcon()->setNumber(countOfUnreadMessages(), hasAnyFeedNewMessages()); + } +} + +void FeedsModel::onItemDataChanged(QList items) { + if (items.size() > RELOAD_MODEL_BORDER_NUM) { + qDebug("There is request to reload feed model for more than %d items, reloading model fully.", RELOAD_MODEL_BORDER_NUM); + reloadWholeLayout(); + } + else { + qDebug("There is request to reload feed model, reloading the %d items individually.", items.size()); + + foreach (RootItem *item, items) { + reloadChangedItem(item); + } + } + + notifyWithCounts(); +} + QStringList FeedsModel::textualFeedIds(const QList &feeds) { QStringList stringy_ids; stringy_ids.reserve(feeds.size()); @@ -680,81 +677,78 @@ void FeedsModel::reloadWholeLayout() { emit layoutChanged(); } -void FeedsModel::loadFromDatabase() { - // Delete all childs of the root node and clear them from the memory. - qDeleteAll(m_rootItem->childItems()); - m_rootItem->clearChildren(); +bool FeedsModel::addServiceAccount(ServiceRoot *root) { + int new_row_index = m_rootItem->childCount(); - QSqlDatabase database = qApp->database()->connection(objectName(), DatabaseFactory::FromSettings); - CategoryAssignment categories; - FeedAssignment feeds; + beginInsertRows(indexForItem(m_rootItem), new_row_index, new_row_index); + m_rootItem->appendChild(root); + endInsertRows(); - // Obtain data for categories from the database. - QSqlQuery query_categories(database); - query_categories.setForwardOnly(true); + // Connect. + connect(root, SIGNAL(itemRemovalRequested(RootItem*)), this, SLOT(removeItem(RootItem*))); + connect(root, SIGNAL(itemReassignmentRequested(RootItem*,RootItem*)), this, SLOT(reassignNodeToNewParent(RootItem*,RootItem*))); + connect(root, SIGNAL(readFeedsFilterInvalidationRequested()), this, SIGNAL(readFeedsFilterInvalidationRequested())); + connect(root, SIGNAL(dataChanged(QList)), this, SLOT(onItemDataChanged(QList))); + connect(root, SIGNAL(reloadMessageListRequested(bool)), this, SIGNAL(reloadMessageListRequested(bool))); + connect(root, SIGNAL(itemExpandRequested(QList,bool)), this, SIGNAL(itemExpandRequested(QList,bool))); - if (!query_categories.exec(QSL("SELECT * FROM Categories;")) || query_categories.lastError().isValid()) { - qFatal("Query for obtaining categories failed. Error message: '%s'.", - qPrintable(query_categories.lastError().text())); - } + root->start(); + return true; +} - while (query_categories.next()) { - CategoryAssignmentItem pair; - pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt(); - pair.second = new Category(query_categories.record()); +bool FeedsModel::restoreAllBins() { + bool result = true; - categories << pair; - } + foreach (ServiceRoot *root, serviceRoots()) { + RecycleBin *bin_of_root = root->recycleBin(); - // All categories are now loaded. - QSqlQuery query_feeds(database); - query_feeds.setForwardOnly(true); - - if (!query_feeds.exec(QSL("SELECT * FROM Feeds;")) || query_feeds.lastError().isValid()) { - qFatal("Query for obtaining feeds failed. Error message: '%s'.", - qPrintable(query_feeds.lastError().text())); - } - - while (query_feeds.next()) { - // Process this feed. - Feed::Type type = static_cast(query_feeds.value(FDS_DB_TYPE_INDEX).toInt()); - - switch (type) { - case Feed::Atom10: - case Feed::Rdf: - case Feed::Rss0X: - case Feed::Rss2X: { - FeedAssignmentItem pair; - pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt(); - pair.second = new Feed(query_feeds.record()); - pair.second->setType(type); - - feeds << pair; - break; - } - - default: - break; + if (bin_of_root != NULL) { + result &= bin_of_root->restore(); } } - // All data are now obtained, lets create the hierarchy. - assembleCategories(categories); - assembleFeeds(feeds); + return result; +} - // As the last item, add recycle bin, which is needed. - m_rootItem->appendChild(m_recycleBin); +bool FeedsModel::emptyAllBins() { + bool result = true; + + foreach (ServiceRoot *root, serviceRoots()) { + RecycleBin *bin_of_root = root->recycleBin(); + + if (bin_of_root != NULL) { + result &= bin_of_root->empty(); + } + } + + return result; +} + +void FeedsModel::loadActivatedServiceAccounts() { + // Iterate all globally available feed "service plugins". + foreach (ServiceEntryPoint *entry_point, qApp->feedServices()) { + // Load all stored root nodes from the entry point and add those to the model. + QList roots = entry_point->initializeSubtree(); + + foreach (ServiceRoot *root, roots) { + addServiceAccount(root); + } + } + + if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) { + qDebug("Requesting update for all feeds on application startup."); + QTimer::singleShot(STARTUP_UPDATE_DELAY, this, SLOT(updateAllFeeds())); + } } QList FeedsModel::feedsForIndex(const QModelIndex &index) { - RootItem *item = itemForIndex(index); - return feedsForItem(item); + return itemForIndex(index)->getSubTreeFeeds(); } Feed *FeedsModel::feedForIndex(const QModelIndex &index) { RootItem *item = itemForIndex(index); - if (item->kind() == RootItem::Feeed) { + if (item->kind() == RootItemKind::Feed) { return item->toFeed(); } else { @@ -762,196 +756,18 @@ Feed *FeedsModel::feedForIndex(const QModelIndex &index) { } } -QList FeedsModel::feedsForIndexes(const QModelIndexList &indexes) { - QList feeds; - - // Get selected feeds for each index. - foreach (const QModelIndex &index, indexes) { - feeds.append(feedsForIndex(index)); - } - - // Now we obtained all feeds from corresponding indexes. - if (indexes.size() != feeds.size()) { - // Selection contains duplicate feeds (for - // example situation where feed and its parent category are both - // selected). So, remove duplicates from the list. - qSort(feeds.begin(), feeds.end(), RootItem::lessThan); - feeds.erase(std::unique(feeds.begin(), feeds.end(), RootItem::isEqual), feeds.end()); - } - - return feeds; +bool FeedsModel::markItemRead(RootItem *item, RootItem::ReadStatus read) { + return item->markAsReadUnread(read); } -bool FeedsModel::markFeedsRead(const QList &feeds, int read) { - QSqlDatabase db_handle = qApp->database()->connection(objectName(), DatabaseFactory::FromSettings); - - if (!db_handle.transaction()) { - qWarning("Starting transaction for feeds read change."); - return false; - } - - QSqlQuery query_read_msg(db_handle); - query_read_msg.setForwardOnly(true); - - if (!query_read_msg.prepare(QString("UPDATE Messages SET is_read = :read " - "WHERE feed IN (%1) AND is_deleted = 0;").arg(textualFeedIds(feeds).join(QSL(", "))))) { - qWarning("Query preparation failed for feeds read change."); - - db_handle.rollback(); - return false; - } - - query_read_msg.bindValue(QSL(":read"), read); - - if (!query_read_msg.exec()) { - qDebug("Query execution for feeds read change failed."); - db_handle.rollback(); - } - - // Commit changes. - if (db_handle.commit()) { - return true; - } - else { - return db_handle.rollback(); - } -} - -bool FeedsModel::markFeedsDeleted(const QList &feeds, int deleted, bool read_only) { - QSqlDatabase db_handle = qApp->database()->connection(objectName(), DatabaseFactory::FromSettings); - - if (!db_handle.transaction()) { - qWarning("Starting transaction for feeds clearing."); - return false; - } - - QSqlQuery query_delete_msg(db_handle); - query_delete_msg.setForwardOnly(true); - - if (read_only) { - if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " - "WHERE feed IN (%1) AND is_deleted = 0 AND is_read = 1;").arg(textualFeedIds(feeds).join(QSL(", "))))) { - qWarning("Query preparation failed for feeds clearing."); - - db_handle.rollback(); - return false; - } - } - else { - if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " - "WHERE feed IN (%1) AND is_deleted = 0;").arg(textualFeedIds(feeds).join(QSL(", "))))) { - qWarning("Query preparation failed for feeds clearing."); - - db_handle.rollback(); - return false; - } - } - - query_delete_msg.bindValue(QSL(":deleted"), deleted); - - if (!query_delete_msg.exec()) { - qDebug("Query execution for feeds clearing failed."); - db_handle.rollback(); - } - - // Commit changes. - if (db_handle.commit()) { - return true; - } - else { - return db_handle.rollback(); - } -} - -QHash FeedsModel::allCategories() { - return categoriesForItem(m_rootItem); -} - -QHash FeedsModel::categoriesForItem(RootItem *root) { - QHash categories; - QList parents; - - parents.append(root->childItems()); - - while (!parents.isEmpty()) { - RootItem *item = parents.takeFirst(); - - if (item->kind() == RootItem::Cattegory) { - // This item is category, add it to the output list and - // scan its children. - int category_id = item->id(); - Category *category = item->toCategory(); - - if (!categories.contains(category_id)) { - categories.insert(category_id, category); - } - - parents.append(category->childItems()); - } - } - - return categories; +bool FeedsModel::markItemCleared(RootItem *item, bool clean_read_only) { + return item->cleanMessages(clean_read_only); } QList FeedsModel::allFeeds() { - return feedsForItem(m_rootItem); + return m_rootItem->getSubTreeFeeds(); } -QList FeedsModel::feedsForItem(RootItem *root) { - QList children = root->getRecursiveChildren(); - QList feeds; - - foreach (RootItem *child, children) { - if (child->kind() == RootItem::Feeed) { - feeds.append(child->toFeed()); - } - } - - return feeds; -} - -void FeedsModel::assembleFeeds(FeedAssignment feeds) { - QHash categories = allCategories(); - - foreach (const FeedAssignmentItem &feed, feeds) { - if (feed.first == NO_PARENT_CATEGORY) { - // This is top-level feed, add it to the root item. - m_rootItem->appendChild(feed.second); - } - else if (categories.contains(feed.first)) { - // This feed belongs to this category. - categories.value(feed.first)->appendChild(feed.second); - } - else { - qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title())); - } - } -} - -RecycleBin *FeedsModel::recycleBin() const { - return m_recycleBin; -} - -void FeedsModel::assembleCategories(CategoryAssignment categories) { - QHash assignments; - assignments.insert(NO_PARENT_CATEGORY, m_rootItem); - - // Add top-level categories. - while (!categories.isEmpty()) { - for (int i = 0; i < categories.size(); i++) { - if (assignments.contains(categories.at(i).first)) { - // Parent category of this category is already added. - assignments.value(categories.at(i).first)->appendChild(categories.at(i).second); - - // Now, added category can be parent for another categories, add it. - assignments.insert(categories.at(i).second->id(), - categories.at(i).second); - - // Remove the category from the list, because it was - // added to the final collection. - categories.removeAt(i); - i--; - } - } - } +QList FeedsModel::allCategories() { + return m_rootItem->getSubTreeCategories(); } diff --git a/src/core/feedsmodel.h b/src/core/feedsmodel.h old mode 100644 new mode 100755 index a4eb30c3d..6807b6709 --- a/src/core/feedsmodel.h +++ b/src/core/feedsmodel.h @@ -20,46 +20,41 @@ #include -#include "core/messagesmodel.h" -#include "core/rootitem.h" - -#include - +#include "core/message.h" +#include "services/abstract/rootitem.h" +#include "core/feeddownloader.h" +class DatabaseCleaner; class Category; class Feed; -class RecycleBin; -class FeedsImportExportModel; +class ServiceRoot; +class ServiceEntryPoint; +class StandardServiceRoot; class QTimer; -typedef QList > CategoryAssignment; -typedef QPair CategoryAssignmentItem; - -typedef QList > FeedAssignment; -typedef QPair FeedAssignmentItem; - class FeedsModel : public QAbstractItemModel { Q_OBJECT - friend class Feed; - friend class Category; - public: // Constructors and destructors. explicit FeedsModel(QObject *parent = 0); virtual ~FeedsModel(); + DatabaseCleaner *databaseCleaner(); + // Model implementation. inline QVariant data(const QModelIndex &index, int role) const { // Return data according to item. return itemForIndex(index)->data(index.column(), role); } + // Drag & drop. QMimeData *mimeData(const QModelIndexList &indexes) const; QStringList mimeTypes() const; bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); Qt::DropActions supportedDropActions() const; Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &child) const; @@ -75,20 +70,23 @@ class FeedsModel : public QAbstractItemModel { return m_rootItem->countOfUnreadMessages(); } + void reloadCountsOfWholeModel(); + // Removes item with given index. - bool removeItem(const QModelIndex &index); + // NOTE: Also deletes item from memory. + void removeItem(const QModelIndex &index); - // Standard category manipulators. - bool addCategory(Category *category, RootItem *parent); - bool editCategory(Category *original_category, Category *new_category_data); + // Returns all activated service roots. + // NOTE: Service root nodes are lying directly UNDER + // the model root item. + QList serviceRoots(); - // Standard feed manipulators. - bool addFeed(Feed *feed, RootItem *parent); + // Determines if there is any account activated from given entry point. + bool containsServiceRootFromEntryPoint(ServiceEntryPoint *point); - // New feed is just temporary feed, it is not added to the model. - // It is used to fetch its data to the original feed - // and the original feed is moved if needed. - bool editFeed(Feed *original_feed, Feed *new_feed_data); + // Direct and the only global accessor to standard service root. + // NOTE: Standard service root is always activated. + StandardServiceRoot *standardServiceRoot(); // Returns the list of feeds which should be updated // according to auto-update schedule. @@ -100,27 +98,15 @@ class FeedsModel : public QAbstractItemModel { // Returns (undeleted) messages for given feeds. // This is usually used for displaying whole feeds // in "newspaper" mode. - QList messagesForFeeds(const QList &feeds); + QList messagesForItem(RootItem *item); - // Returns all categories, each pair - // consists of ID of parent item and pointer to category. - QHash allCategories(); - - // Returns categories from the subtree with given root node, each pair - // consists of ID of parent item and pointer to category. - QHash categoriesForItem(RootItem *root); + // Returns list of all categories contained in the model. + QList allCategories(); // Returns list of all feeds contained in the model. QList allFeeds(); - // Get list of feeds from tree with particular item - // as root. If root itself is a feed, then it is returned. - QList feedsForItem(RootItem *root); - - // Returns list of ALL CHILD feeds which belong to given parent indexes. - QList feedsForIndexes(const QModelIndexList &indexes); - - // Returns ALL CHILD feeds contained within single index. + // Returns ALL RECURSIVE CHILD feeds contained within single index. QList feedsForIndex(const QModelIndex &index); // Returns pointer to feed if it lies on given index @@ -131,15 +117,14 @@ class FeedsModel : public QAbstractItemModel { // or NULL if no category lies on given index. Category *categoryForIndex(const QModelIndex &index) const; - // Returns pointer to recycle bin if lies on given index - // or NULL if no recycle bin lies on given index. - RecycleBin *recycleBinForIndex(const QModelIndex &index) const; - // Returns feed/category which lies at the specified index or // root item if index is invalid. RootItem *itemForIndex(const QModelIndex &index) const; // Returns source QModelIndex on which lies given item. + // NOTE: This goes through all available indexes and + // checks their bound items manually, there is no + // other way to to this. QModelIndex indexForItem(RootItem *item) const; // Determines if any feed has any new messages. @@ -150,13 +135,6 @@ class FeedsModel : public QAbstractItemModel { return m_rootItem; } - // Takes structure residing under given root item and adds feeds/categories from - // it to active structure. - bool mergeModel(FeedsImportExportModel *model, QString &output_message); - - // Access to recycle bin. - RecycleBin *recycleBin() const; - // Resets global auto-update intervals according to settings // and starts/stop the timer as needed. void updateAutoUpdateStatus(); @@ -164,10 +142,31 @@ class FeedsModel : public QAbstractItemModel { // Does necessary job before quitting this component. void quit(); + // Schedules given feeds for update. + void updateFeeds(const QList &feeds); + + // Adds given service root account. + bool addServiceAccount(ServiceRoot *root); + + // Loads feed/categories from the database. + void loadActivatedServiceAccounts(); + public slots: + // Schedules all feeds from all accounts for update. + void updateAllFeeds(); + + // Checks if new parent node is different from one used by original node. + // If it is, then it reassigns original_node to new parent. + void reassignNodeToNewParent(RootItem *original_node, RootItem *new_parent); + + void removeItem(RootItem *deleting_item); + + bool restoreAllBins(); + bool emptyAllBins(); + // Feeds operations. - bool markFeedsRead(const QList &feeds, int read); - bool markFeedsDeleted(const QList &feeds, int deleted, bool read_only); + bool markItemRead(RootItem *item, RootItem::ReadStatus read); + bool markItemCleared(RootItem *item, bool clean_read_only); // Signals that properties (probably counts) // of ALL items have changed. @@ -178,32 +177,54 @@ class FeedsModel : public QAbstractItemModel { // NOTE: This reloads all parent valid indexes too. void reloadChangedLayout(QModelIndexList list); + // Invalidates data under index for the item. + void reloadChangedItem(RootItem *item); + + // Notifies other components about messages + // counts. + void notifyWithCounts(); + private slots: + void onItemDataChanged(QList items); + // Is executed when next auto-update round could be done. void executeNextAutoUpdate(); - protected: - // Returns converted ids of given feeds - // which are suitable as IN clause for SQL queries. - QStringList textualFeedIds(const QList &feeds); - - // Loads feed/categories from the database. - void loadFromDatabase(); - - // Takes lists of feeds/categories and assembles - // them into the tree structure. - void assembleCategories(CategoryAssignment categories); - void assembleFeeds(FeedAssignment feeds); + // Reacts on feed updates. + void onFeedUpdatesStarted(); + void onFeedUpdatesProgress(Feed *feed, int current, int total); + void onFeedUpdatesFinished(FeedDownloadResults results); signals: - void requireItemValidationAfterDragDrop(const QModelIndex &source_index); + // Update of feeds is finished. + void feedsUpdateFinished(); + + // Counts of unread messages are changed in some feeds, + // notify view about this shit. + void readFeedsFilterInvalidationRequested(); // Emitted when model requests update of some feeds. void feedsUpdateRequested(const QList feeds); + // Emitted if counts of messages are changed. + void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages); + + // Emitted if any item requested that any view should expand it. + void itemExpandRequested(QList items, bool expand); + + // Emitted when there is a need of reloading of displayed messages. + void reloadMessageListRequested(bool mark_selected_messages_read); + + // There was some drag/drop operation, notify view about this. + // NOTE: View will probably expand dropped index. + void requireItemValidationAfterDragDrop(const QModelIndex &source_index); + private: + // Returns converted ids of given feeds + // which are suitable as IN clause for SQL queries. + QStringList textualFeedIds(const QList &feeds); + RootItem *m_rootItem; - RecycleBin *m_recycleBin; QList m_headerData; QList m_tooltipData; QIcon m_countsIcon; @@ -213,6 +234,12 @@ class FeedsModel : public QAbstractItemModel { bool m_globalAutoUpdateEnabled; int m_globalAutoUpdateInitialInterval; int m_globalAutoUpdateRemainingInterval; + + QThread *m_feedDownloaderThread; + FeedDownloader *m_feedDownloader; + + QThread *m_dbCleanerThread; + DatabaseCleaner *m_dbCleaner; }; #endif // FEEDSMODEL_H diff --git a/src/core/feedsproxymodel.cpp b/src/core/feedsproxymodel.cpp index 36215a3d8..ea1504d91 100755 --- a/src/core/feedsproxymodel.cpp +++ b/src/core/feedsproxymodel.cpp @@ -20,9 +20,11 @@ #include "definitions/definitions.h" #include "miscellaneous/application.h" #include "core/feedsmodel.h" -#include "core/category.h" -#include "core/feed.h" -#include "core/rootitem.h" +#include "services/abstract/rootitem.h" +#include "services/standard/standardcategory.h" +#include "services/standard/standardfeed.h" + +#include FeedsProxyModel::FeedsProxyModel(QObject *parent) @@ -37,6 +39,8 @@ FeedsProxyModel::FeedsProxyModel(QObject *parent) setFilterRole(Qt::EditRole); setDynamicSortFilter(false); setSourceModel(m_sourceModel); + + connect(m_sourceModel, SIGNAL(readFeedsFilterInvalidationRequested()), this, SLOT(invalidateReadFeedsFilter())); } FeedsProxyModel::~FeedsProxyModel() { @@ -45,18 +49,18 @@ FeedsProxyModel::~FeedsProxyModel() { QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const { QModelIndexList result; - uint matchType = flags & 0x0F; + uint match_type = flags & 0x0F; Qt::CaseSensitivity cs = Qt::CaseInsensitive; bool recurse = flags & Qt::MatchRecursive; bool wrap = flags & Qt::MatchWrap; - bool allHits = (hits == -1); + bool all_hits = (hits == -1); QString entered_text; QModelIndex p = parent(start); int from = start.row(); int to = rowCount(p); for (int i = 0; (wrap && i < 2) || (!wrap && i < 1); ++i) { - for (int r = from; (r < to) && (allHits || result.count() < hits); ++r) { + for (int r = from; (r < to) && (all_hits || result.count() < hits); ++r) { QModelIndex idx = index(r, start.column(), p); if (!idx.isValid()) { @@ -64,10 +68,10 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const } QModelIndex mapped_idx = mapToSource(idx); - QVariant item_value = m_sourceModel->data(m_sourceModel->index(mapped_idx.row(), FDS_MODEL_TITLE_INDEX, mapped_idx.parent()), role); + QVariant item_value = m_sourceModel->itemForIndex(mapped_idx)->title(); // QVariant based matching. - if (matchType == Qt::MatchExactly) { + if (match_type == Qt::MatchExactly) { if (value == item_value) { result.append(idx); } @@ -80,7 +84,7 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const QString item_text = item_value.toString(); - switch (matchType) { + switch (match_type) { case Qt::MatchRegExp: if (QRegExp(entered_text, cs).exactMatch(item_text)) { result.append(idx); @@ -121,7 +125,7 @@ QModelIndexList FeedsProxyModel::match(const QModelIndex &start, int role, const } if (recurse && hasChildren(idx)) { - result += match(index(0, idx.column(), idx), role, (entered_text.isEmpty() ? value : entered_text), (allHits ? -1 : hits - result.count()), flags); + result += match(index(0, idx.column(), idx), role, (entered_text.isEmpty() ? value : entered_text), (all_hits ? -1 : hits - result.count()), flags); } } @@ -155,15 +159,15 @@ bool FeedsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right return QString::localeAwareCompare(left_item->title(), right_item->title()) < 0; } } - else if (left_item->kind() == RootItem::Bin) { + else if (left_item->kind() == RootItemKind::Bin) { // Left item is recycle bin. Make sure it is "biggest" item if we have selected ascending order. return sortOrder() == Qt::DescendingOrder; } - else if (right_item->kind() == RootItem::Bin) { + else if (right_item->kind() == RootItemKind::Bin) { // Right item is recycle bin. Make sure it is "smallest" item if we have selected descending order. return sortOrder() == Qt::AscendingOrder; } - else if (left_item->kind() == RootItem::Feeed) { + else if (left_item->kind() == RootItemKind::Feed) { // Left item is feed, right item is category. return false; } @@ -193,7 +197,7 @@ bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source RootItem *item = m_sourceModel->itemForIndex(idx); - if (item->kind() == RootItem::Bin) { + if (item->kind() == RootItemKind::Bin || item->kind() == RootItemKind::ServiceRoot) { // Recycle bin is always displayed. return true; } @@ -202,7 +206,9 @@ bool FeedsProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source return true; } else { - return item->countOfUnreadMessages() > 0; + // NOTE: If item has < 0 of unread message it may mean, that the count + // of unread messages is not (yet) known, display that item too. + return item->countOfUnreadMessages() != 0; } } @@ -218,6 +224,14 @@ bool FeedsProxyModel::showUnreadOnly() const { return m_showUnreadOnly; } +void FeedsProxyModel::invalidateReadFeedsFilter(bool set_new_value, bool show_unread_only) { + if (set_new_value) { + setShowUnreadOnly(show_unread_only); + } + + QTimer::singleShot(0, this, SLOT(invalidateFilter())); +} + void FeedsProxyModel::setShowUnreadOnly(bool show_unread_only) { m_showUnreadOnly = show_unread_only; qApp->settings()->setValue(GROUP(Feeds), Feeds::ShowOnlyUnreadFeeds, show_unread_only); diff --git a/src/core/feedsproxymodel.h b/src/core/feedsproxymodel.h index aa000b94b..9abd57770 100755 --- a/src/core/feedsproxymodel.h +++ b/src/core/feedsproxymodel.h @@ -18,12 +18,11 @@ #ifndef FEEDSPROXYMODEL_H #define FEEDSPROXYMODEL_H -#include "rootitem.h" - #include class FeedsModel; +class RootItem; class FeedsProxyModel : public QSortFilterProxyModel { Q_OBJECT @@ -38,6 +37,8 @@ class FeedsProxyModel : public QSortFilterProxyModel { return m_sourceModel; } + // Returns index list of items which "match" given value. + // Used for finding items according to entered title text. QModelIndexList match(const QModelIndex &start, int role, const QVariant &value, int hits, Qt::MatchFlags flags) const; // Maps list of indexes. @@ -50,14 +51,16 @@ class FeedsProxyModel : public QSortFilterProxyModel { void setSelectedItem(RootItem *selected_item); public slots: + void invalidateReadFeedsFilter(bool set_new_value = false, bool show_unread_only = false); + + private slots: void invalidateFilter(); - protected: + private: // Compares two rows of data. bool lessThan(const QModelIndex &left, const QModelIndex &right) const; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; - private: // Source model pointer. FeedsModel *m_sourceModel; diff --git a/src/core/feedsselection.cpp b/src/core/feedsselection.cpp deleted file mode 100755 index 4b7328c1b..000000000 --- a/src/core/feedsselection.cpp +++ /dev/null @@ -1,75 +0,0 @@ -// This file is part of RSS Guard. -// -// Copyright (C) 2011-2015 by Martin Rotter -// -// RSS Guard 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. -// -// RSS Guard 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 RSS Guard. If not, see . - -#include "core/feedsselection.h" - -#include "core/rootitem.h" -#include "core/category.h" -#include "core/feed.h" -#include "definitions/definitions.h" - - -FeedsSelection::FeedsSelection(RootItem *root_of_selection) : m_selectedItem(root_of_selection) { -} - -FeedsSelection::FeedsSelection(const FeedsSelection &other) { - m_selectedItem = other.selectedItem(); -} - -FeedsSelection::~FeedsSelection() { -} - -FeedsSelection::SelectionMode FeedsSelection::mode() { - if (m_selectedItem == NULL) { - return FeedsSelection::NoMode; - } - - switch (m_selectedItem->kind()) { - case RootItem::Bin: - return FeedsSelection::MessagesFromRecycleBin; - - case RootItem::Cattegory: - case RootItem::Feeed: - return FeedsSelection::MessagesFromFeeds; - - default: - return FeedsSelection::NoMode; - } -} - -RootItem *FeedsSelection::selectedItem() const { - return m_selectedItem; -} - -QString FeedsSelection::generateListOfIds() { - if (m_selectedItem != NULL && - (m_selectedItem->kind() == RootItem::Feeed || m_selectedItem->kind() == RootItem::Cattegory)) { - QList children = m_selectedItem->getRecursiveChildren(); - QStringList stringy_ids; - - foreach (RootItem *child, children) { - if (child->kind() == RootItem::Feeed) { - stringy_ids.append(QString::number(child->id())); - } - } - - return stringy_ids.join(QSL(", ")); - } - else { - return QString(); - } -} diff --git a/src/core/message.cpp b/src/core/message.cpp new file mode 100755 index 000000000..e74789fff --- /dev/null +++ b/src/core/message.cpp @@ -0,0 +1,104 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "core/message.h" + +#include "miscellaneous/textfactory.h" + +#include + + +Enclosure::Enclosure(const QString &url, const QString &mime) : m_url(url), m_mimeType(mime) { +} + +QList Enclosures::decodeEnclosuresFromString(const QString &enclosures_data) { + QList enclosures; + + foreach (const QString &single_enclosure, enclosures_data.split(ENCLOSURES_OUTER_SEPARATOR, QString::SkipEmptyParts)) { + Enclosure enclosure; + + if (single_enclosure.contains(ECNLOSURES_INNER_SEPARATOR)) { + QStringList mime_url = single_enclosure.split(ECNLOSURES_INNER_SEPARATOR); + + enclosure.m_mimeType = QByteArray::fromBase64(mime_url.at(0).toLocal8Bit()); + enclosure.m_url = QByteArray::fromBase64(mime_url.at(1).toLocal8Bit()); + } + else { + enclosure.m_url = QByteArray::fromBase64(single_enclosure.toLocal8Bit()); + } + + enclosures.append(enclosure); + } + + return enclosures; +} + +QString Enclosures::encodeEnclosuresToString(const QList &enclosures) { + QStringList enclosures_str; + + foreach (const Enclosure &enclosure, enclosures) { + if (enclosure.m_mimeType.isEmpty()) { + enclosures_str.append(enclosure.m_url.toLocal8Bit().toBase64()); + } + else { + enclosures_str.append(QString(enclosure.m_mimeType.toLocal8Bit().toBase64()) + + ECNLOSURES_INNER_SEPARATOR + + enclosure.m_url.toLocal8Bit().toBase64()); + } + } + + return enclosures_str.join(QString(ENCLOSURES_OUTER_SEPARATOR)); +} + +Message::Message() { + m_title = m_url = m_author = m_contents = m_feedId = m_customId = ""; + m_enclosures = QList(); + m_accountId = m_id = 0; + m_isRead = m_isImportant = false; +} + +Message Message::fromSqlRecord(const QSqlRecord &record, bool *result) { + if (record.count() != MSG_DB_CUSTOM_ID_INDEX + 1) { + if (result != NULL) { + *result = false; + return Message(); + } + } + + Message message; + + message.m_id = record.value(MSG_DB_ID_INDEX).toInt(); + message.m_isRead = record.value(MSG_DB_READ_INDEX).toBool(); + //message = record.value(MSG_DB_DELETED_INDEX).toInt(); + message.m_isImportant = record.value(MSG_DB_IMPORTANT_INDEX).toBool(); + message.m_feedId = record.value(MSG_DB_FEED_INDEX).toString(); + message.m_title = record.value(MSG_DB_TITLE_INDEX).toString(); + message.m_url = record.value(MSG_DB_URL_INDEX).toString(); + message.m_author = record.value(MSG_DB_AUTHOR_INDEX).toString(); + message.m_created = TextFactory::parseDateTime(record.value(MSG_DB_DCREATED_INDEX).value()); + message.m_contents = record.value(MSG_DB_CONTENTS_INDEX).toString(); + //message = record.value(MSG_DB_PDELETED_INDEX).toInt(); + message.m_enclosures = Enclosures::decodeEnclosuresFromString(record.value(MSG_DB_ENCLOSURES_INDEX).toString()); + message.m_accountId = record.value(MSG_DB_ACCOUNT_ID_INDEX).toInt(); + message.m_customId = record.value(MSG_DB_CUSTOM_ID_INDEX).toString(); + + if (result != NULL) { + *result = true; + } + + return message; +} diff --git a/src/core/message.h b/src/core/message.h new file mode 100755 index 000000000..299299230 --- /dev/null +++ b/src/core/message.h @@ -0,0 +1,72 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef MESSAGE_H +#define MESSAGE_H + +#include "definitions/definitions.h" + +#include +#include +#include + + +// Represents single enclosure. +struct Enclosure { + QString m_url; + QString m_mimeType; + + explicit Enclosure(const QString &url = QString(), const QString &mime = QString()); +}; + +// Represents single enclosure. +class Enclosures { + public: + static QList decodeEnclosuresFromString(const QString &enclosures_data); + static QString encodeEnclosuresToString(const QList &enclosures); +}; + +// Represents single message. +class Message { + public: + explicit Message(); + + // Creates Message from given record, which contains + // row from query SELECT * FROM Messages WHERE ....; + static Message fromSqlRecord(const QSqlRecord &record, bool *result = NULL); + + QString m_title; + QString m_url; + QString m_author; + QString m_contents; + QDateTime m_created; + QString m_feedId; + int m_accountId; + int m_id; + QString m_customId; + + bool m_isRead; + bool m_isImportant; + + QList m_enclosures; + + // Is true if "created" date was obtained directly + // from the feed, otherwise is false + bool m_createdFromFeed; +}; + +#endif // MESSAGE_H diff --git a/src/core/messagesmodel.cpp b/src/core/messagesmodel.cpp index 2ac6e9e42..baacb58ab 100755 --- a/src/core/messagesmodel.cpp +++ b/src/core/messagesmodel.cpp @@ -22,6 +22,8 @@ #include "miscellaneous/textfactory.h" #include "miscellaneous/databasefactory.h" #include "miscellaneous/iconfactory.h" +#include "gui/dialogs/formmain.h" +#include "services/abstract/serviceroot.h" #include #include @@ -30,7 +32,7 @@ MessagesModel::MessagesModel(QObject *parent) : QSqlTableModel(parent, qApp->database()->connection(QSL("MessagesModel"), DatabaseFactory::FromSettings)), - m_messageFilter(NoHighlighting), m_customDateFormat(QString()) { + m_messageHighlighter(NoHighlighting), m_customDateFormat(QString()) { setObjectName(QSL("MessagesModel")); setupFonts(); setupIcons(); @@ -42,7 +44,7 @@ MessagesModel::MessagesModel(QObject *parent) // via model, but via DIRECT SQL calls are used to do persistent messages. setEditStrategy(QSqlTableModel::OnManualSubmit); setTable(QSL("Messages")); - loadMessages(FeedsSelection()); + loadMessages(NULL); } MessagesModel::~MessagesModel() { @@ -55,11 +57,9 @@ void MessagesModel::setupIcons() { m_unreadIcon = qApp->icons()->fromTheme(QSL("mail-mark-unread")); } -FeedsSelection MessagesModel::loadedSelection() const { - return m_currentSelection; -} +void MessagesModel::fetchAllData() { + select(); -void MessagesModel::fetchAll() { while (canFetchMore()) { fetchMore(); } @@ -71,25 +71,34 @@ void MessagesModel::setupFonts() { m_boldFont.setBold(true); } -void MessagesModel::loadMessages(const FeedsSelection &selection) { - m_currentSelection = selection; +void MessagesModel::loadMessages(RootItem *item) { + m_selectedItem = item; - if (m_currentSelection.mode() == FeedsSelection::MessagesFromRecycleBin) { - setFilter(QSL("is_deleted = 1 AND is_pdeleted = 0")); + if (item == NULL) { + setFilter("true != true"); } else { - QString assembled_ids = m_currentSelection.generateListOfIds(); - - setFilter(QString(QSL("feed IN (%1) AND is_deleted = 0")).arg(assembled_ids)); - qDebug("Loading messages from feeds: %s.", qPrintable(assembled_ids)); + if (!item->getParentServiceRoot()->loadMessagesForItem(item, this)) { + setFilter("true != true"); + qWarning("Loading of messages from item '%s' failed.", qPrintable(item->title())); + qApp->showGuiMessage(tr("Loading of messages from item '%1' failed.").arg(item->title()), + tr("Loading of messages failed, maybe messages could not be downloaded."), + QSystemTrayIcon::Critical, + qApp->mainForm(), + true); + } } - select(); - fetchAll(); + fetchAllData(); } -void MessagesModel::filterMessages(MessagesModel::MessageFilter filter) { - m_messageFilter = filter; +bool MessagesModel::submitAll() { + qFatal("Submitting changes via model is not allowed."); + return false; +} + +void MessagesModel::highlightMessages(MessagesModel::MessageHighlighter highlight) { + m_messageHighlighter = highlight; emit layoutAboutToBeChanged(); emit layoutChanged(); } @@ -98,6 +107,14 @@ int MessagesModel::messageId(int row_index) const { return data(row_index, MSG_DB_ID_INDEX, Qt::EditRole).toInt(); } +RootItem::Importance MessagesModel::messageImportance(int row_index) const { + return (RootItem::Importance) data(row_index, MSG_DB_IMPORTANT_INDEX, Qt::EditRole).toInt(); +} + +RootItem *MessagesModel::loadedItem() const { + return m_selectedItem; +} + void MessagesModel::updateDateFormat() { if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::UseCustomDate)).toBool()) { m_customDateFormat = qApp->settings()->value(GROUP(Messages), SETTING(Messages::CustomDateFormat)).toString(); @@ -112,20 +129,8 @@ void MessagesModel::reloadWholeLayout() { emit layoutChanged(); } -Message MessagesModel::messageAt(int row_index) const { - QSqlRecord rec = record(row_index); - Message message; - - // Fill Message object with details. - message.m_author = rec.value(MSG_DB_AUTHOR_INDEX).toString(); - message.m_contents = rec.value(MSG_DB_CONTENTS_INDEX).toString(); - message.m_enclosures = Enclosures::decodeEnclosuresFromString(rec.value(MSG_DB_ENCLOSURES_INDEX).toString()); - message.m_title = rec.value(MSG_DB_TITLE_INDEX).toString(); - message.m_url = rec.value(MSG_DB_URL_INDEX).toString(); - message.m_feedId = rec.value(MSG_DB_FEED_INDEX).toInt(); - message.m_created = TextFactory::parseDateTime(rec.value(MSG_DB_DCREATED_INDEX).value()).toLocalTime(); - - return message; +Message MessagesModel::messageAt(int row_index) const { + return Message::fromSqlRecord(record(row_index)); } void MessagesModel::setupHeaderData() { @@ -140,7 +145,9 @@ void MessagesModel::setupHeaderData() { /*: Tooltip for creation date of message.*/ tr("Created on") << /*: Tooltip for contents of message.*/ tr("Contents") << /*: Tooltip for "pdeleted" column in msg list.*/ tr("Permanently deleted") << - /*: Tooltip for attachments of message.*/ tr("Attachments"); + /*: Tooltip for attachments of message.*/ tr("Attachments") << + /*: Tooltip for account ID of message.*/ tr("Account ID") << + /*: Tooltip for custom ID of message.*/ tr("Custom ID"); m_tooltipData << tr("Id of the message.") << tr("Is message read?") << tr("Is message deleted?") << tr("Is message important?") << @@ -148,13 +155,18 @@ void MessagesModel::setupHeaderData() { tr("Title of the message.") << tr("Url of the message.") << tr("Author of the message.") << tr("Creation date of the message.") << tr("Contents of the message.") << tr("Is message permanently deleted from recycle bin?") << - tr("List of attachments."); + tr("List of attachments.") << tr("Account ID of the message.") << tr("Custom ID of the message"); } Qt::ItemFlags MessagesModel::flags(const QModelIndex &index) const { Q_UNUSED(index) +#if QT_VERSION >= 0x050000 + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren; +#else + return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable; +#endif } QVariant MessagesModel::data(int row, int column, int role) const { @@ -196,7 +208,7 @@ QVariant MessagesModel::data(const QModelIndex &idx, int role) const { return QSqlTableModel::data(index(idx.row(), MSG_DB_READ_INDEX)).toInt() == 1 ? m_normalFont : m_boldFont; case Qt::ForegroundRole: - switch (m_messageFilter) { + switch (m_messageHighlighter) { case HighlightImportant: return QSqlTableModel::data(index(idx.row(), MSG_DB_IMPORTANT_INDEX)).toInt() == 1 ? QColor(Qt::blue) : QVariant(); @@ -227,17 +239,17 @@ QVariant MessagesModel::data(const QModelIndex &idx, int role) const { } } -bool MessagesModel::setMessageRead(int row_index, int read) { +bool MessagesModel::setMessageRead(int row_index, RootItem::ReadStatus read) { if (data(row_index, MSG_DB_READ_INDEX, Qt::EditRole).toInt() == read) { // Read status is the same is the one currently set. // In that case, no extra work is needed. return true; } - QSqlDatabase db_handle = database(); + Message message = messageAt(row_index); - if (!db_handle.transaction()) { - qWarning("Starting transaction for message read change."); + if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, QList() << message, read)) { + // Cannot change read status of the item. Abort. return false; } @@ -247,172 +259,163 @@ bool MessagesModel::setMessageRead(int row_index, int read) { if (!working_change) { // If rewriting in the model failed, then cancel all actions. qDebug("Setting of new data to the model failed for message read change."); - - db_handle.rollback(); return false; } - int message_id; - QSqlQuery query_read_msg(db_handle); + QSqlQuery query_read_msg(database()); query_read_msg.setForwardOnly(true); if (!query_read_msg.prepare(QSL("UPDATE Messages SET is_read = :read WHERE id = :id;"))) { qWarning("Query preparation failed for message read change."); - - db_handle.rollback(); return false; } - // Rewrite the actual data in the database itself. - message_id = messageId(row_index); - query_read_msg.bindValue(QSL(":id"), message_id); - query_read_msg.bindValue(QSL(":read"), read); - query_read_msg.exec(); + query_read_msg.bindValue(QSL(":id"), message.m_id); + query_read_msg.bindValue(QSL(":read"), (int) read); - // Commit changes. - if (db_handle.commit()) { - // If commit succeeded, then emit changes, so that view - // can reflect. - emit dataChanged(index(row_index, 0), index(row_index, columnCount() - 1)); - emit messageCountsChanged(m_currentSelection.mode(), false, false); - return true; + if (query_read_msg.exec()) { + return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, QList() << message, read); } else { - return db_handle.rollback();; + return false; } } bool MessagesModel::switchMessageImportance(int row_index) { - QSqlDatabase db_handle = database(); + QModelIndex target_index = index(row_index, MSG_DB_IMPORTANT_INDEX); + RootItem::Importance current_importance = (RootItem::Importance) data(target_index, Qt::EditRole).toInt(); + RootItem::Importance next_importance = current_importance == RootItem::Important ? + RootItem::NotImportant : RootItem::Important; + Message message = messageAt(row_index); + QPair pair(message, next_importance); - if (!db_handle.transaction()) { - qWarning("Starting transaction for message importance switch failed."); + if (!m_selectedItem->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_selectedItem, + QList >() << pair)) { return false; } - QModelIndex target_index = index(row_index, MSG_DB_IMPORTANT_INDEX); - int current_importance = data(target_index, Qt::EditRole).toInt(); - // Rewrite "visible" data in the model. - bool working_change = current_importance == 1 ? - setData(target_index, 0) : - setData(target_index, 1); + bool working_change = setData(target_index, next_importance); if (!working_change) { // If rewriting in the model failed, then cancel all actions. qDebug("Setting of new data to the model failed for message importance change."); - - db_handle.rollback(); return false; } - int message_id; - QSqlQuery query_importance_msg(db_handle); + QSqlQuery query_importance_msg(database()); query_importance_msg.setForwardOnly(true); if (!query_importance_msg.prepare(QSL("UPDATE Messages SET is_important = :important WHERE id = :id;"))) { qWarning("Query preparation failed for message importance switch."); - - db_handle.rollback(); return false; } - message_id = messageId(row_index); - query_importance_msg.bindValue(QSL(":id"), message_id); - query_importance_msg.bindValue(QSL(":important"), current_importance == 1 ? 0 : 1); - query_importance_msg.exec(); + query_importance_msg.bindValue(QSL(":id"), message.m_id); + query_importance_msg.bindValue(QSL(":important"), (int) next_importance); + // Commit changes. - if (db_handle.commit()) { - // If commit succeeded, then emit changes, so that view - // can reflect. - emit dataChanged(index(row_index, 0), index(row_index, columnCount() - 1)); - return true; + if (query_importance_msg.exec()) { + return m_selectedItem->getParentServiceRoot()->onAfterSwitchMessageImportance(m_selectedItem, + QList >() << pair); } else { - return db_handle.rollback(); + return false; } } bool MessagesModel::switchBatchMessageImportance(const QModelIndexList &messages) { - QSqlDatabase db_handle = database(); - QSqlQuery query_read_msg(db_handle); + QSqlQuery query_read_msg(database()); QStringList message_ids; + QList > message_states; query_read_msg.setForwardOnly(true); // Obtain IDs of all desired messages. foreach (const QModelIndex &message, messages) { - message_ids.append(QString::number(messageId(message.row()))); + Message msg = messageAt(message.row()); + RootItem::Importance message_importance = messageImportance((message.row())); + + message_states.append(QPair(msg, message_importance)); + message_ids.append(QString::number(msg.m_id)); + } + + if (!m_selectedItem->getParentServiceRoot()->onBeforeSwitchMessageImportance(m_selectedItem, message_states)) { + return false; } if (query_read_msg.exec(QString(QSL("UPDATE Messages SET is_important = NOT is_important WHERE id IN (%1);")) .arg(message_ids.join(QSL(", "))))) { - select(); - fetchAll(); - - //emit messageCountsChanged(false); - return true; + fetchAllData(); + return m_selectedItem->getParentServiceRoot()->onAfterSwitchMessageImportance(m_selectedItem, message_states); } else { return false; } } -bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList &messages, int deleted) { - QSqlDatabase db_handle = database(); - QSqlQuery query_read_msg(db_handle); +bool MessagesModel::setBatchMessagesDeleted(const QModelIndexList &messages) { QStringList message_ids; - - query_read_msg.setForwardOnly(true); + QList msgs; // Obtain IDs of all desired messages. foreach (const QModelIndex &message, messages) { - message_ids.append(QString::number(messageId(message.row()))); + Message msg = messageAt(message.row()); + + msgs.append(msg); + message_ids.append(QString::number(msg.m_id)); } + if (!m_selectedItem->getParentServiceRoot()->onBeforeMessagesDelete(m_selectedItem, msgs)) { + return false; + } + + QSqlQuery query_read_msg(database()); QString sql_delete_query; - if (m_currentSelection.mode() == FeedsSelection::MessagesFromFeeds) { - sql_delete_query = QString(QSL("UPDATE Messages SET is_deleted = %2 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")), - QString::number(deleted)); + query_read_msg.setForwardOnly(true); + + if (m_selectedItem->kind() != RootItemKind::Bin) { + sql_delete_query = QString(QSL("UPDATE Messages SET is_deleted = 1 WHERE id IN (%1);")).arg(message_ids.join(QSL(", "))); } else { - sql_delete_query = QString(QSL("UPDATE Messages SET is_pdeleted = %2 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")), - QString::number(deleted)); + sql_delete_query = QString(QSL("UPDATE Messages SET is_pdeleted = 1 WHERE id IN (%1);")).arg(message_ids.join(QSL(", "))); } if (query_read_msg.exec(sql_delete_query)) { - select(); - fetchAll(); - - emit messageCountsChanged(m_currentSelection.mode(), true, false); - return true; + fetchAllData(); + return m_selectedItem->getParentServiceRoot()->onAfterMessagesDelete(m_selectedItem, msgs); } else { return false; } } -bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, int read) { - QSqlDatabase db_handle = database(); - QSqlQuery query_read_msg(db_handle); +bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read) { QStringList message_ids; - - query_read_msg.setForwardOnly(true); + QList msgs; // Obtain IDs of all desired messages. foreach (const QModelIndex &message, messages) { - message_ids.append(QString::number(messageId(message.row()))); + Message msg = messageAt(message.row()); + + msgs.append(msg); + message_ids.append(QString::number(msg.m_id)); } - if (query_read_msg.exec(QString(QSL("UPDATE Messages SET is_read = %2 WHERE id IN (%1);")).arg(message_ids.join(QSL(", ")), - QString::number(read)))) { - select(); - fetchAll(); + if (!m_selectedItem->getParentServiceRoot()->onBeforeSetMessagesRead(m_selectedItem, msgs, read)) { + return false; + } - emit messageCountsChanged(m_currentSelection.mode(), false, false); - return true; + QSqlQuery query_read_msg(database()); + query_read_msg.setForwardOnly(true); + + if (query_read_msg.exec(QString(QSL("UPDATE Messages SET is_read = %2 WHERE id IN (%1);")) + .arg(message_ids.join(QSL(", ")), read == RootItem::Read ? QSL("1") : QSL("0")))) { + fetchAllData(); + + return m_selectedItem->getParentServiceRoot()->onAfterSetMessagesRead(m_selectedItem, msgs, read); } else { return false; @@ -420,30 +423,30 @@ bool MessagesModel::setBatchMessagesRead(const QModelIndexList &messages, int re } bool MessagesModel::setBatchMessagesRestored(const QModelIndexList &messages) { - if (m_currentSelection.mode() == FeedsSelection::MessagesFromFeeds) { - qDebug("Cannot restore non-deleted messages."); - return false; - } - - QSqlDatabase db_handle = database(); - QSqlQuery query_read_msg(db_handle); QStringList message_ids; - - query_read_msg.setForwardOnly(true); + QList msgs; // Obtain IDs of all desired messages. foreach (const QModelIndex &message, messages) { - message_ids.append(QString::number(messageId(message.row()))); + Message msg = messageAt(message.row()); + + msgs.append(msg); + message_ids.append(QString::number(msg.m_id)); } + if (!m_selectedItem->getParentServiceRoot()->onBeforeMessagesRestoredFromBin(m_selectedItem, msgs)) { + return false; + } + + QSqlQuery query_read_msg(database()); QString sql_delete_query = QString(QSL("UPDATE Messages SET is_deleted = 0 WHERE id IN (%1);")).arg(message_ids.join(QSL(", "))); - if (query_read_msg.exec(sql_delete_query)) { - select(); - fetchAll(); + query_read_msg.setForwardOnly(true); - emit messageCountsChanged(m_currentSelection.mode(), true, true); - return true; + if (query_read_msg.exec(sql_delete_query)) { + fetchAllData(); + + return m_selectedItem->getParentServiceRoot()->onAfterMessagesRestoredFromBin(m_selectedItem, msgs); } else { return false; diff --git a/src/core/messagesmodel.h b/src/core/messagesmodel.h index 3c2138d7c..9a77938c7 100755 --- a/src/core/messagesmodel.h +++ b/src/core/messagesmodel.h @@ -20,97 +20,21 @@ #include "definitions/definitions.h" -#include "core/feedsselection.h" +#include "core/message.h" +#include "services/abstract/rootitem.h" #include #include #include -#include -// Represents single enclosuresh - -struct Enclosure { - QString m_url; - QString m_mimeType; - - explicit Enclosure(const QString &url = QString(), const QString &mime = QString()) : m_url(url), m_mimeType(mime) { - } -}; - -// Represents single enclosure. -class Enclosures { - public: - static QList decodeEnclosuresFromString(const QString &enclosures_data) { - QList enclosures; - - foreach (const QString &single_enclosure, enclosures_data.split(ENCLOSURES_OUTER_SEPARATOR, QString::SkipEmptyParts)) { - Enclosure enclosure; - - if (single_enclosure.contains(ECNLOSURES_INNER_SEPARATOR)) { - QStringList mime_url = single_enclosure.split(ECNLOSURES_INNER_SEPARATOR); - - enclosure.m_mimeType = QByteArray::fromBase64(mime_url.at(0).toLocal8Bit()); - enclosure.m_url = QByteArray::fromBase64(mime_url.at(1).toLocal8Bit()); - } - else { - enclosure.m_url = QByteArray::fromBase64(single_enclosure.toLocal8Bit()); - } - - enclosures.append(enclosure); - } - - return enclosures; - } - - static QString encodeEnclosuresToString(const QList &enclosures) { - QStringList enclosures_str; - - foreach (const Enclosure &enclosure, enclosures) { - if (enclosure.m_mimeType.isEmpty()) { - enclosures_str.append(enclosure.m_url.toLocal8Bit().toBase64()); - } - else { - enclosures_str.append(QString(enclosure.m_mimeType.toLocal8Bit().toBase64()) + - ECNLOSURES_INNER_SEPARATOR + - enclosure.m_url.toLocal8Bit().toBase64()); - } - } - - return enclosures_str.join(QString(ENCLOSURES_OUTER_SEPARATOR)); - } -}; - -// Represents single message. -class Message { - public: - explicit Message() { - m_title = m_url = m_author = m_contents = ""; - m_feedId = 0; - m_enclosures = QList(); - } - - QString m_title; - QString m_url; - QString m_author; - QString m_contents; - QDateTime m_created; - int m_feedId; - - QList m_enclosures; - - // Is true if "created" date was obtained directly - // from the feed, otherwise is false - bool m_createdFromFeed; -}; - class MessagesModel : public QSqlTableModel { Q_OBJECT public: // Enum which describes basic filtering schemes // for messages. - enum MessageFilter { + enum MessageHighlighter { NoHighlighting = 100, HighlightUnread = 101, HighlightImportant = 102 @@ -129,15 +53,9 @@ class MessagesModel : public QSqlTableModel { // Returns message at given index. Message messageAt(int row_index) const; int messageId(int row_index) const; + RootItem::Importance messageImportance(int row_index) const; - FeedsSelection loadedSelection() const; - - public slots: - // To disable persistent changes submissions. - inline bool submitAll() { - qFatal("Submitting changes via model is not allowed."); - return false; - } + RootItem *loadedItem() const; void updateDateFormat(); void reloadWholeLayout(); @@ -147,7 +65,7 @@ class MessagesModel : public QSqlTableModel { // NOTE: Model is NOT reset after one of these methods are applied // but changes ARE written to the database. bool switchMessageImportance(int row_index); - bool setMessageRead(int row_index, int read); + bool setMessageRead(int row_index, RootItem::ReadStatus read); // BATCH messages manipulators. // NOTE: These methods are used for changing of attributes of @@ -155,37 +73,32 @@ class MessagesModel : public QSqlTableModel { // NOTE: Model is reset after one of these methods is applied and // changes ARE written to the database. bool switchBatchMessageImportance(const QModelIndexList &messages); - bool setBatchMessagesDeleted(const QModelIndexList &messages, int deleted); - bool setBatchMessagesRead(const QModelIndexList &messages, int read); + bool setBatchMessagesDeleted(const QModelIndexList &messages); + bool setBatchMessagesRead(const QModelIndexList &messages, RootItem::ReadStatus read); bool setBatchMessagesRestored(const QModelIndexList &messages); // Fetches ALL available data to the model. - void fetchAll(); + void fetchAllData(); + + // Filters messages + void highlightMessages(MessageHighlighter highlight); // Loads messages of given feeds. - void loadMessages(const FeedsSelection &selection); + void loadMessages(RootItem *item); - void filterMessages(MessageFilter filter); - - signals: - // Emitted if some persistent change is made which affects count of "unread/all" messages. - void messageCountsChanged(FeedsSelection::SelectionMode mode, bool total_msg_count_changed, bool any_msg_restored); - - protected: - // Sets up header data. - void setupHeaderData(); - - // Creates "normal" and "bold" fonts. - void setupFonts(); - - // Sets up all icons which are used directly by this model. - void setupIcons(); + private slots: + // To disable persistent changes submissions. + bool submitAll(); private: - MessageFilter m_messageFilter; + void setupHeaderData(); + void setupFonts(); + void setupIcons(); + + MessageHighlighter m_messageHighlighter; QString m_customDateFormat; - FeedsSelection m_currentSelection; + RootItem *m_selectedItem; QList m_headerData; QList m_tooltipData; @@ -197,6 +110,6 @@ class MessagesModel : public QSqlTableModel { QIcon m_unreadIcon; }; -Q_DECLARE_METATYPE(MessagesModel::MessageFilter) +Q_DECLARE_METATYPE(MessagesModel::MessageHighlighter) #endif // MESSAGESMODEL_H diff --git a/src/core/messagesproxymodel.cpp b/src/core/messagesproxymodel.cpp index 4fc5f9ece..9be27cfaa 100755 --- a/src/core/messagesproxymodel.cpp +++ b/src/core/messagesproxymodel.cpp @@ -38,6 +38,37 @@ MessagesProxyModel::~MessagesProxyModel() { qDebug("Destroying MessagesProxyModel instance."); } +QModelIndex MessagesProxyModel::getNextPreviousUnreadItemIndex(int default_row) { + bool started_from_zero = default_row == 0; + QModelIndex next_index = getNextUnreadItemIndex(default_row, rowCount() - 1); + + // There is no next message, check previous. + if (!next_index.isValid() && !started_from_zero) { + next_index = getNextUnreadItemIndex(0, default_row - 1); + } + + return next_index; +} + +QModelIndex MessagesProxyModel::getNextUnreadItemIndex(int default_row, int max_row) { + while (default_row <= max_row) { + // Get info if the message is read or not. + QModelIndex proxy_index = index(default_row, MSG_DB_READ_INDEX); + bool is_read = m_sourceModel->data(mapToSource(proxy_index).row(), + MSG_DB_READ_INDEX, Qt::EditRole).toInt() == 1; + + if (!is_read) { + // We found unread message, mark it. + return proxy_index; + } + else { + default_row++; + } + } + + return QModelIndex(); +} + bool MessagesProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { if (left.column() == MSG_DB_TITLE_INDEX && right.column() == MSG_DB_TITLE_INDEX) { return QString::localeAwareCompare(m_sourceModel->data(left).toString(), diff --git a/src/core/messagesproxymodel.h b/src/core/messagesproxymodel.h old mode 100644 new mode 100755 index 35d7cef64..1bb2faaa1 --- a/src/core/messagesproxymodel.h +++ b/src/core/messagesproxymodel.h @@ -36,6 +36,8 @@ class MessagesProxyModel : public QSortFilterProxyModel { return m_sourceModel; } + QModelIndex getNextPreviousUnreadItemIndex(int default_row); + // Maps list of indexes. QModelIndexList mapListToSource(const QModelIndexList &indexes); QModelIndexList mapListFromSource(const QModelIndexList &indexes, bool deep = false); @@ -43,11 +45,12 @@ class MessagesProxyModel : public QSortFilterProxyModel { // Fix for matching indexes with respect to specifics of the message model. QModelIndexList match(const QModelIndex &start, int role, const QVariant &entered_value, int hits, Qt::MatchFlags flags) const; - protected: + private: + QModelIndex getNextUnreadItemIndex(int default_row, int max_row); + // Compares two rows of data. bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - private: // Source model pointer. MessagesModel *m_sourceModel; }; diff --git a/src/core/parsingfactory.cpp b/src/core/parsingfactory.cpp index fc8a96145..0cdf85104 100755 --- a/src/core/parsingfactory.cpp +++ b/src/core/parsingfactory.cpp @@ -73,7 +73,7 @@ QList ParsingFactory::parseAsATOM10(const QString &data) { for (int i = 0; i < elem_links.size(); i++) { QDomElement link = elem_links.at(i).toElement(); - if (link.attribute(QSL("rel")) == QL1S("enclosure")) { + if (link.attribute(QSL("rel")) == QSL("enclosure")) { new_message.m_enclosures.append(Enclosure(link.attribute(QSL("href")), link.attribute(QSL("type")))); qDebug("Adding enclosure '%s' for the message.", qPrintable(new_message.m_enclosures.last().m_url)); @@ -99,7 +99,7 @@ QList ParsingFactory::parseAsATOM10(const QString &data) { new_message.m_created = current_time; } - // TODO: There is a difference between "" and QString() in terms of NULL SQL values! + // WARNING: There is a difference between "" and QString() in terms of NULL SQL values! // This is because of difference in QString::isNull() and QString::isEmpty(), the "" is not null // while QString() is. if (new_message.m_author.isNull()) { @@ -203,12 +203,12 @@ QList ParsingFactory::parseAsRSS20(const QString &data) { // Deal with titles & descriptions. QString elem_title = message_item.namedItem(QSL("title")).toElement().text().simplified(); - QString elem_description = message_item.namedItem(QSL("description")).toElement().text(); + QString elem_description = message_item.namedItem(QSL("encoded")).toElement().text(); QString elem_enclosure = message_item.namedItem(QSL("enclosure")).toElement().attribute(QSL("url")); QString elem_enclosure_type = message_item.namedItem(QSL("enclosure")).toElement().attribute(QSL("type")); if (elem_description.isEmpty()) { - elem_description = message_item.namedItem(QSL("encoded")).toElement().text(); + elem_description = message_item.namedItem(QSL("description")).toElement().text(); } // Now we obtained maximum of information for title & description. diff --git a/src/core/recyclebin.cpp b/src/core/recyclebin.cpp deleted file mode 100644 index df086ff39..000000000 --- a/src/core/recyclebin.cpp +++ /dev/null @@ -1,185 +0,0 @@ -// This file is part of RSS Guard. -// -// Copyright (C) 2011-2015 by Martin Rotter -// -// RSS Guard 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. -// -// RSS Guard 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 RSS Guard. If not, see . - -#include "core/recyclebin.h" - -#include "miscellaneous/application.h" -#include "miscellaneous/iconfactory.h" - -#include - - -RecycleBin::RecycleBin(RootItem *parent) - : RootItem(parent) { - m_kind = RootItem::Bin; - m_icon = qApp->icons()->fromTheme(QSL("folder-recycle-bin")); - m_id = ID_RECYCLE_BIN; - m_title = tr("Recycle bin"); - m_description = tr("Recycle bin contains all deleted messages from all feeds."); - m_creationDate = QDateTime::currentDateTime(); - - updateCounts(true); -} - -RecycleBin::~RecycleBin() { - qDebug("Destroying RecycleBin instance."); -} - -int RecycleBin::childCount() const { - return 0; -} - -void RecycleBin::appendChild(RootItem *child) { - Q_UNUSED(child) -} - -int RecycleBin::countOfUnreadMessages() const { - return m_unreadCount; -} - -int RecycleBin::countOfAllMessages() const { - return m_totalCount; -} - -QVariant RecycleBin::data(int column, int role) const { - switch (role) { - case Qt::DisplayRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_title; - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString() - .replace(PLACEHOLDER_UNREAD_COUNTS, QString::number(countOfUnreadMessages())) - .replace(PLACEHOLDER_ALL_COUNTS, QString::number(countOfAllMessages())); - } - else { - return QVariant(); - } - - case Qt::EditRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_title; - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - return countOfUnreadMessages(); - } - else { - return QVariant(); - } - - case Qt::FontRole: - return countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont; - - case Qt::DecorationRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_icon; - } - else { - return QVariant(); - } - - case Qt::ToolTipRole: - return tr("Recycle bin\n%1").arg(tr("%n deleted message(s).", 0, countOfAllMessages())); - - case Qt::TextAlignmentRole: - if (column == FDS_MODEL_COUNTS_INDEX) { - return Qt::AlignCenter; - } - else { - return QVariant(); - } - - default: - return QVariant(); - } -} - -bool RecycleBin::empty() { - QSqlDatabase db_handle = qApp->database()->connection(QSL("RecycleBin"), DatabaseFactory::FromSettings); - - if (!db_handle.transaction()) { - qWarning("Starting transaction for recycle bin emptying."); - return false; - } - - QSqlQuery query_empty_bin(db_handle); - query_empty_bin.setForwardOnly(true); - - if (!query_empty_bin.exec(QSL("UPDATE Messages SET is_pdeleted = 1 WHERE is_deleted = 1;"))) { - qWarning("Query execution failed for recycle bin emptying."); - - db_handle.rollback(); - return false; - } - - // Commit changes. - if (db_handle.commit()) { - return true; - } - else { - return db_handle.rollback(); - } -} - -bool RecycleBin::restore() { - QSqlDatabase db_handle = qApp->database()->connection(QSL("RecycleBin"), DatabaseFactory::FromSettings); - - if (!db_handle.transaction()) { - qWarning("Starting transaction for recycle bin restoring."); - return false; - } - - QSqlQuery query_empty_bin(db_handle); - query_empty_bin.setForwardOnly(true); - - if (!query_empty_bin.exec(QSL("UPDATE Messages SET is_deleted = 0 WHERE is_deleted = 1 AND is_pdeleted = 0;"))) { - qWarning("Query execution failed for recycle bin restoring."); - - db_handle.rollback(); - return false; - } - - // Commit changes. - if (db_handle.commit()) { - return true; - } - else { - return db_handle.rollback(); - } -} - -void RecycleBin::updateCounts(bool update_total_count) { - QSqlDatabase database = qApp->database()->connection(QSL("RecycleBin"), DatabaseFactory::FromSettings); - QSqlQuery query_all(database); - query_all.setForwardOnly(true); - - if (query_all.exec(QSL("SELECT count(*) FROM Messages WHERE is_read = 0 AND is_deleted = 1 AND is_pdeleted = 0;")) && query_all.next()) { - m_unreadCount = query_all.value(0).toInt(); - } - else { - m_unreadCount = 0; - } - - if (update_total_count) { - if (query_all.exec(QSL("SELECT count(*) FROM Messages WHERE is_deleted = 1 AND is_pdeleted = 0;")) && query_all.next()) { - m_totalCount = query_all.value(0).toInt(); - } - else { - m_totalCount = 0; - } - } -} diff --git a/src/core/rootitem.cpp b/src/core/rootitem.cpp deleted file mode 100644 index a262b8d96..000000000 --- a/src/core/rootitem.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// This file is part of RSS Guard. -// -// Copyright (C) 2011-2015 by Martin Rotter -// -// RSS Guard 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. -// -// RSS Guard 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 RSS Guard. If not, see . - -#include "core/rootitem.h" - -#include "core/category.h" -#include "core/feed.h" -#include "core/recyclebin.h" -#include "miscellaneous/application.h" - -#include - - -RootItem::RootItem(RootItem *parent_item) - : m_kind(RootItem::Root), - m_id(NO_PARENT_CATEGORY), - m_title(QString()), - m_description(QString()), - m_icon(QIcon()), - m_creationDate(QDateTime()), - m_childItems(QList()), - m_parentItem(parent_item) { - setupFonts(); -} - -RootItem::~RootItem() { - qDeleteAll(m_childItems); -} - -void RootItem::setupFonts() { - m_normalFont = Application::font("FeedsView"); - m_boldFont = m_normalFont; - m_boldFont.setBold(true); -} - -int RootItem::row() const { - if (m_parentItem) { - return m_parentItem->m_childItems.indexOf(const_cast(this)); - } - else { - // This item has no parent. Therefore, its row index is 0. - return 0; - } -} - -QVariant RootItem::data(int column, int role) const { - Q_UNUSED(column) - Q_UNUSED(role) - - // Do not return anything for the root item. - return QVariant(); -} - -int RootItem::countOfAllMessages() const { - int total_count = 0; - - foreach (RootItem *child_item, m_childItems) { - if (child_item->kind() != RootItem::Bin) { - total_count += child_item->countOfAllMessages(); - } - } - - return total_count; -} - -QList RootItem::getRecursiveChildren() { - QList children; - - if (kind() == RootItem::Feeed) { - // Root itself is a FEED. - children.append(this); - } - else { - // Root itself is a CATEGORY or ROOT item. - QList traversable_items; - - traversable_items.append(this); - - // Iterate all nested categories. - while (!traversable_items.isEmpty()) { - RootItem *active_category = traversable_items.takeFirst(); - - foreach (RootItem *child, active_category->childItems()) { - if (child->kind() == RootItem::Feeed) { - // This child is feed. - children.append(child); - } - else if (child->kind() == RootItem::Cattegory) { - // This child is category, add its child feeds too. - traversable_items.append(child); - } - } - } - } - - return children; -} - -bool RootItem::removeChild(RootItem *child) { - return m_childItems.removeOne(child); -} - -RecycleBin *RootItem::toRecycleBin() { - return static_cast(this); -} - -Category *RootItem::toCategory() { - return static_cast(this); -} - -Feed *RootItem::toFeed() { - return static_cast(this); -} - -RootItem *RootItem::child(RootItem::Kind kind_of_child, const QString &identifier) { - foreach (RootItem *child, childItems()) { - if (child->kind() == kind_of_child) { - if ((kind_of_child == Cattegory && child->title() == identifier) || - (kind_of_child == Feeed && child->toFeed()->url() == identifier)) { - return child; - } - } - } - - return NULL; -} - -int RootItem::countOfUnreadMessages() const { - int total_count = 0; - - foreach (RootItem *child_item, m_childItems) { - if (child_item->kind() != RootItem::Bin) { - total_count += child_item->countOfUnreadMessages(); - } - } - - return total_count; -} - -bool RootItem::removeChild(int index) { - if (index >= 0 && index < m_childItems.size()) { - m_childItems.removeAt(index); - return true; - } - else { - return false; - } -} - -bool RootItem::isEqual(RootItem *lhs, RootItem *rhs) { - return (lhs->kind() == rhs->kind()) && (lhs->id() == rhs->id()); -} - -bool RootItem::lessThan(RootItem *lhs, RootItem *rhs) { - if (lhs->kind() == rhs->kind()) { - return lhs->id() < rhs->id(); - } - else { - return false; - } -} diff --git a/src/core/rootitem.h b/src/core/rootitem.h deleted file mode 100644 index 7ee0e49ef..000000000 --- a/src/core/rootitem.h +++ /dev/null @@ -1,210 +0,0 @@ -// This file is part of RSS Guard. -// -// Copyright (C) 2011-2015 by Martin Rotter -// -// RSS Guard 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. -// -// RSS Guard 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 RSS Guard. If not, see . - -#ifndef ROOTITEM_H -#define ROOTITEM_H - -#include - -#include -#include - -class RecycleBin; -class Category; -class Feed; - -// Represents ROOT item of FeedsModel. -// NOTE: This class is derived to add functionality for -// all other non-root items of FeedsModel. -class RootItem { - public: - // Describes the kind of the item. - enum Kind { - Root = 1001, - Bin = 1002, - Feeed = 1003, - Cattegory = 1004 - }; - - // Constructors and destructors. - explicit RootItem(RootItem *parent_item = NULL); - virtual ~RootItem(); - - // Basic operations. - inline virtual RootItem *parent() const { - return m_parentItem; - } - - inline virtual void setParent(RootItem *parent_item) { - m_parentItem = parent_item; - } - - inline virtual RootItem *child(int row) { - return m_childItems.value(row); - } - - virtual RootItem *child(RootItem::Kind kind_of_child, const QString &identifier); - - inline virtual int childCount() const { - return m_childItems.size(); - } - - inline virtual void appendChild(RootItem *child) { - m_childItems.append(child); - child->setParent(this); - } - - virtual int row() const; - virtual QVariant data(int column, int role) const; - - // Each item offers "counts" of messages. - // Returns counts of messages of all child items summed up. - virtual int countOfUnreadMessages() const; - virtual int countOfAllMessages() const; - - // This method is used to permanently - // "remove" (or "unregister") this item. - // This typically removes item and its - // "children" (for example messages or child feeds) - // from the database. - // Returns true if "I" was removed. - virtual bool removeItself() { - return false; - } - - // Access to children. - inline QList childItems() const { - return m_childItems; - } - - // Checks whether THIS object is child (direct or indirect) - // of the given root. - bool isChildOf(RootItem *root) { - if (root == NULL) { - return false; - } - - RootItem *this_item = this; - - while (this_item->kind() != RootItem::Root) { - if (root->childItems().contains(this_item)) { - return true; - } - else { - this_item = this_item->parent(); - } - } - - return false; - } - - bool isParentOf(RootItem *child) { - if (child == NULL) { - return false; - } - else { - return child->isChildOf(this); - } - } - - // Removes all children from this item. - // NOTE: Children are NOT freed from the memory. - inline void clearChildren() { - m_childItems.clear(); - } - - QList getRecursiveChildren(); - - // Removes particular child at given index. - // NOTE: Child is NOT freed from the memory. - bool removeChild(int index); - bool removeChild(RootItem *child); - - inline Kind kind() const { - return m_kind; - } - - // Each item has icon. - inline QIcon icon() const { - return m_icon; - } - - inline void setIcon(const QIcon &icon) { - m_icon = icon; - } - - // Each item has some kind of id. Usually taken from primary key attribute from DB. - inline int id() const { - return m_id; - } - - inline void setId(int id) { - m_id = id; - } - - // Each item has its title. - inline QString title() const { - return m_title; - } - - inline void setTitle(const QString &title) { - m_title = title; - } - - inline QDateTime creationDate() const { - return m_creationDate; - } - - inline void setCreationDate(const QDateTime &creation_date) { - m_creationDate = creation_date; - } - - inline QString description() const { - return m_description; - } - - inline void setDescription(const QString &description) { - m_description = description; - } - - // Converters - RecycleBin *toRecycleBin(); - Category *toCategory(); - Feed *toFeed(); - - // Compares two model items. - static bool isEqual(RootItem *lhs, RootItem *rhs); - static bool lessThan(RootItem *lhs, RootItem *rhs); - - protected: - void setupFonts(); - - Kind m_kind; - int m_id; - QString m_title; - QString m_description; - QIcon m_icon; - QDateTime m_creationDate; - - QFont m_normalFont; - QFont m_boldFont; - - QList m_childItems; - RootItem *m_parentItem; -}; - -#endif // ROOTITEM_H diff --git a/src/definitions/definitions.h.in b/src/definitions/definitions.h.in index adc147a8d..6cf56098b 100755 --- a/src/definitions/definitions.h.in +++ b/src/definitions/definitions.h.in @@ -38,6 +38,9 @@ #define APP_USERAGENT QString("@APP_NAME@/@APP_VERSION@ (@APP_URL@) on @CMAKE_SYSTEM@") #define APP_DONATE_URL "https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XMWPLPK893VH4" +#define SERVICE_CODE_STD_RSS "std-rss" +#define SERVICE_CODE_TT_RSS "tt-rss" + #define ENCLOSURES_OUTER_SEPARATOR '#' #define ECNLOSURES_INNER_SEPARATOR '&' #define URI_SCHEME_FEED "feed://" @@ -50,6 +53,7 @@ #define URL_REGEXP "^(http|https|feed|ftp):\\/\\/[\\w\\-_]+(\\.[\\w\\-_]+)+([\\w\\-\\.,@?^=%&:/~\\+#]*[\\w\\-\\@?^=%&/~\\+#])?$" #define USER_AGENT_HTTP_HEADER "User-Agent" #define TEXT_TITLE_LIMIT 30 +#define RESELECT_MESSAGE_THRESSHOLD 500 #define MAX_ZOOM_FACTOR 10.0 #define ICON_SIZE_SETTINGS 16 #define NO_PARENT_CATEGORY -1 @@ -68,7 +72,7 @@ #define INTERNAL_URL_BLANK "about:blank" #define DEFAULT_AUTO_UPDATE_INTERVAL 15 #define AUTO_UPDATE_INTERVAL 60000 -#define STARTUP_UPDATE_DELAY 15000 +#define STARTUP_UPDATE_DELAY 30000 #define TIMEZONE_OFFSET_LIMIT 6 #define CHANGE_EVENT_DELAY 250 #define FLAG_ICON_SUBFOLDER "flags" @@ -82,10 +86,11 @@ #define ACCEPT_HEADER_FOR_FEED_DOWNLOADER "application/atom+xml,application/xml;q=0.9,text/xml;q=0.8,*/*;q=0.7" #define MIME_TYPE_ITEM_POINTER "@APP_LOW_NAME@/itempointer" #define DOWNLOADER_ICON_SIZE 48 -#define NOTIFICATION_ICON_SIZE 64 +#define NOTIFICATION_ICON_SIZE 32 #define GOOGLE_SEARCH_URL "https://www.google.com/search?q=%1&ie=utf-8&oe=utf-8" #define GOOGLE_SUGGEST_URL "http://suggestqueries.google.com/complete/search?output=toolbar&hl=en&q=%1" #define ENCRYPTION_FILE_NAME "key.private" +#define RELOAD_MODEL_BORDER_NUM 10 #define FEED_INITIAL_OPML_PATTERN "feeds-%1.opml" @@ -116,7 +121,7 @@ #define APP_DB_SQLITE_FILE "database.db" // Keep this in sync with schema versions declared in SQL initialization code. -#define APP_DB_SCHEMA_VERSION "3" +#define APP_DB_SCHEMA_VERSION "4" #define APP_DB_UPDATE_FILE_PATTERN "db_update_%1_%2_%3.sql" #define APP_DB_COMMENT_SPLIT "-- !\n" #define APP_DB_WEB_PATH "data/database/web" @@ -179,6 +184,8 @@ #define MSG_DB_CONTENTS_INDEX 9 #define MSG_DB_PDELETED_INDEX 10 #define MSG_DB_ENCLOSURES_INDEX 11 +#define MSG_DB_ACCOUNT_ID_INDEX 12 +#define MSG_DB_CUSTOM_ID_INDEX 13 // Indexes of columns as they are DEFINED IN THE TABLE for CATEGORIES. #define CAT_DB_ID_INDEX 0 @@ -187,6 +194,8 @@ #define CAT_DB_DESCRIPTION_INDEX 3 #define CAT_DB_DCREATED_INDEX 4 #define CAT_DB_ICON_INDEX 5 +#define CAT_DB_ACCOUNT_ID_INDEX 6 +#define CAT_DB_CUSTOM_ID_INDEX 7 // Indexes of columns as they are DEFINED IN THE TABLE for FEEDS. #define FDS_DB_ID_INDEX 0 @@ -203,6 +212,8 @@ #define FDS_DB_UPDATE_TYPE_INDEX 11 #define FDS_DB_UPDATE_INTERVAL_INDEX 12 #define FDS_DB_TYPE_INDEX 13 +#define FDS_DB_ACCOUNT_ID_INDEX 14 +#define FDS_DB_CUSTOM_ID_INDEX 15 // Indexes of columns for feed models. #define FDS_MODEL_TITLE_INDEX 0 diff --git a/src/dynamic-shortcuts/dynamicshortcutswidget.cpp b/src/dynamic-shortcuts/dynamicshortcutswidget.cpp index 12ad5fac8..73356fa42 100755 --- a/src/dynamic-shortcuts/dynamicshortcutswidget.cpp +++ b/src/dynamic-shortcuts/dynamicshortcutswidget.cpp @@ -71,7 +71,7 @@ void DynamicShortcutsWidget::populate(QList actions) { int row_id = 0; - // TODO: Maybe separate actions into "categories". Each category will start with label. + // FIXME: Maybe separate actions into "categories". Each category will start with label. // I will assign each QAaction a property called "category" with some enum value. // Like: // File, FeedsCategories, Messages, Tools, WebBrowser, Help diff --git a/src/gui/dialogs/formabout.cpp b/src/gui/dialogs/formabout.cpp index 8cce70b07..0df95c066 100755 --- a/src/gui/dialogs/formabout.cpp +++ b/src/gui/dialogs/formabout.cpp @@ -126,7 +126,7 @@ void FormAbout::loadLicenseAndInformation() { m_ui->m_txtInfo->setText(tr("%5 is a (very) tiny feed reader." "

This software is distributed under the terms of GNU General Public License, version 3." "

Contacts:" - "
  • %1 ~email
  • " + "
    • %1 ~e-mail
    • " "
    • %2 ~website
    " "You can obtain source code for %5 from its website." "


    Copyright (C) 2011-%3 %4").arg(APP_EMAIL, diff --git a/src/gui/dialogs/formabout.ui b/src/gui/dialogs/formabout.ui old mode 100644 new mode 100755 index 696b2d41e..c30ecdfc5 --- a/src/gui/dialogs/formabout.ui +++ b/src/gui/dialogs/formabout.ui @@ -104,7 +104,7 @@ - 3 + 2 @@ -166,8 +166,8 @@ p, li { white-space: pre-wrap; } 0 0 - 685 - 184 + 98 + 69 @@ -242,7 +242,7 @@ p, li { white-space: pre-wrap; } 0 0 - 83 + 98 69 diff --git a/src/gui/dialogs/formaddaccount.cpp b/src/gui/dialogs/formaddaccount.cpp new file mode 100755 index 000000000..d191d9128 --- /dev/null +++ b/src/gui/dialogs/formaddaccount.cpp @@ -0,0 +1,91 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "gui/dialogs/formaddaccount.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "core/feedsmodel.h" +#include "services/standard/standardserviceentrypoint.h" + +#if defined(Q_OS_OS2) +#include "gui/messagebox.h" +#endif + + +FormAddAccount::FormAddAccount(const QList &entry_points, FeedsModel *model, QWidget *parent) + : QDialog(parent), m_ui(new Ui::FormAddAccount), m_model(model), m_entryPoints(entry_points) { + m_ui->setupUi(this); + + // Set flags and attributes. + setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::Dialog | Qt::WindowSystemMenuHint); + setWindowIcon(qApp->icons()->fromTheme(QSL("item-new"))); + +#if defined(Q_OS_OS2) + MessageBox::iconify(m_ui->m_buttonBox); +#endif + + connect(m_ui->m_listEntryPoints, SIGNAL(itemDoubleClicked(QListWidgetItem*)), this, SLOT(addSelectedAccount())); + connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(addSelectedAccount())); + connect(m_ui->m_listEntryPoints, SIGNAL(itemSelectionChanged()), this, SLOT(displayActiveEntryPointDetails())); + loadEntryPoints(); +} + +FormAddAccount::~FormAddAccount() { + delete m_ui; +} + +void FormAddAccount::addSelectedAccount() { + accept(); + + ServiceEntryPoint *point = selectedEntryPoint(); + ServiceRoot *new_root = point->createNewRoot(); + + if (new_root != NULL) { + m_model->addServiceAccount(new_root); + } + else { + qCritical("Cannot create new account."); + } +} + +void FormAddAccount::displayActiveEntryPointDetails() { + ServiceEntryPoint *point = selectedEntryPoint(); + + m_ui->m_txtAuthor->setText(point->author()); + m_ui->m_txtDescription->setText(point->description()); + m_ui->m_txtName->setText(point->name()); + m_ui->m_txtVersion->setText(point->version()); +} + +ServiceEntryPoint *FormAddAccount::selectedEntryPoint() { + return m_entryPoints.at(m_ui->m_listEntryPoints->currentRow()); +} + +void FormAddAccount::loadEntryPoints() { + foreach (ServiceEntryPoint *entry_point, m_entryPoints) { + QListWidgetItem *item = new QListWidgetItem(entry_point->icon(), entry_point->name(), m_ui->m_listEntryPoints); + + if (entry_point->isSingleInstanceService() && m_model->containsServiceRootFromEntryPoint(entry_point)) { + // Oops, this item cannot be added, it is single instance and is already added. + item->setFlags(Qt::NoItemFlags); + item->setToolTip(tr("This account can be added only once.")); + } + } + + m_ui->m_listEntryPoints->setCurrentRow(m_entryPoints.size() - 1); +} diff --git a/src/gui/dialogs/formaddaccount.h b/src/gui/dialogs/formaddaccount.h new file mode 100755 index 000000000..bacf59892 --- /dev/null +++ b/src/gui/dialogs/formaddaccount.h @@ -0,0 +1,53 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef FORMADDACCOUNT_H +#define FORMADDACCOUNT_H + +#include + +#include "ui_formaddaccount.h" + + +namespace Ui { + class FormAddAccount; +} + +class ServiceEntryPoint; +class FeedsModel; + +class FormAddAccount : public QDialog { + Q_OBJECT + + public: + explicit FormAddAccount(const QList &entry_points, FeedsModel *model, QWidget *parent = 0); + virtual ~FormAddAccount(); + + private slots: + void addSelectedAccount(); + void displayActiveEntryPointDetails(); + + private: + ServiceEntryPoint *selectedEntryPoint(); + void loadEntryPoints(); + + Ui::FormAddAccount *m_ui; + FeedsModel *m_model; + QList m_entryPoints; +}; + +#endif // FORMADDACCOUNT_H diff --git a/src/gui/dialogs/formaddaccount.ui b/src/gui/dialogs/formaddaccount.ui new file mode 100755 index 000000000..cdb35edbe --- /dev/null +++ b/src/gui/dialogs/formaddaccount.ui @@ -0,0 +1,144 @@ + + + FormAddAccount + + + + 0 + 0 + 685 + 271 + + + + Add new account + + + + + + + 0 + 1 + + + + + 320 + 0 + + + + QListView::Adjust + + + + + + + + 0 + 1 + + + + Details + + + + + + Name + + + + + + + true + + + + + + + Version + + + + + + + true + + + + + + + Author + + + + + + + true + + + + + + + Description + + + + + + + + 0 + 1 + + + + true + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + m_buttonBox + rejected() + FormAddAccount + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/gui/dialogs/formmain.cpp b/src/gui/dialogs/formmain.cpp index 35ef10a99..98e1b6c60 100755 --- a/src/gui/dialogs/formmain.cpp +++ b/src/gui/dialogs/formmain.cpp @@ -36,10 +36,13 @@ #include "gui/dialogs/formabout.h" #include "gui/dialogs/formsettings.h" #include "gui/dialogs/formupdate.h" -#include "gui/dialogs/formimportexport.h" #include "gui/dialogs/formbackupdatabasesettings.h" #include "gui/dialogs/formrestoredatabasesettings.h" +#include "gui/dialogs/formaddaccount.h" #include "gui/notifications/notification.h" +#include "services/abstract/serviceroot.h" +#include "services/abstract/recyclebin.h" +#include "services/standard/gui/formstandardimportexport.h" #include #include @@ -91,8 +94,6 @@ QList FormMain::allActions() { // Add basic actions. actions << m_ui->m_actionSettings; actions << m_ui->m_actionDownloadManager; - actions << m_ui->m_actionImportFeeds; - actions << m_ui->m_actionExportFeeds; actions << m_ui->m_actionRestoreDatabaseSettings; actions << m_ui->m_actionBackupDatabaseSettings; actions << m_ui->m_actionRestart; @@ -104,6 +105,7 @@ QList FormMain::allActions() { actions << m_ui->m_actionSwitchMainMenu; actions << m_ui->m_actionSwitchToolBars; actions << m_ui->m_actionSwitchListHeaders; + actions << m_ui->m_actionSwitchStatusBar; actions << m_ui->m_actionSwitchMessageListOrientation; // Add web browser actions @@ -115,32 +117,28 @@ QList FormMain::allActions() { actions << m_ui->m_actionOpenSelectedSourceArticlesExternally; actions << m_ui->m_actionOpenSelectedSourceArticlesInternally; actions << m_ui->m_actionOpenSelectedMessagesInternally; - actions << m_ui->m_actionMarkAllFeedsRead; - actions << m_ui->m_actionMarkSelectedFeedsAsRead; - actions << m_ui->m_actionMarkSelectedFeedsAsUnread; - actions << m_ui->m_actionClearSelectedFeeds; + actions << m_ui->m_actionMarkAllItemsRead; + actions << m_ui->m_actionMarkSelectedItemsAsRead; + actions << m_ui->m_actionMarkSelectedItemsAsUnread; + actions << m_ui->m_actionClearSelectedItems; + actions << m_ui->m_actionClearAllItems; + actions << m_ui->m_actionShowOnlyUnreadItems; actions << m_ui->m_actionMarkSelectedMessagesAsRead; actions << m_ui->m_actionMarkSelectedMessagesAsUnread; actions << m_ui->m_actionSwitchImportanceOfSelectedMessages; actions << m_ui->m_actionDeleteSelectedMessages; - actions << m_ui->m_actionUpdateAllFeeds; - actions << m_ui->m_actionUpdateSelectedFeeds; - actions << m_ui->m_actionEditSelectedFeedCategory; - actions << m_ui->m_actionDeleteSelectedFeedCategory; + actions << m_ui->m_actionUpdateAllItems; + actions << m_ui->m_actionUpdateSelectedItems; + actions << m_ui->m_actionEditSelectedItem; + actions << m_ui->m_actionDeleteSelectedItem; + actions << m_ui->m_actionServiceAdd; actions << m_ui->m_actionViewSelectedItemsNewspaperMode; - actions << m_ui->m_actionAddCategory; - actions << m_ui->m_actionAddFeed; - actions << m_ui->m_actionSelectNextFeedCategory; - actions << m_ui->m_actionSelectPreviousFeedCategory; + actions << m_ui->m_actionSelectNextItem; + actions << m_ui->m_actionSelectPreviousItem; actions << m_ui->m_actionSelectNextMessage; actions << m_ui->m_actionSelectPreviousMessage; - actions << m_ui->m_actionFetchFeedMetadata; - actions << m_ui->m_actionExpandCollapseFeedCategory; - - // Add recycle bin actions. - actions << m_ui->m_actionRestoreRecycleBin; - actions << m_ui->m_actionEmptyRecycleBin; - actions << m_ui->m_actionRestoreSelectedMessagesFromRecycleBin; + actions << m_ui->m_actionSelectNextUnreadMessage; + actions << m_ui->m_actionExpandCollapseItem; return actions; } @@ -157,8 +155,8 @@ void FormMain::prepareMenus() { // Add needed items to the menu. m_trayMenu->addAction(m_ui->m_actionSwitchMainWindow); m_trayMenu->addSeparator(); - m_trayMenu->addAction(m_ui->m_actionUpdateAllFeeds); - m_trayMenu->addAction(m_ui->m_actionMarkAllFeedsRead); + m_trayMenu->addAction(m_ui->m_actionUpdateAllItems); + m_trayMenu->addAction(m_ui->m_actionMarkAllItemsRead); m_trayMenu->addSeparator(); m_trayMenu->addAction(m_ui->m_actionSettings); m_trayMenu->addAction(m_ui->m_actionQuit); @@ -175,8 +173,106 @@ void FormMain::switchFullscreenMode() { } } -void FormMain::switchMainMenu() { - m_ui->m_menuBar->setVisible(m_ui->m_actionSwitchMainMenu->isChecked()); +void FormMain::updateAddItemMenu() { + // NOTE: Clear here deletes items from memory but only those OWNED by the menu. + m_ui->m_menuAddItem->clear(); + + foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) { + QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuAddItem); + root_menu->setIcon(activated_root->icon()); + root_menu->setToolTip(activated_root->description()); + + QList root_actions = activated_root->addItemMenu(); + + if (root_actions.isEmpty()) { + QAction *no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")), + tr("No possible actions"), + m_ui->m_menuAddItem); + no_action->setEnabled(false); + root_menu->addAction(no_action); + } + else { + root_menu->addActions(root_actions); + } + + m_ui->m_menuAddItem->addMenu(root_menu); + } +} + +void FormMain::updateRecycleBinMenu() { + m_ui->m_menuRecycleBin->clear(); + + foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) { + QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuRecycleBin); + root_menu->setIcon(activated_root->icon()); + root_menu->setToolTip(activated_root->description()); + + RecycleBin *bin = activated_root->recycleBin(); + + if (bin == NULL) { + QAction *no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")), + tr("No recycle bin"), + m_ui->m_menuRecycleBin); + no_action->setEnabled(false); + root_menu->addAction(no_action); + } + else { + QAction *restore_action = new QAction(qApp->icons()->fromTheme(QSL("recycle-bin-restore-all")), + tr("Restore recycle bin"), + m_ui->m_menuRecycleBin); + QAction *empty_action = new QAction(qApp->icons()->fromTheme(QSL("recycle-bin-empty")), + tr("Empty recycle bin"), + m_ui->m_menuRecycleBin); + + connect(restore_action, SIGNAL(triggered()), bin, SLOT(restore())); + connect(empty_action, SIGNAL(triggered()), bin, SLOT(empty())); + + root_menu->addAction(restore_action); + root_menu->addAction(empty_action); + } + + m_ui->m_menuRecycleBin->addMenu(root_menu); + } + + if (!m_ui->m_menuRecycleBin->isEmpty()) { + m_ui->m_menuRecycleBin->addSeparator(); + } + + m_ui->m_menuRecycleBin->addAction(m_ui->m_actionRestoreAllRecycleBins); + m_ui->m_menuRecycleBin->addAction(m_ui->m_actionEmptyAllRecycleBins); +} + +void FormMain::updateAccountsMenu() { + m_ui->m_menuAccounts->clear(); + + foreach (ServiceRoot *activated_root, tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->serviceRoots()) { + QMenu *root_menu = new QMenu(activated_root->title(), m_ui->m_menuAccounts); + root_menu->setIcon(activated_root->icon()); + root_menu->setToolTip(activated_root->description()); + + QList root_actions = activated_root->serviceMenu(); + + if (root_actions.isEmpty()) { + QAction *no_action = new QAction(qApp->icons()->fromTheme(QSL("dialog-error")), + tr("No possible actions"), + m_ui->m_menuAccounts); + no_action->setEnabled(false); + root_menu->addAction(no_action); + } + else { + root_menu->addActions(root_actions); + } + + m_ui->m_menuAccounts->addMenu(root_menu); + } + + if (m_ui->m_menuAccounts->actions().size() > 0) { + m_ui->m_menuAccounts->addSeparator(); + } + + m_ui->m_menuAccounts->addAction(m_ui->m_actionServiceAdd); + m_ui->m_menuAccounts->addAction(m_ui->m_actionServiceEdit); + m_ui->m_menuAccounts->addAction(m_ui->m_actionServiceDelete); } void FormMain::switchVisibility(bool force_hide) { @@ -220,8 +316,6 @@ void FormMain::setupIcons() { m_ui->m_actionCleanupDatabase->setIcon(icon_theme_factory->fromTheme(QSL("cleanup-database"))); m_ui->m_actionReportBugGitHub->setIcon(icon_theme_factory->fromTheme(QSL("application-report-bug"))); m_ui->m_actionReportBugBitBucket->setIcon(icon_theme_factory->fromTheme(QSL("application-report-bug"))); - m_ui->m_actionExportFeeds->setIcon(icon_theme_factory->fromTheme(QSL("document-export"))); - m_ui->m_actionImportFeeds->setIcon(icon_theme_factory->fromTheme(QSL("document-import"))); m_ui->m_actionBackupDatabaseSettings->setIcon(icon_theme_factory->fromTheme(QSL("document-export"))); m_ui->m_actionRestoreDatabaseSettings->setIcon(icon_theme_factory->fromTheme(QSL("document-import"))); m_ui->m_actionDonate->setIcon(icon_theme_factory->fromTheme(QSL("application-donate"))); @@ -234,14 +328,10 @@ void FormMain::setupIcons() { m_ui->m_actionSwitchMainMenu->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-menu"))); m_ui->m_actionSwitchToolBars->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-list"))); m_ui->m_actionSwitchListHeaders->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-list"))); + m_ui->m_actionSwitchStatusBar->setIcon(icon_theme_factory->fromTheme(QSL("dialog-information"))); m_ui->m_actionSwitchMessageListOrientation->setIcon(icon_theme_factory->fromTheme(QSL("view-switch-layout-direction"))); m_ui->m_menuShowHide->setIcon(icon_theme_factory->fromTheme(QSL("view-switch"))); - // Recycle bin. - m_ui->m_actionEmptyRecycleBin->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-empty"))); - m_ui->m_actionRestoreRecycleBin->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-all"))); - m_ui->m_actionRestoreSelectedMessagesFromRecycleBin->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-one"))); - // Web browser. m_ui->m_actionAddBrowser->setIcon(icon_theme_factory->fromTheme(QSL("list-add"))); m_ui->m_actionCloseCurrentTab->setIcon(icon_theme_factory->fromTheme(QSL("list-remove"))); @@ -254,18 +344,16 @@ void FormMain::setupIcons() { // Feeds/messages. m_ui->m_menuAddItem->setIcon(icon_theme_factory->fromTheme(QSL("item-new"))); - m_ui->m_actionUpdateAllFeeds->setIcon(icon_theme_factory->fromTheme(QSL("item-update-all"))); - m_ui->m_actionUpdateSelectedFeeds->setIcon(icon_theme_factory->fromTheme(QSL("item-update-selected"))); - m_ui->m_actionClearSelectedFeeds->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove"))); - m_ui->m_actionClearAllFeeds->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove"))); - m_ui->m_actionDeleteSelectedFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("item-remove"))); + m_ui->m_actionUpdateAllItems->setIcon(icon_theme_factory->fromTheme(QSL("item-update-all"))); + m_ui->m_actionUpdateSelectedItems->setIcon(icon_theme_factory->fromTheme(QSL("item-update-selected"))); + m_ui->m_actionClearSelectedItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove"))); + m_ui->m_actionClearAllItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove"))); + m_ui->m_actionDeleteSelectedItem->setIcon(icon_theme_factory->fromTheme(QSL("item-remove"))); m_ui->m_actionDeleteSelectedMessages->setIcon(icon_theme_factory->fromTheme(QSL("mail-remove"))); - m_ui->m_actionAddCategory->setIcon(icon_theme_factory->fromTheme(QSL("folder-category"))); - m_ui->m_actionAddFeed->setIcon(icon_theme_factory->fromTheme(QSL("folder-feed"))); - m_ui->m_actionEditSelectedFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("item-edit"))); - m_ui->m_actionMarkAllFeedsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read"))); - m_ui->m_actionMarkSelectedFeedsAsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read"))); - m_ui->m_actionMarkSelectedFeedsAsUnread->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread"))); + m_ui->m_actionEditSelectedItem->setIcon(icon_theme_factory->fromTheme(QSL("item-edit"))); + m_ui->m_actionMarkAllItemsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read"))); + m_ui->m_actionMarkSelectedItemsAsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read"))); + m_ui->m_actionMarkSelectedItemsAsUnread->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread"))); m_ui->m_actionMarkSelectedMessagesAsRead->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-read"))); m_ui->m_actionMarkSelectedMessagesAsUnread->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread"))); m_ui->m_actionSwitchImportanceOfSelectedMessages->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-favorite"))); @@ -274,13 +362,19 @@ void FormMain::setupIcons() { m_ui->m_actionOpenSelectedMessagesInternally->setIcon(icon_theme_factory->fromTheme(QSL("item-open-internal"))); m_ui->m_actionSendMessageViaEmail->setIcon(icon_theme_factory->fromTheme(QSL("item-send-email"))); m_ui->m_actionViewSelectedItemsNewspaperMode->setIcon(icon_theme_factory->fromTheme(QSL("item-newspaper"))); - m_ui->m_actionSelectNextFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("go-down"))); - m_ui->m_actionSelectPreviousFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("go-up"))); + m_ui->m_actionSelectNextItem->setIcon(icon_theme_factory->fromTheme(QSL("go-down"))); + m_ui->m_actionSelectPreviousItem->setIcon(icon_theme_factory->fromTheme(QSL("go-up"))); m_ui->m_actionSelectNextMessage->setIcon(icon_theme_factory->fromTheme(QSL("go-down"))); m_ui->m_actionSelectPreviousMessage->setIcon(icon_theme_factory->fromTheme(QSL("go-up"))); - m_ui->m_actionShowOnlyUnreadFeeds->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread"))); - m_ui->m_actionFetchFeedMetadata->setIcon(icon_theme_factory->fromTheme(QSL("download-manager"))); - m_ui->m_actionExpandCollapseFeedCategory->setIcon(icon_theme_factory->fromTheme(QSL("expand-collapse"))); + m_ui->m_actionSelectNextUnreadMessage->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread"))); + m_ui->m_actionShowOnlyUnreadItems->setIcon(icon_theme_factory->fromTheme(QSL("mail-mark-unread"))); + m_ui->m_actionExpandCollapseItem->setIcon(icon_theme_factory->fromTheme(QSL("expand-collapse"))); + m_ui->m_actionRestoreSelectedMessages->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-one"))); + m_ui->m_actionRestoreAllRecycleBins->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-restore-all"))); + m_ui->m_actionEmptyAllRecycleBins->setIcon(icon_theme_factory->fromTheme(QSL("recycle-bin-empty"))); + m_ui->m_actionServiceAdd->setIcon(icon_theme_factory->fromTheme(QSL("item-new"))); + m_ui->m_actionServiceEdit->setIcon(icon_theme_factory->fromTheme(QSL("item-edit"))); + m_ui->m_actionServiceDelete->setIcon(icon_theme_factory->fromTheme(QSL("item-remove"))); // Setup icons for underlying components: opened web browsers... foreach (WebBrowser *browser, WebBrowser::runningWebBrowsers()) { @@ -319,9 +413,10 @@ void FormMain::loadSize() { m_ui->m_tabWidget->feedMessageViewer()->loadSize(); m_ui->m_actionSwitchToolBars->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ToolbarsVisible)).toBool()); m_ui->m_actionSwitchListHeaders->setChecked(settings->value(GROUP(GUI), SETTING(GUI::ListHeadersVisible)).toBool()); + m_ui->m_actionSwitchStatusBar->setChecked(settings->value(GROUP(GUI), SETTING(GUI::StatusBarVisible)).toBool()); // Make sure that only unread feeds are shown if user has that feature set on. - m_ui->m_actionShowOnlyUnreadFeeds->setChecked(settings->value(GROUP(Feeds), SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool()); + m_ui->m_actionShowOnlyUnreadItems->setChecked(settings->value(GROUP(Feeds), SETTING(Feeds::ShowOnlyUnreadFeeds)).toBool()); } void FormMain::saveSize() { @@ -342,6 +437,7 @@ void FormMain::saveSize() { settings->setValue(GROUP(GUI), GUI::MainWindowInitialSize, size()); settings->setValue(GROUP(GUI), GUI::MainWindowStartsMaximized, is_maximized); settings->setValue(GROUP(GUI), GUI::MainWindowStartsFullscreen, is_fullscreen); + settings->setValue(GROUP(GUI), GUI::StatusBarVisible, m_ui->m_actionSwitchStatusBar->isChecked()); m_ui->m_tabWidget->feedMessageViewer()->saveSize(); } @@ -351,18 +447,25 @@ void FormMain::createConnections() { connect(m_statusBar->fullscreenSwitcher(), SIGNAL(toggled(bool)), m_ui->m_actionFullscreen, SLOT(setChecked(bool))); connect(m_ui->m_actionFullscreen, SIGNAL(toggled(bool)), m_statusBar->fullscreenSwitcher(), SLOT(setChecked(bool))); + connect(m_ui->m_menuAddItem, SIGNAL(aboutToShow()), this, SLOT(updateAddItemMenu())); + connect(m_ui->m_menuRecycleBin, SIGNAL(aboutToShow()), this, SLOT(updateRecycleBinMenu())); + connect(m_ui->m_menuAccounts, SIGNAL(aboutToShow()), this, SLOT(updateAccountsMenu())); + + connect(m_ui->m_actionServiceDelete, SIGNAL(triggered()), m_ui->m_actionDeleteSelectedItem, SIGNAL(triggered())); + connect(m_ui->m_actionServiceEdit, SIGNAL(triggered()), m_ui->m_actionEditSelectedItem, SIGNAL(triggered())); + // Menu "File" connections. - connect(m_ui->m_actionExportFeeds, SIGNAL(triggered()), this, SLOT(exportFeeds())); - connect(m_ui->m_actionImportFeeds, SIGNAL(triggered()), this, SLOT(importFeeds())); connect(m_ui->m_actionBackupDatabaseSettings, SIGNAL(triggered()), this, SLOT(backupDatabaseSettings())); connect(m_ui->m_actionRestoreDatabaseSettings, SIGNAL(triggered()), this, SLOT(restoreDatabaseSettings())); connect(m_ui->m_actionRestart, SIGNAL(triggered()), qApp, SLOT(restart())); connect(m_ui->m_actionQuit, SIGNAL(triggered()), qApp, SLOT(quit())); + connect(m_ui->m_actionServiceAdd, SIGNAL(triggered()), this, SLOT(showAddAccountDialog())); // Menu "View" connections. connect(m_ui->m_actionFullscreen, SIGNAL(toggled(bool)), this, SLOT(switchFullscreenMode())); - connect(m_ui->m_actionSwitchMainMenu, SIGNAL(toggled(bool)), this, SLOT(switchMainMenu())); + connect(m_ui->m_actionSwitchMainMenu, SIGNAL(toggled(bool)), m_ui->m_menuBar, SLOT(setVisible(bool))); connect(m_ui->m_actionSwitchMainWindow, SIGNAL(triggered()), this, SLOT(switchVisibility())); + connect(m_ui->m_actionSwitchStatusBar, SIGNAL(toggled(bool)), statusBar(), SLOT(setVisible(bool))); // Menu "Tools" connections. connect(m_ui->m_actionSettings, SIGNAL(triggered()), this, SLOT(showSettings())); @@ -410,20 +513,6 @@ void FormMain::loadWebBrowserMenu(int index) { m_ui->m_actionCloseCurrentTab->setEnabled(m_ui->m_tabWidget->tabBar()->tabType(index) == TabBar::Closable); } -void FormMain::exportFeeds() { - QPointer form = new FormImportExport(this); - form.data()->setMode(FeedsImportExportModel::Export); - form.data()->exec(); - delete form.data(); -} - -void FormMain::importFeeds() { - QPointer form = new FormImportExport(this); - form.data()->setMode(FeedsImportExportModel::Import); - form.data()->exec(); - delete form.data(); -} - void FormMain::backupDatabaseSettings() { QPointer form = new FormBackupDatabaseSettings(this); form.data()->exec(); @@ -476,6 +565,14 @@ void FormMain::showWiki() { } } +void FormMain::showAddAccountDialog() { + QPointer form_update = new FormAddAccount(qApp->feedServices(), + tabWidget()->feedMessageViewer()->feedsView()->sourceModel(), + this); + form_update.data()->exec(); + delete form_update.data(); +} + void FormMain::reportABugOnGitHub() { if (!WebFactory::instance()->openUrlInExternalBrowser(APP_URL_ISSUES_NEW_GITHUB)) { qApp->showGuiMessage(tr("Cannot open external browser"), diff --git a/src/gui/dialogs/formmain.h b/src/gui/dialogs/formmain.h index 39d2c62bf..a5e7dac7b 100755 --- a/src/gui/dialogs/formmain.h +++ b/src/gui/dialogs/formmain.h @@ -21,7 +21,6 @@ #include "ui_formmain.h" #include -#include class StatusBar; @@ -64,19 +63,6 @@ class FormMain : public QMainWindow { void loadSize(); void saveSize(); - protected: - // Creates all needed menus and sets them up. - void prepareMenus(); - - // Creates needed connections for this window. - void createConnections(); - - // Event handler reimplementations. - void changeEvent(QEvent *event); - - // Sets up proper icons for this widget. - void setupIcons(); - public slots: // Displays window on top or switches its visibility. void display(); @@ -87,27 +73,39 @@ class FormMain : public QMainWindow { // Turns on/off fullscreen mode void switchFullscreenMode(); - // Switches visibility of main menu. - void switchMainMenu(); + private slots: + void updateAddItemMenu(); + void updateRecycleBinMenu(); + void updateAccountsMenu(); - protected slots: // Loads web browser menu if user selects to change tabs. void loadWebBrowserMenu(int index); // Displays various dialogs. - void exportFeeds(); - void importFeeds(); void backupDatabaseSettings(); void restoreDatabaseSettings(); void showSettings(); void showAbout(); void showUpdates(); void showWiki(); + void showAddAccountDialog(); void reportABugOnGitHub(); void reportABugOnBitBucket(); void donate(); private: + // Event handler reimplementations. + void changeEvent(QEvent *event); + + // Creates all needed menus and sets them up. + void prepareMenus(); + + // Creates needed connections for this window. + void createConnections(); + + // Sets up proper icons for this widget. + void setupIcons(); + Ui::FormMain *m_ui; QMenu *m_trayMenu; StatusBar *m_statusBar; diff --git a/src/gui/dialogs/formmain.ui b/src/gui/dialogs/formmain.ui index 9aeaa3567..ecbb7f392 100755 --- a/src/gui/dialogs/formmain.ui +++ b/src/gui/dialogs/formmain.ui @@ -55,9 +55,6 @@ &File - - - @@ -88,6 +85,7 @@ + @@ -128,36 +126,33 @@ - Fee&ds && categories + Feeds && categories - Add &new feed/category + Add &new item - - - - + + - - - + + - - + + - - + + - - + + - - - + + + @@ -170,22 +165,32 @@ + + - &Recycle bin + &Recycle bin(s) - - - + + + + + + &Accounts + + + + + @@ -225,6 +230,9 @@ Displays extra info about this application. + + + QAction::AboutRole @@ -261,6 +269,9 @@ Close all tabs except current one. + + + @@ -269,109 +280,135 @@ Close current web browser tab. + + + - + - Update &all feeds + Update &all items Ctrl+Shift+U - + - Update &selected feeds + Update &selected items Ctrl+U - + - &Edit selected feed/category + &Edit selected item + + + - + - &Delete selected feed/category + &Delete selected item + + + Mark &selected messages as &read + + + Mark &selected messages as &unread + + + Switch &importance of selected messages - - - - &Mark selected feeds as read - - - Mark all messages (without message filters) from selected feeds as read. + + - + - &Mark selected feeds as unread + &Mark selected items as read - Mark all messages (without message filters) from selected feeds as unread. + Mark all messages (without message filters) from selected items as read. + + + + + + + + &Mark selected items as unread + + + Mark all messages (without message filters) from selected items as unread. + + + &Delete selected messages - - - - &Clean selected feeds - - - Deletes all messages from selected feeds. + + - + - New &feed + &Clean selected items - Add new feed. + Deletes all messages from selected items. + + + Open selected source articles in &external browser + + + Open selected messages in &internal browser + + + Open selected source articles in &internal browser - - - - New &category - - - Add new category. + + + + false + No actions available @@ -382,20 +419,26 @@ - + - &Mark all feeds as &read + &Mark all items as &read - Marks all messages in all feeds read. This does not take message filters into account. + Marks all messages in all items read. This does not take message filters into account. + + + - View selected feeds in &newspaper mode + View selected items in &newspaper mode - Displays all messages from selected feeds/categories in a new "newspaper mode" tab. Note that messages are not set as read automatically. + Displays all messages from selected item in a new "newspaper mode" tab. Note that messages are not set as read automatically. + + + @@ -408,6 +451,9 @@ Hides main window if it is visible and shows it if it is hidden. + + + @@ -423,28 +469,28 @@ L - + - &Clean all feeds + &Clean all items - Deletes all messages from all feeds. + Deletes all messages from all items. Ctrl+Shift+C - + - Select &next feed/category + Select &next item S - + - Select &previous feed/category + Select &previous item A @@ -473,6 +519,9 @@ Check if new update for the application is available for download. + + + @@ -563,22 +612,6 @@ H - - - &Import feeds - - - Imports feeds you want from selected file. - - - - - &Export feeds - - - Exports feeds you want to selected file. - - Report a bug (BitBucket)... @@ -591,41 +624,41 @@ &Donate via PayPal + + + Display &wiki - - - - &Empty recycle bin - - - - - &Restore all messages - - - - - Restore &selected messages + + &Restart + + + &Restore database/settings + + + &Backup database/settings + + + @@ -639,11 +672,17 @@ &Downloads + + + Send selected message via e-mail + + + @@ -653,33 +692,95 @@ Ctrl+Shift+Del - + true - Show only unread feeds/categories + Show only unread items - Ctrl+Shift+U + U - + - &Fetch feed metadata - - - Ctrl+Shift+F - - - - - &Expand/collapse selected feed/category + &Expand/collapse selected item E + + + &Add new account + + + + + + + + &Restore selected messages + + + + + + + + &Restore all recycle bins + + + + + + + + &Empty all recycle bins + + + + + + + + Select next &unread message + + + + + + + + true + + + true + + + Status bar + + + + + + + + &Edit selected account + + + + + + + + &Delete selected account + + + + + diff --git a/src/gui/dialogs/formsettings.cpp b/src/gui/dialogs/formsettings.cpp index d79214f26..181bd0c45 100755 --- a/src/gui/dialogs/formsettings.cpp +++ b/src/gui/dialogs/formsettings.cpp @@ -84,7 +84,7 @@ FormSettings::FormSettings(QWidget *parent) : QDialog(parent), m_ui(new Ui::Form << /*: Skin list name column. */ tr("Name") << /*: Version column of skin list. */ tr("Version") << tr("Author") - << tr("Email")); + << tr("E-mail")); #if QT_VERSION >= 0x050000 // Setup languages. @@ -732,6 +732,7 @@ void FormSettings::loadInterface() { m_ui->m_cmbNotificationPosition->addItem(tr("Bottom-right corner"), Qt::BottomRightCorner); m_ui->m_cmbNotificationPosition->addItem(tr("Top-right corner"), Qt::TopRightCorner); m_ui->m_cmbNotificationPosition->setCurrentIndex(m_ui->m_cmbNotificationPosition->findData(static_cast(settings->value(GROUP(GUI), SETTING(GUI::FancyNotificationsPosition)).toInt()))); + m_ui->m_grpBaseNotifications->setChecked(settings->value(GROUP(GUI), SETTING(GUI::EnableNotifications)).toBool()); // Load settings of icon theme. QString current_theme = qApp->icons()->currentIconTheme(); @@ -834,6 +835,7 @@ void FormSettings::saveInterface() { // Save notifications. settings->setValue(GROUP(GUI), GUI::UseFancyNotifications, m_ui->m_grpNotifications->isChecked()); + settings->setValue(GROUP(GUI), GUI::EnableNotifications, m_ui->m_grpBaseNotifications->isChecked()); settings->setValue(GROUP(GUI), GUI::FancyNotificationsPosition, static_cast(m_ui->m_cmbNotificationPosition->itemData(m_ui->m_cmbNotificationPosition->currentIndex()).toInt())); // Save selected icon theme. diff --git a/src/gui/dialogs/formsettings.h b/src/gui/dialogs/formsettings.h old mode 100644 new mode 100755 index 810dd3cbe..cdaa14663 --- a/src/gui/dialogs/formsettings.h +++ b/src/gui/dialogs/formsettings.h @@ -48,10 +48,9 @@ class FormSettings : public QDialog { protected: // Does check of controls before dialog can be submitted. bool doSaveCheck(); - bool eventFilter(QObject *obj, QEvent *e); - protected slots: + private slots: // Displays "restart" dialog if some critical settings changed. void promptForRestart(); diff --git a/src/gui/dialogs/formsettings.ui b/src/gui/dialogs/formsettings.ui index c55ccd4d6..d4f2f5e96 100755 --- a/src/gui/dialogs/formsettings.ui +++ b/src/gui/dialogs/formsettings.ui @@ -88,7 +88,7 @@ - 3 + 1 @@ -636,24 +636,39 @@ Authors of this application are NOT responsible for lost data. - + - Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) + Enable notifications + + + false true - - - - - Notification position + + + + + Fancy && modern popup notifications (This uses OS native notifications via D-Bus if available.) + + true + + + + + + Notification position + + + + + + + - - - @@ -1765,5 +1780,21 @@ Authors of this application are NOT responsible for lost data. + + m_grpBaseNotifications + toggled(bool) + m_grpNotifications + setEnabled(bool) + + + 624 + 82 + + + 624 + 89 + + + diff --git a/src/gui/feedmessageviewer.cpp b/src/gui/feedmessageviewer.cpp index 28639c7cd..f9e7be57b 100755 --- a/src/gui/feedmessageviewer.cpp +++ b/src/gui/feedmessageviewer.cpp @@ -25,9 +25,10 @@ #include "miscellaneous/databasecleaner.h" #include "core/messagesproxymodel.h" #include "core/feeddownloader.h" -#include "core/feed.h" -#include "core/feedsselection.h" -#include "core/feedsimportexportmodel.h" +#include "core/feedsproxymodel.h" +#include "services/standard/standardserviceroot.h" +#include "services/standard/standardfeed.h" +#include "services/standard/standardfeedsimportexportmodel.h" #include "network-web/webbrowser.h" #include "gui/messagesview.h" #include "gui/feedsview.h" @@ -63,60 +64,36 @@ FeedMessageViewer::FeedMessageViewer(QWidget *parent) m_toolBarMessages(new MessagesToolBar(tr("Toolbar for messages"), this)), m_messagesView(new MessagesView(this)), m_feedsView(new FeedsView(this)), - m_messagesBrowser(new WebBrowser(this)), - m_feedDownloaderThread(NULL), - m_dbCleanerThread(NULL), - m_feedDownloader(NULL), - m_dbCleaner(NULL) { + m_messagesBrowser(new WebBrowser(this)) { initialize(); initializeViews(); loadMessageViewerFonts(); createConnections(); - - // Now, update all feeds if user has set it. - m_feedsView->updateAllFeedsOnStartup(); } FeedMessageViewer::~FeedMessageViewer() { qDebug("Destroying FeedMessageViewer instance."); } -DatabaseCleaner *FeedMessageViewer::databaseCleaner() { - if (m_dbCleaner == NULL) { - m_dbCleaner = new DatabaseCleaner(); - m_dbCleanerThread = new QThread(); - - // Downloader setup. - qRegisterMetaType("CleanerOrders"); - m_dbCleaner->moveToThread(m_dbCleanerThread); - connect(m_dbCleanerThread, SIGNAL(finished()), m_dbCleanerThread, SLOT(deleteLater())); - - // Connections are made, start the feed downloader thread. - m_dbCleanerThread->start(); - } - - return m_dbCleaner; -} - void FeedMessageViewer::saveSize() { Settings *settings = qApp->settings(); - + m_feedsView->saveExpandedStates(); - + // Store offsets of splitters. settings->setValue(GROUP(GUI), GUI::SplitterFeeds, QString(m_feedSplitter->saveState().toBase64())); settings->setValue(GROUP(GUI), GUI::SplitterMessages, QString(m_messageSplitter->saveState().toBase64())); - + // States of splitters are stored, let's store // widths of columns. int width_column_author = m_messagesView->columnWidth(MSG_DB_AUTHOR_INDEX); int width_column_date = m_messagesView->columnWidth(MSG_DB_DCREATED_INDEX); - + if (width_column_author != 0 && width_column_date != 0) { settings->setValue(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_AUTHOR_INDEX), width_column_author); settings->setValue(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_DCREATED_INDEX), width_column_date); } - + // Store "visibility" of toolbars and list headers. settings->setValue(GROUP(GUI), GUI::ToolbarsVisible, m_toolBarsEnabled); settings->setValue(GROUP(GUI), GUI::ListHeadersVisible, m_listHeadersEnabled); @@ -125,13 +102,11 @@ void FeedMessageViewer::saveSize() { void FeedMessageViewer::loadSize() { Settings *settings = qApp->settings(); int default_msg_section_size = m_messagesView->header()->defaultSectionSize(); - - m_feedsView->loadExpandedStates(); - + // Restore offsets of splitters. m_feedSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterFeeds)).toString().toLocal8Bit())); m_messageSplitter->restoreState(QByteArray::fromBase64(settings->value(GROUP(GUI), SETTING(GUI::SplitterMessages)).toString().toLocal8Bit())); - + // Splitters are restored, now, restore widths of columns. m_messagesView->setColumnWidth(MSG_DB_AUTHOR_INDEX, settings->value(GROUP(GUI), KEY_MESSAGES_VIEW + QString::number(MSG_DB_AUTHOR_INDEX), @@ -144,7 +119,7 @@ void FeedMessageViewer::loadSize() { void FeedMessageViewer::loadMessageViewerFonts() { Settings *settings = qApp->settings(); QWebSettings *view_settings = m_messagesBrowser->view()->settings(); - + view_settings->setFontFamily(QWebSettings::StandardFont, settings->value(GROUP(Messages), SETTING(Messages::PreviewerFontStandard)).toString()); } @@ -153,69 +128,14 @@ void FeedMessageViewer::quit() { // Quit the feeds model (stops auto-update timer etc.). m_feedsView->sourceModel()->quit(); - // Close worker threads. - if (m_feedDownloaderThread != NULL && m_feedDownloaderThread->isRunning()) { - qDebug("Quitting feed downloader thread."); - m_feedDownloaderThread->quit(); - - if (!m_feedDownloaderThread->wait(CLOSE_LOCK_TIMEOUT)) { - qCritical("Feed downloader thread is running despite it was told to quit. Terminating it."); - m_feedDownloaderThread->terminate(); - } - } - - if (m_dbCleanerThread != NULL && m_dbCleanerThread->isRunning()) { - qDebug("Quitting database cleaner thread."); - m_dbCleanerThread->quit(); - - if (!m_dbCleanerThread->wait(CLOSE_LOCK_TIMEOUT)) { - qCritical("Database cleaner thread is running despite it was told to quit. Terminating it."); - m_dbCleanerThread->terminate(); - } - } - - // Close workers. - if (m_feedDownloader != NULL) { - qDebug("Feed downloader exists. Deleting it from memory."); - m_feedDownloader->deleteLater(); - } - - if (m_dbCleaner != NULL) { - qDebug("Database cleaner exists. Deleting it from memory."); - m_dbCleaner->deleteLater(); - } - - if (qApp->settings()->value(GROUP(Messages), SETTING(Messages::ClearReadOnExit)).toBool()) { - m_feedsView->clearAllReadMessages(); - } } -void FeedMessageViewer::loadInitialFeeds() { - QString target_opml_file = APP_INITIAL_FEEDS_PATH + QDir::separator() + FEED_INITIAL_OPML_PATTERN; - QString current_locale = qApp->localization()->loadedLanguage(); - QString file_to_load; +bool FeedMessageViewer::areToolBarsEnabled() const { + return m_toolBarsEnabled; +} - if (QFile::exists(target_opml_file.arg(current_locale))) { - file_to_load = target_opml_file.arg(current_locale); - } - else if (QFile::exists(target_opml_file.arg(DEFAULT_LOCALE))) { - file_to_load = target_opml_file.arg(DEFAULT_LOCALE); - } - - FeedsImportExportModel model; - QString output_msg; - - try { - model.importAsOPML20(IOFactory::readTextFile(file_to_load)); - model.checkAllItems(); - - if (m_feedsView->sourceModel()->mergeModel(&model, output_msg)) { - m_feedsView->expandAll(); - } - } - catch (ApplicationException &ex) { - MessageBox::show(this, QMessageBox::Critical, tr("Error when loading initial feeds"), ex.message()); - } +bool FeedMessageViewer::areListHeadersEnabled() const { + return m_listHeadersEnabled; } void FeedMessageViewer::switchMessageSplitterOrientation() { @@ -239,61 +159,29 @@ void FeedMessageViewer::setListHeadersEnabled(bool enable) { m_messagesView->header()->setVisible(enable); } -void FeedMessageViewer::updateTrayIconStatus(int unread_messages, int total_messages, bool any_unread_messages) { - Q_UNUSED(total_messages) - - if (SystemTrayIcon::isSystemTrayActivated()) { - qApp->trayIcon()->setNumber(unread_messages, any_unread_messages); - } -} - -void FeedMessageViewer::onFeedUpdatesStarted() { - //: Text display in status bar when feed update is started. - qApp->mainForm()->statusBar()->showProgressFeeds(0, tr("Feed update started")); -} - -void FeedMessageViewer::onFeedUpdatesProgress(Feed *feed, int current, int total) { - // Some feed got updated. - m_feedsView->updateCountsOfParticularFeed(feed, true); - qApp->mainForm()->statusBar()->showProgressFeeds((current * 100.0) / total, - //: Text display in status bar when particular feed is updated. - tr("Updated feed '%1'").arg(feed->title())); -} - -void FeedMessageViewer::onFeedUpdatesFinished(FeedDownloadResults results) { - qApp->feedUpdateLock()->unlock(); - qApp->mainForm()->statusBar()->clearProgressFeeds(); - m_messagesView->reloadSelections(true); - - if (!results.m_updatedFeeds.isEmpty()) { - // Now, inform about results via GUI message/notification. - qApp->showGuiMessage(tr("New messages downloaded"), results.getOverview(10), QSystemTrayIcon::NoIcon, - 0, false, qApp->icons()->fromTheme(QSL("item-update-all"))); - } -} - void FeedMessageViewer::switchFeedComponentVisibility() { m_feedsWidget->setVisible(!m_feedsWidget->isVisible()); } void FeedMessageViewer::toggleShowOnlyUnreadFeeds() { QAction *origin = qobject_cast(sender()); - + if (origin == NULL) { - m_feedsView->invalidateReadFeedsFilter(true, false); + m_feedsView->model()->invalidateReadFeedsFilter(true, false); } else { - m_feedsView->invalidateReadFeedsFilter(true, origin->isChecked()); + m_feedsView->model()->invalidateReadFeedsFilter(true, origin->isChecked()); } } void FeedMessageViewer::updateMessageButtonsAvailability() { bool one_message_selected = m_messagesView->selectionModel()->selectedRows().size() == 1; bool atleast_one_message_selected = !m_messagesView->selectionModel()->selectedRows().isEmpty(); - bool recycle_bin_selected = m_messagesView->sourceModel()->loadedSelection().mode() == FeedsSelection::MessagesFromRecycleBin; + bool bin_loaded = m_messagesView->sourceModel()->loadedItem() != NULL && m_messagesView->sourceModel()->loadedItem()->kind() == RootItemKind::Bin; FormMain *form_main = qApp->mainForm(); - + form_main->m_ui->m_actionDeleteSelectedMessages->setEnabled(atleast_one_message_selected); + form_main->m_ui->m_actionRestoreSelectedMessages->setEnabled(atleast_one_message_selected && bin_loaded); form_main->m_ui->m_actionMarkSelectedMessagesAsRead->setEnabled(atleast_one_message_selected); form_main->m_ui->m_actionMarkSelectedMessagesAsUnread->setEnabled(atleast_one_message_selected); form_main->m_ui->m_actionOpenSelectedMessagesInternally->setEnabled(atleast_one_message_selected); @@ -301,63 +189,61 @@ void FeedMessageViewer::updateMessageButtonsAvailability() { form_main->m_ui->m_actionOpenSelectedSourceArticlesInternally->setEnabled(atleast_one_message_selected); form_main->m_ui->m_actionSendMessageViaEmail->setEnabled(one_message_selected); form_main->m_ui->m_actionSwitchImportanceOfSelectedMessages->setEnabled(atleast_one_message_selected); - - form_main->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin->setEnabled(recycle_bin_selected && atleast_one_message_selected); } void FeedMessageViewer::updateFeedButtonsAvailability() { bool critical_action_running = qApp->feedUpdateLock()->isLocked(); - bool feed_selected = !m_feedsView->selectionModel()->selectedRows().isEmpty(); + RootItem *selected_item = feedsView()->selectedItem(); + bool anything_selected = selected_item != NULL; + bool feed_selected = anything_selected && selected_item->kind() == RootItemKind::Feed; + bool category_selected = anything_selected && selected_item->kind() == RootItemKind::Category; + bool service_selected = anything_selected && selected_item->kind() == RootItemKind::ServiceRoot; FormMain *form_main = qApp->mainForm(); - - form_main->m_ui->m_actionAddCategory->setEnabled(!critical_action_running); - form_main->m_ui->m_actionAddFeed->setEnabled(!critical_action_running); + form_main->m_ui->m_actionBackupDatabaseSettings->setEnabled(!critical_action_running); form_main->m_ui->m_actionCleanupDatabase->setEnabled(!critical_action_running); - form_main->m_ui->m_actionClearSelectedFeeds->setEnabled(feed_selected); - form_main->m_ui->m_actionDeleteSelectedFeedCategory->setEnabled(!critical_action_running && feed_selected); - form_main->m_ui->m_actionEditSelectedFeedCategory->setEnabled(!critical_action_running && feed_selected); - form_main->m_ui->m_actionImportFeeds->setEnabled(!critical_action_running); - form_main->m_ui->m_actionMarkSelectedFeedsAsRead->setEnabled(feed_selected); - form_main->m_ui->m_actionMarkSelectedFeedsAsUnread->setEnabled(feed_selected); - form_main->m_ui->m_actionUpdateAllFeeds->setEnabled(!critical_action_running); - form_main->m_ui->m_actionUpdateSelectedFeeds->setEnabled(!critical_action_running && feed_selected); - form_main->m_ui->m_actionViewSelectedItemsNewspaperMode->setEnabled(feed_selected); - form_main->m_ui->m_actionFetchFeedMetadata->setEnabled(feed_selected); - form_main->m_ui->m_actionExpandCollapseFeedCategory->setEnabled(feed_selected); + form_main->m_ui->m_actionClearSelectedItems->setEnabled(anything_selected); + form_main->m_ui->m_actionDeleteSelectedItem->setEnabled(!critical_action_running && anything_selected); + form_main->m_ui->m_actionEditSelectedItem->setEnabled(!critical_action_running && anything_selected); + form_main->m_ui->m_actionMarkSelectedItemsAsRead->setEnabled(anything_selected); + form_main->m_ui->m_actionMarkSelectedItemsAsUnread->setEnabled(anything_selected); + form_main->m_ui->m_actionUpdateAllItems->setEnabled(!critical_action_running); + form_main->m_ui->m_actionUpdateSelectedItems->setEnabled(!critical_action_running && (feed_selected || category_selected || service_selected)); + form_main->m_ui->m_actionViewSelectedItemsNewspaperMode->setEnabled(anything_selected); + form_main->m_ui->m_actionExpandCollapseItem->setEnabled(anything_selected); + + form_main->m_ui->m_actionServiceDelete->setEnabled(service_selected); + form_main->m_ui->m_actionServiceEdit->setEnabled(service_selected); + form_main->m_ui->m_menuAddItem->setEnabled(!critical_action_running); + form_main->m_ui->m_menuAccounts->setEnabled(!critical_action_running); + form_main->m_ui->m_menuRecycleBin->setEnabled(!critical_action_running); } void FeedMessageViewer::createConnections() { FormMain *form_main = qApp->mainForm(); - + // Filtering & searching. connect(m_toolBarMessages, SIGNAL(messageSearchPatternChanged(QString)), m_messagesView, SLOT(searchMessages(QString))); - connect(m_toolBarMessages, SIGNAL(messageFilterChanged(MessagesModel::MessageFilter)), m_messagesView, SLOT(filterMessages(MessagesModel::MessageFilter))); - + connect(m_toolBarMessages, SIGNAL(messageFilterChanged(MessagesModel::MessageHighlighter)), m_messagesView, SLOT(filterMessages(MessagesModel::MessageHighlighter))); + // Message changers. connect(m_messagesView, SIGNAL(currentMessagesRemoved()), m_messagesBrowser, SLOT(clear())); connect(m_messagesView, SIGNAL(currentMessagesChanged(QList)), m_messagesBrowser, SLOT(navigateToMessages(QList))); connect(m_messagesView, SIGNAL(currentMessagesRemoved()), this, SLOT(updateMessageButtonsAvailability())); connect(m_messagesView, SIGNAL(currentMessagesChanged(QList)), this, SLOT(updateMessageButtonsAvailability())); - - connect(m_feedsView, SIGNAL(feedsSelected(FeedsSelection)), this, SLOT(updateFeedButtonsAvailability())); + + connect(m_feedsView, SIGNAL(itemSelected(RootItem*)), this, SLOT(updateFeedButtonsAvailability())); connect(qApp->feedUpdateLock(), SIGNAL(locked()), this, SLOT(updateFeedButtonsAvailability())); connect(qApp->feedUpdateLock(), SIGNAL(unlocked()), this, SLOT(updateFeedButtonsAvailability())); - + // If user selects feeds, load their messages. - connect(m_feedsView, SIGNAL(feedsSelected(FeedsSelection)), m_messagesView, SLOT(loadFeeds(FeedsSelection))); - - // If user changes status of some messages, recalculate message counts. - connect(m_messagesView->sourceModel(), SIGNAL(messageCountsChanged(FeedsSelection::SelectionMode,bool,bool)), - m_feedsView, SLOT(receiveMessageCountsChange(FeedsSelection::SelectionMode,bool,bool))); - + connect(m_feedsView, SIGNAL(itemSelected(RootItem*)), m_messagesView, SLOT(loadItem(RootItem*))); + // State of many messages is changed, then we need // to reload selections. - connect(m_feedsView, SIGNAL(feedsNeedToBeReloaded(bool)), m_messagesView, SLOT(reloadSelections(bool))); - - // If counts of unread/all messages change, update the tray icon. - connect(m_feedsView, SIGNAL(messageCountsChanged(int,int,bool)), this, SLOT(updateTrayIconStatus(int,int,bool))); + connect(m_feedsView->sourceModel(), SIGNAL(reloadMessageListRequested(bool)), m_messagesView, SLOT(reloadSelections(bool))); + connect(m_feedsView->sourceModel(), SIGNAL(feedsUpdateFinished()), this, SLOT(onFeedsUpdateFinished())); // Message openers. connect(m_messagesView, SIGNAL(openLinkMiniBrowser(QString)), m_messagesBrowser, SLOT(navigateToUrl(QString))); @@ -367,10 +253,7 @@ void FeedMessageViewer::createConnections() { form_main->m_ui->m_tabWidget, SLOT(addLinkedBrowser(QString))); connect(m_feedsView, SIGNAL(openMessagesInNewspaperView(QList)), form_main->m_ui->m_tabWidget, SLOT(addBrowserWithMessages(QList))); - - // Downloader connections. - connect(m_feedsView, SIGNAL(feedsUpdateRequested(QList)), this, SLOT(updateFeeds(QList))); - + // Toolbar forwardings. connect(form_main->m_ui->m_actionCleanupDatabase, SIGNAL(triggered()), this, SLOT(showDbCleanupAssistant())); @@ -378,8 +261,6 @@ void FeedMessageViewer::createConnections() { SIGNAL(triggered()), m_messagesView, SLOT(switchSelectedMessagesImportance())); connect(form_main->m_ui->m_actionDeleteSelectedMessages, SIGNAL(triggered()), m_messagesView, SLOT(deleteSelectedMessages())); - connect(form_main->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin, - SIGNAL(triggered()), m_messagesView, SLOT(restoreSelectedMessages())); connect(form_main->m_ui->m_actionMarkSelectedMessagesAsRead, SIGNAL(triggered()), m_messagesView, SLOT(markSelectedMessagesRead())); connect(form_main->m_ui->m_actionMarkSelectedMessagesAsUnread, @@ -392,56 +273,54 @@ void FeedMessageViewer::createConnections() { SIGNAL(triggered()), m_messagesView, SLOT(openSelectedMessagesInternally())); connect(form_main->m_ui->m_actionSendMessageViaEmail, SIGNAL(triggered()), m_messagesView, SLOT(sendSelectedMessageViaEmail())); - connect(form_main->m_ui->m_actionMarkAllFeedsRead, - SIGNAL(triggered()), m_feedsView, SLOT(markAllFeedsRead())); - connect(form_main->m_ui->m_actionMarkSelectedFeedsAsRead, - SIGNAL(triggered()), m_feedsView, SLOT(markSelectedFeedsRead())); - connect(form_main->m_ui->m_actionExpandCollapseFeedCategory, + connect(form_main->m_ui->m_actionMarkAllItemsRead, + SIGNAL(triggered()), m_feedsView, SLOT(markAllItemsRead())); + connect(form_main->m_ui->m_actionMarkSelectedItemsAsRead, + SIGNAL(triggered()), m_feedsView, SLOT(markSelectedItemRead())); + connect(form_main->m_ui->m_actionExpandCollapseItem, SIGNAL(triggered()), m_feedsView, SLOT(expandCollapseCurrentItem())); - connect(form_main->m_ui->m_actionFetchFeedMetadata, SIGNAL(triggered()), - m_feedsView, SLOT(fetchMetadataForSelectedFeed())); - connect(form_main->m_ui->m_actionMarkSelectedFeedsAsUnread, - SIGNAL(triggered()), m_feedsView, SLOT(markSelectedFeedsUnread())); - connect(form_main->m_ui->m_actionClearSelectedFeeds, + connect(form_main->m_ui->m_actionMarkSelectedItemsAsUnread, + SIGNAL(triggered()), m_feedsView, SLOT(markSelectedItemUnread())); + connect(form_main->m_ui->m_actionClearSelectedItems, SIGNAL(triggered()), m_feedsView, SLOT(clearSelectedFeeds())); - connect(form_main->m_ui->m_actionClearAllFeeds, + connect(form_main->m_ui->m_actionClearAllItems, SIGNAL(triggered()), m_feedsView, SLOT(clearAllFeeds())); - connect(form_main->m_ui->m_actionUpdateSelectedFeeds, - SIGNAL(triggered()), m_feedsView, SLOT(updateSelectedFeeds())); - connect(form_main->m_ui->m_actionUpdateAllFeeds, - SIGNAL(triggered()), m_feedsView, SLOT(updateAllFeeds())); - connect(form_main->m_ui->m_actionAddCategory, - SIGNAL(triggered()), m_feedsView, SLOT(addNewCategory())); - connect(form_main->m_ui->m_actionAddFeed, - SIGNAL(triggered()), m_feedsView, SLOT(addNewFeed())); - connect(form_main->m_ui->m_actionEditSelectedFeedCategory, + connect(form_main->m_ui->m_actionUpdateSelectedItems, + SIGNAL(triggered()), m_feedsView, SLOT(updateSelectedItems())); + connect(form_main->m_ui->m_actionUpdateAllItems, + SIGNAL(triggered()), m_feedsView, SLOT(updateAllItems())); + connect(form_main->m_ui->m_actionEditSelectedItem, SIGNAL(triggered()), m_feedsView, SLOT(editSelectedItem())); connect(form_main->m_ui->m_actionViewSelectedItemsNewspaperMode, - SIGNAL(triggered()), m_feedsView, SLOT(openSelectedFeedsInNewspaperMode())); - connect(form_main->m_ui->m_actionEmptyRecycleBin, - SIGNAL(triggered()), m_feedsView, SLOT(emptyRecycleBin())); - connect(form_main->m_ui->m_actionRestoreRecycleBin, - SIGNAL(triggered()), m_feedsView, SLOT(restoreRecycleBin())); - connect(form_main->m_ui->m_actionDeleteSelectedFeedCategory, + SIGNAL(triggered()), m_feedsView, SLOT(openSelectedItemsInNewspaperMode())); + connect(form_main->m_ui->m_actionDeleteSelectedItem, SIGNAL(triggered()), m_feedsView, SLOT(deleteSelectedItem())); connect(form_main->m_ui->m_actionSwitchFeedsList, SIGNAL(triggered()), this, SLOT(switchFeedComponentVisibility())); - connect(form_main->m_ui->m_actionSelectNextFeedCategory, + connect(form_main->m_ui->m_actionSelectNextItem, SIGNAL(triggered()), m_feedsView, SLOT(selectNextItem())); connect(form_main->m_ui->m_actionSwitchToolBars, SIGNAL(toggled(bool)), this, SLOT(setToolBarsEnabled(bool))); connect(form_main->m_ui->m_actionSwitchListHeaders, SIGNAL(toggled(bool)), this, SLOT(setListHeadersEnabled(bool))); - connect(form_main->m_ui->m_actionSelectPreviousFeedCategory, + connect(form_main->m_ui->m_actionSelectPreviousItem, SIGNAL(triggered()), m_feedsView, SLOT(selectPreviousItem())); connect(form_main->m_ui->m_actionSelectNextMessage, SIGNAL(triggered()), m_messagesView, SLOT(selectNextItem())); + connect(form_main->m_ui->m_actionSelectNextUnreadMessage, + SIGNAL(triggered()), m_messagesView, SLOT(selectNextUnreadItem())); connect(form_main->m_ui->m_actionSelectPreviousMessage, SIGNAL(triggered()), m_messagesView, SLOT(selectPreviousItem())); connect(form_main->m_ui->m_actionSwitchMessageListOrientation, SIGNAL(triggered()), this, SLOT(switchMessageSplitterOrientation())); - connect(form_main->m_ui->m_actionShowOnlyUnreadFeeds, SIGNAL(toggled(bool)), + connect(form_main->m_ui->m_actionShowOnlyUnreadItems, SIGNAL(toggled(bool)), this, SLOT(toggleShowOnlyUnreadFeeds())); + connect(form_main->m_ui->m_actionRestoreSelectedMessages, SIGNAL(triggered()), + m_messagesView, SLOT(restoreSelectedMessages())); + connect(form_main->m_ui->m_actionRestoreAllRecycleBins, SIGNAL(triggered()), + m_feedsView->sourceModel(), SLOT(restoreAllBins())); + connect(form_main->m_ui->m_actionEmptyAllRecycleBins, SIGNAL(triggered()), + m_feedsView->sourceModel(), SLOT(emptyAllBins())); } void FeedMessageViewer::initialize() { @@ -450,15 +329,15 @@ void FeedMessageViewer::initialize() { m_toolBarFeeds->setMovable(false); m_toolBarFeeds->setAllowedAreas(Qt::TopToolBarArea); m_toolBarFeeds->loadChangeableActions(); - + m_toolBarMessages->setFloatable(false); m_toolBarMessages->setMovable(false); m_toolBarMessages->setAllowedAreas(Qt::TopToolBarArea); m_toolBarMessages->loadChangeableActions(); - + // Finish web/message browser setup. m_messagesBrowser->setNavigationBarVisible(false); - + // Now refresh visual setup. refreshVisualProperties(); } @@ -468,12 +347,12 @@ void FeedMessageViewer::initializeViews() { m_messagesWidget = new QWidget(this); m_feedSplitter = new QSplitter(Qt::Horizontal, this); m_messageSplitter = new QSplitter(Qt::Vertical, this); - + // Instantiate needed components. QVBoxLayout *central_layout = new QVBoxLayout(this); QVBoxLayout *feed_layout = new QVBoxLayout(m_feedsWidget); QVBoxLayout *message_layout = new QVBoxLayout(m_messagesWidget); - + // Set layout properties. central_layout->setMargin(0); central_layout->setSpacing(0); @@ -481,11 +360,11 @@ void FeedMessageViewer::initializeViews() { feed_layout->setSpacing(0); message_layout->setMargin(0); message_layout->setSpacing(0); - + // Set views. m_feedsView->setFrameStyle(QFrame::NoFrame); m_messagesView->setFrameStyle(QFrame::NoFrame); - + // Setup message splitter. m_messageSplitter->setObjectName(QSL("MessageSplitter")); m_messageSplitter->setHandleWidth(1); @@ -493,30 +372,30 @@ void FeedMessageViewer::initializeViews() { m_messageSplitter->setChildrenCollapsible(false); m_messageSplitter->addWidget(m_messagesView); m_messageSplitter->addWidget(m_messagesBrowser); - + // Assemble message-related components to single widget. message_layout->addWidget(m_toolBarMessages); message_layout->addWidget(m_messageSplitter); - + // Assemble feed-related components to another widget. feed_layout->addWidget(m_toolBarFeeds); feed_layout->addWidget(m_feedsView); - + // Assembler everything together. m_feedSplitter->setHandleWidth(1); m_feedSplitter->setOpaqueResize(false); m_feedSplitter->setChildrenCollapsible(false); m_feedSplitter->addWidget(m_feedsWidget); m_feedSplitter->addWidget(m_messagesWidget); - + // Add toolbar and main feeds/messages widget to main layout. central_layout->addWidget(m_feedSplitter); - + setTabOrder(m_feedsView, m_messagesView); setTabOrder(m_messagesView, m_toolBarFeeds); setTabOrder(m_toolBarFeeds, m_toolBarMessages); setTabOrder(m_toolBarMessages, m_messagesBrowser); - + updateMessageButtonsAvailability(); updateFeedButtonsAvailability(); } @@ -524,14 +403,14 @@ void FeedMessageViewer::initializeViews() { void FeedMessageViewer::showDbCleanupAssistant() { if (qApp->feedUpdateLock()->tryLock()) { QPointer form_pointer = new FormDatabaseCleanup(this); - form_pointer.data()->setCleaner(databaseCleaner()); + form_pointer.data()->setCleaner(m_feedsView->sourceModel()->databaseCleaner()); form_pointer.data()->exec(); - + delete form_pointer.data(); qApp->feedUpdateLock()->unlock(); - + m_messagesView->reloadSelections(false); - m_feedsView->updateCountsOfAllFeeds(true); + m_feedsView->sourceModel()->reloadCountsOfWholeModel(); } else { qApp->showGuiMessage(tr("Cannot cleanup database"), @@ -543,36 +422,11 @@ void FeedMessageViewer::showDbCleanupAssistant() { void FeedMessageViewer::refreshVisualProperties() { Qt::ToolButtonStyle button_style = static_cast(qApp->settings()->value(GROUP(GUI), SETTING(GUI::ToolbarStyle)).toInt()); - + m_toolBarFeeds->setToolButtonStyle(button_style); m_toolBarMessages->setToolButtonStyle(button_style); } -void FeedMessageViewer::updateFeeds(QList feeds) { - if (!qApp->feedUpdateLock()->tryLock()) { - qApp->showGuiMessage(tr("Cannot update all items"), - tr("You cannot update all items because another another critical operation is ongoing."), - QSystemTrayIcon::Warning, qApp->mainForm(), true); - return; - } - - if (m_feedDownloader == NULL) { - m_feedDownloader = new FeedDownloader(); - m_feedDownloaderThread = new QThread(); - - // Downloader setup. - qRegisterMetaType >("QList"); - m_feedDownloader->moveToThread(m_feedDownloaderThread); - - connect(this, SIGNAL(feedsUpdateRequested(QList)), m_feedDownloader, SLOT(updateFeeds(QList))); - connect(m_feedDownloaderThread, SIGNAL(finished()), m_feedDownloaderThread, SLOT(deleteLater())); - connect(m_feedDownloader, SIGNAL(finished(FeedDownloadResults)), this, SLOT(onFeedUpdatesFinished(FeedDownloadResults))); - connect(m_feedDownloader, SIGNAL(started()), this, SLOT(onFeedUpdatesStarted())); - connect(m_feedDownloader, SIGNAL(progress(Feed*,int,int)), this, SLOT(onFeedUpdatesProgress(Feed*,int,int))); - - // Connections are made, start the feed downloader thread. - m_feedDownloaderThread->start(); - } - - emit feedsUpdateRequested(feeds); +void FeedMessageViewer::onFeedsUpdateFinished() { + m_messagesView->reloadSelections(true); } diff --git a/src/gui/feedmessageviewer.h b/src/gui/feedmessageviewer.h old mode 100644 new mode 100755 index 539f4cb35..83b07baa1 --- a/src/gui/feedmessageviewer.h +++ b/src/gui/feedmessageviewer.h @@ -29,8 +29,7 @@ class MessagesView; class MessagesToolBar; class FeedsToolBar; class FeedsView; -class DatabaseCleaner; -class Feed; +class StandardFeed; class QToolBar; class QSplitter; class QProgressBar; @@ -65,8 +64,6 @@ class FeedMessageViewer : public TabContent { return m_toolBarFeeds; } - DatabaseCleaner *databaseCleaner(); - // Loads/saves sizes and states of ALL // underlying widgets, this contains primarily // splitters, toolbar and views. @@ -79,17 +76,10 @@ class FeedMessageViewer : public TabContent { // stops any child widgets/workers. void quit(); - inline bool areToolBarsEnabled() const { - return m_toolBarsEnabled; - } - - inline bool areListHeadersEnabled() const { - return m_listHeadersEnabled; - } + bool areToolBarsEnabled() const; + bool areListHeadersEnabled() const; public slots: - void loadInitialFeeds(); - // Switches orientation horizontal/vertical. void switchMessageSplitterOrientation(); @@ -103,21 +93,15 @@ class FeedMessageViewer : public TabContent { // Reloads some changeable visual settings. void refreshVisualProperties(); - void updateFeeds(QList feeds); - private slots: - // Updates counts of messages for example in tray icon. - void updateTrayIconStatus(int unread_messages, int total_messages, bool any_unread_messages); - - // Reacts on feed updates. - void onFeedUpdatesStarted(); - void onFeedUpdatesProgress(Feed *feed, int current, int total); - void onFeedUpdatesFinished(FeedDownloadResults results); + // Called when feed update finishes. + void onFeedsUpdateFinished(); // Switches visibility of feed list and related // toolbar. void switchFeedComponentVisibility(); + // Toggles displayed feeds. void toggleShowOnlyUnreadFeeds(); void updateMessageButtonsAvailability(); @@ -133,10 +117,6 @@ class FeedMessageViewer : public TabContent { // Sets up connections. void createConnections(); - signals: - // Emitted if user/application requested updating of some feeds. - void feedsUpdateRequested(const QList feeds); - private: bool m_toolBarsEnabled; bool m_listHeadersEnabled; @@ -151,11 +131,6 @@ class FeedMessageViewer : public TabContent { QWidget *m_feedsWidget; QWidget *m_messagesWidget; WebBrowser *m_messagesBrowser; - - QThread *m_feedDownloaderThread; - QThread *m_dbCleanerThread; - FeedDownloader *m_feedDownloader; - DatabaseCleaner *m_dbCleaner; }; #endif // FEEDMESSAGEVIEWER_H diff --git a/src/gui/feedsview.cpp b/src/gui/feedsview.cpp index 89f634c06..b930714e9 100755 --- a/src/gui/feedsview.cpp +++ b/src/gui/feedsview.cpp @@ -18,21 +18,20 @@ #include "gui/feedsview.h" #include "definitions/definitions.h" -#include "core/feed.h" #include "core/feedsmodel.h" #include "core/feedsproxymodel.h" -#include "core/rootitem.h" -#include "core/category.h" -#include "core/recyclebin.h" -#include "core/feed.h" +#include "services/abstract/rootitem.h" #include "miscellaneous/systemfactory.h" #include "miscellaneous/mutex.h" #include "gui/systemtrayicon.h" #include "gui/messagebox.h" #include "gui/styleditemdelegatewithoutfocus.h" #include "gui/dialogs/formmain.h" -#include "gui/dialogs/formcategorydetails.h" -#include "gui/dialogs/formfeeddetails.h" +#include "services/abstract/feed.h" +#include "services/standard/standardcategory.h" +#include "services/standard/standardfeed.h" +#include "services/standard/gui/formstandardcategorydetails.h" +#include "services/standard/gui/formstandardfeeddetails.h" #include #include @@ -47,7 +46,7 @@ FeedsView::FeedsView(QWidget *parent) m_contextMenuCategories(NULL), m_contextMenuFeeds(NULL), m_contextMenuEmptySpace(NULL), - m_contextMenuRecycleBin(NULL) { + m_contextMenuOtherItems(NULL) { setObjectName(QSL("FeedsView")); // Allocate models. @@ -56,7 +55,7 @@ FeedsView::FeedsView(QWidget *parent) // Connections. connect(m_sourceModel, SIGNAL(requireItemValidationAfterDragDrop(QModelIndex)), this, SLOT(validateItemAfterDragDrop(QModelIndex))); - connect(m_sourceModel, SIGNAL(feedsUpdateRequested(QList)), this, SIGNAL(feedsUpdateRequested(QList))); + connect(m_sourceModel, SIGNAL(itemExpandRequested(QList,bool)), this, SLOT(onItemExpandRequested(QList,bool))); connect(header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(saveSortState(int,Qt::SortOrder))); setModel(m_proxyModel); @@ -94,53 +93,44 @@ RootItem *FeedsView::selectedItem() const { if (selected_rows.isEmpty()) { return NULL; } - - RootItem *selected_item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(selected_rows.at(0))); - return selected_item == m_sourceModel->rootItem() ? NULL : selected_item; -} - -Category *FeedsView::selectedCategory() const { - QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex()); - return m_sourceModel->categoryForIndex(current_mapped); -} - -Feed *FeedsView::selectedFeed() const { - QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex()); - return m_sourceModel->feedForIndex(current_mapped); -} - -RecycleBin *FeedsView::selectedRecycleBin() const{ - QModelIndex current_mapped = m_proxyModel->mapToSource(currentIndex()); - return m_sourceModel->recycleBinForIndex(current_mapped); + else { + RootItem *selected_item = m_sourceModel->itemForIndex(m_proxyModel->mapToSource(selected_rows.at(0))); + return selected_item == m_sourceModel->rootItem() ? NULL : selected_item; + } } void FeedsView::saveExpandedStates() { Settings *settings = qApp->settings(); + QList expandable_items; + + expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot)); // Iterate all categories and save their expand statuses. - foreach (Category *category, sourceModel()->allCategories().values()) { + foreach (RootItem *item, expandable_items) { + QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id()); + settings->setValue(GROUP(Categories), - QString::number(category->id()), - isExpanded(model()->mapFromSource(sourceModel()->indexForItem(category)))); + setting_name, + isExpanded(model()->mapFromSource(sourceModel()->indexForItem(item)))); } } void FeedsView::loadExpandedStates() { Settings *settings = qApp->settings(); + QList expandable_items; + + expandable_items.append(sourceModel()->rootItem()->getSubTree(RootItemKind::Category | RootItemKind::ServiceRoot)); // Iterate all categories and save their expand statuses. - foreach (Category *category, sourceModel()->allCategories().values()) { - setExpanded(model()->mapFromSource(sourceModel()->indexForItem(category)), - settings->value(GROUP(Categories), QString::number(category->id()), true).toBool()); - } -} + foreach (RootItem *item, expandable_items) { + QString setting_name = QString::number(item->kind()) + QL1S("-") + QString::number(qHash(item->title())) + QL1S("-") + QString::number(item->id()); -void FeedsView::invalidateReadFeedsFilter(bool set_new_value, bool show_unread_only) { - if (set_new_value) { - m_proxyModel->setShowUnreadOnly(show_unread_only); + setExpanded(model()->mapFromSource(sourceModel()->indexForItem(item)), + settings->value(GROUP(Categories), setting_name, item->childCount() > 0).toBool()); } - QTimer::singleShot(0, m_proxyModel, SLOT(invalidateFilter())); + sortByColumn(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnFeeds)).toInt(), + static_cast(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderFeeds)).toInt())); } void FeedsView::expandCollapseCurrentItem() { @@ -156,137 +146,20 @@ void FeedsView::expandCollapseCurrentItem() { } } -void FeedsView::updateAllFeeds() { - emit feedsUpdateRequested(allFeeds()); +void FeedsView::updateAllItems() { + m_sourceModel->updateAllFeeds(); } -void FeedsView::updateSelectedFeeds() { - emit feedsUpdateRequested(selectedFeeds()); -} - -void FeedsView::updateAllFeedsOnStartup() { - if (qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::FeedsUpdateOnStartup)).toBool()) { - qDebug("Requesting update for all feeds on application startup."); - QTimer::singleShot(STARTUP_UPDATE_DELAY, this, SLOT(updateAllFeeds())); - } -} - -void FeedsView::setSelectedFeedsClearStatus(int clear) { - m_sourceModel->markFeedsDeleted(selectedFeeds(), clear, 0); - updateCountsOfSelectedFeeds(true); - - emit feedsNeedToBeReloaded(true); -} - -void FeedsView::setAllFeedsClearStatus(int clear) { - m_sourceModel->markFeedsDeleted(allFeeds(), clear, 0); - updateCountsOfAllFeeds(true); - - emit feedsNeedToBeReloaded(true); +void FeedsView::updateSelectedItems() { + m_sourceModel->updateFeeds(selectedFeeds()); } void FeedsView::clearSelectedFeeds() { - setSelectedFeedsClearStatus(1); + m_sourceModel->markItemCleared(selectedItem(), false); } void FeedsView::clearAllFeeds() { - setAllFeedsClearStatus(1); -} - -void FeedsView::addNewCategory() { - if (!qApp->feedUpdateLock()->tryLock()) { - // Lock was not obtained because - // it is used probably by feed updater or application - // is quitting. - qApp->showGuiMessage(tr("Cannot add standard category"), - tr("You cannot add new standard category now because another critical operation is ongoing."), - QSystemTrayIcon::Warning, qApp->mainForm(), true); - return; - } - - QPointer form_pointer = new FormCategoryDetails(m_sourceModel, this); - - form_pointer.data()->exec(NULL, selectedItem()); - - delete form_pointer.data(); - - // Changes are done, unlock the update master lock. - qApp->feedUpdateLock()->unlock(); -} - -void FeedsView::editCategory(Category *category) { - QPointer form_pointer = new FormCategoryDetails(m_sourceModel, this); - - form_pointer.data()->exec(category, NULL); - - delete form_pointer.data(); -} - -void FeedsView::addNewFeed() { - if (!qApp->feedUpdateLock()->tryLock()) { - // Lock was not obtained because - // it is used probably by feed updater or application - // is quitting. - qApp->showGuiMessage(tr("Cannot add standard feed"), - tr("You cannot add new standard feed now because another critical operation is ongoing."), - QSystemTrayIcon::Warning, qApp->mainForm(), true); - return; - } - - QPointer form_pointer = new FormFeedDetails(m_sourceModel, this); - - form_pointer.data()->exec(NULL, selectedItem()); - - delete form_pointer.data(); - - // Changes are done, unlock the update master lock. - qApp->feedUpdateLock()->unlock(); -} - -void FeedsView::editFeed(Feed *feed) { - QPointer form_pointer = new FormFeedDetails(m_sourceModel, this); - - form_pointer.data()->exec(feed, NULL); - - delete form_pointer.data(); -} - -void FeedsView::receiveMessageCountsChange(FeedsSelection::SelectionMode mode, - bool total_msg_count_changed, - bool any_msg_restored) { - // If the change came from recycle bin mode, then: - // a) total count of message was changed AND no message was restored - some messages - // were permanently deleted from recycle bin --> we need to update counts of - // just recycle bin, including total counts. - // b) total count of message was changed AND some message was restored - some messages - // were restored --> we need to update counts from all items and bin, including total counts. - // c) total count of message was not changed - state of some messages was switched, no - // deletings or restorings were made --> update counts of just recycle bin, excluding total counts. - // - // If the change came from feed mode, then: - // a) total count of message was changed - some messages were deleted --> we need to update - // counts of recycle bin, including total counts and update counts of selected feeds, including - // total counts. - // b) total count of message was not changed - some messages switched state --> we need to update - // counts of just selected feeds. - if (mode == FeedsSelection::MessagesFromRecycleBin) { - if (total_msg_count_changed) { - if (any_msg_restored) { - updateCountsOfAllFeeds(true); - } - else { - updateCountsOfRecycleBin(true); - } - } - else { - updateCountsOfRecycleBin(false); - } - } - else { - updateCountsOfSelectedFeeds(total_msg_count_changed); - } - - invalidateReadFeedsFilter(); + m_sourceModel->markItemCleared(m_sourceModel->rootItem(), false); } void FeedsView::editSelectedItem() { @@ -297,19 +170,19 @@ void FeedsView::editSelectedItem() { qApp->showGuiMessage(tr("Cannot edit item"), tr("Selected item cannot be edited because another critical operation is ongoing."), QSystemTrayIcon::Warning, qApp->mainForm(), true); - // Thus, cannot delete and quit the method. return; } - Category *category; - Feed *feed; - - if ((category = selectedCategory()) != NULL) { - editCategory(category); + if (selectedItem()->canBeEdited()) { + selectedItem()->editViaGui(); } - else if ((feed = selectedFeed()) != NULL) { - editFeed(feed); + else { + qApp->showGuiMessage(tr("Cannot edit item"), + tr("Selected item cannot be edited, this is not (yet?) supported."), + QSystemTrayIcon::Warning, + qApp->mainForm(), + true); } // Changes are done, unlock the update master lock. @@ -337,158 +210,74 @@ void FeedsView::deleteSelectedItem() { return; } - if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, tr("Delete feed/category"), - tr("You are about to delete selected feed or category."), tr("Do you really want to delete selected item?"), - QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::No) { - // User changed his mind. - qApp->feedUpdateLock()->unlock(); - return; - } + RootItem *selected_item = selectedItem(); - if (m_sourceModel->removeItem(m_proxyModel->mapToSource(current_index))) { - // Item WAS removed, update counts. - notifyWithCounts(); - } - else { - // Item WAS NOT removed, either database-related error occurred - // or update is undergoing. - qApp->showGuiMessage(tr("Deletion of item failed."), - tr("Selected item was not deleted due to error."), - QSystemTrayIcon::Warning, qApp->mainForm(), true); + if (selected_item != NULL) { + if (selected_item->canBeDeleted()) { + // Ask user first. + if (MessageBox::show(qApp->mainForm(), + QMessageBox::Question, + tr("Deleting \"%1\"").arg(selected_item->title()), + tr("You are about to completely delete item \"%1\".").arg(selected_item->title()), + tr("Are you sure?"), + QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::No) { + // User refused. + qApp->feedUpdateLock()->unlock(); + return; + } + + // We have deleteable item selected, remove it via GUI. + if (!selected_item->deleteViaGui()) { + qApp->showGuiMessage(tr("Cannot delete \"%1\"").arg(selected_item->title()), + tr("This item cannot be deleted because something critically failed. Submit bug report."), + QSystemTrayIcon::Critical, + qApp->mainForm(), + true); + } + } + else { + qApp->showGuiMessage(tr("Cannot delete \"%1\"").arg(selected_item->title()), + tr("This item cannot be deleted, because it does not support it\nor this functionality is not implemented yet."), + QSystemTrayIcon::Critical, + qApp->mainForm(), + true); + } } // Changes are done, unlock the update master lock. qApp->feedUpdateLock()->unlock(); } -void FeedsView::markSelectedFeedsReadStatus(int read) { - m_sourceModel->markFeedsRead(selectedFeeds(), read); - updateCountsOfSelectedFeeds(false); - - emit feedsNeedToBeReloaded(read == 1); +void FeedsView::markSelectedItemReadStatus(RootItem::ReadStatus read) { + m_sourceModel->markItemRead(selectedItem(), read); } -void FeedsView::markSelectedFeedsRead() { - markSelectedFeedsReadStatus(1); +void FeedsView::markSelectedItemRead() { + markSelectedItemReadStatus(RootItem::Read); } -void FeedsView::markSelectedFeedsUnread() { - markSelectedFeedsReadStatus(0); +void FeedsView::markSelectedItemUnread() { + markSelectedItemReadStatus(RootItem::Unread); } -void FeedsView::markAllFeedsReadStatus(int read) { - m_sourceModel->markFeedsRead(allFeeds(), read); - updateCountsOfAllFeeds(false); - - emit feedsNeedToBeReloaded(read == 1); +void FeedsView::markAllItemsReadStatus(RootItem::ReadStatus read) { + m_sourceModel->markItemRead(m_sourceModel->rootItem(), read); } -void FeedsView::markAllFeedsRead() { - markAllFeedsReadStatus(1); +void FeedsView::markAllItemsRead() { + markAllItemsReadStatus(RootItem::Read); } -void FeedsView::fetchMetadataForSelectedFeed() { - Feed *selected_feed = selectedFeed(); - - if (selected_feed != NULL) { - selected_feed->fetchMetadataForItself(); - m_sourceModel->reloadChangedLayout(QModelIndexList() << m_proxyModel->mapToSource(selectionModel()->selectedRows(0).at(0))); - } -} - -void FeedsView::clearAllReadMessages() { - m_sourceModel->markFeedsDeleted(allFeeds(), 1, 1); -} - -void FeedsView::openSelectedFeedsInNewspaperMode() { - QList messages = m_sourceModel->messagesForFeeds(selectedFeeds()); +void FeedsView::openSelectedItemsInNewspaperMode() { + QList messages = m_sourceModel->messagesForItem(selectedItem()); if (!messages.isEmpty()) { emit openMessagesInNewspaperView(messages); - QTimer::singleShot(0, this, SLOT(markSelectedFeedsRead())); + QTimer::singleShot(0, this, SLOT(markSelectedItemRead())); } } -void FeedsView::emptyRecycleBin() { - if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, tr("Permanently delete messages"), - tr("You are about to permanenty delete all messages from your recycle bin."), - tr("Do you really want to empty your recycle bin?"), - QString(), QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_sourceModel->recycleBin()->empty(); - updateCountsOfSelectedFeeds(true); - - emit feedsNeedToBeReloaded(true); - } -} - -void FeedsView::restoreRecycleBin() { - m_sourceModel->recycleBin()->restore(); - updateCountsOfAllFeeds(true); - - emit feedsNeedToBeReloaded(true); -} - -void FeedsView::updateCountsOfSelectedFeeds(bool update_total_too) { - foreach (Feed *feed, selectedFeeds()) { - feed->updateCounts(update_total_too); - } - - QModelIndexList selected_indexes = m_proxyModel->mapListToSource(selectionModel()->selectedRows()); - - if (update_total_too) { - // Number of items in recycle bin has changed. - m_sourceModel->recycleBin()->updateCounts(true); - - // We need to refresh data for recycle bin too. - selected_indexes.append(m_sourceModel->indexForItem(m_sourceModel->recycleBin())); - } - - // Make sure that selected view reloads changed indexes. - m_sourceModel->reloadChangedLayout(selected_indexes); - notifyWithCounts(); -} - -void FeedsView::updateCountsOfRecycleBin(bool update_total_too) { - m_sourceModel->recycleBin()->updateCounts(update_total_too); - m_sourceModel->reloadChangedLayout(QModelIndexList() << m_sourceModel->indexForItem(m_sourceModel->recycleBin())); - notifyWithCounts(); -} - -void FeedsView::updateCountsOfAllFeeds(bool update_total_too) { - foreach (Feed *feed, allFeeds()) { - feed->updateCounts(update_total_too); - } - - if (update_total_too) { - // Number of items in recycle bin has changed. - m_sourceModel->recycleBin()->updateCounts(true); - } - - // Make sure that all views reloads its data. - m_sourceModel->reloadWholeLayout(); - notifyWithCounts(); -} - -void FeedsView::updateCountsOfParticularFeed(Feed *feed, bool update_total_too) { - QModelIndex index = m_sourceModel->indexForItem(feed); - - if (index.isValid()) { - feed->updateCounts(update_total_too, false); - m_sourceModel->reloadChangedLayout(QModelIndexList() << index); - } - - invalidateReadFeedsFilter(); - notifyWithCounts(); -} - void FeedsView::selectNextItem() { - // NOTE: Bug #122 requested to not expand in here. - /* - if (!isExpanded(currentIndex())) { - expand(currentIndex()); - } - */ - QModelIndex index_next = moveCursor(QAbstractItemView::MoveDown, Qt::NoModifier); if (index_next.isValid()) { @@ -500,62 +289,97 @@ void FeedsView::selectNextItem() { void FeedsView::selectPreviousItem() { QModelIndex index_previous = moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier); - // NOTE: Bug #122 requested to not expand in here. - /* - if (!isExpanded(index_previous)) { - expand(index_previous); - index_previous = moveCursor(QAbstractItemView::MoveUp, Qt::NoModifier); - } - */ - if (index_previous.isValid()) { setCurrentIndex(index_previous); setFocus(); } } -void FeedsView::initializeContextMenuCategories() { - m_contextMenuCategories = new QMenu(tr("Context menu for categories"), this); +void FeedsView::switchVisibility() { + setVisible(!isVisible()); +} + +QMenu *FeedsView::initializeContextMenuCategories(RootItem *clicked_item) { + if (m_contextMenuCategories == NULL) { + m_contextMenuCategories = new QMenu(tr("Context menu for categories"), this); + } + else { + m_contextMenuCategories->clear(); + } + + QList specific_actions = clicked_item->contextMenu(); + m_contextMenuCategories->addActions(QList() << - qApp->mainForm()->m_ui->m_actionUpdateSelectedFeeds << - qApp->mainForm()->m_ui->m_actionEditSelectedFeedCategory << + qApp->mainForm()->m_ui->m_actionUpdateSelectedItems << + qApp->mainForm()->m_ui->m_actionEditSelectedItem << qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode << - qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsRead << - qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsUnread << - qApp->mainForm()->m_ui->m_actionDeleteSelectedFeedCategory); - m_contextMenuCategories->addSeparator(); - m_contextMenuCategories->addActions(QList() << - qApp->mainForm()->m_ui->m_actionAddCategory << - qApp->mainForm()->m_ui->m_actionAddFeed); + qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead << + qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread << + qApp->mainForm()->m_ui->m_actionDeleteSelectedItem); + + if (!specific_actions.isEmpty()) { + m_contextMenuCategories->addSeparator(); + m_contextMenuCategories->addActions(specific_actions); + } + + return m_contextMenuCategories; } -void FeedsView::initializeContextMenuFeeds() { - m_contextMenuFeeds = new QMenu(tr("Context menu for categories"), this); +QMenu *FeedsView::initializeContextMenuFeeds(RootItem *clicked_item) { + if (m_contextMenuFeeds == NULL) { + m_contextMenuFeeds = new QMenu(tr("Context menu for categories"), this); + } + else { + m_contextMenuFeeds->clear(); + } + + QList specific_actions = clicked_item->contextMenu(); + m_contextMenuFeeds->addActions(QList() << - qApp->mainForm()->m_ui->m_actionUpdateSelectedFeeds << - qApp->mainForm()->m_ui->m_actionEditSelectedFeedCategory << + qApp->mainForm()->m_ui->m_actionUpdateSelectedItems << + qApp->mainForm()->m_ui->m_actionEditSelectedItem << qApp->mainForm()->m_ui->m_actionViewSelectedItemsNewspaperMode << - qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsRead << - qApp->mainForm()->m_ui->m_actionMarkSelectedFeedsAsUnread << - qApp->mainForm()->m_ui->m_actionDeleteSelectedFeedCategory << - qApp->mainForm()->m_ui->m_actionFetchFeedMetadata); + qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsRead << + qApp->mainForm()->m_ui->m_actionMarkSelectedItemsAsUnread << + qApp->mainForm()->m_ui->m_actionDeleteSelectedItem); + + if (!specific_actions.isEmpty()) { + m_contextMenuFeeds->addSeparator(); + m_contextMenuFeeds->addActions(specific_actions); + } + + return m_contextMenuFeeds; } -void FeedsView::initializeContextMenuEmptySpace() { - m_contextMenuEmptySpace = new QMenu(tr("Context menu for empty space"), this); - m_contextMenuEmptySpace->addAction(qApp->mainForm()->m_ui->m_actionUpdateAllFeeds); - m_contextMenuEmptySpace->addSeparator(); - m_contextMenuEmptySpace->addActions(QList() << - qApp->mainForm()->m_ui->m_actionAddCategory << - qApp->mainForm()->m_ui->m_actionAddFeed); +QMenu *FeedsView::initializeContextMenuEmptySpace() { + if (m_contextMenuEmptySpace == NULL) { + m_contextMenuEmptySpace = new QMenu(tr("Context menu for empty space"), this); + m_contextMenuEmptySpace->addAction(qApp->mainForm()->m_ui->m_actionUpdateAllItems); + m_contextMenuEmptySpace->addSeparator(); + } + + return m_contextMenuEmptySpace; } -void FeedsView::initializeContextMenuRecycleBin() { - m_contextMenuRecycleBin = new QMenu(tr("Context menu for recycle bin"), this); - m_contextMenuRecycleBin->addActions(QList() << - qApp->mainForm()->m_ui->m_actionRestoreRecycleBin << - qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin << - qApp->mainForm()->m_ui->m_actionEmptyRecycleBin); +QMenu *FeedsView::initializeContextMenuOtherItem(RootItem *clicked_item) { + if (m_contextMenuOtherItems == NULL) { + m_contextMenuOtherItems = new QMenu(tr("Context menu for other items"), this); + } + else { + m_contextMenuOtherItems->clear(); + } + + QList specific_actions = clicked_item->contextMenu(); + + if (!specific_actions.isEmpty()) { + m_contextMenuOtherItems->addSeparator(); + m_contextMenuOtherItems->addActions(specific_actions); + } + else { + m_contextMenuOtherItems->addAction(qApp->mainForm()->m_ui->m_actionNoActions); + } + + return m_contextMenuOtherItems; } void FeedsView::setupAppearance() { @@ -586,9 +410,6 @@ void FeedsView::setupAppearance() { setItemDelegate(new StyledItemDelegateWithoutFocus(this)); header()->setStretchLastSection(false); header()->setSortIndicatorShown(false); - - sortByColumn(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnFeeds)).toInt(), - static_cast(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderFeeds)).toInt())); } void FeedsView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { @@ -596,8 +417,8 @@ void FeedsView::selectionChanged(const QItemSelection &selected, const QItemSele m_proxyModel->setSelectedItem(selected_item); QTreeView::selectionChanged(selected, deselected); - emit feedsSelected(FeedsSelection(selected_item)); - invalidateReadFeedsFilter(); + emit itemSelected(selected_item); + m_proxyModel->invalidateReadFeedsFilter(); } void FeedsView::keyPressEvent(QKeyEvent *event) { @@ -615,41 +436,21 @@ void FeedsView::contextMenuEvent(QContextMenuEvent *event) { QModelIndex mapped_index = model()->mapToSource(clicked_index); RootItem *clicked_item = sourceModel()->itemForIndex(mapped_index); - if (clicked_item->kind() == RootItem::Cattegory) { + if (clicked_item->kind() == RootItemKind::Category) { // Display context menu for categories. - if (m_contextMenuCategories == NULL) { - // Context menu is not initialized, initialize. - initializeContextMenuCategories(); - } - - m_contextMenuCategories->exec(event->globalPos()); + initializeContextMenuCategories(clicked_item)->exec(event->globalPos()); } - else if (clicked_item->kind() == RootItem::Feeed) { + else if (clicked_item->kind() == RootItemKind::Feed) { // Display context menu for feeds. - if (m_contextMenuFeeds == NULL) { - // Context menu is not initialized, initialize. - initializeContextMenuFeeds(); - } - - m_contextMenuFeeds->exec(event->globalPos()); + initializeContextMenuFeeds(clicked_item)->exec(event->globalPos()); } - else if (clicked_item->kind() == RootItem::Bin) { - // Display context menu for recycle bin. - if (m_contextMenuRecycleBin == NULL) { - initializeContextMenuRecycleBin(); - } - - m_contextMenuRecycleBin->exec(event->globalPos()); + else { + initializeContextMenuOtherItem(clicked_item)->exec(event->globalPos()); } } else { // Display menu for empty space. - if (m_contextMenuEmptySpace == NULL) { - // Context menu is not initialized, initialize. - initializeContextMenuEmptySpace(); - } - - m_contextMenuEmptySpace->exec(event->globalPos()); + initializeContextMenuEmptySpace()->exec(event->globalPos()); } } @@ -666,3 +467,13 @@ void FeedsView::validateItemAfterDragDrop(const QModelIndex &source_index) { setCurrentIndex(mapped); } } + +void FeedsView::onItemExpandRequested(const QList &items, bool exp) { + foreach (RootItem *item, items) { + QModelIndex source_index = m_sourceModel->indexForItem(item); + QModelIndex proxy_index = m_proxyModel->mapFromSource(source_index); + + setExpanded(proxy_index, !exp); + setExpanded(proxy_index, exp); + } +} diff --git a/src/gui/feedsview.h b/src/gui/feedsview.h index 61c249175..4932fe68c 100755 --- a/src/gui/feedsview.h +++ b/src/gui/feedsview.h @@ -18,19 +18,16 @@ #ifndef FEEDSVIEW_H #define FEEDSVIEW_H -#include #include -#include "core/messagesmodel.h" #include "core/feedsmodel.h" -#include "core/feedsselection.h" -#include "miscellaneous/settings.h" + +#include class FeedsProxyModel; class Feed; class Category; -class QTimer; class FeedsView : public QTreeView { Q_OBJECT @@ -59,96 +56,62 @@ class FeedsView : public QTreeView { // Returns pointers to selected feed/category if they are really // selected. RootItem *selectedItem() const; - Category *selectedCategory() const; - Feed *selectedFeed() const; - RecycleBin *selectedRecycleBin() const; // Saves/loads expand states of all nodes (feeds/categories) of the list to/from settings. void saveExpandedStates(); void loadExpandedStates(); public slots: - void invalidateReadFeedsFilter(bool set_new_value = false, bool show_unread_only = false); void expandCollapseCurrentItem(); - void fetchMetadataForSelectedFeed(); // Feed updating. - void updateAllFeeds(); - void updateAllFeedsOnStartup(); - void updateSelectedFeeds(); + void updateAllItems(); + void updateSelectedItems(); // Feed read/unread manipulators. - void markSelectedFeedsReadStatus(int read); - void markSelectedFeedsRead(); - void markSelectedFeedsUnread(); - void markAllFeedsReadStatus(int read); - void markAllFeedsRead(); + void markSelectedItemRead(); + void markSelectedItemUnread(); + void markAllItemsRead(); // Newspaper accessors. - void openSelectedFeedsInNewspaperMode(); - - // Recycle bin operators. - void emptyRecycleBin(); - void restoreRecycleBin(); + void openSelectedItemsInNewspaperMode(); // Feed clearers. - void setSelectedFeedsClearStatus(int clear); - void setAllFeedsClearStatus(int clear); void clearSelectedFeeds(); void clearAllFeeds(); - void clearAllReadMessages(); // Base manipulators. void editSelectedItem(); void deleteSelectedItem(); - // Standard category manipulators. - void addNewCategory(); - void editCategory(Category *category); - - // Standard feed manipulators. - void addNewFeed(); - void editFeed(Feed *feed); - - // Is called when counts of messages are changed externally, - // typically from message view. - void receiveMessageCountsChange(FeedsSelection::SelectionMode mode, bool total_msg_count_changed, bool any_msg_restored); - - // Reloads counts for selected feeds. - void updateCountsOfSelectedFeeds(bool update_total_too); - - // Reloads counts of recycle bin. - void updateCountsOfRecycleBin(bool update_total_too); - - // Reloads counts for all feeds. - void updateCountsOfAllFeeds(bool update_total_too); - - // Reloads counts for particular feed. - void updateCountsOfParticularFeed(Feed *feed, bool update_total_too); - - // Notifies other components about messages - // counts. - inline void notifyWithCounts() { - emit messageCountsChanged(m_sourceModel->countOfUnreadMessages(), - m_sourceModel->countOfAllMessages(), - m_sourceModel->hasAnyFeedNewMessages()); - } - // Selects next/previous item (feed/category) in the list. void selectNextItem(); void selectPreviousItem(); // Switches visibility of the widget. - void switchVisibility() { - setVisible(!isVisible()); - } + void switchVisibility(); - protected: + signals: + // Emitted if user selects new feeds. + void itemSelected(RootItem *item); + + // Requests opening of given messages in newspaper mode. + void openMessagesInNewspaperView(const QList &messages); + + private slots: + void markSelectedItemReadStatus(RootItem::ReadStatus read); + void markAllItemsReadStatus(RootItem::ReadStatus read); + + void saveSortState(int column, Qt::SortOrder order); + void validateItemAfterDragDrop(const QModelIndex &source_index); + void onItemExpandRequested(const QList &items, bool exp); + + private: // Initializes context menus. - void initializeContextMenuCategories(); - void initializeContextMenuFeeds(); - void initializeContextMenuEmptySpace(); - void initializeContextMenuRecycleBin(); + QMenu *initializeContextMenuCategories(RootItem *clicked_item); + QMenu *initializeContextMenuFeeds(RootItem *clicked_item); + QMenu *initializeContextMenuEmptySpace(); + QMenu *initializeContextMenuOtherItem(RootItem *clicked_item); // Sets up appearance of this widget. void setupAppearance(); @@ -162,31 +125,10 @@ class FeedsView : public QTreeView { // Show custom context menu. void contextMenuEvent(QContextMenuEvent *event); - private slots: - void saveSortState(int column, Qt::SortOrder order); - void validateItemAfterDragDrop(const QModelIndex &source_index); - - signals: - // Emitted if user/application requested updating of some feeds. - void feedsUpdateRequested(const QList feeds); - - // Emitted if counts of messages are changed. - void messageCountsChanged(int unread_messages, int total_messages, bool any_feed_has_unread_messages); - - // Emitted if currently selected feeds needs to be reloaded. - void feedsNeedToBeReloaded(bool mark_current_index_read); - - // Emitted if user selects new feeds. - void feedsSelected(const FeedsSelection &selection); - - // Requests opening of given messages in newspaper mode. - void openMessagesInNewspaperView(const QList &messages); - - private: QMenu *m_contextMenuCategories; QMenu *m_contextMenuFeeds; QMenu *m_contextMenuEmptySpace; - QMenu *m_contextMenuRecycleBin; + QMenu *m_contextMenuOtherItems; FeedsModel *m_sourceModel; FeedsProxyModel *m_proxyModel; diff --git a/src/gui/labelwithstatus.cpp b/src/gui/labelwithstatus.cpp old mode 100644 new mode 100755 diff --git a/src/gui/messagestoolbar.cpp b/src/gui/messagestoolbar.cpp index 6d511e4ef..c907a5682 100755 --- a/src/gui/messagestoolbar.cpp +++ b/src/gui/messagestoolbar.cpp @@ -103,7 +103,7 @@ void MessagesToolBar::handleMessageHighlighterChange(QAction *action) { m_btnMessageHighlighter->setIcon(action->icon()); m_btnMessageHighlighter->setToolTip(action->text()); - emit messageFilterChanged(action->data().value()); + emit messageFilterChanged(action->data().value()); } void MessagesToolBar::initializeSearchBox() { diff --git a/src/gui/messagestoolbar.h b/src/gui/messagestoolbar.h old mode 100644 new mode 100755 index 673df7a2f..e1f33d559 --- a/src/gui/messagestoolbar.h +++ b/src/gui/messagestoolbar.h @@ -56,7 +56,7 @@ class MessagesToolBar : public BaseToolBar { void messageSearchPatternChanged(const QString &pattern); // Emitted if message filter is changed. - void messageFilterChanged(MessagesModel::MessageFilter filter); + void messageFilterChanged(MessagesModel::MessageHighlighter filter); private slots: // Called when highlighter gets changed. diff --git a/src/gui/messagesview.cpp b/src/gui/messagesview.cpp index 4a904594b..be30c14a2 100755 --- a/src/gui/messagesview.cpp +++ b/src/gui/messagesview.cpp @@ -28,6 +28,7 @@ #include #include +#include #include @@ -78,8 +79,7 @@ void MessagesView::reloadSelections(bool mark_current_index_read) { QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes); // Reload the model now. - m_sourceModel->select(); - m_sourceModel->fetchAll(); + m_sourceModel->fetchAllData(); sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); @@ -141,23 +141,18 @@ void MessagesView::contextMenuEvent(QContextMenuEvent *event) { return; } - if (m_contextMenu == NULL) { - // Context menu is not initialized, initialize. - initializeContextMenu(); - } - - if (sourceModel()->loadedSelection().mode() == FeedsSelection::MessagesFromRecycleBin) { - m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin); - } - else { - m_contextMenu->removeAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin); - } + // Context menu is not initialized, initialize. + initializeContextMenu(); m_contextMenu->exec(event->globalPos()); } void MessagesView::initializeContextMenu() { - m_contextMenu = new QMenu(tr("Context menu for messages"), this); + if (m_contextMenu == NULL) { + m_contextMenu = new QMenu(tr("Context menu for messages"), this); + } + + m_contextMenu->clear(); m_contextMenu->addActions(QList() << qApp->mainForm()->m_ui->m_actionSendMessageViaEmail << qApp->mainForm()->m_ui->m_actionOpenSelectedSourceArticlesExternally << @@ -166,8 +161,11 @@ void MessagesView::initializeContextMenu() { qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsRead << qApp->mainForm()->m_ui->m_actionMarkSelectedMessagesAsUnread << qApp->mainForm()->m_ui->m_actionSwitchImportanceOfSelectedMessages << - qApp->mainForm()->m_ui->m_actionDeleteSelectedMessages << - qApp->mainForm()->m_ui->m_actionRestoreSelectedMessagesFromRecycleBin); + qApp->mainForm()->m_ui->m_actionDeleteSelectedMessages); + + if (m_sourceModel->loadedItem() != NULL && m_sourceModel->loadedItem()->kind() == RootItemKind::Bin) { + m_contextMenu->addAction(qApp->mainForm()->m_ui->m_actionRestoreSelectedMessages); + } } void MessagesView::mousePressEvent(QMouseEvent *event) { @@ -201,10 +199,6 @@ void MessagesView::mousePressEvent(QMouseEvent *event) { } } -void MessagesView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { - QTreeView::currentChanged(current, previous); -} - void MessagesView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) { QModelIndexList selected_rows = selectionModel()->selectedRows(); QModelIndex current_index = currentIndex(); @@ -218,7 +212,7 @@ void MessagesView::selectionChanged(const QItemSelection &selected, const QItemS if (!m_batchUnreadSwitch) { // Set this message as read only if current item // wasn't changed by "mark selected messages unread" action. - m_sourceModel->setMessageRead(mapped_current_index.row(), 1); + m_sourceModel->setMessageRead(mapped_current_index.row(), RootItem::Read); } emit currentMessagesChanged(QList() << m_sourceModel->messageAt(m_proxyModel->mapToSource(selected_rows.at(0)).row())); @@ -234,8 +228,8 @@ void MessagesView::selectionChanged(const QItemSelection &selected, const QItemS QTreeView::selectionChanged(selected, deselected); } -void MessagesView::loadFeeds(const FeedsSelection &selection) { - m_sourceModel->loadMessages(selection); +void MessagesView::loadItem(RootItem *item) { + m_sourceModel->loadMessages(item); int col = qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortColumnMessages)).toInt(); Qt::SortOrder ord = static_cast(qApp->settings()->value(GROUP(GUI), SETTING(GUI::DefaultSortOrderMessages)).toInt()); @@ -264,8 +258,8 @@ void MessagesView::openSelectedSourceMessagesExternally() { } // Finally, mark opened messages as read. - if (selectionModel()->selectedRows().size() > 1) { - markSelectedMessagesRead(); + if (!selectionModel()->selectedRows().isEmpty()) { + QTimer::singleShot(0, this, SLOT(markSelectedMessagesRead())); } } @@ -285,8 +279,8 @@ void MessagesView::openSelectedSourceMessagesInternally() { } // Finally, mark opened messages as read. - if (selectionModel()->selectedRows().size() > 1) { - markSelectedMessagesRead(); + if (!selectionModel()->selectedRows().isEmpty()) { + QTimer::singleShot(0, this, SLOT(markSelectedMessagesRead())); } } @@ -308,7 +302,7 @@ void MessagesView::openSelectedMessagesInternally() { emit openMessagesInNewspaperView(messages); // Finally, mark opened messages as read. - markSelectedMessagesRead(); + QTimer::singleShot(0, this, SLOT(markSelectedMessagesRead())); } } @@ -326,14 +320,14 @@ void MessagesView::sendSelectedMessageViaEmail() { } void MessagesView::markSelectedMessagesRead() { - setSelectedMessagesReadStatus(1); + setSelectedMessagesReadStatus(RootItem::Read); } void MessagesView::markSelectedMessagesUnread() { - setSelectedMessagesReadStatus(0); + setSelectedMessagesReadStatus(RootItem::Unread); } -void MessagesView::setSelectedMessagesReadStatus(int read) { +void MessagesView::setSelectedMessagesReadStatus(RootItem::ReadStatus read) { QModelIndex current_index = selectionModel()->currentIndex(); if (!current_index.isValid()) { @@ -350,7 +344,7 @@ void MessagesView::setSelectedMessagesReadStatus(int read) { selected_indexes = m_proxyModel->mapListFromSource(mapped_indexes, true); current_index = m_proxyModel->mapFromSource(m_sourceModel->index(mapped_current_index.row(), mapped_current_index.column())); - if (read == 0) { + if (read == RootItem::Unread) { // User selected to mark some messages as unread, if one // of them will be marked as current, then it will be read again. m_batchUnreadSwitch = true; @@ -372,7 +366,7 @@ void MessagesView::deleteSelectedMessages() { QModelIndexList selected_indexes = selectionModel()->selectedRows(); QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes); - m_sourceModel->setBatchMessagesDeleted(mapped_indexes, 1); + m_sourceModel->setBatchMessagesDeleted(mapped_indexes); sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); int row_count = m_sourceModel->rowCount(); @@ -400,24 +394,21 @@ void MessagesView::restoreSelectedMessages() { QModelIndexList selected_indexes = selectionModel()->selectedRows(); QModelIndexList mapped_indexes = m_proxyModel->mapListToSource(selected_indexes); - if (m_sourceModel->setBatchMessagesRestored(mapped_indexes)) { - sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); + m_sourceModel->setBatchMessagesRestored(mapped_indexes); + sortByColumn(header()->sortIndicatorSection(), header()->sortIndicatorOrder()); - int row_count = m_sourceModel->rowCount(); - if (row_count > 0) { - QModelIndex last_item = current_index.row() < row_count ? - m_proxyModel->index(current_index.row(), - MSG_DB_TITLE_INDEX) : - m_proxyModel->index(row_count - 1, - MSG_DB_TITLE_INDEX); + int row_count = m_sourceModel->rowCount(); + if (row_count > 0) { + QModelIndex last_item = current_index.row() < row_count ? + m_proxyModel->index(current_index.row(), MSG_DB_TITLE_INDEX) : + m_proxyModel->index(row_count - 1, MSG_DB_TITLE_INDEX); - setCurrentIndex(last_item); - scrollTo(last_item); - reselectIndexes(QModelIndexList() << last_item); - } - else { - emit currentMessagesRemoved(); - } + setCurrentIndex(last_item); + scrollTo(last_item); + reselectIndexes(QModelIndexList() << last_item); + } + else { + emit currentMessagesRemoved(); } } @@ -439,20 +430,23 @@ void MessagesView::switchSelectedMessagesImportance() { current_index = m_proxyModel->mapFromSource(m_sourceModel->index(mapped_current_index.row(), mapped_current_index.column())); + m_batchUnreadSwitch = true; setCurrentIndex(current_index); scrollTo(current_index); reselectIndexes(selected_indexes); + m_batchUnreadSwitch = false; } void MessagesView::reselectIndexes(const QModelIndexList &indexes) { - QItemSelection selection; + if (indexes.size() < RESELECT_MESSAGE_THRESSHOLD) { + QItemSelection selection; - foreach (const QModelIndex &index, indexes) { - // TODO: THIS IS very slow. Try to select 4000 messages and hit "mark as read" button. - selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); + foreach (const QModelIndex &index, indexes) { + selection.merge(QItemSelection(index, index), QItemSelectionModel::Select); + } + + selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); } - - selectionModel()->select(selection, QItemSelectionModel::Select | QItemSelectionModel::Rows); } void MessagesView::selectNextItem() { @@ -475,6 +469,30 @@ void MessagesView::selectPreviousItem() { } } +void MessagesView::selectNextUnreadItem() { + // FIXME: Use this to solve #112. + + QModelIndexList selected_rows = selectionModel()->selectedRows(); + int active_row; + + if (!selected_rows.isEmpty()) { + // Okay, something is selected, start from it. + active_row = selected_rows.at(0).row(); + } + else { + active_row = 0; + } + + QModelIndex next_unread = m_proxyModel->getNextPreviousUnreadItemIndex(active_row); + + if (next_unread.isValid()) { + // We found unread message, mark it. + setCurrentIndex(next_unread); + selectionModel()->select(next_unread, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + setFocus(); + } +} + void MessagesView::searchMessages(const QString &pattern) { m_proxyModel->setFilterRegExp(pattern); @@ -487,8 +505,8 @@ void MessagesView::searchMessages(const QString &pattern) { } } -void MessagesView::filterMessages(MessagesModel::MessageFilter filter) { - m_sourceModel->filterMessages(filter); +void MessagesView::filterMessages(MessagesModel::MessageHighlighter filter) { + m_sourceModel->highlightMessages(filter); } void MessagesView::adjustColumns() { @@ -531,6 +549,8 @@ void MessagesView::adjustColumns() { hideColumn(MSG_DB_CONTENTS_INDEX); hideColumn(MSG_DB_PDELETED_INDEX); hideColumn(MSG_DB_ENCLOSURES_INDEX); + hideColumn(MSG_DB_ACCOUNT_ID_INDEX); + hideColumn(MSG_DB_CUSTOM_ID_INDEX); qDebug("Adjusting column resize modes for MessagesView."); } diff --git a/src/gui/messagesview.h b/src/gui/messagesview.h index c84c4dd77..991a24d58 100755 --- a/src/gui/messagesview.h +++ b/src/gui/messagesview.h @@ -20,7 +20,7 @@ #include "core/messagesmodel.h" -#include "core/feedsselection.h" +#include "services/abstract/rootitem.h" #include #include @@ -47,9 +47,6 @@ class MessagesView : public QTreeView { return m_sourceModel; } - // Creates needed connections. - void createConnections(); - public slots: void keyboardSearch(const QString &search); @@ -60,7 +57,7 @@ class MessagesView : public QTreeView { void reloadSelections(bool mark_current_index_read); // Loads un-deleted messages from selected feeds. - void loadFeeds(const FeedsSelection &selection); + void loadItem(RootItem *item); // Message manipulators. void openSelectedSourceMessagesExternally(); @@ -70,7 +67,7 @@ class MessagesView : public QTreeView { void sendSelectedMessageViaEmail(); // Works with SELECTED messages only. - void setSelectedMessagesReadStatus(int read); + void setSelectedMessagesReadStatus(RootItem::ReadStatus read); void markSelectedMessagesRead(); void markSelectedMessagesUnread(); void switchSelectedMessagesImportance(); @@ -79,10 +76,11 @@ class MessagesView : public QTreeView { void selectNextItem(); void selectPreviousItem(); + void selectNextUnreadItem(); // Searchs the visible message according to given pattern. void searchMessages(const QString &pattern); - void filterMessages(MessagesModel::MessageFilter filter); + void filterMessages(MessagesModel::MessageHighlighter filter); private slots: // Marks given indexes as selected. @@ -94,20 +92,6 @@ class MessagesView : public QTreeView { // Saves current sort state. void saveSortState(int column, Qt::SortOrder order); - protected: - // Initializes context menu. - void initializeContextMenu(); - - // Sets up appearance. - void setupAppearance(); - - // Event reimplementations. - void contextMenuEvent(QContextMenuEvent *event); - void mousePressEvent(QMouseEvent *event); - void keyPressEvent(QKeyEvent *event); - void currentChanged(const QModelIndex ¤t, const QModelIndex &previous); - void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); - signals: // Link/message openers. void openLinkNewTab(const QString &link); @@ -119,6 +103,21 @@ class MessagesView : public QTreeView { void currentMessagesRemoved(); private: + // Creates needed connections. + void createConnections(); + + // Initializes context menu. + void initializeContextMenu(); + + // Sets up appearance. + void setupAppearance(); + + // Event reimplementations. + void contextMenuEvent(QContextMenuEvent *event); + void mousePressEvent(QMouseEvent *event); + void keyPressEvent(QKeyEvent *event); + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + QMenu *m_contextMenu; MessagesProxyModel *m_proxyModel; diff --git a/src/gui/notifications/notification.cpp b/src/gui/notifications/notification.cpp old mode 100644 new mode 100755 index 28128d088..510acf9c5 --- a/src/gui/notifications/notification.cpp +++ b/src/gui/notifications/notification.cpp @@ -66,10 +66,14 @@ Notification::~Notification() { qDebug("Destroying Notification instance."); } -bool Notification::areNotificationsActivated() { +bool Notification::areFancyNotificationsEnabled() { return qApp->settings()->value(GROUP(GUI), SETTING(GUI::UseFancyNotifications)).toBool(); } +bool Notification::areNotificationsEnabled() { + return qApp->settings()->value(GROUP(GUI), SETTING(GUI::EnableNotifications)).toBool(); +} + void Notification::notify(const QString &text, const QString &title, const QIcon &icon, QObject *invokation_target, const char *invokation_slot) { cancel(); @@ -98,7 +102,6 @@ void Notification::notify(const QString &text, const QString &title, const QIcon argument_list << hints; // hints argument_list << (int)-1; // timeout in ms - // TODO: obrazky https://dev.visucore.com/bitcoin/doxygen/notificator_8cpp_source.html QDBusMessage response = m_dBusInterface->callWithArgumentList(QDBus::AutoDetect, "Notify", argument_list); if (response.arguments().size() == 1) { diff --git a/src/gui/notifications/notification.h b/src/gui/notifications/notification.h old mode 100644 new mode 100755 index 13b11bec5..dceb7bcc4 --- a/src/gui/notifications/notification.h +++ b/src/gui/notifications/notification.h @@ -35,7 +35,8 @@ class Notification : public QWidget { explicit Notification(); virtual ~Notification(); - static bool areNotificationsActivated(); + static bool areFancyNotificationsEnabled(); + static bool areNotificationsEnabled(); public slots: // Main methods for using the netofication. diff --git a/src/gui/systemtrayicon.cpp b/src/gui/systemtrayicon.cpp index 63bfc9ea9..514f061c4 100755 --- a/src/gui/systemtrayicon.cpp +++ b/src/gui/systemtrayicon.cpp @@ -124,7 +124,7 @@ void SystemTrayIcon::setNumber(int number, bool any_new_message) { QPixmap background(m_plainPixmap); QPainter tray_painter; - // TODO: Here draw different background instead of different color of number. + // FIXME: Here draw different background instead of different color of number. tray_painter.begin(&background); tray_painter.setPen(any_new_message ? Qt::blue : Qt::black); tray_painter.setRenderHint(QPainter::SmoothPixmapTransform, true); diff --git a/src/gui/tabwidget.cpp b/src/gui/tabwidget.cpp index f850dd92b..2c0831e54 100755 --- a/src/gui/tabwidget.cpp +++ b/src/gui/tabwidget.cpp @@ -71,7 +71,6 @@ void TabWidget::openMainMenu() { m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuView); m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuFeeds); m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuMessages); - m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuRecycleBin); m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuWebBrowser); m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuTools); m_menuMain->addMenu(qApp->mainForm()->m_ui->m_menuHelp); diff --git a/src/main.cpp b/src/main.cpp index 6a6c2fcde..eb23d82c4 100755 --- a/src/main.cpp +++ b/src/main.cpp @@ -72,6 +72,9 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } + // Register needed metatypes. + qRegisterMetaType >("QList"); + // Add an extra path for non-system icon themes and set current icon theme // and skin. qApp->icons()->setupSearchPaths(); @@ -97,7 +100,7 @@ int main(int argc, char *argv[]) { main_window.setWindowTitle(APP_LONG_NAME); // Now is a good time to initialize dynamic keyboard shortcuts. - DynamicShortcuts::load(qApp->userActions()); + DynamicShortcuts::load(qApp->userActions()); // Display main window. if (qApp->settings()->value(GROUP(GUI), SETTING(GUI::MainWindowStartsHidden)).toBool() && SystemTrayIcon::isSystemTrayActivated()) { @@ -107,32 +110,33 @@ int main(int argc, char *argv[]) { else { qDebug("Showing the main window when the application is starting."); main_window.show(); - - if (qApp->settings()->value(GROUP(General), SETTING(General::FirstRun)).toBool()) { - // This is the first time user runs this application. - qApp->settings()->setValue(GROUP(General), General::FirstRun, false); - - if (MessageBox::show(&main_window, QMessageBox::Question, QObject::tr("Load initial feeds"), - QObject::tr("You started %1 for the first time, now you can load initial set of feeds.").arg(APP_NAME), - QObject::tr("Do you want to load initial set of feeds?"), - QString(), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - qApp->mainForm()->tabWidget()->feedMessageViewer()->loadInitialFeeds(); - } - } } // Display tray icon if it is enabled and available. if (SystemTrayIcon::isSystemTrayActivated()) { qApp->showTrayIcon(); - - if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) { - QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup())); - } } + // Load activated accounts. + qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->loadActivatedServiceAccounts(); + qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->loadExpandedStates(); + // Setup single-instance behavior. QObject::connect(&application, SIGNAL(messageReceived(QString)), &application, SLOT(processExecutionMessage(QString))); - qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1 %2.").arg(APP_NAME, APP_VERSION), QSystemTrayIcon::NoIcon); + + if (qApp->isFirstRun() || qApp->isFirstRun(APP_VERSION)) { + qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.\n\nPlease, check NEW stuff included in this\n" + "version by clicking this popup notification.").arg(APP_LONG_NAME), + QSystemTrayIcon::NoIcon, 0, false, QIcon(), &main_window, SLOT(showAbout())); + } + else { + qApp->showGuiMessage(QSL(APP_NAME), QObject::tr("Welcome to %1.").arg(APP_LONG_NAME), QSystemTrayIcon::NoIcon); + } + + if (qApp->settings()->value(GROUP(General), SETTING(General::UpdateOnStartup)).toBool()) { + QTimer::singleShot(STARTUP_UPDATE_DELAY, application.system(), SLOT(checkForUpdatesOnStartup())); + } + // Enter global event loop. return Application::exec(); diff --git a/src/miscellaneous/application.cpp b/src/miscellaneous/application.cpp index 669a52653..0ff6edeae 100755 --- a/src/miscellaneous/application.cpp +++ b/src/miscellaneous/application.cpp @@ -28,6 +28,10 @@ #include "exceptions/applicationexception.h" #include "adblock/adblockmanager.h" +#include "services/abstract/serviceroot.h" +#include "services/standard/standardserviceentrypoint.h" +#include "services/tt-rss/ttrssserviceentrypoint.h" + #include #include #include @@ -35,7 +39,7 @@ Application::Application(const QString &id, int &argc, char **argv) : QtSingleApplication(id, argc, argv), - m_updateFeedsLock(NULL), m_userActions(QList()), m_mainForm(NULL), + m_updateFeedsLock(NULL), m_feedServices(QList()), m_userActions(QList()), m_mainForm(NULL), m_trayIcon(NULL), m_settings(NULL), m_system(NULL), m_skins(NULL), m_localization(NULL), m_icons(NULL), m_database(NULL), m_downloadManager(NULL), m_shouldRestart(false), m_notification(NULL) { @@ -46,6 +50,17 @@ Application::Application(const QString &id, int &argc, char **argv) Application::~Application() { delete m_updateFeedsLock; + qDeleteAll(m_feedServices); +} + +QList Application::feedServices() { + if (m_feedServices.isEmpty()) { + // NOTE: All installed services create their entry points here. + m_feedServices.append(new StandardServiceEntryPoint()); + m_feedServices.append(new TtRssServiceEntryPoint()); + } + + return m_feedServices; } QList Application::userActions() { @@ -56,6 +71,22 @@ QList Application::userActions() { return m_userActions; } +bool Application::isFirstRun() { + return settings()->value(GROUP(General), SETTING(General::FirstRun)).toBool(); +} + +bool Application::isFirstRun(const QString &version) { + return settings()->value(GROUP(General), QString(General::FirstRun) + QL1C('_') + version, true).toBool(); +} + +void Application::eliminateFirstRun() { + settings()->setValue(GROUP(General), General::FirstRun, false); +} + +void Application::eliminateFirstRun(const QString &version) { + settings()->setValue(GROUP(General), QString(General::FirstRun) + QL1C('_') + version, false); +} + IconFactory *Application::icons() { if (m_icons == NULL) { m_icons = new IconFactory(this); @@ -139,7 +170,8 @@ void Application::processExecutionMessage(const QString &message) { SystemTrayIcon *Application::trayIcon() { if (m_trayIcon == NULL) { m_trayIcon = new SystemTrayIcon(APP_ICON_PATH, APP_ICON_PLAIN_PATH, m_mainForm); - connect(m_trayIcon, SIGNAL(shown()), m_mainForm->tabWidget()->feedMessageViewer()->feedsView(), SLOT(notifyWithCounts())); + connect(m_trayIcon, SIGNAL(shown()), + m_mainForm->tabWidget()->feedMessageViewer()->feedsView()->sourceModel(), SLOT(notifyWithCounts())); } return m_trayIcon; @@ -166,19 +198,25 @@ void Application::showGuiMessage(const QString &title, const QString &message, QSystemTrayIcon::MessageIcon message_type, QWidget *parent, bool show_at_least_msgbox, const QIcon &custom_icon, QObject *invokation_target, const char *invokation_slot) { - if (Notification::areNotificationsActivated()) { - // Show OSD instead if tray icon bubble, depending on settings. - if (custom_icon.isNull()) { - notification()->notify(message, title, message_type, invokation_target, invokation_slot); + if (Notification::areNotificationsEnabled()) { + if (Notification::areFancyNotificationsEnabled()) { + // Show OSD instead if tray icon bubble, depending on settings. + if (custom_icon.isNull()) { + notification()->notify(message, title, message_type, invokation_target, invokation_slot); + } + else { + notification()->notify(message, title, custom_icon, invokation_target, invokation_slot); + } + + return; } - else { - notification()->notify(message, title, custom_icon, invokation_target, invokation_slot); + else if (SystemTrayIcon::isSystemTrayActivated()) { + trayIcon()->showMessage(title, message, message_type, TRAY_ICON_BUBBLE_TIMEOUT, invokation_target, invokation_slot); + return; } } - else if (SystemTrayIcon::isSystemTrayActivated()) { - trayIcon()->showMessage(title, message, message_type, TRAY_ICON_BUBBLE_TIMEOUT, invokation_target, invokation_slot); - } - else if (show_at_least_msgbox) { + + if (show_at_least_msgbox) { // Tray icon or OSD is not available, display simple text box. MessageBox::show(parent, (QMessageBox::Icon) message_type, title, message); } @@ -202,6 +240,9 @@ void Application::onSaveState(QSessionManager &manager) { } void Application::onAboutToQuit() { + eliminateFirstRun(); + eliminateFirstRun(APP_VERSION); + // Make sure that we obtain close lock BEFORE even trying to quit the application. bool locked_safely = feedUpdateLock()->tryLock(4 * CLOSE_LOCK_TIMEOUT); @@ -250,14 +291,6 @@ void Application::onAboutToQuit() { } } -bool Application::shouldRestart() const { - return m_shouldRestart; -} - -void Application::setShouldRestart(bool shouldRestart) { - m_shouldRestart = shouldRestart; -} - void Application::restart() { m_shouldRestart = true; quit(); diff --git a/src/miscellaneous/application.h b/src/miscellaneous/application.h index d8930ee0b..7899cec46 100755 --- a/src/miscellaneous/application.h +++ b/src/miscellaneous/application.h @@ -30,6 +30,7 @@ #include "gui/systemtrayicon.h" #include "gui/notifications/notification.h" #include "network-web/downloadmanager.h" +#include "services/abstract/serviceentrypoint.h" #include @@ -54,8 +55,19 @@ class Application : public QtSingleApplication { explicit Application(const QString &id, int &argc, char **argv); virtual ~Application(); + // List of all installed "feed service plugins", including obligatory + // "standard" service entry point. + QList feedServices(); + + // Globally accessible actions. QList userActions(); + // Check whether this application starts for the first time (ever). + bool isFirstRun(); + + // Check whether GIVEN VERSION of the application starts for the first time. + bool isFirstRun(const QString &version); + inline SystemFactory *system() { if (m_system == NULL) { m_system = new SystemFactory(this); @@ -154,10 +166,8 @@ class Application : public QtSingleApplication { return static_cast(QCoreApplication::instance()); } - bool shouldRestart() const; - void setShouldRestart(bool shouldRestart); - public slots: + // Restarts the application. void restart(); // Processes incoming message from another RSS Guard instance. @@ -170,6 +180,9 @@ class Application : public QtSingleApplication { void onAboutToQuit(); private: + void eliminateFirstRun(); + void eliminateFirstRun(const QString &version); + // This read-write lock is used by application on its close. // Application locks this lock for WRITING. // This means that if application locks that lock, then @@ -183,6 +196,7 @@ class Application : public QtSingleApplication { // tries to lock the lock for writing), then no other // action will be allowed to lock for reading. Mutex *m_updateFeedsLock; + QList m_feedServices; QList m_userActions; FormMain *m_mainForm; SystemTrayIcon *m_trayIcon; diff --git a/src/miscellaneous/databasefactory.cpp b/src/miscellaneous/databasefactory.cpp index 6e0d98ff4..374dcf71e 100755 --- a/src/miscellaneous/databasefactory.cpp +++ b/src/miscellaneous/databasefactory.cpp @@ -203,7 +203,9 @@ QSqlDatabase DatabaseFactory::sqliteInitializeInMemoryDatabase() { copy_contents.exec(QString("ATTACH DATABASE '%1' AS 'storage';").arg(file_database.databaseName())); // Copy all stuff. - QStringList tables; tables << QSL("Information") << QSL("Categories") << QSL("Feeds") << QSL("FeedsData") << QSL("Messages"); + // WARNING: All tables belong here. + QStringList tables; tables << QSL("Information") << QSL("Categories") << QSL("Feeds") << + QSL("Accounts") << QSL("TtRssAccounts") << QSL("Messages"); foreach (const QString &table, tables) { copy_contents.exec(QString("INSERT INTO main.%1 SELECT * FROM storage.%1;").arg(table)); @@ -290,22 +292,25 @@ QSqlDatabase DatabaseFactory::sqliteInitializeFileBasedDatabase(const QString &c } database.commit(); + query_db.finish(); qDebug("File-based SQLite database backend should be ready now."); } else { query_db.next(); - QString installed_db_schema = query_db.value(0).toString(); + query_db.finish(); - if (!updateDatabaseSchema(database, installed_db_schema)) { - qFatal("Database schema was not updated from '%s' to '%s' successully.", - qPrintable(installed_db_schema), - APP_DB_SCHEMA_VERSION); - } - else { - qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.", - qPrintable(installed_db_schema), - APP_DB_SCHEMA_VERSION); + if (installed_db_schema < APP_DB_SCHEMA_VERSION) { + if (sqliteUpdateDatabaseSchema(database, installed_db_schema)) { + qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.", + qPrintable(installed_db_schema), + APP_DB_SCHEMA_VERSION); + } + else { + qFatal("Database schema was not updated from '%s' to '%s' successully.", + qPrintable(installed_db_schema), + APP_DB_SCHEMA_VERSION); + } } qDebug("File-based SQLite database connection '%s' to file '%s' seems to be established.", @@ -313,8 +318,6 @@ QSqlDatabase DatabaseFactory::sqliteInitializeFileBasedDatabase(const QString &c qPrintable(QDir::toNativeSeparators(database.databaseName()))); qDebug("File-based SQLite database has version '%s'.", qPrintable(installed_db_schema)); } - - query_db.finish(); } // Everything is initialized now. @@ -327,24 +330,18 @@ QString DatabaseFactory::sqliteDatabaseFilePath() const { return m_sqliteDatabaseFilePath + QDir::separator() + APP_DB_SQLITE_FILE; } -bool DatabaseFactory::updateDatabaseSchema(QSqlDatabase database, const QString &source_db_schema_version) { - switch (m_activeDatabaseDriver) { - case SQLITE: - case SQLITE_MEMORY: - return sqliteUpdateDatabaseSchema(database, source_db_schema_version); - - case MYSQL: - return mysqlUpdateDatabaseSchema(database, source_db_schema_version); - - default: - return false; - } -} - bool DatabaseFactory::sqliteUpdateDatabaseSchema(QSqlDatabase database, const QString &source_db_schema_version) { int working_version = QString(source_db_schema_version).remove('.').toInt(); int current_version = QString(APP_DB_SCHEMA_VERSION).remove('.').toInt(); + // Now, it would be good to create backup of SQLite DB file. + if (IOFactory::copyFile(sqliteDatabaseFilePath(), sqliteDatabaseFilePath() + ".bak")) { + qDebug("Creating backup of SQLite DB file."); + } + else { + qFatal("Creation of backup SQLite DB file failed."); + } + while (working_version != current_version) { QString update_file_name = QString(APP_MISC_PATH) + QDir::separator() + QString(APP_DB_UPDATE_FILE_PATTERN).arg(QSL("sqlite"), @@ -469,8 +466,9 @@ void DatabaseFactory::sqliteSaveMemoryDatabase() { copy_contents.exec(QString(QSL("ATTACH DATABASE '%1' AS 'storage';")).arg(file_database.databaseName())); // Copy all stuff. - QStringList tables; tables << QSL("Categories") << QSL("Feeds") << QSL("FeedsData") << - QSL("Messages"); + // WARNING: All tables belong here. + QStringList tables; tables << QSL("Information") << QSL("Categories") << QSL("Feeds") << + QSL("Accounts") << QSL("TtRssAccounts") << QSL("Messages"); foreach (const QString &table, tables) { copy_contents.exec(QString(QSL("DELETE FROM storage.%1;")).arg(table)); @@ -609,15 +607,18 @@ QSqlDatabase DatabaseFactory::mysqlInitializeDatabase(const QString &connection_ QString installed_db_schema = query_db.value(0).toString(); - if (!mysqlUpdateDatabaseSchema(database, installed_db_schema)) { - qFatal("Database schema was not updated from '%s' to '%s' successully.", - qPrintable(installed_db_schema), - APP_DB_SCHEMA_VERSION); - } - else { - qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.", - qPrintable(installed_db_schema), - APP_DB_SCHEMA_VERSION); + if (installed_db_schema < APP_DB_SCHEMA_VERSION) { + if (mysqlUpdateDatabaseSchema(database, installed_db_schema)) { + qDebug("Database schema was updated from '%s' to '%s' successully or it is already up to date.", + qPrintable(installed_db_schema), + APP_DB_SCHEMA_VERSION); + + } + else { + qFatal("Database schema was not updated from '%s' to '%s' successully.", + qPrintable(installed_db_schema), + APP_DB_SCHEMA_VERSION); + } } } diff --git a/src/miscellaneous/databasefactory.h b/src/miscellaneous/databasefactory.h index b1f0d2b80..453d8e61f 100755 --- a/src/miscellaneous/databasefactory.h +++ b/src/miscellaneous/databasefactory.h @@ -116,9 +116,6 @@ class DatabaseFactory : public QObject { // application session. void determineDriver(); - // Updates DB schema if necessary. - bool updateDatabaseSchema(QSqlDatabase database, const QString &source_db_schema_version); - // Holds the type of currently activated database backend. UsedDriver m_activeDatabaseDriver; diff --git a/src/miscellaneous/iofactory.cpp b/src/miscellaneous/iofactory.cpp index a10565eea..b9e952d03 100755 --- a/src/miscellaneous/iofactory.cpp +++ b/src/miscellaneous/iofactory.cpp @@ -92,7 +92,7 @@ QByteArray IOFactory::readTextFile(const QString &file_path) { } } -void IOFactory::writeTextFile(const QString &file_path, const QByteArray &data) { +void IOFactory::writeTextFile(const QString &file_path, const QByteArray &data, const QString &encoding) { QFile input_file(file_path); QTextStream stream(&input_file); diff --git a/src/miscellaneous/iofactory.h b/src/miscellaneous/iofactory.h index 3331b5b06..c80c6539b 100755 --- a/src/miscellaneous/iofactory.h +++ b/src/miscellaneous/iofactory.h @@ -52,7 +52,7 @@ class IOFactory { // Throws exception when no such file exists. static QByteArray readTextFile(const QString &file_path); - static void writeTextFile(const QString &file_path, const QByteArray &data); + static void writeTextFile(const QString &file_path, const QByteArray &data, const QString &encoding = "UTF-8"); // Copies file, overwrites destination. static bool copyFile(const QString &source, const QString &destination); diff --git a/src/miscellaneous/settings.cpp b/src/miscellaneous/settings.cpp index dd67b27cc..bf1179f07 100755 --- a/src/miscellaneous/settings.cpp +++ b/src/miscellaneous/settings.cpp @@ -82,7 +82,7 @@ DKEY GUI::ToolbarStyle = "toolbar_style"; DVALUE(Qt::ToolButtonStyle) GUI::ToolbarStyleDef = Qt::ToolButtonIconOnly; DKEY GUI::FeedsToolbarActions = "feeds_toolbar"; -DVALUE(char*) GUI::FeedsToolbarActionsDef = "m_actionUpdateAllFeeds,m_actionMarkAllFeedsRead"; +DVALUE(char*) GUI::FeedsToolbarActionsDef = "m_actionUpdateAllItems,m_actionMarkAllItemsRead"; DKEY GUI::MainWindowInitialSize = "window_size"; DKEY GUI::MainWindowInitialPosition = "window_position"; @@ -105,12 +105,18 @@ DVALUE(bool) GUI::ToolbarsVisibleDef = true; DKEY GUI::ListHeadersVisible = "enable_list_headers"; DVALUE(bool) GUI::ListHeadersVisibleDef = true; +DKEY GUI::StatusBarVisible = "enable_status_bar"; +DVALUE(bool) GUI::StatusBarVisibleDef = true; + DKEY GUI::HideMainWindowWhenMinimized = "hide_when_minimized"; DVALUE(bool) GUI::HideMainWindowWhenMinimizedDef = false; DKEY GUI::UseTrayIcon = "use_tray_icon"; DVALUE(bool) GUI::UseTrayIconDef = true; +DKEY GUI::EnableNotifications = "enable_notifications"; +DVALUE(bool) GUI::EnableNotificationsDef = true; + DKEY GUI::UseFancyNotifications = "use_fancy_notifications"; DVALUE(bool) GUI::UseFancyNotificationsDef = true; diff --git a/src/miscellaneous/settings.h b/src/miscellaneous/settings.h index 5c9d4ac7a..35746dd5b 100755 --- a/src/miscellaneous/settings.h +++ b/src/miscellaneous/settings.h @@ -120,12 +120,18 @@ namespace GUI { KEY ListHeadersVisible; VALUE(bool) ListHeadersVisibleDef; + KEY StatusBarVisible; + VALUE(bool) StatusBarVisibleDef; + KEY HideMainWindowWhenMinimized; VALUE(bool) HideMainWindowWhenMinimizedDef; KEY UseTrayIcon; VALUE(bool) UseTrayIconDef; + KEY EnableNotifications; + VALUE(bool) EnableNotificationsDef; + KEY UseFancyNotifications; VALUE(bool) UseFancyNotificationsDef; @@ -360,6 +366,7 @@ class Settings : public QSettings { // Creates settings file in correct location. static Settings *setupSettings(QObject *parent); + // Returns properties of the actual application-wide settings. static SettingsProperties determineProperties(); private: diff --git a/src/miscellaneous/systemfactory.cpp b/src/miscellaneous/systemfactory.cpp index 6d3e7f2eb..ea424ef60 100755 --- a/src/miscellaneous/systemfactory.cpp +++ b/src/miscellaneous/systemfactory.cpp @@ -172,6 +172,20 @@ bool SystemFactory::removeTrolltechJunkRegistryKeys() { } #endif +QString SystemFactory::getUsername() const { + QString name = qgetenv("USER"); + + if (name.isEmpty()) { + name = qgetenv("USERNAME"); + } + + if (name.isEmpty()) { + name = tr("anonymous"); + } + + return name; +} + QPair SystemFactory::checkForUpdates() { QPair result; QByteArray releases_xml; @@ -258,6 +272,6 @@ void SystemFactory::checkForUpdatesOnStartup() { qApp->showGuiMessage(tr("New version available"), tr("Click the bubble for more information."), QSystemTrayIcon::Information, - NULL, false, QIcon(), qApp->mainForm(), SLOT(showUpdates())); + NULL, true, QIcon(), qApp->mainForm(), SLOT(showUpdates())); } } diff --git a/src/miscellaneous/systemfactory.h b/src/miscellaneous/systemfactory.h index aa5fcacaa..59917f0e1 100755 --- a/src/miscellaneous/systemfactory.h +++ b/src/miscellaneous/systemfactory.h @@ -80,9 +80,18 @@ class SystemFactory : public QObject { QString getAutostartDesktopFileLocation(); #endif + // Retrieves username of currently logged-in user. + QString getUsername() const; + // Tries to download list with new updates. QPair checkForUpdates(); + // Check whether given pointer belongs to instance of given class or not. + template + static bool isInstanceOf(T *ptr) { + return dynamic_cast(ptr) != NULL; + } + // Checks if update is newer than current application version. static bool isUpdateNewer(const QString &update_version); diff --git a/src/network-web/downloader.cpp b/src/network-web/downloader.cpp index 0851c7fa0..66e5a04ea 100755 --- a/src/network-web/downloader.cpp +++ b/src/network-web/downloader.cpp @@ -24,8 +24,9 @@ Downloader::Downloader(QObject *parent) : QObject(parent), m_activeReply(NULL), m_downloadManager(new SilentNetworkAccessManager(this)), - m_timer(new QTimer(this)), m_customHeaders(QHash()), m_lastOutputData(QByteArray()), - m_lastOutputError(QNetworkReply::NoError), m_lastContentType(QVariant()) { + m_timer(new QTimer(this)), m_customHeaders(QHash()), m_inputData(QByteArray()), + m_targetProtected(false), m_targetUsername(QString()), m_targetPassword(QString()), + m_lastOutputData(QByteArray()), m_lastOutputError(QNetworkReply::NoError), m_lastContentType(QVariant()) { m_timer->setInterval(DOWNLOAD_TIMEOUT); m_timer->setSingleShot(true); @@ -37,17 +38,11 @@ Downloader::~Downloader() { m_downloadManager->deleteLater(); } -void Downloader::downloadFile(const QString &url, int timeout, bool protected_contents, const QString &username, const QString &password) { +void Downloader::downloadFile(const QString &url, int timeout, bool protected_contents, const QString &username, + const QString &password) { QNetworkRequest request; - QObject originatingObject; QString non_const_url = url; - // Set credential information as originating object. - originatingObject.setProperty("protected", protected_contents); - originatingObject.setProperty("username", username); - originatingObject.setProperty("password", password); - request.setOriginatingObject(&originatingObject); - foreach (const QByteArray &header_name, m_customHeaders.keys()) { request.setRawHeader(header_name, m_customHeaders.value(header_name)); } @@ -63,11 +58,45 @@ void Downloader::downloadFile(const QString &url, int timeout, bool protected_co request.setUrl(non_const_url); } + m_targetProtected = protected_contents; + m_targetUsername = username; + m_targetPassword = password; + runGetRequest(request); } +void Downloader::uploadData(const QString &url, const QByteArray &data, int timeout, + bool protected_contents, const QString &username, const QString &password) { + QNetworkRequest request; + QString non_const_url = url; + + foreach (const QByteArray &header_name, m_customHeaders.keys()) { + request.setRawHeader(header_name, m_customHeaders.value(header_name)); + } + + m_inputData = data; + + // Set url for this request and fire it up. + m_timer->setInterval(timeout); + + if (non_const_url.startsWith(URI_SCHEME_FEED)) { + qDebug("Replacing URI schemes for '%s'.", qPrintable(non_const_url)); + request.setUrl(non_const_url.replace(QRegExp(QString('^') + URI_SCHEME_FEED), QString(URI_SCHEME_HTTP))); + } + else { + request.setUrl(non_const_url); + } + + m_targetProtected = protected_contents; + m_targetUsername = username; + m_targetPassword = password; + + runPostRequest(request, m_inputData); +} + void Downloader::finished() { QNetworkReply *reply = qobject_cast(sender()); + QNetworkAccessManager::Operation reply_operation = reply->operation(); m_timer->stop(); @@ -89,7 +118,12 @@ void Downloader::finished() { m_activeReply->deleteLater(); m_activeReply = NULL; - runGetRequest(request); + if (reply_operation == QNetworkAccessManager::GetOperation) { + runGetRequest(request); + } + else if (reply_operation == QNetworkAccessManager::PostOperation) { + runPostRequest(request, m_inputData); + } } else { // No redirection is indicated. Final file is obtained in our "reply" object. @@ -120,10 +154,26 @@ void Downloader::timeout() { } } +void Downloader::runPostRequest(const QNetworkRequest &request, const QByteArray &data) { + m_timer->start(); + m_activeReply = m_downloadManager->post(request, data); + + m_activeReply->setProperty("protected", m_targetProtected); + m_activeReply->setProperty("username", m_targetUsername); + m_activeReply->setProperty("password", m_targetPassword); + + connect(m_activeReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(progressInternal(qint64,qint64))); + connect(m_activeReply, SIGNAL(finished()), this, SLOT(finished())); +} + void Downloader::runGetRequest(const QNetworkRequest &request) { m_timer->start(); m_activeReply = m_downloadManager->get(request); + m_activeReply->setProperty("protected", m_targetProtected); + m_activeReply->setProperty("username", m_targetUsername); + m_activeReply->setProperty("password", m_targetPassword); + connect(m_activeReply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(progressInternal(qint64,qint64))); connect(m_activeReply, SIGNAL(finished()), this, SLOT(finished())); } diff --git a/src/network-web/downloader.h b/src/network-web/downloader.h index 8cbdf0366..c803c27ea 100755 --- a/src/network-web/downloader.h +++ b/src/network-web/downloader.h @@ -49,6 +49,12 @@ class Downloader : public QObject { void downloadFile(const QString &url, int timeout = DOWNLOAD_TIMEOUT, bool protected_contents = false, const QString &username = QString(), const QString &password = QString()); + // Performs asynchronous upload of given data as HTTP POST. + // User needs to setup "Content-Encoding" header which + // matches encoding of the data. + void uploadData(const QString &url, const QByteArray &data, int timeout = DOWNLOAD_TIMEOUT, + bool protected_contents = false, const QString &username = QString(), const QString &password = QString()); + signals: // Emitted when new progress is known. void progress(qint64 bytes_received, qint64 bytes_total); @@ -65,6 +71,7 @@ class Downloader : public QObject { void timeout(); private: + void runPostRequest(const QNetworkRequest &request, const QByteArray &data); void runGetRequest(const QNetworkRequest &request); private: @@ -72,6 +79,11 @@ class Downloader : public QObject { SilentNetworkAccessManager *m_downloadManager; QTimer *m_timer; QHash m_customHeaders; + QByteArray m_inputData; + + bool m_targetProtected; + QString m_targetUsername; + QString m_targetPassword; // Response data. QByteArray m_lastOutputData; diff --git a/src/network-web/downloadmanager.cpp b/src/network-web/downloadmanager.cpp index 62c96616b..ab039fe63 100755 --- a/src/network-web/downloadmanager.cpp +++ b/src/network-web/downloadmanager.cpp @@ -559,6 +559,10 @@ QNetworkAccessManager *DownloadManager::networkManager() const { return m_networkManager; } +int DownloadManager::totalDownloads() const { + return m_downloads.size(); +} + void DownloadManager::itemFinished() { emit downloadFinished(); } @@ -789,6 +793,11 @@ bool DownloadModel::removeRows(int row, int count, const QModelIndex &parent) { } m_downloadManager->m_autoSaver->changeOccurred(); + + if (m_downloadManager->totalDownloads() == 0) { + m_downloadManager->m_ui->m_btnCleanup->setEnabled(false); + } + return true; } diff --git a/src/network-web/downloadmanager.h b/src/network-web/downloadmanager.h old mode 100644 new mode 100755 index ea522af12..b479b1d14 --- a/src/network-web/downloadmanager.h +++ b/src/network-web/downloadmanager.h @@ -111,6 +111,7 @@ class DownloadManager : public TabContent { WebBrowser *webBrowser(); QNetworkAccessManager *networkManager() const; + int totalDownloads() const; int activeDownloads() const; int downloadProgress() const; diff --git a/src/network-web/networkfactory.cpp b/src/network-web/networkfactory.cpp index 4acac8bf8..35d2463fc 100755 --- a/src/network-web/networkfactory.cpp +++ b/src/network-web/networkfactory.cpp @@ -148,6 +148,27 @@ QNetworkReply::NetworkError NetworkFactory::downloadIcon(const QList &u return network_result; } +NetworkResult NetworkFactory::uploadData(const QString &url, int timeout, const QByteArray &input_data, + const QString &input_content_type, QByteArray &output, + bool protected_contents, const QString &username, const QString &password) { + Downloader downloader; + QEventLoop loop; + NetworkResult result; + + downloader.appendRawHeader("Content-Type", input_content_type.toLocal8Bit()); + + // We need to quit event loop when the download finishes. + QObject::connect(&downloader, SIGNAL(completed(QNetworkReply::NetworkError)), &loop, SLOT(quit())); + + downloader.uploadData(url, input_data, timeout, protected_contents, username, password); + loop.exec(); + output = downloader.lastOutputData(); + result.first = downloader.lastOutputError(); + result.second = downloader.lastContentType(); + + return result; +} + NetworkResult NetworkFactory::downloadFeedFile(const QString &url, int timeout, QByteArray &output, bool protected_contents, const QString &username, const QString &password) { diff --git a/src/network-web/networkfactory.h b/src/network-web/networkfactory.h old mode 100644 new mode 100755 index 8d11e2a08..d14e8d7d5 --- a/src/network-web/networkfactory.h +++ b/src/network-web/networkfactory.h @@ -43,6 +43,11 @@ class NetworkFactory { // given URL belongs to. static QNetworkReply::NetworkError downloadIcon(const QList &urls, int timeout, QIcon &output); + static NetworkResult uploadData(const QString &url, int timeout, const QByteArray &input_data, + const QString &input_content_type, QByteArray &output, + bool protected_contents = false, const QString &username = QString(), + const QString &password = QString()); + static NetworkResult downloadFeedFile(const QString &url, int timeout, QByteArray &output, bool protected_contents = false, const QString &username = QString(), const QString &password = QString()); diff --git a/src/network-web/silentnetworkaccessmanager.cpp b/src/network-web/silentnetworkaccessmanager.cpp old mode 100644 new mode 100755 index d7acc324f..5b2e5755c --- a/src/network-web/silentnetworkaccessmanager.cpp +++ b/src/network-web/silentnetworkaccessmanager.cpp @@ -44,20 +44,20 @@ SilentNetworkAccessManager *SilentNetworkAccessManager::instance() { } void SilentNetworkAccessManager::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) { - QObject *originating_object = reply->request().originatingObject(); + QList keys = authenticator->options().keys(); - if (originating_object->property("protected").toBool()) { + if (reply->property("protected").toBool()) { // This feed contains authentication information, it is good. - authenticator->setUser(originating_object->property("username").toString()); - authenticator->setPassword(originating_object->property("password").toString()); + authenticator->setUser(reply->property("username").toString()); + authenticator->setPassword(reply->property("password").toString()); reply->setProperty("authentication-given", true); - qDebug("Feed '%s' requested authentication and got it.", qPrintable(reply->url().toString())); + qDebug("Item '%s' requested authentication and got it.", qPrintable(reply->url().toString())); } else { reply->setProperty("authentication-given", false); // Authentication is required but this feed does not contain it. - qWarning("Feed '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString())); + qWarning("Item '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString())); } } diff --git a/src/network-web/webbrowser.cpp b/src/network-web/webbrowser.cpp index eda30878a..9bd5ea1a2 100755 --- a/src/network-web/webbrowser.cpp +++ b/src/network-web/webbrowser.cpp @@ -27,6 +27,7 @@ #include "gui/tabwidget.h" #include "gui/feedmessageviewer.h" #include "gui/feedsview.h" +#include "services/standard/standardserviceroot.h" #include #include @@ -214,7 +215,19 @@ void WebBrowser::onIconChanged() { void WebBrowser::addFeedFromWebsite(const QString &feed_link) { qApp->clipboard()->setText(feed_link); - qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->addNewFeed(); + + StandardServiceRoot *service = qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->standardServiceRoot(); + + if (service != NULL) { + service->addNewFeed(); + } + else { + qApp->showGuiMessage(tr("Cannot add feed"), + tr("You cannot add this feed to %1 because standard RSS/ATOM account is not enabled. Enable it first.").arg(APP_NAME), + QSystemTrayIcon::Warning, + qApp->mainForm(), + true); + } } void WebBrowser::onTitleChanged(const QString &new_title) { diff --git a/src/network-web/webbrowsernetworkaccessmanager.cpp b/src/network-web/webbrowsernetworkaccessmanager.cpp index 0adf4ff1a..b8e433c24 100755 --- a/src/network-web/webbrowsernetworkaccessmanager.cpp +++ b/src/network-web/webbrowsernetworkaccessmanager.cpp @@ -39,7 +39,7 @@ WebBrowserNetworkAccessManager::~WebBrowserNetworkAccessManager() { void WebBrowserNetworkAccessManager::onAuthenticationRequired(QNetworkReply *reply, QAuthenticator *authenticator) { Q_UNUSED(authenticator); - // TODO: Support authentication for web pages. + // FIXME: Support authentication for web pages. qDebug("URL '%s' requested authentication but username/password is not available.", qPrintable(reply->url().toString())); } diff --git a/src/qt-json/json.cpp b/src/qt-json/json.cpp new file mode 100755 index 000000000..1aa505b87 --- /dev/null +++ b/src/qt-json/json.cpp @@ -0,0 +1,603 @@ +/** + * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. + * Copyright (C) 2011 Eeli Reilin + * + * 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 . + */ + +/** + * \file json.cpp + */ + +#include +#include +#include "json.h" + +namespace QtJson { + static QString dateFormat, dateTimeFormat; + + static QString sanitizeString(QString str); + static QByteArray join(const QList &list, const QByteArray &sep); + static QVariant parseValue(const QString &json, int &index, bool &success); + static QVariant parseObject(const QString &json, int &index, bool &success); + static QVariant parseArray(const QString &json, int &index, bool &success); + static QVariant parseString(const QString &json, int &index, bool &success); + static QVariant parseNumber(const QString &json, int &index); + static int lastIndexOfNumber(const QString &json, int index); + static void eatWhitespace(const QString &json, int &index); + static int lookAhead(const QString &json, int index); + static int nextToken(const QString &json, int &index); + + template + QByteArray serializeMap(const T &map, bool &success) { + QByteArray str = "{ "; + QList pairs; + for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { + QByteArray serializedValue = serialize(it.value()); + if (serializedValue.isNull()) { + success = false; + break; + } + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + } + + str += join(pairs, ", "); + str += " }"; + return str; + } + + void insert(QVariant &v, const QString &key, const QVariant &value); + void append(QVariant &v, const QVariant &value); + + template + void cloneMap(QVariant &json, const T &map) { + for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { + insert(json, it.key(), (*it)); + } + } + + template + void cloneList(QVariant &json, const T &list) { + for (typename T::const_iterator it = list.begin(), itend = list.end(); it != itend; ++it) { + append(json, (*it)); + } + } + + /** + * parse + */ + QVariant parse(const QString &json) { + bool success = true; + return parse(json, success); + } + + /** + * parse + */ + QVariant parse(const QString &json, bool &success) { + success = true; + + // Return an empty QVariant if the JSON data is either null or empty + if (!json.isNull() || !json.isEmpty()) { + QString data = json; + // We'll start from index 0 + int index = 0; + + // Parse the first value + QVariant value = parseValue(data, index, success); + + // Return the parsed value + return value; + } else { + // Return the empty QVariant + return QVariant(); + } + } + + /** + * clone + */ + QVariant clone(const QVariant &data) { + QVariant v; + + if (data.type() == QVariant::Map) { + cloneMap(v, data.toMap()); + } else if (data.type() == QVariant::Hash) { + cloneMap(v, data.toHash()); + } else if (data.type() == QVariant::List) { + cloneList(v, data.toList()); + } else if (data.type() == QVariant::StringList) { + cloneList(v, data.toStringList()); + } else { + v = QVariant(data); + } + + return v; + } + + /** + * insert value (map case) + */ + void insert(QVariant &v, const QString &key, const QVariant &value) { + if (!v.canConvert()) v = QVariantMap(); + QVariantMap *p = (QVariantMap *)v.data(); + p->insert(key, clone(value)); + } + + /** + * append value (list case) + */ + void append(QVariant &v, const QVariant &value) { + if (!v.canConvert()) v = QVariantList(); + QVariantList *p = (QVariantList *)v.data(); + p->append(value); + } + + QByteArray serialize(const QVariant &data) { + bool success = true; + return serialize(data, success); + } + + QByteArray serialize(const QVariant &data, bool &success) { + QByteArray str; + success = true; + + if (!data.isValid()) { // invalid or null? + str = "null"; + } else if ((data.type() == QVariant::List) || + (data.type() == QVariant::StringList)) { // variant is a list? + QList values; + const QVariantList list = data.toList(); + Q_FOREACH(const QVariant& v, list) { + QByteArray serializedValue = serialize(v); + if (serializedValue.isNull()) { + success = false; + break; + } + values << serializedValue; + } + + str = "[ " + join( values, ", " ) + " ]"; + } else if (data.type() == QVariant::Hash) { // variant is a hash? + str = serializeMap<>(data.toHash(), success); + } else if (data.type() == QVariant::Map) { // variant is a map? + str = serializeMap<>(data.toMap(), success); + } else if ((data.type() == QVariant::String) || + (data.type() == QVariant::ByteArray)) {// a string or a byte array? + str = sanitizeString(data.toString()).toUtf8(); + } else if (data.type() == QVariant::Double) { // double? + double value = data.toDouble(&success); + if (success) { + str = QByteArray::number(value, 'g'); + if (!str.contains(".") && ! str.contains("e")) { + str += ".0"; + } + } + } else if (data.type() == QVariant::Bool) { // boolean value? + str = data.toBool() ? "true" : "false"; + } else if (data.type() == QVariant::ULongLong) { // large unsigned number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { // any signed number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { //TODO: this code is never executed because all smaller types can be converted to qlonglong + str = QString::number(data.value()).toUtf8(); + } else if (data.type() == QVariant::DateTime) { // datetime value? + str = sanitizeString(dateTimeFormat.isEmpty() + ? data.toDateTime().toString() + : data.toDateTime().toString(dateTimeFormat)).toUtf8(); + } else if (data.type() == QVariant::Date) { // date value? + str = sanitizeString(dateTimeFormat.isEmpty() + ? data.toDate().toString() + : data.toDate().toString(dateFormat)).toUtf8(); + } else if (data.canConvert()) { // can value be converted to string? + // this will catch QUrl, ... (all other types which can be converted to string) + str = sanitizeString(data.toString()).toUtf8(); + } else { + success = false; + } + + if (success) { + return str; + } + return QByteArray(); + } + + QString serializeStr(const QVariant &data) { + return QString::fromUtf8(serialize(data)); + } + + QString serializeStr(const QVariant &data, bool &success) { + return QString::fromUtf8(serialize(data, success)); + } + + + /** + * \enum JsonToken + */ + enum JsonToken { + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 + }; + + static QString sanitizeString(QString str) { + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); + } + + static QByteArray join(const QList &list, const QByteArray &sep) { + QByteArray res; + Q_FOREACH(const QByteArray &i, list) { + if (!res.isEmpty()) { + res += sep; + } + res += i; + } + return res; + } + + /** + * parseValue + */ + static QVariant parseValue(const QString &json, int &index, bool &success) { + // Determine what kind of data we should parse by + // checking out the upcoming token + switch(lookAhead(json, index)) { + case JsonTokenString: + return parseString(json, index, success); + case JsonTokenNumber: + return parseNumber(json, index); + case JsonTokenCurlyOpen: + return parseObject(json, index, success); + case JsonTokenSquaredOpen: + return parseArray(json, index, success); + case JsonTokenTrue: + nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } + + // If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); + } + + /** + * parseObject + */ + static QVariant parseObject(const QString &json, int &index, bool &success) { + QVariantMap map; + int token; + + // Get rid of the whitespace and increment index + nextToken(json, index); + + // Loop through all of the key/value pairs of the object + bool done = false; + while (!done) { + // Get the upcoming token + token = lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantMap(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenCurlyClose) { + nextToken(json, index); + return map; + } else { + // Parse the key/value pair's name + QString name = parseString(json, index, success).toString(); + + if (!success) { + return QVariantMap(); + } + + // Get the next token + token = nextToken(json, index); + + // If the next token is not a colon, flag the failure + // return an empty QVariant + if (token != JsonTokenColon) { + success = false; + return QVariant(QVariantMap()); + } + + // Parse the key/value pair's value + QVariant value = parseValue(json, index, success); + + if (!success) { + return QVariantMap(); + } + + // Assign the value to the key in the map + map[name] = value; + } + } + + // Return the map successfully + return QVariant(map); + } + + /** + * parseArray + */ + static QVariant parseArray(const QString &json, int &index, bool &success) { + QVariantList list; + + nextToken(json, index); + + bool done = false; + while(!done) { + int token = lookAhead(json, index); + + if (token == JsonTokenNone) { + success = false; + return QVariantList(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenSquaredClose) { + nextToken(json, index); + break; + } else { + QVariant value = parseValue(json, index, success); + if (!success) { + return QVariantList(); + } + list.push_back(value); + } + } + + return QVariant(list); + } + + /** + * parseString + */ + static QVariant parseString(const QString &json, int &index, bool &success) { + QString s; + QChar c; + + eatWhitespace(json, index); + + c = json[index++]; + + bool complete = false; + while(!complete) { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + complete = true; + break; + } else if (c == '\\') { + if (index == json.size()) { + break; + } + + c = json[index++]; + + if (c == '\"') { + s.append('\"'); + } else if (c == '\\') { + s.append('\\'); + } else if (c == '/') { + s.append('/'); + } else if (c == 'b') { + s.append('\b'); + } else if (c == 'f') { + s.append('\f'); + } else if (c == 'n') { + s.append('\n'); + } else if (c == 'r') { + s.append('\r'); + } else if (c == 't') { + s.append('\t'); + } else if (c == 'u') { + int remainingLength = json.size() - index; + if (remainingLength >= 4) { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } else { + break; + } + } + } else { + s.append(c); + } + } + + if (!complete) { + success = false; + return QVariant(); + } + + return QVariant(s); + } + + /** + * parseNumber + */ + static QVariant parseNumber(const QString &json, int &index) { + eatWhitespace(json, index); + + int lastIndex = lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; + + numberStr = json.mid(index, charLength); + + index = lastIndex + 1; + bool ok; + + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(NULL)); + } else if (numberStr.startsWith('-')) { + int i = numberStr.toInt(&ok); + if (!ok) { + qlonglong ll = numberStr.toLongLong(&ok); + return ok ? ll : QVariant(numberStr); + } + return i; + } else { + uint u = numberStr.toUInt(&ok); + if (!ok) { + qulonglong ull = numberStr.toULongLong(&ok); + return ok ? ull : QVariant(numberStr); + } + return u; + } + } + + /** + * lastIndexOfNumber + */ + static int lastIndexOfNumber(const QString &json, int index) { + int lastIndex; + + for(lastIndex = index; lastIndex < json.size(); lastIndex++) { + if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) { + break; + } + } + + return lastIndex -1; + } + + /** + * eatWhitespace + */ + static void eatWhitespace(const QString &json, int &index) { + for(; index < json.size(); index++) { + if (QString(" \t\n\r").indexOf(json[index]) == -1) { + break; + } + } + } + + /** + * lookAhead + */ + static int lookAhead(const QString &json, int index) { + int saveIndex = index; + return nextToken(json, saveIndex); + } + + /** + * nextToken + */ + static int nextToken(const QString &json, int &index) { + eatWhitespace(json, index); + + if (index == json.size()) { + return JsonTokenNone; + } + + QChar c = json[index]; + index++; + switch(c.toLatin1()) { + case '{': return JsonTokenCurlyOpen; + case '}': return JsonTokenCurlyClose; + case '[': return JsonTokenSquaredOpen; + case ']': return JsonTokenSquaredClose; + case ',': return JsonTokenComma; + case '"': return JsonTokenString; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': return JsonTokenNumber; + case ':': return JsonTokenColon; + } + index--; // ^ WTF? + + int remainingLength = json.size() - index; + + // True + if (remainingLength >= 4) { + if (json[index] == 't' && json[index + 1] == 'r' && + json[index + 2] == 'u' && json[index + 3] == 'e') { + index += 4; + return JsonTokenTrue; + } + } + + // False + if (remainingLength >= 5) { + if (json[index] == 'f' && json[index + 1] == 'a' && + json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JsonTokenFalse; + } + } + + // Null + if (remainingLength >= 4) { + if (json[index] == 'n' && json[index + 1] == 'u' && + json[index + 2] == 'l' && json[index + 3] == 'l') { + index += 4; + return JsonTokenNull; + } + } + + return JsonTokenNone; + } + + void setDateTimeFormat(const QString &format) { + dateTimeFormat = format; + } + + void setDateFormat(const QString &format) { + dateFormat = format; + } + + QString getDateTimeFormat() { + return dateTimeFormat; + } + + QString getDateFormat() { + return dateFormat; + } + +} //end namespace diff --git a/src/qt-json/json.h b/src/qt-json/json.h new file mode 100755 index 000000000..728328166 --- /dev/null +++ b/src/qt-json/json.h @@ -0,0 +1,134 @@ +/** + * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. + * Copyright (C) 2011 Eeli Reilin + * + * 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 . + */ + +/** + * \file json.h + */ + +#ifndef JSON_H +#define JSON_H + +#include +#include + + +/** + * \namespace QtJson + * \brief A JSON data parser + * + * Json parses a JSON data into a QVariant hierarchy. + */ +namespace QtJson { + typedef QVariantMap JsonObject; + typedef QVariantList JsonArray; + + /** + * Clone a JSON object (makes a deep copy) + * + * \param data The JSON object + */ + QVariant clone(const QVariant &data); + + /** + * Insert value to JSON object (QVariantMap) + * + * \param v The JSON object + * \param key The key + * \param value The value + */ + void insert(QVariant &v, const QString &key, const QVariant &value); + + /** + * Append value to JSON array (QVariantList) + * + * \param v The JSON array + * \param value The value + */ + void append(QVariant &v, const QVariant &value); + + /** + * Parse a JSON string + * + * \param json The JSON data + */ + QVariant parse(const QString &json); + + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + QVariant parse(const QString &json, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data, bool &success); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data); + + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data, bool &success); + + /** + * This method sets date(time) format to be used for QDateTime::toString + * If QString is empty, Qt::TextDate is used. + * + * \param format The JSON data generated by the parser. + */ + void setDateTimeFormat(const QString& format); + void setDateFormat(const QString& format); + + /** + * This method gets date(time) format to be used for QDateTime::toString + * If QString is empty, Qt::TextDate is used. + */ + QString getDateTimeFormat(); + QString getDateFormat(); +} + +#endif //JSON_H diff --git a/src/services/abstract/category.cpp b/src/services/abstract/category.cpp new file mode 100755 index 000000000..097c9bc56 --- /dev/null +++ b/src/services/abstract/category.cpp @@ -0,0 +1,26 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/abstract/category.h" + + +Category::Category(RootItem *parent) : RootItem(parent) { + setKind(RootItemKind::Category); +} + +Category::~Category() { +} diff --git a/src/services/abstract/category.h b/src/services/abstract/category.h new file mode 100755 index 000000000..ea339831a --- /dev/null +++ b/src/services/abstract/category.h @@ -0,0 +1,32 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef CATEGORY_H +#define CATEGORY_H + +#include "services/abstract/rootitem.h" + + +class Category : public RootItem { + Q_OBJECT + + public: + explicit Category(RootItem *parent = NULL); + virtual ~Category(); +}; + +#endif // CATEGORY_H diff --git a/src/services/abstract/feed.cpp b/src/services/abstract/feed.cpp new file mode 100755 index 000000000..b838c33fb --- /dev/null +++ b/src/services/abstract/feed.cpp @@ -0,0 +1,52 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/abstract/feed.h" + +#include "definitions/definitions.h" + + +Feed::Feed(RootItem *parent) : RootItem(parent) { + m_status = Normal; + m_autoUpdateType = DefaultAutoUpdate; + m_autoUpdateInitialInterval = DEFAULT_AUTO_UPDATE_INTERVAL; + m_autoUpdateRemainingInterval = DEFAULT_AUTO_UPDATE_INTERVAL; + + setKind(RootItemKind::Feed); +} + +Feed::~Feed() { +} + +QVariant Feed::data(int column, int role) const { + switch (role) { + case Qt::ForegroundRole: + switch (status()) { + case NewMessages: + return QColor(Qt::blue); + + case Error: + return QColor(Qt::red); + + default: + return QVariant(); + } + + default: + return RootItem::data(column, role); + } +} diff --git a/src/services/abstract/feed.h b/src/services/abstract/feed.h new file mode 100755 index 000000000..4b9b9a550 --- /dev/null +++ b/src/services/abstract/feed.h @@ -0,0 +1,114 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef FEED_H +#define FEED_H + +#include "services/abstract/rootitem.h" + +#include "core/message.h" + +#include + + +// Base class for "feed" nodes. +class Feed : public RootItem { + Q_OBJECT + + public: + // Specifies the auto-update strategy for the feed. + enum AutoUpdateType { + DontAutoUpdate = 0, + DefaultAutoUpdate = 1, + SpecificAutoUpdate = 2 + }; + + // Specifies the actual "status" of the feed. + // For example if it has new messages, error + // occurred, and so on. + enum Status { + Normal = 0, + NewMessages = 1, + Error = 2, + ParsingError = 3, + OtherError = 4 + }; + + // Constructors. + explicit Feed(RootItem *parent = NULL); + virtual ~Feed(); + + ///////////////////////////////////////// + // /* Members to override. + ///////////////////////////////////////// + + // Performs synchronous update and returns number of newly updated messages. + // NOTE: This is called from worker thread, not from main UI thread. + // NOTE: This should COMPLETELY download ALL messages from online source + // into locale "Messages" table, INCLUDING contents (or excerpts) of those + // messages. + virtual int update() = 0; + + QVariant data(int column, int role) const; + + ///////////////////////////////////////// + // Members to override. */ + ///////////////////////////////////////// + + inline int autoUpdateInitialInterval() const { + return m_autoUpdateInitialInterval; + } + + inline void setAutoUpdateInitialInterval(int auto_update_interval) { + // If new initial auto-update interval is set, then + // we should reset time that remains to the next auto-update. + m_autoUpdateInitialInterval = auto_update_interval; + m_autoUpdateRemainingInterval = auto_update_interval; + } + + inline AutoUpdateType autoUpdateType() const { + return m_autoUpdateType; + } + + inline void setAutoUpdateType(const AutoUpdateType &autoUpdateType) { + m_autoUpdateType = autoUpdateType; + } + + inline int autoUpdateRemainingInterval() const { + return m_autoUpdateRemainingInterval; + } + + inline void setAutoUpdateRemainingInterval(int autoUpdateRemainingInterval) { + m_autoUpdateRemainingInterval = autoUpdateRemainingInterval; + } + + inline Status status() const { + return m_status; + } + + inline void setStatus(const Status &status) { + m_status = status; + } + + private: + Status m_status; + AutoUpdateType m_autoUpdateType; + int m_autoUpdateInitialInterval; + int m_autoUpdateRemainingInterval; +}; + +#endif // FEED_H diff --git a/src/services/abstract/recyclebin.cpp b/src/services/abstract/recyclebin.cpp new file mode 100755 index 000000000..7f0ad4fc1 --- /dev/null +++ b/src/services/abstract/recyclebin.cpp @@ -0,0 +1,241 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/abstract/recyclebin.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/textfactory.h" +#include "services/abstract/serviceroot.h" + +#include + + +RecycleBin::RecycleBin(RootItem *parent_item) : RootItem(parent_item) { + setKind(RootItemKind::Bin); + setIcon(qApp->icons()->fromTheme(QSL("folder-recycle-bin"))); + setTitle(tr("Recycle bin")); + setDescription(tr("Recycle bin contains all deleted messages from all feeds.")); + setCreationDate(QDateTime::currentDateTime()); +} + +RecycleBin::~RecycleBin() { +} + +int RecycleBin::countOfUnreadMessages() const { + return m_unreadCount; +} + +int RecycleBin::countOfAllMessages() const { + return m_totalCount; +} + +void RecycleBin::updateCounts(bool update_total_count) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_all(database); + ServiceRoot *parent_root = getParentServiceRoot(); + + query_all.setForwardOnly(true); + query_all.prepare("SELECT count(*) FROM Messages " + "WHERE is_read = 0 AND is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + query_all.bindValue(QSL(":account_id"), parent_root->accountId()); + + + if (query_all.exec() && query_all.next()) { + m_unreadCount = query_all.value(0).toInt(); + } + else { + m_unreadCount = 0; + } + + if (update_total_count) { + query_all.prepare("SELECT count(*) FROM Messages " + "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + query_all.bindValue(QSL(":account_id"), parent_root->accountId()); + + if (query_all.exec() && query_all.next()) { + m_totalCount = query_all.value(0).toInt(); + } + else { + m_totalCount = 0; + } + } +} + +QVariant RecycleBin::data(int column, int role) const { + switch (role) { + case Qt::ToolTipRole: + return tr("Recycle bin\n\n%1").arg(tr("%n deleted message(s).", 0, countOfAllMessages())); + + default: + return RootItem::data(column, role); + } +} + +QList RecycleBin::undeletedMessages() const { + QList messages; + int account_id = const_cast(this)->getParentServiceRoot()->accountId(); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_read_msg(database); + + query_read_msg.setForwardOnly(true); + query_read_msg.prepare("SELECT * " + "FROM Messages " + "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + query_read_msg.bindValue(QSL(":account_id"), account_id); + + // FIXME: Fix those const functions, this is fucking ugly. + + if (query_read_msg.exec()) { + while (query_read_msg.next()) { + bool decoded; + Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded); + + if (decoded) { + messages.append(message); + } + + messages.append(message); + } + } + + return messages; +} + +bool RecycleBin::markAsReadUnread(RootItem::ReadStatus status) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (!db_handle.transaction()) { + qWarning("Starting transaction for recycle bin read change."); + return false; + } + + QSqlQuery query_read_msg(db_handle); + ServiceRoot *parent_root = getParentServiceRoot(); + + query_read_msg.setForwardOnly(true); + + if (!query_read_msg.prepare("UPDATE Messages SET is_read = :read " + "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;")) { + qWarning("Query preparation failed for recycle bin read change."); + + db_handle.rollback(); + return false; + } + + query_read_msg.bindValue(QSL(":read"), status == RootItem::Read ? 1 : 0); + query_read_msg.bindValue(QSL(":account_id"), parent_root->accountId()); + + if (!query_read_msg.exec()) { + qDebug("Query execution for recycle bin read change failed."); + db_handle.rollback(); + } + + // Commit changes. + if (db_handle.commit()) { + updateCounts(false); + + parent_root->itemChanged(QList() << this); + parent_root->requestReloadMessageList(status == RootItem::Read); + return true; + } + else { + return db_handle.rollback(); + } +} + +bool RecycleBin::cleanMessages(bool clear_only_read) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (!db_handle.transaction()) { + qWarning("Starting transaction for recycle bin emptying."); + return false; + } + + ServiceRoot *parent_root = getParentServiceRoot(); + QSqlQuery query_empty_bin(db_handle); + + query_empty_bin.setForwardOnly(true); + + if (clear_only_read) { + query_empty_bin.prepare("UPDATE Messages SET is_pdeleted = 1 " + "WHERE is_read = 1 AND is_deleted = 1 AND account_id = :account_id;"); + } + else { + query_empty_bin.prepare(QSL("UPDATE Messages SET is_pdeleted = 1 WHERE is_deleted = 1 AND account_id = :account_id;")); + } + + query_empty_bin.bindValue(QSL(":account_id"), parent_root->accountId()); + + if (!query_empty_bin.exec()) { + qWarning("Query execution failed for recycle bin emptying."); + + db_handle.rollback(); + return false; + } + + // Commit changes. + if (db_handle.commit()) { + updateCounts(true); + parent_root->itemChanged(QList() << this); + parent_root->requestReloadMessageList(true); + return true; + } + else { + return db_handle.rollback(); + } +} + +bool RecycleBin::empty() { + return cleanMessages(false); +} + +bool RecycleBin::restore() { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (!db_handle.transaction()) { + qWarning("Starting transaction for recycle bin restoring."); + return false; + } + + ServiceRoot *parent_root = getParentServiceRoot(); + QSqlQuery query_empty_bin(db_handle); + + query_empty_bin.setForwardOnly(true); + query_empty_bin.prepare("UPDATE Messages SET is_deleted = 0 " + "WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;"); + query_empty_bin.bindValue(QSL(":account_id"), parent_root->accountId()); + + if (!query_empty_bin.exec()) { + qWarning("Query execution failed for recycle bin restoring."); + + db_handle.rollback(); + return false; + } + + // Commit changes. + if (db_handle.commit()) { + parent_root->updateCounts(true); + parent_root->itemChanged(parent_root->getSubTree()); + parent_root->requestReloadMessageList(true); + parent_root->requestFeedReadFilterReload(); + return true; + } + else { + return db_handle.rollback(); + } +} diff --git a/src/core/recyclebin.h b/src/services/abstract/recyclebin.h old mode 100644 new mode 100755 similarity index 59% rename from src/core/recyclebin.h rename to src/services/abstract/recyclebin.h index 0ffcebda5..a7d26278f --- a/src/core/recyclebin.h +++ b/src/services/abstract/recyclebin.h @@ -1,50 +1,65 @@ -// This file is part of RSS Guard. -// -// Copyright (C) 2011-2015 by Martin Rotter -// -// RSS Guard 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. -// -// RSS Guard 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 RSS Guard. If not, see . - -#ifndef RECYCLEBIN_H -#define RECYCLEBIN_H - -#include "core/rootitem.h" - -#include - - -class RecycleBin : public RootItem { - Q_DECLARE_TR_FUNCTIONS(RecycleBin) - - public: - explicit RecycleBin(RootItem *parent = NULL); - virtual ~RecycleBin(); - - int childCount() const; - void appendChild(RootItem *child); - int countOfUnreadMessages() const; - int countOfAllMessages() const; - QVariant data(int column, int role) const; - - bool empty(); - bool restore(); - - public slots: - void updateCounts(bool update_total_count); - - private: - int m_totalCount; - int m_unreadCount; -}; - -#endif // RECYCLEBIN_H +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef RECYCLEBIN_H +#define RECYCLEBIN_H + +#include "services/abstract/rootitem.h" + + +class RecycleBin : public RootItem { + Q_OBJECT + + public: + explicit RecycleBin(RootItem *parent_item = NULL); + virtual ~RecycleBin(); + + QVariant data(int column, int role) const; + + QList undeletedMessages() const; + + bool markAsReadUnread(ReadStatus status); + bool cleanMessages(bool clear_only_read); + + int countOfUnreadMessages() const; + int countOfAllMessages() const; + + void updateCounts(bool update_total_count); + + public slots: + ///////////////////////////////////////// + // /* Members to override. + ///////////////////////////////////////// + + // Empties the bin - removes all messages from it (does not remove + // them from DB, just permanently hide them, so that they are not + // re-downloaded). + virtual bool empty(); + + // Performs complete restoration of all messages contained in the bin + virtual bool restore(); + + ///////////////////////////////////////// + // Members to override. */ + ///////////////////////////////////////// + + private: + int m_totalCount; + int m_unreadCount; +}; + +#endif // RECYCLEBIN_H diff --git a/src/services/abstract/rootitem.cpp b/src/services/abstract/rootitem.cpp new file mode 100755 index 000000000..f7745907b --- /dev/null +++ b/src/services/abstract/rootitem.cpp @@ -0,0 +1,388 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/abstract/rootitem.h" + +#include "services/abstract/serviceroot.h" +#include "services/abstract/feed.h" +#include "services/abstract/category.h" +#include "services/abstract/recyclebin.h" +#include "miscellaneous/application.h" + +#include + + +RootItem::RootItem(RootItem *parent_item) + : QObject(NULL), + m_kind(RootItemKind::Root), + m_id(NO_PARENT_CATEGORY), + m_title(QString()), + m_description(QString()), + m_icon(QIcon()), + m_creationDate(QDateTime()), + m_childItems(QList()), + m_parentItem(parent_item) { + setupFonts(); +} + +RootItem::~RootItem() { + qDeleteAll(m_childItems); +} + +QList RootItem::contextMenu() { + return QList(); +} + +bool RootItem::canBeEdited() { + return false; +} + +bool RootItem::editViaGui() { + return false; +} + +bool RootItem::canBeDeleted() { + return false; +} + +bool RootItem::deleteViaGui() { + return false; +} + +bool RootItem::markAsReadUnread(ReadStatus status) { + bool result = true; + + foreach (RootItem *child, m_childItems) { + result &= child->markAsReadUnread(status); + } + + return result; +} + +QList RootItem::undeletedMessages() const { + QList messages; + + foreach (RootItem *child, m_childItems) { + messages.append(child->undeletedMessages()); + } + + return messages; +} + +bool RootItem::cleanMessages(bool clear_only_read) { + bool result = true; + RecycleBin *bin = NULL; + + foreach (RootItem *child, m_childItems) { + if (child->kind() == RootItemKind::Bin) { + bin = qobject_cast(child); + } + else { + result &= child->cleanMessages(clear_only_read); + } + } + + if (bin != NULL) { + result &= bin->cleanMessages(clear_only_read); + } + + return result; +} + +void RootItem::updateCounts(bool including_total_count) { + foreach (RootItem *child, m_childItems) { + child->updateCounts(including_total_count); + } +} + +void RootItem::setupFonts() { + m_normalFont = Application::font("FeedsView"); + m_boldFont = m_normalFont; + m_boldFont.setBold(true); +} + +int RootItem::row() const { + if (m_parentItem) { + return m_parentItem->m_childItems.indexOf(const_cast(this)); + } + else { + // This item has no parent. Therefore, its row index is 0. + return 0; + } +} + +QVariant RootItem::data(int column, int role) const { + Q_UNUSED(column) + Q_UNUSED(role) + + switch (role) { + case Qt::ToolTipRole: + if (column == FDS_MODEL_TITLE_INDEX) { + return m_title; + } + else if (column == FDS_MODEL_COUNTS_INDEX) { + //: Tooltip for "unread" column of feed list. + return tr("%n unread message(s).", 0, countOfUnreadMessages()); + } + else { + return QVariant(); + } + + case Qt::EditRole: + if (column == FDS_MODEL_TITLE_INDEX) { + return m_title; + } + else if (column == FDS_MODEL_COUNTS_INDEX) { + return countOfUnreadMessages(); + } + else { + return QVariant(); + } + + case Qt::FontRole: + return countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont; + + case Qt::DisplayRole: + if (column == FDS_MODEL_TITLE_INDEX) { + return m_title; + } + else if (column == FDS_MODEL_COUNTS_INDEX) { + int count_all = countOfAllMessages(); + int count_unread = countOfUnreadMessages(); + + return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString() + .replace(PLACEHOLDER_UNREAD_COUNTS, count_unread < 0 ? QSL("-") : QString::number(count_unread)) + .replace(PLACEHOLDER_ALL_COUNTS, count_all < 0 ? QSL("-") : QString::number(count_all)); + } + else { + return QVariant(); + } + + case Qt::DecorationRole: + if (column == FDS_MODEL_TITLE_INDEX) { + return icon(); + } + else { + return QVariant(); + } + + case Qt::TextAlignmentRole: + if (column == FDS_MODEL_COUNTS_INDEX) { + return Qt::AlignCenter; + } + else { + return QVariant(); + } + + default: + return QVariant(); + } +} + +Qt::ItemFlags RootItem::additionalFlags() const { + return Qt::NoItemFlags; +} + +bool RootItem::performDragDropChange(RootItem *target_item) { + return false; +} + +int RootItem::countOfAllMessages() const { + int total_count = 0; + + foreach (RootItem *child_item, m_childItems) { + total_count += child_item->countOfAllMessages(); + } + + return total_count; +} + +bool RootItem::isChildOf(RootItem *root) { + if (root == NULL) { + return false; + } + + RootItem *this_item = this; + + while (this_item->kind() != RootItemKind::Root) { + if (root->childItems().contains(this_item)) { + return true; + } + else { + this_item = this_item->parent(); + } + } + + return false; +} + +bool RootItem::isParentOf(RootItem *child) { + if (child == NULL) { + return false; + } + else { + return child->isChildOf(this); + } +} + +QList RootItem::getSubTree() { + QList children; + QList traversable_items; + + traversable_items.append(this); + + // Iterate all nested items. + while (!traversable_items.isEmpty()) { + RootItem *active_item = traversable_items.takeFirst(); + + children.append(active_item); + traversable_items.append(active_item->childItems()); + } + + return children; +} + +QList RootItem::getSubTree(RootItemKind::Kind kind_of_item) { + QList children; + QList traversable_items; + + traversable_items.append(this); + + // Iterate all nested items. + while (!traversable_items.isEmpty()) { + RootItem *active_item = traversable_items.takeFirst(); + + if ((active_item->kind() & kind_of_item) > 0) { + children.append(active_item); + } + + traversable_items.append(active_item->childItems()); + } + + return children; +} + +QList RootItem::getSubTreeCategories() { + QList children; + QList traversable_items; + + traversable_items.append(this); + + // Iterate all nested items. + while (!traversable_items.isEmpty()) { + RootItem *active_item = traversable_items.takeFirst(); + + if (active_item->kind() == RootItemKind::Category) { + children.append(active_item->toCategory()); + } + + traversable_items.append(active_item->childItems()); + } + + return children; +} + +QHash RootItem::getHashedSubTreeCategories() { + QHash children; + QList traversable_items; + + traversable_items.append(this); + + // Iterate all nested items. + while (!traversable_items.isEmpty()) { + RootItem *active_item = traversable_items.takeFirst(); + + if (active_item->kind() == RootItemKind::Category && !children.contains(active_item->id())) { + children.insert(active_item->id(), active_item->toCategory()); + } + + traversable_items.append(active_item->childItems()); + } + + return children; +} + +QList RootItem::getSubTreeFeeds() { + QList children; + QList traversable_items; + + traversable_items.append(this); + + // Iterate all nested items. + while (!traversable_items.isEmpty()) { + RootItem *active_item = traversable_items.takeFirst(); + + if (active_item->kind() == RootItemKind::Feed) { + children.append(active_item->toFeed()); + } + + traversable_items.append(active_item->childItems()); + } + + return children; +} + +ServiceRoot *RootItem::getParentServiceRoot() { + RootItem *working_parent = this; + + while (working_parent->kind() != RootItemKind::Root) { + if (working_parent->kind() == RootItemKind::ServiceRoot) { + return working_parent->toServiceRoot(); + } + else { + working_parent = working_parent->parent(); + } + } + + return NULL; +} + +bool RootItem::removeChild(RootItem *child) { + return m_childItems.removeOne(child); +} + +Category *RootItem::toCategory() { + return static_cast(this); +} + +Feed *RootItem::toFeed() { + return static_cast(this); +} + +ServiceRoot *RootItem::toServiceRoot() { + return static_cast(this); +} + +int RootItem::countOfUnreadMessages() const { + int total_count = 0; + + foreach (RootItem *child_item, m_childItems) { + total_count += child_item->countOfUnreadMessages(); + } + + return total_count; +} + +bool RootItem::removeChild(int index) { + if (index >= 0 && index < m_childItems.size()) { + m_childItems.removeAt(index); + return true; + } + else { + return false; + } +} diff --git a/src/services/abstract/rootitem.h b/src/services/abstract/rootitem.h new file mode 100755 index 000000000..54b9be830 --- /dev/null +++ b/src/services/abstract/rootitem.h @@ -0,0 +1,277 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef ROOTITEM_H +#define ROOTITEM_H + +#include "core/message.h" + +#include +#include +#include + + +class Category; +class Feed; +class ServiceRoot; +class QAction; + +namespace RootItemKind { + // Describes the kind of the item. + enum Kind { + Root = 1, + Bin = 2, + Feed = 4, + Category = 8, + ServiceRoot = 16 + }; + + inline Kind operator|(Kind a, Kind b) { + return static_cast(static_cast(a) | static_cast(b)); + } +} + +// Represents ROOT item of FeedsModel. +// NOTE: This class is derived to add functionality for +// all other non-root items of FeedsModel. +class RootItem : public QObject { + Q_OBJECT + + public: + // Holds statuses for feeds/messages + // to be marked read/unread. + enum ReadStatus { + Unread = 0, + Read = 1 + }; + + // Holds statuses for messages + // to be switched importance (starred). + enum Importance { + NotImportant = 0, + Important = 1 + }; + + // Constructors and destructors. + explicit RootItem(RootItem *parent_item = NULL); + virtual ~RootItem(); + + ///////////////////////////////////////// + // /* Members to override. + ///////////////////////////////////////// + + // Returns list of specific actions which can be done with the item. + // Do not include general actions here like actions: Mark as read, Update, ... + // NOTE: Ownership of returned actions is not switched to caller, free them when needed. + virtual QList contextMenu(); + + // Can properties of this item be edited? + virtual bool canBeEdited(); + + // Performs editing of properties of this item (probably via dialog) + // and returns result status. + virtual bool editViaGui(); + + // Can the item be deleted? + virtual bool canBeDeleted(); + + // Performs deletion of the item, this + // method should NOT display any additional dialogs. + // Returns result status. + virtual bool deleteViaGui(); + + // Performs all needed steps (DB update, remote server update) + // to mark this item as read/unread. + virtual bool markAsReadUnread(ReadStatus status); + + // Get ALL undeleted messages from this item in one single list. + // This is currently used for displaying items in "newspaper mode". + virtual QList undeletedMessages() const; + + // This method should "clean" all messages it contains. + // What "clean" means? It means delete messages -> move them to recycle bin + // or eventually remove them completely if there is no recycle bin functionality. + // If this method is called on "recycle bin" instance of your + // service account, it should "empty" the recycle bin. + virtual bool cleanMessages(bool clear_only_read); + + // Updates counts of all/unread messages for this feed. + virtual void updateCounts(bool including_total_count); + + virtual int row() const; + virtual QVariant data(int column, int role) const; + virtual Qt::ItemFlags additionalFlags() const; + virtual bool performDragDropChange(RootItem *target_item); + + // Each item offers "counts" of messages. + // Returns counts of messages of all child items summed up. + virtual int countOfUnreadMessages() const; + virtual int countOfAllMessages() const; + + ///////////////////////////////////////// + // Members to override. */ + ///////////////////////////////////////// + + inline RootItem *parent() const { + return m_parentItem; + } + + inline void setParent(RootItem *parent_item) { + m_parentItem = parent_item; + } + + inline RootItem *child(int row) { + return m_childItems.value(row); + } + + inline int childCount() const { + return m_childItems.size(); + } + + inline void appendChild(RootItem *child) { + m_childItems.append(child); + child->setParent(this); + } + + // Access to children. + inline QList childItems() const { + return m_childItems; + } + + // Removes all children from this item. + // NOTE: Children are NOT freed from the memory. + inline void clearChildren() { + m_childItems.clear(); + } + + inline void setChildItems(QList child_items) { + m_childItems = child_items; + } + + // Removes particular child at given index. + // NOTE: Child is NOT freed from the memory. + bool removeChild(int index); + bool removeChild(RootItem *child); + + // Checks whether "this" object is child (direct or indirect) + // of the given root. + bool isChildOf(RootItem *root); + + // Is "this" item parent (direct or indirect) if given child? + bool isParentOf(RootItem *child); + + // Returns flat list of all items from subtree where this item is a root. + // Returned list includes this item too. + QList getSubTree(); + QList getSubTree(RootItemKind::Kind kind_of_item); + QList getSubTreeCategories(); + QHash getHashedSubTreeCategories(); + QList getSubTreeFeeds(); + + // Returns the service root node which is direct or indirect parent of current item. + ServiceRoot *getParentServiceRoot(); + + inline RootItemKind::Kind kind() const { + return m_kind; + } + + inline void setKind(RootItemKind::Kind kind) { + m_kind = kind; + } + + // Each item can have icon. + inline QIcon icon() const { + return m_icon; + } + + inline void setIcon(const QIcon &icon) { + m_icon = icon; + } + + // Each item has some kind of id. Usually taken from primary key attribute from DB. + inline int id() const { + return m_id; + } + + inline void setId(int id) { + m_id = id; + } + + // Each item has its title. + inline QString title() const { + return m_title; + } + + inline void setTitle(const QString &title) { + m_title = title; + } + + inline QDateTime creationDate() const { + return m_creationDate; + } + + inline void setCreationDate(const QDateTime &creation_date) { + m_creationDate = creation_date; + } + + inline QString description() const { + return m_description; + } + + inline void setDescription(const QString &description) { + m_description = description; + } + + inline QFont normalFont() const { + return m_normalFont; + } + + inline void setNormalFont(const QFont &normal_font) { + m_normalFont = normal_font; + } + + inline QFont boldFont() const { + return m_boldFont; + } + + inline void setBoldFont(const QFont &bold_font) { + m_boldFont = bold_font; + } + + // Converters + Category *toCategory(); + Feed *toFeed(); + ServiceRoot *toServiceRoot(); + + private: + void setupFonts(); + + RootItemKind::Kind m_kind; + int m_id; + QString m_title; + QString m_description; + QIcon m_icon; + QDateTime m_creationDate; + + QFont m_normalFont; + QFont m_boldFont; + + QList m_childItems; + RootItem *m_parentItem; +}; + +#endif // ROOTITEM_H diff --git a/src/services/abstract/serviceentrypoint.cpp b/src/services/abstract/serviceentrypoint.cpp new file mode 100755 index 000000000..d32c3297c --- /dev/null +++ b/src/services/abstract/serviceentrypoint.cpp @@ -0,0 +1,22 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/abstract/serviceentrypoint.h" + + +ServiceEntryPoint::~ServiceEntryPoint() { +} diff --git a/src/services/abstract/serviceentrypoint.h b/src/services/abstract/serviceentrypoint.h new file mode 100755 index 000000000..b5974f446 --- /dev/null +++ b/src/services/abstract/serviceentrypoint.h @@ -0,0 +1,81 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef SERVICE_H +#define SERVICE_H + +#include +#include +#include + + +class ServiceRoot; +class FeedsModel; + +// TOP LEVEL class which provides basic information about the "service" +class ServiceEntryPoint { + public: + // Constructors. + virtual ~ServiceEntryPoint(); + + ///////////////////////////////////////// + // /* Members to override. + ///////////////////////////////////////// + + // Creates new service root item, which is ready to be added + // into the model. This method can for example display + // some kind of first-time configuration dialog inside itself + // before returning the root item. + // Returns NULL if initialization of new root cannot be done. + virtual ServiceRoot *createNewRoot() = 0; + + // Performs initialization of all service accounts created using this entry + // point from persistent DB. + // Returns list of root nodes which will be afterwards added + // to the global feed model. + virtual QList initializeSubtree() = 0; + + // Can this service account be added just once? + // NOTE: This is true particularly for "standard" service + // which operates with normal RSS/ATOM feeds. + virtual bool isSingleInstanceService() = 0; + + // Human readable service name, for example "TT-RSS". + virtual QString name() = 0; + + // Some arbitrary string. + // NOTE: Keep in sync with ServiceRoot::code(). + virtual QString code() = 0; + + // Human readable service description, for example "Services which offers TT-RSS integration.". + virtual QString description() = 0; + + // Version of the service, using of semantic versioning is recommended. + virtual QString version() = 0; + + // Author of the service. + virtual QString author() = 0; + + // Icon of the service. + virtual QIcon icon() = 0; + + ///////////////////////////////////////// + // Members to override. */ + ///////////////////////////////////////// +}; + +#endif // SERVICE_H diff --git a/src/services/abstract/serviceroot.cpp b/src/services/abstract/serviceroot.cpp new file mode 100755 index 000000000..d83bfa2d4 --- /dev/null +++ b/src/services/abstract/serviceroot.cpp @@ -0,0 +1,198 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/abstract/serviceroot.h" + +#include "core/feedsmodel.h" +#include "miscellaneous/application.h" +#include "miscellaneous/textfactory.h" +#include "services/abstract/category.h" + +#include + + +ServiceRoot::ServiceRoot(RootItem *parent) : RootItem(parent), m_accountId(NO_PARENT_CATEGORY) { + setKind(RootItemKind::ServiceRoot); +} + +ServiceRoot::~ServiceRoot() { +} + +bool ServiceRoot::deleteViaGui() { + QSqlDatabase connection = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + // Remove all messages. + if (!QSqlQuery(connection).exec(QString("DELETE FROM Messages WHERE account_id = %1;").arg(accountId()))) { + return false; + } + + // Remove all feeds. + if (!QSqlQuery(connection).exec(QString("DELETE FROM Feeds WHERE account_id = %1;").arg(accountId()))) { + return false; + } + + // Remove all categories. + if (!QSqlQuery(connection).exec(QString("DELETE FROM Categories WHERE account_id = %1;").arg(accountId()))) { + return false; + } + + // Switch "existence" flag. + bool data_removed = QSqlQuery(connection).exec(QString("DELETE FROM Accounts WHERE id = %1;").arg(accountId())); + + if (data_removed) { + requestItemRemoval(this); + } + + return data_removed; +} + +bool ServiceRoot::markAsReadUnread(RootItem::ReadStatus status) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (!db_handle.transaction()) { + qWarning("Starting transaction for feeds read change."); + return false; + } + + QSqlQuery query_read_msg(db_handle); + query_read_msg.setForwardOnly(true); + query_read_msg.prepare(QSL("UPDATE Messages SET is_read = :read WHERE is_pdeleted = 0 AND account_id = :account_id;")); + + query_read_msg.bindValue(QSL(":account_id"), accountId()); + query_read_msg.bindValue(QSL(":read"), status == RootItem::Read ? 1 : 0); + + if (!query_read_msg.exec()) { + qDebug("Query execution for feeds read change failed."); + db_handle.rollback(); + } + + // Commit changes. + if (db_handle.commit()) { + updateCounts(false); + itemChanged(getSubTree()); + requestReloadMessageList(status == RootItem::Read); + return true; + } + else { + return db_handle.rollback(); + } +} + +QList ServiceRoot::undeletedMessages() const { + QList messages; + int account_id = accountId(); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_read_msg(database); + + query_read_msg.setForwardOnly(true); + query_read_msg.prepare("SELECT * " + "FROM Messages " + "WHERE is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;"); + query_read_msg.bindValue(QSL(":account_id"), account_id); + + // FIXME: Fix those const functions, this is fucking ugly. + + if (query_read_msg.exec()) { + while (query_read_msg.next()) { + bool decoded; + Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded); + + if (decoded) { + messages.append(message); + } + + messages.append(message); + } + } + + return messages; +} + +void ServiceRoot::itemChanged(const QList &items) { + emit dataChanged(items); +} + +void ServiceRoot::requestReloadMessageList(bool mark_selected_messages_read) { + emit reloadMessageListRequested(mark_selected_messages_read); +} + +void ServiceRoot::requestFeedReadFilterReload() { + emit readFeedsFilterInvalidationRequested(); +} + +void ServiceRoot::requestItemExpand(const QList &items, bool expand) { + emit itemExpandRequested(items, expand); +} + +void ServiceRoot::requestItemReassignment(RootItem *item, RootItem *new_parent) { + emit itemReassignmentRequested(item, new_parent); +} + +void ServiceRoot::requestItemRemoval(RootItem *item) { + emit itemRemovalRequested(item); +} + +int ServiceRoot::accountId() const { + return m_accountId; +} + +void ServiceRoot::setAccountId(int account_id) { + m_accountId = account_id; +} + +void ServiceRoot::assembleFeeds(Assignment feeds) { + QHash categories = getHashedSubTreeCategories(); + + foreach (const AssignmentItem &feed, feeds) { + if (feed.first == NO_PARENT_CATEGORY) { + // This is top-level feed, add it to the root item. + appendChild(feed.second); + feed.second->updateCounts(true); + } + else if (categories.contains(feed.first)) { + // This feed belongs to this category. + categories.value(feed.first)->appendChild(feed.second); + feed.second->updateCounts(true); + } + else { + qWarning("Feed '%s' is loose, skipping it.", qPrintable(feed.second->title())); + } + } +} + +void ServiceRoot::assembleCategories(Assignment categories) { + QHash assignments; + assignments.insert(NO_PARENT_CATEGORY, this); + + // Add top-level categories. + while (!categories.isEmpty()) { + for (int i = 0; i < categories.size(); i++) { + if (assignments.contains(categories.at(i).first)) { + // Parent category of this category is already added. + assignments.value(categories.at(i).first)->appendChild(categories.at(i).second); + + // Now, added category can be parent for another categories, add it. + assignments.insert(categories.at(i).second->id(), categories.at(i).second); + + // Remove the category from the list, because it was + // added to the final collection. + categories.removeAt(i); + i--; + } + } + } +} diff --git a/src/services/abstract/serviceroot.h b/src/services/abstract/serviceroot.h new file mode 100755 index 000000000..c4c3a0455 --- /dev/null +++ b/src/services/abstract/serviceroot.h @@ -0,0 +1,179 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef SERVICEROOT_H +#define SERVICEROOT_H + +#include "services/abstract/rootitem.h" + +#include "core/message.h" + +#include + + +class FeedsModel; +class RecycleBin; +class QAction; +class QSqlTableModel; + +// Car here represents ID of the item. +typedef QList > Assignment; +typedef QPair AssignmentItem; + +// THIS IS the root node of the service. +// NOTE: The root usually contains some core functionality of the +// service like service account username/password etc. +class ServiceRoot : public RootItem { + Q_OBJECT + + public: + explicit ServiceRoot(RootItem *parent = NULL); + virtual ~ServiceRoot(); + + ///////////////////////////////////////// + // /* Members to override. + ///////////////////////////////////////// + + bool deleteViaGui(); + + bool markAsReadUnread(ReadStatus status); + + // Returns list of specific actions for "Add new item" main window menu. + // So typical list of returned actions could look like: + // a) Add new feed + // b) Add new category + // c) ... + // NOTE: Caller does NOT take ownership of created menu! + virtual QList addItemMenu() = 0; + + // Returns list of specific actions to be shown in main window menu + // bar in sections "Services -> 'this service'". + // NOTE: Caller does NOT take ownership of created menu! + virtual QList serviceMenu() = 0; + + // Access to recycle bin of this account if there is any. + virtual RecycleBin *recycleBin() = 0; + + QList undeletedMessages() const; + + // Start/stop services. + // Start method is called when feed model gets initialized OR after user adds new service. + // Account should synchronously initialize its children (load them from DB is recommended + // here). + // + // Stop method is called just before application exits OR when + // user explicitly deletes existing service instance. + virtual void start() = 0; + virtual void stop() = 0; + + // Returns the UNIQUE code of the given service. + // NOTE: Keep in sync with ServiceEntryRoot::code(). + virtual QString code() = 0; + + // This method should prepare messages for given "item" (download them maybe?) + // into predefined "Messages" table + // and then use method QSqlTableModel::setFilter(....). + // NOTE: It would be more preferable if all messages are downloaded + // right when feeds are updated. + virtual bool loadMessagesForItem(RootItem *item, QSqlTableModel *model) = 0; + + // Called BEFORE this read status update (triggered by user in message list) is stored in DB, + // when false is returned, change is aborted. + // This is the place to make some other changes like updating + // some ONLINE service or something. + // + // "read" is status which is ABOUT TO BE SET. + virtual bool onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read) = 0; + + // Called AFTER this read status update (triggered by user in message list) is stored in DB, + // when false is returned, change is aborted. + // Here service root should inform (via signals) + // which items are actually changed. + // + // "read" is status which is ABOUT TO BE SET. + virtual bool onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read) = 0; + + // Called BEFORE this importance switch update is stored in DB, + // when false is returned, change is aborted. + // This is the place to make some other changes like updating + // some ONLINE service or something. + // + // "changes" - list of pairs - + virtual bool onBeforeSwitchMessageImportance(RootItem *selected_item, const QList > &changes) = 0; + + // Called AFTER this importance switch update is stored in DB, + // when false is returned, change is aborted. + // Here service root should inform (via signals) + // which items are actually changed. + // + // "changes" - list of pairs - + virtual bool onAfterSwitchMessageImportance(RootItem *selected_item, const QList > &changes) = 0; + + // Called BEFORE the list of messages is about to be deleted + // by the user from message list. + virtual bool onBeforeMessagesDelete(RootItem *selected_item, const QList &messages) = 0; + + // Called AFTER the list of messages was deleted + // by the user from message list. + virtual bool onAfterMessagesDelete(RootItem *selected_item, const QList &messages) = 0; + + // Called BEFORE the list of messages is about to be restored from recycle bin + // by the user from message list. + // Selected item is naturally recycle bin. + virtual bool onBeforeMessagesRestoredFromBin(RootItem *selected_item, const QList &messages) = 0; + + // Called AFTER the list of messages was restored from recycle bin + // by the user from message list. + // Selected item is naturally recycle bin. + virtual bool onAfterMessagesRestoredFromBin(RootItem *selected_item, const QList &messages) = 0; + + ///////////////////////////////////////// + // Members to override. */ + ///////////////////////////////////////// + + // Obvious methods to wrap signals. + void itemChanged(const QList &items); + void requestReloadMessageList(bool mark_selected_messages_read); + void requestFeedReadFilterReload(); + void requestItemExpand(const QList &items, bool expand); + void requestItemReassignment(RootItem *item, RootItem *new_parent); + void requestItemRemoval(RootItem *item); + + // Account ID corresponds with DB attribute Accounts (id). + int accountId() const; + void setAccountId(int account_id); + + protected: + // Takes lists of feeds/categories and assembles them into the tree structure. + void assembleCategories(Assignment categories); + void assembleFeeds(Assignment feeds); + + signals: + // Emitted if data in any item belonging to this root are changed. + void dataChanged(QList items); + void readFeedsFilterInvalidationRequested(); + void reloadMessageListRequested(bool mark_selected_messages_read); + void itemExpandRequested(QList items, bool expand); + + void itemReassignmentRequested(RootItem *item, RootItem *new_parent); + void itemRemovalRequested(RootItem *item); + + private: + int m_accountId; +}; + +#endif // SERVICEROOT_H diff --git a/src/gui/dialogs/formcategorydetails.cpp b/src/services/standard/gui/formstandardcategorydetails.cpp similarity index 79% rename from src/gui/dialogs/formcategorydetails.cpp rename to src/services/standard/gui/formstandardcategorydetails.cpp index bac6ad7ec..00e0664d6 100755 --- a/src/gui/dialogs/formcategorydetails.cpp +++ b/src/services/standard/gui/formstandardcategorydetails.cpp @@ -15,11 +15,10 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#include "gui/dialogs/formcategorydetails.h" +#include "services/standard/gui/formstandardcategorydetails.h" #include "definitions/definitions.h" -#include "core/rootitem.h" -#include "core/category.h" +#include "services/abstract/rootitem.h" #include "core/feedsmodel.h" #include "miscellaneous/iconfactory.h" #include "gui/feedsview.h" @@ -27,6 +26,8 @@ #include "gui/messagebox.h" #include "gui/systemtrayicon.h" #include "gui/dialogs/formmain.h" +#include "services/standard/standardcategory.h" +#include "services/standard/standardserviceroot.h" #include #include @@ -38,11 +39,8 @@ #include -FormCategoryDetails::FormCategoryDetails(FeedsModel *model, - QWidget *parent) - : QDialog(parent), - m_editableCategory(NULL), - m_feedsModel(model) { +FormStandardCategoryDetails::FormStandardCategoryDetails(StandardServiceRoot *service_root, QWidget *parent) + : QDialog(parent), m_editableCategory(NULL), m_serviceRoot(service_root) { initialize(); createConnections(); @@ -51,11 +49,11 @@ FormCategoryDetails::FormCategoryDetails(FeedsModel *model, onDescriptionChanged(QString()); } -FormCategoryDetails::~FormCategoryDetails() { +FormStandardCategoryDetails::~FormStandardCategoryDetails() { qDebug("Destroying FormCategoryDetails instance."); } -void FormCategoryDetails::createConnections() { +void FormStandardCategoryDetails::createConnections() { // General connections. connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(apply())); connect(m_ui->m_txtTitle->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onTitleChanged(QString))); @@ -67,7 +65,7 @@ void FormCategoryDetails::createConnections() { connect(m_actionUseDefaultIcon, SIGNAL(triggered()), this, SLOT(onUseDefaultIcon())); } -void FormCategoryDetails::setEditableCategory(Category *editable_category) { +void FormStandardCategoryDetails::setEditableCategory(StandardCategory *editable_category) { m_editableCategory = editable_category; m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) editable_category->parent()))); @@ -76,9 +74,9 @@ void FormCategoryDetails::setEditableCategory(Category *editable_category) { m_ui->m_btnIcon->setIcon(editable_category->icon()); } -int FormCategoryDetails::exec(Category *input_category, RootItem *parent_to_select) { +int FormStandardCategoryDetails::exec(StandardCategory *input_category, RootItem *parent_to_select) { // Load categories. - loadCategories(m_feedsModel->allCategories().values(), m_feedsModel->rootItem(), input_category); + loadCategories(m_serviceRoot->allCategories(), m_serviceRoot, input_category); if (input_category == NULL) { // User is adding new category. @@ -90,10 +88,10 @@ int FormCategoryDetails::exec(Category *input_category, RootItem *parent_to_sele // Load parent from suggested item. if (parent_to_select != NULL) { - if (parent_to_select->kind() == RootItem::Cattegory) { + if (parent_to_select->kind() == RootItemKind::Category) { m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select))); } - else if (parent_to_select->kind() == RootItem::Feeed) { + else if (parent_to_select->kind() == RootItemKind::Feed) { int target_item = m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select->parent())); if (target_item >= 0) { @@ -112,22 +110,23 @@ int FormCategoryDetails::exec(Category *input_category, RootItem *parent_to_sele return QDialog::exec(); } -void FormCategoryDetails::apply() { +void FormStandardCategoryDetails::apply() { RootItem *parent = static_cast(m_ui->m_cmbParentCategory->itemData(m_ui->m_cmbParentCategory->currentIndex()).value()); - Category *new_category = new Category(); + StandardCategory *new_category = new StandardCategory(); new_category->setTitle(m_ui->m_txtTitle->lineEdit()->text()); new_category->setCreationDate(QDateTime::currentDateTime()); new_category->setDescription(m_ui->m_txtDescription->lineEdit()->text()); new_category->setIcon(m_ui->m_btnIcon->icon()); - new_category->setParent(parent); if (m_editableCategory == NULL) { // Add the category. - if (m_feedsModel->addCategory(new_category, parent)) { + if (new_category->addItself(parent)) { + m_serviceRoot->requestItemReassignment(new_category, parent); accept(); } else { + delete new_category; qApp->showGuiMessage(tr("Cannot add category"), tr("Category was not added due to error."), QSystemTrayIcon::Critical, @@ -135,19 +134,25 @@ void FormCategoryDetails::apply() { } } else { - if (m_feedsModel->editCategory(m_editableCategory, new_category)) { + new_category->setParent(parent); + + bool edited = m_editableCategory->editItself(new_category); + + if (edited) { + m_serviceRoot->requestItemReassignment(m_editableCategory, new_category->parent()); accept(); } else { qApp->showGuiMessage(tr("Cannot edit category"), tr("Category was not edited due to error."), - QSystemTrayIcon::Critical, - qApp->mainForm(), true); + QSystemTrayIcon::Critical, this, true); } + + delete new_category; } } -void FormCategoryDetails::onTitleChanged(const QString &new_title){ +void FormStandardCategoryDetails::onTitleChanged(const QString &new_title){ if (new_title.simplified().size() >= MIN_CATEGORY_NAME_LENGTH) { m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); m_ui->m_txtTitle->setStatus(WidgetWithStatus::Ok, tr("Category name is ok.")); @@ -158,7 +163,7 @@ void FormCategoryDetails::onTitleChanged(const QString &new_title){ } } -void FormCategoryDetails::onDescriptionChanged(const QString &new_description) { +void FormStandardCategoryDetails::onDescriptionChanged(const QString &new_description) { if (new_description.simplified().isEmpty()) { m_ui->m_txtDescription->setStatus(LineEditWithStatus::Warning, tr("Description is empty.")); } @@ -167,11 +172,11 @@ void FormCategoryDetails::onDescriptionChanged(const QString &new_description) { } } -void FormCategoryDetails::onNoIconSelected() { +void FormStandardCategoryDetails::onNoIconSelected() { m_ui->m_btnIcon->setIcon(QIcon()); } -void FormCategoryDetails::onLoadIconFromFile() { +void FormStandardCategoryDetails::onLoadIconFromFile() { QFileDialog dialog(this, tr("Select icon file for the category"), qApp->homeFolderPath(), tr("Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga)")); dialog.setFileMode(QFileDialog::ExistingFile); @@ -190,12 +195,12 @@ void FormCategoryDetails::onLoadIconFromFile() { } } -void FormCategoryDetails::onUseDefaultIcon() { +void FormStandardCategoryDetails::onUseDefaultIcon() { m_ui->m_btnIcon->setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); } -void FormCategoryDetails::initialize() { - m_ui = new Ui::FormCategoryDetails(); +void FormStandardCategoryDetails::initialize() { + m_ui = new Ui::FormStandardCategoryDetails(); m_ui->setupUi(this); // Set text boxes. @@ -241,14 +246,14 @@ void FormCategoryDetails::initialize() { m_ui->m_txtTitle->lineEdit()->setFocus(Qt::TabFocusReason); } -void FormCategoryDetails::loadCategories(const QList categories, - RootItem *root_item, - Category *input_category) { +void FormStandardCategoryDetails::loadCategories(const QList categories, + RootItem *root_item, + StandardCategory *input_category) { m_ui->m_cmbParentCategory->addItem(root_item->icon(), root_item->title(), QVariant::fromValue((void*) root_item)); - foreach (Category *category, categories) { + foreach (StandardCategory *category, categories) { if (input_category != NULL && (category == input_category || category->isChildOf(input_category))) { // This category cannot be selected as the new // parent for currently edited category, so diff --git a/src/gui/dialogs/formcategorydetails.h b/src/services/standard/gui/formstandardcategorydetails.h old mode 100644 new mode 100755 similarity index 72% rename from src/gui/dialogs/formcategorydetails.h rename to src/services/standard/gui/formstandardcategorydetails.h index 67abd4196..3717b3251 --- a/src/gui/dialogs/formcategorydetails.h +++ b/src/services/standard/gui/formstandardcategorydetails.h @@ -18,33 +18,33 @@ #ifndef FORMCATEGORYDETAILS_H #define FORMCATEGORYDETAILS_H -#include "ui_formcategorydetails.h" +#include "ui_formstandardcategorydetails.h" #include namespace Ui { - class FormCategoryDetails; + class FormStandardCategoryDetails; } -class Category; -class Category; +class StandardCategory; +class StandardServiceRoot; class FeedsModel; class RootItem; class QMenu; class QAction; -class FormCategoryDetails : public QDialog { +class FormStandardCategoryDetails : public QDialog { Q_OBJECT public: // Constructors and destructors. - explicit FormCategoryDetails(FeedsModel *model, QWidget *parent = 0); - virtual ~FormCategoryDetails(); + explicit FormStandardCategoryDetails(StandardServiceRoot *service_root, QWidget *parent = 0); + virtual ~FormStandardCategoryDetails(); public slots: // Executes add/edit standard category dialog. - int exec(Category *input_category, RootItem *parent_to_select); + int exec(StandardCategory *input_category, RootItem *parent_to_select); protected slots: // Applies changes. @@ -64,7 +64,7 @@ class FormCategoryDetails : public QDialog { void createConnections(); // Sets the category which will be edited. - void setEditableCategory(Category *editable_category); + void setEditableCategory(StandardCategory *editable_category); // Initializes the dialog. void initialize(); @@ -72,12 +72,12 @@ class FormCategoryDetails : public QDialog { // Loads categories into the dialog + give root "category" // and make sure that no childs of input category (including) // input category are loaded. - void loadCategories(const QList categories, RootItem *root_item, Category *input_category); + void loadCategories(const QList categories, RootItem *root_item, StandardCategory *input_category); private: - Ui::FormCategoryDetails *m_ui; - Category *m_editableCategory; - FeedsModel *m_feedsModel; + Ui::FormStandardCategoryDetails *m_ui; + StandardCategory *m_editableCategory; + StandardServiceRoot *m_serviceRoot; QMenu *m_iconMenu; QAction *m_actionLoadIconFromFile; diff --git a/src/gui/dialogs/formcategorydetails.ui b/src/services/standard/gui/formstandardcategorydetails.ui old mode 100644 new mode 100755 similarity index 95% rename from src/gui/dialogs/formcategorydetails.ui rename to src/services/standard/gui/formstandardcategorydetails.ui index 7c2fd546c..d68dedfe4 --- a/src/gui/dialogs/formcategorydetails.ui +++ b/src/services/standard/gui/formstandardcategorydetails.ui @@ -1,13 +1,13 @@ - FormCategoryDetails - + FormStandardCategoryDetails + 0 0 - 346 - 180 + 397 + 209 @@ -153,7 +153,7 @@ m_buttonBox rejected() - FormCategoryDetails + FormStandardCategoryDetails reject() diff --git a/src/gui/dialogs/formfeeddetails.cpp b/src/services/standard/gui/formstandardfeeddetails.cpp similarity index 78% rename from src/gui/dialogs/formfeeddetails.cpp rename to src/services/standard/gui/formstandardfeeddetails.cpp index e022cb814..84ac02b9a 100755 --- a/src/gui/dialogs/formfeeddetails.cpp +++ b/src/services/standard/gui/formstandardfeeddetails.cpp @@ -15,13 +15,14 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#include "gui/dialogs/formfeeddetails.h" +#include "services/standard/gui/formstandardfeeddetails.h" #include "definitions/definitions.h" #include "core/feedsmodel.h" -#include "core/rootitem.h" -#include "core/category.h" -#include "core/feed.h" +#include "services/abstract/rootitem.h" +#include "services/standard/standardserviceroot.h" +#include "services/standard/standardcategory.h" +#include "services/standard/standardfeed.h" #include "miscellaneous/textfactory.h" #include "miscellaneous/iconfactory.h" #include "network-web/networkfactory.h" @@ -39,10 +40,10 @@ #include -FormFeedDetails::FormFeedDetails(FeedsModel *model, QWidget *parent) +FormStandardFeedDetails::FormStandardFeedDetails(StandardServiceRoot *service_root, QWidget *parent) : QDialog(parent), m_editableFeed(NULL), - m_feedsModel(model) { + m_serviceRoot(service_root) { initialize(); createConnections(); @@ -54,13 +55,13 @@ FormFeedDetails::FormFeedDetails(FeedsModel *model, QWidget *parent) onPasswordChanged(QString()); } -FormFeedDetails::~FormFeedDetails() { +FormStandardFeedDetails::~FormStandardFeedDetails() { delete m_ui; } -int FormFeedDetails::exec(Feed *input_feed, RootItem *parent_to_select) { +int FormStandardFeedDetails::exec(StandardFeed *input_feed, RootItem *parent_to_select) { // Load categories. - loadCategories(m_feedsModel->allCategories().values(), m_feedsModel->rootItem()); + loadCategories(m_serviceRoot->allCategories(), m_serviceRoot); if (input_feed == NULL) { // User is adding new category. @@ -77,10 +78,10 @@ int FormFeedDetails::exec(Feed *input_feed, RootItem *parent_to_select) { } if (parent_to_select != NULL) { - if (parent_to_select->kind() == RootItem::Cattegory) { + if (parent_to_select->kind() == RootItemKind::Category) { m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select))); } - else if (parent_to_select->kind() == RootItem::Feeed) { + else if (parent_to_select->kind() == RootItemKind::Feed) { int target_item = m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) parent_to_select->parent())); if (target_item >= 0) { @@ -103,7 +104,7 @@ int FormFeedDetails::exec(Feed *input_feed, RootItem *parent_to_select) { return QDialog::exec(); } -void FormFeedDetails::onTitleChanged(const QString &new_title){ +void FormStandardFeedDetails::onTitleChanged(const QString &new_title){ if (new_title.simplified().size() >= MIN_CATEGORY_NAME_LENGTH) { m_ui->m_txtTitle->setStatus(LineEditWithStatus::Ok, tr("Feed name is ok.")); } @@ -114,7 +115,7 @@ void FormFeedDetails::onTitleChanged(const QString &new_title){ checkOkButtonEnabled(); } -void FormFeedDetails::onDescriptionChanged(const QString &new_description) { +void FormStandardFeedDetails::onDescriptionChanged(const QString &new_description) { if (new_description.simplified().isEmpty()) { m_ui->m_txtDescription->setStatus(LineEditWithStatus::Warning, tr("Description is empty.")); } @@ -123,7 +124,7 @@ void FormFeedDetails::onDescriptionChanged(const QString &new_description) { } } -void FormFeedDetails::onUrlChanged(const QString &new_url) { +void FormStandardFeedDetails::onUrlChanged(const QString &new_url) { if (QRegExp(URL_REGEXP).exactMatch(new_url)) { // New url is well-formed. m_ui->m_txtUrl->setStatus(LineEditWithStatus::Ok, tr("The url is ok.")); @@ -140,7 +141,7 @@ void FormFeedDetails::onUrlChanged(const QString &new_url) { checkOkButtonEnabled(); } -void FormFeedDetails::onUsernameChanged(const QString &new_username) { +void FormStandardFeedDetails::onUsernameChanged(const QString &new_username) { bool is_username_ok = !m_ui->m_gbAuthentication->isChecked() || !new_username.simplified().isEmpty(); m_ui->m_txtUsername->setStatus(is_username_ok ? @@ -151,7 +152,7 @@ void FormFeedDetails::onUsernameChanged(const QString &new_username) { tr("Username is empty.")); } -void FormFeedDetails::onPasswordChanged(const QString &new_password) { +void FormStandardFeedDetails::onPasswordChanged(const QString &new_password) { bool is_password_ok = !m_ui->m_gbAuthentication->isChecked() || !new_password.simplified().isEmpty(); m_ui->m_txtPassword->setStatus(is_password_ok ? @@ -162,27 +163,27 @@ void FormFeedDetails::onPasswordChanged(const QString &new_password) { tr("Password is empty.")); } -void FormFeedDetails::onAuthenticationSwitched() { +void FormStandardFeedDetails::onAuthenticationSwitched() { onUsernameChanged(m_ui->m_txtUsername->lineEdit()->text()); onPasswordChanged(m_ui->m_txtPassword->lineEdit()->text()); } -void FormFeedDetails::onAutoUpdateTypeChanged(int new_index) { - Feed::AutoUpdateType auto_update_type = static_cast(m_ui->m_cmbAutoUpdateType->itemData(new_index).toInt()); +void FormStandardFeedDetails::onAutoUpdateTypeChanged(int new_index) { + StandardFeed::AutoUpdateType auto_update_type = static_cast(m_ui->m_cmbAutoUpdateType->itemData(new_index).toInt()); switch (auto_update_type) { - case Feed::DontAutoUpdate: - case Feed::DefaultAutoUpdate: + case StandardFeed::DontAutoUpdate: + case StandardFeed::DefaultAutoUpdate: m_ui->m_spinAutoUpdateInterval->setEnabled(false); break; - case Feed::SpecificAutoUpdate: + case StandardFeed::SpecificAutoUpdate: default: m_ui->m_spinAutoUpdateInterval->setEnabled(true); } } -void FormFeedDetails::checkOkButtonEnabled() { +void FormStandardFeedDetails::checkOkButtonEnabled() { LineEditWithStatus::StatusType title_status = m_ui->m_txtTitle->status(); LineEditWithStatus::StatusType url_status = m_ui->m_txtUrl->status(); @@ -191,11 +192,11 @@ void FormFeedDetails::checkOkButtonEnabled() { url_status == LineEditWithStatus::Warning)); } -void FormFeedDetails::onNoIconSelected() { +void FormStandardFeedDetails::onNoIconSelected() { m_ui->m_btnIcon->setIcon(QIcon()); } -void FormFeedDetails::onLoadIconFromFile() { +void FormStandardFeedDetails::onLoadIconFromFile() { QFileDialog dialog(this, tr("Select icon file for the feed"), qApp->homeFolderPath(), tr("Images (*.bmp *.jpg *.jpeg *.png *.svg *.tga)")); dialog.setFileMode(QFileDialog::ExistingFile); @@ -214,14 +215,14 @@ void FormFeedDetails::onLoadIconFromFile() { } } -void FormFeedDetails::onUseDefaultIcon() { +void FormStandardFeedDetails::onUseDefaultIcon() { m_ui->m_btnIcon->setIcon(qApp->icons()->fromTheme(QSL("folder-feed"))); } -void FormFeedDetails::apply() { +void FormStandardFeedDetails::apply() { RootItem *parent = static_cast(m_ui->m_cmbParentCategory->itemData(m_ui->m_cmbParentCategory->currentIndex()).value()); - Feed::Type type = static_cast(m_ui->m_cmbType->itemData(m_ui->m_cmbType->currentIndex()).value()); - Feed *new_feed = new Feed(); + StandardFeed::Type type = static_cast(m_ui->m_cmbType->itemData(m_ui->m_cmbType->currentIndex()).value()); + StandardFeed *new_feed = new StandardFeed(); // Setup data for new_feed. new_feed->setTitle(m_ui->m_txtTitle->lineEdit()->text()); @@ -234,24 +235,30 @@ void FormFeedDetails::apply() { new_feed->setPasswordProtected(m_ui->m_gbAuthentication->isChecked()); new_feed->setUsername(m_ui->m_txtUsername->lineEdit()->text()); new_feed->setPassword(m_ui->m_txtPassword->lineEdit()->text()); - new_feed->setAutoUpdateType(static_cast(m_ui->m_cmbAutoUpdateType->itemData(m_ui->m_cmbAutoUpdateType->currentIndex()).toInt())); + new_feed->setAutoUpdateType(static_cast(m_ui->m_cmbAutoUpdateType->itemData(m_ui->m_cmbAutoUpdateType->currentIndex()).toInt())); new_feed->setAutoUpdateInitialInterval(m_ui->m_spinAutoUpdateInterval->value()); - new_feed->setParent(parent); if (m_editableFeed == NULL) { // Add the feed. - if (m_feedsModel->addFeed(new_feed, parent)) { + if (new_feed->addItself(parent)) { + m_serviceRoot->requestItemReassignment(new_feed, parent); accept(); } else { + delete new_feed; qApp->showGuiMessage(tr("Cannot add feed"), tr("Feed was not added due to error."), QSystemTrayIcon::Critical, this, true); } } else { + new_feed->setParent(parent); + // Edit the feed. - if (m_feedsModel->editFeed(m_editableFeed, new_feed)) { + bool edited = m_editableFeed->editItself(new_feed); + + if (edited) { + m_serviceRoot->requestItemReassignment(m_editableFeed, new_feed->parent()); accept(); } else { @@ -259,13 +266,15 @@ void FormFeedDetails::apply() { tr("Feed was not edited due to error."), QSystemTrayIcon::Critical, this, true); } + + delete new_feed; } } -void FormFeedDetails::guessFeed() { - QPair result = Feed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(), - m_ui->m_txtUsername->lineEdit()->text(), - m_ui->m_txtPassword->lineEdit()->text()); +void FormStandardFeedDetails::guessFeed() { + QPair result = StandardFeed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(), + m_ui->m_txtUsername->lineEdit()->text(), + m_ui->m_txtPassword->lineEdit()->text()); if (result.first != NULL) { // Icon or whole feed was guessed. @@ -306,10 +315,10 @@ void FormFeedDetails::guessFeed() { } } -void FormFeedDetails::guessIconOnly() { - QPair result = Feed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(), - m_ui->m_txtUsername->lineEdit()->text(), - m_ui->m_txtPassword->lineEdit()->text()); +void FormStandardFeedDetails::guessIconOnly() { + QPair result = StandardFeed::guessFeed(m_ui->m_txtUrl->lineEdit()->text(), + m_ui->m_txtUsername->lineEdit()->text(), + m_ui->m_txtPassword->lineEdit()->text()); if (result.first != NULL) { // Icon or whole feed was guessed. @@ -337,7 +346,7 @@ void FormFeedDetails::guessIconOnly() { } } -void FormFeedDetails::createConnections() { +void FormStandardFeedDetails::createConnections() { // General connections. connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(apply())); connect(m_ui->m_txtTitle->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onTitleChanged(QString))); @@ -356,7 +365,7 @@ void FormFeedDetails::createConnections() { connect(m_actionUseDefaultIcon, SIGNAL(triggered()), this, SLOT(onUseDefaultIcon())); } -void FormFeedDetails::setEditableFeed(Feed *editable_feed) { +void FormStandardFeedDetails::setEditableFeed(StandardFeed *editable_feed) { m_editableFeed = editable_feed; m_ui->m_cmbParentCategory->setCurrentIndex(m_ui->m_cmbParentCategory->findData(QVariant::fromValue((void*) editable_feed->parent()))); @@ -364,7 +373,7 @@ void FormFeedDetails::setEditableFeed(Feed *editable_feed) { m_ui->m_txtDescription->lineEdit()->setText(editable_feed->description()); m_ui->m_btnIcon->setIcon(editable_feed->icon()); m_ui->m_cmbType->setCurrentIndex(m_ui->m_cmbType->findData(QVariant::fromValue((int) editable_feed->type()))); - m_ui->m_cmbEncoding->setCurrentIndex(m_ui->m_cmbEncoding->findData(editable_feed->encoding(), Qt::DisplayRole)); + m_ui->m_cmbEncoding->setCurrentIndex(m_ui->m_cmbEncoding->findData(editable_feed->encoding(), Qt::DisplayRole, Qt::MatchFixedString)); m_ui->m_gbAuthentication->setChecked(editable_feed->passwordProtected()); m_ui->m_txtUsername->lineEdit()->setText(editable_feed->username()); m_ui->m_txtPassword->lineEdit()->setText(editable_feed->password()); @@ -373,8 +382,8 @@ void FormFeedDetails::setEditableFeed(Feed *editable_feed) { m_ui->m_spinAutoUpdateInterval->setValue(editable_feed->autoUpdateInitialInterval()); } -void FormFeedDetails::initialize() { - m_ui = new Ui::FormFeedDetails(); +void FormStandardFeedDetails::initialize() { + m_ui = new Ui::FormStandardFeedDetails(); m_ui->setupUi(this); // Set flags and attributes. @@ -405,10 +414,10 @@ void FormFeedDetails::initialize() { #endif // Add standard feed types. - m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Atom10), QVariant::fromValue((int) Feed::Atom10)); - m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Rdf), QVariant::fromValue((int) Feed::Rdf)); - m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Rss0X), QVariant::fromValue((int) Feed::Rss0X)); - m_ui->m_cmbType->addItem(Feed::typeToString(Feed::Rss2X), QVariant::fromValue((int) Feed::Rss2X)); + m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Atom10), QVariant::fromValue((int) StandardFeed::Atom10)); + m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rdf), QVariant::fromValue((int) StandardFeed::Rdf)); + m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rss0X), QVariant::fromValue((int) StandardFeed::Rss0X)); + m_ui->m_cmbType->addItem(StandardFeed::typeToString(StandardFeed::Rss2X), QVariant::fromValue((int) StandardFeed::Rss2X)); // Load available encodings. QList encodings = QTextCodec::availableCodecs(); @@ -449,9 +458,9 @@ void FormFeedDetails::initialize() { // Setup auto-update options. m_ui->m_spinAutoUpdateInterval->setValue(DEFAULT_AUTO_UPDATE_INTERVAL); - m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update using global interval"), QVariant::fromValue((int) Feed::DefaultAutoUpdate)); - m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update every"), QVariant::fromValue((int) Feed::SpecificAutoUpdate)); - m_ui->m_cmbAutoUpdateType->addItem(tr("Do not auto-update at all"), QVariant::fromValue((int) Feed::DontAutoUpdate)); + m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update using global interval"), QVariant::fromValue((int) StandardFeed::DefaultAutoUpdate)); + m_ui->m_cmbAutoUpdateType->addItem(tr("Auto-update every"), QVariant::fromValue((int) StandardFeed::SpecificAutoUpdate)); + m_ui->m_cmbAutoUpdateType->addItem(tr("Do not auto-update at all"), QVariant::fromValue((int) StandardFeed::DontAutoUpdate)); // Set tab order. setTabOrder(m_ui->m_cmbParentCategory, m_ui->m_cmbType); @@ -475,15 +484,14 @@ void FormFeedDetails::initialize() { m_ui->m_txtUrl->lineEdit()->setFocus(Qt::TabFocusReason); } -void FormFeedDetails::loadCategories(const QList categories, - RootItem *root_item) { +void FormStandardFeedDetails::loadCategories(const QList categories, + RootItem *root_item) { m_ui->m_cmbParentCategory->addItem(root_item->icon(), root_item->title(), QVariant::fromValue((void*) root_item)); - foreach (Category *category, categories) { - m_ui->m_cmbParentCategory->addItem(category->data(FDS_MODEL_TITLE_INDEX, - Qt::DecorationRole).value(), + foreach (StandardCategory *category, categories) { + m_ui->m_cmbParentCategory->addItem(category->icon(), category->title(), QVariant::fromValue((void*) category)); } diff --git a/src/gui/dialogs/formfeeddetails.h b/src/services/standard/gui/formstandardfeeddetails.h old mode 100644 new mode 100755 similarity index 75% rename from src/gui/dialogs/formfeeddetails.h rename to src/services/standard/gui/formstandardfeeddetails.h index 550b0bcb9..d3651f969 --- a/src/gui/dialogs/formfeeddetails.h +++ b/src/services/standard/gui/formstandardfeeddetails.h @@ -18,31 +18,31 @@ #ifndef FORMSTANDARDFEEDDETAILS_H #define FORMSTANDARDFEEDDETAILS_H -#include "ui_formfeeddetails.h" - #include +#include "ui_formstandardfeeddetails.h" + namespace Ui { - class FormFeedDetails; + class FormStandardFeedDetails; } -class FeedsModel; -class Feed; -class Category; +class StandardServiceRoot; +class StandardFeed; +class StandardCategory; class RootItem; -class FormFeedDetails : public QDialog { +class FormStandardFeedDetails : public QDialog { Q_OBJECT public: // Constructors and destructors. - explicit FormFeedDetails(FeedsModel *model, QWidget *parent = 0); - virtual ~FormFeedDetails(); + explicit FormStandardFeedDetails(StandardServiceRoot *service_root, QWidget *parent = 0); + virtual ~FormStandardFeedDetails(); public slots: // Executes add/edit standard feed dialog. - int exec(Feed *input_feed, RootItem *parent_to_select); + int exec(StandardFeed *input_feed, RootItem *parent_to_select); protected slots: // Applies changes. @@ -72,19 +72,19 @@ class FormFeedDetails : public QDialog { void createConnections(); // Sets the feed which will be edited. - void setEditableFeed(Feed *editable_feed); + void setEditableFeed(StandardFeed *editable_feed); // Initializes the dialog. void initialize(); // Loads categories into the dialog from the model. - void loadCategories(const QList categories, + void loadCategories(const QList categories, RootItem *root_item); private: - Ui::FormFeedDetails *m_ui; - Feed *m_editableFeed; - FeedsModel *m_feedsModel; + Ui::FormStandardFeedDetails *m_ui; + StandardFeed *m_editableFeed; + StandardServiceRoot *m_serviceRoot; QMenu *m_iconMenu; QAction *m_actionLoadIconFromFile; diff --git a/src/gui/dialogs/formfeeddetails.ui b/src/services/standard/gui/formstandardfeeddetails.ui old mode 100644 new mode 100755 similarity index 94% rename from src/gui/dialogs/formfeeddetails.ui rename to src/services/standard/gui/formstandardfeeddetails.ui index dd8755df2..31b13fc7d --- a/src/gui/dialogs/formfeeddetails.ui +++ b/src/services/standard/gui/formstandardfeeddetails.ui @@ -1,13 +1,13 @@ - FormFeedDetails - + FormStandardFeedDetails + 0 0 - 492 - 400 + 517 + 442 @@ -238,7 +238,7 @@ Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. - Requires authentication + Requires HTTP authentication false @@ -325,7 +325,7 @@ m_buttonBox rejected() - FormFeedDetails + FormStandardFeedDetails reject() diff --git a/src/gui/dialogs/formimportexport.cpp b/src/services/standard/gui/formstandardimportexport.cpp similarity index 79% rename from src/gui/dialogs/formimportexport.cpp rename to src/services/standard/gui/formstandardimportexport.cpp index d56421e75..6e7f869cd 100755 --- a/src/gui/dialogs/formimportexport.cpp +++ b/src/services/standard/gui/formstandardimportexport.cpp @@ -15,20 +15,24 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#include "gui/dialogs/formimportexport.h" +#include "services/standard/gui/formstandardimportexport.h" -#include "core/feedsimportexportmodel.h" +#include "services/standard/standardfeedsimportexportmodel.h" +#include "services/standard/standardserviceroot.h" #include "core/feedsmodel.h" #include "miscellaneous/application.h" #include "gui/feedmessageviewer.h" #include "gui/feedsview.h" #include "gui/dialogs/formmain.h" +#include "exceptions/ioexception.h" + #include #include -FormImportExport::FormImportExport(QWidget *parent) : QDialog(parent), m_ui(new Ui::FormImportExport) { +FormStandardImportExport::FormStandardImportExport(StandardServiceRoot *service_root, QWidget *parent) + : QDialog(parent), m_ui(new Ui::FormStandardImportExport), m_serviceRoot(service_root) { m_ui->setupUi(this); m_model = new FeedsImportExportModel(m_ui->m_treeFeeds); @@ -44,16 +48,16 @@ FormImportExport::FormImportExport(QWidget *parent) : QDialog(parent), m_ui(new connect(m_ui->m_btnUncheckAllItems, SIGNAL(clicked()), m_model, SLOT(uncheckAllItems())); } -FormImportExport::~FormImportExport() { +FormStandardImportExport::~FormStandardImportExport() { delete m_ui; } -void FormImportExport::setMode(const FeedsImportExportModel::Mode &mode) { +void FormStandardImportExport::setMode(const FeedsImportExportModel::Mode &mode) { m_model->setMode(mode); switch (mode) { case FeedsImportExportModel::Export: { - m_model->setRootItem(qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->rootItem()); + m_model->setRootItem(m_serviceRoot); m_model->checkAllItems(); m_ui->m_treeFeeds->setModel(m_model); m_ui->m_treeFeeds->expandAll(); @@ -80,7 +84,7 @@ void FormImportExport::setMode(const FeedsImportExportModel::Mode &mode) { m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true); } -void FormImportExport::selectFile() { +void FormStandardImportExport::selectFile() { switch (m_model->mode()) { case FeedsImportExportModel::Import: selectImportFile(); @@ -96,7 +100,7 @@ void FormImportExport::selectFile() { } } -void FormImportExport::selectExportFile() { +void FormStandardImportExport::selectExportFile() { QString filter_opml20 = tr("OPML 2.0 files (*.opml)"); QString filter; @@ -116,7 +120,6 @@ void FormImportExport::selectExportFile() { selected_file += QL1S(".opml"); } } - // NOTE: Add other types here. m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected.")); } @@ -124,7 +127,7 @@ void FormImportExport::selectExportFile() { m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setDisabled(selected_file.isEmpty()); } -void FormImportExport::selectImportFile() { +void FormStandardImportExport::selectImportFile() { QString filter_opml20 = tr("OPML 2.0 files (*.opml)"); QString filter; @@ -140,7 +143,6 @@ void FormImportExport::selectImportFile() { if (selected_filter == filter_opml20) { m_conversionType = OPML20; } - // NOTE: Add other types here. m_ui->m_lblSelectFile->setStatus(WidgetWithStatus::Ok, QDir::toNativeSeparators(selected_file), tr("File is selected.")); parseImportFile(selected_file); @@ -148,7 +150,7 @@ void FormImportExport::selectImportFile() { } } -void FormImportExport::parseImportFile(const QString &file_name) { +void FormStandardImportExport::parseImportFile(const QString &file_name) { QFile input_file(file_name); QByteArray input_data; @@ -186,7 +188,7 @@ void FormImportExport::parseImportFile(const QString &file_name) { m_ui->m_buttonBox->button(QDialogButtonBox::Ok)->setEnabled(parsing_result); } -void FormImportExport::performAction() { +void FormStandardImportExport::performAction() { switch (m_model->mode()) { case FeedsImportExportModel::Import: importFeeds(); @@ -201,30 +203,20 @@ void FormImportExport::performAction() { } } -void FormImportExport::exportFeeds() { +void FormStandardImportExport::exportFeeds() { switch (m_conversionType) { case OPML20: { QByteArray result_data; bool result_export = m_model->exportToOMPL20(result_data); if (result_export) { - // Save exported data. - QFile output_file(m_ui->m_lblSelectFile->label()->text()); + try { + IOFactory::writeTextFile(m_ui->m_lblSelectFile->label()->text(), result_data); - if (output_file.open(QIODevice::Unbuffered | QIODevice::Truncate | QIODevice::WriteOnly)) { - QTextStream stream(&output_file); - - stream.setCodec("UTF-8"); - stream << QString::fromUtf8(result_data); - output_file.flush(); - output_file.close(); - - m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, tr("Feeds were exported successfully."), - tr("Feeds were exported successfully.")); + m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, tr("Feeds were exported successfully."), tr("Feeds were exported successfully.")); } - else { - m_ui->m_lblResult->setStatus(WidgetWithStatus::Error, tr("Cannot write into destination file."), - tr("Cannot write into destination file.")); + catch (IOException &ex) { + m_ui->m_lblResult->setStatus(WidgetWithStatus::Error, tr("Cannot write into destination file."), ex.message()); } } else { @@ -237,11 +229,11 @@ void FormImportExport::exportFeeds() { } } -void FormImportExport::importFeeds() { +void FormStandardImportExport::importFeeds() { QString output_message; - if (qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->sourceModel()->mergeModel(m_model, output_message)) { - qApp->mainForm()->tabWidget()->feedMessageViewer()->feedsView()->expandAll(); + if (m_serviceRoot->mergeImportExportModel(m_model, output_message)) { + m_serviceRoot->requestItemExpand(m_serviceRoot->getSubTree(), true); m_ui->m_lblResult->setStatus(WidgetWithStatus::Ok, output_message, output_message); } else { diff --git a/src/gui/dialogs/formimportexport.h b/src/services/standard/gui/formstandardimportexport.h old mode 100644 new mode 100755 similarity index 75% rename from src/gui/dialogs/formimportexport.h rename to src/services/standard/gui/formstandardimportexport.h index cab60cc05..4b36f1ebc --- a/src/gui/dialogs/formimportexport.h +++ b/src/services/standard/gui/formstandardimportexport.h @@ -20,15 +20,16 @@ #include -#include "ui_formimportexport.h" -#include "core/feedsimportexportmodel.h" - +#include "ui_formstandardimportexport.h" +#include "services/standard/standardfeedsimportexportmodel.h" namespace Ui { - class FormExport; + class FormStandardImportExport; } -class FormImportExport : public QDialog { +class StandardServiceRoot; + +class FormStandardImportExport : public QDialog { Q_OBJECT public: @@ -37,8 +38,8 @@ class FormImportExport : public QDialog { }; // Constructors. - explicit FormImportExport(QWidget *parent = 0); - virtual ~FormImportExport(); + explicit FormStandardImportExport(StandardServiceRoot *service_root, QWidget *parent = 0); + virtual ~FormStandardImportExport(); void setMode(const FeedsImportExportModel::Mode &mode); @@ -54,9 +55,10 @@ class FormImportExport : public QDialog { void exportFeeds(); void importFeeds(); - Ui::FormImportExport *m_ui; + Ui::FormStandardImportExport *m_ui; ConversionType m_conversionType; FeedsImportExportModel *m_model; + StandardServiceRoot *m_serviceRoot; }; #endif // FORMEXPORT_H diff --git a/src/gui/dialogs/formimportexport.ui b/src/services/standard/gui/formstandardimportexport.ui old mode 100644 new mode 100755 similarity index 96% rename from src/gui/dialogs/formimportexport.ui rename to src/services/standard/gui/formstandardimportexport.ui index 46af7e659..53b635d74 --- a/src/gui/dialogs/formimportexport.ui +++ b/src/services/standard/gui/formstandardimportexport.ui @@ -1,7 +1,7 @@ - FormImportExport - + FormStandardImportExport + 0 @@ -149,7 +149,7 @@ m_buttonBox rejected() - FormImportExport + FormStandardImportExport reject() diff --git a/src/core/category.cpp b/src/services/standard/standardcategory.cpp old mode 100644 new mode 100755 similarity index 52% rename from src/core/category.cpp rename to src/services/standard/standardcategory.cpp index cf2fb0bdd..9cbe45021 --- a/src/core/category.cpp +++ b/src/services/standard/standardcategory.cpp @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#include "core/category.h" +#include "services/standard/standardcategory.h" #include "definitions/definitions.h" #include "miscellaneous/databasefactory.h" @@ -23,115 +23,125 @@ #include "miscellaneous/settings.h" #include "miscellaneous/iconfactory.h" #include "core/feedsmodel.h" +#include "gui/dialogs/formmain.h" +#include "gui/feedmessageviewer.h" +#include "gui/feedsview.h" +#include "services/standard/gui/formstandardcategorydetails.h" +#include "services/standard/standardserviceroot.h" +#include "services/standard/standardfeed.h" #include #include #include +#include -Category::Category(RootItem *parent_item) : RootItem(parent_item) { - init(); +StandardCategory::StandardCategory(RootItem *parent_item) : Category(parent_item) { } -Category::Category(const Category &other) - : RootItem(NULL) { - m_kind = other.kind(); - m_id = other.id(); - m_title = other.title(); - m_description = other.description(); - m_icon = other.icon(); - m_creationDate = other.creationDate(); - m_childItems = other.childItems(); - m_parentItem = other.parent(); +StandardCategory::StandardCategory(const StandardCategory &other) + : Category(NULL) { + setId(other.id()); + setTitle(other.title()); + setDescription(other.description()); + setIcon(other.icon()); + setCreationDate(other.creationDate()); + setChildItems(other.childItems()); + setParent(other.parent()); } -Category::~Category() { +StandardCategory::~StandardCategory() { qDebug("Destroying Category instance."); } -void Category::init() { - m_kind = RootItem::Cattegory; +StandardServiceRoot *StandardCategory::serviceRoot() { + return static_cast(getParentServiceRoot()); } -QVariant Category::data(int column, int role) const { +QVariant StandardCategory::data(int column, int role) const { switch (role) { case Qt::ToolTipRole: if (column == FDS_MODEL_TITLE_INDEX) { //: Tooltip for standard feed. return tr("%1 (category)" - "%2%3").arg(m_title, - m_description.isEmpty() ? QString() : QString('\n') + m_description, - m_childItems.size() == 0 ? + "%2%3").arg(title(), + description().isEmpty() ? QString() : QSL("\n") + description(), + childCount() == 0 ? tr("\nThis category does not contain any nested items.") : QString()); } - else if (column == FDS_MODEL_COUNTS_INDEX) { - //: Tooltip for "unread" column of feed list. - return tr("%n unread message(s).", "", countOfUnreadMessages()); - } else { - return QVariant(); - } - - case Qt::EditRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_title; - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - return countOfUnreadMessages(); - } - else { - return QVariant(); - } - - case Qt::FontRole: - return countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont; - - case Qt::DisplayRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_title; - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString() - .replace(PLACEHOLDER_UNREAD_COUNTS, QString::number(countOfUnreadMessages())) - .replace(PLACEHOLDER_ALL_COUNTS, QString::number(countOfAllMessages())); - } - else { - return QVariant(); - } - - case Qt::DecorationRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_icon; - } - else { - return QVariant(); - } - - case Qt::TextAlignmentRole: - if (column == FDS_MODEL_COUNTS_INDEX) { - return Qt::AlignCenter; - } - else { - return QVariant(); + return Category::data(column, role); } default: - return QVariant(); + return Category::data(column, role); } } -bool Category::removeItself() { +Qt::ItemFlags StandardCategory::additionalFlags() const { + return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; +} + +bool StandardCategory::performDragDropChange(RootItem *target_item) { + StandardCategory *category_new = new StandardCategory(*this); + category_new->clearChildren(); + category_new->setParent(target_item); + + if (editItself(category_new)) { + serviceRoot()->requestItemReassignment(this, target_item); + delete category_new; + return true; + } + else { + delete category_new; + return false; + } +} + +bool StandardCategory::editViaGui() { + QPointer form_pointer = new FormStandardCategoryDetails(serviceRoot(), qApp->mainForm()); + + form_pointer.data()->exec(this, NULL); + delete form_pointer.data(); + return false; +} + +bool StandardCategory::deleteViaGui() { + if (removeItself()) { + serviceRoot()->requestItemRemoval(this); + return true; + } + else { + return false; + } +} + +bool StandardCategory::markAsReadUnread(ReadStatus status) { + return serviceRoot()->markFeedsReadUnread(getSubTreeFeeds(), status); +} + +bool StandardCategory::cleanMessages(bool clean_read_only) { + return serviceRoot()->cleanFeeds(getSubTreeFeeds(), clean_read_only); +} + +bool StandardCategory::removeItself() { bool children_removed = true; - // Remove all child items (feeds, categories.) - foreach (RootItem *child, m_childItems) { - children_removed &= child->removeItself(); + // Remove all child items (feeds and categories) + // from the database. + foreach (RootItem *child, childItems()) { + if (child->kind() == RootItemKind::Category) { + children_removed &= static_cast(child)->removeItself(); + } + else if (child->kind() == RootItemKind::Feed) { + children_removed &= static_cast(child)->removeItself(); + } } if (children_removed) { // Children are removed, remove this standard category too. - QSqlDatabase database = qApp->database()->connection(QSL("Category"), DatabaseFactory::FromSettings); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_remove(database); // Remove this category from database. @@ -146,21 +156,21 @@ bool Category::removeItself() { } } -bool Category::addItself(RootItem *parent) { +bool StandardCategory::addItself(RootItem *parent) { // Now, add category to persistent storage. - // Children are removed, remove this standard category too. - QSqlDatabase database = qApp->database()->connection(QSL("Category"), DatabaseFactory::FromSettings); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_add(database); query_add.setForwardOnly(true); query_add.prepare("INSERT INTO Categories " - "(parent_id, title, description, date_created, icon) " - "VALUES (:parent_id, :title, :description, :date_created, :icon);"); + "(parent_id, title, description, date_created, icon, account_id) " + "VALUES (:parent_id, :title, :description, :date_created, :icon, :account_id);"); query_add.bindValue(QSL(":parent_id"), parent->id()); query_add.bindValue(QSL(":title"), title()); query_add.bindValue(QSL(":description"), description()); query_add.bindValue(QSL(":date_created"), creationDate().toMSecsSinceEpoch()); query_add.bindValue(QSL(":icon"), qApp->icons()->toByteArray(icon())); + query_add.bindValue(QSL(":account_id"), parent->getParentServiceRoot()->accountId()); if (!query_add.exec()) { qDebug("Failed to add category to database: %s.", qPrintable(query_add.lastError().text())); @@ -169,26 +179,15 @@ bool Category::addItself(RootItem *parent) { return false; } - query_add.prepare(QSL("SELECT id FROM Categories WHERE title = :title;")); - query_add.bindValue(QSL(":title"), title()); - - if (query_add.exec() && query_add.next()) { - // New category was added, fetch is primary id - // from the database. - setId(query_add.value(0).toInt()); - } - else { - // Something failed. - return false; - } + setId(query_add.lastInsertId().toInt()); return true; } -bool Category::editItself(Category *new_category_data) { - QSqlDatabase database = qApp->database()->connection(QSL("Category"), DatabaseFactory::FromSettings); +bool StandardCategory::editItself(StandardCategory *new_category_data) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_update_category(database); - Category *original_category = this; + StandardCategory *original_category = this; RootItem *new_parent = new_category_data->parent(); query_update_category.setForwardOnly(true); @@ -215,9 +214,7 @@ bool Category::editItself(Category *new_category_data) { return true; } -Category::Category(const QSqlRecord &record) : RootItem(NULL) { - init(); - +StandardCategory::StandardCategory(const QSqlRecord &record) : Category(NULL) { setId(record.value(CAT_DB_ID_INDEX).toInt()); setTitle(record.value(CAT_DB_TITLE_INDEX).toString()); setDescription(record.value(CAT_DB_DESCRIPTION_INDEX).toString()); diff --git a/src/core/category.h b/src/services/standard/standardcategory.h old mode 100644 new mode 100755 similarity index 62% rename from src/core/category.h rename to src/services/standard/standardcategory.h index 3cf04adb1..7a90fd1f1 --- a/src/core/category.h +++ b/src/services/standard/standardcategory.h @@ -18,39 +18,55 @@ #ifndef FEEDSMODELCATEGORY_H #define FEEDSMODELCATEGORY_H -#include "core/rootitem.h" +#include "services/abstract/category.h" #include #include class FeedsModel; +class StandardServiceRoot; // Base class for all categories contained in FeedsModel. // NOTE: This class should be derived to create PARTICULAR category types. // NOTE: This class should not be instantiated directly. -class Category : public RootItem { - Q_DECLARE_TR_FUNCTIONS(Category) +class StandardCategory : public Category { + Q_OBJECT public: // Constructors and destructors - explicit Category(RootItem *parent_item = NULL); - explicit Category(const Category &other); - explicit Category(const QSqlRecord &record); - virtual ~Category(); + explicit StandardCategory(RootItem *parent_item = NULL); + explicit StandardCategory(const StandardCategory &other); + explicit StandardCategory(const QSqlRecord &record); + virtual ~StandardCategory(); + + StandardServiceRoot *serviceRoot(); // Returns the actual data representation of standard category. QVariant data(int column, int role) const; + Qt::ItemFlags additionalFlags() const; + bool performDragDropChange(RootItem *target_item); + + bool canBeEdited() { + return true; + } + + bool canBeDeleted() { + return true; + } + + bool editViaGui(); + bool deleteViaGui(); + + bool markAsReadUnread(ReadStatus status); + bool cleanMessages(bool clean_read_only); // Removes category and all its children from persistent // database. bool removeItself(); bool addItself(RootItem *parent); - bool editItself(Category *new_category_data); - - private: - void init(); + bool editItself(StandardCategory *new_category_data); }; #endif // FEEDSMODELCLASSICCATEGORY_H diff --git a/src/core/feed.cpp b/src/services/standard/standardfeed.cpp old mode 100644 new mode 100755 similarity index 70% rename from src/core/feed.cpp rename to src/services/standard/standardfeed.cpp index 210a8932e..48a8ede25 --- a/src/core/feed.cpp +++ b/src/services/standard/standardfeed.cpp @@ -15,7 +15,7 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#include "core/feed.h" +#include "services/standard/standardfeed.h" #include "definitions/definitions.h" #include "core/parsingfactory.h" @@ -26,83 +26,189 @@ #include "miscellaneous/iconfactory.h" #include "miscellaneous/simplecrypt/simplecrypt.h" #include "network-web/networkfactory.h" +#include "gui/dialogs/formmain.h" +#include "gui/feedmessageviewer.h" +#include "gui/feedsview.h" +#include "services/standard/standardserviceroot.h" +#include "services/standard/gui/formstandardfeeddetails.h" #include #include #include #include #include +#include #include #include #include #include -void Feed::init() { +StandardFeed::StandardFeed(RootItem *parent_item) + : Feed(parent_item) { m_passwordProtected = false; m_username = QString(); m_password = QString(); - m_status = Normal; m_networkError = QNetworkReply::NoError; m_type = Rss0X; m_totalCount = 0; m_unreadCount = 0; - m_autoUpdateType = DontAutoUpdate; - m_autoUpdateInitialInterval = DEFAULT_AUTO_UPDATE_INTERVAL; - m_autoUpdateRemainingInterval = DEFAULT_AUTO_UPDATE_INTERVAL; m_encoding = QString(); m_url = QString(); - m_kind = RootItem::Feeed; } -Feed::Feed(RootItem *parent_item) - : RootItem(parent_item) { - init(); -} - -Feed::Feed(const Feed &other) - : RootItem(NULL) { +StandardFeed::StandardFeed(const StandardFeed &other) + : Feed(NULL) { m_passwordProtected = other.passwordProtected(); m_username = other.username(); m_password = other.password(); - m_status = other.status(); m_networkError = other.networkError(); m_type = other.type(); m_totalCount = other.countOfAllMessages(); m_unreadCount = other.countOfUnreadMessages(); - m_autoUpdateType = other.autoUpdateType(); - m_autoUpdateInitialInterval = other.autoUpdateInitialInterval(); - m_autoUpdateRemainingInterval = other.autoUpdateRemainingInterval(); m_encoding = other.encoding(); m_url = other.url(); - m_kind = RootItem::Feeed; - m_title = other.title(); - m_id = other.id(); - m_icon = other.icon(); - m_childItems = other.childItems(); - m_parentItem = other.parent(); - m_creationDate = other.creationDate(); - m_description = other.description(); + + setStatus(other.status()); + setAutoUpdateType(other.autoUpdateType()); + setAutoUpdateInitialInterval(other.autoUpdateInitialInterval()); + setAutoUpdateRemainingInterval(other.autoUpdateRemainingInterval()); + + setTitle(other.title()); + setId(other.id()); + setIcon(other.icon()); + setChildItems(other.childItems()); + setParent(other.parent()); + setCreationDate(other.creationDate()); + setDescription(other.description()); } -Feed::~Feed() { +StandardFeed::~StandardFeed() { qDebug("Destroying Feed instance."); } -int Feed::childCount() const { - // Because feed has no children. - return 0; -} - -int Feed::countOfAllMessages() const { +int StandardFeed::countOfAllMessages() const { return m_totalCount; } -int Feed::countOfUnreadMessages() const { +int StandardFeed::countOfUnreadMessages() const { return m_unreadCount; } -QString Feed::typeToString(Feed::Type type) { +QList StandardFeed::contextMenu() { + return serviceRoot()->getContextMenuForFeed(this); +} + +StandardServiceRoot *StandardFeed::serviceRoot() { + return static_cast(getParentServiceRoot()); +} + +bool StandardFeed::editViaGui() { + QPointer form_pointer = new FormStandardFeedDetails(serviceRoot(), qApp->mainForm()); + + form_pointer.data()->exec(this, NULL); + delete form_pointer.data(); + return false; +} + +bool StandardFeed::deleteViaGui() { + if (removeItself()) { + serviceRoot()->requestItemRemoval(this); + return true; + } + else { + return false; + } +} + +bool StandardFeed::markAsReadUnread(ReadStatus status) { + return serviceRoot()->markFeedsReadUnread(QList() << this, status); +} + +bool StandardFeed::cleanMessages(bool clean_read_only) { + return serviceRoot()->cleanFeeds(QList() << this, clean_read_only); +} + +QList StandardFeed::undeletedMessages() const { + QList messages; + + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_read_msg(database); + query_read_msg.setForwardOnly(true); + query_read_msg.prepare("SELECT * " + "FROM Messages " + "WHERE is_deleted = 0 AND feed = :feed AND account_id = :account_id;"); + + query_read_msg.bindValue(QSL(":feed"), id()); + query_read_msg.bindValue(QSL(":account_id"), const_cast(this)->serviceRoot()->accountId()); + + // FIXME: Fix those const functions, this is fucking ugly. + + if (query_read_msg.exec()) { + while (query_read_msg.next()) { + bool decoded; + Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded); + + if (decoded) { + messages.append(message); + } + + messages.append(message); + } + } + + return messages; +} + +QVariant StandardFeed::data(int column, int role) const { + switch (role) { + case Qt::ToolTipRole: + if (column == FDS_MODEL_TITLE_INDEX) { + QString auto_update_string; + + switch (autoUpdateType()) { + case DontAutoUpdate: + //: Describes feed auto-update status. + auto_update_string = tr("does not use auto-update"); + break; + + case DefaultAutoUpdate: + //: Describes feed auto-update status. + auto_update_string = tr("uses global settings"); + break; + + case SpecificAutoUpdate: + default: + //: Describes feed auto-update status. + auto_update_string = tr("uses specific settings " + "(%n minute(s) to next auto-update)", + 0, + autoUpdateRemainingInterval()); + break; + } + + //: Tooltip for feed. + return tr("%1 (%2)" + "%3\n\n" + "Network status: %6\n" + "Encoding: %4\n" + "Auto-update status: %5").arg(title(), + StandardFeed::typeToString(type()), + description().isEmpty() ? QString() : QString('\n') + description(), + encoding(), + auto_update_string, + NetworkFactory::networkErrorText(m_networkError)); + } + else { + return Feed::data(column, role); + } + + default: + return Feed::data(column, role); + } +} + +QString StandardFeed::typeToString(StandardFeed::Type type) { switch (type) { case Atom10: return QSL("ATOM 1.0"); @@ -119,32 +225,34 @@ QString Feed::typeToString(Feed::Type type) { } } -void Feed::updateCounts(bool including_total_count, bool update_feed_statuses) { - QSqlDatabase database = qApp->database()->connection(QSL("Feed"), DatabaseFactory::FromSettings); +void StandardFeed::updateCounts(bool including_total_count) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_all(database); query_all.setForwardOnly(true); if (including_total_count) { - if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = %1 AND is_deleted = 0;").arg(id())) && query_all.next()) { + if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = '%1' AND is_deleted = 0 AND account_id = %2;").arg(QString::number(id()), + QString::number(const_cast(this)->serviceRoot()->accountId()))) && query_all.next()) { m_totalCount = query_all.value(0).toInt(); } } // Obtain count of unread messages. - if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = %1 AND is_deleted = 0 AND is_read = 0;").arg(id())) && query_all.next()) { + if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = '%1' AND is_deleted = 0 AND is_read = 0 AND account_id = %2;").arg(QString::number(id()), + QString::number(const_cast(this)->serviceRoot()->accountId()))) && query_all.next()) { int new_unread_count = query_all.value(0).toInt(); - if (update_feed_statuses && m_status == NewMessages && new_unread_count < m_unreadCount) { - m_status = Normal; + if (status() == NewMessages && new_unread_count < m_unreadCount) { + setStatus(Normal); } m_unreadCount = new_unread_count; } } -void Feed::fetchMetadataForItself() { - QPair metadata = guessFeed(url(), username(), password()); +void StandardFeed::fetchMetadataForItself() { + QPair metadata = guessFeed(url(), username(), password()); if (metadata.first != NULL && metadata.second == QNetworkReply::NoError) { // Some properties are not updated when new metadata are fetched. @@ -158,16 +266,20 @@ void Feed::fetchMetadataForItself() { editItself(metadata.first); delete metadata.first; + + // Notify the model about fact, that it needs to reload new information about + // this item, particularly the icon. + serviceRoot()->itemChanged(QList() << this); } else { qApp->showGuiMessage(tr("Metadata not fetched"), - tr("Metadata was not fetched because: %1").arg(NetworkFactory::networkErrorText(metadata.second)), + tr("Metadata was not fetched because: %1.").arg(NetworkFactory::networkErrorText(metadata.second)), QSystemTrayIcon::Critical); } } -QPair Feed::guessFeed(const QString &url, const QString &username, const QString &password) { - QPair result; result.first = NULL; +QPair StandardFeed::guessFeed(const QString &url, const QString &username, const QString &password) { + QPair result; result.first = NULL; QByteArray feed_contents; NetworkResult network_result = NetworkFactory::downloadFeedFile(url, @@ -196,7 +308,7 @@ QPair Feed::guessFeed(const QString &url, con } if (result.first == NULL) { - result.first = new Feed(); + result.first = new StandardFeed(); } QTextCodec *custom_codec = QTextCodec::codecForName(xml_schema_encoding.toLocal8Bit()); @@ -308,114 +420,26 @@ QPair Feed::guessFeed(const QString &url, con return result; } -QVariant Feed::data(int column, int role) const { - switch (role) { - case Qt::DisplayRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_title; - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - return qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::CountFormat)).toString() - .replace(PLACEHOLDER_UNREAD_COUNTS, QString::number(countOfUnreadMessages())) - .replace(PLACEHOLDER_ALL_COUNTS, QString::number(countOfAllMessages())); - } - else { - return QVariant(); - } +Qt::ItemFlags StandardFeed::additionalFlags() const { + return Qt::ItemIsDragEnabled; +} - case Qt::EditRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_title; - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - return countOfUnreadMessages(); - } - else { - return QVariant(); - } +bool StandardFeed::performDragDropChange(RootItem *target_item) { + StandardFeed *feed_new = new StandardFeed(*this); + feed_new->setParent(target_item); - case Qt::DecorationRole: - if (column == FDS_MODEL_TITLE_INDEX) { - return m_icon; - } - else { - return QVariant(); - } - - case Qt::ToolTipRole: - if (column == FDS_MODEL_TITLE_INDEX) { - QString auto_update_string; - - switch (m_autoUpdateType) { - case DontAutoUpdate: - //: Describes feed auto-update status. - auto_update_string = tr("does not use auto-update"); - break; - - case DefaultAutoUpdate: - //: Describes feed auto-update status. - auto_update_string = tr("uses global settings"); - break; - - case SpecificAutoUpdate: - default: - //: Describes feed auto-update status. - auto_update_string = tr("uses specific settings " - "(%n minute(s) to next auto-update)", - 0, - m_autoUpdateRemainingInterval); - break; - } - - //: Tooltip for feed. - return tr("%1 (%2)" - "%3\n\n" - "Network status: %6\n" - "Encoding: %4\n" - "Auto-update status: %5").arg(m_title, - Feed::typeToString(m_type), - m_description.isEmpty() ? QString() : QString('\n') + m_description, - m_encoding, - auto_update_string, - NetworkFactory::networkErrorText(m_networkError)); - } - else if (column == FDS_MODEL_COUNTS_INDEX) { - //: Tooltip for "unread" column of feed list. - return tr("%n unread message(s).", 0, countOfUnreadMessages()); - } - else { - return QVariant(); - } - - case Qt::TextAlignmentRole: - if (column == FDS_MODEL_COUNTS_INDEX) { - return Qt::AlignCenter; - } - else { - return QVariant(); - } - - case Qt::FontRole: - return countOfUnreadMessages() > 0 ? m_boldFont : m_normalFont; - - case Qt::ForegroundRole: - switch (m_status) { - case NewMessages: - return QColor(Qt::blue); - - case NetworkError: - return QColor(Qt::red); - - default: - return QVariant(); - } - - default: - return QVariant(); + if (editItself(feed_new)) { + serviceRoot()->requestItemReassignment(this, target_item); + delete feed_new; + return true; + } + else { + delete feed_new; + return false; } } -int Feed::update() { +int StandardFeed::update() { QByteArray feed_contents; int download_timeout = qApp->settings()->value(GROUP(Feeds), SETTING(Feeds::UpdateTimeout)).toInt(); m_networkError = NetworkFactory::downloadFeedFile(url(), download_timeout, feed_contents, @@ -423,11 +447,11 @@ int Feed::update() { if (m_networkError != QNetworkReply::NoError) { qWarning("Error during fetching of new messages for feed '%s' (id %d).", qPrintable(url()), id()); - m_status = NetworkError; + setStatus(Error); return 0; } - else { - m_status = Normal; + else if (status() != NewMessages) { + setStatus(Normal); } // Encode downloaded data for further parsing. @@ -448,16 +472,16 @@ int Feed::update() { QList messages; switch (type()) { - case Feed::Rss0X: - case Feed::Rss2X: + case StandardFeed::Rss0X: + case StandardFeed::Rss2X: messages = ParsingFactory::parseAsRSS20(formatted_feed_contents); break; - case Feed::Rdf: + case StandardFeed::Rdf: messages = ParsingFactory::parseAsRDF(formatted_feed_contents); break; - case Feed::Atom10: + case StandardFeed::Atom10: messages = ParsingFactory::parseAsATOM10(formatted_feed_contents); default: @@ -467,8 +491,8 @@ int Feed::update() { return updateMessages(messages); } -bool Feed::removeItself() { - QSqlDatabase database = qApp->database()->connection(QSL("Feed"), DatabaseFactory::FromSettings); +bool StandardFeed::removeItself() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_remove(database); query_remove.setForwardOnly(true); @@ -488,15 +512,15 @@ bool Feed::removeItself() { return query_remove.exec(); } -bool Feed::addItself(RootItem *parent) { +bool StandardFeed::addItself(RootItem *parent) { // Now, add feed to persistent storage. - QSqlDatabase database = qApp->database()->connection(QSL("Feed"), DatabaseFactory::FromSettings); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_add_feed(database); query_add_feed.setForwardOnly(true); query_add_feed.prepare("INSERT INTO Feeds " - "(title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type) " - "VALUES (:title, :description, :date_created, :icon, :category, :encoding, :url, :protected, :username, :password, :update_type, :update_interval, :type);"); + "(title, description, date_created, icon, category, encoding, url, protected, username, password, update_type, update_interval, type, account_id) " + "VALUES (:title, :description, :date_created, :icon, :category, :encoding, :url, :protected, :username, :password, :update_type, :update_interval, :type, :account_id);"); query_add_feed.bindValue(QSL(":title"), title()); query_add_feed.bindValue(QSL(":description"), description()); query_add_feed.bindValue(QSL(":date_created"), creationDate().toMSecsSinceEpoch()); @@ -506,7 +530,15 @@ bool Feed::addItself(RootItem *parent) { query_add_feed.bindValue(QSL(":url"), url()); query_add_feed.bindValue(QSL(":protected"), (int) passwordProtected()); query_add_feed.bindValue(QSL(":username"), username()); - query_add_feed.bindValue(QSL(":password"), TextFactory::encrypt(password())); + query_add_feed.bindValue(QSL(":account_id"), parent->getParentServiceRoot()->accountId()); + + if (password().isEmpty()) { + query_add_feed.bindValue(QSL(":password"), password()); + } + else { + query_add_feed.bindValue(QSL(":password"), TextFactory::encrypt(password())); + } + query_add_feed.bindValue(QSL(":update_type"), (int) autoUpdateType()); query_add_feed.bindValue(QSL(":update_interval"), autoUpdateInitialInterval()); query_add_feed.bindValue(QSL(":type"), (int) type()); @@ -518,24 +550,16 @@ bool Feed::addItself(RootItem *parent) { return false; } - query_add_feed.prepare(QSL("SELECT id FROM Feeds WHERE url = :url;")); - query_add_feed.bindValue(QSL(":url"), url()); - if (query_add_feed.exec() && query_add_feed.next()) { - // New feed was added, fetch is primary id from the database. - setId(query_add_feed.value(0).toInt()); - } - else { - // Something failed. - return false; - } + // New feed was added, fetch is primary id from the database. + setId(query_add_feed.lastInsertId().toInt()); return true; } -bool Feed::editItself(Feed *new_feed_data) { - QSqlDatabase database = qApp->database()->connection(QSL("Feed"), DatabaseFactory::FromSettings); +bool StandardFeed::editItself(StandardFeed *new_feed_data) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); QSqlQuery query_update_feed(database); - Feed *original_feed = this; + StandardFeed *original_feed = this; RootItem *new_parent = new_feed_data->parent(); query_update_feed.setForwardOnly(true); @@ -550,7 +574,14 @@ bool Feed::editItself(Feed *new_feed_data) { query_update_feed.bindValue(QSL(":url"), new_feed_data->url()); query_update_feed.bindValue(QSL(":protected"), (int) new_feed_data->passwordProtected()); query_update_feed.bindValue(QSL(":username"), new_feed_data->username()); - query_update_feed.bindValue(QSL(":password"), TextFactory::encrypt(new_feed_data->password())); + + if (password().isEmpty()) { + query_update_feed.bindValue(QSL(":password"), new_feed_data->password()); + } + else { + query_update_feed.bindValue(QSL(":password"), TextFactory::encrypt(new_feed_data->password())); + } + query_update_feed.bindValue(QSL(":update_type"), (int) new_feed_data->autoUpdateType()); query_update_feed.bindValue(QSL(":update_interval"), new_feed_data->autoUpdateInitialInterval()); query_update_feed.bindValue(QSL(":type"), new_feed_data->type()); @@ -579,11 +610,12 @@ bool Feed::editItself(Feed *new_feed_data) { return true; } -int Feed::updateMessages(const QList &messages) { +int StandardFeed::updateMessages(const QList &messages) { int feed_id = id(); int updated_messages = 0; - QSqlDatabase database = qApp->database()->connection(QSL("Feed"), DatabaseFactory::FromSettings); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); bool remove_duplicates = qApp->settings()->value(GROUP(Messages), SETTING(Messages::RemoveDuplicates)).toBool(); + int account_id = serviceRoot()->accountId(); // Prepare queries. QSqlQuery query_select(database); @@ -594,17 +626,17 @@ int Feed::updateMessages(const QList &messages) { // WARNING: One feed CANNOT contain two (or more) messages with same AUTHOR AND TITLE AND URL AND DATE_CREATED. query_select.setForwardOnly(true); query_select.prepare("SELECT id, feed, date_created FROM Messages " - "WHERE feed = :feed AND title = :title AND url = :url AND author = :author;"); + "WHERE feed = :feed AND title = :title AND url = :url AND author = :author AND account_id = :account_id;"); // Used to insert new messages. query_insert.setForwardOnly(true); query_insert.prepare("INSERT INTO Messages " - "(feed, title, url, author, date_created, contents, enclosures) " - "VALUES (:feed, :title, :url, :author, :date_created, :contents, :enclosures);"); + "(feed, title, url, author, date_created, contents, enclosures, account_id) " + "VALUES (:feed, :title, :url, :author, :date_created, :contents, :enclosures, :account_id);"); if (remove_duplicates) { query_update.setForwardOnly(true); - query_update.prepare(QSL("UPDATE Messages SET contents = :contents enclosures = :enclosures WHERE id = :id;")); + query_update.prepare(QSL("UPDATE Messages SET contents = :contents, enclosures = :enclosures WHERE id = :id;")); } if (!database.transaction()) { @@ -631,6 +663,7 @@ int Feed::updateMessages(const QList &messages) { query_select.bindValue(QSL(":title"), message.m_title); query_select.bindValue(QSL(":url"), message.m_url); query_select.bindValue(QSL(":author"), message.m_author); + query_select.bindValue(QSL(":account_id"), account_id); query_select.exec(); QList datetime_stamps; @@ -652,9 +685,9 @@ int Feed::updateMessages(const QList &messages) { query_insert.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); query_insert.bindValue(QSL(":contents"), message.m_contents); query_insert.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); + query_insert.bindValue(QSL(":account_id"), account_id); if (query_insert.exec() && query_insert.numRowsAffected() == 1) { - setStatus(NewMessages); updated_messages++; } @@ -686,7 +719,6 @@ int Feed::updateMessages(const QList &messages) { query_insert.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); if (query_insert.exec() && query_insert.numRowsAffected() == 1) { - setStatus(NewMessages); updated_messages++; } @@ -697,22 +729,28 @@ int Feed::updateMessages(const QList &messages) { } } + if (updated_messages > 0) { + setStatus(NewMessages); + } + if (!database.commit()) { database.rollback(); qDebug("Transaction commit for message downloader failed."); } + else { + updateCounts(true); + serviceRoot()->itemChanged(QList() << this); + } return updated_messages; } -QNetworkReply::NetworkError Feed::networkError() const { +QNetworkReply::NetworkError StandardFeed::networkError() const { return m_networkError; } -Feed::Feed(const QSqlRecord &record) : RootItem(NULL) { - m_kind = RootItem::Feeed; - +StandardFeed::StandardFeed(const QSqlRecord &record) : Feed(NULL) { setTitle(record.value(FDS_DB_TITLE_INDEX).toString()); setId(record.value(FDS_DB_ID_INDEX).toInt()); setDescription(record.value(FDS_DB_DESCRIPTION_INDEX).toString()); @@ -722,8 +760,15 @@ Feed::Feed(const QSqlRecord &record) : RootItem(NULL) { setUrl(record.value(FDS_DB_URL_INDEX).toString()); setPasswordProtected(record.value(FDS_DB_PROTECTED_INDEX).toBool()); setUsername(record.value(FDS_DB_USERNAME_INDEX).toString()); - setPassword(TextFactory::decrypt(record.value(FDS_DB_PASSWORD_INDEX).toString())); + + if (record.value(FDS_DB_PASSWORD_INDEX).toString().isEmpty()) { + setPassword(record.value(FDS_DB_PASSWORD_INDEX).toString()); + } + else { + setPassword(TextFactory::decrypt(record.value(FDS_DB_PASSWORD_INDEX).toString())); + } + + setAutoUpdateType(static_cast(record.value(FDS_DB_UPDATE_TYPE_INDEX).toInt())); setAutoUpdateInitialInterval(record.value(FDS_DB_UPDATE_INTERVAL_INDEX).toInt()); - updateCounts(); } diff --git a/src/core/feed.h b/src/services/standard/standardfeed.h old mode 100644 new mode 100755 similarity index 61% rename from src/core/feed.h rename to src/services/standard/standardfeed.h index 68df38152..84a414c96 --- a/src/core/feed.h +++ b/src/services/standard/standardfeed.h @@ -18,7 +18,7 @@ #ifndef FEEDSMODELFEED_H #define FEEDSMODELFEED_H -#include "core/rootitem.h" +#include "services/abstract/feed.h" #include #include @@ -30,11 +30,12 @@ class Message; class FeedsModel; +class StandardServiceRoot; // Represents BASE class for feeds contained in FeedsModel. // NOTE: This class should be derived to create PARTICULAR feed types. -class Feed : public RootItem { - Q_DECLARE_TR_FUNCTIONS(Feed) +class StandardFeed : public Feed { + Q_OBJECT public: // Describes possible types of feeds. @@ -46,30 +47,13 @@ class Feed : public RootItem { Atom10 = 3 }; - // Specifies the auto-update strategy for the feed. - enum AutoUpdateType { - DontAutoUpdate = 0, - DefaultAutoUpdate = 1, - SpecificAutoUpdate = 2 - }; - - // Specifies the actual "status" of the feed. - // For example if it has new messages, error - // occurred, and so on. - enum Status { - Normal = 0, - NewMessages = 1, - NetworkError = 2 - }; - // Constructors and destructors. - explicit Feed(RootItem *parent_item = NULL); - explicit Feed(const Feed &other); - explicit Feed(const QSqlRecord &record); - virtual ~Feed(); + explicit StandardFeed(RootItem *parent_item = NULL); + explicit StandardFeed(const StandardFeed &other); + explicit StandardFeed(const QSqlRecord &record); + virtual ~StandardFeed(); - // Returns 0, feeds have no children. - int childCount() const; + StandardServiceRoot *serviceRoot(); // Getters/setters for count of messages. // NOTE: For feeds, counts are stored internally @@ -77,17 +61,41 @@ class Feed : public RootItem { int countOfAllMessages() const; int countOfUnreadMessages() const; - // Obtains data related to this feed. + QList contextMenu(); + + bool canBeEdited() { + return true; + } + + bool canBeDeleted() { + return true; + } + + bool editViaGui(); + bool deleteViaGui(); + + bool markAsReadUnread(ReadStatus status); + bool cleanMessages(bool clean_read_only); + + QList undeletedMessages() const; + QVariant data(int column, int role) const; + // Obtains data related to this feed. + Qt::ItemFlags additionalFlags() const; + bool performDragDropChange(RootItem *target_item); + // Perform fetching of new messages. Returns number of newly updated messages. int update(); + // Updates counts of all/unread messages for this feed. + void updateCounts(bool including_total_count); + // Removes this standard feed from persistent // storage. bool removeItself(); bool addItself(RootItem *parent); - bool editItself(Feed *new_feed_data); + bool editItself(StandardFeed *new_feed_data); // Other getters/setters. inline Type type() const { @@ -138,41 +146,6 @@ class Feed : public RootItem { m_url = url; } - inline int autoUpdateInitialInterval() const { - return m_autoUpdateInitialInterval; - } - - inline void setAutoUpdateInitialInterval(int auto_update_interval) { - // If new initial auto-update interval is set, then - // we should reset time that remains to the next auto-update. - m_autoUpdateInitialInterval = auto_update_interval; - m_autoUpdateRemainingInterval = auto_update_interval; - } - - inline AutoUpdateType autoUpdateType() const { - return m_autoUpdateType; - } - - inline void setAutoUpdateType(const AutoUpdateType &autoUpdateType) { - m_autoUpdateType = autoUpdateType; - } - - inline int autoUpdateRemainingInterval() const { - return m_autoUpdateRemainingInterval; - } - - inline void setAutoUpdateRemainingInterval(int autoUpdateRemainingInterval) { - m_autoUpdateRemainingInterval = autoUpdateRemainingInterval; - } - - inline Status status() const { - return m_status; - } - - inline void setStatus(const Status &status) { - m_status = status; - } - QNetworkReply::NetworkError networkError() const; // Tries to guess feed hidden under given URL @@ -180,45 +153,35 @@ class Feed : public RootItem { // Returns pointer to guessed feed (if at least partially // guessed) and retrieved error/status code from network layer // or NULL feed. - static QPair guessFeed(const QString &url, const QString &username, const QString &password); + static QPair guessFeed(const QString &url, const QString &username, const QString &password); // Converts particular feed type to string. static QString typeToString(Type type); public slots: - // Updates counts of all/unread messages for this feed. - void updateCounts(bool including_total_count = true, bool update_feed_statuses = true); - + // Fetches metadata for the feed. void fetchMetadataForItself(); - protected: + private: // Persistently stores given messages into the database // and updates existing messages if newer version is // available. int updateMessages(const QList &messages); private: - void init(); - - private: bool m_passwordProtected; QString m_username; QString m_password; - Status m_status; - QNetworkReply::NetworkError m_networkError; Type m_type; + QNetworkReply::NetworkError m_networkError; int m_totalCount; int m_unreadCount; - AutoUpdateType m_autoUpdateType; - int m_autoUpdateInitialInterval; - int m_autoUpdateRemainingInterval; - QString m_encoding; QString m_url; }; -Q_DECLARE_METATYPE(Feed::Type) +Q_DECLARE_METATYPE(StandardFeed::Type) #endif // FEEDSMODELFEED_H diff --git a/src/core/feedsimportexportmodel.cpp b/src/services/standard/standardfeedsimportexportmodel.cpp old mode 100644 new mode 100755 similarity index 91% rename from src/core/feedsimportexportmodel.cpp rename to src/services/standard/standardfeedsimportexportmodel.cpp index b73d9a1c9..7a7f1a156 --- a/src/core/feedsimportexportmodel.cpp +++ b/src/services/standard/standardfeedsimportexportmodel.cpp @@ -15,10 +15,11 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#include "core/feedsimportexportmodel.h" +#include "services/standard/standardfeedsimportexportmodel.h" -#include "core/feed.h" -#include "core/category.h" +#include "services/standard/standardfeed.h" +#include "services/standard/standardcategory.h" +#include "services/standard/standardserviceroot.h" #include "definitions/definitions.h" #include "miscellaneous/application.h" #include "miscellaneous/iconfactory.h" @@ -99,7 +100,7 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray &result) { } switch (child_item->kind()) { - case RootItem::Cattegory: { + case RootItemKind::Category: { QDomElement outline_category = opml_document.createElement(QSL("outline")); outline_category.setAttribute(QSL("text"), child_item->title()); outline_category.setAttribute(QSL("description"), child_item->description()); @@ -110,8 +111,8 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray &result) { break; } - case RootItem::Feeed: { - Feed *child_feed = child_item->toFeed(); + case RootItemKind::Feed: { + StandardFeed *child_feed = static_cast(child_item); QDomElement outline_feed = opml_document.createElement("outline"); outline_feed.setAttribute(QSL("text"), child_feed->title()); outline_feed.setAttribute(QSL("xmlUrl"), child_feed->url()); @@ -121,16 +122,16 @@ bool FeedsImportExportModel::exportToOMPL20(QByteArray &result) { outline_feed.setAttribute(QSL("rssguard:icon"), QString(qApp->icons()->toByteArray(child_feed->icon()))); switch (child_feed->type()) { - case Feed::Rss0X: - case Feed::Rss2X: + case StandardFeed::Rss0X: + case StandardFeed::Rss2X: outline_feed.setAttribute(QSL("version"), QSL("RSS")); break; - case Feed::Rdf: + case StandardFeed::Rdf: outline_feed.setAttribute(QSL("version"), QSL("RSS1")); break; - case Feed::Atom10: + case StandardFeed::Atom10: outline_feed.setAttribute(QSL("version"), QSL("ATOM")); break; @@ -166,7 +167,7 @@ bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) { return false; } - RootItem *root_item = new RootItem(); + StandardServiceRoot *root_item = new StandardServiceRoot(); QStack model_items; model_items.push(root_item); QStack elements_to_process; elements_to_process.push(opml_document.documentElement().elementsByTagName(QSL("body")).at(0).toElement()); @@ -192,23 +193,23 @@ bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) { QString feed_description = child_element.attribute(QSL("description")); QIcon feed_icon = qApp->icons()->fromByteArray(child_element.attribute(QSL("rssguard:icon")).toLocal8Bit()); - Feed *new_feed = new Feed(active_model_item); + StandardFeed *new_feed = new StandardFeed(active_model_item); new_feed->setTitle(feed_title); new_feed->setDescription(feed_description); new_feed->setEncoding(feed_encoding); new_feed->setUrl(feed_url); new_feed->setCreationDate(QDateTime::currentDateTime()); new_feed->setIcon(feed_icon.isNull() ? qApp->icons()->fromTheme(QSL("folder-feed")) : feed_icon); - new_feed->setAutoUpdateType(Feed::DefaultAutoUpdate); + new_feed->setAutoUpdateType(StandardFeed::DefaultAutoUpdate); if (feed_type == QL1S("RSS1")) { - new_feed->setType(Feed::Rdf); + new_feed->setType(StandardFeed::Rdf); } else if (feed_type == QL1S("ATOM")) { - new_feed->setType(Feed::Atom10); + new_feed->setType(StandardFeed::Atom10); } else { - new_feed->setType(Feed::Rss2X); + new_feed->setType(StandardFeed::Rss2X); } active_model_item->appendChild(new_feed); @@ -230,7 +231,7 @@ bool FeedsImportExportModel::importAsOPML20(const QByteArray &data) { } } - Category *new_category = new Category(active_model_item); + StandardCategory *new_category = new StandardCategory(active_model_item); new_category->setTitle(category_title); new_category->setIcon(category_icon.isNull() ? qApp->icons()->fromTheme(QSL("folder-category")) : category_icon); new_category->setCreationDate(QDateTime::currentDateTime()); @@ -264,7 +265,7 @@ void FeedsImportExportModel::setMode(const FeedsImportExportModel::Mode &mode) { void FeedsImportExportModel::checkAllItems() { foreach (RootItem *root_child, m_rootItem->childItems()) { - if (root_child->kind() != RootItem::Bin) { + if (root_child->kind() != RootItemKind::Bin) { setData(indexForItem(root_child), Qt::Checked, Qt::CheckStateRole); } } @@ -272,7 +273,7 @@ void FeedsImportExportModel::checkAllItems() { void FeedsImportExportModel::uncheckAllItems() { foreach (RootItem *root_child, m_rootItem->childItems()) { - if (root_child->kind() != RootItem::Bin) { + if (root_child->kind() != RootItemKind::Bin) { setData(indexForItem(root_child), Qt::Unchecked, Qt::CheckStateRole); } } @@ -295,7 +296,7 @@ QModelIndex FeedsImportExportModel::index(int row, int column, const QModelIndex } QModelIndex FeedsImportExportModel::indexForItem(RootItem *item) const { - if (item == NULL || item->kind() == RootItem::Root) { + if (item == NULL || item->kind() == RootItemKind::ServiceRoot || item->kind() == RootItemKind::Root) { // Root item lies on invalid index. return QModelIndex(); } @@ -324,7 +325,7 @@ QModelIndex FeedsImportExportModel::indexForItem(RootItem *item) const { for (int i = 0; i < row_count; i++) { RootItem *possible_category = active_item->child(i); - if (possible_category->kind() == RootItem::Cattegory) { + if (possible_category->kind() == RootItemKind::Category) { parents << index(i, 0, active_index); } } @@ -362,7 +363,6 @@ int FeedsImportExportModel::rowCount(const QModelIndex &parent) const { int FeedsImportExportModel::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) - return 1; } @@ -383,9 +383,9 @@ QVariant FeedsImportExportModel::data(const QModelIndex &index, int role) const } else if (role == Qt::DecorationRole) { switch (item->kind()) { - case RootItem::Cattegory: - case RootItem::Bin: - case RootItem::Feeed: + case RootItemKind::Category: + case RootItemKind::Bin: + case RootItemKind::Feed: return item->icon(); default: @@ -394,10 +394,10 @@ QVariant FeedsImportExportModel::data(const QModelIndex &index, int role) const } else if (role == Qt::DisplayRole) { switch (item->kind()) { - case RootItem::Cattegory: + case RootItemKind::Category: return QVariant(item->data(index.column(), role).toString() + tr(" (category)")); - case RootItem::Feeed: + case RootItemKind::Feed: return QVariant(item->data(index.column(), role).toString() + tr(" (feed)")); default: @@ -462,7 +462,7 @@ bool FeedsImportExportModel::setData(const QModelIndex &index, const QVariant &v } Qt::ItemFlags FeedsImportExportModel::flags(const QModelIndex &index) const { - if (!index.isValid() || itemForIndex(index)->kind() == RootItem::Bin) { + if (!index.isValid() || itemForIndex(index)->kind() == RootItemKind::Bin) { return Qt::NoItemFlags; } diff --git a/src/core/feedsimportexportmodel.h b/src/services/standard/standardfeedsimportexportmodel.h old mode 100644 new mode 100755 similarity index 97% rename from src/core/feedsimportexportmodel.h rename to src/services/standard/standardfeedsimportexportmodel.h index 972ed5e3c..3fc5aa9c3 --- a/src/core/feedsimportexportmodel.h +++ b/src/services/standard/standardfeedsimportexportmodel.h @@ -20,7 +20,7 @@ #include -#include "core/rootitem.h" +#include "services/abstract/rootitem.h" class FeedsImportExportModel : public QAbstractItemModel { @@ -73,7 +73,6 @@ class FeedsImportExportModel : public QAbstractItemModel { QHash m_checkStates; RootItem *m_rootItem; - // When it's true, then bool m_recursiveChange; Mode m_mode; }; diff --git a/src/services/standard/standardrecyclebin.cpp b/src/services/standard/standardrecyclebin.cpp new file mode 100755 index 000000000..f99ecb06a --- /dev/null +++ b/src/services/standard/standardrecyclebin.cpp @@ -0,0 +1,32 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/standard/standardrecyclebin.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/standard/standardserviceroot.h" + + +StandardRecycleBin::StandardRecycleBin(RootItem *parent) + : RecycleBin(parent) { + setId(ID_RECYCLE_BIN); +} + +StandardRecycleBin::~StandardRecycleBin() { + qDebug("Destroying RecycleBin instance."); +} diff --git a/src/services/standard/standardrecyclebin.h b/src/services/standard/standardrecyclebin.h new file mode 100755 index 000000000..5dbba31a4 --- /dev/null +++ b/src/services/standard/standardrecyclebin.h @@ -0,0 +1,32 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef STANDARDRECYCLEBIN_H +#define STANDARDRECYCLEBIN_H + +#include "services/abstract/recyclebin.h" + + +class StandardRecycleBin : public RecycleBin { + Q_OBJECT + + public: + explicit StandardRecycleBin(RootItem *parent = NULL); + virtual ~StandardRecycleBin(); +}; + +#endif // STANDARDRECYCLEBIN_H diff --git a/src/services/standard/standardserviceentrypoint.cpp b/src/services/standard/standardserviceentrypoint.cpp new file mode 100755 index 000000000..74a4bfee4 --- /dev/null +++ b/src/services/standard/standardserviceentrypoint.cpp @@ -0,0 +1,100 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + + +#include "services/standard/standardserviceentrypoint.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "services/standard/standardserviceroot.h" + +#include + + +StandardServiceEntryPoint::StandardServiceEntryPoint() { +} + +StandardServiceEntryPoint::~StandardServiceEntryPoint() { +} + +bool StandardServiceEntryPoint::isSingleInstanceService() { + return true; +} + +QString StandardServiceEntryPoint::name() { + return QSL("Standard online feeds (RSS/RDF/ATOM)"); +} + +QString StandardServiceEntryPoint::description() { + return QSL("This service offers integration with standard online RSS/RDF/ATOM feeds and podcasts."); +} + +QString StandardServiceEntryPoint::version() { + return APP_VERSION; +} + +QString StandardServiceEntryPoint::author() { + return APP_AUTHOR; +} + +QIcon StandardServiceEntryPoint::icon() { + return QIcon(APP_ICON_PATH); +} + +QString StandardServiceEntryPoint::code() { + return SERVICE_CODE_STD_RSS; +} + +ServiceRoot *StandardServiceEntryPoint::createNewRoot() { + // Switch DB. + QSqlDatabase database = qApp->database()->connection(QSL("StandardServiceEntryPoint"), DatabaseFactory::FromSettings); + QSqlQuery query(database); + + // First obtain the ID, which can be assigned to this new account. + if (!query.exec("SELECT max(id) FROM Accounts;") || !query.next()) { + return NULL; + } + + int id_to_assign = query.value(0).toInt() + 1; + + if (query.exec(QString("INSERT INTO Accounts (id, type) VALUES (%1, '%2');").arg(QString::number(id_to_assign), + SERVICE_CODE_STD_RSS))) { + StandardServiceRoot *root = new StandardServiceRoot(); + root->setAccountId(id_to_assign); + return root; + } + else { + return NULL; + } +} + +QList StandardServiceEntryPoint::initializeSubtree() { + // Check DB if standard account is enabled. + QSqlDatabase database = qApp->database()->connection(QSL("StandardServiceEntryPoint"), DatabaseFactory::FromSettings); + QSqlQuery query(database); + QList roots; + + if (query.exec(QString("SELECT id FROM Accounts WHERE type = '%1';").arg(SERVICE_CODE_STD_RSS))) { + while (query.next()) { + StandardServiceRoot *root = new StandardServiceRoot(); + root->setAccountId(query.value(0).toInt()); + roots.append(root); + } + } + + return roots; +} diff --git a/src/services/standard/standardserviceentrypoint.h b/src/services/standard/standardserviceentrypoint.h new file mode 100755 index 000000000..b99de25a8 --- /dev/null +++ b/src/services/standard/standardserviceentrypoint.h @@ -0,0 +1,41 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef STANDARDSERVICEENTRYPOINT_H +#define STANDARDSERVICEENTRYPOINT_H + +#include "services/abstract/serviceentrypoint.h" + + +class StandardServiceEntryPoint : public ServiceEntryPoint { + public: + explicit StandardServiceEntryPoint(); + virtual ~StandardServiceEntryPoint(); + + bool isSingleInstanceService(); + QString name(); + QString description(); + QString version(); + QString author(); + QIcon icon(); + QString code(); + + ServiceRoot *createNewRoot(); + QList initializeSubtree(); +}; + +#endif // STANDARDSERVICEENTRYPOINT_H diff --git a/src/services/standard/standardserviceroot.cpp b/src/services/standard/standardserviceroot.cpp new file mode 100755 index 000000000..8fa26db42 --- /dev/null +++ b/src/services/standard/standardserviceroot.cpp @@ -0,0 +1,567 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/standard/standardserviceroot.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/settings.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/mutex.h" +#include "core/feedsmodel.h" +#include "gui/messagebox.h" +#include "gui/dialogs/formmain.h" +#include "exceptions/applicationexception.h" +#include "services/standard/standardserviceentrypoint.h" +#include "services/standard/standardrecyclebin.h" +#include "services/standard/standardfeed.h" +#include "services/standard/standardcategory.h" +#include "services/standard/standardfeedsimportexportmodel.h" +#include "services/standard/gui/formstandardcategorydetails.h" +#include "services/standard/gui/formstandardfeeddetails.h" +#include "services/standard/gui/formstandardimportexport.h" + +#include +#include +#include +#include +#include +#include + + +StandardServiceRoot::StandardServiceRoot(RootItem *parent) + : ServiceRoot(parent), m_recycleBin(new StandardRecycleBin(this)), + m_actionExportFeeds(NULL), m_actionImportFeeds(NULL), m_serviceMenu(QList()), + m_addItemMenu(QList()), m_feedContextMenu(QList()), m_actionFeedFetchMetadata(NULL) { + + setTitle(qApp->system()->getUsername() + QL1S("@") + QL1S(APP_LOW_NAME)); + setIcon(StandardServiceEntryPoint().icon()); + setDescription(tr("This is obligatory service account for standard RSS/RDF/ATOM feeds.")); + setCreationDate(QDateTime::currentDateTime()); +} + +StandardServiceRoot::~StandardServiceRoot() { + qDeleteAll(m_serviceMenu); + qDeleteAll(m_addItemMenu); + qDeleteAll(m_feedContextMenu); +} + +void StandardServiceRoot::start() { + loadFromDatabase(); + + if (qApp->isFirstRun()) { + if (MessageBox::show(qApp->mainForm(), QMessageBox::Question, QObject::tr("Load initial set of feeds"), + tr("You started %1 for the first time, now you can load initial set of feeds.").arg(APP_NAME), + tr("Do you want to load initial set of feeds?"), + QString(), QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + QString target_opml_file = APP_INITIAL_FEEDS_PATH + QDir::separator() + FEED_INITIAL_OPML_PATTERN; + QString current_locale = qApp->localization()->loadedLanguage(); + QString file_to_load; + + if (QFile::exists(target_opml_file.arg(current_locale))) { + file_to_load = target_opml_file.arg(current_locale); + } + else if (QFile::exists(target_opml_file.arg(DEFAULT_LOCALE))) { + file_to_load = target_opml_file.arg(DEFAULT_LOCALE); + } + + FeedsImportExportModel model; + QString output_msg; + + try { + model.importAsOPML20(IOFactory::readTextFile(file_to_load)); + model.checkAllItems(); + + if (mergeImportExportModel(&model, output_msg)) { + requestItemExpand(getSubTree(), true); + } + } + catch (ApplicationException &ex) { + MessageBox::show(qApp->mainForm(), QMessageBox::Critical, tr("Error when loading initial feeds"), ex.message()); + } + } + } +} + +void StandardServiceRoot::stop() { + qDebug("Stopping StandardServiceRoot instance."); +} + +QString StandardServiceRoot::code() { + return SERVICE_CODE_STD_RSS; +} + +bool StandardServiceRoot::canBeEdited() { + return false; +} + +bool StandardServiceRoot::canBeDeleted() { + return true; +} + +bool StandardServiceRoot::deleteViaGui() { + return ServiceRoot::deleteViaGui(); +} + +bool StandardServiceRoot::markAsReadUnread(RootItem::ReadStatus status) { + return ServiceRoot::markAsReadUnread(status); +} + +QVariant StandardServiceRoot::data(int column, int role) const { + switch (role) { + case Qt::ToolTipRole: + if (column == FDS_MODEL_TITLE_INDEX) { + return tr("This is service account for standard RSS/RDF/ATOM feeds.\n\nAccount ID: %1").arg(accountId()); + } + else { + return ServiceRoot::data(column, role); + } + + default: + return ServiceRoot::data(column, role); + } +} + +Qt::ItemFlags StandardServiceRoot::additionalFlags() const { + return Qt::ItemIsDropEnabled; +} + +RecycleBin *StandardServiceRoot::recycleBin() { + return m_recycleBin; +} + +bool StandardServiceRoot::markFeedsReadUnread(QList items, ReadStatus read) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (!db_handle.transaction()) { + qWarning("Starting transaction for feeds read change."); + return false; + } + + QSqlQuery query_read_msg(db_handle); + query_read_msg.setForwardOnly(true); + + if (!query_read_msg.prepare(QString("UPDATE Messages SET is_read = :read " + "WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0;").arg(textualFeedIds(items).join(QSL(", "))))) { + qWarning("Query preparation failed for feeds read change."); + + db_handle.rollback(); + return false; + } + + query_read_msg.bindValue(QSL(":read"), read == RootItem::Read ? 1 : 0); + + if (!query_read_msg.exec()) { + qDebug("Query execution for feeds read change failed."); + db_handle.rollback(); + } + + // Commit changes. + if (db_handle.commit()) { + // Messages are switched, now inform model about need to reload data. + QList itemss; + + foreach (Feed *feed, items) { + feed->updateCounts(false); + itemss.append(feed); + } + + itemChanged(itemss); + requestReloadMessageList(read == RootItem::Read); + return true; + } + else { + return db_handle.rollback(); + } +} + +bool StandardServiceRoot::cleanFeeds(QList items, bool clean_read_only) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_delete_msg(db_handle); + query_delete_msg.setForwardOnly(true); + + if (clean_read_only) { + if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " + "WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0 AND is_read = 1;").arg(textualFeedIds(items).join(QSL(", "))))) { + qWarning("Query preparation failed for feeds clearing."); + return false; + } + } + else { + if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " + "WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0;").arg(textualFeedIds(items).join(QSL(", "))))) { + qWarning("Query preparation failed for feeds clearing."); + return false; + } + } + + query_delete_msg.bindValue(QSL(":deleted"), 1); + + if (!query_delete_msg.exec()) { + qDebug("Query execution for feeds clearing failed."); + return false; + } + else { + // Messages are cleared, now inform model about need to reload data. + QList itemss; + + foreach (Feed *feed, items) { + feed->updateCounts(true); + itemss.append(feed); + } + + m_recycleBin->updateCounts(true); + itemss.append(m_recycleBin); + + itemChanged(itemss); + requestReloadMessageList(true); + return true; + } +} + +void StandardServiceRoot::loadFromDatabase(){ + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + Assignment categories; + Assignment feeds; + + // Obtain data for categories from the database. + QSqlQuery query_categories(database); + query_categories.setForwardOnly(true); + + if (!query_categories.exec(QString("SELECT * FROM Categories WHERE account_id = %1;").arg(accountId())) || query_categories.lastError().isValid()) { + qFatal("Query for obtaining categories failed. Error message: '%s'.", + qPrintable(query_categories.lastError().text())); + } + + while (query_categories.next()) { + AssignmentItem pair; + pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt(); + pair.second = new StandardCategory(query_categories.record()); + + categories << pair; + } + + // All categories are now loaded. + QSqlQuery query_feeds(database); + query_feeds.setForwardOnly(true); + + if (!query_feeds.exec(QString("SELECT * FROM Feeds WHERE account_id = %1;").arg(accountId())) || query_feeds.lastError().isValid()) { + qFatal("Query for obtaining feeds failed. Error message: '%s'.", + qPrintable(query_feeds.lastError().text())); + } + + while (query_feeds.next()) { + // Process this feed. + StandardFeed::Type type = static_cast(query_feeds.value(FDS_DB_TYPE_INDEX).toInt()); + + switch (type) { + case StandardFeed::Atom10: + case StandardFeed::Rdf: + case StandardFeed::Rss0X: + case StandardFeed::Rss2X: { + AssignmentItem pair; + pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt(); + pair.second = new StandardFeed(query_feeds.record()); + qobject_cast(pair.second)->setType(type); + + feeds << pair; + break; + } + + default: + break; + } + } + + // All data are now obtained, lets create the hierarchy. + assembleCategories(categories); + assembleFeeds(feeds); + + // As the last item, add recycle bin, which is needed. + appendChild(m_recycleBin); + m_recycleBin->updateCounts(true); +} + +QList StandardServiceRoot::allCategories() { + QList cats = getSubTreeCategories(); + QList std_cats; + + foreach (Category *category, cats) { + std_cats.append(qobject_cast(category)); + } + + return std_cats; +} + +QList StandardServiceRoot::getContextMenuForFeed(StandardFeed *feed) { + if (m_feedContextMenu.isEmpty()) { + // Initialize. + m_actionFeedFetchMetadata = new QAction(qApp->icons()->fromTheme(QSL("download-manager")), tr("Fetch metadata"), NULL); + m_feedContextMenu.append(m_actionFeedFetchMetadata); + } + + // Make connections. + disconnect(m_actionFeedFetchMetadata, SIGNAL(triggered()), 0, 0); + connect(m_actionFeedFetchMetadata, SIGNAL(triggered()), feed, SLOT(fetchMetadataForItself())); + + return m_feedContextMenu; +} + +bool StandardServiceRoot::mergeImportExportModel(FeedsImportExportModel *model, QString &output_message) { + QStack original_parents; original_parents.push(this); + QStack new_parents; new_parents.push(model->rootItem()); + bool some_feed_category_error = false; + + // Iterate all new items we would like to merge into current model. + while (!new_parents.isEmpty()) { + RootItem *target_parent = original_parents.pop(); + RootItem *source_parent = new_parents.pop(); + + foreach (RootItem *source_item, source_parent->childItems()) { + if (!model->isItemChecked(source_item)) { + // We can skip this item, because it is not checked and should not be imported. + // NOTE: All descendants are thus skipped too. + continue; + } + + if (source_item->kind() == RootItemKind::Category) { + StandardCategory *source_category = static_cast(source_item); + StandardCategory *new_category = new StandardCategory(*source_category); + QString new_category_title = new_category->title(); + + // Add category to model. + new_category->clearChildren(); + + if (new_category->addItself(target_parent)) { + requestItemReassignment(new_category, target_parent); + + // Process all children of this category. + original_parents.push(new_category); + new_parents.push(source_category); + } + else { + delete new_category; + + // Add category failed, but this can mean that the same category (with same title) + // already exists. If such a category exists in current parent, then find it and + // add descendants to it. + RootItem *existing_category = NULL; + foreach (RootItem *child, target_parent->childItems()) { + if (child->kind() == RootItemKind::Category && child->title() == new_category_title) { + existing_category = child; + } + } + + if (existing_category != NULL) { + original_parents.push(existing_category); + new_parents.push(source_category); + } + else { + some_feed_category_error = true; + } + } + } + else if (source_item->kind() == RootItemKind::Feed) { + StandardFeed *source_feed = static_cast(source_item); + StandardFeed *new_feed = new StandardFeed(*source_feed); + + // Append this feed and end this iteration. + if (new_feed->addItself(target_parent)) { + requestItemReassignment(new_feed, target_parent); + } + else { + delete new_feed; + some_feed_category_error = true; + } + } + } + } + + if (some_feed_category_error) { + output_message = tr("Import successfull, but some feeds/categories were not imported due to error."); + } + else { + output_message = tr("Import was completely successfull."); + } + + return !some_feed_category_error; +} + +void StandardServiceRoot::addNewCategory() { + QPointer form_pointer = new FormStandardCategoryDetails(this, qApp->mainForm()); + form_pointer.data()->exec(NULL, NULL); + delete form_pointer.data(); +} + +void StandardServiceRoot::addNewFeed() { + QPointer form_pointer = new FormStandardFeedDetails(this, qApp->mainForm()); + form_pointer.data()->exec(NULL, NULL); + delete form_pointer.data(); +} + +void StandardServiceRoot::importFeeds() { + QPointer form = new FormStandardImportExport(this, qApp->mainForm()); + form.data()->setMode(FeedsImportExportModel::Import); + form.data()->exec(); + delete form.data(); +} + +void StandardServiceRoot::exportFeeds() { + QPointer form = new FormStandardImportExport(this, qApp->mainForm()); + form.data()->setMode(FeedsImportExportModel::Export); + form.data()->exec(); + delete form.data(); +} + +QStringList StandardServiceRoot::textualFeedIds(const QList &feeds) { + QStringList stringy_ids; + stringy_ids.reserve(feeds.size()); + + foreach (Feed *feed, feeds) { + stringy_ids.append(QString("'%1'").arg(QString::number(feed->id()))); + } + + return stringy_ids; +} + +QList StandardServiceRoot::addItemMenu() { + if (m_addItemMenu.isEmpty()) { + QAction *action_new_category = new QAction(qApp->icons()->fromTheme("folder-category"), tr("Add new category"), this); + connect(action_new_category, SIGNAL(triggered()), this, SLOT(addNewCategory())); + + QAction *action_new_feed = new QAction(qApp->icons()->fromTheme("folder-feed"), tr("Add new feed"), this); + connect(action_new_feed, SIGNAL(triggered()), this, SLOT(addNewFeed())); + + m_addItemMenu.append(action_new_category); + m_addItemMenu.append(action_new_feed); + } + + return m_addItemMenu; +} + +QList StandardServiceRoot::serviceMenu() { + if (m_serviceMenu.isEmpty()) { + m_actionExportFeeds = new QAction(qApp->icons()->fromTheme("document-export"), tr("Export feeds"), this); + m_actionImportFeeds = new QAction(qApp->icons()->fromTheme("document-import"), tr("Import feeds"), this); + + connect(m_actionExportFeeds, SIGNAL(triggered()), this, SLOT(exportFeeds())); + connect(m_actionImportFeeds, SIGNAL(triggered()), this, SLOT(importFeeds())); + + m_serviceMenu.append(m_actionExportFeeds); + m_serviceMenu.append(m_actionImportFeeds); + } + + return m_serviceMenu; +} + +QList StandardServiceRoot::contextMenu() { + return serviceMenu(); +} + +bool StandardServiceRoot::loadMessagesForItem(RootItem *item, QSqlTableModel *model) { + if (item->kind() == RootItemKind::Bin) { + model->setFilter(QString("is_deleted = 1 AND is_pdeleted = 0 AND account_id = %1").arg(QString::number(accountId()))); + } + else { + QList children = item->getSubTreeFeeds(); + QString filter_clause = textualFeedIds(children).join(QSL(", ")); + + model->setFilter(QString(QSL("feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = %2")).arg(filter_clause, + QString::number(accountId()))); + qDebug("Loading messages from feeds: %s.", qPrintable(filter_clause)); + } + + return true; +} + +bool StandardServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(messages) + Q_UNUSED(read) + Q_UNUSED(selected_item) + + return true; +} + +bool StandardServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(messages) + Q_UNUSED(read) + + selected_item->updateCounts(false); + + itemChanged(QList() << selected_item); + requestFeedReadFilterReload(); + return true; +} + +bool StandardServiceRoot::onBeforeSwitchMessageImportance(RootItem *selected_item, + const QList > &changes) { + Q_UNUSED(selected_item) + Q_UNUSED(changes) + + return true; +} + +bool StandardServiceRoot::onAfterSwitchMessageImportance(RootItem *selected_item, + const QList > &changes) { + Q_UNUSED(selected_item) + Q_UNUSED(changes) + + return true; +} + +bool StandardServiceRoot::onBeforeMessagesDelete(RootItem *selected_item, const QList &messages) { + Q_UNUSED(selected_item) + Q_UNUSED(messages) + + return true; +} + +bool StandardServiceRoot::onAfterMessagesDelete(RootItem *selected_item, const QList &messages) { + Q_UNUSED(messages) + + // User deleted some messages he selected in message list. + selected_item->updateCounts(true); + + if (selected_item->kind() == RootItemKind::Bin) { + itemChanged(QList() << m_recycleBin); + } + else { + m_recycleBin->updateCounts(true); + itemChanged(QList() << selected_item << m_recycleBin); + } + + + requestFeedReadFilterReload(); + return true; +} + +bool StandardServiceRoot::onBeforeMessagesRestoredFromBin(RootItem *selected_item, const QList &messages) { + Q_UNUSED(selected_item) + Q_UNUSED(messages) + + return true; +} + +bool StandardServiceRoot::onAfterMessagesRestoredFromBin(RootItem *selected_item, const QList &messages) { + Q_UNUSED(selected_item) + Q_UNUSED(messages) + + updateCounts(true); + itemChanged(getSubTree()); + requestFeedReadFilterReload(); + return true; +} diff --git a/src/services/standard/standardserviceroot.h b/src/services/standard/standardserviceroot.h new file mode 100755 index 000000000..95425dc20 --- /dev/null +++ b/src/services/standard/standardserviceroot.h @@ -0,0 +1,123 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef STANDARDSERVICEROOT_H +#define STANDARDSERVICEROOT_H + +#include "services/abstract/serviceroot.h" + +#include +#include + + +class StandardRecycleBin; +class StandardCategory; +class StandardFeed; +class FeedsImportExportModel; +class QMenu; + +class StandardServiceRoot : public ServiceRoot { + Q_OBJECT + + public: + explicit StandardServiceRoot(RootItem *parent = NULL); + virtual ~StandardServiceRoot(); + + // Start/stop root. + void start(); + void stop(); + + QString code(); + + bool canBeEdited(); + bool canBeDeleted(); + bool deleteViaGui(); + + bool markAsReadUnread(ReadStatus status); + + QVariant data(int column, int role) const; + Qt::ItemFlags additionalFlags() const; + + // Access to recycle bin. + RecycleBin *recycleBin(); + + // Return "add feed" and "add category" items. + QList addItemMenu(); + + // Returns menu to be shown in "Services -> service" menu. + QList serviceMenu(); + + // Returns context menu. + QList contextMenu(); + + // Message stuff. + bool loadMessagesForItem(RootItem *item, QSqlTableModel *model); + + bool onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); + bool onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); + + bool onBeforeSwitchMessageImportance(RootItem *selected_item, const QList > &changes); + bool onAfterSwitchMessageImportance(RootItem *selected_item, const QList > &changes); + + bool onBeforeMessagesDelete(RootItem *selected_item, const QList &messages); + bool onAfterMessagesDelete(RootItem *selected_item, const QList &messages); + + bool onBeforeMessagesRestoredFromBin(RootItem *selected_item, const QList &messages); + bool onAfterMessagesRestoredFromBin(RootItem *selected_item, const QList &messages); + + // Returns all categories from this root, each pair + // consists of ID of parent item and pointer to category. + QList allCategories(); + + // Returns context specific menu actions for given feed. + QList getContextMenuForFeed(StandardFeed *feed); + + // Takes structure residing under given root item and adds feeds/categories from + // it to active structure. + // NOTE: This is used for import/export of the model. + bool mergeImportExportModel(FeedsImportExportModel *model, QString &output_message); + + bool markFeedsReadUnread(QList items, ReadStatus read); + bool cleanFeeds(QList items, bool clean_read_only); + + void loadFromDatabase(); + + public slots: + void addNewCategory(); + void addNewFeed(); + void importFeeds(); + void exportFeeds(); + + private: + // Returns converted ids of given feeds + // which are suitable as IN clause for SQL queries. + QStringList textualFeedIds(const QList &feeds); + + StandardRecycleBin *m_recycleBin; + + // Menus. + QAction *m_actionExportFeeds; + QAction *m_actionImportFeeds; + + QList m_serviceMenu; + QList m_addItemMenu; + QList m_feedContextMenu; + + QAction *m_actionFeedFetchMetadata; +}; + +#endif // STANDARDSERVICEROOT_H diff --git a/src/services/tt-rss/definitions.h b/src/services/tt-rss/definitions.h new file mode 100755 index 000000000..ab6e91c26 --- /dev/null +++ b/src/services/tt-rss/definitions.h @@ -0,0 +1,34 @@ +#ifndef DEFINITIONS_H +#define DEFINITIONS_H + +#define MINIMAL_API_LEVEL 10 +#define CONTENT_TYPE "application/json; charset=utf-8" + +/// +/// Errors. +/// +#define NOT_LOGGED_IN "NOT_LOGGED_IN" // Error when user needs to login before making an operation. +#define UNKNOWN_METHOD "UNKNOWN_METHOD" // Given "op" is not recognized. +#define INCORRECT_USAGE "INCORRECT_USAGE" // Given "op" was used with bad parameters. + +// Limitations +#define MAX_MESSAGES 200 + +// General return status codes. +#define API_STATUS_OK 0 +#define API_STATUS_ERR 1 +#define STATUS_OK "OK" + +#define CONTENT_NOT_LOADED -1 + +// Login. +#define API_DISABLED "API_DISABLED" // API is not enabled. +#define LOGIN_ERROR "LOGIN_ERROR" // Incorrect password/username. + +// Logout. +#define LOGOUT_OK "OK" + +// Get feed tree. +#define GFT_TYPE_CATEGORY "category" + +#endif // DEFINITIONS_H diff --git a/src/services/tt-rss/gui/formeditaccount.cpp b/src/services/tt-rss/gui/formeditaccount.cpp new file mode 100755 index 000000000..7d58ae34f --- /dev/null +++ b/src/services/tt-rss/gui/formeditaccount.cpp @@ -0,0 +1,262 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + + +#include "services/tt-rss/gui/formeditaccount.h" + +#include "services/tt-rss/definitions.h" +#include "services/tt-rss/ttrssserviceroot.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" +#include "miscellaneous/iconfactory.h" +#include "network-web/networkfactory.h" + + +FormEditAccount::FormEditAccount(QWidget *parent) + : QDialog(parent), m_ui(new Ui::FormEditAccount), m_editableRoot(NULL) { + m_ui->setupUi(this); + m_btnOk = m_ui->m_buttonBox->button(QDialogButtonBox::Ok); + + setWindowFlags(Qt::MSWindowsFixedSizeDialogHint | Qt::Dialog | Qt::WindowSystemMenuHint); + setWindowIcon(qApp->icons()->fromTheme(QSL("application-ttrss"))); + + m_ui->m_txtHttpUsername->lineEdit()->setPlaceholderText(tr("HTTP authentication username")); + m_ui->m_txtHttpPassword->lineEdit()->setPlaceholderText(tr("HTTP authentication password")); + m_ui->m_txtPassword->lineEdit()->setPlaceholderText(tr("Password for your TT-RSS account")); + m_ui->m_txtUsername->lineEdit()->setPlaceholderText(tr("Username for your TT-RSS account")); + m_ui->m_txtUrl->lineEdit()->setPlaceholderText(tr("FULL URL of your TT-RSS instance WITH trailing \"/api/\" string")); + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Information, + tr("No test done yet."), + tr("Here, results of connection test are shown.")); + + setTabOrder(m_ui->m_txtUrl->lineEdit(), m_ui->m_checkServerSideUpdate); + setTabOrder(m_ui->m_checkServerSideUpdate, m_ui->m_txtUsername->lineEdit()); + setTabOrder(m_ui->m_txtUsername->lineEdit(), m_ui->m_txtPassword->lineEdit()); + setTabOrder(m_ui->m_txtPassword->lineEdit(), m_ui->m_checkShowPassword); + setTabOrder(m_ui->m_checkShowPassword, m_ui->m_gbHttpAuthentication); + setTabOrder(m_ui->m_gbHttpAuthentication, m_ui->m_txtHttpUsername->lineEdit()); + setTabOrder(m_ui->m_txtHttpUsername->lineEdit(), m_ui->m_txtHttpPassword->lineEdit()); + setTabOrder(m_ui->m_txtHttpPassword->lineEdit(), m_ui->m_checkShowHttpPassword); + setTabOrder(m_ui->m_checkShowHttpPassword, m_ui->m_btnTestSetup); + setTabOrder(m_ui->m_btnTestSetup, m_ui->m_buttonBox); + + connect(m_ui->m_checkShowPassword, SIGNAL(toggled(bool)), this, SLOT(displayPassword(bool))); + connect(m_ui->m_buttonBox, SIGNAL(accepted()), this, SLOT(onClickedOk())); + connect(m_ui->m_buttonBox, SIGNAL(rejected()), this, SLOT(onClickedCancel())); + connect(m_ui->m_txtPassword->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onPasswordChanged())); + connect(m_ui->m_txtUsername->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onUsernameChanged())); + connect(m_ui->m_txtHttpPassword->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onHttpPasswordChanged())); + connect(m_ui->m_txtHttpUsername->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onHttpUsernameChanged())); + connect(m_ui->m_txtUrl->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(onUrlChanged())); + connect(m_ui->m_txtPassword->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(checkOkButton())); + connect(m_ui->m_txtUsername->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(checkOkButton())); + connect(m_ui->m_txtUrl->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(checkOkButton())); + connect(m_ui->m_btnTestSetup, SIGNAL(clicked()), this, SLOT(performTest())); + connect(m_ui->m_gbHttpAuthentication, SIGNAL(toggled(bool)), this, SLOT(onHttpPasswordChanged())); + connect(m_ui->m_gbHttpAuthentication, SIGNAL(toggled(bool)), this, SLOT(onHttpUsernameChanged())); + connect(m_ui->m_checkShowHttpPassword, SIGNAL(toggled(bool)), this, SLOT(displayHttpPassword(bool))); + + onPasswordChanged(); + onUsernameChanged(); + onUrlChanged(); + onHttpPasswordChanged(); + onHttpUsernameChanged(); + checkOkButton(); + displayPassword(false); + displayHttpPassword(false); +} + +FormEditAccount::~FormEditAccount() { + delete m_ui; +} + +TtRssServiceRoot *FormEditAccount::execForCreate() { + setWindowTitle(tr("Add new Tiny Tiny RSS account")); + exec(); + return m_editableRoot; +} + +void FormEditAccount::execForEdit(TtRssServiceRoot *existing_root) { + setWindowTitle(tr("Edit existing Tiny Tiny RSS account")); + m_editableRoot = existing_root; + + m_ui->m_gbHttpAuthentication->setChecked(existing_root->network()->authIsUsed()); + m_ui->m_txtHttpPassword->lineEdit()->setText(existing_root->network()->authPassword()); + m_ui->m_txtHttpUsername->lineEdit()->setText(existing_root->network()->authUsername()); + m_ui->m_txtUsername->lineEdit()->setText(existing_root->network()->username()); + m_ui->m_txtPassword->lineEdit()->setText(existing_root->network()->password()); + m_ui->m_txtUrl->lineEdit()->setText(existing_root->network()->url()); + m_ui->m_checkServerSideUpdate->setChecked(existing_root->network()->forceServerSideUpdate()); + + exec(); +} + +void FormEditAccount::displayPassword(bool display) { + m_ui->m_txtPassword->lineEdit()->setEchoMode(display ? QLineEdit::Normal : QLineEdit::Password); +} + +void FormEditAccount::displayHttpPassword(bool display) { + m_ui->m_txtHttpPassword->lineEdit()->setEchoMode(display ? QLineEdit::Normal : QLineEdit::Password); +} + +void FormEditAccount::performTest() { + TtRssNetworkFactory factory; + + factory.setUsername(m_ui->m_txtUsername->lineEdit()->text()); + factory.setPassword(m_ui->m_txtPassword->lineEdit()->text()); + factory.setUrl(m_ui->m_txtUrl->lineEdit()->text()); + factory.setAuthIsUsed(m_ui->m_gbHttpAuthentication->isChecked()); + factory.setAuthUsername(m_ui->m_txtHttpUsername->lineEdit()->text()); + factory.setAuthPassword(m_ui->m_txtHttpPassword->lineEdit()->text()); + factory.setForceServerSideUpdate(m_ui->m_checkServerSideUpdate->isChecked()); + + TtRssLoginResponse result = factory.login(); + + if (factory.lastError() == QNetworkReply::NoError) { + if (result.hasError()) { + QString error = result.error(); + + if (error == API_DISABLED) { + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Error, + tr("API access on selected server is not enabled."), + tr("API access on selected server is not enabled.")); + } + else if (error == LOGIN_ERROR) { + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Error, + tr("Entered credentials are incorrect."), + tr("Entered credentials are incorrect.")); + } + else { + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Error, + tr("Other error occurred, contact developers."), + tr("Other error occurred, contact developers.")); + } + } + else if (result.apiLevel() < MINIMAL_API_LEVEL) { + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Error, + tr("Selected Tiny Tiny RSS server is running unsupported version of API (%1). At least API level %2 is required.").arg(QString::number(result.apiLevel()), + QString::number(MINIMAL_API_LEVEL)), + tr("Selected Tiny Tiny RSS server is running unsupported version of API.")); + } + else { + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Ok, + tr("Tiny Tiny RSS server is okay, running with API level %1, while at least API level %2 is required.").arg(QString::number(result.apiLevel()), + QString::number(MINIMAL_API_LEVEL)), + tr("Tiny Tiny RSS server is okay.")); + } + } + else { + m_ui->m_lblTestResult->setStatus(WidgetWithStatus::Error, + tr("Network error: '%1'.").arg(NetworkFactory::networkErrorText(factory.lastError())), + tr("Network error, have you entered correct Tiny Tiny RSS API endpoint and password?")); + } +} + +void FormEditAccount::onClickedOk() { + bool editing_account = true; + + if (m_editableRoot == NULL) { + // We want to confirm newly created account. + // So save new account into DB, setup its properties. + m_editableRoot = new TtRssServiceRoot(); + editing_account = false; + } + + m_editableRoot->network()->setUrl(m_ui->m_txtUrl->lineEdit()->text()); + m_editableRoot->network()->setUsername(m_ui->m_txtUsername->lineEdit()->text()); + m_editableRoot->network()->setPassword(m_ui->m_txtPassword->lineEdit()->text()); + m_editableRoot->network()->setAuthIsUsed(m_ui->m_gbHttpAuthentication->isChecked()); + m_editableRoot->network()->setAuthUsername(m_ui->m_txtHttpUsername->lineEdit()->text()); + m_editableRoot->network()->setAuthPassword(m_ui->m_txtHttpPassword->lineEdit()->text()); + m_editableRoot->network()->setForceServerSideUpdate(m_ui->m_checkServerSideUpdate->isChecked()); + m_editableRoot->saveAccountDataToDatabase(); + + accept(); + + if (editing_account) { + m_editableRoot->network()->logout(); + m_editableRoot->completelyRemoveAllData(); + m_editableRoot->syncIn(); + } +} + +void FormEditAccount::onClickedCancel() { + reject(); +} + +void FormEditAccount::onUsernameChanged() { + QString username = m_ui->m_txtUsername->lineEdit()->text(); + + if (username.isEmpty()) { + m_ui->m_txtUsername->setStatus(WidgetWithStatus::Error, tr("Username cannot be empty.")); + } + else { + m_ui->m_txtUsername->setStatus(WidgetWithStatus::Ok, tr("Username is okay.")); + } +} + +void FormEditAccount::onPasswordChanged() { + QString password = m_ui->m_txtPassword->lineEdit()->text(); + + if (password.isEmpty()) { + m_ui->m_txtPassword->setStatus(WidgetWithStatus::Error, tr("Password cannot be empty.")); + } + else { + m_ui->m_txtPassword->setStatus(WidgetWithStatus::Ok, tr("Password is okay.")); + } +} + +void FormEditAccount::onHttpUsernameChanged() { + bool is_username_ok = !m_ui->m_gbHttpAuthentication->isChecked() || !m_ui->m_txtHttpUsername->lineEdit()->text().isEmpty(); + + m_ui->m_txtHttpUsername->setStatus(is_username_ok ? + LineEditWithStatus::Ok : + LineEditWithStatus::Warning, + is_username_ok ? + tr("Username is ok or it is not needed.") : + tr("Username is empty.")); +} + +void FormEditAccount::onHttpPasswordChanged() { + bool is_username_ok = !m_ui->m_gbHttpAuthentication->isChecked() || !m_ui->m_txtHttpPassword->lineEdit()->text().isEmpty(); + + m_ui->m_txtHttpPassword->setStatus(is_username_ok ? + LineEditWithStatus::Ok : + LineEditWithStatus::Warning, + is_username_ok ? + tr("Password is ok or it is not needed.") : + tr("Password is empty.")); +} + +void FormEditAccount::onUrlChanged() { + QString url = m_ui->m_txtUrl->lineEdit()->text(); + + if (url.isEmpty()) { + m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL cannot be empty.")); + } + else if (!url.endsWith(QL1S("api/"))) { + m_ui->m_txtUrl->setStatus(WidgetWithStatus::Error, tr("URL must end with \"api/\".")); + } + else { + m_ui->m_txtUrl->setStatus(WidgetWithStatus::Ok, tr("URL is okay.")); + } +} + +void FormEditAccount::checkOkButton() { + m_btnOk->setEnabled(!m_ui->m_txtUsername->lineEdit()->text().isEmpty() && + !m_ui->m_txtPassword->lineEdit()->text().isEmpty() && + !m_ui->m_txtUrl->lineEdit()->text().isEmpty()); +} diff --git a/src/services/tt-rss/gui/formeditaccount.h b/src/services/tt-rss/gui/formeditaccount.h new file mode 100755 index 000000000..4b9c57381 --- /dev/null +++ b/src/services/tt-rss/gui/formeditaccount.h @@ -0,0 +1,63 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + + +#ifndef FORMEDITACCOUNT_H +#define FORMEDITACCOUNT_H + +#include + +#include "ui_formeditaccount.h" + + +namespace Ui { + class FormEditAccount; +} + +class TtRssServiceRoot; + +class FormEditAccount : public QDialog { + Q_OBJECT + + public: + explicit FormEditAccount(QWidget *parent = 0); + virtual ~FormEditAccount(); + + TtRssServiceRoot *execForCreate(); + void execForEdit(TtRssServiceRoot *existing_root); + + private slots: + void displayPassword(bool display); + void displayHttpPassword(bool display); + void performTest(); + void onClickedOk(); + void onClickedCancel(); + + void onUsernameChanged(); + void onPasswordChanged(); + void onHttpUsernameChanged(); + void onHttpPasswordChanged(); + void onUrlChanged(); + void checkOkButton(); + + private: + Ui::FormEditAccount *m_ui; + TtRssServiceRoot *m_editableRoot; + QPushButton *m_btnOk; +}; + +#endif // FORMEDITACCOUNT_H diff --git a/src/services/tt-rss/gui/formeditaccount.ui b/src/services/tt-rss/gui/formeditaccount.ui new file mode 100755 index 000000000..670b544db --- /dev/null +++ b/src/services/tt-rss/gui/formeditaccount.ui @@ -0,0 +1,197 @@ + + + FormEditAccount + + + + 0 + 0 + 465 + 304 + + + + Dialog + + + + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + + + Authentication + + + false + + + false + + + + + + Username + + + m_txtUsername + + + + + + + Password + + + m_txtPassword + + + + + + + + + + + + + Show password + + + + + + + + + + Some feeds require authentication, including GMail feeds. BASIC, NTLM-2 and DIGEST-MD5 authentication schemes are supported. + + + Requires HTTP authentication + + + false + + + true + + + false + + + + + + Username + + + m_txtUsername + + + + + + + + + + Password + + + m_txtPassword + + + + + + + + + + Show password + + + + + + + + + + + 0 + 0 + + + + Qt::RightToLeft + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + URL + + + m_txtUrl + + + + + + + + + + + + &Test setup + + + + + + + Force execution of server-side update when updating feeds from RSS Guard + + + true + + + + + + + + LineEditWithStatus + QWidget +
    lineeditwithstatus.h
    + 1 +
    + + LabelWithStatus + QWidget +
    labelwithstatus.h
    + 1 +
    +
    + + +
    diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.cpp b/src/services/tt-rss/network/ttrssnetworkfactory.cpp new file mode 100755 index 000000000..d14b0957b --- /dev/null +++ b/src/services/tt-rss/network/ttrssnetworkfactory.cpp @@ -0,0 +1,474 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/tt-rss/network/ttrssnetworkfactory.h" + +#include "definitions/definitions.h" +#include "services/abstract/rootitem.h" +#include "services/tt-rss/definitions.h" +#include "services/tt-rss/ttrssfeed.h" +#include "services/tt-rss/ttrsscategory.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/textfactory.h" +#include "network-web/networkfactory.h" + +#include + + +TtRssNetworkFactory::TtRssNetworkFactory() + : m_url(QString()), m_username(QString()), m_password(QString()), m_forceServerSideUpdate(false), m_authIsUsed(false), + m_authUsername(QString()), m_authPassword(QString()), m_sessionId(QString()), + m_lastLoginTime(QDateTime()), m_lastError(QNetworkReply::NoError) { +} + +TtRssNetworkFactory::~TtRssNetworkFactory() { +} + +QString TtRssNetworkFactory::url() const { + return m_url; +} + +void TtRssNetworkFactory::setUrl(const QString &url) { + m_url = url; +} + +QString TtRssNetworkFactory::username() const { + return m_username; +} + +void TtRssNetworkFactory::setUsername(const QString &username) { + m_username = username; +} + +QString TtRssNetworkFactory::password() const { + return m_password; +} + +void TtRssNetworkFactory::setPassword(const QString &password) { + m_password = password; +} + +QDateTime TtRssNetworkFactory::lastLoginTime() const { + return m_lastLoginTime; +} + +QNetworkReply::NetworkError TtRssNetworkFactory::lastError() const { + return m_lastError; +} + +TtRssLoginResponse TtRssNetworkFactory::login() { + if (!m_sessionId.isEmpty()) { + logout(); + } + + QtJson::JsonObject json; + json["op"] = "login"; + json["user"] = m_username; + json["password"] = m_password; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + TtRssLoginResponse login_response(QString::fromUtf8(result_raw)); + + if (network_reply.first == QNetworkReply::NoError) { + m_sessionId = login_response.sessionId(); + m_lastLoginTime = QDateTime::currentDateTime(); + } + + m_lastError = network_reply.first; + return login_response; +} + +TtRssResponse TtRssNetworkFactory::logout() { + if (!m_sessionId.isEmpty()) { + + QtJson::JsonObject json; + json["op"] = "logout"; + json["sid"] = m_sessionId; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + + m_lastError = network_reply.first; + + if (m_lastError == QNetworkReply::NoError) { + m_sessionId.clear(); + } + + return TtRssResponse(QString::fromUtf8(result_raw)); + } + else { + m_lastError = QNetworkReply::NoError; + return TtRssResponse(); + } +} + +TtRssGetFeedsCategoriesResponse TtRssNetworkFactory::getFeedsCategories() { + QtJson::JsonObject json; + json["op"] = "getFeedTree"; + json["sid"] = m_sessionId; + json["include_empty"] = false; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + TtRssGetFeedsCategoriesResponse result(QString::fromUtf8(result_raw)); + + if (result.isNotLoggedIn()) { + // We are not logged in. + login(); + json["sid"] = m_sessionId; + + network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + result = TtRssGetFeedsCategoriesResponse(QString::fromUtf8(result_raw)); + } + + m_lastError = network_reply.first; + return result; +} + +TtRssGetHeadlinesResponse TtRssNetworkFactory::getHeadlines(int feed_id, int limit, int skip, + bool show_content, bool include_attachments, + bool sanitize) { + QtJson::JsonObject json; + json["op"] = "getHeadlines"; + json["sid"] = m_sessionId; + json["feed_id"] = feed_id; + json["force_update"] = m_forceServerSideUpdate; + json["limit"] = limit; + json["skip"] = skip; + json["show_content"] = show_content; + json["include_attachments"] = include_attachments; + json["sanitize"] = sanitize; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + TtRssGetHeadlinesResponse result(QString::fromUtf8(result_raw)); + + if (result.isNotLoggedIn()) { + // We are not logged in. + login(); + json["sid"] = m_sessionId; + + network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + result = TtRssGetHeadlinesResponse(QString::fromUtf8(result_raw)); + } + + m_lastError = network_reply.first; + return result; +} + +TtRssUpdateArticleResponse TtRssNetworkFactory::updateArticles(const QStringList &ids, + UpdateArticle::OperatingField field, + UpdateArticle::Mode mode) { + QtJson::JsonObject json; + json["op"] = "updateArticle"; + json["sid"] = m_sessionId; + json["article_ids"] = ids.join(QSL(",")); + json["mode"] = (int) mode; + json["field"] = (int) field; + + QByteArray result_raw; + NetworkResult network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + TtRssUpdateArticleResponse result(QString::fromUtf8(result_raw)); + + if (result.isNotLoggedIn()) { + // We are not logged in. + login(); + json["sid"] = m_sessionId; + + network_reply = NetworkFactory::uploadData(m_url, DOWNLOAD_TIMEOUT, QtJson::serialize(json), CONTENT_TYPE, result_raw, + m_authIsUsed, m_authUsername, m_authPassword); + result = TtRssUpdateArticleResponse(QString::fromUtf8(result_raw)); + } + + m_lastError = network_reply.first; + return result; +} + +bool TtRssNetworkFactory::forceServerSideUpdate() const { + return m_forceServerSideUpdate; +} + +void TtRssNetworkFactory::setForceServerSideUpdate(bool force_server_side_update) { + m_forceServerSideUpdate = force_server_side_update; +} + +bool TtRssNetworkFactory::authIsUsed() const { + return m_authIsUsed; +} + +void TtRssNetworkFactory::setAuthIsUsed(bool auth_is_used) { + m_authIsUsed = auth_is_used; +} + +QString TtRssNetworkFactory::authUsername() const { + return m_authUsername; +} + +void TtRssNetworkFactory::setAuthUsername(const QString &auth_username) { + m_authUsername = auth_username; +} + +QString TtRssNetworkFactory::authPassword() const { + return m_authPassword; +} + +void TtRssNetworkFactory::setAuthPassword(const QString &auth_password) { + m_authPassword = auth_password; +} + +TtRssResponse::TtRssResponse(const QString &raw_content) { + m_rawContent = QtJson::parse(raw_content).toMap(); +} + +TtRssResponse::~TtRssResponse() { +} + +bool TtRssResponse::isLoaded() const { + return !m_rawContent.empty(); +} + +int TtRssResponse::seq() const { + if (!isLoaded()) { + return CONTENT_NOT_LOADED; + } + else { + return m_rawContent["seq"].toInt(); + } +} + +int TtRssResponse::status() const { + if (!isLoaded()) { + return CONTENT_NOT_LOADED; + } + else { + return m_rawContent["status"].toInt(); + } +} + +bool TtRssResponse::isNotLoggedIn() const { + return status() == API_STATUS_ERR && hasError() && error() == NOT_LOGGED_IN; +} + + +TtRssLoginResponse::TtRssLoginResponse(const QString &raw_content) : TtRssResponse(raw_content) { +} + +TtRssLoginResponse::~TtRssLoginResponse() { +} + +int TtRssLoginResponse::apiLevel() const { + if (!isLoaded()) { + return CONTENT_NOT_LOADED; + } + else { + return m_rawContent["content"].toMap()["api_level"].toInt(); + } +} + +QString TtRssLoginResponse::sessionId() const { + if (!isLoaded()) { + return QString(); + } + else { + return m_rawContent["content"].toMap()["session_id"].toString(); + } +} + +QString TtRssResponse::error() const { + if (!isLoaded()) { + return QString(); + } + else { + return m_rawContent["content"].toMap()["error"].toString(); + } +} + +bool TtRssResponse::hasError() const { + if (!isLoaded()) { + return false; + } + else { + return m_rawContent["content"].toMap().contains("error"); + } +} + + +TtRssGetFeedsCategoriesResponse::TtRssGetFeedsCategoriesResponse(const QString &raw_content) : TtRssResponse(raw_content) { + +} + +TtRssGetFeedsCategoriesResponse::~TtRssGetFeedsCategoriesResponse() { +} + +RootItem *TtRssGetFeedsCategoriesResponse::feedsCategories(bool obtain_icons, QString base_address) const { + RootItem *parent = new RootItem(); + + // Chop the "api/" from the end of the address. + base_address.chop(4); + + if (status() == API_STATUS_OK) { + // We have data, construct object tree according to data. + QList items_to_process = m_rawContent["content"].toMap()["categories"].toMap()["items"].toList(); + QList > pairs; + + foreach (QVariant item, items_to_process) { + pairs.append(QPair(parent, item)); + } + + while (!pairs.isEmpty()) { + QPair pair = pairs.takeFirst(); + RootItem *act_parent = pair.first; + QMap item = pair.second.toMap(); + + int item_id = item["bare_id"].toInt(); + bool is_category = item.contains("type") && item["type"].toString() == GFT_TYPE_CATEGORY; + + if (item_id >= 0) { + if (is_category) { + if (item_id == 0) { + // This is "Uncategorized" category, all its feeds belong to top-level root. + if (item.contains("items")) { + foreach (QVariant child_feed, item["items"].toList()) { + pairs.append(QPair(parent, child_feed)); + } + } + } + else { + TtRssCategory *category = new TtRssCategory(); + + category->setTitle(item["name"].toString()); + category->setCustomId(item_id); + act_parent->appendChild(category); + + if (item.contains("items")) { + foreach (QVariant child, item["items"].toList()) { + pairs.append(QPair(category, child)); + } + } + } + } + else { + // We have feed. + TtRssFeed *feed = new TtRssFeed(); + + if (obtain_icons) { + QString icon_path = item["icon"].type() == QVariant::String ? item["icon"].toString() : QString(); + + if (!icon_path.isEmpty()) { + // Chop the "api/" suffix out and append + QString full_icon_address = base_address + QL1C('/') + icon_path; + QByteArray icon_data; + + if (NetworkFactory::downloadFile(full_icon_address, DOWNLOAD_TIMEOUT, icon_data).first == QNetworkReply::NoError) { + // Icon downloaded, set it up. + QPixmap icon_pixmap; + icon_pixmap.loadFromData(icon_data); + feed->setIcon(QIcon(icon_pixmap)); + } + } + } + + feed->setTitle(item["name"].toString()); + feed->setCustomId(item_id); + act_parent->appendChild(feed); + } + } + } + } + + return parent; +} + + +TtRssGetHeadlinesResponse::TtRssGetHeadlinesResponse(const QString &raw_content) : TtRssResponse(raw_content) { +} + +TtRssGetHeadlinesResponse::~TtRssGetHeadlinesResponse() { +} + +QList TtRssGetHeadlinesResponse::messages() const { + QList messages; + + foreach (QVariant item, m_rawContent["content"].toList()) { + QMap mapped = item.toMap(); + Message message; + + message.m_author = mapped["author"].toString(); + message.m_isRead = !mapped["unread"].toBool(); + message.m_isImportant = mapped["marked"].toBool(); + message.m_contents = mapped["content"].toString(); + + // Multiply by 1000 because Tiny Tiny RSS API does not include miliseconds in Unix + // date/time number. + message.m_created = TextFactory::parseDateTime(mapped["updated"].value() * 1000); + message.m_createdFromFeed = true; + message.m_customId = mapped["id"].toString(); + message.m_feedId = mapped["feed_id"].toString(); + message.m_title = mapped["title"].toString(); + message.m_url = mapped["link"].toString(); + + if (mapped.contains(QSL("attachments"))) { + // Process enclosures. + foreach (QVariant attachment, mapped["attachments"].toList()) { + QMap mapped_attachemnt = attachment.toMap(); + Enclosure enclosure; + + enclosure.m_mimeType = mapped_attachemnt["content_type"].toString(); + enclosure.m_url = mapped_attachemnt["content_url"].toString(); + message.m_enclosures.append(enclosure); + } + } + + messages.append(message); + } + + return messages; +} + + +TtRssUpdateArticleResponse::TtRssUpdateArticleResponse(const QString &raw_content) : TtRssResponse(raw_content) { +} + +TtRssUpdateArticleResponse::~TtRssUpdateArticleResponse() { +} + +QString TtRssUpdateArticleResponse::updateStatus() const { + if (m_rawContent.contains(QSL("content"))) { + return m_rawContent["content"].toMap()["status"].toString(); + } + else { + return QString(); + } +} + +int TtRssUpdateArticleResponse::articlesUpdated() const { + if (m_rawContent.contains(QSL("content"))) { + return m_rawContent["content"].toMap()["updated"].toInt(); + } + else { + return 0; + } +} diff --git a/src/services/tt-rss/network/ttrssnetworkfactory.h b/src/services/tt-rss/network/ttrssnetworkfactory.h new file mode 100755 index 000000000..3d1cd2281 --- /dev/null +++ b/src/services/tt-rss/network/ttrssnetworkfactory.h @@ -0,0 +1,164 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef TTRSSNETWORKFACTORY_H +#define TTRSSNETWORKFACTORY_H + +#include "qt-json/json.h" + +#include "core/message.h" + +#include +#include +#include + + +class RootItem; + +class TtRssResponse { + public: + explicit TtRssResponse(const QString &raw_content = QString()); + virtual ~TtRssResponse(); + + bool isLoaded() const; + + int seq() const; + int status() const; + QString error() const; + bool hasError() const; + bool isNotLoggedIn() const; + + protected: + QtJson::JsonObject m_rawContent; +}; + +class TtRssLoginResponse : public TtRssResponse { + public: + explicit TtRssLoginResponse(const QString &raw_content = QString()); + virtual ~TtRssLoginResponse(); + + int apiLevel() const; + QString sessionId() const; +}; + +class TtRssGetFeedsCategoriesResponse : public TtRssResponse { + public: + explicit TtRssGetFeedsCategoriesResponse(const QString &raw_content = QString()); + virtual ~TtRssGetFeedsCategoriesResponse(); + + // Returns tree of feeds/categories. + // Top-level root of the tree is not needed here. + // Returned items do not have primary IDs assigned. + RootItem *feedsCategories(bool obtain_icons, QString base_address = QString()) const; +}; + +class TtRssGetHeadlinesResponse : public TtRssResponse { + public: + explicit TtRssGetHeadlinesResponse(const QString &raw_content = QString()); + virtual ~TtRssGetHeadlinesResponse(); + + QList messages() const; +}; + +class TtRssUpdateArticleResponse : public TtRssResponse { + public: + explicit TtRssUpdateArticleResponse(const QString &raw_content = QString()); + virtual ~TtRssUpdateArticleResponse(); + + QString updateStatus() const; + int articlesUpdated() const; +}; + +namespace UpdateArticle { + enum Mode { + SetToFalse = 0, + SetToTrue = 1, + Togggle = 2 + }; + + enum OperatingField { + Starred = 0, + Published = 1, + Unread = 2 + }; +} + +class TtRssNetworkFactory { + public: + explicit TtRssNetworkFactory(); + virtual ~TtRssNetworkFactory(); + + QString url() const; + void setUrl(const QString &url); + + QString username() const; + void setUsername(const QString &username); + + QString password() const; + void setPassword(const QString &password); + + bool authIsUsed() const; + void setAuthIsUsed(bool auth_is_used); + + QString authUsername() const; + void setAuthUsername(const QString &auth_username); + + QString authPassword() const; + void setAuthPassword(const QString &auth_password); + + bool forceServerSideUpdate() const; + void setForceServerSideUpdate(bool force_server_side_update); + + + // Metadata. + QDateTime lastLoginTime() const; + + QNetworkReply::NetworkError lastError() const; + + // Operations. + + // Logs user in. + TtRssLoginResponse login(); + + // Logs user out. + TtRssResponse logout(); + + // Gets feeds from the server. + TtRssGetFeedsCategoriesResponse getFeedsCategories(); + + // Gets headlines (messages) from the server. + TtRssGetHeadlinesResponse getHeadlines(int feed_id, int limit, int skip, + bool show_content, bool include_attachments, + bool sanitize); + + TtRssUpdateArticleResponse updateArticles(const QStringList &ids, UpdateArticle::OperatingField field, + UpdateArticle::Mode mode); + + private: + QString m_url; + QString m_username; + QString m_password; + bool m_forceServerSideUpdate; + bool m_authIsUsed; + QString m_authUsername; + QString m_authPassword; + QString m_sessionId; + QDateTime m_lastLoginTime; + QNetworkReply::NetworkError m_lastError; +}; + +#endif // TTRSSNETWORKFACTORY_H diff --git a/src/services/tt-rss/ttrsscategory.cpp b/src/services/tt-rss/ttrsscategory.cpp new file mode 100755 index 000000000..506181f55 --- /dev/null +++ b/src/services/tt-rss/ttrsscategory.cpp @@ -0,0 +1,73 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/tt-rss/ttrsscategory.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "services/tt-rss/definitions.h" +#include "services/tt-rss/ttrssserviceroot.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" + +#include + + +TtRssCategory::TtRssCategory(RootItem *parent) : Category(parent), m_customId(NO_PARENT_CATEGORY) { + setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); +} + +TtRssCategory::TtRssCategory(const QSqlRecord &record) : Category(NULL) { + setIcon(qApp->icons()->fromTheme(QSL("folder-category"))); + setId(record.value(CAT_DB_ID_INDEX).toInt()); + setTitle(record.value(CAT_DB_TITLE_INDEX).toString()); + setCustomId(record.value(CAT_DB_CUSTOM_ID_INDEX).toInt()); +} + +TtRssCategory::~TtRssCategory() { +} + +TtRssServiceRoot *TtRssCategory::serviceRoot() { + return qobject_cast(getParentServiceRoot()); +} + +bool TtRssCategory::markAsReadUnread(RootItem::ReadStatus status) { + QStringList ids = serviceRoot()->customIDSOfMessagesForItem(this); + TtRssUpdateArticleResponse response = serviceRoot()->network()->updateArticles(ids, UpdateArticle::Unread, + status == RootItem::Unread ? + UpdateArticle::SetToTrue : + UpdateArticle::SetToFalse); + + if (serviceRoot()->network()->lastError() != QNetworkReply::NoError || response.updateStatus() != STATUS_OK) { + return false; + } + else { + return serviceRoot()->markFeedsReadUnread(getSubTreeFeeds(), status); + } +} + +bool TtRssCategory::cleanMessages(bool clear_only_read) { + return serviceRoot()->cleanFeeds(getSubTreeFeeds(), clear_only_read); +} + +int TtRssCategory::customId() const { + return m_customId; +} + +void TtRssCategory::setCustomId(int custom_id) { + m_customId = custom_id; +} diff --git a/src/core/feedsselection.h b/src/services/tt-rss/ttrsscategory.h old mode 100644 new mode 100755 similarity index 57% rename from src/core/feedsselection.h rename to src/services/tt-rss/ttrsscategory.h index 2eb1d27cb..1fcd9ccf5 --- a/src/core/feedsselection.h +++ b/src/services/tt-rss/ttrsscategory.h @@ -15,36 +15,34 @@ // You should have received a copy of the GNU General Public License // along with RSS Guard. If not, see . -#ifndef FEEDSSELECTION_H -#define FEEDSSELECTION_H +#ifndef TTRSSCATEGORY_H +#define TTRSSCATEGORY_H -#include -#include +#include "services/abstract/category.h" + +#include -class RootItem; -class Feed; +class TtRssServiceRoot; + +class TtRssCategory : public Category { + Q_OBJECT -class FeedsSelection { public: - enum SelectionMode { - NoMode, - MessagesFromFeeds, - MessagesFromRecycleBin - }; + explicit TtRssCategory(RootItem *parent = NULL); + explicit TtRssCategory(const QSqlRecord &record); + virtual ~TtRssCategory(); - explicit FeedsSelection(RootItem *root_of_selection = NULL); - FeedsSelection(const FeedsSelection &other); - virtual ~FeedsSelection(); + TtRssServiceRoot *serviceRoot(); - SelectionMode mode(); - RootItem *selectedItem() const; - QString generateListOfIds(); + bool markAsReadUnread(ReadStatus status); + bool cleanMessages(bool clear_only_read); + + int customId() const; + void setCustomId(int custom_id); private: - RootItem *m_selectedItem; + int m_customId; }; -Q_DECLARE_METATYPE(FeedsSelection::SelectionMode) - -#endif // FEEDSSELECTION_H +#endif // TTRSSCATEGORY_H diff --git a/src/services/tt-rss/ttrssfeed.cpp b/src/services/tt-rss/ttrssfeed.cpp new file mode 100755 index 000000000..2796008a1 --- /dev/null +++ b/src/services/tt-rss/ttrssfeed.cpp @@ -0,0 +1,299 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/tt-rss/ttrssfeed.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/databasefactory.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/textfactory.h" +#include "services/tt-rss/definitions.h" +#include "services/tt-rss/ttrssserviceroot.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" + +#include +#include + + +TtRssFeed::TtRssFeed(RootItem *parent) + : Feed(parent), m_customId(NO_PARENT_CATEGORY), m_totalCount(0), m_unreadCount(0) { +} + +TtRssFeed::TtRssFeed(const QSqlRecord &record) : Feed(NULL), m_totalCount(0), m_unreadCount(0) { + setTitle(record.value(FDS_DB_TITLE_INDEX).toString()); + setId(record.value(FDS_DB_ID_INDEX).toInt()); + setIcon(qApp->icons()->fromByteArray(record.value(FDS_DB_ICON_INDEX).toByteArray())); + setAutoUpdateType(static_cast(record.value(FDS_DB_UPDATE_TYPE_INDEX).toInt())); + setAutoUpdateInitialInterval(record.value(FDS_DB_UPDATE_INTERVAL_INDEX).toInt()); + setCustomId(record.value(FDS_DB_CUSTOM_ID_INDEX).toInt()); +} + +TtRssFeed::~TtRssFeed() { +} + +TtRssServiceRoot *TtRssFeed::serviceRoot() { + return qobject_cast(getParentServiceRoot()); +} + +void TtRssFeed::updateCounts(bool including_total_count) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_all(database); + + query_all.setForwardOnly(true); + + if (including_total_count) { + if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = '%1' AND is_deleted = 0 AND account_id = %2;").arg(QString::number(customId()), + QString::number(serviceRoot()->accountId()))) && query_all.next()) { + m_totalCount = query_all.value(0).toInt(); + } + } + + // Obtain count of unread messages. + if (query_all.exec(QString("SELECT count(*) FROM Messages WHERE feed = '%1' AND is_deleted = 0 AND is_read = 0 AND account_id = %2;").arg(QString::number(customId()), + QString::number(serviceRoot()->accountId()))) && query_all.next()) { + int new_unread_count = query_all.value(0).toInt(); + + if (status() == NewMessages && new_unread_count < m_unreadCount) { + setStatus(Normal); + } + + m_unreadCount = new_unread_count; + } +} + +int TtRssFeed::countOfAllMessages() const { + return m_totalCount; +} + +int TtRssFeed::countOfUnreadMessages() const { + return m_unreadCount; +} + +int TtRssFeed::update() { + QList messages; + int newly_added_messages = 0; + int limit = MAX_MESSAGES; + int skip = 0; + + do { + TtRssGetHeadlinesResponse headlines = serviceRoot()->network()->getHeadlines(customId(), limit, skip, + true, true, false); + + if (serviceRoot()->network()->lastError() != QNetworkReply::NoError) { + setStatus(Feed::Error); + serviceRoot()->itemChanged(QList() << this); + return 0; + } + else { + QList new_messages = headlines.messages(); + + messages.append(new_messages); + newly_added_messages = new_messages.size(); + skip += newly_added_messages; + } + } + while (newly_added_messages > 0); + + return updateMessages(messages); +} + +QList TtRssFeed::undeletedMessages() const { + QList messages; + int account_id = const_cast(this)->serviceRoot()->accountId(); + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_read_msg(database); + + query_read_msg.setForwardOnly(true); + query_read_msg.prepare("SELECT * " + "FROM Messages " + "WHERE is_deleted = 0 AND is_pdeleted = 0 AND feed = :feed AND account_id = :account_id;"); + + query_read_msg.bindValue(QSL(":feed"), customId()); + query_read_msg.bindValue(QSL(":account_id"), account_id); + + // FIXME: Fix those const functions, this is fucking ugly. + + if (query_read_msg.exec()) { + while (query_read_msg.next()) { + bool decoded; + Message message = Message::fromSqlRecord(query_read_msg.record(), &decoded); + + if (decoded) { + messages.append(message); + } + + messages.append(message); + } + } + + return messages; +} + +bool TtRssFeed::markAsReadUnread(RootItem::ReadStatus status) { + QStringList ids = serviceRoot()->customIDSOfMessagesForItem(this); + TtRssUpdateArticleResponse response = serviceRoot()->network()->updateArticles(ids, UpdateArticle::Unread, + status == RootItem::Unread ? + UpdateArticle::SetToTrue : + UpdateArticle::SetToFalse); + + if (serviceRoot()->network()->lastError() != QNetworkReply::NoError || response.updateStatus() != STATUS_OK) { + return false; + } + else { + return serviceRoot()->markFeedsReadUnread(QList() << this, status); + } +} + +bool TtRssFeed::cleanMessages(bool clear_only_read) { + return serviceRoot()->cleanFeeds(QList() << this, clear_only_read); +} + +int TtRssFeed::customId() const { + return m_customId; +} + +void TtRssFeed::setCustomId(int custom_id) { + m_customId = custom_id; +} + +int TtRssFeed::updateMessages(const QList &messages) { + if (messages.isEmpty()) { + return 0; + } + + int feed_id = customId(); + int updated_messages = 0; + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + int account_id = serviceRoot()->accountId(); + + // Prepare queries. + QSqlQuery query_insert(database); + QSqlQuery query_select(database); + QSqlQuery query_update(database); + + query_update.setForwardOnly(true); + query_update.prepare("UPDATE Messages " + "SET title = :title, is_read = :is_read, is_important = :is_important, url = :url, author = :author, date_created = :date_created, contents = :contents, enclosures = :enclosures " + "WHERE id = :id;"); + + query_select.setForwardOnly(true); + query_select.prepare("SELECT id, date_created, is_read, is_important FROM Messages " + "WHERE account_id = :account_id AND custom_id = :custom_id;"); + + // Used to insert new messages. + query_insert.setForwardOnly(true); + query_insert.prepare("INSERT INTO Messages " + "(feed, title, is_read, is_important, url, author, date_created, contents, enclosures, custom_id, account_id) " + "VALUES (:feed, :title, :is_read, :is_important, :url, :author, :date_created, :contents, :enclosures, :custom_id, :account_id);"); + + if (!database.transaction()) { + database.rollback(); + qDebug("Transaction start for message downloader failed."); + return updated_messages; + } + + foreach (Message message, messages) { + query_select.bindValue(QSL(":account_id"), account_id); + query_select.bindValue(QSL(":custom_id"), message.m_customId); + + query_select.exec(); + + int id_existing_message = -1; + qint64 date_existing_message; + bool is_read_existing_message; + bool is_important_existing_message; + + if (query_select.next()) { + id_existing_message = query_select.value(0).toInt(); + date_existing_message = query_select.value(1).value(); + is_read_existing_message = query_select.value(2).toBool(); + is_important_existing_message = query_select.value(3).toBool(); + } + + query_select.finish(); + + // Now, check if this message is already in the DB. + if (id_existing_message >= 0) { + // Message is already in the DB. + + if (message.m_created.toMSecsSinceEpoch() != date_existing_message || + message.m_isRead != is_read_existing_message || + message.m_isImportant != is_important_existing_message) { + // Message exists, it is changed, update it. + query_update.bindValue(QSL(":title"), message.m_title); + query_update.bindValue(QSL(":is_read"), (int) message.m_isRead); + query_update.bindValue(QSL(":is_important"), (int) message.m_isImportant); + query_update.bindValue(QSL(":url"), message.m_url); + query_update.bindValue(QSL(":author"), message.m_author); + query_update.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); + query_update.bindValue(QSL(":contents"), message.m_contents); + query_update.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); + query_update.bindValue(QSL(":id"), id_existing_message); + + if (query_update.exec()) { + updated_messages++; + } + + query_update.finish(); + + qDebug("Updating message '%s' in DB.", qPrintable(message.m_title)); + } + } + else { + // Message with this URL is not fetched in this feed yet. + query_insert.bindValue(QSL(":feed"), feed_id); + query_insert.bindValue(QSL(":title"), message.m_title); + query_insert.bindValue(QSL(":is_read"), (int) message.m_isRead); + query_insert.bindValue(QSL(":is_important"), (int) message.m_isImportant); + query_insert.bindValue(QSL(":url"), message.m_url); + query_insert.bindValue(QSL(":author"), message.m_author); + query_insert.bindValue(QSL(":date_created"), message.m_created.toMSecsSinceEpoch()); + query_insert.bindValue(QSL(":contents"), message.m_contents); + query_insert.bindValue(QSL(":enclosures"), Enclosures::encodeEnclosuresToString(message.m_enclosures)); + query_insert.bindValue(QSL(":custom_id"), message.m_customId); + query_insert.bindValue(QSL(":account_id"), account_id); + + if (query_insert.exec() && query_insert.numRowsAffected() == 1) { + updated_messages++; + } + + query_insert.finish(); + + qDebug("Adding new message '%s' to DB.", qPrintable(message.m_title)); + } + } + + if (!database.commit()) { + database.rollback(); + + qDebug("Transaction commit for message downloader failed."); + } + else { + if (updated_messages > 0) { + setStatus(NewMessages); + } + else { + setStatus(Normal); + } + + updateCounts(true); + serviceRoot()->itemChanged(QList() << this); + } + + return updated_messages; +} diff --git a/src/services/tt-rss/ttrssfeed.h b/src/services/tt-rss/ttrssfeed.h new file mode 100755 index 000000000..66c6838a6 --- /dev/null +++ b/src/services/tt-rss/ttrssfeed.h @@ -0,0 +1,60 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef TTRSSFEED_H +#define TTRSSFEED_H + +#include "services/abstract/feed.h" + +#include + + +class TtRssServiceRoot; + +class TtRssFeed : public Feed { + Q_OBJECT + + public: + explicit TtRssFeed(RootItem *parent = NULL); + explicit TtRssFeed(const QSqlRecord &record); + virtual ~TtRssFeed(); + + TtRssServiceRoot *serviceRoot(); + + void updateCounts(bool including_total_count); + + int countOfAllMessages() const; + int countOfUnreadMessages() const; + + int update(); + QList undeletedMessages() const; + + bool markAsReadUnread(ReadStatus status); + bool cleanMessages(bool clear_only_read); + + int customId() const; + void setCustomId(int custom_id); + + private: + int updateMessages(const QList &messages); + + int m_customId; + int m_totalCount; + int m_unreadCount; +}; + +#endif // TTRSSFEED_H diff --git a/src/services/tt-rss/ttrssrecyclebin.cpp b/src/services/tt-rss/ttrssrecyclebin.cpp new file mode 100755 index 000000000..0b807b570 --- /dev/null +++ b/src/services/tt-rss/ttrssrecyclebin.cpp @@ -0,0 +1,49 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/tt-rss/ttrssrecyclebin.h" + +#include "services/tt-rss/definitions.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" +#include "services/tt-rss/ttrssserviceroot.h" + + +TtRssRecycleBin::TtRssRecycleBin(RootItem *parent) : RecycleBin(parent) { + +} + +TtRssRecycleBin::~TtRssRecycleBin() { +} + +TtRssServiceRoot *TtRssRecycleBin::serviceRoot() { + return qobject_cast(getParentServiceRoot()); +} + +bool TtRssRecycleBin::markAsReadUnread(RootItem::ReadStatus status) { + QStringList ids = serviceRoot()->customIDSOfMessagesForItem(this); + TtRssUpdateArticleResponse response = serviceRoot()->network()->updateArticles(ids, UpdateArticle::Unread, + status == RootItem::Unread ? + UpdateArticle::SetToTrue : + UpdateArticle::SetToFalse); + + if (serviceRoot()->network()->lastError() != QNetworkReply::NoError || response.updateStatus() != STATUS_OK) { + return false; + } + else { + return RecycleBin::markAsReadUnread(status); + } +} diff --git a/src/services/tt-rss/ttrssrecyclebin.h b/src/services/tt-rss/ttrssrecyclebin.h new file mode 100755 index 000000000..a013aae74 --- /dev/null +++ b/src/services/tt-rss/ttrssrecyclebin.h @@ -0,0 +1,38 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef TTRSSRECYCLEBIN_H +#define TTRSSRECYCLEBIN_H + +#include "services/abstract/recyclebin.h" + + +class TtRssServiceRoot; + +class TtRssRecycleBin : public RecycleBin { + Q_OBJECT + + public: + explicit TtRssRecycleBin(RootItem *parent = 0); + virtual ~TtRssRecycleBin(); + + TtRssServiceRoot *serviceRoot(); + + bool markAsReadUnread(ReadStatus status); +}; + +#endif // TTRSSRECYCLEBIN_H diff --git a/src/services/tt-rss/ttrssserviceentrypoint.cpp b/src/services/tt-rss/ttrssserviceentrypoint.cpp new file mode 100755 index 000000000..14f50bcc9 --- /dev/null +++ b/src/services/tt-rss/ttrssserviceentrypoint.cpp @@ -0,0 +1,102 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/tt-rss/ttrssserviceentrypoint.h" + +#include "definitions/definitions.h" +#include "miscellaneous/application.h" +#include "miscellaneous/iconfactory.h" +#include "miscellaneous/textfactory.h" +#include "gui/dialogs/formmain.h" +#include "services/tt-rss/gui/formeditaccount.h" +#include "services/tt-rss/ttrssserviceroot.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" + +#include +#include + + +TtRssServiceEntryPoint::TtRssServiceEntryPoint(){ +} + + +TtRssServiceEntryPoint::~TtRssServiceEntryPoint() { + +} + +bool TtRssServiceEntryPoint::isSingleInstanceService() { + return false; +} + +QString TtRssServiceEntryPoint::name() { + return QSL("Tiny Tiny RSS"); +} + +QString TtRssServiceEntryPoint::description() { + return QSL("This service offers integration with Tiny Tiny RSS.\n\nTiny Tiny RSS is an open source web-based news feed (RSS/Atom) reader and aggregator, designed to allow you to read news from any location, while feeling as close to a real desktop application as possible."); +} + +QString TtRssServiceEntryPoint::version() { + return APP_VERSION; +} + +QString TtRssServiceEntryPoint::author() { + return APP_AUTHOR; +} + +QIcon TtRssServiceEntryPoint::icon() { + return qApp->icons()->fromTheme(QSL("application-ttrss")); +} + +QString TtRssServiceEntryPoint::code() { + return SERVICE_CODE_TT_RSS; +} + +ServiceRoot *TtRssServiceEntryPoint::createNewRoot() { + QPointer form_acc = new FormEditAccount(qApp->mainForm()); + TtRssServiceRoot *new_root = form_acc.data()->execForCreate(); + delete form_acc.data(); + + return new_root; +} + +QList TtRssServiceEntryPoint::initializeSubtree() { + // Check DB if standard account is enabled. + QSqlDatabase database = qApp->database()->connection(QSL("TtRssServiceEntryPoint"), DatabaseFactory::FromSettings); + QSqlQuery query(database); + QList roots; + + if (query.exec("SELECT * FROM TtRssAccounts;")) { + while (query.next()) { + TtRssServiceRoot *root = new TtRssServiceRoot(); + root->setId(query.value(0).toInt()); + root->setAccountId(query.value(0).toInt()); + root->network()->setUsername(query.value(1).toString()); + root->network()->setPassword(TextFactory::decrypt(query.value(2).toString())); + root->network()->setAuthIsUsed(query.value(3).toBool()); + root->network()->setAuthUsername(query.value(4).toString()); + root->network()->setAuthPassword(TextFactory::decrypt(query.value(5).toString())); + root->network()->setUrl(query.value(6).toString()); + root->network()->setForceServerSideUpdate(query.value(7).toBool()); + + root->updateTitle(); + roots.append(root); + } + } + + return roots; +} diff --git a/src/services/tt-rss/ttrssserviceentrypoint.h b/src/services/tt-rss/ttrssserviceentrypoint.h new file mode 100755 index 000000000..dcd99616b --- /dev/null +++ b/src/services/tt-rss/ttrssserviceentrypoint.h @@ -0,0 +1,42 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + + +#ifndef TTRSSSERVICEENTRYPOINT_H +#define TTRSSSERVICEENTRYPOINT_H + +#include "services/abstract/serviceentrypoint.h" + + +class TtRssServiceEntryPoint : public ServiceEntryPoint { + public: + explicit TtRssServiceEntryPoint(); + virtual ~TtRssServiceEntryPoint(); + + bool isSingleInstanceService(); + QString name(); + QString description(); + QString version(); + QString author(); + QIcon icon(); + QString code(); + + ServiceRoot *createNewRoot(); + QList initializeSubtree(); +}; + +#endif // TTRSSSERVICEENTRYPOINT_H diff --git a/src/services/tt-rss/ttrssserviceroot.cpp b/src/services/tt-rss/ttrssserviceroot.cpp new file mode 100755 index 000000000..f1dd2d921 --- /dev/null +++ b/src/services/tt-rss/ttrssserviceroot.cpp @@ -0,0 +1,700 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#include "services/tt-rss/ttrssserviceroot.h" + +#include "miscellaneous/application.h" +#include "miscellaneous/settings.h" +#include "miscellaneous/textfactory.h" +#include "gui/dialogs/formmain.h" +#include "network-web/networkfactory.h" +#include "services/tt-rss/ttrssserviceentrypoint.h" +#include "services/tt-rss/ttrssfeed.h" +#include "services/tt-rss/ttrssrecyclebin.h" +#include "services/tt-rss/ttrsscategory.h" +#include "services/tt-rss/definitions.h" +#include "services/tt-rss/network/ttrssnetworkfactory.h" +#include "services/tt-rss/gui/formeditaccount.h" + +#include +#include +#include +#include +#include + + +TtRssServiceRoot::TtRssServiceRoot(RootItem *parent) + : ServiceRoot(parent), m_recycleBin(new TtRssRecycleBin(this)), m_actionSyncIn(NULL), m_serviceMenu(QList()), m_network(new TtRssNetworkFactory) { + setIcon(TtRssServiceEntryPoint().icon()); + setCreationDate(QDateTime::currentDateTime()); +} + +TtRssServiceRoot::~TtRssServiceRoot() { + delete m_network; +} + +void TtRssServiceRoot::start() { + loadFromDatabase(); + + if (childCount() == 1 && child(0)->kind() == RootItemKind::Bin) { + syncIn(); + } +} + +void TtRssServiceRoot::stop() { + m_network->logout(); + + qDebug("Stopping Tiny Tiny RSS account, logging out with result '%d'.", (int) m_network->lastError()); +} + +QString TtRssServiceRoot::code() { + return SERVICE_CODE_TT_RSS; +} + +bool TtRssServiceRoot::editViaGui() { + QPointer form_pointer = new FormEditAccount(qApp->mainForm()); + form_pointer.data()->execForEdit(this); + delete form_pointer.data(); + return false; +} + +bool TtRssServiceRoot::deleteViaGui() { + QSqlDatabase connection = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + // Remove extra entry in "Tiny Tiny RSS accounts list" and then delete + // all the categories/feeds and messages. + if (!QSqlQuery(connection).exec(QString("DELETE FROM TtRssAccounts WHERE id = %1;").arg(accountId()))) { + return false; + } + else { + return ServiceRoot::deleteViaGui(); + } +} + +bool TtRssServiceRoot::markAsReadUnread(RootItem::ReadStatus status) { + QStringList ids = customIDSOfMessagesForItem(this); + TtRssUpdateArticleResponse response = m_network->updateArticles(ids, UpdateArticle::Unread, + status == RootItem::Unread ? + UpdateArticle::SetToTrue : + UpdateArticle::SetToFalse); + + if (m_network->lastError() != QNetworkReply::NoError || response.updateStatus() != STATUS_OK) { + return false; + } + else { + return ServiceRoot::markAsReadUnread(status); + } +} + +bool TtRssServiceRoot::canBeEdited() { + return true; +} + +bool TtRssServiceRoot::canBeDeleted() { + return true; +} + +QVariant TtRssServiceRoot::data(int column, int role) const { + switch (role) { + case Qt::ToolTipRole: + if (column == FDS_MODEL_TITLE_INDEX) { + return tr("Tiny Tiny RSS\n\nAccount ID: %3\nUsername: %1\nServer: %2\n" + "Last error: %4\nLast login on: %5").arg(m_network->username(), + m_network->url(), + QString::number(accountId()), + NetworkFactory::networkErrorText(m_network->lastError()), + m_network->lastLoginTime().isValid() ? + m_network->lastLoginTime().toString(Qt::DefaultLocaleShortDate) : + QSL("-")); + } + else { + return ServiceRoot::data(column, role); + } + + default: + return ServiceRoot::data(column, role); + } +} + +QList TtRssServiceRoot::addItemMenu() { + return QList(); +} + +RecycleBin *TtRssServiceRoot::recycleBin() { + return m_recycleBin; +} + +bool TtRssServiceRoot::loadMessagesForItem(RootItem *item, QSqlTableModel *model) { + if (item->kind() == RootItemKind::Bin) { + model->setFilter(QString("is_deleted = 1 AND is_pdeleted = 0 AND account_id = %1").arg(QString::number(accountId()))); + } + else { + QList children = item->getSubTreeFeeds(); + QString filter_clause = textualFeedIds(children).join(QSL(", ")); + + model->setFilter(QString(QSL("feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0 AND account_id = '%2'")).arg(filter_clause, + QString::number(accountId()))); + qDebug("Loading messages from feeds: %s.", qPrintable(filter_clause)); + } + + return true; +} + +QList TtRssServiceRoot::serviceMenu() { + if (m_serviceMenu.isEmpty()) { + m_actionSyncIn = new QAction(qApp->icons()->fromTheme(QSL("item-sync")), tr("Sync in"), this); + + connect(m_actionSyncIn, SIGNAL(triggered()), this, SLOT(syncIn())); + m_serviceMenu.append(m_actionSyncIn); + } + + return m_serviceMenu; +} + +QList TtRssServiceRoot::contextMenu() { + return serviceMenu(); +} + +bool TtRssServiceRoot::onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, + RootItem::ReadStatus read) { + Q_UNUSED(selected_item) + + TtRssUpdateArticleResponse response = m_network->updateArticles(customIDsOfMessages(messages), + UpdateArticle::Unread, + read == RootItem::Unread ? + UpdateArticle::SetToTrue : + UpdateArticle::SetToFalse); + + if (m_network->lastError() == QNetworkReply::NoError && response.updateStatus() == STATUS_OK) { + return true; + } + else { + return false; + } +} + +bool TtRssServiceRoot::onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, RootItem::ReadStatus read) { + Q_UNUSED(messages) + Q_UNUSED(read) + + selected_item->updateCounts(false); + itemChanged(QList() << selected_item); + requestFeedReadFilterReload(); + return true; +} + +bool TtRssServiceRoot::onBeforeSwitchMessageImportance(RootItem *selected_item, const QList > &changes) { + Q_UNUSED(selected_item) + + // NOTE: We just toggle it here, because we know, that there is only + // toggling of starred status supported by RSS Guard right now and + // Tiny Tiny RSS API allows it, which is greate. + TtRssUpdateArticleResponse response = m_network->updateArticles(customIDsOfMessages(changes), + UpdateArticle::Starred, + UpdateArticle::Togggle); + + if (m_network->lastError() == QNetworkReply::NoError && response.updateStatus() == STATUS_OK) { + return true; + } + else { + return false; + } +} + +bool TtRssServiceRoot::onAfterSwitchMessageImportance(RootItem *selected_item, const QList > &changes) { + Q_UNUSED(selected_item) + Q_UNUSED(changes) + + return true; +} + +bool TtRssServiceRoot::onBeforeMessagesDelete(RootItem *selected_item, const QList &messages) { + Q_UNUSED(selected_item) + Q_UNUSED(messages) + + return true; +} + +bool TtRssServiceRoot::onAfterMessagesDelete(RootItem *selected_item, const QList &messages) { + Q_UNUSED(messages) + + // User deleted some messages he selected in message list. + selected_item->updateCounts(true); + + if (selected_item->kind() == RootItemKind::Bin) { + itemChanged(QList() << m_recycleBin); + } + else { + m_recycleBin->updateCounts(true); + itemChanged(QList() << selected_item << m_recycleBin); + } + + requestFeedReadFilterReload(); + return true; +} + +bool TtRssServiceRoot::onBeforeMessagesRestoredFromBin(RootItem *selected_item, const QList &messages) { + return false; +} + +bool TtRssServiceRoot::onAfterMessagesRestoredFromBin(RootItem *selected_item, const QList &messages) { + return false; +} + +TtRssNetworkFactory *TtRssServiceRoot::network() const { + return m_network; +} + +QStringList TtRssServiceRoot::customIDSOfMessagesForItem(RootItem *item) { + if (item->getParentServiceRoot() != this) { + // Not item from this account. + return QStringList(); + } + else { + QStringList list; + + switch (item->kind()) { + case RootItemKind::Category: { + foreach (RootItem *child, item->childItems()) { + list.append(customIDSOfMessagesForItem(child)); + } + + return list; + } + + case RootItemKind::ServiceRoot: { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + + query.prepare(QSL("SELECT custom_id FROM Messages WHERE is_deleted = 0 AND is_pdeleted = 0 AND account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); + + while (query.next()) { + list.append(query.value(0).toString()); + } + + break; + } + + case RootItemKind::Bin: { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + + query.prepare(QSL("SELECT custom_id FROM Messages WHERE is_deleted = 1 AND is_pdeleted = 0 AND account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); + + while (query.next()) { + list.append(query.value(0).toString()); + } + + break; + } + + case RootItemKind::Feed: { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + + query.prepare(QSL("SELECT custom_id FROM Messages WHERE is_deleted = 0 AND is_pdeleted = 0 AND feed = :feed AND account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.bindValue(QSL(":feed"), qobject_cast(item)->customId()); + query.exec(); + + while (query.next()) { + list.append(query.value(0).toString()); + } + + break; + } + + default: + break; + } + + return list; + } +} + +bool TtRssServiceRoot::markFeedsReadUnread(QList items, RootItem::ReadStatus read) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + + if (!db_handle.transaction()) { + qWarning("Starting transaction for feeds read change."); + return false; + } + + QSqlQuery query_read_msg(db_handle); + query_read_msg.setForwardOnly(true); + + if (!query_read_msg.prepare(QString("UPDATE Messages SET is_read = :read " + "WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0;").arg(textualFeedIds(items).join(QSL(", "))))) { + qWarning("Query preparation failed for feeds read change."); + + db_handle.rollback(); + return false; + } + + query_read_msg.bindValue(QSL(":read"), read == RootItem::Read ? 1 : 0); + + if (!query_read_msg.exec()) { + qDebug("Query execution for feeds read change failed."); + db_handle.rollback(); + } + + // Commit changes. + if (db_handle.commit()) { + QList itemss; + + foreach (Feed *feed, items) { + feed->updateCounts(false); + itemss.append(feed); + } + + itemChanged(itemss); + requestReloadMessageList(read == RootItem::Read); + return true; + } + else { + return db_handle.rollback(); + } +} + +bool TtRssServiceRoot::cleanFeeds(QList items, bool clean_read_only) { + QSqlDatabase db_handle = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_delete_msg(db_handle); + query_delete_msg.setForwardOnly(true); + + if (clean_read_only) { + if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " + "WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0 AND is_read = 1;").arg(textualFeedIds(items).join(QSL(", "))))) { + qWarning("Query preparation failed for feeds clearing."); + return false; + } + } + else { + if (!query_delete_msg.prepare(QString("UPDATE Messages SET is_deleted = :deleted " + "WHERE feed IN (%1) AND is_deleted = 0 AND is_pdeleted = 0;").arg(textualFeedIds(items).join(QSL(", "))))) { + qWarning("Query preparation failed for feeds clearing."); + return false; + } + } + + query_delete_msg.bindValue(QSL(":deleted"), 1); + + if (!query_delete_msg.exec()) { + qDebug("Query execution for feeds clearing failed."); + return false; + } + else { + // Messages are cleared, now inform model about need to reload data. + QList itemss; + + foreach (Feed *feed, items) { + feed->updateCounts(true); + itemss.append(feed); + } + + m_recycleBin->updateCounts(true); + itemss.append(m_recycleBin); + + itemChanged(itemss); + requestReloadMessageList(true); + return true; + } +} + +void TtRssServiceRoot::saveAccountDataToDatabase() { + if (accountId() != NO_PARENT_CATEGORY) { + // We are overwritting previously saved data. + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + + query.prepare("UPDATE TtRssAccounts " + "SET username = :username, password = :password, url = :url, auth_protected = :auth_protected, " + "auth_username = :auth_username, auth_password = :auth_password, force_update = :force_update " + "WHERE id = :id;"); + query.bindValue(QSL(":username"), m_network->username()); + query.bindValue(QSL(":password"), TextFactory::encrypt(m_network->password())); + query.bindValue(QSL(":url"), m_network->url()); + query.bindValue(QSL(":auth_protected"), m_network->authIsUsed()); + query.bindValue(QSL(":auth_username"), m_network->authUsername()); + query.bindValue(QSL(":auth_password"), TextFactory::encrypt(m_network->authPassword())); + query.bindValue(QSL(":force_update"), m_network->forceServerSideUpdate()); + query.bindValue(QSL(":id"), accountId()); + + if (query.exec()) { + updateTitle(); + itemChanged(QList() << this); + } + } + else { + // We are probably saving newly added account. + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + + // First obtain the ID, which can be assigned to this new account. + if (!query.exec("SELECT max(id) FROM Accounts;") || !query.next()) { + return; + } + + int id_to_assign = query.value(0).toInt() + 1; + bool saved = true; + + query.prepare(QSL("INSERT INTO Accounts (id, type) VALUES (:id, :type);")); + query.bindValue(QSL(":id"), id_to_assign); + query.bindValue(QSL(":type"), SERVICE_CODE_TT_RSS); + + saved &= query.exec(); + + query.prepare("INSERT INTO TtRssAccounts (id, username, password, auth_protected, auth_username, auth_password, url, force_update) " + "VALUES (:id, :username, :password, :auth_protected, :auth_username, :auth_password, :url, :force_update);"); + query.bindValue(QSL(":id"), id_to_assign); + query.bindValue(QSL(":username"), m_network->username()); + query.bindValue(QSL(":password"), TextFactory::encrypt(m_network->password())); + query.bindValue(QSL(":auth_protected"), m_network->authIsUsed()); + query.bindValue(QSL(":auth_username"), m_network->authUsername()); + query.bindValue(QSL(":auth_password"), TextFactory::encrypt(m_network->authPassword())); + query.bindValue(QSL(":url"), m_network->url()); + query.bindValue(QSL(":force_update"), m_network->forceServerSideUpdate()); + + saved &= query.exec(); + + if (saved) { + setId(id_to_assign); + setAccountId(id_to_assign); + updateTitle(); + } + } +} + +void TtRssServiceRoot::loadFromDatabase() { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + Assignment categories; + Assignment feeds; + + // Obtain data for categories from the database. + QSqlQuery query_categories(database); + query_categories.setForwardOnly(true); + + if (!query_categories.exec(QString("SELECT * FROM Categories WHERE account_id = %1;").arg(accountId())) || query_categories.lastError().isValid()) { + qFatal("Query for obtaining categories failed. Error message: '%s'.", + qPrintable(query_categories.lastError().text())); + } + + while (query_categories.next()) { + AssignmentItem pair; + pair.first = query_categories.value(CAT_DB_PARENT_ID_INDEX).toInt(); + pair.second = new TtRssCategory(query_categories.record()); + + categories << pair; + } + + // All categories are now loaded. + QSqlQuery query_feeds(database); + query_feeds.setForwardOnly(true); + + if (!query_feeds.exec(QString("SELECT * FROM Feeds WHERE account_id = %1;").arg(accountId())) || query_feeds.lastError().isValid()) { + qFatal("Query for obtaining feeds failed. Error message: '%s'.", + qPrintable(query_feeds.lastError().text())); + } + + while (query_feeds.next()) { + AssignmentItem pair; + pair.first = query_feeds.value(FDS_DB_CATEGORY_INDEX).toInt(); + pair.second = new TtRssFeed(query_feeds.record()); + + feeds << pair; + } + + // All data are now obtained, lets create the hierarchy. + assembleCategories(categories); + assembleFeeds(feeds); + + // As the last item, add recycle bin, which is needed. + appendChild(m_recycleBin); + m_recycleBin->updateCounts(true); +} + +void TtRssServiceRoot::updateTitle() { + QString host = QUrl(m_network->url()).host(); + + if (host.isEmpty()) { + host = m_network->url(); + } + + setTitle(m_network->username() + QL1S("@") + host); +} + +void TtRssServiceRoot::completelyRemoveAllData() { + // Purge old data from SQL and clean all model items. + removeOldFeedTree(true); + cleanAllItems(); + updateCounts(true); + itemChanged(QList() << this); + requestReloadMessageList(true); +} + +void TtRssServiceRoot::syncIn() { + QIcon original_icon = icon(); + + setIcon(qApp->icons()->fromTheme(QSL("item-sync"))); + itemChanged(QList() << this); + + TtRssGetFeedsCategoriesResponse feed_cats_response = m_network->getFeedsCategories(); + + if (m_network->lastError() == QNetworkReply::NoError) { + RootItem *new_tree = feed_cats_response.feedsCategories(true, m_network->url()); + + // Purge old data from SQL and clean all model items. + removeOldFeedTree(false); + cleanAllItems(); + + // Model is clean, now store new tree into DB and + // set primary IDs of the items. + storeNewFeedTree(new_tree); + + foreach (RootItem *top_level_item, new_tree->childItems()) { + top_level_item->setParent(NULL); + requestItemReassignment(top_level_item, this); + } + + updateCounts(true); + + new_tree->clearChildren(); + new_tree->deleteLater(); + + QList all_items = getSubTree(); + + itemChanged(all_items); + requestReloadMessageList(true); + requestItemExpand(all_items, true); + } + + setIcon(original_icon); + itemChanged(QList() << this); +} + +QStringList TtRssServiceRoot::customIDsOfMessages(const QList > &changes) { + QStringList list; + + for (int i = 0; i < changes.size(); i++) { + list.append(changes.at(i).first.m_customId); + } + + return list; +} + +QStringList TtRssServiceRoot::customIDsOfMessages(const QList &messages) { + QStringList list; + + foreach (const Message &message, messages) { + list.append(message.m_customId); + } + + return list; +} + +QStringList TtRssServiceRoot::textualFeedIds(const QList &feeds) { + QStringList stringy_ids; + stringy_ids.reserve(feeds.size()); + + foreach (Feed *feed, feeds) { + stringy_ids.append(QString("'%1'").arg(QString::number(qobject_cast(feed)->customId()))); + } + + return stringy_ids; +} + +void TtRssServiceRoot::removeOldFeedTree(bool including_messages) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query(database); + query.setForwardOnly(true); + + query.prepare(QSL("DELETE FROM Feeds WHERE account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); + + query.prepare(QSL("DELETE FROM Categories WHERE account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); + + if (including_messages) { + query.prepare(QSL("DELETE FROM Messages WHERE account_id = :account_id;")); + query.bindValue(QSL(":account_id"), accountId()); + query.exec(); + } +} + +void TtRssServiceRoot::cleanAllItems() { + foreach (RootItem *top_level_item, childItems()) { + if (top_level_item->kind() != RootItemKind::Bin) { + requestItemRemoval(top_level_item); + } + } +} + +void TtRssServiceRoot::storeNewFeedTree(RootItem *root) { + QSqlDatabase database = qApp->database()->connection(metaObject()->className(), DatabaseFactory::FromSettings); + QSqlQuery query_category(database); + QSqlQuery query_feed(database); + + query_category.prepare("INSERT INTO Categories (parent_id, title, account_id, custom_id) " + "VALUES (:parent_id, :title, :account_id, :custom_id);"); + query_feed.prepare("INSERT INTO Feeds (title, icon, category, protected, update_type, update_interval, account_id, custom_id) " + "VALUES (:title, :icon, :category, :protected, :update_type, :update_interval, :account_id, :custom_id);"); + + // Iterate all children. + foreach (RootItem *child, root->getSubTree()) { + if (child->kind() == RootItemKind::Category) { + query_category.bindValue(QSL(":parent_id"), child->parent()->id()); + query_category.bindValue(QSL(":title"), child->title()); + query_category.bindValue(QSL(":account_id"), accountId()); + query_category.bindValue(QSL(":custom_id"), QString::number(qobject_cast(child)->customId())); + + if (query_category.exec()) { + child->setId(query_category.lastInsertId().toInt()); + } + else { + } + } + else if (child->kind() == RootItemKind::Feed) { + TtRssFeed *feed = static_cast(child); + + query_feed.bindValue(QSL(":title"), feed->title()); + query_feed.bindValue(QSL(":icon"), qApp->icons()->toByteArray(feed->icon())); + query_feed.bindValue(QSL(":category"), feed->parent()->id()); + query_feed.bindValue(QSL(":protected"), 0); + query_feed.bindValue(QSL(":update_type"), (int) feed->autoUpdateType()); + query_feed.bindValue(QSL(":update_interval"), feed->autoUpdateInitialInterval()); + query_feed.bindValue(QSL(":account_id"), accountId()); + query_feed.bindValue(QSL(":custom_id"), feed->customId()); + + if (query_feed.exec()) { + feed->setId(query_feed.lastInsertId().toInt()); + } + else { + } + } + } + + if (!childItems().contains(m_recycleBin)) { + // As the last item, add recycle bin, which is needed. + appendChild(m_recycleBin); + m_recycleBin->updateCounts(true); + } +} diff --git a/src/services/tt-rss/ttrssserviceroot.h b/src/services/tt-rss/ttrssserviceroot.h new file mode 100755 index 000000000..af2277e3f --- /dev/null +++ b/src/services/tt-rss/ttrssserviceroot.h @@ -0,0 +1,108 @@ +// This file is part of RSS Guard. +// +// Copyright (C) 2011-2015 by Martin Rotter +// +// RSS Guard 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. +// +// RSS Guard 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 RSS Guard. If not, see . + +#ifndef TTRSSSERVICEROOT_H +#define TTRSSSERVICEROOT_H + +#include "services/abstract/serviceroot.h" + +#include + + +class TtRssCategory; +class TtRssFeed; +class TtRssNetworkFactory; +class TtRssRecycleBin; + +class TtRssServiceRoot : public ServiceRoot { + Q_OBJECT + + public: + explicit TtRssServiceRoot(RootItem *parent = NULL); + virtual ~TtRssServiceRoot(); + + void start(); + void stop(); + + QString code(); + + bool canBeEdited(); + bool canBeDeleted(); + bool editViaGui(); + bool deleteViaGui(); + + bool markAsReadUnread(ReadStatus status); + + QVariant data(int column, int role) const; + + QList addItemMenu(); + QList serviceMenu(); + QList contextMenu(); + + RecycleBin *recycleBin(); + + bool loadMessagesForItem(RootItem *item, QSqlTableModel *model); + + bool onBeforeSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); + bool onAfterSetMessagesRead(RootItem *selected_item, const QList &messages, ReadStatus read); + + bool onBeforeSwitchMessageImportance(RootItem *selected_item, const QList > &changes); + bool onAfterSwitchMessageImportance(RootItem *selected_item, const QList > &changes); + + bool onBeforeMessagesDelete(RootItem *selected_item, const QList &messages); + bool onAfterMessagesDelete(RootItem *selected_item, const QList &messages); + + bool onBeforeMessagesRestoredFromBin(RootItem *selected_item, const QList &messages); + bool onAfterMessagesRestoredFromBin(RootItem *selected_item, const QList &messages); + + TtRssNetworkFactory *network() const; + + // Returns list of custom IDS of all DB messages in given item. + QStringList customIDSOfMessagesForItem(RootItem *item); + + bool markFeedsReadUnread(QList items, ReadStatus read); + bool cleanFeeds(QList items, bool clean_read_only); + + void saveAccountDataToDatabase(); + void updateTitle(); + void completelyRemoveAllData(); + + public slots: + void syncIn(); + + private: + QStringList customIDsOfMessages(const QList > &changes); + QStringList customIDsOfMessages(const QList &messages); + + // Returns converted ids of given feeds + // which are suitable as IN clause for SQL queries. + QStringList textualFeedIds(const QList &feeds); + + void removeOldFeedTree(bool including_messages); + void cleanAllItems(); + void storeNewFeedTree(RootItem *root); + void loadFromDatabase(); + + TtRssRecycleBin *m_recycleBin; + + QAction *m_actionSyncIn; + QList m_serviceMenu; + + TtRssNetworkFactory *m_network; +}; + +#endif // TTRSSSERVICEROOT_H