Коммит 75ad16c3 создал по автору Кривошеин Максим Константинович's avatar Кривошеин Максим Константинович
Просмотр файлов

Лабораторная работа 7: C++-синглтон настроек и QML



- синглтон AppSettings хранит настройки в текстовом файле
  settings.txt (ключ=значение), данные сохраняются между запусками
- методы getAppSettingValue / setAppSettingValue доступны из QML,
  синглтон подключён в rootContext как appSettings
- экраны: список настроек, добавление новой настройки,
  изменение значения существующей настройки
- настройка listview_margin задаёт отступ списка от краёв экрана,
  редактируется ползунком с предпросмотром
- README со скриншотами

Co-Authored-By: default avatarClaude Opus 5.5 <noreply@anthropic.com>
владелец f5567203
# C++ objects and libs
*.slo
*.lo
*.o
*.a
*.la
*.lai
*.so
*.so.*
*.dll
*.dylib
# Qt-es
object_script.*.Release
object_script.*.Debug
*_plugin_import.cpp
/.qmake.cache
/.qmake.stash
*.pro.user
*.pro.user.*
*.qbs.user
*.qbs.user.*
*.moc
moc_*.cpp
moc_*.h
qrc_*.cpp
ui_*.h
*.qmlc
*.jsc
Makefile*
*build-*
build/
*.qm
*.prl
*.qmlls.ini
# Qt unit tests
target_wrapper.*
# QtCreator
*.autosave
# QtCreator Qml
*.qmlproject.user
*.qmlproject.user.*
# QtCreator CMake
CMakeLists.txt.user*
# QtCreator 4.8< compilation database
compile_commands.json
# QtCreator local machine specific files for imported projects
*creator.user*
*_qmlcache.qrc
MIT License
Copyright (c) 2026 Saint-Petersburg State University of Economics
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
# Лабораторная работа № 7. Интеграция C++ и QML: настройки приложения
**Студент:** Кривошеин Максим Константинович, группа ИС-2301
**Дисциплина:** Разработка мобильных приложений, СПбГЭУ
**Платформа:** ОС Аврора 5.2 (Aurora SDK 5.2.0.180), Qt 5.6, C++ / QML (Sailfish Silica)
## Задание
Разработать приложение для интеграции C++ с QML.
- Разработать C++-синглтон, хранящий настройки приложения в файле на диске в виде
текстового файла. Настройки должны сохраняться при перезапуске приложения.
- В QML-части должны быть методы `getAppSettingValue("key")` и
`setAppSettingValue("key", setting_value)`.
- Подключить синглтон как property в `rootContext` и использовать его по всему приложению.
- Первый экран — список сохранённых настроек.
- Второй экран — добавление новой произвольной настройки: ввод имени и значения.
- Третий экран — изменение имеющейся настройки: имя автоматически проставляется из списка,
можно поменять значение.
- В настройках должна быть настройка отступов ListView от краёв экрана с возможностью
изменения и сохранения в общий список.
## Реализация
**C++-синглтон** — `src/appsettings.{h,cpp}`, класс `AppSettings`:
- единственный экземпляр создаётся при первом вызове `AppSettings::instance()`, конструктор
закрыт, копирование запрещено (`Q_DISABLE_COPY`);
- настройки хранятся в текстовом файле
`~/.local/share/ru.krivoshein/SettingsDemo/settings.txt`, по строке `ключ=значение` на настройку;
файл перезаписывается безопасно через `QSaveFile`;
- методы для QML (`Q_INVOKABLE`): `getAppSettingValue(key, defaultValue)`,
`setAppSettingValue(key, value)`, `contains(key)`, `removeAppSetting(key)`, `isValidKey(key)`;
- свойства: `keys` (список имён для ListView), `revision` (номер изменения, на него
подписываются привязки QML), `filePath`;
- при первом запуске создаются настройки по умолчанию: `listview_margin=24`,
`app_title=Мои настройки`, `cover_text=Настройки из файла`.
**Подключение к QML** — `src/main.cpp`:
```cpp
view->rootContext()->setContextProperty(QStringLiteral("appSettings"), AppSettings::instance());
```
Объект `appSettings` используется во всех частях приложения: заголовок главного экрана
берётся из `app_title`, отступы списка — из `listview_margin`, обложка — из `cover_text`.
**Экраны:**
| № | Файл | Назначение |
|---|---|---|
| 1 | `qml/pages/MainPage.qml` | список всех настроек «имя — значение»; отступ списка от краёв экрана равен `listview_margin`; по нажатию открывается экран изменения, в контекстном меню — «Изменить» и «Удалить» |
| 2 | `qml/pages/AddSettingDialog.qml` | добавление новой настройки: имя (латиница, цифры, `_ . -`) и значение; если такое имя уже есть, выводится предупреждение |
| 3 | `qml/pages/EditSettingDialog.qml` | изменение значения; имя приходит из списка и не редактируется. Для `listview_margin` значение проверяется (целое 0–200), есть ползунок и предпросмотр отступа |
После сохранения `listview_margin` список на первом экране сразу перестраивается с новыми
отступами, а значение записывается в файл вместе с остальными настройками.
Пример файла после работы с приложением:
```
# Settings of ru.krivoshein.SettingsDemo, one key=value per line
app_title=Мои настройки
cover_text=Настройки из файла
greeting=Привет из файла настроек
listview_margin=75
```
## Структура проекта
```
ru.krivoshein.SettingsDemo.pro проект qmake
src/main.cpp создание окна и подключение синглтона в rootContext
src/appsettings.h, appsettings.cpp C++-синглтон настроек
qml/SettingsDemo.qml ApplicationWindow
qml/pages/MainPage.qml экран 1 — список настроек
qml/pages/AddSettingDialog.qml экран 2 — новая настройка
qml/pages/EditSettingDialog.qml экран 3 — изменение настройки
qml/pages/AboutPage.qml «О приложении»
qml/cover/DefaultCoverPage.qml обложка с текстом из настроек
rpm/ru.krivoshein.SettingsDemo.spec описание RPM-пакета
```
## Скриншоты
| Настройки по умолчанию | Новая настройка | Изменение отступа | После изменений |
|---|---|---|---|
| ![Список](screenshots/01_list_default.png) | ![Добавление](screenshots/02_add_setting.png) | ![Изменение](screenshots/03_edit_margin.png) | ![После](screenshots/04_list_after_changes.png) |
## Лицензия
MIT, см. [LICENSE](LICENSE).
/**************************************************************************
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Saint Petersburg State University of Economics
* SPDX-Author-list: Maxim Krivoshein 2026
* SPDX-Creation-date: 2026-10-09
**************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
......
/**************************************************************************
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Saint Petersburg State University of Economics
* SPDX-Author-list: Maxim Krivoshein 2026
* SPDX-Creation-date: 2026-10-09
**************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
......@@ -6,10 +13,16 @@ CoverBackground {
CoverTemplate {
objectName: "applicationCover"
primaryText: "App"
secondaryText: qsTr("Template")
primaryText: {
var revision = appSettings.revision
return appSettings.getAppSettingValue("app_title")
}
secondaryText: {
var revision = appSettings.revision
return appSettings.getAppSettingValue("cover_text")
}
icon {
source: Qt.resolvedUrl("../icons/untitled4.svg")
source: Qt.resolvedUrl("../icons/SettingsDemo.png")
sourceSize { width: icon.width; height: icon.height }
}
}
......
/**************************************************************************
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Saint Petersburg State University of Economics
* SPDX-Author-list: Maxim Krivoshein 2026
* SPDX-Creation-date: 2026-10-09
**************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
......@@ -14,35 +21,56 @@ Page {
id: layout
objectName: "layout"
width: parent.width
spacing: Theme.paddingMedium
PageHeader {
objectName: "pageHeader"
title: qsTr("About Application")
title: qsTr("О приложении")
}
Label {
objectName: "descriptionText"
anchors { left: parent.left; right: parent.right; margins: Theme.horizontalPageMargin }
x: Theme.horizontalPageMargin
width: parent.width - 2 * Theme.horizontalPageMargin
color: palette.highlightColor
font.pixelSize: Theme.fontSizeSmall
textFormat: Text.RichText
wrapMode: Text.WordWrap
text: qsTr("#descriptionText")
text: qsTr("Лабораторная работа №7. Интеграция C++ и QML: C++-синглтон AppSettings хранит настройки приложения в текстовом файле и подключён в корневой контекст QML. Из QML доступны методы getAppSettingValue(key) и setAppSettingValue(key, value). Экраны: список настроек, добавление и изменение настройки. Отступ списка от краёв экрана тоже хранится в настройках.")
}
SectionHeader {
objectName: "licenseHeader"
text: qsTr("3-Clause BSD License")
objectName: "authorHeader"
text: qsTr("Автор")
}
Label {
objectName: "licenseText"
anchors { left: parent.left; right: parent.right; margins: Theme.horizontalPageMargin }
objectName: "authorText"
x: Theme.horizontalPageMargin
width: parent.width - 2 * Theme.horizontalPageMargin
color: palette.highlightColor
font.pixelSize: Theme.fontSizeSmall
textFormat: Text.RichText
wrapMode: Text.WordWrap
text: qsTr("#licenseText")
text: qsTr("Кривошеин Максим Константинович, группа ИС-2301. СПбГЭУ, «Разработка мобильных приложений», 2026")
}
SectionHeader {
objectName: "licenseHeader"
text: qsTr("Лицензия MIT")
}
Label {
objectName: "licenseText"
x: Theme.horizontalPageMargin
width: parent.width - 2 * Theme.horizontalPageMargin
color: palette.secondaryHighlightColor
font.pixelSize: Theme.fontSizeExtraSmall
wrapMode: Text.WordWrap
text: "Copyright (c) 2026 Saint-Petersburg State University of Economics. "
+ "Permission is hereby granted, free of charge, to any person obtaining a copy "
+ "of this software and associated documentation files (the \"Software\"), to deal "
+ "in the Software without restriction, subject to the conditions of the MIT License. "
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND."
}
}
}
......
/**************************************************************************
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Saint Petersburg State University of Economics
* SPDX-Author-list: Maxim Krivoshein 2026
* SPDX-Creation-date: 2026-10-09
**************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
// Screen 2: a new arbitrary setting, its name and value are entered by the user.
Dialog {
id: dialog
readonly property string settingKey: keyField.text.trim()
readonly property bool keyExists: {
var revision = appSettings.revision
return appSettings.contains(settingKey)
}
objectName: "addSettingDialog"
allowedOrientations: Orientation.All
canAccept: appSettings.isValidKey(settingKey)
onAccepted: {
appSettings.setAppSettingValue(settingKey, valueField.text)
console.log("Добавлена настройка " + settingKey + " = " + valueField.text)
}
SilicaFlickable {
anchors.fill: parent
contentHeight: column.height + Theme.paddingLarge
Column {
id: column
width: parent.width
spacing: Theme.paddingMedium
DialogHeader {
acceptText: qsTr("Добавить")
cancelText: qsTr("Отмена")
title: qsTr("Новая настройка")
}
TextField {
id: keyField
objectName: "keyField"
width: parent.width
label: qsTr("Имя настройки")
placeholderText: label
description: qsTr("латинские буквы, цифры и символы _ . -")
validator: RegExpValidator { regExp: /^[A-Za-z0-9_.-]{0,64}$/ }
inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
focus: true
EnterKey.iconSource: "image://theme/icon-m-enter-next"
EnterKey.onClicked: valueField.focus = true
}
TextField {
id: valueField
objectName: "valueField"
width: parent.width
label: qsTr("Значение")
placeholderText: label
EnterKey.enabled: dialog.canAccept
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
EnterKey.onClicked: dialog.accept()
}
Label {
x: Theme.horizontalPageMargin
width: parent.width - 2 * Theme.horizontalPageMargin
visible: dialog.keyExists
wrapMode: Text.WordWrap
color: Theme.secondaryHighlightColor
font.pixelSize: Theme.fontSizeSmall
text: qsTr("Настройка «%1» уже есть, её значение будет заменено.").arg(dialog.settingKey)
}
}
VerticalScrollDecorator {}
}
}
/**************************************************************************
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Saint Petersburg State University of Economics
* SPDX-Author-list: Maxim Krivoshein 2026
* SPDX-Creation-date: 2026-10-09
**************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
// Screen 3: the name comes from the list, only the value can be changed.
Dialog {
id: dialog
property string settingKey
readonly property bool isMargin: settingKey === "listview_margin"
readonly property int marginPreview: Math.max(0, Math.min(parseInt(valueField.text, 10) || 0, 200))
objectName: "editSettingDialog"
allowedOrientations: Orientation.All
canAccept: !isMargin || (valueField.text.length > 0 && valueField.acceptableInput)
onAccepted: {
appSettings.setAppSettingValue(settingKey, valueField.text)
console.log("Изменена настройка " + settingKey + " = " + valueField.text)
}
SilicaFlickable {
anchors.fill: parent
contentHeight: column.height + Theme.paddingLarge
Column {
id: column
width: parent.width
spacing: Theme.paddingMedium
DialogHeader {
acceptText: qsTr("Сохранить")
cancelText: qsTr("Отмена")
title: qsTr("Изменение настройки")
}
TextField {
objectName: "keyField"
width: parent.width
label: qsTr("Имя настройки")
text: dialog.settingKey
readOnly: true
}
TextField {
id: valueField
objectName: "valueField"
width: parent.width
label: dialog.isMargin ? qsTr("Значение, px (0–200)") : qsTr("Значение")
placeholderText: label
text: appSettings.getAppSettingValue(dialog.settingKey)
validator: dialog.isMargin ? marginValidator : null
inputMethodHints: dialog.isMargin ? Qt.ImhDigitsOnly : Qt.ImhNone
EnterKey.enabled: dialog.canAccept
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
EnterKey.onClicked: dialog.accept()
onTextChanged: {
if (dialog.isMargin && !marginSlider.down) {
marginSlider.value = dialog.marginPreview
}
}
IntValidator {
id: marginValidator
bottom: 0
top: 200
}
}
Slider {
id: marginSlider
objectName: "marginSlider"
width: parent.width
visible: dialog.isMargin
label: qsTr("Отступ списка от краёв экрана")
minimumValue: 0
maximumValue: 120
stepSize: 1
valueText: Math.round(value) + " px"
Component.onCompleted: value = dialog.marginPreview
onValueChanged: {
if (down) {
valueField.text = String(Math.round(value))
}
}
}
SectionHeader {
visible: dialog.isMargin
text: qsTr("Предпросмотр")
}
Item {
width: parent.width
height: Theme.itemSizeMedium
visible: dialog.isMargin
Rectangle {
anchors {
fill: parent
leftMargin: dialog.marginPreview
rightMargin: dialog.marginPreview
}
radius: Theme.paddingSmall
color: Theme.rgba(Theme.highlightBackgroundColor, 0.3)
Label {
anchors.centerIn: parent
color: Theme.highlightColor
text: qsTr("Строка списка")
}
}
}
}
VerticalScrollDecorator {}
}
}
/**************************************************************************
* SPDX-License-Identifier: MIT
* SPDX-FileCopyrightText: Saint Petersburg State University of Economics
* SPDX-Author-list: Maxim Krivoshein 2026
* SPDX-Creation-date: 2026-10-09
**************************************************************************/
import QtQuick 2.0
import Sailfish.Silica 1.0
// Screen 1: list of all saved settings.
Page {
id: page
// margin of the list from the screen edges, stored in the settings file
readonly property int listMargin: {
var revision = appSettings.revision
var value = parseInt(appSettings.getAppSettingValue("listview_margin", "24"), 10)
return isNaN(value) ? Theme.horizontalPageMargin : Math.max(0, Math.min(value, 200))
}
function settingValue(key) {
var revision = appSettings.revision
return appSettings.getAppSettingValue(key)
}
function editSetting(key) {
pageStack.push(Qt.resolvedUrl("EditSettingDialog.qml"), { "settingKey": key })
}
objectName: "mainPage"
allowedOrientations: Orientation.All
PageHeader {
objectName: "pageHeader"
title: qsTr("Template")
extraContent.children: [
IconButton {
objectName: "aboutButton"
icon.source: "image://theme/icon-m-about"
anchors.verticalCenter: parent.verticalCenter
SilicaListView {
id: listView
anchors.fill: parent
model: appSettings.keys
PullDownMenu {
MenuItem {
text: qsTr("О приложении")
onClicked: pageStack.push(Qt.resolvedUrl("AboutPage.qml"))
}
]
MenuItem {
text: qsTr("Добавить настройку")
onClicked: pageStack.push(Qt.resolvedUrl("AddSettingDialog.qml"))
}
}
header: PageHeader {
objectName: "pageHeader"
title: page.settingValue("app_title")
description: qsTr("Сохранено настроек: %1").arg(listView.count)
}
delegate: ListItem {
id: settingItem
objectName: "setting_" + modelData
contentHeight: Theme.itemSizeMedium
menu: ContextMenu {
MenuItem {
text: qsTr("Изменить")
onClicked: page.editSetting(modelData)
}
MenuItem {
text: qsTr("Удалить")
onClicked: {
var key = modelData
settingItem.remorseDelete(function () {
appSettings.removeAppSetting(key)
})
}
}
}
onClicked: page.editSetting(modelData)
Rectangle {
anchors {
fill: parent
leftMargin: page.listMargin
rightMargin: page.listMargin
topMargin: Theme.paddingSmall / 2
bottomMargin: Theme.paddingSmall / 2
}
radius: Theme.paddingSmall
color: Theme.rgba(Theme.highlightBackgroundColor, settingItem.highlighted ? 0.3 : 0.12)
}
Column {
anchors {
left: parent.left
leftMargin: page.listMargin + Theme.paddingMedium
right: parent.right
rightMargin: page.listMargin + Theme.paddingMedium
verticalCenter: parent.verticalCenter
}
Label {
width: parent.width
truncationMode: TruncationMode.Fade
color: Theme.highlightColor
font { family: "monospace"; pixelSize: Theme.fontSizeExtraSmall }
text: modelData
}
Label {
width: parent.width
truncationMode: TruncationMode.Fade
color: settingItem.highlighted ? Theme.highlightColor : Theme.primaryColor
text: page.settingValue(modelData)
}
}
}
footer: Column {
width: listView.width
spacing: Theme.paddingSmall
Item {
width: 1
height: Theme.paddingLarge
}
Label {
x: page.listMargin
width: parent.width - 2 * page.listMargin
wrapMode: Text.WordWrap
color: Theme.secondaryHighlightColor
font.pixelSize: Theme.fontSizeExtraSmall
text: qsTr("Отступ списка от краёв экрана: %1 px (настройка listview_margin). "
+ "Нажмите на настройку, чтобы изменить её значение.").arg(page.listMargin)
}
Label {
x: page.listMargin
width: parent.width - 2 * page.listMargin
wrapMode: Text.WrapAnywhere
color: Theme.secondaryColor
font.pixelSize: Theme.fontSizeTiny
text: qsTr("Файл: %1").arg(appSettings.filePath)
}
}
VerticalScrollDecorator {}
}
}
Name: ru.template.untitled4
Summary: Моё приложения для ОС Аврора
Version: 0.1
Name: ru.krivoshein.SettingsDemo
Summary: Настройки приложения в C++-синглтоне с хранением в текстовом файле
Version: 1.0
Release: 1
License: BSD-3-Clause
URL: https://auroraos.ru
License: MIT
URL: https://hub.mos.ru/unecon_mobiledevelopment_2026/is-2301/maximkrivoshein
Source0: %{name}-%{version}.tar.bz2
Requires: sailfishsilica-qt5 >= 0.10.9
......@@ -13,7 +13,8 @@ BuildRequires: pkgconfig(Qt5Qml)
BuildRequires: pkgconfig(Qt5Quick)
%description
Короткое описание моего приложения для ОС Аврора
Настройки приложения в C++-синглтоне с хранением в текстовом файле.
Лабораторная работа по дисциплине «Разработка мобильных приложений», СПбГЭУ.
%prep
%autosetup
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать