diff --git a/.gitignore b/.gitignore index ddd93c6..350792c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,10 @@ *~ *.pyc .kdev4/ /*.txt.user /*.kdev4 + +# Default build and install locations +/build +/install diff --git a/AUTHORS b/AUTHORS index fde7822..cbe7173 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,3 +1,3 @@ -Sebastian Gottfried Håvard Frøiland Andreas Nicolai diff --git a/CMakeLists.txt b/CMakeLists.txt index 27572f7..6605209 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,75 +1,74 @@ # KTouch CMakeLists.txt file # # KDE Application Version, managed by release script set (KDE_APPLICATIONS_VERSION_MAJOR "19") set (KDE_APPLICATIONS_VERSION_MINOR "07") set (KDE_APPLICATIONS_VERSION_MICRO "70") set (KDE_APPLICATIONS_VERSION "${KDE_APPLICATIONS_VERSION_MAJOR}.${KDE_APPLICATIONS_VERSION_MINOR}.${KDE_APPLICATIONS_VERSION_MICRO}") cmake_minimum_required(VERSION 3.5) project(ktouch VERSION ${KDE_APPLICATIONS_VERSION}) find_package(ECM 1.0.0 REQUIRED NO_MODULE) find_package(KF5DocTools) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) include(KDEInstallDirs) include(ECMAddTests) include(KDECMakeSettings) include(KDECompilerSettings NO_POLICY_SCOPE) include(ECMInstallIcons) include(ECMOptionalAddSubdirectory) include(ECMSetupVersion) include(FeatureSummary) -find_package(Qt5 5.5 REQUIRED COMPONENTS +find_package(Qt5 5.9 REQUIRED COMPONENTS Gui Qml Quick QuickWidgets - Script + QuickControls2 Sql - Test Widgets X11Extras Xml XmlPatterns ) find_package(Qt5 CONFIG QUIET OPTIONAL_COMPONENTS QuickCompiler) find_package(KF5 REQUIRED COMPONENTS Completion Config ConfigWidgets CoreAddons Declarative DocTools I18n ItemViews KCMUtils TextWidgets WidgetsAddons WindowSystem XmlGui + IconThemes ) option(COMPILE_QML "Precompile QML code" ON) # enable QML debugging for debug builds set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DQT_QML_DEBUG") # subdirectories to build ecm_optional_add_subdirectory(data) ecm_optional_add_subdirectory(doc) ecm_optional_add_subdirectory(src) # ecm_optional_add_subdirectory(sounds) -ecm_optional_add_subdirectory(images) ecm_optional_add_subdirectory(icons) # files to install in the ktouch project root directory install( PROGRAMS org.kde.ktouch.desktop DESTINATION ${XDG_APPS_INSTALL_DIR} ) install(FILES org.kde.ktouch.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/README.md b/README.md index 48b1c6a..1258ad6 100644 --- a/README.md +++ b/README.md @@ -1,53 +1,101 @@ -KTouch -====== +# KTouch [![Build Status](https://build.kde.org/view/Applications/job/Applications%20ktouch%20kf5-qt5%20SUSEQt5.9/badge/icon)](https://build.kde.org/view/Applications/job/Applications%20ktouch%20kf5-qt5%20SUSEQt5.9/) KTouch is a program to learn and practice touch typing. Every finger has its place on the keyboard with associated keys to press. Starting with only a few keys to remember you will advance through different training levels where additional keys are introduced. Because you no longer need to search for the keys on the keyboard you will be able to type quickly and accurately. -Building -======== +# Development Setup + +## Building KTouch has the following build dependencies: - * CMake >= 2.8.12 - * CMake Extra Modules >= 1.0.0 - * Qt >= 5.5 - * KDE Frameworks: - * Config - * ConfigWidgets - * CoreAddons - * Declarative - * DocTools - * I18n - * ItemViews - * KCMUtils - * KIO - * NewStuff - * TextEditor - * WidgetsAddons - * WindowSystem - * XmlGui - * libxkbfile (optional, for keyboard layout auto-detection) - * libxcb (optional, for keyboard layout auto-detection) - -If the build requirements are met, KTouch can be built and installed with: + + * [CMake][cmake] ≥ 3.0.0 + * [Extra CMake Modules][ecm] ≥ 1.0.0 + * [Qt][qt] ≥ 5.9 with the following modules + * Qt GUI + * Qt QML + * Qt Quick + * Qt Quick Widgets + * Qt Quick Controls 2 + * Qt SQL + * Qt Test + * Qt X11 Extras + * Qt XML + * Qt XML Patterns + * [KDE Frameworks 5][kf5] with: + * Completion + * Config + * ConfigWidgets + * CoreAddons + * Declarative + * DocTools + * I18n + * ItemViews + * KCMUtils + * KIO + * TextWidgets + * WidgetsAddons + * WindowSystem + * XmlGui + * IconThemes + * [libxkbfile][libxkbfile] (optional, for keyboard layout auto-detection) + * [libxcb][libxcb] (optional, for keyboard layout auto-detection) + +[cmake]: https://cmake.org/ +[ecm]: https://cgit.kde.org/extra-cmake-modules.git/ +[qt]: https://www.qt.io/ +[kf5]: https://api.kde.org/frameworks/ +[libxkbfile]: https://cgit.freedesktop.org/xorg/lib/libxkbfile +[libxcb]: https://xcb.freedesktop.org/ + +Collecting and installing all dependencies by hand can be a challenge. +Far easier is to use the dependency resolution of your package +manager. Usually they can be instructed to install all dependencies +automatically by using the information of the packaged version of +KTouch: + + apt-get build-dep ktouch # Ubuntu, Debian, ... + zypper source-install --build-deps-only # openSUSE + +If the build requirements are met, execute the following commands in +the root directory of your working copy to built and install KTouch: mkdir build - cd build/ - cmake .. + cd build + cmake .. -DCMAKE_INSTALL_PREFIX=../install make - sudo make install + make install + +These commands install KTouch to the directory `install` in the root of +your working copy. You can install KTouch to any directory of your +choice by passing a different path to `-DCMAKE_INSTALL_PREFIX`. + +## Running + +At runtime KTouch needs the following additional software packages to be +installed: + + * [Breeze icon set][breeze] + * [kqtquickcharts][kqtquickcharts] ≥ 16.12 + * [plasma-desktop][plasma-desktop] (optional, for keyboard layout + configuration inside the application) + +[breeze]: https://phabricator.kde.org/source/breeze-icons/ +[kqtquickcharts]: https://phabricator.kde.org/source/kqtquickcharts/ +[plasma-desktop]: https://phabricator.kde.org/source/plasma-desktop/ + +KTouch needs the following environment variables to be set in order +to find all its installed resources: -Runtime dependencies -==================== + export KDEDIRS=:$KDEDIRS + export XDG_DATA_DIRS=/share:$XDG_DATA_DIRS -At runtime KTouch needs the following software packages to be present: - * kqtquickcharts >= 16.12 (git://anongit.kde.org/kqtquickcharts) - * plasma-desktop (optional, for keyboard layout configuration - from within the application) +After this, KTouch can finally be launched with: + /bin/ktouch diff --git a/extras/scripts/checkqmlimports.py b/extras/scripts/checkqmlimports.py new file mode 100644 index 0000000..21ff296 --- /dev/null +++ b/extras/scripts/checkqmlimports.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2017 Sebastian Gottfried +# +# 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) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import argparse +import re +from pathlib import Path +from collections import namedtuple + +Import = namedtuple('Import', ['name', 'version', 'alias']) + +ALLOWED_IMPORTS = { + 'common': [ + Import('QtQuick.Controls', '2.2', None), + Import('QtQuick.Controls', '2.2', 'Controls'), + Import('org.kde.kquickcontrolsaddons', '2.0', 'Addons'), + ], + '*': [ + Import('QtQuick', '2.9', alias=None), + Import('QtQuick.Layouts', '1.3', alias=None), + Import('ktouch', '1.0', alias=None), + Import('QtGraphicalEffects', '1.0', alias=None), + Import('org.kde.charts', '0.1', alias='Charts'), + ], +} + +IMPORT_RE = re.compile(r'^import ([\w\.]+) ([\d\.]+)( as ([\w\.]+))?$') +LOCAL_IMPORT = re.compile('^import ("|\')[\w\./]+("|\')') + +def dir_arg(path_str): + path = Path(path_str) + if not path.exists(): + msg = "'{}' doesn't exist".format(path) + raise argparse.ArgumentTypeError(msg) + if not path.is_dir(): + msg = "'{}' has to be a directory".format(path) + raise argparse.ArgumentTypeError(msg) + return path + +def check_imports(file, module, local_path): + for line in file: + line = line.strip() + if not line.startswith('import'): + continue + if LOCAL_IMPORT.match(line) is not None: + continue + try: + check_package_import(line, module, local_path) + except ValueError: + print(f"{local_path}: unrecognized import statement: '{line}'") + + +def check_package_import(line, module, local_path): + matches = IMPORT_RE.match(line) + if matches is None: + raise ValueError('unrecognized import') + current_import = Import(*matches.group(1, 2, 4)) + for allowed_module, allowed_imports in ALLOWED_IMPORTS.items(): + if allowed_module == '*' or allowed_module == module: + for allowed_import in allowed_imports: + if current_import == allowed_import: + return + print(f"{local_path}: import not allowed here: '{line}'") + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='check QML imports for KTouch' + ) + parser.add_argument('path', + type=dir_arg, + metavar="PATH", + default="" + ) + args = parser.parse_args() + for qml_file_path in args.path.glob('**/*.qml'): + local_path = qml_file_path.relative_to(args.path) + if len(local_path.parts) > 1: + module = local_path.parts[0] + else: + module = None + check_imports(qml_file_path.open(mode='r'), module, local_path) + diff --git a/images/CMakeLists.txt b/images/CMakeLists.txt deleted file mode 100644 index 4918ee9..0000000 --- a/images/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -set(image_files - accuracymeter-background.png - accuracymeter-scale.png - accuracymeter-hand.png - balloontip.svgz - charactersperminutemeter-background.png - charactersperminutemeter-scale.png - charactersperminutemeter-hand.png - elapsedtimemeter-background.png - elapsedtimemeter-minute-hand.png - elapsedtimemeter-second-hand.png - meterbox-left.png - meterbox-right.png - trainingscreen-footer.png - trainingscreen-header.png - trainingscreen-toolbar.png - trainingscreen-viewport.png - trainingscreen-viewport-shadow.png -) - -install(FILES ${image_files} DESTINATION ${DATA_INSTALL_DIR}/ktouch/images) diff --git a/images/charactersperminutemeter.svgz b/images/charactersperminutemeter.svgz deleted file mode 100644 index 3022909..0000000 Binary files a/images/charactersperminutemeter.svgz and /dev/null differ diff --git a/images/elapsedtimemeter.svgz b/images/elapsedtimemeter.svgz deleted file mode 100644 index 13809e5..0000000 Binary files a/images/elapsedtimemeter.svgz and /dev/null differ diff --git a/images/meterbox.svgz b/images/meterbox.svgz deleted file mode 100644 index e41a303..0000000 Binary files a/images/meterbox.svgz and /dev/null differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c6bb6ca..2293b3f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,157 +1,162 @@ ecm_setup_version(${KDE_APPLICATIONS_VERSION} VARIABLE_PREFIX KTOUCH VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/version.h" PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KTouchConfigVersion.cmake" ) find_package(X11) add_feature_info("X11-Xkbfile" X11_Xkbfile_FOUND "required for automatic keyboard layout detection") find_package(XCB OPTIONAL_COMPONENTS XCB XKB) add_feature_info("XCB-XKB" XCB_XKB_FOUND "required for automatic keyboard layout detection") ecm_optional_add_subdirectory(schemata) # set include directories include_directories( ${CMAKE_CURRENT_BINARY_DIR} ${ktouch_SOURCE_DIR} ) # configure the local configuration file configure_file(ktouch_build_config.h.in ktouch_build_config.h) # set the source code files from which KTouch is compiled set(ktouch_SRCS main.cpp application.cpp mainwindow.cpp bindings/utils.cpp bindings/stringformatter.cpp declarativeitems/griditem.cpp + declarativeitems/kcolorschemeproxy.cpp declarativeitems/lessonpainter.cpp + declarativeitems/lessontexthighlighteritem.cpp declarativeitems/preferencesproxy.cpp declarativeitems/scalebackgrounditem.cpp declarativeitems/traininglinecore.cpp core/resource.cpp core/keyboardlayoutbase.cpp core/keyboardlayout.cpp core/abstractkey.cpp core/key.cpp core/keychar.cpp core/specialkey.cpp core/coursebase.cpp core/course.cpp core/lesson.cpp core/trainingstats.cpp core/profile.cpp core/dataindex.cpp core/dataaccess.cpp core/dbaccess.cpp core/profiledataaccess.cpp core/resourcedataaccess.cpp core/userdataaccess.cpp undocommands/coursecommands.cpp undocommands/keyboardlayoutcommands.cpp models/resourcemodel.cpp models/lessonmodel.cpp models/charactersmodel.cpp models/categorizedresourcesortfilterproxymodel.cpp models/errorsmodel.cpp models/learningprogressmodel.cpp editor/resourceeditor.cpp editor/resourceeditorwidget.cpp editor/newresourceassistant.cpp editor/resourcetypeswidget.cpp editor/newcoursewidget.cpp editor/newkeyboardlayoutwidget.cpp editor/resourcetemplatewidget.cpp editor/abstracteditor.cpp editor/courseeditor.cpp editor/keyboardlayoutcombobox.cpp editor/lessontexteditor.cpp editor/lessontexthighlighter.cpp editor/keyboardlayouteditor.cpp editor/keyboardlayouteditorview.cpp editor/keyboardlayoutpropertieswidget.cpp editor/charactersviewdelegate.cpp trainingconfigwidget.cpp colorsconfigwidget.cpp customlessoneditordialog.cpp ktouchcontext.cpp ) +qt5_add_resources(ktouch_imgs_SRCS images/images.qrc) + if (Qt5QuickCompiler_FOUND AND COMPILE_QML) qtquick_compiler_add_resources(ktouch_qml_SRCS qml/qml.qrc) else () qt5_add_resources(ktouch_qml_SRCS qml/qml.qrc) endif() # compile UI files ki18n_wrap_ui(ktouch_SRCS ui/colorsconfigwidget.ui ui/trainingconfigwidget.ui ui/resourceeditorwidget.ui ui/resourcetypeswidget.ui ui/newcoursewidget.ui ui/newkeyboardlayoutwidget.ui ui/resourcetemplatewidget.ui ui/courseeditor.ui ui/lessontexteditor.ui ui/keyboardlayouteditor.ui ui/keyboardlayoutpropertieswidget.ui ui/customlessoneditordialog.ui ) set (KTOUCH_BUILD_WITH_X11 X11_Xkbfile_FOUND AND XCB_XKB_FOUND AND XCB_FOUND) if (KTOUCH_BUILD_WITH_X11) add_definitions(-DKTOUCH_BUILD_WITH_X11) include_directories(${X11_Xkbfile_INCLUDE_PATH}) set(ktouch_SRCS ${ktouch_SRCS} x11_helper.cpp) else (KTOUCH_BUILD_WITH_X11) set(ktouch_SRCS ${ktouch_SRCS} keyboardlayoutmenu.cpp) endif (KTOUCH_BUILD_WITH_X11) kconfig_add_kcfg_files(ktouch_SRCS preferences.kcfgc) -add_executable(ktouch ${ktouch_SRCS} ${ktouch_qml_SRCS}) +add_executable(ktouch ${ktouch_SRCS} ${ktouch_imgs_SRCS} ${ktouch_qml_SRCS}) set(ktouch_X11_DEPS "") if (KTOUCH_BUILD_WITH_X11) set(ktouch_X11_DEPS ${X11_Xkbfile_LIB} ${X11_LIBRARIES} XCB::XCB XCB::XKB) endif (KTOUCH_BUILD_WITH_X11) #uncomment this if oxygen icons for ktouch are available target_link_libraries(ktouch LINK_PUBLIC Qt5::Qml Qt5::Quick Qt5::QuickWidgets - Qt5::Script + Qt5::QuickControls2 Qt5::Sql Qt5::XmlPatterns Qt5::X11Extras KF5::Completion KF5::ConfigWidgets KF5::Declarative KF5::ItemViews KF5::XmlGui KF5::I18n KF5::KCMUtils KF5::TextWidgets KF5::WindowSystem KF5::CoreAddons + KF5::IconThemes ${ktouch_X11_DEPS} ) #kde4_add_app_icon(ktouch_SRCS "${KDE4_ICON_DIR}/oxygen/*/apps/ktouch.png") #kde4_add_app_icon(ktouch_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/../icons/hi*-app-ktouch.png") install(TARGETS ktouch ${INSTALL_TARGETS_DEFAULT_ARGS}) install(FILES ktouch.kcfg DESTINATION ${KCFG_INSTALL_DIR}) diff --git a/src/application.cpp b/src/application.cpp index 10eec59..f2eebc1 100644 --- a/src/application.cpp +++ b/src/application.cpp @@ -1,154 +1,164 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "application.h" #include +#include #include #include #include +#include #include #include #include #include #include "bindings/utils.h" #include "bindings/stringformatter.h" #include "declarativeitems/griditem.h" +#include "declarativeitems/kcolorschemeproxy.h" #include "declarativeitems/lessonpainter.h" +#include "declarativeitems/lessontexthighlighteritem.h" #include "declarativeitems/preferencesproxy.h" #include "declarativeitems/scalebackgrounditem.h" #include "declarativeitems/traininglinecore.h" #include "core/keyboardlayout.h" #include "core/key.h" #include "core/specialkey.h" #include "core/keychar.h" #include "core/course.h" #include "core/lesson.h" #include "core/profile.h" #include "core/trainingstats.h" #include "core/dataindex.h" #include "core/dataaccess.h" #include "core/profiledataaccess.h" #include "models/resourcemodel.h" #include "models/lessonmodel.h" #include "models/categorizedresourcesortfilterproxymodel.h" #include "models/learningprogressmodel.h" #include "models/errorsmodel.h" Application::Application(int& argc, char** argv, int flags): QApplication(argc, argv, flags), m_dataIndex(new DataIndex(this)) { registerQmlTypes(); migrateKde4Files(); + QIcon::setThemeName("breeze"); + QQuickStyle::setStyle("Default"); + DataAccess dataAccess; dataAccess.loadDataIndex(m_dataIndex); } DataIndex* Application::dataIndex() { Application* app = qobject_cast(QCoreApplication::instance()); return app->m_dataIndex; } QPointer& Application::resourceEditorRef() { Application* app = qobject_cast(QCoreApplication::instance()); return app->m_resourceEditorRef; } void Application::setupDeclarativeBindings(QQmlEngine* qmlEngine) { KDeclarative::KDeclarative kDeclarative; kDeclarative.setDeclarativeEngine(qmlEngine); - kDeclarative.setupBindings(); + kDeclarative.setupContext(); + kDeclarative.setupEngine(qmlEngine); Application* app = static_cast(Application::instance()); foreach (const QString& path, app->m_qmlImportPaths) { qmlEngine->addImportPath(path); } QQmlContext* rootContext = qmlEngine->rootContext(); rootContext->setContextProperty(QStringLiteral("utils"), new Utils()); rootContext->setContextProperty(QStringLiteral("strFormatter"), new StringFormatter()); } QStringList& Application::qmlImportPaths() { return m_qmlImportPaths; } void Application::registerQmlTypes() { qmlRegisterType("ktouch", 1, 0, "KeyboardLayout"); qmlRegisterType("ktouch", 1, 0, "AbstractKey"); qmlRegisterType("ktouch", 1, 0, "Key"); qmlRegisterType("ktouch", 1, 0, "SpecialKey"); qmlRegisterType("ktouch", 1, 0, "KeyChar"); qmlRegisterType("ktouch", 1, 0, "Course"); qmlRegisterType("ktouch", 1, 0, "Lesson"); qmlRegisterType("ktouch", 1, 0, "TrainingStats"); qmlRegisterType("ktouch", 1, 0, "Profile"); qmlRegisterType("ktouch", 1, 0, "DataIndex"); qmlRegisterType("ktouch", 1, 0, "DataIndexCourse"); qmlRegisterType("ktouch", 1, 0, "DataIndexKeyboardLayout"); qmlRegisterType("ktouch", 1, 0, "Preferences"); qmlRegisterType("ktouch", 1, 0, "DataAccess"); qmlRegisterType("ktouch", 1, 0, "ProfileDataAccess"); qmlRegisterType("ktouch", 1, 0, "ResourceModel"); qmlRegisterType("ktouch", 1, 0, "LessonModel"); qmlRegisterType("ktouch", 1, 0, "CategorizedResourceSortFilterProxyModel"); qmlRegisterType("ktouch", 1, 0, "LearningProgressModel"); qmlRegisterType("ktouch", 1, 0, "ErrorsModel"); - qmlRegisterType("ktouch", 1, 0 , "Grid"); + qmlRegisterType("ktouch", 1, 0 , "LineGrid"); qmlRegisterType("ktouch", 1, 0, "ScaleBackgroundItem"); qmlRegisterType("ktouch", 1, 0, "LessonPainter"); + qmlRegisterType("ktouch", 1, 0, "LessonTextHighlighter"); qmlRegisterType("ktouch", 1, 0, "TrainingLineCore"); + qmlRegisterType("ktouch", 1, 0, "KColorScheme"); } void Application::migrateKde4Files() { QStringList configFiles; configFiles << QStringLiteral("ktouchrc"); Kdelibs4ConfigMigrator confMigrator(QStringLiteral("ktouch")); confMigrator.setConfigFiles(configFiles); confMigrator.migrate(); Kdelibs4Migration migration; const QDir dataDir = QDir(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); if (!dataDir.exists()) { dataDir.mkpath(dataDir.path()); } const QString dbPath = dataDir.filePath(QStringLiteral("profiles.db")); const QString oldDbPath = migration.locateLocal("data", QStringLiteral("ktouch/profiles.db")); if (!QFile(dbPath).exists() && !oldDbPath.isEmpty()) { QFile(oldDbPath).copy(dbPath); } } diff --git a/src/bindings/utils.cpp b/src/bindings/utils.cpp index a0e5250..f4b2e19 100644 --- a/src/bindings/utils.cpp +++ b/src/bindings/utils.cpp @@ -1,69 +1,63 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "utils.h" +#include #include #include #include #include Utils::Utils(QObject* parent): QObject(parent) { } -QUrl Utils::findImage(const QString &name) -{ - const QString relPath = QStringLiteral("images/") + name; - const QString path = QStandardPaths::locate(QStandardPaths::DataLocation, relPath); - - if (path.isNull()) - { - qWarning() << "can't find image resource:" << name; - return QUrl(); - } - - return QUrl::fromLocalFile(path); -} - int Utils::getMinutesOfQTime(const QTime& time) { if (!time.isValid()) { qWarning() << "invalid QTime passed"; return 0; } return time.minute(); } int Utils::getSecondsOfQTime(const QTime& time) { if (!time.isValid()) { qWarning() << "invalid QTime passed"; return 0; } return time.second(); } QString Utils::uuid() { return QUuid::createUuid().toString(); } + +QColor Utils::alpha(const QColor& color, float alpha) +{ + auto result = QColor(color); + result.setAlphaF(alpha * result.alphaF()); + return result; +} diff --git a/src/bindings/utils.h b/src/bindings/utils.h index df79847..433e505 100644 --- a/src/bindings/utils.h +++ b/src/bindings/utils.h @@ -1,38 +1,39 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef UTILS_H #define UTILS_H #include #include #include +#include class Utils : public QObject { Q_OBJECT public: - explicit Utils(QObject* parent = 0); - Q_INVOKABLE QUrl findImage(const QString &name); + explicit Utils(QObject* parent = nullptr); Q_INVOKABLE int getMinutesOfQTime(const QTime& time); Q_INVOKABLE int getSecondsOfQTime(const QTime& time); Q_INVOKABLE QString uuid(); + Q_INVOKABLE QColor alpha(const QColor& color, float alpha); }; #endif // UTILS_H diff --git a/src/core/course.cpp b/src/core/course.cpp index ac738ae..ce015e9 100644 --- a/src/core/course.cpp +++ b/src/core/course.cpp @@ -1,200 +1,207 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "course.h" #include #include "lesson.h" #include "dataindex.h" Course::Course(QObject *parent) : CourseBase(parent), m_associatedDataIndexCourse(0), - m_doSyncLessonCharacters(true), + m_kind(Course::SequentialCourse), m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(updateLessonCharacters(int))); } DataIndexCourse* Course::associatedDataIndexCourse() const { return m_associatedDataIndexCourse; } void Course::setAssociatedDataIndexCourse(DataIndexCourse* dataIndexCourse) { if (dataIndexCourse != m_associatedDataIndexCourse) { m_associatedDataIndexCourse = dataIndexCourse; emit associatedDataIndexCourseChanged(); } } -bool Course::doSyncLessonCharacters() const +Course::Kind Course::kind() const { - return m_doSyncLessonCharacters; + return m_kind; } -void Course::setDoSyncLessonCharacters(bool doSync) +void Course::setKind(Kind kind) { - if (doSync != m_doSyncLessonCharacters) + if (kind != m_kind) { - m_doSyncLessonCharacters = doSync; - emit doSyncLessonCharactersChanged(); + m_kind = kind; + emit kindChanged(); } } void Course::setId(const QString& id) { CourseBase::setId(id); if (m_associatedDataIndexCourse) { m_associatedDataIndexCourse->setId(id); } } void Course::setTitle(const QString &title) { CourseBase::setTitle(title); if (m_associatedDataIndexCourse) { m_associatedDataIndexCourse->setTitle(title); } } void Course::setDescription(const QString &description) { CourseBase::setDescription(description); if (m_associatedDataIndexCourse) { m_associatedDataIndexCourse->setDescription(description); } } void Course::setKeyboardLayoutName(const QString &keyboardLayoutName) { CourseBase::setKeyboardLayoutName(keyboardLayoutName); if (m_associatedDataIndexCourse) { m_associatedDataIndexCourse->setKeyboardLayoutName(keyboardLayoutName); } } int Course::lessonCount() const { return m_lessons.count(); } Lesson* Course::lesson(int index) const { Q_ASSERT(index >= 0 && index < m_lessons.count()); return m_lessons.at(index); } void Course::addLesson(Lesson* lesson) { emit lessonAboutToBeAdded(lesson, m_lessons.length()); m_lessons.append(lesson); lesson->setParent(this); const int index = m_lessons.length() - 1; updateLessonCharacters(index); connect(lesson, SIGNAL(newCharactersChanged()), m_signalMapper, SLOT(map())); m_signalMapper->setMapping(lesson, index); emit lessonCountChanged(); emit lessonAdded(); } void Course::insertLesson(int index, Lesson* lesson) { Q_ASSERT(index >= 0 && index < m_lessons.count()); emit lessonAboutToBeAdded(lesson, index); m_lessons.insert(index, lesson); lesson->setParent(this); updateLessonCharacters(index); connect(lesson, SIGNAL(newCharactersChanged()), m_signalMapper, SLOT(map())); m_signalMapper->setMapping(lesson, index); emit lessonCountChanged(); emit lessonAdded(); } void Course::removeLesson(int index) { Q_ASSERT(index >= 0 && index < m_lessons.count()); emit lessonsAboutToBeRemoved(index, index); Lesson* const lesson = m_lessons.at(index); m_lessons.removeAt(index); delete lesson; updateLessonCharacters(index); emit lessonCountChanged(); emit lessonsRemoved(); } +int Course::indexOfLesson(Lesson* lesson) +{ + return m_lessons.indexOf(lesson); +} + void Course::clearLessons() { if (m_lessons.count() == 0) return; emit lessonsAboutToBeRemoved(0, m_lessons.length() - 1); qDeleteAll(m_lessons); m_lessons.clear(); emit lessonCountChanged(); emit lessonsRemoved(); } void Course::copyFrom(Course* source) { setIsValid(false); setId(source->id()); setTitle(source->title()); setDescription(source->description()); setKeyboardLayoutName(source->keyboardLayoutName()); - setDoSyncLessonCharacters(source->doSyncLessonCharacters()); + setKind(source->kind()); clearLessons(); for (int i = 0; i < source->lessonCount(); i++) { Lesson* lesson = new Lesson(this); lesson->copyFrom(source->lesson(i)); addLesson(lesson); } setIsValid(true); } void Course::updateLessonCharacters(int firstIndex) { - if (!m_doSyncLessonCharacters) + if (m_kind == Course::LessonCollection) + { return; + } QString characters = firstIndex > 0? lesson(firstIndex - 1)->characters(): QLatin1String(""); for (int i = firstIndex; i < lessonCount(); i++) { Lesson* const lesson = this->lesson(i); const QString newChars = lesson->newCharacters(); for (int j = 0; j < newChars.length(); j++) { const QChar newChar = newChars.at(j); if (!characters.contains(newChar)) characters.append(newChar); } lesson->setCharacters(characters); } } diff --git a/src/core/course.h b/src/core/course.h index e88bcdc..2a4ac83 100644 --- a/src/core/course.h +++ b/src/core/course.h @@ -1,75 +1,82 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef COURSE_H #define COURSE_H #include "coursebase.h" #include #include class QSignalMapper; class Lesson; class DataIndexCourse; class Course : public CourseBase { Q_OBJECT Q_PROPERTY(DataIndexCourse* associatedDataIndexCourse READ associatedDataIndexCourse WRITE setAssociatedDataIndexCourse NOTIFY associatedDataIndexCourseChanged) Q_PROPERTY(int lessonCount READ lessonCount NOTIFY lessonCountChanged) - Q_PROPERTY(bool doSyncLessonCharacters READ doSyncLessonCharacters WRITE setDoSyncLessonCharacters NOTIFY doSyncLessonCharactersChanged) + Q_PROPERTY(Kind kind READ kind WRITE setKind NOTIFY kindChanged) public: + enum Kind { + SequentialCourse, + LessonCollection + }; + Q_ENUM(Kind) + explicit Course(QObject *parent = 0); DataIndexCourse* associatedDataIndexCourse() const; void setAssociatedDataIndexCourse(DataIndexCourse* dataIndexCourse); int lessonCount() const; - bool doSyncLessonCharacters() const; - void setDoSyncLessonCharacters(bool doSync); + Kind kind() const; + void setKind(Kind kind); void setId(const QString& id); void setTitle(const QString& title); void setDescription(const QString& description); void setKeyboardLayoutName(const QString& keyboardLayoutName); Q_INVOKABLE Lesson* lesson(int index) const; Q_INVOKABLE void addLesson(Lesson* lesson); Q_INVOKABLE void insertLesson(int index, Lesson* lesson); Q_INVOKABLE void removeLesson(int index); + Q_INVOKABLE int indexOfLesson(Lesson* lesson); Q_INVOKABLE void clearLessons(); Q_INVOKABLE void copyFrom(Course* source); signals: void associatedDataIndexCourseChanged(); void lessonCountChanged(); - void doSyncLessonCharactersChanged(); + void kindChanged(); void lessonAboutToBeAdded(Lesson* lesson, int index); void lessonAdded(); void lessonsAboutToBeRemoved(int first, int last); void lessonsRemoved(); private slots: void updateLessonCharacters(int firstIndex = 0); private: Q_DISABLE_COPY(Course) DataIndexCourse* m_associatedDataIndexCourse; - bool m_doSyncLessonCharacters; + Kind m_kind; QList m_lessons; QSignalMapper* m_signalMapper; }; #endif // COURSE_H diff --git a/src/core/key.cpp b/src/core/key.cpp index 965ceb3..a06b98d 100644 --- a/src/core/key.cpp +++ b/src/core/key.cpp @@ -1,127 +1,132 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "key.h" #include "keychar.h" Key::Key(QObject* parent) : AbstractKey(parent), m_fingerIndex(0), m_hasHapticMarker(false) { } QString Key::keyType() const { return QStringLiteral("key"); } int Key::fingerIndex() const { return m_fingerIndex; } void Key::setFingerIndex(int finger) { Q_ASSERT(finger >= 0 && finger <= 8); if(finger != m_fingerIndex) { m_fingerIndex = finger; emit fingerIndexChanged(); } } bool Key::hasHapticMarker() const { return m_hasHapticMarker; } void Key::setHasHapticMarker(bool hasHapticMarker) { if(hasHapticMarker != m_hasHapticMarker) { m_hasHapticMarker = hasHapticMarker; emit hasHapticMarkerChanged(); } } +const QList& Key::keyChars() const +{ + return m_keyChars; +} + int Key::keyCharCount() const { return m_keyChars.count(); } KeyChar* Key::keyChar(int index) const { Q_ASSERT(index >= 0 && index < m_keyChars.length()); return m_keyChars.at(index); } void Key::addKeyChar(KeyChar* keyChar) { emit keyCharAboutToBeAdded(keyChar, m_keyChars.length()); m_keyChars.append(keyChar); keyChar->setParent(this); emit keyCharCountChanged(); emit keyCharAdded(); } void Key::insertKeyChar(int index, KeyChar* keyChar) { Q_ASSERT(index >= 0 && index < m_keyChars.length()); emit keyCharAboutToBeAdded(keyChar, index); m_keyChars.insert(index, keyChar); keyChar->setParent(this); emit keyCharCountChanged(); emit keyCharAdded(); } void Key::removeKeyChar(int index) { Q_ASSERT(index >= 0 && index < m_keyChars.length()); emit keyCharsAboutToBeRemoved(index, index); delete m_keyChars.at(index); m_keyChars.removeAt(index); emit keyCharCountChanged(); emit keyCharsRemoved(); } void Key::clearKeyChars() { if (m_keyChars.count() == 0) return; emit keyCharsAboutToBeRemoved(0, m_keyChars.length() - 1); qDeleteAll(m_keyChars); m_keyChars.clear(); emit keyCharCountChanged(); emit keyCharsRemoved(); } void Key::copyFrom(Key* source) { AbstractKey::copyFrom(source); setFingerIndex(source->fingerIndex()); setHasHapticMarker(source->hasHapticMarker()); clearKeyChars(); for (int j = 0; j < source->keyCharCount(); j++) { KeyChar* keyChar = new KeyChar(this); keyChar->copyFrom(source->keyChar(j)); addKeyChar(keyChar); } } diff --git a/src/core/key.h b/src/core/key.h index b21b218..4850cf8 100644 --- a/src/core/key.h +++ b/src/core/key.h @@ -1,64 +1,65 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KEY_H #define KEY_H #include "abstractkey.h" #include class KeyChar; class Key : public AbstractKey { Q_OBJECT Q_PROPERTY(int fingerIndex READ fingerIndex WRITE setFingerIndex NOTIFY fingerIndexChanged) Q_PROPERTY(bool hasHapticMarker READ hasHapticMarker WRITE setHasHapticMarker NOTIFY hasHapticMarkerChanged) Q_PROPERTY(int keyCharCount READ keyCharCount NOTIFY keyCharCountChanged) public: explicit Key(QObject* parent = 0); Q_INVOKABLE QString keyType() const override; int fingerIndex() const; void setFingerIndex(int finger); bool hasHapticMarker() const; void setHasHapticMarker(bool hasHapticMarker); + const QList& keyChars() const; int keyCharCount() const; Q_INVOKABLE KeyChar* keyChar(int index) const; Q_INVOKABLE void addKeyChar(KeyChar* keyChar); Q_INVOKABLE void insertKeyChar(int index, KeyChar* keyChar); Q_INVOKABLE void removeKeyChar(int index); Q_INVOKABLE void clearKeyChars(); Q_INVOKABLE void copyFrom(Key* source); signals: void fingerIndexChanged(); void hasHapticMarkerChanged(); void keyCharCountChanged(); void keyCharAboutToBeAdded(KeyChar* keyChar, int index); void keyCharAdded(); void keyCharsAboutToBeRemoved(int first, int last); void keyCharsRemoved(); private: int m_fingerIndex; bool m_hasHapticMarker; QList m_keyChars; }; #endif // KEY_H diff --git a/src/core/keyboardlayout.cpp b/src/core/keyboardlayout.cpp index f0e1b95..18b14e6 100644 --- a/src/core/keyboardlayout.cpp +++ b/src/core/keyboardlayout.cpp @@ -1,274 +1,292 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "keyboardlayout.h" #include #include #include #include #include #include #include #include #include "abstractkey.h" #include "key.h" +#include "keychar.h" #include "specialkey.h" #include "dataindex.h" KeyboardLayout::KeyboardLayout(QObject *parent) : KeyboardLayoutBase(parent), m_associatedDataIndexKeyboardLayout(0), m_title(QLatin1String("")), m_name(QLatin1String("")), m_width(0), m_height(0), m_keys(QList()), m_referenceKey(0), m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(onKeyGeometryChanged(int))); } DataIndexKeyboardLayout* KeyboardLayout::associatedDataIndexKeyboardLayout() const { return m_associatedDataIndexKeyboardLayout; } void KeyboardLayout::setAssociatedDataIndexKeyboardLayout(DataIndexKeyboardLayout* dataIndexKeyboardLayout) { if (dataIndexKeyboardLayout != m_associatedDataIndexKeyboardLayout) { m_associatedDataIndexKeyboardLayout = dataIndexKeyboardLayout; emit associatedDataIndexKeyboardLayoutChanged(); } } void KeyboardLayout::setId(const QString& id) { KeyboardLayoutBase::setId(id); if (m_associatedDataIndexKeyboardLayout) { m_associatedDataIndexKeyboardLayout->setId(id); } } void KeyboardLayout::setTitle(const QString& title) { KeyboardLayoutBase::setTitle(title); if (m_associatedDataIndexKeyboardLayout) { m_associatedDataIndexKeyboardLayout->setTitle(title); } } void KeyboardLayout::setName(const QString& name) { KeyboardLayoutBase::setName(name); if (m_associatedDataIndexKeyboardLayout) { m_associatedDataIndexKeyboardLayout->setName(name); } } int KeyboardLayout::width() const { return m_width; } void KeyboardLayout::setWidth(int width) { if(width != m_width) { m_width = width; emit widthChanged(); } } int KeyboardLayout::height() const { return m_height; } void KeyboardLayout::setHeight(int height) { if(height != m_height) { m_height = height; emit heightChanged(); } } int KeyboardLayout::keyCount() const { return m_keys.count(); } AbstractKey* KeyboardLayout::referenceKey() { return m_referenceKey; } void KeyboardLayout::copyFrom(KeyboardLayout* source) { setIsValid(false); setTitle(source->title()); setName(source->name()); setWidth(source->width()); setHeight(source->height()); clearKeys(); for(int i = 0; i < source->keyCount(); i++) { AbstractKey* const abstractSourceKey = source->key(i); AbstractKey* abstractKey = 0; Key* const sourceKey = qobject_cast(abstractSourceKey); if (sourceKey) { Key* key = new Key(this); key->copyFrom(sourceKey); abstractKey = key; } SpecialKey* const sourceSpecialKey = qobject_cast(abstractSourceKey); if (sourceSpecialKey) { SpecialKey* specialKey = new SpecialKey(this); specialKey->copyFrom(sourceSpecialKey); abstractKey = specialKey; } addKey(abstractKey); } setIsValid(true); } +QString KeyboardLayout::allCharacters() const +{ + QString result; + for (const auto abstractKey: m_keys) + { + Key* const key = qobject_cast(abstractKey); + if (key) + { + for (const auto keyChar : key->keyChars()) + { + result.append(keyChar->value()); + } + } + } + return result; +} + AbstractKey* KeyboardLayout::key(int index) const { Q_ASSERT(index >= 0 && index < m_keys.count()); return m_keys.at(index); } int KeyboardLayout::keyIndex(AbstractKey* key) const { return m_keys.indexOf(key); } void KeyboardLayout::addKey(AbstractKey* key) { m_keys.append(key); key->setParent(this); connect(key, SIGNAL(widthChanged()), m_signalMapper, SLOT(map())); connect(key, SIGNAL(heightChanged()), m_signalMapper, SLOT(map())); m_signalMapper->setMapping(key, m_keys.count() - 1); emit keyCountChanged(); updateReferenceKey(key); } void KeyboardLayout::insertKey(int index, AbstractKey* key) { m_keys.insert(index, key); key->setParent(this); connect(key, SIGNAL(widthChanged()), m_signalMapper, SLOT(map())); connect(key, SIGNAL(heightChanged()), m_signalMapper, SLOT(map())); m_signalMapper->setMapping(key, m_keys.count() - 1); emit keyCountChanged(); updateReferenceKey(key); } void KeyboardLayout::removeKey(int index) { Q_ASSERT(index >= 0 && index < m_keys.count()); AbstractKey* key = m_keys.at(index); m_keys.removeAt(index); emit keyCountChanged(); updateReferenceKey(0); key->deleteLater(); } void KeyboardLayout::clearKeys() { if (m_keys.count() == 0) return; qDeleteAll(m_keys); m_keys.clear(); emit keyCountChanged(); updateReferenceKey(0); } QSize KeyboardLayout::size() const { return QSize(m_width, m_height); } void KeyboardLayout::setSize(const QSize& size) { setWidth(size.width()); setHeight(size.height()); } void KeyboardLayout::onKeyGeometryChanged(int keyIndex) { updateReferenceKey(key(keyIndex)); } void KeyboardLayout::updateReferenceKey(AbstractKey *testKey) { if (testKey) { if (!m_referenceKey) { m_referenceKey = testKey; emit referenceKeyChanged(); return; } if (compareKeysForReference(testKey, m_referenceKey)) { m_referenceKey = testKey; emit referenceKeyChanged(); return; } } AbstractKey* canditate = 0; foreach(AbstractKey* key, m_keys) { if (canditate == 0) { canditate = key; } else { if (compareKeysForReference(key, canditate)) { canditate = key; } } } m_referenceKey = canditate; emit referenceKeyChanged(); } bool KeyboardLayout::compareKeysForReference(const AbstractKey *testKey, const AbstractKey *compareKey) const { return testKey->width() * testKey->height() < compareKey->width() * compareKey->height(); } diff --git a/src/core/keyboardlayout.h b/src/core/keyboardlayout.h index b076e59..6989425 100644 --- a/src/core/keyboardlayout.h +++ b/src/core/keyboardlayout.h @@ -1,87 +1,88 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KEYBOARD_H #define KEYBOARD_H #include "keyboardlayoutbase.h" #include #include class QSignalMapper; class AbstractKey; class DataIndexKeyboardLayout; class KeyboardLayout : public KeyboardLayoutBase { Q_OBJECT Q_PROPERTY(DataIndexKeyboardLayout* associatedDataIndexKeyboardLayout READ associatedDataIndexKeyboardLayout WRITE setAssociatedDataIndexKeyboardLayout NOTIFY associatedDataIndexKeyboardLayoutChanged) Q_PROPERTY(AbstractKey* referenceKey READ referenceKey NOTIFY referenceKeyChanged) Q_PROPERTY(int width READ width WRITE setWidth NOTIFY widthChanged) Q_PROPERTY(int height READ height WRITE setHeight NOTIFY heightChanged) Q_PROPERTY(int keyCount READ keyCount NOTIFY keyCountChanged) public: explicit KeyboardLayout(QObject* parent = 0); DataIndexKeyboardLayout* associatedDataIndexKeyboardLayout() const; void setAssociatedDataIndexKeyboardLayout(DataIndexKeyboardLayout* dataIndexKeyboardLayout); void setId(const QString& id); void setTitle(const QString& title); void setName(const QString& name); int width() const ; void setWidth(int width); int height() const; void setHeight(int height); int keyCount() const; Q_INVOKABLE AbstractKey* key(int index) const; Q_INVOKABLE int keyIndex(AbstractKey* key) const; Q_INVOKABLE void addKey(AbstractKey* key); Q_INVOKABLE void insertKey(int index, AbstractKey* key); Q_INVOKABLE void removeKey(int index); Q_INVOKABLE void clearKeys(); AbstractKey* referenceKey(); Q_INVOKABLE void copyFrom(KeyboardLayout* source); + Q_INVOKABLE QString allCharacters() const; QSize size() const; void setSize(const QSize& size); signals: void associatedDataIndexKeyboardLayoutChanged(); void widthChanged(); void heightChanged(); void referenceKeyChanged(); void keyCountChanged(); private slots: void onKeyGeometryChanged(int keyIndex); private: void updateReferenceKey(AbstractKey* newKey=0); bool compareKeysForReference(const AbstractKey* testKey, const AbstractKey* compareKey) const; DataIndexKeyboardLayout* m_associatedDataIndexKeyboardLayout; QString m_title; QString m_name; int m_width; int m_height; QList m_keys; AbstractKey* m_referenceKey; QSignalMapper* m_signalMapper; }; #endif // KEYBOARD_H diff --git a/src/core/profiledataaccess.cpp b/src/core/profiledataaccess.cpp index 5687e00..b387d27 100644 --- a/src/core/profiledataaccess.cpp +++ b/src/core/profiledataaccess.cpp @@ -1,885 +1,885 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "profiledataaccess.h" #include #include #include #include #include #include "core/profile.h" #include "core/course.h" #include "core/lesson.h" #include "core/keyboardlayout.h" #include "core/trainingstats.h" ProfileDataAccess::ProfileDataAccess(QObject* parent) : DbAccess(parent) { } void ProfileDataAccess::loadProfiles() { QSqlDatabase db = database(); if (!db.isOpen()) return; foreach(Profile* profile, m_profiles) profile->deleteLater(); m_profiles.clear(); emit profileCountChanged(); QSqlQuery profileQuery = db.exec(QStringLiteral("SELECT id, name, skill_level, last_used_course_id FROM profiles")); if (db.lastError().isValid()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return; } while(profileQuery.next()) { Profile* profile = new Profile(this); profile->setId(profileQuery.value(0).toInt()); profile->setName(profileQuery.value(1).toString()); int rawSkillLevel = profileQuery.value(2).toInt(); profile->setSkillLevel(rawSkillLevel == 1? Profile::Beginner: Profile::Advanced); profile->setLastUsedCourseId(profileQuery.value(3).toString()); m_profiles.append(profile); } emit profileCountChanged(); } int ProfileDataAccess::profileCount() const { return m_profiles.count(); } Profile* ProfileDataAccess::profile(int index) { Q_ASSERT(index >= 0 && index < m_profiles.count()); return m_profiles.at(index); } Profile* ProfileDataAccess::createProfile() { return new Profile(this); } void ProfileDataAccess::addProfile(Profile* profile) { QSqlDatabase db = database(); if (!db.isOpen()) return; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return; } QSqlQuery addQuery(db); if (!addQuery.prepare(QStringLiteral("INSERT INTO profiles (name, skill_level, last_used_course_id) VALUES (?, ?, ?)"))) { qWarning() << addQuery.lastError().text(); raiseError(addQuery.lastError()); db.rollback(); return; } addQuery.bindValue(0, profile->name()); addQuery.bindValue(1, profile->skillLevel()); addQuery.bindValue(2, profile->lastUsedCourseId()); if (!addQuery.exec()) { qWarning() << addQuery.lastError().text(); raiseError(addQuery.lastError()); db.rollback(); return; } QSqlQuery idQuery = db.exec(QStringLiteral("SELECT last_insert_rowid()")); if (db.lastError().isValid()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } idQuery.next(); profile->setId(idQuery.value(0).toInt()); if (!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } m_profiles.append(profile); emit profileCountChanged(); } void ProfileDataAccess::updateProfile(int index) { Profile* profile = this->profile(index); QSqlDatabase db = database(); if (!db.isOpen()) return; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return; } QSqlQuery updateQuery(db); if (!updateQuery.prepare(QStringLiteral("UPDATE profiles SET name = ?, skill_level = ?, last_used_course_id = ? WHERE id = ?"))) { qWarning() << updateQuery.lastError().text(); raiseError(updateQuery.lastError()); return; } updateQuery.bindValue(0, profile->name()); updateQuery.bindValue(1, profile->skillLevel()); updateQuery.bindValue(2, profile->lastUsedCourseId()); updateQuery.bindValue(3, profile->id()); if (!updateQuery.exec()) { qWarning() << updateQuery.lastError().text(); raiseError(updateQuery.lastError()); db.rollback(); return; } if (!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } } void ProfileDataAccess::removeProfile(int index) { Profile* profile = this->profile(index); QSqlDatabase db = database(); if (!db.isOpen()) return; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } QSqlQuery removeQuery(db); if (!removeQuery.prepare(QStringLiteral("DELETE FROM profiles WHERE id = ?"))) { qWarning() << removeQuery.lastError().text(); raiseError(removeQuery.lastError()); db.rollback(); return; } removeQuery.bindValue(0, profile->id()); if (!removeQuery.exec()) { qWarning() << removeQuery.lastError().text(); raiseError(removeQuery.lastError()); db.rollback(); return; } if (!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } m_profiles.removeAt(index); emit profileCountChanged(); profile->deleteLater(); } int ProfileDataAccess::indexOfProfile(Profile* profile) { for (int i = 0; i < m_profiles.length(); i++) { if (profile == m_profiles.at(i)) { return i; } } return -1; } void ProfileDataAccess::loadReferenceTrainingStats(TrainingStats* stats, Profile* profile, const QString& courseId, const QString& lessonId) { stats->setCharactersTyped(0); stats->setElapsedTime(QTime()); stats->setErrorCount(0); stats->setErrorMap(QMap()); stats->setIsValid(false); QSqlDatabase db = database(); if (!db.isOpen()) return; QSqlQuery selectQuery; if (!selectQuery.prepare(QStringLiteral("SELECT id, characters_typed, error_count, elapsed_time FROM training_stats WHERE profile_id = ? AND course_id = ? AND lesson_id = ? ORDER BY date DESC LIMIT 1"))) { qWarning() << selectQuery.lastError().text(); raiseError(selectQuery.lastError()); return; } selectQuery.bindValue(0, profile->id()); selectQuery.bindValue(1, courseId); selectQuery.bindValue(2, lessonId); if (!selectQuery.exec()) { qWarning() << selectQuery.lastError().text(); raiseError(selectQuery.lastError()); return; } if (!selectQuery.next()) return; const int statsId = selectQuery.value(0).toInt(); stats->setCharactersTyped(selectQuery.value(1).toUInt()); stats->setErrorCount(selectQuery.value(2).toUInt()); stats->setElapsedTime(selectQuery.value(3).toInt()); QSqlQuery errorSelectQuery; errorSelectQuery.prepare(QStringLiteral("SELECT character, count FROM training_stats_errors WHERE stats_id = ?")); errorSelectQuery.bindValue(0, statsId); if (!errorSelectQuery.exec()) { qWarning() << errorSelectQuery.lastError().text(); raiseError(errorSelectQuery.lastError()); return; } QMap errorMap; while (errorSelectQuery.next()) { const QString character = errorSelectQuery.value(0).toString(); const int errorCount = errorSelectQuery.value(1).toInt(); errorMap.insert(character, errorCount); } stats->setErrorMap(errorMap); stats->setIsValid(true); } void ProfileDataAccess::saveTrainingStats(TrainingStats* stats, Profile* profile, const QString& courseId, const QString& lessonId) { QSqlDatabase db = database(); if (!db.isOpen()) return; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return; } QSqlQuery addQuery(db); if (!addQuery.prepare(QStringLiteral("INSERT INTO training_stats (profile_id, course_id, lesson_id, date, characters_typed, error_count, elapsed_time) VALUES (?, ?, ?, ?, ?, ?, ?)"))) { qWarning() << addQuery.lastError().text(); raiseError(addQuery.lastError()); db.rollback(); return; } addQuery.bindValue(0, profile->id()); addQuery.bindValue(1, courseId); addQuery.bindValue(2, lessonId); addQuery.bindValue(3, QDateTime::currentMSecsSinceEpoch()); addQuery.bindValue(4, stats->charactesTyped()); addQuery.bindValue(5, stats->errorCount()); const int rawElapsedTime = QTime(0, 0).msecsTo(stats->elapsedTime()); addQuery.bindValue(6, rawElapsedTime); if (!addQuery.exec()) { qWarning() << addQuery.lastError().text(); raiseError(addQuery.lastError()); db.rollback(); return; } QSqlQuery idQuery = db.exec(QStringLiteral("SELECT last_insert_rowid()")); if (db.lastError().isValid()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } idQuery.next(); int statsId = idQuery.value(0).toInt(); QSqlQuery addErrorsQuery(db); if (!addErrorsQuery.prepare(QStringLiteral("INSERT INTO training_stats_errors (stats_id, character, count) VALUES (?, ?, ?)"))) { qWarning() << addErrorsQuery.lastError().text(); raiseError(addErrorsQuery.lastError()); db.rollback(); return; } QMapIterator errorIterator(stats->errorMap()); while(errorIterator.hasNext()) { errorIterator.next(); addErrorsQuery.bindValue(0, statsId); addErrorsQuery.bindValue(1, errorIterator.key()); addErrorsQuery.bindValue(2, errorIterator.value()); if (!addErrorsQuery.exec()) { qWarning() << addErrorsQuery.lastError().text(); raiseError(addErrorsQuery.lastError()); db.rollback(); return; } } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } } QString ProfileDataAccess::courseProgress(Profile* profile, const QString& courseId, CourseProgressType type) { bool idOk; int id = findCourseProgressId(profile, courseId, type, &idOk); if (!idOk || id == -1) return QString(); QSqlDatabase db = database(); if (!db.isOpen()) return QString(); QSqlQuery selectQuery; selectQuery.prepare(QStringLiteral("SELECT lesson_id FROM course_progress WHERE id = ?")); selectQuery.bindValue(0, id); if (!selectQuery.exec()) { qWarning() << selectQuery.lastError().text(); raiseError(selectQuery.lastError()); return QString(); } selectQuery.next(); return selectQuery.value(0).toString(); } void ProfileDataAccess::saveCourseProgress(const QString& lessonId, Profile* profile, const QString& courseId, CourseProgressType type) { bool idOk; int id = findCourseProgressId(profile, courseId, type, &idOk); if (!idOk) return; QSqlDatabase db = database(); if (!db.isOpen()) return; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return; } if (id == -1) { QSqlQuery insertQuery; insertQuery.prepare(QStringLiteral("INSERT INTO course_progress (profile_id, course_id, type, lesson_id) VALUES (?, ?, ?, ?)")); insertQuery.bindValue(0, profile->id()); insertQuery.bindValue(1, courseId); insertQuery.bindValue(2, type); insertQuery.bindValue(3, lessonId); if (!insertQuery.exec()) { qWarning() << insertQuery.lastError().text(); raiseError(insertQuery.lastError()); db.rollback(); return; } } else { QSqlQuery updateQuery; updateQuery.prepare(QStringLiteral("UPDATE course_progress SET lesson_id = ? WHERE id = ?")); updateQuery.bindValue(0, lessonId); updateQuery.bindValue(1, id); if (!updateQuery.exec()) { qWarning() << updateQuery.lastError().text(); raiseError(updateQuery.lastError()); db.rollback(); return; } } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return; } } int ProfileDataAccess::lessonsTrained(Profile* profile) { QSqlDatabase db = database(); if (!profile) return 0; QString sql = QStringLiteral("SELECT COUNT(*) FROM training_stats WHERE profile_id = ?"); QSqlQuery query(db); query.prepare(sql); query.bindValue(0, profile->id()); if (!query.exec()) { qWarning() << query.lastError().text(); raiseError(query.lastError()); return 0; } query.next(); return query.value(0).toInt(); } quint64 ProfileDataAccess::totalTrainingTime(Profile* profile) { QSqlDatabase db = database(); if (!profile) return 0; QString sql = QStringLiteral("SELECT SUM(elapsed_time) FROM training_stats WHERE profile_id = ?"); QSqlQuery query(db); query.prepare(sql); query.bindValue(0, profile->id()); if (!query.exec()) { qWarning() << query.lastError().text(); raiseError(query.lastError()); return 0; } query.next(); return query.value(0).value(); } QDateTime ProfileDataAccess::lastTrainingSession(Profile* profile) { QSqlDatabase db = database(); if (!profile) return QDateTime(); QString sql = QStringLiteral("SELECT date FROM training_stats WHERE profile_id = ? ORDER BY date DESC LIMIT 1"); QSqlQuery query(db); query.prepare(sql); query.bindValue(0, profile->id()); if (!query.exec()) { qWarning() << query.lastError().text(); raiseError(query.lastError()); return QDateTime(); } if (!query.next()) return QDateTime(); return QDateTime::fromMSecsSinceEpoch(query.value(0).value()); } bool ProfileDataAccess::loadCustomLessons(Profile* profile, const QString& keyboardLayoutNameFilter, Course* target) { target->setIsValid(false); QSqlDatabase db = database(); if (!profile) return false; if (!db.isOpen()) return false; QString sql = QStringLiteral("SELECT id, title, text, keyboard_layout_name FROM custom_lessons WHERE profile_id = ?"); if (!keyboardLayoutNameFilter.isNull()) { sql += QLatin1String(" AND keyboard_layout_name = ?"); } QSqlQuery query(db); query.prepare(sql); query.bindValue(0, profile->id()); if (!keyboardLayoutNameFilter.isNull()) { query.bindValue(1, keyboardLayoutNameFilter); } if (!query.exec()) { qWarning() << query.lastError().text(); raiseError(query.lastError()); return false; } if (!query.isActive()) return false; - target->setDoSyncLessonCharacters(false); + target->setKind(Course::LessonCollection); target->setId(QStringLiteral("custom_lessons")); target->setTitle(i18n("Custom Lessons")); target->setDescription(i18n("A place to store personal lesson texts")); target->setKeyboardLayoutName(keyboardLayoutNameFilter); target->clearLessons(); while (query.next()) { Lesson* lesson = new Lesson(this); lesson->setId(query.value(0).toString()); lesson->setTitle(query.value(1).toString()); const QString text = query.value(2).toString(); QString characters = QLatin1String(""); for (int i = 0; i < text.length(); i++) { const QChar character = text.at(i); if (!characters.contains(character)) { characters.append(character); } } lesson->setText(text); lesson->setCharacters(characters); target->addLesson(lesson); } target->setIsValid(true); return true; } bool ProfileDataAccess::storeCustomLesson(Lesson* lesson, Profile* profile, const QString& keyboardLayoutName) { QSqlDatabase db = database(); if (!db.isOpen()) return false; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery idQuery(db); idQuery.prepare(QStringLiteral("SELECT count(*) FROM custom_lessons WHERE id = ?")); idQuery.bindValue(0, lesson->id()); idQuery.exec(); if (idQuery.lastError().isValid()) { qWarning() << idQuery.lastError().text(); raiseError(idQuery.lastError()); db.rollback(); return false; } idQuery.next(); const bool lessonAlreadyExists = idQuery.value(0).toInt() == 1; if (lessonAlreadyExists) { QSqlQuery updateQuery(db); updateQuery.prepare(QStringLiteral("UPDATE custom_lessons SET profile_id = ?, title = ?, text = ?, keyboard_layout_name = ? WHERE id = ?")); if (updateQuery.lastError().isValid()) { qWarning() << updateQuery.lastError().text(); raiseError(updateQuery.lastError()); db.rollback(); return false; } updateQuery.bindValue(0, profile->id()); updateQuery.bindValue(1, lesson->title()); updateQuery.bindValue(2, lesson->text()); updateQuery.bindValue(3, keyboardLayoutName); updateQuery.bindValue(4, lesson->id()); updateQuery.exec(); if (updateQuery.lastError().isValid()) { qWarning() << updateQuery.lastError().text(); raiseError(updateQuery.lastError()); db.rollback(); return false; } } else { QSqlQuery insertQuery(db); insertQuery.prepare(QStringLiteral("INSERT INTO custom_lessons (id, profile_id, title, text, keyboard_layout_name) VALUES (?, ?, ?, ?, ?)")); if (insertQuery.lastError().isValid()) { qWarning() << insertQuery.lastError().text(); raiseError(insertQuery.lastError()); db.rollback(); return false; } insertQuery.bindValue(0, lesson->id()); insertQuery.bindValue(1, profile->id()); insertQuery.bindValue(2, lesson->title()); insertQuery.bindValue(3, lesson->text()); insertQuery.bindValue(4, keyboardLayoutName); insertQuery.exec(); if (insertQuery.lastError().isValid()) { qWarning() << insertQuery.lastError().text(); raiseError(insertQuery.lastError()); db.rollback(); return false; } } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return false; } return true; } bool ProfileDataAccess::deleteCustomLesson(const QString& id) { QSqlDatabase db = database(); if (!db.isOpen()) return false; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery deleteQuery(db); deleteQuery.prepare(QStringLiteral("DELETE FROM custom_lessons WHERE id = ?")); deleteQuery.bindValue(0, id); deleteQuery.exec(); if (deleteQuery.lastError().isValid()) { qWarning() << deleteQuery.lastError().text(); raiseError(deleteQuery.lastError()); db.rollback(); return false; } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return false; } return true; } QSqlQuery ProfileDataAccess::learningProgressQuery(Profile* profile, Course* courseFilter, Lesson* lessonFilter) { QSqlDatabase db = database(); if (!profile) return QSqlQuery(); if (!db.isOpen()) return QSqlQuery(); QString sql = QStringLiteral("SELECT date, characters_typed, error_count, elapsed_time, lesson_id FROM training_stats WHERE profile_id = ?"); if (courseFilter) { sql += QLatin1String(" AND course_id = ?"); } if (lessonFilter) { sql += QLatin1String(" AND lesson_id = ?"); } QSqlQuery query(db); query.prepare(sql); query.bindValue(0, profile->id()); if (courseFilter) { query.bindValue(1, courseFilter->id()); } if (lessonFilter) { query.bindValue(courseFilter? 2: 1, lessonFilter->id()); } if (!query.exec()) { qWarning() << query.lastError().text(); raiseError(query.lastError()); return QSqlQuery(); } return query; } int ProfileDataAccess::findCourseProgressId(Profile* profile, const QString& courseId, CourseProgressType type, bool* ok) { *ok = false; QSqlDatabase db = database(); if (!db.isOpen()) return -1; QSqlQuery findQuery; findQuery.prepare(QStringLiteral("SELECT id FROM course_progress WHERE profile_id = ? AND course_id = ? AND type = ? LIMIT 1")); findQuery.bindValue(0, profile->id()); findQuery.bindValue(1, courseId); findQuery.bindValue(2, type); if (!findQuery.exec()) { qWarning() << findQuery.lastError().text(); raiseError(findQuery.lastError()); return -1; } *ok = true; if (!findQuery.next()) { return -1; } return findQuery.value(0).toInt(); } diff --git a/src/core/resourcedataaccess.cpp b/src/core/resourcedataaccess.cpp index d038854..62a0569 100644 --- a/src/core/resourcedataaccess.cpp +++ b/src/core/resourcedataaccess.cpp @@ -1,442 +1,443 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "resourcedataaccess.h" #include #include #include #include #include #include #include #include #include #include #include "dataindex.h" #include "keyboardlayout.h" #include "key.h" #include "specialkey.h" #include "keychar.h" #include "course.h" #include "lesson.h" ResourceDataAccess::ResourceDataAccess(QObject *parent) : QObject(parent) { } bool ResourceDataAccess::fillDataIndex(DataIndex* target) { QXmlSchema schema = loadXmlSchema(QStringLiteral("data")); if (!schema.isValid()) return false; foreach (const QString& path, QStandardPaths::locateAll(QStandardPaths::DataLocation, "data.xml")) { QDir dir = QFileInfo(path).dir(); QFile dataIndexFile; dataIndexFile.setFileName(path); if (!dataIndexFile.open(QIODevice::ReadOnly)) { qWarning() << "can't open:" << path; return false; } QDomDocument doc = getDomDocument(dataIndexFile, schema); if (doc.isNull()) { qWarning() << "invalid doc:" << path; return false; } QDomElement root(doc.documentElement()); for (QDomElement dataNode = root.firstChildElement(); !dataNode.isNull(); dataNode = dataNode.nextSiblingElement()) { const QString path = dir.filePath(dataNode.firstChildElement(QStringLiteral("path")).text()); if (dataNode.tagName() == QLatin1String("course")) { DataIndexCourse* course = new DataIndexCourse(this); course->setTitle(dataNode.firstChildElement(QStringLiteral("title")).text()); course->setDescription(dataNode.firstChildElement(QStringLiteral("description")).text()); course->setKeyboardLayoutName(dataNode.firstChildElement(QStringLiteral("keyboardLayout")).text()); course->setId(dataNode.firstChildElement(QStringLiteral("id")).text()); course->setPath(path); course->setSource(DataIndex::BuiltInResource); target->addCourse(course); } else if (dataNode.tagName() == QLatin1String("keyboardLayout")) { DataIndexKeyboardLayout* keyboardLayout = new DataIndexKeyboardLayout(this); keyboardLayout->setTitle(dataNode.firstChildElement(QStringLiteral("title")).text()); keyboardLayout->setName(dataNode.firstChildElement(QStringLiteral("name")).text()); keyboardLayout->setId(dataNode.firstChildElement(QStringLiteral("id")).text()); keyboardLayout->setPath(path); keyboardLayout->setSource(DataIndex::BuiltInResource); target->addKeyboardLayout(keyboardLayout); } } } return true; } bool ResourceDataAccess::loadKeyboardLayout(const QString &path, KeyboardLayout* target) { target->setIsValid(false); QFile keyboardLayoutFile; keyboardLayoutFile.setFileName(path); if (!keyboardLayoutFile.open(QIODevice::ReadOnly)) { qWarning() << "can't open:" << path; return false; } QXmlSchema schema = loadXmlSchema(QStringLiteral("keyboardlayout")); if (!schema.isValid()) return false; QDomDocument doc = getDomDocument(keyboardLayoutFile, schema); if (doc.isNull()) { qWarning() << "invalid doc:" << path; return false; } QDomElement root(doc.documentElement()); target->clearKeys(); target->setId(root.firstChildElement(QStringLiteral("id")).text()); target->setTitle(root.firstChildElement(QStringLiteral("title")).text()); target->setName(root.firstChildElement(QStringLiteral("name")).text()); target->setWidth(root.firstChildElement(QStringLiteral("width")).text().toInt()); target->setHeight(root.firstChildElement(QStringLiteral("height")).text().toInt()); for (QDomElement keyNode = root.firstChildElement(QStringLiteral("keys")).firstChildElement(); !keyNode.isNull(); keyNode = keyNode.nextSiblingElement()) { AbstractKey* abstractKey; if (keyNode.tagName() == QLatin1String("key")) { Key* key = new Key(this); key->setFingerIndex(keyNode.attribute(QStringLiteral("fingerIndex")).toInt()); key->setHasHapticMarker(keyNode.attribute(QStringLiteral("hasHapticMarker")) == QLatin1String("true")); for (QDomElement charNode = keyNode.firstChildElement(QStringLiteral("char")); !charNode.isNull(); charNode = charNode.nextSiblingElement(QStringLiteral("char"))) { KeyChar* keyChar = new KeyChar(key); keyChar->setValue(charNode.text().at(0)); keyChar->setPositionStr(charNode.attribute(QStringLiteral("position"))); keyChar->setModifier(charNode.attribute(QStringLiteral("modifier"))); key->addKeyChar(keyChar); } abstractKey = key; } else if (keyNode.tagName() == QLatin1String("specialKey")) { SpecialKey* specialKey = new SpecialKey(this); specialKey->setTypeStr(keyNode.attribute(QStringLiteral("type"))); specialKey->setModifierId(keyNode.attribute(QStringLiteral("modifierId"))); specialKey->setLabel(keyNode.attribute(QStringLiteral("label"))); abstractKey = specialKey; } else { continue; } abstractKey->setLeft(keyNode.attribute(QStringLiteral("left")).toInt()); abstractKey->setTop(keyNode.attribute(QStringLiteral("top")).toInt()); abstractKey->setWidth(keyNode.attribute(QStringLiteral("width")).toInt()); abstractKey->setHeight(keyNode.attribute(QStringLiteral("height")).toInt()); target->addKey(abstractKey); } target->setIsValid(true); return true; } bool ResourceDataAccess::storeKeyboardLayout(const QString& path, KeyboardLayout* source) { QDomDocument doc; QDomProcessingInstruction header = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); doc.appendChild(header); QDomElement root = doc.createElement(QStringLiteral("keyboardLayout")); doc.appendChild(root); QDomElement idElem = doc.createElement(QStringLiteral("id")); QDomElement titleElem = doc.createElement(QStringLiteral("title")); QDomElement nameElem = doc.createElement(QStringLiteral("name")); QDomElement widthElem = doc.createElement(QStringLiteral("width")); QDomElement heightElem = doc.createElement(QStringLiteral("height")); QDomElement keysElem = doc.createElement(QStringLiteral("keys")); idElem.appendChild(doc.createTextNode(source->id())); titleElem.appendChild(doc.createTextNode(source->title())); nameElem.appendChild(doc.createTextNode(source->name())); heightElem.appendChild(doc.createTextNode(QString::number(source->height()))); widthElem.appendChild(doc.createTextNode(QString::number(source->width()))); for (int i = 0; i < source->keyCount(); i++) { AbstractKey* const abstractKey = source->key(i); QDomElement keyElem = doc.createElement(QStringLiteral("key")); keyElem.setAttribute(QStringLiteral("left"), abstractKey->left()); keyElem.setAttribute(QStringLiteral("top"), abstractKey->top()); keyElem.setAttribute(QStringLiteral("width"), abstractKey->width()); keyElem.setAttribute(QStringLiteral("height"), abstractKey->height()); if (Key* const key = qobject_cast(abstractKey)) { keyElem.setTagName(QStringLiteral("key")); keyElem.setAttribute(QStringLiteral("fingerIndex"), key->fingerIndex()); if (key->hasHapticMarker()) { keyElem.setAttribute(QStringLiteral("hasHapticMarker"), QStringLiteral("true")); } for (int j = 0; j < key->keyCharCount(); j++) { KeyChar* const keyChar = key->keyChar(j); QDomElement keyCharElem = doc.createElement(QStringLiteral("char")); keyCharElem.setAttribute(QStringLiteral("position"), keyChar->positionStr()); const QString modifier = keyChar->modifier(); if (!modifier.isEmpty()) { keyCharElem.setAttribute(QStringLiteral("modifier"), modifier); } const QString value = keyChar->value(); if (value == QLatin1String(" ")) { keyCharElem.appendChild(doc.createCDATASection(value)); } else { keyCharElem.appendChild(doc.createTextNode(value)); } keyElem.appendChild(keyCharElem); } } if (SpecialKey* const specialKey = qobject_cast(abstractKey)) { keyElem.setTagName(QStringLiteral("specialKey")); keyElem.setAttribute(QStringLiteral("type"), specialKey->typeStr()); const QString modifierId = specialKey->modifierId(); if (!modifierId.isNull()) { keyElem.setAttribute(QStringLiteral("modifierId"), modifierId); } const QString label = specialKey->label(); if (!label.isNull()) { keyElem.setAttribute(QStringLiteral("label"), label); } } keysElem.appendChild(keyElem); } root.appendChild(idElem); root.appendChild(titleElem); root.appendChild(nameElem); root.appendChild(widthElem); root.appendChild(heightElem); root.appendChild(keysElem); QFile file; file.setFileName(path); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "can't open:" << file.fileName(); return false; } file.write(doc.toByteArray()); return true; } bool ResourceDataAccess::loadCourse(const QString &path, Course* target) { target->setIsValid(false); QFile courseFile; courseFile.setFileName(path); if (!courseFile.open(QIODevice::ReadOnly)) { qWarning() << "can't open:" << path; return false; } QXmlSchema schema = loadXmlSchema(QStringLiteral("course")); if (!schema.isValid()) return false; QDomDocument doc = getDomDocument(courseFile, schema); if (doc.isNull()) { qWarning() << "invalid doc:" << path; return false; } QDomElement root(doc.documentElement()); target->setId(root.firstChildElement(QStringLiteral("id")).text()); target->setTitle(root.firstChildElement(QStringLiteral("title")).text()); target->setDescription(root.firstChildElement(QStringLiteral("description")).text()); target->setKeyboardLayoutName(root.firstChildElement(QStringLiteral("keyboardLayout")).text()); + target->setKind(Course::SequentialCourse); target->clearLessons(); for (QDomElement lessonNode = root.firstChildElement(QStringLiteral("lessons")).firstChildElement(); !lessonNode.isNull(); lessonNode = lessonNode.nextSiblingElement()) { Lesson* lesson = new Lesson(this); lesson->setId(lessonNode.firstChildElement(QStringLiteral("id")).text()); lesson->setTitle(lessonNode.firstChildElement(QStringLiteral("title")).text()); lesson->setNewCharacters(lessonNode.firstChildElement(QStringLiteral("newCharacters")).text()); lesson->setText(lessonNode.firstChildElement(QStringLiteral("text")).text()); target->addLesson(lesson); } target->setIsValid(true); return true; } bool ResourceDataAccess::storeCourse(const QString& path, Course* source) { QDomDocument doc; QDomProcessingInstruction header = doc.createProcessingInstruction(QStringLiteral("xml"), QStringLiteral("version=\"1.0\"")); doc.appendChild(header); QDomElement root = doc.createElement(QStringLiteral("course")); doc.appendChild(root); QDomElement idElem = doc.createElement(QStringLiteral("id")); QDomElement titleElem = doc.createElement(QStringLiteral("title")); QDomElement descriptionElem = doc.createElement(QStringLiteral("description")); QDomElement keyboardLayoutElem = doc.createElement(QStringLiteral("keyboardLayout")); QDomElement lessonsElem = doc.createElement(QStringLiteral("lessons")); idElem.appendChild(doc.createTextNode(source->id())); titleElem.appendChild(doc.createTextNode(source->title())); keyboardLayoutElem.appendChild(doc.createTextNode(source->keyboardLayoutName())); descriptionElem.appendChild(doc.createTextNode(source->description())); for (int i = 0; i < source->lessonCount(); i++) { Lesson* const lesson = source->lesson(i); QStringList lines; QDomElement lessonElem = doc.createElement(QStringLiteral("lesson")); QDomElement idElem = doc.createElement(QStringLiteral("id")); QDomElement titleElem = doc.createElement(QStringLiteral("title")); QDomElement newCharactersElem = doc.createElement(QStringLiteral("newCharacters")); QDomElement textElem = doc.createElement(QStringLiteral("text")); idElem.appendChild(doc.createTextNode(lesson->id())); titleElem.appendChild(doc.createTextNode(lesson->title())); newCharactersElem.appendChild(doc.createTextNode(lesson->newCharacters())); textElem.appendChild(doc.createTextNode(lesson->text())); lessonElem.appendChild(idElem); lessonElem.appendChild(titleElem); lessonElem.appendChild(newCharactersElem); lessonElem.appendChild(textElem); lessonsElem.appendChild(lessonElem); } root.appendChild(idElem); root.appendChild(titleElem); root.appendChild(descriptionElem); root.appendChild(keyboardLayoutElem); root.appendChild(lessonsElem); QFile file; file.setFileName(path); if (!file.open(QIODevice::WriteOnly)) { qWarning() << "can't open:" << file.fileName(); return false; } file.write(doc.toByteArray()); return true; } QXmlSchema ResourceDataAccess::loadXmlSchema(const QString &name) { QXmlSchema schema; QString relPath = QStringLiteral("schemata/%1.xsd").arg(name); QFile schemaFile; if (!openResourceFile(relPath, schemaFile)) { return schema; } schema.load(&schemaFile, QUrl::fromLocalFile(schemaFile.fileName())); if (!schema.isValid()) { qWarning() << schemaFile.fileName() << "is invalid"; } return schema; } QDomDocument ResourceDataAccess::getDomDocument(QFile &file, QXmlSchema &schema) { QDomDocument doc; QXmlSchemaValidator validator(schema); if (!validator.validate(&file)) { return doc; } file.reset(); QString errorMsg; if (!doc.setContent(&file, &errorMsg)) { qWarning() << errorMsg; } return doc; } bool ResourceDataAccess::openResourceFile(const QString &relPath, QFile& file) { QString path = QStandardPaths::locate(QStandardPaths::DataLocation, relPath); if (path.isNull()) { qWarning() << "can't find resource:" << relPath; return false; } file.setFileName(path); if (!file.open(QIODevice::ReadOnly)) { qWarning() << "can't open" << path; return false; } return true; } diff --git a/src/core/specialkey.h b/src/core/specialkey.h index 5521a22..c47e25d 100644 --- a/src/core/specialkey.h +++ b/src/core/specialkey.h @@ -1,65 +1,65 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef SPECIALKEY_H #define SPECIALKEY_H #include "abstractkey.h" class SpecialKey : public AbstractKey { Q_OBJECT Q_ENUMS(Type) Q_PROPERTY(Type type READ type WRITE setType NOTIFY typeChanged) Q_PROPERTY(QString modifierId READ modifierId WRITE setModifierId NOTIFY modifierIdChanged) Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged) public: enum Type { Tab, Capslock, Shift, Backspace, Return, Space, Other }; explicit SpecialKey(QObject *parent = 0); - Q_INVOKABLE QString keyType() const override; + Q_INVOKABLE QString keyType() const override; QString typeStr() const; void setTypeStr(const QString& typeStr); Type type() const; void setType(Type type); QString modifierId() const; void setModifierId(const QString& modifierId); QString label() const ; void setLabel(const QString& label); Q_INVOKABLE void copyFrom(SpecialKey* source); signals: void typeChanged(); void modifierIdChanged(); void labelChanged(); private: Type m_type; QString m_modifierId; QString m_label; }; #endif // SPECIALKEY_H diff --git a/src/core/userdataaccess.cpp b/src/core/userdataaccess.cpp index 1822b29..9fb9390 100644 --- a/src/core/userdataaccess.cpp +++ b/src/core/userdataaccess.cpp @@ -1,672 +1,673 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "userdataaccess.h" #include #include #include #include #include #include #include "core/dataindex.h" #include "core/course.h" #include "core/lesson.h" #include "core/keyboardlayout.h" #include "core/key.h" #include "core/keychar.h" #include "core/specialkey.h" enum KeyTypeId { KeyId = 1, SpecialKeyId }; UserDataAccess::UserDataAccess(QObject* parent) : DbAccess(parent) { } bool UserDataAccess::fillDataIndex(DataIndex* target) { QSqlDatabase db = database(); if (!db.isOpen()) return false; QSqlQuery courseQuery = db.exec(QStringLiteral("SELECT id, title, description, keyboard_layout_name FROM courses")); if (courseQuery.lastError().isValid()) { qWarning() << courseQuery.lastError().text(); raiseError(courseQuery.lastError()); return false; } while (courseQuery.next()) { DataIndexCourse* course = new DataIndexCourse(); course->setId(courseQuery.value(0).toString()); course->setTitle(courseQuery.value(1).toString()); course->setDescription(courseQuery.value(2).toString()); course->setKeyboardLayoutName(courseQuery.value(3).toString()); course->setSource(DataIndex::UserResource); target->addCourse(course); } QSqlQuery keyboardLayoutQuery = db.exec(QStringLiteral("SELECT id, title, name FROM keyboard_layouts")); if (keyboardLayoutQuery.lastError().isValid()) { qWarning() << keyboardLayoutQuery.lastError().text(); raiseError(keyboardLayoutQuery.lastError()); return false; } while (keyboardLayoutQuery.next()) { DataIndexKeyboardLayout* keyboardLayout = new DataIndexKeyboardLayout(); keyboardLayout->setId(keyboardLayoutQuery.value(0).toString()); keyboardLayout->setTitle(keyboardLayoutQuery.value(1).toString()); keyboardLayout->setName(keyboardLayoutQuery.value(2).toString()); keyboardLayout->setSource(DataIndex::UserResource); target->addKeyboardLayout(keyboardLayout); } return true; } bool UserDataAccess::loadCourse(const QString& id, Course* target) { target->setIsValid(false); QSqlDatabase db = database(); if (!db.isOpen()) return false; QSqlQuery courseQuery(db); courseQuery.prepare(QStringLiteral("SELECT title, description, keyboard_layout_name FROM courses WHERE id = ? LIMIT 1")); courseQuery.bindValue(0, id); courseQuery.exec(); if (courseQuery.lastError().isValid()) { qWarning() << courseQuery.lastError().text(); raiseError(courseQuery.lastError()); return false; } if (!courseQuery.next()) { const QString warning = i18n("No course with ID %1", id); qWarning() << warning; raiseError(warning); } target->setId(id); target->setTitle(courseQuery.value(0).toString()); target->setDescription(courseQuery.value(1).toString()); target->setKeyboardLayoutName(courseQuery.value(2).toString()); + target->setKind(Course::SequentialCourse); target->clearLessons(); QSqlQuery lessonsQuery(db); lessonsQuery.prepare(QStringLiteral("SELECT id, title, new_characters, text FROM course_lessons WHERE course_id = ?")); lessonsQuery.bindValue(0, id); lessonsQuery.exec(); if (lessonsQuery.lastError().isValid()) { qWarning() << lessonsQuery.lastError().text(); raiseError(lessonsQuery.lastError()); return false; } while (lessonsQuery.next()) { Lesson* lesson = new Lesson(); lesson->setId(lessonsQuery.value(0).toString()); lesson->setTitle(lessonsQuery.value(1).toString()); lesson->setNewCharacters(lessonsQuery.value(2).toString()); lesson->setText(lessonsQuery.value(3).toString()); target->addLesson(lesson); } target->setIsValid(true); return true; } bool UserDataAccess::storeCourse(Course* course) { QSqlDatabase db = database(); if (!db.isOpen()) return false; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery cleanUpCourseQuery(db); cleanUpCourseQuery.prepare(QStringLiteral("DELETE FROM courses WHERE id = ?")); cleanUpCourseQuery.bindValue(0, course->id()); cleanUpCourseQuery.exec(); if (cleanUpCourseQuery.lastError().isValid()) { qWarning() << cleanUpCourseQuery.lastError().text(); raiseError(cleanUpCourseQuery.lastError()); db.rollback(); return false; } QSqlQuery cleanUpLessonsQuery(db); cleanUpLessonsQuery.prepare(QStringLiteral("DELETE FROM course_lessons WHERE course_id = ?")); cleanUpLessonsQuery.bindValue(0, course->id()); cleanUpLessonsQuery.exec(); if (cleanUpLessonsQuery.lastError().isValid()) { qWarning() << cleanUpLessonsQuery.lastError().text(); raiseError(cleanUpLessonsQuery.lastError()); db.rollback(); return false; } QSqlQuery insertCourseQuery(db); insertCourseQuery.prepare(QStringLiteral("INSERT INTO courses (id, title, description, keyboard_layout_name) VALUES (?, ?, ?, ?)")); insertCourseQuery.bindValue(0, course->id()); insertCourseQuery.bindValue(1, course->title()); insertCourseQuery.bindValue(2, course->description()); insertCourseQuery.bindValue(3, course->keyboardLayoutName()); insertCourseQuery.exec(); if (insertCourseQuery.lastError().isValid()) { qWarning() << insertCourseQuery.lastError().text(); raiseError(insertCourseQuery.lastError()); db.rollback(); return false; } QSqlQuery insertLessonsQuery(db); insertLessonsQuery.prepare(QStringLiteral("INSERT INTO course_lessons (id, title, new_characters, text, course_id) VALUES(?, ?, ?, ?, ?)")); insertLessonsQuery.bindValue(4, course->id()); for (int i = 0; i < course->lessonCount(); i++) { Lesson* lesson = course->lesson(i); insertLessonsQuery.bindValue(0, lesson->id()); insertLessonsQuery.bindValue(1, lesson->title()); insertLessonsQuery.bindValue(2, lesson->newCharacters()); insertLessonsQuery.bindValue(3, lesson->text()); insertLessonsQuery.exec(); if (insertLessonsQuery.lastError().isValid()) { qWarning() << insertLessonsQuery.lastError().text(); raiseError(insertLessonsQuery.lastError()); db.rollback(); return false; } } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return false; } return true; } bool UserDataAccess::deleteCourse(Course* course) { QSqlDatabase db = database(); if (!db.isOpen()) return false; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery deleteCourseQuery(db); deleteCourseQuery.prepare(QStringLiteral("DELETE FROM courses WHERE id = ?")); deleteCourseQuery.bindValue(0, course->id()); deleteCourseQuery.exec(); if (deleteCourseQuery.lastError().isValid()) { qWarning() << deleteCourseQuery.lastError().text(); raiseError(deleteCourseQuery.lastError()); db.rollback(); return false; } QSqlQuery deleteLessonsQuery(db); deleteLessonsQuery.prepare(QStringLiteral("DELETE FROM course_lessons WHERE course_id = ?")); deleteLessonsQuery.bindValue(0, course->id()); deleteLessonsQuery.exec(); if (deleteLessonsQuery.lastError().isValid()) { qWarning() << deleteLessonsQuery.lastError().text(); raiseError(deleteLessonsQuery.lastError()); db.rollback(); return false; } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return false; } return true; } bool UserDataAccess::loadKeyboardLayout(const QString& id, KeyboardLayout* target) { target->setIsValid(false); QSqlDatabase db = database(); if (!db.isOpen()) return false; QSqlQuery keyboardLayoutQuery(db); keyboardLayoutQuery.prepare(QStringLiteral("SELECT title, name, width, height FROM keyboard_layouts WHERE id = ? LIMIT 1")); keyboardLayoutQuery.bindValue(0, id); keyboardLayoutQuery.exec(); if (keyboardLayoutQuery.lastError().isValid()) { qWarning() << keyboardLayoutQuery.lastError().text(); raiseError(keyboardLayoutQuery.lastError()); return false; } if (!keyboardLayoutQuery.next()) { const QString warning = i18n("No keyboard layout with ID %1", id); qWarning() << warning; raiseError(warning); } target->setId(id); target->setTitle(keyboardLayoutQuery.value(0).toString()); target->setName(keyboardLayoutQuery.value(1).toString()); target->setWidth(keyboardLayoutQuery.value(2).toInt()); target->setHeight(keyboardLayoutQuery.value(3).toInt()); target->clearKeys(); QSqlQuery keysQuery(db); keysQuery.prepare(QStringLiteral("SELECT id, left, top, width, height, type, finger_index, has_haptic_marker, special_key_type, modifier_id, label FROM keyboard_layout_keys WHERE keyboard_layout_id = ?")); keysQuery.bindValue(0, id); keysQuery.exec(); QSqlQuery keyCharsQuery(db); keyCharsQuery.prepare(QStringLiteral("SELECT position, character, modifier FROM keyboard_layout_key_chars WHERE key_id = ?")); if (keysQuery.lastError().isValid()) { qWarning() << keysQuery.lastError().text(); raiseError(keysQuery.lastError()); return false; } while (keysQuery.next()) { AbstractKey* abstractKey; KeyTypeId keyType = static_cast(keysQuery.value(5).toInt()); if (keyType == KeyId) { Key* key = new Key(); key->setFingerIndex(keysQuery.value(6).toInt()); key->setHasHapticMarker(keysQuery.value(7).toBool()); keyCharsQuery.bindValue(0, keysQuery.value(0)); keyCharsQuery.exec(); if (keyCharsQuery.lastError().isValid()) { qWarning() << keyCharsQuery.lastError().text(); raiseError(keyCharsQuery.lastError()); return false; } while (keyCharsQuery.next()) { KeyChar* keyChar = new KeyChar(); keyChar->setPosition(static_cast(keyCharsQuery.value(0).toInt())); keyChar->setValue(keyCharsQuery.value(1).toString().at(0)); keyChar->setModifier(keyCharsQuery.value(2).toString()); key->addKeyChar(keyChar); } abstractKey = key; } else { SpecialKey* specialKey = new SpecialKey(); specialKey->setTypeStr(keysQuery.value(8).toString()); specialKey->setModifierId(keysQuery.value(9).toString()); specialKey->setLabel(keysQuery.value(10).toString()); abstractKey = specialKey; } abstractKey->setLeft(keysQuery.value(1).toInt()); abstractKey->setTop(keysQuery.value(2).toInt()); abstractKey->setWidth(keysQuery.value(3).toInt()); abstractKey->setHeight(keysQuery.value(4).toInt()); target->addKey(abstractKey); } target->setIsValid(true); return true; } bool UserDataAccess::storeKeyboardLayout(KeyboardLayout* keyboardLayout) { QSqlDatabase db = database(); if (!db.isOpen()) return false; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery cleanUpKeyCharsQuery(db); cleanUpKeyCharsQuery.prepare(QStringLiteral("DELETE FROM keyboard_layout_key_chars WHERE key_id IN (SELECT id FROM keyboard_layout_keys WHERE keyboard_layout_id = ?)")); cleanUpKeyCharsQuery.bindValue(0, keyboardLayout->id()); cleanUpKeyCharsQuery.exec(); if (cleanUpKeyCharsQuery.lastError().isValid()) { qWarning() << cleanUpKeyCharsQuery.lastError().text(); raiseError(cleanUpKeyCharsQuery.lastError()); db.rollback(); return false; } QSqlQuery cleanUpKeysQuery(db); cleanUpKeysQuery.prepare(QStringLiteral("DELETE FROM keyboard_layout_keys WHERE keyboard_layout_id = ?")); cleanUpKeysQuery.bindValue(0, keyboardLayout->id()); cleanUpKeysQuery.exec(); if (cleanUpKeysQuery.lastError().isValid()) { qWarning() << cleanUpKeysQuery.lastError().text(); raiseError(cleanUpKeysQuery.lastError()); db.rollback(); return false; } QSqlQuery cleanUpKeyboardLayoutQuery(db); cleanUpKeyboardLayoutQuery.prepare(QStringLiteral("DELETE FROM keyboard_layouts WHERE id = ?")); cleanUpKeyboardLayoutQuery.bindValue(0, keyboardLayout->id()); cleanUpKeyboardLayoutQuery.exec(); if (cleanUpKeyboardLayoutQuery.lastError().isValid()) { qWarning() << cleanUpKeyboardLayoutQuery.lastError().text(); raiseError(cleanUpKeyboardLayoutQuery.lastError()); db.rollback(); return false; } QSqlQuery insertKeyboardLayoutQuery(db); insertKeyboardLayoutQuery.prepare(QStringLiteral("INSERT INTO keyboard_layouts (id, title, name, width, height) VALUES (?, ?, ?, ?, ?)")); insertKeyboardLayoutQuery.bindValue(0, keyboardLayout->id()); insertKeyboardLayoutQuery.bindValue(1, keyboardLayout->title()); insertKeyboardLayoutQuery.bindValue(2, keyboardLayout->name()); insertKeyboardLayoutQuery.bindValue(3, keyboardLayout->width()); insertKeyboardLayoutQuery.bindValue(4, keyboardLayout->height()); insertKeyboardLayoutQuery.exec(); if (insertKeyboardLayoutQuery.lastError().isValid()) { qWarning() << insertKeyboardLayoutQuery.lastError().text(); raiseError(insertKeyboardLayoutQuery.lastError()); db.rollback(); return false; } QSqlQuery insertKeyQuery(db); QSqlQuery insertSpecialKeyQuery(db); QSqlQuery insertKeyCharQuery(db); QSqlQuery idQuery(db); insertKeyQuery.prepare(QStringLiteral("INSERT INTO keyboard_layout_keys (keyboard_layout_id, left, top, width, height, type, finger_index, has_haptic_marker) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")); insertKeyQuery.bindValue(0, keyboardLayout->id()); insertKeyQuery.bindValue(5, KeyId); insertSpecialKeyQuery.prepare(QStringLiteral("INSERT INTO keyboard_layout_keys (keyboard_layout_id, left, top, width, height, type, special_key_type, modifier_id, label) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")); insertSpecialKeyQuery.bindValue(0, keyboardLayout->id()); insertSpecialKeyQuery.bindValue(5, SpecialKeyId); insertKeyCharQuery.prepare(QStringLiteral("INSERT INTO keyboard_layout_key_chars (key_id, position, character, modifier) VALUES (?, ?, ?, ?)")); idQuery.prepare(QStringLiteral("SELECT last_insert_rowid()")); for (int i = 0; i < keyboardLayout->keyCount(); i++) { AbstractKey* const abstractKey = keyboardLayout->key(i); if (Key* const key = qobject_cast(abstractKey)) { insertKeyQuery.bindValue(1, key->left()); insertKeyQuery.bindValue(2, key->top()); insertKeyQuery.bindValue(3, key->width()); insertKeyQuery.bindValue(4, key->height()); insertKeyQuery.bindValue(6, key->fingerIndex()); insertKeyQuery.bindValue(7, key->hasHapticMarker()); insertKeyQuery.exec(); if (insertKeyQuery.lastError().isValid()) { qWarning() << insertKeyQuery.lastError().text(); raiseError(insertKeyQuery.lastError()); db.rollback(); return false; } idQuery.exec(); if (idQuery.lastError().isValid()) { qWarning() << idQuery.lastError().text(); raiseError(idQuery.lastError()); db.rollback(); return false; } idQuery.next(); const int keyId = idQuery.value(0).toInt(); insertKeyCharQuery.bindValue(0, keyId); for (int j = 0; j < key->keyCharCount(); j++) { KeyChar * const keyChar = key->keyChar(j); insertKeyCharQuery.bindValue(1, keyChar->position()); insertKeyCharQuery.bindValue(2, QString(keyChar->value())); insertKeyCharQuery.bindValue(3, keyChar->modifier()); insertKeyCharQuery.exec(); if (insertKeyCharQuery.lastError().isValid()) { qWarning() << insertKeyCharQuery.lastError().text(); raiseError(insertKeyCharQuery.lastError()); db.rollback(); return false; } } } if (SpecialKey* const specialKey = qobject_cast(abstractKey)) { insertSpecialKeyQuery.bindValue(1, specialKey->left()); insertSpecialKeyQuery.bindValue(2, specialKey->top()); insertSpecialKeyQuery.bindValue(3, specialKey->width()); insertSpecialKeyQuery.bindValue(4, specialKey->height()); insertSpecialKeyQuery.bindValue(6, specialKey->typeStr()); insertSpecialKeyQuery.bindValue(7, specialKey->modifierId()); insertSpecialKeyQuery.bindValue(8, specialKey->label()); insertSpecialKeyQuery.exec(); if (insertSpecialKeyQuery.lastError().isValid()) { qWarning() << insertSpecialKeyQuery.lastError().text(); raiseError(insertSpecialKeyQuery.lastError()); db.rollback(); return false; } } } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return false; } return true; } bool UserDataAccess::deleteKeyboardLayout(KeyboardLayout* keyboardLayout) { QSqlDatabase db = database(); if (!db.isOpen()) return false; if (!db.transaction()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); return false; } QSqlQuery deleteKeyCharsQuery(db); deleteKeyCharsQuery.prepare(QStringLiteral("DELETE FROM keyboard_layout_key_chars WHERE key_id IN (SELECT id FROM keyboard_layout_keys WHERE keyboard_layout_id = ?)")); deleteKeyCharsQuery.bindValue(0, keyboardLayout->id()); deleteKeyCharsQuery.exec(); if (deleteKeyCharsQuery.lastError().isValid()) { qWarning() << deleteKeyCharsQuery.lastError().text(); raiseError(deleteKeyCharsQuery.lastError()); db.rollback(); return false; } QSqlQuery deleteKeysQuery(db); deleteKeysQuery.prepare(QStringLiteral("DELETE FROM keyboard_layout_keys WHERE keyboard_layout_id = ?")); deleteKeysQuery.bindValue(0, keyboardLayout->id()); deleteKeysQuery.exec(); if (deleteKeysQuery.lastError().isValid()) { qWarning() << deleteKeysQuery.lastError().text(); raiseError(deleteKeysQuery.lastError()); db.rollback(); return false; } QSqlQuery deleteKeyboardLayoutQuery(db); deleteKeyboardLayoutQuery.prepare(QStringLiteral("DELETE FROM keyboard_layouts WHERE id = ?")); deleteKeyboardLayoutQuery.bindValue(0, keyboardLayout->id()); deleteKeyboardLayoutQuery.exec(); if (deleteKeyboardLayoutQuery.lastError().isValid()) { qWarning() << deleteKeyboardLayoutQuery.lastError().text(); raiseError(deleteKeyboardLayoutQuery.lastError()); db.rollback(); return false; } if(!db.commit()) { qWarning() << db.lastError().text(); raiseError(db.lastError()); db.rollback(); return false; } return true; } diff --git a/src/declarativeitems/kcolorschemeproxy.cpp b/src/declarativeitems/kcolorschemeproxy.cpp new file mode 100644 index 0000000..3bef69e --- /dev/null +++ b/src/declarativeitems/kcolorschemeproxy.cpp @@ -0,0 +1,192 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "kcolorschemeproxy.h" + +#include + +KColorSchemeProxy::KColorSchemeProxy(QObject* parent) : + QObject(parent), + m_colorGroup(QPalette::Active), + m_colorSet(KColorScheme::View), + m_colorScheme(m_colorGroup, m_colorSet) +{ + auto app = qobject_cast(QCoreApplication::instance()); + if (app) + { + connect(app, SIGNAL(paletteChanged(QPalette)), SIGNAL(paletteChanged())); + } +} + +KColorSchemeProxy::ColorGroup KColorSchemeProxy::colorGroup() const +{ + return static_cast(m_colorGroup); +} + +void KColorSchemeProxy::setColorGroup(ColorGroup group) +{ + auto newColorGroup = static_cast(group); + if (newColorGroup != m_colorGroup) + { + m_colorGroup = newColorGroup; + emit colorGroupChanged(); + m_colorScheme = KColorScheme(m_colorGroup, m_colorSet); + emit paletteChanged(); + } +} + +KColorSchemeProxy::ColorSet KColorSchemeProxy::colorSet() const +{ + return static_cast(m_colorSet); +} + +void KColorSchemeProxy::setColorSet(ColorSet colorSet) +{ + auto newColorSet = static_cast(colorSet); + if (newColorSet != m_colorSet) + { + m_colorSet = newColorSet; + emit colorSetChanged(); + m_colorScheme = KColorScheme(m_colorGroup, m_colorSet); + emit paletteChanged(); + } +} + +QColor KColorSchemeProxy::normalBackground() const +{ + return m_colorScheme.background(KColorScheme::NormalBackground).color(); +} + +QColor KColorSchemeProxy::alternateBackground() const +{ + return m_colorScheme.background(KColorScheme::AlternateBackground).color(); +} + +QColor KColorSchemeProxy::activeBackground() const +{ + return m_colorScheme.background(KColorScheme::ActiveBackground).color(); +} + +QColor KColorSchemeProxy::linkBackground() const +{ + return m_colorScheme.background(KColorScheme::LinkBackground).color(); +} + +QColor KColorSchemeProxy::visitedBackground() const +{ + return m_colorScheme.background(KColorScheme::VisitedBackground).color(); +} + +QColor KColorSchemeProxy::negativeBackground() const +{ + return m_colorScheme.background(KColorScheme::NegativeBackground).color(); +} + +QColor KColorSchemeProxy::neutralBackground() const +{ + return m_colorScheme.background(KColorScheme::NeutralBackground).color(); +} + +QColor KColorSchemeProxy::positiveBackground() const +{ + return m_colorScheme.background(KColorScheme::PositiveBackground).color(); +} + +QColor KColorSchemeProxy::focusDecoration() const +{ + return m_colorScheme.decoration(KColorScheme::FocusColor).color(); +} + +QColor KColorSchemeProxy::hoverDecoration() const +{ + return m_colorScheme.decoration(KColorScheme::HoverColor).color(); +} + +QColor KColorSchemeProxy::normalText() const +{ + return m_colorScheme.foreground(KColorScheme::NormalText).color(); +} + +QColor KColorSchemeProxy::activeText() const +{ + return m_colorScheme.foreground(KColorScheme::ActiveText).color(); +} + +QColor KColorSchemeProxy::linkText() const +{ + return m_colorScheme.foreground(KColorScheme::LinkText).color(); +} + +QColor KColorSchemeProxy::visitedText() const +{ + return m_colorScheme.foreground(KColorScheme::VisitedText).color(); +} + +QColor KColorSchemeProxy::negativeText() const +{ + return m_colorScheme.foreground(KColorScheme::NegativeText).color(); +} + +QColor KColorSchemeProxy::neutralText() const +{ + return m_colorScheme.foreground(KColorScheme::NeutralText).color(); +} + +QColor KColorSchemeProxy::positiveText() const +{ + return m_colorScheme.foreground(KColorScheme::PositiveText).color(); +} + +QColor KColorSchemeProxy::lightShade() const +{ + return m_colorScheme.shade(KColorScheme::LightShade); +} + +QColor KColorSchemeProxy::midlightShade() const +{ + return m_colorScheme.shade(KColorScheme::MidlightShade); +} + +QColor KColorSchemeProxy::midShade() const +{ + return m_colorScheme.shade(KColorScheme::MidShade); +} + +QColor KColorSchemeProxy::darkShade() const +{ + return m_colorScheme.shade(KColorScheme::DarkShade); +} + +QColor KColorSchemeProxy::shadowShade() const +{ + return m_colorScheme.shade(KColorScheme::ShadowShade); +} + +qreal KColorSchemeProxy::contrast() const +{ + return KColorScheme::contrastF(); +} + +QColor KColorSchemeProxy::shade(const QColor& color, ShadeRole role) const +{ + return KColorScheme::shade(color, static_cast(role)); +} + +QColor KColorSchemeProxy::shade(const QColor& color, ShadeRole role, qreal contrast, qreal chromaAdjust) const +{ + return KColorScheme::shade(color, static_cast(role), contrast, chromaAdjust); +} diff --git a/src/declarativeitems/kcolorschemeproxy.h b/src/declarativeitems/kcolorschemeproxy.h new file mode 100644 index 0000000..98d48fa --- /dev/null +++ b/src/declarativeitems/kcolorschemeproxy.h @@ -0,0 +1,135 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KCOLORSCHEMEPROXY_H +#define KCOLORSCHEMEPROXY_H + +#include +#include +#include + +class KColorSchemeProxy : public QObject +{ + Q_OBJECT + Q_ENUMS(ColorGroup) + Q_ENUMS(ColorSet) + Q_ENUMS(ShadeRole) + + Q_PROPERTY(ColorGroup colorGroup READ colorGroup WRITE setColorGroup NOTIFY colorGroupChanged) + Q_PROPERTY(ColorSet colorSet READ colorSet WRITE setColorSet NOTIFY colorSetChanged) + + Q_PROPERTY(QColor normalBackground READ normalBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor alternateBackground READ alternateBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor activeBackground READ activeBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor linkBackground READ linkBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor visitedBackground READ visitedBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor negativeBackground READ negativeBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor neutralBackground READ neutralBackground NOTIFY paletteChanged) + Q_PROPERTY(QColor positiveBackground READ positiveBackground NOTIFY paletteChanged) + + Q_PROPERTY(QColor focusDecoration READ focusDecoration NOTIFY paletteChanged) + Q_PROPERTY(QColor hoverDecoration READ hoverDecoration NOTIFY paletteChanged) + + Q_PROPERTY(QColor normalText READ normalText NOTIFY paletteChanged) + Q_PROPERTY(QColor activeText READ activeText NOTIFY paletteChanged) + Q_PROPERTY(QColor linkText READ linkText NOTIFY paletteChanged) + Q_PROPERTY(QColor visitedText READ visitedText NOTIFY paletteChanged) + Q_PROPERTY(QColor negativeText READ negativeText NOTIFY paletteChanged) + Q_PROPERTY(QColor neutralText READ neutralText NOTIFY paletteChanged) + Q_PROPERTY(QColor positiveText READ positiveText NOTIFY paletteChanged) + + Q_PROPERTY(QColor lightShade READ lightShade NOTIFY paletteChanged) + Q_PROPERTY(QColor midlightShade READ midlightShade NOTIFY paletteChanged) + Q_PROPERTY(QColor midShade READ midShade NOTIFY paletteChanged) + Q_PROPERTY(QColor darkShade READ darkShade NOTIFY paletteChanged) + Q_PROPERTY(QColor shadowShade READ shadowShade NOTIFY paletteChanged) + + Q_PROPERTY(qreal contrast READ contrast NOTIFY paletteChanged) + +public: + enum ColorGroup { Active, Disabled, Inactive, NColorGroups, Current, All, Normal = Active }; + + enum ColorSet { + View, + Window, + Button, + Selection, + Tooltip, + Complementary + }; + + enum ShadeRole { + LightShade, + MidlightShade, + MidShade, + DarkShade, + ShadowShade + }; + + explicit KColorSchemeProxy(QObject* parent = 0); + + ColorGroup colorGroup() const; + void setColorGroup(ColorGroup group); + + + ColorSet colorSet() const; + void setColorSet(ColorSet colorSet); + + QColor normalBackground() const; + QColor alternateBackground() const; + QColor activeBackground() const; + QColor linkBackground() const; + QColor visitedBackground() const; + QColor negativeBackground() const; + QColor neutralBackground() const; + QColor positiveBackground() const; + + QColor focusDecoration() const; + QColor hoverDecoration() const; + + QColor normalText() const; + QColor inactiveText() const; + QColor activeText() const; + QColor linkText() const; + QColor visitedText() const; + QColor negativeText() const; + QColor neutralText() const; + QColor positiveText() const; + + QColor lightShade() const; + QColor midlightShade() const; + QColor midShade() const; + QColor darkShade() const; + QColor shadowShade() const; + + qreal contrast() const; + + Q_INVOKABLE QColor shade(const QColor &color, ShadeRole role) const; + Q_INVOKABLE QColor shade(const QColor &color, ShadeRole role, qreal contrast, qreal chromaAdjust = 0.0) const; + +signals: + void colorGroupChanged(); + void colorSetChanged(); + void paletteChanged(); + +private: + QPalette::ColorGroup m_colorGroup; + KColorScheme::ColorSet m_colorSet; + KColorScheme m_colorScheme; +}; + +#endif // KCOLORSCHEMEPROXY_H diff --git a/src/declarativeitems/lessonpainter.cpp b/src/declarativeitems/lessonpainter.cpp index ac1e549..1c8dd87 100644 --- a/src/declarativeitems/lessonpainter.cpp +++ b/src/declarativeitems/lessonpainter.cpp @@ -1,359 +1,371 @@ /* * Copyright 2013 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "lessonpainter.h" #include #include #include #include #include #include #include #include "core/lesson.h" #include "declarativeitems/traininglinecore.h" struct LessonPainterPrivate { LessonPainterPrivate() { blockFormat.setLineHeight(200, QTextBlockFormat::ProportionalHeight); textCharFormat.setFontFamily(QStringLiteral("monospace")); textCharFormat.setFontPointSize(10); textCharFormat.setForeground(QColor("#000")); textCharFormat.setFontHintingPreference(QFont::PreferVerticalHinting); placeHolderCharFormat = textCharFormat; placeHolderCharFormat.setForeground(QColor("#888")); errorCharFormat = textCharFormat; errorCharFormat.setUnderlineStyle(QTextCharFormat::SingleUnderline); errorCharFormat.setUnderlineColor(QColor("#f00")); preeditCharFromat = textCharFormat; preeditCharFromat.setBackground(QColor("#d0d0d0")); titleCharFormat = textCharFormat; titleCharFormat.setFontFamily(QStringLiteral("sans-serif")); titleCharFormat.setFontPointSize(15); } QTextBlockFormat blockFormat; QTextCharFormat textCharFormat; QTextCharFormat placeHolderCharFormat; QTextCharFormat errorCharFormat; QTextCharFormat preeditCharFromat; QTextCharFormat titleCharFormat; }; LessonPainter::LessonPainter(QQuickItem* parent) : QQuickPaintedItem(parent), d(new LessonPainterPrivate()), m_doc(new QTextDocument(this)), m_textScale(1.0), m_maximumWidth(0), m_maximumHeight(-1), m_imageCacheDirty(false), m_trainingLineCore(0), m_currentLine(0) { this->setFlag(QQuickPaintedItem::ItemHasContents, true); m_doc->setUseDesignMetrics(true); } LessonPainter::~LessonPainter() { delete d; } Lesson* LessonPainter::lesson() const { return m_lesson; } void LessonPainter::setLesson(Lesson* lesson) { if (lesson != m_lesson) { if (m_lesson) { m_lesson->disconnect(this); } m_lesson = lesson; if (m_lesson) { connect(m_lesson.data(), &Lesson::titleChanged, this, &LessonPainter::reset); connect(m_lesson.data(), &Lesson::textChanged, this, &LessonPainter::reset); } reset(); emit lessonChanged(); } } qreal LessonPainter::maximumWidth() const { return m_maximumWidth; } void LessonPainter::setMaximumWidth(qreal maximumWidth) { if (maximumWidth != m_maximumWidth) { m_maximumWidth = maximumWidth; emit maximumWidthChanged(); updateLayout(); } } qreal LessonPainter::maximumHeight() const { return m_maximumHeight; } void LessonPainter::setMaximumHeight(qreal maximumHeight) { if (maximumHeight != m_maximumHeight) { m_maximumHeight = maximumHeight; emit maximumHeightChanged(); updateLayout(); } } TrainingLineCore* LessonPainter::trainingLineCore() const { return m_trainingLineCore; } void LessonPainter::setTrainingLineCore(TrainingLineCore* trainingLineCore) { if (trainingLineCore != m_trainingLineCore) { if (m_trainingLineCore) { m_trainingLineCore->disconnect(this); } m_trainingLineCore = trainingLineCore; emit trainingLineCoreChanged(); if (m_trainingLineCore) { connect(m_trainingLineCore, &TrainingLineCore::actualLineChanged, this, &LessonPainter::updateTrainingStatus); connect(m_trainingLineCore, &TrainingLineCore::preeditStringChanged, this, &LessonPainter::updateTrainingStatus); connect(m_trainingLineCore, &TrainingLineCore::done, this, &LessonPainter::advanceToNextTrainingLine); } } } QRectF LessonPainter::cursorRectangle() const { return m_cursorRectangle; } void LessonPainter::reset() { m_lines = m_lesson? m_lesson->text().split('\n'): QStringList(); updateDoc(); resetTrainingStatus(); } void LessonPainter::paint(QPainter* painter) { - checkImageCache(); - painter->drawPixmap(0, 0, m_imageCache); + if (m_imageCacheDirty) + { + const auto device = painter->device(); + QImage img(QSize(qFloor(device->width()), qFloor(device->height())), QImage::Format_ARGB32_Premultiplied); + img.setDevicePixelRatio(img.width() / width()); + img.fill(Qt::transparent); + QPainter painter(&img); + painter.scale(m_textScale, m_textScale); + m_doc->drawContents(&painter); + m_imageCache = img; + m_imageCacheDirty = false; + } + + painter->drawImage(0, 0, m_imageCache); } void LessonPainter::updateLayout() { invalidateImageCache(); if (!m_lesson) { setWidth(0); setHeight(0); return; } // ### reset text width from previous run m_doc->setTextWidth(-1); const qreal docWidth = m_doc->idealWidth(); const qreal docHeight = m_doc->size().height(); m_textScale = m_maximumHeight != -1? qMin(m_maximumWidth / docWidth, m_maximumHeight / docHeight): m_maximumWidth / docWidth; // ### without this text alignment won't work m_doc->setTextWidth(docWidth); setWidth(qCeil(docWidth * m_textScale)); setHeight(qCeil(docHeight * m_textScale)); updateCursorRectangle(); } void LessonPainter::resetTrainingStatus() { if (!m_trainingLineCore || m_lines.length() == 0) return; m_trainingLineCore->reset(); m_currentLine = 0; m_trainingLineCore->setReferenceLine(m_lines[0]); } void LessonPainter::updateTrainingStatus() { if (m_currentLine >= m_lines.length()) return; QTextCursor cursor(m_doc); const QString referenceLine = m_trainingLineCore->referenceLine(); const QString actualLine = m_trainingLineCore->actualLine(); const QString preeditString = m_trainingLineCore->preeditString(); const QTextBlock block = m_doc->findBlockByNumber(m_currentLine + 1); const int blockPosition = block.position(); for (int linePos = 0; linePos < referenceLine.length(); linePos++) { const bool typed = linePos < actualLine.length(); const bool preedit = !typed && linePos - actualLine.length() < preeditString.length(); const bool correct = typed && actualLine.at(linePos) == referenceLine.at(linePos); const QTextCharFormat charFormat = typed? (correct? d->textCharFormat: d->errorCharFormat): (preedit? d->preeditCharFromat: d->placeHolderCharFormat); const QChar displayedChar = typed? actualLine.at(linePos): preedit? preeditString.at(linePos - actualLine.length()): referenceLine.at(linePos); const int charPosition = blockPosition + linePos; cursor.setPosition(charPosition, QTextCursor::MoveAnchor); cursor.setPosition(charPosition + 1, QTextCursor::KeepAnchor); cursor.deleteChar(); cursor.insertText(QString(displayedChar), charFormat); } invalidateImageCache(); updateCursorRectangle(); update(); } void LessonPainter::advanceToNextTrainingLine() { m_currentLine++; if (m_currentLine < m_lines.length()) { m_trainingLineCore->setReferenceLine(m_lines.at(m_currentLine)); } else { m_trainingLineCore->setReferenceLine(QString()); emit done(); } } void LessonPainter::updateDoc() { m_doc->clear(); if (!m_lesson) { updateLayout(); return; } m_doc->setDocumentMargin(20.0); QTextCursor cursor(m_doc); QTextBlockFormat blockFormat = d->blockFormat; const QString lessonTitle = m_lesson->title(); blockFormat.setAlignment(lessonTitle.isRightToLeft()? Qt::AlignRight: Qt::AlignLeft); cursor.setBlockFormat(blockFormat); cursor.setBlockCharFormat(d->titleCharFormat); cursor.insertText(lessonTitle); const QTextCharFormat textFormat = m_trainingLineCore? d->placeHolderCharFormat: d->textCharFormat; foreach (const QString& line, m_lines) { blockFormat.setAlignment(line.isRightToLeft()? Qt::AlignRight: Qt::AlignLeft); cursor.insertBlock(d->blockFormat, textFormat); cursor.insertText(line); } updateLayout(); } void LessonPainter::invalidateImageCache() { m_imageCacheDirty = true; - m_imageCache = QPixmap(); + m_imageCache = QImage(); } void LessonPainter::checkImageCache() { if (!m_imageCacheDirty) return; - QPixmap img(qFloor(width()), qFloor(height())); + QImage img(QSize(qFloor(width()), qFloor(height())), QImage::Format_ARGB32_Premultiplied); img.fill(Qt::transparent); QPainter painter(&img); painter.scale(m_textScale, m_textScale); m_doc->drawContents(&painter); m_imageCache = img; m_imageCacheDirty = false; } void LessonPainter::updateCursorRectangle() { if (!m_trainingLineCore || m_lines.length() == 0 || m_currentLine >= m_lines.length()) return; const QString actualLine = m_trainingLineCore->actualLine(); const QString preeditString = m_trainingLineCore->preeditString(); const QTextBlock block = m_doc->findBlockByNumber(m_currentLine + 1); const QAbstractTextDocumentLayout* docLayout = m_doc->documentLayout(); const QTextLayout* layout = block.layout(); const QPointF blockPos = docLayout->blockBoundingRect(block).topLeft(); const int relCursorPos = actualLine.length() + preeditString.length(); const QTextLine line = layout->lineForTextPosition(relCursorPos); m_cursorRectangle = QRectF( m_textScale * (blockPos.x() + line.cursorToX(relCursorPos)), m_textScale * (blockPos.y() + line.y()), 1, m_textScale * (line.height())); emit cursorRectangleChanged(); } diff --git a/src/declarativeitems/lessonpainter.h b/src/declarativeitems/lessonpainter.h index 6c0bf5e..cd60d9b 100644 --- a/src/declarativeitems/lessonpainter.h +++ b/src/declarativeitems/lessonpainter.h @@ -1,88 +1,88 @@ /* * Copyright 2013 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LESSONPAINTER_H #define LESSONPAINTER_H #include -#include +#include #include class QTextDocument; class Lesson; class TrainingLineCore; struct LessonPainterPrivate; class LessonPainter : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(Lesson* lesson READ lesson WRITE setLesson NOTIFY lessonChanged) Q_PROPERTY(qreal maximumWidth READ maximumWidth WRITE setMaximumWidth NOTIFY maximumWidthChanged) Q_PROPERTY(qreal maximumHeight READ maximumHeight WRITE setMaximumHeight NOTIFY maximumHeightChanged) Q_PROPERTY(TrainingLineCore* trainingLineCore READ trainingLineCore WRITE setTrainingLineCore NOTIFY trainingLineCoreChanged) Q_PROPERTY(QRectF cursorRectangle READ cursorRectangle NOTIFY cursorRectangleChanged) public: explicit LessonPainter(QQuickItem* parent = 0); ~LessonPainter(); Lesson* lesson() const; void setLesson(Lesson* lesson); qreal maximumWidth() const; void setMaximumWidth(qreal maximumWidth); qreal maximumHeight() const; void setMaximumHeight(qreal maximumHeight); TrainingLineCore* trainingLineCore() const; void setTrainingLineCore(TrainingLineCore* trainingLineCore); QRectF cursorRectangle() const; public slots: void reset(); signals: void lessonChanged(); void maximumWidthChanged(); void maximumHeightChanged(); void trainingLineCoreChanged(); void cursorRectangleChanged(); void done(); protected: void paint(QPainter* painter) override; private slots: void updateLayout(); void resetTrainingStatus(); void updateTrainingStatus(); void advanceToNextTrainingLine(); private: void updateDoc(); void invalidateImageCache(); void checkImageCache(); void updateCursorRectangle(); LessonPainterPrivate* d; QPointer m_lesson; QStringList m_lines; QTextDocument* m_doc; qreal m_textScale; qreal m_maximumWidth; qreal m_maximumHeight; - QPixmap m_imageCache; + QImage m_imageCache; bool m_imageCacheDirty; TrainingLineCore* m_trainingLineCore; int m_currentLine; QPointer m_cursorItem; QRectF m_cursorRectangle; }; #endif // LESSONPAINTER_H diff --git a/src/declarativeitems/lessontexthighlighteritem.cpp b/src/declarativeitems/lessontexthighlighteritem.cpp new file mode 100644 index 0000000..e8d46f5 --- /dev/null +++ b/src/declarativeitems/lessontexthighlighteritem.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "lessontexthighlighteritem.h" + +#include + +#include "editor/lessontexthighlighter.h" + +LessonTextHighlighterItem::LessonTextHighlighterItem(): + m_highligher(new LessonTextHighlighter(this)) +{ +} + +QString LessonTextHighlighterItem::allowedCharacters() const +{ + return m_highligher->allowedCharacters(); +} + +void LessonTextHighlighterItem::setAllowedCharacters(const QString& characters) +{ + if (characters != m_highligher->allowedCharacters()) + { + m_highligher->setAllowedCharacters(characters); + emit allowedCharactersChanged(); + } +} + +QQuickTextDocument* LessonTextHighlighterItem::document() const +{ + return m_document; +} + +void LessonTextHighlighterItem::setDocument(QQuickTextDocument* document) +{ + if (document != m_document) + { + m_document = document; + if (document != nullptr) + { + m_highligher->setDocument(document->textDocument()); + } + else + { + m_highligher->setDocument(nullptr); + } + emit documentChanged(); + } +} diff --git a/src/declarativeitems/lessontexthighlighteritem.h b/src/declarativeitems/lessontexthighlighteritem.h new file mode 100644 index 0000000..9a6a4c1 --- /dev/null +++ b/src/declarativeitems/lessontexthighlighteritem.h @@ -0,0 +1,45 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef LESSONTEXTHIGHLIGHTERITEM_H +#define LESSONTEXTHIGHLIGHTERITEM_H + +#include + +class LessonTextHighlighter; +class QQuickTextDocument; + +class LessonTextHighlighterItem : public QQuickItem +{ + Q_OBJECT + Q_PROPERTY(QString allowedCharacters READ allowedCharacters WRITE setAllowedCharacters NOTIFY allowedCharactersChanged) + Q_PROPERTY(QQuickTextDocument* document READ document WRITE setDocument NOTIFY documentChanged) +public: + LessonTextHighlighterItem(); + QString allowedCharacters() const; + void setAllowedCharacters(const QString& characters); + QQuickTextDocument* document() const; + void setDocument(QQuickTextDocument* document); +signals: + void allowedCharactersChanged(); + void documentChanged(); +private: + LessonTextHighlighter* m_highligher; + QQuickTextDocument* m_document; +}; + +#endif // LESSONTEXTHIGHLIGHTERITEM_H diff --git a/src/editor/charactersviewdelegate.h b/src/editor/charactersviewdelegate.h index fe23a79..c5a9da9 100644 --- a/src/editor/charactersviewdelegate.h +++ b/src/editor/charactersviewdelegate.h @@ -1,39 +1,39 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CHARACTERSVIEWDELEGATE_H #define CHARACTERSVIEWDELEGATE_H #include class KeyboardLayout; class CharactersViewDelegate : public QStyledItemDelegate { Q_OBJECT public: - explicit CharactersViewDelegate(QObject* parent = 0); + explicit CharactersViewDelegate(QObject* parent = nullptr); KeyboardLayout* keyboardLayout() const; void setKeyboardLayout(KeyboardLayout* keyboardLayout); - QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; + QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget *editor, QAbstractItemModel* model, const QModelIndex& index) const override; private: KeyboardLayout* m_keyboardLayout; }; #endif // CHARACTERSVIEWDELEGATE_H diff --git a/src/editor/keyboardlayouteditor.cpp b/src/editor/keyboardlayouteditor.cpp index 7dd0445..71ddb82 100644 --- a/src/editor/keyboardlayouteditor.cpp +++ b/src/editor/keyboardlayouteditor.cpp @@ -1,245 +1,245 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "keyboardlayouteditor.h" #include #include #include #include #include #include #include "core/dataindex.h" #include "core/dataaccess.h" #include "core/keyboardlayout.h" #include "core/abstractkey.h" #include "core/key.h" #include "core/keychar.h" #include "core/userdataaccess.h" #include "undocommands/keyboardlayoutcommands.h" #include "application.h" KeyboardLayoutEditor::KeyboardLayoutEditor(QWidget* parent): AbstractEditor(parent), m_dataIndexKeyboardLayout(0), m_keyboardLayout(new KeyboardLayout(this)), m_readOnly(false), m_selectedKey(0), m_zoomLevel(0) { setupUi(this); m_messageWidget->hide(); m_propertiesWidget->setKeyboardLayout(m_keyboardLayout); Application::setupDeclarativeBindings(m_view->engine()); m_view->rootContext()->setContextProperty(QStringLiteral("keyboardLayoutEditor"), this); - m_view->setSource(QUrl(QStringLiteral("qrc:/qml/keyboard/KeyboardLayoutEditor.qml"))); + m_view->setSource(QUrl(QStringLiteral("qrc:/ktouch/qml/keyboard/KeyboardLayoutEditor.qml"))); connect(m_newKeyToolButton, &QAbstractButton::clicked, this, &KeyboardLayoutEditor::createNewKey); connect(m_newSpecialKeyToolButton, &QAbstractButton::clicked, this, &KeyboardLayoutEditor::createNewSpecialKey); connect(m_zoomSlider, &QAbstractSlider::valueChanged, this, &KeyboardLayoutEditor::setZoomLevel); connect(m_deleteKeyToolButton, &QAbstractButton::clicked, this, &KeyboardLayoutEditor::deleteSelectedKey); connect(m_view, &KeyboardLayoutEditorView::clicked, this, &KeyboardLayoutEditor::clearSelection); connect(m_zoomOutToolButton, &QToolButton::clicked, [=](){ m_zoomSlider->setValue(m_zoomSlider->value() - 1); }); connect(m_zoomInToolButton, &QToolButton::clicked, [=](){ m_zoomSlider->setValue(m_zoomSlider->value() + 1); }); } KeyboardLayoutEditor::~KeyboardLayoutEditor() { m_view->setSource(QUrl()); delete m_view; m_view = 0; } void KeyboardLayoutEditor::openKeyboardLayout(DataIndexKeyboardLayout* dataIndexKeyboardLayout) { DataAccess dataAccess; m_dataIndexKeyboardLayout = dataIndexKeyboardLayout; if (currentUndoStack()) { currentUndoStack()->disconnect(this, SLOT(validateSelection())); } initUndoStack(QStringLiteral("keyboard-layout-%1").arg(dataIndexKeyboardLayout->id())); m_propertiesWidget->setUndoStack(currentUndoStack()); setSelectedKey(0); connect(currentUndoStack(), &QUndoStack::indexChanged, this, &KeyboardLayoutEditor::validateSelection); m_keyboardLayout->setAssociatedDataIndexKeyboardLayout(m_dataIndexKeyboardLayout); if (!dataAccess.loadKeyboardLayout(dataIndexKeyboardLayout, m_keyboardLayout)) { KMessageBox::error(this, i18n("Error while opening keyboard layout")); } if (dataIndexKeyboardLayout->source() == DataIndex::BuiltInResource) { setReadOnly(true); m_messageWidget->setMessageType(KMessageWidget::Information); m_messageWidget->setText(i18n("Built-in keyboard layouts can only be viewed.")); m_messageWidget->setCloseButtonVisible(false); m_messageWidget->animatedShow(); } else { setReadOnly(false); m_messageWidget->animatedHide(); } } void KeyboardLayoutEditor::clearUndoStackForKeyboardLayout(DataIndexKeyboardLayout* dataIndexKeyboardLayout) { clearUndoStack(dataIndexKeyboardLayout->path()); } void KeyboardLayoutEditor::save() { if (!m_keyboardLayout || !m_keyboardLayout->isValid()) return; if (currentUndoStack()->isClean()) return; UserDataAccess userDataAccess; userDataAccess.storeKeyboardLayout(m_keyboardLayout); currentUndoStack()->setClean(); } KeyboardLayout* KeyboardLayoutEditor::keyboardLayout() const { return m_keyboardLayout; } bool KeyboardLayoutEditor::readOnly() const { return m_readOnly; } void KeyboardLayoutEditor::setReadOnly(bool readOnly) { if (readOnly != m_readOnly) { m_readOnly = readOnly; emit readOnlyChanged(); m_newKeyToolButton->setEnabled(!readOnly); m_newSpecialKeyToolButton->setEnabled(!readOnly); m_deleteKeyToolButton->setEnabled(!readOnly && m_selectedKey != 0); m_propertiesWidget->setReadOnly(readOnly); } } AbstractKey* KeyboardLayoutEditor::selectedKey() const { return m_selectedKey; } void KeyboardLayoutEditor::setSelectedKey(AbstractKey* key) { if (key != m_selectedKey) { m_selectedKey = key; emit selectedKeyChanged(); m_deleteKeyToolButton->setEnabled(!m_readOnly && m_selectedKey != 0); m_propertiesWidget->setSelectedKey(m_keyboardLayout->keyIndex(key)); } } int KeyboardLayoutEditor::zoomLevel() const { return m_zoomLevel; } void KeyboardLayoutEditor::setZoomLevel(int zoomLevel) { if (zoomLevel != m_zoomLevel) { m_zoomLevel = zoomLevel; emit zoomLevelChanged(); const double zoomFactor = pow(2.0, zoomLevel / 2.0); m_zoomFactorLabel->setText(ki18n("%1%").subs(zoomFactor * 100, 0, 'f', 0).toString()); m_zoomOutToolButton->setEnabled(zoomLevel > m_zoomSlider->minimum()); m_zoomInToolButton->setEnabled(zoomLevel < m_zoomSlider->maximum()); } } void KeyboardLayoutEditor::setKeyGeometry(int keyIndex, int top, int left, int width, int height) { QRect rect(top, left, width, height); if (rect != keyboardLayout()->key(keyIndex)->rect()) { QUndoCommand* command = new SetKeyGeometryCommand(keyboardLayout(), keyIndex, rect); currentUndoStack()->push(command); } } void KeyboardLayoutEditor::clearSelection() { setSelectedKey(0); } void KeyboardLayoutEditor::validateSelection() { if (m_keyboardLayout->keyIndex(m_selectedKey) == -1) { clearSelection(); } } void KeyboardLayoutEditor::createNewKey() { Key* key = new Key(); key->setRect(QRect(0, 0, 80, 80)); QUndoCommand* command = new AddKeyCommand(m_keyboardLayout, key); currentUndoStack()->push(command); setSelectedKey(key); } void KeyboardLayoutEditor::createNewSpecialKey() { SpecialKey* key = new SpecialKey(); key->setRect(QRect(0, 0, 130, 80)); QUndoCommand* command = new AddKeyCommand(m_keyboardLayout, key); currentUndoStack()->push(command); setSelectedKey(key); } void KeyboardLayoutEditor::deleteSelectedKey() { Q_ASSERT(m_selectedKey); const int keyIndex = m_keyboardLayout->keyIndex(m_selectedKey); QUndoCommand* command = new RemoveKeyCommand(m_keyboardLayout, keyIndex); setSelectedKey(0); currentUndoStack()->push(command); } diff --git a/src/editor/keyboardlayouteditorview.h b/src/editor/keyboardlayouteditorview.h index e0a41fd..a6db8ec 100644 --- a/src/editor/keyboardlayouteditorview.h +++ b/src/editor/keyboardlayouteditorview.h @@ -1,34 +1,34 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KEYBOARDLAYOUTEDITORVIEW_H #define KEYBOARDLAYOUTEDITORVIEW_H #include class KeyboardLayoutEditorView : public QQuickWidget { Q_OBJECT public: - explicit KeyboardLayoutEditorView(QWidget* parent = 0); + explicit KeyboardLayoutEditorView(QWidget* parent = nullptr); signals: void clicked(); protected: void mousePressEvent(QMouseEvent* event) override; }; #endif // KEYBOARDLAYOUTEDITORVIEW_H diff --git a/src/editor/lessontexthighlighter.cpp b/src/editor/lessontexthighlighter.cpp index c054c9a..90ee31e 100644 --- a/src/editor/lessontexthighlighter.cpp +++ b/src/editor/lessontexthighlighter.cpp @@ -1,83 +1,84 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "lessontexthighlighter.h" #include LessonTextHighlighter::LessonTextHighlighter(QObject* parent): - QSyntaxHighlighter(parent) + QSyntaxHighlighter(parent), + m_maximumLineLength(60) { KColorScheme inactiveScheme(QPalette::Inactive, KColorScheme::View); KColorScheme activeScheme(QPalette::Active, KColorScheme::View); m_overLongLineFormat.setForeground(inactiveScheme.foreground(KColorScheme::InactiveText)); m_overLongLineFormat.setBackground(inactiveScheme.background(KColorScheme::NegativeBackground)); m_invalidCharFormat.setForeground(activeScheme.background(KColorScheme::NeutralBackground)); m_invalidCharFormat.setBackground(activeScheme.foreground(KColorScheme::NegativeText)); m_invalidCharFormat.setFontWeight(QFont::Bold); } QString LessonTextHighlighter::allowedCharacters() const { return m_allowedCharacters; } void LessonTextHighlighter::setAllowedCharacters(const QString& characters) { if (characters != m_allowedCharacters) { m_allowedCharacters = characters; rehighlight(); }; } int LessonTextHighlighter::maximumLineLength() const { return m_maximumLineLength; } void LessonTextHighlighter::setMaximumLineLength(int length) { if (length != m_maximumLineLength) { m_maximumLineLength = length; rehighlight(); } } void LessonTextHighlighter::highlightBlock(const QString& text) { const QLatin1Char space(32); - if (text.length() > 60) + if (text.length() > m_maximumLineLength) { - setFormat(60, text.length() - 60, m_overLongLineFormat); + setFormat(m_maximumLineLength, text.length() - m_maximumLineLength, m_overLongLineFormat); } if (m_allowedCharacters.isNull()) return; for (int i = 0; i < text.length(); i++) { const QChar c = text.at(i); if (c != space && !m_allowedCharacters.contains(c)) { setFormat(i, 1, m_invalidCharFormat); } } } diff --git a/src/editor/lessontexthighlighter.h b/src/editor/lessontexthighlighter.h index 57d6b56..adeb336 100644 --- a/src/editor/lessontexthighlighter.h +++ b/src/editor/lessontexthighlighter.h @@ -1,45 +1,45 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LESSONTEXTHIGHLIGHTER_H #define LESSONTEXTHIGHLIGHTER_H #include #include class LessonTextHighlighter : public QSyntaxHighlighter { Q_OBJECT public: - explicit LessonTextHighlighter(QObject* parent = 0); + explicit LessonTextHighlighter(QObject* parent = nullptr); int maximumLineLength() const; void setMaximumLineLength(int length); QString allowedCharacters() const; void setAllowedCharacters(const QString& characters); protected: void highlightBlock(const QString& text) override; private: int m_maximumLineLength; QString m_allowedCharacters; QTextCharFormat m_overLongLineFormat; QTextCharFormat m_invalidCharFormat; }; #endif // LESSONTEXTHIGHLIGHTER_H diff --git a/images/accuracymeter-background.png b/src/images/accuracymeter-background.png similarity index 100% rename from images/accuracymeter-background.png rename to src/images/accuracymeter-background.png diff --git a/src/images/accuracymeter-background@2x.png b/src/images/accuracymeter-background@2x.png new file mode 100644 index 0000000..3532577 Binary files /dev/null and b/src/images/accuracymeter-background@2x.png differ diff --git a/images/accuracymeter-hand.png b/src/images/accuracymeter-hand.png similarity index 100% rename from images/accuracymeter-hand.png rename to src/images/accuracymeter-hand.png diff --git a/src/images/accuracymeter-hand@2x.png b/src/images/accuracymeter-hand@2x.png new file mode 100644 index 0000000..1d8f497 Binary files /dev/null and b/src/images/accuracymeter-hand@2x.png differ diff --git a/images/accuracymeter-scale.png b/src/images/accuracymeter-scale.png similarity index 100% rename from images/accuracymeter-scale.png rename to src/images/accuracymeter-scale.png diff --git a/src/images/accuracymeter-scale@2x.png b/src/images/accuracymeter-scale@2x.png new file mode 100644 index 0000000..52663bf Binary files /dev/null and b/src/images/accuracymeter-scale@2x.png differ diff --git a/images/balloontip.svgz b/src/images/balloontip.svgz similarity index 100% rename from images/balloontip.svgz rename to src/images/balloontip.svgz diff --git a/images/charactersperminutemeter-background.png b/src/images/charactersperminutemeter-background.png similarity index 100% rename from images/charactersperminutemeter-background.png rename to src/images/charactersperminutemeter-background.png diff --git a/src/images/charactersperminutemeter-background@2x.png b/src/images/charactersperminutemeter-background@2x.png new file mode 100644 index 0000000..3532577 Binary files /dev/null and b/src/images/charactersperminutemeter-background@2x.png differ diff --git a/images/charactersperminutemeter-hand.png b/src/images/charactersperminutemeter-hand.png similarity index 100% rename from images/charactersperminutemeter-hand.png rename to src/images/charactersperminutemeter-hand.png diff --git a/images/charactersperminutemeter-scale.png b/src/images/charactersperminutemeter-scale.png similarity index 100% rename from images/charactersperminutemeter-scale.png rename to src/images/charactersperminutemeter-scale.png diff --git a/src/images/charactersperminutemeter-scale@2x.png b/src/images/charactersperminutemeter-scale@2x.png new file mode 100644 index 0000000..279c821 Binary files /dev/null and b/src/images/charactersperminutemeter-scale@2x.png differ diff --git a/images/elapsedtimemeter-background.png b/src/images/elapsedtimemeter-background.png similarity index 100% rename from images/elapsedtimemeter-background.png rename to src/images/elapsedtimemeter-background.png diff --git a/src/images/elapsedtimemeter-background@2x.png b/src/images/elapsedtimemeter-background@2x.png new file mode 100644 index 0000000..f892189 Binary files /dev/null and b/src/images/elapsedtimemeter-background@2x.png differ diff --git a/images/elapsedtimemeter-minute-hand.png b/src/images/elapsedtimemeter-minute-hand.png similarity index 100% rename from images/elapsedtimemeter-minute-hand.png rename to src/images/elapsedtimemeter-minute-hand.png diff --git a/src/images/elapsedtimemeter-minute-hand@2x.png b/src/images/elapsedtimemeter-minute-hand@2x.png new file mode 100644 index 0000000..1667335 Binary files /dev/null and b/src/images/elapsedtimemeter-minute-hand@2x.png differ diff --git a/images/elapsedtimemeter-second-hand.png b/src/images/elapsedtimemeter-second-hand.png similarity index 100% rename from images/elapsedtimemeter-second-hand.png rename to src/images/elapsedtimemeter-second-hand.png diff --git a/src/images/elapsedtimemeter-second-hand@2x.png b/src/images/elapsedtimemeter-second-hand@2x.png new file mode 100644 index 0000000..29fa41a Binary files /dev/null and b/src/images/elapsedtimemeter-second-hand@2x.png differ diff --git a/src/images/images.qrc b/src/images/images.qrc new file mode 100644 index 0000000..ca4b6e1 --- /dev/null +++ b/src/images/images.qrc @@ -0,0 +1,33 @@ + + + accuracymeter-background.png + accuracymeter-hand.png + accuracymeter-scale.png + meterbox-left.png + meterbox-right.png + statusled.svgz + trainingscreen-footer.png + trainingscreen-header.png + trainingscreen-toolbar.png + trainingscreen-viewport-shadow.png + trainingscreen-viewport.png + balloontip.svgz + charactersperminutemeter-background.png + charactersperminutemeter-hand.png + charactersperminutemeter-scale.png + elapsedtimemeter-background.png + elapsedtimemeter-background@2x.png + elapsedtimemeter-minute-hand.png + elapsedtimemeter-second-hand.png + elapsedtimemeter-minute-hand@2x.png + elapsedtimemeter-second-hand@2x.png + images.qrc + meterbox-left@2x.png + meterbox-right@2x.png + accuracymeter-background@2x.png + accuracymeter-hand@2x.png + accuracymeter-scale@2x.png + charactersperminutemeter-background@2x.png + charactersperminutemeter-scale@2x.png + + diff --git a/images/meterbox-left.png b/src/images/meterbox-left.png similarity index 100% rename from images/meterbox-left.png rename to src/images/meterbox-left.png diff --git a/src/images/meterbox-left@2x.png b/src/images/meterbox-left@2x.png new file mode 100644 index 0000000..91bf079 Binary files /dev/null and b/src/images/meterbox-left@2x.png differ diff --git a/images/meterbox-right.png b/src/images/meterbox-right.png similarity index 100% rename from images/meterbox-right.png rename to src/images/meterbox-right.png diff --git a/src/images/meterbox-right@2x.png b/src/images/meterbox-right@2x.png new file mode 100644 index 0000000..ba06be2 Binary files /dev/null and b/src/images/meterbox-right@2x.png differ diff --git a/images/statusled.svgz b/src/images/statusled.svgz similarity index 100% rename from images/statusled.svgz rename to src/images/statusled.svgz diff --git a/images/accuracymeter.svgz b/src/images/templates/accuracymeter.svgz similarity index 100% rename from images/accuracymeter.svgz rename to src/images/templates/accuracymeter.svgz diff --git a/src/images/templates/charactersperminutemeter.svgz b/src/images/templates/charactersperminutemeter.svgz new file mode 100644 index 0000000..f8cc1a3 Binary files /dev/null and b/src/images/templates/charactersperminutemeter.svgz differ diff --git a/src/images/templates/elapsedtimemeter.svgz b/src/images/templates/elapsedtimemeter.svgz new file mode 100644 index 0000000..e069460 Binary files /dev/null and b/src/images/templates/elapsedtimemeter.svgz differ diff --git a/src/images/templates/meterbox.svgz b/src/images/templates/meterbox.svgz new file mode 100644 index 0000000..adf0035 Binary files /dev/null and b/src/images/templates/meterbox.svgz differ diff --git a/images/trainingscreen.svgz b/src/images/templates/trainingscreen.svgz similarity index 100% rename from images/trainingscreen.svgz rename to src/images/templates/trainingscreen.svgz diff --git a/images/trainingscreen-footer.png b/src/images/trainingscreen-footer.png similarity index 100% rename from images/trainingscreen-footer.png rename to src/images/trainingscreen-footer.png diff --git a/images/trainingscreen-header.png b/src/images/trainingscreen-header.png similarity index 100% rename from images/trainingscreen-header.png rename to src/images/trainingscreen-header.png diff --git a/images/trainingscreen-toolbar.png b/src/images/trainingscreen-toolbar.png similarity index 100% rename from images/trainingscreen-toolbar.png rename to src/images/trainingscreen-toolbar.png diff --git a/images/trainingscreen-viewport-shadow.png b/src/images/trainingscreen-viewport-shadow.png similarity index 100% rename from images/trainingscreen-viewport-shadow.png rename to src/images/trainingscreen-viewport-shadow.png diff --git a/images/trainingscreen-viewport.png b/src/images/trainingscreen-viewport.png similarity index 100% rename from images/trainingscreen-viewport.png rename to src/images/trainingscreen-viewport.png diff --git a/src/ktouchcontext.cpp b/src/ktouchcontext.cpp index 607848a..4020fef 100644 --- a/src/ktouchcontext.cpp +++ b/src/ktouchcontext.cpp @@ -1,201 +1,212 @@ /* * Copyright 2016 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "ktouchcontext.h" #include #include #include #include #include "application.h" #include "colorsconfigwidget.h" #include "editor/resourceeditor.h" #include "customlessoneditordialog.h" #include "preferences.h" #include "trainingconfigwidget.h" +#include "core/lesson.h" #include #include #include #include #include #include #include #include #ifdef KTOUCH_BUILD_WITH_X11 #include "x11_helper.h" #else #include "keyboardlayoutmenu.h" #endif const QString keyboardKCMName = QStringLiteral("kcm_keyboard"); KTouchContext::KTouchContext(KMainWindow* mainWindow, QQuickView* view, QObject *parent) : QObject(parent), m_actionCollection(new KActionCollection(this)), m_menu(new QMenu(mainWindow)), m_mainWindow(mainWindow), m_view(view) { #ifdef KTOUCH_BUILD_WITH_X11 m_XEventNotifier = new XEventNotifier(); m_XEventNotifier->start(); connect(m_XEventNotifier, &XEventNotifier::layoutChanged, this, &KTouchContext::keyboardLayoutNameChanged); #else m_keyboardLayoutMenu = new KeyboardLayoutMenu(this); m_keyboardLayoutMenu->setDataIndex(Application::dataIndex()); connect(m_keyboardLayoutMenu, SIGNAL(keyboardLayoutNameChanged()), SIGNAL(keyboardLayoutNameChanged())); #endif init(); } KTouchContext::~KTouchContext() { #ifdef KTOUCH_BUILD_WITH_X11 m_XEventNotifier->stop(); delete m_XEventNotifier; #endif } QString KTouchContext::keyboardLayoutName() const { #ifdef KTOUCH_BUILD_WITH_X11 return X11Helper::getCurrentLayout().toString(); #else return m_keyboardLayoutMenu->keyboardLayoutName(); #endif } DataIndex* KTouchContext::dataIndex() { return Application::dataIndex(); } +bool KTouchContext::keyboardKCMAvailable() +{ + return testKCMAvailibility(keyboardKCMName); +} + void KTouchContext::showMenu(int xPos, int yPos) { m_menu->popup(m_view->mapToGlobal(QPoint(xPos, yPos))); } +Lesson* KTouchContext::createLesson() +{ + return new Lesson(); +} + void KTouchContext::showResourceEditor() { QPointer& resourceEditorRef = Application::resourceEditorRef(); if (resourceEditorRef.isNull()) { resourceEditorRef = QPointer(new ResourceEditor()); } ResourceEditor* resourceEditor = resourceEditorRef.data(); resourceEditor->show(); resourceEditor->activateWindow(); } bool KTouchContext::showCustomLessonDialog(Lesson* lesson, KeyboardLayout* keyboardLayout) { CustomLessonEditorDialog* dialog = new CustomLessonEditorDialog(m_mainWindow); dialog->setLesson(lesson); dialog->setKeyboardLayout(keyboardLayout); bool result = dialog->exec() == QDialog::Accepted; delete dialog; return result; } void KTouchContext::showConfigDialog() { if (KConfigDialog::showDialog(QStringLiteral("preferences"))) { return; } KConfigDialog* dialog = new KConfigDialog(m_mainWindow, QStringLiteral("preferences"), Preferences::self()); dialog->setFaceType(KPageDialog::List); dialog->setModal(true); dialog->addPage(new TrainingConfigWidget(), i18n("Training"), QStringLiteral("chronometer"), i18n("Training Settings")); dialog->addPage(new ColorsConfigWidget(), i18n("Colors"), QStringLiteral("preferences-desktop-color"), i18n("Color Settings")); dialog->show(); } void KTouchContext::configureShortcuts() { KShortcutsDialog::configure(m_actionCollection, KShortcutsEditor::LetterShortcutsDisallowed, m_mainWindow); } -void KTouchContext::configureKeyboard() +void KTouchContext::showKeyboardKCM() { QPointer kcm = new KCMultiDialog(m_mainWindow); kcm->setWindowTitle(i18n("Configure Keyboard")); kcm->addModule(keyboardKCMName); kcm->exec(); delete kcm; } void KTouchContext::setFullscreen(bool fullScreen) { KToggleFullScreenAction::setFullScreen(m_mainWindow, fullScreen); } void KTouchContext::init() { m_actionCollection->addAssociatedWidget(m_mainWindow); m_menu->addAction(KStandardAction::fullScreen(this, SLOT(setFullscreen(bool)), m_mainWindow, m_actionCollection)); m_menu->addSeparator(); QAction* editorAction = new QAction(i18n("Course and Keyboard Layout Editor..."), this); connect(editorAction, &QAction::triggered, this, &KTouchContext::showResourceEditor); m_actionCollection->addAction(QStringLiteral("editor"), editorAction); m_menu->addAction(editorAction); m_menu->addSeparator(); m_menu->addAction(KStandardAction::preferences(this, SLOT(showConfigDialog()), m_actionCollection)); m_menu->addAction(KStandardAction::keyBindings(this, SLOT(configureShortcuts()), m_actionCollection)); #ifdef KTOUCH_BUILD_WITH_X11 if (testKCMAvailibility(keyboardKCMName)) { QAction* configureKeyboardAction = new QAction(i18n("Configure Keyboard..."), this); m_menu->addAction(configureKeyboardAction); - connect(configureKeyboardAction, &QAction::triggered, this, &KTouchContext::configureKeyboard); + connect(configureKeyboardAction, &QAction::triggered, this, &KTouchContext::showKeyboardKCM); } #else m_menu->addMenu(m_keyboardLayoutMenu); #endif m_menu->addSeparator(); KHelpMenu* helpMenu = new KHelpMenu(m_mainWindow); m_menu->addMenu(helpMenu->menu()); } bool KTouchContext::testKCMAvailibility(const QString& name) { KService::Ptr service = KService::serviceByStorageId(name + ".desktop"); if (!service) { return false; } return service->hasServiceType(QStringLiteral("KCModule")) && !service->noDisplay(); } diff --git a/src/ktouchcontext.h b/src/ktouchcontext.h index 74d9c16..bbff4f2 100644 --- a/src/ktouchcontext.h +++ b/src/ktouchcontext.h @@ -1,70 +1,73 @@ /* * Copyright 2016 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KTOUCHCONTEXT_H #define KTOUCHCONTEXT_H #include class QMenu; class QQuickView; class KActionCollection; class KMainWindow; class DataIndex; class KeyboardLayout; class KeyboardLayoutMenu; class Lesson; class XEventNotifier; class KTouchContext : public QObject { Q_OBJECT Q_PROPERTY(QString keyboardLayoutName READ keyboardLayoutName NOTIFY keyboardLayoutNameChanged) Q_PROPERTY(DataIndex* globalDataIndex READ dataIndex CONSTANT) + Q_PROPERTY(bool keyboardKCMAvailable READ keyboardKCMAvailable CONSTANT) public: explicit KTouchContext(KMainWindow* mainWindow, QQuickView* view, QObject* parent = 0); ~KTouchContext(); QString keyboardLayoutName() const; DataIndex* dataIndex(); + bool keyboardKCMAvailable(); Q_INVOKABLE void showMenu(int xPos, int yPos); + Q_INVOKABLE Lesson* createLesson(); public slots: void showResourceEditor(); + void showKeyboardKCM(); bool showCustomLessonDialog(Lesson* lesson, KeyboardLayout* keyboardLayout); private slots: void showConfigDialog(); void configureShortcuts(); - void configureKeyboard(); void setFullscreen(bool fullscreen); signals: void keyboardLayoutNameChanged(); private: void init(); bool testKCMAvailibility(const QString& name); KActionCollection* m_actionCollection; QMenu* m_menu; KMainWindow* m_mainWindow; QQuickView* m_view; #ifdef KTOUCH_BUILD_WITH_X11 XEventNotifier* m_XEventNotifier; #else KeyboardLayoutMenu* m_keyboardLayoutMenu; #endif }; #endif // KTOUCHCONTEXT_H diff --git a/src/main.cpp b/src/main.cpp index 9e3f570..e12aeb0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,131 +1,134 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include "application.h" #include "mainwindow.h" #include "version.h" int main(int argc, char **argv) { + Application::setAttribute(Qt::AA_EnableHighDpiScaling); + Application::setAttribute(Qt::AA_UseHighDpiPixmaps); + Application app(argc, argv); KLocalizedString::setApplicationDomain("ktouch"); KAboutData about(QStringLiteral("ktouch"), i18n("Typewriting Trainer"), QStringLiteral(KTOUCH_VERSION_STRING), i18n("Learn and practice typewriting"), KAboutLicense::GPL, i18n( "Copyright (C) 2011-2015 by Sebastian Gottfried\n" "Copyright (C) 2000-2007 by Håvard Frøiland and Andreas Nicolai" ), QString(), QStringLiteral("http://edu.kde.org/ktouch"), QStringLiteral("submit@bugs.kde.org")); about.addAuthor(i18n("Sebastian Gottfried"), i18n("Current maintainer"), QStringLiteral("sebastiangottfried@web.de")); about.addAuthor(i18n("Andreas Nicolai"), i18n("Former maintainer and programmer"), QStringLiteral("Andreas.Nicolai@gmx.net")); about.addAuthor(i18n("Håvard Frøiland"), i18n("Original author"), QStringLiteral("haavard@users.sourceforge.net")); about.addCredit(i18n("David Vignoni"), i18n("Creator of the SVG icon"), QStringLiteral("david80v@tin.it")); about.addCredit(i18n("Anne-Marie Mahfouf"), i18n("Lots of patches, fixes and updates"), QStringLiteral("annma@kde.org")); about.addCredit(i18n("All the creators of training and keyboard files")); about.setOrganizationDomain(QByteArray("kde.org")); KAboutData::setApplicationData(about); app.setApplicationName(about.componentName()); app.setApplicationDisplayName(about.displayName()); app.setOrganizationDomain(about.organizationDomain()); app.setApplicationVersion(about.version()); QApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("ktouch"))); QCommandLineParser parser; about.setupCommandLine(&parser); parser.addOption(QCommandLineOption(QStringLiteral("resource-editor"), i18n("Launch the course and keyboard layout editor"))); parser.addOption({{"I", "import-path"}, i18n("Prepend the path to the list of QML import paths"), QStringLiteral("path")}); parser.process(app); about.processCommandLine(&parser); if (parser.isSet(QStringLiteral("import-path"))) { foreach (const QString& path, parser.values("import-path")) { app.qmlImportPaths().append(path); } } if (app.isSessionRestored()) { for (int i = 1; KMainWindow::canBeRestored(i); i++) { const QString name = KMainWindow::classNameOfToplevel(i); if (name == QLatin1String("MainWindow")) { (new MainWindow)->restore(i); } else if (name == QLatin1String("ResourceEditor")) { QPointer& resourceEditorRef = Application::resourceEditorRef(); if (resourceEditorRef.isNull()) { resourceEditorRef = QPointer(new ResourceEditor()); resourceEditorRef.data()->restore(i); } } } } else { if (parser.isSet(QStringLiteral("resource-editor"))) { QPointer& resourceEditorRef = Application::resourceEditorRef(); if (resourceEditorRef.isNull()) { resourceEditorRef = QPointer(new ResourceEditor()); } ResourceEditor* resourceEditor = resourceEditorRef.data(); resourceEditor->show(); } else { MainWindow *mainWin = 0; mainWin = new MainWindow(); mainWin->show(); } } return app.exec(); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e0f366e..1a43a47 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,77 +1,77 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "mainwindow.h" #include #include #include #include #include #include #include #include "application.h" #include "ktouchcontext.h" MainWindow::MainWindow(QWidget* parent): KMainWindow(parent), m_view(new QQuickView()), m_context(new KTouchContext(this, m_view, this)) { init(); } MainWindow::~MainWindow() { } void MainWindow::init() { QWidget* viewWidget = QWidget::createWindowContainer(m_view, this); viewWidget->setMinimumSize(1000, 700); viewWidget->setFocusPolicy(Qt::StrongFocus); setCentralWidget(viewWidget); Application::setupDeclarativeBindings(m_view->engine()); m_view->connect(m_view, &QQuickView::statusChanged, this, &MainWindow::onViewStatusChanged); m_view->rootContext()->setContextProperty(QStringLiteral("ktouch"), m_context); m_view->setResizeMode(QQuickView::SizeRootObjectToView); - m_view->setSource(QUrl(QStringLiteral("qrc:/qml/main.qml"))); + m_view->setSource(QUrl(QStringLiteral("qrc:/ktouch/qml/main.qml"))); } void MainWindow::onViewStatusChanged(QQuickView::Status status) { if (status == QQuickView::Error) { QStringList errorMessages; foreach (auto error, m_view->errors()) { errorMessages.append(error.toString()); } QMessageBox qmlErrorMsgBox; qmlErrorMsgBox.setText(i18n("%1 has encountered a runtime error and has to be closed.", KAboutData::applicationData().displayName())); qmlErrorMsgBox.setDetailedText(errorMessages.join(QStringLiteral("\n"))); qmlErrorMsgBox.setStandardButtons(QMessageBox::Close); qmlErrorMsgBox.setIcon(QMessageBox::Critical); qmlErrorMsgBox.exec(); exit(1); } } diff --git a/src/models/categorizedresourcesortfilterproxymodel.cpp b/src/models/categorizedresourcesortfilterproxymodel.cpp index ff3ad03..ff74981 100644 --- a/src/models/categorizedresourcesortfilterproxymodel.cpp +++ b/src/models/categorizedresourcesortfilterproxymodel.cpp @@ -1,111 +1,131 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "categorizedresourcesortfilterproxymodel.h" #include CategorizedResourceSortFilterProxyModel::CategorizedResourceSortFilterProxyModel(QObject *parent) : KCategorizedSortFilterProxyModel(parent), m_resourceTypeFilter(ResourceModel::CourseItem | ResourceModel::KeyboardLayoutItem), + m_invertedKeyboardLayoutNameFilter(false), m_resourceModel(0) { setDynamicSortFilter(true); } ResourceModel::ResourceItemTypes CategorizedResourceSortFilterProxyModel::resourceTypeFilter() const { return m_resourceTypeFilter; } void CategorizedResourceSortFilterProxyModel::setResourceTypeFilter(ResourceModel::ResourceItemTypes types) { if (types != m_resourceTypeFilter) { m_resourceTypeFilter = types; invalidateFilter(); invalidate(); sort(0); emit resourceTypeFilterChanged(); } } QString CategorizedResourceSortFilterProxyModel::keyboardLayoutNameFilter() const { return m_keyboardLayoutNameFilter; } void CategorizedResourceSortFilterProxyModel::setKeyboardLayoutNameFilter(const QString &name) { if (name != m_keyboardLayoutNameFilter) { m_keyboardLayoutNameFilter = name; invalidateFilter(); invalidate(); sort(0); emit keyboardLayoutNameFilterChanged(); } } +bool CategorizedResourceSortFilterProxyModel::invertedKeyboardLayoutNameFilter() const +{ + return m_invertedKeyboardLayoutNameFilter; +} + +void CategorizedResourceSortFilterProxyModel::setInvertedKeyboardLayoutNameFilter(bool inverted) +{ + if (inverted != m_invertedKeyboardLayoutNameFilter) + { + m_invertedKeyboardLayoutNameFilter = inverted; + invalidateFilter(); + invalidate(); + sort(0); + emit invertedKeyboardLayoutNameFilterChanged(); + } +} + + ResourceModel* CategorizedResourceSortFilterProxyModel::resourceModel() const { return m_resourceModel; } + void CategorizedResourceSortFilterProxyModel::setResourceModel(ResourceModel* resourceModel) { if (resourceModel != m_resourceModel) { m_resourceModel = resourceModel; setSourceModel(m_resourceModel); sort(0); emit resourceModelChanged(); } } bool CategorizedResourceSortFilterProxyModel::subSortLessThan(const QModelIndex& left, const QModelIndex& right) const { const QString leftStr = sourceModel()->data(left, Qt::DisplayRole).toString(); const QString rightStr = sourceModel()->data(right, Qt::DisplayRole).toString(); QCollator locater; locater.setCaseSensitivity(Qt::CaseInsensitive); const int difference = locater.compare(leftStr, rightStr); if (difference == 0) { return left.row() < right.row(); } return difference < 0; } bool CategorizedResourceSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); const int resourceType = sourceModel()->data(index, ResourceModel::ResourceTypeRole).toInt(); if ((m_resourceTypeFilter & resourceType) == 0) return false; if (m_keyboardLayoutNameFilter.isEmpty()) return true; const QString name = sourceModel()->data(index, ResourceModel::KeyboardLayoutNameRole).toString(); - return name == m_keyboardLayoutNameFilter; + return m_invertedKeyboardLayoutNameFilter ^ (name == m_keyboardLayoutNameFilter); } diff --git a/src/models/categorizedresourcesortfilterproxymodel.h b/src/models/categorizedresourcesortfilterproxymodel.h index f3ec5a7..35ed7c1 100644 --- a/src/models/categorizedresourcesortfilterproxymodel.h +++ b/src/models/categorizedresourcesortfilterproxymodel.h @@ -1,53 +1,58 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CATEGORIZEDRESOURCESORTFILTERPROXYMODEL_H #define CATEGORIZEDRESOURCESORTFILTERPROXYMODEL_H #include "KCategorizedSortFilterProxyModel" #include "models/resourcemodel.h" class CategorizedResourceSortFilterProxyModel : public KCategorizedSortFilterProxyModel { Q_OBJECT Q_PROPERTY(ResourceModel::ResourceItemTypes resourceTypeFilter READ resourceTypeFilter WRITE setResourceTypeFilter NOTIFY resourceTypeFilterChanged) Q_PROPERTY(QString keyboardLayoutNameFilter READ keyboardLayoutNameFilter WRITE setKeyboardLayoutNameFilter NOTIFY keyboardLayoutNameFilterChanged) + Q_PROPERTY(bool invertedKeyboardLayoutNameFilter READ invertedKeyboardLayoutNameFilter WRITE setInvertedKeyboardLayoutNameFilter NOTIFY invertedKeyboardLayoutNameFilterChanged) Q_PROPERTY(ResourceModel* resourceModel READ resourceModel WRITE setResourceModel NOTIFY resourceModelChanged) public: - explicit CategorizedResourceSortFilterProxyModel(QObject* parent = 0); + explicit CategorizedResourceSortFilterProxyModel(QObject* parent = nullptr); ResourceModel::ResourceItemTypes resourceTypeFilter() const; void setResourceTypeFilter(ResourceModel::ResourceItemTypes types); QString keyboardLayoutNameFilter() const; void setKeyboardLayoutNameFilter(const QString& name); + bool invertedKeyboardLayoutNameFilter() const; + void setInvertedKeyboardLayoutNameFilter(bool inverted); ResourceModel* resourceModel() const; void setResourceModel(ResourceModel* resourceModel); signals: void resourceTypeFilterChanged(); void keyboardLayoutNameFilterChanged(); + void invertedKeyboardLayoutNameFilterChanged(); void resourceModelChanged(); protected: bool subSortLessThan(const QModelIndex& left, const QModelIndex& right) const override; bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; private: ResourceModel::ResourceItemTypes m_resourceTypeFilter; QString m_keyboardLayoutNameFilter; + bool m_invertedKeyboardLayoutNameFilter; ResourceModel* m_resourceModel; }; #endif // CATEGORIZEDRESOURCESORTFILTERPROXYMODEL_H diff --git a/src/models/charactersmodel.cpp b/src/models/charactersmodel.cpp index bf9ac1c..edd8182 100644 --- a/src/models/charactersmodel.cpp +++ b/src/models/charactersmodel.cpp @@ -1,323 +1,323 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "charactersmodel.h" #include #include #include "core/keyboardlayout.h" #include "core/key.h" #include "core/keychar.h" #include "undocommands/keyboardlayoutcommands.h" CharactersModel::CharactersModel(QObject *parent) : QAbstractTableModel(parent), - m_keyboardLayout(0), + m_keyboardLayout(nullptr), m_keyIndex(-1), - m_key(0), - m_undoStack(0), + m_key(nullptr), + m_undoStack(nullptr), m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitCharacterChanged(int))); } KeyboardLayout* CharactersModel::keyboardLayout() const { return m_keyboardLayout; } void CharactersModel::setKeyboardLayout(KeyboardLayout* keyboardLayout) { if (keyboardLayout != m_keyboardLayout) { m_keyboardLayout = keyboardLayout; setKeyIndex(-1); emit keyboardLayoutChanged(); } } int CharactersModel::keyIndex() const { return m_keyIndex; } void CharactersModel::setKeyIndex(int keyIndex) { if (!m_keyboardLayout) return; - Key* key = 0; + Key* key = nullptr; if (keyIndex != -1) { key = qobject_cast(m_keyboardLayout->key(keyIndex)); if (!key) keyIndex = -1; } if (keyIndex != m_keyIndex) { beginResetModel(); if (m_key) { m_key->disconnect(this); for (int i = 0; i < m_key->keyCharCount(); i++) { KeyChar* keyChar = m_key->keyChar(i); keyChar->disconnect(m_signalMapper); m_signalMapper->setMapping(keyChar, i); } } m_keyIndex = keyIndex; m_key = key; if (m_key) { connect(m_key, &Key::keyCharAboutToBeAdded, this, &CharactersModel::onKeyCharAboutToBeAdded); connect(m_key, &Key::keyCharAdded, this, &CharactersModel::onKeyCharAdded); connect(m_key, &Key::keyCharsAboutToBeRemoved, this, &CharactersModel::onKeyCharsAboutToBeRemoved); connect(m_key, &Key::keyCharsRemoved, this, &CharactersModel::onKeyCharsRemoved); for (int i = 0; i < m_key->keyCharCount(); i++) { KeyChar* keyChar = m_key->keyChar(i); connect(keyChar, SIGNAL(valueChanged()), m_signalMapper, SLOT(map())); connect(keyChar, SIGNAL(modifierChanged()), m_signalMapper, SLOT(map())); connect(keyChar, SIGNAL(positionChanged()), m_signalMapper, SLOT(map())); m_signalMapper->setMapping(keyChar, i); } } endResetModel(); } } QUndoStack* CharactersModel::undoStack() const { return m_undoStack; } void CharactersModel::setUndoStack(QUndoStack *undoStack) { m_undoStack = undoStack; } Qt::ItemFlags CharactersModel::flags(const QModelIndex& index) const { if (!index.isValid()) return Qt::NoItemFlags; return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable; } QVariant CharactersModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= m_key->keyCharCount()) return QVariant(); KeyChar* keyChar = m_key->keyChar(index.row()); switch (index.column()) { case 0: return characterData(keyChar, role); case 1: return modifierIdData(keyChar, role); case 2: return positionData(keyChar, role); default: return QVariant(); } } bool CharactersModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; if (index.row() >= m_key->keyCharCount()) return false; if (role != Qt::EditRole) return false; if (!m_undoStack) return false; KeyChar* keyChar = m_key->keyChar(index.row()); - QUndoCommand* cmd = 0; + QUndoCommand* cmd = nullptr; switch (index.column()) { case 0: if (value.toString().length() != 1) return false; if (value.toString().at(0) == keyChar->value()) return false; cmd = new SetKeyCharValueCommand(m_keyboardLayout, m_keyIndex, index.row(), value.toString().at(0)); undoStack()->push(cmd); return true; case 1: if (value.toString() == keyChar->modifier()) return false; cmd = new SetKeyCharModifierCommand(m_keyboardLayout, m_keyIndex, index.row(), value.toString()); undoStack()->push(cmd); return true; case 2: if (static_cast(value.toInt()) == keyChar->position()) return false; cmd = new SetKeyCharPositionCommand(m_keyboardLayout, m_keyIndex, index.row(), static_cast(value.toInt())); undoStack()->push(cmd); return true; } return false; } QVariant CharactersModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Vertical) return QVariant(section + 1); switch (section) { case 0: return QVariant(i18n("Character")); case 1: return QVariant(i18n("Modifier ID")); case 2: return QVariant(i18n("Position")); default: return QVariant(); } } int CharactersModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) return 3; } int CharactersModel::rowCount(const QModelIndex& parent) const { if (!m_key) return 0; if (parent.isValid()) return 0; return m_key->keyCharCount(); } void CharactersModel::onKeyCharAboutToBeAdded(KeyChar* keyChar, int index) { connect(keyChar, SIGNAL(valueChanged()), m_signalMapper, SLOT(map())); connect(keyChar, SIGNAL(modifierChanged()), m_signalMapper, SLOT(map())); connect(keyChar, SIGNAL(positionChanged()), m_signalMapper, SLOT(map())); beginInsertRows(QModelIndex(), index, index); } void CharactersModel::onKeyCharAdded() { updateMappings(); endInsertRows(); } void CharactersModel::onKeyCharsAboutToBeRemoved(int first, int last) { beginRemoveRows(QModelIndex(), first, last); } void CharactersModel::onKeyCharsRemoved() { endRemoveRows(); } void CharactersModel::emitCharacterChanged(int row) { emit dataChanged(index(row, 0), index(row, columnCount() - 1)); } void CharactersModel::updateMappings() { for (int i = 0; i < m_key->keyCharCount(); i++) { m_signalMapper->setMapping(m_key->keyChar(i), i); } } QVariant CharactersModel::characterData(KeyChar* keyChar, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return keyChar->value(); - return keyChar->value(); default: return QVariant(); } } QVariant CharactersModel::modifierIdData(KeyChar *keyChar, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return keyChar->modifier(); default: return QVariant(); } } QVariant CharactersModel::positionData(KeyChar *keyChar, int role) const { switch (role) { case Qt::DisplayRole: switch (keyChar->position()) { case KeyChar::TopLeft: return i18n("Top left"); case KeyChar::TopRight: return i18n("Top right"); case KeyChar::BottomLeft: return i18n("Bottom left"); case KeyChar::BottomRight: return i18n("Bottom right"); case KeyChar::Hidden: return i18n("Hidden"); } + return QVariant(); case Qt::EditRole: return QVariant(keyChar->position()); default: return QVariant(); } } diff --git a/src/models/charactersmodel.h b/src/models/charactersmodel.h index b5f272b..49bad45 100644 --- a/src/models/charactersmodel.h +++ b/src/models/charactersmodel.h @@ -1,70 +1,70 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef CHARACTERSMODEL_H #define CHARACTERSMODEL_H #include class QSignalMapper; class QUndoStack; class KeyboardLayout; class Key; class KeyChar; class CharactersModel : public QAbstractTableModel { Q_OBJECT Q_PROPERTY(KeyboardLayout* keyboardLayout READ keyboardLayout WRITE setKeyboardLayout NOTIFY keyboardLayoutChanged) Q_PROPERTY(int keyIndex READ keyIndex WRITE setKeyIndex NOTIFY keyIndexChanged) public: - explicit CharactersModel(QObject *parent = 0); + explicit CharactersModel(QObject *parent = nullptr); KeyboardLayout* keyboardLayout() const; void setKeyboardLayout(KeyboardLayout* keyboardLayout); int keyIndex() const; void setKeyIndex(int keyIndex); QUndoStack* undoStack() const; void setUndoStack(QUndoStack* undoStack); Qt::ItemFlags flags(const QModelIndex& index) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; int columnCount(const QModelIndex& parent = QModelIndex()) const override; signals: void aboutToBeEdited(const QModelIndex& index, const QVariant& newValue); void keyboardLayoutChanged(); void keyIndexChanged(); private slots: void onKeyCharAboutToBeAdded(KeyChar* keyChar, int index); void onKeyCharAdded(); void onKeyCharsAboutToBeRemoved(int first, int last); void onKeyCharsRemoved(); void emitCharacterChanged(int row); private: void updateMappings(); QVariant characterData(KeyChar* keyChar, int role) const; QVariant modifierIdData(KeyChar* keyChar, int role) const; QVariant positionData(KeyChar* keyChar, int role) const; KeyboardLayout* m_keyboardLayout; int m_keyIndex; Key* m_key; QUndoStack* m_undoStack; QSignalMapper* m_signalMapper; }; #endif // CHARACTERSMODEL_H diff --git a/src/models/errorsmodel.h b/src/models/errorsmodel.h index c57fd5f..cbf7e1e 100644 --- a/src/models/errorsmodel.h +++ b/src/models/errorsmodel.h @@ -1,51 +1,51 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef ERRORSMODEL_H #define ERRORSMODEL_H #include class TrainingStats; class ErrorsModel : public QAbstractTableModel { Q_OBJECT Q_PROPERTY(TrainingStats* trainingStats READ trainingStats WRITE setTrainingStats NOTIFY trainingStatsChanged) Q_PROPERTY(int maximumErrorCount READ maximumErrorCount NOTIFY maximumErrorCountChanged) public: - explicit ErrorsModel(QObject* parent = 0); + explicit ErrorsModel(QObject* parent = nullptr); TrainingStats* trainingStats() const; void setTrainingStats(TrainingStats* trainingStats); int maximumErrorCount() const; QVariant data(const QModelIndex& index, int role) const override; int columnCount(const QModelIndex& parent) const override; int rowCount(const QModelIndex& parent) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; Q_INVOKABLE QString character(int row) const; Q_INVOKABLE int errors(int row) const; signals: void trainingStatsChanged(); void maximumErrorCountChanged(); private slots: void buildErrorList(); private: TrainingStats* m_trainingStats; QList > m_errors; }; #endif // ERRORSMODEL_H diff --git a/src/models/learningprogressmodel.cpp b/src/models/learningprogressmodel.cpp index 189b7ee..2bbdadb 100644 --- a/src/models/learningprogressmodel.cpp +++ b/src/models/learningprogressmodel.cpp @@ -1,281 +1,288 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "learningprogressmodel.h" #include #include "core/profile.h" #include "core/course.h" #include "core/lesson.h" #include "core/profiledataaccess.h" LearningProgressModel::LearningProgressModel(QObject* parent) : QSqlQueryModel(parent), m_profile(0), m_courseFilter(0), m_lessonFilter(0) { } Profile* LearningProgressModel::profile() const { return m_profile; } void LearningProgressModel::setProfile(Profile* profile) { if (profile != m_profile) { if (m_profile) { m_profile->disconnect(this); } m_profile = profile; if (m_profile) { connect(m_profile, &Profile::idChanged, this, &LearningProgressModel::update); connect(m_profile, &QObject::destroyed, this, &LearningProgressModel::profileDestroyed); } update(); emit profileChanged(); } } Course* LearningProgressModel::courseFilter() const { return m_courseFilter; } void LearningProgressModel::setCourseFilter(Course* courseFilter) { if (courseFilter != m_courseFilter) { if (m_courseFilter) { m_courseFilter->disconnect(this); } m_courseFilter = courseFilter; if (m_courseFilter) { connect(courseFilter, &Resource::idChanged, this, &LearningProgressModel::update); } update(); emit courseFilterChanged(); } } Lesson* LearningProgressModel::lessonFilter() const { return m_lessonFilter; } void LearningProgressModel::setLessonFilter(Lesson* lessonFilter) { if (lessonFilter != m_lessonFilter) { if (m_lessonFilter) { m_lessonFilter->disconnect(this); } m_lessonFilter = lessonFilter; if (m_lessonFilter) { connect(m_lessonFilter, &Lesson::idChanged, this, &LearningProgressModel::update); } update(); emit lessonFilterChanged(); } } int LearningProgressModel::maxCharactersTypedPerMinute() const { int max = 0; for (int i = 0; i < rowCount(); i++) { max = qMax(max, charactersPerMinute(i)); } return max; } qreal LearningProgressModel::minAccuracy() const { qreal min = 1; for (int i = 0; i < rowCount(); i++) { min = qMin(min, accuracy(i)); } return min; } int LearningProgressModel::columnCount(const QModelIndex& parent) const { Q_UNUSED(parent) const int originalColumnCount = QSqlQueryModel::columnCount(); return originalColumnCount > 0? originalColumnCount + 2: 0; } QVariant LearningProgressModel::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole) return QVariant(); if (orientation == Qt::Vertical) return QSqlQueryModel::headerData(section, orientation, role); const int originalColumnCount = QSqlQueryModel::columnCount(); if (section < originalColumnCount) return QSqlQueryModel::headerData(section, orientation, role); switch (section - originalColumnCount) { case 0: return QVariant("accuracy"); case 1: return QVariant("characters_per_minute"); default: return QVariant(); } } QVariant LearningProgressModel::data(const QModelIndex &item, int role) const { const int originalColumnCount = QSqlQueryModel::columnCount(); if (item.column() < originalColumnCount) return QSqlQueryModel::data(item, role); switch (item.column() - originalColumnCount) { case 0: return accuracyData(item.row(), role); case 1: return charactersPerMinuteData(item.row(), role); default: return QVariant(); } } +QDateTime LearningProgressModel::date(int row) const +{ + LearningProgressModel* model = const_cast(this); + QSqlRecord record = model->record(row); + return QDateTime::fromMSecsSinceEpoch(record.value(0).value()); +} + int LearningProgressModel::charactersPerMinute(int row) const { const int charactersTyped = this->charactersTyped(row); const int elapsedTime = this->elapsedTime(row); return elapsedTime > 0? charactersTyped * 60000 / elapsedTime: 0; } int LearningProgressModel::charactersTyped(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(1).toInt(); } int LearningProgressModel::errorCount(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(2).toInt(); } int LearningProgressModel::elapsedTime(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(3).toInt(); } qreal LearningProgressModel::accuracy(int row) const { const int charactersTyped = this->charactersTyped(row); const int errorCount = this->errorCount(row); const qreal accuracy = charactersTyped > 0? 1.0 - qreal(errorCount) / qreal(errorCount + charactersTyped): errorCount == 0? 1.0: 0.0; return accuracy; } QString LearningProgressModel::lessonId(int row) const { LearningProgressModel* model = const_cast(this); QSqlRecord record = model->record(row); return record.value(4).toString(); } void LearningProgressModel::update() { ProfileDataAccess access; clear(); if (m_profile) { const QSqlQuery query = access.learningProgressQuery(m_profile, m_courseFilter, m_lessonFilter); setQuery(query); } emit maxCharactersTypedPerMinuteChanged(); emit minAccuracyChanged(); } QVariant LearningProgressModel::accuracyData(int row, int role) const { const qreal accuracy = this->accuracy(row); switch(role) { case Qt::DisplayRole: return QVariant(accuracy); default: return QVariant(); } } QVariant LearningProgressModel::charactersPerMinuteData(int row, int role) const { int charactersPerMinute = this->charactersPerMinute(row); switch(role) { case Qt::DisplayRole: return QVariant(charactersPerMinute); default: return QVariant(); } } void LearningProgressModel::profileDestroyed() { setProfile(0); } diff --git a/src/models/learningprogressmodel.h b/src/models/learningprogressmodel.h index 8ded968..f40831e 100644 --- a/src/models/learningprogressmodel.h +++ b/src/models/learningprogressmodel.h @@ -1,74 +1,75 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LEARNINGPROGRESSMODEL_H #define LEARNINGPROGRESSMODEL_H #include class Profile; class Course; class Lesson; class LearningProgressModel : public QSqlQueryModel { Q_OBJECT Q_PROPERTY(Profile* profile READ profile WRITE setProfile NOTIFY profileChanged) Q_PROPERTY(Course* courseFilter READ courseFilter WRITE setCourseFilter NOTIFY courseFilterChanged) Q_PROPERTY(Lesson* lessonFilter READ lessonFilter WRITE setLessonFilter NOTIFY lessonFilterChanged) Q_PROPERTY(int maxCharactersTypedPerMinute READ maxCharactersTypedPerMinute NOTIFY maxCharactersTypedPerMinuteChanged) Q_PROPERTY(qreal minAccuracy READ minAccuracy NOTIFY minAccuracyChanged) public: - explicit LearningProgressModel(QObject* parent = 0); + explicit LearningProgressModel(QObject* parent = nullptr); Profile* profile() const; void setProfile(Profile* profile); Course* courseFilter() const; void setCourseFilter(Course* courseFilter); Lesson* lessonFilter() const; void setLessonFilter(Lesson* lessonFilter); int maxCharactersTypedPerMinute() const; qreal minAccuracy() const; int columnCount(const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + Q_INVOKABLE QDateTime date(int row) const; Q_INVOKABLE int charactersPerMinute(int row) const; Q_INVOKABLE int charactersTyped(int row) const; Q_INVOKABLE int errorCount(int row) const; Q_INVOKABLE int elapsedTime(int row) const; Q_INVOKABLE qreal accuracy(int row) const; Q_INVOKABLE QString lessonId(int row) const; public slots: void update(); signals: void profileChanged(); void courseFilterChanged(); void lessonFilterChanged(); void maxCharactersTypedPerMinuteChanged(); void minAccuracyChanged(); private slots: void profileDestroyed(); private: QVariant data(const QModelIndex& item, int role = Qt::DisplayRole) const override; QVariant accuracyData(int row, int role = Qt::DisplayRole) const; QVariant charactersPerMinuteData(int row, int role = Qt::DisplayRole) const; int m_charactersTypedFieldIndex; Profile* m_profile; Course* m_courseFilter; Lesson* m_lessonFilter; }; #endif // LEARNINGPROGRESSMODEL_H diff --git a/src/models/lessonmodel.cpp b/src/models/lessonmodel.cpp index 16c1468..ef068e4 100644 --- a/src/models/lessonmodel.cpp +++ b/src/models/lessonmodel.cpp @@ -1,147 +1,156 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "lessonmodel.h" #include #include #include #include LessonModel::LessonModel(QObject* parent) : QAbstractListModel(parent), m_course(0), m_signalMapper(new QSignalMapper(this)) { connect(m_signalMapper, SIGNAL(mapped(int)), SLOT(emitLessonChanged(int))); } Course* LessonModel::course() const { return m_course; } void LessonModel::setCourse(Course* course) { if (m_course != course) { beginResetModel(); if (m_course) { m_course->disconnect(this); } m_course = course; if (m_course) { connect(m_course, &Course::lessonAboutToBeAdded, this, &LessonModel::onLessonAboutToBeAdded); connect(m_course, &Course::lessonAdded, this, &LessonModel::onLessonAdded); connect(m_course, &Course::lessonsAboutToBeRemoved, this, &LessonModel::onLessonsAboutToBeRemoved); connect(m_course, &Course::lessonsRemoved, this, &LessonModel::onLessonsRemoved); } endResetModel(); emit courseChanged(); } } QVariant LessonModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); if (index.row() >= m_course->lessonCount()) return QVariant(); Lesson* const lesson = m_course->lesson(index.row()); switch(role) { case Qt::DisplayRole: return !lesson->title().isEmpty()? QVariant(lesson->title()): QVariant(i18n("")); case Qt::ToolTipRole: return QVariant(i18n("

New characters: %1

%2

", lesson->newCharacters(), lesson->text())); + case LessonModel::DataRole: + return QVariant::fromValue(lesson); default: return QVariant(); } } int LessonModel::rowCount(const QModelIndex& parent) const { if (!m_course) return 0; if (parent.isValid()) return 0; return m_course->lessonCount(); } void LessonModel::onLessonAboutToBeAdded(Lesson* lesson, int index) { connect(lesson, SIGNAL(titleChanged()), m_signalMapper, SLOT(map())); connect(lesson, SIGNAL(newCharactersChanged()), m_signalMapper, SLOT(map())); connect(lesson, SIGNAL(textChanged()), m_signalMapper, SLOT(map())); beginInsertRows(QModelIndex(), index, index); } void LessonModel::onLessonAdded() { updateMappings(); endInsertRows(); } void LessonModel::onLessonsAboutToBeRemoved(int first, int last) { beginRemoveRows(QModelIndex(), first, last); } void LessonModel::onLessonsRemoved() { endRemoveRows(); } void LessonModel::emitLessonChanged(int row) { emit lessonChanged(row); emit dataChanged(index(row, 0), index(row, 0)); } QVariant LessonModel::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(i18n("Title")); } +QHash LessonModel::roleNames() const +{ + QHash names = QAbstractItemModel::roleNames(); + names.insert(LessonModel::DataRole, "dataRole"); + return names; +} + void LessonModel::updateMappings() { for (int i = 0; i < m_course->lessonCount(); i++) { m_signalMapper->setMapping(m_course->lesson(i), i); } } diff --git a/src/models/lessonmodel.h b/src/models/lessonmodel.h index 7af3ef2..35494ec 100644 --- a/src/models/lessonmodel.h +++ b/src/models/lessonmodel.h @@ -1,56 +1,61 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef LESSONMODEL_H #define LESSONMODEL_H #include class QSignalMapper; class Course; class Lesson; class LessonModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(Course* course READ course WRITE setCourse NOTIFY courseChanged) + Q_ENUMS(AdditionalRoles) public: - explicit LessonModel(QObject* parent = 0); + enum AdditionalRoles { + DataRole = Qt::UserRole + 1 + }; + explicit LessonModel(QObject* parent = nullptr); Course* course() const; void setCourse(Course* course); QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; signals: void courseChanged(); void lessonChanged(int index); private slots: void onLessonAboutToBeAdded(Lesson* lesson, int index); void onLessonAdded(); void onLessonsAboutToBeRemoved(int first, int last); void onLessonsRemoved(); void emitLessonChanged(int row); private: void updateMappings(); Course* m_course; QSignalMapper* m_signalMapper; }; #endif // LESSONMODEL_H diff --git a/src/models/resourcemodel.h b/src/models/resourcemodel.h index f8e328b..8a9536f 100644 --- a/src/models/resourcemodel.h +++ b/src/models/resourcemodel.h @@ -1,84 +1,84 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef RESOURCEMODEL_H #define RESOURCEMODEL_H #include #include "core/dataindex.h" class QIcon; class QSignalMapper; class DataIndex; class ResourceModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(DataIndex* dataIndex READ dataIndex WRITE setDataIndex NOTIFY dataIndexChanged) Q_ENUMS(ResourceItemTypes) public: enum ResourceItemType { None = 0x0, CourseItem = 0x1, KeyboardLayoutItem = 0x2 }; Q_DECLARE_FLAGS(ResourceItemTypes, ResourceItemType) enum AdditionalRoles { ResourceTypeRole = Qt::UserRole + 1, DataRole, KeyboardLayoutNameRole, PathRole, IndexRole, SourceRole }; Q_ENUM(AdditionalRoles) - explicit ResourceModel( QObject* parent = 0); + explicit ResourceModel( QObject* parent = nullptr); DataIndex* dataIndex() const; void setDataIndex(DataIndex* dataIndex); Qt::ItemFlags flags(const QModelIndex& index) const override; QVariant data(const QModelIndex& index, int role) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent) const override; QHash roleNames() const override; signals: void dataIndexChanged(); private slots: void onCourseAboutToBeAdded(DataIndexCourse* course, int index); void onCoursesAboutToBeRemoved(int first, int last); void onKeyboardLayoutAboutToBeAdded(DataIndexKeyboardLayout* keyboardLayout, int index); void onKeyboardLayoutsAboutToBeRemoved(int first, int last); void onResourceAdded(); void onResourceRemoved(); void emitDataChanged(int row); private: QVariant courseData(int row, int role) const; QVariant keyboardLayoutData(int row, int role) const; void connectToCourse(DataIndexCourse* course); void connectToKeyboardLayout(DataIndexKeyboardLayout* keyboardLayout); void updateMappings(); QIcon resourceIcon(DataIndex::Source source) const; DataIndex* m_dataIndex; QSignalMapper* m_signalMapper; }; Q_DECLARE_OPERATORS_FOR_FLAGS(ResourceModel::ResourceItemTypes) #endif // RESOURCEMODEL_H diff --git a/src/qml/common/AutoTriggerButton.qml b/src/qml/common/AutoTriggerButton.qml new file mode 100644 index 0000000..9d581bf --- /dev/null +++ b/src/qml/common/AutoTriggerButton.qml @@ -0,0 +1,56 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import ktouch 1.0 + +IconButton { + id: control + + property alias duration: progressAnimation.duration + property alias running: progressAnimation.running + + background.children: [ + ProgressBar { + id: progressBar + anchors.fill: parent + value: 0.0 + background: Item {} + contentItem: Item { + Rectangle { + color: control.colorScheme.focusDecoration + opacity: 0.4 + width: parent.width * progressBar.visualPosition + height: parent.height + } + } + + NumberAnimation { + id: progressAnimation + target: progressBar + property: "value" + duration: 10000 + to: 1.0 + running: true + onStopped: { + control.clicked(); + } + } + } + ] +} diff --git a/src/qml/common/Balloon.qml b/src/qml/common/Balloon.qml index e914440..bf91734 100644 --- a/src/qml/common/Balloon.qml +++ b/src/qml/common/Balloon.qml @@ -1,166 +1,166 @@ /* * Copyright 2012 Marco Martin * Copyright 2015 Sebastian Gottfried * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import QtQuick 2.4 +import QtQuick 2.9 import QtGraphicalEffects 1.0 Loader { id: root property Item visualParent property string status: 'closed' default property Item data active: status != 'closed' function open() { root.status = 'loading' } function close() { root.status = 'closing' } sourceComponent: Component { MouseArea { id: dismissArea anchors.fill: parent opacity: root.active && (root.status == 'open' || root.status =='opening')? 1 : 0 layer.enabled: true layer.effect: DropShadow { anchors.fill: parent radius: 5 samples: 11 } Behavior on opacity { SequentialAnimation { NumberAnimation { duration: 250 easing.type: Easing.InOutQuad properties: "opacity" } ScriptAction { script: { root.status = root.status == 'opening' ? 'open' : 'closed' } } } } SystemPalette { id: palette colorGroup: SystemPalette.Active } Rectangle { id: internal color: palette.alternateBase radius: 5 - property variant parentPos: root.visualParent? root.visualParent.mapToItem(dismissArea, 0, 0): Qt.point(0, 0) + property variant parentPos: root.visualParent? root.visualParent.mapToItem(null, 0, 0): Qt.point(0, 0) property bool under: root.visualParent ? internal.parentPos.y + root.visualParent.height + height < dismissArea.height : true - //bindings won't work inside anchors definition + // bindings don't work for anchor definition onUnderChanged: { if (under) { balloonTip.anchors.top = undefined balloonTip.anchors.bottom = balloonTip.parent.top } else { balloonTip.anchors.bottom = undefined balloonTip.anchors.top = balloonTip.parent.bottom } } property int preferedX: internal.parentPos.x - internal.width/2 + root.visualParent.width/2 x: Math.round(Math.max(radius, Math.min(dismissArea.width - internal.width - radius, preferedX))) y: { if (root.visualParent) { if (under) { Math.round(internal.parentPos.y + root.visualParent.height + balloonTip.height + radius) } else { Math.round(internal.parentPos.y - internal.height - balloonTip.height - radius) } } else { Math.round(dismissArea.height/2 - internal.height/2) } } width: contentItem.width + 2 * internal.radius height: contentItem.height + 2 * internal.radius Rectangle { id: balloonTip color: internal.color anchors { horizontalCenter: parent.horizontalCenter horizontalCenterOffset: internal.preferedX - internal.x top: parent.bottom } width: 10 height: 10 visible: false } Image { id: balloonTipMask anchors.fill: balloonTip visible: false - source: utils.findImage("balloontip.svgz") + source: "qrc:///ktouch/images/balloontip.svgz" sourceSize: Qt.size(width, height) } OpacityMask { anchors.fill: balloonTip visible: root.visualParent != null source: balloonTip maskSource: balloonTipMask rotation: internal.under? 0: 180 } Item { id: contentItem x: internal.radius y: internal.radius width: childrenRect.width height: childrenRect.height + 2 data: root.data } } onClicked: { root.close() } Component.onCompleted: { var candidate = root while (candidate.parent.parent) { candidate = candidate.parent } if (candidate) { dismissArea.parent = candidate } root.status = 'opening' } } } } diff --git a/src/qml/common/Collapsable.qml b/src/qml/common/Collapsable.qml new file mode 100644 index 0000000..9849795 --- /dev/null +++ b/src/qml/common/Collapsable.qml @@ -0,0 +1,72 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +Controls.Control { + id: root + + property bool collapsed: false + default property Item data + + signal contentReadyForSwap + + function swapContent() { + contentItem.isSwapping = true + } + + property KColorScheme colorScheme: KColorScheme { + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Window + } + + height: collapsed && contentItem.opacity == 0? 0: contentItem.implicitHeight + visible: height > 0 + + Behavior on height { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } + + background: Rectangle { + color: colorScheme.normalBackground + } + + contentItem: Item { + property bool isSwapping: false + opacity: !root.collapsed && !isSwapping && root.height === implicitHeight? 1: 0 + data: root.data + implicitHeight: children.length > 0? children[0].implicitHeight: 0 + + Behavior on opacity { + NumberAnimation { + duration: 150 + } + } + + onOpacityChanged: { + if (isSwapping && opacity === 0) { + contentReadyForSwap() + isSwapping = false; + } + } + } +} diff --git a/src/qml/common/ComboBox.qml b/src/qml/common/ComboBox.qml new file mode 100644 index 0000000..bedfa79 --- /dev/null +++ b/src/qml/common/ComboBox.qml @@ -0,0 +1,131 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtGraphicalEffects 1.0 +import ktouch 1.0 + +ComboBox { + id: control + + property string iconRole: "icon" + + property KColorScheme colorScheme: KColorScheme { + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Inactive + colorSet: KColorScheme.Button + } + + property alias popupListView: list + + KColorScheme { + id: listColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.View + } + + hoverEnabled: true + spacing: 0.7 * font.pixelSize + + background: Item { + Rectangle { + anchors.fill: parent + opacity: (popup.visible? 0.6: 0.0) + (control.hovered || control.visualFocus? 0.3: 0.0) + color: colorScheme.normalBackground + Behavior on opacity { + NumberAnimation { + duration: 150 + } + } + } + + FocusBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 3 + control: control + } + } + + contentItem: IconLabel { + text: control.displayText + iconName: currentIndex != -1 && currentIndex < model.count ? control.model.get(currentIndex)[control.iconRole]: "" + color: colorScheme.normalText + } + + indicator: MonochromeIcon { + color: colorScheme.normalText + icon: "arrow-down-double" + x: control.width - width - control.contentItem.padding + y: control.topPadding + (control.availableHeight - height) / 2 + rotation: control.popup.opacity * -180 + } + + delegate: ListItem { + width: control.width + text: control.textRole ? (Array.isArray(control.model) ? modelData[control.textRole] : model[control.textRole]) : modelData + iconName: control.iconRole ? (Array.isArray(control.model) ? modelData[control.iconRole] : model[control.iconRole]) : "" + highlighted: control.currentIndex === index + } + + popup: Popup { + y: control.height + width: control.width + implicitHeight: contentItem.implicitHeight + padding: 0 + opacity: 0 + + enter: Transition { + NumberAnimation { + property: "opacity" + to: 1.0 + duration: 150 + } + } + + exit: Transition { + NumberAnimation { + property: "opacity" + to: 0.0 + duration: 150 + } + } + + contentItem: + ListView { + id: list + clip: true + implicitHeight: contentHeight + width: parent.width + model: control.popup.visible ? control.delegateModel : null + currentIndex: control.highlightedIndex + } + + background: Rectangle { + color: listColorScheme.normalBackground + layer.enabled: true + layer.effect: DropShadow { + samples: 24 + horizontalOffset: 0 + verticalOffset: 0 + } + } + } +} diff --git a/src/qml/common/DetailedRadioButton.qml b/src/qml/common/DetailedRadioButton.qml deleted file mode 100644 index 3cdba00..0000000 --- a/src/qml/common/DetailedRadioButton.qml +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2014 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 - -ColumnLayout { - id: root - - property alias checked: radioButton.checked - property alias enabled: radioButton.enabled - property alias label: label.text - property alias hint: hint.text - - spacing: 10 - - Row { - id: radioButtonRow - Layout.fillWidth: true - spacing: Math.round(label.height / 4) - - RadioButton { - id: radioButton - anchors.verticalCenter: parent.verticalCenter - } - - Label { - id: label - anchors.verticalCenter: parent.verticalCenter - /* - * The text wrapping of the label doesn't work if it is invisible - * (wrapped at every character), hence the following hack. - */ - width: visible? parent.width - radioButton.width - parent.spacing: 0 - wrapMode: Text.Wrap - opacity: radioButton.opacity - MouseArea { - anchors.fill: parent - enabled: radioButton.enabled - onClicked: { - radioButton.checked = true - } - } - } - } - - Row { - Layout.fillWidth: true - spacing: radioButtonRow.spacing - - Item { - width: radioButton.width - height: hint.height - } - - Label { - id: hint - font.italic: true - width: visible? parent.width - radioButton.width - parent.spacing: 0 - wrapMode: Text.Wrap - opacity: radioButton.opacity - } - } -} diff --git a/src/qml/common/InfoItem.qml b/src/qml/common/FocusBar.qml similarity index 63% copy from src/qml/common/InfoItem.qml copy to src/qml/common/FocusBar.qml index d61c4e5..3113f1b 100644 --- a/src/qml/common/InfoItem.qml +++ b/src/qml/common/FocusBar.qml @@ -1,24 +1,34 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 -QtObject { - property string title - property string text -} +Rectangle { + property Item control + + height: 2 + color: control.colorScheme.focusDecoration + opacity: control.activeFocus? 1: 0 + Behavior on opacity { + NumberAnimation { + duration: 150 + } + } +} diff --git a/src/qml/common/GridView.qml b/src/qml/common/GridView.qml new file mode 100644 index 0000000..a1749c8 --- /dev/null +++ b/src/qml/common/GridView.qml @@ -0,0 +1,62 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +GridView { + id: control + + property alias colorScheme: colorScheme + property alias background: backgroundItem + + KColorScheme { + id: colorScheme + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.View + } + + Rectangle { + id: backgroundItem + anchors.fill: parent + color: colorScheme.normalBackground + z: -1 + } + + FocusBar { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: 3 + control: control + } + + FocusBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 3 + control: control + } + + Controls.ScrollBar.vertical: ScrollBar { } +} diff --git a/src/qml/common/InfoItem.qml b/src/qml/common/Icon.qml similarity index 81% copy from src/qml/common/InfoItem.qml copy to src/qml/common/Icon.qml index d61c4e5..85b3ec3 100644 --- a/src/qml/common/InfoItem.qml +++ b/src/qml/common/Icon.qml @@ -1,24 +1,22 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import org.kde.kquickcontrolsaddons 2.0 as Addons -QtObject { - property string title - property string text -} +Addons.QIconItem { +} diff --git a/src/qml/common/IconButton.qml b/src/qml/common/IconButton.qml new file mode 100644 index 0000000..c3d2c98 --- /dev/null +++ b/src/qml/common/IconButton.qml @@ -0,0 +1,88 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import ktouch 1.0 +import QtGraphicalEffects 1.0 + +Button { + id: button + + property alias color: content.color + property alias bgColor: bg.color + property alias iconName: content.iconName + property alias colorScheme: buttonColorScheme + + + padding: 0 + + hoverEnabled: true + + KColorScheme { + id: buttonColorScheme + colorGroup: button.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.Button + } + + contentItem: IconLabel { + color: buttonColorScheme.normalText + id: content + text: button.text + elide: "ElideNone" + + Behavior on color { + ColorAnimation { duration: 150 } + } + + } + + background: Item { + Rectangle { + anchors.fill: parent; + id: bg + color: buttonColorScheme.alternateBackground + + HueSaturation { + anchors.fill: bg + source: bg + saturation: hovered? 0.3: 0 + lightness: hovered? -0.04: 0 + Behavior on saturation { + NumberAnimation { + duration: 150 + } + } + Behavior on lightness { + NumberAnimation { + duration: 150 + } + } + } + } + + FocusBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 3 + control: button + } + } +} diff --git a/src/qml/common/IconLabel.qml b/src/qml/common/IconLabel.qml new file mode 100644 index 0000000..0be50ac --- /dev/null +++ b/src/qml/common/IconLabel.qml @@ -0,0 +1,44 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 + +Label { + property string iconName: "" + property bool reserveSpaceForIcon: false + id: label + elide: Text.ElideRight + + padding: Math.ceil(0.7 * font.pixelSize) + leftPadding: (iconItem.visible || reserveSpaceForIcon? padding + iconItem.width: 0) + (label.text !== ""? padding: 0) + verticalAlignment: Text.AlignVCenter + + MonochromeIcon { + id: iconItem + visible: label.iconName != "" + color: label.color + anchors { + left: parent.left + leftMargin: label.text === ""? (label.width - width) / 2: label.padding + verticalCenter: parent.verticalCenter + } + icon: label.iconName + width: 22 + height: 22 + } +} diff --git a/src/qml/common/IconToolButton.qml b/src/qml/common/IconToolButton.qml new file mode 100644 index 0000000..d54d6f5 --- /dev/null +++ b/src/qml/common/IconToolButton.qml @@ -0,0 +1,68 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import ktouch 1.0 + +ToolButton { + id: button + + property alias color: content.color + property alias iconName: content.iconName + property color backgroundColor: button.colorScheme.normalBackground + + property KColorScheme colorScheme: KColorScheme { + id: buttonColorScheme + colorGroup: button.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.Button + } + + padding: 0 + + hoverEnabled: true + + contentItem: IconLabel { + id: content + text: button.text + color: button.colorScheme.normalText + elide: "ElideNone" + } + + background: Item { + Rectangle { + anchors.fill: parent + opacity: (button.checked? 0.6: 0) + (button.activeFocus || button.hovered? 0.3: 0) + color: button.backgroundColor + Behavior on opacity { + NumberAnimation { + duration: 150 + } + } + } + + FocusBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 3 + control: button + } + } +} diff --git a/src/qml/common/InfoItem.qml b/src/qml/common/InfoItem.qml index d61c4e5..9b143fb 100644 --- a/src/qml/common/InfoItem.qml +++ b/src/qml/common/InfoItem.qml @@ -1,24 +1,24 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 QtObject { property string title property string text } diff --git a/src/qml/common/InformationTable.qml b/src/qml/common/InformationTable.qml index 19da428..c4aadc1 100644 --- a/src/qml/common/InformationTable.qml +++ b/src/qml/common/InformationTable.qml @@ -1,65 +1,65 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 import ktouch 1.0 Item { property alias model: repeator.model id: root height: childrenRect.height Column { id: column spacing: 3 width: parent.width height: childrenRect.height Repeater { id: repeator Row { spacing: 5 height: Math.max(titleLabel.height, valueLabel.height) width: root.width Label { id: titleLabel width: Math.round((parent.width - parent.spacing) / 2) horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignTop color: "#888" text: model.modelData.title wrapMode: Text.Wrap height: Math.max(paintedHeight, valueLabel.paintedHeight) } Label { id: valueLabel width: parent.width - titleLabel.width - parent.spacing horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignBottom text: model.modelData.text wrapMode: Text.Wrap height: Math.max(paintedHeight, titleLabel.paintedHeight) } } } } } diff --git a/src/qml/common/InlineToolbar.qml b/src/qml/common/InlineToolbar.qml index 4202560..46e3447 100644 --- a/src/qml/common/InlineToolbar.qml +++ b/src/qml/common/InlineToolbar.qml @@ -1,55 +1,55 @@ /* * Copyright 2013 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 Item { id: root property alias content: contentArea.data property alias color: background.color width: contentArea.width + 6 height: contentArea.height + 6 Rectangle { anchors.fill: parent id: background color: activePalette.mid opacity: 0.7 radius: 3 SystemPalette { id: activePalette colorGroup: SystemPalette.Active } } Behavior on opacity { NumberAnimation { duration: 250 } } Row { id: contentArea anchors.centerIn: parent height: childrenRect.height spacing: 3 } } diff --git a/src/qml/common/InfoItem.qml b/src/qml/common/Label.qml similarity index 61% copy from src/qml/common/InfoItem.qml copy to src/qml/common/Label.qml index d61c4e5..ed213c2 100644 --- a/src/qml/common/InfoItem.qml +++ b/src/qml/common/Label.qml @@ -1,24 +1,34 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 -QtObject { - property string title - property string text -} +Controls.Label { + id: control + + property alias colorScheme: colorScheme + KColorScheme { + id: colorScheme + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.Window + } + + color: colorScheme.normalText +} diff --git a/src/qml/common/LearningProgressChart.qml b/src/qml/common/LearningProgressChart.qml index 99a03c0..5921891 100644 --- a/src/qml/common/LearningProgressChart.qml +++ b/src/qml/common/LearningProgressChart.qml @@ -1,63 +1,115 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import org.kde.charts 0.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import org.kde.charts 0.1 as Charts import ktouch 1.0 -LineChart { - id: chart +Charts.LineChart { + id: root - property Dimension accuracy: accuracyDimension - property Dimension charactersPerMinute: charactersPerMinuteDimension + property Charts.Dimension accuracy: accuracyDimension + property Charts.Dimension charactersPerMinute: charactersPerMinuteDimension pitch: 60 function minAccuracy(accuracy) { var canditades = [0.9, 0.8, 0.5] for (var i = 0; i < canditades.length; i++) { if (canditades[i] < accuracy) { return (canditades[i]) } } return 0; } dimensions: [ - Dimension { + Charts.Dimension { id: accuracyDimension dataColumn: 5 color: "#ffb12d" - minimumValue: chart.minAccuracy(model.minAccuracy) + minimumValue: root.minAccuracy(model.minAccuracy) maximumValue: 1.0 label: i18n("Accuracy") unit: "%" unitFactor: 100 }, - Dimension { + Charts.Dimension { id: charactersPerMinuteDimension dataColumn: 6 color: "#38aef4" maximumValue: Math.max(Math.ceil(model.maxCharactersTypedPerMinute / 120) * 120, 120) label: i18n("Characters per Minute") } ] + + onElemEntered: { + learningProgressPointTooltip.visualParent = elem + learningProgressPointTooltip.row = row + learningProgressPointTooltip.open() + } + + onElemExited: { + learningProgressPointTooltip.close() + } + + Balloon { + id: learningProgressPointTooltip + property int row: -1 + + function findLessonTitle(id) { + var course = model.courseFilter + if (course) { + for (var i = 0; i < course.lessonCount; i++) { + if (course.lesson(i).id === id) { + return course.lesson(i).title + } + } + } + return i18n("Unknown") + } + + InformationTable { + property list infoModel: [ + InfoItem { + title: i18nc("Statistics on lesson:", "Lesson:") + text: learningProgressPointTooltip.row !== -1? learningProgressPointTooltip.findLessonTitle(learningProgressModel.lessonId(learningProgressPointTooltip.row)): "" + }, + InfoItem { + title: i18n("Training on:") + text: learningProgressPointTooltip.row !== -1? + learningProgressModel.date(learningProgressPointTooltip.row).toLocaleString(): + "" + }, + InfoItem { + title: i18n("Accuracy:") + text: learningProgressPointTooltip.row !== -1? strFormatter.formatAccuracy(learningProgressModel.accuracy(learningProgressPointTooltip.row)): "" + }, + InfoItem { + title: i18n("Characters per Minute:") + text: learningProgressPointTooltip.row !== -1? learningProgressModel.charactersPerMinute(learningProgressPointTooltip.row): "" + } + ] + width: 250 + model: infoModel + } + } + } diff --git a/src/qml/common/ListItem.qml b/src/qml/common/ListItem.qml index 2185b57..d114ab4 100644 --- a/src/qml/common/ListItem.qml +++ b/src/qml/common/ListItem.qml @@ -1,100 +1,63 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import org.kde.kquickcontrolsaddons 2.0 +import QtQuick 2.9 +import QtQuick.Controls 2.2 import ktouch 1.0 -Item { +ItemDelegate { id: root - property alias title: label.text - property string iconSource + property string iconName property alias label: label - property bool isCurrent: ListView.isCurrentItem; - signal clicked - signal doubleClicked - height: 2 * content.height + property alias bg: bg + property alias reserveSpaceForIcon: label.reserveSpaceForIcon + hoverEnabled: true + padding: 0 + + KColorScheme { + id: listItemColorSchemeNormal + colorGroup: KColorScheme.Active + colorSet: KColorScheme.View + } - SystemPalette { - id: listItemPallete - colorGroup: SystemPalette.Active + KColorScheme { + id: listItemColorSchemeHighlighted + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Selection } - Rectangle { + background: Rectangle { id: bg anchors.fill: parent - color: Qt.rgba(listItemPallete.highlight.r, listItemPallete.highlight.g, listItemPallete.highlight.b, 0.3) - radius: 0.2 * height - border { - width: 1 - color: listItemPallete.highlight - } - opacity: root.isCurrent || mouseArea.containsMouse? 1: 0 + color: listItemColorSchemeHighlighted.normalBackground + opacity: root.highlighted ? 1 : (root.hovered? 0.3: 0) Behavior on opacity { NumberAnimation { duration: 150 - } - } - } - - Item { - id: content - anchors { - verticalCenter: parent.verticalCenter - left: parent.left - right: parent.right - leftMargin: 5 - rightMargin: 5 - } - - height: Math.max(label.height, label.height) - - QIconItem { - id: iconItem - visible: !!root.iconSource - anchors { - left: parent.left - verticalCenter: parent.verticalCenter - } - icon: root.iconSource - width: 22 - height: 22 - } - - Label { - id: label - elide: Text.ElideRight - anchors { - left: iconItem.visible? iconItem.right: parent.left - right: parent.right - verticalCenter: parent.verticalCenter } } } - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: root.clicked() - onDoubleClicked: root.doubleClicked() + contentItem: IconLabel { + id: label + text: root.text + iconName: root.iconName + color: root.highlighted? listItemColorSchemeHighlighted.activeText: listItemColorSchemeNormal.normalText; } } diff --git a/src/qml/common/ListView.qml b/src/qml/common/ListView.qml new file mode 100644 index 0000000..48284b7 --- /dev/null +++ b/src/qml/common/ListView.qml @@ -0,0 +1,64 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +ListView { + id: control + + property alias colorScheme: colorScheme + + activeFocusOnTab: true + + KColorScheme { + id: colorScheme + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.View + } + + Rectangle { + anchors.fill: parent + color: colorScheme.normalBackground + z: -1 + } + + FocusBar { + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: 3 + control: control + z: -1 + } + + FocusBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: 3 + control: control + z: -1 + } + + Controls.ScrollBar.vertical: ScrollBar { } +} diff --git a/src/qml/common/MessageBox.qml b/src/qml/common/MessageBox.qml index b3021aa..39af6c2 100644 --- a/src/qml/common/MessageBox.qml +++ b/src/qml/common/MessageBox.qml @@ -1,124 +1,111 @@ /* - * Copyright 2012 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried + * Copyright 2018 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import org.kde.kquickcontrolsaddons 2.0 +import QtQuick 2.9 + +import "../common" Rectangle { id: item anchors.verticalCenter: parent.verticalCenter radius: 3 color: "#eee4be" - height: layout.height + 6 - width: layout.width + 6 + height: label.height + 6 + width: label.width + 6 smooth: true function showMessage(msg, iconSource) { item.state = "hidden"; label.text = msg - icon.icon = iconSource || "" + label.iconName = iconSource || "" item.state = "normal" } function clearMessage() { item.state = "cleared" } function clearMessageImmediately() { item.state = "hidden" } border { width: 1 color: "#decd87" } transform: Scale { id: itemScaler origin.x: 0 origin.y: 0 } state: "hidden" - Row { - id: layout + IconLabel { anchors.centerIn: parent - width: icon.width + spacing + label.width - spacing: icon.valid? 3: 0 - - QIconItem { - id: icon - width: height - height: icon? label.height: 0 - } - - Label { - id: label - color: "#000000" - } + id: label + padding: 2 } states: [ State { name: "normal" PropertyChanges { target: itemScaler xScale: 1 } }, State { name: "hidden" PropertyChanges { target: itemScaler xScale: 0 } }, State { name: "cleared" PropertyChanges { target: itemScaler xScale: 0 } } ] transitions: [ Transition { from: "*" to: "normal" NumberAnimation { target: itemScaler property: "xScale" duration: 500 easing.type: Easing.InOutQuad } }, Transition { from: "normal" to: "cleared" NumberAnimation { target: itemScaler property: "xScale" duration: 500 easing.type: Easing.InOutQuad } } ] } diff --git a/src/qml/common/InfoItem.qml b/src/qml/common/MonochromeIcon.qml similarity index 69% copy from src/qml/common/InfoItem.qml copy to src/qml/common/MonochromeIcon.qml index d61c4e5..25008a7 100644 --- a/src/qml/common/InfoItem.qml +++ b/src/qml/common/MonochromeIcon.qml @@ -1,24 +1,30 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtGraphicalEffects 1.0 -QtObject { - property string title - property string text +Icon { + property color color: "#000000" + id: icon + width: 24 + height: 24 + ColorOverlay { + anchors.fill: parent + source: icon + color: parent.color + } } - diff --git a/src/qml/common/PopupDialog.qml b/src/qml/common/PopupDialog.qml new file mode 100644 index 0000000..65c80fb --- /dev/null +++ b/src/qml/common/PopupDialog.qml @@ -0,0 +1,144 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import ktouch 1.0 +import QtGraphicalEffects 1.0 + +Dialog { + id: root + dim: true + + opacity: 0 + scale: 0.9 + leftMargin: Math.floor((parent.width - width) / 2) + topMargin: Math.floor((parent.height - height) / 2) + + Component.onCompleted: { + var candidate = root + while (candidate.parent) { + candidate = candidate.parent + } + if (candidate) { + root.parent = candidate + } + } + + onOpened: { + contentItem.forceActiveFocus() + } + + Item { + id: dimOverlay + parent: root.parent + width: root.parent.width + height: root.parent.height + visible: root.visible && root.dim + opacity: 0 + + ShaderEffectSource { + id: effectSource + sourceItem: root.parent.appContent + anchors.fill: parent + hideSource: false + } + + HueSaturation { + id: desaturatedBackground + source: effectSource + anchors.fill: parent + lightness: -0.3 + saturation: -0.5 + visible: false + } + + FastBlur { + anchors.fill: parent + source: desaturatedBackground + radius: 50 + } + + Rectangle { + anchors.fill: parent + color: "#55000000" + } + } + + enter: Transition { + // grow_fade_in + NumberAnimation { property: "scale"; to: 1.0; easing.type: Easing.OutQuint; duration: 220 } + NumberAnimation { property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: 150 } + NumberAnimation { target: dimOverlay; property: "opacity"; to: 1.0; easing.type: Easing.OutCubic; duration: 220 } + } + + exit: Transition { + // shrink_fade_out + NumberAnimation { property: "scale"; to: 0.9; easing.type: Easing.OutQuint; duration: 220 } + NumberAnimation { property: "opacity"; to: 0.0; easing.type: Easing.OutCubic; duration: 150 } + NumberAnimation { target: dimOverlay; property: "opacity"; to: 0.0; easing.type: Easing.OutCubic; duration: 220 } + } + + background: Rectangle { + color: dialogColorScheme.normalBackground + + KColorScheme { + id: dialogColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Window + } + } + + header: Rectangle { + implicitHeight: Math.max(titleLabel.implicitHeight, closeButton.implicitHeight) + color: toolbarColorScheme.toolbarBackground + + KColorScheme { + id: toolbarColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Complementary + property color toolbarBackground: Qt.darker(toolbarColorScheme.shade(toolbarColorScheme.hoverDecoration, KColorScheme.MidShade, toolbarColorScheme.contrast, -0.2), 1.3) + } + + RowLayout { + anchors.fill: parent + + Label { + id: titleLabel + text: root.title + width: parent.width + color: toolbarColorScheme.normalText + font.bold: true + padding: font.pixelSize + Layout.fillWidth: true + } + + IconToolButton { + id: closeButton + iconName: "window-close-symbolic" + color: toolbarColorScheme.normalText + visible: root.closePolicy & Popup.CloseOnEscape + backgroundColor: toolbarColorScheme.normalBackground + activeFocusOnTab: false + Layout.preferredWidth: titleLabel.implicitHeight + Layout.preferredHeight: titleLabel.implicitHeight + onClicked: root.close() + } + } + } +} diff --git a/src/qml/common/RadioButton.qml b/src/qml/common/RadioButton.qml new file mode 100644 index 0000000..0913feb --- /dev/null +++ b/src/qml/common/RadioButton.qml @@ -0,0 +1,93 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import ktouch 1.0 + +RadioButton { + id: control + + hoverEnabled: true + spacing: label.font.pixelSize + padding: 0 + + property alias colorScheme: colorScheme + property alias selectionColorScheme: selectionColorScheme + property alias label: label + + KColorScheme { + id: colorScheme + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.Button + } + + KColorScheme { + id: selectionColorScheme + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.Selection + } + + indicator: Rectangle { + implicitWidth: control.font.pixelSize + implicitHeight: control.font.pixelSize + x: control.leftPadding + y: parent.height / 2 - height / 2 + radius: height / 2 + + border.color: control.checked? + Qt.lighter(colorScheme.focusDecoration, control.hovered? 1.3: 1.0): + utils.alpha(colorScheme.normalText, control.hovered? 0.9: 0.5) + + color: "#00000000" + + Behavior on border.color { + ColorAnimation { + duration: 150 + } + } + + Rectangle { + anchors.fill: parent + anchors.margins: 3 + radius: height / 2 + color: Qt.lighter(colorScheme.focusDecoration, control.hovered? 1.3: 1.0) + visible: control.checked + } + } + + contentItem: Label { + id: label + text: control.text + color: control.colorScheme.normalText + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + topPadding: 3 + leftPadding: control.indicator.width + control.spacing + bottomPadding: 3 + + FocusBar { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + leftMargin: parent.leftPadding + } + control: control + } + } +} diff --git a/src/qml/common/ScrollBar.qml b/src/qml/common/ScrollBar.qml new file mode 100644 index 0000000..0dee2ae --- /dev/null +++ b/src/qml/common/ScrollBar.qml @@ -0,0 +1,50 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +Controls.ScrollBar { + id: control + + property alias colorScheme: colorScheme + + KColorScheme { + id: colorScheme + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet: KColorScheme.Window + } + + contentItem: Rectangle { + implicitWidth: control.orientation == Qt.Horizontal? 100: 6 + implicitHeight: control.orientation == Qt.Horizontal? 6: 100 + radius: 3 + color: control.pressed ? colorScheme.focusDecoration: colorScheme.alternateBackground + opacity: control.active? 1: 0.3 + Behavior on opacity { + NumberAnimation { + duration: 150 + } + } + Behavior on color { + ColorAnimation { + duration: 150 + } + } + } +} diff --git a/src/qml/common/ScrollView.qml b/src/qml/common/ScrollView.qml new file mode 100644 index 0000000..52b0527 --- /dev/null +++ b/src/qml/common/ScrollView.qml @@ -0,0 +1,42 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +Controls.ScrollView { + id: control + + Controls.ScrollBar.vertical: ScrollBar { + parent: control + x: control.mirrored ? 0 : control.width - width + y: control.topPadding + height: control.availableHeight + active: control.ScrollBar.horizontal.active + visible: size < 1 + } + + Controls.ScrollBar.horizontal: ScrollBar { + parent: control + x: control.leftPadding + y: control.height - height + width: control.availableWidth + active: control.ScrollBar.vertical.active + visible: size < 1 + } +} diff --git a/src/qml/common/SelectionGrip.qml b/src/qml/common/SelectionGrip.qml index 1ac3808..68147ab 100644 --- a/src/qml/common/SelectionGrip.qml +++ b/src/qml/common/SelectionGrip.qml @@ -1,119 +1,119 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Item { id: root property KeyboardLayout keyboardLayout property variant target: null property variant targetItem: null property string horizontalPosition property string verticalPosition property bool interactive property bool xLocked: false property bool yLocked: false property int minSize: 20 x: targetItem? targetItem.x + (horizontalPosition == "left"? 0: targetItem.width - 1): 0 y: targetItem? targetItem.y + (verticalPosition == "top"? 0: targetItem.height - 1): 0 onXChanged: { if (!xLocked && mouseArea.drag.active) { xLocked = true var effX = Math.round(x / scaleFactor) if (horizontalPosition == "left") { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, effX, target.top, target.width + target.left - effX, target.height) } else { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, target.left, target.top, effX - target.left, target.height) } xLocked = false } } onYChanged: { if (!yLocked && mouseArea.drag.active) { yLocked = true var effY = Math.round(y / scaleFactor) if (verticalPosition == "top") { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, target.left, effY, target.width, target.height + target.top - effY) } else { keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, target.left, target.top, target.width, effY - target.top) } yLocked = false } } width: 1 height: 1 SystemPalette { id: palette colorGroup: SystemPalette.Active } Rectangle { anchors { centerIn: parent verticalCenterOffset: verticalPosition == "top"? -10: 10 horizontalCenterOffset: horizontalPosition == "left"? -10: 10 } width: 15 height: 15 color: palette.highlight visible: interactive MouseArea { id: mouseArea anchors.fill: parent cursorShape: ((horizontalPosition == "left") != (verticalPosition == "top"))? Qt.SizeBDiagCursor: Qt.SizeFDiagCursor onClicked: { mouse.accepted = true } drag { target: root axis: Drag.XandYAxis minimumX: !targetItem? 0: horizontalPosition == "left"? 0: targetItem.x + scaleFactor * minSize maximumX: !targetItem? 0: horizontalPosition == "left"? targetItem.x + targetItem.width - minSize: keyboardLayout.width * scaleFactor minimumY: !targetItem? 0: verticalPosition == "top"? 0: targetItem.y + scaleFactor * minSize maximumY: !targetItem? 0: verticalPosition == "top"? targetItem.y + targetItem.height - minSize: keyboardLayout.height * scaleFactor onActiveChanged: { targetItem.manipulated = drag.active if (!drag.active) { var left = 10 * Math.round(target.left / 10) var top = 10 * Math.round(target.top / 10) var width = 10 * Math.round(target.width / 10) var height = 10 * Math.round(target.height / 10) keyboardLayoutEditor.setKeyGeometry(targetItem.keyIndex, left, top, width, height) } } } } } } diff --git a/src/qml/common/SelectionRectangle.qml b/src/qml/common/SelectionRectangle.qml index 941289e..67fa21e 100644 --- a/src/qml/common/SelectionRectangle.qml +++ b/src/qml/common/SelectionRectangle.qml @@ -1,55 +1,55 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Item { id: root; property KeyboardLayout keyboardLayout property variant target: null property bool interactive: true property variant targetItem: findKeyItem(target) anchors.fill: parent visible: !!targetItem function findKeyItem(key) { for (var i = 0; i < keys.count; i++) { var keyItem = keys.itemAt(i) if (keyItem.key == key) { return keyItem } } return null; } Repeater { model: 4 delegate: SelectionGrip { keyboardLayout: root.keyboardLayout target: root.target targetItem: root.targetItem interactive: root.interactive horizontalPosition: index % 2 == 0? "left": "right" verticalPosition: index < 2? "top": "bottom" } } } diff --git a/src/qml/common/SheetDialog.qml b/src/qml/common/SheetDialog.qml deleted file mode 100644 index babfffc..0000000 --- a/src/qml/common/SheetDialog.qml +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2012 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import ktouch 1.0 - -Item { - id: root - - SystemPalette { - id: activePalette - colorGroup: SystemPalette.Active - } - - property alias content: contentArea.children - property int innerMargin: 20 - signal opened - signal closed - - function open() { - root.state = "open" - opened() - } - - function close() { - root.state = "closed" - closed() - } - - function isOpen() { - return root.state == "open" - } - - clip: true - visible: bg.opacity > 0 - - /* swallow all mouse events */ - MouseArea { - anchors.fill: parent - hoverEnabled: true - } - - Rectangle { - id: bg - anchors.fill: parent - color: activePalette.window - opacity: 1.0 - - Behavior on opacity { - NumberAnimation { - duration: 300 - } - } - } - - Rectangle { - id: slider - color: activePalette.window - width: parent.width - height: parent.height - - Item { - id: contentArea - anchors { - fill: parent - } - } - } - - state: "closed" - - states: [ - State { - name: "open" - PropertyChanges { target: bg; opacity: 1.0 } - PropertyChanges { target: slider; y: 0 } - }, - State { - name: "closed" - PropertyChanges { target: bg; opacity: 0 } - PropertyChanges { target: slider; y: -slider.parent.height } - } - ] - - transitions: [ - Transition { - from: "*" - to: "*" - NumberAnimation { target: slider; property: "y"; duration: 300; easing.type: Easing.InOutQuad } - } - ] -} diff --git a/src/qml/common/TextArea.qml b/src/qml/common/TextArea.qml new file mode 100644 index 0000000..8e3c60b --- /dev/null +++ b/src/qml/common/TextArea.qml @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +Controls.TextArea { + id: control + + property KColorScheme colorScheme: KColorScheme { + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet:KColorScheme.View + } + + property KColorScheme selectionColorScheme: KColorScheme { + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet:KColorScheme.Selection + } + + selectByMouse: true + selectByKeyboard: true + color: control.colorScheme.normalText + selectionColor: selectionColorScheme.normalBackground + selectedTextColor: selectionColorScheme.normalText + + background: Rectangle { + color: control.colorScheme.normalBackground + anchors.fill: parent + border.width: 1 + border.color: control.activeFocus? + control.colorScheme.focusDecoration: + Qt.hsva(control.colorScheme.focusDecoration.hslHue, + 0, + control.colorScheme.focusDecoration.hslValue, + control.enabled? 0.5: 0.2) + Behavior on border.color { + ColorAnimation { duration: 150 } + } + } + + +} diff --git a/src/qml/common/TextField.qml b/src/qml/common/TextField.qml new file mode 100644 index 0000000..2e77952 --- /dev/null +++ b/src/qml/common/TextField.qml @@ -0,0 +1,52 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 + +Controls.TextField { + id: control + + property KColorScheme colorScheme: KColorScheme { + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet:KColorScheme.View + } + + property KColorScheme selectionColorScheme: KColorScheme { + colorGroup: control.enabled? KColorScheme.Active: KColorScheme.Disabled + colorSet:KColorScheme.Selection + } + + selectByMouse: true + selectionColor: selectionColorScheme.normalBackground + selectedTextColor: selectionColorScheme.normalText + + background: Rectangle { + color: control.colorScheme.normalBackground + border.width: 1 + border.color: control.activeFocus? + control.colorScheme.focusDecoration: + Qt.hsva(control.colorScheme.focusDecoration.hslHue, + 0, + control.colorScheme.focusDecoration.hslValue, + control.enabled? 0.5: 0.2) + Behavior on border.color { + ColorAnimation { duration: 150 } + } + } +} diff --git a/src/bindings/utils.h b/src/qml/common/ToolBar.qml similarity index 50% copy from src/bindings/utils.h copy to src/qml/common/ToolBar.qml index df79847..33b5146 100644 --- a/src/bindings/utils.h +++ b/src/qml/common/ToolBar.qml @@ -1,38 +1,38 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef UTILS_H -#define UTILS_H +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 -#include -#include -#include +Controls.ToolBar { + id: control -class Utils : public QObject -{ - Q_OBJECT -public: - explicit Utils(QObject* parent = 0); - Q_INVOKABLE QUrl findImage(const QString &name); - Q_INVOKABLE int getMinutesOfQTime(const QTime& time); - Q_INVOKABLE int getSecondsOfQTime(const QTime& time); - Q_INVOKABLE QString uuid(); + property alias colorScheme: colorScheme + property real dimFactor: 1.3 -}; + background: Rectangle { + color: colorScheme.toolbarBackground + } - -#endif // UTILS_H + KColorScheme { + id: colorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Complementary + property color toolbarBackground: Qt.darker(colorScheme.shade(colorScheme.hoverDecoration, KColorScheme.MidShade, colorScheme.contrast, -0.2), control.dimFactor) + } +} diff --git a/src/bindings/utils.h b/src/qml/common/ToolSeparator.qml similarity index 51% copy from src/bindings/utils.h copy to src/qml/common/ToolSeparator.qml index df79847..f3fa665 100644 --- a/src/bindings/utils.h +++ b/src/qml/common/ToolSeparator.qml @@ -1,38 +1,41 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef UTILS_H -#define UTILS_H +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 -#include -#include -#include +Controls.ToolSeparator { + id: control -class Utils : public QObject -{ - Q_OBJECT -public: - explicit Utils(QObject* parent = 0); - Q_INVOKABLE QUrl findImage(const QString &name); - Q_INVOKABLE int getMinutesOfQTime(const QTime& time); - Q_INVOKABLE int getSecondsOfQTime(const QTime& time); - Q_INVOKABLE QString uuid(); + property alias colorScheme: colorScheme -}; + KColorScheme { + id: colorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Complementary + } + padding: vertical? 6: 2 + topPadding: vertical? 2: 6 + bottomPadding: vertical? 2: 6 - -#endif // UTILS_H + contentItem: Rectangle { + implicitWidth: control.vertical? 1: 24 + implicitHeight: control.vertical? 24: 1 + color: control.colorScheme.normalText + } +} diff --git a/src/bindings/utils.h b/src/qml/common/ToolTip.qml similarity index 52% copy from src/bindings/utils.h copy to src/qml/common/ToolTip.qml index df79847..d07f9a8 100644 --- a/src/bindings/utils.h +++ b/src/qml/common/ToolTip.qml @@ -1,38 +1,43 @@ /* - * Copyright 2012 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -#ifndef UTILS_H -#define UTILS_H +import QtQuick 2.9 +import QtQuick.Controls 2.2 as Controls +import ktouch 1.0 -#include -#include -#include +Controls.ToolTip { + id: control -class Utils : public QObject -{ - Q_OBJECT -public: - explicit Utils(QObject* parent = 0); - Q_INVOKABLE QUrl findImage(const QString &name); - Q_INVOKABLE int getMinutesOfQTime(const QTime& time); - Q_INVOKABLE int getSecondsOfQTime(const QTime& time); - Q_INVOKABLE QString uuid(); + property alias colorScheme: colorScheme -}; + KColorScheme { + id: colorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Complementary + } + contentItem: Text { + text: control.text + font: control.font + color: colorScheme.normalText + } -#endif // UTILS_H + background: Rectangle { + radius: height / 6 + color: control.colorScheme.normalBackground + } +} diff --git a/src/qml/homescreen/CourseDescriptionItem.qml b/src/qml/homescreen/CourseDescriptionItem.qml index 1ac8a7c..c27d1d2 100644 --- a/src/qml/homescreen/CourseDescriptionItem.qml +++ b/src/qml/homescreen/CourseDescriptionItem.qml @@ -1,84 +1,50 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 import ktouch 1.0 -Rectangle { +import "../common" + +Collapsable { id: root property string description - property bool active: false - - SystemPalette { - id: palette - colorGroup: SystemPalette.Active - } - - height: active || content.opacity > 0? content.height: 0 - visible: height > 0 - color: palette.base - - Behavior on height { - NumberAnimation { - duration: 150 - easing.type: Easing.InOutQuad - } - } onDescriptionChanged: { - if (content.opacity === 0) { + if (contentItem.opacity === 0) { descriptionLabel.text = description } else { - content.needsUpdate = true + swapContent() } } - Item { - id: content - - property bool needsUpdate: false - - width: parent.width - height: descriptionLabel.height + 6 - opacity: root.active && !content.needsUpdate && root.height === root.childrenRect.height? 1: 0 - - Behavior on opacity { - NumberAnimation { - duration: 150 - } - } - - onOpacityChanged: { - if (needsUpdate && opacity === 0) { - descriptionLabel.text = root.description - needsUpdate = false - } - } + onContentReadyForSwap: { + descriptionLabel.text = description + } - Label { - id: descriptionLabel - anchors.centerIn: parent - width: parent.width - 10 - } + Label { + id: descriptionLabel + leftPadding: 20 + rightPadding: 20 + topPadding: 10 + bottomPadding: 10 } } diff --git a/src/qml/homescreen/CoursePage.qml b/src/qml/homescreen/CoursePage.qml deleted file mode 100644 index 8a0c82a..0000000 --- a/src/qml/homescreen/CoursePage.qml +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright 2012 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import ktouch 1.0 - -Item { - id: root - - property Profile profile - property DataIndexCourse dataIndexCourse - property KeyboardLayout keyboardLayout - property string keyboardLayoutName - signal lessonSelected(variant course, variant lesson) - - property Course course: lessonSelector.visible? lessonSelector.course: customLessonSelector.course - - function getCourse() { - return !!dataIndexCourse? lessonSelector.course: customLessonSelector.course - } - - width: parent.width - height: parent.height - visible: false - opacity: 0 - - function showImmediately() { - root.x = 0 - root.opacity = 1 - root.visible = 1 - } - - function show(direction) { - appearAnimation.stop() - disappearAnimation.stop() - appearAnimation.direction = direction - appearAnimation.start() - } - - function hide(direction) { - appearAnimation.stop() - disappearAnimation.stop() - disappearAnimation.direction = direction - disappearAnimation.start() - } - - - QtObject { - id: priv - property int duration: 250 - } - - LessonSelector { - id: lessonSelector - - anchors.fill: parent - visible: !!dataIndexCourse - - profile: root.profile - dataIndexCourse: root.dataIndexCourse - onLessonSelected: root.lessonSelected(course, lesson) - } - - CustomLessonSelector { - id: customLessonSelector - anchors.fill: parent - visible: !root.dataIndexCourse - profile: root.profile - keyboardLayout: root.keyboardLayout - keyboardLayoutName: root.keyboardLayoutName - onLessonSelected: root.lessonSelected(course, lesson) - } - - SequentialAnimation { - id: appearAnimation - property int direction: Item.Left - PropertyAction { - target: root - property: "visible" - value: true - } - ParallelAnimation { - NumberAnimation { - target: root - property: "x" - from: appearAnimation.direction === Item.Left? root.width: -root.width - to: 0 - easing.type: Easing.InQuad - duration: priv.duration - } - NumberAnimation { - target: root - property: "opacity" - from: 0 - to: 1 - easing.type: Easing.InQuad - duration: priv.duration - } - } - } - - SequentialAnimation { - id: disappearAnimation - property int direction: Item.Left - ParallelAnimation { - NumberAnimation { - target: root - property: "x" - to: disappearAnimation.direction === Item.Left? -root.width: root.width - easing.type: Easing.InQuad - duration: priv.duration - } - NumberAnimation { - target: root - property: "opacity" - to: 0 - easing.type: Easing.InQuad - duration: priv.duration - } - } - PropertyAction { - target: root - property: "visible" - value: false - } - } -} diff --git a/src/qml/homescreen/CourseSelector.qml b/src/qml/homescreen/CourseSelector.qml index a8d6761..f2bfb84 100644 --- a/src/qml/homescreen/CourseSelector.qml +++ b/src/qml/homescreen/CourseSelector.qml @@ -1,218 +1,192 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtQuick.Controls 2.2 as Controls import ktouch 1.0 -Item { +import "../common" + +FocusScope { id: root - property CategorizedResourceSortFilterProxyModel courseModel property Profile profile - property KeyboardLayout keyboardLayout - property string keyboardLayoutName + property string currentKeyboardLayoutName + property string selectedKeyboardLayoutName + property DataIndexKeyboardLayout selectedKeyboardLayout + property DataIndexCourse selectedCourse signal lessonSelected(variant course, variant lesson) + signal courseSelectec(Course course) function selectLastUsedCourse() { if (!profile) { return } var courseId = profile.lastUsedCourseId; - if (courseId === "custom_lessons") { - selectCourse(courseRepeater.count, true) - return - } - - for (var i = 0; i < courseModel.rowCount(); i++) { - var dataIndexCourse = courseModel.data(courseModel.index(i, 0), ResourceModel.DataRole); + // fist try to to select the course the user has used last + for (var i = 0; i < allCoursesModel.rowCount(); i++) { + var dataIndexCourse = allCoursesModel.data(allCoursesModel.index(i, 0), ResourceModel.DataRole); if (dataIndexCourse.id === courseId) { - selectCourse(i, true) + root.selectedCourse = dataIndexCourse return } } - selectCourse(0, true) - } - - function selectCourse(index, automaticSelection) { - if (index === priv.currentIndex) { - return + // if this fails try to select course matching the current keyboard layout + if (coursesForCurrentKeyboardLayoutModel.rowCount() > 0) { + var blub = coursesForCurrentKeyboardLayoutModel.data(coursesForCurrentKeyboardLayoutModel.index(0, 0), ResourceModel.DataRole); + console.log(blub) + root.selectedCourse = blub + return; } - var direction = index > priv.currentIndex? Item.Left: Item.Right - var dataIndexCourse = index < courseModel.rowCount()? - courseModel.data(courseModel.index(index, 0), ResourceModel.DataRole): - null; - var targetPage = automaticSelection? coursePageContainer.activePage: coursePageContainer.inactivePage + // finally just select the first course + if (allCoursesModel.rowCount() > 0) { + root.selectedCourse = allCoursesModel.data(allCoursesModel.index(0, 0), ResourceModel.DataRole); + } + } - priv.currentIndex = index; - targetPage.dataIndexCourse = dataIndexCourse + onSelectedCourseChanged: { + root.selectedKeyboardLayoutName = root.selectedCourse.keyboardLayoutName; - if (!automaticSelection) { - coursePageContainer.inactivePage = coursePageContainer.activePage - coursePageContainer.activePage = targetPage - coursePageContainer.inactivePage.hide(direction) - coursePageContainer.activePage.show(direction) + for (var i = 0; i < ktouch.globalDataIndex.keyboardLayoutCount; i++) + { + var dataIndexLayout = ktouch.globalDataIndex.keyboardLayout(i) - saveLastUsedCourse(dataIndexCourse? dataIndexCourse.id: "custom_lessons") + if (dataIndexLayout.name === root.selectedKeyboardLayoutName) { + root.selectedKeyboardLayout = dataIndexLayout; + return + } } + + root.selectedKeyboardLayout = null; } - function saveLastUsedCourse(courseId) { - profile.lastUsedCourseId = courseId; - profileDataAccess.updateProfile(profileDataAccess.indexOfProfile(profile)); + function saveLastUsedCourse(course) { + if (profile.lastUsedCourseId != course.id) { + profile.lastUsedCourseId = course.id; + profileDataAccess.updateProfile(profileDataAccess.indexOfProfile(profile)); + } } onProfileChanged: selectLastUsedCourse() - Connections { - target: courseModel + ResourceModel { + id: resourceModel + dataIndex: ktouch.globalDataIndex onRowsRemoved: { - nextButton.visible = previousButton.visible = courseModel.rowCount() > 1 - priv.currentIndex = -1 selectLastUsedCourse() } onRowsInserted: { - nextButton.visible = previousButton.visible = courseModel.rowCount() > 1 - priv.currentIndex = -1 selectLastUsedCourse() } } - QtObject { - id: priv - property int currentIndex: -1 + CategorizedResourceSortFilterProxyModel { + id: allCoursesModel + resourceModel: resourceModel + resourceTypeFilter: ResourceModel.CourseItem } - SystemPalette { - id: palette - colorGroup: SystemPalette.Active - } - ColumnLayout { - anchors.fill: parent - spacing: 0 - - Rectangle { - id: head - Layout.fillWidth: true - height: Math.ceil(Math.max(courseTitleLabel.height, courseDescriptionButton.height) + 6) - color: palette.base - - RowLayout { - anchors { - fill: parent - leftMargin: 5 - rightMargin: 5 - topMargin: 3 - bottomMargin: 3 - } + CategorizedResourceSortFilterProxyModel { + id: coursesForCurrentKeyboardLayoutModel + resourceModel: resourceModel + resourceTypeFilter: ResourceModel.CourseItem + keyboardLayoutNameFilter: root.currentKeyboardLayoutName + } - Label { - anchors.verticalCenter: parent.verticalCenter - id: courseTitleLabel - font.pointSize: 1.5 * Qt.font({'family': 'sansserif'}).pointSize - text: coursePageContainer.activePage.course.title - } + CategorizedResourceSortFilterProxyModel { + id: currentKeyboardLayoutsModel + resourceModel: resourceModel + resourceTypeFilter: ResourceModel.KeyboardLayoutItem + keyboardLayoutNameFilter: root.currentKeyboardLayoutName + } - Item { - id: smallSpacer - height: parent.height - width: 3 - } + CategorizedResourceSortFilterProxyModel { + id: otherKeyboardLayoutsModel + resourceModel: resourceModel + resourceTypeFilter: ResourceModel.KeyboardLayoutItem + keyboardLayoutNameFilter: root.currentKeyboardLayoutName + invertedKeyboardLayoutNameFilter: true + } - ToolButton { - id: courseDescriptionButton - anchors.verticalCenter: parent.verticalCenter - iconName: "dialog-information" - checkable: true - } + KColorScheme { + id: courseSelectorColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.View + } - Item { - Layout.fillWidth: true - Layout.fillHeight: true - } + Rectangle { + id: bg + anchors.fill: parent + color: courseSelectorColorScheme.normalBackground + } - ToolButton { - id: previousButton - anchors.verticalCenter: parent.verticalCenter - iconName: "arrow-left" - enabled: priv.currentIndex > 0 - onClicked: { - var newIndex = priv.currentIndex - 1 - root.selectCourse(newIndex, false) - } - } + Flickable { + clip: true + anchors.fill: parent + contentWidth: width + contentHeight: content.height + Column { + id: content + width: parent.width - ToolButton { - id: nextButton - iconName: "arrow-right" - enabled: priv.currentIndex < courseModel.rowCount() - onClicked: { - var newIndex = (priv.currentIndex + 1) % (courseModel.rowCount() + 1) - root.selectCourse(newIndex, false) - } + CourseSelectorKeyboardLayoutList { + width: parent.width + title: i18n('Courses For Your Keyboard Layout') + model: currentKeyboardLayoutsModel + resourceModel: resourceModel + colorScheme: courseSelectorColorScheme + selectedKeyboardLayoutName: root.selectedKeyboardLayoutName + selectedCourse: root.selectedCourse + onCourseSelected: { + root.selectedCourse = course + root.saveLastUsedCourse(course) } } - } - Item { - Layout.fillWidth: true - Layout.minimumHeight: courseDescriptionItem.height - Layout.maximumHeight: courseDescriptionItem.height - CourseDescriptionItem { - id: courseDescriptionItem - active: courseDescriptionButton.checked - description: coursePageContainer.activePage.course.description + CourseSelectorKeyboardLayoutList { width: parent.width + title: i18n('Other Courses') + model: otherKeyboardLayoutsModel + resourceModel: resourceModel + colorScheme: courseSelectorColorScheme + selectedKeyboardLayoutName: root.selectedKeyboardLayoutName + selectedCourse: root.selectedCourse + onCourseSelected: { + root.selectedCourse = course + root.saveLastUsedCourse(course) + } } } - Item { - id: coursePageContainer - property CoursePage activePage: page0 - property CoursePage inactivePage: page1 - - Layout.fillWidth: true - Layout.fillHeight: true - - CoursePage { - id: page0 - profile: root.profile - keyboardLayout: root.keyboardLayout - keyboardLayoutName: root.keyboardLayoutName - onLessonSelected: root.lessonSelected(course, lesson) - Component.onCompleted: page0.showImmediately() - } - - CoursePage { - id: page1 - profile: root.profile - keyboardLayout: root.keyboardLayout - keyboardLayoutName: root.keyboardLayoutName - onLessonSelected: root.lessonSelected(course, lesson) - } - } + Controls.ScrollBar.vertical: ScrollBar { } } + + + + + + } diff --git a/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml b/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml new file mode 100644 index 0000000..4d75e95 --- /dev/null +++ b/src/qml/homescreen/CourseSelectorKeyboardLayoutItem.qml @@ -0,0 +1,116 @@ +/* + * Copyright 2012 Sebastian Gottfried + * Copyright 2015 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import ktouch 1.0 + +import "../common" + +Column { + id: root + property string name + property alias title: keyboardLayoutItem.text + property ResourceModel resourceModel + property string selectedKeyboardLayoutName + property DataIndexCourse selectedCourse + + signal courseSelected(DataIndexCourse course) + + height: keyboardLayoutItem.height + (loader.active? loader.height: 0) + clip: true + + onSelectedKeyboardLayoutNameChanged: { + if (selectedKeyboardLayoutName == root.name) { + loader.active = true + } + } + + CategorizedResourceSortFilterProxyModel { + id: courseModel + resourceModel: root.resourceModel + resourceTypeFilter: ResourceModel.CourseItem + keyboardLayoutNameFilter: loader.keyboardLayoutNameFilter + } + + ListItem { + id: keyboardLayoutItem + iconName: "input-keyboard" + width: parent.width + onClicked: { + loader.active = !loader.active + if (loader.active) { + if (courseModel.rowCount()) { + courseSelected(courseModel.data(courseModel.index(0, 0), ResourceModel.DataRole)) + } + } + } + } + + Loader { + id: loader + width: parent.width + active: false + property string keyboardLayoutNameFilter: root.name + sourceComponent: Component { + id: courseSelectionComponent + + Column { + Repeater { + id: courseRepeater + model: courseModel + ListItem { + text: dataRole.title + width: parent.width + reserveSpaceForIcon: true + highlighted: root.selectedCourse == dataRole + onClicked: { + courseSelected(dataRole) + } + } + } + ListItem { + DataIndexCourse { + id: customLessonsCourse + title: i18n("Custom Lessons") + keyboardLayoutName: root.name + Component.onCompleted: { + id = "custom_lessons" + } + } + + text: customLessonsCourse.title + id: ownLessonsItem + reserveSpaceForIcon: true + width: parent.width + highlighted: root.selectedCourse == customLessonsCourse + onClicked: { + courseSelected(customLessonsCourse) + } + } + } + } + } + + Behavior on height { + NumberAnimation { + duration: 150 + easing.type: Easing.InOutQuad + } + } +} + diff --git a/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml b/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml new file mode 100644 index 0000000..b371841 --- /dev/null +++ b/src/qml/homescreen/CourseSelectorKeyboardLayoutList.qml @@ -0,0 +1,57 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import ktouch 1.0 +import '../common' + +Column { + id: root + property alias title: header.text + property CategorizedResourceSortFilterProxyModel model + property ResourceModel resourceModel + property string selectedKeyboardLayoutName + property DataIndexCourse selectedCourse: null + property KColorScheme colorScheme + signal courseSelected(DataIndexCourse course) + + ListItem { + id: header + width: parent.width + font.bold: true + bg.color: colorScheme.alternateBackground + bg.opacity: 1 + label.opacity: 0.7 + } + + Repeater { + id: repeater + model: root.model + CourseSelectorKeyboardLayoutItem { + width: parent.width + name: keyboardLayoutName + title: display + resourceModel: root.resourceModel + selectedKeyboardLayoutName: root.selectedKeyboardLayoutName + selectedCourse: root.selectedCourse + onCourseSelected: { + root.courseSelected(course) + } + } + } + +} diff --git a/src/qml/homescreen/CustomLessonSelector.qml b/src/qml/homescreen/CustomLessonSelector.qml deleted file mode 100644 index 870ed3c..0000000 --- a/src/qml/homescreen/CustomLessonSelector.qml +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright 2013 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import ktouch 1.0 - -import "../common" - -Item { - id: root - property Profile profile - property KeyboardLayout keyboardLayout - property string keyboardLayoutName - property alias course: courseItem - signal lessonSelected(variant course, variant lesson) - - function update() { - if (!profile) - return - profileDataAccess.loadCustomLessons(profile, keyboardLayoutName, courseItem) - } - - QtObject { - id: internal - property variant deletedLessons: [] - } - - function selectLastLesson() { - if (!course.isValid) - return; - var lessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastSelectedLesson); - if (lessonId !== "") { - for (var index = 0; index < course.lessonCount; index++) { - if (course.lesson(index).id === lessonId) { - lessonList.currentIndex = index - break - } - } - } - } - - function createNewLesson() { - tmpLesson.id = utils.uuid() - tmpLesson.title = "" - tmpLesson.text = "" - if (ktouch.showCustomLessonDialog(tmpLesson, root.keyboardLayout)) { - profileDataAccess.storeCustomLesson(tmpLesson, root.profile, keyboardLayoutName) - update() - lessonList.currentIndex = lessonList.count - 2 - } - } - - function editLesson() { - tmpLesson.copyFrom(base.selectedLesson) - if (ktouch.showCustomLessonDialog(tmpLesson, root.keyboardLayout)) { - profileDataAccess.storeCustomLesson(tmpLesson, root.profile, keyboardLayoutName) - update() - } - } - - function deleteLesson() { - var deletedLessons = internal.deletedLessons - var lesson = Qt.createQmlObject("import ktouch 1.0; Lesson{}", internal, "lesson") - lesson.copyFrom(base.selectedLesson) - deletedLessons.push(lesson) - base.selectedLesson = null - profileDataAccess.deleteCustomLesson(lesson.id) - update() - internal.deletedLessons = deletedLessons - } - - function undoLessonDeletion() { - var deletedLessons = internal.deletedLessons - var lesson = deletedLessons.pop() - internal.deletedLessons = deletedLessons - profileDataAccess.storeCustomLesson(lesson, root.profile, keyboardLayoutName) - update() - } - - function confirmLessonDeletion() { - var deletedLessons = internal.deletedLessons - var lesson = deletedLessons.pop() - internal.deletedLessons = deletedLessons - } - - function updateSelectedLesson() { - if (lessonList.currentIndex !== -1) { - base.selectedLesson = lessonList.currentItem.lesson - } - else { - base.selectedLesson = null - } - } - - onProfileChanged: update() - onKeyboardLayoutNameChanged: update() - - Course { - id: courseItem - } - - Lesson { - id: tmpLesson - } - - LessonSelectorBase { - id: base - - anchors.fill: parent - - list: ScrollView { - width: 500 - height: 500 - ListView { - id: lessonList - anchors.fill: parent - - model: course.isValid? course.lessonCount + 1: 0 - - clip: true - - delegate: ListItem { - property Lesson lesson: index < course.lessonCount? course.lesson(index): null - property bool isNewButton: index == course.lessonCount - width: lessonList.width - onClicked: { - lessonList.currentIndex = index - if (isNewButton) { - createNewLesson() - } - } - onDoubleClicked: { - if (!isNewButton) { - lessonSelected(course, lesson) - } - } - title: isNewButton? i18n("Create New Custom Lesson"): (lesson? lesson.title: "") - iconSource: isNewButton? "list-add": "" - label.font.italic: isNewButton - } - - onCurrentIndexChanged: updateSelectedLesson() - - onModelChanged: selectLastLesson() - } - } - - InlineToolbar { - id: lessonToolbar - parent: base.previewArea - anchors { - top: parent.top - horizontalCenter: parent.horizontalCenter - topMargin: 5 - } - opacity: base.selectedLesson !== null? 1: 0 - content: [ - ToolButton { - iconName: "document-edit" - text: i18n("Edit") - onClicked: editLesson() - }, - ToolButton { - iconName: "edit-delete" - text: i18n("Delete") - onClicked: deleteLesson() - } - ] - } - - InlineToolbar { - id: undoToolbar - property Lesson lastDeletedLesson: internal.deletedLessons.length > 0? internal.deletedLessons[internal.deletedLessons.length - 1]: null - property string lastDeletedLessonTitle: "" - onLastDeletedLessonChanged: { - if (!!lastDeletedLesson) { - lastDeletedLessonTitle = lastDeletedLesson.title - } - } - - parent: screen - color: "#eee4be" - anchors { - top: parent.top - horizontalCenter: parent.horizontalCenter - topMargin: 15 - } - opacity: !!lastDeletedLesson? 1: 0 - content: [ - Label { - text: i18n("'%1' deleted.", undoToolbar.lastDeletedLessonTitle) - anchors.verticalCenter: parent.verticalCenter - }, - Item { - width: 5 - height: 1 - }, - ToolButton { - iconName: "edit-undo" - text: i18n("Undo") - onClicked: undoLessonDeletion() - }, - ToolButton { - iconName: "dialog-close" - text: i18n("Dismiss") - onClicked: confirmLessonDeletion() - } - ] - } - - selectedLesson: null - selectedLessonLocked: false - onStartButtonClicked: lessonSelected(course, lessonList.currentItem.lesson) - } -} diff --git a/src/qml/homescreen/HomeScreen.qml b/src/qml/homescreen/HomeScreen.qml index 6c84623..57e7214 100644 --- a/src/qml/homescreen/HomeScreen.qml +++ b/src/qml/homescreen/HomeScreen.qml @@ -1,173 +1,147 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 import ktouch 1.0 import "../common" FocusScope { id: screen - property CategorizedResourceSortFilterProxyModel courseModel - property KeyboardLayout keyboardLayout - property string keyboardLayoutName + property KeyboardLayout selectedKeyboardLayout: KeyboardLayout {} + property string activeKeyboardLayoutName signal lessonSelected(variant course, variant lesson, variant profile) - QtObject { - id: d - - property Profile profile - property int profileCount: profileDataAccess.profileCount - + Connections { + target: profileDataAccess onProfileCountChanged: findCurrentProfile() } function start() {} function reset() { profileDataAccess.loadProfiles(); } function findCurrentProfile() { - d.profile = null + profileComboBox.profile = null var lastProfileId = preferences.lastUsedProfileId for (var i = 0; i < profileDataAccess.profileCount; i++) { var profile = profileDataAccess.profile(i) if (profile.id === lastProfileId) { - d.profile = profile + profileComboBox.profile = profile return; } } if (profileDataAccess.profileCount > 0) { - d.profile = profileDataAccess.profile(0) - preferences.lastUsedProfileId = d.profile.id + profileComboBox.profile = profileDataAccess.profile(0) + preferences.lastUsedProfileId = profileComboBox.profile.id preferences.writeConfig() } } - function switchToProfile(profile) { - d.profile = profile + function safeLastUsedProfile(profile) { preferences.lastUsedProfileId = profile.id preferences.writeConfig() } - ColumnLayout { + RowLayout { anchors.fill: parent spacing: 0 - ToolBar { - visible: courseSelector.opacity > 0 - id: header - Layout.fillWidth: true - - RowLayout { - anchors.fill: parent - anchors.leftMargin: 3 - anchors.rightMargin: 3 - spacing: 5 - - Button { - // TODO: Find a better control here which supports both an icon and a label - id: profileButton - iconName: "user-identity" - text: d.profile !== null? d.profile.name: "" - onClicked: { - if (checked) { - profileSelectorSheet.open() - } - else { - profileSelectorSheet.close() - } - } - checkable: true - } - - Item { - Layout.fillWidth: true - } - - ToolButton { - id: configureButton - iconName: "configure" - onClicked: { - var position = mapToItem(null, 0, height) - ktouch.showMenu(position.x, position.y) - } - } - } - } - Item { - id: content - Layout.fillWidth: true + id: navigationArea + z: 2 + Layout.preferredWidth: 300 Layout.fillHeight: true - CourseSelector { - id: courseSelector - opacity: 1 - initialProfileForm.opacity - courseModel: screen.courseModel - profile: d.profile - keyboardLayout: screen.keyboardLayout - keyboardLayoutName: screen.keyboardLayoutName - anchors.fill: parent - onLessonSelected: screen.lessonSelected(course, lesson, d.profile) + DropShadow { + anchors.fill: navigationAreaLayout + source: navigationAreaLayout + samples: 16 + horizontalOffset: 0 + verticalOffset: 0 } - InitialProfileForm { - id: initialProfileForm - opacity: profileDataAccess.profileCount == 0? 1: 0 + ColumnLayout { + id: navigationAreaLayout anchors.fill: parent - anchors.margins: 5 + spacing: 0 - Behavior on opacity { - NumberAnimation { - duration: screen.visible? 500: 0 - easing.type: Easing.InOutCubic - } - } - } + ToolBar { + id: header - SheetDialog { - id: profileSelectorSheet - anchors.fill: parent - onOpened: { - if (d.profile) { - var index = profileDataAccess.indexOfProfile(d.profile) - profileSelector.selectProfile(index) + Layout.fillWidth: true + dimFactor: 1.3 + + RowLayout { + anchors.fill: parent + spacing: 5 + + + ProfileComboBox { + id: profileComboBox + colorScheme: header.colorScheme + manageProfileButtonBgColor: header.colorScheme.toolbarBackground + Layout.fillHeight: true + Layout.preferredWidth: 300 + Layout.fillWidth: true + onActivated: { + safeLastUsedProfile(profile) + } + } } } - onClosed: { - profileButton.checked = false; - } - content: ProfileSelector { - id: profileSelector - anchors.fill: parent - onProfileChosen: { - screen.switchToProfile(profile) - profileSelectorSheet.close() + + CourseSelector { + id: courseSelector + Layout.fillHeight: true + Layout.fillWidth: true + profile: profileComboBox.profile + currentKeyboardLayoutName: screen.activeKeyboardLayoutName + onSelectedKeyboardLayoutChanged: { + dataAccess.loadKeyboardLayout(courseSelector.selectedKeyboardLayout, screen.selectedKeyboardLayout) } } + } } + + LessonSelector { + Layout.fillHeight: true + Layout.fillWidth: true + profile: profileComboBox.profile + selectedKeyboardLayout: screen.selectedKeyboardLayout + activeKeyboardLayoutName: screen.activeKeyboardLayoutName + dataIndexCourse: courseSelector.selectedCourse + onLessonSelected: screen.lessonSelected(course, lesson, profileComboBox.profile) + z: 1 + focus: true + } + } + + InitialProfileDialog { + id: initialProfileForm + visible: profileDataAccess.profileCount == 0 } } diff --git a/src/qml/homescreen/InitialProfileForm.qml b/src/qml/homescreen/InitialProfileDialog.qml similarity index 62% rename from src/qml/homescreen/InitialProfileForm.qml rename to src/qml/homescreen/InitialProfileDialog.qml index da1e609..cb0eedc 100644 --- a/src/qml/homescreen/InitialProfileForm.qml +++ b/src/qml/homescreen/InitialProfileDialog.qml @@ -1,56 +1,46 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 -FocusScope { +import '../common' +PopupDialog { id: root + modal: true + title: i18n("Welcome to Typewriting Trainer") + closePolicy: PopupDialog.NoAutoClose + padding: 20 function save() { var profile = profileDataAccess.createProfile() profile.name = form.name profile.skillLevel = form.skillLevel profileDataAccess.addProfile(profile) + close() } - GroupBox { - id: frame - anchors.centerIn: parent - title: i18n("Welcome to Typewriting Trainer") - width: form.width + 150 - height: form.height + 80 - - Item { - id: content - anchors.fill: parent - - ProfileForm { - id: form - width: 400 - anchors.centerIn: parent - doneButtonIconSource: "go-next-view" - doneButtonText: i18n("Start Training") - onDone: save() - } - } + contentItem: ProfileForm { + id: form + doneButtonIconSource: "go-next-view" + doneButtonText: i18n("Start Training") + onDone: save() } } diff --git a/src/qml/homescreen/KeyboardLayoutMismatchMessage.qml b/src/qml/homescreen/KeyboardLayoutMismatchMessage.qml new file mode 100644 index 0000000..30b36b0 --- /dev/null +++ b/src/qml/homescreen/KeyboardLayoutMismatchMessage.qml @@ -0,0 +1,81 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import ktouch 1.0 + +import "../common" + +Collapsable { + id: root + + background: Rectangle { + color: colorScheme.neutralBackground + } + + Item { + implicitWidth: root.width + implicitHeight: layout.implicitHeight + 20 + + GridLayout { + id: layout + anchors.centerIn: parent + width: root.width - 40 + rowSpacing: label.font.pixelSize + columnSpacing: 10 + + Icon { + Layout.column: 0 + Layout.row: 0 + width: 32 + height: 32 + icon: "dialog-warning" + } + + Label { + id: label + Layout.column: 1 + Layout.row: 0 + Layout.fillWidth: true + font.bold: true + wrapMode: Text.Wrap + text: i18n("The selected course doesn't match your computer's active keyboard layout.") + } + Label { + Layout.column: 1 + Layout.row: 1 + Layout.columnSpan: configureKeyboardButton.visible? 2: 1 + Layout.fillWidth: true + wrapMode: Text.Wrap + text: i18n("KTouch can't switch or set up keyboard layouts. Before training, you have to configure your computer to use the correct keyboard layout or you will have to use your current keyboard layout to type the lesson text.") + opacity: 0.7 + } + + IconButton { + id: configureKeyboardButton + Layout.column: 2 + Layout.row: 0 + visible: ktouch.keyboardKCMAvailable + text: i18n("Configure Keyboard") + onClicked: { + ktouch.showKeyboardKCM(); + } + } + } + } +} diff --git a/src/qml/homescreen/LessonDeletedMessage.qml b/src/qml/homescreen/LessonDeletedMessage.qml new file mode 100644 index 0000000..8b424a4 --- /dev/null +++ b/src/qml/homescreen/LessonDeletedMessage.qml @@ -0,0 +1,80 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import ktouch 1.0 + +import "../common" + +Collapsable { + id: root + + collapsed: true + + property Lesson deletedLesson: Lesson {} + signal undeleteRequested() + property bool completed: false + + onVisibleChanged: { + if (!visible && completed) { + destroy(); + } + } + + Component.onCompleted: { + root.collapsed = false; + root.completed = true; + } + + Item { + implicitWidth: root.width + implicitHeight: layout.implicitHeight + 20 + + RowLayout { + id: layout + width: root.width - 40 + anchors.centerIn: parent + + Label { + text: root.deletedLesson.title? + i18n("Lesson %1 deleted.", root.deletedLesson.title): + i18n("Lesson without title deleted.") + wrapMode: Text.Wrap + Layout.fillWidth: true + } + + IconButton { + iconName: "edit-undo" + text: i18n("Undo") + onClicked: { + root.undeleteRequested(); + root.collapsed = true; + } + } + + AutoTriggerButton { + id: okButton + iconName: "dialog-ok" + text: i18n("Confirm") + onClicked: { + root.collapsed = true; + } + } + } + } +} diff --git a/src/qml/homescreen/LessonEditorDialog.qml b/src/qml/homescreen/LessonEditorDialog.qml new file mode 100644 index 0000000..72ce79e --- /dev/null +++ b/src/qml/homescreen/LessonEditorDialog.qml @@ -0,0 +1,109 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import ktouch 1.0 + +import '../common' + +PopupDialog { + id: root + + property Lesson lesson: Lesson {} + property KeyboardLayout keyboardLayout: KeyboardLayout {} + property Profile profile: Profile {} + + onClosed: { + profileDataAccess.storeCustomLesson(root.lesson, root.profile, root.keyboardLayout.name) + } + + title: i18n("Edit lesson") + + margins: { + left: 40 + bottom: 40 + right: 40 + top: 40 + } + + width: parent.width - leftMargin - rightMargin + height: parent.height - topMargin - bottomMargin + + padding: titleLabel.font.pixelSize + + contentItem: GridLayout { + + columnSpacing: titleLabel.font.pixelSize + rowSpacing: titleLabel.font.pixelSize + + Label { + id: titleLabel + text: i18n("Title:") + Layout.row: 0 + Layout.column: 0 + } + + TextField { + id: titleTextField + text: root.lesson? root.lesson.title: "" + onTextChanged: { + if (root.lesson) { + root.lesson.title = text + } + } + Layout.row: 0 + Layout.column: 1 + Layout.fillWidth: true + } + + ScrollView { + Layout.row: 1 + Layout.column: 0 + Layout.columnSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + + TextArea { + id: lessonTextArea + text: root.lesson? root.lesson.text: "" + onTextChanged: { + if (root.lesson) { + root.lesson.text = text + } + } + placeholderText: i18n("Lesson text") + font.family: "monospace" + + LessonTextHighlighter { + document: lessonTextArea.textDocument + allowedCharacters: root.visible? keyboardLayout.allCharacters(): "" + } + } + } + } + + footer: IconButton { + text: i18n("Done") + iconName: "dialog-ok" + bgColor: colorScheme.positiveBackground + onClicked: { + root.close() + } + + } +} diff --git a/src/qml/homescreen/LessonLockedNotice.qml b/src/qml/homescreen/LessonLockedNotice.qml index 7bf65dc..3ab0ad8 100644 --- a/src/qml/homescreen/LessonLockedNotice.qml +++ b/src/qml/homescreen/LessonLockedNotice.qml @@ -1,91 +1,79 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import QtGraphicalEffects 1.0 -import org.kde.kquickcontrolsaddons 2.0 import ktouch 1.0 +import '../common' Item { id: root - width: content.width + 40 - height: content.height + 40 + width: content.width + height: content.height - property alias blurSource: effectSource.sourceItem + property color glowColor: "#ffffff" - SystemPalette { + KColorScheme { id: palette - colorGroup: SystemPalette.Active - } - - ShaderEffectSource { - id: effectSource - anchors.fill: parent - hideSource: false - sourceRect: Qt.rect(root.x, root.y, root.width, root.height) - } - - FastBlur { - anchors.fill: parent - source: effectSource - radius: 25 - } - - Rectangle { - id: background - anchors.fill: parent - color: palette.base - opacity: 0.3 - radius: 15 - } - - Behavior on opacity { - NumberAnimation { - duration: 200 - } + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Window } Column { id: content anchors.centerIn: parent width: Math.max(icon.width, text.width) spacing: 10 - QIconItem { + Icon { id: icon anchors.horizontalCenter: parent.horizontalCenter icon: "object-locked" width: 128 height: 128 } - Label { - id: text - text: i18n("Complete Previous Lessons to Unlock") + Rectangle { anchors.horizontalCenter: parent.horizontalCenter - horizontalAlignment: Text.AlignHCenter - font.weight: Font.Bold - wrapMode: Text.Wrap + width: text.width + 2 * text.font.pixelSize + height: text.height + text.font.pixelSize + radius: text.font.pixelSize + color: palette.neutralBackground + + Label { + anchors.centerIn: parent + id: text + text: i18n("Complete Previous Lessons to Unlock") + horizontalAlignment: Text.AlignHCenter + font.weight: Font.Bold + wrapMode: Text.Wrap + } } } + + Glow { + anchors.fill: content + source: content + color: root.glowColor + samples: 25 + } } diff --git a/src/qml/homescreen/LessonPreview.qml b/src/qml/homescreen/LessonPreview.qml deleted file mode 100644 index 034bd18..0000000 --- a/src/qml/homescreen/LessonPreview.qml +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2013 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import ktouch 1.0 - -Item { - id: item - - property Lesson lesson - property int margin: 30 - - onLessonChanged: swap() - - Connections { - target: lesson - onTextChanged: swap() - onTitleChanged: swap() - } - - function swap() { - if (!lesson) - return - swapPreviewAnimation.stop() - swapPreviewAnimation.from = sheet.lessonPainter == lessonPainter1? lessonPainter1: lessonPainter2 - swapPreviewAnimation.to = sheet.lessonPainter == lessonPainter1? lessonPainter2: lessonPainter1 - swapPreviewAnimation.start() - } - - Rectangle { - id: sheet - - property LessonPainter lessonPainter: lessonPainter1 - - anchors.centerIn: parent - width: lessonPainter.width - height: lessonPainter.height - opacity: lesson? 1: 0 - - Behavior on width { - enabled: swapPreviewAnimation.running - NumberAnimation { - duration: sheet.opacity == 1? 100: 0 - } - } - - Behavior on height { - enabled: swapPreviewAnimation.running - NumberAnimation { - duration: sheet.opacity == 1? 100: 0 - } - } - - Behavior on opacity { - NumberAnimation { - duration: 200 - } - } - - border { - width: 1 - color: "#000" - } - - LessonPainter { - id: lessonPainter1 - opacity: 1 - maximumWidth: item.width - maximumHeight: item.height - lesson: lesson1 - - Lesson { - id: lesson1 - } - } - - LessonPainter { - id: lessonPainter2 - maximumWidth: item.width - maximumHeight: item.height - opacity: 0 - lesson: lesson2 - - Lesson { - id: lesson2 - } - } - } - - SequentialAnimation { - id: swapPreviewAnimation - property LessonPainter from: lessonPainter1 - property LessonPainter to: lessonPainter2 - NumberAnimation { - target: swapPreviewAnimation.from - property: "opacity" - to: 0 - duration: sheet.opacity == 1? 100: 0 - } - ScriptAction { - script: { - swapPreviewAnimation.to.lesson.copyFrom(lesson) - sheet.lessonPainter = swapPreviewAnimation.to - } - } - PauseAnimation { duration: sheet.opacity == 1? 100: 0 } - NumberAnimation { - target: swapPreviewAnimation.to - property: "opacity" - to: 1 - duration: sheet.opacity == 1? 100: 0 - } - } -} diff --git a/src/qml/homescreen/LessonSelector.qml b/src/qml/homescreen/LessonSelector.qml index 0609caa..f7bf538 100644 --- a/src/qml/homescreen/LessonSelector.qml +++ b/src/qml/homescreen/LessonSelector.qml @@ -1,124 +1,391 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 +import QtGraphicalEffects 1.0 import "../common" -Item { - id: item +FocusScope { + id: root property Profile profile property DataIndexCourse dataIndexCourse + property KeyboardLayout selectedKeyboardLayout + property string activeKeyboardLayoutName property alias course: courseItem - signal lessonSelected(variant course, variant lesson) + property Lesson selectedLesson: null + signal lessonSelected(Course course, Lesson lesson) function update() { if (!course.isValid) return; if (!profile) return; + course.updateLastUnlockedLessonIndex(); selectLastLesson() - enableUnlockedLessons() } function selectLastLesson() { var lessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastSelectedLesson); if (lessonId !== "") { for (var index = 0; index < course.lessonCount; index++) { if (course.lesson(index).id === lessonId) { - lessonList.currentIndex = index; - break; + root.selectedLesson = course.lesson(index) + content.currentIndex = index + return } } } - } - - function enableUnlockedLessons() { - if (profile.skillLevel === Profile.Advanced) { - lessonList.lastUnlockedIndex = course.lessonCount - 1; - return; - } - - lessonList.lastUnlockedIndex = 0; - var lessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastUnlockedLesson); - if (lessonId !== "") { - for (var index = 0; index < course.lessonCount; index++) { - if (course.lesson(index).id === lessonId) { - lessonList.lastUnlockedIndex = index; - break; - } - } + if (course.lessonCount > 0) { + root.selectedLesson = course.lesson(0) + content.currentIndex = 0 } } onProfileChanged: update() onDataIndexCourseChanged: { - course.update() - update() + root.selectedLesson = null; + course.update(); + root.update(); } Course { id: courseItem + property int lastUnlockedLessonIndex: -1 + property bool editable: courseItem.isValid && courseItem.id === "custom_lessons" function update() { - if (item.dataIndexCourse === null) + if (root.dataIndexCourse === null) { return - if (isValid && courseItem.id === dataIndexCourse.id) + } + if (dataIndexCourse.id == "custom_lessons") { + profileDataAccess.loadCustomLessons(root.profile, dataIndexCourse.keyboardLayoutName, courseItem) + } + else { + if (isValid && courseItem.id === dataIndexCourse.id) { + return + } + dataAccess.loadCourse(dataIndexCourse, courseItem) + } + } + function updateLastUnlockedLessonIndex() { + lastUnlockedLessonIndex = 0; + if (course.kind == Course.LessonCollection || profile.skillLevel === Profile.Advanced) { + lastUnlockedLessonIndex = course.lessonCount - 1; return - dataAccess.loadCourse(dataIndexCourse, courseItem) + } + var lastUnlockedLessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastUnlockedLesson); + if (lastUnlockedLessonId !== "") { + for (var index = 0; index < course.lessonCount; index++) { + lastUnlockedLessonIndex = index + if (course.lesson(index).id === lastUnlockedLessonId) { + return + } + } + } + } + + function createNewCustomLesson() { + var lesson = ktouch.createLesson(); + lesson.id = utils.uuid() + profileDataAccess.storeCustomLesson(lesson, root.profile, root.selectedKeyboardLayout.name) + course.addLesson(lesson) + updateLastUnlockedLessonIndex() + content.currentIndex = course.indexOfLesson(lesson) + root.selectedLesson = lesson + lessonEditorDialog.open() + } + + function deleteCustomLesson() { + var msgItem = lessonDeletedMessageComponent.createObject(header); + msgItem.deletedLesson.copyFrom(selectedLesson); + profileDataAccess.deleteCustomLesson(selectedLesson.id); + course.removeLesson(course.indexOfLesson(selectedLesson)); + root.selectedLesson = null; + content.currentIndex = -1; } + + function restoreCustomLesson(lesson) { + course.addLesson(lesson); + profileDataAccess.storeCustomLesson(lesson, root.profile, root.selectedKeyboardLayout.name) + content.currentIndex = course.indexOfLesson(lesson); + updateLastUnlockedLessonIndex(); + } + Component.onCompleted: update() } - LessonSelectorBase { + StatPopupDialog { + id: statPopupDialog + profile: root.profile + course: courseItem + lesson: root.selectedLesson + } + + LessonEditorDialog { + id: lessonEditorDialog + profile: root.profile + keyboardLayout: root.selectedKeyboardLayout + lesson: root.selectedLesson + } + + ColumnLayout { anchors.fill: parent + spacing: 0 + + Item { + Layout.fillWidth: true + Layout.preferredHeight: header.height + z: 2 + + Column { + id: header + width: parent.width + + ToolBar { + id: toolbar + width: parent.width + dimFactor: 1.5 + + RowLayout { + anchors.fill: parent + anchors.leftMargin: 20 + spacing: 5 + + Label { + text: root.course? root.course.title: "" + font.bold: true + color: toolbar.colorScheme.normalText + } + + IconToolButton { + id: toggleCourseDesciptionButton + iconName: "help-about" + checkable: true + color: toolbar.colorScheme.normalText + backgroundColor: toolbar.colorScheme.normalBackground + Layout.fillHeight: true + Layout.preferredWidth: toolbar.height + } + + ToolSeparator { + visible: courseItem.editable + } + + IconToolButton { + id: newLessonButton + iconName: "document-new" + text: "Add New Lesson" + color: toolbar.colorScheme.normalText + backgroundColor: toolbar.colorScheme.normalBackground + visible: courseItem.editable + Layout.fillHeight: true + onClicked: { + course.createNewCustomLesson() + } + } + + Item { + Layout.fillWidth: true + } + + IconToolButton { + id: configureButton + iconName: "application-menu" + color: toolbar.colorScheme.normalText + backgroundColor: toolbar.colorScheme.normalBackground + Layout.fillHeight: true + Layout.preferredWidth: toolbar.height + onClicked: { + var position = mapToItem(null, 0, height) + ktouch.showMenu(position.x, position.y) + } + } + } + } + + CourseDescriptionItem { + id: courseDescriptionItem + width: parent.width + collapsed: !toggleCourseDesciptionButton.checked + description: courseItem.description + } - list:ScrollView { - anchors.fill: parent - ListView { + KeyboardLayoutMismatchMessage { + width: parent.width + collapsed: !root.course || !root.course.isValid || root.activeKeyboardLayoutName == root.course.keyboardLayoutName + } + + Component { + id: lessonDeletedMessageComponent + LessonDeletedMessage { + width: parent.width + onUndeleteRequested: { + course.restoreCustomLesson(deletedLesson) + } + } + } + } + + DropShadow { + anchors.fill: header + source: header + samples: 16 + horizontalOffset: 0 + verticalOffset: 0 + } + } + + Item { + Layout.fillHeight: true + Layout.fillWidth: true + z: 1 + + Rectangle { + anchors.fill: parent + color: content.colorScheme.shade(content.colorScheme.normalBackground, KColorScheme.DarkShade, 1, 0.0) + } + + + GridView { + id: content anchors.fill: parent - id: lessonList - property int lastUnlockedIndex: 0 - model: course.isValid? course.lessonCount: 0 - spacing: 3 clip: true - delegate: ListItem { - property Lesson lesson: index < course.lessonCount? course.lesson(index): null - property bool locked: index > lessonList.lastUnlockedIndex - width: lessonList.width - onClicked: lessonList.currentIndex = index - onDoubleClicked: { - if (!locked) { - lessonSelected(course, lesson) + focus: true + background.color: colorScheme.shade(colorScheme.normalBackground, KColorScheme.DarkShade, 1, 0.0) + property int columns: Math.floor(width / (300 + 20)) + cellWidth: Math.floor(content.width / content.columns) + cellHeight: Math.round(cellWidth * 2 / 3) + + Keys.onPressed: { + if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) { + event.accepted = true; + if (root.selectedLesson && content.currentIndex <= course.lastUnlockedLessonIndex) { + lessonSelected(course, root.selectedLesson) } } - iconSource: locked? "object-locked": "" - label.opacity: locked? 0.5: 1.0 - title: lesson? lesson.title: "" + } + + model: LessonModel { + id: lessonModel + course: courseItem + } + onCurrentIndexChanged: { + if (lessonModel.rowCount() > 0 && currentIndex != -1) { + root.selectedLesson = lessonModel.data(lessonModel.index(currentIndex, 0), LessonModel.DataRole) + } + else { + root.selectedLesson = null + } + } + + delegate: Item { + id: item + width: content.cellWidth + height: content.cellHeight + + LessonSelectorItem { + id: lessonItem + anchors.fill: parent + anchors.margins: 10 + anchors.centerIn: parent + lesson: dataRole + selected: content.currentIndex == index + editable: courseItem.editable + onClicked: { + item.forceActiveFocus() + content.currentIndex = index + } + + onDoubleClicked: { + if (index <= course.lastUnlockedLessonIndex) { + lessonSelected(course, dataRole) + } + } + onStatButtonClicked: { + statPopupDialog.open() + } + onEditButtonClicked: { + lessonEditorDialog.open() + } + onDeleteButtonClicked: { + course.deleteCustomLesson(); + } + } + + LessonLockedNotice { + anchors.centerIn: parent + visible: index > course.lastUnlockedLessonIndex + glowColor: lessonItem.background.color + } } - onModelChanged: update() } } - selectedLesson: lessonList.currentItem != null? lessonList.currentItem.lesson: null - selectedLessonLocked: lessonList.currentItem !== null && lessonList.currentItem.locked - onStartButtonClicked: lessonSelected(course, lessonList.currentItem.lesson) + Item { + Layout.fillWidth: true + height: footer.height + z: 2 + + ToolBar { + id: footer + width: parent.width + + KColorScheme { + id: footerColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Window + } + + background: Rectangle { + color: footerColorScheme.normalBackground + } + + RowLayout { + anchors.fill: parent + spacing: 0 + + Item { + Layout.fillWidth: true + height: startButton.implicitHeight + + IconButton { + id: startButton + iconName: "go-next-view" + bgColor: colorScheme.positiveBackground + anchors.centerIn: parent + text: i18n("Start Training") + enabled: root.selectedLesson && content.currentIndex <= course.lastUnlockedLessonIndex + onClicked: lessonSelected(course, root.selectedLesson) + } + } + } + } + + DropShadow { + anchors.fill: footer + source: footer + samples: 16 + horizontalOffset: 0 + verticalOffset: 0 + } + } } } + diff --git a/src/qml/homescreen/LessonSelectorBase.qml b/src/qml/homescreen/LessonSelectorBase.qml deleted file mode 100644 index 0cfe5f9..0000000 --- a/src/qml/homescreen/LessonSelectorBase.qml +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2013 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import ktouch 1.0 - -Item { - id: root - - property alias list: listContainer.data - property alias previewArea: column - property Lesson selectedLesson - property bool selectedLessonLocked - signal startButtonClicked() - - - Row { - anchors.fill: parent - anchors.margins: 5 - spacing: 20 - - Item { - id: listContainer - height: parent.height - width: Math.round((parent.width - parent.spacing) / 2) - - } - - ColumnLayout { - id: column - width: parent.width - listContainer.width - parent.spacing - height: parent.height - spacing: 5 - - LessonPreview { - id: lessonPreview - Layout.fillWidth: true - Layout.fillHeight: true - - lesson: selectedLesson - } - - LessonLockedNotice { - anchors.centerIn: lessonPreview - blurSource: lessonPreview - opacity: selectedLessonLocked? 1: 0 - } - - Item { - id: startButtonContainer - Layout.fillWidth: true - height: Math.round(1.5 * startButton.height) - - Button { - id: startButton - anchors.centerIn: parent - text: i18n("Start Training") - enabled: selectedLesson !== null && !selectedLessonLocked - iconName: "go-next-view" - onClicked: startButtonClicked() - } - } - } - } -} diff --git a/src/qml/homescreen/LessonSelectorItem.qml b/src/qml/homescreen/LessonSelectorItem.qml new file mode 100644 index 0000000..2169831 --- /dev/null +++ b/src/qml/homescreen/LessonSelectorItem.qml @@ -0,0 +1,159 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import ktouch 1.0 +import '../common' + +Item { + id: root + property Lesson lesson + property bool selected + property bool editable: false + property alias background: background + + signal clicked + signal doubleClicked + signal deleteButtonClicked + signal editButtonClicked + signal statButtonClicked + + clip: true + + KColorScheme { + id: selectionColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Selection + } + + Rectangle { + id: background + anchors.fill: parent + color: selected? Qt.tint(selectionColorScheme.normalBackground, "#a0ffffff"): "#ffffff" + } + + MouseArea { + anchors.fill: parent + onClicked: { + root.clicked() + } + onDoubleClicked: { + root.doubleClicked() + } + } + + GridLayout { + id: content + anchors.fill: parent + anchors.margins: 10 + + Label { + id: titleLabel + Layout.column: 0 + Layout.row: 0 + Layout.fillWidth: true + Layout.preferredHeight: buttonRow.implicitHeight + text: lesson? lesson.title: "" + color: "#000000" + font.bold: true + elide: Label.ElideRight + verticalAlignment: Qt.AlignVCenter + + ToolTip { + parent: titleLabel + text: titleLabel.text + visible: titleMouseArea.containsMouse + } + + MouseArea { + id: titleMouseArea + anchors.fill: parent + acceptedButtons: Qt.NoButton + hoverEnabled: titleLabel.truncated + } + } + + Row { + id: buttonRow + Layout.column: 1 + Layout.row: 0 + visible: root.selected + + IconToolButton { + id: editButton + visible: root.editable + iconName: 'edit-entry' + color: "#000000" + backgroundColor: "#c0c0c0c0" + onClicked: { + root.editButtonClicked(); + } + } + + IconToolButton { + id: deleteButton + visible: root.editable + iconName: 'edit-delete' + color: "#000000" + backgroundColor: "#c0c0c0c0" + onClicked: { + root.deleteButtonClicked(); + } + } + + IconToolButton { + iconName: 'view-statistics' + color: "#000000" + backgroundColor: "#c0c0c0c0" + onClicked: { + root.statButtonClicked(); + } + } + } + + Item { + Layout.column: 0 + Layout.row: 1 + Layout.columnSpan: 2 + Layout.fillHeight: true + Layout.fillWidth: true + clip: true + + Label { + id: textLabel + anchors.fill: parent + text: lesson? lesson.text: "" + color: "#000000" + font.family: 'monospace' + lineHeight: 1.5 + scale: Math.min(1, width / implicitWidth) + transformOrigin: Item.TopLeft + } + + Rectangle { + anchors.fill: parent + gradient: Gradient { + GradientStop { position: 0.0; color: "#00000000" } + GradientStop { position: 0.8; color: Qt.rgba(background.color.r, background.color.g, background.color.b, 0) } + GradientStop { position: 1.0; color: background.color } + } + } + } + } +} + diff --git a/src/qml/homescreen/ProfileComboBox.qml b/src/qml/homescreen/ProfileComboBox.qml new file mode 100644 index 0000000..96e0802 --- /dev/null +++ b/src/qml/homescreen/ProfileComboBox.qml @@ -0,0 +1,112 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtGraphicalEffects 1.0 +import ktouch 1.0 + +import "../common" + +ComboBox { + id: root + + property Profile profile + property color manageProfileButtonBgColor + + model: profileDataAccess.profileCount + + contentItem: IconLabel { + iconName: "user-identity" + text: profile !== null? profile.name: "" + color: colorScheme.normalText + + } + + onActivated: { + profile = index < profileDataAccess.profileCount? profileDataAccess.profile(index): null + } + + onProfileChanged: { + for (var i = 0; i < profileDataAccess.profileCount; i++) { + if (profile === profileDataAccess.profile(i)) { + if (i != currentIndex) { + currentIndex = i; + } + break; + } + } + } + + delegate: ListItem { + width: root.width + text: index < profileDataAccess.profileCount? profileDataAccess.profile(index).name: "" + highlighted: root.currentIndex === index + } + + popupListView.footer: IconButton { + id: manageProfileButton + width: parent.width + color: root.colorScheme.normalText + bgColor: root.manageProfileButtonBgColor + text: i18n("Manage Profiles") + iconName: "user-properties" + MouseArea { + anchors.fill: parent + hoverEnabled: false + onPressed: { + mouse.accepted = true + } + onClicked: { + root.popup.close() + mouse.accepted = true + manageProfileDialog.open() + } + } + } + + PopupDialog { + id: manageProfileDialog + + margins: { + left: 40 + bottom: 40 + right: 40 + top: 40 + } + + width: parent.width - leftMargin - rightMargin + height: parent.height - topMargin - bottomMargin + modal: true + focus: true + title: i18n("Manage Profiles") + closePolicy: PopupDialog.CloseOnEscape + padding: 0 + contentItem: ProfileSelector { + id: profileSelector + onProfileChosen: { + root.profile = profile + manageProfileDialog.close() + } + } + onOpened: { + if (profileComboBox.profile) { + var index = profileDataAccess.indexOfProfile(profileComboBox.profile) + profileSelector.selectProfile(index) + } + } + } +} diff --git a/src/qml/homescreen/ProfileDetailsItem.qml b/src/qml/homescreen/ProfileDetailsItem.qml index 8cd6a8b..45975a6 100644 --- a/src/qml/homescreen/ProfileDetailsItem.qml +++ b/src/qml/homescreen/ProfileDetailsItem.qml @@ -1,276 +1,277 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import org.kde.kcoreaddons 1.0 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import org.kde.charts 0.1 as Charts +import org.kde.kcoreaddons 1.0 import ktouch 1.0 import "../common" Item { id: root property Profile profile function update() { if (profile) { var isNewProfile = root.profile.id === -1 profileForm.name = profile.name profileForm.skillLevel = profile.skillLevel profileForm.skillLevelSelectionEnabled = isNewProfile deleteConfirmationLabel.name = profile.name state = isNewProfile? "editor": "info" } } signal deletionRequest(); onProfileChanged: update() SystemPalette { id: activePalette colorGroup: SystemPalette.Active } Item { id: infoContainer width: parent.width height: childrenRect.height anchors.centerIn: parent Column { width: parent.width height: childrenRect.height spacing: 40 LearningProgressModel { id: learningProgressModel profile: root.profile } Rectangle { anchors.horizontalCenter: parent.horizontalCenter - width: parent.width - 40 + width: parent.width height: 250 color: activePalette.base border { width: 1 color: activePalette.text } Column { id: column anchors { fill: parent topMargin: column.spacing + spacing + legend.height leftMargin: column.spacing rightMargin: column.spacing bottomMargin: column.spacing } spacing: 20 width: parent.width height: parent.height - legend.height - parent.spacing LearningProgressChart { id: learningProgressChart anchors.horizontalCenter: parent.horizontalCenter width: parent.width height: parent.height - legend.height - parent.spacing model: learningProgressModel } Row { id: legend anchors.horizontalCenter: parent.horizontalCenter spacing: 20 Charts.LegendItem { dimension: learningProgressChart.accuracy textColor: activePalette.text } Charts.LegendItem { dimension: learningProgressChart.charactersPerMinute textColor: activePalette.text } } } } InformationTable { id: profileInfoTable width: parent.width property int trainedLessonCount: profile && profile.id !== -1? profileDataAccess.lessonsTrained(profile): 0 property list infoModel: [ InfoItem { title: i18n("Lessons trained:") text: profile && profile.id !== -1? profileInfoTable.trainedLessonCount: "" }, InfoItem { title: i18n("Total training time:") text: profile && profile.id !== -1? Format.formatDuration(profileDataAccess.totalTrainingTime(profile)): "" }, InfoItem { title: i18n("Last trained:") text: profile && profile.id !== -1 && profileInfoTable.trainedLessonCount > 0? profileDataAccess.lastTrainingSession(profile).toLocaleDateString(): i18n("Never") } ] model: infoModel } } InlineToolbar { anchors { top: parent.top horizontalCenter: parent.horizontalCenter topMargin: 5 } content: [ - ToolButton { + IconToolButton { iconName: "document-edit" text: i18n("Edit") onClicked: root.state = "editor" }, - ToolButton { + IconToolButton { iconName: "edit-delete" text: i18n("Delete") enabled: profileDataAccess.profileCount > 1 onClicked: root.state = "deleteConfirmation" } ] } } Item { id: editorContainer - width: parent.width - 40 + width: parent.width height: childrenRect.height anchors.centerIn: parent + ProfileForm { id: profileForm width: parent.width - height: childrenRect.height showWelcomeLabel: false onDone: { root.profile.name = profileForm.name root.profile.skillLevel = profileForm.skillLevel if (root.profile.id === -1) { profileDataAccess.addProfile(profile) } else { profileDataAccess.updateProfile(profileDataAccess.indexOfProfile(root.profile)) } root.update() root.state = "info" } } } Item { id: deleteConfirmationContainer - width: parent.width - 40 + width: parent.width height: childrenRect.height anchors.centerIn: parent Column { width: parent.width height: childrenRect.height spacing: 15 Label { property string name id: deleteConfirmationLabel width: parent.width text: i18n("Do you really want to delete the profile \"%1\"?", name) wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter } Row { spacing: 10 anchors.horizontalCenter: parent.horizontalCenter width: childrenRect.width height: childrenRect.height - ToolButton { + IconButton { iconName: "edit-delete" text: i18n("Delete") + bgColor: colorScheme.negativeBackground onClicked: root.deletionRequest() } - ToolButton { + IconButton { text: i18n("Cancel") onClicked: root.state = "info" } } } } states: [ State { name: "info" PropertyChanges { target: infoContainer visible: true } PropertyChanges { target: editorContainer visible: false } PropertyChanges { target: deleteConfirmationContainer visible: false } }, State { name: "editor" PropertyChanges { target: infoContainer visible: false } PropertyChanges { target: editorContainer visible: true } PropertyChanges { target: deleteConfirmationContainer visible: false } }, State { name: "deleteConfirmation" PropertyChanges { target: infoContainer visible: false } PropertyChanges { target: editorContainer visible: false } PropertyChanges { target: deleteConfirmationContainer visible: true } } ] } diff --git a/src/qml/homescreen/ProfileForm.qml b/src/qml/homescreen/ProfileForm.qml index c3993bc..96c8ea1 100644 --- a/src/qml/homescreen/ProfileForm.qml +++ b/src/qml/homescreen/ProfileForm.qml @@ -1,93 +1,115 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" -Column { +ColumnLayout { id: root property alias name: nameTextField.text property int skillLevel: 0 property bool skillLevelSelectionEnabled: true property alias showWelcomeLabel: welcomeLabel.visible - property alias doneButtonIconSource: doneBtn.iconSource + property alias doneButtonIconSource: doneBtn.iconName property alias doneButtonText: doneBtn.text signal done() onSkillLevelChanged: { beginnerRadioButton.checked = skillLevel == Profile.Beginner advancedRadioButton.checked = skillLevel == Profile.Advanced } spacing: 15 Label { id: welcomeLabel - width: parent.width + Layout.fillWidth: true + Layout.preferredWidth: 500 text: i18n("Before you start training, please introduce yourself:") } TextField { id: nameTextField - width: parent.width + Layout.fillWidth: true placeholderText: i18n("Name") } - DetailedRadioButton { + RadioButton { id: beginnerRadioButton - width: parent.width + Layout.maximumWidth: parent.width enabled: root.skillLevelSelectionEnabled - label: i18n("I have no or only very little experience in machine typing") - hint: i18n("Lessons are unlocked as your typing skills improve over time.") + text: i18n("I have no or only very little experience in machine typing") + label.wrapMode: Text.Wrap onCheckedChanged: { if (checked) { root.skillLevel = Profile.Beginner - advancedRadioButton.checked = false } } } - DetailedRadioButton { + Label { + text: i18n("Lessons are unlocked as your typing skills improve over time.") + wrapMode: Text.Wrap + Layout.maximumWidth: parent.width + leftPadding: font.pixelSize * 2 + font.italic: true + enabled: root.skillLevelSelectionEnabled + } + + RadioButton { id: advancedRadioButton - width: parent.width + Layout.maximumWidth: parent.width enabled: root.skillLevelSelectionEnabled - label: i18n("I am an experienced machine typist and want to improve my skills") - hint: i18n("All lessons are unlocked immediately.") + text: i18n("I am an experienced machine typist and want to improve my skills") + label.wrapMode: Text.Wrap onCheckedChanged: { if (checked) { root.skillLevel = Profile.Advanced - beginnerRadioButton.checked = false } } } - Button { - id: doneBtn - anchors.horizontalCenter: parent.horizontalCenter - text: i18n("Done") - enabled: nameTextField.text !== "" && (beginnerRadioButton.checked || advancedRadioButton.checked) - iconName: "dialog-ok" - onClicked: done() + Label { + text: i18n("All lessons are unlocked immediately.") + wrapMode: Text.Wrap + Layout.maximumWidth: parent.width + leftPadding: font.pixelSize * 2 + font.italic: true + enabled: root.skillLevelSelectionEnabled + } + + Item { + Layout.fillWidth: true + Layout.preferredHeight: doneBtn.implicitHeight + + IconButton { + id: doneBtn + anchors.horizontalCenter: parent.horizontalCenter + bgColor: colorScheme.positiveBackground + text: i18n("Done") + enabled: nameTextField.text !== "" && (beginnerRadioButton.checked || advancedRadioButton.checked) + iconName: "dialog-ok" + onClicked: done() + } } } diff --git a/src/qml/homescreen/ProfileSelector.qml b/src/qml/homescreen/ProfileSelector.qml index 609a08d..8e6aeba 100644 --- a/src/qml/homescreen/ProfileSelector.qml +++ b/src/qml/homescreen/ProfileSelector.qml @@ -1,115 +1,125 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried + * Copyright 2017 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" FocusScope { id: root signal profileChosen(variant profile) function createNewProfile() { var profile = profileDataAccess.createProfile() profileForm.profile = profile } function selectProfile(index) { list.currentIndex = index profileForm.profile = profileDataAccess.profile(index) } + KColorScheme { + id: listColorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.View + } + ColumnLayout { anchors.fill: parent - anchors.bottomMargin: 10 - spacing: 10 + anchors.margins: 20 + spacing: 20 RowLayout { Layout.fillWidth: true Layout.fillHeight: true - spacing: 10 + spacing: 20 - Item { - id: listContainer + ListView { + id: list Layout.fillWidth: true Layout.fillHeight: true - - ScrollView { - anchors.fill: parent - ListView { - id: list - anchors.fill: parent - anchors.margins: 3 - spacing: 3 - model: profileDataAccess.profileCount + 1 - clip: true - delegate: ListItem { - property bool isNewButton: index >= profileDataAccess.profileCount - width: list.width - title: isNewButton? - i18n("Create New Profile"): - index < profileDataAccess.profileCount? profileDataAccess.profile(index).name: null - label.font.italic: isNewButton - iconSource: isNewButton? "list-add": "user-identity" - onClicked: { - list.currentIndex = index - if (isNewButton) { - createNewProfile() - } - else { - selectProfile(index) - } - } - onDoubleClicked: { - if (!isNewButton) { - root.profileChosen(profileDataAccess.profile(list.currentIndex)) - } - } + model: profileDataAccess.profileCount + 1 + clip: true + focus: true + delegate: ListItem { + property bool isNewButton: index >= profileDataAccess.profileCount + width: list.width + text: isNewButton? + i18n("Create New Profile"): + index < profileDataAccess.profileCount? profileDataAccess.profile(index).name: null + label.font.italic: isNewButton + iconName: isNewButton? "list-add": "user-identity" + highlighted: ListView.isCurrentItem + onClicked: { + list.forceActiveFocus() + list.currentIndex = index + if (isNewButton) { + createNewProfile() + } + else { + selectProfile(index) + } + } + onDoubleClicked: { + if (!isNewButton) { + root.profileChosen(profileDataAccess.profile(list.currentIndex)) + } + } + } + onCurrentItemChanged: { + if (currentItem != null) { + if (currentItem.isNewButton) { + createNewProfile() + } + else { + selectProfile(currentIndex) } } } } ProfileDetailsItem { id: profileForm Layout.fillWidth: true Layout.fillHeight: true onDeletionRequest: { var index = profileDataAccess.indexOfProfile(profileForm.profile) profileForm.profile = null profileDataAccess.removeProfile(index) selectProfile(Math.max(0, list.currentIndex - 1)) } } } - Button { + IconButton { id: selectButton - anchors.horizontalCenter: parent.horizontalCenter + Layout.fillWidth: true iconName: "go-next-view" text: i18n("Use Selected Profile") enabled: list.currentIndex !== -1 && list.currentIndex < profileDataAccess.profileCount + bgColor: colorScheme.positiveBackground onClicked: root.profileChosen(profileDataAccess.profile(list.currentIndex)) } } } diff --git a/src/qml/homescreen/StatPopupDialog.qml b/src/qml/homescreen/StatPopupDialog.qml new file mode 100644 index 0000000..63dbf0f --- /dev/null +++ b/src/qml/homescreen/StatPopupDialog.qml @@ -0,0 +1,90 @@ +/* + * Copyright 2017 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import ktouch 1.0 +import org.kde.charts 0.1 as Charts + +import '../common' + +PopupDialog { + id: root + + property alias course: learningProgressModel.courseFilter + property alias lesson: learningProgressModel.lessonFilter + property alias profile: learningProgressModel.profile + + title: i18n("Lesson training statistics") + modal: true + + margins: { + left: 40 + bottom: 40 + right: 40 + top: 40 + } + + width: parent.width - leftMargin - rightMargin + height: parent.height - topMargin - bottomMargin + padding: 0 + + + contentItem: Rectangle { + color: colorScheme.normalBackground + + ColumnLayout { + anchors.fill: parent + anchors.margins: titleLabel.font.pixelSize + spacing: titleLabel.font.pixelSize + + Label { + id: titleLabel + text: lesson? lesson.title: "" + font.bold: true + } + + LearningProgressChart { + id: learningProgressChart + Layout.fillHeight: true + Layout.fillWidth: true + backgroundColor: colorScheme.normalBackground + model: LearningProgressModel { + id: learningProgressModel + } + } + + Row { + spacing: 2 * titleLabel.font.pixelSize + Layout.alignment: Qt.AlignTop | Qt.AlignHCenter + Charts.LegendItem { + id: accuracyLegend + dimension: learningProgressChart.accuracy + } + Charts.LegendItem { + id: charactersPerMinuteLegend + dimension: learningProgressChart.charactersPerMinute + } + } + } + KColorScheme { + id: colorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.View + } + } +} diff --git a/src/qml/keyboard/KeyItem.qml b/src/qml/keyboard/KeyItem.qml index cabae92..4685dd9 100644 --- a/src/qml/keyboard/KeyItem.qml +++ b/src/qml/keyboard/KeyItem.qml @@ -1,313 +1,313 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import QtGraphicalEffects 1.0 import ktouch 1.0 Item { id: item property int keyIndex property KeyboardLayout keyboardLayout property bool isHighlighted: false property bool animateHighlight: true property bool enabled: true property bool pressed: false property real horizontalScaleFactor: 1 property real verticalScaleFactor: 1 property AbstractKey key: item.keyboardLayout.key(item.keyIndex) property AbstractKey referenceKey: keyboardLayout.referenceKey function match(data) { var eventText = data var eventKey = -1 if (typeof data === "object") { eventText = data.text eventKey = data.key } if (typeof data === "number") { eventText = "" eventKey = data } switch (key.keyType()) { case "key": for (var i = 0; i < key.keyCharCount; i++) { if (key.keyChar(i).value == eventText) { return true; } } return false case "specialKey": switch (key.type) { case SpecialKey.Tab: return eventKey == Qt.Key_Tab case SpecialKey.Capslock: return eventKey == Qt.Key_CapsLock case SpecialKey.Shift: return eventKey == Qt.Key_Shift case SpecialKey.Backspace: return eventKey == Qt.Key_Backspace case SpecialKey.Return: return eventKey == Qt.Key_Return case SpecialKey.Space: return eventKey == Qt.Key_Space || eventText == " " } return false } return false; } function getTint(color) { color.a = 0.125 return color } property color tint: key && key.keyType() == "key"? getTint(preferences.fingerColor(key.fingerIndex)): "#00000000" x: Math.round(key.left * horizontalScaleFactor) y: Math.round(key.top * verticalScaleFactor) width: Math.round(key.width * horizontalScaleFactor) height: Math.round(key.height * verticalScaleFactor) state: enabled? (pressed? "pressed": "normal"): "disabled" onIsHighlightedChanged: { if (!animateHighlight) { shadow.state = isHighlighted? "highlighted1": "normal" } } Rectangle { id: shadow property int marginSize: 0 anchors.centerIn: parent width: item.width + marginSize height: item.height + marginSize smooth: true radius: body.radius state: "normal" states: [ State { name: "normal" PropertyChanges { target: shadow color: "#000" marginSize: 0 } PropertyChanges { target: shadowEffect - glowRadius: 10 + glowRadius: body.radius } }, State { name: "highlighted1" PropertyChanges { target: shadow color: "#54A7F0" marginSize: 4 } PropertyChanges { target: shadowEffect - glowRadius: 15 + glowRadius: 1.5 * body.radius } }, State { name: "highlighted2" PropertyChanges { target: shadow color: "#54A7F0" marginSize: 0 } PropertyChanges { target: shadowEffect - glowRadius: 15 + glowRadius: 1.5 * body.radius } } ] Behavior on marginSize { enabled: animateHighlight NumberAnimation { duration: 150 easing.type: Easing.InOutQuad } } Behavior on color { enabled: animateHighlight ColorAnimation { duration: 150 } } SequentialAnimation { id: pulseAnimation loops: Animation.Infinite running: isHighlighted && animateHighlight onRunningChanged: { if (!running) shadow.state = "normal" } ScriptAction { script: shadow.state = "highlighted1" } PauseAnimation { duration: 850 } ScriptAction { script: shadow.state = "highlighted2" } PauseAnimation { duration: 150 } } } RectangularGlow { id: shadowEffect anchors.fill: shadow color: shadow.color glowRadius: 5 cornerRadius: glowRadius + shadow.radius Behavior on glowRadius { enabled: animateHighlight NumberAnimation { duration: 150 easing.type: Easing.InOutQuad } } } Rectangle { id: body anchors.fill: parent radius: Math.max(3, Math.min(referenceKey.height, referenceKey.width) / 10 * Math.min(horizontalScaleFactor, verticalScaleFactor)) border.width: 1 border.color: "#000" smooth: true gradient: Gradient { GradientStop { id: gradientStop0; position: 0.0; } GradientStop { id: gradientStop1; position: 0.5; } GradientStop { id: gradientStop2; position: 1.0; } } Rectangle { id: hapticMarker anchors { bottom: parent.bottom horizontalCenter: parent.horizontalCenter bottomMargin: 4 } visible: item.key.keyType() == "key" && item.key.hasHapticMarker height: 3 width: body.width / 3 radius: 1 color: topLeftLabel.color border { width: 1 color: topLeftLabel.color } } } Item { anchors.topMargin: Math.max(referenceKey.width / 20, 3) * verticalScaleFactor anchors.bottomMargin: anchors.topMargin anchors.leftMargin: Math.max(referenceKey.width / 10, 5) * horizontalScaleFactor anchors.rightMargin: anchors.leftMargin anchors.fill: parent KeyLabel { id: topLeftLabel key: item.key position: KeyChar.TopLeft } KeyLabel { id: topRightLabel anchors.right: parent.right key: item.key position: KeyChar.TopRight } KeyLabel { id: bottomLeftLabel anchors.bottom: parent.bottom key: item.key position: KeyChar.BottomLeft } KeyLabel { id: bottomRightLabel anchors.right: parent.right anchors.bottom: parent.bottom key: item.key position: KeyChar.BottomRight } } states: [ State { name: "normal" PropertyChanges { target: gradientStop0 color: Qt.tint("#f0f0f0", item.tint) } PropertyChanges { target: gradientStop1 color: Qt.tint("#d5d5d5", item.tint) } PropertyChanges { target: gradientStop2 color: Qt.tint("#ccc", item.tint) } }, State { name: "pressed" PropertyChanges { target: gradientStop0 color: Qt.tint("#666", item.tint) } PropertyChanges { target: gradientStop1 color: Qt.tint("#888", item.tint) } PropertyChanges { target: gradientStop2 color: Qt.tint("#999", item.tint) } }, State { name: "disabled" PropertyChanges { target: gradientStop0 color: Qt.tint("#444", item.tint) } PropertyChanges { target: gradientStop1 color: Qt.tint("#333", item.tint) } PropertyChanges { target: gradientStop2 color: Qt.tint("#222", item.tint) } } ] } diff --git a/src/qml/keyboard/KeyLabel.qml b/src/qml/keyboard/KeyLabel.qml index ed11262..45a9b48 100644 --- a/src/qml/keyboard/KeyLabel.qml +++ b/src/qml/keyboard/KeyLabel.qml @@ -1,67 +1,67 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Text { id: root property AbstractKey key property variant position function specialKeyLabel(type) { switch (key.type) { case SpecialKey.Other: return key.label case SpecialKey.Tab: return "\u21B9" case SpecialKey.Capslock: return "\u21E9" case SpecialKey.Shift: return "\u21E7" case SpecialKey.Backspace: return "\u2190" case SpecialKey.Return: return "\u21B5" case SpecialKey.Space: return ""; } } function keyChar(position, key) { for (var i = 0; i < key.keyCharCount; i++) { var keyChar = key.keyChar(i); if (position === keyChar.position) { return keyChar } } return null; } function keyLabel(position, key) { var keyChar = root.keyChar(position, key) return keyChar? keyChar.value: "" } color: key.state === "normal"? "#333": "#222" smooth: true font.pixelSize: referenceKey.height * Math.min(horizontalScaleFactor, verticalScaleFactor) / 3 text: key.keyType() === "specialKey"? position === KeyChar.TopLeft? specialKeyLabel(key.type): "": keyLabel(position, key) } diff --git a/src/qml/keyboard/Keyboard.qml b/src/qml/keyboard/Keyboard.qml index ecb2252..ca79cd6 100644 --- a/src/qml/keyboard/Keyboard.qml +++ b/src/qml/keyboard/Keyboard.qml @@ -1,99 +1,99 @@ /* * Copyright 2013 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Item { id: keyboard signal keyboardUpdate property KeyboardLayout keyboardLayout property real aspectRatio: keyboardLayout.width / keyboardLayout.height property real horizontalScaleFactor: width / keyboardLayout.width property real verticalScaleFactor: height / keyboardLayout.height function keyItems() { var items = [] for (var i = 0; i < keys.count; i++) { items.push(keys.itemAt(i)) } return items } function findKeyItems(keyChar) { var matchingKeys = [] for (var i = 0; i < keys.count; i++) { var key = keys.itemAt(i); if (key.match(keyChar)) matchingKeys.push(key) } return matchingKeys } function findModifierKeyItem(modifierId) { for (var i = 0; i < keys.count; i++) { var key = keys.itemAt(i); if (key.key.keyType() === "specialKey" && key.key.modifierId === modifierId) return key } return null } function handleKeyPress(event) { var eventKeys = findKeyItems(event) for (var i = 0; i < eventKeys.length; i++) { eventKeys[i].pressed = true } } function handleKeyRelease(event) { var eventKeys = findKeyItems(event) for (var i = 0; i < eventKeys.length; i++) { eventKeys[i].pressed = false } } Item { id: keyContainer width: childrenRect.width height: childrenRect.height anchors.centerIn: parent Repeater { id: keys model: keyboard.visible && keyboardLayout.isValid? keyboard.keyboardLayout.keyCount: 0 onModelChanged: keyboard.keyboardUpdate() KeyItem { keyboardLayout: keyboard.keyboardLayout; keyIndex: index horizontalScaleFactor: keyboard.horizontalScaleFactor verticalScaleFactor: keyboard.verticalScaleFactor } } } } diff --git a/src/qml/keyboard/KeyboardLayoutEditor.qml b/src/qml/keyboard/KeyboardLayoutEditor.qml index 00da56b..fc8520f 100644 --- a/src/qml/keyboard/KeyboardLayoutEditor.qml +++ b/src/qml/keyboard/KeyboardLayoutEditor.qml @@ -1,114 +1,114 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 import "../common" Item { id: root Preferences { id: preferences } property real scaleFactor: Math.pow(2, keyboardLayoutEditor.zoomLevel / 2) property KeyboardLayout layout: keyboardLayoutEditor.keyboardLayout property int lastZIndex: 0 width: keyContainer.width + 40 height: keyContainer.height + 40 MouseArea { anchors.fill: parent onPressed: { if (mouse.button == Qt.LeftButton) { keyboardLayoutEditor.selectedKey = null mouse.accepted = true } } } - Grid { + LineGrid { id: keyContainer anchors.centerIn: parent width: Math.round(layout.width * scaleFactor) height: Math.round(layout.height * scaleFactor) lineDistance: 10.0 * scaleFactor color: "#121286" backgroundColor: "#cccccc" Repeater { id: keys model: layout.isValid? layout.keyCount: 0 KeyItem { property bool manipulated: false id: keyItem keyboardLayout: layout; keyIndex: index isHighlighted: keyItem.key == keyboardLayoutEditor.selectedKey animateHighlight: false opacity: manipulated? 0.7: 1.0 horizontalScaleFactor: scaleFactor verticalScaleFactor: scaleFactor MouseArea { anchors.fill: parent cursorShape: keyItem.manipulated? Qt.SizeAllCursor: Qt.ArrowCursor onPressed: { if (mouse.button == Qt.LeftButton) { keyboardLayoutEditor.selectedKey = layout.key(index) root.lastZIndex++ keyItem.z = root.lastZIndex } } drag { target: !keyboardLayoutEditor.readOnly? keyItem: undefined axis: Drag.XandYAxis minimumX: 0 maximumX: keyContainer.width - keyItem.width minimumY: 0 maximumY: keyContainer.height - keyItem.height onActiveChanged: { keyItem.manipulated = drag.active if (!drag.active) { var left = 10 * Math.round(keyItem.x / scaleFactor / 10) var top = 10 * Math.round(keyItem.y / scaleFactor / 10) keyboardLayoutEditor.setKeyGeometry(keyIndex, left, top, keyItem.key.width, keyItem.key.height) } } } } Behavior on opacity { NumberAnimation { duration: 150 } } } } SelectionRectangle { keyboardLayout: layout; target: keyboardLayoutEditor.selectedKey z: root.lastZIndex + 1 interactive: !keyboardLayoutEditor.readOnly } } } diff --git a/src/qml/main.qml b/src/qml/main.qml index 8907209..fe9e6cb 100644 --- a/src/qml/main.qml +++ b/src/qml/main.qml @@ -1,239 +1,239 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 import "./common" import "./meters" import "./homescreen" import "./trainingscreen" import "./scorescreen" Rectangle { - SystemPalette { - id: activePallete - colorGroup: SystemPalette.Active - } - id: main - color: activePallete.window + color: activePallete.normalBackground + property Item appContent: appContentItem function switchScreen(from, to) { switchScreenAnimation.from = from switchScreenAnimation.to = to switchScreenAnimation.start() } + KColorScheme { + id: activePallete + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Window + } + DataAccess { id: dataAccess } QtObject { id: helper property string name: ktouch.keyboardLayoutName property int keyboardLayoutCount: ktouch.globalDataIndex.keyboardLayoutCount property int courseCount: ktouch.globalDataIndex.courseCount onNameChanged: { - keyboardLayout.update() + activeKeyboardLayout.update() } onKeyboardLayoutCountChanged: { if (ktouch.globalDataIndex.isValid) - keyboardLayout.update() + activeKeyboardLayout.update() } } ResourceModel { id: resourceModel dataIndex: ktouch.globalDataIndex } ProfileDataAccess { id: profileDataAccess } Preferences { id: preferences } KeyboardLayout { - id: keyboardLayout + id: activeKeyboardLayout Component.onCompleted: { if (ktouch.globalDataIndex.isValid) { - keyboardLayout.update() + activeKeyboardLayout.update() } } function update() { isValid = false var name = ktouch.keyboardLayoutName; // first pass - exact match for (var i = 0; i < ktouch.globalDataIndex.keyboardLayoutCount; i++) { var dataIndexLayout = ktouch.globalDataIndex.keyboardLayout(i) if (dataIndexLayout.name === name) { - dataAccess.loadKeyboardLayout(dataIndexLayout, keyboardLayout) + dataAccess.loadKeyboardLayout(dataIndexLayout, activeKeyboardLayout) return } } // second pass - substring match for (var i = 0; i < ktouch.globalDataIndex.keyboardLayoutCount; i++) { var dataIndexLayout = ktouch.globalDataIndex.keyboardLayout(i) if (name.search(dataIndexLayout.name) === 0) { - dataAccess.loadKeyboardLayout(dataIndexLayout, keyboardLayout) + dataAccess.loadKeyboardLayout(dataIndexLayout, activeKeyboardLayout) return } } } } - CategorizedResourceSortFilterProxyModel { - id: availableCourseModel - resourceModel: resourceModel - resourceTypeFilter: ResourceModel.CourseItem - keyboardLayoutNameFilter: keyboardLayout.isValid? keyboardLayout.name: ktouch.keyboardLayoutName - } - Course { id: selectedCourse property Lesson selectedLesson } Lesson { id: customLessonCopy } - HomeScreen { - id: homeScreen + Item { anchors.fill: parent - courseModel: availableCourseModel - keyboardLayout: keyboardLayout - keyboardLayoutName: keyboardLayout.isValid? keyboardLayout.name: helper.name - visible: false - focus: true - onLessonSelected: { - trainingScreen.profile = profile - var lessonIndex = -1; - for (var i = 0; i < course.lessonCount; i++) { - if (lesson === course.lesson(i)) { - lessonIndex = i - break + id: appContentItem + layer.enabled: true + + HomeScreen { + id: homeScreen + anchors.fill: parent + activeKeyboardLayoutName: activeKeyboardLayout.isValid? activeKeyboardLayout.name: helper.name + visible: false + focus: true + onLessonSelected: { + trainingScreen.profile = profile + var lessonIndex = -1; + for (var i = 0; i < course.lessonCount; i++) { + if (lesson === course.lesson(i)) { + lessonIndex = i + break + } } - } - selectedCourse.copyFrom(course) + selectedCourse.copyFrom(course) - if (lessonIndex !== -1) { - selectedCourse.selectedLesson = selectedCourse.lesson(lessonIndex) + if (lessonIndex !== -1) { + selectedCourse.selectedLesson = selectedCourse.lesson(lessonIndex) + } + else { + customLessonCopy.copyFrom(lesson) + selectedCourse.selectedLesson = customLessonCopy + } + + main.switchScreen(homeScreen, trainingScreen) } - else { - customLessonCopy.copyFrom(lesson) - selectedCourse.selectedLesson = customLessonCopy + Component.onCompleted: { + homeScreen.reset() + homeScreen.visible = true + homeScreen.start() } - - main.switchScreen(homeScreen, trainingScreen) } - Component.onCompleted: { - homeScreen.reset() - homeScreen.visible = true - } - } - TrainingScreen { - id: trainingScreen - anchors.fill: parent - visible: false - keyboardLayout: keyboardLayout - course: selectedCourse - lesson: selectedCourse.selectedLesson - onRestartRequested: main.switchScreen(trainingScreen, trainingScreen) - onAbortRequested: main.switchScreen(trainingScreen, homeScreen) - onFinished: main.switchScreen(trainingScreen, scoreScreen) - } + TrainingScreen { + id: trainingScreen + anchors.fill: parent + visible: false + keyboardLayout: homeScreen.selectedKeyboardLayout + course: selectedCourse + lesson: selectedCourse.selectedLesson + onRestartRequested: main.switchScreen(trainingScreen, trainingScreen) + onAbortRequested: main.switchScreen(trainingScreen, homeScreen) + onFinished: main.switchScreen(trainingScreen, scoreScreen) + } - ScoreScreen { - id: scoreScreen - anchors.fill: parent - visible: false - course: trainingScreen.course - lesson: trainingScreen.lesson - stats: trainingScreen.stats - profile: trainingScreen.profile - referenceStats: trainingScreen.referenceStats - onHomeScreenRequested: main.switchScreen(scoreScreen, homeScreen) - onLessonRepetionRequested: main.switchScreen(scoreScreen, trainingScreen) - onNextLessonRequested: { - selectedCourse.selectedLesson = lesson - main.switchScreen(scoreScreen, trainingScreen) + ScoreScreen { + id: scoreScreen + anchors.fill: parent + visible: false + course: trainingScreen.course + lesson: trainingScreen.lesson + stats: trainingScreen.stats + profile: trainingScreen.profile + referenceStats: trainingScreen.referenceStats + onHomeScreenRequested: main.switchScreen(scoreScreen, homeScreen) + onLessonRepetionRequested: main.switchScreen(scoreScreen, trainingScreen) + onNextLessonRequested: { + selectedCourse.selectedLesson = lesson + main.switchScreen(scoreScreen, trainingScreen) + } } } Rectangle { id: curtain anchors.fill: parent color: "#000" opacity: 0 } SequentialAnimation { id: switchScreenAnimation property Item from property Item to NumberAnimation { target: curtain property: "opacity" to: 1 duration: switchScreenAnimation.to == homeScreen? 250: 750 easing.type: Easing.OutQuad } PropertyAction { target: switchScreenAnimation.from property: "visible" value: false } ScriptAction { script: switchScreenAnimation.to.reset() } PropertyAction { target: switchScreenAnimation.to property: "visible" value: true } ScriptAction { script: { switchScreenAnimation.to.start() switchScreenAnimation.to.forceActiveFocus() } } NumberAnimation { target: curtain property: "opacity" to: 0 duration: switchScreenAnimation.to == homeScreen? 250: 750 easing.type: Easing.InQuad } } } diff --git a/src/qml/meters/AccuracyMeter.qml b/src/qml/meters/AccuracyMeter.qml index e3658df..91fdc05 100644 --- a/src/qml/meters/AccuracyMeter.qml +++ b/src/qml/meters/AccuracyMeter.qml @@ -1,90 +1,90 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Meter { id: meter property real accuracy: 1.0 property real referenceAccuracy: 1.0 label: i18n("Accuracy") value: strFormatter.formatAccuracy(meter.accuracy) referenceValue: strFormatter.formatAccuracyDiff(meter.referenceAccuracy, meter.accuracy) valueStatus: Math.round(1000 * meter.accuracy) >= Math.round(10 * preferences.requiredAccuracy)? "good": "bad" analogPartContent: Image { anchors.centerIn: parent - source: utils.findImage("accuracymeter-background.png") + source: "qrc:///ktouch/images/accuracymeter-background.png" ScaleBackgroundItem { anchors.centerIn: parent anchors.verticalCenterOffset: 25 width: scale.width height: scale.height startAngle: Math.min(135, Math.max(45, 135 - (preferences.requiredAccuracy - 90) * 9)) stopAngle: 45 scaleMarkHeight: 8 color: "#88ff00"; } Image { id: scale anchors.centerIn: parent anchors.verticalCenterOffset: 25 - source: utils.findImage("accuracymeter-scale.png") + source: "qrc:///ktouch/images/accuracymeter-scale.png" } Text { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: 20 } text: "90" font.pixelSize: 10 } Text { anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 20 } text: "100" font.pixelSize: 10 } Image { id: hand anchors.centerIn: parent anchors.verticalCenterOffset: 25 - source: utils.findImage("accuracymeter-hand.png") + source: "qrc:///ktouch/images/accuracymeter-hand.png" transform: Rotation { origin.x: hand.width / 2 origin.y: hand.height / 2 angle: Math.min(90, Math.max(0, accuracy - 0.9) * 900) Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } } } diff --git a/src/qml/meters/CharactersPerMinuteMeter.qml b/src/qml/meters/CharactersPerMinuteMeter.qml index df01d41..7596ec6 100644 --- a/src/qml/meters/CharactersPerMinuteMeter.qml +++ b/src/qml/meters/CharactersPerMinuteMeter.qml @@ -1,94 +1,94 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Meter { id: meter property int charactersPerMinute: 0 property int referenceCharactersPerMinute: 0 property int minimumCharactersPerMinute: preferences.requiredStrokesPerMinute property int diff: meter.charactersPerMinute - meter.referenceCharactersPerMinute label: i18n("Characters per Minute") value: meter.charactersPerMinute referenceValue: strFormatter.formatSign(diff) + " " + (diff > 0? diff: -diff) valueStatus: meter.charactersPerMinute >= minimumCharactersPerMinute? "good": "bad" analogPartContent: Image { anchors.centerIn: parent - source: utils.findImage("charactersperminutemeter-background.png") + source: "qrc:///ktouch/images/charactersperminutemeter-background.png" ScaleBackgroundItem { anchors.centerIn: parent anchors.verticalCenterOffset: 25 width: scale.width height: scale.height startAngle: Math.min(135, Math.max(45, 135 - (minimumCharactersPerMinute * 90 / 360))) stopAngle: 45 scaleMarkHeight: 8 color: "#88ff00"; } Image { id: scale anchors.centerIn: parent anchors.verticalCenterOffset: 25 - source: utils.findImage("charactersperminutemeter-scale.png") + source: "qrc:///ktouch/images/charactersperminutemeter-scale.png" } Text { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: 20 } text: "0" font.pixelSize: 10 } Text { anchors { verticalCenter: parent.verticalCenter right: parent.right rightMargin: 20 } text: "360" font.pixelSize: 10 } Image { id: hand anchors.centerIn: parent anchors.verticalCenterOffset: 25 - source: utils.findImage("charactersperminutemeter-hand.png") + source: "qrc:///ktouch/images/charactersperminutemeter-hand.png" smooth: true transform: Rotation { origin.x: hand.width / 2 origin.y: hand.height / 2 angle: Math.min(90, charactersPerMinute * 90 / 360) Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } } } diff --git a/src/qml/meters/ElapsedTimeMeter.qml b/src/qml/meters/ElapsedTimeMeter.qml index 052a717..c2c74ff 100644 --- a/src/qml/meters/ElapsedTimeMeter.qml +++ b/src/qml/meters/ElapsedTimeMeter.qml @@ -1,66 +1,66 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 +import QtQuick 2.9 import ktouch 1.0 Meter { id: meter property variant elapsedTime property variant referenceElapsedTime label: i18n("Elapsed time") value: strFormatter.formatTime(meter.elapsedTime) referenceValue: strFormatter.formatTimeDiff(meter.referenceElapsedTime, meter.elapsedTime) positiveDiffIsGood: false analogPartContent: Image { anchors.centerIn: parent - source: utils.findImage("elapsedtimemeter-background.png") + source: "qrc:///ktouch/images/elapsedtimemeter-background.png" Image { id: minuteHand anchors.centerIn: parent - source: utils.findImage("elapsedtimemeter-minute-hand.png") + source: "qrc:///ktouch/images/elapsedtimemeter-minute-hand.png" smooth: true transform: Rotation { origin.x: minuteHand.width / 2 origin.y: minuteHand.height / 2 angle: elapsedTime? 6 * utils.getMinutesOfQTime(elapsedTime): 0 Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } Image { id: secondHand anchors.centerIn: parent - source: utils.findImage("elapsedtimemeter-second-hand.png") + source: "qrc:///ktouch/images/elapsedtimemeter-second-hand.png" transform: Rotation { origin.x: secondHand.width / 2 origin.y: secondHand.height / 2 angle: elapsedTime? 6 * utils.getSecondsOfQTime(elapsedTime): 0 Behavior on angle { SpringAnimation { spring: 2; damping: 0.2; modulus: 360; mass: 0.75} } } } } } diff --git a/src/qml/meters/Meter.qml b/src/qml/meters/Meter.qml index 5b197e1..92f0583 100644 --- a/src/qml/meters/Meter.qml +++ b/src/qml/meters/Meter.qml @@ -1,121 +1,121 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 RowLayout { id: meter property alias label: label.text property alias value: value.text; property alias referenceValue: referenceValue.text property bool positiveDiffIsGood: true property string valueStatus: "none" property alias analogPartContent: analogPart.data height: 112 width: 304 spacing: 0 BorderImage { id: analogPart Layout.preferredWidth: height Layout.fillHeight: true border { left: 6 top: 6 right:6 bottom: 6 } - source: utils.findImage("meterbox-left.png") + source: "qrc:///ktouch/images/meterbox-left.png" } BorderImage { id: digitalPart Layout.fillHeight: true Layout.fillWidth: true border { left: 6 top: 6 right:6 bottom: 6 } - source: utils.findImage("meterbox-right.png") + source: "qrc:///ktouch/images/meterbox-right.png" Column { anchors { verticalCenter: parent.verticalCenter left: parent.left leftMargin: 12 right: parent.right rightMargin: 12 } spacing: 0 Row { width: parent.width spacing: 5 Text { id: label width: Math.min(implicitWidth, parent.width - statusLed.width - parent.spacing) color: "#555" font.pixelSize: 15 elide: Text.ElideRight } Rectangle { id: statusLed height: 15 width: 15 radius: height / 2 visible: valueStatus !== "none" color: valueStatus === "good"? "#88ff00": "#424b35" onColorChanged: statusLedAnimaton.restart() SequentialAnimation { id: statusLedAnimaton NumberAnimation { target: statusLed; property: "scale"; to: 1.3; duration: 150; easing.type: Easing.InOutQuad } NumberAnimation { target: statusLed; property: "scale"; to: 1.0; duration: 150; easing.type: Easing.InOutQuad } } } } Text { id: value font.pixelSize: 30 font.bold: true } Text { id: referenceValue font.pixelSize: 15 color: { if (text[0] === "+") return positiveDiffIsGood? "#006E28": "#BF0303" if (text[0] === "-") return positiveDiffIsGood? "#BF0303": "#006E28" return "#555" } } } } } diff --git a/src/qml/meters/StatBox.qml b/src/qml/meters/StatBox.qml index d5277da..0d958c4 100644 --- a/src/qml/meters/StatBox.qml +++ b/src/qml/meters/StatBox.qml @@ -1,50 +1,50 @@ /* * Copyright 2012 Sebastian Gottfried * Copyright 2015 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 RowLayout { property TrainingStats stats property TrainingStats referenceStats height: childrenRect.height spacing: 10 ElapsedTimeMeter { id: elapsedTimeMeter Layout.fillWidth: true elapsedTime: stats.elapsedTime referenceElapsedTime: referenceStats.isValid? referenceStats.elapsedTime: stats.elapsedTime } CharactersPerMinuteMeter { id: charactersPerMinuteMeter Layout.fillWidth: true charactersPerMinute: stats.charactersPerMinute referenceCharactersPerMinute: referenceStats.isValid? referenceStats.charactersPerMinute: stats.charactersPerMinute } AccuracyMeter { id: accuracyMeter Layout.fillWidth: true accuracy: stats.accuracy referenceAccuracy: referenceStats.isValid? referenceStats.accuracy: stats.accuracy } } diff --git a/src/qml/qml.qrc b/src/qml/qml.qrc index 6420751..1f7cd9c 100644 --- a/src/qml/qml.qrc +++ b/src/qml/qml.qrc @@ -1,44 +1,67 @@ - - main.qml - meters/AccuracyMeter.qml - meters/CharactersPerMinuteMeter.qml - meters/ElapsedTimeMeter.qml - meters/Meter.qml - meters/StatBox.qml - common/DetailedRadioButton.qml + + common/AutoTriggerButton.qml + common/Balloon.qml + common/Collapsable.qml + common/ComboBox.qml + common/FocusBar.qml + common/GridView.qml + common/Icon.qml + common/IconButton.qml + common/IconLabel.qml + common/IconToolButton.qml common/InfoItem.qml common/InformationTable.qml common/InlineToolbar.qml + common/Label.qml common/LearningProgressChart.qml common/ListItem.qml + common/ListView.qml common/MessageBox.qml + common/MonochromeIcon.qml + common/PopupDialog.qml + common/RadioButton.qml + common/ScrollBar.qml + common/ScrollView.qml common/SelectionGrip.qml common/SelectionRectangle.qml - common/SheetDialog.qml - common/Balloon.qml - scorescreen/ScoreScreen.qml - trainingscreen/KeyboardUnavailableNotice.qml - trainingscreen/TrainingScreenMenuOverlay.qml - trainingscreen/TrainingScreen.qml - trainingscreen/TrainingScreenToolbar.qml - trainingscreen/TrainingWidget.qml + common/TextArea.qml + common/TextField.qml + common/ToolBar.qml + common/ToolSeparator.qml + common/ToolTip.qml homescreen/CourseDescriptionItem.qml - homescreen/CoursePage.qml homescreen/CourseSelector.qml - homescreen/InitialProfileForm.qml - homescreen/LessonLockedNotice.qml - homescreen/LessonPreview.qml - homescreen/LessonSelectorBase.qml + homescreen/CourseSelectorKeyboardLayoutItem.qml + homescreen/CourseSelectorKeyboardLayoutList.qml homescreen/HomeScreen.qml - homescreen/CustomLessonSelector.qml - homescreen/ProfileForm.qml + homescreen/InitialProfileDialog.qml + homescreen/KeyboardLayoutMismatchMessage.qml + homescreen/LessonDeletedMessage.qml + homescreen/LessonEditorDialog.qml + homescreen/LessonLockedNotice.qml homescreen/LessonSelector.qml - homescreen/ProfileSelector.qml + homescreen/LessonSelectorItem.qml + homescreen/ProfileComboBox.qml homescreen/ProfileDetailsItem.qml - keyboard/Keyboard.qml + homescreen/ProfileForm.qml + homescreen/ProfileSelector.qml + homescreen/StatPopupDialog.qml keyboard/KeyItem.qml keyboard/KeyLabel.qml + keyboard/Keyboard.qml keyboard/KeyboardLayoutEditor.qml + main.qml + meters/AccuracyMeter.qml + meters/CharactersPerMinuteMeter.qml + meters/ElapsedTimeMeter.qml + meters/Meter.qml + meters/StatBox.qml + scorescreen/ScoreScreen.qml + trainingscreen/KeyboardUnavailableNotice.qml + trainingscreen/TrainingScreen.qml + trainingscreen/TrainingScreenMenu.qml + trainingscreen/TrainingScreenToolbar.qml + trainingscreen/TrainingWidget.qml diff --git a/src/qml/scorescreen/ScoreScreen.qml b/src/qml/scorescreen/ScoreScreen.qml index 18888c2..f601a8b 100644 --- a/src/qml/scorescreen/ScoreScreen.qml +++ b/src/qml/scorescreen/ScoreScreen.qml @@ -1,535 +1,421 @@ /* - * Copyright 2012 Sebastian Gottfried - * Copyright 2016 Sebastian Gottfried + * Copyright 2018 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 -import org.kde.kquickcontrolsaddons 2.0 import org.kde.charts 0.1 as Charts import "../common" import "../meters" FocusScope { id: screen function start() {} function reset() { internal.lessonPassed = Math.round(1000 * stats.accuracy) >= Math.round(10 * preferences.requiredAccuracy) && stats.charactersPerMinute >= preferences.requiredStrokesPerMinute var lessonIndex = 0; for (var i = 0; i < course.lessonCount; i++) { if (lesson === course.lesson(i)) { lessonIndex = i break } } var lastUnlockedLessonIndex = 0 if (profile.skillLevel === Profile.Advanced) { lastUnlockedLessonIndex = course.lessonCount - 1; internal.nextLessonUnlocked = false } else { var lastUnlockedLessonId = profileDataAccess.courseProgress(profile, course.id, ProfileDataAccess.LastUnlockedLesson); if (lastUnlockedLessonId !== "") { for (var index = 0; index < course.lessonCount; index++) { if (course.lesson(index).id === lastUnlockedLessonId) { lastUnlockedLessonIndex = index; break; } } } internal.nextLessonUnlocked = internal.lessonPassed && lessonIndex === lastUnlockedLessonIndex && lessonIndex + 1 < course.lessonCount if (internal.nextLessonUnlocked) { lastUnlockedLessonIndex++ } } internal.nextLesson = null; if (lessonIndex + 1 < course.lessonCount && lessonIndex + 1 <= lastUnlockedLessonIndex) { internal.nextLesson = course.lesson(lessonIndex + 1) } if (internal.nextLessonUnlocked) { profileDataAccess.saveCourseProgress(internal.nextLesson.id, profile, course.id, ProfileDataAccess.LastUnlockedLesson) } learningProgressModel.update() } function forceActiveFocus() { if (internal.lessonPassed && internal.nextLesson) { nextLessonButton.forceActiveFocus() } else { repeatLessonButton.forceActiveFocus() } } property Profile profile property Lesson lesson property Course course property TrainingStats stats property TrainingStats referenceStats signal homeScreenRequested signal nextLessonRequested(variant lesson) signal lessonRepetionRequested QtObject { id: internal property bool lessonPassed: false property bool nextLessonUnlocked: false property Lesson nextLesson: null } - SystemPalette { - id: palette - colorGroup: SystemPalette.Active + KColorScheme { + id: colorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.View } + + LearningProgressModel { - property bool filterByLesson: false + property bool filterByLesson: learningProgressFilterComboBox.currentIndex == 1 id: learningProgressModel profile: screen.visible? screen.profile: null courseFilter: screen.visible? screen.course: null lessonFilter: screen.visible && filterByLesson? screen.lesson: null } ErrorsModel { id: errorsModel trainingStats: screen.visible? screen.stats: null } - Balloon { - id: chartTypeDialog - visualParent: chartTypeButton - - Column { - id: chartTypeDialogContents - - Button { - id: progressChartButton - width: Math.max(implicitWidth, errorsChartButton.implicitWidth) - text: i18n("Progress") - iconName: "office-chart-area" - onClicked: { - chartTypeDialog.close() - tabGroup.currentIndex = 0 - } - } - Button { - id: errorsChartButton - width: Math.max(implicitWidth, progressChartButton.implicitWidth) - text: i18n("Errors") - iconName: "office-chart-bar" - onClicked: { - chartTypeDialog.close() - tabGroup.currentIndex = 1 - } - } - } - } - - Balloon { - id: learningProgressDialog - visualParent: learningProgressFilterButton - - Column { - id: learningProgressDialogContents - - Button { - id: allLessonsButton - width: Math.max(implicitWidth, thisLessonButton.implicitWidth) - text: i18n("All Lessons") - iconName: "view-filter" - onClicked: { - learningProgressModel.filterByLesson = false - learningProgressDialog.close() - } - } - Button { - id: thisLessonButton - width: Math.max(implicitWidth, allLessonsButton.implicitWidth) - text: i18n("This Lesson") - iconName: "view-filter" - onClicked: { - learningProgressModel.filterByLesson = true - learningProgressDialog.close() - } - } - } - } - - Balloon { - id: learningProgressPointTooltip - visualParent: parent - property int row: -1 - - function findLessonTitle(id) { - var course = screen.course - for (var i = 0; i < course.lessonCount; i++) { - if (course.lesson(i).id === id) { - return course.lesson(i).title - } - } - return i18n("Unknown") - } - - InformationTable { - property list infoModel: [ - InfoItem { - title: i18nc("Statistics on lesson:", "On:") - text: learningProgressPointTooltip.row !== -1? learningProgressPointTooltip.findLessonTitle(learningProgressModel.lessonId(learningProgressPointTooltip.row)): "" - }, - InfoItem { - title: i18n("Accuracy:") - text: learningProgressPointTooltip.row !== -1? strFormatter.formatAccuracy(learningProgressModel.accuracy(learningProgressPointTooltip.row)): "" - }, - InfoItem { - title: i18n("Characters per Minute:") - text: learningProgressPointTooltip.row !== -1? learningProgressModel.charactersPerMinute(learningProgressPointTooltip.row): "" - } - ] - width: 250 - model: infoModel - } - } - Balloon { id: errorsTooltip visualParent: parent property int row: -1 InformationTable { property list infoModel: [ InfoItem { title: i18n("Character:") text: errorsTooltip.row !== -1? errorsModel.character(errorsTooltip.row): "" }, InfoItem { title: i18n("Errors:") text: errorsTooltip.row !== -1? errorsModel.errors(errorsTooltip.row): "" } ] width: 250 model: infoModel } } ColumnLayout { anchors.fill: parent spacing: 0 ToolBar { id: header Layout.fillWidth: true RowLayout { anchors.fill: parent - anchors.leftMargin: 10 - anchors.rightMargin: 10 + anchors.leftMargin: 20 + anchors.rightMargin: 20 spacing: anchors.leftMargin - Button { + IconToolButton { id: homeScreenButton - anchors.verticalCenter: parent.verticalCenter text: i18n("Return to Home Screen") iconName: "go-home" + colorScheme.colorSet: KColorScheme.Complementary onClicked: screen.homeScreenRequested() } Item { Layout.fillHeight: true Layout.fillWidth: true } - Button { + IconToolButton { id: repeatLessonButton iconName: "view-refresh" text: i18n("Repeat Lesson") + colorScheme.colorSet: KColorScheme.Complementary onClicked: screen.lessonRepetionRequested() } - Button { + IconToolButton { id: nextLessonButton iconName: "go-next-view" text: i18n("Next Lesson") enabled: internal.nextLesson + colorScheme.colorSet: KColorScheme.Complementary onClicked: screen.nextLessonRequested(internal.nextLesson) } } } Rectangle { Layout.fillHeight: true Layout.fillWidth: true - color: palette.base + color: colorScheme.normalBackground ColumnLayout { id: content anchors.fill: parent anchors.margins: 20 spacing: 15 Rectangle { id: caption Layout.fillWidth: true Layout.preferredHeight: captionContent.height + 30 radius: 15 color: "#eee4be" Item { id: captionContent anchors.centerIn: parent width: Math.max(mainCaption.width, subCaption.width) height: subCaption.visible? mainCaption.height + subCaption.height + 5: mainCaption.height Item { id: mainCaption anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter width: captionIcon.width + captionLabel.width + 7 height: Math.max(captionIcon.height, captionLabel.height) - QIconItem { + Icon { id: captionIcon anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter icon: internal.lessonPassed? "dialog-ok-apply": "dialog-cancel" width: height height: captionLabel.height } Label { id: captionLabel anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: internal.lessonPassed? - i18n("Congratulations! You have passed the lesson."): - i18n("You have not passed the lesson.") + i18n("Congratulations! You have passed the lesson."): + i18n("You have not passed the lesson.") font.pointSize: 1.5 * Qt.font({'family': 'sansserif'}).pointSize color: "#000000" } } Label { id: subCaption anchors.bottom: parent.bottom anchors.horizontalCenter: parent.horizontalCenter visible: text !== "" text: { if (!internal.lessonPassed) return i18n("Repeat the lesson. Your skills will improve automatically over time.") if (internal.nextLessonUnlocked) return i18n("Next lesson unlocked") return "" } color: "#000000" } } } Rectangle { id: statsBox Layout.fillWidth: true Layout.preferredHeight: 140 radius: 15 color: "#cccccc" StatBox { anchors.centerIn: parent width: parent.width - 26 stats: screen.stats referenceStats: screen.referenceStats } } Item { id: contentSpacer Layout.preferredHeight: 10 Layout.fillWidth: true } RowLayout { id: chartControls spacing: 5 Layout.fillWidth: true Label { id: showLabel - color: "#888" - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter text: i18nc("Show a specific type of statistic", "Show") + opacity: 0.7 } - Button { - id: chartTypeButton - anchors.verticalCenter: parent.verticalCenter - text: tabGroup.currentTab.title - iconName: tabGroup.currentTab.iconName - Layout.preferredWidth: chartTypeDialogContents.width - checked: chartTypeDialog.status === 'open' || chartTypeDialog.status === 'opening' - onClicked: { - if (checked) { - chartTypeDialog.close() - } - else { - chartTypeDialog.open() + ComboBox { + id: chartTypeComboBox + Layout.alignment: Qt.AlignVCenter + model: ListModel { + Component.onCompleted: { + append({"text": i18n("Progress"), "icon": "office-chart-area"}); + append({"text": i18n("Errors"), "icon": "office-chart-bar"}); } - } + textRole: "text" + currentIndex: 0 } Label { id: overLabel - color: "#888" - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter text: i18nc("Show a statistic over one or more lessons", "Over") - opacity: tabGroup.currentTab === learningProgressTab? 1: 0 + opacity: tabGroup.currentItem === learningProgressTab? 0.7: 0 Behavior on opacity { NumberAnimation {duration: 150} } } - Button { - id: learningProgressFilterButton - anchors.verticalCenter: parent.verticalCenter - text: learningProgressModel.filterByLesson? thisLessonButton.text: allLessonsButton.text - iconName: "view-filter" - checked: chartTypeDialog.status === 'open' || chartTypeDialog.status === 'opening' - opacity: tabGroup.currentTab === learningProgressTab? 1: 0 - onClicked: { - if (checked) { - learningProgressDialog.close() - } - else { - learningProgressDialog.open() + ComboBox { + id: learningProgressFilterComboBox + Layout.alignment: Qt.AlignVCenter + model: ListModel { + Component.onCompleted: { + append({"text": i18n("All Lessons"), "icon": "view-filter"}); + append({"text": i18n("This Lessons"), "icon": "view-filter"}); } } + textRole: "text" + currentIndex: 0 + opacity: tabGroup.currentItem === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } Item { - Layout.fillHeight: true Layout.fillWidth: true } Charts.LegendItem { id: accuracyLegend - textColor: palette.text - anchors.verticalCenter: parent.verticalCenter - opacity: tabGroup.currentTab === learningProgressTab? 1: 0 + textColor: colorScheme.normalText + Layout.alignment: Qt.AlignVCenter + opacity: tabGroup.currentItem === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } Charts.LegendItem { id: charactersPerMinuteLegend - textColor: palette.text - anchors.verticalCenter: parent.verticalCenter - opacity: tabGroup.currentTab === learningProgressTab? 1: 0 + textColor: colorScheme.normalText + Layout.alignment: Qt.AlignVCenter + opacity: tabGroup.currentItem === learningProgressTab? 1: 0 Behavior on opacity { NumberAnimation {duration: 150} } } } - TabView { - id: tabGroup + Item { Layout.fillHeight: true Layout.fillWidth: true - frameVisible: false - tabsVisible: false - property Tab currentTab: count > 0 && currentIndex != -1? getTab(currentIndex): null - Tab { - id: learningProgressTab - title: i18n("Progress") - property string iconName: "office-chart-area" - LearningProgressChart { - id: learningProgressChart - anchors.fill: parent - model: learningProgressModel - backgroundColor: palette.base + StackLayout { + anchors.fill: parent + id: tabGroup + currentIndex: chartTypeComboBox.currentIndex + property Item currentItem: currentIndex != -1? children[currentIndex]: null - onElemEntered: { - learningProgressPointTooltip.visualParent = elem - learningProgressPointTooltip.row = row - learningProgressPointTooltip.open() - } - onElemExited: { - learningProgressPointTooltip.close() - } + LearningProgressChart { + id: learningProgressTab + property string title: i18n("Progress") + property string iconName: "office-chart-area" + model: learningProgressModel + backgroundColor: colorScheme.normalBackground Component.onCompleted: { - accuracyLegend.dimension = learningProgressChart.accuracy - charactersPerMinuteLegend.dimension = learningProgressChart.charactersPerMinute + accuracyLegend.dimension = learningProgressTab.accuracy + charactersPerMinuteLegend.dimension = learningProgressTab.charactersPerMinute } Component.onDestruction: { - accuracyLegend.dimension = 0 - charactersPerMinuteLegend.dimension = 0 + accuracyLegend.dimension = null + charactersPerMinuteLegend.dimension = null } } - } - Tab { - id: errorsTab - title: i18n("Errors") - property string iconName: "office-chart-bar" - - Charts.BarChart{ - anchors.fill: parent - model: errorsModel - pitch: 60 - textRole: 3 // Qt::ToolTipRole - backgroundColor: palette.base - - dimensions: [ - Charts.Dimension { - dataColumn: 0 - color: "#ffb12d" - maximumValue: Math.max(4, Math.ceil(errorsModel.maximumErrorCount / 4) * 4) - label: i18n("Errors") + Item { + id: errorsTab + property string title: i18n("Errors") + property string iconName: "office-chart-bar" + + Charts.BarChart{ + model: errorsModel + pitch: 60 + textRole: 3 // Qt::ToolTipRole + backgroundColor: colorScheme.normalBackground + + dimensions: [ + Charts.Dimension { + dataColumn: 0 + color: "#ffb12d" + maximumValue: Math.max(4, Math.ceil(errorsModel.maximumErrorCount / 4) * 4) + label: i18n("Errors") + } + ] + + onElemEntered: { + errorsTooltip.visualParent = elem; + errorsTooltip.row = row + errorsTooltip.open() } - ] - onElemEntered: { - errorsTooltip.visualParent = elem; - errorsTooltip.row = row - errorsTooltip.open() - } - - onElemExited: { - errorsTooltip.close() + onElemExited: { + errorsTooltip.close() + } } } } } } } } } diff --git a/src/qml/trainingscreen/KeyboardUnavailableNotice.qml b/src/qml/trainingscreen/KeyboardUnavailableNotice.qml index 9e4f094..9dfab5a 100644 --- a/src/qml/trainingscreen/KeyboardUnavailableNotice.qml +++ b/src/qml/trainingscreen/KeyboardUnavailableNotice.qml @@ -1,53 +1,38 @@ /* - * Copyright 2012 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried + * Copyright 2018 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import org.kde.kquickcontrolsaddons 2.0 +import QtQuick 2.9 import ktouch 1.0 +import '../common' + Item { id: root - height: row.height + 20 + property KColorScheme colorScheme: KColorScheme {} + + height: label.height + 20 - Row { - id: row + IconLabel { + id: label anchors.centerIn: parent width: parent.width - 60 - height: Math.max(icon.height, label.height) - spacing: 3 - - QIconItem { - id: icon - anchors.verticalCenter: parent.verticalCenter - width: 22 - height: 22 - icon: "dialog-warning" - } - - Label { - id: label - anchors.verticalCenter: parent.verticalCenter - width: parent.width - icon.width - parent.spacing - text: i18n("No visualization available for your keyboard layout.") - color: "white" - } + iconName: "dialog-warning" + text: i18n("No visualization available for your keyboard layout.") + color: colorScheme.normalText } } diff --git a/src/qml/trainingscreen/TrainingScreen.qml b/src/qml/trainingscreen/TrainingScreen.qml index e5ec84c..20a060e 100644 --- a/src/qml/trainingscreen/TrainingScreen.qml +++ b/src/qml/trainingscreen/TrainingScreen.qml @@ -1,331 +1,333 @@ /* - * Copyright 2013 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried + * Copyright 2018 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.5 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 +import "../common" import "../keyboard" import "../meters" FocusScope { id: screen property KeyboardLayout keyboardLayout property Profile profile property Course course property Lesson lesson property alias stats: stats property alias referenceStats: referenceStats signal restartRequested() signal abortRequested() signal finished() property bool trainingStarted: false property bool trainingFinished: true property bool isActive: Qt.application.active function setLessonKeys() { if (!lesson) return; var chars = lesson.characters; var keyItems = keyboard.keyItems() var modifierItems = [] var usedModifiers = {} for (var i = 0; i < keyItems.length; i++) { var key = keyItems[i].key if (key.keyType() == "key") { keyItems[i].enabled = false; for (var j = 0; j < key.keyCharCount; j++) { var keyChar = key.keyChar(j) if (chars.indexOf(keyChar.value) != -1) { keyItems[i].enabled = true; if (keyChar.modifier !== "") { usedModifiers[keyChar.modifier] = true } } } } else { var type = keyItems[i].key.type if (type != SpecialKey.Return && type != SpecialKey.Backspace && type != SpecialKey.Space) { modifierItems.push(keyItems[i]) keyItems[i].enabled = false } else if (type == SpecialKey.Return && preferences.nextLineWithSpace) { keyItems[i].enabled = false } } } for (i = 0; i < modifierItems.length; i++) { var modifierItem = modifierItems[i] modifierItem.enabled = !!usedModifiers[modifierItem.key.modifierId] } } function reset() { toolbar.reset() trainingWidget.reset() screen.trainingStarted = false screen.trainingFinished = true profileDataAccess.loadReferenceTrainingStats(referenceStats, screen.profile, screen.course.id, screen.lesson.id) profileDataAccess.saveCourseProgress(lesson.id, profile, course.id, ProfileDataAccess.LastSelectedLesson) } function start() { screen.trainingFinished = false screen.trainingStarted = true } function forceActiveFocus() { trainingWidget.forceActiveFocus() } onLessonChanged: setLessonKeys() onIsActiveChanged: { if (!screen.isActive) { stats.stopTraining() } } + KColorScheme { + id: colorScheme + colorGroup: KColorScheme.Active + colorSet: KColorScheme.Complementary + } + TrainingStats { id: stats onTimeIsRunningChanged: { if (timeIsRunning) { screen.trainingStarted = false } } } TrainingStats { id: referenceStats } Shortcut { sequence: "Escape" enabled: screen.visible onActivated: { - if (menuOverlay.active) { - menuOverlay.hide() - } - else { - menuOverlay.show() + if (!menu.visible) { + menu.open() } } - } ColumnLayout { id: screenContent anchors.fill: parent spacing: 0 BorderImage { Layout.fillWidth: true Layout.preferredHeight: 41 border { top: 1 bottom: 1 } cache: false - source: utils.findImage("trainingscreen-toolbar.png") + source: "qrc:///ktouch/images/trainingscreen-toolbar.png" horizontalTileMode: BorderImage.Repeat verticalTileMode: BorderImage.Repeat TrainingScreenToolbar { id: toolbar anchors.fill: parent trainingStarted: screen.trainingStarted trainingFinished: screen.trainingFinished stats: stats - menuOverlayItem: menuOverlay + menu: menu } } BorderImage { id: header Layout.fillWidth: true Layout.preferredHeight: visible? 130: 0 visible: preferences.showStatistics border { top: 1 bottom: 1 } - source: utils.findImage("trainingscreen-header.png") + source: "qrc:///ktouch/images/trainingscreen-header.png" cache: false StatBox { anchors.centerIn: parent width: parent.width - 60 stats: stats referenceStats: referenceStats } } BorderImage { id: body Layout.fillWidth: true Layout.fillHeight: true border { top: 1 bottom: 1 } - source: utils.findImage("trainingscreen-viewport.png") + source: "qrc:///ktouch/images/trainingscreen-viewport.png" cache: false TrainingWidget { id: trainingWidget anchors.fill: parent lesson: screen.lesson keyboardLayout: screen.keyboardLayout trainingStats: stats overlayContainer: trainingOverlayContainer onKeyPressed: keyboard.handleKeyPress(event) onKeyReleased: keyboard.handleKeyRelease(event) onNextCharChanged: keyboard.updateKeyHighlighting() onIsCorrectChanged: keyboard.updateKeyHighlighting() onFinished: { profileDataAccess.saveTrainingStats(stats, screen.profile, screen.course.id, screen.lesson.id) screen.finished(stats) screen.trainingFinished = true } } BorderImage { anchors.fill: parent border { top: 3 bottom: 3 } - source: utils.findImage("trainingscreen-viewport-shadow.png") + source: "qrc:///ktouch/images/trainingscreen-viewport-shadow.png" cache: false } } BorderImage { id: footer visible: preferences.showKeyboard Layout.fillWidth: true Layout.preferredHeight: visible? screen.keyboardLayout.isValid? Math.round(Math.min((parent.height - toolbar.height - header.height) / 2, parent.width / keyboard.aspectRatio)): keyboardUnavailableNotice.height: 0 border { top: 1 bottom: 1 } - source: utils.findImage("trainingscreen-footer.png") + source: "qrc:///ktouch/images/trainingscreen-footer.png" cache: false + Keyboard { id: keyboard property variant highlightedKeys: [] function highlightKey(which) { for (var i = 0; i < highlightedKeys.length; i++) highlightedKeys[i].isHighlighted = false var keys = findKeyItems(which) var newHighlightedKeys = [] for (var index = 0; index < keys.length ; index++) { var key = keys[index] if (key) { key.isHighlighted = true newHighlightedKeys.push(key) if (typeof which == "string") { for (var i = 0; i < key.key.keyCharCount; i++) { var keyChar = key.key.keyChar(i) if (keyChar.value == which && keyChar.modifier != "") { var modifier = findModifierKeyItem(keyChar.modifier) if (modifier) { modifier.isHighlighted = true newHighlightedKeys.push(modifier) } break } } } } } highlightedKeys = newHighlightedKeys } function updateKeyHighlighting() { if (!visible) return; if (trainingWidget.isCorrect) { if (trainingWidget.nextChar !== "") { highlightKey(trainingWidget.nextChar) } else { highlightKey(preferences.nextLineWithSpace? Qt.Key_Space: Qt.Key_Return) } } else { highlightKey(Qt.Key_Backspace) } } keyboardLayout: screen.keyboardLayout anchors { fill: parent leftMargin: 30 rightMargin: 30 topMargin: 10 bottomMargin: 10 } onKeyboardUpdate: { setLessonKeys() highlightedKeys = [] updateKeyHighlighting() } } KeyboardUnavailableNotice { id: keyboardUnavailableNotice + colorScheme: colorScheme visible: !screen.keyboardLayout.isValid + width: parent.width } } } Item { id: trainingOverlayContainer anchors.fill: parent } - TrainingScreenMenuOverlay { - id: menuOverlay - blurSource: screenContent - anchors.fill: parent + TrainingScreenMenu { + id: menu onClosed: trainingWidget.forceActiveFocus() onRestartRequested: screen.restartRequested() onAbortRequested: screen.abortRequested() } } diff --git a/src/qml/trainingscreen/TrainingScreenMenu.qml b/src/qml/trainingscreen/TrainingScreenMenu.qml new file mode 100644 index 0000000..5f483b2 --- /dev/null +++ b/src/qml/trainingscreen/TrainingScreenMenu.qml @@ -0,0 +1,93 @@ +/* + * Copyright 2018 Sebastian Gottfried + * + * 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) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import QtQuick 2.9 +import QtQuick.Layouts 1.3 +import ktouch 1.0 + +import '../common' + + +PopupDialog { + id: root + + signal restartRequested() + signal abortRequested() + + onOpened: { + resumeButton.forceActiveFocus() + } + + title: i18n("Menu") + + padding: 20 + + contentItem: Item { + width: column.width + height: column.height + + Column { + id: column + focus: true + anchors.centerIn: parent + spacing: 20 + width: 1.5 * Math.max(resumeButton.implicitWidth, restartButton.implicitWidth, returnButton.implicitWidth) + + IconButton { + id: resumeButton + iconName: "go-next-view" + text: i18n("Resume Training") + bgColor: colorScheme.positiveBackground + width: parent.width + onClicked: close() + KeyNavigation.backtab: returnButton + KeyNavigation.tab: restartButton + KeyNavigation.down: restartButton + } + + IconButton { + id: restartButton + iconName: "view-refresh" + text: i18n("Restart Lesson") + width: parent.width + onClicked: { + close() + restartRequested() + } + KeyNavigation.backtab: resumeButton + KeyNavigation.tab: returnButton + KeyNavigation.up: resumeButton + KeyNavigation.down: returnButton + } + + IconButton { + id: returnButton + iconName: "go-home" + text: i18n("Return to Home Screen") + width: parent.width + onClicked: { + close() + abortRequested() + } + KeyNavigation.backtab: restartButton + KeyNavigation.tab: resumeButton + KeyNavigation.up: restartButton + } + } + } +} + diff --git a/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml b/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml deleted file mode 100644 index a3a07da..0000000 --- a/src/qml/trainingscreen/TrainingScreenMenuOverlay.qml +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2012 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried - * - * 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) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 -import QtGraphicalEffects 1.0 -import ktouch 1.0 - -Loader { - id: loader - - property Item blurSource; - - signal closed() - signal restartRequested() - signal abortRequested() - - active: false - - function show() { - loader.active = true - } - - function hide() { - item.opacity = 0 - } - - sourceComponent: Component { - FocusScope { - id: item - anchors.fill: parent - - opacity: loader.status == Loader.Ready? 1: 0 - - Component.onCompleted: resumeButton.forceActiveFocus() - - Behavior on opacity { - SequentialAnimation { - NumberAnimation { - duration: 300 - easing.type: Easing.InOutQuad - } - ScriptAction { - script: { - if (opacity == 0) { - loader.active = false; - closed() - } - } - } - } - } - - - ShaderEffectSource { - id: effectSource - sourceItem: blurSource - anchors.fill: parent - hideSource: false - } - - HueSaturation { - id: desaturatedBackground - source: effectSource - anchors.fill: parent - lightness: -0.3 - saturation: -0.5 - visible: false - } - - FastBlur { - anchors.fill: parent - source: desaturatedBackground - radius: 50 - } - - Rectangle { - anchors.fill: parent - color: "#55000000" - } - - /* swallow all mouse events */ - MouseArea { - anchors.fill: parent - enabled: false - hoverEnabled: enabled - } - - - GroupBox { - id: groupBox - anchors.centerIn: parent - width: column.width + 30 - height: column.height + 30 - - Column { - id: column - focus: true - anchors.centerIn: parent - spacing: 15 - width: Math.max(resumeButton.implicitWidth, restartButton.implicitWidth, returnButton.implicitWidth) - - Button { - id: resumeButton - iconName: "go-next-view" - text: i18n("Resume Training") - width: parent.width - onClicked: hide() - KeyNavigation.backtab: returnButton - KeyNavigation.tab: restartButton - KeyNavigation.down: restartButton - } - - Button { - id: restartButton - iconName: "view-refresh" - text: i18n("Restart Lesson") - width: parent.width - onClicked: { - restartRequested() - hide() - } - KeyNavigation.backtab: resumeButton - KeyNavigation.tab: returnButton - KeyNavigation.up: resumeButton - KeyNavigation.down: returnButton - } - - Button { - id: returnButton - iconName: "go-home" - text: i18n("Return to Home Screen") - width: parent.width - onClicked: { - abortRequested() - hide() - } - KeyNavigation.backtab: restartButton - KeyNavigation.tab: resumeButton - KeyNavigation.up: restartButton - } - - Keys.onDownPressed: { - if (resumeButton.focus) - restartButton.focus = true; - else if (restartButton.focus) - returnButton.focus = true; - } - - Keys.onUpPressed: { - if (restartButton.focus) - resumeButton.focus = true; - else if (returnButton.focus) - restartButton.focus = true; - } - - Keys.onEscapePressed: { - hide() - } - } - } - } - - } -} - diff --git a/src/qml/trainingscreen/TrainingScreenToolbar.qml b/src/qml/trainingscreen/TrainingScreenToolbar.qml index 265d7ee..5ad44b5 100644 --- a/src/qml/trainingscreen/TrainingScreenToolbar.qml +++ b/src/qml/trainingscreen/TrainingScreenToolbar.qml @@ -1,81 +1,80 @@ /* - * Copyright 2012 Sebastian Gottfried - * Copyright 2015 Sebastian Gottfried + * Copyright 2018 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 -import QtQuick.Layouts 1.1 +import QtQuick 2.9 +import QtQuick.Layouts 1.3 import ktouch 1.0 import "../common" -Item { - id: item +ToolBar { + id: root property TrainingStats stats property bool trainingStarted: false property bool trainingFinished: true - property Item menuOverlayItem + property PopupDialog menu + + dimFactor: 1.8 function setMessage() { if (!stats.timeIsRunning && !trainingFinished) { var msg = trainingStarted? i18n("Training session started. Time begins running with the first keystroke."): i18n("Training session interrupted. Time begins running again with next keystroke.") messageBox.showMessage(msg, "media-playback-pause") } else { messageBox.clearMessage() } } function reset() { messageBox.clearMessageImmediately() } onTrainingStartedChanged: setMessage() onTrainingFinishedChanged: setMessage() Connections { target: stats onTimeIsRunningChanged: setMessage() } Row { anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: 30 rightMargin: 30 - verticalCenterOffset: -1 } - spacing: 3 - height: menuButton.height + spacing: 5 - ToolButton { + IconToolButton { id: menuButton iconName: "go-home" - onClicked: item.menuOverlayItem.show() + onClicked: root.menu.open() + colorScheme: root.colorScheme } MessageBox { id: messageBox } } } diff --git a/src/qml/trainingscreen/TrainingWidget.qml b/src/qml/trainingscreen/TrainingWidget.qml index bd29820..8139845 100644 --- a/src/qml/trainingscreen/TrainingWidget.qml +++ b/src/qml/trainingscreen/TrainingWidget.qml @@ -1,244 +1,244 @@ /* - * Copyright 2013 Sebastian Gottfried + * Copyright 2018 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -import QtQuick 2.4 -import QtQuick.Controls 1.3 +import QtQuick 2.9 import ktouch 1.0 import "../keyboard" +import "../common" FocusScope { id: trainingWidget property Lesson lesson property KeyboardLayout keyboardLayout property TrainingStats trainingStats property Item overlayContainer property alias nextChar: trainingLine.nextCharacter property alias isCorrect: trainingLine.isCorrect property int position: -1 signal finished signal keyPressed(variant event) signal keyReleased(variant event) function reset() { stats.reset() lessonPainter.reset() sheetFlick.scrollToCurrentLine() trainingLine.active = true } function forceActiveFocus() { trainingLine.forceActiveFocus() } Timer { id: stopTimer interval: 5000 onTriggered: { stats.stopTraining() } } MouseArea { anchors.fill: parent onClicked: trainingLine.forceActiveFocus() } ScrollView { anchors.fill: parent Flickable { id: sheetFlick anchors.fill: parent contentWidth: parent.width contentHeight: sheet.height + 60 clip: true flickableDirection: Flickable.VerticalFlick function currentLineY() { var cursorRect = lessonPainter.cursorRectangle var y = cursorRect.y + sheet.y + (cursorRect.height / 2) return Math.max(Math.min((y - (height / 2)), contentHeight - height), 0) } function scrollToCurrentLine() { scrollAnimation.to = currentLineY() scrollAnimation.start() } onHeightChanged: { contentY = currentLineY() } NumberAnimation { target: sheetFlick id: scrollAnimation duration: 150 property: "contentY" } Rectangle { id: sheet color: "#fff" x: 30 y: 30 width: trainingWidget.width - 60 height: lessonPainter.height border { width: 1 color: "#000" } LessonPainter { id: lessonPainter anchors.centerIn: sheet lesson: trainingWidget.lesson maximumWidth: parent.width trainingLineCore: trainingLine onDone: { trainingLine.active = false trainingWidget.finished(); stats.stopTraining(); } TrainingLineCore { id: trainingLine anchors.fill: parent focus: true trainingStats: stats cursorItem: cursor onActiveFocusChanged: { if (!trainingLine.activeFocus) { trainingStats.stopTraining() } } Keys.onPressed: { if (!trainingLine.active) return cursorAnimation.restart() trainingStats.startTraining() stopTimer.restart() if (!event.isAutoRepeat) { trainingWidget.keyPressed(event) } } Keys.onReleased: { if (!trainingLine.active) return if (!event.isAutoRepeat) { trainingWidget.keyReleased(event) } } KeyNavigation.backtab: trainingLine KeyNavigation.tab: trainingLine } Rectangle { id: cursor color: "#000" x: Math.floor(lessonPainter.cursorRectangle.x) y: lessonPainter.cursorRectangle.y width: lessonPainter.cursorRectangle.width height: lessonPainter.cursorRectangle.height onYChanged: sheetFlick.scrollToCurrentLine() SequentialAnimation { id: cursorAnimation running: trainingLine.active && trainingLine.activeFocus && Qt.application.active loops: Animation.Infinite PropertyAction { target: cursor property: "opacity" value: 1 } PauseAnimation { duration: 500 } PropertyAction { target: cursor property: "opacity" value: 0 } PauseAnimation { duration: 500 } } } } } } } KeyItem { id: hintKey parent: trainingWidget.overlayContainer anchors.horizontalCenter: parent.horizontalCenter y: Math.max(height, Math.min(parent.height - 2 * height, sheetFlick.mapToItem(parent, 0, cursor.y + 3 * cursor.height - sheetFlick.contentY).y)) property Key defaultKey: Key {} property KeyboardLayout defaultKeyboardLayout: KeyboardLayout {} key: { var specialKeyType switch (trainingLine.hintKey) { case Qt.Key_Return: specialKeyType = SpecialKey.Return break case Qt.Key_Backspace: specialKeyType = SpecialKey.Backspace break case Qt.Key_Space: specialKeyType = SpecialKey.Space break default: specialKeyType = -1 } for (var i = 0; i < keyboardLayout.keyCount; i++) { var key = keyboardLayout.key(i) if (key.keyType() === "specialKey" && key.type === specialKeyType) { return key; } } return defaultKey } opacity: trainingLine.hintKey !== -1? 1: 0 isHighlighted: opacity == 1 keyboardLayout: screen.keyboardLayout || defaultKeyboardLayout Behavior on opacity { NumberAnimation { duration: 150 } } } } diff --git a/src/undocommands/coursecommands.h b/src/undocommands/coursecommands.h index 59a90d9..459ce2d 100644 --- a/src/undocommands/coursecommands.h +++ b/src/undocommands/coursecommands.h @@ -1,157 +1,157 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef COURSECOMMANDS_H #define COURSECOMMANDS_H #include class Course; class Lesson; class SetCourseTitleCommand: public QUndoCommand { public: - SetCourseTitleCommand(Course* course, const QString& oldTitle, QUndoCommand* parent = 0); + SetCourseTitleCommand(Course* course, const QString& oldTitle, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; QString m_oldTitle; QString m_newTitle; }; class SetCourseKeyboadLayoutNameCommand: public QUndoCommand { public: - SetCourseKeyboadLayoutNameCommand(Course* course, const QString& oldKeyboardLayoutName, QUndoCommand* parent = 0); + SetCourseKeyboadLayoutNameCommand(Course* course, const QString& oldKeyboardLayoutName, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; QString m_oldKeyboardLayoutName; QString m_newKeyboardLayoutName; }; class SetCourseDescriptionCommand: public QUndoCommand { public: - SetCourseDescriptionCommand(Course* course, const QString& oldDescription, QUndoCommand* parent = 0); + SetCourseDescriptionCommand(Course* course, const QString& oldDescription, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; QString m_oldDescription; QString m_newDescription; }; class AddLessonCommand: public QUndoCommand { public: - AddLessonCommand(Course* course, int lessonIndex, const QString& lessonTitle, QUndoCommand* parent = 0); + AddLessonCommand(Course* course, int lessonIndex, const QString& lessonTitle, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; int m_lessonIndex; QString m_lessonId; }; class RemoveLessonCommand: public QUndoCommand { public: - RemoveLessonCommand(Course* course, int lessonIndex, QUndoCommand* parent = 0); + RemoveLessonCommand(Course* course, int lessonIndex, QUndoCommand* parent = nullptr); virtual ~RemoveLessonCommand(); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; int m_lessonIndex; Lesson* m_backupLesson; }; class MoveLessonCommand: public QUndoCommand { public: - MoveLessonCommand(Course* course, int oldLessonIndex, int newLessonIndex, QUndoCommand* parent = 0); + MoveLessonCommand(Course* course, int oldLessonIndex, int newLessonIndex, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; int m_oldLessonIndex; int m_newLessonIndex; }; class SetLessonTitleCommand: public QUndoCommand { public: SetLessonTitleCommand(Course* course, int lessonIndex, const QString& oldTitle, QUndoCommand* parent=0); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; int m_lessonIndex; QString m_oldTitle; QString m_newTitle; }; class SetLessonNewCharactersCommand: public QUndoCommand { public: - SetLessonNewCharactersCommand(Course* course, int lessonIndex, const QString& oldNewCharacters, QUndoCommand* parent = 0); + SetLessonNewCharactersCommand(Course* course, int lessonIndex, const QString& oldNewCharacters, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; int m_lessonIndex; QString m_oldNewCharacters; QString m_newNewCharacters; }; class SetLessonTextCommand: public QUndoCommand { public: - SetLessonTextCommand(Course* course, int lessonIndex, const QString& oldText, QUndoCommand* parent = 0); + SetLessonTextCommand(Course* course, int lessonIndex, const QString& oldText, QUndoCommand* parent = nullptr); void undo() override; void redo() override; bool mergeWith(const QUndoCommand* command) override; int id() const override; private: Course* m_course; int m_lessonIndex; QString m_oldText; QString m_newText; }; #endif // COURSECOMMANDS_H diff --git a/src/undocommands/keyboardlayoutcommands.h b/src/undocommands/keyboardlayoutcommands.h index 228c385..330c0d8 100644 --- a/src/undocommands/keyboardlayoutcommands.h +++ b/src/undocommands/keyboardlayoutcommands.h @@ -1,267 +1,267 @@ /* * Copyright 2012 Sebastian Gottfried * * 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) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef KEYBOARDLAYOUTCOMMANDS_H #define KEYBOARDLAYOUTCOMMANDS_H #include #include #include "core/specialkey.h" #include "core/keychar.h" class KeyboardLayout; class SetKeyboardLayoutTitleCommand : public QUndoCommand { public: - explicit SetKeyboardLayoutTitleCommand(KeyboardLayout* layout, const QString& newTitle, QUndoCommand* parent = 0); + explicit SetKeyboardLayoutTitleCommand(KeyboardLayout* layout, const QString& newTitle, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; QString m_oldTitle; QString m_newTitle; }; class SetKeyboardLayoutNameCommand : public QUndoCommand { public: - explicit SetKeyboardLayoutNameCommand(KeyboardLayout* layout, const QString& newName, QUndoCommand* parent = 0); + explicit SetKeyboardLayoutNameCommand(KeyboardLayout* layout, const QString& newName, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; QString m_oldName; QString m_newName; }; class SetKeyboardLayoutSizeCommand : public QUndoCommand { public: - explicit SetKeyboardLayoutSizeCommand(KeyboardLayout* layout, const QSize& newSize, QUndoCommand* parent = 0); + explicit SetKeyboardLayoutSizeCommand(KeyboardLayout* layout, const QSize& newSize, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; QSize m_oldSize; QSize m_newSize; }; class AddKeyCommand : public QUndoCommand { public: - AddKeyCommand(KeyboardLayout* layout, AbstractKey* key, QUndoCommand* parent = 0); - ~AddKeyCommand(); + AddKeyCommand(KeyboardLayout* layout, AbstractKey* key, QUndoCommand* parent = nullptr); + ~AddKeyCommand() override; void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; AbstractKey* m_backupKey; }; class RemoveKeyCommand : public QUndoCommand { public: - RemoveKeyCommand(KeyboardLayout* layout, int keyIndex, QUndoCommand* parent = 0); + RemoveKeyCommand(KeyboardLayout* layout, int keyIndex, QUndoCommand* parent = nullptr); ~RemoveKeyCommand(); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; AbstractKey* m_backupKey; }; class SetKeyGeometryCommand : public QUndoCommand { public: - explicit SetKeyGeometryCommand(KeyboardLayout* layout, int keyIndex, const QRect& newRect, QUndoCommand* parent = 0); + explicit SetKeyGeometryCommand(KeyboardLayout* layout, int keyIndex, const QRect& newRect, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; QRect m_oldRect; QRect m_newRect; }; class SetKeyFingerIndexCommand : public QUndoCommand { public: - explicit SetKeyFingerIndexCommand(KeyboardLayout* layout, int keyIndex, int newFingerIndex, QUndoCommand* parent = 0); + explicit SetKeyFingerIndexCommand(KeyboardLayout* layout, int keyIndex, int newFingerIndex, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; int m_oldFingerIndex; int m_newFingerIndex; }; class SetKeyHasHapticMarkerCommand : public QUndoCommand { public: - explicit SetKeyHasHapticMarkerCommand(KeyboardLayout* layout, int keyIndex, bool newHasHapticMarker, QUndoCommand* parent = 0); + explicit SetKeyHasHapticMarkerCommand(KeyboardLayout* layout, int keyIndex, bool newHasHapticMarker, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; bool m_oldHasHapticMarker; bool m_newHasHapticMarker; }; class AddKeyCharCommand : public QUndoCommand { public: - AddKeyCharCommand(KeyboardLayout* layout, int keyIndex, QUndoCommand* parent = 0); + AddKeyCharCommand(KeyboardLayout* layout, int keyIndex, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; }; class RemoveKeyCharCommand : public QUndoCommand { public: - RemoveKeyCharCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, QUndoCommand* parent = 0); + RemoveKeyCharCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, QUndoCommand* parent = nullptr); ~RemoveKeyCharCommand(); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; int m_keyCharIndex; KeyChar* m_backupKeyChar; }; class SetKeyCharValueCommand : public QUndoCommand { public: - explicit SetKeyCharValueCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, QChar newValue, QUndoCommand* parent = 0); + explicit SetKeyCharValueCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, QChar newValue, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; int m_keyCharIndex; QChar m_oldValue; QChar m_newValue; }; class SetKeyCharModifierCommand : public QUndoCommand { public: - explicit SetKeyCharModifierCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, const QString& newModifier, QUndoCommand* parent = 0); + explicit SetKeyCharModifierCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, const QString& newModifier, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; int m_keyCharIndex; QString m_oldModifier; QString m_newModifier; }; class SetKeyCharPositionCommand : public QUndoCommand { public: - explicit SetKeyCharPositionCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, KeyChar::Position newPosition, QUndoCommand* parent = 0); + explicit SetKeyCharPositionCommand(KeyboardLayout* layout, int keyIndex, int keyCharIndex, KeyChar::Position newPosition, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; int m_keyCharIndex; KeyChar::Position m_oldPosition; KeyChar::Position m_newPosition; }; class SetSpecialKeyTypeCommand : public QUndoCommand { public: - explicit SetSpecialKeyTypeCommand(KeyboardLayout* layout, int keyIndex, SpecialKey::Type newType, QUndoCommand* parent = 0); + explicit SetSpecialKeyTypeCommand(KeyboardLayout* layout, int keyIndex, SpecialKey::Type newType, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; SpecialKey::Type m_oldType; SpecialKey::Type m_newType; }; class SetSpecialKeyLabelCommand : public QUndoCommand { public: - explicit SetSpecialKeyLabelCommand(KeyboardLayout* layout, int keyIndex, const QString& newLabel, QUndoCommand* parent = 0); + explicit SetSpecialKeyLabelCommand(KeyboardLayout* layout, int keyIndex, const QString& newLabel, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; QString m_oldLabel; QString m_newLabel; }; class SetSpecialKeyModifierIdCommand : public QUndoCommand { public: - explicit SetSpecialKeyModifierIdCommand(KeyboardLayout* layout, int keyIndex, const QString& newModifiewId, QUndoCommand* parent = 0); + explicit SetSpecialKeyModifierIdCommand(KeyboardLayout* layout, int keyIndex, const QString& newModifiewId, QUndoCommand* parent = nullptr); void undo() override; void redo() override; int id() const override; bool mergeWith(const QUndoCommand* other) override; private: KeyboardLayout* m_layout; int m_keyIndex; QString m_oldModifierId; QString m_newModifierId; }; #endif // KEYBOARDLAYOUTCOMMANDS_H