diff --git a/src/ktouchcontext.cpp b/src/ktouchcontext.cpp index 5d63816..10704ae 100644 --- a/src/ktouchcontext.cpp +++ b/src/ktouchcontext.cpp @@ -1,202 +1,208 @@ /* * 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 #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 = "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, SIGNAL(layoutChanged()), SIGNAL(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(); } 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("preferences")) { return; } KConfigDialog* dialog = new KConfigDialog(m_mainWindow, "preferences", Preferences::self()); dialog->setFaceType(KPageDialog::List); dialog->setModal(true); dialog->addPage(new TrainingConfigWidget(), i18n("Training"), "chronometer", i18n("Training Settings")); dialog->addPage(new ColorsConfigWidget(), i18n("Colors"), "preferences-desktop-color", i18n("Color Settings")); dialog->show(); } void KTouchContext::configureShortcuts() { KShortcutsDialog::configure(m_actionCollection, KShortcutsEditor::LetterShortcutsDisallowed, m_mainWindow); } void KTouchContext::configureKeyboard() { 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("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); } #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("KCModule") && !service->noDisplay(); } diff --git a/src/ktouchcontext.h b/src/ktouchcontext.h index 74d9c16..63662b7 100644 --- a/src/ktouchcontext.h +++ b/src/ktouchcontext.h @@ -1,70 +1,71 @@ /* * 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) public: explicit KTouchContext(KMainWindow* mainWindow, QQuickView* view, QObject* parent = 0); ~KTouchContext(); QString keyboardLayoutName() const; DataIndex* dataIndex(); Q_INVOKABLE void showMenu(int xPos, int yPos); + Q_INVOKABLE Lesson* createLesson(); public slots: void showResourceEditor(); 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/qml/homescreen/LessonSelector.qml b/src/qml/homescreen/LessonSelector.qml index 9512e10..0f79bba 100644 --- a/src/qml/homescreen/LessonSelector.qml +++ b/src/qml/homescreen/LessonSelector.qml @@ -1,346 +1,377 @@ /* * 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 2.0 +import QtQuick.Controls 2.2 import QtQuick.Layouts 1.1 import ktouch 1.0 import QtGraphicalEffects 1.0 import "../common" ColumnLayout { id: root property Profile profile property DataIndexCourse dataIndexCourse property KeyboardLayout keyboardLayout property alias course: courseItem property Lesson selectedLesson: null signal lessonSelected(Course course, Lesson lesson) function update() { if (!course.isValid) return; if (!profile) return; course.updateLastUnlockedLessonIndex(); selectLastLesson() } spacing: 0 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) { root.selectedLesson = course.lesson(index) content.currentIndex = index break } } } } onProfileChanged: update() onDataIndexCourseChanged: { 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 (root.dataIndexCourse === null) { return } 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 (profile.skillLevel === Profile.Advanced) { + if (course.kind == Course.LessonCollection || profile.skillLevel === Profile.Advanced) { lastUnlockedLessonIndex = course.lessonCount - 1; return } 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 } } } } Component.onCompleted: update() } StatPopupDialog { id: statPopupDialog profile: root.profile course: course lesson: root.selectedLesson } LessonEditorDialog { id: lesseonEditorDialog profile: root.profile keyboardLayout: root.keyboardLayout onClosed: { - courseItem.update() - root.update() + courseItem.update(); + courseItem.updateLastUnlockedLessonIndex(); } } Item { Layout.fillWidth: true Layout.preferredHeight: header.height z: 2 Column { id: header width: parent.width height: toolbar.height + courseDescriptionItem.height ToolBar { id: toolbar width: parent.width background: Rectangle { 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.5) } RowLayout { anchors.fill: parent anchors.leftMargin: 20 spacing: 5 Label { text: root.course? root.course.title: "" font.bold: true color: toolbarColorScheme.normalText } IconToolButton { id: toggleCourseDesciptionButton icon: "help-about" checkable: true color: toolbarColorScheme.normalText backgroundColor: toolbarColorScheme.normalBackground Layout.fillHeight: true Layout.preferredWidth: toolbar.height - } + } + + + ToolSeparator { + visible: courseItem.editable + } + + IconToolButton { + id: newLessonButton + icon: "document-new" + text: "Add New Lesson" + color: toolbarColorScheme.normalText + visible: courseItem.editable + backgroundColor: toolbarColorScheme.normalBackground + Layout.fillHeight: true + onClicked: { + var lesson = ktouch.createLesson(); + lesson.id = utils.uuid() + lesseonEditorDialog.editLesson(lesson) + } + } + + IconToolButton { + id: deleteLessonButton + icon: "edit-delete" + text: "Delete Lesson" + color: toolbarColorScheme.normalText + visible: courseItem.editable + backgroundColor: toolbarColorScheme.normalBackground + Layout.fillHeight: true + } Item { Layout.fillWidth: true } IconToolButton { id: configureButton icon: "application-menu" color: toolbarColorScheme.normalText backgroundColor: toolbarColorScheme.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 active: toggleCourseDesciptionButton.checked description: courseItem.description } } DropShadow { anchors.fill: header source: header samples: 16 horizontalOffset: 0 verticalOffset: 0 } } Item { Layout.fillHeight: true Layout.fillWidth: true z: 1 KColorScheme { id: gridColorScheme colorGroup: KColorScheme.Active colorSet: KColorScheme.View } Rectangle { anchors.fill: parent color: gridColorScheme.shade(gridColorScheme.normalBackground, KColorScheme.DarkShade, 1, 0.0) } GridView { id: content anchors.fill: parent anchors.leftMargin: 20 clip: true focus: true 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) } } } model: LessonModel { id: lessonModel course: courseItem } onCurrentIndexChanged: { if (lessonModel.rowCount() > 0) { 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.topMargin: 10 anchors.leftMargin: 0 anchors.rightMargin: 20 anchors.bottomMargin: 10 anchors.centerIn: parent lesson: dataRole selected: content.currentIndex == index - editButtonVisible: course.id == "custom_lessons" + editButtonVisible: courseItem.editable onClicked: { item.forceActiveFocus() content.currentIndex = index } onDoubleClicked: { if (index <= course.lastUnlockedLessonIndex) { lessonSelected(course, dataRole) } } onStatButtonClicked: { statPopupDialog.open() } onEditButtonClicked: { lesseonEditorDialog.editLesson(lesson) } } LessonLockedNotice { anchors.centerIn: parent visible: index > course.lastUnlockedLessonIndex glowColor: lessonItem.background.color } } ScrollBar.vertical: ScrollBar { } } } 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 icon: "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 } } }