diff --git a/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp b/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp index aec6a11..c67dfff 100644 --- a/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp +++ b/autotests/integrationtests/iresourcerepository_integration/test_iresourcerepository.cpp @@ -1,92 +1,92 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_iresourcerepository.h" #include #include #include #include "core/resourcerepository.h" #include "core/contributorrepository.h" #include "core/language.h" #include "core/icourse.h" #include "core/unit.h" #include "../src/settings.h" void TestIResourceRepository::init() { // check that test data is deployed at the expected location QVERIFY(QFile::exists("data/courses/de/de.xml")); QVERIFY(QFile::exists("data/courses/fr/fr.xml")); } void TestIResourceRepository::resourceRepository() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); - QCOMPARE(repository.storageLocation(), "data/courses/"); + QCOMPARE(repository.storageLocation().toLocalFile(), "data/courses/"); performInterfaceTests(&repository); } void TestIResourceRepository::contributorRepository() { ContributorRepository repository; - repository.setStorageLocation("data/contributorrepository/"); // contributor repository requires subdirectory "courses" - QCOMPARE(repository.storageLocation(), "data/contributorrepository/"); + repository.setStorageLocation(QUrl::fromLocalFile("data/contributorrepository/")); // contributor repository requires subdirectory "courses" + QCOMPARE(repository.storageLocation().toLocalFile(), "data/contributorrepository/"); performInterfaceTests(&repository); } void TestIResourceRepository::performInterfaceTests(IResourceRepository *interface) { QVERIFY(interface->languages().count() > 0); // automatically load languages QCOMPARE(interface->courses().count(), 0); // load courses only on demand // test adding QSignalSpy spyAboutToBeAdded(dynamic_cast(interface), SIGNAL(courseAboutToBeAdded(std::shared_ptr, int))); QSignalSpy spyAdded(dynamic_cast(interface), SIGNAL(courseAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); interface->reloadCourses(); // initial loading of courses QCOMPARE(interface->courses().count(), 2); QCOMPARE(spyAboutToBeAdded.count(), 2); QCOMPARE(spyAdded.count(), 2); // test reloading of courses interface->reloadCourses(); // initial loading of courses QCOMPARE(interface->courses().count(), 2); // test removal // note: repository does not provide removal of courses, yet // test access of courses grouped by language auto languages = interface->languages(); std::shared_ptr german; for (auto language : interface->languages()) { if (language->id() == "de") { german = language; break; } } QVERIFY(german != nullptr); // ensure that German language was found QCOMPARE(interface->courses(german->id()).count(), 1); // there is exactly one German course QCOMPARE(interface->courses(nullptr).count(), 2); // all courses in total are 2 QVERIFY(interface->courses().first()->units().size() > 0); } QTEST_GUILESS_MAIN(TestIResourceRepository) diff --git a/autotests/mocks/editablerepositorystub.h b/autotests/mocks/editablerepositorystub.h index 93c56b6..ae28b53 100644 --- a/autotests/mocks/editablerepositorystub.h +++ b/autotests/mocks/editablerepositorystub.h @@ -1,140 +1,140 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef EDITABLEREPOSITORYSTUB_H #define EDITABLEREPOSITORYSTUB_H #include "core/ieditablerepository.h" #include "core/ieditablecourse.h" #include #include class ILanguage; class SkeletonResource; /** * @brief The EditableRepositoryStub is simple sub class only for testing */ class EditableRepositoryStub : public IEditableRepository { Q_OBJECT public: EditableRepositoryStub( std::vector> languages, std::vector> skeletons, std::vector> courses) { for (auto &language : languages) { m_languages.append(std::move(language)); } for (auto &skeleton : skeletons) { m_skeletons.append(std::move(skeleton)); } for (auto &course : courses) { m_courses.append(course); } } ~EditableRepositoryStub() override; - QString storageLocation() const override + QUrl storageLocation() const override { - return QString(); + return QUrl(); } QVector> skeletons() const override { return m_skeletons; } QVector> editableCourses() const override { return m_courses; } QVector> courses() const override { QVector> courses; for (auto course : m_courses) { courses.push_back(course); } return courses; } QVector> courses(const QString &languageId) const override { Q_UNUSED(languageId); return QVector>(); } std::shared_ptr editableCourse(std::shared_ptr language, int index) const override { Q_UNUSED(language); Q_UNUSED(index); return std::shared_ptr(); } void reloadCourses() override { // do nothing } QVector> languages() const override { return m_languages; } void appendCourse(std::shared_ptr course) { emit courseAboutToBeAdded(course, m_courses.count()); m_courses.append(course); emit courseAdded(); } void removeCourse(std::shared_ptr course) { auto index = m_courses.indexOf(course); Q_ASSERT(index >= 0); if (index >= 0) { emit courseAboutToBeRemoved(index); m_courses.remove(index); emit courseRemoved(); } } void appendSkeleton(std::shared_ptr skeleton) { emit skeletonAboutToBeAdded(skeleton, m_skeletons.count()); m_skeletons.append(skeleton); emit skeletonAdded(); } void removeSkeleton(std::shared_ptr skeleton) { auto index = m_skeletons.indexOf(skeleton); Q_ASSERT(index >= 0); if (index >= 0) { emit skeletonAboutToBeRemoved(index); m_skeletons.remove(index); emit skeletonRemoved(); } } void updateCourseFromSkeleton(std::shared_ptr course) override { Q_UNUSED(course); // do nothing } private: QVector> m_languages; QVector> m_skeletons; QVector> m_courses; }; #endif diff --git a/autotests/mocks/resourcerepositorystub.h b/autotests/mocks/resourcerepositorystub.h index 3bb9ae5..866ce86 100644 --- a/autotests/mocks/resourcerepositorystub.h +++ b/autotests/mocks/resourcerepositorystub.h @@ -1,115 +1,115 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef RESOURCEREPOSITORYSTUB_H #define RESOURCEREPOSITORYSTUB_H #include "core/iresourcerepository.h" #include "core/language.h" #include #include #include class ICourse; /** * @brief The ResourceRepositoryStub that only provides languages and a storage location, but nothing else */ class ResourceRepositoryStub : public IResourceRepository { Q_OBJECT public: ResourceRepositoryStub(std::vector> languages) { for (auto &language : languages) { m_languages.append(std::move(language)); } } ResourceRepositoryStub(std::vector> languages) { for (auto &language : languages) { m_languages.append(language); } } ResourceRepositoryStub(std::vector> languages, std::vector> courses) { for (auto &language : languages) { m_languages.append(language); } for (auto &course : courses) { m_courses.append(course); } } ~ResourceRepositoryStub() override; - QString storageLocation() const override + QUrl storageLocation() const override { return m_storageLocation; } QVector> courses() const override { return m_courses; } QVector> courses(const QString &languageId) const override { Q_UNUSED(languageId); return m_courses; // do not filter by languages } void reloadCourses() override { ; // do nothing, stub shall only provide languages } QVector> languages() const override { return m_languages; } void appendCourse(std::shared_ptr course) { emit courseAboutToBeAdded(course, m_courses.count()); m_courses.append(course); emit courseAdded(); } void removeCourse(std::shared_ptr course) { auto index = m_courses.indexOf(course); Q_ASSERT(index >= 0); if (index >= 0) { emit courseAboutToBeRemoved(index); m_courses.remove(index); emit courseRemoved(); } } private: - QString m_storageLocation; + QUrl m_storageLocation; QVector> m_languages; QVector> m_courses; }; #endif diff --git a/autotests/unittests/resourcerepository/test_resourcerepository.cpp b/autotests/unittests/resourcerepository/test_resourcerepository.cpp index 4832f07..783cf19 100644 --- a/autotests/unittests/resourcerepository/test_resourcerepository.cpp +++ b/autotests/unittests/resourcerepository/test_resourcerepository.cpp @@ -1,84 +1,84 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "test_resourcerepository.h" #include #include #include #include "src/core/resourcerepository.h" #include "src/core/language.h" void TestResourceRepository::init() { // check that test data is deployed at the expected location QVERIFY(QFile::exists("data/courses/de/de.xml")); QVERIFY(QFile::exists("data/courses/fr/fr.xml")); } void TestResourceRepository::cleanup() { // TODO cleanup after test run } void TestResourceRepository::createRepository() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); - QCOMPARE(repository.storageLocation(), "data/courses/"); + QCOMPARE(repository.storageLocation().toLocalFile(), "data/courses/"); repository.reloadCourses(); QCOMPARE(repository.courses().count(), 2); } void TestResourceRepository::iResourceRepositoryCompatability() { ResourceRepository repository(QUrl::fromLocalFile("data/courses/")); IResourceRepository *interface = &repository; - QCOMPARE(interface->storageLocation(), "data/courses/"); + QCOMPARE(interface->storageLocation().toLocalFile(), "data/courses/"); QVERIFY(interface->languages().count() > 0); QCOMPARE(interface->courses().count(), 0); // test adding QSignalSpy spyAboutToBeAdded(dynamic_cast(interface), SIGNAL(courseAboutToBeAdded(std::shared_ptr, int))); QSignalSpy spyAdded(dynamic_cast(interface), SIGNAL(courseAdded())); QCOMPARE(spyAboutToBeAdded.count(), 0); QCOMPARE(spyAdded.count(), 0); repository.reloadCourses(); QCOMPARE(interface->courses().count(), 2); QCOMPARE(spyAboutToBeAdded.count(), 2); QCOMPARE(spyAdded.count(), 2); // test removal // note: repository does not provide removal of courses, yet // test access of courses grouped by language auto languages = interface->languages(); std::shared_ptr german; for (auto language : interface->languages()) { if (language->id() == "de") { german = language; break; } } QVERIFY(german != nullptr); // ensure that German language was found QCOMPARE(interface->courses(german->id()).count(), 1); // there is exactly one German course QCOMPARE(interface->courses(nullptr).count(), 2); // all courses in total are 2 } QTEST_GUILESS_MAIN(TestResourceRepository) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 8a647ae..9c3b372 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,186 +1,177 @@ ### # Copyright 2013-2019 Andreas Cord-Landwehr # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. # IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT # NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ### ecm_setup_version(0.99.90 VARIABLE_PREFIX ARTIKULATE VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/ArtikulateConfigVersion.cmake" ) ecm_optional_add_subdirectory(qml) # set include directories include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${artikulate_SOURCE_DIR} ) # set the source code files from which Artikulate is compiled set(artikulateCore_SRCS application.cpp artikulate_debug.cpp core/icourse.h core/ieditablecourse.h core/ieditablephrase.h core/ieditablerepository.h core/ieditableunit.h core/ilanguage.h core/iphrase.h core/isessionactions.h core/iunit.h core/iresourcerepository.h core/drawertrainingactions.cpp core/resourcerepository.cpp core/contributorrepository.cpp core/language.cpp core/phrase.cpp core/phoneme.cpp core/phonemegroup.cpp core/unit.cpp core/editorsession.cpp core/trainingaction.cpp core/trainingactionicon.cpp core/trainingsession.cpp core/resources/courseparser.cpp core/resources/courseresource.cpp core/resources/editablecourseresource.cpp core/resources/skeletonresource.cpp core/player.cpp core/recorder.cpp models/coursemodel.cpp models/coursefiltermodel.cpp models/languagemodel.cpp models/languageresourcemodel.cpp # models/learningprogressmodel.cpp //TODO must be adapted to new trainingsession models/unitmodel.cpp models/unitfiltermodel.cpp models/phrasemodel.cpp models/phraselistmodel.cpp models/phrasefiltermodel.cpp models/phonememodel.cpp models/phonemegroupmodel.cpp models/phonemeunitmodel.cpp models/profilemodel.cpp models/skeletonmodel.cpp qmlcontrols/iconitem.cpp qmlcontrols/imagetexturescache.cpp qmlcontrols/managedtexturenode.cpp ) kconfig_add_kcfg_files (artikulateCore_SRCS settings.kcfgc) add_library(artikulatecore SHARED ${artikulateCore_SRCS}) generate_export_header(artikulatecore BASE_NAME artikulatecore) target_link_libraries(artikulatecore LINK_PUBLIC artikulatelearnerprofile artikulatesound Qt5::XmlPatterns Qt5::Qml Qt5::Quick KF5::Archive KF5::ConfigGui ) # internal library without any API or ABI guarantee set(GENERIC_LIB_VERSION "0") set(GENERIC_LIB_SOVERSION "0") set_target_properties( artikulatecore PROPERTIES VERSION ${GENERIC_LIB_VERSION} SOVERSION ${GENERIC_LIB_SOVERSION} ) install( TARGETS artikulatecore LIBRARY NAMELINK_SKIP DESTINATION ${INSTALL_TARGETS_DEFAULT_ARGS} ) install(FILES artikulate.knsrc DESTINATION ${CONFIG_INSTALL_DIR}) # set the source code files from which Artikulate is compiled set(artikulate_SRCS main.cpp mainwindow.cpp artikulate_debug.cpp - ui/sounddevicedialogpage.cpp - ui/appearencedialogpage.cpp - ui/resourcesdialogpage.cpp ) ki18n_wrap_ui (artikulate_SRCS - ui/resourcesdialogpage.ui ui/sounddevicedialogpage.ui - ui/appearencedialogpage.ui ) qt5_add_resources(artikulate_SRCS resources.qrc) qt5_add_resources(artikulate_SRCS ../data/languages.qrc) kconfig_add_kcfg_files (artikulate_SRCS settings.kcfgc) set(artikulate_editor_SRCS main_editor.cpp mainwindow_editor.cpp artikulate_debug.cpp ui/sounddevicedialogpage.cpp - ui/appearencedialogpage.cpp - ui/resourcesdialogpage.cpp ui/exportghnsdialog.cpp ) ki18n_wrap_ui(artikulate_editor_SRCS - ui/appearencedialogpage.ui ui/exportghnsdialog.ui - ui/resourcesdialogpage.ui ui/sounddevicedialogpage.ui ) qt5_add_resources(artikulate_editor_SRCS resources.qrc) qt5_add_resources(artikulate_editor_SRCS ../data/languages.qrc) kconfig_add_kcfg_files (artikulate_editor_SRCS settings.kcfgc) # executables add_executable(artikulate ${artikulate_SRCS}) target_link_libraries(artikulate LINK_PUBLIC artikulatelearnerprofile artikulatesound artikulatecore Qt5::Qml Qt5::Quick KF5::Crash KF5::NewStuff KF5::XmlGui ) qt5_add_resources(artikulate_editor_SRCS editor.qrc) add_executable(artikulate_editor ${artikulate_editor_SRCS}) target_link_libraries(artikulate_editor LINK_PUBLIC artikulatesound artikulatecore Qt5::Qml Qt5::Quick KF5::Crash KF5::NewStuff ) install(FILES artikulate.kcfg DESTINATION ${KCFG_INSTALL_DIR}) install(TARGETS artikulate ${INSTALL_TARGETS_DEFAULT_ARGS}) install(TARGETS artikulate_editor ${INSTALL_TARGETS_DEFAULT_ARGS}) diff --git a/src/core/contributorrepository.cpp b/src/core/contributorrepository.cpp index f563daf..90064d5 100644 --- a/src/core/contributorrepository.cpp +++ b/src/core/contributorrepository.cpp @@ -1,416 +1,418 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "contributorrepository.h" #include "artikulate_debug.h" #include "language.h" #include "unit.h" #include "phrase.h" #include "phoneme.h" #include "phonemegroup.h" #include "resources/editablecourseresource.h" #include "resources/skeletonresource.h" #include "liblearnerprofile/src/profilemanager.h" #include "liblearnerprofile/src/learninggoal.h" #include #include #include #include #include #include ContributorRepository::ContributorRepository() : IEditableRepository() { loadLanguageResources(); } ContributorRepository::~ContributorRepository() = default; void ContributorRepository::loadLanguageResources() { // load language resources // all other resources are only loaded on demand QDir dir(":/artikulate/languages/"); dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != QLatin1String("xml")) { continue; } addLanguage(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } void ContributorRepository::sync() { for (auto iter = m_courses.begin(); iter != m_courses.end(); ++iter) { for (auto course : iter.value()) { course->sync(); } } for (auto skeleton : m_skeletonResources) { skeleton->sync(); } } bool ContributorRepository::modified() const { for (auto iter = m_courses.constBegin(); iter != m_courses.constEnd(); ++iter) { for (auto course : iter.value()) { if (course->isModified()) { return true; } } } for (auto const &courseRes : m_skeletonResources) { if (courseRes->isModified()) { return true; } } return false; } void ContributorRepository::addLanguage(const QUrl &languageFile) { if (m_loadedResources.contains(languageFile.toLocalFile())) { return; } auto language = Language::create(languageFile); emit languageResourceAboutToBeAdded(language, m_languages.count()); m_languages.append(language); m_loadedResources.append(languageFile.toLocalFile()); m_courses.insert(language->id(), QVector>()); emit languageResourceAdded(); } -QString ContributorRepository::storageLocation() const +QUrl ContributorRepository::storageLocation() const { return m_storageLocation; } -void ContributorRepository::setStorageLocation(const QString &path) +void ContributorRepository::setStorageLocation(const QUrl &path) { m_storageLocation = path; + emit repositoryChanged(); + reloadCourses(); } QVector> ContributorRepository::languages() const { return m_languages; } std::shared_ptr ContributorRepository::language(int index) const { Q_ASSERT(index >= 0 && index < m_languages.count()); return m_languages.at(index); } ILanguage * ContributorRepository::language(LearnerProfile::LearningGoal *learningGoal) const { if (!learningGoal) { return nullptr; } if (learningGoal->category() != LearnerProfile::LearningGoal::Language) { qCritical() << "Cannot translate non-language learning goal to language"; return nullptr; } for (auto language : m_languages) { if (language->id() == learningGoal->identifier()) { return language.get(); } } qCritical() << "No language registered with identifier " << learningGoal->identifier() << ": aborting"; return nullptr; } QVector> ContributorRepository::courseResources(std::shared_ptr language) { if (!language) { QVector> courses; for (auto iter = m_courses.constBegin(); iter != m_courses.constEnd(); ++iter) { courses.append(iter.value()); } return courses; } // return empty list if no course available for language if (!m_courses.contains(language->id())) { return QVector>(); } return m_courses[language->id()]; } QVector> ContributorRepository::courses() const { QVector> courses; for (const auto &courseList : m_courses) { for (const auto &course : courseList) { courses.append(course); } } return courses; } QVector> ContributorRepository::editableCourses() const { QVector> courses; for (const auto &courseList : m_courses) { for (const auto &course : courseList) { courses.append(course); } } return courses; } QVector> ContributorRepository::courses(const QString &languageId) const { if (languageId.isEmpty()) { return courses(); } QVector> courses; if (m_courses.contains(languageId)) { for (const auto &course : m_courses[languageId]) { courses.append(course); } } return courses; } std::shared_ptr ContributorRepository::editableCourse(std::shared_ptr language, int index) const { Q_ASSERT(m_courses.contains(language->id())); Q_ASSERT(index >= 0 && index < m_courses[language->id()].count()); return m_courses[language->id()].at(index); } void ContributorRepository::reloadCourseOrSkeleton(std::shared_ptr courseOrSkeleton) { if (!courseOrSkeleton) { qCritical() << "Cannot reload non-existing course"; return; } if (!courseOrSkeleton->file().isValid()) { qCritical() << "Cannot reload temporary file, aborting."; return; } // figure out if this is a course or a skeleton if (courseOrSkeleton->language()) { // only course files have a language //TODO better add a check if this is contained in the course list // to catch possible errors QUrl file = courseOrSkeleton->file(); m_loadedResources.removeOne(courseOrSkeleton->file().toLocalFile()); removeCourse(courseOrSkeleton); addCourse(file); } else { for (auto resource : m_skeletonResources) { if (resource->id() == courseOrSkeleton->id()) { // TODO no reload available return; } } } } void ContributorRepository::reloadCourses() { // register skeleton resources - QDir skeletonDirectory = QDir(storageLocation()); + QDir skeletonDirectory = QDir(storageLocation().toLocalFile()); skeletonDirectory.setFilter(QDir::Files | QDir::Hidden); if (!skeletonDirectory.cd(QStringLiteral("skeletons"))) { qCritical() << "There is no subdirectory \"skeletons\" in directory " << skeletonDirectory.path() << " cannot load skeletons."; } else { // read skeletons QFileInfoList list = skeletonDirectory.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); addSkeleton(QUrl::fromLocalFile(fileInfo.absoluteFilePath())); } } // register contributor course files - QDir courseDirectory(storageLocation()); + QDir courseDirectory(storageLocation().toLocalFile()); if (!courseDirectory.cd(QStringLiteral("courses"))) { qCritical() << "There is no subdirectory \"courses\" in directory " << courseDirectory.path() << " cannot load courses."; } else { // find courses courseDirectory.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList courseDirList = courseDirectory.entryInfoList(); // traverse all course directories for (const QFileInfo &info : courseDirList) { QDir courseDir = QDir(info.absoluteFilePath()); courseDir.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList courseLangDirList = courseDir.entryInfoList(); // traverse all language directories for each course for (const QFileInfo &langInfo : courseLangDirList) { QDir courseLangDir = QDir(langInfo.absoluteFilePath()); courseLangDir.setFilter(QDir::Files); QStringList nameFilters; nameFilters.append(QStringLiteral("*.xml")); QFileInfoList courses = courseLangDir.entryInfoList(nameFilters); // find and add course files for (const QFileInfo &courseInfo : courses) { addCourse(QUrl::fromLocalFile(courseInfo.filePath())); } } } } //TODO this signal should only be emitted when repository was added/removed // yet the call to this method is very seldom and emitting it too often is not that harmful emit repositoryChanged(); } void ContributorRepository::updateCourseFromSkeleton(std::shared_ptr course) { //TODO implement status information that are shown at mainwindow if (course->foreignId().isEmpty()) { qCritical() << "No skeleton ID specified, aborting update."; return; } std::shared_ptr skeleton; for (const auto &iter : m_skeletonResources) { if (iter->id() == course->foreignId()) { skeleton = iter; break; } } if (!skeleton) { qCritical() << "Could not find skeleton with id " << course->foreignId() << ", aborting update."; } else { course->updateFrom(skeleton); } } std::shared_ptr ContributorRepository::addCourse(const QUrl &courseFile) { std::shared_ptr course; // skip already loaded resources if (m_loadedResources.contains(courseFile.toLocalFile())) { // TODO return existing resource } else { course = EditableCourseResource::create(courseFile, this); if (course->language() == nullptr) { qCritical() << "Could not load course, language unknown:" << courseFile.toLocalFile(); course.reset(); } else { // this is the regular case m_loadedResources.append(courseFile.toLocalFile()); const QString languageId = course->language()->id(); Q_ASSERT(!languageId.isEmpty()); if (!m_courses.contains(languageId)) { m_courses.insert(languageId, QVector>()); } emit courseAboutToBeAdded(course, m_courses[course->language()->id()].count()); m_courses[languageId].append(course); emit courseAdded(); emit languageCoursesChanged(); } } return course; } void ContributorRepository::removeCourse(std::shared_ptr course) { for (int index = 0; index < m_courses[course->language()->id()].length(); ++index) { if (m_courses[course->language()->id()].at(index) == course) { emit courseAboutToBeRemoved(index); m_courses[course->language()->id()].removeAt(index); emit courseRemoved(); return; } } } IEditableCourse * ContributorRepository::createCourse(std::shared_ptr language, std::shared_ptr skeleton) { // set path QString path = QStringLiteral("%1/%2/%3/%4/%4.xml") - .arg(storageLocation(), + .arg(storageLocation().toLocalFile(), QStringLiteral("courses"), skeleton->id(), language->id()); auto course = EditableCourseResource::create(QUrl::fromLocalFile(path), this); Q_ASSERT(course); course->setId(QUuid::createUuid().toString()); course->setTitle(skeleton->title()); course->setDescription(skeleton->description()); course->setLanguage(language); course->setForeignId(skeleton->id()); return course.get(); } std::shared_ptr ContributorRepository::addSkeleton(const QUrl &file) { std::shared_ptr resource; // skip already loaded resources if (m_loadedResources.contains(file.toLocalFile())) { qCInfo(ARTIKULATE_LOG()) << "Skeleton already loaded, using known resource:" << file; for (auto skeleton : m_skeletonResources) { if (skeleton->file() == file) { resource = skeleton; break; } } } else { resource = SkeletonResource::create(file, this); m_loadedResources.append(resource->file().toLocalFile()); emit skeletonAboutToBeAdded(resource.get(), m_skeletonResources.count()); m_skeletonResources.append(resource); emit skeletonAdded(); } return resource; } void ContributorRepository::removeSkeleton(SkeletonResource *skeleton) { for (int index = 0; index < m_skeletonResources.length(); ++index) { if (m_skeletonResources.at(index)->id() == skeleton->id()) { emit skeletonAboutToBeRemoved(index, index); m_skeletonResources.removeAt(index); emit skeletonRemoved(); return; } } } QVector> ContributorRepository::skeletons() const { QVector> skeletonList; for (const auto &skeleton : m_skeletonResources) { skeletonList.append(skeleton); } return skeletonList; } diff --git a/src/core/contributorrepository.h b/src/core/contributorrepository.h index 50ad415..ce17888 100644 --- a/src/core/contributorrepository.h +++ b/src/core/contributorrepository.h @@ -1,196 +1,196 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CONTRIBUTORREPOSITORY_H #define CONTRIBUTORREPOSITORY_H #include "artikulatecore_export.h" #include "ieditablerepository.h" #include #include #include #include #include #include +#include #include "liblearnerprofile/src/learninggoal.h" class SkeletonResource; class EditableCourseResource; class LanguageResource; class Language; class ICourse; -class QUrl; /** * @class ContributorRepository * This class handles the resources of a contributor. */ class ARTIKULATECORE_EXPORT ContributorRepository : public IEditableRepository { Q_OBJECT Q_INTERFACES(IResourceRepository) Q_INTERFACES(IEditableRepository) - Q_PROPERTY(QString repositoryUrl READ storageLocation NOTIFY repositoryChanged) + Q_PROPERTY(QUrl repositoryUrl READ storageLocation WRITE setStorageLocation NOTIFY repositoryChanged) public: explicit ContributorRepository(); ~ContributorRepository() override; /** * save all changes to course resources */ void sync(); /** * \return \c true if any course or skeleton is modified, otherwise \c false */ bool modified() const; /** * \return path to working repository, if one is set */ - QString storageLocation() const override; + QUrl storageLocation() const override; /** * Set path to central storage location * \param path the path to the storage location directory */ - void setStorageLocation(const QString &path); + void setStorageLocation(const QUrl &path); QVector> languages() const override; /** * \return language by \p index */ std::shared_ptr language(int index) const; /** * \return language by \p learningGoal */ Q_INVOKABLE ILanguage * language(LearnerProfile::LearningGoal* learningGoal) const; QVector> courses() const override; QVector> courses(const QString &languageId) const override; QVector> editableCourses() const override; /** * \return list of all loaded courses for language \p language */ QVector> courseResources(std::shared_ptr language); std::shared_ptr editableCourse(std::shared_ptr language, int index) const override; /** * Reset the file for this course or skeleton. * * \param course the course to be reloaded */ void reloadCourseOrSkeleton(std::shared_ptr course); /** * @brief Implementation of course resource reloading */ void reloadCourses() override; void updateCourseFromSkeleton(std::shared_ptr course) override; /** * Add language to resource manager by parsing the given language specification file. * * \param languageFile is the local XML file containing the language */ void addLanguage(const QUrl &languageFile); /** * Adds course to resource manager by parsing the given course specification file. * * \param courseFile is the local XML file containing the course * \return true if loaded successfully, otherwise false */ std::shared_ptr addCourse(const QUrl &courseFile); /** * Adds course to resource manager. If the course's language is not registered, the language * is registered by this method. * * \param resource the course resource to add to resource manager */ std::shared_ptr addCourseResource(std::unique_ptr resource); /** * Remove course from resource manager. If the course is modified its changes are NOT * written. For writing changes, the Course::sync() method must be called directly. * * \param course is the course to be removed */ void removeCourse(std::shared_ptr course); /** * Create new course for \p language and derived from \p skeleton. * * \return created course */ Q_INVOKABLE IEditableCourse * createCourse(std::shared_ptr language, std::shared_ptr skeleton); /** * Adds skeleton resource to resource manager * * \param resource the skeleton resource to add to resource manager */ std::shared_ptr addSkeleton(const QUrl &skeletonFile); /** * Remove skeleton from resource manager. If the skeleton is modified its changes are NOT * written. For writing changes, the Skeleton::sync() method must be called directly. * * \param skeleton is the skeleton to be removed */ void removeSkeleton(SkeletonResource *skeleton); QVector> skeletons() const override; Q_SIGNALS: void languageResourceAdded(); void languageResourceAboutToBeAdded(std::shared_ptr,int); void languageResourceRemoved(); void languageResourceAboutToBeRemoved(int); void repositoryChanged(); void skeletonAdded(); void skeletonAboutToBeAdded(ICourse*,int); void skeletonRemoved(); void skeletonAboutToBeRemoved(int,int); void languageCoursesChanged(); private: /** * This method loads all language files that are provided in the standard directories * for this application. */ void loadLanguageResources(); - QString m_storageLocation; + QUrl m_storageLocation; QVector> m_languages; QMap> > m_courses; //!> (language-id, course-resource) QVector> m_skeletonResources; QStringList m_loadedResources; }; #endif diff --git a/src/core/iresourcerepository.h b/src/core/iresourcerepository.h index a9c159d..ce4aadc 100644 --- a/src/core/iresourcerepository.h +++ b/src/core/iresourcerepository.h @@ -1,78 +1,78 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef IRESOURCEREPOSITORY_H #define IRESOURCEREPOSITORY_H #include "artikulatecore_export.h" #include #include class ICourse; class ILanguage; /** * \class IResourceRepository * This interface provides a generic interface that provides just the methods and signals needed * to integrade a repository into the training part of Artikulate. */ class ARTIKULATECORE_EXPORT IResourceRepository : public QObject { Q_OBJECT public: virtual ~IResourceRepository() = default; /** * \return path to working repository, if one is set */ - virtual QString storageLocation() const = 0; + virtual QUrl storageLocation() const = 0; /** * @return list of all loaded courses */ virtual QVector> courses() const = 0; /** * @param language to use for filtering * @return list of all loaded courses filtered by the named language */ virtual QVector> courses(const QString &languageId) const = 0; /** * @brief Requests a refresh of all resources * * Typical reasons to call this are GHNS signals */ virtual void reloadCourses() = 0; /** * \return list of all available languages */ virtual QVector> languages() const = 0; Q_SIGNALS: void courseAboutToBeAdded(std::shared_ptr,int); void courseAdded(); void courseAboutToBeRemoved(int); void courseRemoved(); }; Q_DECLARE_INTERFACE(IResourceRepository, "IResourceRepository") #endif diff --git a/src/core/resourcerepository.cpp b/src/core/resourcerepository.cpp index 5bfdd98..e2beaf6 100644 --- a/src/core/resourcerepository.cpp +++ b/src/core/resourcerepository.cpp @@ -1,159 +1,159 @@ /* * Copyright 2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "resourcerepository.h" #include "artikulate_debug.h" #include "resources/courseresource.h" #include "core/language.h" #include #include #include #include ResourceRepository::ResourceRepository() : ResourceRepository(QUrl::fromLocalFile(QStandardPaths::standardLocations(QStandardPaths::DataLocation).constFirst() + QStringLiteral("/courses/"))) { } ResourceRepository::ResourceRepository(const QUrl &storageLocation) : IResourceRepository() , m_storageLocation(storageLocation.toLocalFile()) { // load language resources // all other resources are only loaded on demand QDir dir(":/artikulate/languages/"); dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != QLatin1String("xml")) { continue; } loadLanguage(fileInfo.absoluteFilePath()); } } ResourceRepository::~ResourceRepository() = default; -QString ResourceRepository::storageLocation() const +QUrl ResourceRepository::storageLocation() const { return m_storageLocation; } QVector> ResourceRepository::courses() const { QVector> courses; for (const auto &course : m_courses) { courses.append(course); } return courses; } QVector> ResourceRepository::courses(const QString &languageId) const { QVector> courses; for (const auto &course : m_courses) { if (course->language() && course->language()->id() == languageId) { continue; } courses.append(course); } return courses; } QVector> ResourceRepository::languages() const { QVector> languages; for (const auto &language : m_languages) { if (language == nullptr) { continue; } languages.append(language); } return languages; } std::shared_ptr ResourceRepository::language(const QString &id) const { if (m_languages.contains(id)) { return m_languages.value(id); } return nullptr; } void ResourceRepository::reloadCourses() { std::function scanDirectoryForXmlCourseFiles = [this](QDir dir) { dir.setFilter(QDir::Files | QDir::NoSymLinks); QFileInfoList list = dir.entryInfoList(); for (int i = 0; i < list.size(); ++i) { QFileInfo fileInfo = list.at(i); if (fileInfo.completeSuffix() != QLatin1String("xml")) { continue; } loadCourse(fileInfo.absoluteFilePath()); } }; - QDir rootDirectory = QDir(m_storageLocation); + QDir rootDirectory = QDir(m_storageLocation.toLocalFile()); QDirIterator it(rootDirectory, QDirIterator::Subdirectories); qCInfo(ARTIKULATE_CORE()) << "Loading courses from" << rootDirectory.absolutePath(); while (it.hasNext()) { scanDirectoryForXmlCourseFiles(it.next()); } } bool ResourceRepository::loadCourse(const QString &resourceFile) { qCDebug(ARTIKULATE_CORE()) << "Loading resource" << resourceFile; // skip already loaded resources if (m_loadedCourses.contains(resourceFile)) { qCWarning(ARTIKULATE_CORE()) << "Reloading of resources not yet supported, skippen course"; return false; } auto resource = CourseResource::create(QUrl::fromLocalFile(resourceFile), this); if (resource->language() == nullptr) { qCCritical(ARTIKULATE_CORE()) << "Could not load course, language unknown:" << resourceFile; return false; } emit courseAboutToBeAdded(resource, m_courses.count() - 1); m_courses.append(resource); emit courseAdded(); m_loadedCourses.append(resourceFile); return true; } bool ResourceRepository::loadLanguage(const QString &resourceFile) { auto language = Language::create(QUrl::fromLocalFile(resourceFile)); if (!language) { qCWarning(ARTIKULATE_CORE()) << "Could not load language" << resourceFile; return false; } if (m_languages.contains(language->id())) { qCWarning(ARTIKULATE_CORE()) << "Could not load language" << resourceFile; return false; } m_languages.insert(language->id(), language); return true; } diff --git a/src/core/resourcerepository.h b/src/core/resourcerepository.h index bdae7df..1a42e99 100644 --- a/src/core/resourcerepository.h +++ b/src/core/resourcerepository.h @@ -1,97 +1,97 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef RESOURCEREPOSITORY_H #define RESOURCEREPOSITORY_H #include "artikulatecore_export.h" #include "iresourcerepository.h" #include #include #include #include #include +#include -class QUrl; class CourseResource; class ICourse; class Language; class LanguageResource; /** * @class ResourceRepository * * This class provides data handling of all downloaded trainingdata of a user. It dervies from the repository interface * to provide a generalized access to the data. */ class ARTIKULATECORE_EXPORT ResourceRepository : public IResourceRepository { Q_OBJECT Q_INTERFACES(IResourceRepository) public: explicit ResourceRepository(); ~ResourceRepository() override; /** * @brief Construtor for ResourceRepository object with explicitly set course folder * * @param storageLocation relative or absolute path to courses/ folder (including that directory) */ explicit ResourceRepository(const QUrl &storageLocation); /** * @return path to repository location */ - QString storageLocation() const override; + QUrl storageLocation() const override; /** * @return list of available courses */ QVector> courses() const override; /** * @return list of available courses */ QVector> courses(const QString &languageId) const override; /** * @return list of all available language specifications */ QVector> languages() const override; std::shared_ptr language(const QString &id) const; public Q_SLOTS: /** * \brief updates available resources */ void reloadCourses() override; private: bool loadCourse(const QString &resourceFile); bool loadLanguage(const QString &resourceFile); QVector> m_courses; QHash> m_languages; ///>! (language-identifier, language resource) QStringList m_loadedCourses; - const QString m_storageLocation; + const QUrl m_storageLocation; }; #endif // RESOURCEREPOSITORY_H diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index b6331d9..069509d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,192 +1,186 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" -#include "ui/resourcesdialogpage.h" #include "ui/sounddevicedialogpage.h" -#include "ui/appearencedialogpage.h" #include "core/resourcerepository.h" #include "core/trainingsession.h" #include "core/editorsession.h" #include "core/resources/courseresource.h" #include "application.h" #include "models/languagemodel.h" #include "settings.h" #include "liblearnerprofile/src/profilemanager.h" #include "liblearnerprofile/src/learner.h" #include "libsound/src/outputdevicecontroller.h" #include "artikulate_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace LearnerProfile; MainWindow::MainWindow() : m_actionCollection(new KActionCollection(this, QStringLiteral("artikulate"))) , m_profileManager(new LearnerProfile::ProfileManager(this)) , m_trainingSession(new TrainingSession(m_profileManager, this)) { rootContext()->setContextObject(new KLocalizedContext(this)); // load saved sound settings OutputDeviceController::self().setVolume(Settings::audioOutputVolume()); // create menu setupActions(); // set view rootContext()->setContextProperty(QStringLiteral("g_trainingSession"), m_trainingSession); rootContext()->setContextProperty(QStringLiteral("g_profileManager"), m_profileManager); rootContext()->setContextProperty(QStringLiteral("g_artikulateAboutData"), QVariant::fromValue(KAboutData::applicationData())); // set starting screen load(QUrl(QStringLiteral("qrc:/artikulate/qml/Main.qml"))); // create training profile if none exists: if (!m_profileManager->activeProfile()) { m_profileManager->addProfile(i18n("Unnamed Identity")); } // connect to QML signals; connect(rootObjects().constFirst(), SIGNAL(ghnsCourseDataStatusChanged()), this, SLOT(updateCourseResources())); connect(rootObjects().constFirst(), SIGNAL(triggerAction(QString)), this, SLOT(triggerAction(QString))); // set font for the phrase in trainer to default from kcfg file QObject *phraseText = rootObjects().constFirst()->findChild(QStringLiteral("phraseText")); if (phraseText) { phraseText->setProperty("font", Settings::trainingPhraseFont()); } } MainWindow::~MainWindow() { // save current settings for case of closing Settings::self()->save(); m_profileManager->sync(); } KActionCollection * MainWindow::actionCollection() { return m_actionCollection; } void MainWindow::setupActions() { QAction *settingsAction = new QAction(i18nc("@item:inmenu", "Configure Artikulate"), this); connect(settingsAction, &QAction::triggered, this, &MainWindow::showSettingsDialog); actionCollection()->addAction(QStringLiteral("settings"), settingsAction); settingsAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); QAction *configLearnerProfileAction = new QAction(i18nc("@item:inmenu", "Learner Profile"), this); connect(configLearnerProfileAction, &QAction::triggered, this, &MainWindow::configLearnerProfile); actionCollection()->addAction(QStringLiteral("config_learner_profile"), configLearnerProfileAction); configLearnerProfileAction->setIcon(QIcon::fromTheme(QStringLiteral("user-identity"))); KStandardAction::quit(qApp, SLOT(quit()), actionCollection()); } void MainWindow::showSettingsDialog() { if (KConfigDialog::showDialog(QStringLiteral("settings"))) { return; } QPointer dialog = new KConfigDialog(nullptr, QStringLiteral("settings"), Settings::self()); SoundDeviceDialogPage *soundDialog = new SoundDeviceDialogPage(); - AppearenceDialogPage *appearenceDialog = new AppearenceDialogPage(); soundDialog->loadSettings(); - appearenceDialog->loadSettings(); dialog->addPage(soundDialog, i18nc("@item:inmenu", "Sound Devices"), QStringLiteral("audio-headset"), i18nc("@title:tab", "Sound Device Settings"), true); - dialog->addPage(appearenceDialog, i18nc("@item:inmenu", "Fonts"), QStringLiteral("preferences-desktop-font"), i18nc("@title:tab", "Training Phrase Font"), true); connect(dialog.data(), &QDialog::accepted, soundDialog, &SoundDeviceDialogPage::saveSettings); - connect(dialog.data(), &QDialog::accepted, appearenceDialog, &AppearenceDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, this, &MainWindow::updateTrainingPhraseFont); connect(dialog.data(), &QDialog::accepted, this, &MainWindow::updateKcfgUseContributorResources); connect(dialog.data(), &QDialog::finished, soundDialog, &SoundDeviceDialogPage::stopPlaying); connect(dialog.data(), &QDialog::finished, soundDialog, &SoundDeviceDialogPage::stopRecord); dialog->exec(); } void MainWindow::updateCourseResources() { artikulateApp->resourceRepository()->reloadCourses(); } void MainWindow::updateTrainingPhraseFont() { QObject *phraseText = rootObjects().constFirst()->findChild(QStringLiteral("phraseText")); if (!phraseText) { qCDebug(ARTIKULATE_LOG) << "no phraseText context object found, aborting"; return; } phraseText->setProperty("font", Settings::trainingPhraseFont()); } void MainWindow::updateKcfgUseContributorResources() { rootContext()->setContextProperty(QStringLiteral("kcfg_UseContributorResources"), Settings::useCourseRepository()); } void MainWindow::configLearnerProfile() { qCritical() << "Not implemented"; //FIXME } void MainWindow::triggerAction(const QString &actionName) { QAction * action = actionCollection()->action(actionName); if (action) { action->trigger(); } else { qCritical() << "Action is not registered:" << actionName; } } void MainWindow::switchMenuBarVisibility() { Settings::setShowMenuBar(!Settings::showMenuBar()); rootContext()->setContextProperty(QStringLiteral("kcfg_ShowMenuBar"), Settings::showMenuBar()); } bool MainWindow::queryClose() { Settings::self()->save(); // FIXME make sure all learner data is written to database return true; } diff --git a/src/mainwindow_editor.cpp b/src/mainwindow_editor.cpp index d05f178..e2455e4 100644 --- a/src/mainwindow_editor.cpp +++ b/src/mainwindow_editor.cpp @@ -1,166 +1,156 @@ /* * Copyright 2013-2015 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow_editor.h" #include "application.h" -#include "ui/resourcesdialogpage.h" #include "ui/sounddevicedialogpage.h" -#include "ui/appearencedialogpage.h" #include "ui/exportghnsdialog.h" #include "core/editorsession.h" #include "core/resources/courseresource.h" #include "models/languagemodel.h" #include "settings.h" #include "libsound/src/outputdevicecontroller.h" #include "artikulate_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace LearnerProfile; MainWindowEditor::MainWindowEditor(ContributorRepository *repository) : m_repository(repository) , m_editorSession(new EditorSession()) { rootContext()->setContextObject(new KLocalizedContext(this)); rootContext()->setContextProperty(QStringLiteral("g_repository"), m_repository); rootContext()->setContextProperty(QStringLiteral("g_editorSession"), m_editorSession); rootContext()->setContextProperty(QStringLiteral("g_artikulateAboutData"), QVariant::fromValue(KAboutData::applicationData())); - m_repository->setStorageLocation(Settings::courseRepositoryPath()); + m_repository->setStorageLocation(QUrl::fromLocalFile(Settings::courseRepositoryPath())); m_editorSession->setRepository(m_repository); // load saved sound settings OutputDeviceController::self().setVolume(Settings::audioOutputVolume()); m_repository->reloadCourses(); // create menu setupActions(); // set starting screen load(QUrl(QStringLiteral("qrc:/artikulate/qml/Editor.qml"))); // QAction *newAct = KStandardAction::save(this, SLOT(save()), actionCollection()); // actionCollection()->addAction(QStringLiteral("save"), newAct); } MainWindowEditor::~MainWindowEditor() { // save current settings for case of closing Settings::self()->save(); } ContributorRepository * MainWindowEditor::resourceRepository() const { return m_repository; } void MainWindowEditor::setupActions() { // QAction *settingsAction = new QAction(i18nc("@item:inmenu", "Configure Artikulate"), this); // connect(settingsAction, &QAction::triggered, this, &MainWindowEditor::showSettingsDialog); // actionCollection()->addAction(QStringLiteral("settings"), settingsAction); // settingsAction->setIcon(QIcon::fromTheme(QStringLiteral("configure"))); // QAction *exportAction = new QAction(i18nc("@item:inmenu", "Export GHNS Files"), this); // connect(exportAction, &QAction::triggered, this, [=]() { // QPointer dialog = new ExportGhnsDialog(m_repository); // dialog->exec(); // }); // actionCollection()->addAction(QStringLiteral("export_ghns"), exportAction); // exportAction->setIcon(QIcon::fromTheme(QStringLiteral("document-export"))); // KStandardAction::quit(this, SLOT(quit()), actionCollection()); } void MainWindowEditor::showSettingsDialog() { if (KConfigDialog::showDialog(QStringLiteral("settings"))) { return; } QPointer dialog = new KConfigDialog(nullptr, QStringLiteral("settings"), Settings::self()); - ResourcesDialogPage *resourceDialog = new ResourcesDialogPage(m_repository); SoundDeviceDialogPage *soundDialog = new SoundDeviceDialogPage(); - AppearenceDialogPage *appearenceDialog = new AppearenceDialogPage(); - resourceDialog->loadSettings(); soundDialog->loadSettings(); - appearenceDialog->loadSettings(); dialog->addPage(soundDialog, i18nc("@item:inmenu", "Sound Devices"), QStringLiteral("audio-headset"), i18nc("@title:tab", "Sound Device Settings"), true); - dialog->addPage(appearenceDialog, i18nc("@item:inmenu", "Fonts"), QStringLiteral("preferences-desktop-font"), i18nc("@title:tab", "Training Phrase Font"), true); - dialog->addPage(resourceDialog, i18nc("@item:inmenu", "Course Resources"), QStringLiteral("repository"), i18nc("@title:tab", "Resource Repository Settings"), true); - connect(dialog.data(), &QDialog::accepted, resourceDialog, &ResourcesDialogPage::saveSettings); connect(dialog.data(), &QDialog::accepted, soundDialog, &SoundDeviceDialogPage::saveSettings); - connect(dialog.data(), &QDialog::accepted, appearenceDialog, &AppearenceDialogPage::saveSettings); dialog->exec(); } void MainWindowEditor::save() { m_repository->sync(); } void MainWindowEditor::quit() { // if (queryClose()) { // qApp->quit(); // } } //bool MainWindowEditor::queryClose() //{ // if (!m_repository->modified()) { // return true; // } // int result = KMessageBox::warningYesNoCancel(nullptr, i18nc("@info", // "The currently open course contains unsaved changes. Do you want to save them?")); // switch(result) { // case KMessageBox::Yes: // m_repository->sync(); // return true; // case KMessageBox::No: // return true; // default: // return false; // } //} diff --git a/src/qml/Editor.qml b/src/qml/Editor.qml index 291f91e..74065f3 100644 --- a/src/qml/Editor.qml +++ b/src/qml/Editor.qml @@ -1,66 +1,64 @@ /* * Copyright 2013-2019 Andreas Cord-Landwehr * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick 2.5 import QtQuick.Controls 2.3 import QtQuick.Controls 1.4 as QQC1 import QtQuick.Layouts 1.2 import QtQml.Models 2.2 import org.kde.kirigami 2.7 as Kirigami import artikulate 1.0 Kirigami.ApplicationWindow { id: root function changePage(pageItem) { root.pageStack.clear(); root.pageStack.push(pageItem); root.pageStack.push(pageItem); } globalDrawer: EditorDrawer { pageStack: root.pageStack } pageStack.initialPage: welcomePageComponent pageStack.globalToolBar.style: Kirigami.ApplicationHeaderStyle.Titles // pages Component { id: welcomePageComponent WelcomePageEditor { } } Component { id: editCoursePageComponent EditCoursePage { } } Component { id: repositoryPageComponent - Item { - // TODO not implemented yet - } + RepositoryConfigurationPage { } } Component { id: aboutPageComponent Kirigami.AboutPage { aboutData: g_artikulateAboutData } } } diff --git a/src/qml/RepositoryConfigurationPage.qml b/src/qml/RepositoryConfigurationPage.qml new file mode 100644 index 0000000..19a5401 --- /dev/null +++ b/src/qml/RepositoryConfigurationPage.qml @@ -0,0 +1,58 @@ +/* + * Copyright 2019 Andreas Cord-Landwehr + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License or (at your option) version 3 or any later version + * accepted by the membership of KDE e.V. (or its successor approved + * by the membership of KDE e.V.), which shall act as a proxy + * defined in Section 14 of version 3 of the license. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.4 +import QtQuick.Dialogs 1.2 +import org.kde.kirigami 2.7 as Kirigami + +Kirigami.Page { + id: root + + title: i18n("Repository Configuration") + + FileDialog { + id: fileDialog + modality: Qt.WindowModal + title: i18n("Select Repository Folder") + selectFolder: true + folder: g_repository.repositoryUrl + sidebarVisible: true + onAccepted: { + console.log("Accepted: " + fileUrl) + g_repository.repositoryUrl = fileUrl + } + } + + Kirigami.FormLayout { + anchors.fill: parent + TextField { + Kirigami.FormData.label: i18n("Repository:") + readOnly: true + text: g_repository.repositoryUrl.toString().replace(/^(file:\/{2})/,""); + } + Button { + text: i18n("Change Folder") + icon.name: "document-open-folder" + onClicked: fileDialog.open() + } + } +} diff --git a/src/resources.qrc b/src/resources.qrc index 91dd249..cde0045 100644 --- a/src/resources.qrc +++ b/src/resources.qrc @@ -1,28 +1,29 @@ qml/ActionListItem.qml qml/ArtikulateDrawer.qml qml/DownloadPage.qml qml/EditCoursePage.qml qml/Editor.qml qml/EditorDrawer.qml qml/Main.qml qml/PhonemeUnitSelector.qml qml/PhraseEditor.qml qml/PhraseEditorEditStateComponent.qml qml/PhraseEditorSoundComponent.qml qml/PhraseEditorTypeComponent.qml qml/ProfileSelector.qml qml/ProfileSettingsPage.qml qml/ProfileUserImageItem.qml + qml/RepositoryConfigurationPage.qml qml/SoundPlayer.qml qml/SoundRecorder.qml qml/TrainerCourseStatistics.qml qml/TrainingPage.qml qml/WelcomePage.qml qml/WelcomePageEditor.qml artikulate.knsrc diff --git a/src/ui/resourcesdialogpage.cpp b/src/ui/resourcesdialogpage.cpp deleted file mode 100644 index 93a0aa7..0000000 --- a/src/ui/resourcesdialogpage.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2013-2015 Andreas Cord-Landwehr - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "resourcesdialogpage.h" -#include "core/contributorrepository.h" -#include "core/language.h" -#include "settings.h" - -#include -#include -#include -#include -#include - -ResourcesDialogPage::ResourcesDialogPage(ContributorRepository *repository) - : QWidget(nullptr) - , m_repository(repository) - , m_restartNeeded(false) -{ - ui = new Ui::ResourcesDialogPage; - ui->setupUi(this); - - connect(ui->buttonSelectCourseRepository, &QToolButton::clicked, this, [=](){ - const QString dir = QFileDialog::getExistingDirectory(this, - i18n("Open Repository Directory"), - QString(), - QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); - ui->kcfg_CourseRepositoryPath->setText(dir); - }); -} - -ResourcesDialogPage::~ResourcesDialogPage() -{ - delete ui; -} - -void ResourcesDialogPage::loadSettings() -{ - // setup Ui with stored settings - ui->kcfg_CourseRepositoryPath->setText(Settings::courseRepositoryPath()); - ui->kcfg_UseCourseRepository->setChecked(Settings::useCourseRepository()); -} - -void ResourcesDialogPage::saveSettings() -{ - // save settings - Settings::setUseCourseRepository(ui->kcfg_UseCourseRepository->isChecked()); - Settings::setCourseRepositoryPath(ui->kcfg_CourseRepositoryPath->text()); - Settings::self()->save(); - // reloading resources - m_repository->reloadCourses(); -} diff --git a/src/ui/resourcesdialogpage.h b/src/ui/resourcesdialogpage.h deleted file mode 100644 index 0efa9a1..0000000 --- a/src/ui/resourcesdialogpage.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2013-2015 Andreas Cord-Landwehr - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License or (at your option) version 3 or any later version - * accepted by the membership of KDE e.V. (or its successor approved - * by the membership of KDE e.V.), which shall act as a proxy - * defined in Section 14 of version 3 of the license. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef RESOURCESDIALOGPAGE_H -#define RESOURCESDIALOGPAGE_H - -#include "ui_resourcesdialogpage.h" -#include - -class ContributorRepository; -class Course; - -class ResourcesDialogPage : public QWidget -{ - Q_OBJECT - -public: - explicit ResourcesDialogPage(ContributorRepository *repository); - virtual ~ResourcesDialogPage(); - -public Q_SLOTS: - void saveSettings(); - void loadSettings(); - -private: - Ui::ResourcesDialogPage *ui; - ContributorRepository *m_repository{nullptr}; - bool m_restartNeeded; -}; - -#endif diff --git a/src/ui/resourcesdialogpage.ui b/src/ui/resourcesdialogpage.ui deleted file mode 100644 index 777899a..0000000 --- a/src/ui/resourcesdialogpage.ui +++ /dev/null @@ -1,131 +0,0 @@ - - - ResourcesDialogPage - - - - 0 - 0 - 400 - 269 - - - - - 0 - 0 - - - - - 400 - 250 - - - - - 0 - 0 - - - - - - - - 0 - 0 - - - - - 0 - 0 - - - - - 0 - 0 - - - - This dialog allows advanced settings. It is addressed only to translators, speakers, and other users of the course editor. Adding a repository allows you to work on courses and course skeletons in a structured way. For details please refer to the technical documentation. - - - true - - - - - - - Course Contributor Repository: - - - - - - - - - - 0 - 0 - - - - Path to the contributor repository. - - - true - - - - - - - ... - - - - - - - - - - - - Qt::Horizontal - - - - - - - Use this option only if you are a course contributor and you are working on a course repository. - - - Use course contributor repository in trainer mode - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - -