diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 91d27e7..4a122bd 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,196 +1,196 @@ ### # Copyright 2013-2015 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 core/drawertrainingactions.cpp core/course.cpp core/resourcemanager.cpp core/language.cpp core/phrase.cpp core/phoneme.cpp core/phonemegroup.cpp core/unit.cpp core/skeleton.cpp core/editorsession.cpp core/trainingaction.cpp core/trainingactionicon.cpp core/trainingsession.cpp core/resources/resourceinterface.cpp core/resources/languageresource.cpp core/resources/courseresource.cpp core/resources/skeletonresource.cpp core/player.cpp core/recorder.cpp qmlcontrols/iconitem.cpp qmlcontrols/imagetexturescache.cpp qmlcontrols/managedtexturenode.cpp artikulate_debug.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::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 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 application.cpp artikulate_debug.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 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) kconfig_add_kcfg_files (artikulate_SRCS settings.kcfgc) set(artikulate_editor_SRCS main_editor.cpp mainwindow_editor.cpp application.cpp artikulate_debug.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 ui/sounddevicedialogpage.cpp ui/appearencedialogpage.cpp ui/resourcesdialogpage.cpp ui/exportghnsdialog.cpp ) -ki18n_wrap_ui (artikulate_editor_SRCS +ki18n_wrap_ui(artikulate_editor_SRCS ui/appearencedialogpage.ui ui/exportghnsdialog.ui ui/resourcesdialogpage.ui ui/sounddevicedialogpage.ui ) -qt5_add_resources(artikulate_editor_SRCS qml/qml.qrc) +qt5_add_resources(artikulate_editor_SRCS resources.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 Qt5::QuickWidgets KF5::Crash KF5::NewStuff KF5::XmlGui ) 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/qml/Editor.qml b/src/qml/Editor.qml index 5160b1b..a03e198 100644 --- a/src/qml/Editor.qml +++ b/src/qml/Editor.qml @@ -1,265 +1,269 @@ /* * 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 . */ -import QtQuick 2.1 -import QtQuick.Controls 1.4 +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 { 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 } } CourseModel { id: courseModel resourceManager: g_resourceManager 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 } textRole: "title" onCurrentIndexChanged: { editorSession.skeleton = skeletonModel.skeleton(currentIndex) } } Button { id: buttonEditSkeleton Layout.minimumWidth: 200 text: i18n("Edit Prototype") - iconName: "code-class" + 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") - tooltip: i18n("Update the course with elements from prototype.") - iconName: "view-refresh" + 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") - iconName: "journal-new" + icon.name: "journal-new" onClicked: { editorSession.course = g_resourceManager.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 - TreeView { + QQC1.TreeView { id: phraseTree height: { mainRow.height - (newUnitButton.visible ? newUnitButton.height : 0) - 10 } width: Math.floor(main.width * 0.3) - 20 - TableViewColumn { + 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 - iconName: "list-add" + 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 } } } } } diff --git a/src/qml/PhonemeUnitSelector.qml b/src/qml/PhonemeUnitSelector.qml index 1617846..372b035 100644 --- a/src/qml/PhonemeUnitSelector.qml +++ b/src/qml/PhonemeUnitSelector.qml @@ -1,103 +1,103 @@ /* * Copyright 2013-2014 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.Controls 1.2 +import QtQuick.Controls 2.3 import artikulate 1.0 Item { id: root property Course course property PhonemeGroup currentPhonemeGroup signal unitSelected(variant unit) width: phonemeGroupList.width height: phonemeGroupList.height PhonemeGroupModel { id: phonemeGroupModel course: root.course } PhonemeUnitModel { id: phonemeUnitModel course: root.course phonemeGroup: root.currentPhonemeGroup } onCourseChanged: { root.currentPhonemeGroup = null } Component { id: groupDelegate Column { id: content property PhonemeGroup phonemeGroup: model.dataRole height: groupSelectButton.height + unitList.height width: 200 ToolButton { id: groupSelectButton text : model.title onClicked: { root.currentPhonemeGroup = phonemeGroup } } ListView { id: unitList visible: root.currentPhonemeGroup == phonemeGroup height: unitList.visible ? 30 * phonemeUnitModel.count : 0 width: 200 model: phonemeUnitModel delegate: unitDelegate } } } Component { id: unitDelegate ToolButton { text: model.title property Unit unit: model.dataRole onClicked: { root.unitSelected(unit) } } } ListView { id: phonemeGroupList height: 300 width: 200 model: phonemeGroupModel delegate: groupDelegate } } diff --git a/src/qml/PhraseEditor.qml b/src/qml/PhraseEditor.qml index 346dfa4..8defeca 100644 --- a/src/qml/PhraseEditor.qml +++ b/src/qml/PhraseEditor.qml @@ -1,186 +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 . */ -import QtQuick 2.1 -import QtQuick.Controls 1.2 +import QtQuick 2.10 +import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import artikulate 1.0 Item { id: root property Phrase phrase property bool isSkeletonPhrase: false // use for saving property int __changedPhraseType property string __changedPhraseText width: 500 height: editLoader.height Component { id: editComponent Row { width: root.width height: { if (!root.isSkeletonPhrase) textEdit.height + phonemeGrid.height + phraseEditStateSetter.height + phraseRecorder.height + phraseTypeSetter.height; else { // height if only editing skeleton textEdit.height + phraseTypeSetter.height; } } ColumnLayout { id: textEdit height: inputLine.height + originalPhraseInfo.height width: parent.width spacing: 5 Row { id: originalPhraseInfo property string originalPhrase : (root.phrase != null) ? root.phrase.i18nText : "" spacing: 10 visible: { root.phrase != null && originalPhrase != "" && !root.isSkeletonPhrase} Text { text: i18n("Original Phrase:") + " " + originalPhraseInfo.originalPhrase + "" width: root.width - 70 wrapMode: Text.WordWrap } } RowLayout { // controls for setting phrase id: inputLine TextArea { id: phraseInput property Phrase phrase: root.phrase Layout.fillWidth: true Layout.maximumHeight: 100 text: root.phrase.text onTextChanged: { if (root.phrase == null) { return } root.phrase.text = text } onPhraseChanged: { if (root.phrase != null) text = root.phrase.text else text = "" } } } PhraseEditorTypeComponent { id: phraseTypeSetter phrase: root.phrase } PhraseEditorSoundComponent { id: phraseRecorder visible: !root.isSkeletonPhrase phrase: root.phrase } Component { id: phonemeItem Text { Button { width: 100 text: model.title checkable: true checked: { phrase != null && phrase.hasPhoneme(model.dataRole) } onClicked: { //TODO this button has no undo operation yet if (checked) { phrase.addPhoneme(model.dataRole) } else { phrase.removePhoneme(model.dataRole) } } } } } GridView { id: phonemeGrid property int columns : width / cellWidth width: root.width height: 30 * count / columns + 60 cellWidth: 100 cellHeight: 30 model: PhonemeModel { language: { (phrase != null) ? root.phrase.unit.course.language : null } } delegate: phonemeItem } RowLayout { id: controls anchors { left: parent.left right: parent.right } PhraseEditorEditStateComponent { id: phraseEditStateSetter visible: !root.isSkeletonPhrase phrase: root.phrase } Label { // dummy Layout.fillWidth: true } ToolButton { Layout.alignment: Qt.AlignBottom width: 48 height: 48 enabled: editorSession.hasPreviousPhrase - iconName: "go-previous" + icon.name: "go-previous" onClicked: { editorSession.switchToPreviousPhrase() } } ToolButton { Layout.alignment: Qt.AlignBottom width: 48 height: 48 enabled: editorSession.hasNextPhrase - iconName: "go-next" + icon.name: "go-next" onClicked: { editorSession.switchToNextPhrase() } } } } } } ColumnLayout { id: phraseRow Loader { id: editLoader sourceComponent: (phrase != null) ? editComponent : undefined onSourceComponentChanged: { if (sourceComponent == undefined) height = 0 else height = editComponent.height } } } } diff --git a/src/qml/PhraseEditorSoundComponent.qml b/src/qml/PhraseEditorSoundComponent.qml index a7b9906..300ab62 100644 --- a/src/qml/PhraseEditorSoundComponent.qml +++ b/src/qml/PhraseEditorSoundComponent.qml @@ -1,89 +1,89 @@ /* - * Copyright 2013-2014 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.1 -import QtQuick.Controls 1.2 +import QtQuick 2.5 +import QtQuick.Controls 2.3 import artikulate 1.0 Item { id: root property Phrase phrase width: mediaController.width height: mediaController.height Column { id: mediaController Text { id: componentTitle text: i18n("Native Speaker Recording") font.pointSize: 14; } Row { anchors { left: componentTitle.left; leftMargin: 30 } height: 48 Text { anchors.verticalCenter: parent.verticalCenter text: i18n("Existing Recording:") } SoundPlayer { fileUrl: root.phrase == null ? "" : phrase.soundFileUrl } } Row { anchors { left: componentTitle.left; leftMargin: 30 } Text { anchors.verticalCenter: parent.verticalCenter text: i18n("Create New Recording:") } SoundRecorder { id: recorder } SoundPlayer { fileUrl: recorder.outputFileUrl } } Row { anchors { left: componentTitle.left; leftMargin: 30 } visible: recorder.outputFileUrl != "" ToolButton { anchors.verticalCenter: parent.verticalCenter - iconName: "dialog-ok-apply" + icon.name: "dialog-ok-apply" text: i18n("Replace existing recording") onClicked: { recorder.storeToFile(phrase.soundFileOutputPath()) } } ToolButton { anchors.verticalCenter: parent.verticalCenter - iconName: "dialog-cancel" + icon.name: "dialog-cancel" text: i18n("Dismiss") onClicked: { recorder.clearBuffer() } } } } }