diff --git a/src/mainwindow_editor.cpp b/src/mainwindow_editor.cpp index 976656d..dc983bd 100644 --- a/src/mainwindow_editor.cpp +++ b/src/mainwindow_editor.cpp @@ -1,197 +1,198 @@ /* * 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 #include #include #include #include #include #include #include #include #include #include #include "artikulate_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace LearnerProfile; MainWindowEditor::MainWindowEditor(ContributorRepository *repository) : m_repository(repository) , m_editorSession(new EditorSession()) , m_widget(new QQuickWidget) { + m_repository->setStorageLocation(Settings::courseRepositoryPath()); m_editorSession->setContributorRepository(m_repository); setWindowIcon(QIcon::fromTheme(QStringLiteral("artikulate"))); setWindowTitle(qAppName()); setAutoSaveSettings(); // workaround for QTBUG-40765 qApp->setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); // load saved sound settings OutputDeviceController::self().setVolume(Settings::audioOutputVolume()); // load resources if (m_repository->languages().count() == 0) { qFatal("No language resources found, cannot start application."); } m_repository->reloadCourses(); // create menu setupActions(); // set view m_widget->resize(QSize(800, 600)); m_widget->rootContext()->setContextObject(new KLocalizedContext(m_widget)); - m_widget->rootContext()->setContextProperty(QStringLiteral("g_resourceManager"), m_repository); + m_widget->rootContext()->setContextProperty(QStringLiteral("g_repository"), m_repository); m_widget->rootContext()->setContextProperty(QStringLiteral("editorSession"), m_repository); // set starting screen m_widget->setSource(QUrl(QStringLiteral("qrc:/artikulate/qml/Editor.qml"))); m_widget->setResizeMode(QQuickWidget::SizeRootObjectToView); QAction *newAct = KStandardAction::save(this, SLOT(save()), actionCollection()); actionCollection()->addAction(QStringLiteral("save"), newAct); // set status bar statusBar()->setEnabled(true); QLabel *repositoryLabel = new QLabel; repositoryLabel->setText(i18n("Course Repository: %1", m_repository->storageLocation())); connect(m_repository, &ContributorRepository::repositoryChanged, this, [=]() { repositoryLabel->setText(i18n("Course Repository: %1", m_repository->storageLocation())); }); statusBar()->insertWidget(0, repositoryLabel); createGUI(QStringLiteral("artikulateui_editor.rc")); setCentralWidget(m_widget); } 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()); setupGUI(Keys | Save | Create, QStringLiteral("artikulateui_editor.rc")); } 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/models/languageresourcemodel.cpp b/src/models/languageresourcemodel.cpp index 4a260cc..2b5ff6e 100644 --- a/src/models/languageresourcemodel.cpp +++ b/src/models/languageresourcemodel.cpp @@ -1,217 +1,219 @@ /* * Copyright 2013 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 "languageresourcemodel.h" +#include "application.h" #include "core/language.h" #include "core/iresourcerepository.h" #include "core/resources/languageresource.h" #include "core/resources/courseresource.h" #include #include #include #include "artikulate_debug.h" LanguageResourceModel::LanguageResourceModel(QObject* parent) : QAbstractListModel(parent) , m_repository(nullptr) , m_view(LanguageModel::NonEmptyGhnsOnlyLanguages) , m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitLanguageChanged(int))); + setResourceRepository(artikulateApp->resourceRepository()); } QHash< int, QByteArray > LanguageResourceModel::roleNames() const { QHash roles; roles[TitleRole] = "title"; roles[I18nTitleRole] = "i18nTitle"; roles[IdRole] = "id"; roles[DataRole] = "dataRole"; roles[CourseNumberRole] = "courseNumberRole"; return roles; } void LanguageResourceModel::setResourceRepository(IResourceRepository *repository) { if (m_repository == repository) { return; } beginResetModel(); if (m_repository) { m_repository->disconnect(this); } m_repository = repository; updateResources(); endResetModel(); emit resourceRepositoryChanged(); } IResourceRepository * LanguageResourceModel::resourceRepository() const { return m_repository; } QVariant LanguageResourceModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } if (index.row() >= m_resources.count()) { return QVariant(); } Language * const language = m_resources.at(index.row())->language(); switch(role) { case Qt::DisplayRole: return !language->title().isEmpty() ? QVariant(language->title()): QVariant(i18nc("@item:inlistbox:", "unknown")); case Qt::ToolTipRole: return QVariant(language->title()); case TitleRole: return language->title(); case I18nTitleRole: return language->i18nTitle(); case IdRole: return language->id(); case DataRole: return QVariant::fromValue(language); case CourseNumberRole: return m_resources.count(); default: return QVariant(); } } int LanguageResourceModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) { return 0; } return m_resources.count(); } void LanguageResourceModel::onLanguageResourceAboutToBeAdded(LanguageResource *resource, int index) { beginInsertRows(QModelIndex(), index, index); m_resources.append(resource); connect(resource->language(), SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); connect(resource->language(), SIGNAL(phonemesChanged()), m_signalMapper, SLOT(map())); connect(resource->language(), SIGNAL(phonemeGroupsChanged()), m_signalMapper, SLOT(map())); } void LanguageResourceModel::onLanguageResourceAdded() { updateMappings(); endInsertRows(); } void LanguageResourceModel::onLanguageResourceAboutToBeRemoved(int index) { //FIXME adapt to repository // if (!m_repository) { // return; // } // LanguageResource *originalResource = m_repository->languages().at(index); // int modelIndex = m_resources.indexOf(originalResource); // if (modelIndex == -1) { // qCWarning(ARTIKULATE_LOG) << "Cannot remove language from model, not registered"; // return; // } // beginRemoveRows(QModelIndex(), modelIndex, modelIndex); // originalResource->disconnect(m_signalMapper); // m_resources.removeAt(modelIndex); } void LanguageResourceModel::onLanguageResourceRemoved() { endRemoveRows(); } void LanguageResourceModel::emitLanguageChanged(int row) { emit languageChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant LanguageResourceModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) { return QVariant(); } if (orientation == Qt::Vertical) { return QVariant(section + 1); } return QVariant(i18nc("@title:column", "Language")); } void LanguageResourceModel::setView(LanguageModel::LanguageResourceView view) { if (m_view == view) { return; } beginResetModel(); m_view = view; updateResources(); endResetModel(); } void LanguageResourceModel::updateDisplayedLanguages() { beginResetModel(); updateResources(); endResetModel(); } LanguageModel::LanguageResourceView LanguageResourceModel::view() const { return m_view; } void LanguageResourceModel::updateResources() { if (!m_repository) { return; } m_resources.clear(); QVector resources = m_repository->languages(); //FIXME complete switch to language resources // for (LanguageResource *language : resources) { // m_resources.append(language); // } updateMappings(); } void LanguageResourceModel::updateMappings() { int languages = m_resources.count(); for (int i = 0; i < languages; i++) { m_signalMapper->setMapping(m_resources.at(i), i); } } diff --git a/src/models/languageresourcemodel.h b/src/models/languageresourcemodel.h index ec247ff..74db282 100644 --- a/src/models/languageresourcemodel.h +++ b/src/models/languageresourcemodel.h @@ -1,80 +1,80 @@ /* * 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 LANGUAGERESOURCEMODEL_H #define LANGUAGERESOURCEMODEL_H #include #include "languagemodel.h" class IResourceRepository; class LanguageResource; class Language; class QSignalMapper; class LanguageResourceModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(IResourceRepository *resourceRepository READ resourceRepository WRITE setResourceRepository NOTIFY resourceRepositoryChanged) + Q_PROPERTY(IResourceRepository *repository READ resourceRepository WRITE setResourceRepository NOTIFY resourceRepositoryChanged) public: enum LanguageRoles { TitleRole = Qt::UserRole + 1, I18nTitleRole, IdRole, DataRole, CourseNumberRole }; explicit LanguageResourceModel(QObject *parent = nullptr); /** * Reimplemented from QAbstractListModel::roleNames() */ virtual QHash roleNames() const override; void setResourceRepository(IResourceRepository *repository); IResourceRepository * resourceRepository() const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; void setView(LanguageModel::LanguageResourceView view); LanguageModel::LanguageResourceView view() const; Q_SIGNALS: void languageChanged(int index); void resourceRepositoryChanged(); private Q_SLOTS: void onLanguageResourceAboutToBeAdded(LanguageResource *resource, int index); void onLanguageResourceAdded(); void onLanguageResourceAboutToBeRemoved(int index); void onLanguageResourceRemoved(); void emitLanguageChanged(int row); void updateDisplayedLanguages(); private: void updateResources(); void updateMappings(); IResourceRepository *m_repository; QList m_resources; LanguageModel::LanguageResourceView m_view; QSignalMapper *m_signalMapper; }; #endif diff --git a/src/models/skeletonmodel.h b/src/models/skeletonmodel.h index cffc39d..95bdad7 100644 --- a/src/models/skeletonmodel.h +++ b/src/models/skeletonmodel.h @@ -1,77 +1,77 @@ /* * 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 SKELETONMODEL_H #define SKELETONMODEL_H #include class ContributorRepository; class ICourse; class Skeleton; class Language; class QSignalMapper; class SkeletonModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(ContributorRepository *resourceRepository READ resourceRepository WRITE setResourceRepository NOTIFY resourceRepositoryChanged) + Q_PROPERTY(ContributorRepository *repository READ resourceRepository WRITE setResourceRepository NOTIFY resourceRepositoryChanged) Q_PROPERTY(int count READ count NOTIFY countChanged) public: enum courseRoles { TitleRole = Qt::UserRole + 1, DescriptionRole, IdRole, DataRole }; explicit SkeletonModel(QObject *parent = nullptr); void setResourceRepository(ContributorRepository *repository); /** * Reimplemented from QAbstractListModel::roleNames() */ virtual QHash roleNames() const override; ContributorRepository * resourceRepository() const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int count() const; Q_INVOKABLE QVariant skeleton(int index) const; Q_SIGNALS: void skeletonChanged(int index); void resourceRepositoryChanged(); void countChanged(); private Q_SLOTS: void onSkeletonAboutToBeAdded(ICourse *skeleton, int index); void onSkeletonAdded(); void onSkeletonsAboutToBeRemoved(int first, int last); void onSkeletonsRemoved(); void emitSkeletonChanged(int row); private: void updateMappings(); ContributorRepository *m_repository; QSignalMapper *m_signalMapper; }; #endif // SKELETONMODEL_H diff --git a/src/qml/Editor.qml b/src/qml/Editor.qml index 383b49a..abb1ca0 100644 --- a/src/qml/Editor.qml +++ b/src/qml/Editor.qml @@ -1,268 +1,262 @@ /* - * Copyright 2013-2015 Andreas Cord-Landwehr + * 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 artikulate 1.0 -Item -{ +Item { id: root width: 400 //parent.width height: 400 //parent.height Item { id: theme property string backgroundColor: "#ffffff" property int smallIconSize: 18 property int smallMediumIconSize: 22 property int mediumIconSize: 32 property int fontPointSize: 11 } LanguageModel { id: languageModel view: LanguageModel.AllLanguages resourceModel: LanguageResourceModel { - resourceManager: g_resourceManager + repository: g_repository } } CourseModel { id: courseModel language: editorSession.language } UnitModel { id: selectedUnitModel course: editorSession.course } ColumnLayout { id: main anchors { fill: parent topMargin: 20 rightMargin: 20 bottomMargin: 20 leftMargin: 20 } spacing: 10 RowLayout { - Label { - visible: !g_resourceManager.isRepositoryManager - text: i18n("no repository set") - color: "red" - } Label { text: i18n("Course Prototype:") } ComboBox { Layout.minimumWidth: 300 model: SkeletonModel { id: skeletonModel - resourceManager: g_resourceManager + repository: g_repository } textRole: "title" onCurrentIndexChanged: { editorSession.skeleton = skeletonModel.skeleton(currentIndex) } } Button { id: buttonEditSkeleton Layout.minimumWidth: 200 text: i18n("Edit Prototype") icon.name: "code-class" checkable: true onClicked: editorSession.editSkeleton = checked } Item { Layout.fillWidth: true } Button { id: buttonSyncFromSkeleton enabled: !buttonEditSkeleton.checked Layout.minimumWidth: 200 text: i18n("Sync Prototype") icon.name: "view-refresh" ToolTip.visible: hovered ToolTip.delay: 1000 ToolTip.timeout: 5000 ToolTip.text: i18n("Update the course with elements from prototype.") onClicked: editorSession.updateCourseFromSkeleton() } CheckBox { Layout.alignment: Qt.AlignRight enabled: false//FIXME for now deactivating non-skeleton mode text: i18n("Prototype Mode") checked: editorSession.skeletonMode onClicked: { editorSession.skeletonMode = !editorSession.skeletonMode } } } RowLayout { id: languageRow Label { text: i18n("Language:") } ComboBox { Layout.minimumWidth: 200 Layout.fillWidth: true enabled: !buttonEditSkeleton.checked model: languageModel textRole: "i18nTitle" onCurrentIndexChanged: { editorSession.language = languageModel.language(currentIndex) } } } RowLayout { id: courseRow visible: { if (buttonEditSkeleton.checked) { return false } if (editorSession.skeletonMode && editorSession.course !== null) { return false } if (!editorSession.skeletonMode && editorSession.language !== null && editorSession.course !== null ) { return false } return true } Label { text: i18n("There is no course in the selected language.") } ComboBox { // course selection only necessary when we do not edit skeleton derived course id: comboCourse visible: !editorSession.skeletonMode Layout.fillWidth: true model: courseModel textRole: "title" onCurrentIndexChanged: { if (courseModel.course(currentIndex)) { editorSession.course = courseModel.course(currentIndex) } } onVisibleChanged: { if (visible && courseModel.course(currentIndex)) { editorSession.course = courseModel.course(currentIndex) } } } Button { text: i18n("Create Course") icon.name: "journal-new" onClicked: { - editorSession.course = g_resourceManager.createCourse(editorSession.language, editorSession.skeleton) + editorSession.course = g_repository.createCourse(editorSession.language, editorSession.skeleton) } } Item { Layout.fillHeight: true } //dummy } RowLayout { id: mainRow visible: editorSession.course !== null Layout.fillHeight: true ColumnLayout { ScrollView { Layout.minimumWidth: Math.floor(main.width * 0.3) Layout.fillHeight: true QQC1.TreeView { id: phraseTree height: { mainRow.height - (newUnitButton.visible ? newUnitButton.height : 0) - 10 } width: Math.floor(main.width * 0.3) - 20 QQC1.TableViewColumn { title: i18n("Units & Phrases") role: "text" } model: PhraseModel { id: phraseModel course: editorSession.course } selection: ItemSelectionModel { model: phraseTree.model } itemDelegate: Item { Text { anchors.verticalCenter: parent.verticalCenter color: styleData.textColor elide: styleData.elideMode text: styleData.value } } onClicked: { if (phraseModel.isPhrase(index)) { editorSession.phrase = phraseModel.phrase(index) } else { editorSession.phrase = null editorSession.unit = phraseModel.unit(index) } } Connections { target: editorSession onPhraseChanged: { if (editorSession.phrase === null) { return } phraseTree.expand(phraseModel.indexUnit(editorSession.phrase.unit)) phraseTree.selection.setCurrentIndex( phraseModel.indexPhrase(editorSession.phrase), ItemSelectionModel.ClearAndSelect) } } } } Button { // add units only if skeleton id: newUnitButton visible: !editorSession.skeletonMode || editorSession.editSkeleton icon.name: "list-add" text: i18n("New Unit") onClicked: phraseModel.course.createUnit() } } ColumnLayout { UnitEditor { visible: editorSession.unit !== null && editorSession.phrase === null unit: editorSession.unit editPhrases: editorSession.skeletonMode && editorSession.editSkeleton } PhraseEditor { visible: editorSession.phrase !== null phrase: editorSession.phrase isSkeletonPhrase: editorSession.editSkeleton Layout.minimumWidth: Math.floor(main.width * 0.6) Layout.fillHeight: true } } } } }