diff --git a/CHANGELOG.md b/CHANGELOG.md index ba2c1cbd..ecfdc3ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,28 +1,24 @@ +## Ramadan Kareem - رمضان كريم 🕌 +
### التحديثات 💭 -- تصليح بعض الأعطال -- إضافة خطوط مختلفة للآيات -- إضافة نص الآيات المرمز ([#31](https://github.com/0xzer0x/quran-companion/pull/31#issuecomment-1872679733)) -- إزالة ملفات خط QCF 2 من البرنامج و إضافة إمكانية تحميله (تقليص حجم البرنامج) -- إزالة ملفات التفاسير و الترجمات من البرنامج و إضافة إمكانية تحمليها (تقليص حجم البرنامج) -- إضافة خاصية بطاقات السور (الضغط على إطار السورة/اسم السورة في أعلى الصفحة) -- إضافة اختصار لإخفاء التحكم في المشغل من الواجهة الأساسية -- استبدال حجم القارئ المتغير بخاصية تغيير وضع القراءة -- إضافة تفسير الجلالين (إنجليزية) -- نافذة "عن البرنامج" أفضل +- تصليح بعض الأعطال (#49) +- إضافة ترجمة بكتال - إنجليزية (#46) +- تعطيل التشغيل التلقائي عند الذهاب لسورة (#50) +- إضافة خاصية _خواطر_ (#44) +- نافذة التفسير أصبحت تشمل كل أنواع المحتوى (تفاسير، ترجمة، خواطر) (#43 ,#47) +- نقل تغيير إعدادات التفسير لنافذة المحتوى +- إضافة خاصية استيراد/تصدير بيانات المستخدم
### What's Changed 💭 -- Bugfixes -- Added different fonts for displaying verses -- Added annotated Hafs verse text ([#31](https://github.com/0xzer0x/quran-companion/pull/31#issuecomment-1872679733)) -- Move QCF 2 font files out of application bundle and added option to download it (Reduced bundle size) -- Move tafsir and translation files out of the application bundle and added option to download any one of them (Reduced bundle size) -- Added Surah card functionality (Accessed by clicking the Surah frame/clicking the Surah name in the page header) -- Added new shortcut to hide player controls -- Replaced the dynamic resizing of reader panels with panel toggling functionality -- Added Tafsir Al-Jalalayn (English) -- Better "About" dialog \ No newline at end of file +- Bugfixes (#49) +- Added Pickthall - English translation (#46) +- Disabled Auto-play when navigating to a Surah (#50) +- Added _Thoughts_ feature (#44) +- Changed tafsir dialog to display all available content types (tafsir, translation, thoughts) (#43 ,#47) +- Move tafsir setting to content dialog +- Added user data import/export functionality \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 37645d5a..4734f773 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,10 +12,8 @@ set(CMAKE_AUTORCC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(QT NAMES Qt6 REQUIRED COMPONENTS Widgets Sql Multimedia Network - LinguistTools) -find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Sql Multimedia - Network LinguistTools) +find_package(Qt6 REQUIRED COMPONENTS Widgets Sql Multimedia Network + LinguistTools) if(WIN32) set(Vulkan_INCLUDE_DIR "$ENV{VULKAN_SDK}\\Include\\vulkan") @@ -28,34 +26,133 @@ endif() add_subdirectory(third_party/QtAwesome) +include_directories(src) + set(PROJECT_SOURCES src/main.cpp - src/globals.h - src/globals.cpp + src/types/verse.h + src/types/verse.cpp + src/types/reciter.h + src/types/reciter.cpp + src/types/content.h + src/types/content.cpp + src/types/tafsir.h + src/types/tafsir.cpp + src/types/translation.h + src/types/translation.cpp src/core/mainwindow.cpp src/core/mainwindow.h src/core/mainwindow.ui - src/core/searchdialog.h - src/core/searchdialog.cpp - src/core/searchdialog.ui - src/core/settingsdialog.cpp - src/core/settingsdialog.h - src/core/settingsdialog.ui - src/core/downloaderdialog.cpp - src/core/downloaderdialog.h - src/core/downloaderdialog.ui - src/core/bookmarksdialog.h - src/core/bookmarksdialog.cpp - src/core/bookmarksdialog.ui - src/core/tafsirdialog.h - src/core/tafsirdialog.cpp - src/core/tafsirdialog.ui - src/core/khatmahdialog.h - src/core/khatmahdialog.cpp - src/core/khatmahdialog.ui - src/core/copydialog.h - src/core/copydialog.cpp - src/core/copydialog.ui + src/core/quranreader.h + src/core/quranreader.cpp + src/core/quranreader.ui + src/core/playercontrols.h + src/core/playercontrols.cpp + src/core/playercontrols.ui + src/dialogs/searchdialog.h + src/dialogs/searchdialog.cpp + src/dialogs/searchdialog.ui + src/dialogs/settingsdialog.cpp + src/dialogs/settingsdialog.h + src/dialogs/settingsdialog.ui + src/dialogs/downloaderdialog.cpp + src/dialogs/downloaderdialog.h + src/dialogs/downloaderdialog.ui + src/dialogs/bookmarksdialog.h + src/dialogs/bookmarksdialog.cpp + src/dialogs/bookmarksdialog.ui + src/dialogs/contentdialog.h + src/dialogs/contentdialog.cpp + src/dialogs/contentdialog.ui + src/dialogs/khatmahdialog.h + src/dialogs/khatmahdialog.cpp + src/dialogs/khatmahdialog.ui + src/dialogs/copydialog.h + src/dialogs/copydialog.cpp + src/dialogs/copydialog.ui + src/dialogs/aboutdialog.h + src/dialogs/aboutdialog.h + src/dialogs/aboutdialog.cpp + src/dialogs/aboutdialog.ui + src/dialogs/versedialog.h + src/dialogs/versedialog.cpp + src/dialogs/versedialog.ui + src/dialogs/aboutdialog.cpp + src/dialogs/aboutdialog.ui + src/dialogs/versedialog.h + src/dialogs/versedialog.cpp + src/dialogs/versedialog.ui + src/dialogs/fileselector.h + src/dialogs/fileselector.cpp + src/dialogs/importexportdialog.h + src/dialogs/importexportdialog.cpp + src/dialogs/importexportdialog.ui + src/interfaces/userdataimporter.h + src/interfaces/userdataexporter.h + src/interfaces/downloadjob.h + src/interfaces/downloadtask.h + src/interfaces/dbconnection.h + src/interfaces/notificationsender.h + src/notifiers/bookmarksnotifier.h + src/notifiers/bookmarksnotifier.cpp + src/notifiers/updatenotifier.h + src/notifiers/updatenotifier.cpp + src/notifiers/copynotifier.h + src/notifiers/copynotifier.cpp + src/notifiers/jobnotifier.h + src/notifiers/jobnotifier.cpp + src/utils/configuration.h + src/utils/configuration.cpp + src/utils/shortcuthandler.h + src/utils/shortcuthandler.cpp + src/utils/verseplayer.h + src/utils/verseplayer.cpp + src/utils/systemtray.h + src/utils/systemtray.cpp + src/utils/logger.h + src/utils/logger.cpp + src/utils/dirmanager.h + src/utils/dirmanager.cpp + src/utils/stylemanager.h + src/utils/stylemanager.cpp + src/utils/fontmanager.h + src/utils/fontmanager.cpp + src/utils/versionchecker.h + src/utils/versionchecker.cpp + src/utils/jsondataexporter.h + src/utils/jsondataexporter.cpp + src/utils/jsondataimporter.h + src/utils/jsondataimporter.cpp + src/downloader/surahjob.h + src/downloader/surahjob.cpp + src/downloader/recitationtask.h + src/downloader/recitationtask.cpp + src/downloader/taskdownloader.h + src/downloader/taskdownloader.cpp + src/downloader/tafsirtask.h + src/downloader/tafsirtask.cpp + src/downloader/translationtask.h + src/downloader/translationtask.cpp + src/downloader/qcftask.h + src/downloader/qcftask.cpp + src/downloader/contentjob.h + src/downloader/contentjob.cpp + src/downloader/qcfjob.h + src/downloader/qcfjob.cpp + src/downloader/jobmanager.h + src/downloader/jobmanager.cpp + src/database/qurandb.h + src/database/qurandb.cpp + src/database/glyphsdb.h + src/database/glyphsdb.cpp + src/database/betaqatdb.h + src/database/betaqatdb.cpp + src/database/tafsirdb.h + src/database/tafsirdb.cpp + src/database/translationdb.h + src/database/translationdb.cpp + src/database/bookmarksdb.h + src/database/bookmarksdb.cpp src/widgets/quranpagebrowser.h src/widgets/quranpagebrowser.cpp src/widgets/clickablelabel.cpp @@ -70,41 +167,17 @@ set(PROJECT_SOURCES src/widgets/inputfield.cpp src/widgets/shortcutdelegate.h src/widgets/shortcutdelegate.cpp - src/widgets/aboutdialog.h - src/widgets/aboutdialog.cpp - src/widgets/aboutdialog.ui src/widgets/betaqaviewer.h src/widgets/betaqaviewer.cpp src/widgets/betaqaviewer.ui - src/widgets/versedialog.h - src/widgets/versedialog.cpp - src/widgets/versedialog.ui - src/utils/shortcuthandler.h - src/utils/shortcuthandler.cpp - src/utils/dbmanager.h - src/utils/dbmanager.cpp - src/utils/verseplayer.h - src/utils/verseplayer.cpp - src/utils/downloadmanager.h - src/utils/downloadmanager.cpp - src/utils/notificationmanager.h - src/utils/notificationmanager.cpp - src/utils/logger.h - src/utils/logger.cpp resources.qrc qurancompanion.rc) -if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) - qt_add_executable(quran-companion MANUAL_FINALIZATION ${PROJECT_SOURCES}) -else() - add_executable(quran-companion ${PROJECT_SOURCES}) -endif() +qt_add_executable(quran-companion MANUAL_FINALIZATION ${PROJECT_SOURCES}) target_link_libraries( - quran-companion - PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql - Qt${QT_VERSION_MAJOR}::Multimedia Qt${QT_VERSION_MAJOR}::Network - QtAwesome) + quran-companion PRIVATE Qt6::Widgets Qt6::Sql Qt6::Multimedia Qt6::Network + QtAwesome) if(WIN32) set_target_properties(quran-companion PROPERTIES WIN32_EXECUTABLE TRUE) diff --git a/README-AR.md b/README-AR.md index 7b935965..228db921 100644 --- a/README-AR.md +++ b/README-AR.md @@ -133,7 +133,7 @@ Download Flatpak - + Download AppImage diff --git a/README.md b/README.md index 2f79b543..cf0da5e9 100644 --- a/README.md +++ b/README.md @@ -130,7 +130,7 @@ The application can be downloaded in any of the available packages (snap, flatpa Download Flatpak - + Download AppImage diff --git a/VERSION b/VERSION index 0495c4a8..e8ea05db 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.3 +1.2.4 diff --git a/dist/translations/qc_ar.ts b/dist/translations/qc_ar.ts index 6d3f3864..55e655a1 100644 --- a/dist/translations/qc_ar.ts +++ b/dist/translations/qc_ar.ts @@ -224,212 +224,211 @@ تفهيم القرآن - الإنجليزية - + Preferences الإعدادات - + General عام - + Theme المظهر - + Light فاتح - + Sepia بني - + Dark داكن - + Language اللغة - + Audio output device جهاز إخراج الصوت - - + + Features المميزات - + Daily verse الآية اليومية - + Missing recitation warning تحذير التلاوات المفقودة - + Reader القارئ - + Quran page صفحة القرآن - + Foreground Highlight تظليل النص - + Adaptive font size حجم خط تلقائي - + Reader mode وضع القراءة - + Single page صفحة - + Double page صفحتين - + Page font خط الصفحة - + QCF V1 خط 1 - - - + + + QCF V2 خط 2 - - - + + + size الحجم - + Verse font خط الآيات - + QCF خط الصفحة - + Uthmanic عثماني - + Uthmanic (annotated) عثماني (مرمز) - + Side content المحتوى الجانبي - + Font Family نوع الخط - - - Tafsir - التفسير - - - - + + Translation الترجمة - + Shortcuts الاختصارات - + + Tafsir + التفسير + + + Description الوصف - + Key المفتاح - - - - + + + + Restart required مطلوب إعادة التشغيل - + Application theme was changed, restart now? تم تغيير مظهر التطبيق، هل تريد إعادة التشغيل الآن؟ - + Application language was changed, restart now? تم تغيير لغة التطبيق، هل تريد إعادة التشغيل الآن؟ - + Reading mode was changed, restart now? تم تغيير وضع القراءة، هل تريد إعادة التشغيل الآن؟ - + Restart is required to load new quran font, restart now? إعادة التشغيل مطلوبة لتحميل خط القرآن الجديد، هل تريد إعادة التشغيل الآن؟ - + Apply طبّق - + Cancel ألغِ @@ -558,301 +557,271 @@ - - + + Quran Companion رفيق القرآن - - Reciter - القارئ - - - - next - التالي - - - - previous - السابق - - - + View عرض - + Edit تحرير - + File ملف - + Help مساعدة - - + + Navigation التصفح - + Juz الجزء - + Page الصفحة - + Verse الآية - + Search surah بحث السور - + Preferences الإعدادات - + Download manager مدير التحميلات - + Exit خروج - + Find البحث - + Check for updates التحقق من وجود تحديثات - + Bookmarks العلامات - + About Quran Companion عن رفيق القرآن - - + + About Qt عن كيوت - + Tafsir التفسير - + Verse of the day آية اليوم - + + Khatmah الختمات - + Advanced copy النسخ المتقدم - + Toggle reader view تبديل طريقة عرض القارئ - - Khatmah - ختمه - - - - Default - افتراضي - - - - There are currently no updates available. - لا يوجد تحديثات متوفرة حاليا. - - - - - - Update info - معلومات التحديث + + Import + استيراد - - Updates available, do you want to open the update tool? - هناك تحديثات متاحة، هل تود تشغيل مدير التحديثات؟ + + Export + تصدير - - Updates info - معلومات التحديث + + Player controls + التحكم بالمشغل - - Updates are available, use the maintainance tool to install the latest updates. - التحديثات متاحة، استخدم أداة الصيانة لتثبيت أحدث التحديثات. + + Default + افتراضي - + Now playing: يقرأ الآن: - + Surah سورة - - Recitation not found - التلاوة غير موجودة - - - - The recitation files for the current surah is missing, would you like to download it? - ملفات التلاوة الخاصة بالسورة الحالية غير متوفرة، هل تود الذَّهاب إلى صفحة التحميل؟ - - - - + + + Files Missing الملفات مفقودة - + The selected font files are missing, would you like to download it? ملفات الخط المحدد مفقودة، هل ترغب في تحميلها؟ - + The selected tafsir is missing, would you like to download it? التفسير المحدد مفقود، هل ترغب في تحميله؟ - - Verse Of The Day - آية اليوم + + The selected translation is missing, would you like to download it? + الترجمة المحددة مفقود، هل ترغب في تحميله؟ + + + + Recitation not found + التلاوة غير موجودة + + + + The recitation files for the current surah is missing, would you like to download it? + ملفات التلاوة الخاصة بالسورة الحالية غير متوفرة، هل تود الذَّهاب إلى صفحة التحميل؟ AboutDialog - + About Quran Companion عن رفيق القرآن - + Quran Companion رفيق القرآن - + Version إصدار - + About عن البرنامَج - + A free, open-source Quran reader & player قارئ ومشغل للقرآن الكريم مجاني و مفتوح المصدر - + Useful Links روابط مفيدة - + Translators المترجمون - + Credits شكر وتقدير - + Recitations التلاوات - + Tafsir/Translations التفسير/الترجمات - + Surah Cards بطاقات السور - + Libraries المكتبات - + Licensed under the مرخص بموجب - + Waqf General Public License رخصة وقف العامة - + Project Homepage صفحة المشروع - + Report a bug/Request a feature التبليغ عن خلل / طلب ميزة - + Contribute to translations ساهم في الترجمة @@ -860,111 +829,185 @@ BookmarksDialog - + Bookmarks العلامات - + next التالي - + Left يسار - + previous السابق - + Right يمين - + No bookmarks available. Start bookmarking verses to see them here. لا توجد علامات متاحة. أضف علامات على الآيات لتراها هنا. - + Go to verse انتقل إلى الآية - + Remove إزالة - - + + Surah: سورة: - - + + Verse: آية: - + All الكل + + BookmarksNotifier + + + Verse added to bookmarks + تم إضافة علامة + + + + Verse removed from bookmarks + تم إزالة العلامة + + + + ContentDialog + + + Content + المحتوى + + + + Tafsir + التفسير + + + + Translation + الترجمة + + + + Thoughts + خواطر + + + + next + التالي + + + + Left + يسار + + + + previous + السابق + + + + Right + يمين + + + + Surah: + سورة: + + + + Verse: + آية: + + CopyDialog - + Advanced Copy النسخ المتقدم - + Surah سورة - + From من - + To إلى - + Invalid range نطاق غير صالح - + The entered verse range is invalid نطاق الآيات الذي تم إدخاله غير صالح + + CopyNotifier + + + Verse text copied to clipboard + تم نسخ نص الآية إلى الحافظة + + DownloadManager - + bytes بايت - + KB كب - + MB مب @@ -972,72 +1015,124 @@ DownloaderDialog - + Download Manager مدير التحميلات - + Add to queue إضافة تحميل - + Downloads التحميلات - + clear محو - + stop إيقاف - + Name الاسم - + Number الرقم - + Extras إضافات - + Additional files ملفات إضافية - + // Surah: // سورة: - + Downloading: جاري تحميل: - + /sec - + + Download Completed + تم التحميل + + + + Download Failed + فشل التحميل + + + + ImportExportDialog + + + Data Selection + اختيار البيانات + + + + Bookmarks + العلامات + + + + Khatmah + الختمات + + + + Thoughts + خواطر + + + + The following error occured during import + حدث الخطأ التالي أثناء الاستيراد + + + + + Error + خطأ + + + + The following error occured during export + حدث الخطأ التالي أثناء التصدير + + + + JobNotifier + + Download Completed تم التحميل - + Download Failed فشل التحميل @@ -1045,260 +1140,222 @@ KhatmahDialog - + Khatmah Dialog الختمات - + Current Khatmah: الختمة الحالية: - + Default افتراضي - + Start a new khatmah إنشاء ختمة جديدة - + Set as active تفعيل - + Remove إزالة - + Surah: سورة: - + Verse: آية: - + Khatmah ختمه - NotificationManager - - - Play/Pause recitation - تشغيل/إيقاف التلاوة - - - - Show window - إظهار النافذة - - - - Hide window - إخفاء النافذة - + PlayerControls - - Preferences - الإعدادات - - - - Check for updates - التحقق من وجود تحديثات - - - - About - عن البرنامَج - - - - Exit - خروج + + Reciter + القارئ - NotificationPopup - - - Download Completed - تم التحميل - - - - - QCF V2 - خط 2 - - - - Download Failed - فشل التحميل - - - - Verse added to bookmarks - تم إضافة علامة - + QFileDialog - - Verse removed from bookmarks - تم إزالة العلامة + + Open File + فتح ملف - - Verse text copied to clipboard - تم نسخ نص الآية إلى الحافظة - - - - You are running the latest version - لديك أحدث إصدار - - - - Update available - تحديث متاح + + Save File + حفظ الملف QuranPageBrowser - + Zoom In تكبير - + Zoom Out تصغير - + Copy Verse نسخ نص الآية - + Select اختر - + Play قراءة - + Tafsir التفسير - + + Translation + الترجمة + + + + Thoughts + خواطر + + + Add Bookmark اضافة علامة - + Remove Bookmark إزالة العلامة + + QuranReader + + + next + التالي + + + + previous + السابق + + SearchDialog - + Verse search بحث الآيات - + Search البحث - + Find البحث - + Search selected surahs only البحث في السور المختارة فقط - + Whole word كلمة كاملة - + next التالي - + Left يسار - + previous السابق - + Right يمين - + Filter تصفية - + Pages الصفحات - + From من - + To إلى - + Surahs السُور - + Search results ناتج بحث - + Surah: سورة: - + Verse: آية: @@ -1317,49 +1374,85 @@ - TafsirDialog + SystemTray - - Tafsir - التفسير + + Play/Pause recitation + تشغيل/إيقاف التلاوة - - next - التالي + + Show window + إظهار النافذة - - Left - يسار + + Hide window + إخفاء النافذة - - previous - السابق + + Preferences + الإعدادات - - Right - يمين + + Check for updates + التحقق من وجود تحديثات - - Surah: - سورة: + + About + عن البرنامَج - - Verse: - آية: + + Exit + خروج + + + + UpdateNotifier + + + Update available + تحديث متاح + + + + You are running the latest version + لديك أحدث إصدار VerseDialog - + Verse Of The Day آية اليوم + + VersionChecker + + + There are currently no updates available. + لا يوجد تحديثات متوفرة حاليا. + + + + Update info + معلومات التحديث + + + + Updates available, do you want to open the update tool? + هناك تحديثات متاحة، هل تود تشغيل مدير التحديثات؟ + + + + Updates info + معلومات التحديث + + diff --git a/dist/translations/qc_template.ts b/dist/translations/qc_template.ts index 23124d48..282b8683 100644 --- a/dist/translations/qc_template.ts +++ b/dist/translations/qc_template.ts @@ -224,212 +224,211 @@ - + Preferences - + General - + Theme - + Light - + Sepia - + Dark - + Language - + Audio output device - - + + Features - + Daily verse - + Missing recitation warning - + Reader - + Quran page - + Foreground Highlight - + Adaptive font size - + Reader mode - + Single page - + Double page - + Page font - + QCF V1 - - - + + + QCF V2 - - - + + + size - + Verse font - + QCF - + Uthmanic - + Uthmanic (annotated) - + Side content - + Font Family - - - Tafsir + + + Translation - - - Translation + + Shortcuts - - Shortcuts + + Tafsir - + Description - + Key - - - - + + + + Restart required - + Application theme was changed, restart now? - + Application language was changed, restart now? - + Reading mode was changed, restart now? - + Restart is required to load new quran font, restart now? - + Apply - + Cancel @@ -558,301 +557,271 @@ - - + + Quran Companion - - Reciter - - - - - next - - - - - previous - - - - + View - + Edit - + File - + Help - - + + Navigation - + Juz - + Page - + Verse - + Search surah - + Preferences - + Download manager - + Exit - + Find - + Check for updates - + Bookmarks - + About Quran Companion - - + + About Qt - + Tafsir - + Verse of the day - + + Khatmah - + Advanced copy - + Toggle reader view - - Khatmah - - - - - Default + + Import - - There are currently no updates available. + + Export - - - - Update info + + Player controls - - Updates available, do you want to open the update tool? - - - - - Updates info - - - - - Updates are available, use the maintainance tool to install the latest updates. + + Default - + Now playing: - + Surah - - Recitation not found + + + + Files Missing - - The recitation files for the current surah is missing, would you like to download it? + + The selected font files are missing, would you like to download it? - - - Files Missing + + The selected tafsir is missing, would you like to download it? - - The selected font files are missing, would you like to download it? + + The selected translation is missing, would you like to download it? - - The selected tafsir is missing, would you like to download it? + + Recitation not found - - Verse Of The Day + + The recitation files for the current surah is missing, would you like to download it? AboutDialog - + About Quran Companion - + Quran Companion - + Version - + About - + A free, open-source Quran reader & player - + Useful Links - + Translators - + Credits - + Recitations - + Tafsir/Translations - + Surah Cards - + Libraries - + Licensed under the - + Waqf General Public License - + Project Homepage - + Report a bug/Request a feature - + Contribute to translations @@ -860,111 +829,185 @@ BookmarksDialog - + Bookmarks - + next - + Left - + previous - + Right - + No bookmarks available. Start bookmarking verses to see them here. - + Go to verse - + Remove - - + + Surah: - - + + Verse: - + All + + BookmarksNotifier + + + Verse added to bookmarks + + + + + Verse removed from bookmarks + + + + + ContentDialog + + + Content + + + + + Tafsir + + + + + Translation + + + + + Thoughts + + + + + next + + + + + Left + + + + + previous + + + + + Right + + + + + Surah: + + + + + Verse: + + + CopyDialog - + Advanced Copy - + Surah - + From - + To - + Invalid range - + The entered verse range is invalid + + CopyNotifier + + + Verse text copied to clipboard + + + DownloadManager - + bytes - + KB - + MB @@ -972,333 +1015,347 @@ DownloaderDialog - + Download Manager - + Add to queue - + Downloads - + clear - + stop - + Name - + Number - + Extras - + Additional files - + // Surah: - + Downloading: - + /sec - + Download Completed - + Download Failed - KhatmahDialog + ImportExportDialog - - Khatmah Dialog - - - - - Current Khatmah: + + Data Selection - - Default - - - - - Start a new khatmah + + Bookmarks - - Set as active + + Khatmah - - Remove + + Thoughts - - Surah: + + The following error occured during import - - Verse: + + + Error - - Khatmah + + The following error occured during export - NotificationManager - - - Play/Pause recitation - - + JobNotifier - - Show window + + Download Completed - - Hide window + + Download Failed + + + KhatmahDialog - - Preferences + + Khatmah Dialog - - Check for updates + + Current Khatmah: - - About + + Default - - Exit + + Start a new khatmah - - - NotificationPopup - - Download Completed + + Set as active - - - QCF V2 + + Remove - - Download Failed + + Surah: - - Verse added to bookmarks + + Verse: - - Verse removed from bookmarks + + Khatmah + + + PlayerControls - - Verse text copied to clipboard + + Reciter + + + QFileDialog - - You are running the latest version + + Open File - - Update available + + Save File QuranPageBrowser - + Zoom In - + Zoom Out - + Copy Verse - + Select - + Play - + Tafsir - + + Translation + + + + + Thoughts + + + + Add Bookmark - + Remove Bookmark + + QuranReader + + + next + + + + + previous + + + SearchDialog - + Verse search - + Search - + Find - + Search selected surahs only - + Whole word - + next - + Left - + previous - + Right - + Filter - + Pages - + From - + To - + Surahs - + Search results - + Surah: - + Verse: @@ -1317,49 +1374,85 @@ - TafsirDialog + SystemTray - - Tafsir + + Play/Pause recitation - - next + + Show window - - Left + + Hide window - - previous + + Preferences - - Right + + Check for updates - - Surah: + + About - - Verse: + + Exit + + + + + UpdateNotifier + + + Update available + + + + + You are running the latest version VerseDialog - + Verse Of The Day + + VersionChecker + + + There are currently no updates available. + + + + + Update info + + + + + Updates available, do you want to open the update tool? + + + + + Updates info + + + diff --git a/dist/translations/qc_tr.ts b/dist/translations/qc_tr.ts index a111af16..cceeb4a0 100644 --- a/dist/translations/qc_tr.ts +++ b/dist/translations/qc_tr.ts @@ -224,212 +224,211 @@ Tafheem ul-Quran - English - + Preferences Ayarlar - + General Genel - + Theme Tema - + Light Açık - + Sepia Sepya - + Dark Koyu - + Language Dil - + Audio output device Ses Çıkış Cihazı - - + + Features Özellikler - + Daily verse Günlük ayet - + Missing recitation warning Eksik okuyucu uyarısı - + Reader Okuyucu - + Quran page Kuran sayfası - + Foreground Highlight Ön Plan Vurgusu - + Adaptive font size Uyarlanabilir yazı tipi boyutu - + Reader mode Okuyucu modu - + Single page Tek sayfa - + Double page Çift sayfa - + Page font Page font - + QCF V1 QCF V1 - - - + + + QCF V2 QCF V2 - - - + + + size size - + Verse font Verse font - + QCF QCF - + Uthmanic Uthmanic - + Uthmanic (annotated) Uthmanic (annotated) - + Side content Yan içerik - + Font Family Yazı Tipi Ailesi - - - Tafsir - Tefsir - - - - + + Translation Çeviri - + Shortcuts Kısayollar - + + Tafsir + Tefsir + + + Description Açıklama - + Key Tuş - - - - + + + + Restart required Yeniden başlatma gerekli - + Application theme was changed, restart now? Uygulama teması değiştirildi, şimdi yeniden başlatılsın mı? - + Application language was changed, restart now? Uygulama dili değiştirildi, şimdi yeniden başlatılsın mı? - + Reading mode was changed, restart now? Okuma modu değiştirildi, şimdi yeniden başlatılsın mı? - + Restart is required to load new quran font, restart now? Yeni Kuran yazı tipini yüklemek için yeniden başlatma gerekiyor, şimdi yeniden başlatılsın mı? - + Apply Uygula - + Cancel Vazgeç @@ -558,301 +557,271 @@ - - + + Quran Companion Kura-an'ı Kerim Arkadaşı - - Reciter - Okuyucu - - - - next - sonraki - - - - previous - önceki - - - + View Görünüm - + Edit Düzenle - + File Dosya - + Help Yardım - - + + Navigation Gezinme - + Juz Cüz - + Page Sayfa - + Verse Ayet - + Search surah Sure ara - + Preferences Ayarlar - + Download manager İndirme yöneticisi - + Exit Çıkış yap - + Find Bul - + Check for updates Güncellemeleri kontrol et - + Bookmarks Yer işaretleri - + About Quran Companion Kura-an'ı Kerim Arkadaşı hakkında - - + + About Qt Qt Hakkında - + Tafsir Tefsir - + Verse of the day Günün Ayeti - + + Khatmah Hatim - + Advanced copy Gelişmiş kopyalama - + Toggle reader view Toggle reader view - - Khatmah - Hatim - - - - Default - Varsayılan - - - - There are currently no updates available. - Yeni bir güncelleme yok. - - - - - - Update info - Güncelleme bilgisi + + Import + Import - - Updates available, do you want to open the update tool? - Güncellemeler mevcut, güncelleme aracını açmak ister misiniz? + + Export + Export - - Updates info - Güncelleme bilgisi + + Player controls + Player controls - - Updates are available, use the maintainance tool to install the latest updates. - Güncellemeler mevcuttur, en son güncellemeleri yüklemek için bakım aracını kullanın. + + Default + Varsayılan - + Now playing: Şimdi oynatılıyor: - + Surah Sure - - Recitation not found - Kıraat bulunamadı - - - - The recitation files for the current surah is missing, would you like to download it? - Güncel Surenin kıraat dosyaları eksik, indirmek ister misiniz? - - - - + + + Files Missing Files Missing - + The selected font files are missing, would you like to download it? The selected font files are missing, would you like to download it? - + The selected tafsir is missing, would you like to download it? The selected tafsir is missing, would you like to download it? - - Verse Of The Day - Günün Ayeti + + The selected translation is missing, would you like to download it? + The selected translation is missing, would you like to download it? + + + + Recitation not found + Kıraat bulunamadı + + + + The recitation files for the current surah is missing, would you like to download it? + Güncel Surenin kıraat dosyaları eksik, indirmek ister misiniz? AboutDialog - + About Quran Companion Kura-an'ı Kerim Arkadaşı hakkında - + Quran Companion Kura-an'ı Kerim Arkadaşı - + Version Version - + About - Hakkında + About - + A free, open-source Quran reader & player A free, open-source Quran reader & player - + Useful Links Useful Links - + Translators Translators - + Credits Credits - + Recitations Recitations - + Tafsir/Translations Tafsir/Translations - + Surah Cards Surah Cards - + Libraries Libraries - + Licensed under the Licensed under the - + Waqf General Public License Waqf General Public License - + Project Homepage Project Homepage - + Report a bug/Request a feature Report a bug/Request a feature - + Contribute to translations Contribute to translations @@ -860,111 +829,185 @@ BookmarksDialog - + Bookmarks Yer işaretleri - + next - sonraki + next - + Left Sol - + previous - önceki + previous - + Right Sağ - + No bookmarks available. Start bookmarking verses to see them here. Yer işareti yok. Ayetleri burada görmek için yer işareti eklemeye başlayın. - + Go to verse Ayete git - + Remove Kaldır - - + + Surah: Sure: - - + + Verse: Ayet: - + All Tamamı + + BookmarksNotifier + + + Verse added to bookmarks + Verse added to bookmarks + + + + Verse removed from bookmarks + Verse removed from bookmarks + + + + ContentDialog + + + Content + Content + + + + Tafsir + Tefsir + + + + Translation + Çeviri + + + + Thoughts + Thoughts + + + + next + next + + + + Left + Sol + + + + previous + previous + + + + Right + Sağ + + + + Surah: + Sure: + + + + Verse: + Ayet: + + CopyDialog - + Advanced Copy Gelişmiş Kopyalama - + Surah Sure - + From Kimden - + To Alıcı - + Invalid range Geçersiz aralık - + The entered verse range is invalid Girilen Ayet aralığı geçersiz + + CopyNotifier + + + Verse text copied to clipboard + Verse text copied to clipboard + + DownloadManager - + bytes bayt - + KB KB - + MB MB @@ -972,72 +1015,124 @@ DownloaderDialog - + Download Manager İndirme yöneticisi - + Add to queue Kuyruğa ekle - + Downloads İndirilenler - + clear temizle - + stop dur - + Name Ad - + Number Numara - + Extras Extras - + Additional files Additional files - + // Surah: // Sure: - + Downloading: İndiriliyor: - + /sec /sn - + Download Completed İndirme tamamlandı - + + Download Failed + İndirme Başarısız Oldu + + + + ImportExportDialog + + + Data Selection + Data Selection + + + + Bookmarks + Yer işaretleri + + + + Khatmah + Hatim + + + + Thoughts + Thoughts + + + + The following error occured during import + The following error occured during import + + + + + Error + Error + + + + The following error occured during export + The following error occured during export + + + + JobNotifier + + + Download Completed + İndirme tamamlandı + + + Download Failed İndirme Başarısız Oldu @@ -1045,260 +1140,222 @@ KhatmahDialog - + Khatmah Dialog Hatim iletişim kutusu - + Current Khatmah: Şu anki hatim: - + Default Varsayılan - + Start a new khatmah Yeni hatime başla - + Set as active Etkin olarak ayarla - + Remove Kaldır - + Surah: Sure: - + Verse: Ayet: - + Khatmah Hatim - NotificationManager - - - Play/Pause recitation - Okumayı Oynat/Duraklat - - - - Show window - Pencereyi göster - - - - Hide window - Pencereyi gizle - + PlayerControls - - Preferences - Ayarlar - - - - Check for updates - Güncellemeleri kontrol et - - - - About - Hakkında - - - - Exit - Çıkış yap + + Reciter + Reciter - NotificationPopup - - - Download Completed - İndirme tamamlandı - - - - - QCF V2 - QCF V2 - + QFileDialog - - Download Failed - İndirme Başarısız Oldu - - - - Verse added to bookmarks - Ayet yer işaretlerine eklendi - - - - Verse removed from bookmarks - Ayet yer işaretlerinden silindi - - - - Verse text copied to clipboard - Ayet metni panoya kopyalandı - - - - You are running the latest version - En son sürümü çalıştırıyorsunuz + + Open File + Open File - - Update available - Güncelleme var + + Save File + Save File QuranPageBrowser - + Zoom In Yakınlaştır - + Zoom Out Uzaklaştır - + Copy Verse Ayeti kopyala - + Select Seç - + Play Oynat - + Tafsir Tefsir - + + Translation + Çeviri + + + + Thoughts + Thoughts + + + Add Bookmark Yer işareti Ekle - + Remove Bookmark Yer imini kaldır + + QuranReader + + + next + next + + + + previous + previous + + SearchDialog - + Verse search Ayet ara - + Search Ara - + Find Bul - + Search selected surahs only Yalnızca seçilen Sureleri ara - + Whole word Tüm kelime - + next - sonraki + next - + Left Sol - + previous - önceki + previous - + Right Sağ - + Filter Süzgeç - + Pages Sayfalar - + From Kimden - + To Alıcı - + Surahs Sureler - + Search results Arama sonuçları - + Surah: Sure: - + Verse: Ayet: @@ -1317,49 +1374,85 @@ - TafsirDialog + SystemTray - - Tafsir - Tefsir + + Play/Pause recitation + Play/Pause recitation - - next - sonraki + + Show window + Show window - - Left - Sol + + Hide window + Hide window - - previous - önceki + + Preferences + Ayarlar - - Right - Sağ + + Check for updates + Güncellemeleri kontrol et - - Surah: - Sure: + + About + About - - Verse: - Ayet: + + Exit + Çıkış yap + + + + UpdateNotifier + + + Update available + Update available + + + + You are running the latest version + You are running the latest version VerseDialog - + Verse Of The Day Günün Ayeti + + VersionChecker + + + There are currently no updates available. + There are currently no updates available. + + + + Update info + Update info + + + + Updates available, do you want to open the update tool? + Updates available, do you want to open the update tool? + + + + Updates info + Updates info + + diff --git a/dist/translations/update_translations.sh b/dist/translations/update_translations.sh index a86a8420..e871dee8 100755 --- a/dist/translations/update_translations.sh +++ b/dist/translations/update_translations.sh @@ -42,5 +42,5 @@ prompt -s "----- Generating tafasir TS file -----\n" "$SCRIPT_DIR/tafasir.sh" prompt -s "----- Updating template TS file -----\n" -lupdate -recursive -no-obsolete "$SCRIPT_DIR/../../src" -ts "$SCRIPT_DIR/qc_src.ts" -lconvert -i "$SCRIPT_DIR/shortcuts.ts" "$SCRIPT_DIR/reciters.ts" "$SCRIPT_DIR/tafasir.ts" "$SCRIPT_DIR/qc_src.ts" -no-obsolete -o qc_template.ts +~/Qt/6.6.1/gcc_64/bin/lupdate -recursive -no-obsolete "$SCRIPT_DIR/../../src" -ts "$SCRIPT_DIR/qc_src.ts" +~/Qt/6.6.1/gcc_64/bin/lconvert -i "$SCRIPT_DIR/shortcuts.ts" "$SCRIPT_DIR/reciters.ts" "$SCRIPT_DIR/tafasir.ts" "$SCRIPT_DIR/qc_src.ts" -no-obsolete -o qc_template.ts diff --git a/dist/xdg/io.github._0xzer0x.qurancompanion.metainfo.xml b/dist/xdg/io.github._0xzer0x.qurancompanion.metainfo.xml index 0c70fd45..bf06db88 100644 --- a/dist/xdg/io.github._0xzer0x.qurancompanion.metainfo.xml +++ b/dist/xdg/io.github._0xzer0x.qurancompanion.metainfo.xml @@ -6,6 +6,9 @@ MIT LGPL-3.0-or-later + + Youssef Fathy +

Quran Companion is a cross-platform Quran reader and player with recitation download capabilities, verse highlighting, resizable quran font, and a variety of tafsir books and translations

@@ -16,25 +19,44 @@ https://github.com/0xzer0x/quran-companion/issues - https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/light.png + The main application window in light theme. + https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/light.png - https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/dark.png + The main application window in dark theme. + https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/dark.png - https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/sepia.png + The main application window in sepia theme. + https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/sepia.png - https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/ar_light.png + The main application window in light theme with Arabic UI. + https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/ar_light.png - https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/ar_dark.png + The main application window in dark theme with Arabic UI. + https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/ar_dark.png - https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/ar_two-page.png + The main application window in dark theme with Arabic UI and two page mode. + https://raw.githubusercontent.com/0xzer0x/quran-companion/main/screenshots/ar_two-page.png + + +
    +
  • Bugfixes
  • +
  • Added Pickthall - English translation
  • +
  • Disabled Auto-play when navigating to a Surah
  • +
  • Added Thoughts feature
  • +
  • Changed tafsir dialog to display all available content types (tafsir, translation, thoughts)
  • +
  • Move tafsir setting to content dialog
  • +
  • Added user data import/export functionality
  • +
+
+
    diff --git a/extras/translations/en_pickthall.db b/extras/translations/en_pickthall.db new file mode 100644 index 00000000..5f4377cb Binary files /dev/null and b/extras/translations/en_pickthall.db differ diff --git a/resources/dark/dark.qss b/resources/dark/dark.qss index e04d7aff..06ba21ef 100644 --- a/resources/dark/dark.qss +++ b/resources/dark/dark.qss @@ -71,14 +71,14 @@ QWidget#shortcutsTab { border: none; } -QAbstractScrollArea#tedTafsir { +QAbstractScrollArea#tedContent { border: 1px solid #222222; border-radius: 4px; } /* --------- Player Styles --------- */ -QFrame#framePlayer { +QWidget#PlayerControls QFrame#playerFrame { background-color: #202020; border-radius: 8px; } diff --git a/resources/files.xml b/resources/files.xml index 59119258..262af89b 100644 --- a/resources/files.xml +++ b/resources/files.xml @@ -12,7 +12,7 @@ - + @@ -29,6 +29,7 @@ + diff --git a/resources/light/light.qss b/resources/light/light.qss index df428ee1..72e8163c 100644 --- a/resources/light/light.qss +++ b/resources/light/light.qss @@ -71,7 +71,7 @@ QWidget#searchTab { border: none; } -QTextEdit#tedTafsir { +QTextEdit#tedContent { border: 1px solid #e3e3e3; background-color: #fafafa; border-radius: 4px; @@ -79,7 +79,7 @@ QTextEdit#tedTafsir { /* --------- Player Styles --------- */ -QFrame#framePlayer { +QWidget#PlayerControls QFrame#playerFrame { background-color: #fafafa; border-radius: 8px; border: 1px solid #e3e3e3; diff --git a/resources/light/sepia.qss b/resources/light/sepia.qss index a766d56f..0cffe5fd 100644 --- a/resources/light/sepia.qss +++ b/resources/light/sepia.qss @@ -67,7 +67,7 @@ QWidget#searchTab { border: none; } -QTextEdit#tedTafsir { +QTextEdit#tedContent { border: 1px solid #efe3cd; background-color: #f7ebd5; border-radius: 4px; @@ -75,7 +75,7 @@ QTextEdit#tedTafsir { /* --------- Player Styles --------- */ -QFrame#framePlayer { +QWidget#PlayerControls QFrame#playerFrame { background-color: #f7ebd5; border-radius: 8px; border: 1px solid #d9cdb8; diff --git a/resources/reciters.xml b/resources/reciters.xml index 67ac54a2..287a394e 100644 --- a/resources/reciters.xml +++ b/resources/reciters.xml @@ -29,7 +29,7 @@ + url="https://verses.quran.com/Alafasy/mp3/" useid="0" /> - diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index eb253ef3..3d81f402 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: quran-companion base: core22 -version: '1.2.3' +version: '1.2.4' title: Quran Companion website: https://github.com/0xzer0x/quran-companion source-code: https://github.com/0xzer0x/quran-companion diff --git a/src/core/copydialog.h b/src/core/copydialog.h deleted file mode 100644 index 7fede8d0..00000000 --- a/src/core/copydialog.h +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef COPYDIALOG_H -#define COPYDIALOG_H - -#include "../globals.h" -#include "../utils/dbmanager.h" -#include -#include -#include -#include - -namespace Ui { -class CopyDialog; -} - -class CopyDialog : public QDialog -{ - Q_OBJECT - -public: - explicit CopyDialog(QWidget* parent); - void show(const Verse& curr); - ~CopyDialog(); - -protected: - void closeEvent(QCloseEvent* event); - -private: - Ui::CopyDialog* ui; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); - void copyRange(); - int m_surah = -1; - int m_surahCnt = 0; - QIntValidator* m_verseValidator = new QIntValidator(this); -}; - -#endif // COPYDIALOG_H diff --git a/src/core/mainwindow.cpp b/src/core/mainwindow.cpp index b48c184c..d97fd136 100644 --- a/src/core/mainwindow.cpp +++ b/src/core/mainwindow.cpp @@ -4,365 +4,263 @@ */ #include "mainwindow.h" -#include "../widgets/aboutdialog.h" -#include "../widgets/clickablelabel.h" -#include "khatmahdialog.h" #include "ui_mainwindow.h" +#include +#include +#include +#include using namespace fa; +using std::make_pair; MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) , ui(new Ui::MainWindow) - , m_process(new QProcess(this)) - , m_shortcutHandler(new ShortcutHandler(this)) + , m_verseValidator(new QIntValidator(this)) + , m_currVerse(Verse::getCurrent()) + , m_config(Configuration::getInstance()) + , m_shortcutHandler(ShortcutHandler::getInstance()) + , m_bookmarksDb(BookmarksDb::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_translationDb(TranslationDb::getInstance()) + , m_reciters(Reciter::reciters) + , m_tafasir(Tafsir::tafasir) { ui->setupUi(this); - ui->frmCenteralCont->setLayoutDirection(Qt::LeftToRight); loadIcons(); - loadSettings(); - init(); + loadVerse(); + loadComponents(); - if (m_settings->value("WindowState").isNull()) - m_settings->setValue("WindowState", saveState()); + if (m_config.settings().value("WindowState").isNull()) + m_config.settings().setValue("WindowState", saveState()); else - restoreState(m_settings->value("WindowState").toByteArray()); + restoreState(m_config.settings().value("WindowState").toByteArray()); // connectors setupShortcuts(); setupConnections(); setupSurahsDock(); - setupMenubarToggle(); + setupMenubarButton(); this->show(); - m_notifyMgr->setTooltip(tr("Quran Companion")); - if (m_settings->value("VOTD").toBool()) - m_verseDlg->showVOTD(true); - - m_popup->dockLocationChanged(dockWidgetArea(ui->sideDock)); + m_popup->setDockArea(dockWidgetArea(ui->sideDock)); } void MainWindow::loadIcons() { - ui->btnNext->setIcon(m_fa->icon(fa_solid, fa_arrow_left)); - ui->btnPrev->setIcon(m_fa->icon(fa_solid, fa_arrow_right)); - - ui->actionKhatmah->setIcon(m_fa->icon(fa_solid, fa_list)); - ui->actionDownloadManager->setIcon(m_fa->icon(fa_solid, fa_download)); - ui->actionExit->setIcon(m_fa->icon(fa_solid, fa_xmark)); - ui->actionFind->setIcon(m_fa->icon(fa_solid, fa_magnifying_glass)); - ui->actionTafsir->setIcon(m_fa->icon(fa_solid, fa_book_open)); - ui->actionVOTD->setIcon(m_fa->icon(fa_solid, fa_calendar_day)); - ui->actionBookmarks->setIcon(m_fa->icon(fa_solid, fa_bookmark)); - ui->actionPereferences->setIcon(m_fa->icon(fa_solid, fa_gear)); - ui->actionAdvancedCopy->setIcon(m_fa->icon(fa_solid, fa_clipboard)); - ui->actionReaderViewToggle->setIcon(m_fa->icon(fa_solid, fa_columns)); - ui->actionUpdates->setIcon(m_fa->icon(fa_solid, fa_arrow_rotate_right)); - - ui->btnPlay->setIcon(m_fa->icon(fa_solid, fa_play)); - ui->btnPause->setIcon(m_fa->icon(fa_solid, fa_pause)); - ui->btnStop->setIcon(m_fa->icon(fa_solid, fa_stop)); - - ui->lbSpeaker->setText(QString(fa_volume_high)); - ui->lbSpeaker->setFont(m_fa->font(fa_solid, 16)); + fa::QtAwesome& awesome = StyleManager::getInstance().awesome(); + ui->actionKhatmah->setIcon(awesome.icon(fa_solid, fa_list)); + ui->actionDownloadManager->setIcon(awesome.icon(fa_solid, fa_download)); + ui->actionExit->setIcon(awesome.icon(fa_solid, fa_xmark)); + ui->actionFind->setIcon(awesome.icon(fa_solid, fa_magnifying_glass)); + ui->actionTafsir->setIcon(awesome.icon(fa_solid, fa_book_open)); + ui->actionVOTD->setIcon(awesome.icon(fa_solid, fa_calendar_day)); + ui->actionBookmarks->setIcon(awesome.icon(fa_solid, fa_bookmark)); + ui->actionPereferences->setIcon(awesome.icon(fa_solid, fa_gear)); + ui->actionAdvancedCopy->setIcon(awesome.icon(fa_solid, fa_clipboard)); + ui->actionReaderViewToggle->setIcon(awesome.icon(fa_solid, fa_columns)); + ui->actionUpdates->setIcon(awesome.icon(fa_solid, fa_arrow_rotate_right)); + ui->actionImport->setIcon(awesome.icon(fa_solid, fa_file_arrow_down)); + ui->actionExport->setIcon(awesome.icon(fa_solid, fa_file_arrow_up)); } void -MainWindow::loadSettings() +MainWindow::loadVerse() { - int id = m_settings->value("Reader/Khatmah").toInt(); - m_dbMgr->setActiveKhatmah(id); - if (!m_dbMgr->getKhatmahPos(id, m_currVerse)) { - QString name = id ? tr("Khatmah ") + QString::number(id) : tr("Default"); - m_dbMgr->addKhatmah(m_currVerse, name, id); + int id = m_config.settings().value("Reader/Khatmah").toInt(); + Verse v = m_currVerse; + m_bookmarksDb.setActiveKhatmah(id); + if (!m_bookmarksDb.loadVerse(id, v)) { + QString name = + id ? tr("Khatmah") + " " + QString::number(id) : tr("Default"); + m_bookmarksDb.addKhatmah(v, name, id); } + m_currVerse.update(v); } void -MainWindow::loadReader() +MainWindow::loadComponents() { - if (m_readerMode == ReaderMode::SinglePage) { - m_activeQuranBrowser = m_quranBrowsers[0] = - new QuranPageBrowser(ui->frmPageContent, m_currVerse.page); - - QWidget* scrollWidget = new QWidget(); - scrollWidget->setObjectName("scrollWidget"); - QVBoxLayout* vbl = new QVBoxLayout(); - vbl->setDirection(QBoxLayout::BottomToTop); - scrollWidget->setLayout(vbl); - - m_scrlVerseByVerse = new QScrollArea; - m_scrlVerseByVerse->setWidget(scrollWidget); - m_scrlVerseByVerse->setWidgetResizable(true); - m_scrlVerseByVerse->setStyleSheet( - "QLabel, QAbstractScrollArea, QWidget#scrollWidget { " - "background: " - "transparent }"); - - QHBoxLayout* lyt = qobject_cast(ui->frmReader->layout()); - ui->frmSidePanel->layout()->addWidget(m_scrlVerseByVerse); - lyt->setStretch(0, 1); - lyt->setStretch(1, 0); - ui->frmCenteralCont->setMinimumWidth(900); - } + m_player = + new VersePlayer(this, m_config.settings().value("Reciter", 0).toInt()); + m_reader = new QuranReader(this, m_player); + m_playerControls = new PlayerControls(this, m_player, m_reader); + m_settingsDlg = new SettingsDialog(this, m_player); + m_popup = new NotificationPopup(this); + m_betaqaViewer = new BetaqaViewer(this); + m_verseDlg = new VerseDialog(this); + m_cpyDlg = new CopyDialog(this); + m_systemTray = new SystemTray(this); + m_contentDlg = new ContentDialog(this); + m_jobMgr = new JobManager(this); + m_versionChecker = new VersionChecker(this); + m_selectorDlg = new FileSelector(this); + m_importExportDlg = + new ImportExportDialog(this, + QSharedPointer::create(), + QSharedPointer::create()); + + QHBoxLayout* controls = new QHBoxLayout(); + QFrame* controlsFrame = new QFrame(this); + controls->setAlignment(Qt::AlignCenter); + controls->setContentsMargins(0, 0, 0, 0); + controls->setSpacing(0); + controls->addStretch(1); + controls->addWidget(m_playerControls); + controls->addStretch(1); + controlsFrame->setLayout(controls); + ui->scrollAreaWidgetContents->layout()->addWidget(controlsFrame); + ui->scrollAreaWidgetContents->layout()->addWidget(m_reader); - else { - // even Quran pages are always on the left side - if (m_currVerse.page % 2 == 0) { - m_quranBrowsers[0] = - new QuranPageBrowser(ui->frmPageContent, m_currVerse.page - 1); - m_activeQuranBrowser = m_quranBrowsers[1] = - new QuranPageBrowser(ui->frmSidePanel, m_currVerse.page); - - } else { - m_activeQuranBrowser = m_quranBrowsers[0] = - new QuranPageBrowser(ui->frmPageContent, m_currVerse.page); - m_quranBrowsers[1] = - new QuranPageBrowser(ui->frmSidePanel, m_currVerse.page + 1); - } - - ui->frmSidePanel->layout()->addWidget(m_quranBrowsers[1]); - QHBoxLayout* lyt = qobject_cast(ui->frmReader->layout()); - lyt->insertSpacerItem(0, new QSpacerItem(20, 20, QSizePolicy::Expanding)); - lyt->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Expanding)); - - lyt->setStretch(0, 1); - lyt->setStretch(1, 0); - lyt->setStretch(2, 0); - lyt->setStretch(3, 1); - } + ui->cmbPage->setValidator(new QIntValidator(1, 604, this)); - ui->frmPageContent->layout()->addWidget(m_quranBrowsers[0]); -} + setVerseComboBoxRange(true); -void -MainWindow::checkForUpdates() -{ -#if defined Q_OS_WIN - QFileInfo tool(m_updateToolPath); - if (tool.exists()) { - m_process->setWorkingDirectory(QApplication::applicationDirPath()); - m_process->start(m_updateToolPath, QStringList("ch")); - return; + for (int i = 1; i < 605; i++) { + ui->cmbPage->addItem(QString::number(i)); } -#endif - m_downManPtr->getLatestVersion(); + // sets without emitting signal + setCmbVerseIdx(m_currVerse.number() - 1); + setCmbJuzIdx(m_quranDb.getJuzOfPage(m_currVerse.page()) - 1); + + ui->cmbPage->setCurrentIndex(m_currVerse.page() - 1); } void -MainWindow::updateProcessCallback() +MainWindow::setupConnections() { - QString output = m_process->readAll(); - QString displayText; - - if (output.contains("There are currently no updates available.")) { - displayText = tr("There are currently no updates available."); - - if (this->isVisible()) - QMessageBox::information(this, tr("Update info"), displayText); - else - m_notifyMgr->notify(tr("Update info"), displayText); - } - - else { - displayText = tr("Updates available, do you want to open the update tool?"); - if (this->isVisible()) { - QMessageBox::StandardButton btn = - QMessageBox::question(this, tr("Updates info"), displayText); - if (btn == QMessageBox::Yes) - m_process->startDetached(m_updateToolPath); - } - - else { - m_notifyMgr->notify( - tr("Update info"), - tr("Updates are available, use the maintainance tool to install " - "the latest updates.")); - } - } + connectMenubar(); + connectTray(); + connectReader(); + connectPlayer(); + connectControls(); + connectSettings(); + connectNotifiers(); } void MainWindow::setupShortcuts() { - connect(m_shortcutHandler, - &ShortcutHandler::togglePlayerControls, - this, - &MainWindow::togglePlayerControls); - connect(m_shortcutHandler, - &ShortcutHandler::toggleReaderView, - this, - &MainWindow::toggleReaderView); - connect(m_shortcutHandler, - &ShortcutHandler::toggleMenubar, - this, - &MainWindow::toggleMenubar); - connect(m_shortcutHandler, - &ShortcutHandler::toggleNavDock, - this, - &MainWindow::toggleNavDock); - connect(m_shortcutHandler, - &ShortcutHandler::togglePlayback, - this, - &MainWindow::togglePlayback); - connect(m_shortcutHandler, - &ShortcutHandler::incrementVolume, - this, - &MainWindow::incrementVolume); - connect(m_shortcutHandler, - &ShortcutHandler::decrementVolume, - this, - &MainWindow::decrementVolume); - connect(m_shortcutHandler, - &ShortcutHandler::bookmarkCurrent, - this, - &MainWindow::addCurrentToBookmarks); - connect(m_shortcutHandler, - &ShortcutHandler::nextPage, - this, - &MainWindow::btnNextClicked); - connect(m_shortcutHandler, - &ShortcutHandler::prevPage, - this, - &MainWindow::btnPrevClicked); - connect(m_shortcutHandler, - &ShortcutHandler::nextVerse, - this, - &MainWindow::nextVerse); - connect(m_shortcutHandler, - &ShortcutHandler::prevVerse, - this, - &MainWindow::prevVerse); - connect( - m_shortcutHandler, &ShortcutHandler::nextJuz, this, &MainWindow::nextJuz); + m_shortcutHandler.createShortcuts(this); connect( - m_shortcutHandler, &ShortcutHandler::prevJuz, this, &MainWindow::prevJuz); - connect(m_shortcutHandler, - &ShortcutHandler::nextSurah, - this, - &MainWindow::nextSurah); - connect(m_shortcutHandler, - &ShortcutHandler::prevSurah, - this, - &MainWindow::prevSurah); - connect(m_shortcutHandler, - &ShortcutHandler::openDownloads, - this, - &MainWindow::actionDMTriggered); - connect(m_shortcutHandler, - &ShortcutHandler::openBookmarks, - this, - &MainWindow::actionBookmarksTriggered); - connect(m_shortcutHandler, - &ShortcutHandler::openKhatmah, - this, - &MainWindow::actionKhatmahTriggered); - connect(m_shortcutHandler, - &ShortcutHandler::openSearch, - this, - &MainWindow::actionSearchTriggered); - connect(m_shortcutHandler, - &ShortcutHandler::openSettings, + &m_shortcutHandler, &ShortcutHandler::togglePlayerControls, this, [this]() { + ui->actionPlayerControls->setChecked( + !ui->actionPlayerControls->isChecked()); + }); + + for (const auto& connection : { + make_pair(&ShortcutHandler::toggleMenubar, &MainWindow::toggleMenubar), + make_pair(&ShortcutHandler::toggleNavDock, &MainWindow::toggleNavDock), + make_pair(&ShortcutHandler::nextVerse, &MainWindow::nextVerse), + make_pair(&ShortcutHandler::prevVerse, &MainWindow::prevVerse), + make_pair(&ShortcutHandler::nextSurah, &MainWindow::nextSurah), + make_pair(&ShortcutHandler::prevSurah, &MainWindow::prevSurah), + make_pair(&ShortcutHandler::nextJuz, &MainWindow::nextJuz), + make_pair(&ShortcutHandler::prevJuz, &MainWindow::prevJuz), + make_pair(&ShortcutHandler::bookmarkCurrent, + &MainWindow::bookmarkCurrent), + make_pair(&ShortcutHandler::openDownloads, + &MainWindow::actionDMTriggered), + make_pair(&ShortcutHandler::openBookmarks, + &MainWindow::actionBookmarksTriggered), + make_pair(&ShortcutHandler::openKhatmah, + &MainWindow::actionKhatmahTriggered), + make_pair(&ShortcutHandler::openSearch, + &MainWindow::actionSearchTriggered), + make_pair(&ShortcutHandler::openSettings, + &MainWindow::actionPrefTriggered), + make_pair(&ShortcutHandler::openContent, + &MainWindow::actionTafsirTriggered), + make_pair(&ShortcutHandler::openAdvancedCopy, + &MainWindow::actionAdvancedCopyTriggered), + }) { + connect(&m_shortcutHandler, connection.first, this, connection.second); + } +} + +void +MainWindow::connectPlayer() +{ + connect(m_player, + &VersePlayer::missingVerseFile, this, - &MainWindow::actionPrefTriggered); - connect(m_shortcutHandler, - &ShortcutHandler::openTafsir, + &MainWindow::missingRecitationFileWarn); + connect(m_player, + &QMediaPlayer::playbackStateChanged, this, - &MainWindow::actionTafsirTriggered); - connect(m_shortcutHandler, - &ShortcutHandler::openAdvancedCopy, + &MainWindow::updateTrayTooltip); + connect(m_player, + &QMediaPlayer::mediaStatusChanged, this, - &MainWindow::actionAdvancedCopyTriggered); - - for (int i = 0; i <= 1; i++) { - if (m_quranBrowsers[i]) { - connect(m_shortcutHandler, - &ShortcutHandler::zoomIn, - m_quranBrowsers[i], - &QuranPageBrowser::actionZoomIn); - connect(m_shortcutHandler, - &ShortcutHandler::zoomOut, - m_quranBrowsers[i], - &QuranPageBrowser::actionZoomOut); - } - } + &MainWindow::mediaStatusChanged); } void -MainWindow::setupConnections() +MainWindow::connectTray() { - - /* ------------------ UI connectors ------------------ */ - - // ########## Menubar ########## // - connect(ui->actionExit, &QAction::triggered, this, &QApplication::exit); - connect(ui->actionPereferences, - &QAction::triggered, - this, - &MainWindow::actionPrefTriggered); - connect(ui->actionDownloadManager, - &QAction::triggered, - this, - &MainWindow::actionDMTriggered); - connect(ui->actionFind, - &QAction::triggered, - this, - &MainWindow::actionSearchTriggered); - connect(ui->actionTafsir, - &QAction::triggered, - this, - &MainWindow::actionTafsirTriggered); - connect(ui->actionVOTD, - &QAction::triggered, - this, - &MainWindow::actionVotdTriggered); - connect(ui->actionAdvancedCopy, - &QAction::triggered, - this, - &MainWindow::actionAdvancedCopyTriggered); - connect( - ui->actionUpdates, &QAction::triggered, this, &MainWindow::checkForUpdates); - connect( - m_process, &QProcess::finished, this, &MainWindow::updateProcessCallback); - connect(ui->actionBookmarks, - &QAction::triggered, - this, - &MainWindow::actionBookmarksTriggered); - connect(ui->actionKhatmah, - &QAction::triggered, - this, - &MainWindow::actionKhatmahTriggered); - connect(ui->actionAboutQC, - &QAction::triggered, + connect(m_systemTray, &SystemTray::exit, this, &QApplication::exit); + connect(m_systemTray, + &SystemTray::togglePlayback, + m_playerControls, + &PlayerControls::togglePlayback); + connect(m_systemTray, &SystemTray::showWindow, this, &MainWindow::show); + connect(m_systemTray, &SystemTray::hideWindow, this, &MainWindow::hide); + connect(m_systemTray, + &SystemTray::checkForUpdates, + m_versionChecker, + &VersionChecker::checkUpdates); + connect(m_systemTray, + &SystemTray::openAbout, this, &MainWindow::actionAboutTriggered); - connect(ui->actionReaderViewToggle, - &QAction::triggered, + connect(m_systemTray, + &SystemTray::openPrefs, this, - &MainWindow::toggleReaderView); + &MainWindow::actionPrefTriggered); +} + +void +MainWindow::connectReader() +{ connect(m_verseDlg, &VerseDialog::navigateToVerse, - this, - &MainWindow::navigateToVerse); - - // ########## Quran page ########## // - connect(m_quranBrowsers[0], - &QTextBrowser::anchorClicked, - this, - &MainWindow::verseAnchorClicked); - if (m_quranBrowsers[1]) { - connect(m_quranBrowsers[1], - &QTextBrowser::anchorClicked, - this, - &MainWindow::verseAnchorClicked); - } - + m_reader, + &QuranReader::navigateToVerse); + connect(m_reader, + &QuranReader::copyVerseText, + m_cpyDlg, + &CopyDialog::copyVerseText); + connect(m_reader, + &QuranReader::showVerseTafsir, + m_contentDlg, + &ContentDialog::showVerseTafsir); + connect(m_reader, + &QuranReader::showVerseTranslation, + m_contentDlg, + &ContentDialog::showVerseTranslation); + connect(m_reader, + &QuranReader::showVerseThoughts, + m_contentDlg, + &ContentDialog::showVerseThoughts); + connect(m_reader, + &QuranReader::showBetaqa, + m_betaqaViewer, + &BetaqaViewer::showSurah); + connect(m_contentDlg, + &ContentDialog::missingTafsir, + this, + &MainWindow::missingTafsir); + connect(m_contentDlg, + &ContentDialog::missingTranslation, + this, + &MainWindow::missingTranslation); +} + +void +MainWindow::connectControls() +{ // ########## page controls ########## // - connect( - ui->btnNext, &QPushButton::clicked, this, &MainWindow::btnNextClicked); - connect( - ui->btnPrev, &QPushButton::clicked, this, &MainWindow::btnPrevClicked); connect(ui->cmbPage, &QComboBox::currentIndexChanged, this, @@ -375,11 +273,22 @@ MainWindow::setupConnections() &QComboBox::currentIndexChanged, this, &MainWindow::cmbJuzChanged); - connect(m_player, - &VersePlayer::missingVerseFile, + connect(m_reader, + &QuranReader::currentVerseChanged, this, - &MainWindow::missingRecitationFileWarn); - + &MainWindow::currentVerseChanged); + connect(m_reader, + &QuranReader::currentSurahChanged, + this, + &MainWindow::currentSurahChanged); + connect(m_playerControls, + &PlayerControls::currentVerseChanged, + this, + &MainWindow::currentVerseChanged); + connect(m_playerControls, + &PlayerControls::currentSurahChanged, + this, + &MainWindow::currentSurahChanged); // ########## navigation dock ########## // connect(ui->lineEditSearchSurah, &QLineEdit::textChanged, @@ -389,84 +298,11 @@ MainWindow::setupConnections() &QListView::clicked, this, &MainWindow::listSurahNameClicked); +} - // ########## audio slider ########## // - connect(m_player, - &QMediaPlayer::positionChanged, - this, - &MainWindow::mediaPosChanged); - connect(m_player, - &QMediaPlayer::playbackStateChanged, - this, - &MainWindow::mediaStateChanged); - connect(ui->sldrAudioPlayer, - &QSlider::sliderMoved, - m_player, - &QMediaPlayer::setPosition); - connect(ui->sldrVolume, - &QSlider::valueChanged, - this, - &MainWindow::volumeSliderValueChanged); - - // ########## player control ########## // - connect(m_player, - &QMediaPlayer::mediaStatusChanged, - this, - &MainWindow::mediaStatusChanged); - connect( - ui->btnPlay, &QPushButton::clicked, this, &MainWindow::btnPlayClicked); - connect( - ui->btnPause, &QPushButton::clicked, this, &MainWindow::btnPauseClicked); - connect( - ui->btnStop, &QPushButton::clicked, this, &MainWindow::btnStopClicked); - connect(ui->cmbReciter, - &QComboBox::currentIndexChanged, - m_player, - &VersePlayer::changeReciter); - - // ########## system tray ########## // - connect(m_notifyMgr, &NotificationManager::exit, this, &QApplication::exit); - connect(m_notifyMgr, - &NotificationManager::togglePlayback, - this, - &MainWindow::togglePlayback); - connect( - m_notifyMgr, &NotificationManager::showWindow, this, &MainWindow::show); - connect( - m_notifyMgr, &NotificationManager::hideWindow, this, &MainWindow::hide); - connect(m_notifyMgr, - &NotificationManager::checkForUpdates, - this, - &MainWindow::checkForUpdates); - connect(m_notifyMgr, - &NotificationManager::openAbout, - this, - &MainWindow::actionAboutTriggered); - connect(m_notifyMgr, - &NotificationManager::openPrefs, - this, - &MainWindow::actionPrefTriggered); - - // ########## Notification Popup ########## // - connect(ui->sideDock, - &QDockWidget::dockLocationChanged, - m_popup, - &NotificationPopup::dockLocationChanged); - connect(m_downManPtr, - &DownloadManager::downloadCompleted, - m_popup, - &NotificationPopup::completedDownload); - connect(m_downManPtr, - &DownloadManager::downloadErrored, - m_popup, - &NotificationPopup::downloadError); - connect(m_downManPtr, - &DownloadManager::latestVersionFound, - m_popup, - &NotificationPopup::checkUpdate); - - // ########## Settings Dialog ########## // - // Restart signal +void +MainWindow::connectSettings() +{ connect( m_settingsDlg, &SettingsDialog::restartApp, this, &MainWindow::restartApp); // qcf2 missing files warning @@ -475,98 +311,88 @@ MainWindow::setupConnections() // Quran page signals connect(m_settingsDlg, &SettingsDialog::redrawQuranPage, - this, - &MainWindow::redrawQuranPage); + m_reader, + &QuranReader::redrawQuranPage); connect(m_settingsDlg, &SettingsDialog::highlightLayerChanged, - this, - &MainWindow::updateHighlight); - for (int i = 0; i <= 1; i++) { - if (m_quranBrowsers[i]) { - connect(m_settingsDlg, - &SettingsDialog::quranFontChanged, - m_quranBrowsers[i], - &QuranPageBrowser::updateFontSize); - } - } - + m_reader, + &QuranReader::updateHighlight); + connect(m_settingsDlg, + &SettingsDialog::quranFontChanged, + m_reader, + &QuranReader::updatePageFontSize); // Side panel signals connect(m_settingsDlg, &SettingsDialog::redrawSideContent, - this, - &MainWindow::addSideContent); - connect(m_settingsDlg, - &SettingsDialog::tafsirChanged, - this, - &MainWindow::updateLoadedTafsir); + m_reader, + &QuranReader::addSideContent); connect(m_settingsDlg, &SettingsDialog::translationChanged, - this, - &MainWindow::updateLoadedTranslation); + &m_translationDb, + &TranslationDb::updateLoadedTranslation); connect(m_settingsDlg, &SettingsDialog::sideFontChanged, - this, - &MainWindow::updateSideFont); + m_reader, + &QuranReader::updateSideFont); connect(m_settingsDlg, &SettingsDialog::verseTypeChanged, - this, - &MainWindow::updateVerseType); - + m_reader, + &QuranReader::updateVerseType); // audio device signals connect(m_settingsDlg, &SettingsDialog::usedAudioDeviceChanged, m_player, &VersePlayer::changeUsedAudioDevice); - // shortcut change connect(m_settingsDlg, &SettingsDialog::shortcutChanged, - m_shortcutHandler, + &m_shortcutHandler, &ShortcutHandler::shortcutChanged); } void -MainWindow::init() +MainWindow::connectMenubar() { - m_player = - new VersePlayer(this, m_currVerse, m_settings->value("Reciter", 0).toInt()); - m_popup = new NotificationPopup(this); - m_betaqaViewer = new BetaqaViewer(this); - m_verseDlg = new VerseDialog(this); - m_downManPtr = new DownloadManager(this); - m_settingsDlg = new SettingsDialog(this, m_player); - - loadReader(); - updateHighlight(); - - ui->cmbPage->setValidator(new QIntValidator(1, 604, this)); - m_notifyMgr = new NotificationManager(this); - - updateLoadedTafsir(); - updateLoadedTranslation(); - updateSideFont(); - updateVerseType(); - - redrawQuranPage(true); - setVerseComboBoxRange(true); - - if (m_readerMode == ReaderMode::SinglePage) - addSideContent(); - - for (int i = 1; i < 605; i++) { - ui->cmbPage->addItem(QString::number(i)); - } - - foreach (const Reciter& r, m_recitersList) { - ui->cmbReciter->addItem(r.displayName); - } - - // sets without emitting signal - setCmbVerseIdx(m_currVerse.number - 1); - setCmbJuzIdx(m_dbMgr->getJuzOfPage(m_currVerse.page) - 1); - - ui->cmbPage->setCurrentIndex(m_currVerse.page - 1); - ui->cmbReciter->setCurrentIndex(m_settings->value("Reciter", 0).toInt()); + connect(ui->actionExit, &QAction::triggered, this, &QApplication::exit); + connect(ui->actionReaderViewToggle, + &QAction::triggered, + m_reader, + &QuranReader::toggleReaderView); + connect(ui->actionPlayerControls, + &QAction::toggled, + this, + &MainWindow::actionPlayerControlsToggled); + for (const auto& connection : { + make_pair(ui->actionPereferences, &MainWindow::actionPrefTriggered), + make_pair(ui->actionDownloadManager, &MainWindow::actionDMTriggered), + make_pair(ui->actionFind, &MainWindow::actionSearchTriggered), + make_pair(ui->actionTafsir, &MainWindow::actionTafsirTriggered), + make_pair(ui->actionVOTD, &MainWindow::actionVotdTriggered), + make_pair(ui->actionBookmarks, &MainWindow::actionBookmarksTriggered), + make_pair(ui->actionKhatmah, &MainWindow::actionKhatmahTriggered), + make_pair(ui->actionAboutQC, &MainWindow::actionAboutTriggered), + make_pair(ui->actionAboutQt, &MainWindow::actionAboutQttriggered), + make_pair(ui->actionUpdates, &MainWindow::actionUpdatesTriggered), + make_pair(ui->actionImport, &MainWindow::importUserData), + make_pair(ui->actionExport, &MainWindow::exportUserData), + make_pair(ui->actionAdvancedCopy, + &MainWindow::actionAdvancedCopyTriggered), + }) { + connect(connection.first, &QAction::triggered, this, connection.second); + } +} + +void +MainWindow::connectNotifiers() +{ + m_popup->registerSender((NotificationSender*)(m_jobMgr->notifier())); + m_popup->registerSender((NotificationSender*)(m_versionChecker->notifier())); + m_popup->registerSender((NotificationSender*)(m_cpyDlg->notifier())); + m_popup->registerSender((NotificationSender*)(m_bookmarksDb.notifier())); + connect(ui->sideDock, + &QDockWidget::dockLocationChanged, + m_popup, + &NotificationPopup::setDockArea); } void @@ -574,7 +400,7 @@ MainWindow::setupSurahsDock() { for (int i = 1; i < 115; i++) { QString item = QString::number(i).rightJustified(3, '0') + ' ' + - m_dbMgr->surahNameList().at(i - 1); + m_quranDb.surahNames().at(i - 1); m_surahList.append(item); } @@ -582,15 +408,15 @@ MainWindow::setupSurahsDock() ui->listViewSurahs->setModel(&m_surahListModel); QItemSelectionModel* selector = ui->listViewSurahs->selectionModel(); - selector->select(m_surahListModel.index(m_currVerse.surah - 1), + selector->select(m_surahListModel.index(m_currVerse.surah() - 1), QItemSelectionModel::Rows | QItemSelectionModel::Select); - ui->listViewSurahs->scrollTo(m_surahListModel.index(m_currVerse.surah - 1), + ui->listViewSurahs->scrollTo(m_surahListModel.index(m_currVerse.surah() - 1), QAbstractItemView::PositionAtCenter); } void -MainWindow::setupMenubarToggle() +MainWindow::setupMenubarButton() { QPushButton* toggleNav = new QPushButton(this); toggleNav->setObjectName("btnToggleNav"); @@ -604,7 +430,8 @@ MainWindow::setupMenubarToggle() .arg(QString::number(ui->menubar->height()), QString::number(ui->menubar->height() * 2))); ui->menubar->setCornerWidget(toggleNav); - toggleNav->setIcon(m_fa->icon(fa_solid, fa_compass)); + toggleNav->setIcon( + StyleManager::getInstance().awesome().icon(fa_solid, fa_compass)); toggleNav->setIconSize(QSize(20, 20)); connect(toggleNav, &QPushButton::toggled, this, [this](bool checked) { @@ -621,79 +448,12 @@ MainWindow::setupMenubarToggle() }); } -bool -MainWindow::areNeighbors(int page1, int page2) -{ - return page1 % 2 != 0 && page2 % 2 == 0 && page2 == page1 + 1; -} - -void -MainWindow::switchActivePage() -{ - if (!m_quranBrowsers[1]) - return; - - m_activeQuranBrowser->resetHighlight(); - if (m_activeQuranBrowser == m_quranBrowsers[0]) { - m_activeQuranBrowser = m_quranBrowsers[1]; - m_activeVList = &m_vLists[1]; - } else { - m_activeQuranBrowser = m_quranBrowsers[0]; - m_activeVList = &m_vLists[0]; - } -} - -void -MainWindow::btnNextClicked() -{ - if (m_readerMode == ReaderMode::SinglePage || m_currVerse.page % 2 == 0) - nextPage(1); - else - nextPage(2); -} - -void -MainWindow::btnPrevClicked() -{ - if (m_readerMode == ReaderMode::SinglePage || m_currVerse.page % 2 != 0) - prevPage(1); - else - prevPage(2); -} - -void -MainWindow::selectVerse(int browserIdx, int IdxInPage) -{ - if (m_activeQuranBrowser != m_quranBrowsers[browserIdx]) - switchActivePage(); - - const Verse& v = m_vLists[browserIdx].at(IdxInPage); - int currSurah = m_currVerse.surah; - m_currVerse = v; - m_player->setVerse(m_currVerse); - if (currSurah != v.surah) { - setVerseComboBoxRange(); - syncSelectedSurah(); - } else - setCmbVerseIdx(v.number - 1); - - setCmbPageIdx(v.page - 1); - setCmbJuzIdx(m_dbMgr->getJuzOfPage(v.page) - 1); -} - -void -MainWindow::updateSurahVerseCount() -{ - - m_surahCount = m_dbMgr->getSurahVerseCount(m_currVerse.surah); -} - QModelIndex MainWindow::syncSelectedSurah() { QItemSelectionModel* select = ui->listViewSurahs->selectionModel(); select->clearSelection(); - QModelIndex surah = m_surahListModel.index(m_currVerse.surah - 1); + QModelIndex surah = m_surahListModel.index(m_currVerse.surah() - 1); select->select(surah, QItemSelectionModel::Rows | QItemSelectionModel::Select); ui->listViewSurahs->scrollTo(surah, QAbstractItemView::PositionAtCenter); @@ -702,32 +462,18 @@ MainWindow::syncSelectedSurah() } void -MainWindow::updatePageVerseInfoList() +MainWindow::currentVerseChanged() { - if (m_activeQuranBrowser == m_quranBrowsers[0]) { - m_vLists[0] = m_dbMgr->getVerseInfoList(m_currVerse.page); - if (m_readerMode == DoublePage) - m_vLists[1] = m_dbMgr->getVerseInfoList(m_currVerse.page + 1); - - m_activeVList = &m_vLists[0]; - - } else { - m_vLists[0] = m_dbMgr->getVerseInfoList(m_currVerse.page - 1); - m_vLists[1] = m_dbMgr->getVerseInfoList(m_currVerse.page); - - m_activeVList = &m_vLists[1]; - } + setCmbVerseIdx(m_currVerse.number() - 1); + setCmbPageIdx(m_currVerse.page() - 1); + setCmbJuzIdx(m_quranDb.getJuzOfPage(m_currVerse.page()) - 1); } void -MainWindow::setVerseToStartOfPage() +MainWindow::currentSurahChanged() { - // set the current verse to the verse at the top of the page - m_currVerse = m_activeVList->at(0); - // update the player active verse - m_player->setVerse(m_currVerse); - // open newly set verse recitation file - m_player->loadActiveVerse(); + setVerseComboBoxRange(); + syncSelectedSurah(); } void @@ -761,134 +507,17 @@ MainWindow::setCmbJuzIdx(int idx) void MainWindow::setVerseComboBoxRange(bool forceUpdate) { - int oldCount = m_surahCount; - updateSurahVerseCount(); - - if (m_surahCount != oldCount || forceUpdate) { - m_verseValidator->setTop(m_surahCount); - - // updates values in the combobox with the current surah verses - ui->cmbVerse->clear(); - m_internalVerseChange = true; - for (int i = 1; i <= m_surahCount; i++) - ui->cmbVerse->addItem(QString::number(i), i); - m_internalVerseChange = false; - - ui->cmbVerse->setValidator(m_verseValidator); - } - - setCmbVerseIdx(m_currVerse.number - 1); -} - -void -MainWindow::gotoPage(int page, bool updateElements, bool automaticFlip) -{ - m_activeQuranBrowser->resetHighlight(); - - if (!automaticFlip) - m_player->stop(); - - if (m_activeQuranBrowser->page() != page) { - if (m_readerMode == ReaderMode::SinglePage) - gotoSinglePage(page); - else - gotoDoublePage(page); - } - - if (updateElements) { - setVerseToStartOfPage(); - syncSelectedSurah(); - setVerseComboBoxRange(); - setCmbJuzIdx(m_dbMgr->getJuzOfPage(m_currVerse.page) - 1); - setCmbPageIdx(m_currVerse.page - 1); - } -} - -void -MainWindow::gotoSinglePage(int page) -{ - m_currVerse.page = page; - redrawQuranPage(); - - m_currVerse = m_activeVList->at(0); - addSideContent(); -} + m_verseValidator->setTop(m_currVerse.surahCount()); -void -MainWindow::gotoDoublePage(int page) -{ - int currPage = m_currVerse.page; - m_currVerse.page = page; - - int pageBrowerIdx = page % 2 == 0; - - if (areNeighbors(currPage, page) || areNeighbors(page, currPage)) - switchActivePage(); - else { - m_activeQuranBrowser = m_quranBrowsers[pageBrowerIdx]; - redrawQuranPage(); - } -} - -void -MainWindow::nextPage(int step) -{ - bool keepPlaying = m_player->playbackState() == QMediaPlayer::PlayingState; - if (m_currVerse.page + step <= 604) { - setCmbPageIdx(m_currVerse.page + step - 1); - - gotoPage(m_currVerse.page + step, true, true); - - if (m_readerMode == ReaderMode::SinglePage) - m_scrlVerseByVerse->verticalScrollBar()->setValue(0); - - // if the page is flipped automatically, resume playback - if (keepPlaying) { - m_player->play(); - highlightCurrentVerse(); - } - } -} - -void -MainWindow::prevPage(int step) -{ - bool keepPlaying = m_player->playbackState() == QMediaPlayer::PlayingState; - if (m_currVerse.page - step >= 1) { - setCmbPageIdx(m_currVerse.page - step - 1); - - gotoPage(m_currVerse.page - step, true, true); - - if (m_readerMode == ReaderMode::SinglePage) - m_scrlVerseByVerse->verticalScrollBar()->setValue(0); - - if (keepPlaying) { - m_player->play(); - highlightCurrentVerse(); - } - } -} - -void -MainWindow::gotoSurah(int surahIdx) -{ - // getting surah index - int page = m_dbMgr->getSurahStartPage(surahIdx); - gotoPage(page, false); - - m_currVerse.page = page; - m_currVerse.surah = surahIdx; - m_currVerse.number = surahIdx == 9 || surahIdx == 1 ? 1 : 0; - - // syncing the player & playing basmalah - m_player->setVerse(m_currVerse); - m_player->playCurrentVerse(); + // updates values in the combobox with the current surah verses + m_internalVerseChange = true; + ui->cmbVerse->clear(); + for (int i = 1; i <= m_currVerse.surahCount(); i++) + ui->cmbVerse->addItem(QString::number(i), i); + m_internalVerseChange = false; - highlightCurrentVerse(); - setCmbPageIdx(m_currVerse.page - 1); - setCmbJuzIdx(m_dbMgr->getJuzOfPage(m_currVerse.page) - 1); - setVerseComboBoxRange(); - syncSelectedSurah(); + ui->cmbVerse->setValidator(m_verseValidator); + setCmbVerseIdx(m_currVerse.number() - 1); } void @@ -898,7 +527,7 @@ MainWindow::searchSurahTextChanged(const QString& arg1) m_surahListModel.setStringList(m_surahList); syncSelectedSurah(); } else { - QList suggestions = m_dbMgr->searchSurahNames(arg1); + QList suggestions = m_quranDb.searchSurahNames(arg1); QStringList res; foreach (int sura, suggestions) res.append(m_surahList.at(sura - 1)); @@ -911,7 +540,7 @@ void MainWindow::listSurahNameClicked(const QModelIndex& index) { int s = m_surahList.indexOf(index.data().toString()) + 1; - gotoSurah(s); + m_reader->gotoSurah(s); } void @@ -922,7 +551,7 @@ MainWindow::cmbPageChanged(int newIdx) return; } - gotoPage(newIdx + 1); + m_reader->gotoPage(newIdx + 1); } void @@ -937,20 +566,19 @@ MainWindow::cmbVerseChanged(int newVerseIdx) } int verse = newVerseIdx + 1; - int page = m_dbMgr->getVersePage(m_currVerse.surah, verse); + int page = m_quranDb.getVersePage(m_currVerse.surah(), verse); - if (page != m_currVerse.page) - gotoPage(page, false); + if (page != m_currVerse.page()) + m_reader->gotoPage(page, false); - m_currVerse.page = page; - m_currVerse.number = verse; - highlightCurrentVerse(); + m_currVerse.setPage(page); + m_currVerse.setNumber(verse); + m_reader->highlightCurrentVerse(); setCmbPageIdx(page - 1); - setCmbJuzIdx(m_dbMgr->getJuzOfPage(page) - 1); + setCmbJuzIdx(m_quranDb.getJuzOfPage(page) - 1); // open newly set verse recitation file - m_player->setVerse(m_currVerse); m_player->loadActiveVerse(); } @@ -961,129 +589,39 @@ MainWindow::cmbJuzChanged(int newJuzIdx) qDebug() << "Internal jozz change"; return; } - int page = m_dbMgr->getJuzStartPage(newJuzIdx + 1); - gotoPage(page); -} - -void -MainWindow::togglePlayback() -{ - if (m_player->playbackState() == QMediaPlayer::PlayingState) { - btnPauseClicked(); - } else { - btnPlayClicked(); - } -} - -void -MainWindow::btnPauseClicked() -{ - m_player->pause(); -} - -void -MainWindow::btnPlayClicked() -{ - highlightCurrentVerse(); - m_player->play(); -} - -void -MainWindow::btnStopClicked() -{ - m_player->stop(); - setVerseToStartOfPage(); - syncSelectedSurah(); - setVerseComboBoxRange(); + int page = m_quranDb.getJuzStartPage(newJuzIdx + 1); + m_reader->gotoPage(page); } void MainWindow::mediaStatusChanged(QMediaPlayer::MediaStatus status) { - if (status == QMediaPlayer::EndOfMedia) { + if (status == QMediaPlayer::EndOfMedia) nextVerse(); - } -} - -void -MainWindow::mediaStateChanged(QMediaPlayer::PlaybackState state) -{ - if (state == QMediaPlayer::PlayingState) { - ui->btnPlay->setEnabled(false); - ui->btnPause->setEnabled(true); - ui->btnStop->setEnabled(true); - } else if (state == QMediaPlayer::PausedState) { - ui->btnPlay->setEnabled(true); - ui->btnPause->setEnabled(false); - ui->btnStop->setEnabled(true); - } else if (state == QMediaPlayer::StoppedState) { - ui->btnPlay->setEnabled(true); - ui->btnPause->setEnabled(false); - ui->btnStop->setEnabled(false); - } - - updateTrayTooltip(state); } void MainWindow::updateTrayTooltip(QMediaPlayer::PlaybackState state) { if (state == QMediaPlayer::PlayingState) { - m_notifyMgr->setTooltip(tr("Now playing: ") + m_player->reciterName() + - " - " + tr("Surah ") + - m_dbMgr->getSurahName(m_currVerse.surah)); + m_systemTray->setTooltip( + tr("Now playing: ") + m_player->reciterName() + " - " + tr("Surah ") + + m_quranDb.surahNames().at(m_currVerse.surah() - 1)); } else - m_notifyMgr->setTooltip(tr("Quran Companion")); -} - -void -MainWindow::addCurrentToBookmarks() -{ - if (!m_dbMgr->isBookmarked(m_currVerse)) { - m_dbMgr->addBookmark(m_currVerse); - m_popup->bookmarkAdded(); - } + m_systemTray->setTooltip(tr("Quran Companion")); } void -MainWindow::mediaPosChanged(qint64 position) +MainWindow::bookmarkCurrent() { - if (ui->sldrAudioPlayer->maximum() != m_player->duration()) - ui->sldrAudioPlayer->setMaximum(m_player->duration()); - - if (!ui->sldrAudioPlayer->isSliderDown()) - ui->sldrAudioPlayer->setValue(position); + if (!m_bookmarksDb.isBookmarked(m_currVerse)) + m_bookmarksDb.addBookmark(m_currVerse, false); } void -MainWindow::volumeSliderValueChanged(int position) +MainWindow::actionUpdatesTriggered() { - qreal linearVolume = - QAudio::convertVolume(ui->sldrVolume->value() / qreal(100.0), - QAudio::LogarithmicVolumeScale, - QAudio::LinearVolumeScale); - if (linearVolume != m_volume) { - m_volume = linearVolume; - m_player->setPlayerVolume(m_volume); - } -} - -void -MainWindow::missingRecitationFileWarn(int reciterIdx, int surah) -{ - if (!m_settings->value("MissingFileWarning").toBool()) - return; - - QMessageBox::StandardButton btn = - QMessageBox::question(this, - tr("Recitation not found"), - tr("The recitation files for the current surah is " - "missing, would you like to download it?")); - - if (btn == QMessageBox::Yes) { - actionDMTriggered(); - m_downloaderDlg->selectDownload(Recitation, { reciterIdx, surah }); - } + m_versionChecker->checkUpdates(); } void @@ -1096,7 +634,7 @@ MainWindow::missingQCF() if (btn == QMessageBox::Yes) { actionDMTriggered(); - m_downloaderDlg->selectDownload(QCF); + m_downloaderDlg->selectDownload(DownloadJob::Qcf); } } @@ -1110,81 +648,40 @@ MainWindow::missingTafsir(int idx) if (btn == QMessageBox::Yes) { actionDMTriggered(); - m_downloaderDlg->selectDownload(File, { 0, idx }); + m_downloaderDlg->selectDownload(DownloadJob::TafsirFile, { 0, idx }); } } void -MainWindow::verseClicked() +MainWindow::missingTranslation(int idx) { - // object = clickable label, parent = verse frame, verse frame name scheme = - // 'surah_verse' - QStringList data = sender()->parent()->objectName().split('_'); - int surah = data.at(0).toInt(); - int verse = data.at(1).toInt(); - - m_currVerse.number = verse; - m_player->setVerse(m_currVerse); - - if (m_currVerse.surah != surah) { - m_currVerse.surah = surah; - m_player->setVerse(m_currVerse); - setVerseComboBoxRange(); - syncSelectedSurah(); - } + QMessageBox::StandardButton btn = QMessageBox::question( + this, + tr("Files Missing"), + tr("The selected translation is missing, would you like to download it?")); - setCmbVerseIdx(verse - 1); - m_player->loadActiveVerse(); - btnPlayClicked(); + if (btn == QMessageBox::Yes) { + actionDMTriggered(); + m_downloaderDlg->selectDownload(DownloadJob::TranslationFile, { 1, idx }); + } } void -MainWindow::verseAnchorClicked(const QUrl& hrefUrl) +MainWindow::missingRecitationFileWarn(int reciterIdx, int surah) { - if (hrefUrl.toString().at(1) == 'F') { - int surah = hrefUrl.toString().remove("#F").toInt(); - qDebug() << "SURAH CARD:" << surah; - m_betaqaViewer->showSurah(surah); + if (!m_config.settings().value("MissingFileWarning").toBool()) return; - } - QuranPageBrowser* senderBrowser = qobject_cast(sender()); - int browerIdx = senderBrowser == m_quranBrowsers[1]; - int idx = hrefUrl.toString().remove('#').toInt(); - Verse v = m_vLists[browerIdx].at(idx); - - QuranPageBrowser::Action chosenAction = - senderBrowser->lmbVerseMenu(m_dbMgr->isBookmarked(v)); - - switch (chosenAction) { - case QuranPageBrowser::play: - selectVerse(browerIdx, idx); - m_player->loadActiveVerse(); - btnPlayClicked(); - break; - case QuranPageBrowser::select: - selectVerse(browerIdx, idx); - m_player->setSource(QUrl()); - highlightCurrentVerse(); - break; - - case QuranPageBrowser::tafsir: - showVerseTafsir(v); - break; - case QuranPageBrowser::copy: - copyVerseText(v); - m_popup->copiedToClipboard(); - break; - case QuranPageBrowser::addBookmark: - m_dbMgr->addBookmark(v); - m_popup->bookmarkAdded(); - break; - case QuranPageBrowser::removeBookmark: - if (m_dbMgr->removeBookmark(v)) - m_popup->bookmarkRemoved(); - break; - default: - break; + QMessageBox::StandardButton btn = + QMessageBox::question(this, + tr("Recitation not found"), + tr("The recitation files for the current surah is " + "missing, would you like to download it?")); + + if (btn == QMessageBox::Yes) { + actionDMTriggered(); + m_downloaderDlg->selectDownload(DownloadJob::Recitation, + { reciterIdx, surah }); } } @@ -1198,7 +695,7 @@ void MainWindow::actionDMTriggered() { if (m_downloaderDlg == nullptr) - m_downloaderDlg = new DownloaderDialog(this, m_downManPtr); + m_downloaderDlg = new DownloaderDialog(this, m_jobMgr); m_downloaderDlg->show(); } @@ -1210,8 +707,8 @@ MainWindow::actionBookmarksTriggered() m_bookmarksDlg = new BookmarksDialog(this); connect(m_bookmarksDlg, &BookmarksDialog::navigateToVerse, - this, - &MainWindow::navigateToVerse); + m_reader, + &QuranReader::navigateToVerse); } m_bookmarksDlg->showWindow(); @@ -1221,30 +718,28 @@ void MainWindow::actionKhatmahTriggered() { if (m_khatmahDlg == nullptr) { - m_khatmahDlg = new KhatmahDialog(m_currVerse, this); + m_khatmahDlg = new KhatmahDialog(this); connect(m_khatmahDlg, &KhatmahDialog::navigateToVerse, - this, - &MainWindow::navigateToVerse); + m_reader, + &QuranReader::navigateToVerse); } - m_dbMgr->saveActiveKhatmah(m_currVerse); + m_bookmarksDb.saveActiveKhatmah(m_currVerse); m_khatmahDlg->show(); } void MainWindow::actionAdvancedCopyTriggered() { - if (m_cpyDlg == nullptr) - m_cpyDlg = new CopyDialog(this); - m_cpyDlg->show(m_currVerse); + m_cpyDlg->show(); } void MainWindow::actionTafsirTriggered() { - showVerseTafsir(m_currVerse); + m_contentDlg->showVerseTafsir(m_currVerse); } void @@ -1261,7 +756,7 @@ MainWindow::actionAboutTriggered() } void -MainWindow::on_actionAboutQt_triggered() +MainWindow::actionAboutQttriggered() { QMessageBox::aboutQt(this, tr("About Qt")); } @@ -1273,137 +768,38 @@ MainWindow::actionSearchTriggered() m_searchDlg = new SearchDialog(this); connect(m_searchDlg, &SearchDialog::navigateToVerse, - this, - &MainWindow::navigateToVerse); + m_reader, + &QuranReader::navigateToVerse); } m_searchDlg->show(); } void -MainWindow::toggleReaderView() -{ - if (ui->frmPageContent->isVisible() && ui->frmSidePanel->isVisible()) { - ui->frmSidePanel->setVisible(false); - } else if (ui->frmPageContent->isVisible()) { - ui->frmSidePanel->setVisible(true); - ui->frmPageContent->setVisible(false); - } else - ui->frmPageContent->setVisible(true); -} - -void -MainWindow::togglePlayerControls() +MainWindow::actionPlayerControlsToggled(bool checked) { - ui->frmTopControls->setVisible(!ui->frmTopControls->isVisible()); -} - -void -MainWindow::navigateToVerse(Verse v) -{ - gotoPage(v.page, false); - - m_currVerse = v; - setVerseComboBoxRange(); - - setCmbPageIdx(m_currVerse.page - 1); - setCmbVerseIdx(m_currVerse.number - 1); - setCmbJuzIdx(m_dbMgr->getJuzOfPage(m_currVerse.page) - 1); - - syncSelectedSurah(); - highlightCurrentVerse(); - - m_player->setVerse(m_currVerse); - m_player->loadActiveVerse(); + m_playerControls->setVisible(checked); + if (m_config.settings().value("Reader/AdaptiveFont").toBool()) { + m_reader->redrawQuranPage(); + m_reader->highlightCurrentVerse(); + } } void MainWindow::navigateToSurah(QModelIndex& index) { int s = index.row() + 1; - gotoSurah(s); -} - -void -MainWindow::updateLoadedTafsir() -{ - int currTafsir = m_settings->value("Reader/Tafsir").toInt(); - - m_dbMgr->setCurrentTafsir(currTafsir); -} - -void -MainWindow::updateLoadedTranslation() -{ - int currTrans = m_settings->value("Reader/Translation").toInt(); - - m_dbMgr->setCurrentTranslation(currTrans); -} - -void -MainWindow::updateSideFont() -{ - m_sideFont = - qvariant_cast(m_settings->value("Reader/SideContentFont")); -} - -void -MainWindow::updateVerseType() -{ - VerseType type = - qvariant_cast(m_settings->value("Reader/VerseType")); - m_versesFont.setFamily(Globals::verseFontname(type, m_currVerse.page)); - m_versesFont.setPointSize(m_settings->value("Reader/VerseFontSize").toInt()); - m_dbMgr->setVerseType(type); -} - -Verse -MainWindow::incrementVerse() -{ - Verse v = m_currVerse; - if (v == m_activeVList->last()) { - v.page++; - } - - if (v.number < m_surahCount) { - v.number++; - } else if (v.number == m_surahCount) { - v.surah++; - v.number = v.surah == 9 || v.surah == 1 ? 1 : 0; - } - - return v; -} - -Verse -MainWindow::decrementVerse() -{ - Verse v = m_currVerse; - if (v.number == 0) - v.number = 1; - - if (v == m_activeVList->at(0)) { - v.page--; - } - - if (v.number > 1) { - v.number--; - } else if (v.number <= 1) { - v.surah--; - v.number = m_dbMgr->getSurahVerseCount(v.surah); - } - - return v; + m_reader->gotoSurah(s); } void MainWindow::nextVerse() { - if (m_currVerse.surah == 114 && m_currVerse.number == 6) + if (m_currVerse.surah() == 114 && m_currVerse.number() == 6) return; bool keepPlaying = m_player->isOn(); - navigateToVerse(incrementVerse()); + m_reader->navigateToVerse(m_currVerse.next()); if (keepPlaying) m_player->play(); } @@ -1411,11 +807,11 @@ MainWindow::nextVerse() void MainWindow::prevVerse() { - if (m_currVerse.surah == 1 && m_currVerse.number == 1) + if (m_currVerse.surah() == 1 && m_currVerse.number() == 1) return; bool keepPlaying = m_player->isOn(); - navigateToVerse(decrementVerse()); + m_reader->navigateToVerse(m_currVerse.prev()); if (keepPlaying) m_player->play(); } @@ -1437,29 +833,15 @@ MainWindow::prevJuz() void MainWindow::nextSurah() { - if (m_currVerse.surah < 114) - gotoSurah(m_currVerse.surah + 1); + if (m_currVerse.surah() < 114) + m_reader->gotoSurah(m_currVerse.surah() + 1); } void MainWindow::prevSurah() { - if (m_currVerse.surah > 1) - gotoSurah(m_currVerse.surah - 1); -} - -void -MainWindow::incrementVolume() -{ - int val = ui->sldrVolume->value() + 5; - ui->sldrVolume->setValue(val > 100 ? 100 : val); -} - -void -MainWindow::decrementVolume() -{ - int val = ui->sldrVolume->value() - 5; - ui->sldrVolume->setValue(val < 0 ? 0 : val); + if (m_currVerse.surah() > 1) + m_reader->gotoSurah(m_currVerse.surah() - 1); } void @@ -1478,192 +860,19 @@ MainWindow::toggleNavDock() } void -MainWindow::updateHighlight() +MainWindow::importUserData() { - for (int i = 0; i <= 1; i++) { - if (m_quranBrowsers[i]) { - m_quranBrowsers[i]->updateHighlightLayer(); - } - } + QString path = m_selectorDlg->selectJson(FileSelector::Read); + if (!path.isEmpty()) + m_importExportDlg->selectImports(path); } void -MainWindow::redrawQuranPage(bool manualSz) +MainWindow::exportUserData() { - if (m_activeQuranBrowser == m_quranBrowsers[0]) { - m_quranBrowsers[0]->constructPage(m_currVerse.page, manualSz); - if (m_readerMode == DoublePage && m_quranBrowsers[1]) - m_quranBrowsers[1]->constructPage(m_currVerse.page + 1, manualSz); - } else { - m_quranBrowsers[0]->constructPage(m_currVerse.page - 1, manualSz); - m_quranBrowsers[1]->constructPage(m_currVerse.page, manualSz); - } - - updatePageVerseInfoList(); -} - -void -MainWindow::highlightCurrentVerse() -{ - if (m_currVerse.number == 0) - return; - - // idx may be -1 if verse number is 0 (basmallah) - int idx = m_activeVList->indexOf(m_currVerse); - if (idx < 0) - idx = 0; - - m_activeQuranBrowser->highlightVerse(idx); - - if (m_readerMode == ReaderMode::SinglePage) - setHighlightedFrame(); -} - -void -MainWindow::setHighlightedFrame() -{ - if (m_highlightedFrm != nullptr) - m_highlightedFrm->setSelected(false); - - VerseFrame* verseFrame = - m_scrlVerseByVerse->widget()->findChild(QString("%0_%1").arg( - QString::number(m_currVerse.surah), QString::number(m_currVerse.number))); - - verseFrame->setSelected(true); - - m_scrlVerseByVerse->ensureWidgetVisible(verseFrame); - m_highlightedFrm = verseFrame; -} - -void -MainWindow::addSideContent() -{ - if (m_readerMode != ReaderMode::SinglePage) - return; - - if (!m_verseFrameList.isEmpty()) { - qDeleteAll(m_verseFrameList); - m_verseFrameList.clear(); - m_highlightedFrm = nullptr; - } - - ClickableLabel* verselb; - QLabel* contentLb; - VerseFrame* verseContFrame; - QString prevLbContent, currLbContent; - if (m_dbMgr->getVerseType() == qcf) - m_versesFont.setFamily(Globals::pageFontname(m_currVerse.page)); - - for (int i = m_activeVList->size() - 1; i >= 0; i--) { - Verse vInfo = m_activeVList->at(i); - - verseContFrame = new VerseFrame(m_scrlVerseByVerse->widget()); - verselb = new ClickableLabel(verseContFrame); - contentLb = new QLabel(verseContFrame); - - verseContFrame->setObjectName( - QString("%0_%1").arg(vInfo.surah).arg(vInfo.number)); - - verselb->setFont(m_versesFont); - verselb->setText(m_dbMgr->getVerseGlyphs(vInfo.surah, vInfo.number)); - verselb->setAlignment(Qt::AlignCenter); - verselb->setWordWrap(true); - - currLbContent = m_dbMgr->getTranslation(vInfo.surah, vInfo.number); - - if (currLbContent == prevLbContent) { - currLbContent = '-'; - } else { - prevLbContent = currLbContent; - } - - contentLb->setText(currLbContent); - contentLb->setTextFormat(Qt::RichText); - contentLb->setTextInteractionFlags(Qt::TextSelectableByMouse); - contentLb->setAlignment(Qt::AlignCenter); - contentLb->setWordWrap(true); - contentLb->setFont(m_sideFont); - - verseContFrame->layout()->addWidget(verselb); - verseContFrame->layout()->addWidget(contentLb); - m_scrlVerseByVerse->widget()->layout()->addWidget(verseContFrame); - m_verseFrameList.insert(0, verseContFrame); - - // connect clicked signal for each label - connect(verselb, &ClickableLabel::clicked, this, &MainWindow::verseClicked); - } - - if (m_player->playbackState() == QMediaPlayer::PlayingState) { - setHighlightedFrame(); - } -} - -void -MainWindow::showVerseTafsir(Verse v) -{ - static bool reload = false; - if (reload) { - updateLoadedTafsir(); - reload = false; - } - - if (!Globals::tafsirExists(m_dbMgr->currTafsir())) { - int i; - for (i = 0; i < m_tafasirList.size(); i++) - if (m_dbMgr->currTafsir() == &m_tafasirList[i]) - break; - reload = true; - return missingTafsir(i); - } - - if (m_tafsirDlg == nullptr) { - m_tafsirDlg = new TafsirDialog(this); - } - - m_tafsirDlg->setShownVerse(v); - m_tafsirDlg->loadVerseTafsir(); - m_tafsirDlg->show(); -} - -void -MainWindow::copyVerseText(const Verse v) -{ - QClipboard* clip = QApplication::clipboard(); - QString text = m_dbMgr->getVerseText(v.surah, v.number); - QString vNum = QString::number(v.number); - text.remove(text.size() - 1, 1); - text = text.trimmed(); - text = "{" + text + "}"; - text += ' '; - text += "[" + m_dbMgr->surahNameList().at(v.surah - 1) + ":" + vNum + "]"; - clip->setText(text); -} - -void -MainWindow::showVOTDmessage(QPair votd) -{ - QPointer mbox = new QDialog(this); - mbox->setMinimumSize(600, 300); - mbox->setObjectName("dlgVOTD"); - mbox->setLayout(new QVBoxLayout); - mbox->setWindowIcon(ui->actionVOTD->icon()); - mbox->setWindowTitle(tr("Verse Of The Day")); - ClickableLabel* lb = new ClickableLabel(mbox); - lb->setText(votd.second); - lb->setTextFormat(Qt::RichText); - lb->setAlignment(Qt::AlignCenter); - lb->setFont(QFont(qApp->font().families(), 15)); - lb->setCursor(Qt::PointingHandCursor); - if (votd.second.length() > 200) - lb->setWordWrap(true); - - connect(lb, &ClickableLabel::clicked, this, [votd, this, mbox]() { - mbox->close(); - navigateToVerse(votd.first); - }); - - mbox->layout()->addWidget(lb); - mbox->show(); + QString path = m_selectorDlg->selectJson(FileSelector::Write); + if (!path.isEmpty()) + m_importExportDlg->selectExports(path); } void @@ -1678,11 +887,11 @@ MainWindow::resizeEvent(QResizeEvent* event) void MainWindow::saveReaderState() { - m_settings->setValue("WindowState", saveState()); - m_settings->setValue("Reciter", ui->cmbReciter->currentIndex()); - m_settings->sync(); + m_config.settings().setValue("WindowState", saveState()); + m_config.settings().setValue("Reciter", m_playerControls->currentReciter()); + m_config.settings().sync(); - m_dbMgr->saveActiveKhatmah(m_currVerse); + m_bookmarksDb.saveActiveKhatmah(m_currVerse); } void diff --git a/src/core/mainwindow.h b/src/core/mainwindow.h index 75a11e48..a04a7b6d 100644 --- a/src/core/mainwindow.h +++ b/src/core/mainwindow.h @@ -6,34 +6,34 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include "../globals.h" -#include "../utils/dbmanager.h" -#include "../utils/notificationmanager.h" -#include "../utils/shortcuthandler.h" -#include "../utils/verseplayer.h" -#include "../widgets/betaqaviewer.h" -#include "../widgets/notificationpopup.h" -#include "../widgets/quranpagebrowser.h" -#include "../widgets/versedialog.h" -#include "../widgets/verseframe.h" -#include "bookmarksdialog.h" -#include "copydialog.h" -#include "downloaderdialog.h" -#include "khatmahdialog.h" -#include "searchdialog.h" -#include "settingsdialog.h" -#include "tafsirdialog.h" +#include "playercontrols.h" +#include "quranreader.h" #include -#include -#include #include #include -#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -43,8 +43,9 @@ QT_END_NAMESPACE /** * @class MainWindow - * @brief MainWindow class is responsible for the reader interface, audio - * controls, navigation, and opening other dialogs + * @brief MainWindow class is the driver class the connects all the other + * components and is responsible for the navigation dock and menubar + * functionality */ class MainWindow : public QMainWindow { @@ -55,35 +56,20 @@ class MainWindow : public QMainWindow * @brief class constructor * @param parent - pointer to parent widget */ - explicit MainWindow(QWidget* parent = nullptr); + explicit MainWindow(QWidget* parent); ~MainWindow(); - /** - * @brief highlight the currently active ::Verse m_currVerse in the - * active QuranPageBrowser and the side panel depending on the ::ReaderMode - */ - void highlightCurrentVerse(); - /** - * @brief highlight the currently active VerseFrame - */ - void setHighlightedFrame(); - public slots: /** - * @brief show the verse of the day dialog - * @param votd - QPair of the ::Verse of the day and the verse text + * @brief slot for adjusting UI elements to reflect the current verse + * information */ - void showVOTDmessage(QPair votd); + void currentVerseChanged(); /** - * @brief check if there are updates using the maintainence tool if available, - * otherwise check latest version on github + * @brief slot for reseting the verses combobox and + * highlighting the active surah in the navigation dock */ - void checkForUpdates(); - /** - * @brief callback function that handles the output of the maintainence tool - * update check - */ - void updateProcessCallback(); + void currentSurahChanged(); /** * @brief save the current position and window state of the application to the * settings file @@ -103,43 +89,6 @@ public slots: void resizeEvent(QResizeEvent* event); private slots: - /** - * @brief navigates to the next page relative to the current page - */ - void nextPage(int step = 1); - /** - * @brief navigates to the previous page relative to the current page - */ - void prevPage(int step = 1); - /** - * @brief ensure the page given is visible and update other members to match - * the properties of the first verse in the page, calls the appropriate - * navigation function according to the ::ReaderMode - * @param page - page to navigate to - * @param updateElements - boolean flag to indicate whether to update other - * elements or not - * @param automaticFlip - boolean indicating whether the function was called - * by internal signal to automatically flip the page - */ - void gotoPage(int page, - bool updateElements = true, - bool automaticFlip = false); - /** - * @brief single page mode navigation - * @param page - page to navigate to - */ - void gotoSinglePage(int page); - /** - * @brief double page mode navigation - * @param page - page to navigate to - */ - void gotoDoublePage(int page); - /** - * @brief gets the page of the 1st verse in this surah, moves to that page, - * and starts playback of the surah - * @param surahIdx - surah number (1-114) - */ - void gotoSurah(int surahIdx); /** * @brief sets the verse combobox values according to the current surah verse * count (m_surahCount), sets the current verse as the visible index @@ -147,19 +96,6 @@ private slots: * verse count */ void setVerseComboBoxRange(bool forceUpdate = false); - /** - * @brief continues playback of the current verse - */ - void btnPlayClicked(); - /** - * @brief pause playback of the current verse - */ - void btnPauseClicked(); - /** - * @brief stops playback, sets the current verse to the 1st in the page and - * update verses combobox & selected surah - */ - void btnStopClicked(); /** * @brief slot for updating the reader page as the user selects a different * page from the combobox @@ -181,7 +117,7 @@ private slots: /** * @brief display warning message box in case that recitation files are * missing - * @param reciterIdx - ::Globals::recitersList index for the reciter + * @param reciterIdx - ::Reciter::reciters index for the reciter * @param surah */ void missingRecitationFileWarn(int reciterIdx, int surah); @@ -191,21 +127,14 @@ private slots: void missingQCF(); /** * @brief display a warning messagebox when a tafsir db file is not found - * @param idx - index of tafsir in Globals::tafasirList + * @param idx - index of tafsir in Tafsir::tafasir */ void missingTafsir(int idx); /** - * @brief sets the current position in the audio file as the position of the - * slider - * @param position - position in audio file in milliseconds + * @brief display a warning messagebox when a translation db file is not found + * @param idx - index of translation in Translation::translations */ - void mediaPosChanged(qint64 position); - /** - * @brief disables/enables control buttons according to the media player state - * and update the systray tooltip - * @param state - playback state of the current audio file - */ - void mediaStateChanged(QMediaPlayer::PlaybackState state); + void missingTranslation(int idx); /** * @brief move to the next verse as the playback of the current verse ends * @param status - the status of the current verse recitation media @@ -218,18 +147,13 @@ private slots: */ void updateTrayTooltip(QMediaPlayer::PlaybackState state); /** - * @brief change the player volume level as the volume slider changes - * @param position - position in the slider (0 - 100) - */ - void volumeSliderValueChanged(int position); - /** - * @brief adds the current ::Verse to the bookmarks + * @brief adds the current Verse to the bookmarks */ - void addCurrentToBookmarks(); + void bookmarkCurrent(); /** - * @brief toggle play/pause of the current verse + * @brief actionUpdatesTriggered */ - void togglePlayback(); + void actionUpdatesTriggered(); /** * @brief open the SettingsDialog and connect settings change slots */ @@ -251,7 +175,7 @@ private slots: */ void actionAdvancedCopyTriggered(); /** - * @brief open the TafsirDialog for the current ::Verse + * @brief open the ContentDialog for the current Verse */ void actionTafsirTriggered(); /** @@ -263,13 +187,11 @@ private slots: */ void actionSearchTriggered(); /** - * @brief toggle the main reader view by hiding one of the panels + * @brief callback for the checkbox to toggle the visibility of the player + * controls + * @param checked - boolean representing check state of ui checkbox */ - void toggleReaderView(); - /** - * @brief toggle the visibility of the player controls in the main window - */ - void togglePlayerControls(); + void actionPlayerControlsToggled(bool checked); /** * @brief open the about messagebox for the application */ @@ -277,67 +199,36 @@ private slots: /** * @brief open the about messagebox for Qt */ - void on_actionAboutQt_triggered(); - /** - * @brief slot to navigate to the clicked verse in the side panel and update - * UI elements - */ - void verseClicked(); + void actionAboutQttriggered(); /** - * @brief navigate to the surah with the given QModelIndex in the surahs list - * model - */ - void navigateToSurah(QModelIndex& index); - /** - * @brief open TafsirDialog with the shown verse set to the given ::Verse - * @param v - ::Verse to show the tafsir of - */ - void showVerseTafsir(Verse v); - /** - * @brief navigate to the given ::Verse and update UI elements accordingly - * @param v - ::Verse to navigate to - */ - void navigateToVerse(Verse v); - /** - * @brief callback function for clicking verses in the QuranPageBrowser that - * takes actions based on the chosen option in the menu - * @param hrefUrl - "#idx" where idx is the verse index relative to the start - * of the page (=index in the page ::Verse QList) + * @brief move to the next Verse relative to m_currVerse */ - void verseAnchorClicked(const QUrl& hrefUrl); - /** - * @brief copy to clipboard the text of the verse with the given index - * @param IdxInPage - verse index relative to the start of the page - */ - void copyVerseText(const Verse v); + void nextVerse(); /** - * @brief redraw the current Quran page - * @param manualSz - boolean flag to force the use of the manually set - * fontsize + * @brief move to the previous Verse relative to m_currVerse */ - void redrawQuranPage(bool manualSz = false); + void prevVerse(); /** - * @brief updates the side panel with the translation of the current page - * verses + * @brief move to the next Juz */ - void addSideContent(); + void nextJuz(); /** - * @brief set tafsir to the one in the settings, update the selected db + * @brief move to the previous Juz */ - void updateLoadedTafsir(); + void prevJuz(); /** - * @brief set translation to the one in the settings, update the selected db + * @brief move to the next surah */ - void updateLoadedTranslation(); + void nextSurah(); /** - * @brief set side content font to the one in the settings + * @brief move to the previous surah */ - void updateSideFont(); + void prevSurah(); /** - * @brief Updates the type of the verses shown and reload the font family and - * size + * @brief navigate to the surah with the given QModelIndex in the surahs list + * model */ - void updateVerseType(); + void navigateToSurah(QModelIndex& index); /** * @brief search for the surahs with the given argument when the text in the * side dock search box is changed @@ -351,129 +242,108 @@ private slots: */ void listSurahNameClicked(const QModelIndex& index); /** - * @brief increment the current active ::Verse appropriately - * @return the new incremented ::Verse - */ - Verse incrementVerse(); - /** - * @brief decrement the current active ::Verse appropriately - * @return the new decremented ::Verse + * @brief toggles visiblity of menubar */ - Verse decrementVerse(); + void toggleMenubar(); /** - * @brief move to the next ::Verse relative to m_currVerse + * @brief toggles visiblity of the navigation dock */ - void nextVerse(); + void toggleNavDock(); /** - * @brief move to the previous ::Verse relative to m_currVerse + * @brief callback function for importing user data from file */ - void prevVerse(); + void importUserData(); /** - * @brief move to the next Juz + * @brief callback function for exporting user data to file */ - void nextJuz(); + void exportUserData(); + +private: + Ui::MainWindow* ui; /** - * @brief move to the previous Juz + * @brief reference to the shared current verse instance */ - void prevJuz(); + Verse& m_currVerse; /** - * @brief move to the next surah + * @brief reference to the singleton Configuration instance */ - void nextSurah(); + Configuration& m_config; /** - * @brief move to the previous surah + * @brief reference to the singleton ShortcutHandler instance */ - void prevSurah(); + ShortcutHandler& m_shortcutHandler; /** - * @brief utility to increment the VersePlayer playback volume by steps of 5 + * @brief reference to the singleton BookmarksDb instance */ - void incrementVolume(); + BookmarksDb& m_bookmarksDb; /** - * @brief utility to decrement the VersePlayer playback volume by steps of 5 + * @brief reference to the singleton QuranDb instance */ - void decrementVolume(); + const QuranDb& m_quranDb; /** - * @brief toggles visiblity of menubar + * @brief reference to the singleton TranslationDb instance */ - void toggleMenubar(); + const TranslationDb& m_translationDb; /** - * @brief toggles visiblity of the navigation dock + * @brief reference to the static QList of available reciters */ - void toggleNavDock(); + const QList& m_reciters; /** - * @brief update the highlight layer (fg/bg) used by the Quran browser(s) + * @brief reference to the static QList of available tafasir */ - void updateHighlight(); - -private: - const bool m_darkMode = Globals::darkMode; - const QLocale::Language& m_language = Globals::language; - QSettings* const m_settings = Globals::settings; - const QList& m_recitersList = Globals::recitersList; - const QList& m_tafasirList = Globals::tafasirList; - const QString& m_updateToolPath = Globals::updateToolPath; - const ReaderMode& m_readerMode = Globals::readerMode; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); - fa::QtAwesome* m_fa = Globals::awesome; + const QList& m_tafasir; /** * @brief initalizes different parts used by the app */ - void init(); + void loadComponents(); /** * @brief load icons for different UI elements */ void loadIcons(); /** - * @brief set the current ::Verse from settings + * @brief set the current Verse from settings */ - void loadSettings(); + void loadVerse(); /** - * @brief setup the reader layout and create widgets according to the current - * ::ReaderMode + * @brief connect ShortcutHandler signals to their corresponding slots */ - void loadReader(); + void setupShortcuts(); /** - * @brief utility function to check whether 2 pages are beside each other in - * 2-page mode - * @details 2 pages are considered neighbors if the right page is odd and the - * left page is directly after the right page - * @param page1 - the right side page - * @param page2 - the left side page - * @return boolean value indicating whether the given pages are neighbors + * @brief connects signals and slots for different UI components and + * shortcuts */ - bool areNeighbors(int page1, int page2); + void setupConnections(); /** - * @brief 2-page mode utility to switch the active page & verse info list to - * the opposite side of the current - * @details the currently active page is recogonized through - * m_activeQuranBrowser and m_activeVList pointers + * @brief connect VersePlayer signals to their corresponding slots in + * MainWindow */ - void switchActivePage(); + void connectPlayer(); /** - * @brief flip the current page/2-pages to the next page/2-pages + * @brief connect SystemTray signals to their corresponding slots in + * MainWindow */ - void btnNextClicked(); + void connectTray(); /** - * @brief flip the current page/2-pages to the previous page/2-pages + * @brief connect QuranReader specific signals to their corresponding slots */ - void btnPrevClicked(); + void connectReader(); /** - * @brief selects one of the verses in the currently displayed page(s) - * @param browserIdx - index of the QuranPageBrowser which contains the target - * verse - * @param IdxInPage - index of the verse relative to the start of the - * page + * @brief connect Controls/Navigation specific signals to their corresponding + * slots */ - void selectVerse(int browserIdx, int IdxInPage); + void connectControls(); /** - * @brief connect ShortcutHandler signals to their corresponding slots + * @brief connect SettingsDialog signals to their corresponding slots */ - void setupShortcuts(); + void connectSettings(); /** - * @brief connects signals and slots for different UI components and - * shortcuts + * @brief connect menubar actions to their corresponding slots */ - void setupConnections(); + void connectMenubar(); + /** + * @brief register the different notifiers to the NotificationPopup widget + */ + void connectNotifiers(); /** * @brief initialize the surahs QListView in the side dock and select the * current verse's surah @@ -483,27 +353,13 @@ private slots: * @brief set the QPushButton the menubar that toggles the navigation dock and * connect to the appropriate menubar action */ - void setupMenubarToggle(); + void setupMenubarButton(); /** * @brief sync the surahs QListView in the navigation dock to match the - * currently active ::Verse in the VersePlayer + * currently active Verse in the VersePlayer * @return QModelIndex of the currently selected surah */ QModelIndex syncSelectedSurah(); - /** - * @brief updates m_surahCount to match the verse count of the currently - * active verse - */ - void updateSurahVerseCount(); - /** - * @brief updates the list that contains::Verse instances for verses in the - * current page - */ - void updatePageVerseInfoList(); - /** - * @brief sets m_currVerse to the first verse in m_vInfoList - */ - void setVerseToStartOfPage(); /** * @brief set the index of the page combobox without signalling other slots * @param idx - new index to set the combobox to @@ -540,136 +396,97 @@ private slots: */ bool m_internalJuzChange = false; /** - * @brief verse count for the surah of the current active ::Verse - */ - int m_surahCount = 0; - /** - * @brief float value of the current playback volume (0 - 1.0) - */ - qreal m_volume = 1; - /** - * @brief Pointer to access ui elements generated from .ui files - */ - Ui::MainWindow* ui; - /** - * @brief ShortcutHandler instance for handling shortcuts + * @brief QList for surah names as it appears in the navigation dock QListView */ - ShortcutHandler* m_shortcutHandler = nullptr; + QStringList m_surahList; /** - * @brief QScrollArea used in single page mode to display verses & - * translation + * @brief model used with the navigation dock QListView to display complete + * list of surahs */ - QScrollArea* m_scrlVerseByVerse = nullptr; + QStringListModel m_surahListModel; /** - * @brief pointer to currently active QuranPageBrowser instance, must be one - * of the values in m_quranBrowsers array + * @brief m_reader + * + * MODIFIED */ - QuranPageBrowser* m_activeQuranBrowser = nullptr; + QPointer m_reader; /** - * @brief array of QuranPageBrowser instances used in different modes, index 0 - * is used in both modes + * @brief m_playerControls + * + * MODIFIED */ - QuranPageBrowser* m_quranBrowsers[2]{}; + QPointer m_playerControls; /** - * @brief pointer to NotificationManager instance + * @brief pointer to SystemTray instance */ - NotificationManager* m_notifyMgr = nullptr; + QPointer m_systemTray; /** * @brief pointer to NotificationPopup instance */ - NotificationPopup* m_popup = nullptr; + QPointer m_popup; /** * @brief pointer to VersePlayer instance */ - VersePlayer* m_player = nullptr; + QPointer m_player; /** - * @brief pointer to TafsirDialog instance + * @brief pointer to ContentDialog instance */ - TafsirDialog* m_tafsirDlg = nullptr; + QPointer m_contentDlg; /** * @brief pointer to SearchDialog instance */ - SearchDialog* m_searchDlg = nullptr; + QPointer m_searchDlg; /** * @brief pointer to SettingsDialog instance */ - SettingsDialog* m_settingsDlg = nullptr; + QPointer m_settingsDlg; /** * @brief pointer to BookmarksDialog instance */ - BookmarksDialog* m_bookmarksDlg = nullptr; + QPointer m_bookmarksDlg; /** * @brief pointer to KhatmahDialog instance */ - KhatmahDialog* m_khatmahDlg = nullptr; + QPointer m_khatmahDlg; /** * @brief pointer to CopyDialog instance */ - CopyDialog* m_cpyDlg = nullptr; + QPointer m_cpyDlg; /** * @brief pointer to DownloaderDialog instance */ - DownloaderDialog* m_downloaderDlg = nullptr; + QPointer m_downloaderDlg; /** * @brief pointer to DownloadManager instance */ - DownloadManager* m_downManPtr = nullptr; - /** - * @brief pointer to the currently highlighted VerseFrame in the side panel - */ - VerseFrame* m_highlightedFrm = nullptr; + QPointer m_jobMgr; /** * @brief pointer to the surah card (betaqa) widget */ - BetaqaViewer* m_betaqaViewer = nullptr; + QPointer m_betaqaViewer; /** * @brief pointer to the votd dialog */ - VerseDialog* m_verseDlg = nullptr; - /** - * @brief the currently selected ::Verse - */ - Verse m_currVerse{ 1, 1, 1 }; + QPointer m_verseDlg; /** - * @brief QList of QFrame pointers to VerseFrame elements in the single page - * mode side panel + * @brief pointer to the FileSelector dialog used for selecting files for + * import/export */ - QList m_verseFrameList; + QPointer m_selectorDlg; /** - * @brief pointer to the currently active page ::Verse list + * @brief pointer to dialog used for selecting which user data parts to + * import/export */ - const QList* m_activeVList; - /** - * @brief array of 2 QLists of ::Verse instances for the verses in the - * displayed page(s), index 0 is used in both reader modes - */ - QList m_vLists[2]; + QPointer m_importExportDlg; + /** - * @brief pointer to the QProcess instance of the maintainence tool that - * checks for updates + * @brief pointer to VersionChecker instance */ - QProcess* m_process = nullptr; + QPointer m_versionChecker; /** * @brief pointer to the validator for the editable verse combobox to ensure * the number entered is within the surah verse range */ - QIntValidator* m_verseValidator = new QIntValidator(this); - /** - * @brief QList for surah names as it appears in the navigation dock QListView - */ - QStringList m_surahList; - /** - * @brief model used with the navigation dock QListView to display complete - * list of surahs - */ - QStringListModel m_surahListModel; - /** - * @brief QFont used in the side panel translation - */ - QFont m_sideFont; - /** - * @brief QFont used in displaying Quranic verse - */ - QFont m_versesFont; + QPointer m_verseValidator; }; #endif // MAINWINDOW_H diff --git a/src/core/mainwindow.ui b/src/core/mainwindow.ui index b82a513e..c479be11 100644 --- a/src/core/mainwindow.ui +++ b/src/core/mainwindow.ui @@ -25,14 +25,7 @@ false - QDialogButtonBox { -dialogbuttonbox-buttons-have-icons: true; -dialog-apply-icon: off; -dialog-cancel-icon: off; -dialog-ok-icon: off; -dialog-yes-icon: off; -dialog-no-icon: off; -} + QDialogButtonBox { dialogbuttonbox-buttons-have-icons: true; dialog-apply-icon: off; dialog-cancel-icon: off; dialog-ok-icon: off; dialog-yes-icon: off; dialog-no-icon: off; } false @@ -77,595 +70,22 @@ dialog-no-icon: off; 933 - + - 9 + 0 0 - 9 + 6 0 - 9 + 6 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - - 66 - 0 - - - - - - - - - 0 - 0 - - - - - 900 - 16777215 - - - - - 20 - - - 6 - - - 20 - - - 6 - - - - - - 50 - 16777215 - - - - Reciter - - - - - - - - 0 - 0 - - - - - 150 - 0 - - - - - 150 - 16777215 - - - - PointingHandCursor - - - - - - - - 0 - 0 - - - - - 200 - 0 - - - - - 350 - 16777215 - - - - PointingHandCursor - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 42 - 27 - - - - - 52 - 27 - - - - PointingHandCursor - - - QPushButton { - min-width: 40px; - min-height: 25px; - max-width: 50px; - max-height: 25px; -} - - - - - - - :/resources/play.png - - - - false - - - - - - - - 0 - 0 - - - - - 42 - 27 - - - - - 52 - 27 - - - - - Noto Sans Display - - - - PointingHandCursor - - - QPushButton { - min-width: 40px; - min-height: 25px; - max-width: 50px; - max-height: 25px; -} - - - - - - - :/resources/pause.png - - - - - - - - - 0 - 0 - - - - - 42 - 27 - - - - - 52 - 27 - - - - PointingHandCursor - - - QPushButton { - min-width: 40px; - min-height: 25px; - max-width: 50px; - max-height: 25px; -} - - - - - - - :/resources/stop.png - - - - - - - - - 0 - 0 - - - - - 160 - 0 - - - - - 200 - 16777215 - - - - PointingHandCursor - - - - - - 100 - - - 5 - - - 100 - - - Qt::Horizontal - - - false - - - false - - - - - - - - 0 - 0 - - - - - 25 - 25 - - - - - 25 - 25 - - - - - Noto Sans Display - 12 - - - - - - - Qt::PlainText - - - true - - - Qt::AlignCenter - - - 0 - - - - - - - - - - Qt::Horizontal - - - - 66 - 0 - - - - - - - - - - - - 0 - 0 - - - - QFrame::NoFrame - - - QFrame::Raised - - - - 0 - - - 0 - - - - - - 0 - 0 - - - - - 60 - 30 - - - - - Noto Sans Display - 12 - true - - - - PointingHandCursor - - - next - - - QPushButton { - min-width: 60px; - text-align: center; -} - - - - - - false - - - - - - - - 2 - - - 4 - - - 4 - - - 4 - - - 4 - - - - - QFrame#frmSidePanel -{ - background: transparent; -} - - - - QFrame::StyledPanel - - - QFrame::Raised - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - 0 - 0 - - - - - 550 - 0 - - - - *.a { - background-color:palette(base); -} - - - QFrame::NoFrame - - - QFrame::Raised - - - 0 - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - 0 - 0 - - - - - 62 - 0 - - - - - 60 - 30 - - - - - Noto Sans Display - 12 - - - - PointingHandCursor - - - previous - - - QPushButton { - min-width: 60px; - text-align: center; -} - - - - - - - - - @@ -694,6 +114,7 @@ dialog-no-icon: off; + @@ -710,6 +131,9 @@ dialog-no-icon: off; + + + @@ -965,9 +389,7 @@ dialog-no-icon: off; - QLineEdit{ -placeholder-text-color: #c0c0c0; -} + QLineEdit{ placeholder-text-color: #c0c0c0; } Search surah @@ -1099,6 +521,27 @@ placeholder-text-color: #c0c0c0; Toggle reader view + + + Import + + + + + Export + + + + + true + + + true + + + Player controls + + diff --git a/src/core/playercontrols.cpp b/src/core/playercontrols.cpp new file mode 100644 index 00000000..563ee821 --- /dev/null +++ b/src/core/playercontrols.cpp @@ -0,0 +1,188 @@ +#include "playercontrols.h" +#include "ui_playercontrols.h" +#include +#include +#include +using namespace fa; + +PlayerControls::PlayerControls(QWidget* parent, + VersePlayer* player, + QuranReader* reader) + : QWidget(parent) + , ui(new Ui::PlayerControls) + , m_player(player) + , m_reader(reader) + , m_currVerse(Verse::getCurrent()) + , m_config(Configuration::getInstance()) + , m_reciters(Reciter::reciters) +{ + ui->setupUi(this); + loadIcons(); + setupConnections(); + + foreach (const Reciter& r, m_reciters) + ui->cmbReciter->addItem(r.displayName()); + + ui->cmbReciter->setCurrentIndex( + m_config.settings().value("Reciter", 0).toInt()); +} + +void +PlayerControls::loadIcons() +{ + fa::QtAwesome& awesome = StyleManager::getInstance().awesome(); + ui->btnPlay->setIcon(awesome.icon(fa_solid, fa_play)); + ui->btnPause->setIcon(awesome.icon(fa_solid, fa_pause)); + ui->btnStop->setIcon(awesome.icon(fa_solid, fa_stop)); + + ui->lbSpeaker->setText(QString(fa_volume_high)); + ui->lbSpeaker->setFont(awesome.font(fa_solid, 16)); +} + +void +PlayerControls::setupConnections() +{ + const ShortcutHandler& handler = ShortcutHandler::getInstance(); + connect(&handler, + &ShortcutHandler::togglePlayback, + this, + &PlayerControls::togglePlayback); + connect(&handler, + &ShortcutHandler::incrementVolume, + this, + &PlayerControls::incrementVolume); + connect(&handler, + &ShortcutHandler::decrementVolume, + this, + &PlayerControls::decrementVolume); + + connect(m_player, + &QMediaPlayer::positionChanged, + this, + &PlayerControls::mediaPosChanged); + connect(m_player, + &QMediaPlayer::playbackStateChanged, + this, + &PlayerControls::mediaStateChanged); + + connect(ui->sldrAudioPlayer, + &QSlider::sliderMoved, + m_player, + &QMediaPlayer::setPosition); + connect(ui->sldrVolume, + &QSlider::valueChanged, + this, + &PlayerControls::volumeSliderValueChanged); + + connect( + ui->btnPlay, &QPushButton::clicked, this, &PlayerControls::btnPlayClicked); + connect(ui->btnPause, + &QPushButton::clicked, + this, + &PlayerControls::btnPauseClicked); + connect( + ui->btnStop, &QPushButton::clicked, this, &PlayerControls::btnStopClicked); + + connect(ui->cmbReciter, + &QComboBox::currentIndexChanged, + m_player, + &VersePlayer::changeReciter); +} + +void +PlayerControls::togglePlayback() +{ + if (m_player->playbackState() == QMediaPlayer::PlayingState) { + btnPauseClicked(); + } else { + btnPlayClicked(); + } +} + +void +PlayerControls::mediaPosChanged(qint64 position) +{ + if (ui->sldrAudioPlayer->maximum() != m_player->duration()) + ui->sldrAudioPlayer->setMaximum(m_player->duration()); + + if (!ui->sldrAudioPlayer->isSliderDown()) + ui->sldrAudioPlayer->setValue(position); +} + +void +PlayerControls::btnPlayClicked() +{ + m_reader->highlightCurrentVerse(); + m_player->play(); +} + +void +PlayerControls::btnPauseClicked() +{ + m_player->pause(); +} + +void +PlayerControls::btnStopClicked() +{ + m_player->stop(); + m_reader->setVerseToStartOfPage(); + emit currentVerseChanged(); + emit currentSurahChanged(); +} + +void +PlayerControls::mediaStateChanged(QMediaPlayer::PlaybackState state) +{ + if (state == QMediaPlayer::PlayingState) { + ui->btnPlay->setEnabled(false); + ui->btnPause->setEnabled(true); + ui->btnStop->setEnabled(true); + } else if (state == QMediaPlayer::PausedState) { + ui->btnPlay->setEnabled(true); + ui->btnPause->setEnabled(false); + ui->btnStop->setEnabled(true); + } else if (state == QMediaPlayer::StoppedState) { + ui->btnPlay->setEnabled(true); + ui->btnPause->setEnabled(false); + ui->btnStop->setEnabled(false); + } +} + +void +PlayerControls::volumeSliderValueChanged(int position) +{ + qreal linearVolume = + QAudio::convertVolume(ui->sldrVolume->value() / qreal(100.0), + QAudio::LogarithmicVolumeScale, + QAudio::LinearVolumeScale); + if (linearVolume != m_volume) { + m_volume = linearVolume; + m_player->setPlayerVolume(m_volume); + } +} + +void +PlayerControls::incrementVolume() +{ + int val = ui->sldrVolume->value() + 5; + ui->sldrVolume->setValue(val > 100 ? 100 : val); +} + +void +PlayerControls::decrementVolume() +{ + int val = ui->sldrVolume->value() - 5; + ui->sldrVolume->setValue(val < 0 ? 0 : val); +} + +int +PlayerControls::currentReciter() const +{ + return ui->cmbReciter->currentIndex(); +} + +PlayerControls::~PlayerControls() +{ + delete ui; +} diff --git a/src/core/playercontrols.h b/src/core/playercontrols.h new file mode 100644 index 00000000..97c957a9 --- /dev/null +++ b/src/core/playercontrols.h @@ -0,0 +1,116 @@ +#ifndef PLAYERCONTROLS_H +#define PLAYERCONTROLS_H + +#include "quranreader.h" +#include +#include +#include + +namespace Ui { +class PlayerControls; +} + +/** + * @class PlayerControls + * @brief PlayerControls class provides a widget for controlling the + * playback of audio verses from the Quran. + */ +class PlayerControls : public QWidget +{ + Q_OBJECT + +public: + explicit PlayerControls(QWidget* parent, + VersePlayer* player, + QuranReader* reader); + ~PlayerControls(); + + int currentReciter() const; + +public slots: + /** + * @brief toggle play/pause of the current verse + */ + void togglePlayback(); + +signals: + void currentVerseChanged(); + void currentSurahChanged(); + +private slots: + /** + * @brief sets the current position in the audio file as the position of the + * slider + * @param position - position in audio file in milliseconds + */ + void mediaPosChanged(qint64 position); + /** + * @brief continues playback of the current verse + */ + void btnPlayClicked(); + /** + * @brief pause playback of the current verse + */ + void btnPauseClicked(); + /** + * @brief stops playback, sets the current verse to the 1st in the page and + * update verses combobox & selected surah + */ + void btnStopClicked(); + /** + * @brief disables/enables control buttons according to the media player state + * and update the systray tooltip + * @param state - playback state of the current audio file + */ + void mediaStateChanged(QMediaPlayer::PlaybackState state); + /** + * @brief change the player volume level as the volume slider changes + * @param position - position in the slider (0 - 100) + */ + void volumeSliderValueChanged(int position); + /** + * @brief utility to increment the VersePlayer playback volume by steps of 5 + */ + void incrementVolume(); + /** + * @brief utility to decrement the VersePlayer playback volume by steps of 5 + */ + void decrementVolume(); + +private: + Ui::PlayerControls* ui; + /** + * @brief reference to the shared current verse instance + */ + const Verse& m_currVerse; + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the static QList of available reciters + */ + const QList& m_reciters; + /** + * @brief load icons for different UI elements + */ + void loadIcons(); + /** + * @brief connect ShortcutHandler signals to their corresponding slots + */ + void setupConnections(); + /** + * @brief float value of the current playback volume (0 - 1.0) + */ + qreal m_volume = 1; + /** + * @brief pointer to VersePlayer instance + */ + QPointer m_player; + /** + * @brief pointer to the QuranReader instance + */ + QPointer m_reader; +}; + +#endif // PLAYERCONTROLS_H diff --git a/src/core/playercontrols.ui b/src/core/playercontrols.ui new file mode 100644 index 00000000..43350ab4 --- /dev/null +++ b/src/core/playercontrols.ui @@ -0,0 +1,317 @@ + + + PlayerControls + + + + 0 + 0 + 900 + 80 + + + + + 0 + 0 + + + + + 900 + 16777215 + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 4 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 30 + + + 5 + + + 28 + + + 5 + + + + + + 50 + 16777215 + + + + Reciter + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + PointingHandCursor + + + + + + + + 0 + 0 + + + + + 200 + 0 + + + + + 350 + 16777215 + + + + PointingHandCursor + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + 42 + 27 + + + + + 52 + 27 + + + + PointingHandCursor + + + QPushButton { min-width: 40px; min-height: 25px; max-width: 50px; max-height: 25px; } + + + + + + + + 0 + 0 + + + + + 42 + 27 + + + + + 52 + 27 + + + + + Noto Sans Display + 10 + + + + PointingHandCursor + + + QPushButton { min-width: 40px; min-height: 25px; max-width: 50px; max-height: 25px; } + + + + + + + + 0 + 0 + + + + + 42 + 27 + + + + + 52 + 27 + + + + PointingHandCursor + + + QPushButton { min-width: 40px; min-height: 25px; max-width: 50px; max-height: 25px; } + + + + + + + + 0 + 0 + + + + + 160 + 0 + + + + + 200 + 16777215 + + + + PointingHandCursor + + + + + + 100 + + + 5 + + + 100 + + + Qt::Horizontal + + + false + + + false + + + + + + + + 0 + 0 + + + + + 25 + 25 + + + + + 25 + 25 + + + + + Noto Sans Display + 12 + + + + + + + Qt::PlainText + + + true + + + Qt::AlignCenter + + + 0 + + + + + + + + + + + diff --git a/src/core/quranreader.cpp b/src/core/quranreader.cpp new file mode 100644 index 00000000..90aa9a29 --- /dev/null +++ b/src/core/quranreader.cpp @@ -0,0 +1,586 @@ +#include "quranreader.h" +#include "ui_quranreader.h" +#include +#include +#include +#include +#include +using namespace fa; + +QuranReader::QuranReader(QWidget* parent, VersePlayer* player) + : QWidget(parent) + , ui(new Ui::QuranReader) + , m_player(player) + , m_currVerse(Verse::getCurrent()) + , m_config(Configuration::getInstance()) + , m_tafsirDb(TafsirDb::getInstance()) + , m_translationDb(TranslationDb::getInstance()) + , m_bookmarksDb(BookmarksDb::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_glyphsDb(GlyphsDb::getInstance()) + , m_tafasir(Tafsir::tafasir) +{ + ui->setupUi(this); + setLayoutDirection(Qt::LeftToRight); + loadIcons(); + loadReader(); + updateHighlight(); + updateSideFont(); + updateVerseType(); + redrawQuranPage(true); + if (m_config.readerMode() == ReaderMode::SinglePage) + addSideContent(); + + setupConnections(); +} + +void +QuranReader::loadIcons() +{ + ui->btnNext->setIcon( + StyleManager::getInstance().awesome().icon(fa_solid, fa_arrow_left)); + ui->btnPrev->setIcon( + StyleManager::getInstance().awesome().icon(fa_solid, fa_arrow_right)); +} + +void +QuranReader::loadReader() +{ + if (m_config.readerMode() == ReaderMode::SinglePage) { + m_activeQuranBrowser = m_quranBrowsers[0] = + new QuranPageBrowser(ui->frmPageContent, m_currVerse.page()); + + QWidget* scrollWidget = new QWidget(); + scrollWidget->setObjectName("scrollWidget"); + QVBoxLayout* vbl = new QVBoxLayout(); + vbl->setDirection(QBoxLayout::BottomToTop); + scrollWidget->setLayout(vbl); + + m_scrlVerseByVerse = new QScrollArea; + m_scrlVerseByVerse->setWidget(scrollWidget); + m_scrlVerseByVerse->setWidgetResizable(true); + m_scrlVerseByVerse->setStyleSheet( + "QLabel, QAbstractScrollArea, QWidget#scrollWidget { " + "background: " + "transparent }"); + + QHBoxLayout* lyt = qobject_cast(ui->frmReader->layout()); + ui->frmSidePanel->layout()->addWidget(m_scrlVerseByVerse); + lyt->setStretch(0, 1); + lyt->setStretch(1, 0); + this->setMinimumWidth(900); + } + + else { + // even Quran pages are always on the left side + if (m_currVerse.page() % 2 == 0) { + m_quranBrowsers[0] = + new QuranPageBrowser(ui->frmPageContent, m_currVerse.page() - 1); + m_activeQuranBrowser = m_quranBrowsers[1] = + new QuranPageBrowser(ui->frmSidePanel, m_currVerse.page()); + + } else { + m_activeQuranBrowser = m_quranBrowsers[0] = + new QuranPageBrowser(ui->frmPageContent, m_currVerse.page()); + m_quranBrowsers[1] = + new QuranPageBrowser(ui->frmSidePanel, m_currVerse.page() + 1); + } + + ui->frmSidePanel->layout()->addWidget(m_quranBrowsers[1]); + QHBoxLayout* lyt = qobject_cast(ui->frmReader->layout()); + lyt->insertSpacerItem(0, new QSpacerItem(20, 20, QSizePolicy::Expanding)); + lyt->addSpacerItem(new QSpacerItem(20, 20, QSizePolicy::Expanding)); + + lyt->setStretch(0, 1); + lyt->setStretch(1, 0); + lyt->setStretch(2, 0); + lyt->setStretch(3, 1); + } + + ui->frmPageContent->layout()->addWidget(m_quranBrowsers[0]); +} + +void +QuranReader::setupConnections() +{ + connect( + ui->btnNext, &QPushButton::clicked, this, &QuranReader::btnNextClicked); + connect( + ui->btnPrev, &QPushButton::clicked, this, &QuranReader::btnPrevClicked); + for (int i = 0; i <= 1; i++) + if (m_quranBrowsers[i]) + connect(m_quranBrowsers[i], + &QTextBrowser::anchorClicked, + this, + &QuranReader::verseAnchorClicked); + connect(this, + &QuranReader::currentVerseChanged, + m_player, + &VersePlayer::loadActiveVerse); + + const ShortcutHandler& handler = ShortcutHandler::getInstance(); + connect( + &handler, &ShortcutHandler::nextPage, this, &QuranReader::btnNextClicked); + connect( + &handler, &ShortcutHandler::prevPage, this, &QuranReader::btnPrevClicked); + connect(&handler, + &ShortcutHandler::toggleReaderView, + this, + &QuranReader::toggleReaderView); + + for (int i = 0; i <= 1; i++) { + if (m_quranBrowsers[i]) { + connect(&handler, + &ShortcutHandler::zoomIn, + m_quranBrowsers[i], + &QuranPageBrowser::actionZoomIn); + connect(&handler, + &ShortcutHandler::zoomOut, + m_quranBrowsers[i], + &QuranPageBrowser::actionZoomOut); + } + } +} + +void +QuranReader::toggleReaderView() +{ + if (ui->frmPageContent->isVisible() && ui->frmSidePanel->isVisible()) { + ui->frmSidePanel->setVisible(false); + } else if (ui->frmPageContent->isVisible()) { + ui->frmSidePanel->setVisible(true); + ui->frmPageContent->setVisible(false); + } else + ui->frmPageContent->setVisible(true); +} + +void +QuranReader::updateSideFont() +{ + m_sideFont = + qvariant_cast(m_config.settings().value("Reader/SideContentFont")); +} + +void +QuranReader::updateVerseType() +{ + Configuration::VerseType type = qvariant_cast( + m_config.settings().value("Reader/VerseType")); + m_versesFont.setFamily( + FontManager::getInstance().verseFontname(type, m_currVerse.page())); + m_versesFont.setPointSize( + m_config.settings().value("Reader/VerseFontSize").toInt()); + m_config.setVerseType(type); +} + +void +QuranReader::updateHighlight() +{ + for (int i = 0; i <= 1; i++) { + if (m_quranBrowsers[i]) { + m_quranBrowsers[i]->updateHighlightLayer(); + } + } +} + +void +QuranReader::updatePageFontSize() +{ + for (int i = 0; i <= 1; i++) + if (m_quranBrowsers[i]) + m_quranBrowsers[i]->updateFontSize(); +} + +void +QuranReader::redrawQuranPage(bool manualSz) +{ + if (m_activeQuranBrowser == m_quranBrowsers[0]) { + m_quranBrowsers[0]->constructPage(m_currVerse.page(), manualSz); + if (m_config.readerMode() == Configuration::DoublePage && + m_quranBrowsers[1]) + m_quranBrowsers[1]->constructPage(m_currVerse.page() + 1, manualSz); + } else { + m_quranBrowsers[0]->constructPage(m_currVerse.page() - 1, manualSz); + m_quranBrowsers[1]->constructPage(m_currVerse.page(), manualSz); + } + + updatePageVerseInfoList(); +} + +void +QuranReader::addSideContent() +{ + if (m_config.readerMode() != ReaderMode::SinglePage) + return; + + if (!m_verseFrameList.isEmpty()) { + qDeleteAll(m_verseFrameList); + m_verseFrameList.clear(); + m_highlightedFrm = nullptr; + } + + ClickableLabel* verselb; + QLabel* contentLb; + VerseFrame* verseContFrame; + QString prevLbContent, currLbContent, glyphs; + if (m_config.verseType() == Configuration::Qcf) + m_versesFont.setFamily( + FontManager::getInstance().pageFontname(m_currVerse.page())); + + m_translationDb.setCurrentTranslation( + m_config.settings().value("Reader/Translation").toInt()); + for (int i = m_activeVList->size() - 1; i >= 0; i--) { + const Verse* verse = &(m_activeVList->at(i)); + + verseContFrame = new VerseFrame(m_scrlVerseByVerse->widget()); + verselb = new ClickableLabel(verseContFrame); + contentLb = new QLabel(verseContFrame); + glyphs = m_config.verseType() == Configuration::Qcf + ? m_glyphsDb.getVerseGlyphs(verse->surah(), verse->number()) + : m_quranDb.verseText(verse->surah(), verse->number()); + + verseContFrame->setObjectName( + QString("%0_%1").arg(verse->surah()).arg(verse->number())); + + verselb->setFont(m_versesFont); + verselb->setText(glyphs); + verselb->setAlignment(Qt::AlignCenter); + verselb->setWordWrap(true); + + currLbContent = + m_translationDb.getTranslation(verse->surah(), verse->number()); + + if (currLbContent == prevLbContent) { + currLbContent = '-'; + } else { + prevLbContent = currLbContent; + } + + contentLb->setText(currLbContent); + contentLb->setTextInteractionFlags(Qt::TextSelectableByMouse); + contentLb->setAlignment(Qt::AlignCenter); + contentLb->setWordWrap(true); + contentLb->setFont(m_sideFont); + + verseContFrame->layout()->addWidget(verselb); + verseContFrame->layout()->addWidget(contentLb); + m_scrlVerseByVerse->widget()->layout()->addWidget(verseContFrame); + m_verseFrameList.insert(0, verseContFrame); + + // connect clicked signal for each label + connect( + verselb, &ClickableLabel::clicked, this, &QuranReader::verseClicked); + } + + if (m_player->playbackState() == QMediaPlayer::PlayingState) { + setHighlightedFrame(); + } +} + +void +QuranReader::highlightCurrentVerse() +{ + if (m_currVerse.number() == 0) + return; + + // idx may be -1 if verse number is 0 (basmallah) + int idx = m_activeVList->indexOf(m_currVerse); + if (idx < 0) + idx = 0; + + m_activeQuranBrowser->highlightVerse(idx); + + if (m_config.readerMode() == ReaderMode::SinglePage) + setHighlightedFrame(); +} + +void +QuranReader::setHighlightedFrame() +{ + if (m_highlightedFrm != nullptr) + m_highlightedFrm->setSelected(false); + + VerseFrame* verseFrame = m_scrlVerseByVerse->widget()->findChild( + QString("%0_%1").arg(QString::number(m_currVerse.surah()), + QString::number(m_currVerse.number()))); + + verseFrame->setSelected(true); + + m_scrlVerseByVerse->ensureWidgetVisible(verseFrame); + m_highlightedFrm = verseFrame; +} + +void +QuranReader::navigateToVerse(const Verse& v) +{ + gotoPage(v.page(), false); + + m_currVerse.update(v); + emit currentSurahChanged(); + emit currentVerseChanged(); + highlightCurrentVerse(); +} + +void +QuranReader::updatePageVerseInfoList() +{ + if (m_activeQuranBrowser == m_quranBrowsers[0]) { + m_vLists[0] = Verse::fromList(m_quranDb.verseInfoList(m_currVerse.page())); + if (m_config.readerMode() == Configuration::DoublePage) + m_vLists[1] = + Verse::fromList(m_quranDb.verseInfoList(m_currVerse.page() + 1)); + + m_activeVList = &m_vLists[0]; + + } else { + m_vLists[0] = + Verse::fromList(m_quranDb.verseInfoList(m_currVerse.page() - 1)); + m_vLists[1] = Verse::fromList(m_quranDb.verseInfoList(m_currVerse.page())); + + m_activeVList = &m_vLists[1]; + } +} + +void +QuranReader::btnNextClicked() +{ + if (m_config.readerMode() == ReaderMode::SinglePage || + m_currVerse.page() % 2 == 0) + nextPage(1); + else + nextPage(2); +} + +void +QuranReader::btnPrevClicked() +{ + if (m_config.readerMode() == ReaderMode::SinglePage || + m_currVerse.page() % 2 != 0) + prevPage(1); + else + prevPage(2); +} + +bool +QuranReader::areNeighbors(int page1, int page2) +{ + return page1 % 2 != 0 && page2 % 2 == 0 && page2 == page1 + 1; +} + +void +QuranReader::switchActivePage() +{ + if (!m_quranBrowsers[1]) + return; + + m_activeQuranBrowser->resetHighlight(); + if (m_activeQuranBrowser == m_quranBrowsers[0]) { + m_activeQuranBrowser = m_quranBrowsers[1]; + m_activeVList = &m_vLists[1]; + } else { + m_activeQuranBrowser = m_quranBrowsers[0]; + m_activeVList = &m_vLists[0]; + } +} + +void +QuranReader::selectVerse(int browserIdx, int IdxInPage) +{ + if (m_activeQuranBrowser != m_quranBrowsers[browserIdx]) + switchActivePage(); + + const Verse& v = m_vLists[browserIdx].at(IdxInPage); + int currSurah = m_currVerse.surah(); + m_currVerse.update(v); + emit currentVerseChanged(); + + if (currSurah != v.surah()) + emit currentSurahChanged(); +} + +void +QuranReader::setVerseToStartOfPage() +{ + // set the current verse to the verse at the top of the page + m_currVerse.update(m_activeVList->at(0)); +} + +void +QuranReader::verseAnchorClicked(const QUrl& hrefUrl) +{ + if (hrefUrl.toString().at(1) == 'F') { + int surah = hrefUrl.toString().remove("#F").toInt(); + qDebug() << "SURAH CARD:" << surah; + emit showBetaqa(surah); + return; + } + + QuranPageBrowser* senderBrowser = qobject_cast(sender()); + int browerIdx = senderBrowser == m_quranBrowsers[1]; + int idx = hrefUrl.toString().remove('#').toInt(); + Verse v(m_vLists[browerIdx].at(idx)); + + QuranPageBrowser::Action chosenAction = + senderBrowser->lmbVerseMenu(m_bookmarksDb.isBookmarked(v)); + + switch (chosenAction) { + case QuranPageBrowser::Play: + selectVerse(browerIdx, idx); + highlightCurrentVerse(); + m_player->playCurrentVerse(); + break; + case QuranPageBrowser::Select: + selectVerse(browerIdx, idx); + m_player->loadActiveVerse(); + highlightCurrentVerse(); + break; + case QuranPageBrowser::Tafsir: + emit showVerseTafsir(v); + break; + case QuranPageBrowser::Translation: + emit showVerseTranslation(v); + break; + case QuranPageBrowser::Thoughts: + emit showVerseThoughts(v); + break; + case QuranPageBrowser::Copy: + emit copyVerseText(v); + break; + case QuranPageBrowser::AddBookmark: + m_bookmarksDb.addBookmark(v, false); + break; + case QuranPageBrowser::RemoveBookmark: + m_bookmarksDb.removeBookmark(v, false); + default: + break; + } +} + +void +QuranReader::verseClicked() +{ + // object = clickable label, parent = verse frame, verse frame name scheme = + // 'surah_verse' + QStringList data = sender()->parent()->objectName().split('_'); + int surah = data.at(0).toInt(); + int verse = data.at(1).toInt(); + + m_currVerse.setNumber(verse); + emit currentVerseChanged(); + + if (m_currVerse.surah() != surah) { + m_currVerse.setSurah(surah); + emit currentSurahChanged(); + } + + m_player->loadActiveVerse(); + highlightCurrentVerse(); + m_player->play(); +} + +void +QuranReader::gotoPage(int page, bool updateElements, bool automaticFlip) +{ + m_activeQuranBrowser->resetHighlight(); + + if (!automaticFlip) + m_player->stop(); + + if (m_activeQuranBrowser->page() != page) { + if (m_config.readerMode() == ReaderMode::SinglePage) + gotoSinglePage(page); + else + gotoDoublePage(page); + } + + if (updateElements) { + setVerseToStartOfPage(); + emit currentVerseChanged(); + emit currentSurahChanged(); + } +} + +void +QuranReader::gotoSinglePage(int page) +{ + m_currVerse.setPage(page); + redrawQuranPage(); + + m_currVerse.update(m_activeVList->at(0)); + addSideContent(); +} + +void +QuranReader::gotoDoublePage(int page) +{ + int currPage = m_currVerse.page(); + m_currVerse.setPage(page); + + int pageBrowerIdx = page % 2 == 0; + + if (areNeighbors(currPage, page) || areNeighbors(page, currPage)) + switchActivePage(); + else { + m_activeQuranBrowser = m_quranBrowsers[pageBrowerIdx]; + redrawQuranPage(); + } +} + +void +QuranReader::nextPage(int step) +{ + bool keepPlaying = m_player->playbackState() == QMediaPlayer::PlayingState; + if (m_currVerse.page() + step <= 604) { + gotoPage(m_currVerse.page() + step, true, true); + + if (m_config.readerMode() == ReaderMode::SinglePage) + m_scrlVerseByVerse->verticalScrollBar()->setValue(0); + + // if the page is flipped automatically, resume playback + if (keepPlaying) { + m_player->play(); + highlightCurrentVerse(); + } + } +} + +void +QuranReader::prevPage(int step) +{ + bool keepPlaying = m_player->playbackState() == QMediaPlayer::PlayingState; + if (m_currVerse.page() - step >= 1) { + gotoPage(m_currVerse.page() - step, true, true); + + if (m_config.readerMode() == ReaderMode::SinglePage) + m_scrlVerseByVerse->verticalScrollBar()->setValue(0); + + if (keepPlaying) { + m_player->play(); + highlightCurrentVerse(); + } + } +} + +void +QuranReader::gotoSurah(int surahIdx) +{ + // getting surah index + int page = m_quranDb.surahStartPage(surahIdx); + gotoPage(page, false); + + m_currVerse.setPage(page); + m_currVerse.setSurah(surahIdx); + m_currVerse.setNumber((surahIdx == 9 || surahIdx == 1) ? 1 : 0); + + // syncing the player & playing basmalah + m_player->playCurrentVerse(); + + highlightCurrentVerse(); + emit currentVerseChanged(); + emit currentSurahChanged(); +} + +QuranReader::~QuranReader() +{ + delete ui; +} diff --git a/src/core/quranreader.h b/src/core/quranreader.h new file mode 100644 index 00000000..d2a1e531 --- /dev/null +++ b/src/core/quranreader.h @@ -0,0 +1,283 @@ +#ifndef QURANREADER_H +#define QURANREADER_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +typedef Configuration::ReaderMode ReaderMode; + +namespace Ui { +class QuranReader; +} + +/** + * @class QuranReader + * @brief The QuranReader class is responsible for displaying the Quran page(s) + * and the side content + */ +class QuranReader : public QWidget +{ + Q_OBJECT + +public: + /** + * @brief class constructor + * @param parent - pointer to parent widget + * @param player - pointer to VersePlayer instance + */ + explicit QuranReader(QWidget* parent, VersePlayer* player); + ~QuranReader(); + +public slots: + /** + * @brief highlight the currently active Verse m_currVerse in the + * active QuranPageBrowser and the side panel depending on the ::ReaderMode + */ + void highlightCurrentVerse(); + /** + * @brief highlight the currently active VerseFrame + */ + void setHighlightedFrame(); + /** + * @brief navigate to the given Verse and update UI elements accordingly + * @param v - Verse to navigate to + */ + void navigateToVerse(const Verse& v); + /** + * @brief toggle the main reader view by hiding one of the panels + */ + void toggleReaderView(); + /** + * @brief redraw the current Quran page + * @param manualSz - boolean flag to force the use of the manually set + * fontsize + */ + void redrawQuranPage(bool manualSz = false); + /** + * @brief updates the side panel with the translation of the current page + * verses + */ + void addSideContent(); + /** + * @brief set side content font to the one in the settings + */ + void updateSideFont(); + /** + * @brief Updates the type of the verses shown and reload the font family and + * size + */ + void updateVerseType(); + /** + * @brief update the highlight layer (fg/bg) used by the Quran browser(s) + */ + void updateHighlight(); + /** + * @brief gets the page of the 1st verse in this surah, moves to that page, + * and starts playback of the surah + * @param surahIdx - surah number (1-114) + */ + void gotoSurah(int surahIdx); + /** + * @brief ensure the page given is visible and update other members to match + * the properties of the first verse in the page, calls the appropriate + * navigation function according to the ::ReaderMode + * @param page - page to navigate to + * @param updateElements - boolean flag to indicate whether to update other + * elements or not + * @param automaticFlip - boolean indicating whether the function was called + * by internal signal to automatically flip the page + */ + void gotoPage(int page, + bool updateElements = true, + bool automaticFlip = false); + /** + * @brief sets m_currVerse to the first verse in m_vInfoList + */ + void setVerseToStartOfPage(); + /** + * @brief slot for updating the page font size of all quran pages + */ + void updatePageFontSize(); + +signals: + void currentVerseChanged(); + void currentSurahChanged(); + void showBetaqa(int surah); + void copyVerseText(const Verse& v); + void showVerseTafsir(const Verse& v); + void showVerseTranslation(const Verse& v); + void showVerseThoughts(const Verse& v); + +private slots: + /** + * @brief callback function for clicking verses in the QuranPageBrowser that + * takes actions based on the chosen option in the menu + * @param hrefUrl - "#idx" where idx is the verse index relative to the start + * of the page (=index in the page Verse QList) + */ + void verseAnchorClicked(const QUrl& hrefUrl); + /** + * @brief slot to navigate to the clicked verse in the side panel and update + * UI elements + */ + void verseClicked(); + /** + * @brief navigates to the next page relative to the current page + */ + void nextPage(int step = 1); + /** + * @brief navigates to the previous page relative to the current page + */ + void prevPage(int step = 1); + /** + * @brief single page mode navigation + * @param page - page to navigate to + */ + void gotoSinglePage(int page); + /** + * @brief double page mode navigation + * @param page - page to navigate to + */ + void gotoDoublePage(int page); + +private: + Ui::QuranReader* ui; + /** + * @brief reference to the shared current verse instance + */ + Verse& m_currVerse; + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the singleton TafsirDb instance + */ + TafsirDb& m_tafsirDb; + /** + * @brief reference to the singleton TranslationDb instance + */ + TranslationDb& m_translationDb; + /** + * @brief reference to the singleton BookmarksDb instance + */ + BookmarksDb& m_bookmarksDb; + /** + * @brief reference to the singleton QuranDb instance + */ + QuranDb& m_quranDb; + /** + * @brief reference to the singleton GlyphsDb instance + */ + const GlyphsDb& m_glyphsDb; + /** + * @brief reference to the static QList of available tafasir + */ + const QList& m_tafasir; + /** + * @brief connects signals and slots for different UI components and + * shortcuts + */ + void setupConnections(); + /** + * @brief load icons for different UI elements + */ + void loadIcons(); + /** + * @brief setup the reader layout and create widgets according to the current + * ::ReaderMode + */ + void loadReader(); + /** + * @brief utility function to check whether 2 pages are beside each other in + * 2-page mode + * @details 2 pages are considered neighbors if the right page is odd and the + * left page is directly after the right page + * @param page1 - the right side page + * @param page2 - the left side page + * @return boolean value indicating whether the given pages are neighbors + */ + bool areNeighbors(int page1, int page2); + /** + * @brief 2-page mode utility to switch the active page & verse info list to + * the opposite side of the current + * @details the currently active page is recogonized through + * m_activeQuranBrowser and m_activeVList pointers + */ + void switchActivePage(); + /** + * @brief flip the current page/2-pages to the next page/2-pages + */ + void btnNextClicked(); + /** + * @brief flip the current page/2-pages to the previous page/2-pages + */ + void btnPrevClicked(); + /** + * @brief selects one of the verses in the currently displayed page(s) + * @param browserIdx - index of the QuranPageBrowser which contains the target + * verse + * @param IdxInPage - index of the verse relative to the start of the + * page + */ + void selectVerse(int browserIdx, int IdxInPage); + /** + * @brief updates the list that containsVerse instances for verses in the + * current page + */ + void updatePageVerseInfoList(); + /** + * @brief QScrollArea used in single page mode to display verses & + * translation + */ + QPointer m_scrlVerseByVerse; + /** + * @brief pointer to currently active QuranPageBrowser instance, must be one + * of the values in m_quranBrowsers array + */ + QPointer m_activeQuranBrowser; + /** + * @brief array of QuranPageBrowser instances used in different modes, index 0 + * is used in both modes + */ + QPointer m_quranBrowsers[2]; + /** + * @brief QList of QFrame pointers to VerseFrame elements in the single page + * mode side panel + */ + QList> m_verseFrameList; + /** + * @brief pointer to the currently active page Verse list + */ + const QList* m_activeVList; + /** + * @brief array of 2 QLists of Verse instances for the verses in the + * displayed page(s), index 0 is used in both reader modes + */ + QList m_vLists[2]; + /** + * @brief pointer to the currently highlighted VerseFrame in the side panel + */ + QPointer m_highlightedFrm; + /** + * @brief pointer to the VersePlayer instance + */ + QPointer m_player; + /** + * @brief QFont used in the side panel translation + */ + QFont m_sideFont; + /** + * @brief QFont used in displaying Quranic verse + */ + QFont m_versesFont; +}; + +#endif // QURANREADER_H diff --git a/src/core/quranreader.ui b/src/core/quranreader.ui new file mode 100644 index 00000000..5fcbce07 --- /dev/null +++ b/src/core/quranreader.ui @@ -0,0 +1,210 @@ + + + QuranReader + + + + 0 + 0 + 942 + 700 + + + + + + + + 6 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 60 + 30 + + + + + Noto Sans Display + 12 + true + + + + PointingHandCursor + + + next + + + QPushButton { min-width: 60px; text-align: center; } + + + + + + + + + false + + + + + + + + 2 + + + 4 + + + 0 + + + 4 + + + 0 + + + + + QFrame#frmSidePanel { background: transparent; } + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + + 550 + 0 + + + + *.a { background-color:palette(base); } + + + QFrame::NoFrame + + + QFrame::Raised + + + 0 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + 0 + 0 + + + + + 62 + 0 + + + + + 60 + 30 + + + + + Noto Sans Display + 12 + + + + PointingHandCursor + + + previous + + + QPushButton { min-width: 60px; text-align: center; } + + + + + + + + + + + + + + diff --git a/src/core/tafsirdialog.cpp b/src/core/tafsirdialog.cpp deleted file mode 100644 index c8cb5c1c..00000000 --- a/src/core/tafsirdialog.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @file tafsirdialog.cpp - * @brief Implementation file for TafsirDialog - */ - -#include "tafsirdialog.h" -#include "ui_tafsirdialog.h" - -TafsirDialog::TafsirDialog(QWidget* parent) - : QDialog(parent) - , ui(new Ui::TafsirDialog) -{ - setWindowIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_book_open)); - ui->setupUi(this); - ui->btnNext->setIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_left)); - ui->btnPrev->setIcon( - Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_right)); - - setTafsirAsTitle(); - setLayoutDirection(Qt::LeftToRight); - if (m_qcfVer == 1) - m_fontSZ = 18; - else - m_fontSZ = 16; - - // connectors - setupConnections(); -} - -void -TafsirDialog::btnNextClicked() -{ - if (m_shownVerse.number == m_dbMgr->getSurahVerseCount(m_shownVerse.surah) && - m_shownVerse.surah < 114) { - m_shownVerse.number = 1; - m_shownVerse.surah++; - } else { - m_shownVerse.number++; - } - - m_shownVerse.page = - m_dbMgr->getVersePage(m_shownVerse.surah, m_shownVerse.number); - loadVerseTafsir(); -} - -void -TafsirDialog::btnPrevClicked() -{ - if (m_shownVerse.number == 1 && m_shownVerse.surah > 1) { - m_shownVerse.surah--; - m_shownVerse.number = m_dbMgr->getSurahVerseCount(m_shownVerse.surah); - } else { - m_shownVerse.number--; - } - - m_shownVerse.page = - m_dbMgr->getVersePage(m_shownVerse.surah, m_shownVerse.number); - loadVerseTafsir(); -} - -void -TafsirDialog::setupConnections() -{ - connect( - ui->btnNext, &QPushButton::clicked, this, &TafsirDialog::btnNextClicked); - connect( - ui->btnPrev, &QPushButton::clicked, this, &TafsirDialog::btnPrevClicked); -} - -void -TafsirDialog::setTafsirAsTitle() -{ - QString title = m_dbMgr->currTafsir()->displayName; - setWindowTitle(title); -} - -void -TafsirDialog::loadVerseTafsir() -{ - QString title = tr("Surah: ") + m_dbMgr->getSurahName(m_shownVerse.surah) + - " - " + tr("Verse: ") + QString::number(m_shownVerse.number); - QString glyphs = - m_dbMgr->getVerseGlyphs(m_shownVerse.surah, m_shownVerse.number); - QString fontFamily = - Globals::verseFontname(m_dbMgr->getVerseType(), m_shownVerse.page); - - ui->lbVerseInfo->setText(title); - ui->lbVerseText->setWordWrap(true); - ui->lbVerseText->setFont(QFont(fontFamily, m_fontSZ)); - ui->lbVerseText->setText(glyphs); - - QFont sideFont = - qvariant_cast(m_settings->value("Reader/SideContentFont")); - ui->tedTafsir->setFont(sideFont); - - if (m_dbMgr->currTafsir()->text) - ui->tedTafsir->setText( - m_dbMgr->getTafsir(m_shownVerse.surah, m_shownVerse.number)); - else - ui->tedTafsir->setHtml( - m_dbMgr->getTafsir(m_shownVerse.surah, m_shownVerse.number)); - - if (m_shownVerse.surah == 1 && m_shownVerse.number == 1) - ui->btnPrev->setDisabled(true); - else if (m_shownVerse.surah == 114 && m_shownVerse.number == 6) - ui->btnNext->setDisabled(true); - else { - ui->btnPrev->setDisabled(false); - ui->btnNext->setDisabled(false); - } -} - -void -TafsirDialog::setShownVerse(const Verse& newShownVerse) -{ - m_shownVerse = newShownVerse; -} - -void -TafsirDialog::closeEvent(QCloseEvent* event) -{ - this->hide(); -} - -void -TafsirDialog::showEvent(QShowEvent* event) -{ - setTafsirAsTitle(); - QDialog::showEvent(event); -} - -TafsirDialog::~TafsirDialog() -{ - delete ui; -} diff --git a/src/core/tafsirdialog.h b/src/core/tafsirdialog.h deleted file mode 100644 index 0932dd8e..00000000 --- a/src/core/tafsirdialog.h +++ /dev/null @@ -1,100 +0,0 @@ -/** - * @file tafsirdialog.h - * @brief Header file for TafsirDialog - */ - -#ifndef TAFSIRDIALOG_H -#define TAFSIRDIALOG_H - -#include "../globals.h" -#include "../utils/dbmanager.h" -#include -#include -#include - -namespace Ui { -class TafsirDialog; -} - -/** - * @brief TafsirDialog interface for reading Quran tafsir. - * @details Tafsir is shown for a single verse at a time. Navigation between - * verses is independant of the main Quran reader navigation for easier - * navigation. Tafsir is displayed using the side content font set in the - * Globals::settings. - */ -class TafsirDialog : public QDialog -{ - Q_OBJECT - -public: - /** - * @brief Class constructor - * @param parent - pointer to parent widget - */ - explicit TafsirDialog(QWidget* parent = nullptr); - ~TafsirDialog(); - - /** - * @brief loads the info and tafsir of TafsirDialog::m_shownVerse - */ - void loadVerseTafsir(); - /** - * @brief setter member for TafsirDialog::m_shownVerse - * @param newShownVerse - */ - void setShownVerse(const Verse& newShownVerse); - -protected: - /** @brief Re-implementation of QWidget::closeEvent() in order to hide the - * window instead of closing it. - * @param event - */ - void closeEvent(QCloseEvent* event); - /** - * @brief Re-implementation of QWidget::showEvent() in order to change the - * dialog title to match the current tafsir. - * @param event - */ - void showEvent(QShowEvent* event); - -private slots: - /** - * @brief increment the TafsirDialog::m_shownVerse and load the new verse - * tafsir. - */ - void btnNextClicked(); - /** - * @brief decrement the TafsirDialog::m_shownVerse and load the new verse - * tafsir. - */ - void btnPrevClicked(); - -private: - const int m_qcfVer = Globals::qcfVersion; - const QSettings* m_settings = Globals::settings; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); - /** - * @brief connects signals and slots for different UI - * components and shortcuts. - */ - void setupConnections(); - /** - * @brief sets the Tafsir dialog title to match the displayed tafsir name. - */ - void setTafsirAsTitle(); - /** - * @brief Pointer to access ui elements generated from .ui files. - */ - Ui::TafsirDialog* ui; - /** - * @brief fixed font size for the verse text displayed above the tafsir. - */ - int m_fontSZ; - /** - * @brief ::Verse instance representing the shown verse. - */ - Verse m_shownVerse{ 1, 1, 1 }; -}; - -#endif // TAFSIRDIALOG_H diff --git a/src/core/tafsirdialog.ui b/src/core/tafsirdialog.ui deleted file mode 100644 index ee91c579..00000000 --- a/src/core/tafsirdialog.ui +++ /dev/null @@ -1,152 +0,0 @@ - - - TafsirDialog - - - - 0 - 0 - 910 - 593 - - - - Tafsir - - - - - - s-v - - - - - - - - Inter - 10 - - - - - - - - - - - - PakType Naskh Basic - 15 - - - - QFrame { - border-radius: 4px; -} - - - false - - - true - - - 0 - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - 3 - - - 3 - - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - - Noto Sans Display - 12 - false - - - - PointingHandCursor - - - next - - - - - - Left - - - - - - - - 0 - 25 - - - - - 16777215 - 25 - - - - - Noto Sans Display - 12 - 50 - false - false - false - true - - - - PointingHandCursor - - - previous - - - - - - Right - - - - - - - - - - diff --git a/src/database/betaqatdb.cpp b/src/database/betaqatdb.cpp new file mode 100644 index 00000000..b465435f --- /dev/null +++ b/src/database/betaqatdb.cpp @@ -0,0 +1,50 @@ +#include "betaqatdb.h" +#include + +BetaqatDb& +BetaqatDb::getInstance() +{ + static BetaqatDb bdb; + return bdb; +} + +BetaqatDb::BetaqatDb() + : QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "BetaqatCon")) + , m_assetsDir(DirManager::getInstance().assetsDir()) + , m_config(Configuration::getInstance()) +{ + BetaqatDb::open(); +} + +void +BetaqatDb::open() +{ + setDatabaseName(m_assetsDir.absoluteFilePath("betaqat.db")); + if (!QSqlDatabase::open()) + qFatal("Error opening betaqat db"); +} + +DbConnection::Type +BetaqatDb::type() +{ + return DbConnection::Betaqat; +} + +QString +BetaqatDb::getBetaqa(const int surah) +{ + QSqlQuery dbQuery(*this); + + if (m_config.language() == QLocale::Arabic) + dbQuery.prepare("SELECT text FROM content WHERE sura=:i"); + else + dbQuery.prepare("SELECT text_en FROM content WHERE sura=:i"); + + dbQuery.bindValue(0, surah); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getBetaqa SQL statment exec"; + } + + dbQuery.next(); + return dbQuery.value(0).toString(); +} diff --git a/src/database/betaqatdb.h b/src/database/betaqatdb.h new file mode 100644 index 00000000..c52edf91 --- /dev/null +++ b/src/database/betaqatdb.h @@ -0,0 +1,52 @@ +#ifndef BETAQATDB_H +#define BETAQATDB_H + +#include +#include +#include +#include +#include + +/** + * @class BetaqatDb + * @brief The BetaqatDb class represents a connection to the betaqat db file + */ +class BetaqatDb + : public DbConnection + , QSqlDatabase +{ +public: + /** + * @brief get a reference to the single class instance + * @return reference to the static class instance + */ + static BetaqatDb& getInstance(); + /** + * @brief sets and opens the sqlite database file + */ + void open(); + /** + * @brief getter for the type of the connection + * @return - DbConnection::Betaqat + */ + Type type(); + /** + * @brief get the surah card (betaqa) content + * @param surah - surah number + * @return QString of the surah card text + */ + QString getBetaqa(const int surah); + +private: + BetaqatDb(); + /** + * @brief reference to the singleton Configuration instance + */ + const Configuration& m_config; + /** + * @brief reference to the app assets directory + */ + const QDir& m_assetsDir; +}; + +#endif // BETAQATDB_H diff --git a/src/database/bookmarksdb.cpp b/src/database/bookmarksdb.cpp new file mode 100644 index 00000000..2d4a2644 --- /dev/null +++ b/src/database/bookmarksdb.cpp @@ -0,0 +1,338 @@ +#include "bookmarksdb.h" +#include +#include + +BookmarksDb& +BookmarksDb::getInstance() +{ + static BookmarksDb bookmarkDb; + return bookmarkDb; +} + +BookmarksDb::BookmarksDb() + : QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "BookmarksCon")) + , m_notifier(this) + , m_config(Configuration::getInstance()) + , m_configDir(DirManager::getInstance().configDir()) + , m_quranDb(QuranDb::getInstance()) +{ + BookmarksDb::open(); + QSqlQuery dbQuery(*this); + dbQuery.exec( + "CREATE TABLE IF NOT EXISTS khatmah(id INTEGER PRIMARY KEY " + "AUTOINCREMENT, name TEXT, page INTEGER, surah INTEGER, number INTEGER)"); + + dbQuery.exec("CREATE TABLE IF NOT EXISTS favorites(id INTEGER PRIMARY KEY " + "AUTOINCREMENT," + "page INTEGER, surah INTEGER, number INTEGER)"); + + dbQuery.exec("CREATE TABLE IF NOT EXISTS thoughts(id INTEGER PRIMARY KEY " + "UNIQUE," + "page INTEGER, surah INTEGER, number INTEGER, text TEXT)"); +} + +void +BookmarksDb::open() +{ + setDatabaseName(m_configDir.absoluteFilePath("bookmarks.db")); + if (!QSqlDatabase::open()) + qFatal("Error opening bookmarks db"); +} + +DbConnection::Type +BookmarksDb::type() +{ + return DbConnection::Bookmarks; +} + +bool +BookmarksDb::saveActiveKhatmah(const Verse& verse) +{ + QSqlQuery dbQuery(*this); + QString q = QString::asprintf( + "UPDATE khatmah SET page=%i, surah=%i, number=%i WHERE id=%i", + verse.page(), + verse.surah(), + verse.number(), + m_activeKhatmah); + if (!dbQuery.exec(q)) { + qCritical() << "Couldn't save position in mushaf"; + return false; + } + if (!commit()) + return false; + + return true; +} + +QList +BookmarksDb::getAllKhatmah() const +{ + QList res; + QSqlQuery dbQuery(*this); + if (!dbQuery.exec("SELECT id FROM khatmah")) + qCritical() << "Couldn't execute sql query: " << dbQuery.lastQuery(); + + while (dbQuery.next()) + res.append(dbQuery.value(0).toInt()); + + return res; +} + +QString +BookmarksDb::getKhatmahName(const int id) const +{ + QSqlQuery dbQuery(*this); + if (!dbQuery.exec("SELECT name FROM khatmah WHERE id=" + QString::number(id))) + qCritical() << "Couldn't execute sql query: " << dbQuery.lastQuery(); + + dbQuery.next(); + return dbQuery.value(0).toString(); +} + +bool +BookmarksDb::loadVerse(const int khatmahId, Verse& verse) const +{ + QSqlQuery dbQuery(*this); + + QString q = QString::asprintf( + "SELECT page,surah,number FROM khatmah WHERE id=%i", khatmahId); + if (!dbQuery.exec(q)) { + qCritical() << "Couldn't execute getPosition SQL query!"; + return false; + } + if (!dbQuery.next()) + return false; + + verse.setPage(dbQuery.value(0).toInt()); + verse.setSurah(dbQuery.value(1).toInt()); + verse.setNumber(dbQuery.value(2).toInt()); + return true; +} + +int +BookmarksDb::addKhatmah(const Verse& verse, + const QString name, + const int id) const +{ + QSqlQuery dbQuery(*this); + QString q; + if (id == -1) { + q = "INSERT INTO khatmah(name, page, surah, number) VALUES ('%0', %1, %2, " + "%3)"; + dbQuery.prepare(q.arg(name, + QString::number(verse.page()), + QString::number(verse.surah()), + QString::number(verse.number()))); + } else { + q = "REPLACE INTO khatmah VALUES " + "(%0, " + "'%1', %2, %3, %4)"; + dbQuery.prepare(q.arg(QString::number(id), + name, + QString::number(verse.page()), + QString::number(verse.surah()), + QString::number(verse.number()))); + } + + if (!dbQuery.exec()) { + qCritical() << "Couldn't create new khatmah entry!"; + qDebug() << lastError(); + return -1; + } + + if (id != -1) + return id; + + dbQuery.exec("SELECT id FROM khatmah ORDER BY id DESC limit 1"); + dbQuery.next(); + return dbQuery.value(0).toInt(); +} + +bool +BookmarksDb::editKhatmahName(const int khatmahId, QString newName) +{ + QSqlQuery dbQuery(*this); + QString q = "SELECT DISTINCT id FROM khatmah WHERE name='%0'"; + if (!dbQuery.exec(q.arg(newName))) { + qCritical() << "Couldn't execute sql query: " << dbQuery.lastQuery(); + qDebug() << lastError(); + return false; + } + if (dbQuery.next()) + return false; + + q = "UPDATE khatmah SET name='%0' WHERE id=%1"; + if (!dbQuery.exec(q.arg(newName, QString::number(khatmahId)))) { + qCritical() << "Couldn't rename khatmah entry!"; + qDebug() << lastError(); + return false; + } + + commit(); + return true; +} + +void +BookmarksDb::removeKhatmah(const int id) const +{ + QSqlQuery dbQuery(*this); + if (!dbQuery.exec(QString::asprintf("DELETE FROM khatmah WHERE id=%i", id))) + qDebug() << "Couldn't execute query: " << dbQuery.lastQuery(); +} + +QList +BookmarksDb::bookmarkedVerses(int surahIdx) const +{ + QList results; + QSqlQuery dbQuery(*this); + QString q = "SELECT page,surah,number FROM favorites"; + if (surahIdx != -1) + q.append(" WHERE surah=" + QString::number(surahIdx)); + + dbQuery.prepare(q.append(" ORDER BY surah, number")); + if (!dbQuery.exec()) + qCritical() << "Couldn't execute bookmarkedVerses SELECT query"; + + while (dbQuery.next()) { + results.append(Verse(dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt())); + } + + return results; +} + +bool +BookmarksDb::isBookmarked(const Verse& verse) const +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare( + "SELECT page FROM favorites WHERE page=:p AND surah=:s AND number=:n"); + dbQuery.bindValue(0, verse.page()); + dbQuery.bindValue(1, verse.surah()); + dbQuery.bindValue(2, verse.number()); + + if (!dbQuery.exec()) { + qWarning() << "Couldn't check if verse is bookmarked"; + return false; + } + + dbQuery.next(); + + return dbQuery.isValid(); +} + +bool +BookmarksDb::addBookmark(const Verse& verse, bool silent) +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare( + "INSERT INTO favorites(page, surah, number) VALUES (:p, :s, :n)"); + dbQuery.bindValue(0, verse.page()); + dbQuery.bindValue(1, verse.surah()); + dbQuery.bindValue(2, verse.number()); + + if (!dbQuery.exec()) { + qWarning() << "Couldn't add verse to bookmarks db"; + return false; + } + + commit(); + if (!silent) + m_notifier.notifyAdded(); + return true; +} + +bool +BookmarksDb::removeBookmark(const Verse& verse, bool silent) +{ + QSqlQuery dbQuery(*this); + dbQuery.prepare( + "DELETE FROM favorites WHERE page=:p AND surah=:s AND number=:n"); + dbQuery.bindValue(0, verse.page()); + dbQuery.bindValue(1, verse.surah()); + dbQuery.bindValue(2, verse.number()); + + if (!dbQuery.exec()) { + qWarning() << "Couldn't remove verse from bookmarks"; + return false; + } + + if (!silent) + m_notifier.notifyRemoved(); + return true; +} + +void +BookmarksDb::saveThoughts(Verse& verse, const QString& text) +{ + int id = Verse::id(verse.surah(), verse.number()); + QSqlQuery dbQuery(*this); + dbQuery.prepare("REPLACE INTO thoughts(id, page, surah, number, text) " + "VALUES(:i, :p, :s, :n, :t)"); + dbQuery.bindValue(0, id); + dbQuery.bindValue(1, verse.page()); + dbQuery.bindValue(2, verse.surah()); + dbQuery.bindValue(3, verse.number()); + dbQuery.bindValue(4, text); + + if (!dbQuery.exec()) + qCritical() << "SQL statement execution error:" << dbQuery.lastError(); + + commit(); +} + +QString +BookmarksDb::getThoughts(const Verse& verse) const +{ + QSqlQuery dbQuery(*this); + dbQuery.prepare( + "SELECT text FROM thoughts WHERE page=:p AND surah=:s AND number=:n"); + dbQuery.bindValue(0, verse.page()); + dbQuery.bindValue(1, verse.surah()); + dbQuery.bindValue(2, verse.number()); + + if (!dbQuery.exec()) + qCritical() << "SQL statement execution error:" << dbQuery.lastError(); + + dbQuery.next(); + return dbQuery.value(0).toString(); +} + +QList> +BookmarksDb::allThoughts() const +{ + QList> all; + QSqlQuery dbQuery(*this); + dbQuery.exec("SELECT page,surah,number,text FROM thoughts WHERE text!=''"); + while (dbQuery.next()) { + const Verse verse(dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt()); + + all.append({ verse, dbQuery.value(3).toString() }); + } + + return all; +} + +void +BookmarksDb::setActiveKhatmah(const int id) +{ + m_activeKhatmah = id; +} + +int +BookmarksDb::activeKhatmah() const +{ + return m_activeKhatmah; +} + +const BookmarksNotifier* +BookmarksDb::notifier() const +{ + return &m_notifier; +} diff --git a/src/database/bookmarksdb.h b/src/database/bookmarksdb.h new file mode 100644 index 00000000..bf734d86 --- /dev/null +++ b/src/database/bookmarksdb.h @@ -0,0 +1,169 @@ +#ifndef BOOKMARKSDB_H +#define BOOKMARKSDB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @class BookmarksDb + * @brief The BookmarksDb class represents a connection to the bookmarks db file + * used for storing user data (bookmarks,khatmah,thoughts) + */ +class BookmarksDb + : public DbConnection + , QSqlDatabase +{ +public: + /** + * @brief get a reference to the single class instance + * @return reference to the static class instance + */ + static BookmarksDb& getInstance(); + /** + * @brief sets and opens the sqlite database file + */ + void open(); + /** + * @brief getter for the type of the connection + * @return - DbConnection::Bookmarks + */ + Type type(); + /** + * @brief sets the given Verse as the last position reached in the current + * active khatmah + * @param v - Verse reached in khatmah + */ + bool saveActiveKhatmah(const Verse& verse); + /** + * @brief get all available khatmah ids + * @return QList of khatmah id(s) + */ + QList getAllKhatmah() const; + /** + * @brief get the name of the khatmah with id given + * @return QString containing the khatmah name + */ + QString getKhatmahName(const int id) const; + /** + * @brief gets the last position saved for the khatmah with the id given and + * stores the position in the Verse v + * @return boolean indicating a successful operation (false in case of error + * and in case id does not exist) + */ + bool loadVerse(const int khatmahId, Verse& verse) const; + /** + * @brief add a new khatmah/replace khatmah with given id with position of + * Verse v + * @param v - Verse to set as the khatmah position + * @param name - new khatmah name + * @param id - id of khatmah to replace, -1 means do not replace (default: -1) + * @return id of newly added khatmah or id parameter if defined + */ + int addKhatmah(const Verse& verse, + const QString name, + const int id = -1) const; + /** + * @brief rename the khatmah with the given id to newName + * @param khatmahId - id of khatmah to rename + * @param newName - new name to set + * @return boolean indicating a successful operation (false in case the name + * exists) + */ + bool editKhatmahName(const int khatmahId, QString newName); + /** + * @brief remove the khatmah with the given id from database + * @param id - id of khatmah to remove + */ + void removeKhatmah(const int id) const; + /** + * @brief gets a QList of Verse instances representing the bookmarked verse + * within the given sura (default gets all) + * @param surahIdx - sura number (-1 returns all bookmarks) + * @return QList of bookmarked verses + */ + QList bookmarkedVerses(int surahIdx = -1) const; + /** + * @brief checks whether the given Verse is bookmarked + * @param vInfo - Verse instance to check + * @return boolean + */ + bool isBookmarked(const Verse& verse) const; + /** + * @brief add the given Verse to bookmarks + * @param vInfo - Verse instance to add + * @return boolean + */ + bool addBookmark(const Verse& verse, bool silent); + /** + * @brief remove the given Verse from bookmarks + * @param vInfo - Verse instance to remove + * @return boolean indicating successful removal + */ + bool removeBookmark(const Verse& verse, bool silent); + /** + * @brief save thoughts for the verse given + * @param verse - Verse to save the thoughts for + * @param text - QString of the thoughts text + */ + void saveThoughts(Verse& verse, const QString& text); + /** + * @brief get the user stored thoughts for the verse given + * @param verse - Verse to get thoughts for + * @return QString of the thought text + */ + QString getThoughts(const Verse& verse) const; + /** + * @brief get all user stored thoughts + * @return QList of thoughts represented as QPair(s) of Verse and QString of + * the thought text + */ + QList> allThoughts() const; + /** + * @brief setter for m_activeKhatmah + * @param id - id of the active khatmah + */ + void setActiveKhatmah(const int id); + /** + * @brief getter for m_activeKhatmah + * @return int - id of active khatmah + */ + int activeKhatmah() const; + /** + * @brief returns the address of the class notifier + * @return - pointer to BookmarksNotifier instance + */ + const BookmarksNotifier* notifier() const; + +private: + BookmarksDb(); + /** + * @brief reference to the singleton QuranDb instance + */ + const QuranDb& m_quranDb; + /** + * @brief reference to the singleton Configuration instance + */ + const Configuration& m_config; + /** + * @brief reference to the app configuration directory + */ + const QDir& m_configDir; + /** + * @brief notifer instance used for sending notifications + */ + BookmarksNotifier m_notifier; + /** + * @brief integer id of the current active khatmah + */ + int m_activeKhatmah = 0; +}; + +#endif // BOOKMARKSDB_H diff --git a/src/database/glyphsdb.cpp b/src/database/glyphsdb.cpp new file mode 100644 index 00000000..88cdd6c5 --- /dev/null +++ b/src/database/glyphsdb.cpp @@ -0,0 +1,101 @@ +#include "glyphsdb.h" +#include + +GlyphsDb& +GlyphsDb::getInstance() +{ + static GlyphsDb gdb; + return gdb; +} + +GlyphsDb::GlyphsDb() + : QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "GlyphsCon")) + , m_config(Configuration::getInstance()) + , m_assetsDir(DirManager::getInstance().assetsDir()) +{ + GlyphsDb::open(); +} + +void +GlyphsDb::open() +{ + setDatabaseName(m_assetsDir.absoluteFilePath("glyphs.db")); + if (!QSqlDatabase::open()) + qFatal("Error opening glyphs db"); +} + +DbConnection::Type +GlyphsDb::type() +{ + return DbConnection::Glyphs; +} + +QStringList +GlyphsDb::getPageLines(const int page) const +{ + QSqlQuery dbQuery(*this); + + QString query = "SELECT %0 FROM pages WHERE page_no=%1"; + query = query.arg("qcf_v" + QString::number(m_config.qcfVersion()), + QString::number(page)); + + dbQuery.prepare(query); + if (!dbQuery.exec()) + qFatal("Couldn't execute getPageLines query!"); + + dbQuery.next(); + QStringList lines = dbQuery.value(0).toString().trimmed().split('\n'); + + return lines; +} + +QString +GlyphsDb::getSurahNameGlyph(const int sura) const +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare("SELECT qcf_v1 FROM surah_glyphs WHERE surah=:i"); + dbQuery.bindValue(0, sura); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getSurahNameGlyph SQL statment exec"; + } + + dbQuery.next(); + + return dbQuery.value(0).toString(); +} + +QString +GlyphsDb::getJuzGlyph(const int juz) const +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare("SELECT text FROM juz_glyphs WHERE juz=:j"); + dbQuery.bindValue(0, juz); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getJuzGlyph SQL statment exec"; + } + + dbQuery.next(); + + return dbQuery.value(0).toString(); +} + +QString +GlyphsDb::getVerseGlyphs(const int sIdx, const int vIdx) const +{ + QSqlQuery dbQuery(*this); + + QString query = "SELECT %0 FROM ayah_glyphs WHERE surah=%1 AND ayah=%2"; + query = query.arg("qcf_v" + QString::number(m_config.qcfVersion()), + QString::number(sIdx), + QString::number(vIdx)); + + dbQuery.prepare(query); + if (!dbQuery.exec()) + qFatal("Couldn't execute getVerseGlyphs query!"); + + dbQuery.next(); + + return dbQuery.value(0).toString(); +} diff --git a/src/database/glyphsdb.h b/src/database/glyphsdb.h new file mode 100644 index 00000000..9dafd8ab --- /dev/null +++ b/src/database/glyphsdb.h @@ -0,0 +1,73 @@ +#ifndef GLYPHSDB_H +#define GLYPHSDB_H + +#include +#include +#include +#include +#include +#include + +/** + * @class GlyphsDb + * @brief The GlyphsDb class represents a connection to the glyphs db file + */ +class GlyphsDb + : public DbConnection + , QSqlDatabase +{ +public: + /** + * @brief get a reference to the single class instance + * @return reference to the static class instance + */ + static GlyphsDb& getInstance(); + /** + * @brief sets and opens the sqlite database file + */ + void open(); + /** + * @brief getter for the type of the connection + * @return - DbConnection::Glyphs + */ + Type type(); + /** + * @brief get Quran page QCF glyphs separated as lines + * @param page - Quran page number + * @return QList of page lines + */ + QStringList getPageLines(const int page) const; + /** + * @brief gets the surah name glyph for the QCF_BSML font, used to render + * surah frame in Quran page + * @param sura - sura number (1-114) + * @return QString of glyphs + */ + QString getSurahNameGlyph(const int sura) const; + /** + * @brief gets the juz name in arabic, used in page header + * @param juz - juz number + * @return QString of the juz name + */ + QString getJuzGlyph(const int juz) const; + /** + * @brief gets the verse QCF glyphs for the corresponding QCF page font + * @param sIdx - sura number (1-114) + * @param vIdx - verse number + * @return QString of verse glyphs + */ + QString getVerseGlyphs(const int sIdx, const int vIdx) const; + +private: + GlyphsDb(); + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the app assets directory + */ + const QDir& m_assetsDir; +}; + +#endif // GLYPHSDB_H diff --git a/src/database/qurandb.cpp b/src/database/qurandb.cpp new file mode 100644 index 00000000..5b2ea980 --- /dev/null +++ b/src/database/qurandb.cpp @@ -0,0 +1,339 @@ +#include "qurandb.h" +#include +#include + +QuranDb& +QuranDb::getInstance() +{ + static QuranDb qdb; + return qdb; +} + +QuranDb::QuranDb() + : QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "QuranCon")) + , m_assetsDir(DirManager::getInstance().assetsDir()) + , m_config(Configuration::getInstance()) +{ + QuranDb::open(); + for (int i = 1; i <= 114; i++) + m_surahNames.append(surahName(i)); +} + +void +QuranDb::open() +{ + setDatabaseName(m_assetsDir.absoluteFilePath("quran.db")); + if (!QSqlDatabase::open()) + qFatal("Error opening quran db"); +} + +DbConnection::Type +QuranDb::type() +{ + return DbConnection::Quran; +} + +QPair +QuranDb::pageMetadata(const int page) const +{ + QSqlQuery dbQuery(*this); + dbQuery.prepare( + "SELECT sura_no,jozz FROM verses_v1 WHERE page=:p ORDER BY id"); + dbQuery.bindValue(0, page); + + if (!dbQuery.exec()) + qCritical() << "Error occurred during getPageMetadata SQL statment exec"; + + dbQuery.next(); + // { surahIdx, jozz } + return { dbQuery.value(0).toInt(), dbQuery.value(1).toInt() }; +} + +int +QuranDb::getVersePage(const int& surahIdx, const int& verse) const +{ + QSqlQuery dbQuery(*this); + + QString query = "SELECT page FROM verses_v%0 WHERE sura_no=%1 AND aya_no=%2"; + dbQuery.prepare(query.arg(QString::number(m_config.qcfVersion()), + QString::number(surahIdx), + QString::number(verse))); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getVersePage SQL statment exec"; + } + dbQuery.next(); + + return dbQuery.value(0).toInt(); +} + +int +QuranDb::getJuzStartPage(const int juz) const +{ + QSqlQuery dbQuery(*this); + + QString query = + "SELECT page FROM verses_v1 WHERE jozz=" + QString::number(juz) + + " ORDER BY id"; + dbQuery.prepare(query); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getJuzStartPage SQL statment exec"; + } + dbQuery.next(); + + return dbQuery.value(0).toInt(); +} + +int +QuranDb::getJuzOfPage(const int page) const +{ + QSqlQuery dbQuery(*this); + + QString query = + "SELECT jozz FROM verses_v1 WHERE page=" + QString::number(page); + dbQuery.prepare(query); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getJuzOfPage SQL statment exec"; + } + dbQuery.next(); + + return dbQuery.value(0).toInt(); +} + +QList> +QuranDb::verseInfoList(const int page) const +{ + QList> viList; + QSqlQuery dbQuery(*this); + + QString query = + "SELECT sura_no,aya_no FROM verses_v%0 WHERE page=%1 ORDER BY id"; + dbQuery.prepare( + query.arg(QString::number(m_config.qcfVersion()), QString::number(page))); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getVerseInfoList SQL statment exec"; + } + + while (dbQuery.next()) { + QList v{ page, dbQuery.value(0).toInt(), dbQuery.value(1).toInt() }; + viList.append(v); + } + + return viList; +} + +QString +QuranDb::verseText(const int sIdx, const int vIdx) const +{ + QSqlQuery dbQuery(*this); + if (m_config.verseType() == Configuration::Annotated) + dbQuery.prepare("SELECT aya_text_annotated FROM verses_v1 WHERE sura_no=:s " + "AND aya_no=:v"); + else + dbQuery.prepare( + "SELECT aya_text FROM verses_v1 WHERE sura_no=:s AND aya_no=:v"); + + dbQuery.bindValue(0, sIdx); + dbQuery.bindValue(1, vIdx); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getVerseText SQL statment exec"; + } + dbQuery.next(); + + return dbQuery.value(0).toString(); +} + +int +QuranDb::surahStartPage(int surahIdx) const +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare("SELECT page FROM verses_v1 WHERE sura_no=:sn AND aya_no=1"); + dbQuery.bindValue(0, surahIdx); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getSurahStartPage SQL statment exec"; + } + dbQuery.next(); + + return dbQuery.value(0).toInt(); +} + +QString +QuranDb::surahName(const int sIdx, bool ar) const +{ + QSqlQuery dbQuery(*this); + + if (m_config.language() == QLocale::Arabic || ar) + dbQuery.prepare("SELECT sura_name_ar FROM verses_v1 WHERE sura_no=:i"); + else + dbQuery.prepare("SELECT sura_name_en FROM verses_v1 WHERE sura_no=:i"); + + dbQuery.bindValue(0, sIdx); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getSurahName SQL statment exec"; + } + + dbQuery.next(); + return dbQuery.value(0).toString(); +} + +QList +QuranDb::verseById(const int id) const +{ + QSqlQuery dbQuery(*this); + dbQuery.prepare("SELECT page,sura_no,aya_no FROM verses_v1 WHERE id=:i"); + dbQuery.bindValue(0, id); + + if (!dbQuery.exec()) + qCritical() << "Error occurred during getVerseById SQL statement exec"; + + dbQuery.next(); + + return { dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }; +} + +int +QuranDb::versePage(const int& surahIdx, const int& verse) const +{ + QSqlQuery dbQuery(*this); + + QString query = "SELECT page FROM verses_v%0 WHERE sura_no=%1 AND aya_no=%2"; + dbQuery.prepare(query.arg(QString::number(m_config.qcfVersion()), + QString::number(surahIdx), + QString::number(verse))); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during getVersePage SQL statment exec"; + } + dbQuery.next(); + + return dbQuery.value(0).toInt(); +} + +QList +QuranDb::searchSurahNames(QString text) const +{ + QList results; + QSqlQuery dbQuery(*this); + QString q = + "SELECT DISTINCT sura_no FROM verses_v1 WHERE (sura_name_ar like '%" + + text + + "%' OR " + "sura_name_en like '%" + + text + "%')"; + + dbQuery.prepare(q); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during searchSurahNames SQL statment exec"; + } + + while (dbQuery.next()) { + results.append(dbQuery.value(0).toInt()); + } + + return results; +} + +QList> +QuranDb::searchSurahs(QString searchText, + const QList surahs, + const bool whole) const +{ + QList> results; + QSqlQuery dbQuery(*this); + + QString q = "SELECT page,sura_no,aya_no FROM verses_v" + + QString::number(m_config.qcfVersion()) + " WHERE ("; + for (int i = 0; i < surahs.size(); i++) { + q.append("sura_no=" + QString::number(surahs.at(i)) + ' '); + if (i != surahs.size() - 1) + q.append("OR "); + } + + if (whole) + q.append(") AND (aya_text_emlaey like '" + searchText + + " %' OR aya_text_emlaey like '% " + searchText + + " %') ORDER BY id"); + else + q.append(") AND (aya_text_emlaey like '%" + searchText + "%') ORDER BY id"); + + dbQuery.prepare(q); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during searchSurahs SQL statment exec"; + } + + while (dbQuery.next()) { + results.append({ dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }); + } + + return results; +} + +QList> +QuranDb::searchVerses(QString searchText, + const int range[], + const bool whole) const +{ + QList> results; + QSqlQuery dbQuery(*this); + + QString q = "SELECT page,sura_no,aya_no FROM verses_v" + + QString::number(m_config.qcfVersion()) + + " WHERE (page >= " + QString::number(range[0]) + + " AND page <= " + QString::number(range[1]) + ")"; + + if (whole) + q.append(" AND (aya_text_emlaey like '" + searchText + + " %' OR aya_text_emlaey like '% " + searchText + + " %') ORDER BY id"); + else + q.append(" AND (aya_text_emlaey like '%" + searchText + "%') ORDER BY id"); + + dbQuery.prepare(q); + if (!dbQuery.exec()) { + qCritical() << "Error occurred during searchVerses SQL statment exec"; + } + + while (dbQuery.next()) { + QList entry{ dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }; + results.append(entry); + } + + return results; +} + +QList +QuranDb::randomVerse() const +{ + QSqlQuery dbQuery(*this); + + int id = QRandomGenerator::global()->bounded(1, 6237); + dbQuery.prepare("SELECT page,sura_no,aya_no FROM verses_v" + + QString::number(m_config.qcfVersion()) + + " WHERE id=" + QString::number(id)); + + if (!dbQuery.exec()) { + qCritical() << "Error occurred during randomVerse SQL statment exec"; + } + dbQuery.next(); + return { dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }; +} + +QStringList +QuranDb::surahNames() const +{ + return m_surahNames; +} diff --git a/src/database/qurandb.h b/src/database/qurandb.h new file mode 100644 index 00000000..3c4e2605 --- /dev/null +++ b/src/database/qurandb.h @@ -0,0 +1,155 @@ +#ifndef QURANDB_H +#define QURANDB_H + +#include +#include +#include +#include +#include +#include + +/** + * @class QuranDb + * @brief The QuranDb class represents a connection to the quran db file + */ +class QuranDb + : public DbConnection + , QSqlDatabase +{ +public: + /** + * @brief get a reference to the single class instance + * @return reference to the static class instance + */ + static QuranDb& getInstance(); + /** + * @brief sets and opens the sqlite database file + */ + void open(); + /** + * @brief getter for the type of the connection + * @return - DbConnection::Quran + */ + Type type(); + /** + * @brief gets the surah number and juz number of the first verse in the page, + * used to display page header information + * @param page - Quran page number + * @return QList of 2 integers [0: surah index, 1: juz number] + */ + QPair pageMetadata(const int page) const; + /** + * @brief gets the page where the verse is found + * @param surahIdx - sura number + * @param verse - verse number + * @return page number + */ + int getVersePage(const int& surahIdx, const int& verse) const; + /** + * @brief gets the page where the corresponding juz starts + * @param juz - juz number + * @return page number + */ + int getJuzStartPage(const int juz) const; + /** + * @brief get the juz which the passed page is a part of + * @param page - page number + * @return juz number + */ + int getJuzOfPage(const int page) const; + /** + * @brief gets a QList of Verse instances for the page verses + * @param page - Quran page number + * @return QList of Verse instances + */ + QList> verseInfoList(const int page) const; + /** + * @brief gets the verse text + * @param sIdx - sura number (1-114) + * @param vIdx - verse number + * @return QString of the verse text + */ + QString verseText(const int sIdx, const int vIdx) const; + /** + * @brief gets the page where the surah begins + * @param surahIdx - sura number + * @return page of the first verse in the sura + */ + int surahStartPage(int surahIdx) const; + /** + * @brief gets the surah name in English or Arabic (default is English) + * @param sIdx - sura number + * @param ar - boolean to return arabic sura name + * @return QString containing the sura name + */ + QString surahName(const int sIdx, bool ar = false) const; + /** + * @brief get the verse with the corresponding id and return it as a Verse + * instance + * @param id - verse id + * @return Verse instance + */ + QList verseById(const int id) const; + /** + * @brief gets the page where the verse is found + * @param surahIdx - sura number + * @param verse - verse number + * @return page number + */ + int versePage(const int& surahIdx, const int& verse) const; + /** + * @brief searches the database for surahs matching the given text pattern, + * the pattern can be either in English or Arabic + * @param text - name / part of the name of the sura + * @return QList of sura numbers which contain the given text + */ + QList searchSurahNames(QString text) const; + /** + * @brief search specific surahs for the given search text + * @param searchText - text to search for + * @param surahs - QList of surah numbers to search in + * @param whole - boolean value to search for whole words only + * @return QList of Verse instances representing the search results + */ + QList> searchSurahs(QString searchText, + const QList surahs, + const bool whole = false) const; + /** + * @brief search a range of pages for the given search text + * @param searchText - text to search for + * @param range - array of start & end page numbers + * @param whole - boolean value to indicate search for whole words only + * @return QList of Verse instances representing the search results + */ + QList> searchVerses(QString searchText, + const int range[2] = new int[2]{ 1, 604 }, + const bool whole = false) const; + /** + * @brief gets a random verse from the Quran + * @return QPair of Verse instance and verse text + */ + QList randomVerse() const; + /** + * @brief surahNames + * @return + */ + QStringList surahNames() const; + +private: + QuranDb(); + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the app assets directory + */ + const QDir& m_assetsDir; + /** + * @brief QList of sura names (Arabic if UI language is Arabic, Otherwise + * English) + */ + QStringList m_surahNames; +}; + +#endif // QURANDB_H diff --git a/src/database/tafsirdb.cpp b/src/database/tafsirdb.cpp new file mode 100644 index 00000000..83ae6396 --- /dev/null +++ b/src/database/tafsirdb.cpp @@ -0,0 +1,82 @@ +#include "tafsirdb.h" +#include + +TafsirDb& +TafsirDb::getInstance() +{ + static TafsirDb tadb; + return tadb; +} + +TafsirDb::TafsirDb() + : QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "TafsirCon")) + , m_config(Configuration::getInstance()) + , m_dirMgr(DirManager::getInstance()) + , m_tafasir(Tafsir::tafasir) +{ + updateLoadedTafsir(); +} + +void +TafsirDb::open() +{ + setDatabaseName(m_tafsirFile.absoluteFilePath()); + if (!QSqlDatabase::open()) + qFatal("Error opening tafsir db"); +} + +DbConnection::Type +TafsirDb::type() +{ + return DbConnection::Tafsir; +} + +void +TafsirDb::updateLoadedTafsir() +{ + int curr = m_config.settings().value("Reader/Tafsir").toInt(); + setCurrentTafsir(curr); +} + +bool +TafsirDb::setCurrentTafsir(int idx) +{ + if (idx < 0 || idx >= m_tafasir.size()) + return false; + if (m_currTafsir == &m_tafasir[idx]) + return true; + + m_currTafsir = &m_tafasir[idx]; + const QDir& baseDir = + m_currTafsir->isExtra() ? m_dirMgr.downloadsDir() : m_dirMgr.assetsDir(); + QString path = "tafasir/" + m_currTafsir->filename(); + if (!baseDir.exists(path)) + return false; + + m_tafsirFile.setFile(baseDir.filePath(path)); + TafsirDb::open(); + return true; +} + +QString +TafsirDb::getTafsir(const int sIdx, const int vIdx) +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare("SELECT text FROM content WHERE sura=:s AND aya=:v"); + dbQuery.bindValue(0, sIdx); + dbQuery.bindValue(1, vIdx); + + if (!dbQuery.exec()) + qCritical("Couldn't execute getTafsir query!"); + + dbQuery.next(); + + return dbQuery.value(0).toString(); +} + +const Tafsir* +TafsirDb::currTafsir() const +{ + return m_currTafsir; +} diff --git a/src/database/tafsirdb.h b/src/database/tafsirdb.h new file mode 100644 index 00000000..5489a4c0 --- /dev/null +++ b/src/database/tafsirdb.h @@ -0,0 +1,85 @@ +#ifndef TAFSIRDB_H +#define TAFSIRDB_H + +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @class TafsirDb + * @brief The TafsirDb class represents a connection to the currently selected + * tafsir db + */ +class TafsirDb + : public DbConnection + , QSqlDatabase + +{ +public: + /** + * @brief get a reference to the single class instance + * @return reference to the static class instance + */ + static TafsirDb& getInstance(); + /** + * @brief sets and opens the sqlite database file + */ + void open(); + /** + * @brief getter for the type of the connection + * @return - DbConnection::Tafsir + */ + Type type(); + /** + * @brief set tafsir to the one in the settings, update the selected db + */ + void updateLoadedTafsir(); + /** + * @brief sets the active tafsir + * @param tafsirName - DBManager::Tafsir entry + */ + bool setCurrentTafsir(int idx); + /** + * @brief gets the tafsir content for the given verse using the active + * DBManager::Tafsir + * @param sIdx - surah number + * @param vIdx - verse number + * @return QString containing the tafsir of the verse + */ + QString getTafsir(const int sIdx, const int vIdx); + /** + * @brief getter for m_currTafsir + * @return pointer to the currently selected Tafasir + */ + const ::Tafsir* currTafsir() const; + +private: + TafsirDb(); + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the singleton DirManager instance + */ + const DirManager& m_dirMgr; + /** + * @brief reference to the static QList of available tafasir + */ + const QList<::Tafsir>& m_tafasir; + /** + * @brief pointer to the currently selected Tafsir + */ + const ::Tafsir* m_currTafsir; + /** + * @brief path to the currently active tafsir database file + */ + QFileInfo m_tafsirFile; +}; + +#endif // TAFSIRDB_H diff --git a/src/database/translationdb.cpp b/src/database/translationdb.cpp new file mode 100644 index 00000000..59c5c5c9 --- /dev/null +++ b/src/database/translationdb.cpp @@ -0,0 +1,82 @@ +#include "translationdb.h" +#include + +TranslationDb& +TranslationDb::getInstance() +{ + static TranslationDb tdb; + return tdb; +} + +TranslationDb::TranslationDb() + : QSqlDatabase(QSqlDatabase::addDatabase("QSQLITE", "TranslationCon")) + , m_dirMgr(DirManager::getInstance()) + , m_config(Configuration::getInstance()) + , m_translations(Translation::translations) +{ +} + +void +TranslationDb::open() +{ + setDatabaseName(m_translationFile.absoluteFilePath()); + if (!QSqlDatabase::open()) + qFatal("Error opening translation db"); +} + +DbConnection::Type +TranslationDb::type() +{ + return DbConnection::Translation; +} + +void +TranslationDb::updateLoadedTranslation() +{ + int curr = m_config.settings().value("Reader/Translation").toInt(); + setCurrentTranslation(curr); +} + +bool +TranslationDb::setCurrentTranslation(int idx) +{ + if (idx < 0 || idx >= m_translations.size()) + return false; + if (m_currTr == &m_translations[idx]) + return true; + + m_currTr = &m_translations[idx]; + const QDir& baseDir = m_currTr->isExtra() + ? DirManager::getInstance().downloadsDir() + : DirManager::getInstance().assetsDir(); + QString path = "translations/" + m_currTr->filename(); + if (!baseDir.exists(path)) + return false; + + m_translationFile.setFile(baseDir.filePath(path)); + TranslationDb::open(); + return true; +} + +QString +TranslationDb::getTranslation(const int sIdx, const int vIdx) const +{ + QSqlQuery dbQuery(*this); + + dbQuery.prepare("SELECT text FROM content WHERE sura=:s AND aya=:v"); + dbQuery.bindValue(0, sIdx); + dbQuery.bindValue(1, vIdx); + + if (!dbQuery.exec()) + qCritical("Couldn't execute getTranslation query!"); + + dbQuery.next(); + + return dbQuery.value(0).toString(); +} + +const Translation* +TranslationDb::currTranslation() const +{ + return m_currTr; +} diff --git a/src/database/translationdb.h b/src/database/translationdb.h new file mode 100644 index 00000000..4a42e34b --- /dev/null +++ b/src/database/translationdb.h @@ -0,0 +1,88 @@ +#ifndef TRANSLATIONDB_H +#define TRANSLATIONDB_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * @class TranslationDb + * @brief The TranslationDb class represents a connection to the currently + * selected translation db + */ +class TranslationDb + : public DbConnection + , QSqlDatabase +{ + Q_OBJECT +public: + /** + * @brief get a reference to the single class instance + * @return reference to the static class instance + */ + static TranslationDb& getInstance(); + /** + * @brief sets and opens the sqlite database file + */ + void open(); + /** + * @brief getter for the type of the connection + * @return - DbConnection::Translation + */ + Type type(); + /** + * @brief sets the active translation + * @param translationName - DBManager::Translation entry + */ + bool setCurrentTranslation(int idx); + /** + * @brief gets the translation of the given verse using the active + * DBManager::Translation + * @param sIdx - surah number + * @param vIdx - verse number + * @return QString containing the verse translation + */ + QString getTranslation(const int sIdx, const int vIdx) const; + /** + * @brief getter for m_currTr + * @return pointer to the currently selected translation + */ + const ::Translation* currTranslation() const; + +public slots: + /** + * @brief set translation to the one in the settings, update the selected db + */ + void updateLoadedTranslation(); + +private: + TranslationDb(); + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the singleton DirManager instance + */ + const DirManager& m_dirMgr; + /** + * @brief reference to the static QList of available translations + */ + const QList<::Translation>& m_translations; + /** + * @brief the current active DBManager::Translation + */ + const ::Translation* m_currTr; + /** + * @brief path to the currently active translation database file + */ + QFileInfo m_translationFile; +}; + +#endif // TRANSLATIONDB_H diff --git a/src/widgets/aboutdialog.cpp b/src/dialogs/aboutdialog.cpp similarity index 92% rename from src/widgets/aboutdialog.cpp rename to src/dialogs/aboutdialog.cpp index 65153abb..4fd94dbf 100644 --- a/src/widgets/aboutdialog.cpp +++ b/src/dialogs/aboutdialog.cpp @@ -4,6 +4,7 @@ AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::AboutDialog) + , m_config(Configuration::getInstance()) { ui->setupUi(this); QFont bolded = qApp->font(); @@ -26,7 +27,7 @@ AboutDialog::AboutDialog(QWidget* parent) ui->lbContribTranslation->text().arg(tr("Contribute to translations"))); connect(ui->btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - if (m_lang == QLocale::Arabic) + if (m_config.language() == QLocale::Arabic) ui->aboutTabWidget->setObjectName("rtlTabWidget"); } diff --git a/src/dialogs/aboutdialog.h b/src/dialogs/aboutdialog.h new file mode 100644 index 00000000..8618d818 --- /dev/null +++ b/src/dialogs/aboutdialog.h @@ -0,0 +1,37 @@ +#ifndef ABOUTDIALOG_H +#define ABOUTDIALOG_H + +#include +#include +#include + +namespace Ui { +class AboutDialog; +} + +/** + * @class AboutDialog + * @brief AboutDialog class is used for displaying information about the + * application + */ +class AboutDialog : public QDialog +{ + Q_OBJECT + +public: + /** + * @brief class constructor + * @param parent - pointer to parent widget + */ + explicit AboutDialog(QWidget* parent = nullptr); + ~AboutDialog(); + +private: + Ui::AboutDialog* ui; + /** + * @brief reference to the singleton Configuration instance + */ + const Configuration& m_config; +}; + +#endif // ABOUTDIALOG_H diff --git a/src/widgets/aboutdialog.ui b/src/dialogs/aboutdialog.ui similarity index 100% rename from src/widgets/aboutdialog.ui rename to src/dialogs/aboutdialog.ui diff --git a/src/core/bookmarksdialog.cpp b/src/dialogs/bookmarksdialog.cpp similarity index 79% rename from src/core/bookmarksdialog.cpp rename to src/dialogs/bookmarksdialog.cpp index 780bdcfa..823db12f 100644 --- a/src/core/bookmarksdialog.cpp +++ b/src/dialogs/bookmarksdialog.cpp @@ -6,18 +6,26 @@ #include "bookmarksdialog.h" #include "ui_bookmarksdialog.h" #include +#include +#include BookmarksDialog::BookmarksDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::BookmarksDialog) + , m_config(Configuration::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_bookmarksDb(BookmarksDb::getInstance()) + , m_glpyhsDb(GlyphsDb::getInstance()) { ui->setupUi(this); ui->navBar->setLayoutDirection(Qt::LeftToRight); - ui->btnNext->setIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_left)); - ui->btnPrev->setIcon( - Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_right)); + ui->btnNext->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_left)); + ui->btnPrev->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_right)); ui->scrollArea->setLayoutDirection(Qt::LeftToRight); - setWindowIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_bookmark)); + setWindowIcon( + StyleManager::getInstance().awesome().icon(fa::fa_solid, fa::fa_bookmark)); ui->listViewBookmarkedSurahs->setModel(&m_surahsModel); loadDefault(); @@ -79,7 +87,7 @@ BookmarksDialog::loadBookmarks(int surah) { if (m_shownSurah != surah) { m_shownSurah = surah; - m_shownVerses = m_dbMgr->bookmarkedVerses(surah); + m_shownVerses = m_bookmarksDb.bookmarkedVerses(surah); if (m_shownSurah == -1) m_allBookmarked = m_shownVerses; } @@ -102,9 +110,9 @@ BookmarksDialog::loadBookmarks(int surah) } for (int i = m_startIdx; i < end; i++) { - Verse verse = m_shownVerses.at(i); - QString fontName = - Globals::verseFontname(m_dbMgr->getVerseType(), verse.page); + const Verse& verse = m_shownVerses.at(i); + QString fontName = FontManager::getInstance().verseFontname( + m_config.verseType(), verse.page()); QFrame* frame = new QFrame(ui->scrlBookmarks); frame->setProperty("bookmark", true); @@ -128,13 +136,18 @@ BookmarksDialog::loadBookmarks(int surah) removeFromFav, &QPushButton::clicked, this, &BookmarksDialog::btnRemove); QString info = tr("Surah: ") + - m_dbMgr->surahNameList().at(verse.surah - 1) + " - " + - tr("Verse: ") + QString::number(verse.number); + m_quranDb.surahNames().at(verse.surah() - 1) + " - " + + tr("Verse: ") + QString::number(verse.number()); + QString glyphs = + m_config.verseType() == Configuration::Qcf + ? m_glpyhsDb.getVerseGlyphs(verse.surah(), verse.number()) + : m_quranDb.verseText(verse.surah(), verse.number()); + lbMeta->setText(info); lbMeta->setAlignment(Qt::AlignLeft); verseLb->setFont(QFont(fontName, 15)); - verseLb->setText(m_dbMgr->getVerseGlyphs(verse.surah, verse.number)); + verseLb->setText(glyphs); verseLb->setAlignment(Qt::AlignLeft); verseLb->setWordWrap(true); verseLb->setMargin(5); @@ -154,9 +167,9 @@ BookmarksDialog::loadBookmarks(int surah) frmLayout->addItem(lbLayout); frame->setLayout(frmLayout); - frame->setObjectName(QString::number(verse.page) + '-' + - QString::number(verse.surah) + '-' + - QString::number(verse.number)); + frame->setObjectName(QString::number(verse.page()) + '-' + + QString::number(verse.surah()) + '-' + + QString::number(verse.number())); ui->layoutFavorites->addWidget(frame); m_frames.append(frame); @@ -175,11 +188,11 @@ BookmarksDialog::loadSurahs() std::set surahs; foreach (const Verse& v, m_allBookmarked) { - surahs.insert(v.surah); + surahs.insert(v.surah()); } for (int s : surahs) { - item = new QStandardItem(m_dbMgr->surahNameList().at(s - 1)); + item = new QStandardItem(m_quranDb.surahNames().at(s - 1)); item->setData(Qt::AlignCenter, Qt::TextAlignmentRole); item->setToolTip(item->text()); item->setData(s, Qt::UserRole); @@ -209,7 +222,7 @@ void BookmarksDialog::btnGoToVerse() { QStringList info = sender()->parent()->objectName().split('-'); - Verse verse{ info.at(0).toInt(), info.at(1).toInt(), info.at(2).toInt() }; + Verse verse(info.at(0).toInt(), info.at(1).toInt(), info.at(2).toInt()); emit navigateToVerse(verse); } @@ -219,7 +232,7 @@ BookmarksDialog::btnRemove() QStringList info = sender()->parent()->objectName().split('-'); Verse verse{ info.at(0).toInt(), info.at(1).toInt(), info.at(2).toInt() }; - if (m_dbMgr->removeBookmark(verse)) { + if (m_bookmarksDb.removeBookmark(verse, true)) { QFrame* frm = qobject_cast(sender()->parent()); int idx = m_frames.indexOf(frm); if (idx != -1) diff --git a/src/core/bookmarksdialog.h b/src/dialogs/bookmarksdialog.h similarity index 79% rename from src/core/bookmarksdialog.h rename to src/dialogs/bookmarksdialog.h index 58fe2838..f8bd941e 100644 --- a/src/core/bookmarksdialog.h +++ b/src/dialogs/bookmarksdialog.h @@ -6,14 +6,17 @@ #ifndef BOOKMARKSDIALOG_H #define BOOKMARKSDIALOG_H -#include "../globals.h" -#include "../utils/dbmanager.h" #include #include +#include #include #include #include #include +#include +#include +#include +#include namespace Ui { class BookmarksDialog; @@ -22,8 +25,7 @@ class BookmarksDialog; /** * @brief BookmarksDialog is the interface for interacting with saved bookmarks. * @details Bookmarks allow the user to save a verse for easier & faster - * access. Bookmarks are represented as entries in a sqlite database. Thus, a - * DBManager instance is required to create an instance of BookmarksDialog. + * access. Bookmarks are represented as entries in a sqlite database. */ class BookmarksDialog : public QDialog { @@ -71,19 +73,19 @@ class BookmarksDialog : public QDialog * @fn void navigateToVerse(Verse v) * @brief Emitted when 'go to verse' button is clicked to signal the * navigation and selection of a verse. - * @param v - ::Verse to navigate to + * @param v - Verse to navigate to */ - void navigateToVerse(Verse v); + void navigateToVerse(const Verse& v); public slots: /** - * @brief generates ::Verse object from the name of the Frame containing the + * @brief generates Verse object from the name of the Frame containing the * navigation buttons. Then emits BookmarksDialog::navigateToVerse(Verse). */ void btnGoToVerse(); /** - * @brief generates ::Verse object from the name of the Frame containing the - * navigation buttons then deletes the corresponding ::Verse from the + * @brief generates Verse object from the name of the Frame containing the + * navigation buttons then deletes the corresponding Verse from the * bookmarks database. * @details After removing the bookmark from the database, the corresponding * frame is removed. If there are no more bookmarks in this surah, the default @@ -115,8 +117,23 @@ private slots: void surahSelected(const QModelIndex& index); private: - const int m_qcfVer = Globals::qcfVersion; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); + Ui::BookmarksDialog* ui; + /** + * @brief reference to the singleton Configuration instance + */ + const Configuration& m_config; + /** + * @brief reference to the singleton GlyphsDb instance + */ + const GlyphsDb& m_glpyhsDb; + /** + * @brief reference to the singleton QuranDb instance + */ + const QuranDb& m_quranDb; + /** + * @brief reference to the singleton BookmarksDb instance + */ + BookmarksDb& m_bookmarksDb; /** * @brief connects signals and slots for different UI * components and shortcuts. @@ -138,21 +155,17 @@ private slots: */ int m_shownSurah = 0; /** - * @brief pointer to access ui elements generated from .ui files. - */ - Ui::BookmarksDialog* ui; - /** - * @brief ::Verse QList for all bookmarked verses. + * @brief Verse QList for all bookmarked verses. */ QList m_allBookmarked; /** - * @brief ::Verse QList for the shown verses (all or for a specific surah). + * @brief Verse QList for the shown verses (all or for a specific surah). */ QList m_shownVerses; /** * @brief QFrames for shown verses. */ - QList m_frames; + QList> m_frames; /** * @brief model for surahs of bookmarked verses. */ diff --git a/src/core/bookmarksdialog.ui b/src/dialogs/bookmarksdialog.ui similarity index 100% rename from src/core/bookmarksdialog.ui rename to src/dialogs/bookmarksdialog.ui diff --git a/src/dialogs/contentdialog.cpp b/src/dialogs/contentdialog.cpp new file mode 100644 index 00000000..0e542d65 --- /dev/null +++ b/src/dialogs/contentdialog.cpp @@ -0,0 +1,322 @@ +/** + * @file tafsirdialog.cpp + * @brief Implementation file for ContentDialog + */ + +#include "contentdialog.h" +#include "ui_contentdialog.h" +#include +#include +#include + +ContentDialog::ContentDialog(QWidget* parent) + : QDialog(parent) + , ui(new Ui::ContentDialog) + , m_config(Configuration::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_bookmarksDb(BookmarksDb::getInstance()) + , m_glyphsDb(GlyphsDb::getInstance()) + , m_tafsirDb(TafsirDb::getInstance()) + , m_translationDb(TranslationDb::getInstance()) + , m_tafasir(Tafsir::tafasir) + , m_translations(Translation::translations) + , m_currMode(Tafsir) + , m_internalLoading(false) +{ + setWindowIcon( + StyleManager::getInstance().awesome().icon(fa::fa_solid, fa::fa_book_open)); + ui->setupUi(this); + ui->frmNav->setLayoutDirection(Qt::LeftToRight); + ui->btnNext->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_left)); + ui->btnPrev->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_right)); + + if (m_config.qcfVersion() == 1) + m_fontSZ = 18; + else + m_fontSZ = 16; + + m_tafsir = m_config.settings().value("Reader/Tafsir").toInt(); + m_translation = m_config.settings().value("Reader/Translation").toInt(); + // connectors + setupConnections(); +} + +void +ContentDialog::setupConnections() +{ + connect( + ui->btnNext, &QPushButton::clicked, this, &ContentDialog::btnNextClicked); + connect( + ui->btnPrev, &QPushButton::clicked, this, &ContentDialog::btnPrevClicked); + connect(ui->cmbType, + &QComboBox::currentIndexChanged, + this, + &ContentDialog::typeChanged); + connect(ui->cmbContent, + &QComboBox::currentIndexChanged, + this, + &ContentDialog::contentChanged); +} + +void +ContentDialog::showVerseTafsir(const Verse& v) +{ + static bool reload = false; + if (reload) { + m_tafsirDb.updateLoadedTafsir(); + reload = false; + } + + if (!m_tafsirDb.currTafsir()->isAvailable()) { + int i = m_tafasir.indexOf(*m_tafsirDb.currTafsir()); + reload = true; + emit missingTafsir(i); + return; + } + + setShownVerse(v); + loadContent(Mode::Tafsir); + show(); +} + +void +ContentDialog::showVerseTranslation(const Verse& v) +{ + static bool reload = false; + if (reload) { + m_translationDb.updateLoadedTranslation(); + reload = false; + } + + if (!m_translationDb.currTranslation()->isAvailable()) { + int i = m_translations.indexOf(*m_translationDb.currTranslation()); + reload = true; + emit missingTranslation(i); + return; + } + + setShownVerse(v); + loadContent(Mode::Translation); + show(); +} + +void +ContentDialog::showVerseThoughts(const Verse& v) +{ + setShownVerse(v); + loadContent(Mode::Thoughts); + show(); +} + +void +ContentDialog::loadSideFont() +{ + QFont sideFont = + qvariant_cast(m_config.settings().value("Reader/SideContentFont")); + ui->tedContent->setFont(sideFont); +} + +void +ContentDialog::setShownVerse(const Verse& newShownVerse) +{ + m_shownVerse = newShownVerse; + if (m_shownVerse.number() == 0) + m_shownVerse.setNumber(1); + + QString title = tr("Surah: ") + m_quranDb.surahName(m_shownVerse.surah()) + + " - " + tr("Verse: ") + + QString::number(m_shownVerse.number()); + QString glyphs = + m_config.verseType() == Configuration::Qcf + ? m_glyphsDb.getVerseGlyphs(m_shownVerse.surah(), m_shownVerse.number()) + : m_quranDb.verseText(m_shownVerse.surah(), m_shownVerse.number()); + QString fontFamily = FontManager::getInstance().getInstance().verseFontname( + m_config.verseType(), m_shownVerse.page()); + + ui->lbVerseInfo->setText(title); + ui->lbVerseText->setWordWrap(true); + ui->lbVerseText->setFont(QFont(fontFamily, m_fontSZ)); + ui->lbVerseText->setText(glyphs); + + if (m_shownVerse.surah() == 1 && m_shownVerse.number() == 1) + ui->btnPrev->setDisabled(true); + else if (m_shownVerse.surah() == 114 && m_shownVerse.number() == 6) + ui->btnNext->setDisabled(true); + else { + ui->btnPrev->setDisabled(false); + ui->btnNext->setDisabled(false); + } +} +void +ContentDialog::btnNextClicked() +{ + setShownVerse(m_shownVerse.next(false)); + loadContent(m_currMode); +} + +void +ContentDialog::btnPrevClicked() +{ + setShownVerse(m_shownVerse.prev(false)); + loadContent(m_currMode); +} + +void +ContentDialog::typeChanged() +{ + if (m_internalLoading) + return; + + if (m_currMode == Mode::Thoughts) + saveVerseThoughts(); + loadContent((Mode)ui->cmbType->currentIndex()); +} + +void +ContentDialog::contentChanged() +{ + if (m_internalLoading) + return; + + switch (m_currMode) { + case Mode::Tafsir: + tafsirChanged(); + break; + case Mode::Translation: + translationChanged(); + break; + default: + break; + } +} + +void +ContentDialog::tafsirChanged() +{ + m_tafsir = ui->cmbContent->currentData().toInt(); + m_config.settings().setValue("Reader/Tafsir", m_tafsir); + if (m_tafsirDb.setCurrentTafsir(m_tafsir)) + loadVerseTafsir(); +} + +void +ContentDialog::translationChanged() +{ + m_translation = ui->cmbContent->currentData().toInt(); + loadVerseTranslation(); +} + +void +ContentDialog::loadContent(Mode mode) +{ + m_internalLoading = true; + loadSideFont(); + ui->cmbType->setCurrentIndex((int)mode); + updateContentComboBox(mode); + switch (mode) { + case Mode::Tafsir: + loadVerseTafsir(); + break; + case Mode::Translation: + loadVerseTranslation(); + break; + case Mode::Thoughts: + loadVerseThoughts(); + break; + } + m_currMode = mode; + m_internalLoading = false; +} + +void +ContentDialog::updateContentComboBox(Mode mode) +{ + ui->cmbContent->clear(); + switch (mode) { + case Mode::Tafsir: + ui->cmbContent->setDisabled(false); + cmbLoadTafasir(); + break; + case Mode::Translation: + ui->cmbContent->setDisabled(false); + cmbLoadTranslations(); + break; + case Mode::Thoughts: + ui->cmbContent->setDisabled(true); + break; + } +} + +void +ContentDialog::cmbLoadTafasir() +{ + for (int i = 0; i < m_tafasir.size(); i++) { + if (m_tafasir.at(i).isAvailable()) + ui->cmbContent->addItem(m_tafasir.at(i).displayName(), i); + } + + int idx = ui->cmbContent->findData(m_tafsir); + ui->cmbContent->setCurrentIndex(idx); +} + +void +ContentDialog::cmbLoadTranslations() +{ + for (int i = 0; i < m_translations.size(); i++) { + if (m_translations.at(i).isAvailable()) + ui->cmbContent->addItem(m_translations.at(i).displayName(), i); + } + + int idx = ui->cmbContent->findData(m_translation); + ui->cmbContent->setCurrentIndex(idx); +} + +void +ContentDialog::loadVerseTafsir() +{ + if (m_tafsirDb.currTafsir()->isText()) + ui->tedContent->setText( + m_tafsirDb.getTafsir(m_shownVerse.surah(), m_shownVerse.number())); + else + ui->tedContent->setHtml( + m_tafsirDb.getTafsir(m_shownVerse.surah(), m_shownVerse.number())); +} + +void +ContentDialog::loadVerseTranslation() +{ + m_translationDb.setCurrentTranslation(m_translation); + ui->tedContent->setText(m_translationDb.getTranslation( + m_shownVerse.surah(), m_shownVerse.number())); +} + +void +ContentDialog::loadVerseThoughts() +{ + ui->tedContent->setText(m_bookmarksDb.getThoughts(m_shownVerse)); + ui->tedContent->setReadOnly(false); + ui->tedContent->setCursorWidth(1); +} + +void +ContentDialog::saveVerseThoughts() +{ + ui->tedContent->setCursorWidth(0); + ui->tedContent->setReadOnly(true); + m_bookmarksDb.saveThoughts(m_shownVerse, ui->tedContent->toPlainText()); +} + +void +ContentDialog::closeEvent(QCloseEvent* event) +{ + if (m_currMode == Mode::Thoughts) + saveVerseThoughts(); + this->hide(); +} + +ContentDialog::~ContentDialog() +{ + delete ui; +} diff --git a/src/dialogs/contentdialog.h b/src/dialogs/contentdialog.h new file mode 100644 index 00000000..d76a4138 --- /dev/null +++ b/src/dialogs/contentdialog.h @@ -0,0 +1,222 @@ +/** + * @file contentdialog.h + * @brief Header file for ContentDialog + */ + +#ifndef CONTENTDIALOG_H +#define CONTENTDIALOG_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class ContentDialog; +} + +/** + * @brief ContentDialog is used to display any additional content (tafsir, + * translation, thoughts). + */ +class ContentDialog : public QDialog +{ + Q_OBJECT + +public: + enum Mode + { + Tafsir, + Translation, + Thoughts + }; + /** + * @brief Class constructor + * @param parent - pointer to parent widget + */ + explicit ContentDialog(QWidget* parent = nullptr); + ~ContentDialog(); + +public slots: + /** + * @brief open ContentDialog with the shown verse set to the given Verse in + * Mode::Tafsir + * @param v - Verse to show the tafsir of + */ + void showVerseTafsir(const Verse& v); + /** + * @brief open ContentDialog with the shown verse set to the given Verse in + * Mode::Translation + * @param v - Verse to show the translation of + */ + void showVerseTranslation(const Verse& v); + /** + * @brief open ContentDialog with the shown verse set to the given Verse in + * Mode::Thoughts + * @param v - Verse to show the thoughts of + */ + void showVerseThoughts(const Verse& v); + +protected: + /** @brief Re-implementation of QWidget::closeEvent() in order to hide the + * window instead of closing it. + * @param event + */ + void closeEvent(QCloseEvent* event); + +signals: + void missingTafsir(int idx); + void missingTranslation(int idx); + +private slots: + /** + * @brief callback for calling the appropriate method when the selected item + * in the secondary combobox changes + */ + void contentChanged(); + /** + * @brief callback for changing the ContentDialog::Mode when the selected mode + * in the primary combobox changes + */ + void typeChanged(); + /** + * @brief increment the m_shownVerse and load the new verse + * tafsir. + */ + void btnNextClicked(); + /** + * @brief decrement the m_shownVerse and load the new verse + * tafsir. + */ + void btnPrevClicked(); + +private: + Ui::ContentDialog* ui; + /** + * @brief reference to the singleton Configuration instance + */ + Configuration& m_config; + /** + * @brief reference to the singleton TafsirDb instance + */ + TafsirDb& m_tafsirDb; + /** + * @brief reference to the singleton TranslationDb instance + */ + TranslationDb& m_translationDb; + /** + * @brief reference to the singleton BookmarksDb instance + */ + BookmarksDb& m_bookmarksDb; + /** + * @brief reference to the singleton QuranDb instance + */ + const QuranDb& m_quranDb; + /** + * @brief reference to the singleton GlyphsDb instance + */ + const GlyphsDb& m_glyphsDb; + /** + * @brief reference to the static QList of available tafasir + */ + const QList<::Tafsir>& m_tafasir; + /** + * @brief reference to the static QList of available translations + */ + const QList<::Translation>& m_translations; + /** + * @brief loads the QFont selected for the side content + */ + void loadSideFont(); + /** + * @brief connects signals and slots for different UI + * components and shortcuts. + */ + void setupConnections(); + /** + * @brief setter member for m_shownVerse + * @param newShownVerse + */ + void setShownVerse(const Verse& newShownVerse); + /** + * @brief loads the content of Mode given for the currently set Verse + * @param mode - Mode of the content shown + */ + void loadContent(Mode mode); + /** + * @brief updates the second combobox according to the given Mode + */ + void updateContentComboBox(Mode mode); + /** + * @brief load all available tafasir in the secondary combobox + */ + void cmbLoadTafasir(); + /** + * @brief load all available translations in the secondary combobox + */ + void cmbLoadTranslations(); + /** + * @brief callback function for changing the selected tafsir in the secondary + * combobox + */ + void tafsirChanged(); + /** + * @brief callback function for changing the selected translation in the + * secondary combobox + */ + void translationChanged(); + /** + * @brief loads the tafsir for the currently shown verse in the QTextEdit + * widget + */ + void loadVerseTafsir(); + /** + * @brief loads the selected translation for the currently shown verse in the + * QTextEdit widget + */ + void loadVerseTranslation(); + /** + * @brief loads the user stored thoughts for the currently shown verse in the + * QTextEdit widget and enables editing + */ + void loadVerseThoughts(); + /** + * @brief saves the user stored thoughts for the currently shown verse in the + * QTextEdit widget and disables editing + */ + void saveVerseThoughts(); + /** + * @brief the current Mode of the ContentDialog + */ + Mode m_currMode; + /** + * @brief index of the currently shown Tafsir in Tafsir::tafasir + */ + int m_tafsir; + /** + * @brief index of the currently shown Translation in + * Translation::translations + */ + int m_translation; + /** + * @brief fixed font size for the verse text displayed above the tafsir. + */ + int m_fontSZ; + /** + * @brief Verse instance representing the shown verse. + */ + Verse m_shownVerse; + /** + * @brief boolean to indicate internal loading of content in the secondary + * combobox + */ + bool m_internalLoading; +}; + +#endif // CONTENTDIALOG_H diff --git a/src/dialogs/contentdialog.ui b/src/dialogs/contentdialog.ui new file mode 100644 index 00000000..7de9a942 --- /dev/null +++ b/src/dialogs/contentdialog.ui @@ -0,0 +1,201 @@ + + + ContentDialog + + + + 0 + 0 + 910 + 593 + + + + Content + + + + + + + + s-v + + + + + + + + 0 + 0 + + + + + 150 + 0 + + + + + Tafsir + + + + + Translation + + + + + Thoughts + + + + + + + + + + + + + + + + + + Inter + 10 + + + + + + + + + + + + PakType Naskh Basic + 15 + + + + QFrame { + border-radius: 4px; +} + + + false + + + true + + + 0 + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + + + 3 + + + 0 + + + 3 + + + + + + 0 + 25 + + + + + 16777215 + 25 + + + + + Noto Sans Display + 12 + false + + + + PointingHandCursor + + + next + + + + + + Left + + + + + + + + 0 + 25 + + + + + 16777215 + 25 + + + + + Noto Sans Display + 12 + false + false + false + true + + + + PointingHandCursor + + + previous + + + + + + Right + + + + + + + + + + + diff --git a/src/core/copydialog.cpp b/src/dialogs/copydialog.cpp similarity index 51% rename from src/core/copydialog.cpp rename to src/dialogs/copydialog.cpp index d57b425a..a6294da8 100644 --- a/src/core/copydialog.cpp +++ b/src/dialogs/copydialog.cpp @@ -4,6 +4,10 @@ CopyDialog::CopyDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::CopyDialog) + , m_verseValidator(new QIntValidator(this)) + , m_currVerse(Verse::getCurrent()) + , m_quranDb(QuranDb::getInstance()) + , m_notifier(this) { ui->setupUi(this); ui->cmbCopyFrom->setValidator(m_verseValidator); @@ -13,25 +17,18 @@ CopyDialog::CopyDialog(QWidget* parent) } void -CopyDialog::show(const Verse& curr) +CopyDialog::copyVerseText(const Verse v) { - ui->cmbCopyFrom->clear(); - ui->cmbCopyTo->clear(); - ui->lbCopySurahName->setText(m_dbMgr->getSurahName(curr.surah)); - - m_surah = curr.surah; - m_surahCnt = m_dbMgr->getSurahVerseCount(curr.surah); - for (int i = 1; i <= m_surahCnt; i++) { - ui->cmbCopyFrom->addItem(QString::number(i)); - ui->cmbCopyTo->addItem(QString::number(i)); - } - int idx = curr.number ? curr.number - 1 : 0; - ui->cmbCopyFrom->setCurrentIndex(idx); - ui->cmbCopyTo->setCurrentIndex(idx); - m_verseValidator->setBottom(1); - m_verseValidator->setTop(m_surahCnt); - - QDialog::show(); + QClipboard* clip = QApplication::clipboard(); + QString text = m_quranDb.verseText(v.surah(), v.number()); + QString vNum = QString::number(v.number()); + text.remove(text.size() - 1, 1); + text = text.trimmed(); + text = "{" + text + "}"; + text += ' '; + text += "[" + m_quranDb.surahNames().at(v.surah() - 1) + ":" + vNum + "]"; + clip->setText(text); + m_notifier.copied(); } void @@ -39,8 +36,8 @@ CopyDialog::copyRange() { int from = ui->cmbCopyFrom->currentText().toInt(); int to = ui->cmbCopyTo->currentText().toInt(); - if (to < from || from <= 0 || from > m_surahCnt || to <= 0 || - to > m_surahCnt) { + if (to < from || from <= 0 || from > m_currVerse.surahCount() || to <= 0 || + to > m_currVerse.surahCount()) { QMessageBox::warning( this, tr("Invalid range"), tr("The entered verse range is invalid")); return; @@ -49,14 +46,42 @@ CopyDialog::copyRange() QString final = "{ "; QClipboard* clip = QApplication::clipboard(); for (int i = from; i <= to; i++) { - QString text = m_dbMgr->getVerseText(m_surah, i); + QString text = m_quranDb.verseText(m_currVerse.surah(), i); text.remove(text.size() - 1, 1); text += "(" + QString::number(i) + ") "; final.append(text); } - final += "} [" + m_dbMgr->surahNameList().at(m_surah - 1) + "]"; + final += "} [" + m_quranDb.surahNames().at(m_currVerse.surah() - 1) + "]"; clip->setText(final); + m_notifier.copied(); +} + +void +CopyDialog::show() +{ + ui->lbCopySurahName->setText(m_quranDb.surahName(m_currVerse.surah())); + + ui->cmbCopyFrom->clear(); + ui->cmbCopyTo->clear(); + for (int i = 1; i <= m_currVerse.surahCount(); i++) { + ui->cmbCopyFrom->addItem(QString::number(i)); + ui->cmbCopyTo->addItem(QString::number(i)); + } + + int idx = m_currVerse.number() ? m_currVerse.number() - 1 : 0; + ui->cmbCopyFrom->setCurrentIndex(idx); + ui->cmbCopyTo->setCurrentIndex(idx); + m_verseValidator->setBottom(1); + m_verseValidator->setTop(m_currVerse.surahCount()); + + QDialog::show(); +} + +const CopyNotifier* +CopyDialog::notifier() const +{ + return &m_notifier; } void diff --git a/src/dialogs/copydialog.h b/src/dialogs/copydialog.h new file mode 100644 index 00000000..08056137 --- /dev/null +++ b/src/dialogs/copydialog.h @@ -0,0 +1,77 @@ +#ifndef COPYDIALOG_H +#define COPYDIALOG_H + +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class CopyDialog; +} +/** + * @class CopyDialog + * @brief The CopyDialog class is used for selecting a range of verses within + * the current surah to copy to clipboard or copying the current Verse + */ +class CopyDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @brief class consrtuctor + * @param parent - pointer to parent widget + */ + explicit CopyDialog(QWidget* parent); + ~CopyDialog(); + + /** + * @brief sets the currently active surah name in the corresponding QLabel and + * fills the range selection comboboxes before showing the CopyDialog + */ + void show(); + /** + * @brief getter for the address of the CopyNotifier used for sending + * notifications + * @return pointer to CopyNotifier instance + */ + const CopyNotifier* notifier() const; + +public slots: + /** + * @brief copy to clipboard the text of the verse with the given index + * @param IdxInPage - verse index relative to the start of the page + */ + void copyVerseText(const Verse v); + +protected: + void closeEvent(QCloseEvent* event); + +private slots: + void copyRange(); + +private: + Ui::CopyDialog* ui; + /** + * @brief reference to the singleton QuranDb instance + */ + const QuranDb& m_quranDb; + /** + * @brief reference to the shared current verse instance + */ + const Verse& m_currVerse; + /** + * @brief pointer to QIntValidator used for validating the selected range of + * verses + */ + QPointer m_verseValidator; + /** + * @brief CopyNotifier instance used for sending notifications + */ + CopyNotifier m_notifier; +}; + +#endif // COPYDIALOG_H diff --git a/src/core/copydialog.ui b/src/dialogs/copydialog.ui similarity index 100% rename from src/core/copydialog.ui rename to src/dialogs/copydialog.ui diff --git a/src/core/downloaderdialog.cpp b/src/dialogs/downloaderdialog.cpp similarity index 65% rename from src/core/downloaderdialog.cpp rename to src/dialogs/downloaderdialog.cpp index 0dd434a8..90cfb4cf 100644 --- a/src/core/downloaderdialog.cpp +++ b/src/dialogs/downloaderdialog.cpp @@ -5,18 +5,28 @@ #include "downloaderdialog.h" #include "ui_downloaderdialog.h" +#include +#include +#include +#include -DownloaderDialog::DownloaderDialog(QWidget* parent, DownloadManager* downloader) +DownloaderDialog::DownloaderDialog(QWidget* parent, JobManager* manager) : QDialog(parent) , ui(new Ui::DownloaderDialog) - , m_downloaderPtr{ downloader } - , m_surahDisplayNames{ m_dbMgr->surahNameList() } + , m_jobMgr(manager) + , m_config(Configuration::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_reciters(Reciter::reciters) + , m_tafasir(Tafsir::tafasir) + , m_translations(Translation::translations) { ui->setupUi(this); - setWindowIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_download)); + setWindowIcon( + StyleManager::getInstance().awesome().icon(fa::fa_solid, fa::fa_download)); // treeview setup + m_surahDisplayNames = m_quranDb.surahNames(); QStringList headers; headers.append(tr("Name")); headers.append(tr("Number")); @@ -52,28 +62,24 @@ DownloaderDialog::setupConnections() this, &DownloaderDialog::clearQueue); - connect(m_downloaderPtr, - &DownloadManager::downloadCompleted, + connect(m_jobMgr, + &JobManager::jobCompleted, this, &DownloaderDialog::downloadCompleted); - - connect(m_downloaderPtr, - &DownloadManager::filesFound, + connect(m_jobMgr, + &JobManager::filesFound, this, &DownloaderDialog::downloadCompleted); - - connect(m_downloaderPtr, - &DownloadManager::downloadCanceled, + connect(m_jobMgr, + &JobManager::jobAborted, this, &DownloaderDialog::downloadAborted); - - connect(m_downloaderPtr, - &DownloadManager::downloadErrored, + connect(m_jobMgr, + &JobManager::jobFailed, this, &DownloaderDialog::topTaskDownloadError); - - connect(m_downloaderPtr, - &DownloadManager::downloadSpeedUpdated, + connect(m_jobMgr, + &JobManager::downloadSpeedUpdated, this, &DownloaderDialog::updateDownloadSpeed); } @@ -82,9 +88,9 @@ void DownloaderDialog::populateTreeModel() { // add reciters - for (const Reciter& reciter : m_recitersList) { - QStandardItem* item = new QStandardItem(reciter.displayName); - item->setToolTip(reciter.displayName); + for (const Reciter& reciter : m_reciters) { + QStandardItem* item = new QStandardItem(reciter.displayName()); + item->setToolTip(reciter.displayName()); m_treeModel.invisibleRootItem()->appendRow(item); for (int j = 1; j <= 114; j++) { @@ -104,11 +110,11 @@ DownloaderDialog::populateTreeModel() tafsir->setData("tafsir", Qt::UserRole); m_treeModel.invisibleRootItem()->appendRow(tafsir); // -- tafasir - for (int i = 0; i < m_tafasirList.size(); i++) { - const Tafsir& t = m_tafasirList.at(i); - if (!t.extra) + for (int i = 0; i < m_tafasir.size(); i++) { + const Tafsir& t = m_tafasir.at(i); + if (!t.isExtra()) continue; - QStandardItem* item = new QStandardItem(t.displayName); + QStandardItem* item = new QStandardItem(t.displayName()); item->setData("tadb", Qt::UserRole); item->setData(i, Qt::UserRole + 1); tafsir->appendRow(item); @@ -120,11 +126,11 @@ DownloaderDialog::populateTreeModel() tafsir->setData("translation", Qt::UserRole); m_treeModel.invisibleRootItem()->appendRow(translation); // -- translations - for (int i = 0; i < m_trList.size(); i++) { - const Translation& tr = m_trList.at(i); - if (!tr.extra) + for (int i = 0; i < m_translations.size(); i++) { + const Translation& tr = m_translations.at(i); + if (!tr.isExtra()) continue; - QStandardItem* item = new QStandardItem(tr.displayName); + QStandardItem* item = new QStandardItem(tr.displayName()); item->setData("trdb", Qt::UserRole); item->setData(i, Qt::UserRole + 1); translation->appendRow(item); @@ -146,23 +152,23 @@ void DownloaderDialog::addToDownloading(int reciter, int surah) { // add surah to downloading tasks - QSet& downloading = m_downloadingTasks[reciter]; + QSet& downloading = m_downloadingSurahs[reciter]; downloading.insert(surah); } void DownloaderDialog::removeFromDownloading(int reciter, int surah) { - QSet& downloading = m_downloadingTasks[reciter]; + QSet& downloading = m_downloadingSurahs[reciter]; downloading.remove(surah); if (downloading.isEmpty()) - m_downloadingTasks.remove(reciter); + m_downloadingSurahs.remove(reciter); } void DownloaderDialog::addToQueue() { - static int recitersnum = m_recitersList.size(); + static int recitersnum = m_reciters.size(); QModelIndexList selected = ui->treeView->selectionModel()->selectedRows(); foreach (const QModelIndex& i, selected) { @@ -180,43 +186,42 @@ DownloaderDialog::addToQueue() enqueueSurah(parent, current + 1); // tafasir else if (i.data(Qt::UserRole).toString() == "tadb") { - QPair info(0, i.data(Qt::UserRole + 1).toInt()); - m_downloaderPtr->addToQueue(File, info); - addTaskProgress(File, info); + QSharedPointer job = QSharedPointer::create( + DownloadJob::TafsirFile, i.data(Qt::UserRole + 1).toInt()); + m_jobMgr->addJob(job); + addTaskProgress(job); } // translation else if (i.data(Qt::UserRole).toString() == "trdb") { - QPair info(1, i.data(Qt::UserRole + 1).toInt()); - m_downloaderPtr->addToQueue(File, info); - addTaskProgress(File, info); + QSharedPointer job = QSharedPointer::create( + DownloadJob::TranslationFile, i.data(Qt::UserRole + 1).toInt()); + m_jobMgr->addJob(job); + addTaskProgress(job); } // extras else if (i.data(Qt::UserRole).toString() == "qcf") { - m_downloaderPtr->addToQueue(QCF); - addTaskProgress(QCF); + QSharedPointer job = QSharedPointer::create(); + m_jobMgr->addJob(job); + addTaskProgress(job); } } setCurrentBar(); - m_downloaderPtr->startQueue(); + m_jobMgr->start(); } void -DownloaderDialog::addTaskProgress(DownloadType type, QPair info) +DownloaderDialog::addTaskProgress(QSharedPointer job) { - int total = 0; + int total = job->total(); QString objName; - if (type == Recitation) { - QString reciter = m_recitersList.at(info.first).displayName; - QString surahName = m_surahDisplayNames.at(info.second - 1); + if (job->type() == DownloadJob::Recitation) { + SurahJob* sJob = qobject_cast(job.data()); + QString reciter = m_reciters.at(sJob->reciter()).displayName(); + QString surahName = m_surahDisplayNames.at(sJob->surah() - 1); objName = reciter + tr(" // Surah: ") + surahName; - total = m_dbMgr->getSurahVerseCount(info.second); - } else if (type == QCF) { - objName = qApp->translate("SettingsDialog", "QCF V2"); - total = 604; - } else if (type == File) { - objName = info.first ? m_trList.at(info.second).displayName - : m_tafasirList.at(info.second).displayName; + } else { + objName = job->name(); } QFrame* prgFrm = new QFrame(ui->scrollAreaWidgetContents); @@ -224,7 +229,7 @@ DownloaderDialog::addTaskProgress(DownloadType type, QPair info) prgFrm->setObjectName(objName); QBoxLayout* downInfo; - if (m_languageCode == QLocale::Arabic) + if (m_config.language() == QLocale::Arabic) downInfo = new QBoxLayout(QBoxLayout::RightToLeft, prgFrm); else downInfo = new QHBoxLayout(prgFrm); @@ -237,10 +242,12 @@ DownloaderDialog::addTaskProgress(DownloadType type, QPair info) downSpeed->setAlignment(Qt::AlignRight); downInfo->addWidget(lbTitle); + downInfo->addStretch(1); downInfo->addWidget(downSpeed); prgFrm->layout()->addItem(downInfo); - DownloadProgressBar* dpb = new DownloadProgressBar(prgFrm, type, total); + DownloadProgressBar* dpb = + new DownloadProgressBar(prgFrm, job->type(), total); prgFrm->layout()->addWidget(dpb); m_frameLst.append(prgFrm); @@ -250,13 +257,16 @@ DownloaderDialog::addTaskProgress(DownloadType type, QPair info) void DownloaderDialog::enqueueSurah(int reciter, int surah) { - bool currentlyDownloading = m_downloadingTasks.value(reciter).contains(surah); + bool currentlyDownloading = + m_downloadingSurahs.value(reciter).contains(surah); if (currentlyDownloading) return; + QSharedPointer sj = + QSharedPointer::create(reciter, surah); addToDownloading(reciter, surah); - addTaskProgress(Recitation, QPair(reciter, surah)); - m_downloaderPtr->addToQueue(reciter, surah); + addTaskProgress(sj); + m_jobMgr->addJob(sj); } void @@ -271,8 +281,8 @@ DownloaderDialog::setCurrentBar() m_currentLb->parent()->objectName()); m_currentBar = m_frameLst.at(0)->findChild(); - connect(m_downloaderPtr, - &DownloadManager::downloadProgressed, + connect(m_jobMgr, + &JobManager::jobProgressed, m_currentBar, &DownloadProgressBar::updateProgress); } @@ -284,18 +294,19 @@ DownloaderDialog::updateDownloadSpeed(int value, QString unit) } void -DownloaderDialog::selectDownload(DownloadType type, QPair info) +DownloaderDialog::selectDownload(DownloadJob::Type type, QPair info) { QItemSelectionModel* selector = ui->treeView->selectionModel(); QModelIndex parent; QModelIndex task; - if (type == Recitation) { + if (type == DownloadJob::Recitation) { parent = m_treeModel.index(info.first, 0); task = m_treeModel.index(info.second - 1, 0, parent); - } else if (type == QCF) { + } else if (type == DownloadJob::Qcf) { parent = m_treeModel.index(m_treeModel.rowCount() - 1, 0); task = m_treeModel.index(0, 0, parent); - } else if (type == File) { + } else if (type == DownloadJob::TafsirFile || + type == DownloadJob::TranslationFile) { parent = m_treeModel.index(m_treeModel.rowCount() - 2 - !info.first, 0); // remove default db indices from current index as defaults are not // downloadable @@ -318,8 +329,8 @@ DownloaderDialog::selectDownload(DownloadType type, QPair info) void DownloaderDialog::clearQueue() { - m_downloadingTasks.clear(); - m_downloaderPtr->stopQueue(); + m_downloadingSurahs.clear(); + m_jobMgr->stop(); if (!m_finishedFrames.isEmpty()) { qDeleteAll(m_finishedFrames); m_finishedFrames.clear(); @@ -329,14 +340,14 @@ DownloaderDialog::clearQueue() void DownloaderDialog::btnStopClicked() { - m_downloadingTasks.clear(); - m_downloaderPtr->stopQueue(); + m_downloadingSurahs.clear(); + m_jobMgr->stop(); } void DownloaderDialog::downloadAborted() { - m_downloadingTasks.clear(); + m_downloadingSurahs.clear(); if (!m_frameLst.isEmpty()) { qDeleteAll(m_frameLst); m_frameLst.clear(); @@ -344,49 +355,56 @@ DownloaderDialog::downloadAborted() } void -DownloaderDialog::downloadCompleted(DownloadType type, - const QList& metainfo) +DownloaderDialog::downloadCompleted(QSharedPointer finished) { m_currentBar->setStyling(DownloadProgressBar::completed); m_currentLb->setText(m_currentLb->parent()->objectName()); m_currDownSpeedLb->setText(tr("Download Completed")); - disconnect(m_downloaderPtr, - &DownloadManager::downloadProgressed, + disconnect(m_jobMgr, + &JobManager::jobProgressed, m_currentBar, &DownloadProgressBar::updateProgress); - if (type == Recitation) - removeFromDownloading(metainfo[0], metainfo[1]); - if (m_currentBar->maximum() == 1) - m_currentBar->setFormat("1 / 1"); + if (finished->type() == DownloadJob::Recitation) { + QSharedPointer sj = finished.dynamicCast(); + removeFromDownloading(sj->reciter(), sj->surah()); + } + + m_currentBar->finished(); m_finishedFrames.append(m_frameLst.front()); m_frameLst.pop_front(); setCurrentBar(); + m_jobMgr->processJobs(); } void -DownloaderDialog::topTaskDownloadError(DownloadType type, - const QList& metainfo) +DownloaderDialog::topTaskDownloadError(QSharedPointer failed) { m_currentBar->setStyling(DownloadProgressBar::aborted); m_currentLb->setText(m_currentLb->parent()->objectName()); m_currDownSpeedLb->setText(tr("Download Failed")); - disconnect(m_downloaderPtr, - &DownloadManager::downloadProgressed, + disconnect(m_jobMgr, + &JobManager::jobProgressed, m_currentBar, &DownloadProgressBar::updateProgress); - if (type == Recitation) - removeFromDownloading(metainfo[0], metainfo[1]); + if (failed->type() == DownloadJob::Recitation) { + QSharedPointer sj = failed.dynamicCast(); + removeFromDownloading(sj->reciter(), sj->surah()); + } + + m_currentBar->failed(); m_finishedFrames.append(m_frameLst.front()); m_frameLst.pop_front(); setCurrentBar(); + m_jobMgr->processJobs(); } void DownloaderDialog::openDownloadsDir() { - QUrl url = QUrl::fromLocalFile(Globals::downloadsDir.absolutePath()); + QUrl url = QUrl::fromLocalFile( + DirManager::getInstance().downloadsDir().absolutePath()); QDesktopServices::openUrl(url); } diff --git a/src/core/downloaderdialog.h b/src/dialogs/downloaderdialog.h similarity index 70% rename from src/core/downloaderdialog.h rename to src/dialogs/downloaderdialog.h index 6e42ef60..7b7b4358 100644 --- a/src/core/downloaderdialog.h +++ b/src/dialogs/downloaderdialog.h @@ -6,10 +6,6 @@ #ifndef DOWNLOADERDIALOG_H #define DOWNLOADERDIALOG_H -#include "../globals.h" -#include "../utils/dbmanager.h" -#include "../utils/downloadmanager.h" -#include "../widgets/downloadprogressbar.h" #include #include #include @@ -18,6 +14,13 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include namespace Ui { class DownloaderDialog; @@ -36,33 +39,29 @@ class DownloaderDialog : public QDialog public: /** - * @brief Class constructor + * @brief class contructor * @param parent - pointer to parent widget - * @param downloader - pointer to a DownloadManager instance - * @param dbMan - pointer to DBManager instance + * @param manager - pointer to the JobManager instance used for managing + * DownloadJob(s) */ explicit DownloaderDialog(QWidget* parent = nullptr, - DownloadManager* downloader = nullptr); + JobManager* manager = nullptr); ~DownloaderDialog(); public slots: /** - * @brief Adds the currently selected surahs in the QTreeView to the + * @brief Adds the currently selected jobs in the QTreeView to the * DownloadManager download queue. - * @details Adding surah to download queue is done through getting the surah - * length through the database instance, then adding download tasks for every - * verse in the surah selected. */ void addToQueue(); /** - * @brief Sets the currently active download - * task progress bar in order to update displayed info. + * @brief Sets the currently active download task progress bar in order to + * update displayed info. * @details This is typically the first incomplete bar in the dialog. */ void setCurrentBar(); /** - * @brief slot to delete all download tasks / - * progress bars from dialog + * @brief slot to delete all download tasks/progress bars from dialog */ void downloadAborted(); /** @@ -70,14 +69,14 @@ public slots: * @param type - DownloadType of the download group * @param metainfo - QList of download group information */ - void downloadCompleted(DownloadType type, const QList& metainfo); + void downloadCompleted(QSharedPointer finished); /** * @brief Callback function to update UI elements when the current active * download group fails * @param type - DownloadType of the download group * @param metainfo - QList of download group information */ - void topTaskDownloadError(DownloadType type, const QList& metainfo); + void topTaskDownloadError(QSharedPointer failed); /** * @brief slot to update the displayed download speed in the currently active * download. @@ -90,7 +89,7 @@ public slots: * @param type - DownloadType of the download group to select * @param info - metainfo for the download task */ - void selectDownload(DownloadType type, + void selectDownload(DownloadJob::Type type, QPair info = QPair(0, 1)); /** * @brief Stops downloading tasks and clears the downloads scrollarea @@ -118,11 +117,12 @@ private slots: void closeEvent(QCloseEvent* event); private: - const int m_languageCode = Globals::language; - const QList& m_recitersList = Globals::recitersList; - const QList& m_tafasirList = Globals::tafasirList; - const QList& m_trList = Globals::translationsList; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); + Ui::DownloaderDialog* ui; + const Configuration& m_config; + const QuranDb& m_quranDb; + const QList& m_reciters; + const QList& m_tafasir; + const QList& m_translations; /** * @brief connects signals and slots for different UI * components and shortcuts. @@ -139,11 +139,10 @@ private slots: * @param info - download metainfo QPair used by DownloadType::Recitation and * DownloadType::File */ - void addTaskProgress(DownloadType type, - QPair info = QPair(-1, -1)); + void addTaskProgress(QSharedPointer job); /** * @brief enqueue a surah to download - * @param reciter - ::Globals::recitersList index for the reciter whose + * @param reciter - ::Reciter::reciters index for the reciter whose * recitations are being downloaded * @param surah - number of surah to download */ @@ -151,7 +150,7 @@ private slots: /** * @brief Adds the combination of reciter & surah to the currently active * downloads QHash. - * @param reciter - ::Globals::recitersList index for the reciter whose + * @param reciter - ::Reciter::reciters index for the reciter whose * recitations are being downloaded * @param surah - surah number */ @@ -159,41 +158,34 @@ private slots: /** * @brief Removes the surah from the reciters currently active * downloads. - * @param reciter - ::Globals::recitersList index for the reciter whose + * @param reciter - ::Reciter::reciters index for the reciter whose * recitations are being downloaded * @param surah - surah number */ void removeFromDownloading(int reciter, int surah); - /** - * @brief Pointer to access ui elements generated from .ui files. - */ - Ui::DownloaderDialog* ui; /** * @brief Pointer to the currently active DownloadProgressBar to update as * verses are downloaded. */ - DownloadProgressBar* m_currentBar; - /** - * @brief Pointer to DownloadManager instance. - */ - DownloadManager* m_downloaderPtr; + QPointer m_currentBar; + QPointer m_jobMgr; /** * @brief Pointer to QLabel which contains the state and information for the * currently active download. */ - QLabel* m_currentLb; + QPointer m_currentLb; /** * @brief Pointer to QLabel which contains the download speed/state. */ - QLabel* m_currDownSpeedLb; + QPointer m_currDownSpeedLb; /** * @brief QFrames for the currently active & queued download tasks. */ - QList m_frameLst; + QList> m_frameLst; /** * @brief QFrames for the downloaded tasks. */ - QList m_finishedFrames; + QList> m_finishedFrames; /** * @brief Model for the recitation download QTreeView. */ @@ -210,7 +202,7 @@ private slots: * checked against this QHash in order to determine whether its a duplicate or * not. */ - QHash> m_downloadingTasks; + QHash> m_downloadingSurahs; }; #endif // DOWNLOADERDIALOG_H diff --git a/src/core/downloaderdialog.ui b/src/dialogs/downloaderdialog.ui similarity index 100% rename from src/core/downloaderdialog.ui rename to src/dialogs/downloaderdialog.ui diff --git a/src/dialogs/fileselector.cpp b/src/dialogs/fileselector.cpp new file mode 100644 index 00000000..3728e5d4 --- /dev/null +++ b/src/dialogs/fileselector.cpp @@ -0,0 +1,68 @@ +#include "fileselector.h" +#include +#include + +FileSelector::FileSelector(QWidget* parent) + : QFileDialog(parent) +{ + setFileMode(QFileDialog::AnyFile); + setDirectory(DirManager::getInstance().downloadsDir().absolutePath()); +} + +QString +FileSelector::selectJson(Mode mode) +{ + setCaption(mode); + setAcceptMode(mode); + setDefaultSuffix("json"); + setNameFilter("JSON Document (*.json)"); + if (!exec() || !validFile(mode, selectedFiles().at(0))) + return ""; + return selectedFiles().at(0); +} + +void +FileSelector::setCaption(Mode mode) +{ + switch (mode) { + case Mode::Read: + setWindowTitle(qApp->translate("QFileDialog", "Open File")); + break; + case Mode::Write: + setWindowTitle(qApp->translate("QFileDialog", "Save File")); + break; + default: + break; + } +} + +void +FileSelector::setAcceptMode(Mode mode) +{ + switch (mode) { + case Mode::Read: + QFileDialog::setAcceptMode(QFileDialog::AcceptOpen); + break; + case Mode::Write: + QFileDialog::setAcceptMode(QFileDialog::AcceptSave); + break; + default: + break; + } +} + +bool +FileSelector::validFile(Mode mode, const QString& file) +{ + if (file.isEmpty()) + return false; + + QFileInfo fInf(file); + if (mode == Read) + return fInf.isReadable(); + if (mode == Write) + return fInf.isWritable() || + QFileInfo(fInf.dir().absolutePath()).isWritable(); + + return false; +} diff --git a/src/dialogs/fileselector.h b/src/dialogs/fileselector.h new file mode 100644 index 00000000..03665d20 --- /dev/null +++ b/src/dialogs/fileselector.h @@ -0,0 +1,23 @@ +#ifndef FILESELECTOR_H +#define FILESELECTOR_H + +#include + +class FileSelector : public QFileDialog +{ +public: + enum Mode + { + Read, + Write + }; + FileSelector(QWidget* parent = nullptr); + QString selectJson(Mode mode); + +private: + void setCaption(Mode mode); + void setAcceptMode(Mode mode); + bool validFile(Mode mode, const QString& file); +}; + +#endif // FILESELECTOR_H diff --git a/src/dialogs/importexportdialog.cpp b/src/dialogs/importexportdialog.cpp new file mode 100644 index 00000000..749672e8 --- /dev/null +++ b/src/dialogs/importexportdialog.cpp @@ -0,0 +1,154 @@ +#include "importexportdialog.h" +#include "ui_importexportdialog.h" + +#include + +ImportExportDialog::ImportExportDialog( + QWidget* parent, + QSharedPointer importer, + QSharedPointer exporter) + : ui(new Ui::ImportExportDialog) + , m_importer(importer) + , m_exporter(exporter) + , QDialog(parent) +{ + ui->setupUi(this); + connect(ui->buttonBox, + &QDialogButtonBox::clicked, + this, + &ImportExportDialog::dialogButtonClicked); + connect(m_importer.data(), + &UserDataImporter::error, + this, + &ImportExportDialog::importError); + connect(m_exporter.data(), + &UserDataExporter::error, + this, + &ImportExportDialog::exportError); +} + +void +ImportExportDialog::selectImports(QString filepath) +{ + m_mode = Mode::Import; + m_importer->setFile(filepath); + if (m_importer->read()) { + setCheckedImports(); + open(); + } +} + +void +ImportExportDialog::selectExports(QString filepath) +{ + m_mode = Mode::Export; + m_exporter->setFile(filepath); + setCheckedExports(); + open(); +} + +void +ImportExportDialog::setCheckedImports() +{ + ui->chkBookmarks->setEnabled(m_importer->fileContains("bookmarks")); + ui->chkKhatmah->setEnabled(m_importer->fileContains("khatmah")); + ui->chkThoughts->setEnabled(m_importer->fileContains("thoughts")); + + ui->chkBookmarks->setChecked(ui->chkBookmarks->isEnabled()); + ui->chkKhatmah->setChecked(ui->chkKhatmah->isEnabled()); + ui->chkThoughts->setChecked(ui->chkThoughts->isEnabled()); +} + +void +ImportExportDialog::setCheckedExports() +{ + ui->chkBookmarks->setEnabled(true); + ui->chkKhatmah->setEnabled(true); + ui->chkThoughts->setEnabled(true); + + ui->chkBookmarks->setChecked(true); + ui->chkKhatmah->setChecked(true); + ui->chkThoughts->setChecked(true); +} + +void +ImportExportDialog::importSelected() const +{ + if (ui->chkBookmarks->isChecked()) + m_importer->importBookmarks(); + if (ui->chkKhatmah->isChecked()) + m_importer->importKhatmah(); + if (ui->chkThoughts->isChecked()) + m_importer->importThoughts(); +} + +void +ImportExportDialog::exportSelected() const +{ + if (ui->chkBookmarks->isChecked()) + m_exporter->exportBookmarks(); + if (ui->chkKhatmah->isChecked()) + m_exporter->exportKhatmah(); + if (ui->chkThoughts->isChecked()) + m_exporter->exportThoughts(); + + m_exporter->save(); +} + +void +ImportExportDialog::setExporter(QSharedPointer newExporter) +{ + m_exporter = newExporter; +} + +void +ImportExportDialog::setImporter(QSharedPointer newImporter) +{ + m_importer = newImporter; +} + +void +ImportExportDialog::accept() +{ + switch (m_mode) { + case Mode::Import: + importSelected(); + break; + case Mode::Export: + exportSelected(); + break; + default: + break; + } + hide(); +} + +void +ImportExportDialog::importError(UserDataImporter::Error err, QString str) +{ + QString msg(tr("The following error occured during import") + ":" + "\n%0"); + QMessageBox::warning(this, tr("Error"), msg.arg(str)); + reject(); +} + +void +ImportExportDialog::exportError(UserDataExporter::Error err, QString str) +{ + QString msg(tr("The following error occured during export") + ":" + "\n%0"); + QMessageBox::warning(this, tr("Error"), msg.arg(str)); + reject(); +} + +void +ImportExportDialog::dialogButtonClicked(QAbstractButton* btn) +{ + if (ui->buttonBox->buttonRole(btn) == QDialogButtonBox::AcceptRole) + accept(); + else + reject(); +} + +ImportExportDialog::~ImportExportDialog() +{ + delete ui; +} diff --git a/src/dialogs/importexportdialog.h b/src/dialogs/importexportdialog.h new file mode 100644 index 00000000..9df29395 --- /dev/null +++ b/src/dialogs/importexportdialog.h @@ -0,0 +1,51 @@ +#ifndef IMPORTEXPORTDIALOG_H +#define IMPORTEXPORTDIALOG_H + +#include +#include +#include +#include +#include + +namespace Ui { +class ImportExportDialog; +} + +class ImportExportDialog : public QDialog +{ + Q_OBJECT +public: + enum Mode + { + Import, + Export + }; + explicit ImportExportDialog(QWidget* parent, + QSharedPointer importer, + QSharedPointer exporter); + ~ImportExportDialog(); + + void selectImports(QString filepath); + void selectExports(QString filepath); + + void setImporter(QSharedPointer newImporter); + void setExporter(QSharedPointer newExporter); + +private slots: + void dialogButtonClicked(QAbstractButton* btn); + void importError(UserDataImporter::Error err, QString str); + void exportError(UserDataExporter::Error err, QString str); + void accept(); + +private: + Ui::ImportExportDialog* ui; + void setCheckedImports(); + void setCheckedExports(); + void importSelected() const; + void exportSelected() const; + QSharedPointer m_importer; + QSharedPointer m_exporter; + Mode m_mode; +}; + +#endif // IMPORTEXPORTDIALOG_H diff --git a/src/dialogs/importexportdialog.ui b/src/dialogs/importexportdialog.ui new file mode 100644 index 00000000..317f2957 --- /dev/null +++ b/src/dialogs/importexportdialog.ui @@ -0,0 +1,139 @@ + + + ImportExportDialog + + + + 0 + 0 + 295 + 176 + + + + + 295 + 176 + + + + + 417 + 246 + + + + Data Selection + + + + + + + + + Bookmarks + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + Khatmah + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + Thoughts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + diff --git a/src/core/khatmahdialog.cpp b/src/dialogs/khatmahdialog.cpp similarity index 74% rename from src/core/khatmahdialog.cpp rename to src/dialogs/khatmahdialog.cpp index b314df08..2731fe22 100644 --- a/src/core/khatmahdialog.cpp +++ b/src/dialogs/khatmahdialog.cpp @@ -1,16 +1,22 @@ #include "khatmahdialog.h" #include "ui_khatmahdialog.h" -#include -#include +#include +#include +#include -KhatmahDialog::KhatmahDialog(const Verse& curr, QWidget* parent) +KhatmahDialog::KhatmahDialog(QWidget* parent) : QDialog(parent) - , m_currVerse(curr) , ui(new Ui::KhatmahDialog) + , m_currVerse(Verse::getCurrent()) + , m_config(Configuration::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_bookmarksDb(BookmarksDb::getInstance()) { ui->setupUi(this); - setWindowIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_list)); - ui->lbCurrKhatmah->setText(m_dbMgr->getKhatmahName(m_dbMgr->activeKhatmah())); + setWindowIcon( + StyleManager::getInstance().awesome().icon(fa::fa_solid, fa::fa_list)); + ui->lbCurrKhatmah->setText( + m_bookmarksDb.getKhatmahName(m_bookmarksDb.activeKhatmah())); loadAll(); connect(ui->btnStartKhatmah, @@ -19,15 +25,15 @@ KhatmahDialog::KhatmahDialog(const Verse& curr, QWidget* parent) &KhatmahDialog::startNewKhatmah); } -InputField* +QPointer KhatmahDialog::loadKhatmah(const int id) { Verse v; - m_dbMgr->getKhatmahPos(id, v); + m_bookmarksDb.loadVerse(id, v); QFrame* frame = new QFrame(ui->scrlDialogContent); // property used for styling frame->setProperty("khatmah", true); - if (!m_darkmode) { + if (!m_config.darkMode()) { QGraphicsDropShadowEffect* shadow = new QGraphicsDropShadowEffect(frame); shadow->setColor(palette().color(QPalette::Shadow)); shadow->setOffset(1); @@ -38,7 +44,7 @@ KhatmahDialog::loadKhatmah(const int id) QHBoxLayout* frmLayout = new QHBoxLayout(); QVBoxLayout* lbLayout = new QVBoxLayout(); QVBoxLayout* btnLayout = new QVBoxLayout(); - InputField* ifName = new InputField(frame, m_dbMgr->getKhatmahName(id)); + InputField* ifName = new InputField(frame, m_bookmarksDb.getKhatmahName(id)); QLabel* lbPosition = new QLabel(frame); QPushButton* activate = new QPushButton(tr("Set as active"), frame); QPushButton* remove = new QPushButton(tr("Remove"), frame); @@ -49,14 +55,14 @@ KhatmahDialog::loadKhatmah(const int id) activate->setStyleSheet( "QPushButton { min-width: 150px; max-width: 150px; }"); remove->setStyleSheet("QPushButton { min-width: 150px; max-width: 150px; }"); - if (id == m_dbMgr->activeKhatmah()) { + if (id == m_bookmarksDb.activeKhatmah()) { m_currActive = frame; activate->setDisabled(true); remove->setDisabled(true); } - QString info = tr("Surah: ") + m_dbMgr->surahNameList().at(v.surah - 1) + - " - " + tr("Verse: ") + QString::number(v.number); + QString info = tr("Surah: ") + m_quranDb.surahNames().at(v.surah() - 1) + + " - " + tr("Verse: ") + QString::number(v.number()); lbPosition->setText(info); activate->setFocusPolicy(Qt::NoFocus); @@ -94,10 +100,10 @@ KhatmahDialog::loadKhatmah(const int id) void KhatmahDialog::loadAll() { - m_khatmahIds = m_dbMgr->getAllKhatmah(); + m_khatmahIds = m_bookmarksDb.getAllKhatmah(); for (int i = 0; i < m_khatmahIds.size(); i++) { m_names.insert(m_khatmahIds.at(i), - m_dbMgr->getKhatmahName(m_khatmahIds.at(i))); + m_bookmarksDb.getKhatmahName(m_khatmahIds.at(i))); loadKhatmah(m_khatmahIds.at(i)); } } @@ -105,9 +111,9 @@ KhatmahDialog::loadAll() void KhatmahDialog::startNewKhatmah() { - int id = m_dbMgr->addKhatmah(m_currVerse, "new"); + int id = m_bookmarksDb.addKhatmah(m_currVerse.toList(), "new"); QString gen = tr("Khatmah ") + QString::number(id); - m_dbMgr->editKhatmahName(id, gen); + m_bookmarksDb.editKhatmahName(id, gen); InputField* inpField = loadKhatmah(id); m_khatmahIds.append(id); m_names.insert(id, gen); @@ -119,14 +125,14 @@ KhatmahDialog::renameKhatmah(QString name) { InputField* caller = qobject_cast(sender()); int id = caller->parent()->objectName().toInt(); - bool ok = m_dbMgr->editKhatmahName(id, name); + bool ok = m_bookmarksDb.editKhatmahName(id, name); if (!ok) caller->setText(m_names.value(id)); else { m_names[id] = name; caller->setText(name); caller->clearFocus(); - if (id == m_dbMgr->activeKhatmah()) + if (id == m_bookmarksDb.activeKhatmah()) ui->lbCurrKhatmah->setText(name); } } @@ -135,7 +141,7 @@ void KhatmahDialog::removeKhatmah() { int id = sender()->parent()->objectName().toInt(); - m_dbMgr->removeKhatmah(id); + m_bookmarksDb.removeKhatmah(id); QFrame* rem = ui->scrlDialogContent->findChild(QString::number(id)); m_frmLst[m_frmLst.indexOf(rem)] = nullptr; delete rem; @@ -148,10 +154,10 @@ KhatmahDialog::setActiveKhatmah() QFrame* newActive = qobject_cast(sender()->parent()); QVariant id = newActive->objectName(); - m_settings->setValue("Reader/Khatmah", id); - m_dbMgr->saveActiveKhatmah(m_currVerse); - m_dbMgr->setActiveKhatmah(id.toInt()); - m_dbMgr->getKhatmahPos(id.toInt(), v); + m_config.settings().setValue("Reader/Khatmah", id); + m_bookmarksDb.saveActiveKhatmah(m_currVerse); + m_bookmarksDb.setActiveKhatmah(id.toInt()); + m_bookmarksDb.loadVerse(id.toInt(), v); newActive->findChild("activate")->setEnabled(false); newActive->findChild("remove")->setEnabled(false); @@ -159,16 +165,10 @@ KhatmahDialog::setActiveKhatmah() m_currActive->findChild("remove")->setEnabled(true); m_currActive = newActive; - ui->lbCurrKhatmah->setText(m_dbMgr->getKhatmahName(id.toInt())); + ui->lbCurrKhatmah->setText(m_bookmarksDb.getKhatmahName(id.toInt())); emit navigateToVerse(v); } -void -KhatmahDialog::closeEvent(QCloseEvent* event) -{ - this->hide(); -} - void KhatmahDialog::show() { @@ -178,6 +178,12 @@ KhatmahDialog::show() QDialog::show(); } +void +KhatmahDialog::closeEvent(QCloseEvent* event) +{ + this->hide(); +} + KhatmahDialog::~KhatmahDialog() { delete ui; diff --git a/src/core/khatmahdialog.h b/src/dialogs/khatmahdialog.h similarity index 78% rename from src/core/khatmahdialog.h rename to src/dialogs/khatmahdialog.h index ea64fad0..13ffad17 100644 --- a/src/core/khatmahdialog.h +++ b/src/dialogs/khatmahdialog.h @@ -1,12 +1,13 @@ #ifndef KHATMAHDIALOG_H #define KHATMAHDIALOG_H -#include "../globals.h" -#include "../utils/dbmanager.h" -#include "../widgets/inputfield.h" #include #include +#include #include +#include +#include +#include namespace Ui { class KhatmahDialog; @@ -26,7 +27,7 @@ class KhatmahDialog : public QDialog * @param curr - reference to the current active verse * @param parent - pointer to the parent widget */ - explicit KhatmahDialog(const Verse& curr, QWidget* parent = nullptr); + explicit KhatmahDialog(QWidget* parent = nullptr); ~KhatmahDialog(); /** * @brief reload shown khatmah entries and show the dialog @@ -37,9 +38,9 @@ class KhatmahDialog : public QDialog /** * @fn void navigateToVerse(Verse v) * @brief Emitted when changing the active khatmah - * @param v - ::Verse to navigate to + * @param v - Verse to navigate to */ - void navigateToVerse(Verse v); + void navigateToVerse(const Verse& v); protected: /** @@ -72,10 +73,11 @@ private slots: void setActiveKhatmah(); private: + Ui::KhatmahDialog* ui; const Verse& m_currVerse; - const bool m_darkmode = Globals::themeId == 2; - QSettings* m_settings = Globals::settings; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); + QuranDb& m_quranDb; + BookmarksDb& m_bookmarksDb; + Configuration& m_config; /** * @brief load all khatmah entries available */ @@ -85,19 +87,15 @@ private slots: * @param id - id of khatmah to load * @return InputField* - pointer to the InputField of the loaded khatmah name */ - InputField* loadKhatmah(const int id); - /** - * @brief pointer to access ui elements generated from .ui files. - */ - Ui::KhatmahDialog* ui; + QPointer loadKhatmah(const int id); /** * @brief pointer to the current active khatmah QFrame */ - QFrame* m_currActive = nullptr; + QPointer m_currActive; /** * @brief QList of all QFrame(s) loaded for the different khatmah entries */ - QList m_frmLst; + QList> m_frmLst; /** * @brief QList of all available khatmah id(s) */ diff --git a/src/core/khatmahdialog.ui b/src/dialogs/khatmahdialog.ui similarity index 100% rename from src/core/khatmahdialog.ui rename to src/dialogs/khatmahdialog.ui diff --git a/src/core/searchdialog.cpp b/src/dialogs/searchdialog.cpp similarity index 72% rename from src/core/searchdialog.cpp rename to src/dialogs/searchdialog.cpp index b74441ab..b3057f5e 100644 --- a/src/core/searchdialog.cpp +++ b/src/dialogs/searchdialog.cpp @@ -4,28 +4,34 @@ */ #include "searchdialog.h" -#include "../widgets/clickablelabel.h" #include "ui_searchdialog.h" +#include +#include +#include SearchDialog::SearchDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::SearchDialog) - , m_surahNames{ m_dbMgr->surahNameList() } + , m_config(Configuration::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_glyphsDb(GlyphsDb::getInstance()) { - setWindowIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_magnifying_glass)); + setWindowIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_magnifying_glass)); ui->setupUi(this); ui->frmNavBtns->setLayoutDirection(Qt::LeftToRight); - ui->btnNext->setIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_left)); - ui->btnPrev->setIcon( - Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_right)); + ui->btnNext->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_left)); + ui->btnPrev->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_right)); ui->btnNext->setDisabled(true); ui->btnPrev->setDisabled(true); - ui->btnTransfer->setIcon( - Globals::awesome->icon(fa::fa_solid, fa::fa_arrow_right_arrow_left)); + ui->btnTransfer->setIcon(StyleManager::getInstance().awesome().icon( + fa::fa_solid, fa::fa_arrow_right_arrow_left)); ui->listViewAllSurahs->setModel(&m_modelAllSurahs); ui->listViewSelected->setModel(&m_modelSelectedSurahs); - if (m_lang == QLocale::Arabic) + if (m_config.language() == QLocale::Arabic) ui->searchTabWidget->setObjectName("rtlTabWidget"); fillListView(); @@ -70,11 +76,13 @@ SearchDialog::getResults() ui->spnEndPage->setValue(range[0]); range[1] = ui->spnEndPage->value(); - m_currResults = - m_dbMgr->searchVerses(m_searchText, range, ui->chkWholeWord->isChecked()); + m_currResults = Verse::fromList(m_quranDb.searchVerses( + m_searchText, range, ui->chkWholeWord->isChecked())); } else { - m_currResults = m_dbMgr->searchSurahs( - m_searchText, m_selectedSurahMap.values(), ui->chkWholeWord->isChecked()); + m_currResults = + Verse::fromList(m_quranDb.searchSurahs(m_searchText, + m_selectedSurahMap.values(), + ui->chkWholeWord->isChecked())); } ui->lbResultCount->setText(QString::number(m_currResults.size()) + tr(" Search results")); @@ -86,7 +94,7 @@ void SearchDialog::verseClicked() { QStringList data = sender()->objectName().split('-'); - Verse selected{ data.at(0).toInt(), data.at(1).toInt(), data.at(2).toInt() }; + Verse selected(data.at(0).toInt(), data.at(1).toInt(), data.at(2).toInt()); emit navigateToVerse(selected); } @@ -107,25 +115,30 @@ SearchDialog::showResults() for (int i = m_startResult; i < endIdx; i++) { Verse v = m_currResults.at(i); - QString fontName = Globals::verseFontname(m_dbMgr->getVerseType(), v.page); + QString fontName = + FontManager::getInstance().verseFontname(m_config.verseType(), v.page()); VerseFrame* vFrame = new VerseFrame(ui->srclResults); QLabel* lbInfo = new QLabel(vFrame); ClickableLabel* clkLb = new ClickableLabel(vFrame); - QString info = tr("Surah: ") + m_surahNames.at(v.surah - 1) + " - " + - tr("Verse: ") + QString::number(v.number); + QString info = tr("Surah: ") + m_quranDb.surahNames().at(v.surah() - 1) + + " - " + tr("Verse: ") + QString::number(v.number()); + QString glyphs = m_config.verseType() == Configuration::Qcf + ? m_glyphsDb.getVerseGlyphs(v.surah(), v.number()) + : m_quranDb.verseText(v.surah(), v.number()); + lbInfo->setText(info); lbInfo->setMaximumHeight(50); lbInfo->setAlignment(Qt::AlignLeft); lbInfo->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum); clkLb->setMargin(5); - clkLb->setObjectName(QString::number(v.page) + '-' + - QString::number(v.surah) + '-' + - QString::number(v.number)); + clkLb->setObjectName(QString::number(v.page()) + '-' + + QString::number(v.surah()) + '-' + + QString::number(v.number())); clkLb->setFont(QFont(fontName, 15)); - clkLb->setText(m_dbMgr->getVerseGlyphs(v.surah, v.number)); + clkLb->setText(glyphs); clkLb->setAlignment(Qt::AlignLeft); clkLb->setWordWrap(true); @@ -173,7 +186,7 @@ void SearchDialog::fillListView() { for (int i = 1; i <= 114; i++) { - QStandardItem* surah = new QStandardItem(m_surahNames.at(i - 1)); + QStandardItem* surah = new QStandardItem(m_quranDb.surahNames().at(i - 1)); m_modelAllSurahs.invisibleRootItem()->appendRow(surah); } } @@ -191,7 +204,7 @@ SearchDialog::btnTransferClicked() if (m_selectedSurahMap.contains(midx.data().toString())) continue; // KEY: visible surah name - VALUE: surah number - int sIdx = m_surahNames.indexOf(midx.data().toString()); + int sIdx = m_quranDb.surahNames().indexOf(midx.data().toString()); m_selectedSurahMap.insert(midx.data().toString(), sIdx + 1); m_modelSelectedSurahs.appendRow( new QStandardItem(midx.data().toString())); // add surah to selected view diff --git a/src/core/searchdialog.h b/src/dialogs/searchdialog.h similarity index 83% rename from src/core/searchdialog.h rename to src/dialogs/searchdialog.h index d03aaafe..671a803a 100644 --- a/src/core/searchdialog.h +++ b/src/dialogs/searchdialog.h @@ -6,16 +6,17 @@ #ifndef SEARCHDIALOG_H #define SEARCHDIALOG_H -#include "../globals.h" -#include "../utils/dbmanager.h" -#include "../widgets/verseframe.h" #include +#include #include #include #include #include #include #include +#include +#include +#include namespace Ui { class SearchDialog; @@ -48,7 +49,7 @@ public slots: void getResults(); /** * @brief Slot that is called when one of the result verse labels is clicked. - * Extracts a ::Verse from the emitting object name and emits + * Extracts a Verse from the emitting object name and emits * SearchDialog::navigateToVerse(Verse) signal. */ void verseClicked(); @@ -72,9 +73,9 @@ public slots: * @fn void navigateToVerse(Verse v) * @brief Emitted when a search result is clicked to signal the * navigation and selection of that verse. - * @param v - ::Verse to navigate to + * @param v - Verse to navigate to */ - void navigateToVerse(Verse v); + void navigateToVerse(const Verse& v); protected: /** @brief Re-implementation of QWidget::closeEvent() in order to hide the @@ -95,8 +96,10 @@ private slots: void btnTransferClicked(); private: - const QLocale::Language m_lang = Globals::language; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); + Ui::SearchDialog* ui; + const Configuration& m_config; + const QuranDb& m_quranDb; + const GlyphsDb& m_glyphsDb; /** * @brief connects signals and slots for different UI * components and shortcuts. @@ -112,17 +115,13 @@ private slots: * SearchDialog::m_currResults. */ int m_startResult = 0; - /** - * @brief Pointer to access ui elements generated from .ui files. - */ - Ui::SearchDialog* ui; /** * @brief QList for all visible HighlightFrame widgets containing search * results. */ - QList m_lbLst; + QList> m_lbLst; /** - * @brief ::Verse QList for the current search results. + * @brief Verse QList for the current search results. */ QList m_currResults; /** @@ -133,11 +132,6 @@ private slots: * @brief Current search text. */ QString m_searchText; - /** - * @brief QStringList for the surah names which are displayed above search - * results and in the QListView. - */ - QStringList m_surahNames; /** * @brief Model for the QListView that shows all surahs to select from. */ diff --git a/src/core/searchdialog.ui b/src/dialogs/searchdialog.ui similarity index 100% rename from src/core/searchdialog.ui rename to src/dialogs/searchdialog.ui diff --git a/src/core/settingsdialog.cpp b/src/dialogs/settingsdialog.cpp similarity index 71% rename from src/core/settingsdialog.cpp rename to src/dialogs/settingsdialog.cpp index 2a521653..6cdb8f1c 100644 --- a/src/core/settingsdialog.cpp +++ b/src/dialogs/settingsdialog.cpp @@ -4,25 +4,32 @@ */ #include "settingsdialog.h" -#include "../widgets/shortcutdelegate.h" #include "ui_settingsdialog.h" +#include +#include +#include SettingsDialog::SettingsDialog(QWidget* parent, VersePlayer* vPlayerPtr) : QDialog(parent) , ui(new Ui::SettingsDialog) , m_vPlayerPtr(vPlayerPtr) + , m_config(Configuration::getInstance()) + , m_downloadsDir(DirManager::getInstance().downloadsDir()) + , m_shortcutDescription(ShortcutHandler::getInstance().shortcutsDescription()) + , m_tafasir(Tafsir::tafasir) + , m_translations(Translation::translations) { ui->setupUi(this); ui->cmbQuranFontSz->setValidator(new QIntValidator(10, 72)); ui->cmbSideFontSz->setValidator(new QIntValidator(10, 72)); - setWindowIcon(Globals::awesome->icon(fa::fa_solid, fa::fa_gear)); + setWindowIcon( + StyleManager::getInstance().awesome().icon(fa::fa_solid, fa::fa_gear)); ui->tableViewShortcuts->setModel(&m_shortcutsModel); ui->tableViewShortcuts->horizontalHeader()->setStretchLastSection(true); ui->tableViewShortcuts->setItemDelegate(new ShortcutDelegate); fillLanguageCombobox(); setCurrentSettingsAsRef(); - // connectors setupConnections(); } @@ -47,7 +54,7 @@ SettingsDialog::populateShortcutsModel() QStandardItem *desc = new QStandardItem(), *keySeq = new QStandardItem(); desc->setData(key, Qt::UserRole); desc->setText(tr(m_shortcutDescription.value(key).toStdString().c_str())); - keySeq->setText(m_settings->value("Shortcuts/" + key).toString()); + keySeq->setText(m_config.settings().value("Shortcuts/" + key).toString()); QList row; row.append(desc); @@ -67,42 +74,36 @@ SettingsDialog::fillLanguageCombobox() void SettingsDialog::updateContentCombobox() { - ui->cmbTafsir->clear(); - for (int i = 0; i < m_tafasirList.size(); i++) { - const Tafsir& t = m_tafasirList[i]; - if (Globals::tafsirExists(&t)) - ui->cmbTafsir->addItem(t.displayName, i); - } ui->cmbTranslation->clear(); - for (int i = 0; i < m_trList.size(); i++) { - const Translation& tr = m_trList[i]; - if (Globals::translationExists(&tr)) - ui->cmbTranslation->addItem(tr.displayName, i); + for (int i = 0; i < m_translations.size(); i++) { + const Translation& tr = m_translations[i]; + if (tr.isAvailable()) + ui->cmbTranslation->addItem(tr.displayName(), i); } - m_tafsir = ui->cmbTafsir->findData(m_settings->value("Reader/Tafsir")); - m_translation = - ui->cmbTranslation->findData(m_settings->value("Reader/Translation")); - ui->cmbTafsir->setCurrentIndex(m_tafsir); + m_translation = ui->cmbTranslation->findData( + m_config.settings().value("Reader/Translation")); ui->cmbTranslation->setCurrentIndex(m_translation); } void SettingsDialog::setCurrentSettingsAsRef() { - m_votd = m_settings->value("VOTD").toBool(); - m_fgHighlight = m_settings->value("Reader/FGHighlight").toBool(); - m_missingFileWarning = m_settings->value("MissingFileWarning").toBool(); + m_votd = m_config.settings().value("VOTD").toBool(); + m_fgHighlight = m_config.settings().value("Reader/FGHighlight").toBool(); + m_missingFileWarning = + m_config.settings().value("MissingFileWarning").toBool(); - m_adaptive = m_settings->value("Reader/AdaptiveFont").toBool(); + m_adaptive = m_config.settings().value("Reader/AdaptiveFont").toBool(); m_quranFontSize = - m_settings->value("Reader/QCF" + QString::number(m_qcfVer) + "Size") + m_config.settings() + .value("Reader/QCF" + QString::number(m_config.qcfVersion()) + "Size") .toInt(); m_sideFont = - qvariant_cast(m_settings->value("Reader/SideContentFont")); + qvariant_cast(m_config.settings().value("Reader/SideContentFont")); - m_verseType = m_settings->value("Reader/VerseType").toInt(); - m_verseFontSize = m_settings->value("Reader/VerseFontSize").toInt(); + m_verseType = m_config.settings().value("Reader/VerseType").toInt(); + m_verseFontSize = m_config.settings().value("Reader/VerseFontSize").toInt(); m_audioDevices = QMediaDevices::audioOutputs(); ui->cmbAudioDevices->clear(); @@ -114,10 +115,10 @@ SettingsDialog::setCurrentSettingsAsRef() // set ui elements to current settings ui->cmbAudioDevices->setCurrentIndex(m_audioOutIdx); - ui->cmbLang->setCurrentIndex(ui->cmbLang->findData(m_languageCode)); - ui->cmbTheme->setCurrentIndex(m_themeIdx); - ui->cmbReaderMode->setCurrentIndex(m_readerMode); - ui->cmbQCF->setCurrentIndex(m_qcfVer - 1); + ui->cmbLang->setCurrentIndex(ui->cmbLang->findData(m_config.language())); + ui->cmbTheme->setCurrentIndex(m_config.themeId()); + ui->cmbReaderMode->setCurrentIndex(m_config.readerMode()); + ui->cmbQCF->setCurrentIndex(m_config.qcfVersion() - 1); ui->cmbQuranFontSz->setCurrentText(QString::number(m_quranFontSize)); ui->fntCmbSide->setCurrentFont(m_sideFont); ui->cmbSideFontSz->setCurrentText(QString::number(m_sideFont.pointSize())); @@ -142,28 +143,15 @@ SettingsDialog::checkShortcuts() const QString& key = m_shortcutsModel.item(row, 0)->data(Qt::UserRole).toString(); const QString& keySequence = m_shortcutsModel.item(row, 1)->text(); - if (m_settings->value("Shortcuts/" + key).toString() != keySequence) + if (m_config.settings().value("Shortcuts/" + key).toString() != keySequence) updateShortcut(key, keySequence); } } -bool -SettingsDialog::qcfExists() -{ - QString filename = "QCFV2/QCF2%0.ttf"; - for (int i = 1; i <= 604; i++) { - if (!m_downloadsDir.exists( - filename.arg(QString::number(i).rightJustified(3, '0')))) - return false; - } - - return true; -} - void SettingsDialog::updateTheme(int themeIdx) { - m_settings->setValue("Theme", themeIdx); + m_config.settings().setValue("Theme", themeIdx); if (m_restartReq) return; @@ -178,7 +166,7 @@ SettingsDialog::updateTheme(int themeIdx) void SettingsDialog::updateLang(QLocale::Language lang) { - m_settings->setValue("Language", lang); + m_config.settings().setValue("Language", lang); if (m_restartReq) return; @@ -193,26 +181,19 @@ SettingsDialog::updateLang(QLocale::Language lang) void SettingsDialog::updateDailyVerse(bool on) { - m_settings->setValue("VOTD", on); + m_config.settings().setValue("VOTD", on); } void SettingsDialog::updateFileWarning(bool on) { - m_settings->setValue("MissingFileWarning", on); -} - -void -SettingsDialog::updateTafsir(int idx) -{ - m_settings->setValue("Reader/Tafsir", idx); - emit tafsirChanged(); + m_config.settings().setValue("MissingFileWarning", on); } void SettingsDialog::updateTranslation(int idx) { - m_settings->setValue("Reader/Translation", idx); + m_config.settings().setValue("Reader/Translation", idx); emit translationChanged(); m_renderSideContent = true; @@ -221,7 +202,7 @@ SettingsDialog::updateTranslation(int idx) void SettingsDialog::updateReaderMode(int idx) { - m_settings->setValue("Reader/Mode", idx); + m_config.settings().setValue("Reader/Mode", idx); if (m_restartReq) return; @@ -234,13 +215,13 @@ SettingsDialog::updateReaderMode(int idx) void SettingsDialog::updateQuranFont(int qcfV) { - if (qcfV == 2 && !qcfExists()) { + if (qcfV == 2 && !FontManager::getInstance().getInstance().qcfExists()) { emit qcf2Missing(); ui->cmbQCF->setCurrentIndex(0); return; } - m_settings->setValue("Reader/QCF", qcfV); + m_config.settings().setValue("Reader/QCF", qcfV); if (m_restartReq) return; @@ -255,13 +236,14 @@ SettingsDialog::updateQuranFont(int qcfV) void SettingsDialog::updateAdaptiveFont(bool on) { - m_settings->setValue("Reader/AdaptiveFont", on); + m_config.settings().setValue("Reader/AdaptiveFont", on); } void SettingsDialog::updateQuranFontSize(QString size) { - m_settings->setValue("Reader/QCF" + QString::number(m_qcfVer) + "Size", size); + m_config.settings().setValue( + "Reader/QCF" + QString::number(m_config.qcfVersion()) + "Size", size); emit quranFontChanged(); m_renderQuranPage = true; } @@ -269,7 +251,7 @@ SettingsDialog::updateQuranFontSize(QString size) void SettingsDialog::updateFgHighlight(bool on) { - m_settings->setValue("Reader/FGHighlight", on); + m_config.settings().setValue("Reader/FGHighlight", on); emit highlightLayerChanged(); } @@ -279,7 +261,7 @@ SettingsDialog::updateSideFont(QFont fnt) fnt.setPointSize(m_sideFont.pointSize()); m_sideFont = fnt; - m_settings->setValue("Reader/SideContentFont", m_sideFont); + m_config.settings().setValue("Reader/SideContentFont", m_sideFont); emit sideFontChanged(); m_renderSideContent = true; } @@ -288,23 +270,23 @@ void SettingsDialog::updateSideFontSize(QString size) { m_sideFont.setPointSize(size.toInt()); - m_settings->setValue("Reader/SideContentFont", m_sideFont); + m_config.settings().setValue("Reader/SideContentFont", m_sideFont); emit sideFontChanged(); m_renderSideContent = true; } void -SettingsDialog::updateVerseText(int vt) +SettingsDialog::updateVerseType(int vt) { - m_settings->setValue("Reader/VerseType", vt); + m_config.settings().setValue("Reader/VerseType", vt); emit verseTypeChanged(); m_renderSideContent = true; } void -SettingsDialog::updateVerseTextFontsize(QString size) +SettingsDialog::updateVerseFontsize(QString size) { - m_settings->setValue("Reader/VerseFontSize", size); + m_config.settings().setValue("Reader/VerseFontSize", size); emit verseTypeChanged(); m_renderSideContent = true; } @@ -312,7 +294,7 @@ SettingsDialog::updateVerseTextFontsize(QString size) void SettingsDialog::updateShortcut(QString key, QString keySequence) { - m_settings->setValue("Shortcuts/" + key, keySequence); + m_config.settings().setValue("Shortcuts/" + key, keySequence); emit shortcutChanged(key); } @@ -321,11 +303,11 @@ SettingsDialog::applyAllChanges() { QLocale::Language chosenLang = qvariant_cast(ui->cmbLang->currentData()); - if (chosenLang != m_languageCode) { + if (chosenLang != m_config.language()) { updateLang(chosenLang); } - if (ui->cmbTheme->currentIndex() != m_themeIdx) + if (ui->cmbTheme->currentIndex() != m_config.themeId()) updateTheme(ui->cmbTheme->currentIndex()); if (ui->chkDailyVerse->isChecked() != m_votd) @@ -337,25 +319,22 @@ SettingsDialog::applyAllChanges() if (ui->chkFgHighlight->isChecked() != m_fgHighlight) updateFgHighlight(ui->chkFgHighlight->isChecked()); - if (ui->cmbTafsir->currentIndex() != m_tafsir) - updateTafsir(ui->cmbTafsir->currentData().toInt()); - if (ui->cmbTranslation->currentIndex() != m_translation) updateTranslation(ui->cmbTranslation->currentData().toInt()); - if (ui->cmbQCF->currentIndex() + 1 != m_qcfVer) + if (ui->cmbQCF->currentIndex() + 1 != m_config.qcfVersion()) updateQuranFont(ui->cmbQCF->currentIndex() + 1); if (ui->cmbVerseText->currentIndex() != m_verseType) - updateVerseText(ui->cmbVerseText->currentIndex()); + updateVerseType(ui->cmbVerseText->currentIndex()); if (ui->cmbVersesFontSz->currentText() != QString::number(m_verseFontSize)) - updateVerseTextFontsize(ui->cmbVersesFontSz->currentText()); + updateVerseFontsize(ui->cmbVersesFontSz->currentText()); if (ui->chkAdaptive->isChecked() != m_adaptive) updateAdaptiveFont(ui->chkAdaptive->isChecked()); - if (ui->cmbReaderMode->currentIndex() != m_readerMode) + if (ui->cmbReaderMode->currentIndex() != m_config.readerMode()) updateReaderMode(ui->cmbReaderMode->currentIndex()); bool forceManualFont = false; diff --git a/src/core/settingsdialog.h b/src/dialogs/settingsdialog.h similarity index 86% rename from src/core/settingsdialog.h rename to src/dialogs/settingsdialog.h index 5935bab1..99730069 100644 --- a/src/core/settingsdialog.h +++ b/src/dialogs/settingsdialog.h @@ -6,7 +6,8 @@ #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H -#include "../utils/verseplayer.h" +#include "fileselector.h" +#include "importexportdialog.h" #include #include #include @@ -16,11 +17,20 @@ #include #include #include +#include #include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include +typedef Configuration::ReaderMode ReaderMode; namespace Ui { class SettingsDialog; @@ -33,7 +43,6 @@ class SettingsDialog; class SettingsDialog : public QDialog { Q_OBJECT - public: /** * @brief Class constructor @@ -73,11 +82,6 @@ public slots: * @param on - boolean flag representing the new setting value */ void updateFileWarning(bool on); - /** - * @brief Update the selected tafsir - * @param idx - index of the tafsir value in DBManager::Tafsir enum - */ - void updateTafsir(int idx); /** * @brief Update the selected translation * @param idx - index of the translation value in DBManager::Translation enum @@ -120,8 +124,8 @@ public slots: * @param size - QString representing the new font size */ void updateSideFontSize(QString size); - void updateVerseText(int vt); - void updateVerseTextFontsize(QString size); + void updateVerseType(int vt); + void updateVerseFontsize(QString size); /** * @brief update the key sequence that trigger the shortcut with the given key * @param key - QString of the shortcut name in the settings file @@ -170,11 +174,6 @@ public slots: * Quran page construction. */ void redrawQuranPage(bool manual); - /** - * @fn tafsirChanged() - * @brief signal emitted to notify changes in the displayed tafsir - */ - void tafsirChanged(); /** * @fn translationChanged() * @brief signal emitted to notify changes in the displayed translation @@ -218,16 +217,12 @@ public slots: void closeEvent(QCloseEvent* event); private: - const int m_qcfVer = Globals::qcfVersion; - const int m_themeIdx = Globals::themeId; - const ReaderMode m_readerMode = Globals::readerMode; - const QLocale::Language m_languageCode = Globals::language; - QSettings* const m_settings = Globals::settings; - const QDir& m_downloadsDir = Globals::downloadsDir; - const QList& m_tafasirList = Globals::tafasirList; - const QList& m_trList = Globals::translationsList; - const QMap& m_shortcutDescription = - Globals::shortcutDescription; + Ui::SettingsDialog* ui; + Configuration& m_config; + const QDir& m_downloadsDir; + const QList& m_tafasir; + const QList& m_translations; + const QMap& m_shortcutDescription; /** * @brief connects signals and slots for different UI * components and shortcuts. @@ -255,13 +250,6 @@ public slots: * @brief check if any shortcut was changed and updated it */ void checkShortcuts(); - /** - * @brief check if QCF2 font files exist - * - * @return true - all 604 QCF2 files are found - * @return false - one of the files is missing - */ - bool qcfExists(); /** * @brief QCF font size used in constructing Quran page. */ @@ -271,14 +259,11 @@ public slots: * SettingsDialog::m_audioDevices. */ int m_audioOutIdx; - /** - * @brief DBManager::Tafsir enum value mapped to the tafsir index in the - * combobox. - */ - int m_tafsir; /** * @brief DBManager::Translation enum value mapped to the translation index in * the combobox. + * + * MODIFIED */ int m_translation; int m_verseType; @@ -321,14 +306,10 @@ public slots: * @brief boolean flag to indicate that foreground highlighting is active. */ bool m_fgHighlight = true; - /** - * @brief Pointer to access ui elements generated from .ui files. - */ - Ui::SettingsDialog* ui; /** * @brief pointer to VersePlayer instance. */ - VersePlayer* m_vPlayerPtr = nullptr; + QPointer m_vPlayerPtr; /** * @brief model used by the shortcuts QTableView */ diff --git a/src/core/settingsdialog.ui b/src/dialogs/settingsdialog.ui similarity index 97% rename from src/core/settingsdialog.ui rename to src/dialogs/settingsdialog.ui index ee6c7a64..4c10d3c2 100644 --- a/src/core/settingsdialog.ui +++ b/src/dialogs/settingsdialog.ui @@ -29,7 +29,7 @@ QTabWidget::Rounded - 1 + 0 Qt::ElideNone @@ -578,20 +578,6 @@ - - - - - - Tafsir - - - - - - - - diff --git a/src/widgets/versedialog.cpp b/src/dialogs/versedialog.cpp similarity index 71% rename from src/widgets/versedialog.cpp rename to src/dialogs/versedialog.cpp index 0c7eaee7..a66a9c9f 100644 --- a/src/widgets/versedialog.cpp +++ b/src/dialogs/versedialog.cpp @@ -4,22 +4,30 @@ VerseDialog::VerseDialog(QWidget* parent) : QDialog(parent) , ui(new Ui::VerseDialog) + , m_config(Configuration::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_translationDb(TranslationDb::getInstance()) + , m_timestampFile( + DirManager::getInstance().configDir().absoluteFilePath("votd.log")) { ui->setupUi(this); QFont sideFont = - qvariant_cast(m_settings->value("Reader/SideContentFont")); + qvariant_cast(m_config.settings().value("Reader/SideContentFont")); ui->lbContent->setFont(sideFont); ui->lbInfo->setFont(sideFont); ui->lbVerse->setFont(QFont("kfgqpc_hafs_uthmanic _script", 20)); + + if (m_config.settings().value("VOTD").toBool()) + showVOTD(true); } QString VerseDialog::votdStringEntry() const { - QString entry = QString::number(m_votd.page).rightJustified(3, '0') + ":" + - QString::number(m_votd.surah).rightJustified(3, '0') + ":" + - QString::number(m_votd.number).rightJustified(3, '0'); + QString entry = QString::number(m_votd.page()).rightJustified(3, '0') + ":" + + QString::number(m_votd.surah()).rightJustified(3, '0') + ":" + + QString::number(m_votd.number()).rightJustified(3, '0'); return entry; } @@ -44,7 +52,7 @@ VerseDialog::votdShown() void VerseDialog::genVerseOfTheDay() { - m_votd = m_dbMgr->randomVerse(); + m_votd = m_quranDb.randomVerse(); } void @@ -79,12 +87,13 @@ void VerseDialog::updateLabels() { ui->lbVerse->setText( - "ﵩ " + m_dbMgr->getVerseText(m_votd.surah, m_votd.number) + " ﵨ"); - ui->lbContent->setText(m_dbMgr->getTranslation(m_votd.surah, m_votd.number)); + "ﵩ " + m_quranDb.verseText(m_votd.surah(), m_votd.number()) + " ﵨ"); + ui->lbContent->setText( + m_translationDb.getTranslation(m_votd.surah(), m_votd.number())); ui->lbInfo->setText(qApp->translate("BookmarksDialog", "Surah: ") + - m_dbMgr->getSurahName(m_votd.surah) + " - " + + m_quranDb.surahName(m_votd.surah()) + " - " + qApp->translate("BookmarksDialog", "Verse: ") + - QString::number(m_votd.number)); + QString::number(m_votd.number())); } void diff --git a/src/widgets/versedialog.h b/src/dialogs/versedialog.h similarity index 77% rename from src/widgets/versedialog.h rename to src/dialogs/versedialog.h index c281df33..890990c0 100644 --- a/src/widgets/versedialog.h +++ b/src/dialogs/versedialog.h @@ -1,10 +1,11 @@ #ifndef VERSEDIALOG_H #define VERSEDIALOG_H -#include "../globals.h" -#include "../utils/dbmanager.h" #include #include +#include +#include +#include namespace Ui { class VerseDialog; @@ -26,7 +27,7 @@ public slots: void showVOTD(bool startup); signals: - void navigateToVerse(Verse v); + void navigateToVerse(const Verse& v); protected: void mouseReleaseEvent(QMouseEvent* event); @@ -34,10 +35,10 @@ public slots: private: Ui::VerseDialog* ui; - const QSettings* m_settings = Globals::settings; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); - QFile m_timestampFile = Globals::configDir.absoluteFilePath("votd.log"); - fa::QtAwesome* m_fa = Globals::awesome; + Configuration& m_config; + const QuranDb& m_quranDb; + const TranslationDb& m_translationDb; + QFile m_timestampFile; /** * @brief generate the verse of the day and set the votd html */ diff --git a/src/widgets/versedialog.ui b/src/dialogs/versedialog.ui similarity index 100% rename from src/widgets/versedialog.ui rename to src/dialogs/versedialog.ui diff --git a/src/downloader/contentjob.cpp b/src/downloader/contentjob.cpp new file mode 100644 index 00000000..9f1be23d --- /dev/null +++ b/src/downloader/contentjob.cpp @@ -0,0 +1,85 @@ +#include "contentjob.h" +#include +#include + +ContentJob::ContentJob(Type type, int idx) + : m_idx(idx) + , m_type(type) + , m_tafasir(Tafsir::tafasir) + , m_translations(Translation::translations) + , m_isDownloading(false) + , m_taskDlr(this) +{ + if (type == DownloadJob::TafsirFile) + m_task = new TafsirTask(idx); + else if (type == DownloadJob::TranslationFile) + m_task = new TranslationTask(idx); + + connect(&m_taskDlr, &TaskDownloader::completed, this, &DownloadJob::finished); + connect(&m_taskDlr, &TaskDownloader::taskError, this, &DownloadJob::failed); + connect( + &m_taskDlr, &TaskDownloader::progressed, this, &DownloadJob::progressed); + connect(&m_taskDlr, + &TaskDownloader::downloadSpeedUpdated, + this, + &DownloadJob::downloadSpeedUpdated); +} + +void +ContentJob::start() +{ + if (m_isDownloading) + return; + m_isDownloading = true; + if (m_task->destination().exists()) { + emit fileFound(); + return; + } + m_taskDlr.process(m_task, &m_netMgr); +} + +void +ContentJob::stop() +{ + if (!m_isDownloading) + return; + m_taskDlr.cancel(); + m_isDownloading = false; +} + +bool +ContentJob::isDownloading() +{ + return m_isDownloading; +} + +int +ContentJob::completed() +{ + return m_taskDlr.bytes() / 1024; +} + +int +ContentJob::total() +{ + return m_taskDlr.total() / 1024; +} + +DownloadJob::Type +ContentJob::type() +{ + return m_type; +} + +QString +ContentJob::name() +{ + return m_type == DownloadJob::TafsirFile + ? m_tafasir.at(m_idx).displayName() + : m_translations.at(m_idx).displayName(); +} + +ContentJob::~ContentJob() +{ + delete m_task; +} diff --git a/src/downloader/contentjob.h b/src/downloader/contentjob.h new file mode 100644 index 00000000..1cb5debf --- /dev/null +++ b/src/downloader/contentjob.h @@ -0,0 +1,38 @@ +#ifndef CONTENTJOB_H +#define CONTENTJOB_H + +#include "taskdownloader.h" +#include +#include +#include + +class ContentJob : public DownloadJob +{ + Q_OBJECT +public: + ContentJob(Type type, int idx); + ~ContentJob(); + + void start() override; + void stop() override; + bool isDownloading() override; + int completed() override; + int total() override; + Type type() override; + QString name() override; + +signals: + void fileFound(); + +private: + QList& m_tafasir; + QList& m_translations; + TaskDownloader m_taskDlr; + QNetworkAccessManager m_netMgr; + DownloadTask* m_task; + Type m_type; + bool m_isDownloading; + int m_idx; +}; + +#endif // CONTENTJOB_H diff --git a/src/downloader/jobmanager.cpp b/src/downloader/jobmanager.cpp new file mode 100644 index 00000000..865dcd89 --- /dev/null +++ b/src/downloader/jobmanager.cpp @@ -0,0 +1,147 @@ +#include "jobmanager.h" +#include "contentjob.h" + +JobManager::JobManager(QObject* parent) + : QObject(parent) + , m_notifier(this) +{ +} + +void +JobManager::addJob(QSharedPointer job) +{ + m_queue.enqueue(job); +} + +void +JobManager::start() +{ + if (m_isOn) + return; + m_isOn = true; + processJobs(); +} + +void +JobManager::stop() +{ + if (!m_isOn) + return; + m_isOn = false; + if (!m_active.isNull()) + m_active->stop(); + m_queue.clear(); + disconnectActive(); + emit jobAborted(); +} + +void +JobManager::processJobs() +{ + if (m_queue.isEmpty()) { + m_isOn = false; + return; + } + m_active = m_queue.dequeue(); + connectActive(); + m_active->start(); +} + +void +JobManager::connectActive() +{ + if (!m_active.isNull()) + disconnectActive(); + + if (m_active->type() == DownloadJob::TafsirFile || + m_active->type() == DownloadJob::TranslationFile) + connect(qobject_cast(m_active.data()), + &ContentJob::fileFound, + this, + &JobManager::handleFilesFound); + + connect( + m_active.data(), &DownloadJob::failed, this, &JobManager::handleFailed); + connect(m_active.data(), + &DownloadJob::finished, + this, + &JobManager::handleCompleted); + connect(m_active.data(), + &DownloadJob::progressed, + this, + &JobManager::handleProgressed); + connect(m_active.data(), + &DownloadJob::downloadSpeedUpdated, + this, + &JobManager::downloadSpeedUpdated); +} + +void +JobManager::disconnectActive() +{ + if (m_active->type() == DownloadJob::TafsirFile || + m_active->type() == DownloadJob::TranslationFile) + disconnect(qobject_cast(m_active.data()), + &ContentJob::fileFound, + this, + &JobManager::handleFilesFound); + + disconnect( + m_active.data(), &DownloadJob::failed, this, &JobManager::handleFailed); + disconnect(m_active.data(), + &DownloadJob::finished, + this, + &JobManager::handleCompleted); + disconnect(m_active.data(), + &DownloadJob::progressed, + this, + &JobManager::handleProgressed); + disconnect(m_active.data(), + &DownloadJob::downloadSpeedUpdated, + this, + &JobManager::downloadSpeedUpdated); +} + +void +JobManager::handleProgressed() +{ + emit jobProgressed(m_active); +} + +void +JobManager::handleFailed() +{ + m_notifier.notifyFailed(m_active.data()); + emit jobFailed(m_active); +} + +void +JobManager::handleCompleted() +{ + m_notifier.notifyCompleted(m_active.data()); + emit jobCompleted(m_active); +} + +void +JobManager::handleFilesFound() +{ + emit filesFound(m_active); +} + +bool +JobManager::isOn() const +{ + return m_isOn; +} + +QSharedPointer +JobManager::active() const +{ + return m_active; +} + +const JobNotifier* +JobManager::notifier() const +{ + return &m_notifier; +} diff --git a/src/downloader/jobmanager.h b/src/downloader/jobmanager.h new file mode 100644 index 00000000..1ee39a38 --- /dev/null +++ b/src/downloader/jobmanager.h @@ -0,0 +1,50 @@ +#ifndef JOBMANAGER_H +#define JOBMANAGER_H + +#include +#include +#include +#include +#include + +class JobManager : public QObject +{ + Q_OBJECT +public: + explicit JobManager(QObject* parent); + + void start(); + void stop(); + void addJob(QSharedPointer job); + + bool isOn() const; + QSharedPointer active() const; + const JobNotifier* notifier() const; + +public slots: + void processJobs(); + +signals: + void jobAborted(); + void jobCompleted(QSharedPointer job); + void jobFailed(QSharedPointer job); + void jobProgressed(QSharedPointer job); + void downloadSpeedUpdated(int speed, QString unit); + void filesFound(QSharedPointer job); + +private slots: + void handleProgressed(); + void handleFailed(); + void handleCompleted(); + void handleFilesFound(); + +private: + void connectActive(); + void disconnectActive(); + QQueue> m_queue; + QSharedPointer m_active; + JobNotifier m_notifier; + bool m_isOn = false; +}; + +#endif // JOBMANAGER_H diff --git a/src/downloader/qcfjob.cpp b/src/downloader/qcfjob.cpp new file mode 100644 index 00000000..d8282c82 --- /dev/null +++ b/src/downloader/qcfjob.cpp @@ -0,0 +1,120 @@ +#include "qcfjob.h" +#include + +QcfJob::QcfJob() + : m_completed(0) + , m_active(0) + , m_isDownloading(false) + , m_taskDlr(this) +{ + connect(&m_taskDlr, &TaskDownloader::completed, this, &QcfJob::taskFinished); + connect(&m_taskDlr, &TaskDownloader::taskError, this, &QcfJob::taskFailed); + connect(&m_taskDlr, + &TaskDownloader::downloadSpeedUpdated, + this, + &DownloadJob::downloadSpeedUpdated); +} + +void +QcfJob::enqueueTasks() +{ + for (int i = 1; i <= 604; i++) + m_queue.enqueue(QcfTask(i)); +} + +void +QcfJob::processTasks() +{ + if (m_queue.isEmpty()) { + m_isDownloading = false; + return; + } + + m_active = m_queue.dequeue(); + while (m_active.destination().exists()) { + m_completed++; + if (m_completed == 604) { + emit DownloadJob::progressed(); + emit DownloadJob::finished(); + } + + if (m_queue.isEmpty()) + return; + + m_active = m_queue.dequeue(); + } + + m_taskDlr.process(&m_active, &m_netMgr); +} + +void +QcfJob::taskFinished() +{ + m_completed++; + emit DownloadJob::progressed(); + if (m_completed == 604) + emit DownloadJob::finished(); + processTasks(); +} + +void +QcfJob::taskFailed() +{ + m_isDownloading = false; + m_queue.clear(); + emit DownloadJob::failed(); +} + +void +QcfJob::start() +{ + if (m_isDownloading) + return; + if (m_queue.isEmpty()) + enqueueTasks(); + m_isDownloading = true; + processTasks(); +} + +void +QcfJob::stop() +{ + if (!m_isDownloading) + return; + m_taskDlr.cancel(); + m_isDownloading = false; + m_queue.clear(); + emit DownloadJob::aborted(); +} + +bool +QcfJob::isDownloading() +{ + return m_isDownloading; +} + +int +QcfJob::completed() +{ + return m_completed; +} + +int +QcfJob::total() +{ + return 604; +} + +DownloadJob::Type +QcfJob::type() +{ + return DownloadJob::Qcf; +} + +QString +QcfJob::name() +{ + return qApp->translate("SettingsDialog", "QCF V2"); +} + +QcfJob::~QcfJob() {} diff --git a/src/downloader/qcfjob.h b/src/downloader/qcfjob.h new file mode 100644 index 00000000..579cb192 --- /dev/null +++ b/src/downloader/qcfjob.h @@ -0,0 +1,41 @@ +#ifndef QCFJOB_H +#define QCFJOB_H + +#include "taskdownloader.h" +#include +#include + +#include + +class QcfJob : public DownloadJob +{ + Q_OBJECT +public: + QcfJob(); + ~QcfJob(); + + void start() override; + void stop() override; + bool isDownloading() override; + int completed() override; + int total() override; + Type type() override; + QString name() override; + + void enqueueTasks(); + +private slots: + void processTasks(); + void taskFinished(); + void taskFailed(); + +private: + TaskDownloader m_taskDlr; + QQueue m_queue; + QNetworkAccessManager m_netMgr; + QcfTask m_active; + bool m_isDownloading; + int m_completed; +}; + +#endif // QCFJOB_H diff --git a/src/downloader/qcftask.cpp b/src/downloader/qcftask.cpp new file mode 100644 index 00000000..e6264be8 --- /dev/null +++ b/src/downloader/qcftask.cpp @@ -0,0 +1,38 @@ +#include "qcftask.h" + +QcfTask::QcfTask(int page) + : m_page(page) +{ +} + +QcfTask::QcfTask(const QcfTask &other) +{ + m_page = other.m_page; +} + +QcfTask::QcfTask(const QcfTask &&other) +{ + m_page = other.m_page; +} + +QcfTask &QcfTask::operator=(const QcfTask other) +{ + m_page = other.m_page; + return *this; +} + +QUrl +QcfTask::url() const +{ + return QUrl::fromEncoded( + ("https://github.com/0xzer0x/quran-companion/raw/main/extras/QCFV2/QCF2" + + QString::number(m_page).rightJustified(3, '0') + ".ttf") + .toLatin1()); +} + +QFileInfo +QcfTask::destination() const +{ + return QFileInfo(m_downloadsDir.absoluteFilePath( + "QCFV2/QCF2" + QString::number(m_page).rightJustified(3, '0') + ".ttf")); +} diff --git a/src/downloader/qcftask.h b/src/downloader/qcftask.h new file mode 100644 index 00000000..caa8c321 --- /dev/null +++ b/src/downloader/qcftask.h @@ -0,0 +1,24 @@ +#ifndef QCFTASK_H +#define QCFTASK_H + +#include +#include + +class QcfTask : public DownloadTask +{ +public: + QcfTask(int page); + QcfTask(const QcfTask& other); + QcfTask(const QcfTask&& other); + + QcfTask& operator=(const QcfTask other); + + QUrl url() const override; + QFileInfo destination() const override; + +private: + const QDir& m_downloadsDir = DirManager::getInstance().downloadsDir(); + int m_page; +}; + +#endif // QCFTASK_H diff --git a/src/downloader/recitationtask.cpp b/src/downloader/recitationtask.cpp new file mode 100644 index 00000000..57303060 --- /dev/null +++ b/src/downloader/recitationtask.cpp @@ -0,0 +1,66 @@ +#include "recitationtask.h" + +#include + +RecitationTask::RecitationTask() + : m_reciter(-1) + , m_surah(-1) + , m_verse(-1) +{ +} + +RecitationTask::RecitationTask(int reciter, int surah, int verse) + : m_reciter(reciter) + , m_surah(surah) + , m_verse(verse) +{ +} + +RecitationTask::RecitationTask(const RecitationTask& other) +{ + m_reciter = other.m_reciter; + m_surah = other.m_surah; + m_verse = other.m_verse; +} + +RecitationTask::RecitationTask(const RecitationTask&& other) +{ + m_reciter = other.m_reciter; + m_surah = other.m_surah; + m_verse = other.m_verse; +} + +RecitationTask& +RecitationTask::operator=(RecitationTask other) +{ + m_reciter = other.m_reciter; + m_surah = other.m_surah; + m_verse = other.m_verse; + return *this; +} + +QUrl +RecitationTask::url() const +{ + const Reciter& r = m_reciters.at(m_reciter); + QString url = r.baseUrl(); + if (r.useId()) + url.append(QString::number(Verse::id(m_surah, m_verse)) + ".mp3"); + else + url.append(QString::number(m_surah).rightJustified(3, '0') + + QString::number(m_verse).rightJustified(3, '0') + ".mp3"); + + return QUrl::fromEncoded(url.toLatin1()); +} + +QFileInfo +RecitationTask::destination() const +{ + static const QString path = "recitations/%0/%1.mp3"; + return QFileInfo(m_downloadsDir.absoluteFilePath( + path.arg(m_reciters.at(m_reciter).baseDirName(), + QString::number(m_surah).rightJustified(3, '0') + + QString::number(m_verse).rightJustified(3, '0')))); +} + +RecitationTask::~RecitationTask() {} diff --git a/src/downloader/recitationtask.h b/src/downloader/recitationtask.h new file mode 100644 index 00000000..b5961bac --- /dev/null +++ b/src/downloader/recitationtask.h @@ -0,0 +1,32 @@ +#ifndef RECITATIONTASK_H +#define RECITATIONTASK_H + +#include +#include +#include +#include + +class RecitationTask : public DownloadTask +{ +public: + RecitationTask(); + RecitationTask(int reciter, int surah, int verse); + RecitationTask(const RecitationTask& other); + RecitationTask(const RecitationTask&& other); + ~RecitationTask(); + + RecitationTask& operator=(RecitationTask other); + + QUrl url() const override; + QFileInfo destination() const override; + +private: + const QuranDb& m_quranDb = QuranDb::getInstance(); + const QDir& m_downloadsDir = DirManager::getInstance().downloadsDir(); + const QList& m_reciters = Reciter::reciters; + int m_reciter; + int m_surah; + int m_verse; +}; + +#endif // RECITATIONTASK_H diff --git a/src/downloader/surahjob.cpp b/src/downloader/surahjob.cpp new file mode 100644 index 00000000..d6cfa448 --- /dev/null +++ b/src/downloader/surahjob.cpp @@ -0,0 +1,139 @@ +#include "surahjob.h" +#include + +SurahJob::SurahJob(int reciter, int surah) + : m_reciter(reciter) + , m_surah(surah) + , m_completed(0) + , m_surahCount(Verse::surahVerseCount(surah)) + , m_isDownloading(false) + , m_taskDlr(this) + , m_quranDb(QuranDb::getInstance()) + , m_reciters(Reciter::reciters) +{ + connect( + &m_taskDlr, &TaskDownloader::completed, this, &SurahJob::taskFinished); + connect(&m_taskDlr, &TaskDownloader::taskError, this, &SurahJob::taskFailed); + connect(&m_taskDlr, + &TaskDownloader::downloadSpeedUpdated, + this, + &DownloadJob::downloadSpeedUpdated); +} + +void +SurahJob::enqueueTasks() +{ + for (int i = 1; i <= m_surahCount; i++) + m_queue.enqueue(RecitationTask(m_reciter, m_surah, i)); +} + +void +SurahJob::processTasks() +{ + if (m_queue.isEmpty()) { + m_isDownloading = false; + return; + } + + m_active = m_queue.dequeue(); + while (m_active.destination().exists()) { + m_completed++; + if (m_completed == m_surahCount) { + emit DownloadJob::progressed(); + emit DownloadJob::finished(); + m_isDownloading = false; + } + + if (m_queue.isEmpty()) + return; + + m_active = m_queue.dequeue(); + } + + m_taskDlr.process(&m_active, &m_netMgr); +} + +void +SurahJob::taskFinished() +{ + m_completed++; + emit DownloadJob::progressed(); + if (m_completed == m_surahCount) + emit DownloadJob::finished(); + processTasks(); +} + +void +SurahJob::start() +{ + if (m_isDownloading) + return; + if (m_queue.isEmpty()) + enqueueTasks(); + m_isDownloading = true; + processTasks(); +} + +void +SurahJob::stop() +{ + if (!m_isDownloading) + return; + m_taskDlr.cancel(); + m_isDownloading = false; + m_queue.clear(); + emit DownloadJob::aborted(); +} + +void +SurahJob::taskFailed() +{ + m_isDownloading = false; + m_queue.clear(); + emit DownloadJob::failed(); +} + +bool +SurahJob::isDownloading() +{ + return m_isDownloading; +} + +int +SurahJob::completed() +{ + return m_completed; +} + +int +SurahJob::total() +{ + return m_surahCount; +} + +DownloadJob::Type +SurahJob::type() +{ + return DownloadJob::Recitation; +} + +QString +SurahJob::name() +{ + return m_reciters.at(m_reciter).displayName() + " - " + + m_quranDb.surahNames().at(m_surah - 1); +} + +int +SurahJob::reciter() const +{ + return m_reciter; +} + +int +SurahJob::surah() const +{ + return m_surah; +} + +SurahJob::~SurahJob() {} diff --git a/src/downloader/surahjob.h b/src/downloader/surahjob.h new file mode 100644 index 00000000..f608a3d9 --- /dev/null +++ b/src/downloader/surahjob.h @@ -0,0 +1,48 @@ +#ifndef SURAHJOB_H +#define SURAHJOB_H + +#include "recitationtask.h" +#include "taskdownloader.h" +#include +#include +#include + +class SurahJob : public DownloadJob +{ + Q_OBJECT +public: + SurahJob(int reciter, int surah); + ~SurahJob(); + + void start() override; + void stop() override; + bool isDownloading() override; + int completed() override; + int total() override; + Type type() override; + QString name() override; + + void enqueueTasks(); + int reciter() const; + int surah() const; + +private slots: + void processTasks(); + void taskFinished(); + void taskFailed(); + +private: + const QuranDb& m_quranDb; + QList& m_reciters; + TaskDownloader m_taskDlr; + QQueue m_queue; + QNetworkAccessManager m_netMgr; + RecitationTask m_active; + bool m_isDownloading; + int m_reciter; + int m_surah; + int m_completed; + int m_surahCount; +}; + +#endif // SURAHJOB_H diff --git a/src/downloader/tafsirtask.cpp b/src/downloader/tafsirtask.cpp new file mode 100644 index 00000000..1f6c6499 --- /dev/null +++ b/src/downloader/tafsirtask.cpp @@ -0,0 +1,39 @@ +#include "tafsirtask.h" + +TafsirTask::TafsirTask(int idx) + : m_idx(idx) +{ +} + +TafsirTask::TafsirTask(const TafsirTask& other) +{ + m_idx = other.m_idx; +} + +TafsirTask::TafsirTask(const TafsirTask&& other) +{ + m_idx = other.m_idx; +} + +TafsirTask& +TafsirTask::operator=(const TafsirTask other) +{ + m_idx = other.m_idx; + return *this; +} + +QUrl +TafsirTask::url() const +{ + return QUrl::fromEncoded( + ("https://github.com/0xzer0x/quran-companion/raw/main/extras/tafasir/" + + m_tafasir.at(m_idx).filename()) + .toLatin1()); +} + +QFileInfo +TafsirTask::destination() const +{ + return QFileInfo(m_downloadsDir.absoluteFilePath( + "tafasir/" + m_tafasir.at(m_idx).filename())); +} diff --git a/src/downloader/tafsirtask.h b/src/downloader/tafsirtask.h new file mode 100644 index 00000000..7da71e8e --- /dev/null +++ b/src/downloader/tafsirtask.h @@ -0,0 +1,26 @@ +#ifndef TAFSIRTASK_H +#define TAFSIRTASK_H + +#include +#include +#include + +class TafsirTask : public DownloadTask +{ +public: + TafsirTask(int idx); + TafsirTask(const TafsirTask& other); + TafsirTask(const TafsirTask&& other); + + TafsirTask& operator=(const TafsirTask other); + + QUrl url() const override; + QFileInfo destination() const override; + +private: + const QDir& m_downloadsDir = DirManager::getInstance().downloadsDir(); + const QList& m_tafasir = Tafsir::tafasir; + int m_idx; +}; + +#endif // TAFSIRTASK_H diff --git a/src/downloader/taskdownloader.cpp b/src/downloader/taskdownloader.cpp new file mode 100644 index 00000000..1fe1356f --- /dev/null +++ b/src/downloader/taskdownloader.cpp @@ -0,0 +1,128 @@ +#include "taskdownloader.h" +#include + +TaskDownloader::TaskDownloader(QObject* parent) + : QObject(parent) + , m_reply(nullptr) + , m_task(nullptr) + , m_bytes(0) + , m_total(1) +{ +} + +void +TaskDownloader::process(DownloadTask* task, QNetworkAccessManager* manager) +{ + m_task = task; + if (m_reply) { + disconnect( + m_reply, &QNetworkReply::finished, this, &TaskDownloader::finish); + disconnect(m_reply, + &QNetworkReply::downloadProgress, + this, + &TaskDownloader::progressed); + disconnect(m_reply, + &QNetworkReply::downloadProgress, + this, + &TaskDownloader::taskProgress); + } + + QNetworkRequest req(m_task->url()); + m_reply = manager->get(req); + m_reply->ignoreSslErrors(); + m_startTime = QTime::currentTime(); + + connect(m_reply, &QNetworkReply::finished, this, &TaskDownloader::finish); + connect(m_reply, + &QNetworkReply::downloadProgress, + this, + &TaskDownloader::progressed); + connect(m_reply, + &QNetworkReply::downloadProgress, + this, + &TaskDownloader::taskProgress); +} + +void +TaskDownloader::cancel() +{ + m_reply->abort(); + m_reply->close(); +} + +void +TaskDownloader::taskProgress(qint64 bytes, qint64 total) +{ + m_bytes = bytes; + m_total = total; + int secs = m_startTime.secsTo(QTime::currentTime()); + if (secs < 1) + secs = 1; + + int speedPerSec = bytes / secs; + QString unit = qApp->translate("DownloadManager", "bytes"); + if (speedPerSec >= 1024) { + unit = qApp->translate("DownloadManager", "KB"); + speedPerSec /= 1024; + } + if (speedPerSec >= 1024) { + unit = qApp->translate("DownloadManager", "MB"); + speedPerSec /= 1024; + } + + emit downloadSpeedUpdated(speedPerSec, unit); +} + +void +TaskDownloader::finish() +{ + if (m_reply->error() != QNetworkReply::NoError) + return handleError(m_reply->error()); + m_bytes = m_total; + saveFile(); + emit completed(); +} + +void +TaskDownloader::handleError(QNetworkReply::NetworkError err) +{ + switch (err) { + case QNetworkReply::OperationCanceledError: + qInfo() << m_reply->errorString(); + break; + default: + qInfo() << m_reply->errorString(); + emit taskError(); + } +} + +bool +TaskDownloader::saveFile() +{ + QFile localFile(m_task->destination().absoluteFilePath()); + if (!localFile.open(QIODevice::WriteOnly)) { + qWarning() << "Couldn't open file:" << m_task->destination(); + return false; + } + + const QByteArray fdata = m_reply->readAll(); + m_reply->close(); + + localFile.write(fdata); + localFile.close(); + return true; +} + +int +TaskDownloader::bytes() const +{ + return m_bytes; +} + +int +TaskDownloader::total() const +{ + return m_total; +} + +TaskDownloader::~TaskDownloader() {} diff --git a/src/downloader/taskdownloader.h b/src/downloader/taskdownloader.h new file mode 100644 index 00000000..cb743f5b --- /dev/null +++ b/src/downloader/taskdownloader.h @@ -0,0 +1,42 @@ +#ifndef TASKDOWNLOADER_H +#define TASKDOWNLOADER_H + +#include +#include +#include +#include + +class TaskDownloader : public QObject +{ + Q_OBJECT +public: + explicit TaskDownloader(QObject* parent); + ~TaskDownloader(); + + void process(DownloadTask* task, QNetworkAccessManager* manager); + void cancel(); + + int bytes() const; + int total() const; + +signals: + void downloadSpeedUpdated(int speed, QString unit); + void progressed(qint64 bytes, qint64 total); + void taskError(); + void completed(); + +private slots: + void taskProgress(qint64 bytes, qint64 total); + void handleError(QNetworkReply::NetworkError err); + bool saveFile(); + void finish(); + +private: + QTime m_startTime; + DownloadTask* m_task; + QNetworkReply* m_reply; + int m_bytes; + int m_total; +}; + +#endif // TASKDOWNLOADER_H diff --git a/src/downloader/translationjob.cpp b/src/downloader/translationjob.cpp new file mode 100644 index 00000000..e231b680 --- /dev/null +++ b/src/downloader/translationjob.cpp @@ -0,0 +1,59 @@ +#include "translationjob.h" + +TranslationJob::TranslationJob(int idx) + : m_idx(idx) + , m_task(TranslationTask(idx)) + , m_taskDlr(new TaskDownloader(this)) +{ + connect(m_taskDlr, &TaskDownloader::completed, this, &DownloadJob::finished); + connect(m_taskDlr, &TaskDownloader::taskError, this, &DownloadJob::failed); + connect(m_taskDlr, + &TaskDownloader::downloadSpeedUpdated, + this, + &DownloadJob::downloadSpeedUpdated); +} + +void +TranslationJob::start() +{ + if (m_isDownloading) + return; + m_isDownloading = true; + m_taskDlr->process(&m_task, &m_netMgr); +} + +void +TranslationJob::stop() +{ + if (!m_isDownloading) + return; + m_taskDlr->cancel(); + m_isDownloading = false; +} + +bool +TranslationJob::isDownloading() +{ +} + +int +TranslationJob::completed() +{ +} + +int +TranslationJob::total() +{ +} + +DownloadJob::Type +TranslationJob::type() +{ +} + +QString +TranslationJob::name() +{ +} + +TranslationJob::~TranslationJob() {} diff --git a/src/downloader/translationjob.h b/src/downloader/translationjob.h new file mode 100644 index 00000000..befe0147 --- /dev/null +++ b/src/downloader/translationjob.h @@ -0,0 +1,33 @@ +#ifndef TRANSLATIONJOB_H +#define TRANSLATIONJOB_H + +#include "../types/translation.h" +#include "../types/translationtask.h" +#include "downloadjob.h" +#include "taskdownloader.h" + +class TranslationJob : public DownloadJob +{ +public: + TranslationJob(int idx); + ~TranslationJob(); + + void start() override; + void stop() override; + bool isDownloading() override; + int completed() override; + int total() override; + Type type() override; + QString name() override; + +private: + QList>& m_translations = + Translation::translations; + QPointer m_taskDlr; + QNetworkAccessManager m_netMgr; + TranslationTask m_task; + bool m_isDownloading; + int m_idx; +}; + +#endif // TRANSLATIONJOB_H diff --git a/src/downloader/translationtask.cpp b/src/downloader/translationtask.cpp new file mode 100644 index 00000000..4ca4fc35 --- /dev/null +++ b/src/downloader/translationtask.cpp @@ -0,0 +1,39 @@ +#include "translationtask.h" + +TranslationTask::TranslationTask(int idx) + : m_idx(idx) +{ +} + +TranslationTask::TranslationTask(const TranslationTask& other) +{ + m_idx = other.m_idx; +} + +TranslationTask::TranslationTask(const TranslationTask&& other) +{ + m_idx = other.m_idx; +} + +TranslationTask& +TranslationTask::operator=(const TranslationTask other) +{ + m_idx = other.m_idx; + return *this; +} + +QUrl +TranslationTask::url() const +{ + return QUrl::fromEncoded(("https://github.com/0xzer0x/quran-companion/raw/" + "main/extras/translations/" + + m_translations.at(m_idx).filename()) + .toLatin1()); +} + +QFileInfo +TranslationTask::destination() const +{ + return QFileInfo(m_downloadsDir.absoluteFilePath( + "translations/" + m_translations.at(m_idx).filename())); +} diff --git a/src/downloader/translationtask.h b/src/downloader/translationtask.h new file mode 100644 index 00000000..7e9f3dff --- /dev/null +++ b/src/downloader/translationtask.h @@ -0,0 +1,27 @@ +#ifndef TRANSLATIONTASK_H +#define TRANSLATIONTASK_H + +#include +#include +#include + +class TranslationTask : public DownloadTask +{ +public: + TranslationTask(int idx); + TranslationTask(const TranslationTask& other); + TranslationTask(const TranslationTask&& other); + + TranslationTask& operator=(const TranslationTask other); + + QUrl url() const override; + QFileInfo destination() const override; + +private: + const QDir& m_downloadsDir = DirManager::getInstance().downloadsDir(); + const QList& m_translations = + Translation::translations; + int m_idx; +}; + +#endif // TRANSLATIONTASK_H diff --git a/src/globals.cpp b/src/globals.cpp deleted file mode 100644 index ebce296c..00000000 --- a/src/globals.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @file globals.cpp - * @brief Instantiation of application-wide variables. - */ - -#include "globals.h" -#include - -namespace Globals { -// app settings -int themeId = 0; -ReaderMode readerMode = ReaderMode::SinglePage; -bool darkMode = false; -QSettings* settings = nullptr; -QObject* databaseManager = nullptr; -QLocale::Language language; - -// qcf fonts -int qcfVersion = 1; -QString qcfFontPrefix = "QCF_P"; - -// app directories -QDir themeResources; -QDir assetsDir; -QDir bismillahDir; -QDir downloadsDir = - QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); -QDir configDir = - QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation); - -// application specific -QList recitersList; -QList tafasirList; -QList translationsList; -QString updateToolPath; -QMap shortcutDescription; -fa::QtAwesome* awesome; - -QString -pageFontname(int page) -{ - return qcfFontPrefix + QString::number(page).rightJustified(3, '0'); -} - -QString -verseFontname(VerseType type, int page) -{ - QString fontname; - switch (type) { - case qcf: - fontname = pageFontname(page); - break; - case uthmanic: - fontname = "kfgqpc_hafs_uthmanic _script"; - break; - case annotated: - fontname = "Emine"; - break; - } - return fontname; -} - -bool -tafsirExists(const Tafsir* tafsir) -{ - const QDir& baseDir = tafsir->extra ? downloadsDir : assetsDir; - return baseDir.exists("tafasir/" + tafsir->filename); -} - -bool -translationExists(const Translation* tr) -{ - const QDir& baseDir = tr->extra ? downloadsDir : assetsDir; - return baseDir.exists("translations/" + tr->filename); -} -}; diff --git a/src/globals.h b/src/globals.h deleted file mode 100644 index 3b024d00..00000000 --- a/src/globals.h +++ /dev/null @@ -1,184 +0,0 @@ -/** - * @file globals.h - * @brief Header file defining application-wide variables. - */ - -#ifndef GLOBALS_H -#define GLOBALS_H - -#include -#include -#include -#include -#include -#include -#include - -/** - * @struct Verse - * @brief Verse struct represents a single quran verse - * @details Quran verses consist of 3 attributes. page (1-604). surah (1-114). - * number represents the number of the verse in the surah (0-surah verse count). - * Basmallah before the 1st verse is represented as verse number 0. - */ -struct Verse -{ - int page{ -1 }; ///< verse page - int surah{ -1 }; ///< verse surah number - int number{ -1 }; ///< verse number in surah - bool operator==(const Verse& v2) const - { - return (this->number == v2.number && this->surah == v2.surah); - } - bool operator!=(const Verse& v2) const - { - return (this->number != v2.number || this->surah != v2.surah); - } - bool operator<(const Verse& v2) const - { - if (this->surah == v2.surah) - return this->number < v2.number; - - return this->surah < v2.surah; - } - bool operator>(const Verse& v2) const - { - if (this->surah == v2.surah) - return this->number > v2.number; - - return this->surah > v2.surah; - } -}; -/** - * @struct Reciter - * @brief Reciter struct represents a quran reciter - */ -struct Reciter -{ - QString baseDirName{}; ///< The name of the directory conatining recitations - ///< in the application recitations directory. - QString displayName{}; ///< The reciter name as its displayed in the UI. - QString - basmallahPath{}; ///< Absolute path to the reciters bismillah audio file. - QString baseUrl{}; ///< Url to download recitation files from. - bool useId{ - false - }; ///< Boolean value representing whether the verse recitations should be - ///< downloading using the verse number relative to the beginning of the - ///< Quran or a combination of surah and verse numbers. -}; -/** - * @brief Tafsir struct contains data about a single tafsir - * @details tafasir are stored in the resource file "files.xml" - */ -struct Tafsir -{ - QString displayName; - QString filename; - bool text; - bool extra; -}; -/** - * @brief Translation struct holds different values representing available Quran - * translations - */ -struct Translation -{ - QString displayName; - QString filename; - bool extra; -}; - -enum DownloadType -{ - QCF, - Recitation, - File -}; - -enum VerseType -{ - qcf, - uthmanic, - annotated -}; - -/** - * @brief ReaderMode enum represents the available modes for the Quran reader in - * MainWindow - */ -enum ReaderMode -{ - SinglePage, ///< Single Quran page, side panel is used for displaying verses - ///< with translation - DoublePage ///< Two Quran pages, both panels are used to display Quran pages, - ///< no translation -}; - -namespace Globals { -extern int - themeId; ///< global variable represnting the application theme index. - -extern QSettings* - settings; ///< global pointer to the application QSettings instance. - -extern bool - darkMode; ///< global boolean to indicate if application is in dark mode. - -extern QLocale::Language - language; ///< global QLocale::Language instance for application languge. - -extern QString - updateToolPath; ///< global absolute path for the application update tool. - -extern int qcfVersion; ///< global variable for the QCF version in use. - -extern ReaderMode readerMode; - -extern QString qcfFontPrefix; ///< global variable for the QCF font prefix to - ///< generate font name from. - -extern QDir themeResources; ///< global QDir for the current theme resources - ///< (icons & styles). -extern QDir - configDir; ///< global QDir representing application config directiory. - -extern QDir assetsDir; ///< global QDir representing the application assets - ///< directory (fonts, translations, tafsir). - -extern QDir - bismillahDir; ///< global QDir representing the reciters basmallah files. - -extern QDir downloadsDir; ///< global QDir representing the top-level path - ///< for downloaded files. - -extern QList recitersList; ///< global QList containing reciters - ///< supported by the application. - -extern QList tafasirList; - -extern QList translationsList; - -extern QMap - shortcutDescription; ///< global QMap containing all available - ///< application shortcuts as keys and their descriptions - ///< as values. - -extern QObject* databaseManager; ///< global pointer to the application-wide - ///< DBManager instance for interacting with - ///< different database files. - -extern fa::QtAwesome* - awesome; ///< global pointer used for generating font awesome icons - -extern QString -pageFontname(int page); -extern QString -verseFontname(VerseType type, int page); -extern bool -tafsirExists(const Tafsir* tafsir); -extern bool -translationExists(const Translation* tr); -}; - -#endif // GLOBALS_H diff --git a/src/interfaces/dbconnection.h b/src/interfaces/dbconnection.h new file mode 100644 index 00000000..84258ac3 --- /dev/null +++ b/src/interfaces/dbconnection.h @@ -0,0 +1,28 @@ +#ifndef DBCONNECTION_H +#define DBCONNECTION_H + +#include +#include + +class DbConnection : public QObject +{ +public: + /** + * @brief Type enum holds different values representing database files + * used in different member functions. + */ + enum Type + { + Quran, ///< (quran.db) main Quran database file + Glyphs, ///< (glyphs.db) QCF glyphs database + Betaqat, + Bookmarks, ///< (bookmarks.db) bookmarked verses and khatmah database + Tafsir, ///< currently selected tafsir database file + Translation ///< currently selected translation database file + }; + + virtual void open() = 0; + virtual Type type() = 0; +}; + +#endif // DBCONNECTION_H diff --git a/src/interfaces/downloadjob.h b/src/interfaces/downloadjob.h new file mode 100644 index 00000000..e095513b --- /dev/null +++ b/src/interfaces/downloadjob.h @@ -0,0 +1,34 @@ +#ifndef DOWNLOADJOB_H +#define DOWNLOADJOB_H + +#include + +class DownloadJob : public QObject +{ + Q_OBJECT +public: + enum Type + { + TafsirFile, + TranslationFile, + Qcf, + Recitation + }; + virtual void start() = 0; + virtual void stop() = 0; + virtual bool isDownloading() = 0; + virtual int completed() = 0; + virtual int total() = 0; + virtual Type type() = 0; + virtual QString name() = 0; + virtual ~DownloadJob(){}; + +signals: + void aborted(); + void finished(); + void progressed(); + void failed(); + void downloadSpeedUpdated(int speed, QString unit); +}; + +#endif // DOWNLOADJOB_H diff --git a/src/interfaces/downloadtask.h b/src/interfaces/downloadtask.h new file mode 100644 index 00000000..af12c3c3 --- /dev/null +++ b/src/interfaces/downloadtask.h @@ -0,0 +1,17 @@ +#ifndef DOWNLOADTASK_H +#define DOWNLOADTASK_H + +#include +#include +#include +#include + +class DownloadTask +{ +public: + virtual QUrl url() const = 0; + virtual QFileInfo destination() const = 0; + virtual ~DownloadTask(){}; +}; + +#endif // DOWNLOADTASK_H diff --git a/src/interfaces/notificationsender.h b/src/interfaces/notificationsender.h new file mode 100644 index 00000000..8f631ee8 --- /dev/null +++ b/src/interfaces/notificationsender.h @@ -0,0 +1,27 @@ +#ifndef NOTIFICATIONSENDER_H +#define NOTIFICATIONSENDER_H + +#include + +class NotificationSender : public QObject +{ + Q_OBJECT +public: + /** + * @brief The Type enum represents all possible action to notify the user of + */ + enum Type + { + info, ///< general information + success, ///< successful operation + fail, ///< failed operation + bookmarkAdd, ///< bookmark addition + bookmarkRemove, ///< bookmark removal + copiedText, ///< verse text copied + updateInfo ///< version information + }; +signals: + void notify(Type type, QString msg); +}; + +#endif // NOTIFICATIONSENDER_H diff --git a/src/interfaces/userdataexporter.h b/src/interfaces/userdataexporter.h new file mode 100644 index 00000000..509aafa6 --- /dev/null +++ b/src/interfaces/userdataexporter.h @@ -0,0 +1,25 @@ +#ifndef USERDATAEXPORTER_H +#define USERDATAEXPORTER_H + +#include +#include + +class UserDataExporter : public QObject +{ + Q_OBJECT +public: + enum Error + { + IOError + }; + virtual void setFile(QString path) = 0; + virtual void exportBookmarks() = 0; + virtual void exportKhatmah() = 0; + virtual void exportThoughts() = 0; + virtual bool save() = 0; + +signals: + void error(Error err, QString msg); +}; + +#endif // USERDATAEXPORTER_H diff --git a/src/interfaces/userdataimporter.h b/src/interfaces/userdataimporter.h new file mode 100644 index 00000000..8d95c623 --- /dev/null +++ b/src/interfaces/userdataimporter.h @@ -0,0 +1,28 @@ +#ifndef USERDATAIMPORTER_H +#define USERDATAIMPORTER_H + +#include +#include + +class UserDataImporter : public QObject +{ + Q_OBJECT +public: + enum Error + { + IOError, + ParseError, + MissingKeyError, + InvalidValueError + }; + virtual void importBookmarks() = 0; + virtual void importKhatmah() = 0; + virtual void importThoughts() = 0; + virtual void setFile(QString path) = 0; + virtual bool fileContains(QString key) = 0; + virtual bool read() = 0; +signals: + void error(Error err, QString msg); +}; + +#endif // USERDATAIMPORTER_H diff --git a/src/main.cpp b/src/main.cpp index 30d2a1cd..e5f82773 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,107 +3,18 @@ * @brief Application entry point. */ -#include "core/mainwindow.h" -#include "globals.h" -#include "utils/logger.h" #include -#include -#include -#include -#include #include -#include -#include -#include -#include -#include -using namespace Globals; - -/*! - * @brief sets the theme for the application. - * @details Application theme consists of a QPalette for the application and a - * custom stylesheet for different UI components. works through passing the - * theme index (0 = light, 1 = sepia , 2 = dark). - * - * @param themeIdx - index for theme as it appears in the application settings. - */ -void -setTheme(int themeIdx); -/** - * @brief loads the QCF fonts for Quran pages before starting the - * GUI. - * @details The application depends mainly on QCF fonts to display Quranic - * verses and pages, the QCF fonts are a set of fonts designed specifically for - * Madani Mushaf printing. As each page font includes unicode glyphs for each - * word in that page. The fonts are loaded at application startup by loading 604 - * truetype files (ttf). The available QCF versions are 1 and 2. - * - * @param qcfVersion - font version to use. - */ -void -addFonts(int qcfVersion); -/** - * @brief loads UI translation. - * @details Application UI translation is done through compiled Qt translation - * files (.qm). addTranslation() loads the correct qm files for the language - * given. The loaded translation files consist of a Qt base translation and a - * custom application translation file. - * - * @param localeCode - instance from QLocale::Language that defines a - * specific language - */ -void -addTranslation(QLocale::Language localeCode); -/** - * @brief checks the default settings groups and sets them with default - * values if not found. - * @details application settings are stored as ini-formatted files in order to - * provide the same functionality on different platforms. The configuration file - * is stored in Globals::configDir - * directory. - * - * @param settings - pointer to QSettings instance to access app settings - */ -void -checkSettings(QSettings* settings); -/** - * @brief set the default values for non-existing keys in a certain settings - * group (0 - general, 1 - Reader). - * @param settings - pointer to QSettings instance to access app settings - * @param group - integer refering to group to check - */ -void -checkSettingsGroup(QSettings* settings, int group); -/** - * @brief fills the global reciters list and creates their corresponding - * directories. - * @details Fills the QList that contains ::Reciter instances - * that represent the reciters supported by the application. The reciters list - * is used in many parts of the application by creating a constant reference to - * the global QList. It also creates the reciters directories in the application - * recitations directory as needed. - */ -void -populateRecitersList(); -/** - * @brief populates the global tafasir and translation lists with data from - * files.xml - */ -void -populateContentLists(); -/** - * @brief populates the Globals::shortcutDescription QMap with key-value pairs - * of (name - description) and set the default value if shortcut is not found in - * settings, any new shortcuts should be added to shortcuts.xml along with the - * default keybinding for it. - */ -void -populateShortcutsMap(); -/** - * @brief set application-wide variables that represents used paths. - */ -void -setGlobalPaths(); +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include /** * @brief application entry point @@ -117,31 +28,23 @@ main(int argc, char* argv[]) QApplication a(argc, argv); QApplication::setApplicationName("Quran Companion"); QApplication::setOrganizationName("0xzer0x"); - QApplication::setApplicationVersion("1.2.3"); + QApplication::setApplicationVersion("1.2.4"); QSplashScreen splash(QPixmap(":/resources/splash.png")); splash.show(); - setGlobalPaths(); - Logger::startLogger(configDir.absolutePath()); + Logger::startLogger(DirManager::getInstance().configDir().absolutePath()); Logger::attach(); - settings = new QSettings( - configDir.filePath("qurancompanion.conf"), QSettings::IniFormat, &a); - checkSettings(settings); + Configuration::getInstance().loadUiTranslation(); + ShortcutHandler::getInstance().populateDescriptionMap(); + StyleManager::getInstance().loadTheme(); + FontManager::getInstance().loadFonts(); - themeId = settings->value("Theme").toInt(); - qcfVersion = settings->value("Reader/QCF").toInt(); - language = qvariant_cast(settings->value("Language")); - readerMode = qvariant_cast(settings->value("Reader/Mode")); - setTheme(themeId); - addFonts(qcfVersion); - addTranslation(language); - populateRecitersList(); - populateShortcutsMap(); - populateContentLists(); + Tafsir::populateTafasir(); + Translation::populateTranslations(); + Reciter::populateReciters(); - databaseManager = new DBManager(&a); MainWindow w(nullptr); splash.finish(&w); w.show(); @@ -150,314 +53,3 @@ main(int argc, char* argv[]) Logger::stopLogger(); return exitcode; } - -void -setGlobalPaths() -{ - // data - assetsDir = QApplication::applicationDirPath() + QDir::separator() + "assets"; - bismillahDir = - QApplication::applicationDirPath() + QDir::separator() + "bismillah"; - - // config & downloads - if (!configDir.exists("QuranCompanion")) - configDir.mkpath("QuranCompanion"); - configDir.cd("QuranCompanion"); - - if (!downloadsDir.exists("QuranCompanion")) - downloadsDir.mkpath("QuranCompanion"); - downloadsDir.cd("QuranCompanion"); - - if (!downloadsDir.exists("recitations")) - downloadsDir.mkpath("recitations"); - if (!downloadsDir.exists("QCFV2")) - downloadsDir.mkpath("QCFV2"); - if (!downloadsDir.exists("tafasir")) - downloadsDir.mkpath("tafasir"); - if (!downloadsDir.exists("translations")) - downloadsDir.mkpath("translations"); - -#ifdef Q_OS_WIN - updateToolPath = QApplication::applicationDirPath() + QDir::separator() + - "QCMaintenanceTool.exe"; -#endif -} - -void -checkSettings(QSettings* settings) -{ - for (int i = 0; i < 2; i++) - checkSettingsGroup(settings, i); -} - -void -checkSettingsGroup(QSettings* settings, int group) -{ - switch (group) { - case 0: - settings->setValue("Language", - settings->value("Language", (int)QLocale::English)); - settings->setValue("Theme", settings->value("Theme", 0)); - settings->setValue("VOTD", settings->value("VOTD", true)); - settings->setValue("MissingFileWarning", - settings->value("MissingFileWarning", true)); - break; - case 1: - settings->beginGroup("Reader"); - settings->setValue("Mode", settings->value("Mode", 0)); - settings->setValue("FGHighlight", settings->value("FGHighlight", 1)); - settings->setValue("Khatmah", settings->value("Khatmah", 0)); - settings->setValue("AdaptiveFont", settings->value("AdaptiveFont", true)); - settings->setValue("QCF1Size", settings->value("QCF1Size", 22)); - settings->setValue("QCF2Size", settings->value("QCF2Size", 20)); - settings->setValue("QCF", settings->value("QCF", 1)); - settings->setValue("VerseType", settings->value("VerseType", 0)); - settings->setValue("VerseFontSize", settings->value("VerseFontSize", 20)); - settings->setValue("Tafsir", settings->value("Tafsir", 6)); - settings->setValue("Translation", settings->value("Translation", 5)); - settings->setValue( - "SideContentFont", - settings->value("SideContentFont", QFont("Expo Arabic", 14))); - settings->endGroup(); - break; - } -} - -void -addFonts(int qcfVersion) -{ - QDir fontsDir; - fontsDir = QApplication::applicationDirPath() + QDir::separator() + "assets" + - QDir::separator() + "fonts"; - - // ui fonts - foreach (const QFileInfo& font, fontsDir.entryInfoList(QDir::Files)) - QFontDatabase::addApplicationFont(font.absoluteFilePath()); - - // font for surah frames - QFontDatabase::addApplicationFont(fontsDir.filePath("QCFV1/QCF_BSML.ttf")); - switch (qcfVersion) { - case 1: - fontsDir.cd("QCFV1"); - break; - case 2: - fontsDir.setPath(downloadsDir.absolutePath() + "/QCFV2"); - qcfFontPrefix = "QCF2"; - break; - } - - // add required fonts - for (int i = 1; i < 605; i++) { - QString fontName = pageFontname(i) + ".ttf"; - - if (qcfVersion == 2 && !fontsDir.exists(fontName)) { - settings->setValue("Reader/QCF", 1); - settings->sync(); - qFatal() << fontsDir.filePath(fontName) - << " font file not found, fallback to QCF v1"; - } else - QFontDatabase::addApplicationFont(fontsDir.filePath(fontName)); - } - - // set default UI fonts to use - QStringList uiFonts; - uiFonts << "Noto Sans Display" - << "Expo Arabic"; - qApp->setFont(QFont(uiFonts, qApp->font().pointSize())); -} - -void -setTheme(int themeIdx) -{ - qApp->setStyle(QStyleFactory::create("Fusion")); - - QPalette themeColors; - QFile styles, palette; - switch (themeIdx) { - case 0: - themeResources.setPath(":/resources/light/"); - styles.setFileName(themeResources.filePath("light.qss")); - palette.setFileName(themeResources.filePath("light.xml")); - darkMode = false; - break; - - case 1: - themeResources.setPath(":/resources/light/"); - styles.setFileName(themeResources.filePath("sepia.qss")); - palette.setFileName(themeResources.filePath("sepia.xml")); - darkMode = false; - break; - - case 2: - themeResources.setPath(":/resources/dark/"); - styles.setFileName(themeResources.filePath("dark.qss")); - palette.setFileName(themeResources.filePath("dark.xml")); - darkMode = true; - break; - } - - if (!palette.open(QIODevice::ReadOnly | QIODevice::Text)) { - qCritical() << "Couldn't Read Color palette xml"; - } - - QXmlStreamReader paletteReader(&palette); - QPalette::ColorGroup group = QPalette::All; - while (!paletteReader.atEnd() && !paletteReader.hasError()) { - QXmlStreamReader::TokenType token = paletteReader.readNext(); - if (token == QXmlStreamReader::StartElement) { - if (paletteReader.name().toString() == "enabled") - group = QPalette::All; - else if (paletteReader.name().toString() == "disabled") - group = QPalette::Disabled; - // color element - else { - QPalette::ColorRole role; - int red, green, blue; - role = static_cast( - paletteReader.attributes().value("role").toInt()); - red = paletteReader.attributes().value("red").toInt(); - green = paletteReader.attributes().value("green").toInt(); - blue = paletteReader.attributes().value("blue").toInt(); - - themeColors.setColor(group, role, QColor(red, green, blue)); - } - } - } - - palette.close(); - qApp->setPalette(themeColors); - - // load stylesheet - if (styles.open(QIODevice::ReadOnly)) { - qApp->setStyleSheet(styles.readAll()); - styles.close(); - } - - awesome = new fa::QtAwesome(qApp); - awesome->initFontAwesome(); -} - -void -addTranslation(QLocale::Language localeCode) -{ - if (localeCode == QLocale::English) - return; - - QString code = QLocale::languageToCode(localeCode); - QTranslator *translation = new QTranslator(qApp), - *qtBase = new QTranslator(qApp); - - if (translation->load(":/i18n/qc_" + code + ".qm")) { - qInfo() << translation->language() << "translation loaded"; - qInfo() << "base translation:" << qtBase->load(":/base/" + code + ".qm"); - qApp->installTranslator(translation); - qApp->installTranslator(qtBase); - - } else { - qWarning() << code + " translation not loaded!"; - delete translation; - delete qtBase; - } -} - -void -populateRecitersList() -{ - QFile reciters(":/resources/reciters.xml"); - if (!reciters.open(QIODevice::ReadOnly)) - qFatal("Couldn't Open Reciters XML, Exiting"); - - QXmlStreamReader reader(&reciters); - while (!reader.atEnd() && !reader.hasError()) { - QXmlStreamReader::TokenType token = reader.readNext(); - if (token == QXmlStreamReader::StartElement) { - if (reader.name().toString() == "reciter") { - Reciter reciter; - reciter.baseDirName = reader.attributes().value("dirname").toString(); - reciter.displayName = qApp->translate( - "MainWindow", reader.attributes().value("display").toLatin1()); - reciter.baseUrl = reader.attributes().value("url").toString(); - reciter.basmallahPath = bismillahDir.absoluteFilePath( - reader.attributes().value("basmallah").toString()); - reciter.useId = reader.attributes().value("useid").toInt(); - - recitersList.append(reciter); - } - } - } - - reciters.close(); - recitersList.squeeze(); - - // create reciters directories - downloadsDir.cd("recitations"); - foreach (const Reciter& r, recitersList) { - if (!downloadsDir.exists(r.baseDirName)) - downloadsDir.mkdir(r.baseDirName); - } - downloadsDir.cdUp(); -} - -void -populateShortcutsMap() -{ - QFile shortcuts(":/resources/shortcuts.xml"); - if (!shortcuts.open(QIODevice::ReadOnly)) - qCritical("Couldn't Open Shortcuts XML"); - - settings->beginGroup("Shortcuts"); - QXmlStreamReader reader(&shortcuts); - while (!reader.atEnd() && !reader.hasError()) { - QXmlStreamReader::TokenType token = reader.readNext(); - if (token == QXmlStreamReader::StartElement) { - if (reader.name().toString() == "shortcut") { - QString key = reader.attributes().value("key").toString(); - QString defBind = reader.attributes().value("default").toString(); - QString desc = - qApp->translate("SettingsDialog", - reader.attributes().value("description").toLatin1()); - - shortcutDescription.insert(key, desc); - if (!settings->contains(key)) - settings->setValue(key, defBind); - } - } - } - - settings->endGroup(); - shortcuts.close(); -} - -void -populateContentLists() -{ - QFile content(":/resources/files.xml"); - if (!content.open(QIODevice::ReadOnly)) - qCritical("Couldn't Open Files XML"); - - QXmlStreamReader reader(&content); - while (!reader.atEnd() && !reader.hasError()) { - QXmlStreamReader::TokenType token = reader.readNext(); - if (token == QXmlStreamReader::StartElement) { - if (reader.name().toString() == "tafsir") { - QString name = qApp->translate( - "SettingsDialog", reader.attributes().value("name").toLatin1()); - QString file = reader.attributes().value("file").toString(); - bool text = reader.attributes().value("text").toInt(); - bool extra = reader.attributes().value("extra").toInt(); - tafasirList.append(Tafsir{ name, file, text, extra }); - } - // translations - else if (reader.name().toString() == "translation") { - QString name = reader.attributes().value("name").toString(); - QString file = reader.attributes().value("file").toString(); - bool extra = reader.attributes().value("extra").toInt(); - translationsList.append(Translation{ name, file, extra }); - } - } - } - - content.close(); - tafasirList.squeeze(); - translationsList.squeeze(); -} diff --git a/src/notifiers/bookmarksnotifier.cpp b/src/notifiers/bookmarksnotifier.cpp new file mode 100644 index 00000000..25840399 --- /dev/null +++ b/src/notifiers/bookmarksnotifier.cpp @@ -0,0 +1,23 @@ +#include "bookmarksnotifier.h" +#include + +BookmarksNotifier::BookmarksNotifier(QObject* parent) +{ + setParent(parent); +} + +void +BookmarksNotifier::notifyAdded() +{ + QString msg = + qApp->translate("BookmarksNotifier", "Verse added to bookmarks"); + emit notify(bookmarkAdd, msg); +} + +void +BookmarksNotifier::notifyRemoved() +{ + QString msg = + qApp->translate("BookmarksNotifier", "Verse removed from bookmarks"); + emit notify(bookmarkRemove, msg); +} diff --git a/src/notifiers/bookmarksnotifier.h b/src/notifiers/bookmarksnotifier.h new file mode 100644 index 00000000..d6b1ae95 --- /dev/null +++ b/src/notifiers/bookmarksnotifier.h @@ -0,0 +1,14 @@ +#ifndef BOOKMARKSNOTIFIER_H +#define BOOKMARKSNOTIFIER_H + +#include + +class BookmarksNotifier : public NotificationSender +{ +public: + BookmarksNotifier(QObject* parent); + void notifyAdded(); + void notifyRemoved(); +}; + +#endif // BOOKMARKSNOTIFIER_H diff --git a/src/notifiers/copynotifier.cpp b/src/notifiers/copynotifier.cpp new file mode 100644 index 00000000..e253ee0a --- /dev/null +++ b/src/notifiers/copynotifier.cpp @@ -0,0 +1,15 @@ +#include "copynotifier.h" +#include + +CopyNotifier::CopyNotifier(QObject* parent) +{ + setParent(parent); +} + +void +CopyNotifier::copied() +{ + QString msg = + qApp->translate("CopyNotifier", "Verse text copied to clipboard"); + emit notify(copiedText, msg); +} diff --git a/src/notifiers/copynotifier.h b/src/notifiers/copynotifier.h new file mode 100644 index 00000000..fca6191b --- /dev/null +++ b/src/notifiers/copynotifier.h @@ -0,0 +1,13 @@ +#ifndef COPYNOTIFIER_H +#define COPYNOTIFIER_H + +#include + +class CopyNotifier : public NotificationSender +{ +public: + CopyNotifier(QObject* parent); + void copied(); +}; + +#endif // COPYNOTIFIER_H diff --git a/src/notifiers/jobnotifier.cpp b/src/notifiers/jobnotifier.cpp new file mode 100644 index 00000000..7c0cfa4d --- /dev/null +++ b/src/notifiers/jobnotifier.cpp @@ -0,0 +1,23 @@ +#include "jobnotifier.h" +#include + +JobNotifier::JobNotifier(QObject* parent) +{ + setParent(parent); +} + +void +JobNotifier::notifyCompleted(QPointer job) +{ + QString msg = + qApp->translate("JobNotifier", "Download Completed") + ": " + job->name(); + emit notify(success, msg); +} + +void +JobNotifier::notifyFailed(QPointer job) +{ + QString msg = + qApp->translate("JobNotifier", "Download Failed") + ": " + job->name(); + emit notify(fail, msg); +} diff --git a/src/notifiers/jobnotifier.h b/src/notifiers/jobnotifier.h new file mode 100644 index 00000000..5088891c --- /dev/null +++ b/src/notifiers/jobnotifier.h @@ -0,0 +1,16 @@ +#ifndef JOBNOTIFIER_H +#define JOBNOTIFIER_H + +#include +#include +#include + +class JobNotifier : public NotificationSender +{ +public: + JobNotifier(QObject* parent); + void notifyCompleted(QPointer job); + void notifyFailed(QPointer job); +}; + +#endif // JOBNOTIFIER_H diff --git a/src/notifiers/updatenotifier.cpp b/src/notifiers/updatenotifier.cpp new file mode 100644 index 00000000..bd94c5ee --- /dev/null +++ b/src/notifiers/updatenotifier.cpp @@ -0,0 +1,20 @@ +#include "updatenotifier.h" +#include + +UpdateNotifier::UpdateNotifier(QObject* parent) {} + +void +UpdateNotifier::notifyUpdate(QString version) +{ + QString msg = + qApp->translate("UpdateNotifier", "Update available") + ": " + version; + emit notify(updateInfo, msg); +} + +void +UpdateNotifier::notifyLatest() +{ + QString msg = + qApp->translate("UpdateNotifier", "You are running the latest version"); + emit notify(success, msg); +} diff --git a/src/notifiers/updatenotifier.h b/src/notifiers/updatenotifier.h new file mode 100644 index 00000000..5c6c92fe --- /dev/null +++ b/src/notifiers/updatenotifier.h @@ -0,0 +1,14 @@ +#ifndef UPDATENOTIFIER_H +#define UPDATENOTIFIER_H + +#include + +class UpdateNotifier : public NotificationSender +{ +public: + UpdateNotifier(QObject* parent); + void notifyUpdate(QString version); + void notifyLatest(); +}; + +#endif // UPDATENOTIFIER_H diff --git a/src/types/content.cpp b/src/types/content.cpp new file mode 100644 index 00000000..3067780c --- /dev/null +++ b/src/types/content.cpp @@ -0,0 +1,26 @@ +#include "content.h" + +Content::Content(QString display, QString filename, bool isExtra) + : m_displayName(display) + , m_filename(filename) + , m_isExtra(isExtra) +{ +} + +const QString& +Content::displayName() const +{ + return m_displayName; +} + +const QString& +Content::filename() const +{ + return m_filename; +} + +const bool& +Content::isExtra() const +{ + return m_isExtra; +} diff --git a/src/types/content.h b/src/types/content.h new file mode 100644 index 00000000..b8df8a11 --- /dev/null +++ b/src/types/content.h @@ -0,0 +1,21 @@ +#ifndef CONTENT_H +#define CONTENT_H + +#include + +class Content +{ +public: + explicit Content(QString display, QString filename, bool isExtra); + const QString& displayName() const; + const QString& filename() const; + const bool& isExtra() const; + virtual bool isAvailable() const = 0; + +private: + QString m_displayName; + QString m_filename; + bool m_isExtra; +}; + +#endif // CONTENT_H diff --git a/src/types/reciter.cpp b/src/types/reciter.cpp new file mode 100644 index 00000000..f282ec84 --- /dev/null +++ b/src/types/reciter.cpp @@ -0,0 +1,89 @@ +#include "reciter.h" +#include +#include +#include +#include + +QList Reciter::reciters; + +void +Reciter::populateReciters() +{ + QFile recitersFile(":/resources/reciters.xml"); + if (!recitersFile.open(QIODevice::ReadOnly)) + qFatal("Couldn't Open Reciters XML, Exiting"); + + QXmlStreamReader reader(&recitersFile); + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name().toString() == "reciter") { + QString baseDirName = reader.attributes().value("dirname").toString(); + QString displayName = qApp->translate( + "MainWindow", reader.attributes().value("display").toLatin1()); + QString baseUrl = reader.attributes().value("url").toString(); + QString basmallahPath = + DirManager::getInstance().basmallahDir().absoluteFilePath( + reader.attributes().value("basmallah").toString()); + bool useId = reader.attributes().value("useid").toInt(); + + reciters.append( + Reciter(baseDirName, displayName, basmallahPath, baseUrl, useId)); + } + } + } + + recitersFile.close(); + reciters.squeeze(); + + // create reciters directories + QString temp = "recitations/%0"; + foreach (const Reciter& r, reciters) { + QString path = temp.arg(r.baseDirName()); + if (!DirManager::getInstance().downloadsDir().exists(path)) + DirManager::getInstance().downloadsDir().mkdir(path); + } +} + +Reciter::Reciter(QString dir, + QString display, + QString basmallah, + QString url, + bool useId) + : m_baseDirName(dir) + , m_displayName(display) + , m_basmallahPath(basmallah) + , m_baseUrl(url) + , m_useId(useId) +{ +} + +QString +Reciter::baseUrl() const +{ + return m_baseUrl; +} + +QString +Reciter::basmallahPath() const +{ + return m_basmallahPath; +} + +QString +Reciter::displayName() const +{ + return m_displayName; +} + +QString +Reciter::baseDirName() const +{ + return m_baseDirName; +} + +bool +Reciter::useId() const +{ + return m_useId; +} diff --git a/src/types/reciter.h b/src/types/reciter.h new file mode 100644 index 00000000..b3c30bfd --- /dev/null +++ b/src/types/reciter.h @@ -0,0 +1,56 @@ +#ifndef RECITER_H +#define RECITER_H + +#include +#include +#include + +/** + * @class Reciter + * @brief Reciter class represents a quran reciter + */ +class Reciter +{ +public: + static QList reciters; + static void populateReciters(); + + explicit Reciter(QString dir, + QString display, + QString basmallah, + QString url, + bool useId = false); + + QString basmallahPath() const; + QString displayName() const; + QString baseDirName() const; + QString baseUrl() const; + bool useId() const; + +private: + /** + * @brief The name of the directory conatining recitations in the application + * recitations directory. + */ + QString m_baseDirName; + /** + * @brief The reciter name as its displayed in the UI. + */ + QString m_displayName; + /** + * @brief Absolute path to the reciters bismillah audio file. + */ + QString m_basmallahPath; + /** + * @brief Url to download recitation files from. + */ + QString m_baseUrl; + /** + * @brief Boolean value representing whether the verse recitations should be + * downloading using the verse number relative to the beginning of the Quran + * or a combination of surah and verse numbers. + */ + bool m_useId; +}; + +#endif // RECITER_H diff --git a/src/types/tafsir.cpp b/src/types/tafsir.cpp new file mode 100644 index 00000000..e6f62023 --- /dev/null +++ b/src/types/tafsir.cpp @@ -0,0 +1,67 @@ +#include "tafsir.h" +#include "content.h" +#include +#include +#include +#include +#include + +QList Tafsir::tafasir; + +void +Tafsir::populateTafasir() +{ + QFile content(":/resources/files.xml"); + if (!content.open(QIODevice::ReadOnly)) + qCritical("Couldn't Open Files XML"); + + QXmlStreamReader reader(&content); + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name().toString() == "tafsir") { + QString name = qApp->translate( + "SettingsDialog", reader.attributes().value("name").toLatin1()); + QString file = reader.attributes().value("file").toString(); + bool isText = reader.attributes().value("text").toInt(); + bool isExtra = reader.attributes().value("extra").toInt(); + tafasir.append(Tafsir(name, file, isText, isExtra)); + } + } + } + + content.close(); + tafasir.squeeze(); +} + +Tafsir::Tafsir(QString display, QString filename, bool isText, bool isExtra) + : Content(display, filename, isExtra) + , m_isText(isText) +{ +} + +bool +Tafsir::operator==(const Tafsir& v2) const +{ + return this->filename() == v2.filename(); +} + +bool +Tafsir::operator!=(const Tafsir& v2) const +{ + return this->filename() != v2.filename(); +} + +bool +Tafsir::isAvailable() const +{ + const QDir& baseDir = isExtra() ? DirManager::getInstance().downloadsDir() + : DirManager::getInstance().assetsDir(); + return baseDir.exists("tafasir/" + filename()); +} + +const bool +Tafsir::isText() const +{ + return m_isText; +} diff --git a/src/types/tafsir.h b/src/types/tafsir.h new file mode 100644 index 00000000..05c00e93 --- /dev/null +++ b/src/types/tafsir.h @@ -0,0 +1,26 @@ +#ifndef TAFSIR_H +#define TAFSIR_H + +#include "content.h" +#include +#include +#include + +class Tafsir : public Content +{ +public: + static void populateTafasir(); + static QList tafasir; + + bool operator==(const Tafsir& v2) const; + bool operator!=(const Tafsir& v2) const; + + explicit Tafsir(QString display, QString filename, bool isText, bool isExtra); + const bool isText() const; + bool isAvailable() const; + +private: + bool m_isText; +}; + +#endif // TAFSIR_H diff --git a/src/types/translation.cpp b/src/types/translation.cpp new file mode 100644 index 00000000..4d0236f1 --- /dev/null +++ b/src/types/translation.cpp @@ -0,0 +1,57 @@ +#include "translation.h" +#include +#include +#include +#include +#include + +QList Translation::translations; + +void +Translation::populateTranslations() +{ + QFile content(":/resources/files.xml"); + if (!content.open(QIODevice::ReadOnly)) + qCritical("Couldn't Open Files XML"); + + QXmlStreamReader reader(&content); + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name().toString() == "translation") { + QString name = reader.attributes().value("name").toString(); + QString file = reader.attributes().value("file").toString(); + bool extra = reader.attributes().value("extra").toInt(); + translations.append(Translation(name, file, extra)); + } + } + } + + content.close(); + translations.squeeze(); +} + +Translation::Translation(QString display, QString filename, bool isExtra) + : Content(display, filename, isExtra) +{ +} + +bool +Translation::operator==(const Translation& v2) const +{ + return this->filename() == v2.filename(); +} + +bool +Translation::operator!=(const Translation& v2) const +{ + return this->filename() != v2.filename(); +} + +bool +Translation::isAvailable() const +{ + const QDir& baseDir = isExtra() ? DirManager::getInstance().downloadsDir() + : DirManager::getInstance().assetsDir(); + return baseDir.exists("translations/" + filename()); +} diff --git a/src/types/translation.h b/src/types/translation.h new file mode 100644 index 00000000..e4fd91d2 --- /dev/null +++ b/src/types/translation.h @@ -0,0 +1,22 @@ +#ifndef TRANSLATION_H +#define TRANSLATION_H + +#include "content.h" +#include +#include +#include + +class Translation : public Content +{ +public: + static void populateTranslations(); + static QList translations; + + bool operator==(const Translation& v2) const; + bool operator!=(const Translation& v2) const; + + explicit Translation(QString display, QString filename, bool isExtra); + bool isAvailable() const; +}; + +#endif // TRANSLATION_H diff --git a/src/types/verse.cpp b/src/types/verse.cpp new file mode 100644 index 00000000..eacc0182 --- /dev/null +++ b/src/types/verse.cpp @@ -0,0 +1,209 @@ +#include "verse.h" + +const QList Verse::verseCount = { + 7, 286, 200, 176, 120, 165, 206, 75, 129, 109, 123, 111, 43, 52, 99, + 128, 111, 110, 98, 135, 112, 78, 118, 64, 77, 227, 93, 88, 69, 60, + 34, 30, 73, 54, 45, 83, 182, 88, 75, 85, 54, 53, 89, 59, 37, + 35, 38, 29, 18, 45, 60, 49, 62, 55, 78, 96, 29, 22, 24, 13, + 14, 11, 11, 18, 12, 12, 30, 52, 52, 44, 28, 28, 20, 56, 40, + 31, 50, 40, 46, 42, 29, 19, 36, 25, 22, 17, 19, 26, 30, 20, + 15, 21, 11, 8, 8, 19, 5, 8, 8, 11, 11, 8, 3, 9, 5, + 4, 7, 3, 6, 3, 5, 4, 5, 6 +}; + +const int +Verse::surahVerseCount(int surah) +{ + if (surah > 114 || surah < 1) + return 0; + return verseCount.at(surah - 1); +} + +int +Verse::id(int surah, int verse) +{ + int id = 0; + for (int i = 0; i < surah - 1; i++) + id += verseCount.at(i); + id += verse; + return id; +} + +Verse& +Verse::getCurrent() +{ + static Verse current(1, 1, 1); + return current; +} + +QList +Verse::fromList(QList> lst) +{ + QList final(lst.size()); + for (int i = 0; i < lst.size(); i++) + final[i].update(lst[i]); + + return final; +} + +Verse::Verse() {} + +Verse::Verse(int page, int surah, int number) + : m_page(page) + , m_number(number) +{ + setSurah(surah); +} + +Verse::Verse(const QList vInfo) +{ + m_page = vInfo[0]; + setSurah(vInfo[1]); + m_number = vInfo[2]; +} + +Verse& +Verse::operator=(const QList& vInfo) +{ + m_page = vInfo[0]; + setSurah(vInfo[1]); + m_number = vInfo[2]; + return *this; +} + +Verse& +Verse::operator=(const Verse& cp) +{ + m_page = cp.m_page; + m_surah = cp.m_surah; + m_surahCount = cp.m_surahCount; + m_number = cp.m_number; + return *this; +} + +bool +Verse::operator==(const Verse& v2) const +{ + return (m_number == v2.m_number && m_surah == v2.m_surah); +} + +bool +Verse::operator!=(const Verse& v2) const +{ + return (m_number != v2.m_number || m_surah != v2.m_surah); +} + +bool +Verse::operator<(const Verse& v2) const +{ + if (m_surah == v2.surah()) + return m_number < v2.m_number; + + return m_surah < v2.m_surah; +} + +bool +Verse::operator>(const Verse& v2) const +{ + if (m_surah == v2.m_surah) + return m_number > v2.m_number; + + return m_surah > v2.m_surah; +} + +void +Verse::update(const Verse& v) +{ + m_page = v.m_page; + setSurah(v.m_surah); + m_number = v.m_number; +} + +void +Verse::update(const QList& vInfo) +{ + m_page = vInfo[0]; + setSurah(vInfo[1]); + m_number = vInfo[2]; +} + +Verse +Verse::next(bool basmalah) +{ + if (!m_number) { + m_number = 1; + return *this; + } + + Verse v(m_quranDb.verseById(id(m_surah, m_number) + 1)); + + if (v.number() == 1 && v.surah() != 9 && v.surah() != 1 && basmalah) + v.setNumber(0); + + return v; +} + +Verse +Verse::prev(bool basmalah) +{ + if (m_number == 1 && m_surah != 9 && m_surah != 1 && basmalah) { + m_number = 0; + return *this; + } + + if (!m_number) + m_number = 1; + + return Verse(m_quranDb.verseById(id(m_surah, m_number) - 1)); +} + +void +Verse::setPage(int newPage) +{ + m_page = newPage; +} + +void +Verse::setSurah(int newSurah) +{ + if (m_surah == newSurah) + return; + m_surah = newSurah; + m_surahCount = verseCount.at(m_surah - 1); +} + +void +Verse::setNumber(int newNumber) +{ + m_number = newNumber; +} + +QList +Verse::toList() const +{ + return { m_page, m_surah, m_number }; +} + +int +Verse::surahCount() const +{ + return m_surahCount; +} + +int +Verse::page() const +{ + return m_page; +} + +int +Verse::surah() const +{ + return m_surah; +} + +int +Verse::number() const +{ + return m_number; +} diff --git a/src/types/verse.h b/src/types/verse.h new file mode 100644 index 00000000..0586941f --- /dev/null +++ b/src/types/verse.h @@ -0,0 +1,57 @@ +#ifndef VERSE_H +#define VERSE_H + +#include +#include + +/** + * @brief Verse class represents a single quran verse + * @details Quran verses consist of 3 attributes. page (1-604). surah (1-114). + * number represents the number of the verse in the surah (0-surah verse count). + * Basmallah before the 1st verse is represented as verse number 0. + */ +class Verse +{ +public: + static const QList verseCount; + static const int surahVerseCount(int surah); + static int id(int surah, int verse); + static Verse& getCurrent(); + static QList fromList(QList> lst); + + Verse(); + Verse(const QList vInfo); + Verse(const Verse& cp) = default; + explicit Verse(int page, int surah, int number); + + QList toList() const; + void update(const Verse& v); + void update(const QList& vInfo); + Verse next(bool basmalah = true); + Verse prev(bool basmalah = true); + + Verse& operator=(const Verse& cp); + Verse& operator=(const QList& vInfo); + bool operator==(const Verse& v2) const; + bool operator!=(const Verse& v2) const; + bool operator<(const Verse& v2) const; + bool operator>(const Verse& v2) const; + + int page() const; + int surah() const; + int number() const; + int surahCount() const; + void setPage(int newPage); + void setSurah(int newSurah); + void setNumber(int newNumber); + +private: + const QuranDb& m_quranDb = QuranDb::getInstance(); + + int m_page = -1; ///< verse page + int m_surah = -1; ///< verse surah number + int m_number = -1; ///< verse number in surah + int m_surahCount = 0; ///< surah verse count +}; + +#endif // VERSE_H diff --git a/src/utils/configuration.cpp b/src/utils/configuration.cpp new file mode 100644 index 00000000..4a69c689 --- /dev/null +++ b/src/utils/configuration.cpp @@ -0,0 +1,138 @@ +#include "configuration.h" +#include "dirmanager.h" +#include +#include +#include +#include + +Configuration& +Configuration::getInstance() +{ + static Configuration config; + return config; +} + +Configuration::Configuration() + : m_settings(DirManager::getInstance().configDir().absoluteFilePath( + "qurancompanion.conf"), + QSettings::IniFormat) +{ + checkGroups(); + m_themeId = m_settings.value("Theme").toInt(); + m_qcfVersion = m_settings.value("Reader/QCF").toInt(); + m_language = qvariant_cast(m_settings.value("Language")); + m_readerMode = qvariant_cast(m_settings.value("Reader/Mode")); + m_darkMode = m_themeId == 2; +} + +void +Configuration::checkGroups() +{ + for (int i = 0; i < 2; i++) + checkConfGroup(i); +} + +void +Configuration::checkConfGroup(int gId) +{ + switch (gId) { + case 0: + m_settings.setValue("Language", + m_settings.value("Language", (int)QLocale::English)); + m_settings.setValue("Theme", m_settings.value("Theme", 0)); + m_settings.setValue("VOTD", m_settings.value("VOTD", true)); + m_settings.setValue("MissingFileWarning", + m_settings.value("MissingFileWarning", true)); + break; + case 1: + m_settings.beginGroup("Reader"); + m_settings.setValue("Mode", m_settings.value("Mode", 0)); + m_settings.setValue("FGHighlight", m_settings.value("FGHighlight", 1)); + m_settings.setValue("Khatmah", m_settings.value("Khatmah", 0)); + m_settings.setValue("AdaptiveFont", + m_settings.value("AdaptiveFont", true)); + m_settings.setValue("QCF1Size", m_settings.value("QCF1Size", 22)); + m_settings.setValue("QCF2Size", m_settings.value("QCF2Size", 20)); + m_settings.setValue("QCF", m_settings.value("QCF", 1)); + m_settings.setValue("VerseType", m_settings.value("VerseType", 0)); + m_settings.setValue("VerseFontSize", + m_settings.value("VerseFontSize", 20)); + m_settings.setValue("Tafsir", m_settings.value("Tafsir", 6)); + m_settings.setValue("Translation", m_settings.value("Translation", 5)); + m_settings.setValue( + "SideContentFont", + m_settings.value("SideContentFont", QFont("Expo Arabic", 14))); + m_settings.endGroup(); + break; + } +} + +void +Configuration::loadUiTranslation() +{ + if (m_language == QLocale::English) + return; + + QString code = QLocale::languageToCode(m_language); + QTranslator *translation = new QTranslator(qApp), + *qtBase = new QTranslator(qApp); + + if (translation->load(":/i18n/qc_" + code + ".qm")) { + qInfo() << translation->language() << "translation loaded"; + qInfo() << "base translation:" << qtBase->load(":/base/" + code + ".qm"); + qApp->installTranslator(translation); + qApp->installTranslator(qtBase); + } else { + qWarning() << code + " translation not loaded!"; + delete translation; + delete qtBase; + } +} + +QSettings& +Configuration::settings() +{ + return m_settings; +} + +int +Configuration::themeId() const +{ + return m_themeId; +} + +bool +Configuration::darkMode() const +{ + return m_darkMode; +} + +int +Configuration::qcfVersion() const +{ + return m_qcfVersion; +} + +QLocale::Language +Configuration::language() const +{ + return m_language; +} + +Configuration::ReaderMode +Configuration::readerMode() const +{ + return m_readerMode; +} + +Configuration::VerseType +Configuration::verseType() const +{ + return m_verseType; +} + +void +Configuration::setVerseType(VerseType newVerseType) +{ + m_verseType = newVerseType; +} diff --git a/src/utils/configuration.h b/src/utils/configuration.h new file mode 100644 index 00000000..5bd840ad --- /dev/null +++ b/src/utils/configuration.h @@ -0,0 +1,54 @@ +#ifndef CONFIGURATION_H +#define CONFIGURATION_H + +#include +#include +#include + +class Configuration +{ +public: + enum VerseType + { + Qcf, + Uthmanic, + Annotated + }; + /** + * @brief ReaderMode enum represents the available modes for the Quran reader + * in MainWindow + */ + enum ReaderMode + { + SinglePage, ///< Single Quran page, side panel is used for displaying verses + ///< with translation + DoublePage ///< Two Quran pages, both panels are used to display Quran + ///< pages, no translation + }; + + static Configuration& getInstance(); + void checkConfGroup(int gId); + void loadUiTranslation(); + void checkGroups(); + + QSettings& settings(); + int themeId() const; + bool darkMode() const; + int qcfVersion() const; + QLocale::Language language() const; + ReaderMode readerMode() const; + VerseType verseType() const; + void setVerseType(VerseType newVerseType); + +private: + Configuration(); + int m_themeId; + bool m_darkMode; + int m_qcfVersion; + QLocale::Language m_language; + QSettings m_settings; + ReaderMode m_readerMode; + VerseType m_verseType; +}; + +#endif // CONFIGURATION_H diff --git a/src/utils/dbmanager.cpp b/src/utils/dbmanager.cpp index af9d0856..3bfe0f34 100644 --- a/src/utils/dbmanager.cpp +++ b/src/utils/dbmanager.cpp @@ -4,14 +4,21 @@ */ #include "dbmanager.h" -#include + +QSharedPointer +DBManager::current() +{ + static QSharedPointer controller = + QSharedPointer::create(); + return controller; +} DBManager::DBManager(QObject* parent) : QObject(parent) { - m_quranDbPath.setFile(m_assetsDir.filePath("quran.db")); - m_glyphsDbPath.setFile(m_assetsDir.filePath("glyphs.db")); - m_betaqatDbPath.setFile(m_assetsDir.filePath("betaqat.db")); + m_quranDbPath.setFile(m_assetsDir->filePath("quran.db")); + m_glyphsDbPath.setFile(m_assetsDir->filePath("glyphs.db")); + m_betaqatDbPath.setFile(m_assetsDir->filePath("betaqat.db")); // set database driver, set the path & open a connection with the db QSqlDatabase::addDatabase("QSQLITE", "QuranCon"); @@ -21,15 +28,17 @@ DBManager::DBManager(QObject* parent) QSqlDatabase::addDatabase("QSQLITE", "TafsirCon"); QSqlDatabase::addDatabase("QSQLITE", "TranslationCon"); - for (int i = 1; i <= 114; i++) { + for (int i = 1; i <= 114; i++) m_surahNames.append(getSurahName(i)); - } + + updateLoadedTafsir(); + updateLoadedTranslation(); } /* ---------------- Database handling ---------------- */ void -DBManager::setOpenDatabase(Database db, QString filePath) +DBManager::setOpenDatabase(Database db, QString path) { if (m_currentDb == db) return; @@ -37,63 +46,82 @@ DBManager::setOpenDatabase(Database db, QString filePath) m_currentDb = db; m_openDBCon.close(); switch (db) { - case null: + case Null: break; - case quran: + case Quran: m_openDBCon = QSqlDatabase::database("QuranCon"); break; - case glyphs: + case Glyphs: m_openDBCon = QSqlDatabase::database("GlyphsCon"); break; - case bookmarks: + case Bookmarks: m_openDBCon = QSqlDatabase::database("BookmarksCon"); break; - case tafsir: + case Tafsir: m_openDBCon = QSqlDatabase::database("TafsirCon"); break; - case translation: + case Translation: m_openDBCon = QSqlDatabase::database("TranslationCon"); break; - case betaqat: + case Betaqat: m_openDBCon = QSqlDatabase::database("BetaqatCon"); break; } - m_openDBCon.setDatabaseName(filePath); + updateOpenDbFile(path); +} + +void +DBManager::updateOpenDbFile(const QString& filepath) +{ + m_openDBCon.setDatabaseName(filepath); if (!m_openDBCon.open()) qFatal("Couldn't open Database!"); } -void -DBManager::setCurrentTafsir(int tafsirIdx) +bool +DBManager::setCurrentTafsir(int idx) { - if (tafsirIdx < 0 || tafsirIdx >= m_tafasirList.size()) - return; + if (idx < 0 || idx >= m_tafasir.size()) + return false; + if (m_currTafsir == m_tafasir[idx]) + return true; + + m_currTafsir = m_tafasir[idx]; + const QDir& baseDir = + m_currTafsir->isExtra() ? *m_downloadsDir : *m_assetsDir; + QString path = "tafasir/" + m_currTafsir->filename(); + if (!baseDir.exists(path)) + return false; - m_currTafsir = &m_tafasirList[tafsirIdx]; - const QDir& baseDir = m_currTafsir->extra ? m_downloadsDir : m_assetsDir; - QString path = "tafasir/" + m_currTafsir->filename; - if (baseDir.exists(path)) - m_tafsirDbPath.setFile(baseDir.filePath(path)); + m_tafsirDbPath.setFile(baseDir.filePath(path)); + updateOpenDbFile(m_tafsirDbPath.absoluteFilePath()); + return true; } -void -DBManager::setCurrentTranslation(int translationIdx) +bool +DBManager::setCurrentTranslation(int idx) { - if (translationIdx < 0 || translationIdx >= m_translationsList.size()) - return; + if (idx < 0 || idx >= m_translations.size()) + return false; + if (m_currTr == m_translations[idx]) + return true; + + m_currTr = m_translations[idx]; + const QDir& baseDir = m_currTr->isExtra() ? *m_downloadsDir : *m_assetsDir; + QString path = "translations/" + m_currTr->filename(); + if (!baseDir.exists(path)) + return false; - m_currTrans = &m_translationsList[translationIdx]; - const QDir& baseDir = m_currTrans->extra ? m_downloadsDir : m_assetsDir; - QString path = "translations/" + m_currTrans->filename; - if (baseDir.exists(path)) - m_transDbPath.setFile(baseDir.filePath(path)); + m_transDbPath.setFile(baseDir.filePath(path)); + updateOpenDbFile(m_transDbPath.absoluteFilePath()); + return true; } /* ---------------- Page-related methods ---------------- */ @@ -101,7 +129,7 @@ DBManager::setCurrentTranslation(int translationIdx) QPair DBManager::getPageMetadata(const int page) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare( "SELECT sura_no,jozz FROM verses_v1 WHERE page=:p ORDER BY id"); @@ -118,7 +146,7 @@ DBManager::getPageMetadata(const int page) QStringList DBManager::getPageLines(const int page) { - setOpenDatabase(Database::glyphs, m_glyphsDbPath.filePath()); + setOpenDatabase(Database::Glyphs, m_glyphsDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString query = "SELECT %0 FROM pages WHERE page_no=%1"; @@ -134,11 +162,11 @@ DBManager::getPageLines(const int page) return lines; } -QList +QList> DBManager::getVerseInfoList(int page) { - QList viList; - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + QList> viList; + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString query = @@ -150,7 +178,7 @@ DBManager::getVerseInfoList(int page) } while (dbQuery.next()) { - Verse v{ page, dbQuery.value(0).toInt(), dbQuery.value(1).toInt() }; + QList v{ page, dbQuery.value(0).toInt(), dbQuery.value(1).toInt() }; viList.append(v); } @@ -160,7 +188,7 @@ DBManager::getVerseInfoList(int page) int DBManager::getJuzStartPage(const int juz) { - setOpenDatabase(Database::glyphs, m_glyphsDbPath.filePath()); + setOpenDatabase(Database::Glyphs, m_glyphsDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString query = @@ -179,7 +207,7 @@ int DBManager::getJuzOfPage(const int page) { // returns the jozz number which the passed page belongs to - setOpenDatabase(Database::glyphs, m_glyphsDbPath.filePath()); + setOpenDatabase(Database::Glyphs, m_glyphsDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString query = @@ -199,7 +227,7 @@ DBManager::getJuzOfPage(const int page) QString DBManager::getSurahNameGlyph(const int sura) { - setOpenDatabase(Database::glyphs, m_glyphsDbPath.filePath()); + setOpenDatabase(Database::Glyphs, m_glyphsDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare("SELECT qcf_v1 FROM surah_glyphs WHERE surah=:i"); @@ -216,7 +244,7 @@ DBManager::getSurahNameGlyph(const int sura) QString DBManager::getJuzGlyph(const int juz) { - setOpenDatabase(Database::glyphs, m_glyphsDbPath.filePath()); + setOpenDatabase(Database::Glyphs, m_glyphsDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare("SELECT text FROM juz_glyphs WHERE juz=:j"); @@ -233,10 +261,10 @@ DBManager::getJuzGlyph(const int juz) QString DBManager::getVerseGlyphs(const int sIdx, const int vIdx) { - if (m_verseType != VerseType::qcf) + if (m_verseType != VerseType::Qcf) return getVerseText(sIdx, vIdx); - setOpenDatabase(Database::glyphs, m_glyphsDbPath.filePath()); + setOpenDatabase(Database::Glyphs, m_glyphsDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); @@ -259,7 +287,7 @@ DBManager::getVerseGlyphs(const int sIdx, const int vIdx) QString DBManager::getSurahName(const int sIdx, bool ar) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); if (m_languageCode == QLocale::Arabic || ar) @@ -279,7 +307,7 @@ DBManager::getSurahName(const int sIdx, bool ar) QString DBManager::getBetaqa(const int surah) { - setOpenDatabase(betaqat, m_betaqatDbPath.filePath()); + setOpenDatabase(Betaqat, m_betaqatDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); if (m_languageCode == QLocale::Arabic) @@ -299,7 +327,7 @@ DBManager::getBetaqa(const int surah) int DBManager::getVerseId(const int sIdx, const int vIdx) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare("SELECT id FROM verses_v1 WHERE sura_no=:s AND aya_no=:v"); dbQuery.bindValue(0, sIdx); @@ -313,10 +341,10 @@ DBManager::getVerseId(const int sIdx, const int vIdx) return dbQuery.value(0).toInt(); } -Verse +QList DBManager::getVerseById(const int id) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare("SELECT page,sura_no,aya_no FROM verses_v1 WHERE id=:i"); dbQuery.bindValue(0, id); @@ -326,15 +354,15 @@ DBManager::getVerseById(const int id) dbQuery.next(); - return Verse{ dbQuery.value(0).toInt(), - dbQuery.value(1).toInt(), - dbQuery.value(2).toInt() }; + return { dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }; } int DBManager::getSurahVerseCount(const int surahIdx) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare( @@ -353,7 +381,7 @@ DBManager::getSurahVerseCount(const int surahIdx) int DBManager::getSurahStartPage(int surahIdx) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare("SELECT page FROM verses_v1 WHERE sura_no=:sn AND aya_no=1"); @@ -374,13 +402,13 @@ DBManager::surahNameList() return m_surahNames; } -QList +QList> DBManager::searchSurahs(QString searchText, const QList surahs, const bool whole) { - QList results; - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + QList> results; + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString q = "SELECT page,sura_no,aya_no FROM verses_v" + @@ -404,9 +432,9 @@ DBManager::searchSurahs(QString searchText, } while (dbQuery.next()) { - results.append(Verse{ dbQuery.value(0).toInt(), - dbQuery.value(1).toInt(), - dbQuery.value(2).toInt() }); + results.append({ dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }); } return results; @@ -416,7 +444,7 @@ QList DBManager::searchSurahNames(QString text) { QList results; - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString q = "SELECT DISTINCT sura_no FROM verses_v1 WHERE (sura_name_ar like '%" + @@ -440,9 +468,9 @@ DBManager::searchSurahNames(QString text) /* ---------------- Verse-related methods ---------------- */ bool -DBManager::getKhatmahPos(const int khatmahId, Verse& v) +DBManager::loadVerse(const int khatmahId, QList& vInfo) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); QString q = QString::asprintf( @@ -454,16 +482,16 @@ DBManager::getKhatmahPos(const int khatmahId, Verse& v) if (!dbQuery.next()) return false; - v.page = dbQuery.value(0).toInt(); - v.surah = dbQuery.value(1).toInt(); - v.number = dbQuery.value(2).toInt(); + vInfo[0] = dbQuery.value(0).toInt(); + vInfo[1] = dbQuery.value(1).toInt(); + vInfo[2] = dbQuery.value(2).toInt(); return true; } int -DBManager::addKhatmah(const Verse& v, const QString name, const int id) +DBManager::addKhatmah(QList vInfo, const QString name, const int id) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); dbQuery.exec( "CREATE TABLE IF NOT EXISTS khatmah(id INTEGER PRIMARY KEY " @@ -473,18 +501,18 @@ DBManager::addKhatmah(const Verse& v, const QString name, const int id) q = "INSERT INTO khatmah(name, page, surah, number) VALUES ('%0', %1, %2, " "%3)"; dbQuery.prepare(q.arg(name, - QString::number(v.page), - QString::number(v.surah), - QString::number(v.number))); + QString::number(vInfo[0]), + QString::number(vInfo[1]), + QString::number(vInfo[2]))); } else { q = "REPLACE INTO khatmah VALUES " "(%0, " "'%1', %2, %3, %4)"; dbQuery.prepare(q.arg(QString::number(id), name, - QString::number(v.page), - QString::number(v.surah), - QString::number(v.number))); + QString::number(vInfo[0]), + QString::number(vInfo[1]), + QString::number(vInfo[2]))); } if (!dbQuery.exec()) { @@ -504,7 +532,7 @@ DBManager::addKhatmah(const Verse& v, const QString name, const int id) bool DBManager::editKhatmahName(const int khatmahId, QString newName) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); QString q = "SELECT DISTINCT id FROM khatmah WHERE name='%0'"; if (!dbQuery.exec(q.arg(newName))) { @@ -529,22 +557,22 @@ DBManager::editKhatmahName(const int khatmahId, QString newName) void DBManager::removeKhatmah(const int id) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); if (!dbQuery.exec(QString::asprintf("DELETE FROM khatmah WHERE id=%i", id))) qDebug() << "Couldn't execute query: " << dbQuery.lastQuery(); } bool -DBManager::saveActiveKhatmah(const Verse& v) +DBManager::saveActiveKhatmah(QList vInfo) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); QString q = QString::asprintf( "UPDATE khatmah SET page=%i, surah=%i, number=%i WHERE id=%i", - v.page, - v.surah, - v.number, + vInfo[0], + vInfo[1], + vInfo[2], m_activeKhatmah); if (!dbQuery.exec(q)) { qCritical() << "Couldn't save position in mushaf"; @@ -559,9 +587,9 @@ DBManager::saveActiveKhatmah(const Verse& v) QString DBManager::getVerseText(const int sIdx, const int vIdx) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); - if (m_verseType == VerseType::annotated) + if (m_verseType == VerseType::Annotated) dbQuery.prepare("SELECT aya_text_annotated FROM verses_v1 WHERE sura_no=:s " "AND aya_no=:v"); else @@ -583,7 +611,7 @@ QList DBManager::getAllKhatmah() { QList res; - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); if (!dbQuery.exec("SELECT id FROM khatmah")) qCritical() << "Couldn't execute sql query: " << dbQuery.lastQuery(); @@ -597,7 +625,7 @@ DBManager::getAllKhatmah() QString DBManager::getKhatmahName(const int id) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); if (!dbQuery.exec("SELECT name FROM khatmah WHERE id=" + QString::number(id))) qCritical() << "Couldn't execute sql query: " << dbQuery.lastQuery(); @@ -606,10 +634,10 @@ DBManager::getKhatmahName(const int id) return dbQuery.value(0).toString(); } -Verse +QList DBManager::randomVerse() { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); int id = QRandomGenerator::global()->bounded(1, 6237); @@ -629,7 +657,7 @@ DBManager::randomVerse() int DBManager::getVersePage(const int& surahIdx, const int& verse) { - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString query = "SELECT page FROM verses_v%0 WHERE sura_no=%1 AND aya_no=%2"; @@ -645,13 +673,13 @@ DBManager::getVersePage(const int& surahIdx, const int& verse) return dbQuery.value(0).toInt(); } -QList +QList> DBManager::searchVerses(QString searchText, const int range[2], const bool whole) { - QList results; - setOpenDatabase(Database::quran, m_quranDbPath.filePath()); + QList> results; + setOpenDatabase(Database::Quran, m_quranDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); QString q = "SELECT page,sura_no,aya_no FROM verses_v" + @@ -672,20 +700,20 @@ DBManager::searchVerses(QString searchText, } while (dbQuery.next()) { - Verse entry{ dbQuery.value(0).toInt(), - dbQuery.value(1).toInt(), - dbQuery.value(2).toInt() }; + QList entry{ dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }; results.append(entry); } return results; } -QList +QList> DBManager::bookmarkedVerses(int surahIdx) { - QList results; - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + QList> results; + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); QString q = "SELECT page,surah,number FROM favorites"; if (surahIdx != -1) @@ -696,25 +724,25 @@ DBManager::bookmarkedVerses(int surahIdx) qCritical() << "Couldn't execute bookmarkedVerses SELECT query"; while (dbQuery.next()) { - results.append(Verse{ dbQuery.value(0).toInt(), - dbQuery.value(1).toInt(), - dbQuery.value(2).toInt() }); + results.append({ dbQuery.value(0).toInt(), + dbQuery.value(1).toInt(), + dbQuery.value(2).toInt() }); } return results; } bool -DBManager::isBookmarked(Verse v) +DBManager::isBookmarked(QList vInfo) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare( "SELECT page FROM favorites WHERE page=:p AND surah=:s AND number=:n"); - dbQuery.bindValue(0, v.page); - dbQuery.bindValue(1, v.surah); - dbQuery.bindValue(2, v.number); + dbQuery.bindValue(0, vInfo[0]); + dbQuery.bindValue(1, vInfo[1]); + dbQuery.bindValue(2, vInfo[2]); if (!dbQuery.exec()) { qWarning() << "Couldn't check if verse is bookmarked"; @@ -727,9 +755,9 @@ DBManager::isBookmarked(Verse v) } bool -DBManager::addBookmark(Verse v) +DBManager::addBookmark(QList vInfo) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); dbQuery.exec("CREATE TABLE IF NOT EXISTS favorites(id INTEGER PRIMARY KEY " "AUTOINCREMENT," @@ -737,37 +765,37 @@ DBManager::addBookmark(Verse v) dbQuery.prepare( "INSERT INTO favorites(page, surah, number) VALUES (:p, :s, :n)"); - dbQuery.bindValue(0, v.page); - dbQuery.bindValue(1, v.surah); - dbQuery.bindValue(2, v.number); + dbQuery.bindValue(0, vInfo[0]); + dbQuery.bindValue(1, vInfo[1]); + dbQuery.bindValue(2, vInfo[2]); if (!dbQuery.exec()) { qWarning() << "Couldn't add verse to bookmarks db"; return false; } - if (!m_openDBCon.commit()) - return false; - + m_openDBCon.commit(); + emit bookmarkAdded(); return true; } bool -DBManager::removeBookmark(Verse v) +DBManager::removeBookmark(QList vInfo) { - setOpenDatabase(Database::bookmarks, m_bookmarksFilepath); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); QSqlQuery dbQuery(m_openDBCon); dbQuery.prepare( "DELETE FROM favorites WHERE page=:p AND surah=:s AND number=:n"); - dbQuery.bindValue(0, v.page); - dbQuery.bindValue(1, v.surah); - dbQuery.bindValue(2, v.number); + dbQuery.bindValue(0, vInfo[0]); + dbQuery.bindValue(1, vInfo[1]); + dbQuery.bindValue(2, vInfo[2]); if (!dbQuery.exec()) { qWarning() << "Couldn't remove verse from bookmarks"; return false; } + emit bookmarkRemoved(); return true; } @@ -776,7 +804,7 @@ DBManager::removeBookmark(Verse v) QString DBManager::getTafsir(const int sIdx, const int vIdx) { - setOpenDatabase(Database::tafsir, m_tafsirDbPath.filePath()); + setOpenDatabase(Database::Tafsir, m_tafsirDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); @@ -795,7 +823,7 @@ DBManager::getTafsir(const int sIdx, const int vIdx) QString DBManager::getTranslation(const int sIdx, const int vIdx) { - setOpenDatabase(Database::translation, m_transDbPath.filePath()); + setOpenDatabase(Database::Translation, m_transDbPath.filePath()); QSqlQuery dbQuery(m_openDBCon); @@ -811,6 +839,62 @@ DBManager::getTranslation(const int sIdx, const int vIdx) return dbQuery.value(0).toString(); } +void +DBManager::saveThoughts(QList vInfo, const QString& text) +{ + int id = getVerseId(vInfo[1], vInfo[2]); + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); + QSqlQuery dbQuery(m_openDBCon); + dbQuery.exec("CREATE TABLE IF NOT EXISTS thoughts(id INTEGER PRIMARY KEY " + "UNIQUE," + "page INTEGER, surah INTEGER, number INTEGER, text TEXT)"); + + dbQuery.prepare("REPLACE INTO thoughts(id, page, surah, number, text) " + "VALUES(:i, :p, :s, :n, :t)"); + dbQuery.bindValue(0, id); + dbQuery.bindValue(1, vInfo[0]); + dbQuery.bindValue(2, vInfo[1]); + dbQuery.bindValue(3, vInfo[2]); + dbQuery.bindValue(4, text); + + if (!dbQuery.exec()) + qCritical() << "SQL statement execution error:" << dbQuery.lastError(); + + m_openDBCon.commit(); +} + +QString +DBManager::getThoughts(QList vInfo) +{ + setOpenDatabase(Database::Bookmarks, m_bookmarksFilepath); + QSqlQuery dbQuery(m_openDBCon); + dbQuery.prepare( + "SELECT text FROM thoughts WHERE page=:p AND surah=:s AND number=:n"); + dbQuery.bindValue(0, vInfo[0]); + dbQuery.bindValue(1, vInfo[1]); + dbQuery.bindValue(2, vInfo[2]); + + if (!dbQuery.exec()) + qCritical() << "SQL statement execution error:" << dbQuery.lastError(); + + dbQuery.next(); + return dbQuery.value(0).toString(); +} + +void +DBManager::updateLoadedTafsir() +{ + int currTafsir = m_settings->value("Reader/Tafsir").toInt(); + setCurrentTafsir(currTafsir); +} + +void +DBManager::updateLoadedTranslation() +{ + int currTrans = m_settings->value("Reader/Translation").toInt(); + setCurrentTranslation(currTrans); +} + void DBManager::setActiveKhatmah(const int id) { @@ -835,8 +919,14 @@ DBManager::activeKhatmah() const return m_activeKhatmah; } -const Tafsir* +QSharedPointer DBManager::currTafsir() const { return m_currTafsir; } + +QSharedPointer +DBManager::currTranslation() const +{ + return m_currTr; +} diff --git a/src/utils/dbmanager.h b/src/utils/dbmanager.h index e17b9c39..58a2b0c0 100644 --- a/src/utils/dbmanager.h +++ b/src/utils/dbmanager.h @@ -6,16 +6,21 @@ #ifndef DBMANAGER_H #define DBMANAGER_H -#include "../globals.h" +#include "types/tafsir.h" +#include "types/translation.h" +#include "utils/dirmanager.h" +#include "utils/settings.h" #include #include #include #include #include #include +#include #include #include #include +typedef Settings::VerseType VerseType; /** * @brief DBManager is as an interface for preforming queries to @@ -26,19 +31,20 @@ class DBManager : public QObject Q_OBJECT public: + static QSharedPointer current(); /** * @brief Database enum holds different values representing database files * used in different member functions. */ enum Database { - null, ///< default value - quran, ///< (quran.db) main Quran database file - glyphs, ///< (glyphs.db) QCF glyphs database - betaqat, - bookmarks, ///< (bookmarks.db) bookmarked verses and khatmah database - tafsir, ///< currently selected tafsir database file - translation ///< currently selected translation database file + Null, ///< default value + Quran, ///< (quran.db) main Quran database file + Glyphs, ///< (glyphs.db) QCF glyphs database + Betaqat, + Bookmarks, ///< (bookmarks.db) bookmarked verses and khatmah database + Tafsir, ///< currently selected tafsir database file + Translation ///< currently selected translation database file }; /** @@ -55,19 +61,12 @@ class DBManager : public QObject * @brief sets the active tafsir * @param tafsirName - DBManager::Tafsir entry */ - void setCurrentTafsir(int tafsirIdx); + bool setCurrentTafsir(int idx); /** * @brief sets the active translation * @param translationName - DBManager::Translation entry */ - void setCurrentTranslation(int translationIdx); - /** - * @brief sets the currently active sqlite database file and opens - * corresponding connection based on the DBManager::Database type - * @param db - DBManager::Database entry - * @param filePath - path to the database file - */ - void setOpenDatabase(Database db, QString filePath); + bool setCurrentTranslation(int idx); /** * @brief gets the surah number and juz number of the first verse in the page, * used to display page header information @@ -86,7 +85,7 @@ class DBManager : public QObject * @param page - Quran page number * @return QList of ::Verse instances */ - QList getVerseInfoList(const int page); + QList> getVerseInfoList(const int page); /** * @brief gets the surah name glyph for the QCF_BSML font, used to render * surah frame in Quran page @@ -119,7 +118,7 @@ class DBManager : public QObject * active khatmah * @param v - ::Verse reached in khatmah */ - bool saveActiveKhatmah(const Verse& v); + bool saveActiveKhatmah(QList vInfo); /** * @brief get all available khatmah ids * @return QList of khatmah id(s) @@ -136,7 +135,7 @@ class DBManager : public QObject * @return boolean indicating a successful operation (false in case of error * and in case id does not exist) */ - bool getKhatmahPos(const int khatmahId, Verse& v); + bool loadVerse(const int khatmahId, QList& vInfo); /** * @brief add a new khatmah/replace khatmah with given id with position of * ::Verse v @@ -145,7 +144,7 @@ class DBManager : public QObject * @param id - id of khatmah to replace, -1 means do not replace (default: -1) * @return id of newly added khatmah or id parameter if defined */ - int addKhatmah(const Verse& v, const QString name, const int id = -1); + int addKhatmah(QList vInfo, const QString name, const int id = -1); /** * @brief rename the khatmah with the given id to newName * @param khatmahId - id of khatmah to rename @@ -197,7 +196,7 @@ class DBManager : public QObject * @param id - verse id * @return ::Verse instance */ - Verse getVerseById(const int id); + QList getVerseById(const int id); /** * @brief gets the page where the verse is found * @param surahIdx - sura number @@ -231,9 +230,9 @@ class DBManager : public QObject * @param whole - boolean value to search for whole words only * @return QList of ::Verse instances representing the search results */ - QList searchSurahs(QString searchText, - const QList surahs, - const bool whole = false); + QList> searchSurahs(QString searchText, + const QList surahs, + const bool whole = false); /** * @brief search a range of pages for the given search text * @param searchText - text to search for @@ -241,9 +240,9 @@ class DBManager : public QObject * @param whole - boolean value to indicate search for whole words only * @return QList of ::Verse instances representing the search results */ - QList searchVerses(QString searchText, - const int range[2] = new int[2]{ 1, 604 }, - const bool whole = false); + QList> searchVerses(QString searchText, + const int range[2] = new int[2]{ 1, 604 }, + const bool whole = false); /** * @brief gets the tafsir content for the given verse using the active * DBManager::Tafsir @@ -264,42 +263,32 @@ class DBManager : public QObject * @brief gets a random verse from the Quran * @return QPair of ::Verse instance and verse text */ - Verse randomVerse(); + QList randomVerse(); /** * @brief gets a QList of ::Verse instances representing the bookmarked verse * within the given sura (default gets all) * @param surahIdx - sura number (-1 returns all bookmarks) * @return QList of bookmarked verses */ - QList bookmarkedVerses(int surahIdx = -1); + QList> bookmarkedVerses(int surahIdx = -1); /** * @brief checks whether the given ::Verse is bookmarked - * @param v - ::Verse instance to check + * @param vInfo - ::Verse instance to check * @return boolean */ - bool isBookmarked(Verse v); + bool isBookmarked(QList vInfo); /** * @brief add the given ::Verse to bookmarks - * @param v - ::Verse instance to add + * @param vInfo - ::Verse instance to add * @return boolean */ - bool addBookmark(Verse v); + bool addBookmark(QList vInfo); /** * @brief remove the given ::Verse from bookmarks - * @param v - ::Verse instance to remove + * @param vInfo - ::Verse instance to remove * @return boolean indicating successful removal */ - bool removeBookmark(Verse v); - /** - * @brief getter for m_currTafsir - * @return the currently set DBManager::Tafasir - */ - const Tafsir* currTafsir() const; - /** - * @brief getter for m_activeKhatmah - * @return the currently active khatmah id - */ - const int activeKhatmah() const; + bool removeBookmark(QList vInfo); /** * @brief setter for m_activeKhatmah * @param id - id of the active khatmah @@ -310,22 +299,73 @@ class DBManager : public QObject * @param newVerseType */ void setVerseType(VerseType newVerseType); + /** + * MODIFIED + */ + void saveThoughts(QList vInfo, const QString& text); + /** + * MODIFIED + */ + QString getThoughts(QList vInfo); + /** + * @brief getter for m_activeKhatmah + * @return the currently active khatmah id + */ + const int activeKhatmah() const; /** * @brief getter for m_verseType * @return VerseType */ VerseType getVerseType() const; + /** + * @brief getter for m_currTafsir + * @return the currently set DBManager::Tafasir + */ + QSharedPointer<::Tafsir> currTafsir() const; + /** + * @brief currTranslation + * @return + * + * MODIFIED + */ + QSharedPointer<::Translation> currTranslation() const; + +public slots: + /** + * @brief set tafsir to the one in the settings, update the selected db + */ + void updateLoadedTafsir(); + /** + * @brief set translation to the one in the settings, update the selected db + */ + void updateLoadedTranslation(); + +signals: + void bookmarkAdded(); + void bookmarkRemoved(); private: - const QDir& m_assetsDir = Globals::assetsDir; - const QDir& m_downloadsDir = Globals::downloadsDir; - const int m_qcfVer = Globals::qcfVersion; - const QSettings* m_settings = Globals::settings; - const QLocale::Language m_languageCode = Globals::language; - const QList& m_tafasirList = Globals::tafasirList; - const QList& m_translationsList = Globals::translationsList; + const int m_qcfVer = Settings::qcfVersion; + const QLocale::Language m_languageCode = Settings::language; + const QSharedPointer m_assetsDir = DirManager::assetsDir; + const QSharedPointer m_downloadsDir = DirManager::downloadsDir; + const QSharedPointer m_settings = Settings::settings; + const QList>& m_tafasir = Tafsir::tafasir; + const QList>& m_translations = + Translation::translations; const QString m_bookmarksFilepath = - Globals::configDir.absoluteFilePath("bookmarks.db"); + DirManager::configDir->absoluteFilePath("bookmarks.db"); + /** + * @brief sets the currently active sqlite database file and opens + * corresponding connection based on the DBManager::Database type + * @param db - DBManager::Database entry + * @param filePath - path to the database file + */ + void setOpenDatabase(Database db, QString path); + /** + * MODIFIED + */ + void updateOpenDbFile(const QString& filepath); /** * @brief integer id of the current active khatmah */ @@ -333,22 +373,26 @@ class DBManager : public QObject /** * @brief the currently active database type */ - Database m_currentDb = null; + Database m_currentDb = Null; /** * @brief QSqlDatabase instance to interact with the different sqlite * databases */ QSqlDatabase m_openDBCon; - - VerseType m_verseType = VerseType::qcf; + /** + * @brief m_verseType + * + * MODIFIED + */ + VerseType m_verseType = Settings::Qcf; /** * @brief the current active DBManager::Tafasir */ - const Tafsir* m_currTafsir = nullptr; + QSharedPointer<::Tafsir> m_currTafsir; /** * @brief the current active DBManager::Translation */ - const Translation* m_currTrans = nullptr; + QSharedPointer<::Translation> m_currTr; /** * @brief path to the currently active tafsir database file */ @@ -365,7 +409,9 @@ class DBManager : public QObject * @brief path to the QCF glyphs database file */ QFileInfo m_glyphsDbPath; - + /** + * MODIFIED + */ QFileInfo m_betaqatDbPath; /** * @brief QList of sura names (Arabic if UI language is Arabic, Otherwise diff --git a/src/utils/dirmanager.cpp b/src/utils/dirmanager.cpp new file mode 100644 index 00000000..310171e6 --- /dev/null +++ b/src/utils/dirmanager.cpp @@ -0,0 +1,104 @@ +#include "dirmanager.h" +#include +#include + +DirManager& +DirManager::getInstance() +{ + static DirManager dirmanager; + return dirmanager; +} + +DirManager::DirManager() +{ + m_downloadsDir.setPath( + QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); + m_configDir.setPath( + QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation)); + m_assetsDir.setPath(QApplication::applicationDirPath() + QDir::separator() + + "assets"); + m_fontsDir.setPath(m_assetsDir.absoluteFilePath("fonts")); + m_basmallahDir.setPath(QApplication::applicationDirPath() + + QDir::separator() + "bismillah"); + + // config & downloads + if (!m_configDir.exists("QuranCompanion")) + m_configDir.mkpath("QuranCompanion"); + m_configDir.cd("QuranCompanion"); + + if (!m_downloadsDir.exists("QuranCompanion")) + m_downloadsDir.mkpath("QuranCompanion"); + m_downloadsDir.cd("QuranCompanion"); + + if (!m_downloadsDir.exists("recitations")) + m_downloadsDir.mkpath("recitations"); + + if (!m_downloadsDir.exists("QCFV2")) + m_downloadsDir.mkpath("QCFV2"); + + if (!m_downloadsDir.exists("tafasir")) + m_downloadsDir.mkpath("tafasir"); + + if (!m_downloadsDir.exists("translations")) + m_downloadsDir.mkpath("translations"); +} + +void +DirManager::setFontsDir(const QDir& newFontsDir) +{ + m_fontsDir = newFontsDir; +} + +void +DirManager::setConfigDir(const QDir& newConfigDir) +{ + m_configDir = newConfigDir; +} + +void +DirManager::setAssetsDir(const QDir& newAssetsDir) +{ + m_assetsDir = newAssetsDir; +} + +void +DirManager::setDownloadsDir(const QDir& newDownloadsDir) +{ + m_downloadsDir = newDownloadsDir; +} + +void +DirManager::setBasmallahDir(const QDir& newBasmallahDir) +{ + m_basmallahDir = newBasmallahDir; +} + +const QDir& +DirManager::fontsDir() const +{ + return m_fontsDir; +} + +const QDir& +DirManager::configDir() const +{ + return m_configDir; +} + +const QDir& +DirManager::assetsDir() const +{ + return m_assetsDir; +} + +const QDir& +DirManager::downloadsDir() const +{ + return m_downloadsDir; +} + +const QDir& +DirManager::basmallahDir() const +{ + return m_basmallahDir; +} diff --git a/src/utils/dirmanager.h b/src/utils/dirmanager.h new file mode 100644 index 00000000..1575716c --- /dev/null +++ b/src/utils/dirmanager.h @@ -0,0 +1,33 @@ +#ifndef DIRMANAGER_H +#define DIRMANAGER_H + +#include +#include + +class DirManager +{ +public: + static DirManager& getInstance(); + + void setFontsDir(const QDir& newFontsDir); + void setConfigDir(const QDir& newConfigDir); + void setAssetsDir(const QDir& newAssetsDir); + void setDownloadsDir(const QDir& newDownloadsDir); + void setBasmallahDir(const QDir& newBasmallahDir); + + const QDir& fontsDir() const; + const QDir& configDir() const; + const QDir& assetsDir() const; + const QDir& downloadsDir() const; + const QDir& basmallahDir() const; + +private: + DirManager(); + QDir m_fontsDir; + QDir m_configDir; + QDir m_assetsDir; + QDir m_downloadsDir; + QDir m_basmallahDir; +}; + +#endif // DIRMANAGER_H diff --git a/src/utils/downloadmanager.cpp b/src/utils/downloadmanager.cpp deleted file mode 100644 index a960cc23..00000000 --- a/src/utils/downloadmanager.cpp +++ /dev/null @@ -1,315 +0,0 @@ -/** - * @file downloadmanager.cpp - * @brief Implementation file for DownloadManager - */ - -#include "downloadmanager.h" - -DownloadManager::DownloadManager(QObject* parent) - : QObject(parent) - , m_netMan{ new QNetworkAccessManager(this) } -{ - connect(m_netMan, - &QNetworkAccessManager::finished, - this, - &DownloadManager::finishupTask); - - m_versionReq.setUrl(QUrl::fromEncoded( - "https://raw.githubusercontent.com/0xzer0x/quran-companion/main/VERSION")); - m_versionReq.setTransferTimeout(1500); - m_versionReq.setAttribute(QNetworkRequest::User, 1); -} - -void -DownloadManager::getLatestVersion() -{ - m_versionReply = m_netMan->get(m_versionReq); - m_versionReply->ignoreSslErrors(); -} - -void -DownloadManager::addToQueue(DownloadType type, QPair info) -{ - m_downloadQueue.enqueue(QPair>(type, info)); -} - -void -DownloadManager::addToQueue(int reciter, int surah) -{ - m_downloadQueue.enqueue(QPair>( - Recitation, QPair(reciter, surah))); -} - -void -DownloadManager::startQueue() -{ - if (!m_isDownloading) { - processTaskQueue(); - emit downloadStarted(); - } -} - -void -DownloadManager::stopQueue() -{ - if (m_isDownloading) { - cancelCurrentTask(); - m_isDownloading = false; - m_downloadQueue.clear(); - m_taskQueue.clear(); - } -} - -void -DownloadManager::cancelCurrentTask() -{ - if (m_activeTask.reply == nullptr) - return; - - m_activeTask.reply->abort(); - m_activeTask.reply->close(); - emit downloadCanceled(); -} - -void -DownloadManager::enqeueQCF() -{ - static const QString base = - "https://github.com/0xzer0x/quran-companion/raw/main/extras/"; - QString path; - DownloadTask t; - for (int i = 1; i <= 604; i++) { - path = QString("QCFV2/QCF2%0.ttf") - .arg(QString::number(i).rightJustified(3, '0')); - t.metainfo = { -1, -1, i }; - t.metainfo.squeeze(); - t.downloadPath.setFile(m_downloadsDir.absoluteFilePath(path)); - t.link = QUrl::fromEncoded((base + path).toLatin1()); - m_taskQueue.enqueue(t); - } -} - -void -DownloadManager::enqeueTask(QPair info) -{ - static const QString base = - "https://github.com/0xzer0x/quran-companion/raw/main/extras/"; - QString path; - if (info.first) - path = "translations/" + m_trList.at(info.second).filename; - else - path = "tafasir/" + m_tafasirList.at(info.second).filename; - DownloadTask t; - t.metainfo = { info.first, info.second, 0 }; - t.metainfo.squeeze(); - t.link = QUrl::fromEncoded((base + path).toLatin1()); - t.downloadPath.setFile(m_downloadsDir.absoluteFilePath(path)); - m_taskQueue.enqueue(t); -} - -void -DownloadManager::enqeueTask(int reciterIdx, int surah, int verse) -{ - static const QString path = "recitations/%0/%1.mp3"; - DownloadTask t; - t.metainfo = { reciterIdx, surah, verse }; - t.metainfo.squeeze(); - t.link = downloadUrl(reciterIdx, surah, verse); - t.downloadPath.setFile(m_downloadsDir.absoluteFilePath( - path.arg(m_recitersList.at(reciterIdx).baseDirName, - QString::number(surah).rightJustified(3, '0') + - QString::number(verse).rightJustified(3, '0')))); - - m_taskQueue.enqueue(t); -} - -void -DownloadManager::processDownloadQueue() -{ - if (m_downloadQueue.empty()) { - m_isDownloading = false; - return; - } - - m_isDownloading = true; - QPair> current = m_downloadQueue.dequeue(); - QPair& info = current.second; - m_activeType = current.first; - if (current.first == QCF) { - m_activeTotal = 604; - enqeueQCF(); - } else if (current.first == Recitation) { - m_activeTotal = m_dbMgr->getSurahVerseCount(info.second); - for (int v = 1; v <= m_activeTotal; v++) - enqeueTask(info.first, info.second, v); - } else if (current.first == File) { - m_activeTotal = 1; - enqeueTask(info); - } -} - -void -DownloadManager::processTaskQueue() -{ - if (m_taskQueue.empty()) { - processDownloadQueue(); - if (!m_isDownloading) - return; - } - - m_activeTask = m_taskQueue.dequeue(); - - while (m_activeTask.downloadPath.exists()) { - if (m_activeTask.metainfo[2] == m_activeTotal || m_activeType == File) { - emit downloadProgressed(m_activeTotal, m_activeTotal); - emit filesFound(m_activeType, m_activeTask.metainfo); - } - - if (m_taskQueue.empty()) { - processDownloadQueue(); - if (!m_isDownloading) - return; - } - - m_activeTask = m_taskQueue.dequeue(); - } - - QNetworkRequest req(m_activeTask.link); - m_activeTask.reply = m_netMan->get(req); - m_activeTask.reply->ignoreSslErrors(); - m_downloadStart = QTime::currentTime(); - - connect(m_activeTask.reply, - &QNetworkReply::downloadProgress, - this, - &DownloadManager::downloadProgress); -} - -void -DownloadManager::downloadProgress(qint64 bytes, qint64 total) -{ - if (m_activeType == File) { - if (!m_activeTask.metainfo[2]) - m_activeTotal = total / 1024; - m_activeTask.metainfo[2] = bytes / 1024; - emit downloadProgressed(m_activeTask.metainfo[2], m_activeTotal); - } - - int secs = m_downloadStart.secsTo(QTime::currentTime()); - if (secs < 1) - secs = 1; - - int speedPerSec = bytes / secs; - QString unit = tr("bytes"); - if (speedPerSec >= 1024) { - unit = tr("KB"); - speedPerSec /= 1024; - } - - if (speedPerSec >= 1024) { - unit = tr("MB"); - speedPerSec /= 1024; - } - - emit downloadSpeedUpdated(speedPerSec, unit); -} - -void -DownloadManager::finishupTask(QNetworkReply* replyData) -{ - if (replyData->request().attribute(QNetworkRequest::User).toInt() == 1) - return handleVersionReply(); - - if (m_activeTask.reply->error() != QNetworkReply::NoError) - return handleConError(m_activeTask.reply->error()); - - saveFile(replyData); - - emit downloadProgressed(m_activeTask.metainfo[2], m_activeTotal); - if (m_activeTask.metainfo[2] == m_activeTotal) { - emit downloadCompleted(m_activeType, m_activeTask.metainfo); - } - - disconnect(m_activeTask.reply, - &QNetworkReply::downloadProgress, - this, - &DownloadManager::downloadProgress); - - processTaskQueue(); -} - -bool -DownloadManager::saveFile(QNetworkReply* data) -{ - QFile localFile(m_activeTask.downloadPath.absoluteFilePath()); - - if (!localFile.open(QIODevice::WriteOnly)) { - qWarning() << "Couldn't open file:" << m_activeTask.downloadPath; - return false; - } - - const QByteArray fdata = data->readAll(); - m_activeTask.reply->close(); - - localFile.write(fdata); - localFile.close(); - - return true; -} - -QUrl -DownloadManager::downloadUrl(const int reciterIdx, - const int surah, - const int verse) const -{ - const Reciter& r = m_recitersList.at(reciterIdx); - QString url = r.baseUrl; - if (r.useId) - url.append(QString::number(m_dbMgr->getVerseId(surah, verse)) + ".mp3"); - else - url.append(QString::number(surah).rightJustified(3, '0') + - QString::number(verse).rightJustified(3, '0') + ".mp3"); - - return QUrl::fromEncoded(url.toLatin1()); -} - -void -DownloadManager::handleConError(QNetworkReply::NetworkError err) -{ - switch (err) { - case QNetworkReply::OperationCanceledError: - qInfo() << m_activeTask.reply->errorString(); - break; - - default: - qInfo() << m_activeTask.reply->errorString(); - emit downloadErrored(m_activeType, m_activeTask.metainfo); - } -} - -void -DownloadManager::handleVersionReply() -{ - int status = - m_versionReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); - if (status == 200) - emit latestVersionFound(m_versionReply->readAll().trimmed()); -} - -QNetworkAccessManager* -DownloadManager::netMan() const -{ - return m_netMan; -} - -DownloadManager::DownloadTask -DownloadManager::currentTask() const -{ - return m_activeTask; -} - -bool -DownloadManager::isDownloading() const -{ - return m_isDownloading; -} diff --git a/src/utils/downloadmanager.h b/src/utils/downloadmanager.h deleted file mode 100644 index 79692e6f..00000000 --- a/src/utils/downloadmanager.h +++ /dev/null @@ -1,273 +0,0 @@ -/** - * @file downloadmanager.h - * @brief Header file for DownloadManager - */ - -#ifndef DOWNLOADMANAGER_H -#define DOWNLOADMANAGER_H - -#include "../globals.h" -#include "dbmanager.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/** - * @brief DownloadManager class is responsible for downloading files and - * preforming web requests - */ -class DownloadManager : public QObject -{ - Q_OBJECT -public: - /** - * @brief DownloadTask struct represents a single verse file download task - * @details downloads are separated into 3 different types - * DownloadType::Recitation - MP3 recitation for a single verse - reciter - * combination - * DownloadType::QCF - QCF v2 font files - * DownloadType::File - single file download, used for downloading tafsir and - * translation DB files - */ - struct DownloadTask - { - /** - * @brief metainfo vector for storing information about the download task - * @details data in the metainfo QList depends on the DownloadType - * DownloadType::Recitation - { reciter, surah, verse } - * DownloadType::QCF - { -1, -1, page } - * DownloadType::File - { k, idx, bytes } where - * k: kind of file (0 for tafsir, 1 for translation) - * idx: index of the file in its corresponding global QList - * bytes: is the current number of bytes downloaded (updated automatically) - */ - QList metainfo; - /** - * @brief download link for the verse - */ - QUrl link; - /** - * @brief reply data of the web request - */ - QNetworkReply* reply = nullptr; - /** - * @brief QFileInfo representing the path to save the file to - */ - QFileInfo downloadPath; - }; - - /** - * @brief Class constructor - * @param parent - pointer to parent widget - */ - explicit DownloadManager(QObject* parent = nullptr); - /** - * @brief gets the latest release of the application from the github repo - */ - void getLatestVersion(); - /** - * @brief getter for m_isDownloading - * @return boolean - */ - bool isDownloading() const; - /** - * @brief getter for m_currentTask - * @return DownloadTask - */ - DownloadTask currentTask() const; - /** - * @brief getter for m_netMan - * @return QNetworkAccessManager* - */ - QNetworkAccessManager* netMan() const; - -public slots: - /** - * @brief starts the download queue to process download tasks - */ - void startQueue(); - /** - * @brief stops the download process - */ - void stopQueue(); - /** - * @brief cancels the currently active download task - */ - void cancelCurrentTask(); - /** - * @brief process download queue front task. sets the networkReply for the - * current task - */ - void processTaskQueue(); - /** - * @brief process the download group queue by adding appropriate download - * tasks according to the DownloadType and metainfo - */ - void processDownloadQueue(); - /** - * @brief enqueues an entry in the download group queue - * @param type - DownloadType of group - * @param info - additional info of the download required - */ - void addToQueue(DownloadType type, QPair info = { -1, -1 }); - /** - * @brief overload to enqueue a Recitation entry in the download group queue - * @param reciter - reciter index in Globals::recitersList - * @param surah - surah number to download - */ - void addToQueue(int reciter, int surah); - /** - * @brief calculate download speed and emit signal for UI component to update - * its value - * @param bytes - bytes downloaded so far - * @param total - total bytes - */ - void downloadProgress(qint64 bytes, qint64 total); - /** - * @brief handle finished tasks and emit the correct signal - * @param replyData - received data from request - */ - void finishupTask(QNetworkReply* replyData); - /** - * @brief save downloaded verse file locally - * @param data - downloaded binary data - * @return boolean value to indicate successful write operation - */ - bool saveFile(QNetworkReply* data); - -signals: - /** - * @fn void latestVersionFound(QString) - * @brief Emitted when the application version is fetched from github - * @param appVer - application version string - */ - void latestVersionFound(QString appVer); - /** - * @fn void downloadStarted() - * @brief Emitted when the download queue begins processing - */ - void downloadStarted(); - /** - * @fn void downloadCanceled() - */ - void downloadCanceled(); - /** - * @fn void downloadProgressed(int, int) - * @brief Emitted when a download task from the queue completed - */ - void downloadProgressed(int downloaded, int total); - /** - * @fn void downloadSpeedUpdated(int, QString) - * @brief Emitted to signal UI change for the displayed download speed - */ - void downloadSpeedUpdated(int valuePerSec, QString unit); - /** - * @fn void downloadCompleted(int, int) - * @brief Emitted when the currently active download group is completed - */ - void downloadCompleted(DownloadType type, const QList& metainfo); - /** - * @fn void downloadErrored(int, int) - */ - void downloadErrored(DownloadType type, const QList& metainfo); - /** - * @fn void filesFound(int, int) - * @brief Emitted when the current surah verses are found in recitations - * directory - */ - void filesFound(DownloadType type, const QList& metainfo); - -private: - const QDir& m_downloadsDir = Globals::downloadsDir; - const QList& m_recitersList = Globals::recitersList; - const QList& m_tafasirList = Globals::tafasirList; - const QList& m_trList = Globals::translationsList; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); - /** - * @brief generate download url for specified verse using the reciter download - * url - * @param reciterIdx - ::Globals::recitersList index for the reciter - * @param surah - surah number - * @param verse - verse number - * @return - */ - QUrl downloadUrl(const int reciterIdx, - const int surah, - const int verse) const; - /** - * @brief enqueues QCF font file tasks - */ - void enqeueQCF(); - /** - * @brief enqueues a File task based on the given info - * @param info - */ - void enqeueTask(QPair info); - /** - * @brief create a DownloadTask and add it to the download Queue - * @param reciterIdx - ::Globals::recitersList index for the reciter - * @param surah - surah number - * @param verse - verse number - */ - void enqeueTask(int reciterIdx, int surah, int verse); - - /** - * @brief emit signal according to the download error that occured - * @param err - network error received - */ - void handleConError(QNetworkReply::NetworkError err); - /** - * @brief emit signal with the latest application version - */ - void handleVersionReply(); - /** - * @brief boolean value representing the download state - */ - bool m_isDownloading = false; - /** - * @brief the total count of files in the current group download / total bytes - * if downloading a single file - */ - int m_activeTotal; - QNetworkRequest m_versionReq; - /** - * @brief QNetworkReply for version info request - */ - QNetworkReply* m_versionReply; - /** - * @brief QNetwrokAccessManager instance responsible for sending download - * requests - */ - QNetworkAccessManager* m_netMan; - /** - * @brief the currently active DownloadTask - */ - DownloadTask m_activeTask; - /** - * @brief currently active DownloadType - */ - DownloadType m_activeType = Recitation; - /** - * @brief download group queue, used for quickly adding downloads that will be - * expanding into separate DownloadTask (s) when its processed - */ - QQueue>> m_downloadQueue; - /** - * @brief individual DownloadTask queue - */ - QQueue m_taskQueue; - /** - * @brief QTime object to get the download start time, used in calculating - * download speed - */ - QTime m_downloadStart; -}; - -#endif // DOWNLOADMANAGER_H diff --git a/src/utils/fontmanager.cpp b/src/utils/fontmanager.cpp new file mode 100644 index 00000000..f78da54a --- /dev/null +++ b/src/utils/fontmanager.cpp @@ -0,0 +1,105 @@ +#include "fontmanager.h" +#include "configuration.h" +#include +#include + +FontManager& +FontManager::getInstance() +{ + static FontManager fontmanager; + return fontmanager; +} + +FontManager::FontManager() + : m_dirMgr(DirManager::getInstance()) + , m_config(Configuration::getInstance()) +{ +} + +void +FontManager::loadFonts() +{ + loadUiFonts(); + loadQcf(); +} + +void +FontManager::loadQcf() +{ + QFontDatabase::addApplicationFont( + m_dirMgr.fontsDir().filePath("QCFV1/QCF_BSML.ttf")); + switch (m_config.qcfVersion()) { + case 1: + m_dirMgr.setFontsDir(m_dirMgr.fontsDir().absoluteFilePath("QCFV1")); + m_qcfFontPrefix = "QCF_P"; + break; + case 2: + m_dirMgr.setFontsDir(m_dirMgr.downloadsDir().absolutePath() + "/QCFV2"); + m_qcfFontPrefix = "QCF2"; + break; + } + + // add required fonts + for (int i = 1; i < 605; i++) { + QString fontName = pageFontname(i) + ".ttf"; + + if (m_config.qcfVersion() == 2 && !m_dirMgr.fontsDir().exists(fontName)) { + m_config.settings().setValue("Reader/QCF", 1); + m_config.settings().sync(); + qFatal() << m_dirMgr.fontsDir().filePath(fontName) + << " font file not found, fallback to QCF v1"; + } else + QFontDatabase::addApplicationFont(m_dirMgr.fontsDir().filePath(fontName)); + } +} + +void +FontManager::loadUiFonts() +{ + // ui fonts + foreach (const QFileInfo& font, + m_dirMgr.fontsDir().entryInfoList(QDir::Files)) + QFontDatabase::addApplicationFont(font.absoluteFilePath()); + // set default UI fonts to use + QStringList uiFonts; + uiFonts << "Noto Sans Display" + << "Expo Arabic"; + qApp->setFont(QFont(uiFonts, qApp->font().pointSize())); +} + +bool +FontManager::qcfExists() +{ + QString filename = "QCFV2/QCF2%0.ttf"; + for (int i = 1; i <= 604; i++) { + if (!m_dirMgr.downloadsDir().exists( + filename.arg(QString::number(i).rightJustified(3, '0')))) + return false; + } + + return true; +} + +QString +FontManager::pageFontname(int page) +{ + return m_qcfFontPrefix + QString::number(page).rightJustified(3, '0'); +} + +QString +FontManager::verseFontname(Configuration::VerseType type, int page) +{ + QString fontname; + switch (type) { + case Configuration::Qcf: + fontname = pageFontname(page); + break; + case Configuration::Uthmanic: + fontname = "kfgqpc_hafs_uthmanic _script"; + break; + case Configuration::Annotated: + fontname = "Emine"; + break; + } + return fontname; +} diff --git a/src/utils/fontmanager.h b/src/utils/fontmanager.h new file mode 100644 index 00000000..5cc2edc7 --- /dev/null +++ b/src/utils/fontmanager.h @@ -0,0 +1,26 @@ +#ifndef FONTMANAGER_H +#define FONTMANAGER_H + +#include "configuration.h" +#include "dirmanager.h" +#include + +class FontManager +{ +public: + static FontManager& getInstance(); + QString pageFontname(int page); + QString verseFontname(Configuration::VerseType type, int page); + void loadFonts(); + bool qcfExists(); + +private: + FontManager(); + void loadQcf(); + void loadUiFonts(); + Configuration& m_config; + DirManager& m_dirMgr; + QString m_qcfFontPrefix; +}; + +#endif // FONTMANAGER_H diff --git a/src/utils/jsondataexporter.cpp b/src/utils/jsondataexporter.cpp new file mode 100644 index 00000000..f24cd63d --- /dev/null +++ b/src/utils/jsondataexporter.cpp @@ -0,0 +1,92 @@ +#include "jsondataexporter.h" +#include + +JsonDataExporter::JsonDataExporter() {} + +void +JsonDataExporter::exportBookmarks() +{ + QJsonArray bookmarks; + QList all = m_bookmarksDb.bookmarkedVerses(); + foreach (const Verse& v, all) { + bookmarks.append(verseJson(v)); + } + + m_fileObj["bookmarks"] = bookmarks; +} + +void +JsonDataExporter::exportKhatmah() +{ + QJsonArray khatmah; + QList ids = m_bookmarksDb.getAllKhatmah(); + foreach (const int id, ids) { + Verse v; + m_bookmarksDb.loadVerse(id, v); + QString name = m_bookmarksDb.getKhatmahName(id); + khatmah.append(khatmahJson({ name, v })); + } + + m_fileObj["khatmah"] = khatmah; +} + +void +JsonDataExporter::exportThoughts() +{ + QJsonArray thoughts; + QList> all = m_bookmarksDb.allThoughts(); + for (const QPair& item : all) { + thoughts.append(thoughtJson(item)); + } + m_fileObj["thoughts"] = thoughts; +} + +QJsonObject +JsonDataExporter::verseJson(const Verse& v) +{ + QJsonObject obj; + obj["page"] = v.page(); + obj["surah"] = v.surah(); + obj["number"] = v.number(); + return obj; +} + +QJsonObject +JsonDataExporter::khatmahJson(const QPair& entry) +{ + QJsonObject obj; + obj["name"] = entry.first; + obj["verse"] = verseJson(entry.second); + return obj; +} + +QJsonObject +JsonDataExporter::thoughtJson(const QPair& entry) +{ + QJsonObject obj; + obj["verse"] = verseJson(entry.first); + obj["text"] = entry.second; + return obj; +} + +void +JsonDataExporter::setFile(QString path) +{ + m_file.setFile(path); +} + +bool +JsonDataExporter::save() +{ + QFile jsonFile(m_file.absoluteFilePath()); + if (!jsonFile.open(QIODevice::WriteOnly)) { + qWarning() << "Failed to open JSON file for writing"; + emit UserDataExporter::error( + IOError, "Failed to open json file: " + m_file.absoluteFilePath()); + return false; + } + + jsonFile.write(QJsonDocument(m_fileObj).toJson()); + jsonFile.close(); + return true; +} diff --git a/src/utils/jsondataexporter.h b/src/utils/jsondataexporter.h new file mode 100644 index 00000000..05c42f79 --- /dev/null +++ b/src/utils/jsondataexporter.h @@ -0,0 +1,29 @@ +#ifndef JSONDATAEXPORTER_H +#define JSONDATAEXPORTER_H + +#include +#include +#include +#include +#include + +class JsonDataExporter : public UserDataExporter +{ +public: + JsonDataExporter(); + void exportBookmarks(); + void exportKhatmah(); + void exportThoughts(); + void setFile(QString path); + bool save(); + +private: + BookmarksDb& m_bookmarksDb = BookmarksDb::getInstance(); + QJsonObject verseJson(const Verse& v); + QJsonObject khatmahJson(const QPair& entry); + QJsonObject thoughtJson(const QPair& entry); + QJsonObject m_fileObj; + QFileInfo m_file; +}; + +#endif // JSONDATAEXPORTER_H diff --git a/src/utils/jsondataimporter.cpp b/src/utils/jsondataimporter.cpp new file mode 100644 index 00000000..00d60f01 --- /dev/null +++ b/src/utils/jsondataimporter.cpp @@ -0,0 +1,191 @@ +#include "jsondataimporter.h" +#include +#include + +JsonDataImporter::JsonDataImporter() {} + +void +JsonDataImporter::importBookmarks() +{ + if (!validArray("bookmarks")) + return; + QJsonArray arr = m_fileObj.value("bookmarks").toArray(); + foreach (const QJsonValue& item, arr) { + Verse bookmark = verseFromJson(item.toObject()); + if (bookmark.page() < 1 || bookmark.surah() < 1 || bookmark.number() < 1) + continue; + if (!m_bookmarksDb.isBookmarked(bookmark)) + m_bookmarksDb.addBookmark(bookmark, true); + } +} + +void +JsonDataImporter::importKhatmah() +{ + if (!validArray("khatmah")) + return; + QJsonArray arr = m_fileObj.value("khatmah").toArray(); + foreach (const QJsonValue& item, arr) { + QPair khatmah = khatmahFromJson(item.toObject()); + if (!khatmah.first.isEmpty()) + m_bookmarksDb.addKhatmah(khatmah.second, khatmah.first); + } +} + +void +JsonDataImporter::importThoughts() +{ + if (!validArray("thoughts")) + return; + QJsonArray arr = m_fileObj.value("thoughts").toArray(); + foreach (const QJsonValue& item, arr) { + QPair thought = thoughtFromJson(item.toObject()); + if (!thought.second.isEmpty()) + m_bookmarksDb.saveThoughts(thought.first, thought.second); + } +} + +bool +JsonDataImporter::validArray(QString key) +{ + if (!m_fileObj.contains(key)) { + emit UserDataImporter::error(MissingKeyError, "Missing key: " + key); + return false; + } + if (!m_fileObj.value(key).isArray()) { + emit UserDataImporter::error(InvalidValueError, "Invalid array: " + key); + return false; + } + return true; +} + +bool +JsonDataImporter::validVerse(const QJsonObject& obj) +{ + if (!obj.contains("page") || !obj.contains("surah") || + !obj.contains("number")) { + emit UserDataImporter::error(MissingKeyError, "Missing verse key"); + return false; + } + + int page = obj.value("page").toInt(), surah = obj.value("surah").toInt(), + number = obj.value("number").toInt(); + if (page < 1 || page > 604 || surah < 1 || surah > 114 || number < 1 || + number > 286) { + emit UserDataImporter::error(InvalidValueError, "Invalid verse values"); + return false; + } + + return true; +} + +bool +JsonDataImporter::validKhatmah(const QJsonObject& obj) +{ + if (!obj.contains("name") || !obj.contains("verse")) { + emit UserDataImporter::error(MissingKeyError, "Missing khatmah keys"); + return false; + } + + if (!obj.value("name").isString() || + !validVerse(obj.value("verse").toObject())) { + emit UserDataImporter::error(InvalidValueError, "Invalid khatmah values"); + return false; + } + + return true; +} + +bool +JsonDataImporter::validThought(const QJsonObject& obj) +{ + if (!obj.contains("text") || !obj.contains("verse")) { + emit UserDataImporter::error(MissingKeyError, "Missing thought keys"); + return false; + } + + if (!obj.value("text").isString() || + !validVerse(obj.value("verse").toObject())) { + emit UserDataImporter::error(InvalidValueError, "Invalid thought values"); + return false; + } + + return true; +} + +Verse +JsonDataImporter::verseFromJson(const QJsonObject& obj) +{ + Verse v; + if (!validVerse(obj)) + return v; + + v.setPage(obj.value("page").toInt()); + v.setSurah(obj.value("surah").toInt()); + v.setNumber(obj.value("number").toInt()); + return v; +} + +QPair +JsonDataImporter::khatmahFromJson(const QJsonObject& obj) +{ + QString name; + Verse v(1, 1, 1); + if (!validKhatmah(obj)) + return { name, v }; + + name = obj.value("name").toString(); + v = verseFromJson(obj.value("verse").toObject()); + return { name, v }; +} + +QPair +JsonDataImporter::thoughtFromJson(const QJsonObject& obj) +{ + Verse v(1, 1, 1); + QString text; + if (!validThought(obj)) + return { v, text }; + + v = verseFromJson(obj.value("verse").toObject()); + text = obj.value("text").toString(); + return { v, text }; +} + +void +JsonDataImporter::setFile(QString path) +{ + m_file.setFile(path); +} + +bool +JsonDataImporter::read() +{ + QFile jsonFile(m_file.absoluteFilePath()); + if (!jsonFile.open(QIODevice::ReadOnly)) { + qWarning() << "Failed to open json file during import"; + emit UserDataImporter::error( + IOError, "Failed to open json file: " + m_file.absoluteFilePath()); + return false; + } + + QJsonParseError* err = nullptr; + QJsonDocument document = QJsonDocument::fromJson(jsonFile.readAll(), err); + if (document.isNull()) { + qWarning() << "Failed to parse json file"; + emit UserDataImporter::error( + ParseError, "Failed to parse json file: " + m_file.absoluteFilePath()); + if (err) + qWarning() << "Error string:" << err->errorString(); + return false; + } + + m_fileObj = document.object(); + return true; +} + +bool +JsonDataImporter::fileContains(QString key) +{ + return m_fileObj.contains(key); +} diff --git a/src/utils/jsondataimporter.h b/src/utils/jsondataimporter.h new file mode 100644 index 00000000..2ba9158b --- /dev/null +++ b/src/utils/jsondataimporter.h @@ -0,0 +1,34 @@ +#ifndef JSONDATAIMPORTER_H +#define JSONDATAIMPORTER_H + +#include +#include +#include +#include +#include + +class JsonDataImporter : public UserDataImporter +{ +public: + JsonDataImporter(); + void importBookmarks() override; + void importKhatmah() override; + void importThoughts() override; + void setFile(QString path) override; + bool fileContains(QString key) override; + bool read() override; + +private: + BookmarksDb& m_bookmarksDb = BookmarksDb::getInstance(); + bool validArray(const QString key); + bool validVerse(const QJsonObject& obj); + bool validKhatmah(const QJsonObject& obj); + bool validThought(const QJsonObject& obj); + Verse verseFromJson(const QJsonObject& obj); + QPair khatmahFromJson(const QJsonObject& obj); + QPair thoughtFromJson(const QJsonObject& obj); + QFileInfo m_file; + QJsonObject m_fileObj; +}; + +#endif // JSONDATAIMPORTER_H diff --git a/src/utils/shortcuthandler.cpp b/src/utils/shortcuthandler.cpp index cea724ea..825238de 100644 --- a/src/utils/shortcuthandler.cpp +++ b/src/utils/shortcuthandler.cpp @@ -4,14 +4,60 @@ */ #include "shortcuthandler.h" +#include +#include +#include +using std::make_pair; -ShortcutHandler::ShortcutHandler(QObject* parent) - : QObject(parent) +ShortcutHandler& +ShortcutHandler::getInstance() +{ + static ShortcutHandler handler; + return handler; +} + +ShortcutHandler::ShortcutHandler() + : m_config(Configuration::getInstance()) +{ +} + +void +ShortcutHandler::populateDescriptionMap() +{ + QFile shortcuts(":/resources/shortcuts.xml"); + if (!shortcuts.open(QIODevice::ReadOnly)) + qCritical("Couldn't Open Shortcuts XML"); + + m_config.settings().beginGroup("Shortcuts"); + QXmlStreamReader reader(&shortcuts); + while (!reader.atEnd() && !reader.hasError()) { + QXmlStreamReader::TokenType token = reader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (reader.name().toString() == "shortcut") { + QString key = reader.attributes().value("key").toString(); + QString defBind = reader.attributes().value("default").toString(); + QString desc = + qApp->translate("SettingsDialog", + reader.attributes().value("description").toLatin1()); + + m_shortcutsDescription.insert(key, desc); + if (!m_config.settings().contains(key)) + m_config.settings().setValue(key, defBind); + } + } + } + + m_config.settings().endGroup(); + shortcuts.close(); +} + +void +ShortcutHandler::createShortcuts(QObject* context) { foreach (const QString& key, m_shortcutsDescription.keys()) { - QKeySequence seq = - qvariant_cast(m_settings->value("Shortcuts/" + key)); - m_shortcuts.insert(key, new QShortcut(seq, parent)); + QKeySequence seq = qvariant_cast( + m_config.settings().value("Shortcuts/" + key)); + m_shortcuts.insert(key, new QShortcut(seq, context)); } m_shortcuts.value("TogglePlayback")->setContext(Qt::ApplicationShortcut); m_shortcuts.value("BookmarkCurrent")->setContext(Qt::ApplicationShortcut); @@ -22,114 +68,53 @@ ShortcutHandler::ShortcutHandler(QObject* parent) void ShortcutHandler::setupConnections() { - connect(m_shortcuts.value("Quit"), &QShortcut::activated, this, []() { - emit QApplication::exit(); - }); - connect(m_shortcuts.value("TogglePlayerControls"), - &QShortcut::activated, - this, - &ShortcutHandler::togglePlayerControls); - connect(m_shortcuts.value("ToggleReaderView"), - &QShortcut::activated, - this, - &ShortcutHandler::toggleReaderView); - connect(m_shortcuts.value("ToggleMenubar"), - &QShortcut::activated, - this, - &ShortcutHandler::toggleMenubar); - connect(m_shortcuts.value("ToggleNavDock"), - &QShortcut::activated, - this, - &ShortcutHandler::toggleNavDock); - connect(m_shortcuts.value("TogglePlayback"), - &QShortcut::activated, - this, - &ShortcutHandler::togglePlayback); - connect(m_shortcuts.value("VolumeUp"), - &QShortcut::activated, - this, - &ShortcutHandler::incrementVolume); - connect(m_shortcuts.value("VolumeDown"), - &QShortcut::activated, - this, - &ShortcutHandler::decrementVolume); - connect(m_shortcuts.value("BookmarkCurrent"), - &QShortcut::activated, - this, - &ShortcutHandler::bookmarkCurrent); - connect(m_shortcuts.value("NextPage"), - &QShortcut::activated, - this, - &ShortcutHandler::nextPage); - connect(m_shortcuts.value("PrevPage"), - &QShortcut::activated, - this, - &ShortcutHandler::prevPage); - connect(m_shortcuts.value("NextVerse"), - &QShortcut::activated, - this, - &ShortcutHandler::nextVerse); - connect(m_shortcuts.value("PrevVerse"), - &QShortcut::activated, - this, - &ShortcutHandler::prevVerse); - connect(m_shortcuts.value("NextJuz"), - &QShortcut::activated, - this, - &ShortcutHandler::nextJuz); - connect(m_shortcuts.value("PrevJuz"), - &QShortcut::activated, - this, - &ShortcutHandler::prevJuz); - connect(m_shortcuts.value("NextSurah"), - &QShortcut::activated, - this, - &ShortcutHandler::nextSurah); - connect(m_shortcuts.value("PrevSurah"), - &QShortcut::activated, - this, - &ShortcutHandler::prevSurah); - connect(m_shortcuts.value("ZoomIn"), - &QShortcut::activated, - this, - &ShortcutHandler::zoomIn); - connect(m_shortcuts.value("ZoomOut"), - &QShortcut::activated, - this, - &ShortcutHandler::zoomOut); - connect(m_shortcuts.value("DownloaderDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openDownloads); - connect(m_shortcuts.value("BookmarksDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openBookmarks); - connect(m_shortcuts.value("KhatmahDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openKhatmah); - connect(m_shortcuts.value("SearchDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openSearch); - connect(m_shortcuts.value("SettingsDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openSettings); - connect(m_shortcuts.value("TafsirDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openTafsir); - connect(m_shortcuts.value("CopyDialog"), - &QShortcut::activated, - this, - &ShortcutHandler::openAdvancedCopy); + connect(m_shortcuts.value("Quit"), + &QShortcut::activated, + qApp, + &QApplication::quit); + for (const auto& connection : { + make_pair("TogglePlayerControls", + &ShortcutHandler::togglePlayerControls), + make_pair("ToggleReaderView", &ShortcutHandler::toggleReaderView), + make_pair("ToggleMenubar", &ShortcutHandler::toggleMenubar), + make_pair("ToggleNavDock", &ShortcutHandler::toggleNavDock), + make_pair("TogglePlayback", &ShortcutHandler::togglePlayback), + make_pair("VolumeUp", &ShortcutHandler::incrementVolume), + make_pair("VolumeDown", &ShortcutHandler::decrementVolume), + make_pair("BookmarkCurrent", &ShortcutHandler::bookmarkCurrent), + make_pair("NextPage", &ShortcutHandler::nextPage), + make_pair("PrevPage", &ShortcutHandler::prevPage), + make_pair("NextJuz", &ShortcutHandler::nextJuz), + make_pair("PrevJuz", &ShortcutHandler::prevJuz), + make_pair("NextSurah", &ShortcutHandler::nextSurah), + make_pair("PrevSurah", &ShortcutHandler::prevSurah), + make_pair("NextVerse", &ShortcutHandler::nextVerse), + make_pair("PrevVerse", &ShortcutHandler::prevVerse), + make_pair("ZoomIn", &ShortcutHandler::zoomIn), + make_pair("ZoomOut", &ShortcutHandler::zoomOut), + make_pair("DownloaderDialog", &ShortcutHandler::openDownloads), + make_pair("BookmarksDialog", &ShortcutHandler::openBookmarks), + make_pair("KhatmahDialog", &ShortcutHandler::openKhatmah), + make_pair("SearchDialog", &ShortcutHandler::openSearch), + make_pair("SettingsDialog", &ShortcutHandler::openSettings), + make_pair("ContentDialog", &ShortcutHandler::openContent), + make_pair("CopyDialog", &ShortcutHandler::openAdvancedCopy), + }) { + connect(m_shortcuts.value(connection.first), + &QShortcut::activated, + this, + connection.second); + } } void ShortcutHandler::shortcutChanged(QString key) { - m_shortcuts.value(key)->setKey( - qvariant_cast(m_settings->value("Shortcuts/" + key))); + m_shortcuts.value(key)->setKey(qvariant_cast( + m_config.settings().value("Shortcuts/" + key))); +} + +const QMap &ShortcutHandler::shortcutsDescription() const +{ + return m_shortcutsDescription; } diff --git a/src/utils/shortcuthandler.h b/src/utils/shortcuthandler.h index 3e48b3f7..6cf39387 100644 --- a/src/utils/shortcuthandler.h +++ b/src/utils/shortcuthandler.h @@ -6,7 +6,8 @@ #ifndef SHORTCUTHANDLER_H #define SHORTCUTHANDLER_H -#include "../globals.h" +#include "configuration.h" +#include #include #include #include @@ -22,12 +23,11 @@ class ShortcutHandler : public QObject { Q_OBJECT public: - /** - * @brief class constructor - * @param parent - pointer to parent widget that will recieve the shortcut - * events - */ - explicit ShortcutHandler(QObject* parent = nullptr); + static ShortcutHandler& getInstance(); + void populateDescriptionMap(); + void createShortcuts(QObject* context); + + const QMap& shortcutsDescription() const; public slots: /** @@ -60,13 +60,17 @@ public slots: void openDownloads(); void openSearch(); void openSettings(); - void openTafsir(); + void openContent(); void openAdvancedCopy(); private: - const QSettings* m_settings = Globals::settings; - const QMap& m_shortcutsDescription = - Globals::shortcutDescription; + Configuration& m_config; + /** + * @brief class constructor + * @param parent - pointer to parent widget that will recieve the shortcut + * events + */ + ShortcutHandler(); /** * @brief connect different QShortcut signals to their * corresponding signal in ShortcutHandler @@ -77,6 +81,7 @@ public slots: * settings name */ QHash m_shortcuts; + QMap m_shortcutsDescription; }; #endif // SHORTCUTHANDLER_H diff --git a/src/utils/stylemanager.cpp b/src/utils/stylemanager.cpp new file mode 100644 index 00000000..e35affe0 --- /dev/null +++ b/src/utils/stylemanager.cpp @@ -0,0 +1,97 @@ +#include "stylemanager.h" +#include +#include +#include +#include + +StyleManager& +StyleManager::getInstance() +{ + static StyleManager stylemanager; + return stylemanager; +} + +StyleManager::StyleManager() + : m_config(Configuration::getInstance()) +{ +} + +void +StyleManager::loadTheme() +{ + qApp->setStyle(QStyleFactory::create("Fusion")); + + QPalette themeColors; + QFile styles, palette; + switch (m_config.themeId()) { + case 0: + m_themeResources.setPath(":/resources/light/"); + styles.setFileName(m_themeResources.filePath("light.qss")); + palette.setFileName(m_themeResources.filePath("light.xml")); + break; + + case 1: + m_themeResources.setPath(":/resources/light/"); + styles.setFileName(m_themeResources.filePath("sepia.qss")); + palette.setFileName(m_themeResources.filePath("sepia.xml")); + break; + + case 2: + m_themeResources.setPath(":/resources/dark/"); + styles.setFileName(m_themeResources.filePath("dark.qss")); + palette.setFileName(m_themeResources.filePath("dark.xml")); + break; + } + + if (!palette.open(QIODevice::ReadOnly | QIODevice::Text)) { + qCritical() << "Couldn't Read Color palette xml"; + } + + QXmlStreamReader paletteReader(&palette); + QPalette::ColorGroup group = QPalette::All; + while (!paletteReader.atEnd() && !paletteReader.hasError()) { + QXmlStreamReader::TokenType token = paletteReader.readNext(); + if (token == QXmlStreamReader::StartElement) { + if (paletteReader.name().toString() == "enabled") + group = QPalette::All; + else if (paletteReader.name().toString() == "disabled") + group = QPalette::Disabled; + // color element + else { + QPalette::ColorRole role; + int red, green, blue; + role = static_cast( + paletteReader.attributes().value("role").toInt()); + red = paletteReader.attributes().value("red").toInt(); + green = paletteReader.attributes().value("green").toInt(); + blue = paletteReader.attributes().value("blue").toInt(); + + themeColors.setColor(group, role, QColor(red, green, blue)); + } + } + } + + palette.close(); + qApp->setPalette(themeColors); + + // load stylesheet + if (styles.open(QIODevice::ReadOnly)) { + qApp->setStyleSheet(styles.readAll()); + styles.close(); + } + + m_awesome = new fa::QtAwesome(qApp); + m_awesome->initFontAwesome(); +} + +fa::QtAwesome& +StyleManager::awesome() +{ + return *m_awesome; +} + +const QDir& +StyleManager::themeResources() const +{ + return m_themeResources; +} diff --git a/src/utils/stylemanager.h b/src/utils/stylemanager.h new file mode 100644 index 00000000..ba03b9f7 --- /dev/null +++ b/src/utils/stylemanager.h @@ -0,0 +1,26 @@ +#ifndef STYLEMANAGER_H +#define STYLEMANAGER_H + +#include +#include +#include +#include +#include "configuration.h" + +class StyleManager +{ +public: + static StyleManager& getInstance(); + + void loadTheme(); + fa::QtAwesome& awesome(); + const QDir& themeResources() const; + +private: + StyleManager(); + const Configuration& m_config; + QPointer m_awesome; + QDir m_themeResources; +}; + +#endif // STYLEMANAGER_H diff --git a/src/utils/notificationmanager.cpp b/src/utils/systemtray.cpp similarity index 52% rename from src/utils/notificationmanager.cpp rename to src/utils/systemtray.cpp index 6b4c0056..05868338 100644 --- a/src/utils/notificationmanager.cpp +++ b/src/utils/systemtray.cpp @@ -1,35 +1,37 @@ /** * @file notificationmanager.cpp - * @brief Implementation file for NotificationManager + * @brief Implementation file for SystemTray */ -#include "notificationmanager.h" +#include "systemtray.h" +#include -NotificationManager::NotificationManager(QObject* parent) - : QObject{ parent } - , m_sysTray{ new QSystemTrayIcon(this) } - , m_trayMenu{ new QMenu() } +SystemTray::SystemTray(QObject* parent) + : QObject(parent) + , m_sysTray(new QSystemTrayIcon(this)) + , m_trayMenu(new QMenu()) { addActions(); + setTooltip(qApp->translate("MainWindow", "Quran Companion")); m_sysTray->setContextMenu(m_trayMenu); m_sysTray->setIcon(QIcon(":/resources/tray.png")); m_sysTray->show(); } void -NotificationManager::notify(QString title, QString msg) +SystemTray::notify(QString title, QString msg) { m_sysTray->showMessage(title, msg); } void -NotificationManager::setTooltip(QString text) +SystemTray::setTooltip(QString text) { m_sysTray->setToolTip(text); } void -NotificationManager::addActions() +SystemTray::addActions() { QAction* togglePlay = new QAction(tr("Play/Pause recitation"), m_trayMenu); QAction* show = new QAction(tr("Show window"), m_trayMenu); @@ -50,20 +52,16 @@ NotificationManager::addActions() m_trayMenu->addSeparator(); m_trayMenu->addAction(exit); - connect(togglePlay, - &QAction::triggered, - this, - &NotificationManager::togglePlayback); - connect(show, &QAction::triggered, this, &NotificationManager::showWindow); - connect(hide, &QAction::triggered, this, &NotificationManager::hideWindow); - connect(prefs, &QAction::triggered, this, &NotificationManager::openPrefs); - connect(exit, &QAction::triggered, this, &NotificationManager::exit); - connect(about, &QAction::triggered, this, &NotificationManager::openAbout); - connect( - update, &QAction::triggered, this, &NotificationManager::checkForUpdates); + connect(togglePlay, &QAction::triggered, this, &SystemTray::togglePlayback); + connect(show, &QAction::triggered, this, &SystemTray::showWindow); + connect(hide, &QAction::triggered, this, &SystemTray::hideWindow); + connect(prefs, &QAction::triggered, this, &SystemTray::openPrefs); + connect(exit, &QAction::triggered, this, &SystemTray::exit); + connect(about, &QAction::triggered, this, &SystemTray::openAbout); + connect(update, &QAction::triggered, this, &SystemTray::checkForUpdates); } -NotificationManager::~NotificationManager() +SystemTray::~SystemTray() { delete m_trayMenu; } diff --git a/src/utils/notificationmanager.h b/src/utils/systemtray.h similarity index 79% rename from src/utils/notificationmanager.h rename to src/utils/systemtray.h index b80a6d47..903a8ee6 100644 --- a/src/utils/notificationmanager.h +++ b/src/utils/systemtray.h @@ -3,25 +3,24 @@ * @brief Header file for NotificationManager */ -#ifndef NOTIFICATIONMANAGER_H -#define NOTIFICATIONMANAGER_H +#ifndef SYSTEMTRAY_H +#define SYSTEMTRAY_H -#include "../globals.h" -#include "dbmanager.h" #include #include #include #include #include #include +#include #include #include /** - * @brief NotificationManager class is responsible for system tray functionality + * @brief SystemTray class is responsible for system tray functionality * and verse of the day notification */ -class NotificationManager : public QObject +class SystemTray : public QObject { Q_OBJECT @@ -30,8 +29,8 @@ class NotificationManager : public QObject * @brief Class constructor * @param parent - pointer to parent widget */ - explicit NotificationManager(QObject* parent = nullptr); - ~NotificationManager(); + explicit SystemTray(QObject* parent = nullptr); + ~SystemTray(); /** * @brief send a desktop notification message @@ -84,7 +83,6 @@ class NotificationManager : public QObject void openAbout(); private: - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); /** * @brief adds system tray actions and set their connections */ @@ -92,11 +90,11 @@ class NotificationManager : public QObject /** * @brief system tray context menu */ - QMenu* m_trayMenu; + QPointer m_trayMenu; /** * @brief QSystemTrayIcon instance */ - QSystemTrayIcon* m_sysTray; + QPointer m_sysTray; }; -#endif // NOTIFICATIONMANAGER_H +#endif // SYSTEMTRAY_H diff --git a/src/utils/verseplayer.cpp b/src/utils/verseplayer.cpp index ecd8a71e..aa452f70 100644 --- a/src/utils/verseplayer.cpp +++ b/src/utils/verseplayer.cpp @@ -5,15 +5,18 @@ #include "verseplayer.h" -VersePlayer::VersePlayer(QObject* parent, Verse initVerse, int reciterIdx) +VersePlayer::VersePlayer(QObject* parent, int reciterIdx) : QMediaPlayer(parent) - , m_activeVerse{ initVerse } - , m_reciter{ reciterIdx } - , m_output{ new QAudioOutput(this) } + , m_reciter(reciterIdx) + , m_output(new QAudioOutput(this)) + , m_activeVerse(Verse::getCurrent()) + , m_reciterDir( + DirManager::getInstance().downloadsDir().absoluteFilePath("recitations")) + , m_reciters(Reciter::reciters) { setAudioOutput(m_output); - m_reciterDir.cd(m_recitersList.at(m_reciter).baseDirName); + m_reciterDir.cd(m_reciters.at(m_reciter).baseDirName()); loadActiveVerse(); } @@ -44,12 +47,6 @@ VersePlayer::stop() QMediaPlayer::stop(); } -void -VersePlayer::setVerse(Verse& newVerse) -{ - m_activeVerse = newVerse; -} - void VersePlayer::changeUsedAudioDevice(QAudioDevice dev) { @@ -64,12 +61,12 @@ VersePlayer::setPlayerVolume(qreal volume) } QString -VersePlayer::constructVerseFilename(Verse v) +VersePlayer::constructVerseFilename(const Verse& v) { // construct verse mp3 filename e.g. 002005.mp3 QString filename; - filename.append(QString::number(v.surah).rightJustified(3, '0')); - filename.append(QString::number(v.number).rightJustified(3, '0')); + filename.append(QString::number(v.surah()).rightJustified(3, '0')); + filename.append(QString::number(v.number()).rightJustified(3, '0')); filename.append(".mp3"); return filename; @@ -85,13 +82,13 @@ VersePlayer::playCurrentVerse() bool VersePlayer::changeReciter(int reciterIdx) { - if (m_activeVerse.number == 0) - m_activeVerse.number = 1; + if (m_activeVerse.number() == 0) + m_activeVerse.setNumber(1); stop(); if (reciterIdx != m_reciter) { m_reciterDir.cdUp(); - m_reciterDir.cd(m_recitersList.at(reciterIdx).baseDirName); + m_reciterDir.cd(m_reciters.at(reciterIdx).baseDirName()); m_reciter = reciterIdx; } @@ -104,7 +101,7 @@ VersePlayer::setVerseFile(const QString& newVerseFilename) if (!m_reciterDir.exists(newVerseFilename)) { setSource(QUrl()); qDebug() << "file " + newVerseFilename + " is missing."; - emit missingVerseFile(m_reciter, m_activeVerse.surah); + emit missingVerseFile(m_reciter, m_activeVerse.surah()); return false; } @@ -117,20 +114,18 @@ VersePlayer::setVerseFile(const QString& newVerseFilename) bool VersePlayer::loadActiveVerse() { - if (m_activeVerse.number == 0) { - setSource(QUrl::fromLocalFile(m_recitersList.at(m_reciter).basmallahPath)); + if (m_activeVerse.number() == 0) { + setSource(QUrl::fromLocalFile(m_reciters.at(m_reciter).basmallahPath())); return true; } return setVerseFile(constructVerseFilename(m_activeVerse)); } -/* -------------------- Getters ----------------------- */ - QString VersePlayer::reciterName() const { - return m_recitersList.at(m_reciter).displayName; + return m_reciters.at(m_reciter).displayName(); } QAudioOutput* @@ -144,9 +139,3 @@ VersePlayer::verseFilename() const { return m_verseFile; } - -Verse -VersePlayer::activeVerse() const -{ - return m_activeVerse; -} diff --git a/src/utils/verseplayer.h b/src/utils/verseplayer.h index c3866d92..b1c6ab29 100644 --- a/src/utils/verseplayer.h +++ b/src/utils/verseplayer.h @@ -6,14 +6,15 @@ #ifndef VERSEPLAYER_H #define VERSEPLAYER_H -#include "../globals.h" -#include "dbmanager.h" #include #include #include #include #include #include +#include +#include +#include /*! * @brief VersePlayer class is responsible for the playback of Quran verse @@ -30,9 +31,7 @@ class VersePlayer : public QMediaPlayer * @param initVerse - inital ::Verse to load recitation for * @param reciterIdx - ::Globals::recitersList index for the reciter */ - explicit VersePlayer(QObject* parent = nullptr, - Verse initVerse = Verse{}, - int reciterIdx = 0); + explicit VersePlayer(QObject* parent, int reciterIdx); /** * @brief construct the verse filename for the ::Verse given using the @@ -40,12 +39,7 @@ class VersePlayer : public QMediaPlayer * @param v - ::Verse to get the filename of * @return QString of the filename */ - QString constructVerseFilename(Verse v); - /** - * @brief setter for the m_activeVerse attribute - * @param newVerse - ::Verse instance - */ - void setVerse(Verse& newVerse); + QString constructVerseFilename(const Verse& v); /** * @brief load the verse mp3 from the current reciter directory and set the * m_verseFile variable @@ -69,11 +63,6 @@ class VersePlayer : public QMediaPlayer * @return QString containing the verse filename */ QString verseFilename() const; - /** - * @brief getter for m_activeVerse - * @return ::Verse instance - */ - Verse activeVerse() const; /** * @brief getter for m_output, used for controlling audio output device * @return pointer to the used QAudioOutput object @@ -127,9 +116,9 @@ public slots: void missingVerseFile(int reciterIdx, int surah); private: - QDir m_reciterDir = Globals::downloadsDir.absoluteFilePath("recitations"); - const QList& m_recitersList = Globals::recitersList; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); + Verse& m_activeVerse; + QDir m_reciterDir; + const QList& m_reciters; /** * @brief boolean indicating whether the player is on or off, 'on' implies * that playback should continue in case of verse change @@ -139,10 +128,6 @@ public slots: * @brief ::Globals::recitersList index for the reciter */ int m_reciter = 0; - /** - * @brief ::Verse instance representing the current verse being played - */ - Verse m_activeVerse; /** * @brief current verse mp3 filename */ @@ -150,7 +135,7 @@ public slots: /** * @brief QAudioOutput used for playback */ - QAudioOutput* m_output; + QPointer m_output; }; #endif // VERSEPLAYER_H diff --git a/src/utils/versionchecker.cpp b/src/utils/versionchecker.cpp new file mode 100644 index 00000000..18ee6df0 --- /dev/null +++ b/src/utils/versionchecker.cpp @@ -0,0 +1,85 @@ +#include "versionchecker.h" +#include +#include +#include + +VersionChecker::VersionChecker(QObject* parent) + : QObject(parent) + , m_notifier(this) + , m_updateTool(QApplication::applicationDirPath() + QDir::separator() + + "QCMaintenanceTool.exe") +{ + m_versionReq.setUrl(QUrl::fromEncoded( + "https://raw.githubusercontent.com/0xzer0x/quran-companion/main/VERSION")); + m_versionReq.setTransferTimeout(1500); + + connect( + &m_runner, &QProcess::finished, this, &VersionChecker::handleToolOutput); + connect(&m_netMgr, + &QNetworkAccessManager::finished, + this, + &VersionChecker::handleReply); +} + +void +VersionChecker::checkUpdates() +{ + if (toolExists()) { + m_runner.setWorkingDirectory(QApplication::applicationDirPath()); + m_runner.start(m_updateTool, QStringList("ch")); + return; + } + getLatestVersion(); +} + +void +VersionChecker::handleToolOutput() +{ + QString output = m_runner.readAll(); + QString displayText; + + if (output.contains("There are currently no updates available.")) { + displayText = tr("There are currently no updates available."); + QMessageBox::information(nullptr, tr("Update info"), displayText); + } else { + displayText = tr("Updates available, do you want to open the update tool?"); + QMessageBox::StandardButton btn = + QMessageBox::question(nullptr, tr("Updates info"), displayText); + if (btn == QMessageBox::Yes) + m_runner.startDetached(m_updateTool); + } +} + +void +VersionChecker::handleReply(QPointer reply) +{ + int status = + reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + if (status != 200) + return; + + QString version(reply->readAll().trimmed()); + if (qApp->applicationVersion() == version) + m_notifier.notifyLatest(); + else + m_notifier.notifyUpdate(version); +} + +void +VersionChecker::getLatestVersion() +{ + QNetworkReply* reply = m_netMgr.get(m_versionReq); + reply->ignoreSslErrors(); +} + +bool +VersionChecker::toolExists() +{ + return QFileInfo::exists(m_updateTool); +} + +const UpdateNotifier* +VersionChecker::notifier() const +{ + return &m_notifier; +} diff --git a/src/utils/versionchecker.h b/src/utils/versionchecker.h new file mode 100644 index 00000000..2a03430b --- /dev/null +++ b/src/utils/versionchecker.h @@ -0,0 +1,37 @@ +#ifndef VERSIONCHECKER_H +#define VERSIONCHECKER_H + +#include +#include +#include +#include +#include +#include +#include +#include + +class VersionChecker : public QObject +{ + Q_OBJECT +public: + explicit VersionChecker(QObject* parent = nullptr); + const UpdateNotifier* notifier() const; + +public slots: + void checkUpdates(); + +private slots: + void handleToolOutput(); + void handleReply(QPointer reply); + +private: + bool toolExists(); + void getLatestVersion(); + QProcess m_runner; + QString m_updateTool; + QNetworkRequest m_versionReq; + QNetworkAccessManager m_netMgr; + UpdateNotifier m_notifier; +}; + +#endif // VERSIONCHECKER_H diff --git a/src/widgets/aboutdialog.h b/src/widgets/aboutdialog.h deleted file mode 100644 index 14cba813..00000000 --- a/src/widgets/aboutdialog.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef ABOUTDIALOG_H -#define ABOUTDIALOG_H - -#include "../globals.h" -#include - -namespace Ui { -class AboutDialog; -} - -class AboutDialog : public QDialog -{ - Q_OBJECT - -public: - explicit AboutDialog(QWidget* parent = nullptr); - ~AboutDialog(); - -private: - Ui::AboutDialog* ui; - QLocale::Language m_lang = Globals::language; -}; - -#endif // ABOUTDIALOG_H diff --git a/src/widgets/betaqaviewer.cpp b/src/widgets/betaqaviewer.cpp index 2c54e910..63792630 100644 --- a/src/widgets/betaqaviewer.cpp +++ b/src/widgets/betaqaviewer.cpp @@ -10,6 +10,7 @@ BetaqaViewer::BetaqaViewer(QWidget* parent) , ui(new Ui::BetaqaViewer) , m_shadowEffect(new QGraphicsDropShadowEffect(this)) , m_sizeAnim(new QPropertyAnimation(this, "size")) + , m_betaqatDb(BetaqatDb::getInstance()) { this->hide(); ui->setupUi(this); @@ -37,7 +38,7 @@ void BetaqaViewer::showSurah(int surah) { if (surah != m_surah) { - ui->betaqaTextBrowser->setHtml(m_dbMgr->getBetaqa(surah)); + ui->betaqaTextBrowser->setHtml(m_betaqatDb.getBetaqa(surah)); m_surah = surah; } @@ -51,7 +52,7 @@ BetaqaViewer::showSurah(int surah) void BetaqaViewer::center() { - int w = std::max((int)(parentWidget()->width() * 0.4), 600); + int w = std::max((int)(parentWidget()->width() * 0.6), 600); int h = std::max((int)(parentWidget()->height() * 0.8), 600); this->resize(w, h); m_sizeAnim->setEndValue(size()); diff --git a/src/widgets/betaqaviewer.h b/src/widgets/betaqaviewer.h index 47a00674..017da5d6 100644 --- a/src/widgets/betaqaviewer.h +++ b/src/widgets/betaqaviewer.h @@ -1,12 +1,12 @@ #ifndef BETAQAVIEWER_H #define BETAQAVIEWER_H -#include "../globals.h" -#include "../utils/dbmanager.h" #include +#include #include #include #include +#include namespace Ui { class BetaqaViewer; @@ -20,18 +20,21 @@ class BetaqaViewer : public QWidget explicit BetaqaViewer(QWidget* parent = nullptr); ~BetaqaViewer(); - void showSurah(int surah); void center(); +public slots: + void showSurah(int surah); + protected: void focusOutEvent(QFocusEvent* event); private: Ui::BetaqaViewer* ui; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); + BetaqatDb& m_betaqatDb; + int m_surah = -1; - QGraphicsDropShadowEffect* m_shadowEffect = nullptr; - QPropertyAnimation* m_sizeAnim = nullptr; + QPointer m_shadowEffect; + QPointer m_sizeAnim; }; #endif // BETAQAVIEWER_H diff --git a/src/widgets/downloadprogressbar.cpp b/src/widgets/downloadprogressbar.cpp index 5a7916e8..6696b103 100644 --- a/src/widgets/downloadprogressbar.cpp +++ b/src/widgets/downloadprogressbar.cpp @@ -4,29 +4,53 @@ */ #include "downloadprogressbar.h" +#include +#include #include -DownloadProgressBar::DownloadProgressBar(QWidget* parent, - DownloadType type, - int max) +DownloadProgressBar::DownloadProgressBar(QWidget* parent, Type type, int max) : QProgressBar(parent) + , m_type(type) { setStyling(downloading); setMaximum(max); setValue(0); - if (type == File) + if (m_type == DownloadJob::TafsirFile || + m_type == DownloadJob::TranslationFile) setFormat("%v / %m " + qApp->translate("DownloadManager", "KB")); else setFormat("%v / %m"); } void -DownloadProgressBar::updateProgress(qint64 downloaded, qint64 total) +DownloadProgressBar::updateProgress(QSharedPointer job) { - if (maximum() != total) - setMaximum(total); + if (maximum() != job->total()) + setMaximum(job->total()); - setValue(downloaded); + setValue(job->completed()); +} + +void +DownloadProgressBar::finished() +{ + if (m_type == DownloadJob::TafsirFile || + m_type == DownloadJob::TranslationFile) { + setValue(1); + setMaximum(1); + } + setFormat("%v / %m"); +} + +void +DownloadProgressBar::failed() +{ + if (m_type == DownloadJob::TafsirFile || + m_type == DownloadJob::TranslationFile) { + setValue(0); + setMaximum(1); + } + setFormat("%v / %m"); } void diff --git a/src/widgets/downloadprogressbar.h b/src/widgets/downloadprogressbar.h index f785a87e..cbf2c2ce 100644 --- a/src/widgets/downloadprogressbar.h +++ b/src/widgets/downloadprogressbar.h @@ -6,8 +6,9 @@ #ifndef DOWNLOADPROGRESSBAR_H #define DOWNLOADPROGRESSBAR_H -#include "../globals.h" #include +#include +typedef DownloadJob::Type Type; /** * @brief DownloadProgressBar class is a modified QProgressBar to change its @@ -22,7 +23,7 @@ class DownloadProgressBar : public QProgressBar * @param max - maximum value for the progress bar (defaults to longest surah * in the Quran) */ - DownloadProgressBar(QWidget* parent, DownloadType type, int max); + DownloadProgressBar(QWidget* parent, Type type, int max); /** * @brief The State enum represents the different states of the progressbar UI * component @@ -35,8 +36,13 @@ class DownloadProgressBar : public QProgressBar }; public slots: - void updateProgress(qint64 downloaded, qint64 total); void setStyling(State); + void updateProgress(QSharedPointer job); + void finished(); + void failed(); + +private: + Type m_type; }; #endif // DOWNLOADPROGRESSBAR_H diff --git a/src/widgets/notificationpopup.cpp b/src/widgets/notificationpopup.cpp index e1e1a3b9..56f5b97e 100644 --- a/src/widgets/notificationpopup.cpp +++ b/src/widgets/notificationpopup.cpp @@ -4,13 +4,15 @@ */ #include "notificationpopup.h" +#include +#include using namespace fa; NotificationPopup::NotificationPopup(QWidget* parent) - : QFrame{ parent } - , m_iconWidget{ new QLabel(this) } - , m_textWidget{ new QLabel(this) } - , m_opacityEffect{ new QGraphicsOpacityEffect(this) } + : QFrame(parent) + , m_iconWidget(new QLabel(this)) + , m_textWidget(new QLabel(this)) + , m_opacityEffect(new QGraphicsOpacityEffect(this)) { setObjectName("Popup"); this->setGraphicsEffect(m_opacityEffect); @@ -56,102 +58,25 @@ NotificationPopup::setupConnections() } void -NotificationPopup::dockLocationChanged(Qt::DockWidgetArea dockPos) +NotificationPopup::registerSender(NotificationSender* sender) { - m_dockArea = dockPos; -} - -void -NotificationPopup::notify(QString message, NotificationPopup::Action icon) -{ - QFontMetrics fm(m_textWidget->fontMetrics()); - m_textWidget->setText(message); - setNotificationIcon(icon); - - resize(fm.size(Qt::TextSingleLine, message).width() + 50, 40); - adjustLocation(); - move(m_notificationPos); - this->show(); - - if (m_fadeoutAnim->state() == QAbstractAnimation::Running) - m_fadeoutAnim->stop(); - m_opacityEffect->setOpacity(1); - m_notificationPeriod.start(); + connect( + sender, &NotificationSender::notify, this, &NotificationPopup::notify); } void -NotificationPopup::completedDownload(DownloadType type, - const QList& metainfo) +NotificationPopup::setStyle(NotificationType type) { - setStyleSheet(""); - QString msg = tr("Download Completed") + ": "; - if (type == Recitation) - msg += m_recitersList.at(metainfo[0]).displayName + " - " + - m_dbMgr->surahNameList().at(metainfo[1] - 1); - else if (type == QCF) - msg += tr("QCF V2"); - else if (type == File) - msg += metainfo[0] ? m_trList.at(metainfo[1]).displayName - : m_tafasirList.at(metainfo[1]).displayName; - - this->notify(msg, success); + if (type == NotificationSender::fail) + setStyleSheet("QFrame#Popup { background-color: #a50500 }"); + else + setStyleSheet(""); } void -NotificationPopup::downloadError(DownloadType type, const QList& metainfo) +NotificationPopup::setDockArea(Qt::DockWidgetArea dockPos) { - setStyleSheet("QFrame#Popup { background-color: #a50500 }"); - QString msg = tr("Download Failed") + ": "; - if (type == Recitation) - msg += m_recitersList.at(metainfo[0]).displayName + " - " + - m_dbMgr->surahNameList().at(metainfo[1] - 1); - else if (type == QCF) - msg += tr("QCF V2"); - else if (type == File) - msg += metainfo[0] ? m_trList.at(metainfo[1]).displayName - : m_tafasirList.at(metainfo[1]).displayName; - - this->notify(msg, fail); -} - -void -NotificationPopup::bookmarkAdded() -{ - setStyleSheet(""); - QString msg = tr("Verse added to bookmarks"); - this->notify(msg, bookmarkAdd); -} - -void -NotificationPopup::bookmarkRemoved() -{ - setStyleSheet(""); - QString msg = tr("Verse removed from bookmarks"); - this->notify(msg, bookmarkRemove); -} - -void -NotificationPopup::copiedToClipboard() -{ - setStyleSheet(""); - QString msg = tr("Verse text copied to clipboard"); - this->notify(msg, copiedText); -} - -void -NotificationPopup::checkUpdate(QString appVer) -{ - if (appVer.isEmpty()) - return; - - QString msg; - if (qApp->applicationVersion() == appVer) { - msg = tr("You are running the latest version"); - this->notify(msg, success); - } else { - msg = tr("Update available") + ": " + appVer; - this->notify(msg, updateInfo); - } + m_dockArea = dockPos; } void @@ -173,39 +98,59 @@ NotificationPopup::adjustLocation() } void -NotificationPopup::setNotificationIcon(Action icon) +NotificationPopup::setNotificationIcon(NotificationType type) { QString ico; int faStyle = fa_solid; - switch (icon) { - case NotificationPopup::info: + switch (type) { + case NotificationSender::info: ico = fa_info_circle; break; - case NotificationPopup::success: + case NotificationSender::success: ico = fa_check_circle; break; - case NotificationPopup::fail: + case NotificationSender::fail: ico = fa_xmark_circle; break; - case NotificationPopup::bookmarkAdd: + case NotificationSender::bookmarkAdd: ico = fa_bookmark; break; - case NotificationPopup::bookmarkRemove: + case NotificationSender::bookmarkRemove: ico = fa_bookmark; faStyle = fa_regular; break; - case NotificationPopup::copiedText: + case NotificationSender::copiedText: ico = fa_clipboard; break; - case NotificationPopup::updateInfo: + case NotificationSender::updateInfo: ico = fa_circle_up; break; } - m_iconWidget->setFont(Globals::awesome->font(faStyle, 18)); + m_iconWidget->setFont( + StyleManager::getInstance().awesome().font(faStyle, 18)); m_iconWidget->setText(ico); } +void +NotificationPopup::notify(NotificationType type, QString message) +{ + QFontMetrics fm(m_textWidget->fontMetrics()); + m_textWidget->setText(message); + setNotificationIcon(type); + setStyle(type); + + resize(fm.size(Qt::TextSingleLine, message).width() + 50, 40); + adjustLocation(); + move(m_notificationPos); + this->show(); + + if (m_fadeoutAnim->state() == QAbstractAnimation::Running) + m_fadeoutAnim->stop(); + m_opacityEffect->setOpacity(1); + m_notificationPeriod.start(); +} + QPoint NotificationPopup::notificationPos() const { diff --git a/src/widgets/notificationpopup.h b/src/widgets/notificationpopup.h index f6c6388c..262e2f8f 100644 --- a/src/widgets/notificationpopup.h +++ b/src/widgets/notificationpopup.h @@ -6,17 +6,22 @@ #ifndef NOTIFICATIONPOPUP_H #define NOTIFICATIONPOPUP_H -#include "../globals.h" -#include "../utils/dbmanager.h" #include #include #include #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +typedef NotificationSender::Type NotificationType; /** * @brief NotificationPopup class represents an in-app popup for notifying the @@ -26,20 +31,6 @@ class NotificationPopup : public QFrame { Q_OBJECT public: - /** - * @brief The Action enum represents all possible action to notify the user of - */ - enum Action - { - info, ///< general information - success, ///< successful operation - fail, ///< failed operation - bookmarkAdd, ///< bookmark addition - bookmarkRemove, ///< bookmark removal - copiedText, ///< verse text copied - updateInfo ///< version information - }; - /** * @brief class constructor * @param parent - pointer to parent widget @@ -47,12 +38,12 @@ class NotificationPopup : public QFrame */ explicit NotificationPopup(QWidget* parent = nullptr); /** - * @brief show popup with the given message and action icon - * @param message - QString of message to show - * @param icon - NotificationPopup::Action entry + * @brief registerSender + * @param sender + * + * MODIFIED */ - void notify(QString message, Action icon); - + void registerSender(NotificationSender* sender); /** * @brief adjust the popup position based on the position of the side dock * position in the main window @@ -71,57 +62,36 @@ public slots: * @brief slot to update m_dockArea variable on dock position change * @param dockPos - new dock position relative to the main window */ - void dockLocationChanged(Qt::DockWidgetArea dockPos); - /** - * @brief slot to show a notification on download completion - * @param reciterIdx - ::Globals::recitersList index for the reciter - * @param surah - the surah that was downloaded - */ - void completedDownload(DownloadType type, const QList& metainfo); - /** - * @brief slot to show a notification on download error - * @param reciterIdx - ::Globals::recitersList index for the reciter - * @param surah - the surah that was downloaded - */ - void downloadError(DownloadType type, const QList& metainfo); - /** - * @brief slot to show a notification on bookmark addition - */ - void bookmarkAdded(); - /** - * @brief slot to show a notification on bookmark removal - */ - void bookmarkRemoved(); - /** - * @brief slot to show a notification after verse text is copied to clipboard - */ - void copiedToClipboard(); + void setDockArea(Qt::DockWidgetArea dockPos); + +private slots: /** - * @brief slot to check the passed version against the application version and - * show notification accordingly - * @param appVer - the fetched application version + * @brief show popup with the given message and action icon + * @param message - QString of message to show + * @param icon - NotificationPopup::Action entry */ - void checkUpdate(QString appVer); + void notify(NotificationType icon, QString message); private: - const QList& m_recitersList = Globals::recitersList; - const QList& m_tafasirList = Globals::tafasirList; - const QList& m_trList = Globals::translationsList; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); /** * @brief connects signals and slots for different UI * components and shortcuts. */ void setupConnections(); + /** + * @brief setStyle + * @param type + */ + void setStyle(NotificationType type); /** * @brief set the popup icon according to the given Action * @param icon - NotificationPopup::Action entry */ - void setNotificationIcon(Action icon); - QLabel* m_iconWidget; - QLabel* m_textWidget; - QPropertyAnimation* m_fadeoutAnim; - QGraphicsOpacityEffect* m_opacityEffect; + void setNotificationIcon(NotificationType type); + QPointer m_iconWidget; + QPointer m_textWidget; + QPointer m_fadeoutAnim; + QPointer m_opacityEffect; Qt::DockWidgetArea m_dockArea; QPoint m_notificationPos; QTimer m_notificationPeriod; diff --git a/src/widgets/quranpagebrowser.cpp b/src/widgets/quranpagebrowser.cpp index 2041e55a..d9760dd0 100644 --- a/src/widgets/quranpagebrowser.cpp +++ b/src/widgets/quranpagebrowser.cpp @@ -6,12 +6,18 @@ #include "quranpagebrowser.h" #include #include +#include +#include using namespace fa; QuranPageBrowser::QuranPageBrowser(QWidget* parent, int initPage) : QTextBrowser(parent) - , m_highlighter{ new QTextCursor(document()) } - , m_highlightColor{ QBrush(qApp->palette().color(QPalette::Highlight)) } + , m_highlighter(new QTextCursor(document())) + , m_highlightColor(QBrush(qApp->palette().color(QPalette::Highlight))) + , m_config(Configuration::getInstance()) + , m_styleMgr(StyleManager::getInstance()) + , m_quranDb(QuranDb::getInstance()) + , m_glyphsDb(GlyphsDb::getInstance()) { setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setTextInteractionFlags(Qt::TextInteractionFlag::LinksAccessibleByMouse); @@ -19,7 +25,7 @@ QuranPageBrowser::QuranPageBrowser(QWidget* parent, int initPage) createActions(); updateFontSize(); - m_pageFont = Globals::pageFontname(initPage); + m_pageFont = FontManager::getInstance().getInstance().pageFontname(initPage); m_pageFormat.setAlignment(Qt::AlignCenter); m_pageFormat.setNonBreakableLines(true); m_pageFormat.setLayoutDirection(Qt::RightToLeft); @@ -41,7 +47,8 @@ void QuranPageBrowser::updateFontSize() { m_fontSize = - m_settings->value("Reader/QCF" + QString::number(m_qcfVer) + "Size", 22) + m_config.settings() + .value("Reader/QCF" + QString::number(m_config.qcfVersion()) + "Size", 22) .toInt(); highlightVerse(m_highlightedIdx); } @@ -98,7 +105,7 @@ QuranPageBrowser::surahFrame(int surah) QString frmText; frmText.append("ﰦ"); frmText.append("ﮌ"); - frmText.append(m_dbMgr->getSurahNameGlyph(surah)); + frmText.append(m_glyphsDb.getSurahNameGlyph(surah)); // draw on top of the image the surah name text QPainter p(&baseImage); @@ -106,7 +113,7 @@ QuranPageBrowser::surahFrame(int surah) p.setFont(QFont("QCF_BSML", 77)); p.drawText(baseImage.rect(), Qt::AlignCenter, frmText); - if (m_darkMode) + if (m_config.darkMode()) baseImage.invertPixels(); return baseImage; @@ -130,14 +137,14 @@ QuranPageBrowser::setHref(QTextCursor* cursor, int to, QString url) QString QuranPageBrowser::pageHeader(int page) { - m_headerData = m_dbMgr->getPageMetadata(page); + m_headerData = m_quranDb.pageMetadata(page); QString suraHeader, jozzHeader; suraHeader.append("سورة "); - suraHeader.append(m_dbMgr->getSurahName(m_headerData.first, true)); + suraHeader.append(m_quranDb.surahName(m_headerData.first, true)); suraHeader.append("$"); jozzHeader.append("الجزء "); - jozzHeader.append(m_dbMgr->getJuzGlyph(m_headerData.second)); + jozzHeader.append(m_glyphsDb.getJuzGlyph(m_headerData.second)); return suraHeader + jozzHeader; } @@ -154,17 +161,19 @@ QuranPageBrowser::constructPage(int pageNo, bool forceCustomSize) m_verseCoordinates.clear(); this->document()->clear(); - m_pageFont = Globals::pageFontname(pageNo); + m_pageFont = FontManager::getInstance().pageFontname(pageNo); QTextCursor textCursor(this->document()); m_currPageHeader = this->pageHeader(m_page); - m_currPageLines = m_dbMgr->getPageLines(m_page); + m_currPageLines = m_glyphsDb.getPageLines(m_page); // automatic font adjustment check - if (!forceCustomSize && m_settings->value("Reader/AdaptiveFont").toBool()) { + if (!forceCustomSize && + m_config.settings().value("Reader/AdaptiveFont").toBool()) { m_fontSize = this->bestFitFontSize(); - m_settings->setValue("Reader/QCF" + QString::number(m_qcfVer) + "Size", - m_fontSize); + m_config.settings().setValue( + "Reader/QCF" + QString::number(m_config.qcfVersion()) + "Size", + m_fontSize); } m_pageLineSize = this->calcPageLineSize(m_currPageLines); @@ -175,7 +184,7 @@ QuranPageBrowser::constructPage(int pageNo, bool forceCustomSize) QBrush(qApp->palette().color(QPalette::PlaceholderText))); // smaller header font size for long juz > 10 - if (m_qcfVer == 1 && pageNo >= 202) + if (m_config.qcfVersion() == 1 && pageNo >= 202) m_headerTextFormat.setFontPointSize(std::max(4, m_fontSize - 8)); else m_headerTextFormat.setFontPointSize(m_fontSize - 6); @@ -185,8 +194,7 @@ QuranPageBrowser::constructPage(int pageNo, bool forceCustomSize) setHref(&textCursor, 1, "#F" + QString::number(m_headerData.first)); } - this->parentWidget()->setMinimumWidth(m_pageLineSize.width() + 70); - this->setMinimumWidth(m_pageLineSize.width() + 70); + parentWidget()->setMinimumWidth(m_pageLineSize.width() + 70); // page lines drawing int counter = 0, prevAnchor = pageNo < 3 ? 0 : m_currPageHeader.size() + 1; @@ -209,7 +217,7 @@ QuranPageBrowser::constructPage(int pageNo, bool forceCustomSize) prevAnchor += 2; } else if (l.contains("bsml")) { QImage bsml(":/resources/basmalah.png"); - if (m_darkMode) + if (m_config.darkMode()) bsml.invertPixels(); textCursor.insertBlock(m_pageFormat, m_bodyTextFormat); @@ -279,7 +287,7 @@ QuranPageBrowser::resetHighlight() { QTextCharFormat tcf; if (m_fgHighlight) - tcf.setForeground(m_darkMode ? Qt::white : Qt::black); + tcf.setForeground(m_config.darkMode() ? Qt::white : Qt::black); else tcf.setBackground(Qt::transparent); @@ -293,11 +301,13 @@ QuranPageBrowser::Action QuranPageBrowser::lmbVerseMenu(bool favoriteVerse) { QMenu lmbMenu(this); - lmbMenu.addAction(m_playAct); - lmbMenu.addAction(m_selectAct); - lmbMenu.addAction(m_tafsirAct); + lmbMenu.addAction(m_actPlay); + lmbMenu.addAction(m_actSelect); + lmbMenu.addAction(m_actTafsir); + lmbMenu.addAction(m_actTranslation); + lmbMenu.addAction(m_actThoughts); lmbMenu.addSeparator(); - lmbMenu.addAction(m_copyAct); + lmbMenu.addAction(m_actCopy); if (favoriteVerse) { lmbMenu.addAction(m_actRemBookmark); } else { @@ -306,19 +316,23 @@ QuranPageBrowser::lmbVerseMenu(bool favoriteVerse) QAction* chosen = lmbMenu.exec(QCursor::pos()); - QuranPageBrowser::Action actionIdx = null; - if (chosen == m_playAct) - actionIdx = play; - else if (chosen == m_selectAct) - actionIdx = select; - else if (chosen == m_tafsirAct) - actionIdx = tafsir; - else if (chosen == m_copyAct) - actionIdx = copy; + QuranPageBrowser::Action actionIdx = Action::Null; + if (chosen == m_actPlay) + actionIdx = Action::Play; + else if (chosen == m_actSelect) + actionIdx = Action::Select; + else if (chosen == m_actTafsir) + actionIdx = Action::Tafsir; + else if (chosen == m_actTranslation) + actionIdx = Action::Translation; + else if (chosen == m_actThoughts) + actionIdx = Action::Thoughts; + else if (chosen == m_actCopy) + actionIdx = Action::Copy; else if (chosen == m_actAddBookmark) - actionIdx = addBookmark; + actionIdx = Action::AddBookmark; else if (chosen == m_actRemBookmark) - actionIdx = removeBookmark; + actionIdx = Action::RemoveBookmark; this->clearFocus(); return actionIdx; @@ -350,25 +364,32 @@ QuranPageBrowser::bestFitFontSize() void QuranPageBrowser::createActions() { - m_zoomIn = new QAction(tr("Zoom In"), this); - m_zoomOut = new QAction(tr("Zoom Out"), this); - m_copyAct = new QAction(tr("Copy Verse"), this); - m_selectAct = new QAction(tr("Select"), this); - m_playAct = new QAction(tr("Play"), this); - m_tafsirAct = new QAction(tr("Tafsir"), this); + m_actZoomIn = new QAction(tr("Zoom In"), this); + m_actZoomOut = new QAction(tr("Zoom Out"), this); + m_actCopy = new QAction(tr("Copy Verse"), this); + m_actSelect = new QAction(tr("Select"), this); + m_actPlay = new QAction(tr("Play"), this); + m_actTafsir = new QAction(tr("Tafsir"), this); + m_actTranslation = new QAction(tr("Translation"), this); + m_actThoughts = new QAction(tr("Thoughts"), this); m_actAddBookmark = new QAction(tr("Add Bookmark"), this); m_actRemBookmark = new QAction(tr("Remove Bookmark"), this); - m_zoomIn->setIcon(m_fa->icon(fa_solid, fa_magnifying_glass_plus)); - m_zoomOut->setIcon(m_fa->icon(fa_solid, fa_magnifying_glass_minus)); - m_playAct->setIcon(m_fa->icon(fa_solid, fa_play)); - m_selectAct->setIcon(m_fa->icon(fa_solid, fa_hand_pointer)); - m_tafsirAct->setIcon(m_fa->icon(fa_solid, fa_book_open)); - m_copyAct->setIcon(m_fa->icon(fa_solid, fa_clipboard)); - m_actAddBookmark->setIcon(m_fa->icon(fa_regular, fa_bookmark)); - m_actRemBookmark->setIcon(m_fa->icon(fa_solid, fa_bookmark)); - connect(m_zoomIn, &QAction::triggered, this, &QuranPageBrowser::actionZoomIn); + m_actZoomIn->setIcon( + m_styleMgr.awesome().icon(fa_solid, fa_magnifying_glass_plus)); + m_actZoomOut->setIcon( + m_styleMgr.awesome().icon(fa_solid, fa_magnifying_glass_minus)); + m_actPlay->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_play)); + m_actSelect->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_hand_pointer)); + m_actTafsir->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_book_open)); + m_actTranslation->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_language)); + m_actThoughts->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_comment)); + m_actCopy->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_clipboard)); + m_actAddBookmark->setIcon(m_styleMgr.awesome().icon(fa_regular, fa_bookmark)); + m_actRemBookmark->setIcon(m_styleMgr.awesome().icon(fa_solid, fa_bookmark)); connect( - m_zoomOut, &QAction::triggered, this, &QuranPageBrowser::actionZoomOut); + m_actZoomIn, &QAction::triggered, this, &QuranPageBrowser::actionZoomIn); + connect( + m_actZoomOut, &QAction::triggered, this, &QuranPageBrowser::actionZoomOut); } #ifndef QT_NO_CONTEXTMENU @@ -376,8 +397,8 @@ void QuranPageBrowser::contextMenuEvent(QContextMenuEvent* event) { QMenu menu(this); - menu.addAction(m_zoomIn); - menu.addAction(m_zoomOut); + menu.addAction(m_actZoomIn); + menu.addAction(m_actZoomOut); m_mousePos = event->pos(); m_mouseGlobalPos = event->globalPos(); @@ -389,8 +410,8 @@ void QuranPageBrowser::actionZoomIn() { m_fontSize++; - m_settings->setValue("Reader/QCF" + QString::number(m_qcfVer) + "Size", - m_fontSize); + m_config.settings().setValue( + "Reader/QCF" + QString::number(m_config.qcfVersion()) + "Size", m_fontSize); constructPage(m_page, true); highlightVerse(m_highlightedIdx); } @@ -399,8 +420,8 @@ void QuranPageBrowser::actionZoomOut() { m_fontSize--; - m_settings->setValue("Reader/QCF" + QString::number(m_qcfVer) + "Size", - m_fontSize); + m_config.settings().setValue( + "Reader/QCF" + QString::number(m_config.qcfVersion()) + "Size", m_fontSize); constructPage(m_page, true); highlightVerse(m_highlightedIdx); } @@ -410,7 +431,7 @@ QuranPageBrowser::updateHighlightLayer() { int old = m_highlightedIdx; resetHighlight(); - m_fgHighlight = m_settings->value("Reader/FGHighlight").toBool(); + m_fgHighlight = m_config.settings().value("Reader/FGHighlight").toBool(); QColor hc = m_highlightColor.color(); if (m_fgHighlight) diff --git a/src/widgets/quranpagebrowser.h b/src/widgets/quranpagebrowser.h index f86dc656..50e4e083 100644 --- a/src/widgets/quranpagebrowser.h +++ b/src/widgets/quranpagebrowser.h @@ -6,18 +6,21 @@ #ifndef QURANPAGEBROWSER_H #define QURANPAGEBROWSER_H -#include "../globals.h" -#include "../utils/dbmanager.h" #include #include #include #include +#include #include #include #include #include #include #include +#include +#include +#include +#include /** * @brief QuranPageBrowser class is a modified QTextBrowser for displaying a @@ -33,13 +36,15 @@ class QuranPageBrowser : public QTextBrowser */ enum Action { - null, ///< no action (default) - play, ///< select the verse and start playback - select, ///< only select the verse - tafsir, ///< show the tafsir for the verse - copy, ///< copy the verse text to clipboard - addBookmark, ///< add the verse to bookmarks - removeBookmark ///< remove the verse from bookmarks + Null, ///< no action (default) + Play, ///< select the verse and start playback + Select, ///< only select the verse + Tafsir, ///< show the tafsir for the verse + Translation, ///< show the translation for the verse + Thoughts, ///< show user thoughts for the verse + Copy, ///< copy the verse text to clipboard + AddBookmark, ///< add the verse to bookmarks + RemoveBookmark ///< remove the verse from bookmarks }; /** * @brief class constructor @@ -48,7 +53,6 @@ class QuranPageBrowser : public QTextBrowser * @param initPage - inital page to load */ QuranPageBrowser(QWidget* parent = nullptr, int initPage = 1); - /** * @brief sets m_fontSize to the fontsize in the settings file */ @@ -142,11 +146,10 @@ public slots: #endif private: - QSettings* const m_settings = Globals::settings; - const int m_qcfVer = Globals::qcfVersion; - const bool m_darkMode = Globals::darkMode; - DBManager* m_dbMgr = qobject_cast(Globals::databaseManager); - fa::QtAwesome* m_fa = Globals::awesome; + Configuration& m_config; + StyleManager& m_styleMgr; + const QuranDb& m_quranDb; + const GlyphsDb& m_glyphsDb; /** * @brief utility for creating menu actions for interacting with the widget */ @@ -224,39 +227,51 @@ public slots: /** * @brief QAction for zoom-in functionality */ - QAction* m_zoomIn; + QPointer m_actZoomIn; /** * @brief QAction for zoom-out functionality */ - QAction* m_zoomOut; + QPointer m_actZoomOut; /** * @brief QAction for copy functionality */ - QAction* m_copyAct; + QPointer m_actCopy; /** * @brief QAction for verse selection functionality */ - QAction* m_selectAct; + QPointer m_actSelect; /** * @brief QAction for verse playback functionality */ - QAction* m_playAct; + QPointer m_actPlay; /** * @brief QAction for showing tafsir functionality */ - QAction* m_tafsirAct; + QPointer m_actTafsir; + /** + * @brief m_actTranslation + * + * MODIFIED + */ + QPointer m_actTranslation; + /** + * @brief m_actThoughts + * + * MODIFIED + */ + QPointer m_actThoughts; /** * @brief QAction for bookmark addition functionality */ - QAction* m_actAddBookmark; + QPointer m_actAddBookmark; /** * @brief QAction for bookmark removal functionality */ - QAction* m_actRemBookmark; + QPointer m_actRemBookmark; /** * @brief QTextCursor used in highlighting verses */ - QTextCursor* m_highlighter; + QSharedPointer m_highlighter; /** * @brief page format properties used in inserting lines */