From 21b9cc0590106c8c6e4765c06499585ee0644a63 Mon Sep 17 00:00:00 2001 From: taynpg Date: Fri, 27 Feb 2026 12:29:27 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B4=A6=E5=8D=95=E8=AE=B0=E5=BD=95=E5=99=A8?= =?UTF-8?q?=E8=BF=81=E7=A7=BB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .clang-format | 17 + .gitignore | 42 ++ .gitmodules | 3 + .vscode/settings.json | 183 +++++ CMakeLists.txt | 92 +++ README.md | 3 + SqlOpr.cpp | 495 +++++++++++++ SqlOpr.h | 111 +++ filterform.cpp | 439 +++++++++++ filterform.h | 52 ++ filterform.ui | 248 +++++++ main.cpp | 31 + mainwidget.cpp | 344 +++++++++ mainwidget.h | 50 ++ mainwidget.ui | 341 +++++++++ ofen | 1 + recordedit.cpp | 133 ++++ recordedit.h | 39 + recordedit.ui | 206 ++++++ repayment.cpp | 225 ++++++ repayment.h | 41 ++ repayment.ui | 86 +++ res.qrc | 5 + resource/SimpleAccount.rc | 1 + resource/ico.ico | Bin 0 -> 67646 bytes resource/qss.qrc | 28 + resource/qss/flatgray.css | 694 ++++++++++++++++++ resource/qss/flatgray/add_bottom.png | Bin 0 -> 336 bytes resource/qss/flatgray/add_left.png | Bin 0 -> 370 bytes resource/qss/flatgray/add_right.png | Bin 0 -> 358 bytes resource/qss/flatgray/add_top.png | Bin 0 -> 332 bytes resource/qss/flatgray/arrow_bottom.png | Bin 0 -> 337 bytes resource/qss/flatgray/arrow_left.png | Bin 0 -> 376 bytes resource/qss/flatgray/arrow_right.png | Bin 0 -> 360 bytes resource/qss/flatgray/arrow_top.png | Bin 0 -> 361 bytes resource/qss/flatgray/branch_close.png | Bin 0 -> 263 bytes resource/qss/flatgray/branch_open.png | Bin 0 -> 444 bytes resource/qss/flatgray/calendar_nextmonth.png | Bin 0 -> 655 bytes resource/qss/flatgray/calendar_prevmonth.png | Bin 0 -> 740 bytes resource/qss/flatgray/checkbox_checked.png | Bin 0 -> 616 bytes .../qss/flatgray/checkbox_checked_disable.png | Bin 0 -> 639 bytes resource/qss/flatgray/checkbox_parcial.png | Bin 0 -> 341 bytes .../qss/flatgray/checkbox_parcial_disable.png | Bin 0 -> 331 bytes resource/qss/flatgray/checkbox_unchecked.png | Bin 0 -> 612 bytes .../flatgray/checkbox_unchecked_disable.png | Bin 0 -> 646 bytes resource/qss/flatgray/menu_checked.png | Bin 0 -> 542 bytes resource/qss/flatgray/radiobutton_checked.png | Bin 0 -> 1513 bytes .../flatgray/radiobutton_checked_disable.png | Bin 0 -> 1628 bytes .../qss/flatgray/radiobutton_unchecked.png | Bin 0 -> 1294 bytes .../radiobutton_unchecked_disable.png | Bin 0 -> 1374 bytes statistic.cpp | 42 ++ statistic.h | 19 + util.cpp | 145 ++++ util.h | 79 ++ 54 files changed, 4195 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 .vscode/settings.json create mode 100644 CMakeLists.txt create mode 100644 README.md create mode 100644 SqlOpr.cpp create mode 100644 SqlOpr.h create mode 100644 filterform.cpp create mode 100644 filterform.h create mode 100644 filterform.ui create mode 100644 main.cpp create mode 100644 mainwidget.cpp create mode 100644 mainwidget.h create mode 100644 mainwidget.ui create mode 160000 ofen create mode 100644 recordedit.cpp create mode 100644 recordedit.h create mode 100644 recordedit.ui create mode 100644 repayment.cpp create mode 100644 repayment.h create mode 100644 repayment.ui create mode 100644 res.qrc create mode 100644 resource/SimpleAccount.rc create mode 100644 resource/ico.ico create mode 100644 resource/qss.qrc create mode 100644 resource/qss/flatgray.css create mode 100644 resource/qss/flatgray/add_bottom.png create mode 100644 resource/qss/flatgray/add_left.png create mode 100644 resource/qss/flatgray/add_right.png create mode 100644 resource/qss/flatgray/add_top.png create mode 100644 resource/qss/flatgray/arrow_bottom.png create mode 100644 resource/qss/flatgray/arrow_left.png create mode 100644 resource/qss/flatgray/arrow_right.png create mode 100644 resource/qss/flatgray/arrow_top.png create mode 100644 resource/qss/flatgray/branch_close.png create mode 100644 resource/qss/flatgray/branch_open.png create mode 100644 resource/qss/flatgray/calendar_nextmonth.png create mode 100644 resource/qss/flatgray/calendar_prevmonth.png create mode 100644 resource/qss/flatgray/checkbox_checked.png create mode 100644 resource/qss/flatgray/checkbox_checked_disable.png create mode 100644 resource/qss/flatgray/checkbox_parcial.png create mode 100644 resource/qss/flatgray/checkbox_parcial_disable.png create mode 100644 resource/qss/flatgray/checkbox_unchecked.png create mode 100644 resource/qss/flatgray/checkbox_unchecked_disable.png create mode 100644 resource/qss/flatgray/menu_checked.png create mode 100644 resource/qss/flatgray/radiobutton_checked.png create mode 100644 resource/qss/flatgray/radiobutton_checked_disable.png create mode 100644 resource/qss/flatgray/radiobutton_unchecked.png create mode 100644 resource/qss/flatgray/radiobutton_unchecked_disable.png create mode 100644 statistic.cpp create mode 100644 statistic.h create mode 100644 util.cpp create mode 100644 util.h diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..36e4997 --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +PointerAlignment: Left +AccessModifierOffset: -4 +BreakBeforeBraces: Custom +BraceWrapping: + AfterFunction: true + AfterClass: true +Cpp11BracedListStyle: true +ReflowComments: true +SpacesBeforeTrailingComments: 3 +TabWidth: 4 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ColumnLimit: 130 +AllowShortBlocksOnASingleLine: Never +AllowShortFunctionsOnASingleLine: None +AllowShortEnumsOnASingleLine: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7034ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Prerequisites +*.d +.idea +cmake-build-* + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +build +*.user +compile_commands.json +.vs +out +.cache +CMakeLists.txt.* +build* \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..f5e7bbc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ofen"] + path = ofen + url = https://www.sinxmiao.cn/taynpg/ofen diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9163a59 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,183 @@ +{ + "files.autoSave": "onFocusChange", + "editor.fontSize": 14, + "cmake.configureOnOpen": true, + "editor.fontFamily": "'Maple Mono NL NF CN Light', 'Maple Mono NL NF CN Light', 'Maple Mono NL NF CN Light'", + "cmake.debugConfig": { + "console": "externalTerminal", + "setupCommands": [ + { + "description": "-gdb-set charset utf-8", + "text": "-gdb-set charset UTF-8" + }, + { + "description": "Enable gdb pretty-printing", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ], + //"visualizerFile": "${workspaceRoot}/.vscode/qt6.natvis", + "args": [] + }, + "cmake.environment": { + //"EXTER_LIB_DIR": "C:/local" + }, + "cmake.configureSettings": { + "CMAKE_PREFIX_PATH": "C:\\Qt\\Qt5.14.2\\5.14.2\\msvc2017_64", + //"CMAKE_TOOLCHAIN_FILE": "${env:TT_VCPKG}" + }, + "cmake.configureArgs": [ + "-Wno-dev" + ], + "cmake.options.statusBarVisibility": "visible", + "cmake.generator": "Ninja", + "C_Cpp.default.compileCommands": "${workspaceRoot}/build/compile_commands.json", + "C_Cpp.default.cppStandard": "c++17", + "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", + "editor.inlayHints.enabled": "off", + "editor.unicodeHighlight.allowedLocales": { + "ja": true, + "zh-hant": true, + "zh-hans": true + }, + "files.associations": { + "string": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "format": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "queue": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "cfenv": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp", + "variant": "cpp", + "ios": "cpp", + "locale": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xutility": "cpp", + "coroutine": "cpp", + "csignal": "cpp", + "future": "cpp", + "stdfloat": "cpp", + "regex": "cpp", + "stack": "cpp", + "valarray": "cpp", + "filesystem": "cpp", + "flat_map": "cpp", + "flat_set": "cpp", + "stacktrace": "cpp", + "singleapplication": "cpp", + "strstream": "cpp", + "barrier": "cpp", + "complex": "cpp", + "csetjmp": "cpp", + "cuchar": "cpp", + "expected": "cpp", + "fstream": "cpp", + "generator": "cpp", + "latch": "cpp", + "print": "cpp", + "scoped_allocator": "cpp", + "spanstream": "cpp", + "syncstream": "cpp", + "typeindex": "cpp" + }, + "editor.tokenColorCustomizations": { + "textMateRules": [ + { + "scope": "googletest.failed", + "settings": { + "foreground": "#f00" + } + }, + { + "scope": "googletest.passed", + "settings": { + "foreground": "#0f0" + } + }, + { + "scope": "googletest.run", + "settings": { + "foreground": "#0f0" + } + } + ] + } +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d9334b2 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.16) + +project(SimpleAccount VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +if(MSVC) + add_compile_options(/utf-8) +endif() + +set(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}/) + +add_definitions(-DFMT_HEADER_ONLY) +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network Sql) +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Sql) + +set(QAPPLICATION_CLASS QApplication CACHE STRING "Inheritance class for SingleApplication") +add_subdirectory(ofen/SingleApplication) +include_directories(ofen) + +set(PROJECT_SOURCES + main.cpp + mainwidget.cpp + mainwidget.h + mainwidget.ui + SqlOpr.h SqlOpr.cpp + filterform.h filterform.cpp filterform.ui + recordedit.cpp recordedit.h recordedit.ui + repayment.ui repayment.cpp repayment.h + resource/SimpleAccount.rc res.qrc + resource/qss.qrc + statistic.h statistic.cpp + util.h util.cpp +) + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(SimpleAccount + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + util.h util.cpp + ) +# Define target properties for Android with Qt 6 as: +# set_property(TARGET SimpleAccount APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR +# ${CMAKE_CURRENT_SOURCE_DIR}/android) +# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation +else() + if(ANDROID) + add_library(SimpleAccount SHARED + ${PROJECT_SOURCES} + ) +# Define properties for Android with Qt 5 after find_package() calls as: +# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") + else() + add_executable(SimpleAccount + ${PROJECT_SOURCES} + ) + endif() +endif() + +target_link_libraries(SimpleAccount PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Sql Qt${QT_VERSION_MAJOR}::Network) +target_link_libraries(SimpleAccount PRIVATE SingleApplication::SingleApplication) + +# Qt for iOS sets MACOSX_BUNDLE_GUI_IDENTIFIER automatically since Qt 6.1. +# If you are developing for iOS or macOS you should consider setting an +# explicit, fixed bundle identifier manually though. +if(${QT_VERSION} VERSION_LESS 6.1.0) + set(BUNDLE_ID_OPTION MACOSX_BUNDLE_GUI_IDENTIFIER com.example.SimpleAccount) +endif() +set_target_properties(SimpleAccount PROPERTIES + ${BUNDLE_ID_OPTION} + MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} + MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} + MACOSX_BUNDLE TRUE + WIN32_EXECUTABLE TRUE +) + +include(GNUInstallDirs) +install(TARGETS SimpleAccount + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(SimpleAccount) +endif() diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3f13a9 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SimpleAccount + +记账工具。 \ No newline at end of file diff --git a/SqlOpr.cpp b/SqlOpr.cpp new file mode 100644 index 0000000..a204707 --- /dev/null +++ b/SqlOpr.cpp @@ -0,0 +1,495 @@ +#include "SqlOpr.h" +#include +#include +#include +#include +#include + +ACTSqlOpr::~ACTSqlOpr() +{ + if (db_.isOpen()) { + db_.close(); + } +} + +bool ACTSqlOpr::OpenDb(const std::string& path) +{ + dbPath_ = path; + db_ = QSqlDatabase::addDatabase("QSQLITE", "account_connection"); + db_.setDatabaseName(QString::fromStdString(path)); + + if (!db_.open()) { + lastErr_ = fmt::format("无法打开数据库:{}", db_.lastError().text().toStdString()); + return false; + } + + if (!generateDefaultTB()) { + return false; + } + return true; +} + +bool ACTSqlOpr::UpdateAccount(AccountRecord& record) +{ + QSqlQuery query(db_); + query.prepare("UPDATE account_records SET money = ?, pay_type = ?, dt = ?, " + "thing = ?, classify = ?, remark = ?, addition_file = ? WHERE id = ?"); + + query.addBindValue(record.money); + query.addBindValue(QString::fromStdString(record.payType)); + query.addBindValue(QString::fromStdString(record.dt)); + query.addBindValue(QString::fromStdString(record.thing)); + query.addBindValue(QString::fromStdString(record.classify)); + query.addBindValue(QString::fromStdString(record.remark)); + query.addBindValue(QString::fromStdString(record.additionFile)); + query.addBindValue(record.id); + + if (!query.exec()) { + lastErr_ = fmt::format("执行更新失败: {}", query.lastError().text().toStdString()); + return false; + } + return true; +} + +QSqlDatabase& ACTSqlOpr::GetDb() +{ + return db_; +} + +const std::string& ACTSqlOpr::GetLastErr() +{ + return lastErr_; +} + +bool ACTSqlOpr::AppendAccount(AccountRecord& record) +{ + QSqlQuery query(db_); + query.prepare("INSERT INTO account_records " + "(money, pay_type, dt, thing, classify, remark, addition_file) " + "VALUES (?, ?, ?, ?, ?, ?, ?)"); + + query.addBindValue(record.money); + query.addBindValue(QString::fromStdString(record.payType)); + query.addBindValue(QString::fromStdString(record.dt)); + query.addBindValue(QString::fromStdString(record.thing)); + query.addBindValue(QString::fromStdString(record.classify)); + query.addBindValue(QString::fromStdString(record.remark)); + query.addBindValue(QString::fromStdString(record.additionFile)); + + if (!query.exec()) { + lastErr_ = fmt::format("执行插入失败: {}", query.lastError().text().toStdString()); + return false; + } + + record.id = query.lastInsertId().toInt(); + return true; +} + +bool ACTSqlOpr::DeleteAccount(int32_t id) +{ + QSqlQuery query(db_); + query.prepare("DELETE FROM account_records WHERE id = ?"); + query.addBindValue(id); + + if (!query.exec()) { + lastErr_ = fmt::format("执行删除失败: {}", query.lastError().text().toStdString()); + return false; + } + return true; +} + +bool ACTSqlOpr::GetAccountList(AccountRecordList& ret) +{ + QSqlQuery query(db_); + query.prepare("SELECT id, money, pay_type, dt, thing, classify, remark, addition_file " + "FROM account_records ORDER BY dt DESC"); + + if (!query.exec()) { + lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString()); + return false; + } + + ret.clear(); + while (query.next()) { + AccountRecord record; + record.id = query.value(0).toInt(); + record.money = query.value(1).toInt(); + record.payType = query.value(2).toString().toStdString(); + record.dt = query.value(3).toString().toStdString(); + record.thing = query.value(4).toString().toStdString(); + record.classify = query.value(5).toString().toStdString(); + record.remark = query.value(6).toString().toStdString(); + record.additionFile = query.value(7).toString().toStdString(); + + ret.push_back(record); + } + return true; +} + +bool ACTSqlOpr::generateDefaultTB() +{ + QSqlQuery query(db_); + QString sql = "CREATE TABLE IF NOT EXISTS account_records (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "money INTEGER NOT NULL, " + "pay_type TEXT NOT NULL, " + "dt TEXT NOT NULL, " + "thing TEXT, " + "classify TEXT, " + "remark TEXT, " + "addition_file TEXT)"; + + if (!query.exec(sql)) { + lastErr_ = fmt::format("创建表失败: {}", query.lastError().text().toStdString()); + return false; + } + return true; +} + +ACTSqlOpr::ACTSqlOpr() +{ +} + +ComSqlOpr::ComSqlOpr(QSqlDatabase& db) : db_(db) +{ + generateDefaultTB(); + CheckContent(); +} + +bool ComSqlOpr::GetClassifyList(ClassifyRecordList& ret) +{ + QSqlQuery query(db_); + query.prepare("SELECT id, key, value, type, mark FROM common_records WHERE key = '分类'"); + + if (!query.exec()) { + lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString()); + return false; + } + + ret.clear(); + while (query.next()) { + CommonRecord record; + record.id = query.value(0).toInt(); + record.key = query.value(1).toString().toStdString(); + record.value = query.value(2).toString().toStdString(); + record.type = query.value(3).toString().toStdString(); + record.mark = query.value(4).toString().toStdString(); + ret.push_back(record); + } + return true; +} + +bool ComSqlOpr::GetItem(CommonRecord& ret, const std::string& key) +{ + QSqlQuery query(db_); + query.prepare("SELECT id, key, value, type, mark FROM common_records " + "WHERE key = ? ORDER BY id DESC LIMIT 1"); + query.addBindValue(QString::fromStdString(key)); + + if (!query.exec()) { + lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString()); + return false; + } + + if (query.next()) { + ret.id = query.value(0).toInt(); + ret.key = query.value(1).toString().toStdString(); + ret.value = query.value(2).toString().toStdString(); + ret.type = query.value(3).toString().toStdString(); + ret.mark = query.value(4).toString().toStdString(); + return true; + } + + lastErr_ = fmt::format("未找到key为'{}'的记录", key); + return false; +} + +bool ComSqlOpr::UpdateItem(CommonRecord& ret) +{ + if (ret.id <= 0) { + lastErr_ = "无效的记录ID"; + return false; + } + + QSqlQuery query(db_); + query.prepare("UPDATE common_records SET " + "key = ?, value = ?, type = ?, mark = ? " + "WHERE id = ?"); + + query.addBindValue(QString::fromStdString(ret.key)); + query.addBindValue(QString::fromStdString(ret.value)); + query.addBindValue(QString::fromStdString(ret.type)); + query.addBindValue(QString::fromStdString(ret.mark)); + query.addBindValue(ret.id); + + if (!query.exec()) { + lastErr_ = fmt::format("更新失败: {}", query.lastError().text().toStdString()); + return false; + } + return true; +} + +bool ComSqlOpr::InserItem(CommonRecord& ret) +{ + QSqlQuery query(db_); + query.prepare("INSERT INTO common_records (key, value, type, mark) " + "VALUES (?, ?, ?, ?)"); + + query.addBindValue(QString::fromStdString(ret.key)); + query.addBindValue(QString::fromStdString(ret.value)); + query.addBindValue(QString::fromStdString(ret.type)); + query.addBindValue(QString::fromStdString(ret.mark)); + + if (!query.exec()) { + lastErr_ = fmt::format("插入失败: {}", query.lastError().text().toStdString()); + return false; + } + + ret.id = query.lastInsertId().toInt(); + return true; +} + +bool ComSqlOpr::DeleteItem(const std::string& value) +{ + QSqlQuery query(db_); + query.prepare("DELETE FROM common_records WHERE value = ? and key = '分类'"); + query.addBindValue(QString::fromStdString(value)); + + if (!query.exec()) { + lastErr_ = fmt::format("删除失败: {}", query.lastError().text().toStdString()); + return false; + } + + if (query.numRowsAffected() == 0) { + lastErr_ = fmt::format("未找到value为'{}'的记录", value); + return false; + } + return true; +} + +bool ComSqlOpr::CheckContent() +{ + if (!db_.transaction()) { + lastErr_ = fmt::format("开始事务失败: {}", db_.lastError().text().toStdString()); + return false; + } + + const std::vector> defaultCategories = {{"分类", "默认"}}; + + for (const auto& [key, val] : defaultCategories) { + if (!CheckAndInsert(key, val, "", "")) { + db_.rollback(); + return false; + } + } + + if (!db_.commit()) { + lastErr_ = fmt::format("提交事务失败: {}", db_.lastError().text().toStdString()); + return false; + } + return true; +} + +bool ComSqlOpr::CheckClassifyExist(const std::string& value) +{ + QSqlQuery query(db_); + query.prepare("SELECT 1 FROM common_records WHERE value = ? and key = '分类'"); + query.addBindValue(QString::fromStdString(value)); + + if (!query.exec()) { + lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString()); + return false; + } + return query.next(); +} + +const std::string& ComSqlOpr::GetLastErr() +{ + return lastErr_; +} + +bool ComSqlOpr::CheckAndInsert(const std::string& key, const std::string& value, const std::string& type, const std::string& mark) +{ + QSqlQuery query(db_); + query.prepare("SELECT 1 FROM common_records WHERE key = ? LIMIT 1"); + query.addBindValue(QString::fromStdString(key)); + + if (!query.exec()) { + lastErr_ = fmt::format("查询失败: {}", query.lastError().text().toStdString()); + return false; + } + + if (query.next()) { + return true; // 记录已存在 + } + + query.prepare("INSERT INTO common_records (key, value, type, mark) VALUES (?, ?, ?, ?)"); + query.addBindValue(QString::fromStdString(key)); + query.addBindValue(QString::fromStdString(value)); + query.addBindValue(QString::fromStdString(type)); + query.addBindValue(QString::fromStdString(mark)); + + if (!query.exec()) { + lastErr_ = fmt::format("插入失败: {}", query.lastError().text().toStdString()); + return false; + } + return true; +} + +bool ComSqlOpr::generateDefaultTB() +{ + QSqlQuery query(db_); + QString sql = "CREATE TABLE IF NOT EXISTS common_records (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "key TEXT NOT NULL, " + "value TEXT, " + "type TEXT, " + "mark TEXT)"; + + if (!query.exec(sql)) { + lastErr_ = fmt::format("创建表失败: {}", query.lastError().text().toStdString()); + return false; + } + return true; +} + +RepaySqlOpr::RepaySqlOpr(QSqlDatabase& db) : db_(db) +{ + generateDefaultTB(); +} + +bool RepaySqlOpr::GetRepayResult(RepayRecordList& ret, int32_t accID) +{ + ret.clear(); + lastErr_.clear(); + + if (!db_.isOpen()) { + lastErr_ = "Database is not open"; + return false; + } + + QSqlQuery query(db_); + query.prepare("SELECT id, accID, money, dt, remark FROM repay_record WHERE accID = :accID"); + query.bindValue(":accID", accID); + + if (!query.exec()) { + lastErr_ = query.lastError().text().toStdString(); + return false; + } + + while (query.next()) { + RepayRecord record; + record.id = query.value("id").toInt(); + record.accID = query.value("accID").toInt(); + record.money = query.value("money").toInt(); + record.dt = query.value("dt").toString().toStdString(); + record.remark = query.value("remark").toString().toStdString(); + ret.push_back(record); + } + + return true; +} + +bool RepaySqlOpr::InsertRepayRecord(RepayRecord& ret) +{ + lastErr_.clear(); + + if (!db_.isOpen()) { + lastErr_ = "Database is not open"; + return false; + } + + QSqlQuery query(db_); + query.prepare("INSERT INTO repay_record (accID, money, dt, remark) " + "VALUES (:accID, :money, :dt, :remark)"); + query.bindValue(":accID", ret.accID); + query.bindValue(":money", ret.money); + query.bindValue(":dt", QString::fromStdString(ret.dt)); + query.bindValue(":remark", QString::fromStdString(ret.remark)); + + if (!query.exec()) { + lastErr_ = query.lastError().text().toStdString(); + return false; + } + + // Get the auto-generated id + ret.id = query.lastInsertId().toInt(); + return true; +} + +bool RepaySqlOpr::updateRepayRecord(RepayRecord& ret) +{ + lastErr_.clear(); + + if (!db_.isOpen()) { + lastErr_ = "Database is not open"; + return false; + } + + QSqlQuery query(db_); + query.prepare("UPDATE repay_record SET accID = :accID, money = :money, " + "dt = :dt, remark = :remark WHERE id = :id"); + query.bindValue(":accID", ret.accID); + query.bindValue(":money", ret.money); + query.bindValue(":dt", QString::fromStdString(ret.dt)); + query.bindValue(":remark", QString::fromStdString(ret.remark)); + query.bindValue(":id", ret.id); + + if (!query.exec()) { + lastErr_ = query.lastError().text().toStdString(); + return false; + } + + return query.numRowsAffected() > 0; +} + +bool RepaySqlOpr::deleteRepayRecord(int32_t id) +{ + lastErr_.clear(); + + if (!db_.isOpen()) { + lastErr_ = "Database is not open"; + return false; + } + + QSqlQuery query(db_); + query.prepare("DELETE FROM repay_record WHERE id = :id"); + query.bindValue(":id", id); + + if (!query.exec()) { + lastErr_ = query.lastError().text().toStdString(); + return false; + } + + return query.numRowsAffected() > 0; +} + +const std::string& RepaySqlOpr::GetLastErr() +{ + return lastErr_; +} + +bool RepaySqlOpr::generateDefaultTB() +{ + lastErr_.clear(); + + if (!db_.isOpen()) { + lastErr_ = "Database is not open"; + return false; + } + + QSqlQuery query(db_); + QString createTable = "CREATE TABLE IF NOT EXISTS repay_record (" + "id INTEGER PRIMARY KEY AUTOINCREMENT, " + "accID INTEGER NOT NULL, " + "money INTEGER NOT NULL, " + "dt TEXT NOT NULL, " + "remark TEXT)"; + + if (!query.exec(createTable)) { + lastErr_ = query.lastError().text().toStdString(); + return false; + } + + return true; +} diff --git a/SqlOpr.h b/SqlOpr.h new file mode 100644 index 0000000..12cb186 --- /dev/null +++ b/SqlOpr.h @@ -0,0 +1,111 @@ +#ifndef SQL_OPR_H +#define SQL_OPR_H + +#include +#include +#include +#include +#include + +struct AccountRecord { + int32_t id{}; + int32_t money{}; + std::string payType{}; + std::string dt; + std::string thing; + std::string classify; + std::string remark; + std::string additionFile{}; +}; + +struct CommonRecord { + int32_t id{}; + std::string key; + std::string value; + std::string type; + std::string mark; +}; + +struct RepayRecord { + int32_t id{}; + int32_t accID{}; + int32_t money{}; + std::string dt; + std::string remark; +}; + +using ClassifyRecordList = std::vector; +using AccountRecordList = std::vector; +using RepayRecordList = std::vector; + +class ACTSqlOpr +{ +public: + ACTSqlOpr(); + ~ACTSqlOpr(); + +public: + bool OpenDb(const std::string& path); + bool GetAccountList(AccountRecordList& ret); + bool AppendAccount(AccountRecord& ret); + bool DeleteAccount(int32_t id); + bool UpdateAccount(AccountRecord& ret); + QSqlDatabase& GetDb(); + const std::string& GetLastErr(); + +private: + bool generateDefaultTB(); + +private: + QSqlDatabase db_; + std::string dbPath_{}; + std::string lastErr_{}; +}; + +class ComSqlOpr +{ +public: + ComSqlOpr(QSqlDatabase& db); + +public: + bool GetClassifyList(ClassifyRecordList& ret); + bool GetItem(CommonRecord& ret, const std::string& key); + bool UpdateItem(CommonRecord& ret); + bool InserItem(CommonRecord& ret); + bool DeleteItem(const std::string& value); + bool CheckContent(); + bool CheckClassifyExist(const std::string& value); + const std::string& GetLastErr(); + +private: + bool CheckAndInsert(const std::string& key, const std::string& value, const std::string& type, const std::string& mark); + +private: + bool generateDefaultTB(); + +private: + QSqlDatabase& db_; + std::string lastErr_{}; +}; + +class RepaySqlOpr +{ +public: + RepaySqlOpr(QSqlDatabase& db); + +public: + bool GetRepayResult(RepayRecordList& ret, int32_t accID); + bool InsertRepayRecord(RepayRecord& ret); + bool updateRepayRecord(RepayRecord& ret); + bool deleteRepayRecord(int32_t id); + const std::string& GetLastErr(); + +private: + bool generateDefaultTB(); + +private: + QSqlDatabase& db_; + std::string lastErr_{}; +}; + +#endif // SQL_OPR_H \ No newline at end of file diff --git a/filterform.cpp b/filterform.cpp new file mode 100644 index 0000000..e46325c --- /dev/null +++ b/filterform.cpp @@ -0,0 +1,439 @@ +#include "filterform.h" + +#include "recordedit.h" +#include "ui_filterform.h" +#include +#include +#include +#include +#include +#include +#include +#include + +FilterForm::FilterForm(QWidget* parent, std::unique_ptr& sqlOpr, std::unique_ptr& comSqlOpr, + std::unique_ptr& repaySqlOpr) + : QDialog(parent), ui(new Ui::FilterForm), sqlOpr_(sqlOpr), comSqlOpr_(comSqlOpr), repaySqlOpr_(repaySqlOpr) +{ + ui->setupUi(this); + statistic_ = std::make_shared(repaySqlOpr_); + Init(); + setWindowTitle("结果"); +} + +FilterForm::~FilterForm() +{ + delete ui; +} + +int FilterForm::exec() +{ + ShowResult(); + if (!over_) { + return QDialog::Rejected; + } + return QDialog::exec(); +} + +void FilterForm::Init() +{ + auto* lay = new QVBoxLayout(this); + tw_ = new QTableWidget(this); + + QStringList headers; + headers << "ID" << "类型" << "分类" << "金额" << "日期" << "内容" << "备注"; + + tw_->setColumnCount(headers.size()); + tw_->setHorizontalHeaderLabels(headers); + tw_->setSelectionBehavior(QAbstractItemView::SelectRows); + + tw_->setColumnWidth(0, 50); + tw_->setColumnWidth(1, 100); + tw_->setColumnWidth(2, 100); + tw_->setColumnWidth(3, 100); + tw_->setColumnWidth(4, 150); + tw_->setColumnWidth(5, 300); + tw_->setColumnWidth(6, 100); + + lay->addWidget(tw_); + ui->widget->setLayout(lay); + + ui->edCashIn->setReadOnly(true); + ui->edCashOut->setReadOnly(true); + ui->edCreditIn->setReadOnly(true); + ui->edCreditOut->setReadOnly(true); + ui->edCreditCash->setReadOnly(true); + + tw_->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tw_, &QTableWidget::customContextMenuRequested, this, &FilterForm::ShowContextMenu); +} + +void FilterForm::ShowResult() +{ + over_ = false; + AccountRecordList list; + if (!sqlOpr_->GetAccountList(list)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return; + } + + result_.clear(); + if (!Filter(list, result_)) { + QMessageBox::warning(this, "错误", "筛选失败"); + return; + } + + if (result_.empty()) { + QMessageBox::warning(this, "提示", "没有符合条件的记录"); + return; + } + + for (const auto& item : result_) { + tw_->insertRow(tw_->rowCount()); + int row = tw_->rowCount() - 1; + + auto* i1 = new QTableWidgetItem(QString::number(item.id)); + i1->setFlags(i1->flags() & ~Qt::ItemIsEditable); + + auto* i2 = new QTableWidgetItem(QString::fromStdString(item.payType)); + i2->setFlags(i2->flags() & ~Qt::ItemIsEditable); + + auto* i3 = new QTableWidgetItem(QString::fromStdString(item.classify)); + i3->setFlags(i3->flags() & ~Qt::ItemIsEditable); + + auto* i4 = new QTableWidgetItem(QString::number(item.money / 100.0)); + i4->setFlags(i4->flags() & ~Qt::ItemIsEditable); + + auto* i5 = new QTableWidgetItem(QString::fromStdString(item.dt)); + i5->setFlags(i5->flags() & ~Qt::ItemIsEditable); + + auto* i6 = new QTableWidgetItem(QString::fromStdString(item.thing)); + i6->setFlags(i6->flags() & ~Qt::ItemIsEditable); + + auto* i7 = new QTableWidgetItem(QString::fromStdString(item.remark)); + i7->setFlags(i7->flags() & ~Qt::ItemIsEditable); + + tw_->setItem(row, 0, i1); + tw_->setItem(row, 1, i2); + tw_->setItem(row, 2, i3); + tw_->setItem(row, 3, i4); + tw_->setItem(row, 4, i5); + tw_->setItem(row, 5, i6); + tw_->setItem(row, 6, i7); + } + CalcShow(); + over_ = true; +} + +void FilterForm::CalcShow() +{ + statistic_->Calculate(result_); + auto* d = SharedData::instance(); + ui->edCashIn->setText(QString::number(d->ttCashIn_ / 100.0)); + ui->edCashOut->setText(QString::number(d->ttCashOut_ / 100.0)); + ui->edCreditIn->setText(QString::number(d->ttCreditIn_ / 100.0)); + ui->edCreditOut->setText(QString::number(d->ttCreditOut_ / 100.0)); + ui->edCreditCash->setText(QString::number(d->ttCreditCash_ / 100.0)); + ui->edCreditPay->setText(QString::number(d->ttCashPay_ / 100.0)); + + auto ret1 = GetClassifyCash(result_, true); + auto ret2 = GetClassifyCash(result_, false); + + ui->lwCashCl->clear(); + for (const auto& item : ret1) { + ui->lwCashCl->addItem(QString("%1 => %2/%3") + .arg(QString::fromStdString(item.first)) + .arg(QString::number(item.second.first / 100.0)) + .arg(QString::number(item.second.second / 100.0))); + } + ui->lwCreditCl->clear(); + for (const auto& item : ret2) { + ui->lwCreditCl->addItem(QString("%1 => %2/%3") + .arg(QString::fromStdString(item.first)) + .arg(QString::number(item.second.first / 100.0)) + .arg(QString::number(item.second.second / 100.0))); + } +} + +int32_t FilterForm::rePayValue(int accID) +{ + RepayRecordList reuslt; + if (!repaySqlOpr_->GetRepayResult(reuslt, accID)) { + return 0; + } + int32_t sum = 0; + for (const auto& item : reuslt) { + sum += item.money; + } + return sum; +} + +bool FilterForm::Filter(const AccountRecordList& list, AccountRecordList& result) +{ + auto* d = SharedData::instance(); + result.clear(); + for (const auto& item : list) { + if (d->flType) { + if (item.payType != d->type.toStdString()) { + continue; + } + } + if (d->flKeys) { + auto qkeys = d->key.toUpper(); + auto skeys = QString::fromStdString(item.thing).toUpper(); + if (!skeys.contains(qkeys)) { + continue; + } + } + switch (d->filter) { + case CashFilter::FIL_BETWEEN: { + if (item.money < d->lowMoney || item.money > d->highMoney) { + continue; + } + break; + } + case CashFilter::FIL_HIGHER: { + if (item.money <= d->highMoney) { + continue; + } + break; + } + case CashFilter::FIL_LOWER: { + if (item.money >= d->lowMoney) { + continue; + } + break; + } + default: + break; + } + if (d->flClassify) { + auto qkeys = d->classify.toUpper(); + auto skeys = QString::fromStdString(item.classify).toUpper(); + if (!skeys.contains(qkeys)) { + continue; + } + } + if (d->flDays) { + auto days = d->days; + QDateTime datetime = QDateTime::fromString(QString::fromStdString(item.dt), "yyyy-MM-dd hh:mm:ss"); + QDateTime now = QDateTime::currentDateTime(); + if (datetime.daysTo(now) > days) { + continue; + } + } + result.push_back(item); + } + return true; +} + +std::vector>> FilterForm::GetClassifyCash(const AccountRecordList& list, + bool isCash) +{ + std::vector>> ret; + std::unordered_map> classifyMap; + for (const auto& item : list) { + if (isCash) { + if (item.payType == "现金支出") { + if (!classifyMap.count(item.classify)) { + classifyMap[item.classify] = {0, 0}; + } + classifyMap[item.classify].first += item.money; + } else if (item.payType == "现金收入") { + if (!classifyMap.count(item.classify)) { + classifyMap[item.classify] = {0, 0}; + } + classifyMap[item.classify].second += item.money; + } + } else { + if (item.payType == "信用支出") { + if (!classifyMap.count(item.classify)) { + classifyMap[item.classify] = {0, 0}; + } + classifyMap[item.classify].first += item.money; + } else if (item.payType == "信用收入") { + if (!classifyMap.count(item.classify)) { + classifyMap[item.classify] = {0, 0}; + } + classifyMap[item.classify].second += item.money; + } else if (item.payType == "信用借款") { + // 信用借款 + if (!classifyMap.count(item.classify)) { + classifyMap[item.classify] = {0, 0}; + } + classifyMap[item.classify].first += item.money; + } + } + } + for (const auto& item : classifyMap) { + ret.emplace_back(item.first, item.second); + } + std::sort(ret.begin(), ret.end(), + [](const std::pair>& a, + const std::pair>& b) { + auto at = a.second.first + a.second.second; + auto bt = b.second.first + b.second.second; + return at > bt; + }); + return ret; +} +void FilterForm::ShowContextMenu(const QPoint& pos) +{ + QList selectedItems = tw_->selectedItems(); + if (selectedItems.isEmpty()) { + return; + } + + QMenu menu(this); + + if (selectedItems.size() != 7) { + return; + } + QAction* acFile = menu.addAction("查看附件"); + int id = selectedItems[0]->text().toInt(); + QString fileName; + + for (const auto& item : result_) { + if (item.id == id) { + fileName = QString::fromStdString(item.additionFile); + break; + } + } + TransMenu(id, menu, selectedItems[1]); + connect(acFile, &QAction::triggered, [this, selectedItems, id, fileName]() { + if (fileName.isEmpty()) { + QMessageBox::warning(this, "警告", "附件文件名为空"); + return; + } + QString mediaDir = QString::fromStdString(Util::GetMediaDir()); + QString filePath = QDir(mediaDir).filePath(fileName); + if (!QFile::exists(filePath)) { + QMessageBox::critical(this, "错误", QString("文件不存在: %1").arg(filePath)); + return; + } + if (!QDesktopServices::openUrl(QUrl::fromLocalFile(filePath))) { + QMessageBox::critical(nullptr, "错误", "无法打开图片文件"); + } + }); + QAction* acEdit = menu.addAction("编辑"); + connect(acEdit, &QAction::triggered, [this, id, selectedItems]() { + RecordEdit* re = new RecordEdit(this, comSqlOpr_, repaySqlOpr_); + AccountRecord* recPtr = nullptr; + for (auto& item : result_) { + if (item.id == id) { + re->record_ = item; + recPtr = &item; + break; + } + } + re->exec(); + + // headers << "ID" << "类型" << "分类" << "金额" << "日期" << "内容" << "备注"; + if (!re->modify_) { + return; + } + + if (!sqlOpr_->UpdateAccount(re->record_)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return; + } + + selectedItems[1]->setText(QString::fromStdString(re->record_.payType)); + selectedItems[2]->setText(QString::fromStdString(re->record_.classify)); + selectedItems[3]->setText(QString::number(re->record_.money / 100.0)); + selectedItems[4]->setText(QString::fromStdString(re->record_.dt)); + selectedItems[5]->setText(QString::fromStdString(re->record_.thing)); + selectedItems[6]->setText(QString::fromStdString(re->record_.remark)); + + *recPtr = re->record_; + CalcShow(); + }); + + QAction* acDelete = menu.addAction("删除"); + connect(acDelete, &QAction::triggered, [this, id, selectedItems]() { + if (QMessageBox::question(this, "确认删除", "确定删除记录吗?") != QMessageBox::Yes) { + return; + } + + if (!sqlOpr_->DeleteAccount(id)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return; + } + + // 从结果列表中移除记录 + for (auto it = result_.begin(); it != result_.end(); ++it) { + if (it->id == id) { + result_.erase(it); + break; + } + } + + // 从表格中移除行 + tw_->removeRow(selectedItems[0]->row()); + CalcShow(); + }); + + menu.exec(tw_->viewport()->mapToGlobal(pos)); +} + +void FilterForm::TransMenu(int id, QMenu& menu, QTableWidgetItem* item) +{ + QString payType; + for (const auto& item : result_) { + if (item.id == id) { + payType = QString::fromStdString(item.payType); + break; + } + } + if (payType.isEmpty()) { + QMessageBox::warning(this, "警告", QString("未找到ID为%1的支付类型。").arg(id)); + return; + } + + // 根据支付类型执行不同操作 + QAction* acFile = nullptr; + QString pur; + if (payType == "现金支出") { + acFile = menu.addAction("转为信用支出"); + pur = "信用支出"; + } else if (payType == "现金收入") { + acFile = menu.addAction("转为信用收入"); + pur = "信用收入"; + } else if (payType == "信用支出") { + acFile = menu.addAction("转为现金支出"); + pur = "现金支出"; + } else if (payType == "信用收入") { + acFile = menu.addAction("转为现金收入"); + pur = "现金收入"; + } + + if (acFile == nullptr) { + return; + } + + connect(acFile, &QAction::triggered, [this, id, pur, item]() { + // 执行支付类型转换逻辑 + if (QMessageBox::question(this, "确认转换", QString("确定将ID为%1的记录转为%2吗?").arg(id).arg(pur), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + // 1. 更新数据库中的支付类型 + bool done = false; + for (auto& item : result_) { + if (item.id != id) { + continue; + } + item.payType = pur.toStdString(); + if (!sqlOpr_->UpdateAccount(item)) { + QMessageBox::critical(this, "错误", "更新数据库失败"); + return; + } + done = true; + break; + } + if (done) { + item->setText(pur); + CalcShow(); + QMessageBox::information(this, "成功", QString("ID为%1的记录已成功转为%2。").arg(id).arg(pur)); + } + } + }); +} \ No newline at end of file diff --git a/filterform.h b/filterform.h new file mode 100644 index 0000000..5f4e4c6 --- /dev/null +++ b/filterform.h @@ -0,0 +1,52 @@ +#ifndef FILTERFORM_H +#define FILTERFORM_H + +#include "SqlOpr.h" +#include "statistic.h" +#include "util.h" +#include +#include +#include +#include + +namespace Ui { +class FilterForm; +} + +class FilterForm : public QDialog +{ + Q_OBJECT + +public: + explicit FilterForm(QWidget* parent, std::unique_ptr& sqlOpr, std::unique_ptr& comSqlOpr, + std::unique_ptr& repaySqlOpr); + ~FilterForm(); + int exec() override; + +public: + static bool Filter(const AccountRecordList& list, AccountRecordList& result); + static std::vector>> GetClassifyCash(const AccountRecordList& list, + bool isCash); + +private: + void Init(); + +private: + void ShowResult(); + void ShowContextMenu(const QPoint& pos); + void TransMenu(int id, QMenu& menu, QTableWidgetItem* item); + void CalcShow(); + int32_t rePayValue(int accID); + +private: + bool over_{}; + QTableWidget* tw_{}; + AccountRecordList result_; + std::shared_ptr statistic_; + std::unique_ptr& sqlOpr_; + std::unique_ptr& comSqlOpr_; + std::unique_ptr& repaySqlOpr_; + Ui::FilterForm* ui; +}; + +#endif // FILTERFORM_H diff --git a/filterform.ui b/filterform.ui new file mode 100644 index 0000000..98752fa --- /dev/null +++ b/filterform.ui @@ -0,0 +1,248 @@ + + + FilterForm + + + + 0 + 0 + 1472 + 710 + + + + Dialog + + + + + + 1.清单 + + + + + + + 0 + 0 + + + + + + + + + + 上一页 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + + + + Qt::Orientation::Vertical + + + + + + + + 0 + 0 + + + + + 80 + 16777215 + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 下一页 + + + + + + + + + + + + + 0 + 0 + + + + + 200 + 16777215 + + + + 2.统计 + + + + + + + + 总计现金支出: + + + + + + + + + + + + + + 总计现金收入: + + + + + + + + + + + + + + 总计信用支出: + + + + + + + + + + + + + + 总计信用收入: + + + + + + + + + + + + + + 总计信用借款: + + + + + + + + + + + + + + 总计信用还款: + + + + + + + + + + + + 现金分类占比(支出/收入): + + + + + + + + + + 信用分类占比(支出/收入): + + + + + + + + + + + + + + diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..f1c5ee4 --- /dev/null +++ b/main.cpp @@ -0,0 +1,31 @@ +#include "mainwidget.h" +#include +#include +#include + +int main(int argc, char* argv[]) +{ + SingleApplication a(argc, argv); + // a.setStyle("fusion"); + +#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) + QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); +#endif + + MainWidget w; + + QObject::connect(&a, &SingleApplication::instanceStarted, &w, [&w]() { + w.showNormal(); + w.raise(); + w.activateWindow(); + }); + + + QFile file(":/qss/flatgray.css"); + if (file.open(QFile::ReadOnly)) { + a.setStyleSheet(file.readAll()); + } + + w.show(); + return a.exec(); +} diff --git a/mainwidget.cpp b/mainwidget.cpp new file mode 100644 index 0000000..fd187a3 --- /dev/null +++ b/mainwidget.cpp @@ -0,0 +1,344 @@ +#include "mainwidget.h" +#include "./ui_mainwidget.h" +#include "filterform.h" +#include +#include +#include +#include +#include +#include + +MainWidget::MainWidget(QWidget* parent) : QDialog(parent), ui(new Ui::MainWidget) +{ + ui->setupUi(this); + BaseInit(); + Init(); + RefreshData(); + setWindowTitle("账单记录器V1.0.5"); + Calculate(); + + auto size = Util::GetMainSize(); + resize(size.first, size.second); + + setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); + setWindowIcon(QIcon("://resource/ico.ico")); +} + +MainWidget::~MainWidget() +{ + delete ui; +} + +void MainWidget::Init() +{ + Util::Init(); + auto dbPath = Util::GetWorkDir() + "/account.db"; + sqlOpr_ = std::make_unique(); + if (!sqlOpr_->OpenDb(dbPath)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return; + } + comSqlOpr_ = std::make_unique(sqlOpr_->GetDb()); + repaySqlOpr_ = std::make_unique(sqlOpr_->GetDb()); + statistic_ = std::make_shared(repaySqlOpr_); + + auto mediaDir = Util::GetMediaDir(); + if (!QDir(QString::fromStdString(mediaDir)).exists()) { + QDir().mkdir(QString::fromStdString(mediaDir)); + } + ui->timeEdit->setDisplayFormat("HH:mm:ss"); + ui->rbNoLimit->setChecked(true); + ui->btnRecord->setStyleSheet("background-color: #b1a5ccff;"); + ui->dateEdit->setDate(QDate::currentDate()); + ui->timeEdit->setTime(QTime::currentTime()); + ui->cbClassify->setEditable(true); + + ui->edCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this)); + ui->edHighCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this)); + ui->edLowCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this)); + ui->edDays->setValidator(new QIntValidator(0, 10000000, this)); + + ui->lbTotal->setStyleSheet("QLabel {" + "font-size: 20px;" + "color: blue;" + "}"); + + ui->lbCurCash->setStyleSheet("QLabel {" + "color: blue;" + "}"); + ui->lbCredit->setStyleSheet("QLabel {" + "color: blue;" + "}"); + + connect(ui->btnClassifyAdd, &QPushButton::clicked, this, &MainWidget::AddClassify); + connect(ui->btnClassifyDel, &QPushButton::clicked, this, &MainWidget::DelClassify); + connect(ui->btnRecord, &QPushButton::clicked, this, &MainWidget::Record); + connect(ui->btnSet, &QPushButton::clicked, this, [this]() { + QProcess process; + auto editor = Util::GetEditor(); + QStringList args; + args << QString::fromStdString(Util::GetConfigPath()); + process.startDetached(QString::fromStdString(editor), args); + process.waitForStarted(); + }); + connect(ui->btnSearch, &QPushButton::clicked, this, &MainWidget::Search); + connect(ui->btnSetNow, &QPushButton::clicked, this, [this]() { + ui->dateEdit->setDate(QDate::currentDate()); + ui->timeEdit->setTime(QTime::currentTime()); + }); + connect(ui->btnAddFile, &QPushButton::clicked, this, &MainWidget::SelectImg); + connect(ui->btnClearFile, &QPushButton::clicked, this, [this]() { ui->edFile->clear(); }); +} + +bool MainWidget::RefreshData() +{ + if (!RefreshClassify()) { + return false; + } + return true; +} + +bool MainWidget::Calculate() +{ + AccountRecordList list; + if (!sqlOpr_->GetAccountList(list)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return false; + } + + // 计算账户余额等信息 + statistic_->Calculate(list); + + auto* d = SharedData::instance(); + auto curCash = static_cast(Util::GetCurCash() * 100); + auto curSave = static_cast(Util::GetCurSave() * 100); + + auto curRemain = d->ttCashIn_ - d->ttCashOut_ + curCash - d->ttCashPay_ + d->ttCreditCash_; + ui->lbCurCash->setText(QString::number(curRemain / 100.0)); + + auto creditOutTotal = d->ttCreditOut_ - d->ttCashPay_ + d->ttCreditCash_; + QString credit = QString("%1/%2").arg(creditOutTotal / 100.0, 0, 'f', 2).arg(d->ttCreditIn_ / 100.0, 0, 'f', 2); + ui->lbCredit->setText(credit); + + auto youHave = curRemain + d->ttCreditIn_ - (d->ttCreditOut_ + d->ttCreditCash_ - d->ttCashPay_); + ui->lbTotal->setText("¥" + QString::number(youHave / 100.0)); + + return true; +} + +void MainWidget::closeEvent(QCloseEvent* event) +{ + auto size = this->size(); + Util::SetMainSize(size.width(), size.height()); + QWidget::closeEvent(event); +} + +void MainWidget::BaseInit() +{ + ui->cbCashType->addItem("现金支出"); + ui->cbCashType->addItem("现金收入"); + ui->cbCashType->addItem("信用支出"); + ui->cbCashType->addItem("信用收入"); + ui->cbCashType->addItem("信用借款"); + ui->cbCashType->setCurrentIndex(0); +} + +bool MainWidget::RefreshClassify() +{ + ClassifyRecordList classifyList; + if (!comSqlOpr_->GetClassifyList(classifyList)) { + QMessageBox::warning(this, "错误", QString::fromStdString(comSqlOpr_->GetLastErr())); + return false; + } + ui->cbClassify->clear(); + for (const auto& classify : classifyList) { + ui->cbClassify->addItem(QString::fromStdString(classify.value)); + } + if (!classifyList.empty()) { + ui->cbClassify->setCurrentIndex(0); + } + return true; +} + +void MainWidget::SelectImg() +{ + QString imagePath = + QFileDialog::getOpenFileName(this, "选择图片", QDir::homePath(), "图片文件 (*.jpg *.jpeg *.png *.bmp *.gif)"); + if (imagePath.isEmpty()) { + return; + } + ui->edFile->setText(imagePath); +} + +bool MainWidget::AddClassify() +{ + // 弹出输入对话框 + bool ok; + QString text = QInputDialog::getText(this, tr("添加分类"), tr("请输入分类名称:"), QLineEdit::Normal, "", &ok); + text = text.trimmed(); + + if (!ok || text.isEmpty()) { + return false; + } + + // 先检查是否已存在 + if (comSqlOpr_->CheckClassifyExist(text.toStdString())) { + QMessageBox::warning(this, tr("错误"), tr("分类已存在")); + return false; + } + + CommonRecord record; + record.key = "分类"; + record.value = text.toStdString(); + record.type = ""; + + if (!comSqlOpr_->InserItem(record)) { + QMessageBox::warning(this, tr("错误"), tr("添加分类失败")); + return false; + } + RefreshClassify(); + return true; +} + +bool MainWidget::DelClassify() +{ + auto curClassify = ui->cbClassify->currentText(); + if (curClassify == "默认") { + QMessageBox::warning(this, "错误", "默认分类无法删除"); + return false; + } + if (QMessageBox::question(this, "删除分类", QString("确定删除分类<%1>吗?").arg(curClassify)) != QMessageBox::Yes) { + return false; + } + + if (!comSqlOpr_->DeleteItem(curClassify.toStdString())) { + QMessageBox::warning(this, "错误", QString::fromStdString(comSqlOpr_->GetLastErr())); + return false; + } + RefreshClassify(); + return true; +} + +void MainWidget::Record() +{ + // 确认添加 + if (QMessageBox::question(this, "确认添加", "确定添加记录吗?") != QMessageBox::Yes) { + return; + } + + // 款项分类 + auto curType = ui->cbCashType->currentText(); + + // 基础分类 + auto curClassify = ui->cbClassify->currentText(); + + // 记录事项 + auto thing = ui->lineEdit->text().trimmed(); + if (thing.isEmpty()) { + QMessageBox::warning(this, "错误", "记录事项不能为空"); + return; + } + + // 时间日期 + auto date = ui->dateEdit->date(); + auto time = ui->timeEdit->time(); + QDateTime dateTime(date, time); + auto dateTimeStr = dateTime.toString("yyyy-MM-dd HH:mm:ss"); + + // 金额 + // 查看附件 + auto attach = ui->edFile->text().trimmed(); + if (!attach.isEmpty()) { + QFile file(attach); + if (!file.exists()) { + QMessageBox::warning(this, "错误", "附件文件不存在"); + return; + } + } + AccountRecord record; + record.money = Util::CashInt(ui->edCash->text().toDouble()); + record.thing = thing.toStdString(); + record.dt = dateTimeStr.toStdString(); + record.classify = curClassify.toStdString(); + record.payType = curType.toStdString(); + if (!attach.isEmpty()) { + record.additionFile = Util::NewUUIDName(attach).toStdString(); + } + if (!sqlOpr_->AppendAccount(record)) { + QMessageBox::warning(this, "错误", QString::fromStdString(sqlOpr_->GetLastErr())); + return; + } + if (!attach.isEmpty()) { + QFile::copy(attach, QString::fromStdString(Util::GetMediaDir() + "/" + record.additionFile)); + }; + Calculate(); + QMessageBox::information(this, "提示", "添加成功"); +} + +void MainWidget::Search() +{ + auto* d = SharedData::instance(); + if (ui->ckType->isChecked()) { + d->flType = true; + d->type = ui->cbCashType->currentText(); + } else { + d->flType = false; + } + if (ui->ckKey->isChecked()) { + d->flKeys = true; + d->key = ui->edKey->text().trimmed(); + if (d->key.isEmpty()) { + QMessageBox::warning(this, "错误", "搜索关键字不能为空"); + return; + } + } else { + d->flKeys = false; + } + if (ui->ckDays->isChecked()) { + d->flDays = true; + if (ui->edDays->text().isEmpty()) { + QMessageBox::warning(this, "错误", "天数不能为空"); + return; + } + d->days = ui->edDays->text().toInt(); + } else { + d->flDays = false; + } + if (ui->rbBetween->isChecked()) { + d->filter = CashFilter::FIL_BETWEEN; + if (ui->edLowCash->text().isEmpty() || ui->edHighCash->text().isEmpty()) { + QMessageBox::warning(this, "错误", "金额范围不能为空"); + return; + } + d->lowMoney = static_cast(ui->edLowCash->text().toDouble() * 100); + d->highMoney = static_cast(ui->edHighCash->text().toDouble() * 100); + } else if (ui->rbHigh->isChecked()) { + d->filter = CashFilter::FIL_HIGHER; + if (ui->edHighCash->text().isEmpty()) { + QMessageBox::warning(this, "错误", "金额高范围不能为空"); + return; + } + d->highMoney = static_cast(ui->edHighCash->text().toDouble() * 100); + } else if (ui->rbLow->isChecked()) { + d->filter = CashFilter::FIL_LOWER; + if (ui->edLowCash->text().isEmpty()) { + QMessageBox::warning(this, "错误", "金额低范围不能为空"); + return; + } + d->lowMoney = static_cast(ui->edLowCash->text().toDouble() * 100); + } else { + d->filter = CashFilter::FIL_NO_LIMIT; + } + + if (ui->ckClassify->isChecked()) { + d->flClassify = true; + d->classify = ui->cbClassify->currentText(); + } else { + d->flClassify = false; + } + + FilterForm* filterForm = new FilterForm(this, sqlOpr_, comSqlOpr_, repaySqlOpr_); + filterForm->exec(); + + Calculate(); +} \ No newline at end of file diff --git a/mainwidget.h b/mainwidget.h new file mode 100644 index 0000000..6918c91 --- /dev/null +++ b/mainwidget.h @@ -0,0 +1,50 @@ +#ifndef MAINWIDGET_H +#define MAINWIDGET_H + +#include "SqlOpr.h" +#include "util.h" +#include "statistic.h" +#include +#include + +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWidget; +} +QT_END_NAMESPACE + +class MainWidget : public QDialog +{ + Q_OBJECT + +public: + MainWidget(QWidget* parent = nullptr); + ~MainWidget(); + +public: + void Init(); + +private: + bool RefreshData(); + bool Calculate(); + void closeEvent(QCloseEvent* event) override; + +private: + void BaseInit(); + bool RefreshClassify(); + void SelectImg(); + +private: + bool AddClassify(); + bool DelClassify(); + void Record(); + void Search(); + +private: + std::shared_ptr statistic_; + std::unique_ptr sqlOpr_; + std::unique_ptr comSqlOpr_; + std::unique_ptr repaySqlOpr_; + Ui::MainWidget* ui; +}; +#endif // MAINWIDGET_H diff --git a/mainwidget.ui b/mainwidget.ui new file mode 100644 index 0000000..b385177 --- /dev/null +++ b/mainwidget.ui @@ -0,0 +1,341 @@ + + + MainWidget + + + + 0 + 0 + 687 + 237 + + + + MainWidget + + + + + + NULL + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + 剩余总现金(元): + + + + + + + NULL + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 信用收支(元)(支出/收入): + + + + + + + NULL + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + 款项类型: + + + + + + + + 0 + 0 + + + + + + + + + + + + + + 设置为现在 + + + + + + + 分类增 + + + + + + + 分类删 + + + + + + + 设定 + + + + + + + + + + + 记录事项: + + + + + + + + + + 金额(元): + + + + + + + + 0 + 0 + + + + + 100 + 16777215 + + + + + + + + + 0 + 0 + + + + + 100 + 0 + + + + + + + + 记录 + + + + + + + + + Qt::Orientation::Horizontal + + + + + + + + + 款项筛选 + + + + + + + 最近天数 + + + + + + + 关键字筛 + + + + + + + 分类筛选 + + + + + + + 金额小: + + + + + + + + + + 金额大: + + + + + + + + + + + + + + 金额大于 + + + + + + + 金额小于 + + + + + + + 金额之间 + + + + + + + 金额不限 + + + + + + + 关键字: + + + + + + + + + + 天范围: + + + + + + + + + + + + + + 清除 + + + + + + + 添加附件图 + + + + + + + + + + 筛选查找 + + + + + + + + + + diff --git a/ofen b/ofen new file mode 160000 index 0000000..55f0045 --- /dev/null +++ b/ofen @@ -0,0 +1 @@ +Subproject commit 55f0045958a858a517ea23e2bfc767f6f754e39c diff --git a/recordedit.cpp b/recordedit.cpp new file mode 100644 index 0000000..06d9ed1 --- /dev/null +++ b/recordedit.cpp @@ -0,0 +1,133 @@ +#include "recordedit.h" + +#include "repayment.h" +#include "ui_recordedit.h" +#include "util.h" +#include +#include + +RecordEdit::RecordEdit(QWidget* parent, std::unique_ptr& comSqlOpr, std::unique_ptr& repaySqlOpr) + : QDialog(parent), ui(new Ui::RecordEdit), comSqlOpr_(comSqlOpr), repaySqlOpr_(repaySqlOpr) +{ + ui->setupUi(this); + ui->cbCalssify->setEditable(true); + ui->timeEdit->setDisplayFormat("HH:mm:ss"); + ui->edCash->setValidator(new QDoubleValidator(0.0, 10000000.0, 2, this)); + ui->edId->setReadOnly(true); + connect(ui->btnModify, &QPushButton::clicked, this, &RecordEdit::Modify); + connect(ui->btnCancel, &QPushButton::clicked, this, &RecordEdit::reject); + + connect(ui->btnCurDate, &QPushButton::clicked, this, [this]() { + auto curContent = ui->pedMark->toPlainText(); + curContent += "\n"; + auto curDt = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss"); + curContent += curDt; + ui->pedMark->setPlainText(curContent); + }); + + connect(ui->btnYiHuanQing, &QPushButton::clicked, this, [this]() { + auto curContent = ui->pedMark->toPlainText(); + if (curContent.contains("已还清")) { + return; + } + curContent += "\n"; + curContent += "已还清。"; + ui->pedMark->setPlainText(curContent); + }); + + connect(ui->btnRepay, &QPushButton::clicked, this, [this]() { + QString info; + info.append(QString::number(record_.id)); + info.append(" "); + info.append(QString::fromStdString(record_.payType)); + info.append(" "); + info.append(QString::fromStdString(record_.dt)); + info.append(" "); + info.append(QString::fromStdString(record_.classify)); + + auto* payment = new Repayment(this, repaySqlOpr_); + payment->SetInformation(info); + payment->SetRecord(record_); + payment->exec(); + }); + + setWindowTitle("编辑"); +} + +RecordEdit::~RecordEdit() +{ + delete ui; +} + +int RecordEdit::exec() +{ + modify_ = false; + ShowData(); + if (!over_) { + return QDialog::Rejected; + } + return QDialog::exec(); +} + +void RecordEdit::ShowData() +{ + over_ = false; + ClassifyRecordList classifyList; + if (!comSqlOpr_->GetClassifyList(classifyList)) { + QMessageBox::warning(this, "错误", QString::fromStdString(comSqlOpr_->GetLastErr())); + return; + } + ui->cbCalssify->clear(); + for (const auto& classify : classifyList) { + ui->cbCalssify->addItem(QString::fromStdString(classify.value)); + } + if (!classifyList.empty()) { + ui->cbCalssify->setCurrentText(QString::fromStdString(record_.classify)); + } + ui->edId->setText(QString::number(record_.id)); + ui->cbType->addItem("现金支出"); + ui->cbType->addItem("现金收入"); + ui->cbType->addItem("信用支出"); + ui->cbType->addItem("信用收入"); + ui->cbType->addItem("信用借款"); + QDateTime datetime = QDateTime::fromString(QString::fromStdString(record_.dt), "yyyy-MM-dd hh:mm:ss"); + ui->dateEdit->setDate(datetime.date()); + ui->timeEdit->setTime(datetime.time()); + + ui->edCash->setText(QString::number(record_.money / 100.0)); + ui->pedContent->setPlainText(QString::fromStdString(record_.thing)); + ui->pedMark->setPlainText(QString::fromStdString(record_.remark)); + + ui->cbType->setCurrentText(QString::fromStdString(record_.payType)); + if (record_.payType == "现金支出" || record_.payType == "现金收入") { + ui->btnRepay->setEnabled(false); + } else { + ui->btnRepay->setEnabled(true); + } + + over_ = true; +} + +void RecordEdit::GetData() +{ + record_.classify = ui->cbCalssify->currentText().toStdString(); + record_.payType = ui->cbType->currentText().toStdString(); + QDateTime datetime; + datetime.setDate(ui->dateEdit->date()); + datetime.setTime(ui->timeEdit->time()); + auto strMoney = ui->edCash->text(); + record_.money = Util::CashInt(strMoney.toDouble()); + record_.dt = datetime.toString("yyyy-MM-dd hh:mm:ss").toStdString(); + record_.thing = ui->pedContent->toPlainText().toStdString(); + record_.remark = ui->pedMark->toPlainText().toStdString(); +} + +void RecordEdit::Modify() +{ + if (QMessageBox::question(this, "确认修改", "确定修改记录吗?") != QMessageBox::Yes) { + return; + } + modify_ = true; + GetData(); + accept(); +} \ No newline at end of file diff --git a/recordedit.h b/recordedit.h new file mode 100644 index 0000000..58dd17b --- /dev/null +++ b/recordedit.h @@ -0,0 +1,39 @@ +#ifndef RECORDEDIT_H +#define RECORDEDIT_H + +#include "SqlOpr.h" +#include + +namespace Ui { +class RecordEdit; +} + +class RecordEdit : public QDialog +{ + Q_OBJECT + +public: + explicit RecordEdit(QWidget* parent, std::unique_ptr& comSqlOpr, std::unique_ptr& repaySqlOpr); + ~RecordEdit(); + +public: + int exec() override; + +public: + bool modify_ = false; + AccountRecord record_; + +private: + void ShowData(); + void GetData(); + void Modify(); + +private: + bool autoChange_{false}; + bool over_{}; + std::unique_ptr& repaySqlOpr_; + std::unique_ptr& comSqlOpr_; + Ui::RecordEdit* ui; +}; + +#endif // RECORDEDIT_H diff --git a/recordedit.ui b/recordedit.ui new file mode 100644 index 0000000..e4dc9b2 --- /dev/null +++ b/recordedit.ui @@ -0,0 +1,206 @@ + + + RecordEdit + + + + 0 + 0 + 305 + 386 + + + + Dialog + + + + + + + + 序号: + + + + + + + + + + + + + + 类型: + + + + + + + + 0 + 0 + + + + + + + + 分类: + + + + + + + + 0 + 0 + + + + + + + + + + + + 金额: + + + + + + + + + + + + + + 日期: + + + + + + + + + + + + + + + 内容: + + + + + + + + + + 备注: + + + + + + + + + 已还清 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 还款 + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 当前时间 + + + + + + + + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 修改 + + + + + + + 取消 + + + + + + + + + + diff --git a/repayment.cpp b/repayment.cpp new file mode 100644 index 0000000..da2768e --- /dev/null +++ b/repayment.cpp @@ -0,0 +1,225 @@ +#include "repayment.h" +#include "ui_repayment.h" +#include "util.h" +#include +#include +#include +#include + +Repayment::Repayment(QWidget* parent, std::unique_ptr& repaySqlOpr) + : QDialog(parent), ui(new Ui::Repayment), repaySqlOpr_(repaySqlOpr) +{ + ui->setupUi(this); + Init(); + setWindowTitle("还款情况"); + resize(800, 600); +} + +Repayment::~Repayment() +{ + delete ui; +} + +int Repayment::exec() +{ + ui->lbInfo->setText(info_); + ShowData(); + if (!over_) { + return QDialog::Rejected; + } + return QDialog::exec(); +} + +void Repayment::SetInformation(const QString& info) +{ + info_ = info; +} + +void Repayment::SetRecord(const AccountRecord& record) +{ + record_ = record; +} + +void Repayment::ShowData() +{ + over_ = false; + RepayRecordList reuslt; + if (!repaySqlOpr_->GetRepayResult(reuslt, record_.id)) { + QMessageBox::critical(this, "错误", "获取还款记录失败"); + return; + } + + tw_->setRowCount(reuslt.size()); + for (int row = 0; row < reuslt.size(); ++row) { + QTableWidgetItem* item = new QTableWidgetItem(QString::number(reuslt[row].id)); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + tw_->setItem(row, 0, item); + + item = new QTableWidgetItem(QString::number(reuslt[row].accID)); + item->setFlags(item->flags() & ~Qt::ItemIsEditable); + tw_->setItem(row, 1, item); + + item = new QTableWidgetItem(QString::number(Util::CashDouble(reuslt[row].money))); + tw_->setItem(row, 2, item); + + item = new QTableWidgetItem(QString::fromStdString(reuslt[row].dt)); + tw_->setItem(row, 3, item); + + item = new QTableWidgetItem(QString::fromStdString(reuslt[row].remark)); + tw_->setItem(row, 4, item); + } + + ui->edPay->setText(QString::number(Util::CashDouble(record_.money)) + "元"); + + // 统计还了多少钱 + BasicAnasys(reuslt); + over_ = true; +} + +void Repayment::Init() +{ + ui->edPay->setReadOnly(true); + ui->edResult->setReadOnly(true); + + auto* lay = new QVBoxLayout(this); + tw_ = new QTableWidget(this); + + QStringList headers; + headers << "ID" << "记录ID" << "金额" << "日期" << "备注"; + + tw_->setColumnCount(headers.size()); + tw_->setHorizontalHeaderLabels(headers); + tw_->setSelectionBehavior(QAbstractItemView::SelectRows); + + tw_->setColumnWidth(0, 50); + tw_->setColumnWidth(1, 50); + tw_->setColumnWidth(2, 100); + tw_->setColumnWidth(3, 150); + tw_->setColumnWidth(4, 100); + + lay->addWidget(tw_); + ui->widget->setLayout(lay); + tw_->setContextMenuPolicy(Qt::CustomContextMenu); + connect(tw_, &QTableWidget::customContextMenuRequested, this, &Repayment::ShowContextMenu); + connect(ui->btnSave, &QPushButton::clicked, this, &Repayment::Save); +} + +void Repayment::ShowContextMenu(const QPoint& pos) +{ + QList selectedItems = tw_->selectedItems(); + if (selectedItems.isEmpty()) { + QAction* acAdd = new QAction("添加", this); + QMenu menu(this); + connect(acAdd, &QAction::triggered, this, [this]() { + bool ok = false; + double amount = + QInputDialog::getDouble(this, "录入金额", "请输入金额:", 0.0, 0.0, 1000000.0, 2, &ok, Qt::WindowFlags(), 1.0); + + if (!ok || amount <= 0) { + return; + } + + int row = tw_->rowCount(); + tw_->insertRow(row); + + QString currentDateTime = QDateTime::currentDateTime().toString("yyyy-MM-dd HH:mm:ss"); + auto* nId = new QTableWidgetItem(""); + nId->setFlags(nId->flags() & ~Qt::ItemIsEditable); + tw_->setItem(row, 0, nId); + + auto* rIdItem = new QTableWidgetItem(QString::number(record_.id)); + rIdItem->setFlags(rIdItem->flags() & ~Qt::ItemIsEditable); + + tw_->setItem(row, 1, rIdItem); + tw_->setItem(row, 2, new QTableWidgetItem(QString::number(amount, 'f', 2))); + + auto* dateCur = new QTableWidgetItem(currentDateTime); + dateCur->setFlags(dateCur->flags() & ~Qt::ItemIsEditable); + + tw_->setItem(row, 3, dateCur); + tw_->setItem(row, 4, new QTableWidgetItem("")); + + tw_->resizeRowToContents(row); + tw_->scrollToItem(tw_->item(row, 0)); + }); + menu.addAction(acAdd); + menu.exec(tw_->viewport()->mapToGlobal(pos)); + return; + } + if (selectedItems.size() == 5) { + QAction* acDel = new QAction("删除", this); + QMenu menu(this); + connect(acDel, &QAction::triggered, this, [this, selectedItems]() { + auto strId = selectedItems[0]->text(); + if (strId.isEmpty()) { + return; + } + int id = strId.toInt(); + // 确认 + if (QMessageBox::No == QMessageBox::question(this, "删除", "确定删除吗?")) { + return; + } + if (!repaySqlOpr_->deleteRepayRecord(id)) { + QMessageBox::critical(this, "错误", "删除失败"); + return; + } + tw_->removeRow(selectedItems[0]->row()); + }); + menu.addAction(acDel); + menu.exec(tw_->viewport()->mapToGlobal(pos)); + } +} +void Repayment::Save() +{ + int rowCount = tw_->rowCount(); + if (rowCount < 1) { + return; + } + for (int row = 0; row < rowCount; ++row) { + auto* id = tw_->item(row, 0); + auto* rId = tw_->item(row, 1); + auto* amount = tw_->item(row, 2); + auto* date = tw_->item(row, 3); + auto* note = tw_->item(row, 4); + + auto idText = id->text(); + if (idText.isEmpty()) { + RepayRecord rd; + rd.dt = date->text().toStdString(); + rd.accID = record_.id; + rd.money = Util::CashInt(amount->text().toDouble()); + rd.remark = note->text().toStdString(); + if (!repaySqlOpr_->InsertRepayRecord(rd)) { + QMessageBox::critical(this, "错误", "添加还款记录失败"); + return; + } + id->setText(QString::number(rd.id)); + } else { + RepayRecord rd; + rd.id = idText.toInt(); + rd.dt = date->text().toStdString(); + rd.accID = record_.id; + rd.money = Util::CashInt(amount->text().toDouble()); + rd.remark = note->text().toStdString(); + if (!repaySqlOpr_->updateRepayRecord(rd)) { + QMessageBox::critical(this, "错误", "更新还款记录失败"); + return; + } + } + } + + QMessageBox::information(this, "提示", "保存成功"); +} + +void Repayment::BasicAnasys(const RepayRecordList& rList) +{ + int32_t total{}; + for (const auto& rd : rList) { + total += rd.money; + } + if (total >= record_.money) { + ui->edResult->setText("剩余" + QString::number(Util::CashDouble(total - record_.money)) + "元,已还清。"); + } else { + ui->edResult->setText("剩余" + QString::number(Util::CashDouble(record_.money - total)) + "元,未还清。"); + } +} diff --git a/repayment.h b/repayment.h new file mode 100644 index 0000000..619eb33 --- /dev/null +++ b/repayment.h @@ -0,0 +1,41 @@ +#ifndef REPAYMENT_H +#define REPAYMENT_H + +#include "SqlOpr.h" +#include +#include + +namespace Ui { +class Repayment; +} + +class Repayment : public QDialog +{ + Q_OBJECT + +public: + explicit Repayment(QWidget* parent, std::unique_ptr& repaySqlOpr); + ~Repayment(); + +public: + int exec() override; + void SetInformation(const QString& info); + void SetRecord(const AccountRecord& record); + +private: + void ShowData(); + void Init(); + void ShowContextMenu(const QPoint& pos); + void Save(); + void BasicAnasys(const RepayRecordList& rList); + +private: + bool over_{}; + QString info_; + Ui::Repayment* ui; + AccountRecord record_; + QTableWidget* tw_{}; + std::unique_ptr& repaySqlOpr_; +}; + +#endif // REPAYMENT_H diff --git a/repayment.ui b/repayment.ui new file mode 100644 index 0000000..21c44f6 --- /dev/null +++ b/repayment.ui @@ -0,0 +1,86 @@ + + + Repayment + + + + 0 + 0 + 476 + 300 + + + + Dialog + + + + + + + 0 + 0 + + + + 统计 + + + + + + NULL + + + + + + + + + 总白条: + + + + + + + + + + 结果: + + + + + + + + + + 保存 + + + + + + + + + + + + 详情 + + + + + + + + + + + + + diff --git a/res.qrc b/res.qrc new file mode 100644 index 0000000..225705f --- /dev/null +++ b/res.qrc @@ -0,0 +1,5 @@ + + + resource/ico.ico + + diff --git a/resource/SimpleAccount.rc b/resource/SimpleAccount.rc new file mode 100644 index 0000000..1d4bc7c --- /dev/null +++ b/resource/SimpleAccount.rc @@ -0,0 +1 @@ +IDI_APP_ICON ICON "ico.ico" \ No newline at end of file diff --git a/resource/ico.ico b/resource/ico.ico new file mode 100644 index 0000000000000000000000000000000000000000..c28f7e65512abf191609eaf3979ef667b5ad1cc1 GIT binary patch literal 67646 zcmeEv2Y6Lg+VxHuvC>=UReA^M(pw-1gx;$ZMX?~*00pFoC<;;(MUW=Fg%W!2o%BXX zAb}KuZDvUF{%h@X_RUEM$T#JmneQ8Xw&&*NrrovP_13+QQf~Nf$`pnFmr@mq3{ci_M8z<=L?|4Z+{JNRP)i8sukpy|z1Aat$MyIa3!lr^eidH7$}MmEwQhOa{HUI7_r1#D^NmtIGnJ=$ z5${TVrE;|XPkVR%cm2MFZ>dtERKa?C)KmS|t7kj-s{)ty3>iOAGZe9cSc@*ff#WR1W4d4oR z(+=EdyHY9dL+ZJXt5g$pS*d<29_xGm_eFolJ5cMeD)YuxmAlD0_3Yr)s-VvXRk+(` zx5C}Gd0=@K>apDoOL6gE;oe(Rp}{*g?!dLS)l#uTzOjkP&iMEWo2@=SY8FI zZtfO8G|JulhX#3CuBe%-*$)+;ZMe9^Gc`ZY|3s-N&$vH3_D}BS*LCZtYApIi73{WE z73$@uN{&14UeI@&C-~rzuia|5TrIv;PYm3z3b*}CJ=E;px?DyIbLITa;H1KWp{c=~1|cU$H{nwlsnL4=&JY z-HiMlf7%FrznIT^T?F(#F2B$ElmcBg|G7~2ZE1yi?2tk|v3mPS;l8`YtKXjch5PRM ztx&HWi3NM?xKY4&TL|p_Xg;5f-{tABc0%sfE4$`w@?G_(tIW<_sNW7%XaI0lPwBs| z$k4+c`F%Ee)eRbk()US2(SbSdP!bzZ?P8!r^-yx~_Y;VRg5vlQ;V zQ;H7WCnZK6mNKuMkn-ctO2zjtNtKVTO4X?WQvH*_^y)LPJ_(c>GXnXsRGW59s!X{e z6+gHr72drd<=#3YrCvEE#fBY}A_M%>VE?xZbltKmZ-<{Io3;_UW1nRF3E8sG=iJxM?5J=0~>ztyif6oz|;79e+})2iAZ8WqJoZ z7M)SWI&W2l#~fEB1{{IT?{xR-v&W-QcR#nHJ-5UDx8;N__b%LPhhO0y+x}dr+h*AJ zCIS9Zr2p=;lB18_hpnbnpAnQ^_lt08v@lYdEsd5|z_{(ISn06lrg*QtDIM3{5});E z#Xa!26F%1oui@(W6BoV)%Nt+ces!#XC+YZ}>CL~tk=l51)cv}jhox8j_^On9`%HR? zmk&$fKD%PTg?YJKt*on5C4>(ZRk4wK-4Hi;Btc(?CN!p7`qwl})RYWSaq3j!{=yTE#Kb*}ZY&oUrq@BOOSKz~(Y$R1UE;BL1f{q}kk>bc7ecCL!_ z-CYtI@Re6@zhAt1Zr65R(05C!SHIn zFWJw%MEo7AGhXjnHXk$x$OC(I0w0Vn5P!YM0h@M+C8CV4k*X20m4G))P5OexeHaZga#<)5^Sl zQpaZS<u`15=4YB0}y&8z|1H@N(R z%>%=Q3_l_C5e*hZq*k7MIlcIZLsGE&_5kpp-%~}#dMH&~74+Tik+(hSQSH_!)GE}o zZ4m4JFaMWwg1;(;ys0?wFE+#b)Zkem)hrrkVaoeO3P(8w5?IMiFdaxx3MyC@3xiCWpF@qB3m9fbil^naKP|@Kf~eye8~m} z2j~xs4mhty6A&*@6R1guDWwJCNN_f_(xl7j#fBf00$sNr&DnfK^<3@NtHOQvqPDcv zqtt6bs>BIKY+yIBT*UV*(?ne;$wfrA6ixWLwd%=nWF zSvUY5xcq_P0XV?e!`tS7@eSkw^9l0c`FGEYSAYM%NRcM!u!Fi3wzB0OB1r{KqmZ+CfGi~6%zn^a?EHzW&KV)AWW5b?VNkQP8-|{ z`TD7XU3aKFK8OwfL;pR|dp&U85Bv|P;zRet*ZX@G=?}dhxVJIvKDr3?zVFWaxTXVt zoj28+7pAqI?gj6po>5PXQ~7*0d6XP; zRh1ZZN#*Xi8qJJa|4}T+(K{9~9%4PL;zRej74Es)qmYjuu^&~W->$!S_1y_yPrcu+ z>$8<7U6RI&B6YmPIEeusY`8)CFl3Vm2)CV0WfUopZCN?1E zE*)^hg_+|5@PKtiohQWVI&>q{6Dz)d0sW|5cXPE_(=ul(%r2nsTvJ5l+%4#Lq6hYG z-dD`gi<-X=xO44A?S7BDyZQot-+!^je!!ml#Rl$4XS`Qx%u%T`H$>NOsH?=w;I7|q z`#tA#J+@=%D>Z;0XVwQNAN1$Q9~*CjH8CD`FiA!nPL`LkcpZKyNrnPD4dv^)-Sbby-&0Ricvt164ix_P)B*RCCsnaQ$m<8~ zH_@)HNn&jS%W6- zW;y+V6&qye0DBCqX(1*^tNuxVcn$FXOP==YU&`5jgUau_!?W}*%%u#v2u(nZ;NR^Z za=cQ|`%(jU!~UW9-S)VZ8@u15*x-F@na>p+NbLR5tJx!EUpp=h=7;O}p6?g2>uuX= zR{YJ!P9E^}=1$mc>iMm3O4s+5~C2 zK0(@SOq32=5~bs|B=O#wC~Y?-N-I47;+h0`;fLE&ZE>7bU4-@ZEvd5LrZm9!>9p|< zv?3WiNRhYB-h)1*Ku3~=9OLgn9vD5)@uG@y8sn zYXlAsuxCjQ)R-BV?lov%dhQMzI_E^aFn^b=o|s?J^?_%Gp8dB_ffAz*p-;D8l^ul` zZ|FhKB7+Yh?%y}9D6}59{}r`f*uTG&M!&q#f(YF=p^XsFKETT?;;$F6ei#?**N`v7 zow`g7_T6?zny!eG+DmUq!yj);d*DL6dlK(mHqOM9*h0^_eC*X}^Bw81;kLB-DPCHw ziIWy9Z%MNsZ^?^T&G5Lzns{jqPPN~3M{`2+0eot{E>Ws{b6d(U1kb>Wf%}rdgP-MX zXaeH}{$A85@|-zMrure{LR$keazra0)G>jr1MDY(1E>+~OUc=0U9G2^GA7*aQNVYX zD$vzWc{KdsU&?`8uOC+>M;ungh8~9PAMh;Be1G`C;l(j?Q*7|w)M7*S3GpvA`iRtr zzo*@6?17(-!+{~MdTB0LoX}ceV@;k|+^}Bf#Jwwc)cA*6Qt!JsX}K2u7q;AUccOHs zZSP2sF2Gzb*fOs#fhR9*1&+X;tK%l%x{jFNlqTQDNd3h(r0&<(rS5|3Qt#_%slVig zH2w}gdBsg>vFetzTpcH^*2YVF_<*j^g&up8p$*AWcV(iK{Q9=kS$;bgp3H!%Q5o>I23nXkUQ*5Ow3!Dj!{8UJ(9l!|#j!sq%c4*L$OT{?6M~ zejm)V)*bn;;Xum^s`RL%s?^IzSmW_vt*7|#Lv3OE_c5cDj=Vpe_%p|=`&qEGN3KKc zdlP41P8_*h_-B1=<8O4qd<}g(zejV}XRReSrP&(fWYB41-W8a41=iiNSzQsMbpiGo z|IN3h<3`y0+Bj*tEJo^l87Y;fhe+A?u1Tr4FH5O6FH6~pSEb4)A^LL-mqg=rzydsI zxe6SBPPG2%wzS_Z({?v4fLBon*DfFbAUdh zEqnuc&}mzu^xc0~y7{L_rSB7@j4Q3xD{7ht&tH zzCo-s##}*J%pxKm{N4*S^viV zm)(@Q-(n^U{$1NU@Fw;a=FTf>H8uy#RpSr+?~J-~hYj)a;t#P>Zfb}W9CSj;^x7{q z2ON+_!}m+m;d`a=kX=%%_YNuEaf>|HX^#|#Ce*@QS-k~O((s#T?JJsR!-2N&3qJ5c z{SMxhR-2QhD72s^e8X5<3tl;%qGJ};DCiF|`2t5wV0?fxFKCMy#RgwTq?R0WO!9bd z8j!2QCRG4EBP`C8DAj7%KOcDhgfZW#lB18SqA#CR#YY_REH?Zw>eNTyqOF%0ao~Q* zmvIID<=!}jIgRVOK0@p?_IoloV7TDofP)K0>&b=w@aJ8CS^aOJzpLWK7g|reyTYIQ z0{iZGY%L3O;+=U}dZ6_m`1{~{wS|UM`YcjvkG~`%W?hy^-&~U~R$rAR>o3W78!yPx zjpt?2PiJKIcgJMJq&-p;F=TuA zly-;{+i$_|u$(IkM zm&BFvUZbzVHMKA_QxThe59s7(7IR2FWIkZp$|w0l>ZfPsvhf zK6D@cVA7ReWIS>N)+w@bAXDAgjtyOYfjwl*lyI(yv+4PJ?DT7o+Hjqvcii%HK#i#D zF6Hsg#eb>>cud-?O22lRn7ft0e!G%yp3hNo)REJS@k)+7lv--kA!q<<`qQrJ8kn(r z=4r&8SK{tm2K%g6zNYQ!TT*vPjLz4L-fK*4e0v(~!7&#PT+dm&AQ!lZKjTB@^-aFN zDN|QR%Ceo&vioF=TnxM=mxANuvR-%`bPLbjl=IhO<;2D7;&y!Yi{sfAueJ>tLk zy1aM>R2trM!`sH2`GM#A<-9-UBf)M3H8Uurb2;KB26pXQvjuF(;1#_L@DxitRfYVkqq z0kr_O?p&>5TlJR2>b%Vt_|eZBdnfLCAvWv*>{%0f0Dpc?f8N5M--CHVJJk6Gti2^` z_Qs0e$v6p&NRY7TL}$7Z^-)X=cU`+3sP%- z6yJYohFP?hhyhxIqiyj04E|0IctZ!sfp(}d_C{>zvpYrd&j+u!CCij+zsdyE3z!?Q zp2^unT_dpZcX2@L0BeNc1~mY&bZg9^*ZwR-iVQuN{9NmeWu9t+K7sE}_uRgFRql5- z|5NaP?wy0GQmU?gSWgVZ3wLbmWZ~<3~3)XAR>&SubSgiL^L)xL%J7Igg z>^K%DrvnouHvXR6xRoLiHOl@%35k<)0kLxMSdffca#3o21^k!T_-p&eTo3SP z9ianeg*#{sh!^jTSg0>p9DqLn56FS8$Pe<*zay=;LI=PB)()uytPxnfgN%NG=76?; zt46H%B7g&&EyP^m{R;10pda|Qz#z<0k38y;tIcwiAN!_Mvwv`pX!1SS{z+AO;z`DS z9wkQ~Q`rBqfIU2{)Y7<;2UR|}py%g_BXKwQ8x7E0aN%!s!14|J8rpw5*i*g527BaC z@Y%%P;7Qz_%f$tae`cN->=`#`{81BZkJ{ZwyKl>`lksvPBuU~Devz2?6p4%h{w@u$ zbRZzy(tr!_1xHVZ%DdlPlp6CQp#M?wB6`-XfPY))e>-4A{Jnv_H*yQEj=-Na2G*F! z0dML6RYftyk@XS7flPkD!2y#K&<|j~Sc;E2nwG2M)<(~^ z-K6sO*yWzfdymRL+S&vAj}n6CI&Dw|Upc1Aj6LaI7XH7?Yp3d9PuyQiy>b+F-J|Jc za4k3Pq%_AK4EDne_QXHaq6RpY@dw16_+yKv)LD2#=jqJfi9LO{#u(q1-`gz10mF-o zSRljxwf@_7?~6Jx>;D~q|1|$QvisC+xfGTp@xVXUg@2;<0l^U&et5X^4Li~Y=Ct&O?8ZqP9Dm@^gZgK!alb7h~#EUg9jm>eMf z%!g~wkA`kyb_4#L@tU!FVr_6Y*X-~&959?P`1b(zdhLPzBPKWaUkOi^_(bA=Payr3|03eS2k`$s8vY;nLyOzn_;=(S2<+auv<^T6r~{e<$PL>g9_Wvl zu<7QzQeaVnj6VIdOocBPeeABT6I!)_2jjpD{1FpsKM*TTmPDoLSg@;~Z|+XpRKebR zJaP{^s&dX)^$)@SPamyTW!}7?%Dj5Qz4WUmcx{Y5IKq86?Bx>wa&MlNw##o=*zaZk z)#|~rC+FlrCN6*%mOp?d0`C_V!Jd#$>sZ~P`A+Ov7s!e~KX35PdZq5Wu!rsU)c7aM z^u2e)|MYEf;vYr)5eJ4u-N_UO(*BtX96uW_AO4Wp{tf=^;Qwj=PW*{GdEoQ|h69WV z+anGgcqBz?tVx#A%aY{X%fHGb)Qg8<28Hu1#t&qU15GYq*9O1=%uu9qX14IaeMfxK zMRiXSw|t$qD9qmcLvn%YuU$};-#(#AymG>$#4E?4{U?`mFEv(b+1D-nneVa2N_;i; zx(5#p0RB$>H(W4!K-;J8H$yG10qQ#sVDG@&)QDa9<8wv>vb*-g=X=8L^+FA)2k`IZ z5B=Yn8UNd~|J$(tnC$Vt78>uue-iMoN&QC;yBYnzZT|*;J^$mde;>!=PDTTCEP%WK zI?!=@k_ncN+>w#wNa{b9^o z=H)pU9sbT4@(n+$lH*US=f|IOfBu~d>iG#5%9nlP^si;doe-?_a=4az^OUqiov*)b z_d|d^S9Thp^}yBy`U%cbG0v&AAll{gU7BxWZ*X^B$$?%E!{2-k*!RkUe^21w5%_=N zu>Y+1XXF6z0muWdArGYf)BX+q*bmOWTn7Fw{m;;TV(-EqxgC2BE)M8ez{(BTH|nzQ zp5$4WD8r8bEHi?BlQEb*W53AMh;<#nt__%)faMFU9uYO5+Q;Y-_S?6i2=ekWZ=G>3 zfO=r@VF&*%A5eDsF;$w{Kkl>#_SCDgZ=RXKIaSnl?-Td3i1}(@UXAllgKXS0<4+AR zzQEE0@`9Q`+h>2gF8Zu(Q9q&Wn^@1-cpqp(cFTO;@W5O(C$iwLIbhpA@%IM)GxlZZ zzro+=|6}2g_&Hn=5z=!#KmPPzE2iSk)+yNGA1FR7?*mze;|Bxh;Q5&KkU|qoJ z2eNX&r2~uu*4#{QjF=R?fxqVNxUJfA?Y1Bez_~Y=6;i!1KmIrVD?jD3D*yHwxAO0t zQ{^X|&j&vrOWe!7bt=6)>OAw45|jV*AeUvMt+fFCG3*+?ODG5dYKvJMj-^9mrJ!uwp>k zKkI=fwEw>-wUGZY|99f=ivMiv>GwMWd$TflVBv2#z&f$65!!yB=u+@=SBlID`Az49 z{Cn9mejp#97U-PN5eNJjbDwkc1^e!sm%r~`Rb<#9kK#iPDAk3ttHJ4=$+k z@0|C<0{#~U5^vz1${HW>ul~_x>4{i{^KXn>ocQzcP$w5`pFkhLxR3sg@qQ=7`$qG7 zkle@E2dXYNmM#ec;ACj39` zU*nG&KnUu9rWVLLpvM24rT?|Qj9|=d;ji<5EB^Op{FlY{v%}xV)&O$A+h_oC!k(xL zbO82+ze$vdmwwgx0N+>k3Rxd^?K80JgT@b-+AuYs{+D4=eB{v^PdEQ5_Y)1)sKWjC zxp@u$+hTyy)BIKCcTcO*Z(ndP^ES>xn|OXR_vJnR&Y9E-@0>xM_lz_`t&g><5%@hh zJFPLdbwG20x{!qfw0+{*6g{-YOJlXovu;Pb?L&-h+ztLN4%p9IT*&YXPR>}qA_ITT z0eoFA7yii_|4(55d(ULp{{#3tasZ8gu;u@!thgi(;*Z)7ZPCVG`+bM)lLHy{Zywt^ z;PL~g4Q+*gJa$jYtw@%pTkgs%XaHx)SQj-Kpy!G0c~X}@z|S^yLG*^w%DsJ7@^#rS~tnRr>I%s`TNNVihKyPp&ZGtW=nACcPr?uQ2hP zbimB|2+TDQe`Ehn4mhzlF(L8i-mcnTMoDM<{Z89=Vz1*vJ4SSJ!SKPt+Tu*+$C>f( zodthy+CTjNUOWFY_J7@J|1KY3^#Lvcf9n5~<(cts4f}70I!t@mB=M*3C;rCnd1Vij ztFu`)H+=B7b8-H#BXR-O2D|zr7TkMJ3NJ~Nw=evnHGumROdPVsze4{I%u-Cxbs+?z^l;$gFm@o7BP1|Hu(1@_E?Z`?e+S}k*Zz#wXQu&{7NE96EM4|*X#nwOF3npA29&;SeccH19slm8a0!83!`;! zmiW{5iJ22;?xoNxEA|fj4bJ9D?466xXK28K_*4HcqyDe;{|@Z`X0q-9h9UlA4q)NW zUa%AY%TgQoH~l7xXZ=VUZU5~3*#2L0;E#NOg}>$iV?kR7I@%i0LFWQLYy7*x7tBUY zXbk4YI1}%x3)?Y(bHAzP0_s6+;RC8en+p#3-dyy_~_$Tlp2mR=*BCrp@-d`>hkVm*?*HC zj;LC*gH?q|S3R**<&UmSuKeLesq*24`&DoS52WF|5WRnr^Nhsb*nb8Gj0RX-VC=^_ z5cym4i%1>!>AJpc`&!>^o2QT9a%q75*!Tb^A51K0u1+qP{Ezit-2-6#7e1gP=Ju!i z-xmKz5K9zan^k4G&?k=CZ z%Wmw;IpY4@J3Af={}L-OQ}$UPdOl~}%fk0p`snIT))lHux|CXV@+Bb`+Mwn$_T)Y6 zk#*n?4ImFR7i>K+T0r~HQmPx?h}IrZP31#;ql88aZK&RhQ9=s)&pN=u#}3JqWn zkUBsf=vt8G0knX6z?hwQWs@7Q7T~gfI~QPIK+h;0K^(X|NzV+iFYv0PChR(g!sG+^ zd9GXlS_E#U^6c7z{SNfX-+LcwK}S5E?EQE%0cAgoP&H-;xz(P3UDa6-ovZT4S7WOH zcVM4hbqcOiE{pHRTk;0>yK#>+=h~U080^i}_yqa@;!o|bi`kjx%WvwKkNTfg_sJ21 zy}sI^XiD{y$M} zyYRnj`TvLDPyK&?*+r@LS(x6x${rBUscDKi0M3Fn$JvwQ17||W2dxRff_t94G1rj6 z0h}9PazI=){@?+7gE|+)@7Wf)V4nji(g^+GDyz`13j9sa3-bM9-OSogmRSQ__=5}W zR$F?_XxPBS3|9j z*w>nIO@{a<$eX}_4EojFKaYeqVkX#*`T6${OUv#pA3;Ci zk#40hyQhl&@myH(rD?!F$P-Idn;AT$ zD)0yPsntKe%&Ro`B1Fa@=Og}{S1|ayuHb=_5A*^2y}1Xd=DaA~C#LPwW@*D3e_&3W z%_9Ehv4y{dxsw;{Hq{hF=n%C%?+2zQ-h@3`&XqD_V1K@>yngw`-+r( z_nH)&7%1M*vGu#c<$TZ`J^x|p|3mOc{Kx$N^u=ANCs0GiNw(CTChSTK_GB{xTfDde+aUgwwH}ZhK zm<=kjBvFPR`&rKjQD@mRA2k8_T+x9(@s@l^l>-! zUgRIzf8)jHRn=*Mo>iw~o@iFkv}#j<|HoJESI3ncXudc?#$i7T&p{ymBW?TFnDeoX zzu^FLK5EJYHBYWjbz_cjldw!{=_vE!2M4|JU_@ z)PKw@u^n?&WCD4wM!$rI&g$di@7kf&-akSA*{mOs^ADu1fK zTyivDFZuc(()#cM&Mu@LShK>m7U*07{s20q7qQgZZ(;!x2SVQt-;=VK4QadWuFOOX zz~{opURw%TxbXFd&6Qu<2l&&+H~Q{|l$&^2s*gA<&0jevz7r0~u#b+&*jY#A?az+N z#4nD?#Lw|~&JlTa`ayYh#$kEmv*WUIM*!x$Z)@y1|4ZyS1I9T}E>i=#iW!hom#)j) zWhbQb=ylS4+*TPjX}7#OZJ)e0W50}eg|6TvtvLt;n*MAKhC1U9O)jnVndFo8nc2<5Px8wT5~#h zfXAKJ#L5KJrpKL1k8y@-6l%nRvy0dS-Jvo=8gZ}7MJ0oER}Zs-TJ z*`6XLmnQ03-~{X=b;f{311uk4pKH%J0}E$Jr1Cx%Uc-)#C_MOxDn9z8r$_H2kH){7 zsJfqrxHViFs~UZG>**S^g0I(@evNy0)3}e9tJjt|dH<3V|76qvlXV?{wE*I8_+ap7 z%vxm` z(fH??dsNl_GQz#~*U_r>!s{iG2mV%rxX-wj&bYtM>_8a={NBUw@jC4v{-5<B}8jPFvQx&Z~K>v9|CwwomLceVu~`8FK;Fd^qHR|Xo+0W4 z`^A?LFPsD~HtxJAJ-@h!{S47O&jtH-*e^nSuJxaGZ`r@GeHRCy|K2#O#0Rs=y$+xs zxHwTpU^eVy%m%RzWX%Mizr+{-f4_5?co6wUYF+FRD)Gwc)g@m$i#d*q9z};g8vmz1 zKd9<0xb9wW@l924NlfLMpN6Dr{AUIL{{X4?S&)nX2DE<{{_p|BK4V$fQv--U@#Oi( zHReX@*q`{*_Z$3)uM_*MkFD7vVs9ad{y#hX z5d);Y@I|PU9D8~@_iR?2aKXLO#{rMj|0haJP<6kGbg#Q0TGgF@y>>0+wl#r!tyuw5 z8>{~3A@T}v{lGrgg8rZVKQ8^))&lwgQx_(_%zv;AK&4(keXzhFoYC^yDYrVaf**;$+I1cF zALW6WO!dOz=ti~Xgb?@i+R%M2&ijmo<`aL$f2{u)4)8iUlMYyYLE_&W{r=jh{c+BR z`MS&Yp$)o62u-7|aghgn&feQ$`# zglP?N&R*#^&YUTZy<2S-UsIJQp(XT)b7aS@Mf~@5xJTWE5vswGsCKpI1k>lI)%i3~ z`~F7r!(<$KzMM~`{u}%?_O=EX9pLXt{imHb{WeBQgIyoEh-1z_uXtTn(q zXI(S!2M2(EKg`XJJDMW5bovqRqo#VUF*P2)bzQp?*_C$FZhILEcyms+3)4d zzvhJT5e_c6IAFBFwt2<}Mx4)t&MY=fPHG+IYGQiMkF$Q<^Tn&hfd{o9I~_2wqw)KEjph-!6m`>%H=sA< z6JHw?6e~-1MQF`uuFw7tapw#O&og1&BD=*iinxeB=Rx`b|H_yLYO+4bg+FJYSx;v_ z)xw|GjQiLSf7AognkVF;O@xs?ps?n0D zo^^=-oFK%J#6L)yVCH86W)i313=5mV{yjBksQva8nMQy&?1V zL`zqk>A)HfYtzKND{$wTb8A<~I<6|b`)K?pFG_&jM|i;YRg-U{I@XaMQ4)a+cSys`LP7~82#QYhi}Tk z^YIcIeOF_D9X6}u0&pZ#ZU7z_UAMSl*9E}|`gg|ntOxoZ36lOZj?0k60WxVL;=C#d9!%zbcQ zFXw$||3>#U2W-3z{=8<(0X#p%Z~)xM@B?>cD71j*tn|kF)dR~Hxq%PrXEkva-^+-- zfAYT}2T<=1i%!zIZDK=W?9u?+?y2D>M&M&zOCbKx0p@tuP%}P%<+^ANvo=62su|9R;XFV@`MLNtlkD1UYJrmX(^DVmHYxj6q+s%YOFmvK=9(P6GnyU+z z@d31do=M#sa{$E`awa5MpC2|HHpTv_6MvTvaKr%MUur{~4^j5*bI0=FzDu}Awc86{ zL_9qB!)KoOBtSJ^am&5MngrEy?VW1qU!~LTU-%*e_TV?N#^fIj{~4Y1Cp<4Y@&M+7r~{^Q794wbb`=|X0_XLdb#M4>)Fbilw*Ibavo_wn{gx!< zy)C(1gM|@)sR!I&m=}`X05g9Lk^jAZELk{@#Iufh_Bl1cr31DPF!)mg7|XHuUwKBT zj)TZu_HK;*>sSwelfgQ>s}p~x{advF>VUP#fq_`G{Wgf5d*K|Rm3v}k>#>`1HH@~O z?6Q4lzGn2DwY(D-Z^%yc5u$FUNHTIso)1G`z}IKIEP;Ven^fC_wZTl%2>_uj={bbicPU}OKr2V~6! zk^iUi%;j?9FK#UP#syVw!ex(|A3XX@kYPtfwOJkOhPt!rx+gg=W}NQ8_ND%pq1am% zDh+U^-)PimJ`MR@*o(66k6}1q`vGbJ7Gpu;Pdv%TN}q)4`Um@e?A^QY&w{;mUk;lO z&^jj{vg&_!8qgo-FZbA!EKOG4mYJL3_xH!j@yqerexsrLM*Fqx15;zy)ct_4I9atb zOy;c#mb2I5BoX$XfSM9>N5%^}7I4G?tnr+L27Iy<`=D_jV&*y4xiIhp;CyBc$j<)B z1KQ~^6VIFWi&Wd6^h4nvu&pYUf3x8+6zW0iBoq1Yn;2i0uoDD~<-^2kr_cw8X?E^CG-^Sme|2p@#@$ZV6gbHIuL2GaK7_jyk2XIo-qt+9Q^^I2@)_=F+W~j zN8Zi%ne*?&U*Fq|-_L=+)BbTjRBH8U0nGoqmU!)qDnId}N4@Efj{iuGfk%?j0}l7B z^HsRQ{`@Z+EWo_$SE2Xm`>6q4*2T&!oKeA z<^TqNqwjx=KYWRc1IYIf=b5GRK>B{vjN3pr2cXuzat~}DexK(%Qu9qdXYxLy|Hk$& zV7}+=?J?4P3uX&or>l@Jp1cyTZQp1B&k58uW7G&tE)ak^?|Epz);*V{+pIG<9}xQk zmZIN>`OgP5z^?m|1C|Cj@VE7!K0x1x1hHQq@VU%_+fr-U9U0Fv#G$90rQ#ksT|>%( zzq201o@{}TV-MeD_k0Wgcn{D!JJS{Jci@RL&K2_5*BT+`hX&klK>VQr z9e%tapP&vs!_EO21CRrT2V?Dckn`${1K0;(O&|NxbPT|JhQi|aerU))wgBN))-m0KSoXfzbMrBiG6nXb520}ec14}qfzjm z=)a)n%KN>{^N*7QXKumg-*xpATyX$vM$R|@^}yrjBV;BtfPFH~{54-@Gyr`cXaM(r z=vKjeiqywY1-wC3zW~(M#hF z?D4U|e>k2Srfr{ffXw)_Up^3;OWWU0zYp7|?rZ#UrR_WIpBU2bU&3tXRKI8)H*mj5 zXT*G;0r1!MZ)1_%rS&ew^3Yb39UdqITkg z_r5!3u6SP;p3}Mf$Za`t;kI1Abx-GloG&o5!X|GpaRB!S9K#-wsox+DK-|?7K0xmg z)^UKX0nRuO7}DlV-fwb0;?H|Y^nmZL{X>Ej`t+8JfbP@xbDsobe|?`z=!l5{oE%^t zh&~{4|1f!e;>DH4UOl5qy?NfF>1@{i{`lWf%mDV?lVHEFEb2kA=ZHHN`ibGv5RZq! zHs*!@8GRtc0Ehvd_`hcR0&`_;fIfgRKwX@jQhiRi_I;cKA)k%?YYy1>vu;HDAA#7x zT9*AAe?Sdjz1J+(e>%eU`$JnY**b|sld%XnPrw?N6R}|P+{U#pwkKp|VE#I$WKX8ZnKbO$~tpU&i#sJLyYR?WP z{$omxJFhSs?dd&!sZxFa*!~OOx~2N%hdHc$+IVz~Ilv><7`;b3aV^DM84KZ|OXMeE?(s85#gzVB_z+Y9F9E z!I;3-0Gk8-Vf!s%`_s{Tq3s{L6sL3b4D79bzf=Dizj3B>|A|QHkH3fK!gH@E&j=#^ zth)|E?R~?^1UY^s0X^iOGiiX{FP=3QfCe1FIkE3AIxe;6hUz%b>=Wl6AXhGEd;st> zxLa4t_jiQ`d82lna|-50a36uOcz;<(rSE4hZLl|2!vU=Wz@KLWSDhLF{4dml?W>9( zUU6^yQ|KQXKVDJzmZ)~CPb=Kd!UOlRP@T5j>ECo|6!A}O0v>Rtpu?&dnTZ(iGn`Sw zdtEUvWgKAe*IaOLfb~H3f_cA#iqnGiJP3P1oC9VI@IVac!asuxnefNwhhx$H8T)m? z`_&Wg?~m~Nto2<(zE7Ly+=pfR8TecAdxq_Qz7zK&#J(H)B;GrW`1b++{SbS1KX^}O z9ZQl!=h5?vNPbWQ;9pElki9|rlC#j0)tk>s+nMKawi)*SI5a@-1GHQ1?te1CSQrJH}BfBRX)C?(8Fz1*yaP+{Z|oxmJbjM zvJOD}T^t}6fWL_aj1S;G58gwi3Ti?8ySW#Hc|c|#K+H36cWQv;6X+K*@MqlL8aDFb zrdU}I+dq0SPUmQ5?&m@Lt$r?T|G9tPlm`_3r?Wt9f7%TI}U(NvLcG7luHZ)@>#{P`|xGej(?{RL|0TBDAHpZS4#Qt0J4m_a>k2vk# zY11v$c=`3m#((LBK-Fh|lB*7gTIM-mFE1{MOl$fLYFv0ca(|*Mi2SoYqli9$^I(Pp zh6e_J;{#ah=e=yoydR+VLU10CaiGov5hFTd0AkHJKrg%p%zX_PocJ?V^T9p&mhOb@ zAC1*@eqzq~4sHLyh+1v5pLo$Y*A`v?PoKW7X_W4^@iT%w!`;QY`pRvdsCBGv=U44J71a6X9Vh3!2OC?mc& zh4Uh?=N&$P=S7&F0Ovwn^S(Cz#GX3DoWIEQSSdOqM!zq#3D*2*`x<*2|BM{K#DT~s zHpi#)K4I0S1^l`EhgZs%nQ&26o*LlZV%wd^#-1{Ti^_c_s`#xFOdguUkZJ|IQoEWbil*|tOwH8aUUxw`$2%V2kLG= z=7MC4WA3gV<9Xa~%3sEDF-P3&_ejf7#XPY4oY`!!q ztp%>+z{@yCaXw-I)&uDSSfk*|$^mKueSpybp3zVd`?IM5oC(oALA>uq10IHdh7P3Q zy-Csge}|&< zX3y9%<1Zrazv@|IVUVhI@-Nvz{K)6pp#L}U^slPTPjQ~O&!xhBlqb`V0ss4~0n!h2 zU4K*NA_tg@T<~4w0a^o`93U5{30S5^zhhn~q_j1^AOST@lDW|X9);7=jjGp^O4B*7yW&6m{=kJQtXRorB!n0!fLSOsY z_-osz{o5SSGeyvV-q3)#(12r?ljIsSAX7a6TEqCl83%AaM4uV{%_(^SX9np#;5d&$ z#{e$;9kvg@-(-2bJT(?IHr&&A2x{hhpLn(r<9`0G%>8NqPCsBY0QrBq-e)x<==Z8q z1It(Z@S3VQGsM02;y~OI?2qgIS;_FisZ*-!(YvbuzGU~I$A3{TpZcXb&bqlz4S@Ei zlLM`Oh?e(Z_g^CpO?6U|)c706AS{MzBVpS>uJJ@Dtx*Z32AEbcQh z_z%F(dl?$A4H|GN5HrQ`mIfraG$3O}#L@uNgpms_+i*@=eTCUc?15n|$b~=SKWabX zKF0lBPz*@_pI`9z|LGf539mMj=MKm9Hp9W z2+0cVV}CsU^v`PIrC(Iu_pZ6;e(#z>%(|r|G@#}8*Hc?ALyrR-7`XGc&I1_-awg;r z)B+3#Tvy<)V*~1dT@T{l*B*PxOW{7t&EX$;zaQ$b$p;zZ>lhF^;KcuB%=i+2-aBIq zUcdQBoE*Ib+lS`!EI;o3G5bE8{y!A)Ib(k2``lk|**^Co^BfTid%d^Zwtc+w>10!dt{8o_#Eds=m#=!0I}mJ;7;s$9RORL z2oCMSIqv6!6RmkY#AWQ!aG!_H{on&kj!xV6KOTYJZXE6*3!e|WqU}4e=iXwoztn|4 zVgNl`3Liku4M8lh@m#W;z+QvMn?LJ(kTXJN&l&v;a}3>AL|^DA_JF?j{S|3~dxvul zj`s-T9&ddn6|nCLyKk{3UY>k8L@La-&POq4r1Pu?;_tM7R}9EJz|IALe_Bh_d~j~d zh3)~Vo=uMY6?6R8-Cp=IF z^Z}d;&>DbTz={R3(E>Xr;G7W83uGU>?4&@cHZxTBMOhOfCb}*NyCVlI-*4fr@yFK= z!G7rX@Hzi8=--7|wK?|bxwqpMVgStn;t#(ZhPaPCzG*vf<}~Und|&DFU1yNM&ze2u zdT%kbU+*`j{o5KqKfwJ))PQ%PVTUgz$@$P^Js-#!5&m3Pe;6E~1_VYX$SIr~J?_Uq z^rg8c2R$bCnC(5DFCqTyjC)e%eKTD0y&a})fHiS#`!@ExhiWGL9Wem?0Bb;)`@mk` zFsbxWz$>N4Us4q(UGr#+J)BjKMj0so=Igj?zp7Vuq^Pcbi5|#L)PMtbhqQwRv|1LO zO8?J1uoHBA266zc0nh@*0U7w?F)+9Mf~^Y_XV1)F-`_&8Yt3H1M z`pWm3{)Ta<*^MovH;WcUS_AArsi51x}#WZWfPC!rr;EI@y3 z#e)tG7#{!*3_J0&OvcYRbTJY8{}S}92IqCT|1SRrv@PhKyT9Hr5#DC_ujihS;uX=B09Cr z3dDnm1v^3mrXvTS1~3=k{s4o&i3xe77MR$8IU#3+=m&^-)tNZ20y@C{AnQWZ=s~be zav+lq*cvbj8bA(A#P?lyJ_++$*qehh-NK=}QK<2Tq85AbT#QWI8>eT3s5M-~n02T} zEf?-KH=KJ-$btS?+;6t=d@}A6jUI3~W(?tHA{qB#58&qGH)QPgn|hAV%=qbhP_xg7 zc(3`&IC*kJu$2BNT4Mj`vE%^fCh|$ z4;YFyoIJqy`s@_;{-Iy9=+qtg)a^IimgmNVNRfBL zbWe!)!Xp0Wet5*6_TL9z!@VAy_s`D$Ip;yYUuA00sPZ3N!?}Gy9@9{_@m_i3k0AXY zfBuytaZ1g^e6W9#NB;vUYS5v3ZMdf$_Mg^u)QVqJ-Q zoyvJ5e(~`*yYro^+85B47!M4f zb^{~Mf9c#1uV+2LtdV$3P1ZF+Y6V_LzG%FGxv_Pv1-2e!wR?j-eMDCLO^#sUZ`TM} zFTm2ZBi0bHSX1Z&JLlQ+oGIW;A!k$D{S+@b$An70H<9OKpAhSLjP(rm=03>A{>?S3 z58(awxWB(1&Yi71Ex1$p4+2!x>A@aKEmv(f{o`%lFlktDf~vkWQNb6wk3wyD0|%j~wW*>IQmGH>AVr7@2@Q!V9CUe30`Yz2Sfhd)xLOAAjZw&;c87Vy>(0+ zgMSb7bo$^mE#U8;MvkBFjgU;(Q}?-C*fTdZ`tR}q(0@lA05N|m=R0Bh>oDV^YR(99 zulsoz&g%@reLk40|8xFLz9;IH)4!u1_-EB^dxA&TZ3(Omy~6VbQ3ui*zRt=SqwyV0YE*km|l8VRHx61lq5RNo5SscEt_t z2flz0;G8h)g6t1w#ozg{sTpSC0A@ygFqd0tI_&1<3sQ4V81_eVZpiA{5C_X2ICx<2 z*YMvVDsK8h_xfHQ&ac*mD`X?~56-ZosbelqhlU zA1Eazhf7P;e~CSNI@;%h<1YLWJG(eQ4X|@S9S4F7^a1qyIOjL5KJvY4Q-gX|#(w@9 zp9JBo-e6T{Hs<^Oj(dGNApXOLub?k5Bl0)+ffV=GF8)Qma`yKU=sm{~d#p5X>ng88 z%b5@I9yaeGAIRzlGUK0-7m@>vDg1uaJZ*#z4uU~KD&gYE%^Qtw#fj{F?WBa_aZsfuqKi{eQ)c?NtJoeE`-q%9q zPlJP`;;cwL`@z^v`+LTFHue_&z@GTq93T%27YzQ4|FLiAKKuK%W(F@Q`vGeFzmLrYPLE)x*~>5qUv!Z=ONzy*efpOUMBq+>dB7 zaA#d;M&PfyHq4xmxSQpQ1+*V99I)mI84s{uOnv6rG0o63F8byb$vfnn)IhC(=O;48 zVr*#fz}5rcsc|>gEOu`=;JP~W--*AgF6hAD*uLR_#-9EjxN~ldwcWDl;d%5AlDx0u z{FDW}*BR;$&;q0RPV5c-obSty16CfW`@X5I@%Pu96&zUOUa+YRPeUxg z{y59A$sP71wz9_Nyveu7y_-rI+9cScRuvSZ@u z!r9on<2k3@KZrfG1=Rx_}e;Qd;oMmoxRc*=7s%IYer~&N2Rfie7 z+>PeDaJRKy--86U-VeMW_o~miF3$`Lg3Skr*TgWXx9GZ6!!-Gw{yj4LeBy2v^L}e( zaKOZX(EW7n^Kq7Aty!VIRi*~3+OtDFv0(pUs>Zm#zsCPRfIQPnC#Iw**oT7OMSU>Y zEdaHraY6T=>ALy$N!EkO0rrNN6RW!6E7bGQ zc>lU!=BWw#o~7Oe5Bi>wy!}sO&%;&Brd>ym1)2aJ&=*sqom?<{$cXb;6V9-6jXSc$N(%~@bAT?H^n0A}0X;sTK!fWDI7Sm=Ao0^8>hd%$XNDIbd=DlNa)8W(uhV zdS9^}7x4WuJSg{JpyU~JR-W}eAq5AW#yyI0_Ac(h!G1zFV8&dGTCL~%aMd+E*s;m$ zO^uKB1!6rAt3PbKC+w3xydivf`S(L4_t2~2(fx`%JtRO%e1LoZ;JzlDi{Kna2JV<+ z(6|$O3xD4KHWT*v*l2*}fz|;l_G9fAHNL+##d!iXXNNAR@o9jn_hqO@Mj3Wp~l7jkm|ZM!K3%iBW#Q{fLz;niVUNCt;yTkc4m??>n;)oM-K@*$w7BhM+a9&*loL5(6N{EztGeGhWy(~GpT@a5h z7v!n_SETTp=-JK2ec&;-#J#eE!3@H16~-s?+8~<-IWx97y!&zT+Jb6j$CI3_u~@wn4TdCCV@JkH^L zQgU=WC4cI8T0A?RksQ9~CFj6PQt;JlQf3nNEZ~f%7jce3d+>$(ESZy0w+G^95@+pK zfVsh*S3c+D0QbRazhG-YCi@2;nD-g%QRDeR)ja3cVp+6X^_ih=wLc42wW#@Z&pZr< zf9dn1cZ8_c>rzz9HFs5)Es37pcily=IHf}8^>LB3f5rnH!2#w5qYfnM{UhuXuwQKQ zf=A*1Fb)_VFi&8<$oW!g0^euqG3TV1H_$h;?%n|R=BkCWNUF_7{T$~~Rrm;dWv2vV zZ(@j4ofRfE=SB$5L6^qR0iNSR|IrzBDB3x7*}~k?cVev1FvrhxUKvlg@W*RhK7jZ; zbby*+IDp?Z9ltB)t8rfDoUlbJ5l=N+7U^DVR)`xu#(g=%RL`Zjm+!yvzY+e|Rns+z zs_E)Pg|qBEQ5R6X{gcb!OyW>-0QRr*0zMv&_hBZ^5g`Yd8*+v`s}5N9pD|02H73-1 zPVF4Q#RJR~7)>A-s0+kG-+vzViZhO^d1~;2+~8gsy@wY5n;)|;qvt~%SW{PM#|H0V zh#!U{p2_ZB$A$;m_G$kbe@p-A1G4i2u=_N|dd+ZtRP8z8Up8JJrRpyXcSoG>Rtx9$ z!w;yo-~9e>jC{tg_Rj7U)n?-z)n>yT_<}ngsEH}`LcO|dj6diD?etj_a~~_6zF+|6 zho@j>j2z&ZGCWhlSt}lA`+#hF$H1Mw!1x2b$H0yam@k@h=*R`?0)2$JF9kWFd%m{a zI`3W2#}>90cm7{{R{|YXd4=yRIU>@7+A1DBJ*9NPQ|*a}SPWnW2#{nlOBRe!NP=~% zRz*bwYEM!r2uDQ-AZghGW|_$b5f=~?Y^#SVr*6fKEE6*Oq*7ukl38BA``-LBZ(e9E z7J);VbN=(*_xAbk{l9zv?f!hNoVYznBymhUN8W-1pc6v=S`JX`0OTL%e%TjPI6q=X z%og_bMnB!YU{cM8G`6bEa2+^*6yk7Qzw8qlQg`xmIFb@M{d@O7<{0aUcyd81+7Z&z zjDxh`V4tzDHMq*geClvVA^!;LKrzPTKKk}idE5w(A?1E!_6x=Mkpu_8{ww95{Q_lQ z$Uj8~P)CrtAl_~ubV2BYtP9Wwp%d!$t5`pjxd)ho4DJ|^Ab+lZCiDRNvL3~`Kv};) z(E%ZUtsPMDe+---&O@8o_;s#O`tr7H)br?ul^w=0Pj%>Sc&eSofb*xV51)Pko`N@8 zUp_*JsYF&J_~!Z?XV8#a7h-)Vg${gSVZRVu03C=GU@p;p$a}U2JePod0=5V9G-HT_ zE(pI+9zVkTuSzU?bz=D|@u)D?C~g~=6GY;%C#(x1@q7cp6)bGu;gT}0Ar;8Syx^aA`K%^)A zh`Z|!65`xZ(F;9P{6a`)X$cu^+pxc*XEgl6gRBEKhdY9G1vyD`5MS~)kUR6hQp6c& zE>O1>$^K!YuOP|)Co2DH9gz7TZWEXX1Q!S$5H=#A194lS@IfjM2>J6??6V)hau-}6 zbbxIE%U&edfEfI}2;y|cCSy+E__eO8A3xeQXh`0>G-hR|;Yy6Lu58?mc3sr*uKntG zEhjCIGjPM~Iwuvs6r_^f0h+Vh&75GEy5|^~cl8dkH3uHIH~Nm-oH7?gP(O%rd>Q6~ z$I(W_daxK=u!y-p)rXwm|9Bg*Xd4iEpq9NlK4mgKX{vu9@&nYemwkb_{2}`Yd{5Mau@?33U7w(hAh1e{7{oKXz>w-B{U08P9T@ z#&bjVLfy(ymzniafJ`+#G_eML;r5{3jDDkh_=yFr!E{@b|BbkB$bMnO?({@UFqUjS z#u70PEQMX*HbbEY$~e-v51{b{61G6)2gv1L3ML79)j$HTmd?*5E5oy)FX_~GCY4}E}g?lNRlcIq-#L+)!1(@!3IQ)JmW zxwUWak^MsSn-KcOba{v~^fGi~@liCdwFK_5Hu$_&)Dd74Vpixuexo-!=amrr!(N#O zmSCOJg!XU1B@?t#ZQbwf*6Yxo4L1Iz~;hl<+}xxT=CNnAe=z5w?b$!!D7|0)~s@BBbD56E+ma?lQVjbMl>NwT^g~QXpMNgoZd%{<`nbxDEP4aB9CAmWp*~}^%P?$lCk?v4o2FH} zXzs(2)0yRG-McQoHb7-BdMUd(K)G0Vpm4iiXK4%=a_arihXCa@`iEK@{WGmj-%d-t z=Sy=f`jgOxoZsMy&1m(9?|k*|k%#|sG{)^lye=Il-WT>k?pI{n5a)yh4x7OpCys^6yW&6fa3tYOZ?ro>3TfE@< z!-n+74j>-W$C#hzqOr^Vbk@n@On%Tq$=d9L9{3HpjUin|m5WBL z>Ll2NADSJ$$1D!-L7T%HwV}S4k9tE9+74#D5QrfrSs1(!#u{ajg@64#y6Bz07_XVZ zW6-${$#z2YGbZW}@pglRUL^QISaXnat+1{@ttx z`HhDur!hpP=RJhJW1ST-Xe`d4zRb}@Gn+hgOG_XP$I_ARvp4u!to2^61$+w|Ar+u4 zZ92xELm#Bu_w?|3#jytH9QiXYhi;=HPJX z!Oi9^-Gx?1pv2w~xZ7Umebid(USg~Btbx8%<<)wtva7pSSnE9Hg$@3>c4zPo^H$Hy z?9E+y<{Izyrq%7=8J7Q!KJAA6^wWi(((GMF=(c^mbnlyeI!i;qn2WNG`}N~jBEIV2 z<231skI3Ye=!z>wor=GH{u!Dam{$W1{Vt|DZ2FAMJ0n!&^pd^at+T;rv^oPu8)P!2 zCa8bpt>ZLr|1r8`{(m3$R`V))(i5dt@X$`AlHI)q8|;u3ItL$5pYvQdW!H94!9Iz` zJ<~?g9+g`?!;4Z$3b_nh&UKn@8q|{05&MzL_ESIk&;y z7&I2O^cV{pp(9SOv7iaKIbk0@j{A+p+d{Y|WVF`%4CWfXC!n{~cI$FBxpY%D zd_=cCk2tCuC5&x3LNi(=nzZMD(QN zW7tWH9G}tjx{oLy$yRrOW>t4k=2o=1&;JKyfL}_se@-*25i_&oE~14Ml(u5^*Zh$4 zx1G;{vo{CO9{F{CvCNdaz9br|K2VV!rSjx~F9%mp(FIBTZZ(lUD}iHaoIk_&iUY?7 zF*c?P>^3o`Ck0+5LmDA)mVz(LR1QQ|Dp+5t;C=;?Zlr?yD&hys3f>=w^dkk=2z+U| zd>~ukv`oga_~Y>xHZzV67w2_0#{DLNxuI8*`Vx=|86$><_+7*JeSwHBWBlhN9?aO0 z#A)Tg_9WKt#phJ3a3=8ZB-WMU^Gr!h+wgf6D*TKjr3@)5mLv}ElK3IVQl^ssK$23X z7=Bxl+=+{^b4kkQ6J01tiQMJu21(K~_4P(cO5`RVHj%GGiw7 z^;%4uQ_llesIRBZkyKo&!8R?9rrb`plzXEIX54@A|8z3*O{4`iA?*aNTKz~f?_dy@aGKS+naqy8X$DPzeb9%$Sv z;{@MdST5uD#rex+C?x_X$L&S0c!2!6qyf;&ufq$ol?M#ktK2VbRq%-NIQCb_2d2cK z)Gud|ewe_K-|_j2O9hs;n1HLx6S!{#(WUaAR}RIH(WcDAX<6vJa!2x4LFGxH-uo9= C)E7kn literal 0 HcmV?d00001 diff --git a/resource/qss.qrc b/resource/qss.qrc new file mode 100644 index 0000000..940aeb7 --- /dev/null +++ b/resource/qss.qrc @@ -0,0 +1,28 @@ + + + qss/flatgray.css + qss/flatgray/add_bottom.png + qss/flatgray/add_left.png + qss/flatgray/add_right.png + qss/flatgray/add_top.png + qss/flatgray/arrow_bottom.png + qss/flatgray/arrow_left.png + qss/flatgray/arrow_right.png + qss/flatgray/arrow_top.png + qss/flatgray/branch_close.png + qss/flatgray/branch_open.png + qss/flatgray/calendar_nextmonth.png + qss/flatgray/calendar_prevmonth.png + qss/flatgray/checkbox_checked.png + qss/flatgray/checkbox_checked_disable.png + qss/flatgray/checkbox_parcial.png + qss/flatgray/checkbox_parcial_disable.png + qss/flatgray/checkbox_unchecked.png + qss/flatgray/checkbox_unchecked_disable.png + qss/flatgray/menu_checked.png + qss/flatgray/radiobutton_checked.png + qss/flatgray/radiobutton_checked_disable.png + qss/flatgray/radiobutton_unchecked.png + qss/flatgray/radiobutton_unchecked_disable.png + + diff --git a/resource/qss/flatgray.css b/resource/qss/flatgray.css new file mode 100644 index 0000000..fe82b5b --- /dev/null +++ b/resource/qss/flatgray.css @@ -0,0 +1,694 @@ +QPalette{background:#FFFFFF;}*{outline:0px;color:#57595B;} + +QGraphicsView{ +border:1px solid #B6B6B6; +qproperty-backgroundBrush:#FFFFFF; +} + +QWidget[form="true"],QLabel[frameShape="1"]{ +border:1px solid #B6B6B6; +border-radius:0px; +} + +QWidget[form="bottom"]{ +background:#E4E4E4; +} + +QWidget[form="bottom"] .QFrame{ +border:1px solid #57595B; +} + +QWidget[form="bottom"] QLabel,QWidget[form="title"] QLabel{ +border-radius:0px; +color:#57595B; +background:none; +border-style:none; +} + +QWidget[form="title"],QWidget[nav="left"],QWidget[nav="top"] QAbstractButton{ +border-style:none; +border-radius:0px; +padding:5px; +color:#57595B; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +QWidget[nav="top"] QAbstractButton:hover,QWidget[nav="top"] QAbstractButton:pressed,QWidget[nav="top"] QAbstractButton:checked{ +border-style:solid; +border-width:0px 0px 2px 0px; +padding:4px 4px 2px 4px; +border-color:#575959; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); +} + +QWidget[nav="left"] QAbstractButton{ +border-radius:0px; +color:#57595B; +background:none; +border-style:none; +} + +QWidget[nav="left"] QAbstractButton:hover{ +color:#FFFFFF; +background-color:#575959; +} + +QWidget[nav="left"] QAbstractButton:checked,QWidget[nav="left"] QAbstractButton:pressed{ +color:#57595B; +border-style:solid; +border-width:0px 0px 0px 2px; +padding:4px 4px 4px 2px; +border-color:#575959; +background-color:#FFFFFF; +} + +QWidget[video="true"] QLabel{ +color:#57595B; +border:1px solid #B6B6B6; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +QWidget[video="true"] QLabel:focus{ +border:1px solid #575959; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); +} + +QLineEdit:read-only{ +background-color:#E4E4E4; +} + +QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ +border:1px solid #B6B6B6; +border-radius:3px; +padding:2px; +background:none; +selection-background-color:#575959; +selection-color:#FFFFFF; +} + +QLineEdit:focus,QTextEdit:focus,QPlainTextEdit:focus,QSpinBox:focus,QDoubleSpinBox:focus,QComboBox:focus,QDateEdit:focus,QTimeEdit:focus,QDateTimeEdit:focus,QLineEdit:hover,QTextEdit:hover,QPlainTextEdit:hover,QSpinBox:hover,QDoubleSpinBox:hover,QComboBox:hover,QDateEdit:hover,QTimeEdit:hover,QDateTimeEdit:hover{ +border:1px solid #B6B6B6; +} + +QLineEdit[echoMode="2"]{ +lineedit-password-character:9679; +} + +.QFrame{ +border:1px solid #B6B6B6; +border-radius:3px; +} + +.QGroupBox{ +border:1px solid #B6B6B6; +border-radius:5px; +margin-top:9px; +} + +.QGroupBox::title{ +subcontrol-origin:margin; +position:relative; +left:10px; +} + +.QPushButton,.QToolButton{ +border-style:none; +border:1px solid #B6B6B6; +color:#57595B; +padding:5px; +min-height:15px; +border-radius:5px; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +.QPushButton:hover,.QToolButton:hover{ +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); +} + +.QPushButton:pressed,.QToolButton:pressed{ +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +.QToolButton::menu-indicator{ +image:None; +} + +QToolButton#btnMenu,QPushButton#btnMenu_Min,QPushButton#btnMenu_Max,QPushButton#btnMenu_Close{ +border-radius:3px; +color:#57595B; +padding:3px; +margin:0px; +background:none; +border-style:none; +} + +QToolButton#btnMenu:hover,QPushButton#btnMenu_Min:hover,QPushButton#btnMenu_Max:hover{ +color:#FFFFFF; +margin:1px 1px 2px 1px; +background-color:rgba(51,127,209,230); +} + +QPushButton#btnMenu_Close:hover{ +color:#FFFFFF; +margin:1px 1px 2px 1px; +background-color:rgba(238,0,0,128); +} + +QRadioButton::indicator{ +width:15px; +height:15px; +} + +QRadioButton::indicator::unchecked{ +image:url(:/qss/flatgray/radiobutton_unchecked.png); +} + +QRadioButton::indicator::unchecked:disabled{ +image:url(:/qss/flatgray/radiobutton_unchecked_disable.png); +} + +QRadioButton::indicator::checked{ +image:url(:/qss/flatgray/radiobutton_checked.png); +} + +QRadioButton::indicator::checked:disabled{ +image:url(:/qss/flatgray/radiobutton_checked_disable.png); +} + +QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ +padding:0px 0px 0px 0px; +} + +QCheckBox::indicator,QGroupBox::indicator,QTreeView::indicator,QListView::indicator,QTableView::indicator{ +width:13px; +height:13px; +} + +QCheckBox::indicator:unchecked,QGroupBox::indicator:unchecked,QTreeView::indicator:unchecked,QListView::indicator:unchecked,QTableView::indicator:unchecked{ +image:url(:/qss/flatgray/checkbox_unchecked.png); +} + +QCheckBox::indicator:unchecked:disabled,QGroupBox::indicator:unchecked:disabled,QTreeView::indicator:unchecked:disabled,QListView::indicator:unchecked:disabled,QTableView::indicator:unchecked:disabled{ +image:url(:/qss/flatgray/checkbox_unchecked_disable.png); +} + +QCheckBox::indicator:checked,QGroupBox::indicator:checked,QTreeView::indicator:checked,QListView::indicator:checked,QTableView::indicator:checked{ +image:url(:/qss/flatgray/checkbox_checked.png); +} + +QCheckBox::indicator:checked:disabled,QGroupBox::indicator:checked:disabled,QTreeView::indicator:checked:disabled,QListView::indicator:checked:disabled,QTableView::indicator:checked:disabled{ +image:url(:/qss/flatgray/checkbox_checked_disable.png); +} + +QCheckBox::indicator:indeterminate,QGroupBox::indicator:indeterminate,QTreeView::indicator:indeterminate,QListView::indicator:indeterminate,QTableView::indicator:indeterminate{ +image:url(:/qss/flatgray/checkbox_parcial.png); +} + +QCheckBox::indicator:indeterminate:disabled,QGroupBox::indicator:indeterminate:disabled,QTreeView::indicator:indeterminate:disabled,QListView::indicator:indeterminate:disabled,QTableView::indicator:indeterminate:disabled{ +image:url(:/qss/flatgray/checkbox_parcial_disable.png); +} + +QTimeEdit::up-button,QDateEdit::up-button,QDateTimeEdit::up-button,QDoubleSpinBox::up-button,QSpinBox::up-button{ +image:url(:/qss/flatgray/add_top.png); +width:10px; +height:10px; +padding:2px 5px 0px 0px; +} + +QTimeEdit::down-button,QDateEdit::down-button,QDateTimeEdit::down-button,QDoubleSpinBox::down-button,QSpinBox::down-button{ +image:url(:/qss/flatgray/add_bottom.png); +width:10px; +height:10px; +padding:0px 5px 2px 0px; +} + +QTimeEdit::up-button:pressed,QDateEdit::up-button:pressed,QDateTimeEdit::up-button:pressed,QDoubleSpinBox::up-button:pressed,QSpinBox::up-button:pressed{ +top:-2px; +} + +QTimeEdit::down-button:pressed,QDateEdit::down-button:pressed,QDateTimeEdit::down-button:pressed,QDoubleSpinBox::down-button:pressed,QSpinBox::down-button:pressed,QSpinBox::down-button:pressed{ +bottom:-2px; +} + +QComboBox::down-arrow,QDateEdit[calendarPopup="true"]::down-arrow,QTimeEdit[calendarPopup="true"]::down-arrow,QDateTimeEdit[calendarPopup="true"]::down-arrow{ +image:url(:/qss/flatgray/add_bottom.png); +width:10px; +height:10px; +right:2px; +} + +QComboBox::drop-down,QDateEdit::drop-down,QTimeEdit::drop-down,QDateTimeEdit::drop-down{ +subcontrol-origin:padding; +subcontrol-position:top right; +width:15px; +border-left-width:0px; +border-left-style:solid; +border-top-right-radius:3px; +border-bottom-right-radius:3px; +border-left-color:#B6B6B6; +} + +QComboBox::drop-down:on{ +top:1px; +} + +QMenuBar::item{ +color:#57595B; +background-color:#E4E4E4; +margin:0px; +padding:3px 10px; +} + +QMenu,QMenuBar,QMenu:disabled,QMenuBar:disabled{ +color:#57595B; +background-color:#E4E4E4; +border:1px solid #B6B6B6; +margin:0px; +} + +QMenu::item{ +padding:3px 20px; +} + +QMenu::indicator{ +width:20px; +height:13px; +} + +QMenu::indicator::checked{ +image:url(:/qss/flatgray/menu_checked.png); +} + +QMenu::right-arrow{ +image:url(:/qss/flatgray/arrow_right.png); +width:13px; +height:13px; +padding:0px 3px 0px 0px; +} + +QMenu::item:selected,QMenuBar::item:selected{ +color:#57595B; +border:0px solid #B6B6B6; +background:#F6F6F6; +} + +QMenu::separator{ +height:1px; +background:#B6B6B6; +} + +QProgressBar{ +min-height:10px; +background:#E4E4E4; +border-radius:5px; +text-align:center; +border:1px solid #E4E4E4; +} + +QProgressBar:chunk{ +border-radius:5px; +background-color:#B6B6B6; +} + +QSlider::groove:horizontal{ +height:8px; +border-radius:4px; +background:#E4E4E4; +} + +QSlider::add-page:horizontal{ +height:8px; +border-radius:4px; +background:#E4E4E4; +} + +QSlider::sub-page:horizontal{ +height:8px; +border-radius:4px; +background:#B6B6B6; +} + +QSlider::handle:horizontal{ +width:13px; +margin-top:-3px; +margin-bottom:-3px; +border-radius:6px; +background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6); +} + +QSlider::groove:vertical{ +width:8px; +border-radius:4px; +background:#E4E4E4; +} + +QSlider::add-page:vertical{ +width:8px; +border-radius:4px; +background:#B6B6B6; +} + +QSlider::sub-page:vertical{ +width:8px; +border-radius:4px; +background:#E4E4E4; +} + +QSlider::handle:vertical{ +height:14px; +margin-left:-3px; +margin-right:-3px; +border-radius:6px; +background:qradialgradient(spread:pad,cx:0.5,cy:0.5,radius:0.5,fx:0.5,fy:0.5,stop:0.6 #FFFFFF,stop:0.8 #B6B6B6); +} + +QScrollBar:horizontal{ +background:#E4E4E4; +padding:0px; +border-radius:6px; +max-height:12px; +} + +QScrollBar::handle:horizontal{ +background:#B6B6B6; +min-width:50px; +border-radius:6px; +} + +QScrollBar::handle:horizontal:hover{ +background:#575959; +} + +QScrollBar::handle:horizontal:pressed{ +background:#575959; +} + +QScrollBar::add-page:horizontal{ +background:none; +} + +QScrollBar::sub-page:horizontal{ +background:none; +} + +QScrollBar::add-line:horizontal{ +background:none; +} + +QScrollBar::sub-line:horizontal{ +background:none; +} + +QScrollBar:vertical{ +background:#E4E4E4; +padding:0px; +border-radius:6px; +max-width:12px; +} + +QScrollBar::handle:vertical{ +background:#B6B6B6; +min-height:50px; +border-radius:6px; +} + +QScrollBar::handle:vertical:hover{ +background:#575959; +} + +QScrollBar::handle:vertical:pressed{ +background:#575959; +} + +QScrollBar::add-page:vertical{ +background:none; +} + +QScrollBar::sub-page:vertical{ +background:none; +} + +QScrollBar::add-line:vertical{ +background:none; +} + +QScrollBar::sub-line:vertical{ +background:none; +} + +QScrollArea{ +border:0px; +} + +QTreeView,QListView,QTableView,QTabWidget::pane{ +border:1px solid #B6B6B6; +selection-background-color:#F6F6F6; +selection-color:#57595B; +alternate-background-color:#F6F6F6; +gridline-color:#B6B6B6; +} + +QTreeView::branch:closed:has-children{ +margin:4px; +border-image:url(:/qss/flatgray/branch_open.png); +} + +QTreeView::branch:open:has-children{ +margin:4px; +border-image:url(:/qss/flatgray/branch_close.png); +} + +QTreeView,QListView,QTableView,QSplitter::handle,QTreeView::branch{ +background:#FFFFFF; +} + +QTableView::item:selected,QListView::item:selected,QTreeView::item:selected{ +color:#57595B; +background:#E4E4E4; +} + +QTableView::item:hover,QListView::item:hover,QTreeView::item:hover,QHeaderView,QHeaderView::section,QTableCornerButton:section{ +color:#57595B; +background:#F6F6F6; +} + +QTableView::item,QListView::item,QTreeView::item{ +padding:1px; +margin:0px; +border:0px; +} + +QHeaderView::section,QTableCornerButton:section{ +padding:3px; +margin:0px; +border:1px solid #B6B6B6; +border-left-width:0px; +border-right-width:1px; +border-top-width:0px; +border-bottom-width:1px; +} + +QTabBar::tab{ +border:1px solid #B6B6B6; +color:#57595B; +margin:0px; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); +} + +QTabBar::tab:selected{ +border-style:solid; +border-color:#575959; +background:#FFFFFF; +} + +QTabBar::tab:top,QTabBar::tab:bottom{ +padding:3px 8px 3px 8px; +} + +QTabBar::tab:left,QTabBar::tab:right{ +padding:8px 3px 8px 3px; +} + +QTabBar::tab:top:selected{ +border-width:2px 0px 0px 0px; +} + +QTabBar::tab:right:selected{ +border-width:0px 0px 0px 2px; +} + +QTabBar::tab:bottom:selected{ +border-width:0px 0px 2px 0px; +} + +QTabBar::tab:left:selected{ +border-width:0px 2px 0px 0px; +} + +QTabBar::tab:first:top:selected,QTabBar::tab:first:bottom:selected{ +border-left-width:1px; +border-left-color:#B6B6B6; +} + +QTabBar::tab:first:left:selected,QTabBar::tab:first:right:selected{ +border-top-width:1px; +border-top-color:#B6B6B6; +} + +QTabBar::tab:last:top:selected,QTabBar::tab:last:bottom:selected{ +border-right-width:1px; +border-right-color:#B6B6B6; +} + +QTabBar::tab:last:left:selected,QTabBar::tab:last:right:selected{ +border-bottom-width:1px; +border-bottom-color:#B6B6B6; +} + +QStatusBar::item{ +border:0px solid #E4E4E4; +border-radius:3px; +} + +QToolBox::tab,QWidget[form="panel"]{ +padding:3px; +border-radius:5px; +color:#57595B; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +QWidget[flag="paneltitle"]{ +border-bottom-left-radius:0px; +border-bottom-right-radius:0px; +} + +QWidget[flag="panelcontrol"]{ +border-top-width:0px; +border-top-left-radius:0px; +border-top-right-radius:0px; +} + +QToolTip{ +border:0px solid #57595B; +padding:1px; +color:#57595B; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +QToolBox::tab:selected{ +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #F6F6F6,stop:1 #F6F6F6); +} + +QPrintPreviewDialog QToolButton{ +border:0px solid #57595B; +border-radius:0px; +margin:0px; +padding:3px; +background:none; +} + +QColorDialog QPushButton,QFileDialog QPushButton{ +min-width:80px; +} + +QToolButton#qt_calendar_prevmonth{ +icon-size:0px; +min-width:20px; +image:url(:/qss/flatgray/calendar_prevmonth.png); +} + +QToolButton#qt_calendar_nextmonth{ +icon-size:0px; +min-width:20px; +image:url(:/qss/flatgray/calendar_nextmonth.png); +} + +QToolButton#qt_calendar_prevmonth,QToolButton#qt_calendar_nextmonth,QToolButton#qt_calendar_monthbutton,QToolButton#qt_calendar_yearbutton{ +border:0px solid #57595B; +border-radius:3px; +margin:3px 3px 3px 3px; +padding:3px; +background:none; +} + +QToolButton#qt_calendar_prevmonth:hover,QToolButton#qt_calendar_nextmonth:hover,QToolButton#qt_calendar_monthbutton:hover,QToolButton#qt_calendar_yearbutton:hover,QToolButton#qt_calendar_prevmonth:pressed,QToolButton#qt_calendar_nextmonth:pressed,QToolButton#qt_calendar_monthbutton:pressed,QToolButton#qt_calendar_yearbutton:pressed{ +border:1px solid #B6B6B6; +} + +QCalendarWidget QSpinBox#qt_calendar_yearedit{ +margin:2px; +} + +QCalendarWidget QToolButton::menu-indicator{ +image:None; +} + +QCalendarWidget QTableView{ +border-width:0px; +} + +QCalendarWidget QWidget#qt_calendar_navigationbar{ +border:1px solid #B6B6B6; +border-width:1px 1px 0px 1px; +background:qlineargradient(spread:pad,x1:0,y1:0,x2:0,y2:1,stop:0 #E4E4E4,stop:1 #E4E4E4); +} + +QTableView[model="true"]::item{ +padding:0px; +margin:0px; +} + +QTableView QLineEdit,QTableView QComboBox,QTableView QSpinBox,QTableView QDoubleSpinBox,QTableView QDateEdit,QTableView QTimeEdit,QTableView QDateTimeEdit{ +border-width:0px; +border-radius:0px; +} + +QTableView QLineEdit:focus,QTableView QComboBox:focus,QTableView QSpinBox:focus,QTableView QDoubleSpinBox:focus,QTableView QDateEdit:focus,QTableView QTimeEdit:focus,QTableView QDateTimeEdit:focus{ +border-width:0px; +border-radius:0px; +} + +QLineEdit,QTextEdit,QPlainTextEdit,QSpinBox,QDoubleSpinBox,QComboBox,QDateEdit,QTimeEdit,QDateTimeEdit{ +background:#FFFFFF; +} + +QTabWidget::pane:top{top:-1px;} +QTabWidget::pane:bottom{bottom:-1px;} +QTabWidget::pane:left{right:-1px;} +QTabWidget::pane:right{left:-1px;} + +QDialog,QDial,#QUIWidgetMain{ +background-color:#FFFFFF; +color:#57595B; +} + +QDialogButtonBox>QPushButton{ +min-width:50px; +} + +QListView[noborder="true"],QTreeView[noborder="true"],QTabWidget[noborder="true"]::pane{ +border-width:0px; +} + +QToolBar>*,QStatusBar>*{ +margin:2px; +} + +*:disabled,QMenu::item:disabled,QTabBar:tab:disabled,QHeaderView::section:disabled{ +background:#FFFFFF; +border-color:#E4E4E4; +color:#B6B6B6; +} + +QLabel:disabled{ +background:none; +} + +/*TextColor:#57595B*/ +/*PanelColor:#FFFFFF*/ +/*BorderColor:#B6B6B6*/ +/*NormalColorStart:#E4E4E4*/ +/*NormalColorEnd:#E4E4E4*/ +/*DarkColorStart:#F6F6F6*/ +/*DarkColorEnd:#F6F6F6*/ +/*HighColor:#575959*/ \ No newline at end of file diff --git a/resource/qss/flatgray/add_bottom.png b/resource/qss/flatgray/add_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..868e68710ff1bf5a9d02044f9404f9a913849805 GIT binary patch literal 336 zcmV-W0k8gvP)ly0>K-Zgo6Yi{EJM+K_U>G$SgR>0E9otY&ggS1Q>>)Zkpz~s;Z+g i<}(LQ(=<)f?w||cl8Oc-KhUxO0000&qqgOM%||O^opYIQ+gAV?0_ih734|OGyH>!hry2i34Mx zH4Y4c3LF>#RX8vJDsiw9sK&txU=AE40(0RY0oeEbag6bn3^+&tj^p?Nyb3I{Ee1AC z^BzLD23`c7A0<75&beI(;fxH(Y^xEhAxNj+z&RQgMO0!j<7!$!&7 Q{Qv*}07*qoM6N<$g3{cXX#fBK literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/add_right.png b/resource/qss/flatgray/add_right.png new file mode 100644 index 0000000000000000000000000000000000000000..be8dd1acec168ba524cbd56ec0c067db8155781a GIT binary patch literal 358 zcmV-s0h#`ZP)LS*?IqfGGF)LJ@}O~P%4%Fc9ya{j^jmDRZnJi8e`n_ zegCS0eOIL{*L8hwX4k-_nY~QYbR}~Almv`1o*j(m%d$MHkx9T`k|T40AVzusL5TDM zf(Ype1Od_;2+l|a5FC*zAUGkFKyW~+fslz@0|;Np+Zbcg;{?_N0=%19Og&l)xD6rP z9doz_a2rCn8HVBGm}50C3kOe<~2JAv^~rZn!y&ccS1mq$8WHt_`_<^yMdbf(Pl=ToXRk51b6o zKN_lk{r}VLazbMPtGcQq8*_On_|?Zm39qz$ zH=cEBcf81vUdJ?R+0V3Rj0bF|XI-4yVRgIU^F)c{K8C)Np54zX|7G3&rv984=x*KQ Z`V-SCY9*@s7Xd?p!PC{xWt~$(695EChtU84 literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/arrow_bottom.png b/resource/qss/flatgray/arrow_bottom.png new file mode 100644 index 0000000000000000000000000000000000000000..37307b75041d36df89dff1f3ef9f49e8d0722815 GIT binary patch literal 337 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9EWz97u_dHRv%3=E9S zo-U3d5v^~pS$nY>O0+#R7v$^c?H0JQnYVhQq+T}mD5xl@NtjTauz96Jh2xuj#fLZNZ`*fx-+S|( z6OqL-4T;>=b6e8%3nbdi4gBAR?4Na@BA%bg!C~3@@4*v(yRDe7CK$PM_UY~mty?b` z^gTXaRQ~*6#BScSn_T7P)q9y#BsqL12JWzLyuc#a+s)(@nj)kp#SuE;K7^ zdg8zt=#2wMpaKU@Kot%gfJz*U1gdc`0+<5_iNIVqNC4J#y=`!j0|yDfw%uwCo)wtB z3J54VE`UNd6`0cP4uUidaysw731mL?#Cw^a94M7ae>+QAuIsz2D(q$$=e;p~ zY1=myY+seM9J)u~hRE1@7WHIuEpi>+OkBU%PX?D3?NyCT1paOaWEK$g$ZQ~JkuE^c zA>DwWLAnCLAL$MRU!(*Gen=S*e2`KgggUz;PnTAfN+|i$;0_J#btU(hC@Xiu literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/branch_close.png b/resource/qss/flatgray/branch_close.png new file mode 100644 index 0000000000000000000000000000000000000000..f5b6d34625ad9470f379c703553e6021608d3cbd GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9EWz97u_dHRv%K*61! zE{-7)t#8j6avd^|IQsGX(X7J4()Z64;ugjoIJ>f${R3Cty~QF67t9vTU(o(RqN{uD ztR2U$_;QNg%RlX{P~Xte(eYw_r?t=Ilocjs3~X}NTNuBE-k6uW`Atr|#&3b+g$w3I zRP`}4s2)(!dH?xVYwZiyKyior(P6zkcU)_$uBq_+2|LX6^=Rxprt-|H)&~#tPpGL* z5qT%Iqq#1GJG0c!%ixra6J<(U>M#4r$GOU mb{oU7@B5FE9Ttn_uk#6&*j+33@dYjb0000%pStyx1_=4QR zO-qq3qJM(ZT?B^?&Mq!#AqI31^j~lgM3U=bK*Yhpf-Xg>z4tg&6S=KvJolXVg1_;c z=RWz6aL&UG{LeogvC+i${iC^DZhsud>#bJnvyGnLlL_eieqMR`72pPt1J+4cYBpD& zSkZlJYL3el?jT(Mq0a$?xpKAAj@s>4Hh{|Hp<1oxDKEc*=xe+57#A&#`FedWJI@e! zd}89PKz+dT){sU(1!Efa?NMe0X$DM^*TB0WN;4yk4oRoe*(lFcpNr-)P~4-uD2VaF zF+RmnyZyFQnSLtBMI0iHfdDT%oj0zLnSkIPnHdOfkye1<4!L6%hheC?-R=pkwGlp! znbpEwU~Gu;4_fr%{k65nLjmW5#Y=*|2OKfM8D=1|_bmlMa15jYoE#Z#I|G02-Xuws zngS+9O6zBP;w&tjPj48FI!76oL|;uW{z*VU9p16>I~YI77Yz^7i+$kM)_V;bBPI2d zXsvITPER${8@*E;MbXFdbmgFk%wp+f>KAagN@q@mVHhUq%``-UASlJCbEcoBL?*S? zHyQ4t9*Elwm?^%$G^xDg5$LK799RUpz=5?FcQ-h&0Cb6i%>OL7#X$z(C~#mnNiHle zA5vc2aE${)U~zM^49pBsnh6JPXK`j67y>^P3K8&PK#3I`xH%-&aA5zMv5EsL7qwp$ pFMma#Ou~a^bM+aZE!h5!d<9TqM+;NWIKBV?002ovPDHLkV1gUqAq@Zk literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/calendar_prevmonth.png b/resource/qss/flatgray/calendar_prevmonth.png new file mode 100644 index 0000000000000000000000000000000000000000..8a17d0f4e4fe94e90b2f2078c92ded3f8604dc07 GIT binary patch literal 740 zcmVnY+iKO~GDF?%rH- zMf`4e9MALN-WT21s&*Taqt)^d zH^A+Nfp4r*TIBPegVt~94`$3U6QJjfZ~#C+Tw(5!=@FBXVH-!pPrx|P{A>DB(PCwp zK2pG7IIOyGZq82S*JOGHo`b=0?yx8pKS(2Lu2{fdhO3qrw3`fKlRrCcuEW z4F+A;6fQ|Yh@50HxsuIhU0=X}xUk|Gfx0V6L^uHGQJ_8?86WSiz3>1{^V&rMKZ*di z6Yh;~Imz0S2k4S}1Hhqh7*^-XUhvI?8Dh50IJX0JwF&jU-;6W9I-oKvYP= znnv`s_UPI2TbAXd`upZdQ4;`$(1wFmgE$OK7GD(J)n5FyxnUT(64UPjn1H{H?FxW< z46MrPRJfmt#i9j(n*t?~C0kR6Ur$eemqqSP!&9Baa351$zHsX7nK$L7rOmbj_XE<` zxj0Is?}K|($Urv>Qb>o>CkF>!l@}Mc+76UL5{}0lcd+-w(9H7k!mptHqz>*v2-~gN zcfj-*{#KlGU0s_gnO@R23qlCnbt<<4Af?jM)kgs2$Bm7ICDWtj>$mMWd3ug0b`*pG z?46U0_m#MI`R&W6KO+G)L6&S?y)>K4{gAC4_D+Qi(>$+0$uO|MReMGV5z3OcZ}S^B WE<_{l;^y+Z$@MBe0IcD17sXpNC<1319%8Si6bQ6W2HZfqN4*J!@?nK&-Xx7;}QM;u<#{ z9gbSJ9Z@wmE1C;(7*U`wi89s82IRKJ$(uCs3{!eoZt)&fNpqt z2-<53mkCV6l|)vG%LICuH)*&6WaYR_U~J;mq6cS?wcs*=GU0VH6BHpQ$U$8=0P(k; zWt0ycdZK$l5cKEy!76c(TL)z=Ryitjl6B#ruuw0DtYTs;@g`XeS=BgO@?AV1vdUR| zj%J}8vg$>^oZU=5vW_jq0_|2hvaS`%g8kq$()YJF>e}z*6zXzOxDdEo6=*g~chq&_ z;AXS1e6Bn2w*mR&T9FkI3C*bGy#*b*#>_^ce?IvB-t>--hkp#a3YV@?|0DYKYW>b` z6h&i6K6|*o-@5(t*9(OQ&KBybBQg5!d@u-;ndmQ$Rxdid=;6bnzWS&r<)JBY$s z_y;WQL{J+G8wCrS11+!A#$*VVLJ$tD#4?3od54E=l#M!Lp#c-KJDX%TYjJNoJK2Jjp>R3+}Oc12_}48yl#j9CL-0Y|D< zP1Lv6t}EaX{~r$Elv3(}0?t+~$tX}jt!hO^$5hn&_SSE0UyBkH(5I}3)12)J&E(`b%Zj=wX3}7m5l-9Z@A`hpHl@0Xb ze3c2zjWfoqIp^+78_NUyAR;$`*MKbpN5*Ew2ka$Daxo0UtvHT1fUO+J!f-xdFG-Rs ztyXIvAc~@49LKjj$U<>hz%kq?53+Dv8t@x%e-h_GmV!$It~%!qXHA+w{wNEV2CRw5 z({8tWJ}0tS77jp+F)n57lTzwhqtV!z!*{t9rs2uNB7Hwz2)T>)RJR%G;10qv?4X*uV%m9_RB(61UZ zrQUEjd?EmyPUnn>yaY~Hj2iW(Uawybf?!VpG@H%$wOZ|*h&%(53Ss-e?KqAbK@jW# Z`~s1oc)XT$aOVI3002ovPDHLkV1iM65RCu; literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/checkbox_parcial.png b/resource/qss/flatgray/checkbox_parcial.png new file mode 100644 index 0000000000000000000000000000000000000000..97376f3abaa68fd367b45cd948f157e36026d4c3 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9EWz97u_dHRv%3=E9y zo-U3d5v^};Ec9|N6gmF!z2)pXD-Ovy9Bz7VC?M~^UT>fyHSH+7LBtzvqp-ZY%o6XS zC3Kn!B4=1!C@Q?39O#%R;rB!Oocg~@=Kov#4?ld_xH6Xe%ZU>Y7CKjlO?xWjz~~nw zwZQDOEW5&n?|D}KnqL`JmR`xfDCuTie#d`XR@d6cdD&H*|8@Bs%DR@>h9}sUpGq^k zaEG(L(_uZwmo5Ut@^IFjXJa>Zc1*b?GFN*;=(`#BdESYCecXF)`Q@v#jf^6m_5D&S z`+n)RY-s4#Y3tu+*ywGWKTn^BZ?o*HwB8lIVx_TRpbDwB5ra`q&A zXPzjkekW4IrKC-G-NQfg9;nx3%u0(6eSSbovh&1Wi@x^K*m4FYfn*ydy~d?;cIwY_ zN!h<@m361#JBI5Air!r{kM+t7Z#4cF&zImE>MwfZjO|PT5se~!wKW;<4#wMLJayFO zkE*x{E${H}pHohMj7%rgAOBlEp>zqnHb!yaZuU-K{TUmsBe P1_XnrtDnm{r-UW|6gYnM^t-5em${H(@=}?G11zU@z6>@#`@Q$9 zo6h?UFQ%_I{``)om7?}SC9&TtqJ7tfI|{7Y`EkvzZ(6>V z4NeOu%(kg1iFEP4>y!BV^|x!R_rEhUUkEFFG`VM1cH{}hAN%C5?EO^RGBr=FAf+g4 z6~ozWpL_LP`SQPFqL~#M&dcpouDo2Ko5*mne|P2e)sr?q zpFWK*MR)syn0d}Bx8?*q^;KD+RwEdywew@x2LRk|(HCE}K=E?elsafWeo?ao(+cYQBmeQ|fuyC>i8$mFwzHa5=<^^lvPo?@r# zC^>K4^}Svtmqq73dcJzKec2~V_KW?QoPW7PHLtIFdi!I}3nj6b@@p%0t}Ad#$X^w9 q$Unr~<+i`g`QKAs?bca*BLC8&#k<;6*_ z(+PntIB_fb$FEjGNN$|GxO~_US1ya zj=2?n*3b1ACw4!sxNzp&5^fdUDW{)K-+LhSUgo)-?FZUZp3Etp&KR>YWEF$^rS`qw z*RQ_%DowwU;c3y&)id|{zdxJ9QI^;7KWb~#-6>nPRqso0s9(+SY%Ok-d!FH}PujKZ z)$jIv-{0{5HuLkoZz0pJye#4KvYIFPm#rW`YM~A= ziqg{jJ&znn^z4sb8+LwQip+8GM)m{yQocl9Qc_tC6kC1Y(p)*yWOE*Ch1yHj4Oe+* ze6Qo!q4d(`f3?2t&~WqS&E!e9Ci#^ye_lWH=H#nauXZYMm?>=To_+S2 z`}@BdA70tt+@^B(>?4KGtd|cNlqf4Ji|_evt~!^sZ5PjPrjj|u-?O&fx~;c=b;kRz Z?54?k9A`+BoB}2+22WQ%mvv4FO#opgI12y( literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/menu_checked.png b/resource/qss/flatgray/menu_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..6a1c7294bc0651f85199c5d7b34e6ab718675c38 GIT binary patch literal 542 zcmV+(0^$9MP){-P@(K~F!$PlGMLWUv5E~0Y2uC7k z?+XY%fiGbpvToR0Y))(hZ3KH+PY~U+(L(SIWM^z#-E|W)$xb31`86TKKba39nfwRJ zlqpjt*T`p0?5(a=T*bDAuNt-aI}8t^>fEAX0A3KxiIxLk3K$cdmFb!3+rQ0b^L=gM zz!%)Uz_+g9FW@XiAqxe@;5vy!7WNJ%;JWjOl7)J`dJfMjk}EJhPFl*9V$GzRe1Zfl32aLy=A#LM&fWGffwQ)`a#*%Ith0Fs7U0<;h zsU+h7^kiE*M?nz0u2p&Apz8^G-rBh1wvQ3Mn@-}2T6}`hyl_200MGa59h^VFsM%yZ zz8nqf)8YDnkO^rzRcai>tb_KE*>Lc|pTr(AI}Q@st-+Cl;lQ9D%#Iu!2Zjx6dZZm3 zr0A&4k@j$qvgJ=k+QmVJJxM&$J`OT&n);FX;2`6+^YY^2t};7wC|VBzr>^Mr`rcm4 gvaL*+GXF)M0T62ge$3|rApigX07*qoM6N<$f*dLJ+5i9m literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/radiobutton_checked.png b/resource/qss/flatgray/radiobutton_checked.png new file mode 100644 index 0000000000000000000000000000000000000000..513a41e342995511d72dc2992c375750b915aa2a GIT binary patch literal 1513 zcmV zm@#Vp6^!d3{HT#UABjYM`Nsggfk2JO@J0}=1VbyR5p$XZb!0}gPvh~n+kXq7&*yuJ zN!tscrWgknG0q9f?21OCLxohI6j0Z7t+J~69cH{XEwJ%kO@cgdG!i*7c~u9k+pwX` zoi+~xsD5N{`C?ocxZSrUkw}*9xT_L$XfWA(+AadYU~uMx5xGVPybh*HC&*N?Z@sD?C19gJ z5Fwz}rn4auyek$CpL2k>$m*OTbX~ks+pyx8CNs|fRH3M{a7q2r>)n?wT^om;{}KoUJR=gv)cK8%$D-j6 ziUpe}UDvg_p6Y!7o>%x79!TC=lRwlV-~6YEO=>vmd=zj1kVquVfn?uy2CgXllvP$$ z`SVM1fPp~3LjtREl&iqP5%*nz2{w}Bd7KuczHTeT^0R9Q4(m7RCI)F|fFM^Uux56Qr zO7?x_1m`o1;FEWLO9;3=f~4J$v|F$^G`DhI!s`zxM&zNSnLenBBC+emv1b7QUaz;5 zK=qa$m^z)J(ByA^k6CO}&RZh40QNNcnp^DJ$K&yPU`km1%BRNxYTAs2st#PFXgsQH zbZDKO{UIm4<@c|*e*72<_kpna#t5l7X$(ud4j)BdS!Hr2m+D1_$c~M#Z zAmhmax`-Ai>fEy%UT-K=L4c=FKoFj_GxhiP-&B=n$efb{w9%lQp(LGtqF^`xP6u13 zISx1hGF!I6e?tNQBy-AQQ4c$SYz_l(uTUopR?ag3FbqSvK!p@$4p1FfQ1`@u`7i?@l{wcF(0%3e)+qhlh)Vs8dmKaZ^+C zb9N3+w6>;WN88_oq!2R_45S!H0sMt@!5@o;w>z!UIiBiQ0F+uji*aV`Ik)_!;FChq z#Ely)Xd8gSEi)w2eY+5g87(+gZV|{l5uzU zrM|lQx&;7PZV!k^U$V6RPIuSE%hQEp=8Z}tp)DG2|Hc*&0BTmO_(98LUIpm!2_`|n z%DVcx1@m?N%+<4JGsS@U{r>VLOPB2fuvTRs{q@{=?KoFlsXppV{uA%xMz* z@$a)|e{T1~J*HqVICCV!Z!@sYeoyA%w7bNsI=S24EHoO%DuUQE*$oy0mx*v5#6OQW z&?10e0Mk6D_8^!p$n}v(M49v*?`E0;f#s0cU+^_s(c)nj@n$|thos*ComT$*UsKgzU+6ksTN`b& P00000NkvXXu0mjfzunN5 literal 0 HcmV?d00001 diff --git a/resource/qss/flatgray/radiobutton_checked_disable.png b/resource/qss/flatgray/radiobutton_checked_disable.png new file mode 100644 index 0000000000000000000000000000000000000000..8d16af56015b8e8c3ba8c2c715a74540bebb346c GIT binary patch literal 1628 zcmV-i2BZ0jP)TcV$Bzh!aI&h;sd+_zH?BzJJ&h2}q4udvE5jsBF+q5a$%ZZA@?*x=rhvb|&jR ze?K%0?d`c~`XczjKjcHsbDrn<-RGWr-hltOWU7wYY}T4Rd-la5as$aLMPxC6Q!3`) zN$v*Fqm=6I>FN2|wr%~d1Co?Np|DtM{Um^!07UC_?FX<$L|#axQV0GOK-YCIBl#Z5 zD*&j;BpD?Qpg?j%YisK-GXj{+W@C+wjW3dX5I|^(V15k(c&4|v_oazmeNDi8KEEIo z3grPTo*}R?BH39km+x$EZ|@806A0)y&LWbX04|syj|boofNx122LKV758!eD^D)72 z5RsjcNaUvG=H_E{18&{Ab)iyfTMal3V7G`o+uz@xTeD_OIUvqlrPNxIkBi9pH8^*a zN~JbaCC35ga=FHEINT{B%L9TPAh{u(P8URkHIi0k+qP`jFf*Ffg#r z|FGlAZ&}tx5m{!4p+zJci^VQ&ZEbbyg{uJSbUOXM=XsX^*kS4;BCF%^_-cRq7{JO@ zve5vp0ld}U-+xykkto&+=2s?@IT(w@k^nw2n4kZ*pzFGqX|1;#0QYIFZ)|I8+do~n(Q;k4No(x_ znCmm`RZ6W$CXE|ym*i%Wo0U>K;_>)z zj^o@z(m0ig2uZ#H;IPl6MC6tcKLr4#Qt4~}mm1=1%VaV~O$oButku-i^g4j&05ll7 z%pv)n>$*<{L8elv{Q$l)m@X<53UjM~TI&S>8hxEkh=^+n3;^+Xd@X?WsJ&8zNNy?= z3OAUHl`3vGn9lM%@2n~y$&yLD*L|2F#h!(29O{5Q#E>? zw~ORX1HdxNvK|duPp6`6Hfx2$;gf$Qw-JXb{C1ke%yXFPfutI^ffwG6`p)xRAdQJ;uB zKWo;k*C#qe<@5P@;c(a{c^}OCMxe&AqSnpAZ6x2V5flJCl6yqta}oJ&v_WgVL_{(e zo+S7bk;!CC%j+0TYRbsx^YcQX(9;0! z#rQ4KL^>iOuhjElO&M@Rwr#7WOP5|l@(uvY0i2KVAJbm|_KC=606KbldiG59p89`H aNB;nCuyXHkR2mWh0000DIO}r0oIPN^FfLCS;6F zgeZoC3*BKCX-zqZ@uC+JER24YJqv82>ex=_&6 z2$8ij@8_Z0vb*zU|Fpa6!S8AI_wRl7&Ad1Be!zcRw9-Uf*EPT2U++M)fvFLMO#s#= z+`oZw6NDc%lEbEH{_>9j2122F;cz#Iwt}JMl*Ak-L4(eM_Jw8j-YE)TM@PpqOxh6u z_4y3o663NU=TIaPxt~jTRzO|XwW{ix_nGnLs=!i5wF&am)24YUduRsj+_$f+ByRTr zs3{m+vKUwH-kCWNjYe&+5Kq8hFjy`jA^??T9|v<_j05-+0Enmzkc|MX&mni>kMgHF zzd3d+o-^R$%q>`1;BH9J$$PO|7rbU591gF0v>>+&fw#a^l?ie#HnUe%M+vwq7%~ZH z@F;el1Romc@4uV@-lafC=rsX82%-{o1Pu*DP903Q`dn}wd%B3|Rdo^mZZ3BF?Q?^J zGx@*)!1%S1iH5e;Gn#as2dF|3vT<`u)6KEb(a99-$iEv$xs!N4UgGwKwNReD%G580Uk*Ffj*;xT30By)>RdOsgJzi8g02;{= zrTvlP2NHljqB=#)gJRy^CC$#xPOIz=*_A-rKA5I^MIlO4wn^{?WOls<{|yNMkj!%~ zkNUP3T!vvN7pRcJ1`>dRq1=cK!%%+F6jC;z1{87{NB}x(*NiEXS+o@oZBfMZg4+n& zG|fpM0ciE~+yk>~Q4mz`=E7<5olETs*YMKC<3&O=qzK%!d-t9fi@*i|sPxyo4xlvM z&Wwvo*N@-Dg3qgpu%P#f0jKM_M&JW32f=1LKIj6p`uoQj?Ap}{Xy`D)`wI=Y!C!k= zb>?A-^Oj{jZ~+1UNt{#_W9IH%hM{^^R;0y0%5`KVSTGnY-`uq2D1aR*f8yx*(bUwGOl4NcFpLHV;!Drw5ObUafA;(O^W+*Q{C7UnS3UW6YDP`i6+islmCYSS+^t zRk8vwo6R-_gTW3FSz0C8QB_@^OeVWTL~Bf1Ryv(F&CShgfe(P|{Cre(+r^6)*LnYJ zMftU3g-tl38rK4nn z54Z(B>+9=#E*6UwMhoUuDwR4BjYi|Z_dezbuqm6(-rx(Us$#8u1(@NDRMl?_g~F@V z*4kJUi^U4XV(~@%D>*M54nIG1pu7eh$608t-Q@##*jl@yy}kX&MB#?YahzsrtpiN= zm<}0Zmc`@oQ!u=OD}3NA@a|;80kpQZ?h}!(eN1y**DY^Q1F(Dd?kTF;>h)CB1Fq|C zpKLe)5m8nB6gcHE84-D0RizAAEEaD8=KJK?nM$S3)B_M8kw_c?e(^Eg+11rGy$oor zoeea3oGy!qQ!h9uS8&1% zY#|T`T&^b^fa|&^fs3BusEAw-wJ-9sSFT+7r*32=MIw;_uAUaYss$Jbg+k+Q2_{Q} zfX97oHwS~k#(Dz`3=H^fPzww|3D6jGmdY|u>+kQst=@pDIvem7qM)kRZW3$lA>f?H zsnHnITyH>QObeAut=AZHtPB_k1Wo`aeBvL=<#G-6f^!__CRKgdJFcn+B9X}HGGII& z{~P$(C&yxA%p>&zwAQu*bG_ptvMUygU0Z`C{lL|3Yp5c?o7rsEyND;O(tF?QR6gEQ zKyL6y+333NK2_c8O(Y^q!r}01gO~SYD3i&AgTdeyV4jaTU%I1*1Ex}`OTb4!(Gy@) zbz?rCe|j?DR8^Xqnl_4vU%oTOn2(3H6T`~e(9qxj-}wYafY0;!{7Vz5WICNTj^k`r z)lJ^HOWNAn+PZ(}K*eMuw44_BCKL*7XlZFVKNh%-j*eSJWGm2G1!R9_@H`4qi2Mj_@9yqC gINtBn|7#-p7ZZ^7mcSw#Pyhe`07*qoM6N<$f@6P{@&Et; literal 0 HcmV?d00001 diff --git a/statistic.cpp b/statistic.cpp new file mode 100644 index 0000000..ef0a6f6 --- /dev/null +++ b/statistic.cpp @@ -0,0 +1,42 @@ +#include "statistic.h" +#include "util.h" + +Statistic::Statistic(std::unique_ptr& repaySqlOpr) : repaySqlOpr_(repaySqlOpr) +{ +} + +Statistic::~Statistic() +{ +} + +void Statistic::Calculate(const AccountRecordList& list) +{ + auto* d = SharedData::instance(); + d->ClearTt(); + + for (const auto& item : list) { + if (item.payType == "现金支出") { + d->ttCashOut_ += item.money; + } else if (item.payType == "现金收入") { + d->ttCashIn_ += item.money; + } else if (item.payType == "信用支出") { + d->ttCreditOut_ += item.money; + RepayRecordList repayList; + if (repaySqlOpr_->GetRepayResult(repayList, item.id)) { + for (const auto& repay : repayList) { + d->ttCashPay_ += repay.money; + } + } + } else if (item.payType == "信用收入") { + d->ttCreditIn_ += item.money; + } else if (item.payType == "信用借款") { + d->ttCreditCash_ += item.money; + RepayRecordList repayList; + if (repaySqlOpr_->GetRepayResult(repayList, item.id)) { + for (const auto& repay : repayList) { + d->ttCashPay_ += repay.money; + } + } + } + } +} diff --git a/statistic.h b/statistic.h new file mode 100644 index 0000000..cd21e1d --- /dev/null +++ b/statistic.h @@ -0,0 +1,19 @@ +#ifndef STATISTIC_H +#define STATISTIC_H + +#include "SqlOpr.h" + +class Statistic +{ +public: + Statistic(std::unique_ptr& repaySqlOpr); + ~Statistic(); + +public: + void Calculate(const AccountRecordList& list); + +private: + std::unique_ptr& repaySqlOpr_; +}; + +#endif // STATISTIC_H \ No newline at end of file diff --git a/util.cpp b/util.cpp new file mode 100644 index 0000000..6c13dc4 --- /dev/null +++ b/util.cpp @@ -0,0 +1,145 @@ +#include "util.h" +#include +#include +#include + +static std::string gConfigPath; +static std::string gWorkDir; +static nlohmann::json gConfig; + +void GenerateDefaultConfig(const std::string& path) +{ + nlohmann::json config; + config["MainWidth"] = 1000; + config["MainHeight"] = 600; + config["Editor"] = "Notepad"; + config["MediaDir"] = "./Media"; + config["CurCash"] = 0; + config["CurSave"] = 0; + std::ofstream configFile(path); + configFile << config.dump(4); + configFile.close(); + gConfig = config; +} + +Util::Util() +{ +} + +bool Util::Init() +{ + gWorkDir = QDir::homePath().toStdString() + "/.config/SimpleAccount"; + if (!QDir(QString::fromStdString(gWorkDir)).exists()) { + QDir().mkdir(QString::fromStdString(gWorkDir)); + } + gConfigPath = gWorkDir + "/config.json"; + return LoadConfig(); +} + +std::string Util::GetWorkDir() +{ + return gWorkDir; +} + +std::string Util::GetMediaDir() +{ + return gConfig["MediaDir"].get(); +} + +std::string Util::GetConfigPath() +{ + return gConfigPath; +} + +std::pair Util::GetMainSize() +{ + return std::make_pair(gConfig["MainWidth"].get(), gConfig["MainHeight"].get()); +} + +bool Util::SetMainSize(int w, int h) +{ + LoadConfig(); + + gConfig["MainWidth"] = w; + gConfig["MainHeight"] = h; + + std::ofstream configFile(gConfigPath); + if (!configFile.is_open()) { + return false; + } + configFile << gConfig.dump(4); + configFile.close(); + return true; +} + +std::string Util::GetEditor() +{ + return gConfig["Editor"].get(); +} + +QString Util::NewUUIDName(const QString& path) +{ + QFileInfo fileInfo(path); + QString originalSuffix = fileInfo.suffix(); + QString newName = QUuid::createUuid().toString(QUuid::WithoutBraces); + if (!originalSuffix.isEmpty()) { + newName += "." + originalSuffix; + } + return newName; +} + +double Util::GetCurCash() +{ + return gConfig["CurCash"].get(); +} + +double Util::GetCurSave() +{ + return gConfig["CurSave"].get(); +} + +int Util::CashInt(double cash) +{ + return static_cast(round(cash * 100)); +} + +double Util::CashDouble(int cash) +{ + return static_cast(cash) / 100.0; +} + +bool Util::LoadConfig() +{ + QFile file(QString::fromStdString(gConfigPath)); + if (file.exists()) { + std::ifstream configFile(gConfigPath); + if (!configFile.is_open()) { + return false; + } + gConfig = nlohmann::json::parse(configFile); + configFile.close(); + } else { + GenerateDefaultConfig(gConfigPath); + } + return true; +} + +SharedData::~SharedData() +{ +} + +SharedData* SharedData::instance() +{ + static SharedData data; + return &data; +} + +void SharedData::ClearTt() +{ + ttCashOut_ = 0; + ttCashIn_ = 0; + ttCreditIn_ = 0; + ttCreditOut_ = 0; + ttCreditCash_ = 0; + ttCashPay_ = 0; +} diff --git a/util.h b/util.h new file mode 100644 index 0000000..529ffe5 --- /dev/null +++ b/util.h @@ -0,0 +1,79 @@ +#ifndef UTIL_H +#define UTIL_H + +#include +#include +#include +#include + +class Util +{ +public: + Util(); + +public: + static bool Init(); + +public: + static std::string GetWorkDir(); + static std::string GetMediaDir(); + static std::string GetConfigPath(); + static std::pair GetMainSize(); + static bool SetMainSize(int w, int h); + static std::string GetEditor(); + static QString NewUUIDName(const QString& path); + static double GetCurCash(); + static double GetCurSave(); + static int CashInt(double cash); + static double CashDouble(int cash); + +private: + static bool LoadConfig(); +}; + +enum CashFilter { + FIL_NO_LIMIT = 0, + FIL_LOWER, + FIL_HIGHER, + FIL_BETWEEN +}; + +class SharedData +{ +private: + SharedData() = default; + ~SharedData(); + +public: + static SharedData* instance(); + + // 条件 +public: + bool flType{}; + QString type{}; + bool flDays{}; + bool flKeys{}; + bool flClassify{}; + QString classify{}; + QString key{}; + int days{}; + int lowMoney{}; + int highMoney{}; + CashFilter filter{}; + +public: + void ClearTt(); + + // 结果 +public: + int32_t ttCashOut_{}; + int32_t ttCashIn_{}; + int32_t ttCreditIn_{}; + int32_t ttCreditOut_{}; + int32_t ttCreditCash_{}; + // 还款金额 + int32_t ttCashPay_{}; + // 不计算现有现金的理论现金 + int32_t ttCash_{}; +}; +#endif // UTIL_H