diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..f8b5523 --- /dev/null +++ b/.clang-format @@ -0,0 +1,258 @@ +--- +Language: Cpp +# BasedOnStyle: Chromium +AccessModifierOffset: -1 +AlignAfterOpenBracket: AlwaysBreak +AlignArrayOfStructures: None +AlignConsecutiveAssignments: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: true +AlignConsecutiveBitFields: + Enabled: false + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveDeclarations: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignConsecutiveMacros: + Enabled: true + AcrossEmptyLines: false + AcrossComments: false + AlignCompound: false + PadOperators: false +AlignEscapedNewlines: Left +AlignOperands: Align +AlignTrailingComments: + Kind: Always + OverEmptyLines: 0 +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: Always +AllowShortCaseLabelsOnASingleLine: false +AllowShortEnumsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: WithoutElse +AllowShortLambdasOnASingleLine: All +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +AttributeMacros: + - __capability +BinPackArguments: true +BinPackParameters: false +BitFieldColonSpacing: Both +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: Never + AfterEnum: false + AfterExternBlock: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + BeforeCatch: false + BeforeElse: false + BeforeLambdaBody: false + BeforeWhile: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakAfterAttributes: Never +BreakAfterJavaFieldAnnotations: false +BreakArrays: true +BreakBeforeBinaryOperators: None +BreakBeforeConceptDeclarations: Always +BreakBeforeBraces: Attach +BreakBeforeInlineASMColon: OnlyMultiline +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeColon +BreakInheritanceList: BeforeColon +BreakStringLiterals: true +ColumnLimit: 160 +CommentPragmas: "^ IWYU pragma:" +CompactNamespaces: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +EmptyLineAfterAccessModifier: Never +EmptyLineBeforeAccessModifier: LogicalBlock +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IfMacros: + - KJ_IF_MAYBE +IncludeBlocks: Regroup +IncludeCategories: + - Regex: '^' + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: '^<.*\.h>' + Priority: 1 + SortPriority: 0 + CaseSensitive: false + - Regex: "^<.*" + Priority: 2 + SortPriority: 0 + CaseSensitive: false + - Regex: ".*" + Priority: 3 + SortPriority: 0 + CaseSensitive: false +IncludeIsMainRegex: "([-_](test|unittest))?$" +IncludeIsMainSourceRegex: "" +IndentAccessModifiers: true +IndentCaseBlocks: false +IndentCaseLabels: true +IndentExternBlock: AfterExternBlock +IndentGotoLabels: true +IndentPPDirectives: None +IndentRequiresClause: true +IndentWidth: 2 +IndentWrappedFunctionNames: false +InsertBraces: false +InsertNewlineAtEOF: true +InsertTrailingCommas: None +IntegerLiteralSeparator: + Binary: 0 + BinaryMinDigits: 0 + Decimal: 0 + DecimalMinDigits: 0 + Hex: 0 + HexMinDigits: 0 +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +LambdaBodyIndentation: Signature +LineEnding: LF +MacroBlockBegin: "" +MacroBlockEnd: "" +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 2 +ObjCBreakBeforeNestedBlockParam: true +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLine +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakOpenParenthesis: 0 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyIndentedWhitespace: 0 +PenaltyReturnTypeOnItsOwnLine: 200 +PointerAlignment: Left +PPIndentWidth: -1 +QualifierAlignment: Leave +RawStringFormats: + - Language: Cpp + Delimiters: + - cc + - CC + - cpp + - Cpp + - CPP + - "c++" + - "C++" + CanonicalDelimiter: "" + BasedOnStyle: google + - Language: TextProto + Delimiters: + - pb + - PB + - proto + - PROTO + EnclosingFunctions: + - EqualsProto + - EquivToProto + - PARSE_PARTIAL_TEXT_PROTO + - PARSE_TEST_PROTO + - PARSE_TEXT_PROTO + - ParseTextOrDie + - ParseTextProtoOrDie + - ParseTestProto + - ParsePartialTestProto + CanonicalDelimiter: pb + BasedOnStyle: google +ReferenceAlignment: Pointer +ReflowComments: true +RemoveBracesLLVM: false +RemoveSemicolon: false +RequiresClausePosition: OwnLine +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Leave +ShortNamespaceLines: 1 +SortIncludes: CaseSensitive +SortJavaStaticImport: Before +SortUsingDeclarations: LexicographicNumeric +SpaceAfterCStyleCast: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceAroundPointerQualifiers: Default +SpaceBeforeAssignmentOperators: true +SpaceBeforeCaseColon: false +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeParensOptions: + AfterControlStatements: true + AfterForeachMacros: true + AfterFunctionDefinitionName: false + AfterFunctionDeclarationName: false + AfterIfMacros: true + AfterOverloadedOperator: false + AfterRequiresInClause: false + AfterRequiresInExpression: false + BeforeNonEmptyParentheses: false +SpaceBeforeRangeBasedForLoopColon: true +SpaceBeforeSquareBrackets: false +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: Never +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInLineCommentPrefix: + Minimum: 1 + Maximum: -1 +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Auto +StatementAttributeLikeMacros: + - Q_EMIT +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 8 +UseTab: Never +WhitespaceSensitiveMacros: + - BOOST_PP_STRINGIZE + - CF_SWIFT_NAME + - NS_SWIFT_NAME + - PP_STRINGIZE + - STRINGIZE +--- diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..359a391 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + Add: [-std=c++20] diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 09d0c70..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,12 +0,0 @@ -# These are supported funding model platforms - -github: JoshStrobl -patreon: joshuastrobl -open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username -tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel -community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: joshuastrobl -issuehunt: # Replace with a single IssueHunt username -otechie: # Replace with a single Otechie username -custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/koto-alpha-image.png b/.github/koto-alpha-image.png deleted file mode 100644 index 061257a..0000000 Binary files a/.github/koto-alpha-image.png and /dev/null differ diff --git a/.gitignore b/.gitignore index 4beaadd..983430f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,90 @@ -DesiredSettings.md -builddir -.buildconfig -src/theme/style.css +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + +.cache/ +.ccls-cache/ +.idea/ +.kdev4/ +.qt/ +.rcc/ +.zed/ +bin/ +build*/ +cmake-build-debug/ +**/qmldir +**/meta_types +**/qmltypes +**/*.qrc +CMakeCache.txt +*.kdev4 diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json deleted file mode 100644 index 04ba0dd..0000000 --- a/.vscode/c_cpp_properties.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "configurations": [ - { - "name": "Linux", - "includePath": [ - "${workspaceFolder}/**", - "/usr/include/**" - ], - "defines": [], - "compilerPath": "/usr/bin/gcc", - "cStandard": "gnu17", - "cppStandard": "c++20", - "intelliSenseMode": "linux-gcc-x64" - } - ], - "version": 4 -} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index d477032..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch (GDB)", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/builddir/src/com.github.joshstrobl.koto", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [ - {"name" : "G_MESSAGES_DEBUG", "value": "all" }, - { "name": "GTK_THEME", "value": "Adwaita:dark" } - ], - "externalConsole": false, - "MIMode": "gdb", - "setupCommands": [ - { - "description": "Enable pretty-printing for gdb", - "text": "-enable-pretty-printing", - "ignoreFailures": true - } - ], - "preLaunchTask": "Meson Configure and Build" - }, - { - "name": "Launch", - "type": "cppdbg", - "request": "launch", - "program": "${workspaceFolder}/builddir/src/com.github.joshstrobl.koto", - "args": [], - "stopAtEntry": false, - "cwd": "${workspaceFolder}", - "environment": [ - {"name" : "G_MESSAGES_DEBUG", "value": "all" }, - { "name": "GTK_THEME", "value": "Adwaita:dark" } - ], - "externalConsole": false, - "linux": { - "MIMode": "gdb", - "miDebuggerPath": "" - }, - "preLaunchTask": "Meson Configure and Build" - }, - ] -} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2ea2f84..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "files.associations": { - "glib.h": "c", - "ios": "c", - "__node_handle": "c", - "gtk.h": "c", - "gtktreeview.h": "c", - "cartographer.h": "c", - "structs.h": "c", - "gst.h": "c", - "player.h": "c", - "config.h": "c", - "toml.h": "c", - "chrono": "c", - "sqlite3.h": "c", - "unistd.h": "c", - "ui.h": "c", - "koto-utils.h": "c", - "random": "c", - "add-remove-track-popover.h": "c" - } -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index 1359393..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Clean builddir", - "type": "shell", - "command": "rm", - "args": [ - "-rf", - "builddir" - ], - "problemMatcher": [] - }, - { - "label": "Format", - "type": "shell", - "command": "uncrustify", - "args": [ - "-c", - "jsc.cfg", - "--no-backup", - "**/*.c", - "**/*.h" - ] - }, - { - "label": "Meson Configure and Build", - "type": "shell", - "command": "", - "dependsOrder": "sequence", - "dependsOn": ["Meson Configure", "Meson Compile"] - }, - { - "label": "Meson Configure", - "type": "shell", - "command": "meson", - "args": [ - "--prefix=/usr", - "--libdir=\"libdir\"", - "--sysconfdir=/etc", - "builddir" - ], - "problemMatcher": [] - }, - { - "label": "Meson Compile", - "type": "shell", - "command": "meson", - "args": [ - "compile", - "-C", - "builddir", - ], - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": true - } - }, - { - "label": "Meson Dist", - "type": "shell", - "command": "meson", - "args": [ - "dist", - "-C", - "builddir", - "--formats", - "xztar", - "--include-subprojects" - ], - "problemMatcher": [] - }, - { - "label": "Meson Install", - "type": "shell", - "command": "sudo", - "args": [ - "meson", - "install", - "-C", - "builddir", - "--destdir", - "/", - "--no-rebuild" - ], - "problemMatcher": [] - } - ] -} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..544751b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.16) + +project(koto VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_STANDARD 23) + +add_subdirectory(desktop) diff --git a/COPYING b/COPYING index d645695..d0a1fa1 100644 --- a/COPYING +++ b/COPYING @@ -1,202 +1,373 @@ +Mozilla Public License Version 2.0 +================================== - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +1. Definitions +-------------- - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. - 1. Definitions. +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +1.3. "Contribution" + means Covered Software of a particular Contributor. - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. +1.5. "Incompatible With Secondary Licenses" + means - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. +1.6. "Executable Form" + means any form of the work other than Source Code Form. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. +1.8. "License" + means this document. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. +1.10. "Modifications" + means any of the following: - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. + (b) any new file in Source Code Form that contains any Covered + Software. - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and +1.13. "Source Code Form" + means the form of the work preferred for making modifications. - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. +2. License Grants and Conditions +-------------------------------- - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. +2.1. Grants - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. +2.2. Effective Date - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. - END OF TERMS AND CONDITIONS +2.3. Limitations on Grant Scope - APPENDIX: How to apply the Apache License to your work. +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. +(a) for any code that a Contributor has removed from Covered Software; + or - Copyright [yyyy] [name of copyright owner] +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. - http://www.apache.org/licenses/LICENSE-2.0 +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/data/com.github.joshstrobl.koto.desktop.in b/KotoQt/data/com.github.joshstrobl.koto.desktop similarity index 100% rename from data/com.github.joshstrobl.koto.desktop.in rename to KotoQt/data/com.github.joshstrobl.koto.desktop diff --git a/README.md b/README.md index 90c97d4..2bd8c81 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,13 @@ -# Koto +# Koto Qt -![koto alpha](https://raw.githubusercontent.com/JoshStrobl/koto/master/.github/koto-alpha-image.png) +Work in progress implementation of Koto in Qt6, Kirigami, and C++. -Koto is an in-development audiobook, music, and podcast manager that is designed *for* and caters *to* a modern desktop -Linux experience. **Nothing to see here yet**. +To clone this repository on [Radicle](https://radicle.xyz), simple run: -## Blog +``` +rad clone rad://z2RuqdobvbGje3mWhtMQ9cUVNfizp +``` -- [Dev Diary 13: Koto - The Resurrection](https://joshuastrobl.com/2024/11/01/dev-diary-13-koto-the-resurrection) -- [Dev Diary 12: Koto August Progress Report](https://joshuastrobl.com/2021/09/06/dev-diary-12-koto-august-progress-report) -- [Dev Diary 11: Koto July Progress Report](https://joshuastrobl.com/2021/08/08/dev-diary-11-koto-july-progress-report/) -- [Dev Diary 10: Koto June Progress Report](https://joshuastrobl.com/2021/07/08/dev-diary-10-koto-june-progress-report/) -- [Dev Diary 9: Koto May Progress Report (B-side)](https://joshuastrobl.com/2021/06/10/dev-diary-9-koto-may-progress-report-b-side/) -- [Dev Diary 8: Koto May Progress Report (A-side)](https://joshuastrobl.com/2021/05/27/dev-diary-8-koto-may-progress-report-a-side/) -- [Dev Diary 7: Koto April Progress Report (B-side)](https://joshuastrobl.com/2021/05/07/dev-diary-7-koto-april-progress-report-b-side/) -- [Dev Diary 6: Koto April Progress Report (A-side)](https://joshuastrobl.com/2021/04/26/dev-diary-6-koto-april-progress-report-a-side/) -- [Dev Diary 5: Koto March Progress Report (B-side)](https://joshuastrobl.com/2021/04/08/dev-diary-5-koto-march-progress-report-b-side/) -- [Dev Diary 4: Koto March Progress Report (A-side)](https://joshuastrobl.com/2021/03/26/dev-diary-4-koto-march-progress-report-a-side/) -- [Dev Diary 3: Koto February Progress Report (B-side)](https://joshuastrobl.com/2021/03/05/dev-diary-3-koto-february-progress-report-b-side/) -- [Dev Diary 2: Koto February Progress Report (A-side)](https://joshuastrobl.com/2021/02/17/dev-diary-2-koto-february-progress-report-a-side/) -- [Dev Diary 1: Koto - Foundations](https://joshuastrobl.com/2021/01/25/dev-diary-1-koto-foundations/) +## LICENSE -## License - -Koto is licensed under the Apache 2.0 license. +Licensed under the Mozilla Public License 2.0 (MPL-2.0). diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..674989e --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,17 @@ +version: "3" + +tasks: + sdbus-gen-desktop: + cmds: + - sdbus-c++-xml2cpp desktop/dbus/schema.xml --adaptor=desktop/dbus/daemon-server.h --proxy=desktop/dbus/daemon-client.h + setup-desktop: + desc: "Run cmake configuration for desktop Koto" + cmds: + - cmake -S . -B build + build: cmake --build build + build-watch-desktop: watchman-make -p '**/*.cpp' '**/*.h' --run "task cook-desktop" + cook-desktop: + cmds: + - task setup-desktop + - task build + install: sudo make install -C build diff --git a/build-aux/meson/postinstall.py b/build-aux/meson/postinstall.py deleted file mode 100755 index cfa1e2d..0000000 --- a/build-aux/meson/postinstall.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env python3 - -from os import environ, path -from subprocess import call - -prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local') -datadir = path.join(prefix, 'share') -destdir = environ.get('DESTDIR', '') - -# Package managers set this so we don't need to run -if not destdir: - print('Updating desktop database...') - call(['update-desktop-database', '-q', path.join(datadir, 'applications')]) - diff --git a/com.github.joshstrobl.koto.json b/com.github.joshstrobl.koto.json deleted file mode 100644 index 2d66e1e..0000000 --- a/com.github.joshstrobl.koto.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "app-id" : "com.github.joshstrobl.koto", - "runtime" : "org.gnome.Platform", - "runtime-version" : "40", - "sdk" : "org.gnome.Sdk", - "command" : "com.github.joshstrobl.koto", - "finish-args" : [ - "--share=network", - "--share=ipc", - "--socket=fallback-x11", - "--socket=wayland" - ], - "cleanup" : [ - "/include", - "/lib/pkgconfig", - "/man", - "/share/doc", - "/share/gtk-doc", - "/share/man", - "/share/pkgconfig", - "*.la", - "*.a" - ], - "modules" : [ - { - "name" : "koto", - "builddir" : true, - "buildsystem" : "meson", - "sources" : [ - { - "type" : "git", - "url" : "https://github.com/JoshStrobl/koto.git" - } - ] - } - ], - "build-options" : { - "env" : { } - } -} diff --git a/data/com.github.joshstrobl.koto.appdata.xml.in b/data/com.github.joshstrobl.koto.appdata.xml.in deleted file mode 100644 index 04164f4..0000000 --- a/data/com.github.joshstrobl.koto.appdata.xml.in +++ /dev/null @@ -1,33 +0,0 @@ - - - com.github.joshstrobl.koto.desktop - CC0-1.0 - Apache-2.0 - Koto - Koto is an in-development audiobook, music, and podcast manager. - -

Koto is an in-development audiobook, music, and podcast manager that is designed for and caters to a modern desktop Linux experience.

-
- com.github.joshstrobl.koto.desktop - https://github.com/JoshStrobl/koto - https://github.com/JoshStrobl/koto/issues - https://patreon.com/joshuastrobl - https://liberapay.com/joshuastrobl - Joshua Strobl - joshua.strobl_AT_outlook.com - - mild - - - com.github.joshstrobl.koto - - - pointing - touch - 1600 - - - keyboard - 1366 - -
diff --git a/data/com.github.joshstrobl.koto.gschema.xml b/data/com.github.joshstrobl.koto.gschema.xml deleted file mode 100644 index 7ff368a..0000000 --- a/data/com.github.joshstrobl.koto.gschema.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/data/genres/business-and-personal-finance.png b/data/genres/business-and-personal-finance.png deleted file mode 100644 index 789aadb..0000000 Binary files a/data/genres/business-and-personal-finance.png and /dev/null differ diff --git a/data/genres/foreign-languages.png b/data/genres/foreign-languages.png deleted file mode 100644 index 5499f4e..0000000 Binary files a/data/genres/foreign-languages.png and /dev/null differ diff --git a/data/genres/mystery-and-thriller.png b/data/genres/mystery-and-thriller.png deleted file mode 100644 index d42cded..0000000 Binary files a/data/genres/mystery-and-thriller.png and /dev/null differ diff --git a/data/genres/sci-fi.png b/data/genres/sci-fi.png deleted file mode 100644 index 959b6c8..0000000 Binary files a/data/genres/sci-fi.png and /dev/null differ diff --git a/data/genres/travel.png b/data/genres/travel.png deleted file mode 100644 index 41ba389..0000000 Binary files a/data/genres/travel.png and /dev/null differ diff --git a/data/meson.build b/data/meson.build deleted file mode 100644 index 99a118c..0000000 --- a/data/meson.build +++ /dev/null @@ -1,41 +0,0 @@ -desktop_file = i18n.merge_file( - input: 'com.github.joshstrobl.koto.desktop.in', - output: 'com.github.joshstrobl.koto.desktop', - type: 'desktop', - po_dir: '../po', - install: true, - install_dir: join_paths(get_option('datadir'), 'applications') -) - -desktop_utils = find_program('desktop-file-validate', required: false) -if desktop_utils.found() - test('Validate desktop file', desktop_utils, - args: [desktop_file] - ) -endif - -appstream_file = i18n.merge_file( - input: 'com.github.joshstrobl.koto.appdata.xml.in', - output: 'com.github.joshstrobl.koto.appdata.xml', - po_dir: '../po', - install: true, - install_dir: join_paths(get_option('datadir'), 'appdata') -) - -appstream_util = find_program('appstream-util', required: false) -if appstream_util.found() - test('Validate appstream file (relaxed)', appstream_util, - args: ['validate-relax', appstream_file] - ) -endif - -install_data('com.github.joshstrobl.koto.gschema.xml', - install_dir: join_paths(get_option('datadir'), 'glib-2.0/schemas') -) - -compile_schemas = find_program('glib-compile-schemas', required: false) -if compile_schemas.found() - test('Validate schema file', compile_schemas, - args: ['--strict', '--dry-run', meson.current_source_dir()] - ) -endif diff --git a/data/vectors/business-and-personal-finance.svg b/data/vectors/business-and-personal-finance.svg deleted file mode 100644 index 7bb925c..0000000 --- a/data/vectors/business-and-personal-finance.svg +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/vectors/foreign-languages.svg b/data/vectors/foreign-languages.svg deleted file mode 100644 index c423fb9..0000000 --- a/data/vectors/foreign-languages.svg +++ /dev/null @@ -1,153 +0,0 @@ - - - - - - - - - - - - - - - - - - - Bonjour - Hello - - - - diff --git a/data/vectors/multimedia-backwards-jump.svg b/data/vectors/multimedia-backwards-jump.svg deleted file mode 100644 index f5c9016..0000000 --- a/data/vectors/multimedia-backwards-jump.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/data/vectors/multimedia-forwards-jump.svg b/data/vectors/multimedia-forwards-jump.svg deleted file mode 100644 index 9db39bf..0000000 --- a/data/vectors/multimedia-forwards-jump.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/data/vectors/mystery-and-thriller.svg b/data/vectors/mystery-and-thriller.svg deleted file mode 100644 index 9b7dad0..0000000 --- a/data/vectors/mystery-and-thriller.svg +++ /dev/null @@ -1,572 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/vectors/sci-fi.svg b/data/vectors/sci-fi.svg deleted file mode 100644 index 5b4161e..0000000 --- a/data/vectors/sci-fi.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - diff --git a/data/vectors/travel.svg b/data/vectors/travel.svg deleted file mode 100644 index d70a1bc..0000000 --- a/data/vectors/travel.svg +++ /dev/null @@ -1,133 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/desktop/CMakeLists.txt b/desktop/CMakeLists.txt new file mode 100644 index 0000000..4dd9a9a --- /dev/null +++ b/desktop/CMakeLists.txt @@ -0,0 +1,49 @@ +find_package(Qt6 6.4 REQUIRED COMPONENTS Quick QuickControls2 Sql) +find_package(ECM REQUIRED NO_MODULE) +find_package(KF6Baloo) +find_package(KF6FileMetaData) +set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake) + +include(KDEInstallDirs) +include(ECMFindQmlModule) +include(ECMQmlModule) + +qt_standard_project_setup() + +qt_add_executable(com.github.joshstrobl.koto + config/config.cpp + config/library.cpp + config/ui_prefs.cpp + datalake/album.cpp + datalake/artist.cpp + datalake/cartographer.cpp + datalake/database.cpp + datalake/indexer.cpp + datalake/models.cpp + datalake/track.cpp + datalake/cartographer.hpp + datalake/structs.hpp + main.cpp + datalake/models.cpp +) + +target_include_directories(com.github.joshstrobl.koto PUBLIC datalake includes) +ecm_add_qml_module(com.github.joshstrobl.koto URI "com.github.joshstrobl.koto" GENERATE_PLUGIN_SOURCE) + +ecm_target_qml_sources(com.github.joshstrobl.koto + SOURCES + qml/PlayerBar/PlayerBar.qml + qml/PrimaryNavigation.qml + qml/HomePage.qml + qml/Main.qml + qml/Root.qml +) + +target_link_libraries(com.github.joshstrobl.koto + PRIVATE KF6::Baloo KF6::FileMetaData Qt6::Quick Qt6::QuickControls2 Qt6::Sql +) + +install(FILES com.github.joshstrobl.koto.desktop DESTINATION ${KDE_INSTALL_APPDIR}) +install(TARGETS com.github.joshstrobl.koto ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) + +ecm_finalize_qml_module(com.github.joshstrobl.koto) diff --git a/desktop/config/config.cpp b/desktop/config/config.cpp new file mode 100644 index 0000000..8fb1adb --- /dev/null +++ b/desktop/config/config.cpp @@ -0,0 +1,98 @@ +#include "config.hpp" + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +KotoConfig::KotoConfig() { + // Define our application's config location + auto configDir = QDir(QStandardPaths::writableLocation(QStandardPaths::StandardLocation::AppConfigLocation)); + auto configDirPath = configDir.absolutePath(); + this->i_configDirPath = configDirPath; + this->i_libraries = QList(); + + fs::path filePath {}; + auto configPathStd = configDirPath.toStdString(); + filePath /= configPathStd; + filePath /= "config.toml"; + this->i_configPath = QString {filePath.c_str()}; + + if (QFileInfo::exists(i_configPath)) { + this->parseConfigFile(filePath); + } else { + this->bootstrap(); + } +} + +KotoConfig& KotoConfig::instance() { + static KotoConfig _instance; + return _instance; +} + +void KotoConfig::bootstrap() { + this->i_uiPreferences = new KotoUiPreferences(); + + auto musicDir = QDir(QStandardPaths::writableLocation(QStandardPaths::StandardLocation::MusicLocation)); + auto musicLibrary = new KotoLibraryConfig("Music", musicDir.absolutePath().toStdString(), KotoLibraryType::Music); + + this->i_libraries.append(musicLibrary); + + this->save(); +} + +QString KotoConfig::getConfigDirPath() { + return QString {this->i_configDirPath}; +} + +KotoUiPreferences* KotoConfig::getUiPreferences() { + return this->i_uiPreferences; +} + +QList KotoConfig::getLibraries() { + return this->i_libraries; +} + +void KotoConfig::parseConfigFile(std::string filePath) { + auto data = toml::parse(filePath); + std::optional ui_prefs; + + if (data.contains("preferences.ui")) { + auto ui_prefs_at = data.at("preferences.ui"); + if (ui_prefs_at.is_table()) ui_prefs = ui_prefs_at.as_table(); + } + + auto prefs = new KotoUiPreferences(ui_prefs); + this->i_uiPreferences = prefs; + + for (const auto& lib_value : toml::find>(data, "libraries")) { + auto lib = new KotoLibraryConfig(lib_value); + this->i_libraries.append(lib); + } +} + +void KotoConfig::save() { + toml::ordered_value config_table(toml::ordered_table {}); + config_table["preferences.ui"] = this->i_uiPreferences->serialize(); + + toml::ordered_value libraries_array(toml::ordered_array {}); + for (auto lib : this->i_libraries) { + auto lib_table = lib->serialize(); + libraries_array.push_back(lib_table); + } + config_table["libraries"] = libraries_array; + + auto configContent = toml::format(config_table); + + auto config_dir = QDir {this->i_configDirPath}; + if (!config_dir.exists()) config_dir.mkpath("."); + auto config_file = QFile {this->i_configPath}; + + auto out = QTextStream {&config_file}; + if (config_file.open(QIODevice::WriteOnly | QIODevice::Text)) { + out << configContent.c_str(); + config_file.close(); + } +} diff --git a/desktop/config/config.hpp b/desktop/config/config.hpp new file mode 100644 index 0000000..1acb3ce --- /dev/null +++ b/desktop/config/config.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +#include "library.hpp" +#include "ui_prefs.hpp" + +class KotoConfig { + public: + KotoConfig(); + static KotoConfig& instance(); + static KotoConfig* create() { return &instance(); } + void save(); + + QString getConfigDirPath(); + QList getLibraries(); + KotoUiPreferences* getUiPreferences(); + + private: + void bootstrap(); + void parseConfigFile(std::string filePath); + + QString i_configDirPath; + QString i_configPath; + QList i_libraries; + KotoUiPreferences* i_uiPreferences; +}; diff --git a/desktop/config/library.cpp b/desktop/config/library.cpp new file mode 100644 index 0000000..3022366 --- /dev/null +++ b/desktop/config/library.cpp @@ -0,0 +1,60 @@ +#include "library.hpp" + +#include +#include + +KotoLibraryConfig::KotoLibraryConfig(std::string name, fs::path path, KotoLibraryType type) { + this->i_name = name; + this->i_path = path; + this->i_type = type; + qDebug() << "Library: " << this->i_name.c_str() << " at " << this->i_path.c_str(); +} + +KotoLibraryConfig::~KotoLibraryConfig() {} + +KotoLibraryConfig::KotoLibraryConfig(const toml::value& v) { + this->i_name = toml::find(v, "name"); + this->i_path = toml::find(v, "path"); + this->i_type = libraryTypeFromString(toml::find(v, "type")); +} + +std::string KotoLibraryConfig::getName() { + return this->i_name; +} + +fs::path KotoLibraryConfig::getPath() { + return this->i_path; +} + +KotoLibraryType KotoLibraryConfig::getType() { + return this->i_type; +} + +toml::ordered_value KotoLibraryConfig::serialize() { + toml::ordered_value library_table(toml::ordered_table {}); + library_table["name"] = this->i_name; + library_table["path"] = this->i_path.string(); + auto stringifiedType = libraryTypeToString(this->i_type); + library_table["type"] = stringifiedType; + return library_table; +} + +std::string libraryTypeToString(KotoLibraryType type) { + switch (type) { + case KotoLibraryType::Audiobooks: + return std::string {"audiobooks"}; + case KotoLibraryType::Music: + return std::string {"music"}; + case KotoLibraryType::Podcasts: + return std::string {"podcasts"}; + default: + return std::string {"unknown"}; + } +} + +KotoLibraryType libraryTypeFromString(const std::string& type) { + if (type == "audiobooks") return KotoLibraryType::Audiobooks; + if (type == "music") return KotoLibraryType::Music; + if (type == "podcasts") return KotoLibraryType::Podcasts; + throw std::invalid_argument("Unknown KotoLibraryType: " + type); +} diff --git a/desktop/config/library.hpp b/desktop/config/library.hpp new file mode 100644 index 0000000..bfbb16c --- /dev/null +++ b/desktop/config/library.hpp @@ -0,0 +1,32 @@ +#pragma once +#include +#include + +#include "includes/toml.hpp" + +namespace fs = std::filesystem; + +enum class KotoLibraryType { + Audiobooks, + Music, + Podcasts, +}; + +KotoLibraryType libraryTypeFromString(const std::string& type); +std::string libraryTypeToString(KotoLibraryType type); + +class KotoLibraryConfig { + public: + KotoLibraryConfig(std::string name, fs::path path, KotoLibraryType type); + KotoLibraryConfig(const toml::value& v); + ~KotoLibraryConfig(); + std::string getName(); + fs::path getPath(); + KotoLibraryType getType(); + toml::ordered_value serialize(); + + private: + std::string i_name; + fs::path i_path; + KotoLibraryType i_type; +}; diff --git a/desktop/config/ui_prefs.cpp b/desktop/config/ui_prefs.cpp new file mode 100644 index 0000000..24bad2f --- /dev/null +++ b/desktop/config/ui_prefs.cpp @@ -0,0 +1,71 @@ +#include "ui_prefs.hpp" + +KotoUiPreferences::KotoUiPreferences() + : i_albumInfoShowDescription(true), i_albumInfoShowGenre(true), i_albumInfoShowNarrator(true), i_albumInfoShowYear(true), i_lastUsedVolume(0.5) {} + +KotoUiPreferences::KotoUiPreferences(std::optional v) { + // No UI prefs provided + if (!v.has_value()) return; + toml::value& uiPrefs = v.value(); + auto showDescription = toml::find_or(uiPrefs, "album_info_show_description", false); + auto showGenre = toml::find_or(uiPrefs, "album_info_show_genre", false); + auto showNarrator = toml::find_or(uiPrefs, "album_info_show_narrator", false); + auto showYear = toml::find_or(uiPrefs, "album_info_show_year", false); + auto lastUsedVolume = toml::find_or(uiPrefs, "last_used_volume", 0.5); + + this->setAlbumInfoShowDescription(showDescription); + this->setAlbumInfoShowGenre(showGenre); + this->setAlbumInfoShowNarrator(showNarrator); + this->setAlbumInfoShowYear(showYear); + this->setLastUsedVolume(lastUsedVolume); +} + +bool KotoUiPreferences::getAlbumInfoShowDescription() { + return this->i_albumInfoShowDescription; +} + +bool KotoUiPreferences::getAlbumInfoShowGenre() { + return this->i_albumInfoShowGenre; +} + +bool KotoUiPreferences::getAlbumInfoShowNarrator() { + return this->i_albumInfoShowNarrator; +} + +bool KotoUiPreferences::getAlbumInfoShowYear() { + return this->i_albumInfoShowYear; +} + +float KotoUiPreferences::getLastUsedVolume() { + return this->i_lastUsedVolume; +} + +toml::ordered_value KotoUiPreferences::serialize() { + toml::ordered_value ui_prefs_table(toml::ordered_table {}); + ui_prefs_table["album_info_show_description"] = this->i_albumInfoShowDescription; + ui_prefs_table["album_info_show_genre"] = this->i_albumInfoShowGenre; + ui_prefs_table["album_info_show_narrator"] = this->i_albumInfoShowNarrator; + ui_prefs_table["album_info_show_year"] = this->i_albumInfoShowYear; + ui_prefs_table["last_used_volume"] = this->i_lastUsedVolume; + return ui_prefs_table; +} + +void KotoUiPreferences::setAlbumInfoShowDescription(bool show) { + this->i_albumInfoShowDescription = show; +} + +void KotoUiPreferences::setAlbumInfoShowGenre(bool show) { + this->i_albumInfoShowGenre = show; +} + +void KotoUiPreferences::setAlbumInfoShowNarrator(bool show) { + this->i_albumInfoShowNarrator = show; +} + +void KotoUiPreferences::setAlbumInfoShowYear(bool show) { + this->i_albumInfoShowYear = show; +} + +void KotoUiPreferences::setLastUsedVolume(float volume) { + this->i_lastUsedVolume = volume; +} diff --git a/desktop/config/ui_prefs.hpp b/desktop/config/ui_prefs.hpp new file mode 100644 index 0000000..a5a5978 --- /dev/null +++ b/desktop/config/ui_prefs.hpp @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include + +#include "includes/toml.hpp" + +class KotoUiPreferences { + public: + KotoUiPreferences(); + KotoUiPreferences(std::optional v); + ~KotoUiPreferences(); + + bool getAlbumInfoShowDescription(); + bool getAlbumInfoShowGenre(); + bool getAlbumInfoShowNarrator(); + bool getAlbumInfoShowYear(); + float getLastUsedVolume(); + + toml::ordered_value serialize(); + + void setAlbumInfoShowDescription(bool show); + void setAlbumInfoShowGenre(bool show); + void setAlbumInfoShowNarrator(bool show); + void setAlbumInfoShowYear(bool show); + void setLastUsedVolume(float volume); + + private: + bool i_albumInfoShowDescription; + bool i_albumInfoShowGenre; + bool i_albumInfoShowNarrator; + bool i_albumInfoShowYear; + float i_lastUsedVolume; +}; diff --git a/desktop/datalake/album.cpp b/desktop/datalake/album.cpp new file mode 100644 index 0000000..3e80def --- /dev/null +++ b/desktop/datalake/album.cpp @@ -0,0 +1,119 @@ +#include + +#include "database.hpp" +#include "structs.hpp" + +KotoAlbum::KotoAlbum() { + this->uuid = QUuid::createUuid(); + this->tracks = QList(); +} + +KotoAlbum* KotoAlbum::fromDb(const QSqlQuery& query, const QSqlRecord& record) { + KotoAlbum* album = new KotoAlbum(); + album->uuid = QUuid {query.value(record.indexOf("id")).toString()}; + album->artist_uuid = QUuid {query.value(record.indexOf("artist_id")).toString()}; + album->title = QString {query.value(record.indexOf("name")).toString()}; + album->year = query.value(record.indexOf("year")).toInt(); + album->description = QString {query.value(record.indexOf("description")).toString()}; + album->narrator = QString {query.value(record.indexOf("narrator")).toString()}; + album->album_art_path = QString {query.value(record.indexOf("art_path")).toString()}; + album->genres = QList {query.value(record.indexOf("genres")).toString().split(", ")}; + return album; +} + +KotoAlbum::~KotoAlbum() { + for (auto track : this->tracks) { delete track; } + this->tracks.clear(); +} + +void KotoAlbum::addTrack(KotoTrack* track) { + this->tracks.append(track); +} + +void KotoAlbum::commit() { + QSqlQuery query(KotoDatabase::instance().getDatabase()); + + query.prepare( + "INSERT INTO albums(id, artist_id, name, description, narrator, art_path, genres, year) " + "VALUES (:id, :artist_id, :name, :description, :narrator, :art_path, :genres, :year) " + "ON CONFLICT(id) DO UPDATE SET artist_id = :artist_id, name = :name, description = :description, narrator = :narrator, art_path = " + ":art_path, genres = :genres, year = :year"); + + query.bindValue(":id", this->uuid.toString()); + query.bindValue(":artist_id", this->artist_uuid.toString()); + query.bindValue(":name", this->title); + query.bindValue(":year", this->year.value_or(NULL)); + query.bindValue(":description", this->description); + query.bindValue(":art_path", this->album_art_path); + query.bindValue(":narrator", this->narrator); + query.bindValue(":genres", this->genres.join(", ")); + query.exec(); +} + +QString KotoAlbum::getAlbumArtPath() { + return QString {this->album_art_path}; +} + +QString KotoAlbum::getDescription() { + return QString {this->description}; +} + +QList KotoAlbum::getGenres() { + return QList {this->genres}; +} + +QString KotoAlbum::getPath() { + return this->path; +} + +QString KotoAlbum::getNarrator() { + return QString {this->narrator}; +} + +QString KotoAlbum::getTitle() { + return QString {this->title}; +} + +QList KotoAlbum::getTracks() { + return QList {this->tracks}; +} + +std::optional KotoAlbum::getYear() { + return this->year; +} + +int KotoAlbum::getYearQml() { + return this->year.value_or(0); +} + +void KotoAlbum::removeTrack(KotoTrack* track) { + this->tracks.removeOne(track); +} + +void KotoAlbum::setAlbumArtPath(QString str) { + this->album_art_path = QString {path}; +} + +void KotoAlbum::setDescription(QString str) { + this->description = QString {str}; +} + +void KotoAlbum::setGenres(QList list) { + this->genres = QList {list}; +} + +void KotoAlbum::setNarrator(QString str) { + this->narrator = QString {str}; +} + +void KotoAlbum::setPath(QString str) { + this->path = QString {str}; +} + +void KotoAlbum::setTitle(QString str) { + this->title = QString {str}; +} + +void KotoAlbum::setYear(int num) { + this->year = num; +} diff --git a/desktop/datalake/artist.cpp b/desktop/datalake/artist.cpp new file mode 100644 index 0000000..45dd72f --- /dev/null +++ b/desktop/datalake/artist.cpp @@ -0,0 +1,91 @@ +#include + +#include "database.hpp" +#include "structs.hpp" + +KotoArtist::KotoArtist() { + this->uuid = QUuid::createUuid(); +} + +KotoArtist* KotoArtist::fromDb(const QSqlQuery& query, const QSqlRecord& record) { + KotoArtist* artist = new KotoArtist(); + artist->uuid = QUuid {query.value(record.indexOf("id")).toString()}; + artist->name = QString {query.value(record.indexOf("name")).toString()}; + artist->path = QString {query.value(record.indexOf("art_path")).toString()}; + return artist; +} + +KotoArtist::~KotoArtist() { + for (auto album : this->albums) { delete album; } + for (auto track : this->tracks) { delete track; } + this->albums.clear(); + this->tracks.clear(); +} + +void KotoArtist::addAlbum(KotoAlbum* album) { + this->albums.append(album); +} + +void KotoArtist::addTrack(KotoTrack* track) { + this->tracks.append(track); + if (!track->album_uuid.has_value()) return; + for (auto album : this->albums) { + if (album->uuid == track->album_uuid.value()) { + album->addTrack(track); + return; + } + } +} + +void KotoArtist::commit() { + QSqlQuery query(KotoDatabase::instance().getDatabase()); + query.prepare("INSERT INTO artists(id, name, art_path) VALUES (:id, :name, :art_path) ON CONFLICT(id) DO UPDATE SET name = :name, art_path = :art_path"); + query.bindValue(":id", this->uuid.toString()); + query.bindValue(":name", this->name); + query.bindValue(":art_path", this->path); + query.exec(); +} + +QList KotoArtist::getAlbums() { + return QList {this->albums}; +} + +std::optional KotoArtist::getAlbumByName(QString name) { + for (auto album : this->albums) { + if (album->getTitle().contains(name)) { return std::optional {album}; } + } + + return std::nullopt; +} + +QString KotoArtist::getName() { + return QString {this->name}; +} + +QString KotoArtist::getPath() { + return QString {this->path}; +} + +QList KotoArtist::getTracks() { + return QList {this->tracks}; +} + +QUuid KotoArtist::getUuid() { + return this->uuid; +} + +void KotoArtist::removeAlbum(KotoAlbum* album) { + this->albums.removeOne(album); +} + +void KotoArtist::removeTrack(KotoTrack* track) { + this->tracks.removeOne(track); +} + +void KotoArtist::setName(QString str) { + this->name = QString {str}; +} + +void KotoArtist::setPath(QString str) { + this->path = QString {str}; +} diff --git a/desktop/datalake/cartographer.cpp b/desktop/datalake/cartographer.cpp new file mode 100644 index 0000000..7f47b5e --- /dev/null +++ b/desktop/datalake/cartographer.cpp @@ -0,0 +1,63 @@ +#include "cartographer.hpp" + +#include + +Cartographer::Cartographer(QObject* parent) + : QObject(parent), + i_albums(QHash()), + i_artists_model(new KotoArtistModel(QList())), + i_artists_by_name(QHash()), + i_tracks(QHash()) {} + +Cartographer& Cartographer::instance() { + static Cartographer _instance(nullptr); + return _instance; +} + +void Cartographer::addAlbum(KotoAlbum* album) { + this->i_albums.insert(album->uuid, album); +} + +void Cartographer::addArtist(KotoArtist* artist) { + this->i_artists_model->addArtist(artist); + this->i_artists_by_name.insert(artist->getName(), artist); +} + +void Cartographer::addTrack(KotoTrack* track) { + this->i_tracks.insert(track->uuid, track); +} + +std::optional Cartographer::getAlbum(QUuid uuid) { + auto album = this->i_albums.value(uuid, nullptr); + return album ? std::optional {album} : std::nullopt; +} + +std::optional Cartographer::getArtist(QUuid uuid) { + for (auto artist : this->i_artists_model->getArtists()) { + if (artist->uuid == uuid) { return std::optional {artist}; } + } + return std::nullopt; +} + +QList Cartographer::getArtists() { + return this->i_artists_model->getArtists(); +} + +KotoArtistModel* Cartographer::getArtistsModel() { + // if (this->i_artists_model == nullptr) { this->i_artists_model = new KotoArtistModel(this->i_artists); } + return this->i_artists_model; +} + +std::optional Cartographer::getArtist(QString name) { + auto artist = this->i_artists_by_name.value(name, nullptr); + return artist ? std::optional {artist} : std::nullopt; +} + +std::optional Cartographer::getTrack(QUuid uuid) { + auto track = this->i_tracks.value(uuid, nullptr); + return track ? std::optional {track} : std::nullopt; +} + +QList Cartographer::getTracks() { + return this->i_tracks.values(); +} diff --git a/desktop/datalake/cartographer.hpp b/desktop/datalake/cartographer.hpp new file mode 100644 index 0000000..a7b99fb --- /dev/null +++ b/desktop/datalake/cartographer.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "structs.hpp" + +class Cartographer : public QObject { + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + // Q_PROPERTY(QQmlListProperty albums READ getAlbumsQml) + Q_PROPERTY(KotoArtistModel* artists READ getArtistsModel CONSTANT) + // Q_PROPERTY(QQmlListProperty tracks READ getTracksQml) + + public: + Cartographer(QObject* parent); + static Cartographer& instance(); + static Cartographer* create(QQmlEngine*, QJSEngine*) { + QQmlEngine::setObjectOwnership(&instance(), QQmlEngine::CppOwnership); + return &instance(); + } + + void addAlbum(KotoAlbum* album); + void addArtist(KotoArtist* artist); + void addTrack(KotoTrack* track); + + // QQmlListProperty getAlbumsQml(); + KotoArtistModel* getArtistsModel(); + // QQmlListProperty getTracksQml(); + + std::optional getAlbum(QUuid uuid); + QList getAlbums(); + std::optional getArtist(QUuid uuid); + QList getArtists(); + std::optional getArtist(QString name); + std::optional getTrack(QUuid uuid); + QList getTracks(); + + private: + QHash i_albums; + KotoArtistModel* i_artists_model; + QHash i_artists_by_name; + QHash i_tracks; +}; diff --git a/desktop/datalake/database.cpp b/desktop/datalake/database.cpp new file mode 100644 index 0000000..f470ccd --- /dev/null +++ b/desktop/datalake/database.cpp @@ -0,0 +1,102 @@ +#include "database.hpp" + +#include +#include +#include +#include +#include + +#include "cartographer.hpp" +#include "config/config.hpp" + +KotoDatabase::KotoDatabase() { + QString dbPath = QDir(KotoConfig::instance().getConfigDirPath()).filePath("koto.db"); + this->shouldBootstrap = !QFileInfo::exists(dbPath); + + this->db = QSqlDatabase::addDatabase("QSQLITE"); + std::cout << "Database path: " << dbPath.toStdString() << std::endl; + + this->db.setDatabaseName(dbPath); +} + +KotoDatabase& KotoDatabase::instance() { + static KotoDatabase _instance; + return _instance; +} + +void KotoDatabase::connect() { + if (!this->db.open()) { + std::cerr << "Failed to open database" << std::endl; + QCoreApplication::quit(); + } + + if (this->shouldBootstrap) this->bootstrap(); +} + +void KotoDatabase::disconnect() { + this->db.close(); +} + +QSqlDatabase KotoDatabase::getDatabase() { + return this->db; +} + +bool KotoDatabase::requiredBootstrap() { + return this->shouldBootstrap; +} + +void KotoDatabase::bootstrap() { + QSqlQuery query(this->db); + + query.exec("CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);"); + query.exec( + "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, description string, narrator string, art_path string, " + "genres strings, year int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);"); + query.exec( + "CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id string, name string, disc int, position int, duration int, " + "genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE, FOREIGN KEY (album_id) REFERENCES albums(id) ON DELETE CASCADE);"); + query.exec( + "CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES " + "albums(id) " + "ON DELETE CASCADE);"); + query.exec( + "CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES " + "artists(id) ON DELETE CASCADE);"); + query.exec( + "CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES " + "tracks(id) " + "ON DELETE CASCADE);"); + query.exec( + "CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int, album_id string, track_id " + "string, " + "playback_position_of_track int);"); + query.exec( + "CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, FOREIGN KEY(playlist_id) " + "REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);"); +} + +void KotoDatabase::load() { + QSqlQuery query(this->db); + + query.exec("SELECT * FROM artists;"); + while (query.next()) { + KotoArtist* artist = KotoArtist::fromDb(query, query.record()); + Cartographer::instance().addArtist(artist); + } + + query.exec("SELECT * FROM albums;"); + while (query.next()) { + KotoAlbum* album = KotoAlbum::fromDb(query, query.record()); + auto artist = Cartographer::instance().getArtist(album->artist_uuid); + if (artist.has_value()) { artist.value()->addAlbum(album); } + Cartographer::instance().addAlbum(album); + } + + query.exec("SELECT * FROM tracks;"); + while (query.next()) { + KotoTrack* track = KotoTrack::fromDb(query, query.record()); + auto artist = Cartographer::instance().getArtist(track->artist_uuid); + if (artist.has_value()) { artist.value()->addTrack(track); } + Cartographer::instance().addTrack(track); + } +} diff --git a/desktop/datalake/database.hpp b/desktop/datalake/database.hpp new file mode 100644 index 0000000..b5df848 --- /dev/null +++ b/desktop/datalake/database.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +class KotoDatabase { + public: + KotoDatabase(); + static KotoDatabase& instance(); + static KotoDatabase* create() { return &instance(); } + + void connect(); + void disconnect(); + QSqlDatabase getDatabase(); + void load(); + bool requiredBootstrap(); + + private: + void bootstrap(); + bool shouldBootstrap; + QSqlDatabase db; +}; diff --git a/desktop/datalake/indexer.cpp b/desktop/datalake/indexer.cpp new file mode 100644 index 0000000..00cdfdb --- /dev/null +++ b/desktop/datalake/indexer.cpp @@ -0,0 +1,88 @@ +#include "indexer.hpp" + +#include +#include +#include +#include +#include + +FileIndexer::FileIndexer(KotoLibraryConfig* config) { + this->i_root = QString {config->getPath().c_str()}; +} + +FileIndexer::~FileIndexer() = default; + +void FileIndexer::index() { + QMimeDatabase db; + KFileMetaData::ExtractorCollection extractors; + + QStringList root_dirs {this->i_root.split(QDir::separator())}; + + QDirIterator it {this->i_root, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, QDirIterator::Subdirectories}; + + while (it.hasNext()) { + QString path = it.next(); + QFileInfo info {path}; + + if (info.isDir()) { + auto diffPath = info.dir().relativeFilePath(this->i_root); + auto diffDirs = diffPath.split(".."); + auto diffDirsSize = diffDirs.size() - 1; + + // This is going to be an artist + if (diffDirsSize == 0) { + auto artist = new KotoArtist(); + artist->setName(info.fileName()); + artist->setPath(path); + artist->commit(); + this->i_artists.append(artist); + Cartographer::instance().addArtist(artist); + continue; + } else if (diffDirsSize == 1) { + auto album = new KotoAlbum(); + album->setTitle(info.fileName()); + + auto artistDir = QDir(info.dir()); + auto artistName = artistDir.dirName(); + auto artistOptional = Cartographer::instance().getArtist(artistName); + + if (artistOptional.has_value()) { + auto artist = artistOptional.value(); + album->artist_uuid = artist->uuid; + artist->addAlbum(album); + } + + album->commit(); + Cartographer::instance().addAlbum(album); + continue; + } + } + + // This is a file + QMimeType mime = db.mimeTypeForFile(info); + if (mime.name().startsWith("audio/")) { + auto extractorList = extractors.fetchExtractors(mime.name()); + if (extractorList.isEmpty()) { continue; } + + auto result = KFileMetaData::SimpleExtractionResult(path, mime.name(), KFileMetaData::ExtractionResult::ExtractMetaData); + extractorList.first()->extract(&result); + + if (!result.types().contains(KFileMetaData::Type::Audio)) { continue; } + + auto track = KotoTrack::fromMetadata(result, info); + + this->i_tracks.append(track); + track->commit(); + Cartographer::instance().addTrack(track); + } else if (mime.name().startsWith("image/")) { + // This is an image, TODO add cover art to album + } + } +} + +void indexAllLibraries() { + for (auto library : KotoConfig::instance().getLibraries()) { + auto indexer = new FileIndexer(library); + indexer->index(); + } +} diff --git a/desktop/datalake/indexer.hpp b/desktop/datalake/indexer.hpp new file mode 100644 index 0000000..4ae06da --- /dev/null +++ b/desktop/datalake/indexer.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "cartographer.hpp" +#include "config/config.hpp" +#include "structs.hpp" + +class FileIndexer { + public: + FileIndexer(KotoLibraryConfig* config); + ~FileIndexer(); + + QList getArtists(); + QList getFiles(); + QString getRoot(); + + void index(); + + protected: + QList i_artists; + QList i_tracks; + QString i_root; +}; + +void indexAllLibraries(); diff --git a/desktop/datalake/models.cpp b/desktop/datalake/models.cpp new file mode 100644 index 0000000..ade5f64 --- /dev/null +++ b/desktop/datalake/models.cpp @@ -0,0 +1,49 @@ +#include "structs.hpp" + +KotoArtistModel::KotoArtistModel(const QList& artists, QObject* parent) : QAbstractListModel(parent), m_artists(artists) {} + +KotoArtistModel::~KotoArtistModel() { + this->beginResetModel(); + this->m_artists.clear(); + this->endResetModel(); +} + +void KotoArtistModel::addArtist(KotoArtist* artist) { + this->beginInsertRows(QModelIndex(), this->m_artists.count(), this->m_artists.count()); + this->m_artists.append(artist); + this->endInsertRows(); +} + +int KotoArtistModel::rowCount(const QModelIndex& parent) const { + return this->m_artists.count(); +} + +QVariant KotoArtistModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) { return {}; } + + if (index.row() >= this->m_artists.size()) { return {}; } + + if (role == Qt::DisplayRole) { + return this->m_artists.at(index.row())->getName(); + } else if (role == KotoArtistRoles::NameRole) { + return this->m_artists.at(index.row())->getName(); + } else if (role == KotoArtistRoles::PathRole) { + return this->m_artists.at(index.row())->getPath(); + } else if (role == KotoArtistRoles::UuidRole) { + return this->m_artists.at(index.row())->uuid; + } else { + return {}; + } +} + +QList KotoArtistModel::getArtists() { + return this->m_artists; +} + +QHash KotoArtistModel::roleNames() const { + QHash roles; + roles[NameRole] = QByteArrayLiteral("name"); + roles[PathRole] = QByteArrayLiteral("path"); + roles[UuidRole] = QByteArrayLiteral("uuid"); + return roles; +} diff --git a/desktop/datalake/structs.hpp b/desktop/datalake/structs.hpp new file mode 100644 index 0000000..07d18ab --- /dev/null +++ b/desktop/datalake/structs.hpp @@ -0,0 +1,227 @@ +#pragma once +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +class KotoArtist; +class KotoArtistModel; +class KotoAlbum; +class KotoAlbumModel; +class KotoTrack; +class KotoTrackModel; + +class KotoArtist : public QObject { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString path READ getPath WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QList albums READ getAlbums NOTIFY albumsChanged) + Q_PROPERTY(QList tracks READ getTracks NOTIFY tracksChanged) + Q_PROPERTY(QUuid uuid READ getUuid) + + public: + KotoArtist(); + static KotoArtist* fromDb(const QSqlQuery& query, const QSqlRecord& record); + virtual ~KotoArtist(); + + QUuid uuid; + + void addAlbum(KotoAlbum* album); + void addTrack(KotoTrack* track); + void commit(); + QList getAlbums(); + std::optional getAlbumByName(QString name); + QString getName(); + QString getPath(); + QList getTracks(); + QUuid getUuid(); + void removeAlbum(KotoAlbum* album); + void removeTrack(KotoTrack* track); + void setName(QString str); + void setPath(QString str); + + signals: + void albumsChanged(); + void nameChanged(); + void pathChanged(); + void tracksChanged(); + + private: + QString path; + QString name; + + QList albums; + QList tracks; +}; + +class KotoArtistModel : public QAbstractListModel { + Q_OBJECT + + public: + explicit KotoArtistModel(const QList& artists, QObject* parent = nullptr); + + void addArtist(KotoArtist* artist); + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + virtual ~KotoArtistModel(); + + QList getArtists(); + + enum KotoArtistRoles { + NameRole = Qt::UserRole + 1, + PathRole, + UuidRole, + }; + + private: + QList m_artists; +}; + +class KotoAlbum : public QObject { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString albumArtPath READ getAlbumArtPath WRITE setAlbumArtPath NOTIFY albumArtChanged) + Q_PROPERTY(QString description READ getDescription WRITE setDescription NOTIFY descriptionChanged) + Q_PROPERTY(QList genres READ getGenres WRITE setGenres NOTIFY genresChanged) + Q_PROPERTY(QString narrator READ getNarrator WRITE setNarrator NOTIFY narratorChanged) + Q_PROPERTY(QString path READ getPath WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(QList tracks READ getTracks NOTIFY tracksChanged) + Q_PROPERTY(int year READ getYearQml WRITE setYear NOTIFY yearChanged) + + public: + KotoAlbum(); + static KotoAlbum* fromDb(const QSqlQuery& query, const QSqlRecord& record); + virtual ~KotoAlbum(); + + QUuid uuid; + QUuid artist_uuid; + + void commit(); + QString getAlbumArtPath(); + QString getDescription(); + QList getGenres(); + QString getNarrator(); + QString getPath(); + QString getTitle(); + QList getTracks(); + std::optional getYear(); + int getYearQml(); + + void addTrack(KotoTrack* track); + void removeTrack(KotoTrack* track); + void setAlbumArtPath(QString str); + void setDescription(QString str); + void setGenres(QList list); + void setNarrator(QString str); + void setPath(QString str); + void setTitle(QString str); + void setYear(int num); + + signals: + void albumArtChanged(); + void descriptionChanged(); + void genresChanged(); + void narratorChanged(); + void pathChanged(); + void titleChanged(); + void tracksChanged(); + void yearChanged(); + + private: + QString title; + QString description; + QString narrator; + std::optional year; + + QList genres; + QList tracks; + + QString path; + QString album_art_path; +}; + +class KotoTrack : public QObject { + Q_OBJECT + QML_ELEMENT + Q_PROPERTY(QString album_uuid READ getAlbumUuid NOTIFY albumChanged) + Q_PROPERTY(QUuid artist_uuid READ getArtistUuid NOTIFY artistChanged) + Q_PROPERTY(QUuid uuid READ getUuid) + Q_PROPERTY(int discNumber READ getDiscNumber WRITE setDiscNumber NOTIFY discNumberChanged) + Q_PROPERTY(int duration READ getDuration WRITE setDuration NOTIFY durationChanged) + Q_PROPERTY(QStringList genres READ getGenres WRITE setGenres NOTIFY genresChanged) + Q_PROPERTY(QString lyrics READ getLyrics WRITE setLyrics NOTIFY lyricsChanged) + Q_PROPERTY(QString narrator READ getNarrator WRITE setNarrator NOTIFY narratorChanged) + Q_PROPERTY(QString path READ getPath WRITE setPath NOTIFY pathChanged) + Q_PROPERTY(QString title READ getTitle WRITE setTitle NOTIFY titleChanged) + Q_PROPERTY(int trackNumber READ getTrackNumber WRITE setTrackNumber NOTIFY trackNumberChanged) + Q_PROPERTY(int year READ getYear WRITE setYear NOTIFY yearChanged) + + public: + KotoTrack(); // No-op constructor + static KotoTrack* fromDb(const QSqlQuery& query, const QSqlRecord& record); + static KotoTrack* fromMetadata(const KFileMetaData::SimpleExtractionResult& metadata, const QFileInfo& info); + virtual ~KotoTrack(); + + std::optional album_uuid; + QUuid artist_uuid; + QUuid uuid; + + void commit(); + QString getAlbumUuid(); + QUuid getArtistUuid(); + int getDiscNumber(); + int getDuration(); + QStringList getGenres(); + QString getLyrics(); + QString getNarrator(); + QString getPath(); + QString getTitle(); + int getTrackNumber(); + QUuid getUuid(); + int getYear(); + + void setAlbum(KotoAlbum* album); + void setArtist(KotoArtist* artist); + void setDiscNumber(int num); + void setDuration(int num); + void setGenres(QList list); + void setLyrics(QString str); + void setNarrator(QString str); + void setPath(QString path); + void setTitle(QString str); + void setTrackNumber(int num); + void setYear(int num); + + signals: + void albumChanged(); + void artistChanged(); + void discNumberChanged(); + void durationChanged(); + void genresChanged(); + void lyricsChanged(); + void narratorChanged(); + void pathChanged(); + void titleChanged(); + void trackNumberChanged(); + void yearChanged(); + + private: + int disc_number; + int duration; + QStringList genres; + QString lyrics; + QString narrator; + QString path; + QString title; + int track_number; + int year; +}; diff --git a/desktop/datalake/track.cpp b/desktop/datalake/track.cpp new file mode 100644 index 0000000..5e5ee29 --- /dev/null +++ b/desktop/datalake/track.cpp @@ -0,0 +1,198 @@ +#include +#include + +#include "cartographer.hpp" +#include "database.hpp" +#include "structs.hpp" + +KotoTrack::KotoTrack() { + this->uuid = QUuid::createUuid(); +} + +KotoTrack* KotoTrack::fromDb(const QSqlQuery& query, const QSqlRecord& record) { + KotoTrack* track = new KotoTrack(); + track->uuid = QUuid {query.value(record.indexOf("id")).toString()}; + auto artist_id = query.value(record.indexOf("artist_id")); + if (!artist_id.isNull()) { track->artist_uuid = QUuid {artist_id.toString()}; } + + auto album_id = query.value(record.indexOf("album_id")); + if (!album_id.isNull()) { track->album_uuid = QUuid {album_id.toString()}; } + + track->title = QString {query.value(record.indexOf("name")).toString()}; + track->disc_number = query.value(record.indexOf("disc")).toInt(); + track->track_number = query.value(record.indexOf("position")).toInt(); + track->duration = query.value(record.indexOf("duration")).toInt(); + track->genres = QList {query.value(record.indexOf("genres")).toString().split(", ")}; + return track; +} + +KotoTrack* KotoTrack::fromMetadata(const KFileMetaData::SimpleExtractionResult& metadata, const QFileInfo& info) { + auto props = metadata.properties(); + KotoTrack* track = new KotoTrack(); + track->disc_number = props.value(KFileMetaData::Property::DiscNumber, 0).toInt(); + track->duration = props.value(KFileMetaData::Property::Duration, 0).toInt(); + + QStringList genres; + for (auto v : props.values(KFileMetaData::Property::Genre)) { genres.append(v.toString()); } + + track->genres = genres; + + track->lyrics = props.value(KFileMetaData::Property::Lyrics).toString(); + track->narrator = props.value(KFileMetaData::Property::Performer).toString(); + track->path = info.absolutePath(); + track->track_number = props.value(KFileMetaData::Property::TrackNumber, 0).toInt(); + track->year = props.value(KFileMetaData::Property::ReleaseYear, 0).toInt(); + + auto titleResult = props.value(KFileMetaData::Property::Title); + if (titleResult.isValid() && !titleResult.isNull()) { + track->title = titleResult.toString(); + } else { + // TODO: mirror the same logic we had for cleaning up file name to determine track name, position, chapter, artist, etc. + track->title = info.fileName(); + } + + auto artistResult = props.value(KFileMetaData::Property::Artist); + auto artistOptional = std::optional(); + if (artistResult.isValid()) { + artistOptional = Cartographer::instance().getArtist(artistResult.toString()); + + if (artistOptional.has_value()) { + auto artist = artistOptional.value(); + track->artist_uuid = QUuid(artist->uuid); + artist->addTrack(track); + } + } + + auto albumResult = props.value(KFileMetaData::Property::Album); + if (albumResult.isValid() && artistOptional.has_value()) { + auto artist = artistOptional.value(); + + auto albumMetaName = albumResult.toString(); + auto albumOptional = artist->getAlbumByName(albumMetaName); + + if (albumOptional.has_value()) { + auto album = albumOptional.value(); + track->album_uuid = QUuid(album->uuid); + album->addTrack(track); + if (album->getTitle() != albumMetaName) album->setTitle(albumMetaName); + } + } + + return track; +} + +KotoTrack::~KotoTrack() {} + +void KotoTrack::commit() { + QSqlQuery query(KotoDatabase::instance().getDatabase()); + query.prepare( + "INSERT INTO tracks(id, artist_id, album_id, name, disc, position, duration, genres) " + "VALUES (:id, :artist_id, :album_id, :name, :disc, :position, :duration, :genres) " + "ON CONFLICT(id) DO UPDATE SET artist_id = :artist_id, album_id = :album_id, name = :name, disc = :disc, position = :position, duration = :duration, " + "genres = :genres"); + query.bindValue(":id", this->uuid.toString()); + query.bindValue(":artist_id", !this->artist_uuid.isNull() ? this->artist_uuid.toString() : NULL); + query.bindValue(":album_id", this->album_uuid.has_value() ? this->album_uuid.value().toString() : NULL); + query.bindValue(":name", this->title); + query.bindValue(":disc", this->disc_number); + query.bindValue(":position", this->track_number); + query.bindValue(":duration", this->duration); + query.bindValue(":genres", this->genres.join(", ")); + + query.exec(); +} + +QString KotoTrack::getAlbumUuid() { + if (!this->album_uuid.has_value()) return this->album_uuid.value().toString(); + return {}; +} + +QUuid KotoTrack::getArtistUuid() { + return this->artist_uuid; +} + +int KotoTrack::getDiscNumber() { + return this->disc_number; +} + +int KotoTrack::getDuration() { + return this->duration; +} + +QList KotoTrack::getGenres() { + return QList {this->genres}; +} + +QString KotoTrack::getLyrics() { + return QString {this->lyrics}; +} + +QString KotoTrack::getNarrator() { + return QString {this->narrator}; +} + +QString KotoTrack::getPath() { + return QString {this->path}; +} + +QString KotoTrack::getTitle() { + return QString {this->title}; +} + +int KotoTrack::getTrackNumber() { + return this->track_number; +} + +QUuid KotoTrack::getUuid() { + return this->uuid; +} + +int KotoTrack::getYear() { + return this->year; +} + +void KotoTrack::setAlbum(KotoAlbum* album) { + this->album_uuid = QUuid(album->uuid); + + if (this->artist_uuid.isNull()) QUuid(album->artist_uuid); +} + +void KotoTrack::setArtist(KotoArtist* artist) { + this->artist_uuid = QUuid(artist->uuid); +} + +void KotoTrack::setDiscNumber(int num) { + this->disc_number = num; +} + +void KotoTrack::setDuration(int num) { + this->duration = num; +} + +void KotoTrack::setGenres(QList list) { + this->genres = QList {list}; +} + +void KotoTrack::setLyrics(QString str) { + this->lyrics = QString {str}; +} + +void KotoTrack::setNarrator(QString str) { + this->narrator = QString {str}; +} + +void KotoTrack::setPath(QString str) { + this->path = QString {str}; +} + +void KotoTrack::setTitle(QString str) { + this->title = QString {str}; +} + +void KotoTrack::setTrackNumber(int num) { + this->track_number = num; +} + +void KotoTrack::setYear(int num) { + this->year = num; +} diff --git a/desktop/example-config.toml b/desktop/example-config.toml new file mode 100644 index 0000000..84bdd49 --- /dev/null +++ b/desktop/example-config.toml @@ -0,0 +1,11 @@ +["preferences.ui"] +album_info_show_description = true +album_info_show_genre = true +album_info_show_narrator = true +album_info_show_year = true +last_used_volume = 0.5 + +[[libraries]] +name = "Music" +path = "/home/joshua/Music" +type = "music" \ No newline at end of file diff --git a/desktop/includes/toml.hpp b/desktop/includes/toml.hpp new file mode 100644 index 0000000..fe5695c --- /dev/null +++ b/desktop/includes/toml.hpp @@ -0,0 +1,17240 @@ +#ifndef TOML11_VERSION_HPP +#define TOML11_VERSION_HPP + +#define TOML11_VERSION_MAJOR 4 +#define TOML11_VERSION_MINOR 2 +#define TOML11_VERSION_PATCH 0 + +#ifndef __cplusplus +# error "__cplusplus is not defined" +#endif + +// Since MSVC does not define `__cplusplus` correctly unless you pass +// `/Zc:__cplusplus` when compiling, the workaround macros are added. +// +// The value of `__cplusplus` macro is defined in the C++ standard spec, but +// MSVC ignores the value, maybe because of backward compatibility. Instead, +// MSVC defines _MSVC_LANG that has the same value as __cplusplus defined in +// the C++ standard. So we check if _MSVC_LANG is defined before using `__cplusplus`. +// +// FYI: https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus?view=msvc-170 +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros?view=msvc-170 +// + +#if defined(_MSVC_LANG) && defined(_MSC_VER) && 190024210 <= _MSC_FULL_VER +# define TOML11_CPLUSPLUS_STANDARD_VERSION _MSVC_LANG +#else +# define TOML11_CPLUSPLUS_STANDARD_VERSION __cplusplus +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION < 201103L +# error "toml11 requires C++11 or later." +#endif + +#if ! defined(__has_include) +# define __has_include(x) 0 +#endif + +#if ! defined(__has_cpp_attribute) +# define __has_cpp_attribute(x) 0 +#endif + +#if ! defined(__has_builtin) +# define __has_builtin(x) 0 +#endif + +// hard to remember + +#ifndef TOML11_CXX14_VALUE +#define TOML11_CXX14_VALUE 201402L +#endif//TOML11_CXX14_VALUE + +#ifndef TOML11_CXX17_VALUE +#define TOML11_CXX17_VALUE 201703L +#endif//TOML11_CXX17_VALUE + +#ifndef TOML11_CXX20_VALUE +#define TOML11_CXX20_VALUE 202002L +#endif//TOML11_CXX20_VALUE + +#if defined(__cpp_char8_t) +# if __cpp_char8_t >= 201811L +# define TOML11_HAS_CHAR8_T 1 +# endif +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# define TOML11_HAS_STRING_VIEW 1 +# endif +#endif + +#ifndef TOML11_DISABLE_STD_FILESYSTEM +# if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# define TOML11_HAS_FILESYSTEM 1 +# endif +# endif +#endif + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# define TOML11_HAS_OPTIONAL 1 +# endif +#endif + +#if defined(TOML11_COMPILE_SOURCES) +# define TOML11_INLINE +#else +# define TOML11_INLINE inline +#endif + +namespace toml +{ + +inline const char* license_notice() noexcept +{ + return R"(The MIT License (MIT) + +Copyright (c) 2017-now Toru Niina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.)"; +} + +} // toml +#endif // TOML11_VERSION_HPP +#ifndef TOML11_FORMAT_HPP +#define TOML11_FORMAT_HPP + +#ifndef TOML11_FORMAT_FWD_HPP +#define TOML11_FORMAT_FWD_HPP + +#include +#include +#include + +#include +#include + +namespace toml +{ + +// toml types with serialization info + +enum class indent_char : std::uint8_t +{ + space, // use space + tab, // use tab + none // no indent +}; + +std::ostream& operator<<(std::ostream& os, const indent_char& c); +std::string to_string(const indent_char c); + +// ---------------------------------------------------------------------------- +// boolean + +struct boolean_format_info +{ + // nothing, for now +}; + +inline bool operator==(const boolean_format_info&, const boolean_format_info&) noexcept +{ + return true; +} +inline bool operator!=(const boolean_format_info&, const boolean_format_info&) noexcept +{ + return false; +} + +// ---------------------------------------------------------------------------- +// integer + +enum class integer_format : std::uint8_t +{ + dec = 0, + bin = 1, + oct = 2, + hex = 3, +}; + +std::ostream& operator<<(std::ostream& os, const integer_format f); +std::string to_string(const integer_format); + +struct integer_format_info +{ + integer_format fmt = integer_format::dec; + bool uppercase = true; // hex with uppercase + std::size_t width = 0; // minimal width (may exceed) + std::size_t spacer = 0; // position of `_` (if 0, no spacer) + std::string suffix = ""; // _suffix (library extension) +}; + +bool operator==(const integer_format_info&, const integer_format_info&) noexcept; +bool operator!=(const integer_format_info&, const integer_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// floating + +enum class floating_format : std::uint8_t +{ + defaultfloat = 0, + fixed = 1, // does not include exponential part + scientific = 2, // always include exponential part + hex = 3 // hexfloat extension +}; + +std::ostream& operator<<(std::ostream& os, const floating_format f); +std::string to_string(const floating_format); + +struct floating_format_info +{ + floating_format fmt = floating_format::defaultfloat; + std::size_t prec = 0; // precision (if 0, use the default) + std::string suffix = ""; // 1.0e+2_suffix (library extension) +}; + +bool operator==(const floating_format_info&, const floating_format_info&) noexcept; +bool operator!=(const floating_format_info&, const floating_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// string + +enum class string_format : std::uint8_t +{ + basic = 0, + literal = 1, + multiline_basic = 2, + multiline_literal = 3 +}; + +std::ostream& operator<<(std::ostream& os, const string_format f); +std::string to_string(const string_format); + +struct string_format_info +{ + string_format fmt = string_format::basic; + bool start_with_newline = false; +}; + +bool operator==(const string_format_info&, const string_format_info&) noexcept; +bool operator!=(const string_format_info&, const string_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// datetime + +enum class datetime_delimiter_kind : std::uint8_t +{ + upper_T = 0, + lower_t = 1, + space = 2, +}; +std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d); +std::string to_string(const datetime_delimiter_kind); + +struct offset_datetime_format_info +{ + datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T; + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] +}; + +bool operator==(const offset_datetime_format_info&, const offset_datetime_format_info&) noexcept; +bool operator!=(const offset_datetime_format_info&, const offset_datetime_format_info&) noexcept; + +struct local_datetime_format_info +{ + datetime_delimiter_kind delimiter = datetime_delimiter_kind::upper_T; + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] +}; + +bool operator==(const local_datetime_format_info&, const local_datetime_format_info&) noexcept; +bool operator!=(const local_datetime_format_info&, const local_datetime_format_info&) noexcept; + +struct local_date_format_info +{ + // nothing, for now +}; + +bool operator==(const local_date_format_info&, const local_date_format_info&) noexcept; +bool operator!=(const local_date_format_info&, const local_date_format_info&) noexcept; + +struct local_time_format_info +{ + bool has_seconds = true; + std::size_t subsecond_precision = 6; // [us] +}; + +bool operator==(const local_time_format_info&, const local_time_format_info&) noexcept; +bool operator!=(const local_time_format_info&, const local_time_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// array + +enum class array_format : std::uint8_t +{ + default_format = 0, + oneline = 1, + multiline = 2, + array_of_tables = 3 // [[format.in.this.way]] +}; + +std::ostream& operator<<(std::ostream& os, const array_format f); +std::string to_string(const array_format); + +struct array_format_info +{ + array_format fmt = array_format::default_format; + indent_char indent_type = indent_char::space; + std::int32_t body_indent = 4; // indent in case of multiline + std::int32_t closing_indent = 0; // indent of `]` +}; + +bool operator==(const array_format_info&, const array_format_info&) noexcept; +bool operator!=(const array_format_info&, const array_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// table + +enum class table_format : std::uint8_t +{ + multiline = 0, // [foo] \n bar = "baz" + oneline = 1, // foo = {bar = "baz"} + dotted = 2, // foo.bar = "baz" + multiline_oneline = 3, // foo = { \n bar = "baz" \n } + implicit = 4 // [x] defined by [x.y.z]. skip in serializer. +}; + +std::ostream& operator<<(std::ostream& os, const table_format f); +std::string to_string(const table_format); + +struct table_format_info +{ + table_format fmt = table_format::multiline; + indent_char indent_type = indent_char::space; + std::int32_t body_indent = 0; // indent of values + std::int32_t name_indent = 0; // indent of [table] + std::int32_t closing_indent = 0; // in case of {inline-table} +}; + +bool operator==(const table_format_info&, const table_format_info&) noexcept; +bool operator!=(const table_format_info&, const table_format_info&) noexcept; + +// ---------------------------------------------------------------------------- +// wrapper + +namespace detail +{ +template +struct value_with_format +{ + using value_type = T; + using format_type = F; + + value_with_format() = default; + ~value_with_format() = default; + value_with_format(const value_with_format&) = default; + value_with_format(value_with_format&&) = default; + value_with_format& operator=(const value_with_format&) = default; + value_with_format& operator=(value_with_format&&) = default; + + value_with_format(value_type v, format_type f) + : value{std::move(v)}, format{std::move(f)} + {} + + template + value_with_format(value_with_format other) + : value{std::move(other.value)}, format{std::move(other.format)} + {} + + value_type value; + format_type format; +}; +} // detail + +} // namespace toml +#endif // TOML11_FORMAT_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_FORMAT_IMPL_HPP +#define TOML11_FORMAT_IMPL_HPP + + +#include +#include + +namespace toml +{ + +// toml types with serialization info + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const indent_char& c) +{ + switch(c) + { + case indent_char::space: {os << "space" ; break;} + case indent_char::tab: {os << "tab" ; break;} + case indent_char::none: {os << "none" ; break;} + default: + { + os << "unknown indent char: " << static_cast(c); + } + } + return os; +} + +TOML11_INLINE std::string to_string(const indent_char c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +// ---------------------------------------------------------------------------- +// boolean + +// ---------------------------------------------------------------------------- +// integer + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const integer_format f) +{ + switch(f) + { + case integer_format::dec: {os << "dec"; break;} + case integer_format::bin: {os << "bin"; break;} + case integer_format::oct: {os << "oct"; break;} + case integer_format::hex: {os << "hex"; break;} + default: + { + os << "unknown integer_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const integer_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + + +TOML11_INLINE bool operator==(const integer_format_info& lhs, const integer_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.uppercase == rhs.uppercase && + lhs.width == rhs.width && + lhs.spacer == rhs.spacer && + lhs.suffix == rhs.suffix ; +} +TOML11_INLINE bool operator!=(const integer_format_info& lhs, const integer_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// floating + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const floating_format f) +{ + switch(f) + { + case floating_format::defaultfloat: {os << "defaultfloat"; break;} + case floating_format::fixed : {os << "fixed" ; break;} + case floating_format::scientific : {os << "scientific" ; break;} + case floating_format::hex : {os << "hex" ; break;} + default: + { + os << "unknown floating_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const floating_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const floating_format_info& lhs, const floating_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.prec == rhs.prec && + lhs.suffix == rhs.suffix ; +} +TOML11_INLINE bool operator!=(const floating_format_info& lhs, const floating_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// string + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const string_format f) +{ + switch(f) + { + case string_format::basic : {os << "basic" ; break;} + case string_format::literal : {os << "literal" ; break;} + case string_format::multiline_basic : {os << "multiline_basic" ; break;} + case string_format::multiline_literal: {os << "multiline_literal"; break;} + default: + { + os << "unknown string_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const string_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const string_format_info& lhs, const string_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.start_with_newline == rhs.start_with_newline ; +} +TOML11_INLINE bool operator!=(const string_format_info& lhs, const string_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} +// ---------------------------------------------------------------------------- +// datetime + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const datetime_delimiter_kind d) +{ + switch(d) + { + case datetime_delimiter_kind::upper_T: { os << "upper_T, "; break; } + case datetime_delimiter_kind::lower_t: { os << "lower_t, "; break; } + case datetime_delimiter_kind::space: { os << "space, "; break; } + default: + { + os << "unknown datetime delimiter: " << static_cast(d); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const datetime_delimiter_kind c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const offset_datetime_format_info& lhs, const offset_datetime_format_info& rhs) noexcept +{ + return lhs.delimiter == rhs.delimiter && + lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision ; +} +TOML11_INLINE bool operator!=(const offset_datetime_format_info& lhs, const offset_datetime_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +TOML11_INLINE bool operator==(const local_datetime_format_info& lhs, const local_datetime_format_info& rhs) noexcept +{ + return lhs.delimiter == rhs.delimiter && + lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision ; +} +TOML11_INLINE bool operator!=(const local_datetime_format_info& lhs, const local_datetime_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +TOML11_INLINE bool operator==(const local_date_format_info&, const local_date_format_info&) noexcept +{ + return true; +} +TOML11_INLINE bool operator!=(const local_date_format_info& lhs, const local_date_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +TOML11_INLINE bool operator==(const local_time_format_info& lhs, const local_time_format_info& rhs) noexcept +{ + return lhs.has_seconds == rhs.has_seconds && + lhs.subsecond_precision == rhs.subsecond_precision ; +} +TOML11_INLINE bool operator!=(const local_time_format_info& lhs, const local_time_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// array + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const array_format f) +{ + switch(f) + { + case array_format::default_format : {os << "default_format" ; break;} + case array_format::oneline : {os << "oneline" ; break;} + case array_format::multiline : {os << "multiline" ; break;} + case array_format::array_of_tables: {os << "array_of_tables"; break;} + default: + { + os << "unknown array_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const array_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const array_format_info& lhs, const array_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.indent_type == rhs.indent_type && + lhs.body_indent == rhs.body_indent && + lhs.closing_indent == rhs.closing_indent ; +} +TOML11_INLINE bool operator!=(const array_format_info& lhs, const array_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +// ---------------------------------------------------------------------------- +// table + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const table_format f) +{ + switch(f) + { + case table_format::multiline : {os << "multiline" ; break;} + case table_format::oneline : {os << "oneline" ; break;} + case table_format::dotted : {os << "dotted" ; break;} + case table_format::multiline_oneline: {os << "multiline_oneline"; break;} + case table_format::implicit : {os << "implicit" ; break;} + default: + { + os << "unknown table_format: " << static_cast(f); + break; + } + } + return os; +} +TOML11_INLINE std::string to_string(const table_format c) +{ + std::ostringstream oss; + oss << c; + return oss.str(); +} + +TOML11_INLINE bool operator==(const table_format_info& lhs, const table_format_info& rhs) noexcept +{ + return lhs.fmt == rhs.fmt && + lhs.indent_type == rhs.indent_type && + lhs.body_indent == rhs.body_indent && + lhs.name_indent == rhs.name_indent && + lhs.closing_indent == rhs.closing_indent ; +} +TOML11_INLINE bool operator!=(const table_format_info& lhs, const table_format_info& rhs) noexcept +{ + return !(lhs == rhs); +} + +} // namespace toml +#endif // TOML11_FORMAT_IMPL_HPP +#endif + +#endif// TOML11_FORMAT_HPP +#ifndef TOML11_DATETIME_HPP +#define TOML11_DATETIME_HPP + +#ifndef TOML11_DATETIME_FWD_HPP +#define TOML11_DATETIME_FWD_HPP + +#include +#include +#include + +#include +#include +#include + +namespace toml +{ + +enum class month_t : std::uint8_t +{ + Jan = 0, + Feb = 1, + Mar = 2, + Apr = 3, + May = 4, + Jun = 5, + Jul = 6, + Aug = 7, + Sep = 8, + Oct = 9, + Nov = 10, + Dec = 11 +}; + +// ---------------------------------------------------------------------------- + +struct local_date +{ + std::int16_t year{0}; // A.D. (like, 2018) + std::uint8_t month{0}; // [0, 11] + std::uint8_t day{0}; // [1, 31] + + local_date(int y, month_t m, int d) + : year {static_cast(y)}, + month{static_cast(m)}, + day {static_cast(d)} + {} + + explicit local_date(const std::tm& t) + : year {static_cast(t.tm_year + 1900)}, + month{static_cast(t.tm_mon)}, + day {static_cast(t.tm_mday)} + {} + + explicit local_date(const std::chrono::system_clock::time_point& tp); + explicit local_date(const std::time_t t); + + operator std::chrono::system_clock::time_point() const; + operator std::time_t() const; + + local_date() = default; + ~local_date() = default; + local_date(local_date const&) = default; + local_date(local_date&&) = default; + local_date& operator=(local_date const&) = default; + local_date& operator=(local_date&&) = default; +}; +bool operator==(const local_date& lhs, const local_date& rhs); +bool operator!=(const local_date& lhs, const local_date& rhs); +bool operator< (const local_date& lhs, const local_date& rhs); +bool operator<=(const local_date& lhs, const local_date& rhs); +bool operator> (const local_date& lhs, const local_date& rhs); +bool operator>=(const local_date& lhs, const local_date& rhs); + +std::ostream& operator<<(std::ostream& os, const local_date& date); +std::string to_string(const local_date& date); + +// ----------------------------------------------------------------------------- + +struct local_time +{ + std::uint8_t hour{0}; // [0, 23] + std::uint8_t minute{0}; // [0, 59] + std::uint8_t second{0}; // [0, 60] + std::uint16_t millisecond{0}; // [0, 999] + std::uint16_t microsecond{0}; // [0, 999] + std::uint16_t nanosecond{0}; // [0, 999] + + local_time(int h, int m, int s, + int ms = 0, int us = 0, int ns = 0) + : hour {static_cast(h)}, + minute{static_cast(m)}, + second{static_cast(s)}, + millisecond{static_cast(ms)}, + microsecond{static_cast(us)}, + nanosecond {static_cast(ns)} + {} + + explicit local_time(const std::tm& t) + : hour {static_cast(t.tm_hour)}, + minute{static_cast(t.tm_min )}, + second{static_cast(t.tm_sec )}, + millisecond{0}, microsecond{0}, nanosecond{0} + {} + + template + explicit local_time(const std::chrono::duration& t) + { + const auto h = std::chrono::duration_cast(t); + this->hour = static_cast(h.count()); + const auto t2 = t - h; + const auto m = std::chrono::duration_cast(t2); + this->minute = static_cast(m.count()); + const auto t3 = t2 - m; + const auto s = std::chrono::duration_cast(t3); + this->second = static_cast(s.count()); + const auto t4 = t3 - s; + const auto ms = std::chrono::duration_cast(t4); + this->millisecond = static_cast(ms.count()); + const auto t5 = t4 - ms; + const auto us = std::chrono::duration_cast(t5); + this->microsecond = static_cast(us.count()); + const auto t6 = t5 - us; + const auto ns = std::chrono::duration_cast(t6); + this->nanosecond = static_cast(ns.count()); + } + + operator std::chrono::nanoseconds() const; + + local_time() = default; + ~local_time() = default; + local_time(local_time const&) = default; + local_time(local_time&&) = default; + local_time& operator=(local_time const&) = default; + local_time& operator=(local_time&&) = default; +}; + +bool operator==(const local_time& lhs, const local_time& rhs); +bool operator!=(const local_time& lhs, const local_time& rhs); +bool operator< (const local_time& lhs, const local_time& rhs); +bool operator<=(const local_time& lhs, const local_time& rhs); +bool operator> (const local_time& lhs, const local_time& rhs); +bool operator>=(const local_time& lhs, const local_time& rhs); + +std::ostream& operator<<(std::ostream& os, const local_time& time); +std::string to_string(const local_time& time); + +// ---------------------------------------------------------------------------- + +struct time_offset +{ + std::int8_t hour{0}; // [-12, 12] + std::int8_t minute{0}; // [-59, 59] + + time_offset(int h, int m) + : hour {static_cast(h)}, + minute{static_cast(m)} + {} + + operator std::chrono::minutes() const; + + time_offset() = default; + ~time_offset() = default; + time_offset(time_offset const&) = default; + time_offset(time_offset&&) = default; + time_offset& operator=(time_offset const&) = default; + time_offset& operator=(time_offset&&) = default; +}; + +bool operator==(const time_offset& lhs, const time_offset& rhs); +bool operator!=(const time_offset& lhs, const time_offset& rhs); +bool operator< (const time_offset& lhs, const time_offset& rhs); +bool operator<=(const time_offset& lhs, const time_offset& rhs); +bool operator> (const time_offset& lhs, const time_offset& rhs); +bool operator>=(const time_offset& lhs, const time_offset& rhs); + +std::ostream& operator<<(std::ostream& os, const time_offset& offset); + +std::string to_string(const time_offset& offset); + +// ----------------------------------------------------------------------------- + +struct local_datetime +{ + local_date date{}; + local_time time{}; + + local_datetime(local_date d, local_time t): date{d}, time{t} {} + + explicit local_datetime(const std::tm& t): date{t}, time{t}{} + + explicit local_datetime(const std::chrono::system_clock::time_point& tp); + explicit local_datetime(const std::time_t t); + + operator std::chrono::system_clock::time_point() const; + operator std::time_t() const; + + local_datetime() = default; + ~local_datetime() = default; + local_datetime(local_datetime const&) = default; + local_datetime(local_datetime&&) = default; + local_datetime& operator=(local_datetime const&) = default; + local_datetime& operator=(local_datetime&&) = default; +}; + +bool operator==(const local_datetime& lhs, const local_datetime& rhs); +bool operator!=(const local_datetime& lhs, const local_datetime& rhs); +bool operator< (const local_datetime& lhs, const local_datetime& rhs); +bool operator<=(const local_datetime& lhs, const local_datetime& rhs); +bool operator> (const local_datetime& lhs, const local_datetime& rhs); +bool operator>=(const local_datetime& lhs, const local_datetime& rhs); + +std::ostream& operator<<(std::ostream& os, const local_datetime& dt); + +std::string to_string(const local_datetime& dt); + +// ----------------------------------------------------------------------------- + +struct offset_datetime +{ + local_date date{}; + local_time time{}; + time_offset offset{}; + + offset_datetime(local_date d, local_time t, time_offset o) + : date{d}, time{t}, offset{o} + {} + offset_datetime(const local_datetime& dt, time_offset o) + : date{dt.date}, time{dt.time}, offset{o} + {} + // use the current local timezone offset + explicit offset_datetime(const local_datetime& ld); + explicit offset_datetime(const std::chrono::system_clock::time_point& tp); + explicit offset_datetime(const std::time_t& t); + explicit offset_datetime(const std::tm& t); + + operator std::chrono::system_clock::time_point() const; + + operator std::time_t() const; + + offset_datetime() = default; + ~offset_datetime() = default; + offset_datetime(offset_datetime const&) = default; + offset_datetime(offset_datetime&&) = default; + offset_datetime& operator=(offset_datetime const&) = default; + offset_datetime& operator=(offset_datetime&&) = default; + + private: + + static time_offset get_local_offset(const std::time_t* tp); +}; + +bool operator==(const offset_datetime& lhs, const offset_datetime& rhs); +bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs); +bool operator< (const offset_datetime& lhs, const offset_datetime& rhs); +bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs); +bool operator> (const offset_datetime& lhs, const offset_datetime& rhs); +bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs); + +std::ostream& operator<<(std::ostream& os, const offset_datetime& dt); + +std::string to_string(const offset_datetime& dt); + +}//toml +#endif // TOML11_DATETIME_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_DATETIME_IMPL_HPP +#define TOML11_DATETIME_IMPL_HPP + + +#include +#include +#include +#include +#include + +#include +#include + +namespace toml +{ + +// To avoid non-threadsafe std::localtime. In C11 (not C++11!), localtime_s is +// provided in the absolutely same purpose, but C++11 is actually not compatible +// with C11. We need to dispatch the function depending on the OS. +namespace detail +{ +// TODO: find more sophisticated way to handle this +#if defined(_MSC_VER) +TOML11_INLINE std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_s(&dst, src); + if (result) { throw std::runtime_error("localtime_s failed."); } + return dst; +} +TOML11_INLINE std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_s(&dst, src); + if (result) { throw std::runtime_error("gmtime_s failed."); } + return dst; +} +#elif (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 1) || defined(_XOPEN_SOURCE) || defined(_BSD_SOURCE) || defined(_SVID_SOURCE) || defined(_POSIX_SOURCE) +TOML11_INLINE std::tm localtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::localtime_r(src, &dst); + if (!result) { throw std::runtime_error("localtime_r failed."); } + return dst; +} +TOML11_INLINE std::tm gmtime_s(const std::time_t* src) +{ + std::tm dst; + const auto result = ::gmtime_r(src, &dst); + if (!result) { throw std::runtime_error("gmtime_r failed."); } + return dst; +} +#else // fallback. not threadsafe +TOML11_INLINE std::tm localtime_s(const std::time_t* src) +{ + const auto result = std::localtime(src); + if (!result) { throw std::runtime_error("localtime failed."); } + return *result; +} +TOML11_INLINE std::tm gmtime_s(const std::time_t* src) +{ + const auto result = std::gmtime(src); + if (!result) { throw std::runtime_error("gmtime failed."); } + return *result; +} +#endif +} // detail + +// ---------------------------------------------------------------------------- + +TOML11_INLINE local_date::local_date(const std::chrono::system_clock::time_point& tp) +{ + const auto t = std::chrono::system_clock::to_time_t(tp); + const auto time = detail::localtime_s(&t); + *this = local_date(time); +} + +TOML11_INLINE local_date::local_date(const std::time_t t) + : local_date{std::chrono::system_clock::from_time_t(t)} +{} + +TOML11_INLINE local_date::operator std::chrono::system_clock::time_point() const +{ + // std::mktime returns date as local time zone. no conversion needed + std::tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = static_cast(this->day); + t.tm_mon = static_cast(this->month); + t.tm_year = static_cast(this->year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + return std::chrono::system_clock::from_time_t(std::mktime(&t)); +} + +TOML11_INLINE local_date::operator std::time_t() const +{ + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); +} + +TOML11_INLINE bool operator==(const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) == + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +TOML11_INLINE bool operator!=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const local_date& lhs, const local_date& rhs) +{ + return std::make_tuple(lhs.year, lhs.month, lhs.day) < + std::make_tuple(rhs.year, rhs.month, rhs.day); +} +TOML11_INLINE bool operator<=(const local_date& lhs, const local_date& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const local_date& lhs, const local_date& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const local_date& lhs, const local_date& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_date& date) +{ + os << std::setfill('0') << std::setw(4) << static_cast(date.year ) << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.month) + 1 << '-'; + os << std::setfill('0') << std::setw(2) << static_cast(date.day ) ; + return os; +} + +TOML11_INLINE std::string to_string(const local_date& date) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << date; + return oss.str(); +} + +// ----------------------------------------------------------------------------- + +TOML11_INLINE local_time::operator std::chrono::nanoseconds() const +{ + return std::chrono::nanoseconds (this->nanosecond) + + std::chrono::microseconds(this->microsecond) + + std::chrono::milliseconds(this->millisecond) + + std::chrono::seconds(this->second) + + std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); +} + +TOML11_INLINE bool operator==(const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) == + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +TOML11_INLINE bool operator!=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const local_time& lhs, const local_time& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute, lhs.second, lhs.millisecond, lhs.microsecond, lhs.nanosecond) < + std::make_tuple(rhs.hour, rhs.minute, rhs.second, rhs.millisecond, rhs.microsecond, rhs.nanosecond); +} +TOML11_INLINE bool operator<=(const local_time& lhs, const local_time& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const local_time& lhs, const local_time& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const local_time& lhs, const local_time& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_time& time) +{ + os << std::setfill('0') << std::setw(2) << static_cast(time.hour ) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.minute) << ':'; + os << std::setfill('0') << std::setw(2) << static_cast(time.second); + if(time.millisecond != 0 || time.microsecond != 0 || time.nanosecond != 0) + { + os << '.'; + os << std::setfill('0') << std::setw(3) << static_cast(time.millisecond); + if(time.microsecond != 0 || time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.microsecond); + if(time.nanosecond != 0) + { + os << std::setfill('0') << std::setw(3) << static_cast(time.nanosecond); + } + } + } + return os; +} + +TOML11_INLINE std::string to_string(const local_time& time) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << time; + return oss.str(); +} + +// ---------------------------------------------------------------------------- + +TOML11_INLINE time_offset::operator std::chrono::minutes() const +{ + return std::chrono::minutes(this->minute) + + std::chrono::hours(this->hour); +} + +TOML11_INLINE bool operator==(const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) == + std::make_tuple(rhs.hour, rhs.minute); +} +TOML11_INLINE bool operator!=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const time_offset& lhs, const time_offset& rhs) +{ + return std::make_tuple(lhs.hour, lhs.minute) < + std::make_tuple(rhs.hour, rhs.minute); +} +TOML11_INLINE bool operator<=(const time_offset& lhs, const time_offset& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const time_offset& lhs, const time_offset& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const time_offset& offset) +{ + if(offset.hour == 0 && offset.minute == 0) + { + os << 'Z'; + return os; + } + int minute = static_cast(offset.hour) * 60 + offset.minute; + if(minute < 0){os << '-'; minute = std::abs(minute);} else {os << '+';} + os << std::setfill('0') << std::setw(2) << minute / 60 << ':'; + os << std::setfill('0') << std::setw(2) << minute % 60; + return os; +} + +TOML11_INLINE std::string to_string(const time_offset& offset) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << offset; + return oss.str(); +} + +// ----------------------------------------------------------------------------- + +TOML11_INLINE local_datetime::local_datetime(const std::chrono::system_clock::time_point& tp) +{ + const auto t = std::chrono::system_clock::to_time_t(tp); + std::tm ltime = detail::localtime_s(&t); + + this->date = local_date(ltime); + this->time = local_time(ltime); + + // std::tm lacks subsecond information, so diff between tp and tm + // can be used to get millisecond & microsecond information. + const auto t_diff = tp - + std::chrono::system_clock::from_time_t(std::mktime(<ime)); + this->time.millisecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.microsecond = static_cast( + std::chrono::duration_cast(t_diff).count()); + this->time.nanosecond = static_cast( + std::chrono::duration_cast(t_diff).count()); +} + +TOML11_INLINE local_datetime::local_datetime(const std::time_t t) + : local_datetime{std::chrono::system_clock::from_time_t(t)} +{} + +TOML11_INLINE local_datetime::operator std::chrono::system_clock::time_point() const +{ + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // Normally DST begins at A.M. 3 or 4. If we re-use conversion operator + // of local_date and local_time independently, the conversion fails if + // it is the day when DST begins or ends. Since local_date considers the + // time is 00:00 A.M. and local_time does not consider DST because it + // does not have any date information. We need to consider both date and + // time information at the same time to convert it correctly. + + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + + // std::mktime returns date as local time zone. no conversion needed + auto dt = std::chrono::system_clock::from_time_t(std::mktime(&t)); + dt += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + return dt; +} + +TOML11_INLINE local_datetime::operator std::time_t() const +{ + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); +} + +TOML11_INLINE bool operator==(const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) == + std::make_tuple(rhs.date, rhs.time); +} +TOML11_INLINE bool operator!=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const local_datetime& lhs, const local_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time) < + std::make_tuple(rhs.date, rhs.time); +} +TOML11_INLINE bool operator<=(const local_datetime& lhs, const local_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const local_datetime& lhs, const local_datetime& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const local_datetime& dt) +{ + os << dt.date << 'T' << dt.time; + return os; +} + +TOML11_INLINE std::string to_string(const local_datetime& dt) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << dt; + return oss.str(); +} + +// ----------------------------------------------------------------------------- + + +TOML11_INLINE offset_datetime::offset_datetime(const local_datetime& ld) + : date{ld.date}, time{ld.time}, offset{get_local_offset(nullptr)} + // use the current local timezone offset +{} +TOML11_INLINE offset_datetime::offset_datetime(const std::chrono::system_clock::time_point& tp) + : offset{0, 0} // use gmtime +{ + const auto timet = std::chrono::system_clock::to_time_t(tp); + const auto tm = detail::gmtime_s(&timet); + this->date = local_date(tm); + this->time = local_time(tm); +} +TOML11_INLINE offset_datetime::offset_datetime(const std::time_t& t) + : offset{0, 0} // use gmtime +{ + const auto tm = detail::gmtime_s(&t); + this->date = local_date(tm); + this->time = local_time(tm); +} +TOML11_INLINE offset_datetime::offset_datetime(const std::tm& t) + : offset{0, 0} // assume gmtime +{ + this->date = local_date(t); + this->time = local_time(t); +} + +TOML11_INLINE offset_datetime::operator std::chrono::system_clock::time_point() const +{ + // get date-time + using internal_duration = + typename std::chrono::system_clock::time_point::duration; + + // first, convert it to local date-time information in the same way as + // local_datetime does. later we will use time_t to adjust time offset. + std::tm t; + t.tm_sec = static_cast(this->time.second); + t.tm_min = static_cast(this->time.minute); + t.tm_hour = static_cast(this->time.hour); + t.tm_mday = static_cast(this->date.day); + t.tm_mon = static_cast(this->date.month); + t.tm_year = static_cast(this->date.year) - 1900; + t.tm_wday = 0; // the value will be ignored + t.tm_yday = 0; // the value will be ignored + t.tm_isdst = -1; + const std::time_t tp_loc = std::mktime(std::addressof(t)); + + auto tp = std::chrono::system_clock::from_time_t(tp_loc); + tp += std::chrono::duration_cast( + std::chrono::milliseconds(this->time.millisecond) + + std::chrono::microseconds(this->time.microsecond) + + std::chrono::nanoseconds (this->time.nanosecond)); + + // Since mktime uses local time zone, it should be corrected. + // `12:00:00+09:00` means `03:00:00Z`. So mktime returns `03:00:00Z` if + // we are in `+09:00` timezone. To represent `12:00:00Z` there, we need + // to add `+09:00` to `03:00:00Z`. + // Here, it uses the time_t converted from date-time info to handle + // daylight saving time. + const auto ofs = get_local_offset(std::addressof(tp_loc)); + tp += std::chrono::hours (ofs.hour); + tp += std::chrono::minutes(ofs.minute); + + // We got `12:00:00Z` by correcting local timezone applied by mktime. + // Then we will apply the offset. Let's say `12:00:00-08:00` is given. + // And now, we have `12:00:00Z`. `12:00:00-08:00` means `20:00:00Z`. + // So we need to subtract the offset. + tp -= std::chrono::minutes(this->offset); + return tp; +} + +TOML11_INLINE offset_datetime::operator std::time_t() const +{ + return std::chrono::system_clock::to_time_t( + std::chrono::system_clock::time_point(*this)); +} + +TOML11_INLINE time_offset offset_datetime::get_local_offset(const std::time_t* tp) +{ + // get local timezone with the same date-time information as mktime + const auto t = detail::localtime_s(tp); + + std::array buf; + const auto result = std::strftime(buf.data(), 6, "%z", &t); // +hhmm\0 + if(result != 5) + { + throw std::runtime_error("toml::offset_datetime: cannot obtain " + "timezone information of current env"); + } + const int ofs = std::atoi(buf.data()); + const int ofs_h = ofs / 100; + const int ofs_m = ofs - (ofs_h * 100); + return time_offset(ofs_h, ofs_m); +} + +TOML11_INLINE bool operator==(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) == + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +TOML11_INLINE bool operator!=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs == rhs); +} +TOML11_INLINE bool operator< (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return std::make_tuple(lhs.date, lhs.time, lhs.offset) < + std::make_tuple(rhs.date, rhs.time, rhs.offset); +} +TOML11_INLINE bool operator<=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +TOML11_INLINE bool operator> (const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs <= rhs); +} +TOML11_INLINE bool operator>=(const offset_datetime& lhs, const offset_datetime& rhs) +{ + return !(lhs < rhs); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const offset_datetime& dt) +{ + os << dt.date << 'T' << dt.time << dt.offset; + return os; +} + +TOML11_INLINE std::string to_string(const offset_datetime& dt) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << dt; + return oss.str(); +} + +}//toml +#endif // TOML11_DATETIME_IMPL_HPP +#endif + +#endif // TOML11_DATETIME_HPP +#ifndef TOML11_COMPAT_HPP +#define TOML11_COMPAT_HPP + + +#include +#include +#include +#include +#include + +#include + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if __has_include() +# include +# endif +#endif + +#include + +// ---------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if __has_cpp_attribute(deprecated) +# define TOML11_HAS_ATTR_DEPRECATED 1 +# endif +#endif + +#if defined(TOML11_HAS_ATTR_DEPRECATED) +# define TOML11_DEPRECATED(msg) [[deprecated(msg)]] +#elif defined(__GNUC__) +# define TOML11_DEPRECATED(msg) __attribute__((deprecated(msg))) +#elif defined(_MSC_VER) +# define TOML11_DEPRECATED(msg) __declspec(deprecated(msg)) +#else +# define TOML11_DEPRECATED(msg) +#endif + +// ---------------------------------------------------------------------------- + +#if defined(__cpp_if_constexpr) +# if __cpp_if_constexpr >= 201606L +# define TOML11_HAS_CONSTEXPR_IF 1 +# endif +#endif + +#if defined(TOML11_HAS_CONSTEXPR_IF) +# define TOML11_CONSTEXPR_IF if constexpr +#else +# define TOML11_CONSTEXPR_IF if +#endif + +// ---------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_make_unique) +# if __cpp_lib_make_unique >= 201304L +# define TOML11_HAS_STD_MAKE_UNIQUE 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ + +#if defined(TOML11_HAS_STD_MAKE_UNIQUE) + +using std::make_unique; + +#else + +template +std::unique_ptr make_unique(Ts&& ... args) +{ + return std::unique_ptr(new T(std::forward(args)...)); +} + +#endif // TOML11_HAS_STD_MAKE_UNIQUE + +} // cxx +} // toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_make_reverse_iterator) +# if __cpp_lib_make_reverse_iterator >= 201402L +# define TOML11_HAS_STD_MAKE_REVERSE_ITERATOR 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +# if defined(TOML11_HAS_STD_MAKE_REVERSE_ITERATOR) + +using std::make_reverse_iterator; + +#else + +template +std::reverse_iterator make_reverse_iterator(Iterator iter) +{ + return std::reverse_iterator(iter); +} + +#endif // TOML11_HAS_STD_MAKE_REVERSE_ITERATOR + +} // cxx +} // toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if defined(__cpp_lib_clamp) +# if __cpp_lib_clamp >= 201603L +# define TOML11_HAS_STD_CLAMP 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_CLAMP) + +using std::clamp; + +#else + +template +T clamp(const T& x, const T& low, const T& high) noexcept +{ + assert(low <= high); + return (std::min)((std::max)(x, low), high); +} + +#endif // TOML11_HAS_STD_CLAMP + +} // cxx +} // toml + +// --------------------------------------------------------------------------- + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if defined(__cpp_lib_bit_cast) +# if __cpp_lib_bit_cast >= 201806L +# define TOML11_HAS_STD_BIT_CAST 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_BIT_CAST) + +using std::bit_cast; + +#else + +template +U bit_cast(const T& x) noexcept +{ + static_assert(sizeof(T) == sizeof(U), ""); + static_assert(std::is_default_constructible::value, ""); + + U z; + std::memcpy(reinterpret_cast(std::addressof(z)), + reinterpret_cast(std::addressof(x)), + sizeof(T)); + + return z; +} + +#endif // TOML11_HAS_STD_BIT_CAST + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++20 remove_cvref_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX20_VALUE +# if defined(__cpp_lib_remove_cvref) +# if __cpp_lib_remove_cvref >= 201711L +# define TOML11_HAS_STD_REMOVE_CVREF 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_REMOVE_CVREF) + +using std::remove_cvref; +using std::remove_cvref_t; + +#else + +template +struct remove_cvref +{ + using type = typename std::remove_cv< + typename std::remove_reference::type>::type; +}; + +template +using remove_cvref_t = typename remove_cvref::type; + +#endif // TOML11_HAS_STD_REMOVE_CVREF + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++17 and/or/not + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_logical_traits) +# if __cpp_lib_logical_traits >= 201510L +# define TOML11_HAS_STD_CONJUNCTION 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_CONJUNCTION) + +using std::conjunction; +using std::disjunction; +using std::negation; + +#else + +template struct conjunction : std::true_type{}; +template struct conjunction : T{}; +template +struct conjunction : + std::conditional(T::value), conjunction, T>::type +{}; + +template struct disjunction : std::false_type{}; +template struct disjunction : T {}; +template +struct disjunction : + std::conditional(T::value), T, disjunction>::type +{}; + +template +struct negation : std::integral_constant(T::value)>{}; + +#endif // TOML11_HAS_STD_CONJUNCTION + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++14 index_sequence + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_integer_sequence) +# if __cpp_lib_integer_sequence >= 201304L +# define TOML11_HAS_STD_INTEGER_SEQUENCE 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_INTEGER_SEQUENCE) + +using std::index_sequence; +using std::make_index_sequence; + +#else + +template struct index_sequence{}; + +template +struct double_index_sequence; + +template +struct double_index_sequence> +{ + using type = index_sequence; +}; +template +struct double_index_sequence> +{ + using type = index_sequence; +}; + +template +struct index_sequence_maker +{ + using type = typename double_index_sequence< + N % 2 == 1, N/2, typename index_sequence_maker::type + >::type; +}; +template<> +struct index_sequence_maker<0> +{ + using type = index_sequence<>; +}; + +template +using make_index_sequence = typename index_sequence_maker::type; + +#endif // TOML11_HAS_STD_INTEGER_SEQUENCE + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// C++14 enable_if_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if defined(__cpp_lib_transformation_trait_aliases) +# if __cpp_lib_transformation_trait_aliases >= 201304L +# define TOML11_HAS_STD_ENABLE_IF_T 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_ENABLE_IF_T) + +using std::enable_if_t; + +#else + +template +using enable_if_t = typename std::enable_if::type; + +#endif // TOML11_HAS_STD_ENABLE_IF_T + +} // cxx +} // toml + +// --------------------------------------------------------------------------- +// return_type_of_t + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_is_invocable) +# if __cpp_lib_is_invocable >= 201703 +# define TOML11_HAS_STD_INVOKE_RESULT 1 +# endif +# endif +#endif + +namespace toml +{ +namespace cxx +{ +#if defined(TOML11_HAS_STD_INVOKE_RESULT) + +template +using return_type_of_t = std::invoke_result_t; + +#else + +// result_of is deprecated after C++17 +template +using return_type_of_t = typename std::result_of::type; + +#endif // TOML11_HAS_STD_INVOKE_RESULT + +} // cxx +} // toml + +// ---------------------------------------------------------------------------- +// (subset of) source_location + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= 202002L +# if __has_include() +# define TOML11_HAS_STD_SOURCE_LOCATION +# endif // has_include +#endif // c++20 + +#if ! defined(TOML11_HAS_STD_SOURCE_LOCATION) +# if defined(__GNUC__) && ! defined(__clang__) +# if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX14_VALUE +# if __has_include() +# define TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION +# endif +# endif +# endif // GNU g++ +#endif // not TOML11_HAS_STD_SOURCE_LOCATION + +#if ! defined(TOML11_HAS_STD_SOURCE_LOCATION) && ! defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION) +# if defined(__GNUC__) && ! defined(__clang__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 9)) +# define TOML11_HAS_BUILTIN_FILE_LINE 1 +# define TOML11_BUILTIN_LINE_TYPE int +# endif +# elif defined(__clang__) // clang 9.0.0 implements builtin_FILE/LINE +# if __has_builtin(__builtin_FILE) && __has_builtin(__builtin_LINE) +# define TOML11_HAS_BUILTIN_FILE_LINE 1 +# define TOML11_BUILTIN_LINE_TYPE unsigned int +# endif +# elif defined(_MSVC_LANG) && defined(_MSC_VER) +# if _MSC_VER > 1926 +# define TOML11_HAS_BUILTIN_FILE_LINE 1 +# define TOML11_BUILTIN_LINE_TYPE int +# endif +# endif +#endif + +#if defined(TOML11_HAS_STD_SOURCE_LOCATION) +#include +namespace toml +{ +namespace cxx +{ +using source_location = std::source_location; + +inline std::string to_string(const source_location& loc) +{ + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(loc.file_name()); +} +} // cxx +} // toml +#elif defined(TOML11_HAS_EXPERIMENTAL_SOURCE_LOCATION) +#include +namespace toml +{ +namespace cxx +{ +using source_location = std::experimental::source_location; + +inline std::string to_string(const source_location& loc) +{ + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(loc.file_name()); +} +} // cxx +} // toml +#elif defined(TOML11_HAS_BUILTIN_FILE_LINE) +namespace toml +{ +namespace cxx +{ +struct source_location +{ + using line_type = TOML11_BUILTIN_LINE_TYPE; + static source_location current(const line_type line = __builtin_LINE(), + const char* file = __builtin_FILE()) + { + return source_location(line, file); + } + + source_location(const line_type line, const char* file) + : line_(line), file_name_(file) + {} + + line_type line() const noexcept {return line_;} + const char* file_name() const noexcept {return file_name_;} + + private: + + line_type line_; + const char* file_name_; +}; + +inline std::string to_string(const source_location& loc) +{ + return std::string(" at line ") + std::to_string(loc.line()) + + std::string(" in file ") + std::string(loc.file_name()); +} +} // cxx +} // toml +#else // no builtin +namespace toml +{ +namespace cxx +{ +struct source_location +{ + static source_location current() { return source_location{}; } +}; + +inline std::string to_string(const source_location&) +{ + return std::string(""); +} +} // cxx +} // toml +#endif // TOML11_HAS_STD_SOURCE_LOCATION + +// ---------------------------------------------------------------------------- +// (subset of) optional + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if __has_include() +# include +# endif // has_include(optional) +#endif // C++17 + +#if TOML11_CPLUSPLUS_STANDARD_VERSION >= TOML11_CXX17_VALUE +# if defined(__cpp_lib_optional) +# if __cpp_lib_optional >= 201606L +# define TOML11_HAS_STD_OPTIONAL 1 +# endif +# endif +#endif + +#if defined(TOML11_HAS_STD_OPTIONAL) + +namespace toml +{ +namespace cxx +{ +using std::optional; + +inline std::nullopt_t make_nullopt() {return std::nullopt;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const std::nullopt_t&) +{ + os << "nullopt"; + return os; +} + +} // cxx +} // toml + +#else // TOML11_HAS_STD_OPTIONAL + +namespace toml +{ +namespace cxx +{ + +struct nullopt_t{}; +inline nullopt_t make_nullopt() {return nullopt_t{};} + +inline bool operator==(const nullopt_t&, const nullopt_t&) noexcept {return true;} +inline bool operator!=(const nullopt_t&, const nullopt_t&) noexcept {return false;} +inline bool operator< (const nullopt_t&, const nullopt_t&) noexcept {return false;} +inline bool operator<=(const nullopt_t&, const nullopt_t&) noexcept {return true;} +inline bool operator> (const nullopt_t&, const nullopt_t&) noexcept {return false;} +inline bool operator>=(const nullopt_t&, const nullopt_t&) noexcept {return true;} + +template +std::basic_ostream& +operator<<(std::basic_ostream& os, const nullopt_t&) +{ + os << "nullopt"; + return os; +} + +template +class optional +{ + public: + + using value_type = T; + + public: + + optional() noexcept : has_value_(false), null_('\0') {} + optional(nullopt_t) noexcept : has_value_(false), null_('\0') {} + + optional(const T& x): has_value_(true), value_(x) {} + optional(T&& x): has_value_(true), value_(std::move(x)) {} + + template::value, std::nullptr_t> = nullptr> + explicit optional(U&& x): has_value_(true), value_(std::forward(x)) {} + + optional(const optional& rhs): has_value_(rhs.has_value_) + { + if(rhs.has_value_) + { + this->assigner(rhs.value_); + } + } + optional(optional&& rhs): has_value_(rhs.has_value_) + { + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + } + + optional& operator=(const optional& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(rhs.value_); + } + return *this; + } + optional& operator=(optional&& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + return *this; + } + + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + explicit optional(const optional& rhs): has_value_(rhs.has_value_), null_('\0') + { + if(rhs.has_value_) + { + this->assigner(rhs.value_); + } + } + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + explicit optional(optional&& rhs): has_value_(rhs.has_value_), null_('\0') + { + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + } + + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + optional& operator=(const optional& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(rhs.value_); + } + return *this; + } + + template>, std::is_constructible + >::value, std::nullptr_t> = nullptr> + optional& operator=(optional&& rhs) + { + if(this == std::addressof(rhs)) {return *this;} + + this->cleanup(); + this->has_value_ = rhs.has_value_; + if(this->has_value_) + { + this->assigner(std::move(rhs.value_)); + } + return *this; + } + ~optional() noexcept + { + this->cleanup(); + } + + explicit operator bool() const noexcept + { + return has_value_; + } + + bool has_value() const noexcept {return has_value_;} + + value_type const& value(source_location loc = source_location::current()) const + { + if( ! this->has_value_) + { + throw std::runtime_error("optional::value(): bad_unwrap" + to_string(loc)); + } + return this->value_; + } + value_type& value(source_location loc = source_location::current()) + { + if( ! this->has_value_) + { + throw std::runtime_error("optional::value(): bad_unwrap" + to_string(loc)); + } + return this->value_; + } + + value_type const& value_or(const value_type& opt) const + { + if(this->has_value_) {return this->value_;} else {return opt;} + } + value_type& value_or(value_type& opt) + { + if(this->has_value_) {return this->value_;} else {return opt;} + } + + private: + + void cleanup() noexcept + { + if(this->has_value_) + { + value_.~T(); + } + } + + template + void assigner(U&& x) + { + const auto tmp = ::new(std::addressof(this->value_)) value_type(std::forward(x)); + assert(tmp == std::addressof(this->value_)); + (void)tmp; + } + + private: + + bool has_value_; + union + { + char null_; + T value_; + }; +}; +} // cxx +} // toml +#endif // TOML11_HAS_STD_OPTIONAL + +#endif // TOML11_COMPAT_HPP +#ifndef TOML11_VALUE_T_HPP +#define TOML11_VALUE_T_HPP + +#ifndef TOML11_VALUE_T_FWD_HPP +#define TOML11_VALUE_T_FWD_HPP + + +#include +#include +#include + +#include + +namespace toml +{ + +// forward decl +template +class basic_value; + +// ---------------------------------------------------------------------------- +// enum representing toml types + +enum class value_t : std::uint8_t +{ + empty = 0, + boolean = 1, + integer = 2, + floating = 3, + string = 4, + offset_datetime = 5, + local_datetime = 6, + local_date = 7, + local_time = 8, + array = 9, + table = 10 +}; + +std::ostream& operator<<(std::ostream& os, value_t t); +std::string to_string(value_t t); + + +// ---------------------------------------------------------------------------- +// meta functions for internal use + +namespace detail +{ + +template +using value_t_constant = std::integral_constant; + +template +struct type_to_enum : value_t_constant {}; + +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; +template struct type_to_enum : value_t_constant {}; + +template +struct enum_to_type { using type = void; }; + +template struct enum_to_type { using type = typename V::boolean_type ; }; +template struct enum_to_type { using type = typename V::integer_type ; }; +template struct enum_to_type { using type = typename V::floating_type ; }; +template struct enum_to_type { using type = typename V::string_type ; }; +template struct enum_to_type { using type = typename V::offset_datetime_type; }; +template struct enum_to_type { using type = typename V::local_datetime_type ; }; +template struct enum_to_type { using type = typename V::local_date_type ; }; +template struct enum_to_type { using type = typename V::local_time_type ; }; +template struct enum_to_type { using type = typename V::array_type ; }; +template struct enum_to_type { using type = typename V::table_type ; }; + +template +using enum_to_type_t = typename enum_to_type::type; + +template +struct enum_to_fmt_type { using type = void; }; + +template<> struct enum_to_fmt_type { using type = boolean_format_info ; }; +template<> struct enum_to_fmt_type { using type = integer_format_info ; }; +template<> struct enum_to_fmt_type { using type = floating_format_info ; }; +template<> struct enum_to_fmt_type { using type = string_format_info ; }; +template<> struct enum_to_fmt_type { using type = offset_datetime_format_info; }; +template<> struct enum_to_fmt_type { using type = local_datetime_format_info ; }; +template<> struct enum_to_fmt_type { using type = local_date_format_info ; }; +template<> struct enum_to_fmt_type { using type = local_time_format_info ; }; +template<> struct enum_to_fmt_type { using type = array_format_info ; }; +template<> struct enum_to_fmt_type { using type = table_format_info ; }; + +template +using enum_to_fmt_type_t = typename enum_to_fmt_type::type; + +template +struct is_exact_toml_type0 : cxx::disjunction< + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same + >{}; +template struct is_exact_toml_type: is_exact_toml_type0, V> {}; +template struct is_not_toml_type : cxx::negation> {}; + +} // namespace detail +} // namespace toml +#endif // TOML11_VALUE_T_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_VALUE_T_IMPL_HPP +#define TOML11_VALUE_T_IMPL_HPP + + +#include +#include +#include + +namespace toml +{ + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, value_t t) +{ + switch(t) + { + case value_t::boolean : os << "boolean"; return os; + case value_t::integer : os << "integer"; return os; + case value_t::floating : os << "floating"; return os; + case value_t::string : os << "string"; return os; + case value_t::offset_datetime : os << "offset_datetime"; return os; + case value_t::local_datetime : os << "local_datetime"; return os; + case value_t::local_date : os << "local_date"; return os; + case value_t::local_time : os << "local_time"; return os; + case value_t::array : os << "array"; return os; + case value_t::table : os << "table"; return os; + case value_t::empty : os << "empty"; return os; + default : os << "unknown"; return os; + } +} + +TOML11_INLINE std::string to_string(value_t t) +{ + std::ostringstream oss; + oss << t; + return oss.str(); +} + +} // namespace toml +#endif // TOML11_VALUE_T_IMPL_HPP +#endif + +#endif // TOML11_VALUE_T_HPP +#ifndef TOML11_STORAGE_HPP +#define TOML11_STORAGE_HPP + + +namespace toml +{ +namespace detail +{ + +// It owns a pointer to T. It does deep-copy when copied. +// This struct is introduced to implement a recursive type. +// +// `toml::value` contains `std::vector` to represent a toml array. +// But, in the definition of `toml::value`, `toml::value` is still incomplete. +// `std::vector` of an incomplete type is not allowed in C++11 (it is allowed +// after C++17). To avoid this, we need to use a pointer to `toml::value`, like +// `std::vector>`. Although `std::unique_ptr` is +// noncopyable, we want to make `toml::value` copyable. `storage` is introduced +// to resolve those problems. +template +struct storage +{ + using value_type = T; + + explicit storage(value_type v): ptr_(cxx::make_unique(std::move(v))) {} + ~storage() = default; + + storage(const storage& rhs): ptr_(cxx::make_unique(*rhs.ptr_)) {} + storage& operator=(const storage& rhs) + { + this->ptr_ = cxx::make_unique(*rhs.ptr_); + return *this; + } + + storage(storage&&) = default; + storage& operator=(storage&&) = default; + + bool is_ok() const noexcept {return static_cast(ptr_);} + + value_type& get() const noexcept {return *ptr_;} + + private: + std::unique_ptr ptr_; +}; + +} // detail +} // toml +#endif // TOML11_STORAGE_HPP +#ifndef TOML11_COMMENTS_HPP +#define TOML11_COMMENTS_HPP + +#ifndef TOML11_COMMENTS_FWD_HPP +#define TOML11_COMMENTS_FWD_HPP + +// to use __has_builtin + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// This file provides mainly two classes, `preserve_comments` and `discard_comments`. +// Those two are a container that have the same interface as `std::vector` +// but bahaves in the opposite way. `preserve_comments` is just the same as +// `std::vector` and each `std::string` corresponds to a comment line. +// Conversely, `discard_comments` discards all the strings and ignores everything +// assigned in it. `discard_comments` is always empty and you will encounter an +// error whenever you access to the element. +namespace toml +{ +class discard_comments; // forward decl + +class preserve_comments +{ + public: + // `container_type` is not provided in discard_comments. + // do not use this inner-type in a generic code. + using container_type = std::vector; + + using size_type = container_type::size_type; + using difference_type = container_type::difference_type; + using value_type = container_type::value_type; + using reference = container_type::reference; + using const_reference = container_type::const_reference; + using pointer = container_type::pointer; + using const_pointer = container_type::const_pointer; + using iterator = container_type::iterator; + using const_iterator = container_type::const_iterator; + using reverse_iterator = container_type::reverse_iterator; + using const_reverse_iterator = container_type::const_reverse_iterator; + + public: + + preserve_comments() = default; + ~preserve_comments() = default; + preserve_comments(preserve_comments const&) = default; + preserve_comments(preserve_comments &&) = default; + preserve_comments& operator=(preserve_comments const&) = default; + preserve_comments& operator=(preserve_comments &&) = default; + + explicit preserve_comments(const std::vector& c): comments(c){} + explicit preserve_comments(std::vector&& c) + : comments(std::move(c)) + {} + preserve_comments& operator=(const std::vector& c) + { + comments = c; + return *this; + } + preserve_comments& operator=(std::vector&& c) + { + comments = std::move(c); + return *this; + } + + explicit preserve_comments(const discard_comments&) {} + + explicit preserve_comments(size_type n): comments(n) {} + preserve_comments(size_type n, const std::string& x): comments(n, x) {} + preserve_comments(std::initializer_list x): comments(x) {} + template + preserve_comments(InputIterator first, InputIterator last) + : comments(first, last) + {} + + template + void assign(InputIterator first, InputIterator last) {comments.assign(first, last);} + void assign(std::initializer_list ini) {comments.assign(ini);} + void assign(size_type n, const std::string& val) {comments.assign(n, val);} + + // Related to the issue #97. + // + // `std::vector::insert` and `std::vector::erase` in the STL implementation + // included in GCC 4.8.5 takes `std::vector::iterator` instead of + // `std::vector::const_iterator`. It causes compilation error in GCC 4.8.5. +#if defined(__GNUC__) && defined(__GNUC_MINOR__) && defined(__GNUC_PATCHLEVEL__) && !defined(__clang__) +# if (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) <= 40805 +# define TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION +# endif +#endif + +#ifdef TOML11_WORKAROUND_GCC_4_8_X_STANDARD_LIBRARY_IMPLEMENTATION + iterator insert(iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + void insert(iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + void insert(iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + void insert(iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(iterator pos) {return comments.erase(pos);} + iterator erase(iterator first, iterator last) + { + return comments.erase(first, last); + } +#else + iterator insert(const_iterator p, const std::string& x) + { + return comments.insert(p, x); + } + iterator insert(const_iterator p, std::string&& x) + { + return comments.insert(p, std::move(x)); + } + iterator insert(const_iterator p, size_type n, const std::string& x) + { + return comments.insert(p, n, x); + } + template + iterator insert(const_iterator p, InputIterator first, InputIterator last) + { + return comments.insert(p, first, last); + } + iterator insert(const_iterator p, std::initializer_list ini) + { + return comments.insert(p, ini); + } + + template + iterator emplace(const_iterator p, Ts&& ... args) + { + return comments.emplace(p, std::forward(args)...); + } + + iterator erase(const_iterator pos) {return comments.erase(pos);} + iterator erase(const_iterator first, const_iterator last) + { + return comments.erase(first, last); + } +#endif + + void swap(preserve_comments& other) {comments.swap(other.comments);} + + void push_back(const std::string& v) {comments.push_back(v);} + void push_back(std::string&& v) {comments.push_back(std::move(v));} + void pop_back() {comments.pop_back();} + + template + void emplace_back(Ts&& ... args) {comments.emplace_back(std::forward(args)...);} + + void clear() {comments.clear();} + + size_type size() const noexcept {return comments.size();} + size_type max_size() const noexcept {return comments.max_size();} + size_type capacity() const noexcept {return comments.capacity();} + bool empty() const noexcept {return comments.empty();} + + void reserve(size_type n) {comments.reserve(n);} + void resize(size_type n) {comments.resize(n);} + void resize(size_type n, const std::string& c) {comments.resize(n, c);} + void shrink_to_fit() {comments.shrink_to_fit();} + + reference operator[](const size_type n) noexcept {return comments[n];} + const_reference operator[](const size_type n) const noexcept {return comments[n];} + reference at(const size_type n) {return comments.at(n);} + const_reference at(const size_type n) const {return comments.at(n);} + reference front() noexcept {return comments.front();} + const_reference front() const noexcept {return comments.front();} + reference back() noexcept {return comments.back();} + const_reference back() const noexcept {return comments.back();} + + pointer data() noexcept {return comments.data();} + const_pointer data() const noexcept {return comments.data();} + + iterator begin() noexcept {return comments.begin();} + iterator end() noexcept {return comments.end();} + const_iterator begin() const noexcept {return comments.begin();} + const_iterator end() const noexcept {return comments.end();} + const_iterator cbegin() const noexcept {return comments.cbegin();} + const_iterator cend() const noexcept {return comments.cend();} + + reverse_iterator rbegin() noexcept {return comments.rbegin();} + reverse_iterator rend() noexcept {return comments.rend();} + const_reverse_iterator rbegin() const noexcept {return comments.rbegin();} + const_reverse_iterator rend() const noexcept {return comments.rend();} + const_reverse_iterator crbegin() const noexcept {return comments.crbegin();} + const_reverse_iterator crend() const noexcept {return comments.crend();} + + friend bool operator==(const preserve_comments&, const preserve_comments&); + friend bool operator!=(const preserve_comments&, const preserve_comments&); + friend bool operator< (const preserve_comments&, const preserve_comments&); + friend bool operator<=(const preserve_comments&, const preserve_comments&); + friend bool operator> (const preserve_comments&, const preserve_comments&); + friend bool operator>=(const preserve_comments&, const preserve_comments&); + + friend void swap(preserve_comments&, std::vector&); + friend void swap(std::vector&, preserve_comments&); + + private: + + container_type comments; +}; + +bool operator==(const preserve_comments& lhs, const preserve_comments& rhs); +bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs); +bool operator< (const preserve_comments& lhs, const preserve_comments& rhs); +bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs); +bool operator> (const preserve_comments& lhs, const preserve_comments& rhs); +bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs); + +void swap(preserve_comments& lhs, preserve_comments& rhs); +void swap(preserve_comments& lhs, std::vector& rhs); +void swap(std::vector& lhs, preserve_comments& rhs); + +std::ostream& operator<<(std::ostream& os, const preserve_comments& com); + +namespace detail +{ + +// To provide the same interface with `preserve_comments`, `discard_comments` +// should have an iterator. But it does not contain anything, so we need to +// add an iterator that points nothing. +// +// It always points null, so DO NOT unwrap this iterator. It always crashes +// your program. +template +struct empty_iterator +{ + using value_type = T; + using reference_type = typename std::conditional::type; + using pointer_type = typename std::conditional::type; + using difference_type = std::ptrdiff_t; + using iterator_category = std::random_access_iterator_tag; + + empty_iterator() = default; + ~empty_iterator() = default; + empty_iterator(empty_iterator const&) = default; + empty_iterator(empty_iterator &&) = default; + empty_iterator& operator=(empty_iterator const&) = default; + empty_iterator& operator=(empty_iterator &&) = default; + + // DO NOT call these operators. + reference_type operator*() const noexcept {std::terminate();} + pointer_type operator->() const noexcept {return nullptr;} + reference_type operator[](difference_type) const noexcept {return this->operator*();} + + // These operators do nothing. + empty_iterator& operator++() noexcept {return *this;} + empty_iterator operator++(int) noexcept {return *this;} + empty_iterator& operator--() noexcept {return *this;} + empty_iterator operator--(int) noexcept {return *this;} + + empty_iterator& operator+=(difference_type) noexcept {return *this;} + empty_iterator& operator-=(difference_type) noexcept {return *this;} + + empty_iterator operator+(difference_type) const noexcept {return *this;} + empty_iterator operator-(difference_type) const noexcept {return *this;} +}; + +template +bool operator==(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator!=(const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator< (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator<=(const empty_iterator&, const empty_iterator&) noexcept {return true;} +template +bool operator> (const empty_iterator&, const empty_iterator&) noexcept {return false;} +template +bool operator>=(const empty_iterator&, const empty_iterator&) noexcept {return true;} + +template +typename empty_iterator::difference_type +operator-(const empty_iterator&, const empty_iterator&) noexcept {return 0;} + +template +empty_iterator +operator+(typename empty_iterator::difference_type, const empty_iterator& rhs) noexcept {return rhs;} +template +empty_iterator +operator+(const empty_iterator& lhs, typename empty_iterator::difference_type) noexcept {return lhs;} + +} // detail + +// The default comment type. It discards all the comments. It requires only one +// byte to contain, so the memory footprint is smaller than preserve_comments. +// +// It just ignores `push_back`, `insert`, `erase`, and any other modifications. +// IT always returns size() == 0, the iterator taken by `begin()` is always the +// same as that of `end()`, and accessing through `operator[]` or iterators +// always causes a segmentation fault. DO NOT access to the element of this. +// +// Why this is chose as the default type is because the last version (2.x.y) +// does not contain any comments in a value. To minimize the impact on the +// efficiency, this is chosen as a default. +// +// To reduce the memory footprint, later we can try empty base optimization (EBO). +class discard_comments +{ + public: + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using value_type = std::string; + using reference = std::string&; + using const_reference = std::string const&; + using pointer = std::string*; + using const_pointer = std::string const*; + using iterator = detail::empty_iterator; + using const_iterator = detail::empty_iterator; + using reverse_iterator = detail::empty_iterator; + using const_reverse_iterator = detail::empty_iterator; + + public: + discard_comments() = default; + ~discard_comments() = default; + discard_comments(discard_comments const&) = default; + discard_comments(discard_comments &&) = default; + discard_comments& operator=(discard_comments const&) = default; + discard_comments& operator=(discard_comments &&) = default; + + explicit discard_comments(const std::vector&) noexcept {} + explicit discard_comments(std::vector&&) noexcept {} + discard_comments& operator=(const std::vector&) noexcept {return *this;} + discard_comments& operator=(std::vector&&) noexcept {return *this;} + + explicit discard_comments(const preserve_comments&) noexcept {} + + explicit discard_comments(size_type) noexcept {} + discard_comments(size_type, const std::string&) noexcept {} + discard_comments(std::initializer_list) noexcept {} + template + discard_comments(InputIterator, InputIterator) noexcept {} + + template + void assign(InputIterator, InputIterator) noexcept {} + void assign(std::initializer_list) noexcept {} + void assign(size_type, const std::string&) noexcept {} + + iterator insert(const_iterator, const std::string&) {return iterator{};} + iterator insert(const_iterator, std::string&&) {return iterator{};} + iterator insert(const_iterator, size_type, const std::string&) {return iterator{};} + template + iterator insert(const_iterator, InputIterator, InputIterator) {return iterator{};} + iterator insert(const_iterator, std::initializer_list) {return iterator{};} + + template + iterator emplace(const_iterator, Ts&& ...) {return iterator{};} + iterator erase(const_iterator) {return iterator{};} + iterator erase(const_iterator, const_iterator) {return iterator{};} + + void swap(discard_comments&) {return;} + + void push_back(const std::string&) {return;} + void push_back(std::string&& ) {return;} + void pop_back() {return;} + + template + void emplace_back(Ts&& ...) {return;} + + void clear() {return;} + + size_type size() const noexcept {return 0;} + size_type max_size() const noexcept {return 0;} + size_type capacity() const noexcept {return 0;} + bool empty() const noexcept {return true;} + + void reserve(size_type) {return;} + void resize(size_type) {return;} + void resize(size_type, const std::string&) {return;} + void shrink_to_fit() {return;} + + // DO NOT access to the element of this container. This container is always + // empty, so accessing through operator[], front/back, data causes address + // error. + + reference operator[](const size_type) noexcept {never_call("toml::discard_comment::operator[]");} + const_reference operator[](const size_type) const noexcept {never_call("toml::discard_comment::operator[]");} + reference at(const size_type) {throw std::out_of_range("toml::discard_comment is always empty.");} + const_reference at(const size_type) const {throw std::out_of_range("toml::discard_comment is always empty.");} + reference front() noexcept {never_call("toml::discard_comment::front");} + const_reference front() const noexcept {never_call("toml::discard_comment::front");} + reference back() noexcept {never_call("toml::discard_comment::back");} + const_reference back() const noexcept {never_call("toml::discard_comment::back");} + + pointer data() noexcept {return nullptr;} + const_pointer data() const noexcept {return nullptr;} + + iterator begin() noexcept {return iterator{};} + iterator end() noexcept {return iterator{};} + const_iterator begin() const noexcept {return const_iterator{};} + const_iterator end() const noexcept {return const_iterator{};} + const_iterator cbegin() const noexcept {return const_iterator{};} + const_iterator cend() const noexcept {return const_iterator{};} + + reverse_iterator rbegin() noexcept {return iterator{};} + reverse_iterator rend() noexcept {return iterator{};} + const_reverse_iterator rbegin() const noexcept {return const_iterator{};} + const_reverse_iterator rend() const noexcept {return const_iterator{};} + const_reverse_iterator crbegin() const noexcept {return const_iterator{};} + const_reverse_iterator crend() const noexcept {return const_iterator{};} + + private: + + [[noreturn]] static void never_call(const char *const this_function) + { +#if __has_builtin(__builtin_unreachable) + __builtin_unreachable(); +#endif + throw std::logic_error{this_function}; + } +}; + +inline bool operator==(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator!=(const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator< (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator<=(const discard_comments&, const discard_comments&) noexcept {return true;} +inline bool operator> (const discard_comments&, const discard_comments&) noexcept {return false;} +inline bool operator>=(const discard_comments&, const discard_comments&) noexcept {return true;} + +inline void swap(const discard_comments&, const discard_comments&) noexcept {return;} + +inline std::ostream& operator<<(std::ostream& os, const discard_comments&) {return os;} + +} // toml11 +#endif // TOML11_COMMENTS_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_COMMENTS_IMPL_HPP +#define TOML11_COMMENTS_IMPL_HPP + + +namespace toml +{ + +TOML11_INLINE bool operator==(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments == rhs.comments;} +TOML11_INLINE bool operator!=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments != rhs.comments;} +TOML11_INLINE bool operator< (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments < rhs.comments;} +TOML11_INLINE bool operator<=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments <= rhs.comments;} +TOML11_INLINE bool operator> (const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments > rhs.comments;} +TOML11_INLINE bool operator>=(const preserve_comments& lhs, const preserve_comments& rhs) {return lhs.comments >= rhs.comments;} + +TOML11_INLINE void swap(preserve_comments& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs); + return; +} +TOML11_INLINE void swap(preserve_comments& lhs, std::vector& rhs) +{ + lhs.comments.swap(rhs); + return; +} +TOML11_INLINE void swap(std::vector& lhs, preserve_comments& rhs) +{ + lhs.swap(rhs.comments); + return; +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const preserve_comments& com) +{ + for(const auto& c : com) + { + if(c.front() != '#') + { + os << '#'; + } + os << c << '\n'; + } + return os; +} + +} // toml11 +#endif // TOML11_COMMENTS_IMPL_HPP +#endif + +#endif // TOML11_COMMENTS_HPP +#ifndef TOML11_COLOR_HPP +#define TOML11_COLOR_HPP + +#ifndef TOML11_COLOR_FWD_HPP +#define TOML11_COLOR_FWD_HPP + +#include + +#ifdef TOML11_COLORIZE_ERROR_MESSAGE +#define TOML11_ERROR_MESSAGE_COLORIZED true +#else +#define TOML11_ERROR_MESSAGE_COLORIZED false +#endif + +#ifdef TOML11_USE_THREAD_LOCAL_COLORIZATION +#define TOML11_THREAD_LOCAL_COLORIZATION thread_local +#else +#define TOML11_THREAD_LOCAL_COLORIZATION +#endif + +namespace toml +{ +namespace color +{ +// put ANSI escape sequence to ostream +inline namespace ansi +{ +namespace detail +{ + +// Control color mode globally +class color_mode +{ + public: + + void enable() noexcept + { + should_color_ = true; + } + void disable() noexcept + { + should_color_ = false; + } + bool should_color() const noexcept + { + return should_color_; + } + + private: + + bool should_color_ = TOML11_ERROR_MESSAGE_COLORIZED; +}; + +inline color_mode& color_status() noexcept +{ + static TOML11_THREAD_LOCAL_COLORIZATION color_mode status; + return status; +} + +} // detail + +std::ostream& reset (std::ostream& os); +std::ostream& bold (std::ostream& os); +std::ostream& grey (std::ostream& os); +std::ostream& gray (std::ostream& os); +std::ostream& red (std::ostream& os); +std::ostream& green (std::ostream& os); +std::ostream& yellow (std::ostream& os); +std::ostream& blue (std::ostream& os); +std::ostream& magenta(std::ostream& os); +std::ostream& cyan (std::ostream& os); +std::ostream& white (std::ostream& os); + +} // ansi + +inline void enable() +{ + return detail::color_status().enable(); +} +inline void disable() +{ + return detail::color_status().disable(); +} +inline bool should_color() +{ + return detail::color_status().should_color(); +} + +} // color +} // toml +#endif // TOML11_COLOR_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_COLOR_IMPL_HPP +#define TOML11_COLOR_IMPL_HPP + + +#include + +namespace toml +{ +namespace color +{ +// put ANSI escape sequence to ostream +inline namespace ansi +{ + +TOML11_INLINE std::ostream& reset(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[00m";} + return os; +} +TOML11_INLINE std::ostream& bold(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[01m";} + return os; +} +TOML11_INLINE std::ostream& grey(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[30m";} + return os; +} +TOML11_INLINE std::ostream& gray(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[30m";} + return os; +} +TOML11_INLINE std::ostream& red(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[31m";} + return os; +} +TOML11_INLINE std::ostream& green(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[32m";} + return os; +} +TOML11_INLINE std::ostream& yellow(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[33m";} + return os; +} +TOML11_INLINE std::ostream& blue(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[34m";} + return os; +} +TOML11_INLINE std::ostream& magenta(std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[35m";} + return os; +} +TOML11_INLINE std::ostream& cyan (std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[36m";} + return os; +} +TOML11_INLINE std::ostream& white (std::ostream& os) +{ + if(detail::color_status().should_color()) {os << "\033[37m";} + return os; +} + +} // ansi +} // color +} // toml +#endif // TOML11_COLOR_IMPL_HPP +#endif + +#endif // TOML11_COLOR_HPP +#ifndef TOML11_SPEC_HPP +#define TOML11_SPEC_HPP + +#include +#include + +#include + +namespace toml +{ + +struct semantic_version +{ + constexpr semantic_version(std::uint32_t mjr, std::uint32_t mnr, std::uint32_t p) noexcept + : major{mjr}, minor{mnr}, patch{p} + {} + + std::uint32_t major; + std::uint32_t minor; + std::uint32_t patch; +}; + +constexpr inline semantic_version +make_semver(std::uint32_t mjr, std::uint32_t mnr, std::uint32_t p) noexcept +{ + return semantic_version(mjr, mnr, p); +} + +constexpr inline bool +operator==(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return lhs.major == rhs.major && + lhs.minor == rhs.minor && + lhs.patch == rhs.patch; +} +constexpr inline bool +operator!=(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return !(lhs == rhs); +} +constexpr inline bool +operator<(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return lhs.major < rhs.major || + (lhs.major == rhs.major && lhs.minor < rhs.minor) || + (lhs.major == rhs.major && lhs.minor == rhs.minor && lhs.patch < rhs.patch); +} +constexpr inline bool +operator>(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return rhs < lhs; +} +constexpr inline bool +operator<=(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return !(lhs > rhs); +} +constexpr inline bool +operator>=(const semantic_version& lhs, const semantic_version& rhs) noexcept +{ + return !(lhs < rhs); +} + +inline std::ostream& operator<<(std::ostream& os, const semantic_version& v) +{ + os << v.major << '.' << v.minor << '.' << v.patch; + return os; +} + +inline std::string to_string(const semantic_version& v) +{ + std::ostringstream oss; + oss << v; + return oss.str(); +} + +struct spec +{ + constexpr static spec default_version() noexcept + { + return spec::v(1, 0, 0); + } + + constexpr static spec v(std::uint32_t mjr, std::uint32_t mnr, std::uint32_t p) noexcept + { + return spec(make_semver(mjr, mnr, p)); + } + + constexpr explicit spec(const semantic_version& semver) noexcept + : version{semver}, + v1_1_0_allow_control_characters_in_comments {semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_newlines_in_inline_tables {semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_trailing_comma_in_inline_tables{semantic_version{1, 1, 0} <= semver}, + v1_1_0_allow_non_english_in_bare_keys {semantic_version{1, 1, 0} <= semver}, + v1_1_0_add_escape_sequence_e {semantic_version{1, 1, 0} <= semver}, + v1_1_0_add_escape_sequence_x {semantic_version{1, 1, 0} <= semver}, + v1_1_0_make_seconds_optional {semantic_version{1, 1, 0} <= semver}, + ext_hex_float {false}, + ext_num_suffix{false}, + ext_null_value{false} + {} + + semantic_version version; // toml version + + // diff from v1.0.0 -> v1.1.0 + bool v1_1_0_allow_control_characters_in_comments; + bool v1_1_0_allow_newlines_in_inline_tables; + bool v1_1_0_allow_trailing_comma_in_inline_tables; + bool v1_1_0_allow_non_english_in_bare_keys; + bool v1_1_0_add_escape_sequence_e; + bool v1_1_0_add_escape_sequence_x; + bool v1_1_0_make_seconds_optional; + + // library extensions + bool ext_hex_float; // allow hex float (in C++ style) + bool ext_num_suffix; // allow number suffix (in C++ style) + bool ext_null_value; // allow `null` as a value +}; + +} // namespace toml +#endif // TOML11_SPEC_HPP +#ifndef TOML11_ORDERED_MAP_HPP +#define TOML11_ORDERED_MAP_HPP + +#include +#include +#include +#include + +namespace toml +{ + +namespace detail +{ +template +struct ordered_map_ebo_container +{ + Cmp cmp_; // empty base optimization for empty Cmp type +}; +} // detail + +template, + typename Allocator = std::allocator>> +class ordered_map : detail::ordered_map_ebo_container +{ + public: + using key_type = Key; + using mapped_type = Val; + using value_type = std::pair; + + using key_compare = Cmp; + using allocator_type = Allocator; + + using container_type = std::vector; + using reference = typename container_type::reference; + using pointer = typename container_type::pointer; + using const_reference = typename container_type::const_reference; + using const_pointer = typename container_type::const_pointer; + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + using size_type = typename container_type::size_type; + using difference_type = typename container_type::difference_type; + + private: + + using ebo_base = detail::ordered_map_ebo_container; + + public: + + ordered_map() = default; + ~ordered_map() = default; + ordered_map(const ordered_map&) = default; + ordered_map(ordered_map&&) = default; + ordered_map& operator=(const ordered_map&) = default; + ordered_map& operator=(ordered_map&&) = default; + + ordered_map(const ordered_map& other, const Allocator& alloc) + : container_(other.container_, alloc) + {} + ordered_map(ordered_map&& other, const Allocator& alloc) + : container_(std::move(other.container_), alloc) + {} + + explicit ordered_map(const Cmp& cmp, const Allocator& alloc = Allocator()) + : ebo_base{cmp}, container_(alloc) + {} + explicit ordered_map(const Allocator& alloc) + : container_(alloc) + {} + + template + ordered_map(InputIterator first, InputIterator last, const Cmp& cmp = Cmp(), const Allocator& alloc = Allocator()) + : ebo_base{cmp}, container_(first, last, alloc) + {} + template + ordered_map(InputIterator first, InputIterator last, const Allocator& alloc) + : container_(first, last, alloc) + {} + + ordered_map(std::initializer_list v, const Cmp& cmp = Cmp(), const Allocator& alloc = Allocator()) + : ebo_base{cmp}, container_(std::move(v), alloc) + {} + ordered_map(std::initializer_list v, const Allocator& alloc) + : container_(std::move(v), alloc) + {} + ordered_map& operator=(std::initializer_list v) + { + this->container_ = std::move(v); + return *this; + } + + iterator begin() noexcept {return container_.begin();} + iterator end() noexcept {return container_.end();} + const_iterator begin() const noexcept {return container_.begin();} + const_iterator end() const noexcept {return container_.end();} + const_iterator cbegin() const noexcept {return container_.cbegin();} + const_iterator cend() const noexcept {return container_.cend();} + + bool empty() const noexcept {return container_.empty();} + std::size_t size() const noexcept {return container_.size();} + std::size_t max_size() const noexcept {return container_.max_size();} + + void clear() {container_.clear();} + + void push_back(const value_type& v) + { + if(this->contains(v.first)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(v); + } + void push_back(value_type&& v) + { + if(this->contains(v.first)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(std::move(v)); + } + void emplace_back(key_type k, mapped_type v) + { + if(this->contains(k)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.emplace_back(std::move(k), std::move(v)); + } + void pop_back() {container_.pop_back();} + + void insert(value_type kv) + { + if(this->contains(kv.first)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.push_back(std::move(kv)); + } + void emplace(key_type k, mapped_type v) + { + if(this->contains(k)) + { + throw std::out_of_range("ordered_map: value already exists"); + } + container_.emplace_back(std::move(k), std::move(v)); + } + + std::size_t count(const key_type& key) const + { + if(this->find(key) != this->end()) + { + return 1; + } + else + { + return 0; + } + } + bool contains(const key_type& key) const + { + return this->find(key) != this->end(); + } + iterator find(const key_type& key) noexcept + { + return std::find_if(this->begin(), this->end(), + [&key, this](const value_type& v) {return this->cmp_(v.first, key);}); + } + const_iterator find(const key_type& key) const noexcept + { + return std::find_if(this->begin(), this->end(), + [&key, this](const value_type& v) {return this->cmp_(v.first, key);}); + } + + mapped_type& at(const key_type& k) + { + const auto iter = this->find(k); + if(iter == this->end()) + { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + mapped_type const& at(const key_type& k) const + { + const auto iter = this->find(k); + if(iter == this->end()) + { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + mapped_type& operator[](const key_type& k) + { + const auto iter = this->find(k); + if(iter == this->end()) + { + this->container_.emplace_back(k, mapped_type{}); + return this->container_.back().second; + } + return iter->second; + } + + mapped_type const& operator[](const key_type& k) const + { + const auto iter = this->find(k); + if(iter == this->end()) + { + throw std::out_of_range("ordered_map: no such element"); + } + return iter->second; + } + + key_compare key_comp() const {return this->cmp_;} + + void swap(ordered_map& other) + { + container_.swap(other.container_); + } + + private: + + container_type container_; +}; + +template +bool operator==(const ordered_map& lhs, const ordered_map& rhs) +{ + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); +} +template +bool operator!=(const ordered_map& lhs, const ordered_map& rhs) +{ + return !(lhs == rhs); +} +template +bool operator<(const ordered_map& lhs, const ordered_map& rhs) +{ + return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); +} +template +bool operator>(const ordered_map& lhs, const ordered_map& rhs) +{ + return rhs < lhs; +} +template +bool operator<=(const ordered_map& lhs, const ordered_map& rhs) +{ + return !(lhs > rhs); +} +template +bool operator>=(const ordered_map& lhs, const ordered_map& rhs) +{ + return !(lhs < rhs); +} + +template +void swap(ordered_map& lhs, ordered_map& rhs) +{ + lhs.swap(rhs); + return; +} + + +} // toml +#endif // TOML11_ORDERED_MAP_HPP +#ifndef TOML11_INTO_HPP +#define TOML11_INTO_HPP + +namespace toml +{ + +template +struct into; +// { +// static toml::value into_toml(const T& user_defined_type) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_INTO_HPP +#ifndef TOML11_FROM_HPP +#define TOML11_FROM_HPP + +namespace toml +{ + +template +struct from; +// { +// static T from_toml(const toml::value& v) +// { +// // User-defined conversions ... +// } +// }; + +} // toml +#endif // TOML11_FROM_HPP +#ifndef TOML11_TRAITS_HPP +#define TOML11_TRAITS_HPP + + +#include +#include +#include +#include +#include +#include +#include + +#if defined(TOML11_HAS_STRING_VIEW) +#include +#endif + +namespace toml +{ +template +class basic_value; + +namespace detail +{ +// --------------------------------------------------------------------------- +// check whether type T is a kind of container/map class + +struct has_iterator_impl +{ + template static std::true_type check(typename T::iterator*); + template static std::false_type check(...); +}; +struct has_value_type_impl +{ + template static std::true_type check(typename T::value_type*); + template static std::false_type check(...); +}; +struct has_key_type_impl +{ + template static std::true_type check(typename T::key_type*); + template static std::false_type check(...); +}; +struct has_mapped_type_impl +{ + template static std::true_type check(typename T::mapped_type*); + template static std::false_type check(...); +}; +struct has_reserve_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().reserve(std::declval()))*); +}; +struct has_push_back_method_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval().push_back(std::declval()))*); +}; +struct is_comparable_impl +{ + template static std::false_type check(...); + template static std::true_type check( + decltype(std::declval() < std::declval())*); +}; + +struct has_from_toml_method_impl +{ + template + static std::true_type check( + decltype(std::declval().from_toml(std::declval<::toml::basic_value>()))*); + + template + static std::false_type check(...); +}; +struct has_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().into_toml())*); + template + static std::false_type check(...); +}; + +struct has_template_into_toml_method_impl +{ + template + static std::true_type check(decltype(std::declval().template into_toml())*); + template + static std::false_type check(...); +}; + +struct has_specialized_from_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::from*); +}; +struct has_specialized_into_impl +{ + template + static std::false_type check(...); + template)> + static std::true_type check(::toml::into*); +}; + + +/// Intel C++ compiler can not use decltype in parent class declaration, here +/// is a hack to work around it. https://stackoverflow.com/a/23953090/4692076 +#ifdef __INTEL_COMPILER +#define decltype(...) std::enable_if::type +#endif + +template +struct has_iterator: decltype(has_iterator_impl::check(nullptr)){}; +template +struct has_value_type: decltype(has_value_type_impl::check(nullptr)){}; +template +struct has_key_type: decltype(has_key_type_impl::check(nullptr)){}; +template +struct has_mapped_type: decltype(has_mapped_type_impl::check(nullptr)){}; +template +struct has_reserve_method: decltype(has_reserve_method_impl::check(nullptr)){}; +template +struct has_push_back_method: decltype(has_push_back_method_impl::check(nullptr)){}; +template +struct is_comparable: decltype(is_comparable_impl::check(nullptr)){}; + +template +struct has_from_toml_method: decltype(has_from_toml_method_impl::check(nullptr)){}; + +template +struct has_into_toml_method: decltype(has_into_toml_method_impl::check(nullptr)){}; + +template +struct has_template_into_toml_method: decltype(has_template_into_toml_method_impl::check(nullptr)){}; + +template +struct has_specialized_from: decltype(has_specialized_from_impl::check(nullptr)){}; +template +struct has_specialized_into: decltype(has_specialized_into_impl::check(nullptr)){}; + +#ifdef __INTEL_COMPILER +#undef decltype +#endif + +// --------------------------------------------------------------------------- +// type checkers + +template struct is_std_pair_impl : std::false_type{}; +template +struct is_std_pair_impl> : std::true_type{}; +template +using is_std_pair = is_std_pair_impl>; + +template struct is_std_tuple_impl : std::false_type{}; +template +struct is_std_tuple_impl> : std::true_type{}; +template +using is_std_tuple = is_std_tuple_impl>; + +template struct is_std_array_impl : std::false_type{}; +template +struct is_std_array_impl> : std::true_type{}; +template +using is_std_array = is_std_array_impl>; + +template struct is_std_forward_list_impl : std::false_type{}; +template +struct is_std_forward_list_impl> : std::true_type{}; +template +using is_std_forward_list = is_std_forward_list_impl>; + +template struct is_std_basic_string_impl : std::false_type{}; +template +struct is_std_basic_string_impl> : std::true_type{}; +template +using is_std_basic_string = is_std_basic_string_impl>; + +template struct is_1byte_std_basic_string_impl : std::false_type{}; +template +struct is_1byte_std_basic_string_impl> + : std::integral_constant {}; +template +using is_1byte_std_basic_string = is_std_basic_string_impl>; + +#if defined(TOML11_HAS_STRING_VIEW) +template struct is_std_basic_string_view_impl : std::false_type{}; +template +struct is_std_basic_string_view_impl> : std::true_type{}; +template +using is_std_basic_string_view = is_std_basic_string_view_impl>; + +template +struct is_string_view_of : std::false_type {}; +template +struct is_string_view_of, std::basic_string> : std::true_type {}; +#endif + +template struct is_chrono_duration_impl: std::false_type{}; +template +struct is_chrono_duration_impl>: std::true_type{}; +template +using is_chrono_duration = is_chrono_duration_impl>; + +template +struct is_map_impl : cxx::conjunction< // map satisfies all the following conditions + has_iterator, // has T::iterator + has_value_type, // has T::value_type + has_key_type, // has T::key_type + has_mapped_type // has T::mapped_type + >{}; +template +using is_map = is_map_impl>; + +template +struct is_container_impl : cxx::conjunction< + cxx::negation>, // not a map + cxx::negation>, // not a std::string +#ifdef TOML11_HAS_STRING_VIEW + cxx::negation>, // not a std::string_view +#endif + has_iterator, // has T::iterator + has_value_type // has T::value_type + >{}; +template +using is_container = is_container_impl>; + +template +struct is_basic_value_impl: std::false_type{}; +template +struct is_basic_value_impl<::toml::basic_value>: std::true_type{}; +template +using is_basic_value = is_basic_value_impl>; + +}// detail +}//toml +#endif // TOML11_TRAITS_HPP +#ifndef TOML11_EXCEPTION_HPP +#define TOML11_EXCEPTION_HPP + +#include + +namespace toml +{ + +struct exception : public std::exception +{ + public: + virtual ~exception() noexcept override = default; + virtual const char* what() const noexcept override {return "";} +}; + +} // toml +#endif // TOMl11_EXCEPTION_HPP +#ifndef TOML11_RESULT_HPP +#define TOML11_RESULT_HPP + + +#include +#include +#include +#include + +#include + +namespace toml +{ + +struct bad_result_access final : public ::toml::exception +{ + public: + explicit bad_result_access(std::string what_arg) + : what_(std::move(what_arg)) + {} + ~bad_result_access() noexcept override = default; + const char* what() const noexcept override {return what_.c_str();} + + private: + std::string what_; +}; + +// ----------------------------------------------------------------------------- + +template +struct success +{ + static_assert( ! std::is_same::value, ""); + + using value_type = T; + + explicit success(value_type v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template, T>::value, + std::nullptr_t> = nullptr> + explicit success(U&& v): value(std::forward(v)) {} + + template + explicit success(success v): value(std::move(v.value)) {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; + + value_type& get() noexcept {return value;} + value_type const& get() const noexcept {return value;} + + private: + + value_type value; +}; + +template +struct success> +{ + static_assert( ! std::is_same::value, ""); + + using value_type = T; + + explicit success(std::reference_wrapper v) noexcept + : value(std::move(v)) + {} + + ~success() = default; + success(const success&) = default; + success(success&&) = default; + success& operator=(const success&) = default; + success& operator=(success&&) = default; + + value_type& get() noexcept {return value.get();} + value_type const& get() const noexcept {return value.get();} + + private: + + std::reference_wrapper value; +}; + +template +success::type> ok(T&& v) +{ + return success::type>(std::forward(v)); +} +template +success ok(const char (&literal)[N]) +{ + return success(std::string(literal)); +} + +// ----------------------------------------------------------------------------- + +template +struct failure +{ + using value_type = T; + + explicit failure(value_type v) + noexcept(std::is_nothrow_move_constructible::value) + : value(std::move(v)) + {} + + template, T>::value, + std::nullptr_t> = nullptr> + explicit failure(U&& v): value(std::forward(v)) {} + + template + explicit failure(failure v): value(std::move(v.value)) {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; + + value_type& get() noexcept {return value;} + value_type const& get() const noexcept {return value;} + + private: + + value_type value; +}; + +template +struct failure> +{ + using value_type = T; + + explicit failure(std::reference_wrapper v) noexcept + : value(std::move(v)) + {} + + ~failure() = default; + failure(const failure&) = default; + failure(failure&&) = default; + failure& operator=(const failure&) = default; + failure& operator=(failure&&) = default; + + value_type& get() noexcept {return value.get();} + value_type const& get() const noexcept {return value.get();} + + private: + + std::reference_wrapper value; +}; + +template +failure::type> err(T&& v) +{ + return failure::type>(std::forward(v)); +} + +template +failure err(const char (&literal)[N]) +{ + return failure(std::string(literal)); +} + +/* ============================================================================ + * _ _ + * _ _ ___ ____ _| | |_ + * | '_/ -_|_-< || | | _| + * |_| \___/__/\_,_|_|\__| + */ + +template +struct result +{ + using success_type = success; + using failure_type = failure; + using value_type = typename success_type::value_type; + using error_type = typename failure_type::value_type; + + result(success_type s): is_ok_(true), succ_(std::move(s)) {} + result(failure_type f): is_ok_(false), fail_(std::move(f)) {} + + template, value_type>>, + std::is_convertible, value_type> + >::value, std::nullptr_t> = nullptr> + result(success s): is_ok_(true), succ_(std::move(s.value)) {} + + template, error_type>>, + std::is_convertible, error_type> + >::value, std::nullptr_t> = nullptr> + result(failure f): is_ok_(false), fail_(std::move(f.value)) {} + + result& operator=(success_type s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(s)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + return *this; + } + result& operator=(failure_type f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(f)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + return *this; + } + + template + result& operator=(success s) + { + this->cleanup(); + this->is_ok_ = true; + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(s.value)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + return *this; + } + template + result& operator=(failure f) + { + this->cleanup(); + this->is_ok_ = false; + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(f.value)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + return *this; + } + + ~result() noexcept {this->cleanup();} + + result(const result& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(other.succ_); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(other.fail_); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + result(result&& other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.succ_)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.fail_)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + result& operator=(const result& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(other.succ_); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(other.fail_); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + result& operator=(result&& other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.succ_)); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.fail_)); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + template, value_type>>, + cxx::negation, error_type>>, + std::is_convertible, value_type>, + std::is_convertible, error_type> + >::value, std::nullptr_t> = nullptr> + result(result other): is_ok_(other.is_ok()) + { + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + } + + template, value_type>>, + cxx::negation, error_type>>, + std::is_convertible, value_type>, + std::is_convertible, error_type> + >::value, std::nullptr_t> = nullptr> + result& operator=(result other) + { + this->cleanup(); + if(other.is_ok()) + { + auto tmp = ::new(std::addressof(this->succ_)) success_type(std::move(other.as_ok())); + assert(tmp == std::addressof(this->succ_)); + (void)tmp; + } + else + { + auto tmp = ::new(std::addressof(this->fail_)) failure_type(std::move(other.as_err())); + assert(tmp == std::addressof(this->fail_)); + (void)tmp; + } + is_ok_ = other.is_ok(); + return *this; + } + + bool is_ok() const noexcept {return is_ok_;} + bool is_err() const noexcept {return !is_ok_;} + + explicit operator bool() const noexcept {return is_ok_;} + + value_type& unwrap(cxx::source_location loc = cxx::source_location::current()) + { + if(this->is_err()) + { + throw bad_result_access("toml::result: bad unwrap" + cxx::to_string(loc)); + } + return this->succ_.get(); + } + value_type const& unwrap(cxx::source_location loc = cxx::source_location::current()) const + { + if(this->is_err()) + { + throw bad_result_access("toml::result: bad unwrap" + cxx::to_string(loc)); + } + return this->succ_.get(); + } + + value_type& unwrap_or(value_type& opt) noexcept + { + if(this->is_err()) {return opt;} + return this->succ_.get(); + } + value_type const& unwrap_or(value_type const& opt) const noexcept + { + if(this->is_err()) {return opt;} + return this->succ_.get(); + } + + error_type& unwrap_err(cxx::source_location loc = cxx::source_location::current()) + { + if(this->is_ok()) + { + throw bad_result_access("toml::result: bad unwrap_err" + cxx::to_string(loc)); + } + return this->fail_.get(); + } + error_type const& unwrap_err(cxx::source_location loc = cxx::source_location::current()) const + { + if(this->is_ok()) + { + throw bad_result_access("toml::result: bad unwrap_err" + cxx::to_string(loc)); + } + return this->fail_.get(); + } + + value_type& as_ok() noexcept + { + assert(this->is_ok()); + return this->succ_.get(); + } + value_type const& as_ok() const noexcept + { + assert(this->is_ok()); + return this->succ_.get(); + } + + error_type& as_err() noexcept + { + assert(this->is_err()); + return this->fail_.get(); + } + error_type const& as_err() const noexcept + { + assert(this->is_err()); + return this->fail_.get(); + } + + private: + + void cleanup() noexcept + { +#if defined(__GNUC__) && ! defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wduplicated-branches" +#endif + + if(this->is_ok_) {this->succ_.~success_type();} + else {this->fail_.~failure_type();} + +#if defined(__GNUC__) && ! defined(__clang__) +#pragma GCC diagnostic pop +#endif + return; + } + + private: + + bool is_ok_; + union + { + success_type succ_; + failure_type fail_; + }; +}; + +// ---------------------------------------------------------------------------- + +namespace detail +{ +struct none_t {}; +inline bool operator==(const none_t&, const none_t&) noexcept {return true;} +inline bool operator!=(const none_t&, const none_t&) noexcept {return false;} +inline bool operator< (const none_t&, const none_t&) noexcept {return false;} +inline bool operator<=(const none_t&, const none_t&) noexcept {return true;} +inline bool operator> (const none_t&, const none_t&) noexcept {return false;} +inline bool operator>=(const none_t&, const none_t&) noexcept {return true;} +inline std::ostream& operator<<(std::ostream& os, const none_t&) +{ + os << "none"; + return os; +} +} // detail + +inline success ok() noexcept +{ + return success(detail::none_t{}); +} +inline failure err() noexcept +{ + return failure(detail::none_t{}); +} + +} // toml +#endif // TOML11_RESULT_HPP +#ifndef TOML11_UTILITY_HPP +#define TOML11_UTILITY_HPP + + +#include +#include + +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +// to output character in an error message. +inline std::string show_char(const int c) +{ + using char_type = unsigned char; + if(std::isgraph(c)) + { + return std::string(1, static_cast(c)); + } + else + { + std::array buf; + buf.fill('\0'); + const auto r = std::snprintf(buf.data(), buf.size(), "0x%02x", c & 0xFF); + assert(r == static_cast(buf.size()) - 1); + (void) r; // Unused variable warning + auto in_hex = std::string(buf.data()); + switch(c) + { + case char_type('\0'): {in_hex += "(NUL)"; break;} + case char_type(' ') : {in_hex += "(SPACE)"; break;} + case char_type('\n'): {in_hex += "(LINE FEED)"; break;} + case char_type('\r'): {in_hex += "(CARRIAGE RETURN)"; break;} + case char_type('\t'): {in_hex += "(TAB)"; break;} + case char_type('\v'): {in_hex += "(VERTICAL TAB)"; break;} + case char_type('\f'): {in_hex += "(FORM FEED)"; break;} + case char_type('\x1B'): {in_hex += "(ESCAPE)"; break;} + default: break; + } + return in_hex; + } +} + +// --------------------------------------------------------------------------- + +template +void try_reserve_impl(Container& container, std::size_t N, std::true_type) +{ + container.reserve(N); + return; +} +template +void try_reserve_impl(Container&, std::size_t, std::false_type) noexcept +{ + return; +} + +template +void try_reserve(Container& container, std::size_t N) +{ + try_reserve_impl(container, N, has_reserve_method{}); + return; +} + +// --------------------------------------------------------------------------- + +template +result from_string(const std::string& str) +{ + T v; + std::istringstream iss(str); + iss >> v; + if(iss.fail()) + { + return err(); + } + return ok(v); +} + +// --------------------------------------------------------------------------- + +// helper function to avoid std::string(0, 'c') or std::string(iter, iter) +template +std::string make_string(Iterator first, Iterator last) +{ + if(first == last) {return "";} + return std::string(first, last); +} +inline std::string make_string(std::size_t len, char c) +{ + if(len == 0) {return "";} + return std::string(len, c); +} + +// --------------------------------------------------------------------------- + +template +struct string_conv_impl +{ + static_assert(sizeof(Char) == sizeof(char), ""); + static_assert(sizeof(Char2) == sizeof(char), ""); + + static std::basic_string invoke(std::basic_string s) + { + std::basic_string retval; + std::transform(s.begin(), s.end(), std::back_inserter(retval), + [](const Char2 c) {return static_cast(c);}); + return retval; + } + template + static std::basic_string invoke(const Char2 (&s)[N]) + { + std::basic_string retval; + // "string literal" has null-char at the end. to skip it, we use prev. + std::transform(std::begin(s), std::prev(std::end(s)), std::back_inserter(retval), + [](const Char2 c) {return static_cast(c);}); + return retval; + } +}; + +template +struct string_conv_impl +{ + static_assert(sizeof(Char) == sizeof(char), ""); + + static std::basic_string invoke(std::basic_string s) + { + return s; + } + template + static std::basic_string invoke(const Char (&s)[N]) + { + return std::basic_string(s); + } +}; + +template +cxx::enable_if_t::value, S> +string_conv(std::basic_string s) +{ + using C = typename S::value_type; + using T = typename S::traits_type; + using A = typename S::allocator_type; + return string_conv_impl::invoke(std::move(s)); +} +template +cxx::enable_if_t::value, S> +string_conv(const char (&s)[N]) +{ + using C = typename S::value_type; + using T = typename S::traits_type; + using A = typename S::allocator_type; + using C2 = char; + using T2 = std::char_traits; + using A2 = std::allocator; + + return string_conv_impl::template invoke(s); +} + +} // namespace detail +} // namespace toml +#endif // TOML11_UTILITY_HPP +#ifndef TOML11_LOCATION_HPP +#define TOML11_LOCATION_HPP + +#ifndef TOML11_LOCATION_FWD_HPP +#define TOML11_LOCATION_FWD_HPP + + +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +class region; // fwd decl + +// +// To represent where we are reading in the parse functions. +// Since it "points" somewhere in the input stream, the length is always 1. +// +class location +{ + public: + + using char_type = unsigned char; // must be unsigned + using container_type = std::vector; + using difference_type = typename container_type::difference_type; // to suppress sign-conversion warning + using source_ptr = std::shared_ptr; + + public: + + location(source_ptr src, std::string src_name) + : source_(std::move(src)), source_name_(std::move(src_name)), + location_(0), line_number_(1) + {} + + location(const location&) = default; + location(location&&) = default; + location& operator=(const location&) = default; + location& operator=(location&&) = default; + ~location() = default; + + void advance(std::size_t n = 1) noexcept; + void retrace(std::size_t n = 1) noexcept; + + bool is_ok() const noexcept { return static_cast(this->source_); } + + bool eof() const noexcept; + char_type current() const; + + char_type peek(); + + std::size_t get_location() const noexcept + { + return this->location_; + } + void set_location(const std::size_t loc) noexcept; + + std::size_t line_number() const noexcept + { + return this->line_number_; + } + std::string get_line() const; + std::size_t column_number() const noexcept; + + source_ptr const& source() const noexcept {return this->source_;} + std::string const& source_name() const noexcept {return this->source_name_;} + + private: + + void advance_line_number(const std::size_t n); + void retrace_line_number(const std::size_t n); + + private: + + friend region; + + private: + + source_ptr source_; + std::string source_name_; + std::size_t location_; // std::vector<>::difference_type is signed + std::size_t line_number_; +}; + +bool operator==(const location& lhs, const location& rhs) noexcept; +bool operator!=(const location& lhs, const location& rhs); + +location prev(const location& loc); +location next(const location& loc); +location make_temporary_location(const std::string& str) noexcept; + +template +result +find_if(const location& first, const location& last, const F& func) noexcept +{ + if(first.source() != last.source()) { return err(); } + if(first.get_location() >= last.get_location()) { return err(); } + + auto loc = first; + while(loc.get_location() != last.get_location()) + { + if(func(loc.current())) + { + return ok(loc); + } + loc.advance(); + } + return err(); +} + +template +result +rfind_if(location first, const location& last, const F& func) +{ + if(first.source() != last.source()) { return err(); } + if(first.get_location() >= last.get_location()) { return err(); } + + auto loc = last; + while(loc.get_location() != first.get_location()) + { + if(func(loc.current())) + { + return ok(loc); + } + loc.retrace(); + } + if(func(first.current())) + { + return ok(first); + } + return err(); +} + +result find(const location& first, const location& last, + const location::char_type val); +result rfind(const location& first, const location& last, + const location::char_type val); + +std::size_t count(const location& first, const location& last, + const location::char_type& c); + +} // detail +} // toml +#endif // TOML11_LOCATION_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_LOCATION_IMPL_HPP +#define TOML11_LOCATION_IMPL_HPP + + +namespace toml +{ +namespace detail +{ + +TOML11_INLINE void location::advance(std::size_t n) noexcept +{ + assert(this->is_ok()); + if(this->location_ + n < this->source_->size()) + { + this->advance_line_number(n); + this->location_ += n; + } + else + { + this->advance_line_number(this->source_->size() - this->location_); + this->location_ = this->source_->size(); + } +} +TOML11_INLINE void location::retrace(std::size_t n) noexcept +{ + assert(this->is_ok()); + if(this->location_ < n) + { + this->location_ = 0; + this->line_number_ = 1; + } + else + { + this->retrace_line_number(n); + this->location_ -= n; + } +} + +TOML11_INLINE bool location::eof() const noexcept +{ + assert(this->is_ok()); + return this->location_ >= this->source_->size(); +} +TOML11_INLINE location::char_type location::current() const +{ + assert(this->is_ok()); + if(this->eof()) {return '\0';} + + assert(this->location_ < this->source_->size()); + return this->source_->at(this->location_); +} + +TOML11_INLINE location::char_type location::peek() +{ + assert(this->is_ok()); + if(this->location_ >= this->source_->size()) + { + return '\0'; + } + else + { + return this->source_->at(this->location_ + 1); + } +} + +TOML11_INLINE void location::set_location(const std::size_t loc) noexcept +{ + if(this->location_ == loc) + { + return ; + } + + if(loc == 0) + { + this->line_number_ = 1; + } + else if(this->location_ < loc) + { + const auto d = loc - this->location_; + this->advance_line_number(d); + } + else + { + const auto d = this->location_ - loc; + this->retrace_line_number(d); + } + this->location_ = loc; +} + +TOML11_INLINE std::string location::get_line() const +{ + assert(this->is_ok()); + const auto iter = std::next(this->source_->cbegin(), static_cast(this->location_)); + const auto riter = cxx::make_reverse_iterator(iter); + + const auto prev = std::find(riter, this->source_->crend(), char_type('\n')); + const auto next = std::find(iter, this->source_->cend(), char_type('\n')); + + return make_string(std::next(prev.base()), next); +} +TOML11_INLINE std::size_t location::column_number() const noexcept +{ + assert(this->is_ok()); + const auto iter = std::next(this->source_->cbegin(), static_cast(this->location_)); + const auto riter = cxx::make_reverse_iterator(iter); + const auto prev = std::find(riter, this->source_->crend(), char_type('\n')); + + assert(prev.base() <= iter); + return static_cast(std::distance(prev.base(), iter) + 1); // 1-origin +} + + +TOML11_INLINE void location::advance_line_number(const std::size_t n) +{ + assert(this->is_ok()); + assert(this->location_ + n <= this->source_->size()); + + const auto iter = this->source_->cbegin(); + this->line_number_ += static_cast(std::count( + std::next(iter, static_cast(this->location_)), + std::next(iter, static_cast(this->location_ + n)), + char_type('\n'))); + + return; +} +TOML11_INLINE void location::retrace_line_number(const std::size_t n) +{ + assert(this->is_ok()); + assert(n <= this->location_); // loc - n >= 0 + + const auto iter = this->source_->cbegin(); + const auto dline_num = static_cast(std::count( + std::next(iter, static_cast(this->location_ - n)), + std::next(iter, static_cast(this->location_)), + char_type('\n'))); + + if(this->line_number_ <= dline_num) + { + this->line_number_ = 1; + } + else + { + this->line_number_ -= dline_num; + } + return; +} + +TOML11_INLINE bool operator==(const location& lhs, const location& rhs) noexcept +{ + if( ! lhs.is_ok() || ! rhs.is_ok()) + { + return (!lhs.is_ok()) && (!rhs.is_ok()); + } + return lhs.source() == rhs.source() && + lhs.source_name() == rhs.source_name() && + lhs.get_location() == rhs.get_location(); +} +TOML11_INLINE bool operator!=(const location& lhs, const location& rhs) +{ + return !(lhs == rhs); +} + +TOML11_INLINE location prev(const location& loc) +{ + location p(loc); + p.retrace(1); + return p; +} +TOML11_INLINE location next(const location& loc) +{ + location p(loc); + p.advance(1); + return p; +} + +TOML11_INLINE location make_temporary_location(const std::string& str) noexcept +{ + location::container_type cont(str.size()); + std::transform(str.begin(), str.end(), cont.begin(), + [](const std::string::value_type& c) { + return cxx::bit_cast(c); + }); + return location(std::make_shared( + std::move(cont)), "internal temporary"); +} + +TOML11_INLINE result +find(const location& first, const location& last, const location::char_type val) +{ + return find_if(first, last, [val](const location::char_type c) { + return c == val; + }); +} +TOML11_INLINE result +rfind(const location& first, const location& last, const location::char_type val) +{ + return rfind_if(first, last, [val](const location::char_type c) { + return c == val; + }); +} + +TOML11_INLINE std::size_t +count(const location& first, const location& last, const location::char_type& c) +{ + if(first.source() != last.source()) { return 0; } + if(first.get_location() >= last.get_location()) { return 0; } + + auto loc = first; + std::size_t num = 0; + while(loc.get_location() != last.get_location()) + { + if(loc.current() == c) + { + num += 1; + } + loc.advance(); + } + return num; +} + +} // detail +} // toml +#endif // TOML11_LOCATION_HPP +#endif + +#endif // TOML11_LOCATION_HPP +#ifndef TOML11_REGION_HPP +#define TOML11_REGION_HPP + +#ifndef TOML11_REGION_FWD_HPP +#define TOML11_REGION_FWD_HPP + + +#include +#include + +#include + +namespace toml +{ +namespace detail +{ + +// +// To represent where is a toml::value defined, or where does an error occur. +// Stored in toml::value. source_location will be constructed based on this. +// +class region +{ + public: + + using char_type = location::char_type; + using container_type = location::container_type; + using difference_type = location::difference_type; + using source_ptr = location::source_ptr; + + using iterator = typename container_type::iterator; + using const_iterator = typename container_type::const_iterator; + + public: + + // a value that is constructed manually does not have input stream info + region() + : source_(nullptr), source_name_(""), length_(0), + first_line_(0), first_column_(0), last_line_(0), last_column_(0) + {} + + // a value defined in [first, last). + // Those source must be the same. Instread, `region` does not make sense. + region(const location& first, const location& last); + + // shorthand of [loc, loc+1) + explicit region(const location& loc); + + ~region() = default; + region(const region&) = default; + region(region&&) = default; + region& operator=(const region&) = default; + region& operator=(region&&) = default; + + bool is_ok() const noexcept { return static_cast(this->source_); } + + operator bool() const noexcept { return this->is_ok(); } + + std::size_t length() const noexcept {return this->length_;} + + std::size_t first_line_number() const noexcept + { + return this->first_line_; + } + std::size_t first_column_number() const noexcept + { + return this->first_column_; + } + std::size_t last_line_number() const noexcept + { + return this->last_line_; + } + std::size_t last_column_number() const noexcept + { + return this->last_column_; + } + + char_type at(std::size_t i) const; + + const_iterator begin() const noexcept; + const_iterator end() const noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + + std::string as_string() const; + std::vector as_lines() const; + + source_ptr const& source() const noexcept {return this->source_;} + std::string const& source_name() const noexcept {return this->source_name_;} + + private: + + source_ptr source_; + std::string source_name_; + std::size_t length_; + std::size_t first_; + std::size_t first_line_; + std::size_t first_column_; + std::size_t last_; + std::size_t last_line_; + std::size_t last_column_; +}; + +} // namespace detail +} // namespace toml +#endif // TOML11_REGION_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_REGION_IMPL_HPP +#define TOML11_REGION_IMPL_HPP + + +#include +#include +#include +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +// a value defined in [first, last). +// Those source must be the same. Instread, `region` does not make sense. +TOML11_INLINE region::region(const location& first, const location& last) + : source_(first.source()), source_name_(first.source_name()), + length_(last.get_location() - first.get_location()), + first_(first.get_location()), + first_line_(first.line_number()), + first_column_(first.column_number()), + last_(last.get_location()), + last_line_(last.line_number()), + last_column_(last.column_number()) +{ + assert(first.source() == last.source()); + assert(first.source_name() == last.source_name()); +} + + // shorthand of [loc, loc+1) +TOML11_INLINE region::region(const location& loc) + : source_(loc.source()), source_name_(loc.source_name()), length_(0), + first_line_(0), first_column_(0), last_line_(0), last_column_(0) +{ + // if the file ends with LF, the resulting region points no char. + if(loc.eof()) + { + if(loc.get_location() == 0) + { + this->length_ = 0; + this->first_ = 0; + this->first_line_ = 0; + this->first_column_ = 0; + this->last_ = 0; + this->last_line_ = 0; + this->last_column_ = 0; + } + else + { + const auto first = prev(loc); + this->first_ = first.get_location(); + this->first_line_ = first.line_number(); + this->first_column_ = first.column_number(); + this->last_ = loc.get_location(); + this->last_line_ = loc.line_number(); + this->last_column_ = loc.column_number(); + this->length_ = 1; + } + } + else + { + this->first_ = loc.get_location(); + this->first_line_ = loc.line_number(); + this->first_column_ = loc.column_number(); + this->last_ = loc.get_location() + 1; + this->last_line_ = loc.line_number(); + this->last_column_ = loc.column_number() + 1; + this->length_ = 1; + } +} + +TOML11_INLINE region::char_type region::at(std::size_t i) const +{ + if(this->last_ <= this->first_ + i) + { + throw std::out_of_range("range::at: index " + std::to_string(i) + + " exceeds length " + std::to_string(this->length_)); + } + const auto iter = std::next(this->source_->cbegin(), + static_cast(this->first_ + i)); + return *iter; +} + +TOML11_INLINE region::const_iterator region::begin() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->first_)); +} +TOML11_INLINE region::const_iterator region::end() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->last_)); +} +TOML11_INLINE region::const_iterator region::cbegin() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->first_)); +} +TOML11_INLINE region::const_iterator region::cend() const noexcept +{ + return std::next(this->source_->cbegin(), + static_cast(this->last_)); +} + +TOML11_INLINE std::string region::as_string() const +{ + if(this->is_ok()) + { + const auto begin = std::next(this->source_->cbegin(), static_cast(this->first_)); + const auto end = std::next(this->source_->cbegin(), static_cast(this->last_ )); + return ::toml::detail::make_string(begin, end); + } + else + { + return std::string(""); + } +} + +TOML11_INLINE std::vector region::as_lines() const +{ + assert(this->is_ok()); + if(this->length_ == 0) + { + return std::vector{""}; + } + + // Consider the following toml file + // ``` + // array = [ + // ] # comment + // ``` + // and the region represnets + // ``` + // [ + // ] + // ``` + // but we want to show the following. + // ``` + // array = [ + // ] # comment + // ``` + // So we need to find LFs before `begin` and after `end`. + // + // But, if region ends with LF, it should not include the next line. + // ``` + // a = 42 + // ^^^- with the last LF + // ``` + // So we start from `end-1` when looking for LF. + + const auto begin_idx = static_cast(this->first_); + const auto end_idx = static_cast(this->last_) - 1; + + // length_ != 0, so begin < end. then begin <= end-1 + assert(begin_idx <= end_idx); + + const auto begin = std::next(this->source_->cbegin(), begin_idx); + const auto end = std::next(this->source_->cbegin(), end_idx); + + const auto line_begin = std::find(cxx::make_reverse_iterator(begin), this->source_->crend(), char_type('\n')).base(); + const auto line_end = std::find(end, this->source_->cend(), char_type('\n')); + + const auto reg_lines = make_string(line_begin, line_end); + + if(reg_lines == "") // the region is an empty line that only contains LF + { + return std::vector{""}; + } + + std::istringstream iss(reg_lines); + + std::vector lines; + std::string line; + while(std::getline(iss, line)) + { + lines.push_back(line); + } + return lines; +} + +} // namespace detail +} // namespace toml +#endif // TOML11_REGION_IMPL_HPP +#endif + +#endif // TOML11_REGION_HPP +#ifndef TOML11_SOURCE_LOCATION_HPP +#define TOML11_SOURCE_LOCATION_HPP + +#ifndef TOML11_SOURCE_LOCATION_FWD_HPP +#define TOML11_SOURCE_LOCATION_FWD_HPP + + +#include +#include +#include + +namespace toml +{ + +// A struct to contain location in a toml file. +struct source_location +{ + public: + + explicit source_location(const detail::region& r); + ~source_location() = default; + source_location(source_location const&) = default; + source_location(source_location &&) = default; + source_location& operator=(source_location const&) = default; + source_location& operator=(source_location &&) = default; + + bool is_ok() const noexcept {return this->is_ok_;} + std::size_t length() const noexcept {return this->length_;} + + std::size_t first_line_number() const noexcept {return this->first_line_;} + std::size_t first_column_number() const noexcept {return this->first_column_;} + std::size_t last_line_number() const noexcept {return this->last_line_;} + std::size_t last_column_number() const noexcept {return this->last_column_;} + + std::string const& file_name() const noexcept {return this->file_name_;} + + std::size_t num_lines() const noexcept {return this->line_str_.size();} + + std::string const& first_line() const; + std::string const& last_line() const; + + std::vector const& lines() const noexcept {return line_str_;} + + private: + + bool is_ok_; + std::size_t first_line_; + std::size_t first_column_; + std::size_t last_line_; + std::size_t last_column_; + std::size_t length_; + std::string file_name_; + std::vector line_str_; +}; + +namespace detail +{ + +std::size_t integer_width_base10(std::size_t i) noexcept; + +inline std::size_t line_width() noexcept {return 0;} + +template +std::size_t line_width(const source_location& loc, const std::string& /*msg*/, + const Ts& ... tail) noexcept +{ + return (std::max)( + integer_width_base10(loc.last_line_number()), line_width(tail...)); +} + +std::ostringstream& +format_filename(std::ostringstream& oss, const source_location& loc); + +std::ostringstream& +format_empty_line(std::ostringstream& oss, const std::size_t lnw); + +std::ostringstream& format_line(std::ostringstream& oss, + const std::size_t lnw, const std::size_t linenum, const std::string& line); + +std::ostringstream& format_underline(std::ostringstream& oss, + const std::size_t lnw, const std::size_t col, const std::size_t len, + const std::string& msg); + +std::string format_location_impl(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, const std::string& msg); + +inline std::string format_location_rec(const std::size_t, const std::string&) +{ + return ""; +} + +template +std::string format_location_rec(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, const std::string& msg, + const Ts& ... tail) +{ + return format_location_impl(lnw, prev_fname, loc, msg) + + format_location_rec(lnw, loc.file_name(), tail...); +} + +} // namespace detail + +// format a location info without title +template +std::string format_location( + const source_location& loc, const std::string& msg, const Ts& ... tail) +{ + const auto lnw = detail::line_width(loc, msg, tail...); + + const std::string f(""); // at the 1st iteration, no prev_filename is given + return detail::format_location_rec(lnw, f, loc, msg, tail...); +} + +} // toml +#endif // TOML11_SOURCE_LOCATION_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_SOURCE_LOCATION_IMPL_HPP +#define TOML11_SOURCE_LOCATION_IMPL_HPP + + + +#include +#include +#include +#include + +#include + +namespace toml +{ + +TOML11_INLINE source_location::source_location(const detail::region& r) + : is_ok_(false), + first_line_(1), + first_column_(1), + last_line_(1), + last_column_(1), + length_(0), + file_name_("unknown file") +{ + if(r.is_ok()) + { + this->is_ok_ = true; + this->file_name_ = r.source_name(); + this->first_line_ = r.first_line_number(); + this->first_column_ = r.first_column_number(); + this->last_line_ = r.last_line_number(); + this->last_column_ = r.last_column_number(); + this->length_ = r.length(); + this->line_str_ = r.as_lines(); + } +} + +TOML11_INLINE std::string const& source_location::first_line() const +{ + if(this->line_str_.size() == 0) + { + throw std::out_of_range("toml::source_location::first_line: `lines` is empty"); + } + return this->line_str_.front(); +} +TOML11_INLINE std::string const& source_location::last_line() const +{ + if(this->line_str_.size() == 0) + { + throw std::out_of_range("toml::source_location::first_line: `lines` is empty"); + } + return this->line_str_.back(); +} + +namespace detail +{ + +TOML11_INLINE std::size_t integer_width_base10(std::size_t i) noexcept +{ + std::size_t width = 0; + while(i != 0) + { + i /= 10; + width += 1; + } + return width; +} + +TOML11_INLINE std::ostringstream& +format_filename(std::ostringstream& oss, const source_location& loc) +{ + // --> example.toml + oss << color::bold << color::blue << " --> " << color::reset + << color::bold << loc.file_name() << '\n' << color::reset; + return oss; +} + +TOML11_INLINE std::ostringstream& format_empty_line(std::ostringstream& oss, + const std::size_t lnw) +{ + // | + oss << detail::make_string(lnw + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + return oss; +} + +TOML11_INLINE std::ostringstream& format_line(std::ostringstream& oss, + const std::size_t lnw, const std::size_t linenum, const std::string& line) +{ + // 10 | key = "value" + oss << ' ' << color::bold << color::blue + << std::setw(static_cast(lnw)) + << std::right << linenum << " | " << color::reset; + for(const char c : line) + { + if(std::isgraph(c) || c == ' ') + { + oss << c; + } + else + { + oss << show_char(c); + } + } + oss << '\n'; + return oss; +} +TOML11_INLINE std::ostringstream& format_underline(std::ostringstream& oss, + const std::size_t lnw, const std::size_t col, const std::size_t len, + const std::string& msg) +{ + // | ^^^^^^^-- this part + oss << make_string(lnw + 1, ' ') + << color::bold << color::blue << " | " << color::reset; + + oss << make_string(col-1 /*1-origin*/, ' ') + << color::bold << color::red + << make_string(len, '^') << "-- " + << color::reset << msg << '\n'; + + return oss; +} + +TOML11_INLINE std::string format_location_impl(const std::size_t lnw, + const std::string& prev_fname, + const source_location& loc, const std::string& msg) +{ + std::ostringstream oss; + + if(loc.file_name() != prev_fname) + { + format_filename(oss, loc); + if( ! loc.lines().empty()) + { + format_empty_line(oss, lnw); + } + } + + if(loc.lines().size() == 1) + { + // when column points LF, it exceeds the size of the first line. + std::size_t underline_limit = 1; + if(loc.first_line().size() < loc.first_column_number()) + { + underline_limit = 1; + } + else + { + underline_limit = loc.first_line().size() - loc.first_column_number() + 1; + } + const auto underline_len = (std::min)(underline_limit, loc.length()); + + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_number(), underline_len, msg); + } + else if(loc.lines().size() == 2) + { + const auto first_underline_len = + loc.first_line().size() - loc.first_column_number() + 1; + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_number(), + first_underline_len, ""); + + format_line(oss, lnw, loc.last_line_number(), loc.last_line()); + format_underline(oss, lnw, 1, loc.last_column_number(), msg); + } + else if(loc.lines().size() > 2) + { + const auto first_underline_len = + loc.first_line().size() - loc.first_column_number() + 1; + format_line(oss, lnw, loc.first_line_number(), loc.first_line()); + format_underline(oss, lnw, loc.first_column_number(), + first_underline_len, "and"); + + if(loc.lines().size() == 3) + { + format_line(oss, lnw, loc.first_line_number()+1, loc.lines().at(1)); + format_underline(oss, lnw, 1, loc.lines().at(1).size(), "and"); + } + else + { + format_line(oss, lnw, loc.first_line_number()+1, " ..."); + format_empty_line(oss, lnw); + } + format_line(oss, lnw, loc.last_line_number(), loc.last_line()); + format_underline(oss, lnw, 1, loc.last_column_number(), msg); + } + // if loc is empty, do nothing. + return oss.str(); +} + +} // namespace detail +} // toml +#endif // TOML11_SOURCE_LOCATION_IMPL_HPP +#endif + +#endif // TOML11_SOURCE_LOCATION_HPP +#ifndef TOML11_ERROR_INFO_HPP +#define TOML11_ERROR_INFO_HPP + +#ifndef TOML11_ERROR_INFO_FWD_HPP +#define TOML11_ERROR_INFO_FWD_HPP + + +namespace toml +{ + +// error info returned from parser. +struct error_info +{ + error_info(std::string t, source_location l, std::string m, std::string s = "") + : title_(std::move(t)), locations_{std::make_pair(std::move(l), std::move(m))}, + suffix_(std::move(s)) + {} + + error_info(std::string t, std::vector> l, + std::string s = "") + : title_(std::move(t)), locations_(std::move(l)), suffix_(std::move(s)) + {} + + std::string const& title() const noexcept {return title_;} + std::string & title() noexcept {return title_;} + + std::vector> const& + locations() const noexcept {return locations_;} + + void add_locations(source_location loc, std::string msg) noexcept + { + locations_.emplace_back(std::move(loc), std::move(msg)); + } + + std::string const& suffix() const noexcept {return suffix_;} + std::string & suffix() noexcept {return suffix_;} + + private: + + std::string title_; + std::vector> locations_; + std::string suffix_; // hint or something like that +}; + +// forward decl +template +class basic_value; + +namespace detail +{ +inline error_info make_error_info_rec(error_info e) +{ + return e; +} +inline error_info make_error_info_rec(error_info e, std::string s) +{ + e.suffix() = s; + return e; +} + +template +error_info make_error_info_rec(error_info e, + const basic_value& v, std::string msg, Ts&& ... tail); + +template +error_info make_error_info_rec(error_info e, + source_location loc, std::string msg, Ts&& ... tail) +{ + e.add_locations(std::move(loc), std::move(msg)); + return make_error_info_rec(std::move(e), std::forward(tail)...); +} + +} // detail + +template +error_info make_error_info( + std::string title, source_location loc, std::string msg, Ts&& ... tail) +{ + error_info ei(std::move(title), std::move(loc), std::move(msg)); + return detail::make_error_info_rec(ei, std::forward(tail) ... ); +} + +std::string format_error(const std::string& errkind, const error_info& err); +std::string format_error(const error_info& err); + +// for custom error message +template +std::string format_error(std::string title, + source_location loc, std::string msg, Ts&& ... tail) +{ + return format_error("", make_error_info(std::move(title), + std::move(loc), std::move(msg), std::forward(tail)...)); +} + +std::ostream& operator<<(std::ostream& os, const error_info& e); + +} // toml +#endif // TOML11_ERROR_INFO_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_ERROR_INFO_IMPL_HPP +#define TOML11_ERROR_INFO_IMPL_HPP + + +#include + +namespace toml +{ + +TOML11_INLINE std::string format_error(const std::string& errkind, const error_info& err) +{ + std::string errmsg; + if( ! errkind.empty()) + { + errmsg = errkind; + errmsg += ' '; + } + errmsg += err.title(); + errmsg += '\n'; + + const auto lnw = [&err]() { + std::size_t width = 0; + for(const auto& l : err.locations()) + { + width = (std::max)(detail::integer_width_base10(l.first.last_line_number()), width); + } + return width; + }(); + + bool first = true; + std::string prev_fname; + for(const auto& lm : err.locations()) + { + if( ! first) + { + std::ostringstream oss; + oss << detail::make_string(lnw + 1, ' ') + << color::bold << color::blue << " |" << color::reset + << color::bold << " ...\n" << color::reset; + oss << detail::make_string(lnw + 1, ' ') + << color::bold << color::blue << " |\n" << color::reset; + errmsg += oss.str(); + } + + const auto& l = lm.first; + const auto& m = lm.second; + + errmsg += detail::format_location_impl(lnw, prev_fname, l, m); + + prev_fname = l.file_name(); + first = false; + } + + errmsg += err.suffix(); + + return errmsg; +} + +TOML11_INLINE std::string format_error(const error_info& err) +{ + std::ostringstream oss; + oss << color::red << color::bold << "[error]" << color::reset; + return format_error(oss.str(), err); +} + +TOML11_INLINE std::ostream& operator<<(std::ostream& os, const error_info& e) +{ + os << format_error(e); + return os; +} + +} // toml +#endif // TOML11_ERROR_INFO_IMPL_HPP +#endif + +#endif // TOML11_ERROR_INFO_HPP +#ifndef TOML11_VALUE_HPP +#define TOML11_VALUE_HPP + + +#ifdef TOML11_HAS_STRING_VIEW +#include +#endif + +#include + +namespace toml +{ +template +class basic_value; + +struct type_error final : public ::toml::exception +{ + public: + type_error(std::string what_arg, source_location loc) + : what_(std::move(what_arg)), loc_(std::move(loc)) + {} + ~type_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + + source_location const& location() const noexcept {return loc_;} + + private: + std::string what_; + source_location loc_; +}; + +// only for internal use +namespace detail +{ +template +error_info make_type_error(const basic_value&, const std::string&, const value_t); + +template +error_info make_not_found_error(const basic_value&, const std::string&, const typename basic_value::key_type&); + +template +void change_region_of_value(basic_value&, const basic_value&); + +template +struct getter; +} // detail + +template +class basic_value +{ + public: + + using config_type = TypeConfig; + using key_type = typename config_type::string_type; + using value_type = basic_value; + using boolean_type = typename config_type::boolean_type; + using integer_type = typename config_type::integer_type; + using floating_type = typename config_type::floating_type; + using string_type = typename config_type::string_type; + using local_time_type = ::toml::local_time; + using local_date_type = ::toml::local_date; + using local_datetime_type = ::toml::local_datetime; + using offset_datetime_type = ::toml::offset_datetime; + using array_type = typename config_type::template array_type; + using table_type = typename config_type::template table_type; + using comment_type = typename config_type::comment_type; + using char_type = typename string_type::value_type; + + private: + + using region_type = detail::region; + + public: + + basic_value() noexcept + : type_(value_t::empty), empty_('\0'), region_{}, comments_{} + {} + ~basic_value() noexcept {this->cleanup();} + + // copy/move constructor/assigner ===================================== {{{ + + basic_value(const basic_value& v) + : type_(v.type_), region_(v.region_), comments_(v.comments_) + { + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default : assigner(empty_ , '\0' ); break; + } + } + basic_value(basic_value&& v) + : type_(v.type()), region_(std::move(v.region_)), + comments_(std::move(v.comments_)) + { + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default : assigner(empty_ , '\0' ); break; + } + } + + basic_value& operator=(const basic_value& v) + { + if(this == std::addressof(v)) {return *this;} + + this->cleanup(); + this->type_ = v.type_; + this->region_ = v.region_; + this->comments_ = v.comments_; + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , v.boolean_ ); break; + case value_t::integer : assigner(integer_ , v.integer_ ); break; + case value_t::floating : assigner(floating_ , v.floating_ ); break; + case value_t::string : assigner(string_ , v.string_ ); break; + case value_t::offset_datetime: assigner(offset_datetime_, v.offset_datetime_); break; + case value_t::local_datetime : assigner(local_datetime_ , v.local_datetime_ ); break; + case value_t::local_date : assigner(local_date_ , v.local_date_ ); break; + case value_t::local_time : assigner(local_time_ , v.local_time_ ); break; + case value_t::array : assigner(array_ , v.array_ ); break; + case value_t::table : assigner(table_ , v.table_ ); break; + default : assigner(empty_ , '\0' ); break; + } + return *this; + } + basic_value& operator=(basic_value&& v) + { + if(this == std::addressof(v)) {return *this;} + + this->cleanup(); + this->type_ = v.type_; + this->region_ = std::move(v.region_); + this->comments_ = std::move(v.comments_); + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default : assigner(empty_ , '\0' ); break; + } + return *this; + } + // }}} + + // constructor to overwrite commnets ================================== {{{ + + basic_value(basic_value v, std::vector com) + : type_(v.type()), region_(std::move(v.region_)), + comments_(std::move(com)) + { + switch(this->type_) + { + case value_t::boolean : assigner(boolean_ , std::move(v.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(v.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(v.floating_ )); break; + case value_t::string : assigner(string_ , std::move(v.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(v.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(v.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(v.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(v.local_time_ )); break; + case value_t::array : assigner(array_ , std::move(v.array_ )); break; + case value_t::table : assigner(table_ , std::move(v.table_ )); break; + default : assigner(empty_ , '\0' ); break; + } + } + // }}} + + // conversion between different basic_values ========================== {{{ + + template + basic_value(basic_value other) + : type_(other.type_), + region_(std::move(other.region_)), + comments_(std::move(other.comments_)) + { + switch(other.type_) + { + // use auto-convert in constructor + case value_t::boolean : assigner(boolean_ , std::move(other.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(other.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(other.floating_ )); break; + case value_t::string : assigner(string_ , std::move(other.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(other.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(other.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(other.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(other.local_time_ )); break; + + // may have different container type + case value_t::array : + { + array_type tmp( + std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, array_storage( + detail::storage(std::move(tmp)), + other.array_.format + )); + break; + } + case value_t::table : + { + table_type tmp( + std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, table_storage( + detail::storage(std::move(tmp)), + other.table_.format + )); + break; + } + default: break; + } + } + + template + basic_value(basic_value other, std::vector com) + : type_(other.type_), + region_(std::move(other.region_)), + comments_(std::move(com)) + { + switch(other.type_) + { + // use auto-convert in constructor + case value_t::boolean : assigner(boolean_ , std::move(other.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(other.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(other.floating_ )); break; + case value_t::string : assigner(string_ , std::move(other.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(other.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(other.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(other.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(other.local_time_ )); break; + + // may have different container type + case value_t::array : + { + array_type tmp( + std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, array_storage( + detail::storage(std::move(tmp)), + other.array_.format + )); + break; + } + case value_t::table : + { + table_type tmp( + std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, table_storage( + detail::storage(std::move(tmp)), + other.table_.format + )); + break; + } + default: break; + } + } + template + basic_value& operator=(basic_value other) + { + this->cleanup(); + this->region_ = other.region_; + this->comments_ = comment_type(other.comments_); + this->type_ = other.type_; + switch(other.type_) + { + // use auto-convert in constructor + case value_t::boolean : assigner(boolean_ , std::move(other.boolean_ )); break; + case value_t::integer : assigner(integer_ , std::move(other.integer_ )); break; + case value_t::floating : assigner(floating_ , std::move(other.floating_ )); break; + case value_t::string : assigner(string_ , std::move(other.string_ )); break; + case value_t::offset_datetime: assigner(offset_datetime_, std::move(other.offset_datetime_)); break; + case value_t::local_datetime : assigner(local_datetime_ , std::move(other.local_datetime_ )); break; + case value_t::local_date : assigner(local_date_ , std::move(other.local_date_ )); break; + case value_t::local_time : assigner(local_time_ , std::move(other.local_time_ )); break; + + // may have different container type + case value_t::array : + { + array_type tmp( + std::make_move_iterator(other.array_.value.get().begin()), + std::make_move_iterator(other.array_.value.get().end())); + assigner(array_, array_storage( + detail::storage(std::move(tmp)), + other.array_.format + )); + break; + } + case value_t::table : + { + table_type tmp( + std::make_move_iterator(other.table_.value.get().begin()), + std::make_move_iterator(other.table_.value.get().end())); + assigner(table_, table_storage( + detail::storage(std::move(tmp)), + other.table_.format + )); + break; + } + default: break; + } + return *this; + } + // }}} + + // constructor (boolean) ============================================== {{{ + + basic_value(boolean_type x) + : basic_value(x, boolean_format_info{}, std::vector{}, region_type{}) + {} + basic_value(boolean_type x, boolean_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(boolean_type x, std::vector com) + : basic_value(x, boolean_format_info{}, std::move(com), region_type{}) + {} + basic_value(boolean_type x, boolean_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(boolean_type x, boolean_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::boolean), boolean_(boolean_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(boolean_type x) + { + boolean_format_info fmt; + if(this->is_boolean()) + { + fmt = this->as_boolean_fmt(); + } + this->cleanup(); + this->type_ = value_t::boolean; + this->region_ = region_type{}; + assigner(this->boolean_, boolean_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (integer) ============================================== {{{ + + basic_value(integer_type x) + : basic_value(std::move(x), integer_format_info{}, std::vector{}, region_type{}) + {} + basic_value(integer_type x, integer_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(integer_type x, std::vector com) + : basic_value(std::move(x), integer_format_info{}, std::move(com), region_type{}) + {} + basic_value(integer_type x, integer_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + basic_value(integer_type x, integer_format_info fmt, std::vector com, region_type reg) + : type_(value_t::integer), integer_(integer_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(integer_type x) + { + integer_format_info fmt; + if(this->is_integer()) + { + fmt = this->as_integer_fmt(); + } + this->cleanup(); + this->type_ = value_t::integer; + this->region_ = region_type{}; + assigner(this->integer_, integer_storage(std::move(x), std::move(fmt))); + return *this; + } + + private: + + template + using enable_if_integer_like_t = cxx::enable_if_t, boolean_type>>, + cxx::negation, integer_type>>, + std::is_integral> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), integer_format_info{}, std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, integer_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), integer_format_info{}, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, integer_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, integer_format_info fmt, std::vector com, region_type reg) + : type_(value_t::integer), integer_(integer_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + template = nullptr> + basic_value& operator=(T x) + { + integer_format_info fmt; + if(this->is_integer()) + { + fmt = this->as_integer_fmt(); + } + this->cleanup(); + this->type_ = value_t::integer; + this->region_ = region_type{}; + assigner(this->integer_, integer_storage(x, std::move(fmt))); + return *this; + } + + // }}} + + // constructor (floating) ============================================= {{{ + + basic_value(floating_type x) + : basic_value(std::move(x), floating_format_info{}, std::vector{}, region_type{}) + {} + basic_value(floating_type x, floating_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(floating_type x, std::vector com) + : basic_value(std::move(x), floating_format_info{}, std::move(com), region_type{}) + {} + basic_value(floating_type x, floating_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + basic_value(floating_type x, floating_format_info fmt, std::vector com, region_type reg) + : type_(value_t::floating), floating_(floating_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(floating_type x) + { + floating_format_info fmt; + if(this->is_floating()) + { + fmt = this->as_floating_fmt(); + } + this->cleanup(); + this->type_ = value_t::floating; + this->region_ = region_type{}; + assigner(this->floating_, floating_storage(std::move(x), std::move(fmt))); + return *this; + } + + private: + + template + using enable_if_floating_like_t = cxx::enable_if_t, floating_type>>, + std::is_floating_point> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(x, floating_format_info{}, std::vector{}, region_type{}) + {} + + template = nullptr> + basic_value(T x, floating_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(x, floating_format_info{}, std::move(com), region_type{}) + {} + + template = nullptr> + basic_value(T x, floating_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + + template = nullptr> + basic_value(T x, floating_format_info fmt, std::vector com, region_type reg) + : type_(value_t::floating), floating_(floating_storage(x, std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + + template = nullptr> + basic_value& operator=(T x) + { + floating_format_info fmt; + if(this->is_floating()) + { + fmt = this->as_floating_fmt(); + } + this->cleanup(); + this->type_ = value_t::floating; + this->region_ = region_type{}; + assigner(this->floating_, floating_storage(x, std::move(fmt))); + return *this; + } + + // }}} + + // constructor (string) =============================================== {{{ + + basic_value(string_type x) + : basic_value(std::move(x), string_format_info{}, std::vector{}, region_type{}) + {} + basic_value(string_type x, string_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(string_type x, std::vector com) + : basic_value(std::move(x), string_format_info{}, std::move(com), region_type{}) + {} + basic_value(string_type x, string_format_info fmt, std::vector com) + : basic_value(std::move(x), std::move(fmt), std::move(com), region_type{}) + {} + basic_value(string_type x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), string_(string_storage(std::move(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(string_type x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; + assigner(this->string_, string_storage(x, std::move(fmt))); + return *this; + } + + // "string literal" + + basic_value(const typename string_type::value_type* x) + : basic_value(x, string_format_info{}, std::vector{}, region_type{}) + {} + basic_value(const typename string_type::value_type* x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(const typename string_type::value_type* x, std::vector com) + : basic_value(x, string_format_info{}, std::move(com), region_type{}) + {} + basic_value(const typename string_type::value_type* x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + basic_value(const typename string_type::value_type* x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), string_(string_storage(string_type(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(const typename string_type::value_type* x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; + assigner(this->string_, string_storage(string_type(x), std::move(fmt))); + return *this; + } + +#if defined(TOML11_HAS_STRING_VIEW) + using string_view_type = std::basic_string_view< + typename string_type::value_type, typename string_type::traits_type>; + + basic_value(string_view_type x) + : basic_value(x, string_format_info{}, std::vector{}, region_type{}) + {} + basic_value(string_view_type x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(string_view_type x, std::vector com) + : basic_value(x, string_format_info{}, std::move(com), region_type{}) + {} + basic_value(string_view_type x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + basic_value(string_view_type x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), string_(string_storage(string_type(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(string_view_type x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; + assigner(this->string_, string_storage(string_type(x), std::move(fmt))); + return *this; + } + +#endif // TOML11_HAS_STRING_VIEW + + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x) + : basic_value(x, string_format_info{}, std::vector{}, region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt) + : basic_value(x, std::move(fmt), std::vector{}, region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, std::vector com) + : basic_value(x, string_format_info{}, std::move(com), region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt, std::vector com) + : basic_value(x, std::move(fmt), std::move(com), region_type{}) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value(const T& x, string_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::string), + string_(string_storage(detail::string_conv(x), std::move(fmt))), + region_(std::move(reg)), comments_(std::move(com)) + {} + template, string_type>>, + detail::is_1byte_std_basic_string + >::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& x) + { + string_format_info fmt; + if(this->is_string()) + { + fmt = this->as_string_fmt(); + } + this->cleanup(); + this->type_ = value_t::string; + this->region_ = region_type{}; + assigner(this->string_, string_storage(detail::string_conv(x), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (local_date) =========================================== {{{ + + basic_value(local_date_type x) + : basic_value(x, local_date_format_info{}, std::vector{}, region_type{}) + {} + basic_value(local_date_type x, local_date_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(local_date_type x, std::vector com) + : basic_value(x, local_date_format_info{}, std::move(com), region_type{}) + {} + basic_value(local_date_type x, local_date_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(local_date_type x, local_date_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::local_date), local_date_(local_date_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(local_date_type x) + { + local_date_format_info fmt; + if(this->is_local_date()) + { + fmt = this->as_local_date_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_date; + this->region_ = region_type{}; + assigner(this->local_date_, local_date_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (local_time) =========================================== {{{ + + basic_value(local_time_type x) + : basic_value(x, local_time_format_info{}, std::vector{}, region_type{}) + {} + basic_value(local_time_type x, local_time_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(local_time_type x, std::vector com) + : basic_value(x, local_time_format_info{}, std::move(com), region_type{}) + {} + basic_value(local_time_type x, local_time_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(local_time_type x, local_time_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::local_time), local_time_(local_time_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(local_time_type x) + { + local_time_format_info fmt; + if(this->is_local_time()) + { + fmt = this->as_local_time_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_time; + this->region_ = region_type{}; + assigner(this->local_time_, local_time_storage(x, fmt)); + return *this; + } + + template + basic_value(const std::chrono::duration& x) + : basic_value(local_time_type(x), local_time_format_info{}, std::vector{}, region_type{}) + {} + template + basic_value(const std::chrono::duration& x, local_time_format_info fmt) + : basic_value(local_time_type(x), std::move(fmt), std::vector{}, region_type{}) + {} + template + basic_value(const std::chrono::duration& x, std::vector com) + : basic_value(local_time_type(x), local_time_format_info{}, std::move(com), region_type{}) + {} + template + basic_value(const std::chrono::duration& x, local_time_format_info fmt, std::vector com) + : basic_value(local_time_type(x), std::move(fmt), std::move(com), region_type{}) + {} + template + basic_value(const std::chrono::duration& x, + local_time_format_info fmt, + std::vector com, region_type reg) + : basic_value(local_time_type(x), std::move(fmt), std::move(com), std::move(reg)) + {} + template + basic_value& operator=(const std::chrono::duration& x) + { + local_time_format_info fmt; + if(this->is_local_time()) + { + fmt = this->as_local_time_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_time; + this->region_ = region_type{}; + assigner(this->local_time_, local_time_storage(local_time_type(x), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (local_datetime) =========================================== {{{ + + basic_value(local_datetime_type x) + : basic_value(x, local_datetime_format_info{}, std::vector{}, region_type{}) + {} + basic_value(local_datetime_type x, local_datetime_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(local_datetime_type x, std::vector com) + : basic_value(x, local_datetime_format_info{}, std::move(com), region_type{}) + {} + basic_value(local_datetime_type x, local_datetime_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(local_datetime_type x, local_datetime_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::local_datetime), local_datetime_(local_datetime_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(local_datetime_type x) + { + local_datetime_format_info fmt; + if(this->is_local_datetime()) + { + fmt = this->as_local_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::local_datetime; + this->region_ = region_type{}; + assigner(this->local_datetime_, local_datetime_storage(x, fmt)); + return *this; + } + + // }}} + + // constructor (offset_datetime) =========================================== {{{ + + basic_value(offset_datetime_type x) + : basic_value(x, offset_datetime_format_info{}, std::vector{}, region_type{}) + {} + basic_value(offset_datetime_type x, offset_datetime_format_info fmt) + : basic_value(x, fmt, std::vector{}, region_type{}) + {} + basic_value(offset_datetime_type x, std::vector com) + : basic_value(x, offset_datetime_format_info{}, std::move(com), region_type{}) + {} + basic_value(offset_datetime_type x, offset_datetime_format_info fmt, std::vector com) + : basic_value(x, fmt, std::move(com), region_type{}) + {} + basic_value(offset_datetime_type x, offset_datetime_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::offset_datetime), offset_datetime_(offset_datetime_storage(x, fmt)), + region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(offset_datetime_type x) + { + offset_datetime_format_info fmt; + if(this->is_offset_datetime()) + { + fmt = this->as_offset_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_ = region_type{}; + assigner(this->offset_datetime_, offset_datetime_storage(x, fmt)); + return *this; + } + + // system_clock::time_point + + basic_value(std::chrono::system_clock::time_point x) + : basic_value(offset_datetime_type(x), offset_datetime_format_info{}, std::vector{}, region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, offset_datetime_format_info fmt) + : basic_value(offset_datetime_type(x), fmt, std::vector{}, region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, std::vector com) + : basic_value(offset_datetime_type(x), offset_datetime_format_info{}, std::move(com), region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, offset_datetime_format_info fmt, std::vector com) + : basic_value(offset_datetime_type(x), fmt, std::move(com), region_type{}) + {} + basic_value(std::chrono::system_clock::time_point x, offset_datetime_format_info fmt, + std::vector com, region_type reg) + : basic_value(offset_datetime_type(x), std::move(fmt), std::move(com), std::move(reg)) + {} + basic_value& operator=(std::chrono::system_clock::time_point x) + { + offset_datetime_format_info fmt; + if(this->is_offset_datetime()) + { + fmt = this->as_offset_datetime_fmt(); + } + this->cleanup(); + this->type_ = value_t::offset_datetime; + this->region_ = region_type{}; + assigner(this->offset_datetime_, offset_datetime_storage(offset_datetime_type(x), fmt)); + return *this; + } + + // }}} + + // constructor (array) ================================================ {{{ + + basic_value(array_type x) + : basic_value(std::move(x), array_format_info{}, std::vector{}, region_type{}) + {} + basic_value(array_type x, array_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(array_type x, std::vector com) + : basic_value(std::move(x), array_format_info{}, std::move(com), region_type{}) + {} + basic_value(array_type x, array_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + basic_value(array_type x, array_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::array), array_(array_storage( + detail::storage(std::move(x)), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(array_type x) + { + array_format_info fmt; + if(this->is_array()) + { + fmt = this->as_array_fmt(); + } + this->cleanup(); + this->type_ = value_t::array; + this->region_ = region_type{}; + assigner(this->array_, array_storage( + detail::storage(std::move(x)), std::move(fmt))); + return *this; + } + + private: + + template + using enable_if_array_like_t = cxx::enable_if_t, + cxx::negation>, + cxx::negation>, +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, +#endif + cxx::negation>, + cxx::negation> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), array_format_info{}, std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, array_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), array_format_info{}, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, array_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, array_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::array), array_(array_storage( + detail::storage(array_type( + std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())) + ), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) + {} + template = nullptr> + basic_value& operator=(T x) + { + array_format_info fmt; + if(this->is_array()) + { + fmt = this->as_array_fmt(); + } + this->cleanup(); + this->type_ = value_t::array; + this->region_ = region_type{}; + + array_type a(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())); + assigner(this->array_, array_storage( + detail::storage(std::move(a)), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (table) ================================================ {{{ + + basic_value(table_type x) + : basic_value(std::move(x), table_format_info{}, std::vector{}, region_type{}) + {} + basic_value(table_type x, table_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + basic_value(table_type x, std::vector com) + : basic_value(std::move(x), table_format_info{}, std::move(com), region_type{}) + {} + basic_value(table_type x, table_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + basic_value(table_type x, table_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::table), table_(table_storage( + detail::storage(std::move(x)), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) + {} + basic_value& operator=(table_type x) + { + table_format_info fmt; + if(this->is_table()) + { + fmt = this->as_table_fmt(); + } + this->cleanup(); + this->type_ = value_t::table; + this->region_ = region_type{}; + assigner(this->table_, table_storage( + detail::storage(std::move(x)), std::move(fmt))); + return *this; + } + + // table-like + + private: + + template + using enable_if_table_like_t = cxx::enable_if_t>, + detail::is_map, + cxx::negation>, + cxx::negation> + >::value, std::nullptr_t>; + + public: + + template = nullptr> + basic_value(T x) + : basic_value(std::move(x), table_format_info{}, std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, table_format_info fmt) + : basic_value(std::move(x), std::move(fmt), std::vector{}, region_type{}) + {} + template = nullptr> + basic_value(T x, std::vector com) + : basic_value(std::move(x), table_format_info{}, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, table_format_info fmt, std::vector com) + : basic_value(std::move(x), fmt, std::move(com), region_type{}) + {} + template = nullptr> + basic_value(T x, table_format_info fmt, + std::vector com, region_type reg) + : type_(value_t::table), table_(table_storage( + detail::storage(table_type( + std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end()) + )), std::move(fmt) + )), region_(std::move(reg)), comments_(std::move(com)) + {} + template = nullptr> + basic_value& operator=(T x) + { + table_format_info fmt; + if(this->is_table()) + { + fmt = this->as_table_fmt(); + } + this->cleanup(); + this->type_ = value_t::table; + this->region_ = region_type{}; + + table_type t(std::make_move_iterator(x.begin()), + std::make_move_iterator(x.end())); + assigner(this->table_, table_storage( + detail::storage(std::move(t)), std::move(fmt))); + return *this; + } + + // }}} + + // constructor (user_defined) ========================================= {{{ + + template::value, std::nullptr_t> = nullptr> + basic_value(const T& ud) + : basic_value( + into>::template into_toml(ud)) + {} + template::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value( + into>::template into_toml(ud), + std::move(com)) + {} + template::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) + { + *this = into>::template into_toml(ud); + return *this; + } + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud): basic_value(ud.into_toml()) {} + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.into_toml(), std::move(com)) + {} + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.into_toml(); + return *this; + } + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud): basic_value(ud.template into_toml()) {} + + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value(const T& ud, std::vector com) + : basic_value(ud.template into_toml(), std::move(com)) + {} + template, + cxx::negation> + >::value, std::nullptr_t> = nullptr> + basic_value& operator=(const T& ud) + { + *this = ud.template into_toml(); + return *this; + } + // }}} + + // empty value with region info ======================================= {{{ + + // mainly for `null` extension + basic_value(detail::none_t, region_type reg) noexcept + : type_(value_t::empty), empty_('\0'), region_(std::move(reg)), comments_{} + {} + + // }}} + + // type checking ====================================================== {{{ + + template, value_type>::value, + std::nullptr_t> = nullptr> + bool is() const noexcept + { + return detail::type_to_enum::value == this->type_; + } + bool is(value_t t) const noexcept {return t == this->type_;} + + bool is_empty() const noexcept {return this->is(value_t::empty );} + bool is_boolean() const noexcept {return this->is(value_t::boolean );} + bool is_integer() const noexcept {return this->is(value_t::integer );} + bool is_floating() const noexcept {return this->is(value_t::floating );} + bool is_string() const noexcept {return this->is(value_t::string );} + bool is_offset_datetime() const noexcept {return this->is(value_t::offset_datetime);} + bool is_local_datetime() const noexcept {return this->is(value_t::local_datetime );} + bool is_local_date() const noexcept {return this->is(value_t::local_date );} + bool is_local_time() const noexcept {return this->is(value_t::local_time );} + bool is_array() const noexcept {return this->is(value_t::array );} + bool is_table() const noexcept {return this->is(value_t::table );} + + bool is_array_of_tables() const noexcept + { + if( ! this->is_array()) {return false;} + const auto& a = this->as_array(std::nothrow); // already checked. + + // when you define [[array.of.tables]], at least one empty table will be + // assigned. In case of array of inline tables, `array_of_tables = []`, + // there is no reason to consider this as an array of *tables*. + // So empty array is not an array-of-tables. + if(a.empty()) {return false;} + + // since toml v1.0.0 allows array of heterogeneous types, we need to + // check all the elements. if any of the elements is not a table, it + // is a heterogeneous array and cannot be expressed by `[[aot]]` form. + for(const auto& e : a) + { + if( ! e.is_table()) {return false;} + } + return true; + } + + value_t type() const noexcept {return type_;} + + // }}} + + // as_xxx (noexcept) version ========================================== {{{ + + template + detail::enum_to_type_t> const& + as(const std::nothrow_t&) const noexcept + { + return detail::getter::get_nothrow(*this); + } + template + detail::enum_to_type_t>& + as(const std::nothrow_t&) noexcept + { + return detail::getter::get_nothrow(*this); + } + + boolean_type const& as_boolean (const std::nothrow_t&) const noexcept {return this->boolean_.value;} + integer_type const& as_integer (const std::nothrow_t&) const noexcept {return this->integer_.value;} + floating_type const& as_floating (const std::nothrow_t&) const noexcept {return this->floating_.value;} + string_type const& as_string (const std::nothrow_t&) const noexcept {return this->string_.value;} + offset_datetime_type const& as_offset_datetime(const std::nothrow_t&) const noexcept {return this->offset_datetime_.value;} + local_datetime_type const& as_local_datetime (const std::nothrow_t&) const noexcept {return this->local_datetime_.value;} + local_date_type const& as_local_date (const std::nothrow_t&) const noexcept {return this->local_date_.value;} + local_time_type const& as_local_time (const std::nothrow_t&) const noexcept {return this->local_time_.value;} + array_type const& as_array (const std::nothrow_t&) const noexcept {return this->array_.value.get();} + table_type const& as_table (const std::nothrow_t&) const noexcept {return this->table_.value.get();} + + boolean_type & as_boolean (const std::nothrow_t&) noexcept {return this->boolean_.value;} + integer_type & as_integer (const std::nothrow_t&) noexcept {return this->integer_.value;} + floating_type & as_floating (const std::nothrow_t&) noexcept {return this->floating_.value;} + string_type & as_string (const std::nothrow_t&) noexcept {return this->string_.value;} + offset_datetime_type& as_offset_datetime(const std::nothrow_t&) noexcept {return this->offset_datetime_.value;} + local_datetime_type & as_local_datetime (const std::nothrow_t&) noexcept {return this->local_datetime_.value;} + local_date_type & as_local_date (const std::nothrow_t&) noexcept {return this->local_date_.value;} + local_time_type & as_local_time (const std::nothrow_t&) noexcept {return this->local_time_.value;} + array_type & as_array (const std::nothrow_t&) noexcept {return this->array_.value.get();} + table_type & as_table (const std::nothrow_t&) noexcept {return this->table_.value.get();} + + // }}} + + // as_xxx (throw) ===================================================== {{{ + + template + detail::enum_to_type_t> const& as() const + { + return detail::getter::get(*this); + } + template + detail::enum_to_type_t>& as() + { + return detail::getter::get(*this); + } + + boolean_type const& as_boolean() const + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean()", value_t::boolean); + } + return this->boolean_.value; + } + integer_type const& as_integer() const + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer()", value_t::integer); + } + return this->integer_.value; + } + floating_type const& as_floating() const + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating()", value_t::floating); + } + return this->floating_.value; + } + string_type const& as_string() const + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string()", value_t::string); + } + return this->string_.value; + } + offset_datetime_type const& as_offset_datetime() const + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime()", value_t::offset_datetime); + } + return this->offset_datetime_.value; + } + local_datetime_type const& as_local_datetime() const + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime()", value_t::local_datetime); + } + return this->local_datetime_.value; + } + local_date_type const& as_local_date() const + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date()", value_t::local_date); + } + return this->local_date_.value; + } + local_time_type const& as_local_time() const + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time()", value_t::local_time); + } + return this->local_time_.value; + } + array_type const& as_array() const + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array()", value_t::array); + } + return this->array_.value.get(); + } + table_type const& as_table() const + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table()", value_t::table); + } + return this->table_.value.get(); + } + + // ------------------------------------------------------------------------ + // nonconst reference + + boolean_type& as_boolean() + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean()", value_t::boolean); + } + return this->boolean_.value; + } + integer_type& as_integer() + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer()", value_t::integer); + } + return this->integer_.value; + } + floating_type& as_floating() + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating()", value_t::floating); + } + return this->floating_.value; + } + string_type& as_string() + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string()", value_t::string); + } + return this->string_.value; + } + offset_datetime_type& as_offset_datetime() + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime()", value_t::offset_datetime); + } + return this->offset_datetime_.value; + } + local_datetime_type& as_local_datetime() + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime()", value_t::local_datetime); + } + return this->local_datetime_.value; + } + local_date_type& as_local_date() + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date()", value_t::local_date); + } + return this->local_date_.value; + } + local_time_type& as_local_time() + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time()", value_t::local_time); + } + return this->local_time_.value; + } + array_type& as_array() + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array()", value_t::array); + } + return this->array_.value.get(); + } + table_type& as_table() + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table()", value_t::table); + } + return this->table_.value.get(); + } + + // }}} + + // format accessors (noexcept) ======================================== {{{ + + template + detail::enum_to_fmt_type_t const& + as_fmt(const std::nothrow_t&) const noexcept + { + return detail::getter::get_fmt_nothrow(*this); + } + template + detail::enum_to_fmt_type_t& + as_fmt(const std::nothrow_t&) noexcept + { + return detail::getter::get_fmt_nothrow(*this); + } + + boolean_format_info & as_boolean_fmt (const std::nothrow_t&) noexcept {return this->boolean_.format;} + integer_format_info & as_integer_fmt (const std::nothrow_t&) noexcept {return this->integer_.format;} + floating_format_info & as_floating_fmt (const std::nothrow_t&) noexcept {return this->floating_.format;} + string_format_info & as_string_fmt (const std::nothrow_t&) noexcept {return this->string_.format;} + offset_datetime_format_info& as_offset_datetime_fmt(const std::nothrow_t&) noexcept {return this->offset_datetime_.format;} + local_datetime_format_info & as_local_datetime_fmt (const std::nothrow_t&) noexcept {return this->local_datetime_.format;} + local_date_format_info & as_local_date_fmt (const std::nothrow_t&) noexcept {return this->local_date_.format;} + local_time_format_info & as_local_time_fmt (const std::nothrow_t&) noexcept {return this->local_time_.format;} + array_format_info & as_array_fmt (const std::nothrow_t&) noexcept {return this->array_.format;} + table_format_info & as_table_fmt (const std::nothrow_t&) noexcept {return this->table_.format;} + + boolean_format_info const& as_boolean_fmt (const std::nothrow_t&) const noexcept {return this->boolean_.format;} + integer_format_info const& as_integer_fmt (const std::nothrow_t&) const noexcept {return this->integer_.format;} + floating_format_info const& as_floating_fmt (const std::nothrow_t&) const noexcept {return this->floating_.format;} + string_format_info const& as_string_fmt (const std::nothrow_t&) const noexcept {return this->string_.format;} + offset_datetime_format_info const& as_offset_datetime_fmt(const std::nothrow_t&) const noexcept {return this->offset_datetime_.format;} + local_datetime_format_info const& as_local_datetime_fmt (const std::nothrow_t&) const noexcept {return this->local_datetime_.format;} + local_date_format_info const& as_local_date_fmt (const std::nothrow_t&) const noexcept {return this->local_date_.format;} + local_time_format_info const& as_local_time_fmt (const std::nothrow_t&) const noexcept {return this->local_time_.format;} + array_format_info const& as_array_fmt (const std::nothrow_t&) const noexcept {return this->array_.format;} + table_format_info const& as_table_fmt (const std::nothrow_t&) const noexcept {return this->table_.format;} + + // }}} + + // format accessors (throw) =========================================== {{{ + + template + detail::enum_to_fmt_type_t const& as_fmt() const + { + return detail::getter::get_fmt(*this); + } + template + detail::enum_to_fmt_type_t& as_fmt() + { + return detail::getter::get_fmt(*this); + } + + boolean_format_info const& as_boolean_fmt() const + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean_fmt()", value_t::boolean); + } + return this->boolean_.format; + } + integer_format_info const& as_integer_fmt() const + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer_fmt()", value_t::integer); + } + return this->integer_.format; + } + floating_format_info const& as_floating_fmt() const + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating_fmt()", value_t::floating); + } + return this->floating_.format; + } + string_format_info const& as_string_fmt() const + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string_fmt()", value_t::string); + } + return this->string_.format; + } + offset_datetime_format_info const& as_offset_datetime_fmt() const + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime_fmt()", value_t::offset_datetime); + } + return this->offset_datetime_.format; + } + local_datetime_format_info const& as_local_datetime_fmt() const + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime_fmt()", value_t::local_datetime); + } + return this->local_datetime_.format; + } + local_date_format_info const& as_local_date_fmt() const + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date_fmt()", value_t::local_date); + } + return this->local_date_.format; + } + local_time_format_info const& as_local_time_fmt() const + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time_fmt()", value_t::local_time); + } + return this->local_time_.format; + } + array_format_info const& as_array_fmt() const + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array_fmt()", value_t::array); + } + return this->array_.format; + } + table_format_info const& as_table_fmt() const + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table_fmt()", value_t::table); + } + return this->table_.format; + } + + // ------------------------------------------------------------------------ + // nonconst reference + + boolean_format_info& as_boolean_fmt() + { + if(this->type_ != value_t::boolean) + { + this->throw_bad_cast("toml::value::as_boolean_fmt()", value_t::boolean); + } + return this->boolean_.format; + } + integer_format_info& as_integer_fmt() + { + if(this->type_ != value_t::integer) + { + this->throw_bad_cast("toml::value::as_integer_fmt()", value_t::integer); + } + return this->integer_.format; + } + floating_format_info& as_floating_fmt() + { + if(this->type_ != value_t::floating) + { + this->throw_bad_cast("toml::value::as_floating_fmt()", value_t::floating); + } + return this->floating_.format; + } + string_format_info& as_string_fmt() + { + if(this->type_ != value_t::string) + { + this->throw_bad_cast("toml::value::as_string_fmt()", value_t::string); + } + return this->string_.format; + } + offset_datetime_format_info& as_offset_datetime_fmt() + { + if(this->type_ != value_t::offset_datetime) + { + this->throw_bad_cast("toml::value::as_offset_datetime_fmt()", value_t::offset_datetime); + } + return this->offset_datetime_.format; + } + local_datetime_format_info& as_local_datetime_fmt() + { + if(this->type_ != value_t::local_datetime) + { + this->throw_bad_cast("toml::value::as_local_datetime_fmt()", value_t::local_datetime); + } + return this->local_datetime_.format; + } + local_date_format_info& as_local_date_fmt() + { + if(this->type_ != value_t::local_date) + { + this->throw_bad_cast("toml::value::as_local_date_fmt()", value_t::local_date); + } + return this->local_date_.format; + } + local_time_format_info& as_local_time_fmt() + { + if(this->type_ != value_t::local_time) + { + this->throw_bad_cast("toml::value::as_local_time_fmt()", value_t::local_time); + } + return this->local_time_.format; + } + array_format_info& as_array_fmt() + { + if(this->type_ != value_t::array) + { + this->throw_bad_cast("toml::value::as_array_fmt()", value_t::array); + } + return this->array_.format; + } + table_format_info& as_table_fmt() + { + if(this->type_ != value_t::table) + { + this->throw_bad_cast("toml::value::as_table_fmt()", value_t::table); + } + return this->table_.format; + } + // }}} + + // table accessors ==================================================== {{{ + + value_type& at(const key_type& k) + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::at(key_type)", value_t::table); + } + auto& table = this->as_table(std::nothrow); + const auto found = table.find(k); + if(found == table.end()) + { + this->throw_key_not_found_error("toml::value::at", k); + } + assert(found->first == k); + return found->second; + } + value_type const& at(const key_type& k) const + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::at(key_type)", value_t::table); + } + const auto& table = this->as_table(std::nothrow); + const auto found = table.find(k); + if(found == table.end()) + { + this->throw_key_not_found_error("toml::value::at", k); + } + assert(found->first == k); + return found->second; + } + value_type& operator[](const key_type& k) + { + if(this->is_empty()) + { + (*this) = table_type{}; + } + else if( ! this->is_table()) // initialized, but not a table + { + this->throw_bad_cast("toml::value::operator[](key_type)", value_t::table); + } + return (this->as_table(std::nothrow))[k]; + } + std::size_t count(const key_type& k) const + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::count(key_type)", value_t::table); + } + return this->as_table(std::nothrow).count(k); + } + bool contains(const key_type& k) const + { + if(!this->is_table()) + { + this->throw_bad_cast("toml::value::contains(key_type)", value_t::table); + } + const auto& table = this->as_table(std::nothrow); + return table.find(k) != table.end(); + } + // }}} + + // array accessors ==================================================== {{{ + + value_type& at(const std::size_t idx) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::at(idx)", value_t::array); + } + auto& ar = this->as_array(std::nothrow); + + if(ar.size() <= idx) + { + std::ostringstream oss; + oss << "actual length (" << ar.size() + << ") is shorter than the specified index (" << idx << ")."; + throw std::out_of_range(format_error( + "toml::value::at(idx): no element corresponding to the index", + this->location(), oss.str() + )); + } + return ar.at(idx); + } + value_type const& at(const std::size_t idx) const + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::at(idx)", value_t::array); + } + const auto& ar = this->as_array(std::nothrow); + + if(ar.size() <= idx) + { + std::ostringstream oss; + oss << "actual length (" << ar.size() + << ") is shorter than the specified index (" << idx << ")."; + + throw std::out_of_range(format_error( + "toml::value::at(idx): no element corresponding to the index", + this->location(), oss.str() + )); + } + return ar.at(idx); + } + + value_type& operator[](const std::size_t idx) noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + value_type const& operator[](const std::size_t idx) const noexcept + { + // no check... + return this->as_array(std::nothrow)[idx]; + } + + void push_back(const value_type& x) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::push_back(idx)", value_t::array); + } + this->as_array(std::nothrow).push_back(x); + return; + } + void push_back(value_type&& x) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::push_back(idx)", value_t::array); + } + this->as_array(std::nothrow).push_back(std::move(x)); + return; + } + + template + value_type& emplace_back(Ts&& ... args) + { + if(!this->is_array()) + { + this->throw_bad_cast("toml::value::emplace_back(idx)", value_t::array); + } + auto& ar = this->as_array(std::nothrow); + ar.emplace_back(std::forward(args) ...); + return ar.back(); + } + + std::size_t size() const + { + switch(this->type_) + { + case value_t::array: + { + return this->as_array(std::nothrow).size(); + } + case value_t::table: + { + return this->as_table(std::nothrow).size(); + } + case value_t::string: + { + return this->as_string(std::nothrow).size(); + } + default: + { + throw type_error(format_error( + "toml::value::size(): bad_cast to container types", + this->location(), + "the actual type is " + to_string(this->type_) + ), this->location()); + } + } + } + + // }}} + + source_location location() const + { + return source_location(this->region_); + } + + comment_type const& comments() const noexcept {return this->comments_;} + comment_type& comments() noexcept {return this->comments_;} + + private: + + // private helper functions =========================================== {{{ + + void cleanup() noexcept + { + switch(this->type_) + { + case value_t::boolean : { boolean_ .~boolean_storage (); break; } + case value_t::integer : { integer_ .~integer_storage (); break; } + case value_t::floating : { floating_ .~floating_storage (); break; } + case value_t::string : { string_ .~string_storage (); break; } + case value_t::offset_datetime : { offset_datetime_.~offset_datetime_storage (); break; } + case value_t::local_datetime : { local_datetime_ .~local_datetime_storage (); break; } + case value_t::local_date : { local_date_ .~local_date_storage (); break; } + case value_t::local_time : { local_time_ .~local_time_storage (); break; } + case value_t::array : { array_ .~array_storage (); break; } + case value_t::table : { table_ .~table_storage (); break; } + default : { break; } + } + this->type_ = value_t::empty; + return; + } + + template + static void assigner(T& dst, U&& v) + { + const auto tmp = ::new(std::addressof(dst)) T(std::forward(v)); + assert(tmp == std::addressof(dst)); + (void)tmp; + } + + [[noreturn]] + void throw_bad_cast(const std::string& funcname, const value_t ty) const + { + throw type_error(format_error(detail::make_type_error(*this, funcname, ty)), + this->location()); + } + + [[noreturn]] + void throw_key_not_found_error(const std::string& funcname, const key_type& key) const + { + throw std::out_of_range(format_error( + detail::make_not_found_error(*this, funcname, key))); + } + + template + friend void detail::change_region_of_value(basic_value&, const basic_value&); + + template + friend class basic_value; + + // }}} + + private: + + using boolean_storage = detail::value_with_format; + using integer_storage = detail::value_with_format; + using floating_storage = detail::value_with_format; + using string_storage = detail::value_with_format; + using offset_datetime_storage = detail::value_with_format; + using local_datetime_storage = detail::value_with_format; + using local_date_storage = detail::value_with_format; + using local_time_storage = detail::value_with_format; + using array_storage = detail::value_with_format, array_format_info >; + using table_storage = detail::value_with_format, table_format_info >; + + private: + + value_t type_; + union + { + char empty_; // the smallest type + boolean_storage boolean_; + integer_storage integer_; + floating_storage floating_; + string_storage string_; + offset_datetime_storage offset_datetime_; + local_datetime_storage local_datetime_; + local_date_storage local_date_; + local_time_storage local_time_; + array_storage array_; + table_storage table_; + }; + region_type region_; + comment_type comments_; +}; + +template +bool operator==(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) {return false;} + if(lhs.comments() != rhs.comments()) {return false;} + + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() == rhs.as_boolean(); + } + case value_t::integer : + { + return lhs.as_integer() == rhs.as_integer(); + } + case value_t::floating : + { + return lhs.as_floating() == rhs.as_floating(); + } + case value_t::string : + { + return lhs.as_string() == rhs.as_string(); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() == rhs.as_offset_datetime(); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() == rhs.as_local_datetime(); + } + case value_t::local_date: + { + return lhs.as_local_date() == rhs.as_local_date(); + } + case value_t::local_time: + { + return lhs.as_local_time() == rhs.as_local_time(); + } + case value_t::array : + { + return lhs.as_array() == rhs.as_array(); + } + case value_t::table : + { + return lhs.as_table() == rhs.as_table(); + } + case value_t::empty : {return true; } + default: {return false;} + } +} + +template +bool operator!=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs == rhs); +} + +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator<(const basic_value& lhs, const basic_value& rhs) +{ + if(lhs.type() != rhs.type()) + { + return (lhs.type() < rhs.type()); + } + switch(lhs.type()) + { + case value_t::boolean : + { + return lhs.as_boolean() < rhs.as_boolean() || + (lhs.as_boolean() == rhs.as_boolean() && + lhs.comments() < rhs.comments()); + } + case value_t::integer : + { + return lhs.as_integer() < rhs.as_integer() || + (lhs.as_integer() == rhs.as_integer() && + lhs.comments() < rhs.comments()); + } + case value_t::floating : + { + return lhs.as_floating() < rhs.as_floating() || + (lhs.as_floating() == rhs.as_floating() && + lhs.comments() < rhs.comments()); + } + case value_t::string : + { + return lhs.as_string() < rhs.as_string() || + (lhs.as_string() == rhs.as_string() && + lhs.comments() < rhs.comments()); + } + case value_t::offset_datetime: + { + return lhs.as_offset_datetime() < rhs.as_offset_datetime() || + (lhs.as_offset_datetime() == rhs.as_offset_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_datetime: + { + return lhs.as_local_datetime() < rhs.as_local_datetime() || + (lhs.as_local_datetime() == rhs.as_local_datetime() && + lhs.comments() < rhs.comments()); + } + case value_t::local_date: + { + return lhs.as_local_date() < rhs.as_local_date() || + (lhs.as_local_date() == rhs.as_local_date() && + lhs.comments() < rhs.comments()); + } + case value_t::local_time: + { + return lhs.as_local_time() < rhs.as_local_time() || + (lhs.as_local_time() == rhs.as_local_time() && + lhs.comments() < rhs.comments()); + } + case value_t::array : + { + return lhs.as_array() < rhs.as_array() || + (lhs.as_array() == rhs.as_array() && + lhs.comments() < rhs.comments()); + } + case value_t::table : + { + return lhs.as_table() < rhs.as_table() || + (lhs.as_table() == rhs.as_table() && + lhs.comments() < rhs.comments()); + } + case value_t::empty : + { + return lhs.comments() < rhs.comments(); + } + default: + { + return lhs.comments() < rhs.comments(); + } + } +} + +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator<=(const basic_value& lhs, const basic_value& rhs) +{ + return (lhs < rhs) || (lhs == rhs); +} +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator>(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs <= rhs); +} +template +cxx::enable_if_t::array_type>, + detail::is_comparable::table_type> + >::value, bool> +operator>=(const basic_value& lhs, const basic_value& rhs) +{ + return !(lhs < rhs); +} + +// error_info helper +namespace detail +{ +template +error_info make_error_info_rec(error_info e, + const basic_value& v, std::string msg, Ts&& ... tail) +{ + return make_error_info_rec(std::move(e), v.location(), std::move(msg), std::forward(tail)...); +} +} // detail + +template +error_info make_error_info( + std::string title, const basic_value& v, std::string msg, Ts&& ... tail) +{ + return make_error_info(std::move(title), + v.location(), std::move(msg), std::forward(tail)...); +} +template +std::string format_error(std::string title, + const basic_value& v, std::string msg, Ts&& ... tail) +{ + return format_error(std::move(title), + v.location(), std::move(msg), std::forward(tail)...); +} + +namespace detail +{ + +template +error_info make_type_error(const basic_value& v, const std::string& fname, const value_t ty) +{ + return make_error_info(fname + ": bad_cast to " + to_string(ty), + v.location(), "the actual type is " + to_string(v.type())); +} +template +error_info make_not_found_error(const basic_value& v, const std::string& fname, const typename basic_value::key_type& key) +{ + const auto loc = v.location(); + const std::string title = fname + ": key \"" + string_conv(key) + "\" not found"; + + std::vector> locs; + if( ! loc.is_ok()) + { + return error_info(title, locs); + } + + if(loc.first_line_number() == 1 && loc.first_column_number() == 1 && loc.length() == 1) + { + // The top-level table has its region at the 0th character of the file. + // That means that, in the case when a key is not found in the top-level + // table, the error message points to the first character. If the file has + // the first table at the first line, the error message would be like this. + // ```console + // [error] key "a" not found + // --> example.toml + // | + // 1 | [table] + // | ^------ in this table + // ``` + // It actually points to the top-level table at the first character, not + // `[table]`. But it is too confusing. To avoid the confusion, the error + // message should explicitly say "key not found in the top-level table". + locs.emplace_back(v.location(), "at the top-level table"); + } + else + { + locs.emplace_back(v.location(), "in this table"); + } + return error_info(title, locs); +} + +#define TOML11_DETAIL_GENERATE_COMPTIME_GETTER(ty) \ + template \ + struct getter \ + { \ + using value_type = basic_value; \ + using result_type = enum_to_type_t; \ + using format_type = enum_to_fmt_type_t; \ + \ + static result_type& get(value_type& v) \ + { \ + return v.as_ ## ty(); \ + } \ + static result_type const& get(const value_type& v) \ + { \ + return v.as_ ## ty(); \ + } \ + \ + static result_type& get_nothrow(value_type& v) noexcept \ + { \ + return v.as_ ## ty(std::nothrow); \ + } \ + static result_type const& get_nothrow(const value_type& v) noexcept \ + { \ + return v.as_ ## ty(std::nothrow); \ + } \ + \ + static format_type& get_fmt(value_type& v) \ + { \ + return v.as_ ## ty ## _fmt(); \ + } \ + static format_type const& get_fmt(const value_type& v) \ + { \ + return v.as_ ## ty ## _fmt(); \ + } \ + \ + static format_type& get_fmt_nothrow(value_type& v) noexcept \ + { \ + return v.as_ ## ty ## _fmt(std::nothrow); \ + } \ + static format_type const& get_fmt_nothrow(const value_type& v) noexcept \ + { \ + return v.as_ ## ty ## _fmt(std::nothrow); \ + } \ + }; + +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(boolean ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(integer ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(floating ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(string ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(offset_datetime) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_datetime ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_date ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(local_time ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(array ) +TOML11_DETAIL_GENERATE_COMPTIME_GETTER(table ) + +#undef TOML11_DETAIL_GENERATE_COMPTIME_GETTER + +template +void change_region_of_value(basic_value& dst, const basic_value& src) +{ + dst.region_ = std::move(src.region_); + return; +} + +} // namespace detail +} // namespace toml +#endif // TOML11_VALUE_HPP +#ifndef TOML11_VISIT_HPP +#define TOML11_VISIT_HPP + + +namespace toml +{ + +template +cxx::return_type_of_t::boolean_type&> +visit(Visitor&& visitor, const basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); +} + +template +cxx::return_type_of_t::boolean_type&> +visit(Visitor&& visitor, basic_value& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(v.as_boolean ());} + case value_t::integer : {return visitor(v.as_integer ());} + case value_t::floating : {return visitor(v.as_floating ());} + case value_t::string : {return visitor(v.as_string ());} + case value_t::offset_datetime: {return visitor(v.as_offset_datetime());} + case value_t::local_datetime : {return visitor(v.as_local_datetime ());} + case value_t::local_date : {return visitor(v.as_local_date ());} + case value_t::local_time : {return visitor(v.as_local_time ());} + case value_t::array : {return visitor(v.as_array ());} + case value_t::table : {return visitor(v.as_table ());} + case value_t::empty : break; + default: break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); +} + +template +cxx::return_type_of_t::boolean_type&&> +visit(Visitor&& visitor, basic_value&& v) +{ + switch(v.type()) + { + case value_t::boolean : {return visitor(std::move(v.as_boolean ()));} + case value_t::integer : {return visitor(std::move(v.as_integer ()));} + case value_t::floating : {return visitor(std::move(v.as_floating ()));} + case value_t::string : {return visitor(std::move(v.as_string ()));} + case value_t::offset_datetime: {return visitor(std::move(v.as_offset_datetime()));} + case value_t::local_datetime : {return visitor(std::move(v.as_local_datetime ()));} + case value_t::local_date : {return visitor(std::move(v.as_local_date ()));} + case value_t::local_time : {return visitor(std::move(v.as_local_time ()));} + case value_t::array : {return visitor(std::move(v.as_array ()));} + case value_t::table : {return visitor(std::move(v.as_table ()));} + case value_t::empty : break; + default: break; + } + throw type_error(format_error("[error] toml::visit: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); +} + +} // toml +#endif // TOML11_VISIT_HPP +#ifndef TOML11_TYPES_HPP +#define TOML11_TYPES_HPP + + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace toml +{ + +// forward decl +template +class basic_value; + +// when you use a special integer type as toml::value::integer_type, parse must +// be able to read it. So, type_config has static member functions that read the +// integer_type as {dec, hex, oct, bin}-integer. But, in most cases, operator<< +// is enough. To make config easy, we provide the default read functions. +// +// Before this functions is called, syntax is checked and prefix(`0x` etc) and +// spacer(`_`) are removed. + +template +result +read_dec_int(const std::string& str, const source_location src) +{ + constexpr auto max_digits = std::numeric_limits::digits; + assert( ! str.empty()); + + T val{0}; + std::istringstream iss(str); + iss >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_dec_integer: " + "too large integer: current max digits = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_hex_int(const std::string& str, const source_location src) +{ + constexpr auto max_digits = std::numeric_limits::digits; + assert( ! str.empty()); + + T val{0}; + std::istringstream iss(str); + iss >> std::hex >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_hex_integer: " + "too large integer: current max value = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_oct_int(const std::string& str, const source_location src) +{ + constexpr auto max_digits = std::numeric_limits::digits; + assert( ! str.empty()); + + T val{0}; + std::istringstream iss(str); + iss >> std::oct >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_oct_integer: " + "too large integer: current max value = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_bin_int(const std::string& str, const source_location src) +{ + constexpr auto is_bounded = std::numeric_limits::is_bounded; + constexpr auto max_digits = std::numeric_limits::digits; + const auto max_value = (std::numeric_limits::max)(); + + T val{0}; + T base{1}; + for(auto i = str.rbegin(); i != str.rend(); ++i) + { + const auto c = *i; + if(c == '1') + { + val += base; + // prevent `base` from overflow + if(is_bounded && max_value / 2 < base && std::next(i) != str.rend()) + { + base = 0; + } + else + { + base *= 2; + } + } + else + { + assert(c == '0'); + + if(is_bounded && max_value / 2 < base && std::next(i) != str.rend()) + { + base = 0; + } + else + { + base *= 2; + } + } + } + if(base == 0) + { + return err(make_error_info("toml::parse_bin_integer: " + "too large integer: current max value = 2^" + std::to_string(max_digits), + std::move(src), "must be < 2^" + std::to_string(max_digits))); + } + return ok(val); +} + +template +result +read_int(const std::string& str, const source_location src, const std::uint8_t base) +{ + assert(base == 10 || base == 16 || base == 8 || base == 2); + switch(base) + { + case 2: { return read_bin_int(str, src); } + case 8: { return read_oct_int(str, src); } + case 16: { return read_hex_int(str, src); } + default: + { + assert(base == 10); + return read_dec_int(str, src); + } + } +} + +inline result +read_hex_float(const std::string& str, const source_location src, float val) +{ +#if defined(_MSC_VER) && ! defined(__clang__) + const auto res = ::sscanf_s(str.c_str(), "%a", std::addressof(val)); +#else + const auto res = std::sscanf(str.c_str(), "%a", std::addressof(val)); +#endif + if(res != 1) + { + return err(make_error_info("toml::parse_floating: " + "failed to read hexadecimal floating point value ", + std::move(src), "here")); + } + return ok(val); +} +inline result +read_hex_float(const std::string& str, const source_location src, double val) +{ +#if defined(_MSC_VER) && ! defined(__clang__) + const auto res = ::sscanf_s(str.c_str(), "%la", std::addressof(val)); +#else + const auto res = std::sscanf(str.c_str(), "%la", std::addressof(val)); +#endif + if(res != 1) + { + return err(make_error_info("toml::parse_floating: " + "failed to read hexadecimal floating point value ", + std::move(src), "here")); + } + return ok(val); +} +template +cxx::enable_if_t, double>>, + cxx::negation, float>> + >::value, result> +read_hex_float(const std::string&, const source_location src, T) +{ + return err(make_error_info("toml::parse_floating: failed to read " + "floating point value because of unknown type in type_config", + std::move(src), "here")); +} + +template +result +read_dec_float(const std::string& str, const source_location src) +{ + T val; + std::istringstream iss(str); + iss >> val; + if(iss.fail()) + { + return err(make_error_info("toml::parse_floating: " + "failed to read floating point value from stream", + std::move(src), "here")); + } + return ok(val); +} + +template +result +read_float(const std::string& str, const source_location src, const bool is_hex) +{ + if(is_hex) + { + return read_hex_float(str, src, T{}); + } + else + { + return read_dec_float(str, src); + } +} + +struct type_config +{ + using comment_type = preserve_comments; + + using boolean_type = bool; + using integer_type = std::int64_t; + using floating_type = double; + using string_type = std::string; + + template + using array_type = std::vector; + template + using table_type = std::unordered_map; + + static result + parse_int(const std::string& str, const source_location src, const std::uint8_t base) + { + return read_int(str, src, base); + } + static result + parse_float(const std::string& str, const source_location src, const bool is_hex) + { + return read_float(str, src, is_hex); + } +}; + +using value = basic_value; +using table = typename value::table_type; +using array = typename value::array_type; + +struct ordered_type_config +{ + using comment_type = preserve_comments; + + using boolean_type = bool; + using integer_type = std::int64_t; + using floating_type = double; + using string_type = std::string; + + template + using array_type = std::vector; + template + using table_type = ordered_map; + + static result + parse_int(const std::string& str, const source_location src, const std::uint8_t base) + { + return read_int(str, src, base); + } + static result + parse_float(const std::string& str, const source_location src, const bool is_hex) + { + return read_float(str, src, is_hex); + } +}; + +using ordered_value = basic_value; +using ordered_table = typename ordered_value::table_type; +using ordered_array = typename ordered_value::array_type; + +// ---------------------------------------------------------------------------- +// meta functions for internal use + +namespace detail +{ + +// ---------------------------------------------------------------------------- +// check if type T has all the needed member types + +struct has_comment_type_impl +{ + template static std::true_type check(typename T::comment_type*); + template static std::false_type check(...); +}; +template +using has_comment_type = decltype(has_comment_type_impl::check(nullptr)); + +struct has_integer_type_impl +{ + template static std::true_type check(typename T::integer_type*); + template static std::false_type check(...); +}; +template +using has_integer_type = decltype(has_integer_type_impl::check(nullptr)); + +struct has_floating_type_impl +{ + template static std::true_type check(typename T::floating_type*); + template static std::false_type check(...); +}; +template +using has_floating_type = decltype(has_floating_type_impl::check(nullptr)); + +struct has_string_type_impl +{ + template static std::true_type check(typename T::string_type*); + template static std::false_type check(...); +}; +template +using has_string_type = decltype(has_string_type_impl::check(nullptr)); + +struct has_array_type_impl +{ + template static std::true_type check(typename T::template array_type*); + template static std::false_type check(...); +}; +template +using has_array_type = decltype(has_array_type_impl::check(nullptr)); + +struct has_table_type_impl +{ + template static std::true_type check(typename T::template table_type*); + template static std::false_type check(...); +}; +template +using has_table_type = decltype(has_table_type_impl::check(nullptr)); + +struct has_parse_int_impl +{ + template static std::true_type check(decltype(std::declval().parse_int( + std::declval(), + std::declval(), + std::declval() + ))*); + template static std::false_type check(...); +}; +template +using has_parse_int = decltype(has_parse_int_impl::check(nullptr)); + +struct has_parse_float_impl +{ + template static std::true_type check(decltype(std::declval().parse_float( + std::declval(), + std::declval(), + std::declval() + ))*); + template static std::false_type check(...); +}; +template +using has_parse_float = decltype(has_parse_float_impl::check(nullptr)); + +template +using is_type_config = cxx::conjunction< + has_comment_type, + has_integer_type, + has_floating_type, + has_string_type, + has_array_type, + has_table_type, + has_parse_int, + has_parse_float + >; + +} // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +extern template class basic_value; +extern template class basic_value; +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_TYPES_HPP +#ifndef TOML11_GET_HPP +#define TOML11_GET_HPP + +#include + + +#if defined(TOML11_HAS_STRING_VIEW) +#include +#endif // string_view + +namespace toml +{ + +// ============================================================================ +// T is toml::value; identity transformation. + +template +cxx::enable_if_t>::value, T>& +get(basic_value& v) +{ + return v; +} + +template +cxx::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + return v; +} + +template +cxx::enable_if_t>::value, T> +get(basic_value&& v) +{ + return basic_value(std::move(v)); +} + +// ============================================================================ +// exact toml::* type + +template +cxx::enable_if_t>::value, T> & +get(basic_value& v) +{ + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(v); +} + +template +cxx::enable_if_t>::value, T> const& +get(const basic_value& v) +{ + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(v); +} + +template +cxx::enable_if_t>::value, T> +get(basic_value&& v) +{ + constexpr auto ty = detail::type_to_enum>::value; + return detail::getter::get(std::move(v)); +} + +// ============================================================================ +// T is toml::basic_value + +template +cxx::enable_if_t, + cxx::negation>> + >::value, T> +get(basic_value v) +{ + return T(std::move(v)); +} + +// ============================================================================ +// integer convertible from toml::value::integer_type + +template +cxx::enable_if_t, + cxx::negation>, + detail::is_not_toml_type>, + cxx::negation>, + cxx::negation> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_integer()); +} + +// ============================================================================ +// floating point convertible from toml::value::floating_type + +template +cxx::enable_if_t, + detail::is_not_toml_type>, + cxx::negation>, + cxx::negation> + >::value, T> +get(const basic_value& v) +{ + return static_cast(v.as_floating()); +} + +// ============================================================================ +// std::string with different char/trait/allocator + +template +cxx::enable_if_t>, + detail::is_1byte_std_basic_string + >::value, T> +get(const basic_value& v) +{ + return detail::string_conv>(v.as_string()); +} + +// ============================================================================ +// std::string_view + +#if defined(TOML11_HAS_STRING_VIEW) + +template +cxx::enable_if_t::string_type>::value, T> +get(const basic_value& v) +{ + return T(v.as_string()); +} + +#endif // string_view + +// ============================================================================ +// std::chrono::duration from toml::local_time + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + return std::chrono::duration_cast( + std::chrono::nanoseconds(v.as_local_time())); +} + +// ============================================================================ +// std::chrono::system_clock::time_point from toml::datetime variants + +template +cxx::enable_if_t< + std::is_same::value, T> +get(const basic_value& v) +{ + switch(v.type()) + { + case value_t::local_date: + { + return std::chrono::system_clock::time_point(v.as_local_date()); + } + case value_t::local_datetime: + { + return std::chrono::system_clock::time_point(v.as_local_datetime()); + } + case value_t::offset_datetime: + { + return std::chrono::system_clock::time_point(v.as_offset_datetime()); + } + default: + { + const auto loc = v.location(); + throw type_error(format_error("toml::get: " + "bad_cast to std::chrono::system_clock::time_point", loc, + "the actual type is " + to_string(v.type())), loc); + } + } +} + +// ============================================================================ +// forward declaration to use this recursively. ignore this and go ahead. + +// array-like (w/ push_back) +template +cxx::enable_if_t, // T is a container + detail::has_push_back_method, // .push_back() works + detail::is_not_toml_type>, // but not toml::array + cxx::negation>, // but not std::basic_string +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, // but not std::basic_string_view +#endif + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value&); + +// std::array +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::forward_list +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::pair +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::tuple +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// std::map (key is convertible from toml::value::key_type) +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + std::is_convertible::key_type, + typename T::key_type>, // keys are convertible + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v); + +// std::map (key is not convertible from toml::value::key_type, but +// is a std::basic_string) +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + cxx::negation::key_type, + typename T::key_type>>, // keys are NOT convertible + detail::is_1byte_std_basic_string, // is std::basic_string + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v); + +// toml::from::from_toml(v) +template +cxx::enable_if_t::value, T> +get(const basic_value&); + +// has T.from_toml(v) but no from +template +cxx::enable_if_t, // has T.from_toml() + cxx::negation>, // no toml::from + std::is_default_constructible // T{} works + >::value, T> +get(const basic_value&); + +// T(const toml::value&) and T is not toml::basic_value, +// and it does not have `from` nor `from_toml`. +template +cxx::enable_if_t&>, // has T(const basic_value&) + cxx::negation>, // but not basic_value itself + cxx::negation>, // no .from_toml() + cxx::negation> // no toml::from + >::value, T> +get(const basic_value&); + +// ============================================================================ +// array-like types; most likely STL container, like std::vector, etc. + +template +cxx::enable_if_t, // T is a container + detail::has_push_back_method, // .push_back() works + detail::is_not_toml_type>, // but not toml::array + cxx::negation>, // but not std::basic_string +#if defined(TOML11_HAS_STRING_VIEW) + cxx::negation>, // but not std::basic_string_view +#endif + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& a = v.as_array(); + + T container; + detail::try_reserve(container, a.size()); // if T has .reserve(), call it + + for(const auto& elem : a) + { + container.push_back(get(elem)); + } + return container; +} + +// ============================================================================ +// std::array + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + const auto& a = v.as_array(); + + T container; + if(a.size() != container.size()) + { + const auto loc = v.location(); + throw std::out_of_range(format_error("toml::get: while converting to an array: " + " array size is " + std::to_string(container.size()) + + " but there are " + std::to_string(a.size()) + " elements in toml array.", + loc, "here")); + } + for(std::size_t i=0; i(a.at(i)); + } + return container; +} + +// ============================================================================ +// std::forward_list + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + using value_type = typename T::value_type; + + T container; + for(const auto& elem : v.as_array()) + { + container.push_front(get(elem)); + } + container.reverse(); + return container; +} + +// ============================================================================ +// std::pair + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + using first_type = typename T::first_type; + using second_type = typename T::second_type; + + const auto& ar = v.as_array(); + if(ar.size() != 2) + { + const auto loc = v.location(); + throw std::out_of_range(format_error("toml::get: while converting std::pair: " + " but there are " + std::to_string(ar.size()) + " > 2 elements in toml array.", + loc, "here")); + } + return std::make_pair(::toml::get(ar.at(0)), + ::toml::get(ar.at(1))); +} + +// ============================================================================ +// std::tuple. + +namespace detail +{ +template +T get_tuple_impl(const Array& a, cxx::index_sequence) +{ + return std::make_tuple( + ::toml::get::type>(a.at(I))...); +} +} // detail + +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + const auto& ar = v.as_array(); + if(ar.size() != std::tuple_size::value) + { + const auto loc = v.location(); + throw std::out_of_range(format_error("toml::get: while converting std::tuple: " + " there are " + std::to_string(ar.size()) + " > " + + std::to_string(std::tuple_size::value) + " elements in toml array.", + loc, "here")); + } + return detail::get_tuple_impl(ar, + cxx::make_index_sequence::value>{}); +} + +// ============================================================================ +// map-like types; most likely STL map, like std::map or std::unordered_map. + +// key is convertible from toml::value::key_type +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + std::is_convertible::key_type, + typename T::key_type>, // keys are convertible + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + static_assert( + std::is_convertible::key_type, key_type>::value, + "toml::get only supports map type of which key_type is " + "convertible from toml::basic_value::key_type."); + + T m; + for(const auto& kv : v.as_table()) + { + m.emplace(key_type(kv.first), get(kv.second)); + } + return m; +} + +// key is NOT convertible from toml::value::key_type but std::basic_string +template +cxx::enable_if_t, // T is map + detail::is_not_toml_type>, // but not toml::table + cxx::negation::key_type, + typename T::key_type>>, // keys are NOT convertible + detail::is_1byte_std_basic_string, // is std::basic_string + cxx::negation>, // no T.from_toml() + cxx::negation>, // no toml::from + cxx::negation&>> + >::value, T> +get(const basic_value& v) +{ + using key_type = typename T::key_type; + using mapped_type = typename T::mapped_type; + + T m; + for(const auto& kv : v.as_table()) + { + m.emplace(detail::string_conv(kv.first), get(kv.second)); + } + return m; +} + +// ============================================================================ +// user-defined, but convertible types. + +// toml::from +template +cxx::enable_if_t::value, T> +get(const basic_value& v) +{ + return ::toml::from::from_toml(v); +} + +// has T.from_toml(v) but no from +template +cxx::enable_if_t, // has T.from_toml() + cxx::negation>, // no toml::from + std::is_default_constructible // T{} works + >::value, T> +get(const basic_value& v) +{ + T ud; + ud.from_toml(v); + return ud; +} + +// T(const toml::value&) and T is not toml::basic_value, +// and it does not have `from` nor `from_toml`. +template +cxx::enable_if_t&>, // has T(const basic_value&) + cxx::negation>, // but not basic_value itself + cxx::negation>, // no .from_toml() + cxx::negation> // no toml::from + >::value, T> +get(const basic_value& v) +{ + return T(v); +} + +// ============================================================================ +// get_or(value, fallback) + +template +cxx::enable_if_t::value, basic_value> const& +get_or(const basic_value& v, const basic_value&) +{ + return v; +} + +template +cxx::enable_if_t::value, basic_value>& +get_or(basic_value& v, basic_value&) +{ + return v; +} + +template +cxx::enable_if_t::value, basic_value> +get_or(basic_value&& v, basic_value&&) +{ + return v; +} + +// ---------------------------------------------------------------------------- +// specialization for the exact toml types (return type becomes lvalue ref) + +template +cxx::enable_if_t< + detail::is_exact_toml_type>::value, T> const& +get_or(const basic_value& v, const T& opt) noexcept +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t>, + detail::is_exact_toml_type> + >::value, T>& +get_or(basic_value& v, T& opt) noexcept +{ + try + { + return get>(v); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t, + basic_value>::value, cxx::remove_cvref_t> +get_or(basic_value&& v, T&& opt) noexcept +{ + try + { + return get>(std::move(v)); + } + catch(...) + { + return cxx::remove_cvref_t(std::forward(opt)); + } +} + +// ---------------------------------------------------------------------------- +// specialization for string literal + +// template +// typename basic_value::string_type +// get_or(const basic_value& v, +// const typename basic_value::string_type::value_type (&opt)[N]) +// { +// try +// { +// return v.as_string(); +// } +// catch(...) +// { +// return typename basic_value::string_type(opt); +// } +// } +// +// The above only matches to the literal, like `get_or(v, "foo");` but not +// ```cpp +// const auto opt = "foo"; +// const auto str = get_or(v, opt); +// ``` +// . And the latter causes an error. +// To match to both `"foo"` and `const auto opt = "foo"`, we take a pointer to +// a character here. + +template +typename basic_value::string_type +get_or(const basic_value& v, + const typename basic_value::string_type::value_type* opt) +{ + try + { + return v.as_string(); + } + catch(...) + { + return typename basic_value::string_type(opt); + } +} + +// ---------------------------------------------------------------------------- +// others (require type conversion and return type cannot be lvalue reference) + +template +cxx::enable_if_t>, + cxx::negation>>, + cxx::negation, typename basic_value::string_type::value_type const*>> + >::value, cxx::remove_cvref_t> +get_or(const basic_value& v, T&& opt) +{ + try + { + return get>(v); + } + catch(...) + { + return cxx::remove_cvref_t(std::forward(opt)); + } +} + +} // toml +#endif // TOML11_GET_HPP +#ifndef TOML11_FIND_HPP +#define TOML11_FIND_HPP + +#include + + +#if defined(TOML11_HAS_STRING_VIEW) +#include +#endif + +namespace toml +{ + +// ---------------------------------------------------------------------------- +// find(value, key); + +template +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const typename basic_value::key_type& ky) +{ + return ::toml::get(v.at(ky)); +} + +template +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const typename basic_value::key_type& ky) +{ + return ::toml::get(v.at(ky)); +} + +template +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const typename basic_value::key_type& ky) +{ + return ::toml::get(std::move(v.at(ky))); +} + +// ---------------------------------------------------------------------------- +// find(value, idx) + +template +decltype(::toml::get(std::declval const&>())) +find(const basic_value& v, const std::size_t idx) +{ + return ::toml::get(v.at(idx)); +} +template +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const std::size_t idx) +{ + return ::toml::get(v.at(idx)); +} +template +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const std::size_t idx) +{ + return ::toml::get(std::move(v.at(idx))); +} + +// ---------------------------------------------------------------------------- +// find(value, key/idx), w/o conversion + +template +cxx::enable_if_t::value, basic_value>& +find(basic_value& v, const typename basic_value::key_type& ky) +{ + return v.at(ky); +} +template +cxx::enable_if_t::value, basic_value> const& +find(basic_value const& v, const typename basic_value::key_type& ky) +{ + return v.at(ky); +} +template +cxx::enable_if_t::value, basic_value> +find(basic_value&& v, const typename basic_value::key_type& ky) +{ + return basic_value(std::move(v.at(ky))); +} + +template +cxx::enable_if_t::value, basic_value>& +find(basic_value& v, const std::size_t idx) +{ + return v.at(idx); +} +template +cxx::enable_if_t::value, basic_value> const& +find(basic_value const& v, const std::size_t idx) +{ + return v.at(idx); +} +template +cxx::enable_if_t::value, basic_value> +find(basic_value&& v, const std::size_t idx) +{ + return basic_value(std::move(v.at(idx))); +} + +// -------------------------------------------------------------------------- +// toml::find(toml::value, toml::key, Ts&& ... keys) + +namespace detail +{ + +// It suppresses warnings by -Wsign-conversion when we pass integer literal +// to toml::find. integer literal `0` is deduced as an int, and will be +// converted to std::size_t. This causes sign-conversion. + +template +std::size_t key_cast(const std::size_t& v) noexcept +{ + return v; +} +template +cxx::enable_if_t>::value, std::size_t> +key_cast(const T& v) noexcept +{ + return static_cast(v); +} + +// for string-like (string, string literal, string_view) + +template +typename basic_value::key_type const& +key_cast(const typename basic_value::key_type& v) noexcept +{ + return v; +} +template +typename basic_value::key_type +key_cast(const typename basic_value::key_type::value_type* v) +{ + return typename basic_value::key_type(v); +} +#if defined(TOML11_HAS_STRING_VIEW) +template +typename basic_value::key_type +key_cast(const std::string_view v) +{ + return typename basic_value::key_type(v); +} +#endif // string_view + +} // detail + +// ---------------------------------------------------------------------------- +// find(v, keys...) + +template +cxx::enable_if_t::value, basic_value> const& +find(const basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +cxx::enable_if_t::value, basic_value>& +find(basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +cxx::enable_if_t::value, basic_value> +find(basic_value&& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(std::move(v.at(detail::key_cast(k1))), detail::key_cast(k2), ks...); +} + +// ---------------------------------------------------------------------------- +// find(v, keys...) + +template +decltype(::toml::get(std::declval&>())) +find(const basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +decltype(::toml::get(std::declval&>())) +find(basic_value& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(v.at(detail::key_cast(k1)), detail::key_cast(k2), ks...); +} +template +decltype(::toml::get(std::declval&&>())) +find(basic_value&& v, const K1& k1, const K2& k2, const Ks& ... ks) +{ + return find(std::move(v.at(detail::key_cast(k1))), detail::key_cast(k2), ks...); +} + +// =========================================================================== +// find_or(value, key, fallback) + +// --------------------------------------------------------------------------- +// find_or(v, key, other_v) + +template +cxx::enable_if_t::value, basic_value>& +find_or(basic_value& v, const K& k, basic_value& opt) noexcept +{ + try + { + return ::toml::find(v, detail::key_cast(k)); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t::value, basic_value> const& +find_or(const basic_value& v, const K& k, const basic_value& opt) noexcept +{ + try + { + return ::toml::find(v, detail::key_cast(k)); + } + catch(...) + { + return opt; + } +} +template +cxx::enable_if_t::value, basic_value> +find_or(basic_value&& v, const K& k, basic_value&& opt) noexcept +{ + try + { + return ::toml::find(v, detail::key_cast(k)); + } + catch(...) + { + return opt; + } +} + +// --------------------------------------------------------------------------- +// toml types (return type can be a reference) + +template +cxx::enable_if_t>::value, + cxx::remove_cvref_t const&> +find_or(const basic_value& v, const K& k, const T& opt) +{ + try + { + return ::toml::get(v.at(detail::key_cast(k))); + } + catch(...) + { + return opt; + } +} + +template +cxx::enable_if_t>, + detail::is_exact_toml_type> + >::value, cxx::remove_cvref_t&> +find_or(basic_value& v, const K& k, T& opt) +{ + try + { + return ::toml::get(v.at(detail::key_cast(k))); + } + catch(...) + { + return opt; + } +} + +template +cxx::enable_if_t>::value, + cxx::remove_cvref_t> +find_or(basic_value&& v, const K& k, T opt) +{ + try + { + return ::toml::get(std::move(v.at(detail::key_cast(k)))); + } + catch(...) + { + return T(std::move(opt)); + } +} + +// --------------------------------------------------------------------------- +// string literal (deduced as std::string) + +// XXX to avoid confusion when T is explicitly specified in find_or(), +// we restrict the string type as std::string. +template +cxx::enable_if_t::value, std::string> +find_or(const basic_value& v, const K& k, const char* opt) +{ + try + { + return ::toml::get(v.at(detail::key_cast(k))); + } + catch(...) + { + return std::string(opt); + } +} + +// --------------------------------------------------------------------------- +// other types (requires type conversion and return type cannot be a reference) + +template +cxx::enable_if_t>>, + detail::is_not_toml_type, basic_value>, + cxx::negation, + const typename basic_value::string_type::value_type*>> + >::value, cxx::remove_cvref_t> +find_or(const basic_value& v, const K& ky, T opt) +{ + try + { + return ::toml::get>(v.at(detail::key_cast(ky))); + } + catch(...) + { + return cxx::remove_cvref_t(std::move(opt)); + } +} + +// ---------------------------------------------------------------------------- +// recursive + +namespace detail +{ + +template +auto last_one(Ts&&... args) + -> decltype(std::get(std::forward_as_tuple(std::forward(args)...))) +{ + return std::get(std::forward_as_tuple(std::forward(args)...)); +} + +} // detail + +template +auto find_or(Value&& v, const K1& k1, const K2& k2, K3&& k3, Ks&& ... keys) noexcept + -> cxx::enable_if_t< + detail::is_basic_value>::value, + decltype(find_or(v, k2, std::forward(k3), std::forward(keys)...)) + > +{ + try + { + return find_or(v.at(k1), k2, std::forward(k3), std::forward(keys)...); + } + catch(...) + { + return detail::last_one(k3, keys...); + } +} + +template +T find_or(const basic_value& v, const K1& k1, const K2& k2, const K3& k3, const Ks& ... keys) noexcept +{ + try + { + return find_or(v.at(k1), k2, k3, keys...); + } + catch(...) + { + return static_cast(detail::last_one(k3, keys...)); + } +} + +} // toml +#endif // TOML11_FIND_HPP +#ifndef TOML11_CONVERSION_HPP +#define TOML11_CONVERSION_HPP + + +#if defined(TOML11_HAS_OPTIONAL) + +#include + +namespace toml +{ +namespace detail +{ + +template +inline constexpr bool is_optional_v = false; + +template +inline constexpr bool is_optional_v> = true; + +template +void find_member_variable_from_value(T& obj, const basic_value& v, const char* var_name) +{ + if constexpr(is_optional_v) + { + if(v.contains(var_name)) + { + obj = toml::find(v, var_name); + } + else + { + obj = std::nullopt; + } + } + else + { + obj = toml::find(v, var_name); + } +} + +template +void assign_member_variable_to_value(const T& obj, basic_value& v, const char* var_name) +{ + if constexpr(is_optional_v) + { + if(obj.has_value()) + { + v[var_name] = obj.value(); + } + } + else + { + v[var_name] = obj; + } +} + +} // detail +} // toml + +#else + +namespace toml +{ +namespace detail +{ + +template +void find_member_variable_from_value(T& obj, const basic_value& v, const char* var_name) +{ + obj = toml::find(v, var_name); +} + +template +void assign_member_variable_to_value(const T& obj, basic_value& v, const char* var_name) +{ + v[var_name] = obj; +} + +} // detail +} // toml + +#endif // optional + +// use it in the following way. +// ```cpp +// namespace foo +// { +// struct Foo +// { +// std::string s; +// double d; +// int i; +// }; +// } // foo +// +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(foo::Foo, s, d, i) +// ``` +// +// And then you can use `toml::get(v)` and `toml::find(file, "foo");` +// + +#define TOML11_STRINGIZE_AUX(x) #x +#define TOML11_STRINGIZE(x) TOML11_STRINGIZE_AUX(x) + +#define TOML11_CONCATENATE_AUX(x, y) x##y +#define TOML11_CONCATENATE(x, y) TOML11_CONCATENATE_AUX(x, y) + +// ============================================================================ +// TOML11_DEFINE_CONVERSION_NON_INTRUSIVE + +#ifndef TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +// ---------------------------------------------------------------------------- +// TOML11_ARGS_SIZE + +#define TOML11_INDEX_RSEQ() \ + 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, \ + 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#define TOML11_ARGS_SIZE_IMPL(\ + ARG1, ARG2, ARG3, ARG4, ARG5, ARG6, ARG7, ARG8, ARG9, ARG10, \ + ARG11, ARG12, ARG13, ARG14, ARG15, ARG16, ARG17, ARG18, ARG19, ARG20, \ + ARG21, ARG22, ARG23, ARG24, ARG25, ARG26, ARG27, ARG28, ARG29, ARG30, \ + ARG31, ARG32, N, ...) N +#define TOML11_ARGS_SIZE_AUX(...) TOML11_ARGS_SIZE_IMPL(__VA_ARGS__) +#define TOML11_ARGS_SIZE(...) TOML11_ARGS_SIZE_AUX(__VA_ARGS__, TOML11_INDEX_RSEQ()) + +// ---------------------------------------------------------------------------- +// TOML11_FOR_EACH_VA_ARGS + +#define TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, ARG1 ) FUNCTOR(ARG1) +#define TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_1( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_2( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_3( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_4( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_5( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_6( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_7( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_8( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_9( FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_10(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_11(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_12(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_13(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_14(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_15(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_16(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_17(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_18(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_19(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_20(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_21(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_22(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_23(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_24(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_25(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_26(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_27(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_28(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_29(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_30(FUNCTOR, __VA_ARGS__) +#define TOML11_FOR_EACH_VA_ARGS_AUX_32(FUNCTOR, ARG1, ...) FUNCTOR(ARG1) TOML11_FOR_EACH_VA_ARGS_AUX_31(FUNCTOR, __VA_ARGS__) + +#define TOML11_FOR_EACH_VA_ARGS(FUNCTOR, ...)\ + TOML11_CONCATENATE(TOML11_FOR_EACH_VA_ARGS_AUX_, TOML11_ARGS_SIZE(__VA_ARGS__))(FUNCTOR, __VA_ARGS__) + + +#define TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE(VAR_NAME)\ + toml::detail::find_member_variable_from_value(obj.VAR_NAME, v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE(VAR_NAME)\ + toml::detail::assign_member_variable_to_value(obj.VAR_NAME, v, TOML11_STRINGIZE(VAR_NAME)); + +#define TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(NAME, ...)\ + namespace toml { \ + template<> \ + struct from \ + { \ + template \ + static NAME from_toml(const basic_value& v) \ + { \ + NAME obj; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_FIND_MEMBER_VARIABLE_FROM_VALUE, __VA_ARGS__) \ + return obj; \ + } \ + }; \ + template<> \ + struct into \ + { \ + template \ + static basic_value into_toml(const NAME& obj) \ + { \ + ::toml::basic_value v = typename ::toml::basic_value::table_type{}; \ + TOML11_FOR_EACH_VA_ARGS(TOML11_ASSIGN_MEMBER_VARIABLE_TO_VALUE, __VA_ARGS__) \ + return v; \ + } \ + }; \ + } /* toml */ + +#endif// TOML11_WITHOUT_DEFINE_NON_INTRUSIVE + +#endif // TOML11_CONVERSION_HPP +#ifndef TOML11_CONTEXT_HPP +#define TOML11_CONTEXT_HPP + + +#include + +namespace toml +{ +namespace detail +{ + +template +class context +{ + public: + + explicit context(const spec& toml_spec) + : toml_spec_(toml_spec), errors_{} + {} + + bool has_error() const noexcept {return !errors_.empty();} + + std::vector const& errors() const noexcept {return errors_;} + + semantic_version& toml_version() noexcept {return toml_spec_.version;} + semantic_version const& toml_version() const noexcept {return toml_spec_.version;} + + spec& toml_spec() noexcept {return toml_spec_;} + spec const& toml_spec() const noexcept {return toml_spec_;} + + void report_error(error_info err) + { + this->errors_.push_back(std::move(err)); + } + + error_info pop_last_error() + { + assert( ! errors_.empty()); + auto e = std::move(errors_.back()); + errors_.pop_back(); + return e; + } + + private: + + spec toml_spec_; + std::vector errors_; +}; + +} // detail +} // toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; +namespace detail +{ +extern template class context<::toml::type_config>; +extern template class context<::toml::ordered_type_config>; +} // detail +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_CONTEXT_HPP +#ifndef TOML11_SCANNER_HPP +#define TOML11_SCANNER_HPP + +#ifndef TOML11_SCANNER_FWD_HPP +#define TOML11_SCANNER_FWD_HPP + + +#include +#include +#include +#include + +#include +#include +#include + +namespace toml +{ +namespace detail +{ + +class scanner_base +{ + public: + virtual ~scanner_base() = default; + virtual region scan(location& loc) const = 0; + virtual scanner_base* clone() const = 0; + + // returns expected character or set of characters or literal. + // to show the error location, it changes loc (in `sequence`, especially). + virtual std::string expected_chars(location& loc) const = 0; + virtual std::string name() const = 0; +}; + +// make `scanner*` copyable +struct scanner_storage +{ + template>::value, + std::nullptr_t> = nullptr> + explicit scanner_storage(Scanner&& s) + : scanner_(cxx::make_unique>(std::forward(s))) + {} + ~scanner_storage() = default; + + scanner_storage(const scanner_storage& other); + scanner_storage& operator=(const scanner_storage& other); + scanner_storage(scanner_storage&&) = default; + scanner_storage& operator=(scanner_storage&&) = default; + + bool is_ok() const noexcept {return static_cast(scanner_);} + + region scan(location& loc) const; + + std::string expected_chars(location& loc) const; + + scanner_base& get() const noexcept; + + std::string name() const; + + private: + + std::unique_ptr scanner_; +}; + +// ---------------------------------------------------------------------------- + +class character final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + explicit character(const char_type c) noexcept + : value_(c) + {} + ~character() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + char_type value_; +}; + +// ---------------------------------------------------------------------------- + +class character_either final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + explicit character_either(std::initializer_list cs) noexcept + : chars_(std::move(cs)) + { + assert(! this->chars_.empty()); + } + + template + explicit character_either(const char (&cs)[N]) noexcept + : chars_(N-1, '\0') + { + static_assert(N >= 1, ""); + for(std::size_t i=0; i+1 chars_; +}; + +// ---------------------------------------------------------------------------- + +class character_in_range final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + explicit character_in_range(const char_type from, const char_type to) noexcept + : from_(from), to_(to) + {} + ~character_in_range() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + char_type from_; + char_type to_; +}; + +// ---------------------------------------------------------------------------- + +class literal final : public scanner_base +{ + public: + + using char_type = location::char_type; + + public: + + template + explicit literal(const char (&cs)[N]) noexcept + : value_(cs), size_(N-1) // remove null character at the end + {} + ~literal() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + const char* value_; + std::size_t size_; +}; + +// ---------------------------------------------------------------------------- + +class sequence final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + explicit sequence(Ts&& ... args) + { + push_back_all(std::forward(args)...); + } + sequence(const sequence&) = default; + sequence(sequence&&) = default; + sequence& operator=(const sequence&) = default; + sequence& operator=(sequence&&) = default; + ~sequence() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + template + void push_back(Scanner&& other_scanner) + { + this->others_.emplace_back(std::forward(other_scanner)); + } + + std::string name() const override; + + private: + + void push_back_all() + { + return; + } + template + void push_back_all(T&& head, Ts&& ... args) + { + others_.emplace_back(std::forward(head)); + push_back_all(std::forward(args)...); + return; + } + + private: + std::vector others_; +}; + +// ---------------------------------------------------------------------------- + +class either final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + explicit either(Ts&& ... args) + { + push_back_all(std::forward(args)...); + } + either(const either&) = default; + either(either&&) = default; + either& operator=(const either&) = default; + either& operator=(either&&) = default; + ~either() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + template + void push_back(Scanner&& other_scanner) + { + this->others_.emplace_back(std::forward(other_scanner)); + } + + std::string name() const override; + + private: + + void push_back_all() + { + return; + } + template + void push_back_all(T&& head, Ts&& ... args) + { + others_.emplace_back(std::forward(head)); + push_back_all(std::forward(args)...); + return; + } + + private: + std::vector others_; +}; + +// ---------------------------------------------------------------------------- + +class repeat_exact final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + repeat_exact(const std::size_t length, Scanner&& other) + : length_(length), other_(std::forward(other)) + {} + repeat_exact(const repeat_exact&) = default; + repeat_exact(repeat_exact&&) = default; + repeat_exact& operator=(const repeat_exact&) = default; + repeat_exact& operator=(repeat_exact&&) = default; + ~repeat_exact() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + std::size_t length_; + scanner_storage other_; +}; + +// ---------------------------------------------------------------------------- + +class repeat_at_least final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + repeat_at_least(const std::size_t length, Scanner&& s) + : length_(length), other_(std::forward(s)) + {} + repeat_at_least(const repeat_at_least&) = default; + repeat_at_least(repeat_at_least&&) = default; + repeat_at_least& operator=(const repeat_at_least&) = default; + repeat_at_least& operator=(repeat_at_least&&) = default; + ~repeat_at_least() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location& loc) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + std::size_t length_; + scanner_storage other_; +}; + +// ---------------------------------------------------------------------------- + +class maybe final: public scanner_base +{ + public: + using char_type = location::char_type; + + public: + + template + explicit maybe(Scanner&& s) + : other_(std::forward(s)) + {} + maybe(const maybe&) = default; + maybe(maybe&&) = default; + maybe& operator=(const maybe&) = default; + maybe& operator=(maybe&&) = default; + ~maybe() override = default; + + region scan(location& loc) const override; + + std::string expected_chars(location&) const override; + + scanner_base* clone() const override; + + std::string name() const override; + + private: + scanner_storage other_; +}; + +} // detail +} // toml +#endif // TOML11_SCANNER_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_SCANNER_IMPL_HPP +#define TOML11_SCANNER_IMPL_HPP + + +namespace toml +{ +namespace detail +{ + +TOML11_INLINE scanner_storage::scanner_storage(const scanner_storage& other) + : scanner_(nullptr) +{ + if(other.is_ok()) + { + scanner_.reset(other.get().clone()); + } +} +TOML11_INLINE scanner_storage& scanner_storage::operator=(const scanner_storage& other) +{ + if(this == std::addressof(other)) {return *this;} + if(other.is_ok()) + { + scanner_.reset(other.get().clone()); + } + return *this; +} + +TOML11_INLINE region scanner_storage::scan(location& loc) const +{ + assert(this->is_ok()); + return this->scanner_->scan(loc); +} + +TOML11_INLINE std::string scanner_storage::expected_chars(location& loc) const +{ + assert(this->is_ok()); + return this->scanner_->expected_chars(loc); +} + +TOML11_INLINE scanner_base& scanner_storage::get() const noexcept +{ + assert(this->is_ok()); + return *scanner_; +} + +TOML11_INLINE std::string scanner_storage::name() const +{ + assert(this->is_ok()); + return this->scanner_->name(); +} + +// ---------------------------------------------------------------------------- + +TOML11_INLINE region character::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + if(loc.current() == this->value_) + { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + return region{}; +} + +TOML11_INLINE std::string character::expected_chars(location&) const +{ + return show_char(value_); +} + +TOML11_INLINE scanner_base* character::clone() const +{ + return new character(*this); +} + +TOML11_INLINE std::string character::name() const +{ + return "character{" + show_char(value_) + "}"; +} + +// ---------------------------------------------------------------------------- + +TOML11_INLINE region character_either::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + for(const auto c : this->chars_) + { + if(loc.current() == c) + { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + } + return region{}; +} + +TOML11_INLINE std::string character_either::expected_chars(location&) const +{ + assert( ! chars_.empty()); + + std::string expected; + if(chars_.size() == 1) + { + expected += show_char(chars_.at(0)); + } + else if(chars_.size() == 2) + { + expected += show_char(chars_.at(0)) + " or " + show_char(chars_.at(1)); + } + else + { + for(std::size_t i=0; ichars_) + { + n += show_char(c); + n += ", "; + } + if( ! this->chars_.empty()) + { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; +} + +// ---------------------------------------------------------------------------- +// character_in_range + +TOML11_INLINE region character_in_range::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + const auto curr = loc.current(); + if(this->from_ <= curr && curr <= this->to_) + { + const auto first = loc; + loc.advance(1); + return region(first, loc); + } + return region{}; +} + +TOML11_INLINE std::string character_in_range::expected_chars(location&) const +{ + std::string expected("from `"); + expected += show_char(from_); + expected += "` to `"; + expected += show_char(to_); + expected += "`"; + return expected; +} + +TOML11_INLINE scanner_base* character_in_range::clone() const +{ + return new character_in_range(*this); +} + +TOML11_INLINE std::string character_in_range::name() const +{ + return "character_in_range{" + show_char(from_) + "," + show_char(to_) + "}"; +} + +// ---------------------------------------------------------------------------- +// literal + +TOML11_INLINE region literal::scan(location& loc) const +{ + const auto first = loc; + for(std::size_t i=0; iothers_.empty()) + { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; +} + +// ---------------------------------------------------------------------------- +// either + +TOML11_INLINE region either::scan(location& loc) const +{ + for(const auto& other : others_) + { + const auto reg = other.scan(loc); + if(reg.is_ok()) + { + return reg; + } + } + return region{}; +} + +TOML11_INLINE std::string either::expected_chars(location& loc) const +{ + assert( ! others_.empty()); + + std::string expected = others_.at(0).expected_chars(loc); + if(others_.size() == 2) + { + expected += " or "; + expected += others_.at(1).expected_chars(loc); + } + else + { + for(std::size_t i=1; iothers_.empty()) + { + n.pop_back(); + n.pop_back(); + } + n += "}"; + return n; +} + +// ---------------------------------------------------------------------------- +// repeat_exact + +TOML11_INLINE region repeat_exact::scan(location& loc) const +{ + const auto first = loc; + for(std::size_t i=0; i(b1); + } + else if((b1 >> 5) == 6) // 0b110 == 6 + { + const auto b2 = loc.current(); loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 5) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t codep = (c1 << 6) + c2; + + if(codep < 0x80) + { + return 0xFFFFFFFF; + } + return codep; + } + else if((b1 >> 4) == 14) // 0b1110 == 14 + { + const auto b2 = loc.current(); loc.advance(1); if(loc.eof()) {return 0xFFFFFFFF;} + const auto b3 = loc.current(); loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 4) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t c3 = b3 & ((1 << 6) - 1); + + const std::uint32_t codep = (c1 << 12) + (c2 << 6) + c3; + if(codep < 0x800) + { + return 0xFFFFFFFF; + } + return codep; + } + else if((b1 >> 3) == 30) // 0b11110 == 30 + { + const auto b2 = loc.current(); loc.advance(1); if(loc.eof()) {return 0xFFFFFFFF;} + const auto b3 = loc.current(); loc.advance(1); if(loc.eof()) {return 0xFFFFFFFF;} + const auto b4 = loc.current(); loc.advance(1); + + const std::uint32_t c1 = b1 & ((1 << 3) - 1); + const std::uint32_t c2 = b2 & ((1 << 6) - 1); + const std::uint32_t c3 = b3 & ((1 << 6) - 1); + const std::uint32_t c4 = b4 & ((1 << 6) - 1); + const std::uint32_t codep = (c1 << 18) + (c2 << 12) + (c3 << 6) + c4; + + if(codep < 0x10000) + { + return 0xFFFFFFFF; + } + return codep; + } + else // not a Unicode codepoint in UTF-8 + { + return 0xFFFFFFFF; + } +} + +TOML11_INLINE region non_ascii_key_char::scan(location& loc) const +{ + if(loc.eof()) {return region{};} + + const auto first = loc; + + const auto cp = read_utf8(loc); + + if(cp == 0xFFFFFFFF) + { + return region{}; + } + + // ALPHA / DIGIT / %x2D / %x5F ; a-z A-Z 0-9 - _ + // / %xB2 / %xB3 / %xB9 / %xBC-BE ; superscript digits, fractions + // / %xC0-D6 / %xD8-F6 / %xF8-37D ; non-symbol chars in Latin block + // / %x37F-1FFF ; exclude GREEK QUESTION MARK, which is basically a semi-colon + // / %x200C-200D / %x203F-2040 ; from General Punctuation Block, include the two tie symbols and ZWNJ, ZWJ + // / %x2070-218F / %x2460-24FF ; include super-/subscripts, letterlike/numberlike forms, enclosed alphanumerics + // / %x2C00-2FEF / %x3001-D7FF ; skip arrows, math, box drawing etc, skip 2FF0-3000 ideographic up/down markers and spaces + // / %xF900-FDCF / %xFDF0-FFFD ; skip D800-DFFF surrogate block, E000-F8FF Private Use area, FDD0-FDEF intended for process-internal use (unicode) + // / %x10000-EFFFF ; all chars outside BMP range, excluding Private Use planes (F0000-10FFFF) + + if(cp == 0xB2 || cp == 0xB3 || cp == 0xB9 || (0xBC <= cp && cp <= 0xBE) || + (0xC0 <= cp && cp <= 0xD6 ) || (0xD8 <= cp && cp <= 0xF6) || (0xF8 <= cp && cp <= 0x37D) || + (0x37F <= cp && cp <= 0x1FFF) || + (0x200C <= cp && cp <= 0x200D) || (0x203F <= cp && cp <= 0x2040) || + (0x2070 <= cp && cp <= 0x218F) || (0x2460 <= cp && cp <= 0x24FF) || + (0x2C00 <= cp && cp <= 0x2FEF) || (0x3001 <= cp && cp <= 0xD7FF) || + (0xF900 <= cp && cp <= 0xFDCF) || (0xFDF0 <= cp && cp <= 0xFFFD) || + (0x10000 <= cp && cp <= 0xEFFFF) ) + { + return region(first, loc); + } + loc = first; + return region{}; +} + +TOML11_INLINE repeat_at_least unquoted_key(const spec& s) +{ + auto keychar = either( + alpha(s), digit(s), character{0x2D}, character{0x5F} + ); + + if(s.v1_1_0_allow_non_english_in_bare_keys) + { + keychar.push_back(non_ascii_key_char(s)); + } + + return repeat_at_least(1, std::move(keychar)); +} + +TOML11_INLINE either quoted_key(const spec& s) +{ + return either(basic_string(s), literal_string(s)); +} + +TOML11_INLINE either simple_key(const spec& s) +{ + return either(unquoted_key(s), quoted_key(s)); +} + +TOML11_INLINE sequence dot_sep(const spec& s) +{ + return sequence(ws(s), character('.'), ws(s)); +} + +TOML11_INLINE sequence dotted_key(const spec& s) +{ + return sequence( + simple_key(s), + repeat_at_least(1, sequence(dot_sep(s), simple_key(s))) + ); +} + +TOML11_INLINE key::key(const spec& s) noexcept + : scanner_(dotted_key(s), simple_key(s)) +{} + +TOML11_INLINE sequence keyval_sep(const spec& s) +{ + return sequence(ws(s), character('='), ws(s)); +} + +// =========================================================================== +// Table key + +TOML11_INLINE sequence std_table(const spec& s) +{ + return sequence(character('['), ws(s), key(s), ws(s), character(']')); +} + +TOML11_INLINE sequence array_table(const spec& s) +{ + return sequence(literal("[["), ws(s), key(s), ws(s), literal("]]")); +} + +// =========================================================================== +// extension: null + +TOML11_INLINE literal null_value(const spec&) +{ + return literal("null"); +} + +} // namespace syntax +} // namespace detail +} // namespace toml +#endif // TOML11_SYNTAX_IMPL_HPP +#endif + +#endif// TOML11_SYNTAX_HPP +#ifndef TOML11_SKIP_HPP +#define TOML11_SKIP_HPP + + +#include + +namespace toml +{ +namespace detail +{ + +template +bool skip_whitespace(location& loc, const context& ctx) +{ + return syntax::ws(ctx.toml_spec()).scan(loc).is_ok(); +} + +template +bool skip_empty_lines(location& loc, const context& ctx) +{ + return repeat_at_least(1, sequence( + syntax::ws(ctx.toml_spec()), + syntax::newline(ctx.toml_spec()) + )).scan(loc).is_ok(); +} + +// For error recovery. +// +// In case if a comment line contains an invalid character, we need to skip it +// to advance parsing. +template +void skip_comment_block(location& loc, const context& ctx) +{ + while( ! loc.eof()) + { + skip_whitespace(loc, ctx); + if(loc.current() == '#') + { + while( ! loc.eof()) + { + // both CRLF and LF ends with LF. + if(loc.current() == '\n') + { + loc.advance(); + break; + } + } + } + else if(syntax::newline(ctx.toml_spec()).scan(loc).is_ok()) + { + ; // an empty line. skip this also + } + else + { + // the next token is neither a comment nor empty line. + return ; + } + } + return ; +} + +template +void skip_empty_or_comment_lines(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + repeat_at_least(0, sequence( + syntax::ws(spec), + maybe(syntax::comment(spec)), + syntax::newline(spec)) + ).scan(loc); + return ; +} + +// For error recovery. +// +// Sometimes we need to skip a value and find a delimiter, like `,`, `]`, or `}`. +// To find delimiter, we need to skip delimiters in a string. +// Since we are skipping invalid value while error recovery, we don't need +// to check the syntax. Here we just skip string-like region until closing quote +// is found. +template +void skip_string_like(location& loc, const context&) +{ + // if """ is found, skip until the closing """ is found. + if(literal("\"\"\"").scan(loc).is_ok()) + { + while( ! loc.eof()) + { + if(literal("\"\"\"").scan(loc).is_ok()) + { + return; + } + loc.advance(); + } + } + else if(literal("'''").scan(loc).is_ok()) + { + while( ! loc.eof()) + { + if(literal("'''").scan(loc).is_ok()) + { + return; + } + loc.advance(); + } + } + // if " is found, skip until the closing " or newline is found. + else if(loc.current() == '"') + { + while( ! loc.eof()) + { + loc.advance(); + if(loc.current() == '"' || loc.current() == '\n') + { + loc.advance(); + return; + } + } + } + else if(loc.current() == '\'') + { + while( ! loc.eof()) + { + loc.advance(); + if(loc.current() == '\'' || loc.current() == '\n') + { + loc.advance(); + return ; + } + } + } + return; +} + +template +void skip_value(location& loc, const context& ctx); +template +void skip_array_like(location& loc, const context& ctx); +template +void skip_inline_table_like(location& loc, const context& ctx); +template +void skip_key_value_pair(location& loc, const context& ctx); + +template +result +guess_value_type(const location& loc, const context& ctx); + +template +void skip_array_like(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + assert(loc.current() == '['); + loc.advance(); + + while( ! loc.eof()) + { + if(loc.current() == '\"' || loc.current() == '\'') + { + skip_string_like(loc, ctx); + } + else if(loc.current() == '#') + { + skip_comment_block(loc, ctx); + } + else if(loc.current() == '{') + { + skip_inline_table_like(loc, ctx); + } + else if(loc.current() == '[') + { + const auto checkpoint = loc; + if(syntax::std_table(spec).scan(loc).is_ok() || + syntax::array_table(spec).scan(loc).is_ok()) + { + loc = checkpoint; + break; + } + // if it is not a table-definition, then it is an array. + skip_array_like(loc, ctx); + } + else if(loc.current() == '=') + { + // key-value pair cannot be inside the array. + // guessing the error is "missing closing bracket `]`". + // find the previous key just before `=`. + while(loc.get_location() != 0) + { + loc.retrace(); + if(loc.current() == '\n') + { + loc.advance(); + break; + } + } + break; + } + else if(loc.current() == ']') + { + break; // found closing bracket + } + else + { + loc.advance(); + } + } + return ; +} + +template +void skip_inline_table_like(location& loc, const context& ctx) +{ + assert(loc.current() == '{'); + loc.advance(); + + const auto& spec = ctx.toml_spec(); + + while( ! loc.eof()) + { + if(loc.current() == '\n' && ! spec.v1_1_0_allow_newlines_in_inline_tables) + { + break; // missing closing `}`. + } + else if(loc.current() == '\"' || loc.current() == '\'') + { + skip_string_like(loc, ctx); + } + else if(loc.current() == '#') + { + skip_comment_block(loc, ctx); + if( ! spec.v1_1_0_allow_newlines_in_inline_tables) + { + // comment must end with newline. + break; // missing closing `}`. + } + } + else if(loc.current() == '[') + { + const auto checkpoint = loc; + if(syntax::std_table(spec).scan(loc).is_ok() || + syntax::array_table(spec).scan(loc).is_ok()) + { + loc = checkpoint; + break; // missing closing `}`. + } + // if it is not a table-definition, then it is an array. + skip_array_like(loc, ctx); + } + else if(loc.current() == '{') + { + skip_inline_table_like(loc, ctx); + } + else if(loc.current() == '}') + { + // closing brace found. guessing the error is inside the table. + break; + } + else + { + // skip otherwise. + loc.advance(); + } + } + return ; +} + +template +void skip_value(location& loc, const context& ctx) +{ + value_t ty = guess_value_type(loc, ctx).unwrap_or(value_t::empty); + if(ty == value_t::string) + { + skip_string_like(loc, ctx); + } + else if(ty == value_t::array) + { + skip_array_like(loc, ctx); + } + else if(ty == value_t::table) + { + // In case of multiline tables, it may skip key-value pair but not the + // whole table. + skip_inline_table_like(loc, ctx); + } + else // others are an "in-line" values. skip until the next line + { + while( ! loc.eof()) + { + if(loc.current() == '\n') + { + break; + } + else if(loc.current() == ',' || loc.current() == ']' || loc.current() == '}') + { + break; + } + loc.advance(); + } + } + return; +} + +template +void skip_key_value_pair(location& loc, const context& ctx) +{ + while( ! loc.eof()) + { + if(loc.current() == '=') + { + skip_whitespace(loc, ctx); + skip_value(loc, ctx); + return; + } + else if(loc.current() == '\n') + { + // newline is found before finding `=`. assuming "missing `=`". + return; + } + loc.advance(); + } + return ; +} + +template +void skip_until_next_table(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + while( ! loc.eof()) + { + if(loc.current() == '\n') + { + loc.advance(); + const auto line_begin = loc; + + skip_whitespace(loc, ctx); + if(syntax::std_table(spec).scan(loc).is_ok()) + { + loc = line_begin; + return ; + } + if(syntax::array_table(spec).scan(loc).is_ok()) + { + loc = line_begin; + return ; + } + } + loc.advance(); + } +} + +} // namespace detail +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; + +namespace detail +{ +extern template bool skip_whitespace (location& loc, const context&); +extern template bool skip_empty_lines (location& loc, const context&); +extern template void skip_comment_block (location& loc, const context&); +extern template void skip_empty_or_comment_lines(location& loc, const context&); +extern template void skip_string_like (location& loc, const context&); +extern template void skip_array_like (location& loc, const context&); +extern template void skip_inline_table_like (location& loc, const context&); +extern template void skip_value (location& loc, const context&); +extern template void skip_key_value_pair (location& loc, const context&); +extern template void skip_until_next_table (location& loc, const context&); + +extern template bool skip_whitespace (location& loc, const context&); +extern template bool skip_empty_lines (location& loc, const context&); +extern template void skip_comment_block (location& loc, const context&); +extern template void skip_empty_or_comment_lines(location& loc, const context&); +extern template void skip_string_like (location& loc, const context&); +extern template void skip_array_like (location& loc, const context&); +extern template void skip_inline_table_like (location& loc, const context&); +extern template void skip_value (location& loc, const context&); +extern template void skip_key_value_pair (location& loc, const context&); +extern template void skip_until_next_table (location& loc, const context&); + +} // detail +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_SKIP_HPP +#ifndef TOML11_PARSER_HPP +#define TOML11_PARSER_HPP + + +#include +#include + +#include +#include + +#if defined(TOML11_HAS_FILESYSTEM) && TOML11_HAS_FILESYSTEM +#include +#endif + +namespace toml +{ + +struct syntax_error final : public ::toml::exception +{ + public: + syntax_error(std::string what_arg, std::vector err) + : what_(std::move(what_arg)), err_(std::move(err)) + {} + ~syntax_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + + std::vector const& errors() const noexcept + { + return err_; + } + + private: + std::string what_; + std::vector err_; +}; + +struct file_io_error final : public ::toml::exception +{ + public: + + file_io_error(const std::string& msg, const std::string& fname) + : errno_(cxx::make_nullopt()), + what_(msg + " \"" + fname + "\"") + {} + file_io_error(int errnum, const std::string& msg, const std::string& fname) + : errno_(errnum), + what_(msg + " \"" + fname + "\": errno=" + std::to_string(errnum)) + {} + ~file_io_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + + bool has_errno() const noexcept {return errno_.has_value();} + int get_errno() const noexcept {return errno_.value_or(0);} + + private: + + cxx::optional errno_; + std::string what_; +}; + +namespace detail +{ + +/* ============================================================================ + * __ ___ _ __ _ __ ___ _ _ + * / _/ _ \ ' \| ' \/ _ \ ' \ + * \__\___/_|_|_|_|_|_\___/_||_| + */ + +template +error_info make_syntax_error(std::string title, + const S& scanner, location loc, std::string suffix = "") +{ + auto msg = std::string("expected ") + scanner.expected_chars(loc); + auto src = source_location(region(loc)); + return make_error_info( + std::move(title), std::move(src), std::move(msg), std::move(suffix)); +} + + +/* ============================================================================ + * _ + * __ ___ _ __ _ __ ___ _ _| |_ + * / _/ _ \ ' \| ' \/ -_) ' \ _| + * \__\___/_|_|_|_|_|_\___|_||_\__| + */ + +template +result, error_info> +parse_comment_line(location& loc, context& ctx) +{ + const auto& spec = ctx.toml_spec(); + const auto first = loc; + + skip_whitespace(loc, ctx); + + const auto com_reg = syntax::comment(spec).scan(loc); + if(com_reg.is_ok()) + { + // once comment started, newline must follow (or reach EOF). + if( ! loc.eof() && ! syntax::newline(spec).scan(loc).is_ok()) + { + while( ! loc.eof()) // skip until newline to continue parsing + { + loc.advance(); + if(loc.current() == '\n') { /*skip LF*/ loc.advance(); break; } + } + return err(make_error_info("toml::parse_comment_line: " + "newline (LF / CRLF) or EOF is expected", + source_location(region(loc)), "but got this", + "Hint: most of the control characters are not allowed in comments")); + } + return ok(cxx::optional(com_reg.as_string())); + } + else + { + loc = first; // rollback whitespace to parse indent + return ok(cxx::optional(cxx::make_nullopt())); + } +} + +/* ============================================================================ + * ___ _ + * | _ ) ___ ___| |___ __ _ _ _ + * | _ \/ _ \/ _ \ / -_) _` | ' \ + * |___/\___/\___/_\___\__,_|_||_| + */ + +template +result, error_info> +parse_boolean(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::boolean(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_boolean: " + "invalid boolean: boolean must be `true` or `false`, in lowercase. " + "string must be surrounded by `\"`", syntax::boolean(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + const auto val = [&str]() { + if(str == "true") + { + return true; + } + else + { + assert(str == "false"); + return false; + } + }(); + + // ---------------------------------------------------------------------- + // no format info for boolean + boolean_format_info fmt; + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +/* ============================================================================ + * ___ _ + * |_ _|_ _| |_ ___ __ _ ___ _ _ + * | || ' \ _/ -_) _` / -_) '_| + * |___|_||_\__\___\__, \___|_| + * |___/ + */ + +template +result, error_info> +parse_bin_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::bin_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_bin_integer: " + "invalid integer: bin_int must be like: 0b0101, 0b1111_0000", + syntax::bin_int(spec), loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::bin; + fmt.width = str.size() - 2 - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0b` and zeros and underscores at the MSB + str.erase(str.begin(), std::find(std::next(str.begin(), 2), str.end(), '1')); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0b0000_0000 becomes empty. + if(str.empty()) { str = "0"; } + + const auto val = TC::parse_int(str, source_location(region(loc)), 2); + if(val.is_ok()) + { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + else + { + loc = first; + return err(val.as_err()); + } +} + +// ---------------------------------------------------------------------------- + +template +result, error_info> +parse_oct_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::oct_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_oct_integer: " + "invalid integer: oct_int must be like: 0o775, 0o04_44", + syntax::oct_int(spec), loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::oct; + fmt.width = str.size() - 2 - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0o` and zeros and underscores at the MSB + str.erase(str.begin(), std::find_if( + std::next(str.begin(), 2), str.end(), [](const char c) { + return c != '0' && c != '_'; + })); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0o0000_0000 becomes empty. + if(str.empty()) { str = "0"; } + + const auto val = TC::parse_int(str, source_location(region(loc)), 8); + if(val.is_ok()) + { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + else + { + loc = first; + return err(val.as_err()); + } +} + +template +result, error_info> +parse_hex_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + auto reg = syntax::hex_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_hex_integer: " + "invalid integer: hex_int must be like: 0xC0FFEE, 0xdead_beef", + syntax::hex_int(spec), loc)); + } + + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::hex; + fmt.width = str.size() - 2 - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // skip prefix `0x` and zeros and underscores at the MSB + str.erase(str.begin(), std::find_if( + std::next(str.begin(), 2), str.end(), [](const char c) { + return c != '0' && c != '_'; + })); + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + // 0x0000_0000 becomes empty. + if(str.empty()) { str = "0"; } + + // prefix zero and _ is removed. check if it uses upper/lower case. + // if both upper and lower case letters are found, set upper=true. + const auto lower_not_found = std::find_if(str.begin(), str.end(), + [](const char c) { return std::islower(static_cast(c)) != 0; }) == str.end(); + const auto upper_found = std::find_if(str.begin(), str.end(), + [](const char c) { return std::isupper(static_cast(c)) != 0; }) != str.end(); + fmt.uppercase = lower_not_found || upper_found; + + const auto val = TC::parse_int(str, source_location(region(loc)), 16); + if(val.is_ok()) + { + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); + } + else + { + loc = first; + return err(val.as_err()); + } +} + +template +result, error_info> +parse_dec_integer(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::dec_int(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_dec_integer: " + "invalid integer: dec_int must be like: 42, 123_456_789", + syntax::dec_int(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + auto str = reg.as_string(); + + integer_format_info fmt; + fmt.fmt = integer_format::dec; + fmt.width = str.size() - static_cast(std::count(str.begin(), str.end(), '_')); + + const auto first_underscore = std::find(str.rbegin(), str.rend(), '_'); + if(first_underscore != str.rend()) + { + fmt.spacer = static_cast(std::distance(str.rbegin(), first_underscore)); + } + + // remove all `_` before calling TC::parse_int + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + auto src = source_location(region(loc)); + const auto val = TC::parse_int(str, src, 10); + if(val.is_err()) + { + loc = first; + return err(val.as_err()); + } + + // ---------------------------------------------------------------------- + // parse suffix (extension) + + if(spec.ext_num_suffix && loc.current() == '_') + { + const auto sfx_reg = syntax::num_suffix(spec).scan(loc); + if( ! sfx_reg.is_ok()) + { + loc = first; + return err(make_error_info("toml::parse_dec_integer: " + "invalid suffix: should be `_ non-digit-graph (graph | _graph)`", + source_location(region(loc)), "here")); + } + auto sfx = sfx_reg.as_string(); + assert( ! sfx.empty() && sfx.front() == '_'); + sfx.erase(sfx.begin()); // remove the first `_` + + fmt.suffix = sfx; + } + + return ok(basic_value(val.as_ok(), std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_integer(location& loc, const context& ctx) +{ + const auto first = loc; + + if( ! loc.eof() && (loc.current() == '+' || loc.current() == '-')) + { + // skip +/- to diagnose +0xDEADBEEF or -0b0011 (invalid). + // without this, +0xDEAD_BEEF will be parsed as a decimal int and + // unexpected "xDEAD_BEEF" will appear after integer "+0". + loc.advance(); + } + + if( ! loc.eof() && loc.current() == '0') + { + loc.advance(); + if(loc.eof()) + { + // `[+-]?0`. parse as an decimal integer. + loc = first; + return parse_dec_integer(loc, ctx); + } + + const auto prefix = loc.current(); + auto prefix_src = source_location(region(loc)); + + loc = first; + + if(prefix == 'b') {return parse_bin_integer(loc, ctx);} + if(prefix == 'o') {return parse_oct_integer(loc, ctx);} + if(prefix == 'x') {return parse_hex_integer(loc, ctx);} + + if(std::isdigit(prefix)) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_integer: " + "leading zero in an decimal integer is not allowed", + std::move(src), "leading zero")); + } + } + + loc = first; + return parse_dec_integer(loc, ctx); +} + +/* ============================================================================ + * ___ _ _ _ + * | __| |___ __ _| |_(_)_ _ __ _ + * | _|| / _ \/ _` | _| | ' \/ _` | + * |_| |_\___/\__,_|\__|_|_||_\__, | + * |___/ + */ + +template +result, error_info> +parse_floating(location& loc, const context& ctx) +{ + using floating_type = typename basic_value::floating_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + // ---------------------------------------------------------------------- + // check syntax + bool is_hex = false; + std::string str; + region reg; + if(spec.ext_hex_float && sequence(character('0'), character('x')).scan(loc).is_ok()) + { + loc = first; + is_hex = true; + + reg = syntax::hex_floating(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_floating: " + "invalid hex floating: float must be like: 0xABCp-3f", + syntax::floating(spec), loc)); + } + str = reg.as_string(); + } + else + { + reg = syntax::floating(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_floating: " + "invalid floating: float must be like: -3.14159_26535, 6.022e+23, " + "inf, or nan (lowercase).", syntax::floating(spec), loc)); + } + str = reg.as_string(); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + floating_format_info fmt; + + if(is_hex) + { + fmt.fmt = floating_format::hex; + } + else + { + // since we already checked that the string conforms the TOML standard. + if(std::find(str.begin(), str.end(), 'e') != str.end() || + std::find(str.begin(), str.end(), 'E') != str.end()) + { + fmt.fmt = floating_format::scientific; // use exponent part + } + else + { + fmt.fmt = floating_format::fixed; // do not use exponent part + } + } + + str.erase(std::remove(str.begin(), str.end(), '_'), str.end()); + + floating_type val{0}; + + if(str == "inf" || str == "+inf") + { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_infinity) + { + val = std::numeric_limits::infinity(); + } + else + { + return err(make_error_info("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: inf is not supported")); + } + } + else if(str == "-inf") + { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_infinity) + { + val = -std::numeric_limits::infinity(); + } + else + { + return err(make_error_info("toml::parse_floating: inf value found" + " but the current environment does not support inf. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: inf is not supported")); + } + } + else if(str == "nan" || str == "+nan") + { + TOML11_CONSTEXPR_IF(std::numeric_limits::has_quiet_NaN) + { + val = std::numeric_limits::quiet_NaN(); + } + else TOML11_CONSTEXPR_IF(std::numeric_limits::has_signaling_NaN) + { + val = std::numeric_limits::signaling_NaN(); + } + else + { + return err(make_error_info("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: NaN is not supported")); + } + } + else if(str == "-nan") + { + using std::copysign; + TOML11_CONSTEXPR_IF(std::numeric_limits::has_quiet_NaN) + { + val = copysign(std::numeric_limits::quiet_NaN(), floating_type(-1)); + } + else TOML11_CONSTEXPR_IF(std::numeric_limits::has_signaling_NaN) + { + val = copysign(std::numeric_limits::signaling_NaN(), floating_type(-1)); + } + else + { + return err(make_error_info("toml::parse_floating: NaN value found" + " but the current environment does not support NaN. Please" + " make sure that the floating-point implementation conforms" + " IEEE 754/ISO 60559 international standard.", + source_location(region(loc)), + "floating_type: NaN is not supported")); + } + } + else + { + // set precision + const auto has_sign = ! str.empty() && (str.front() == '+' || str.front() == '-'); + const auto decpoint = std::find(str.begin(), str.end(), '.'); + const auto exponent = std::find_if(str.begin(), str.end(), + [](const char c) { return c == 'e' || c == 'E'; }); + if(decpoint != str.end() && exponent != str.end()) + { + assert(decpoint < exponent); + } + + if(fmt.fmt == floating_format::scientific) + { + // total width + fmt.prec = static_cast(std::distance(str.begin(), exponent)); + if(has_sign) + { + fmt.prec -= 1; + } + if(decpoint != str.end()) + { + fmt.prec -= 1; + } + } + else if(fmt.fmt == floating_format::hex) + { + fmt.prec = std::numeric_limits::max_digits10; + } + else + { + // width after decimal point + fmt.prec = static_cast(std::distance(std::next(decpoint), exponent)); + } + + auto src = source_location(region(loc)); + const auto res = TC::parse_float(str, src, is_hex); + if(res.is_ok()) + { + val = res.as_ok(); + } + else + { + return err(res.as_err()); + } + } + + // ---------------------------------------------------------------------- + // parse suffix (extension) + + if(spec.ext_num_suffix && loc.current() == '_') + { + const auto sfx_reg = syntax::num_suffix(spec).scan(loc); + if( ! sfx_reg.is_ok()) + { + auto src = source_location(region(loc)); + loc = first; + return err(make_error_info("toml::parse_floating: " + "invalid suffix: should be `_ non-digit-graph (graph | _graph)`", + std::move(src), "here")); + } + auto sfx = sfx_reg.as_string(); + assert( ! sfx.empty() && sfx.front() == '_'); + sfx.erase(sfx.begin()); // remove the first `_` + + fmt.suffix = sfx; + } + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +/* ============================================================================ + * ___ _ _ _ + * | \ __ _| |_ ___| |_(_)_ __ ___ + * | |) / _` | _/ -_) _| | ' \/ -_) + * |___/\__,_|\__\___|\__|_|_|_|_\___| + */ + +// all the offset_datetime, local_datetime, local_date parses date part. +template +result, error_info> +parse_local_date_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + local_date_format_info fmt; + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::local_date(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_local_date: " + "invalid date: date must be like: 1234-05-06, yyyy-mm-dd.", + syntax::local_date(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + + // 0123456789 + // yyyy-mm-dd + const auto year_r = from_string(str.substr(0, 4)); + const auto month_r = from_string(str.substr(5, 2)); + const auto day_r = from_string(str.substr(8, 2)); + + if(year_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read year `" + str.substr(0, 4) + "`", + std::move(src), "here")); + } + if(month_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read month `" + str.substr(5, 2) + "`", + std::move(src), "here")); + } + if(day_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: " + "failed to read day `" + str.substr(8, 2) + "`", + std::move(src), "here")); + } + + const auto year = year_r.unwrap(); + const auto month = month_r.unwrap(); + const auto day = day_r.unwrap(); + + { + // We briefly check whether the input date is valid or not. + // Actually, because of the historical reasons, there are several + // edge cases, such as 1582/10/5-1582/10/14 (only in several countries). + // But here, we do not care about it. + // It makes the code complicated and there is only low probability + // that such a specific date is needed in practice. If someone need to + // validate date accurately, that means that the one need a specialized + // library for their purpose in another layer. + + const bool is_leap = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); + const auto max_day = [month, is_leap]() { + if(month == 2) + { + return is_leap ? 29 : 28; + } + if(month == 4 || month == 6 || month == 9 || month == 11) + { + return 30; + } + return 31; + }(); + + if((month < 1 || 12 < month) || (day < 1 || max_day < day)) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_date: invalid date.", + std::move(src), "month must be 01-12, day must be any of " + "01-28,29,30,31 depending on the month/year.")); + } + } + + return ok(std::make_tuple( + local_date(year, static_cast(month - 1), day), + std::move(fmt), std::move(reg) + )); +} + +template +result, error_info> +parse_local_date(location& loc, const context& ctx) +{ + auto val_fmt_reg = parse_local_date_only(loc, ctx); + if(val_fmt_reg.is_err()) + { + return err(val_fmt_reg.unwrap_err()); + } + + auto val = std::move(std::get<0>(val_fmt_reg.unwrap())); + auto fmt = std::move(std::get<1>(val_fmt_reg.unwrap())); + auto reg = std::move(std::get<2>(val_fmt_reg.unwrap())); + + return ok(basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); +} + +// all the offset_datetime, local_datetime, local_time parses date part. +template +result, error_info> +parse_local_time_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + local_time_format_info fmt; + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::local_time(spec).scan(loc); + if( ! reg.is_ok()) + { + if(spec.v1_1_0_make_seconds_optional) + { + return err(make_syntax_error("toml::parse_local_time: " + "invalid time: time must be HH:MM(:SS.sss) (seconds are optional)", + syntax::local_time(spec), loc)); + } + else + { + return err(make_syntax_error("toml::parse_local_time: " + "invalid time: time must be HH:MM:SS(.sss) (subseconds are optional)", + syntax::local_time(spec), loc)); + } + } + + // ---------------------------------------------------------------------- + // it matches. gen value + const auto str = reg.as_string(); + + // at least we have HH:MM. + // 01234 + // HH:MM + const auto hour_r = from_string(str.substr(0, 2)); + const auto minute_r = from_string(str.substr(3, 2)); + + if(hour_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read hour `" + str.substr(0, 2) + "`", + std::move(src), "here")); + } + if(minute_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read minute `" + str.substr(3, 2) + "`", + std::move(src), "here")); + } + + const auto hour = hour_r.unwrap(); + const auto minute = minute_r.unwrap(); + + if((hour < 0 || 24 <= hour) || (minute < 0 || 60 <= minute)) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: invalid time.", + std::move(src), "hour must be 00-23, minute must be 00-59.")); + } + + // ----------------------------------------------------------------------- + // we have hour and minute. + // Since toml v1.1.0, second and subsecond part becomes optional. + // Check the version and return if second does not exist. + + if(str.size() == 5 && spec.v1_1_0_make_seconds_optional) + { + fmt.has_seconds = false; + fmt.subsecond_precision = 0; + return ok(std::make_tuple(local_time(hour, minute, 0), std::move(fmt), std::move(reg))); + } + assert(str.at(5) == ':'); + + // we have at least `:SS` part. `.subseconds` are optional. + + // 0 1 + // 012345678901234 + // HH:MM:SS.subsec + const auto sec_r = from_string(str.substr(6, 2)); + if(sec_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read second `" + str.substr(6, 2) + "`", + std::move(src), "here")); + } + const auto sec = sec_r.unwrap(); + + if(sec < 0 || 60 < sec) // :60 is allowed + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: invalid time.", + std::move(src), "second must be 00-60.")); + } + + if(str.size() == 8) + { + fmt.has_seconds = true; + fmt.subsecond_precision = 0; + return ok(std::make_tuple(local_time(hour, minute, sec), std::move(fmt), std::move(reg))); + } + + assert(str.at(8) == '.'); + + auto secfrac = str.substr(9, str.size() - 9); + + fmt.has_seconds = true; + fmt.subsecond_precision = secfrac.size(); + + while(secfrac.size() < 9) + { + secfrac += '0'; + } + assert(9 <= secfrac.size()); + const auto ms_r = from_string(secfrac.substr(0, 3)); + const auto us_r = from_string(secfrac.substr(3, 3)); + const auto ns_r = from_string(secfrac.substr(6, 3)); + + if(ms_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read milliseconds `" + secfrac.substr(0, 3) + "`", + std::move(src), "here")); + } + if(us_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read microseconds`" + str.substr(3, 3) + "`", + std::move(src), "here")); + } + if(ns_r.is_err()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_local_time: " + "failed to read nanoseconds`" + str.substr(6, 3) + "`", + std::move(src), "here")); + } + const auto ms = ms_r.unwrap(); + const auto us = us_r.unwrap(); + const auto ns = ns_r.unwrap(); + + return ok(std::make_tuple(local_time(hour, minute, sec, ms, us, ns), std::move(fmt), std::move(reg))); +} + +template +result, error_info> +parse_local_time(location& loc, const context& ctx) +{ + const auto first = loc; + + auto val_fmt_reg = parse_local_time_only(loc, ctx); + if(val_fmt_reg.is_err()) + { + return err(val_fmt_reg.unwrap_err()); + } + + auto val = std::move(std::get<0>(val_fmt_reg.unwrap())); + auto fmt = std::move(std::get<1>(val_fmt_reg.unwrap())); + auto reg = std::move(std::get<2>(val_fmt_reg.unwrap())); + + return ok(basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_local_datetime(location& loc, const context& ctx) +{ + using char_type = location::char_type; + + const auto first = loc; + + local_datetime_format_info fmt; + + // ---------------------------------------------------------------------- + + auto date_fmt_reg = parse_local_date_only(loc, ctx); + if(date_fmt_reg.is_err()) + { + return err(date_fmt_reg.unwrap_err()); + } + + if(loc.current() == char_type('T')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::upper_T; + } + else if(loc.current() == char_type('t')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::lower_t; + } + else if(loc.current() == char_type(' ')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::space; + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_local_datetime: " + "expect date-time delimiter `T`, `t` or ` `(space).", + std::move(src), "here")); + } + + auto time_fmt_reg = parse_local_time_only(loc, ctx); + if(time_fmt_reg.is_err()) + { + return err(time_fmt_reg.unwrap_err()); + } + + fmt.has_seconds = std::get<1>(time_fmt_reg.unwrap()).has_seconds; + fmt.subsecond_precision = std::get<1>(time_fmt_reg.unwrap()).subsecond_precision; + + // ---------------------------------------------------------------------- + + region reg(first, loc); + local_datetime val(std::get<0>(date_fmt_reg.unwrap()), + std::get<0>(time_fmt_reg.unwrap())); + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_offset_datetime(location& loc, const context& ctx) +{ + using char_type = location::char_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + offset_datetime_format_info fmt; + + // ---------------------------------------------------------------------- + // date part + + auto date_fmt_reg = parse_local_date_only(loc, ctx); + if(date_fmt_reg.is_err()) + { + return err(date_fmt_reg.unwrap_err()); + } + + // ---------------------------------------------------------------------- + // delimiter + + if(loc.current() == char_type('T')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::upper_T; + } + else if(loc.current() == char_type('t')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::lower_t; + } + else if(loc.current() == char_type(' ')) + { + loc.advance(); + fmt.delimiter = datetime_delimiter_kind::space; + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "expect date-time delimiter `T` or ` `(space).", std::move(src), "here" + )); + } + + // ---------------------------------------------------------------------- + // time part + + auto time_fmt_reg = parse_local_time_only(loc, ctx); + if(time_fmt_reg.is_err()) + { + return err(time_fmt_reg.unwrap_err()); + } + + fmt.has_seconds = std::get<1>(time_fmt_reg.unwrap()).has_seconds; + fmt.subsecond_precision = std::get<1>(time_fmt_reg.unwrap()).subsecond_precision; + + // ---------------------------------------------------------------------- + // offset part + + const auto ofs_reg = syntax::time_offset(spec).scan(loc); + if( ! ofs_reg.is_ok()) + { + return err(make_syntax_error("toml::parse_offset_datetime: " + "invalid offset: offset must be like: Z, +01:00, or -10:00.", + syntax::time_offset(spec), loc)); + } + + const auto ofs_str = ofs_reg.as_string(); + + time_offset offset(0, 0); + + assert(ofs_str.size() != 0); + + if(ofs_str.at(0) == char_type('+') || ofs_str.at(0) == char_type('-')) + { + const auto hour_r = from_string(ofs_str.substr(1, 2)); + const auto minute_r = from_string(ofs_str.substr(4, 2)); + if(hour_r.is_err()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "Failed to read offset hour part", std::move(src), "here")); + } + if(minute_r.is_err()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_offset_datetime: " + "Failed to read offset minute part", std::move(src), "here")); + } + const auto hour = hour_r.unwrap(); + const auto minute = minute_r.unwrap(); + + if(ofs_str.at(0) == '+') + { + offset = time_offset(hour, minute); + } + else + { + offset = time_offset(-hour, -minute); + } + } + else + { + assert(ofs_str.at(0) == char_type('Z') || ofs_str.at(0) == char_type('z')); + } + + if (offset.hour < -24 || 24 < offset.hour || + offset.minute < -60 || 60 < offset.minute) + { + return err(make_error_info("toml::parse_offset_datetime: " + "too large offset: |hour| <= 24, |minute| <= 60", + source_location(region(first, loc)), "here")); + } + + + // ---------------------------------------------------------------------- + + region reg(first, loc); + offset_datetime val(local_datetime(std::get<0>(date_fmt_reg.unwrap()), + std::get<0>(time_fmt_reg.unwrap())), + offset); + + return ok(basic_value(val, std::move(fmt), {}, std::move(reg))); +} + +/* ============================================================================ + * ___ _ _ + * / __| |_ _ _(_)_ _ __ _ + * \__ \ _| '_| | ' \/ _` | + * |___/\__|_| |_|_||_\__, | + * |___/ + */ + +template +result::string_type, error_info> +parse_utf8_codepoint(const region& reg) +{ + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + + // assert(reg.as_lines().size() == 1); // XXX heavy check + + const auto str = reg.as_string(); + assert( ! str.empty()); + assert(str.front() == 'u' || str.front() == 'U' || str.front() == 'x'); + + std::uint_least32_t codepoint; + std::istringstream iss(str.substr(1)); + iss >> std::hex >> codepoint; + + const auto to_char = [](const std::uint_least32_t i) noexcept -> char_type { + const auto uc = static_cast(i & 0xFF); + return cxx::bit_cast(uc); + }; + + string_type character; + if(codepoint < 0x80) // U+0000 ... U+0079 ; just an ASCII. + { + character += static_cast(codepoint); + } + else if(codepoint < 0x800) //U+0080 ... U+07FF + { + // 110yyyyx 10xxxxxx; 0x3f == 0b0011'1111 + character += to_char(0xC0|(codepoint >> 6 )); + character += to_char(0x80|(codepoint & 0x3F)); + } + else if(codepoint < 0x10000) // U+0800...U+FFFF + { + if(0xD800 <= codepoint && codepoint <= 0xDFFF) + { + auto src = source_location(reg); + return err(make_error_info("toml::parse_utf8_codepoint: " + "[0xD800, 0xDFFF] is not a valid UTF-8", + std::move(src), "here")); + } + assert(codepoint < 0xD800 || 0xDFFF < codepoint); + // 1110yyyy 10yxxxxx 10xxxxxx + character += to_char(0xE0| (codepoint >> 12)); + character += to_char(0x80|((codepoint >> 6 ) & 0x3F)); + character += to_char(0x80|((codepoint ) & 0x3F)); + } + else if(codepoint < 0x110000) // U+010000 ... U+10FFFF + { + // 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx + character += to_char(0xF0| (codepoint >> 18)); + character += to_char(0x80|((codepoint >> 12) & 0x3F)); + character += to_char(0x80|((codepoint >> 6 ) & 0x3F)); + character += to_char(0x80|((codepoint ) & 0x3F)); + } + else // out of UTF-8 region + { + auto src = source_location(reg); + return err(make_error_info("toml::parse_utf8_codepoint: " + "input codepoint is too large.", + std::move(src), "must be in range [0x00, 0x10FFFF]")); + } + return ok(character); +} + +template +result::string_type, error_info> +parse_escape_sequence(location& loc, const context& ctx) +{ + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + + const auto& spec = ctx.toml_spec(); + + assert( ! loc.eof()); + assert(loc.current() == '\\'); + loc.advance(); // consume the first backslash + + string_type retval; + + if (loc.current() == '\\') { retval += char_type('\\'); loc.advance(); } + else if(loc.current() == '"') { retval += char_type('\"'); loc.advance(); } + else if(loc.current() == 'b') { retval += char_type('\b'); loc.advance(); } + else if(loc.current() == 'f') { retval += char_type('\f'); loc.advance(); } + else if(loc.current() == 'n') { retval += char_type('\n'); loc.advance(); } + else if(loc.current() == 'r') { retval += char_type('\r'); loc.advance(); } + else if(loc.current() == 't') { retval += char_type('\t'); loc.advance(); } + else if(spec.v1_1_0_add_escape_sequence_e && loc.current() == 'e') + { + retval += char_type('\x1b'); + loc.advance(); + } + else if(spec.v1_1_0_add_escape_sequence_x && loc.current() == 'x') + { + auto scanner = sequence(character('x'), repeat_exact(2, syntax::hexdig(spec))); + const auto reg = scanner.scan(loc); + if( ! reg.is_ok()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\xhh", + std::move(src), "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if(utf8.is_err()) + { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } + else if(loc.current() == 'u') + { + auto scanner = sequence(character('u'), repeat_exact(4, syntax::hexdig(spec))); + const auto reg = scanner.scan(loc); + if( ! reg.is_ok()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\uhhhh", + std::move(src), "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if(utf8.is_err()) + { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } + else if(loc.current() == 'U') + { + auto scanner = sequence(character('U'), repeat_exact(8, syntax::hexdig(spec))); + const auto reg = scanner.scan(loc); + if( ! reg.is_ok()) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_escape_sequence: " + "invalid token found in UTF-8 codepoint \\Uhhhhhhhh", + std::move(src), "here")); + } + const auto utf8 = parse_utf8_codepoint(reg); + if(utf8.is_err()) + { + return err(utf8.as_err()); + } + retval += utf8.unwrap(); + } + else + { + auto src = source_location(region(loc)); + std::string escape_seqs = "allowed escape seqs: \\\\, \\\", \\b, \\f, \\n, \\r, \\t"; + if(spec.v1_1_0_add_escape_sequence_e) + { + escape_seqs += ", \\e"; + } + if(spec.v1_1_0_add_escape_sequence_x) + { + escape_seqs += ", \\xhh"; + } + escape_seqs += ", \\uhhhh, or \\Uhhhhhhhh"; + + return err(make_error_info("toml::parse_escape_sequence: " + "unknown escape sequence.", std::move(src), escape_seqs)); + } + return ok(retval); +} + +template +result, error_info> +parse_ml_basic_string(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + string_format_info fmt; + fmt.fmt = string_format::multiline_basic; + + auto reg = syntax::ml_basic_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_ml_basic_string: " + "invalid string format", + syntax::ml_basic_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + // we already checked that it starts with """ and ends with """. + assert(str.substr(0, 3) == "\"\"\""); + str.erase(0, 3); + + assert(str.size() >= 3); + assert(str.substr(str.size()-3, 3) == "\"\"\""); + str.erase(str.size()-3, 3); + + // the first newline just after """ is trimmed + if(str.size() >= 1 && str.at(0) == '\n') + { + str.erase(0, 1); + fmt.start_with_newline = true; + } + else if(str.size() >= 2 && str.at(0) == '\r' && str.at(1) == '\n') + { + str.erase(0, 2); + fmt.start_with_newline = true; + } + + using string_type = typename basic_value::string_type; + string_type val; + { + auto iter = str.cbegin(); + while(iter != str.cend()) + { + if(*iter == '\\') // remove whitespaces around escaped-newline + { + // we assume that the string is not too long to copy + auto loc2 = make_temporary_location(make_string(iter, str.cend())); + if(syntax::escaped_newline(spec).scan(loc2).is_ok()) + { + std::advance(iter, loc2.get_location()); // skip escaped newline and indent + // now iter points non-WS char + assert(iter == str.end() || (*iter != ' ' && *iter != '\t')); + } + else // normal escape seq. + { + auto esc = parse_escape_sequence(loc2, ctx); + + // syntax does not check its value. the unicode codepoint may be + // invalid, e.g. out-of-bound, [0xD800, 0xDFFF] + if(esc.is_err()) + { + return err(esc.unwrap_err()); + } + + val += esc.unwrap(); + std::advance(iter, loc2.get_location()); + } + } + else // we already checked the syntax. we don't need to check it again. + { + val += static_cast(*iter); + ++iter; + } + } + } + + return ok(basic_value( + std::move(val), std::move(fmt), {}, std::move(reg) + )); +} + +template +result::string_type, region>, error_info> +parse_basic_string_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::basic_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_basic_string: " + "invalid string format", + syntax::basic_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.back() == '\"'); + str.pop_back(); + assert(str.at(0) == '\"'); + str.erase(0, 1); + + using string_type = typename basic_value::string_type; + using char_type = typename string_type::value_type; + string_type val; + + { + auto iter = str.begin(); + while(iter != str.end()) + { + if(*iter == '\\') + { + auto loc2 = make_temporary_location(make_string(iter, str.end())); + + auto esc = parse_escape_sequence(loc2, ctx); + + // syntax does not check its value. the unicode codepoint may be + // invalid, e.g. out-of-bound, [0xD800, 0xDFFF] + if(esc.is_err()) + { + return err(esc.unwrap_err()); + } + + val += esc.unwrap(); + std::advance(iter, loc2.get_location()); + } + else + { + val += char_type(*iter); // we already checked the syntax. + ++iter; + } + } + } + return ok(std::make_pair(val, reg)); +} + +template +result, error_info> +parse_basic_string(location& loc, const context& ctx) +{ + const auto first = loc; + + string_format_info fmt; + fmt.fmt = string_format::basic; + + auto val_res = parse_basic_string_only(loc, ctx); + if(val_res.is_err()) + { + return err(std::move(val_res.unwrap_err())); + } + auto val = std::move(val_res.unwrap().first ); + auto reg = std::move(val_res.unwrap().second); + + return ok(basic_value(std::move(val), std::move(fmt), {}, std::move(reg))); +} + +template +result, error_info> +parse_ml_literal_string(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + string_format_info fmt; + fmt.fmt = string_format::multiline_literal; + + auto reg = syntax::ml_literal_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_ml_literal_string: " + "invalid string format", + syntax::ml_literal_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.substr(0, 3) == "'''"); + assert(str.substr(str.size()-3, 3) == "'''"); + str.erase(0, 3); + str.erase(str.size()-3, 3); + + // the first newline just after """ is trimmed + if(str.size() >= 1 && str.at(0) == '\n') + { + str.erase(0, 1); + fmt.start_with_newline = true; + } + else if(str.size() >= 2 && str.at(0) == '\r' && str.at(1) == '\n') + { + str.erase(0, 2); + fmt.start_with_newline = true; + } + + using string_type = typename basic_value::string_type; + string_type val(str.begin(), str.end()); + + return ok(basic_value( + std::move(val), std::move(fmt), {}, std::move(reg) + )); +} + +template +result::string_type, region>, error_info> +parse_literal_string_only(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::literal_string(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_literal_string: " + "invalid string format", + syntax::literal_string(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + auto str = reg.as_string(); + + assert(str.back() == '\''); + str.pop_back(); + assert(str.at(0) == '\''); + str.erase(0, 1); + + using string_type = typename basic_value::string_type; + string_type val(str.begin(), str.end()); + + return ok(std::make_pair(std::move(val), std::move(reg))); +} + +template +result, error_info> +parse_literal_string(location& loc, const context& ctx) +{ + const auto first = loc; + + string_format_info fmt; + fmt.fmt = string_format::literal; + + auto val_res = parse_literal_string_only(loc, ctx); + if(val_res.is_err()) + { + return err(std::move(val_res.unwrap_err())); + } + auto val = std::move(val_res.unwrap().first ); + auto reg = std::move(val_res.unwrap().second); + + return ok(basic_value( + std::move(val), std::move(fmt), {}, std::move(reg) + )); +} + +template +result, error_info> +parse_string(location& loc, const context& ctx) +{ + const auto first = loc; + + if( ! loc.eof() && loc.current() == '"') + { + if(literal("\"\"\"").scan(loc).is_ok()) + { + loc = first; + return parse_ml_basic_string(loc, ctx); + } + else + { + loc = first; + return parse_basic_string(loc, ctx); + } + } + else if( ! loc.eof() && loc.current() == '\'') + { + if(literal("'''").scan(loc).is_ok()) + { + loc = first; + return parse_ml_literal_string(loc, ctx); + } + else + { + loc = first; + return parse_literal_string(loc, ctx); + } + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_string: " + "not a string", std::move(src), "here")); + } +} + +template +result, error_info> +parse_null(location& loc, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + if( ! spec.ext_null_value) + { + return err(make_error_info("toml::parse_null: " + "invalid spec: spec.ext_null_value must be true.", + source_location(region(loc)), "here")); + } + + // ---------------------------------------------------------------------- + // check syntax + auto reg = syntax::null_value(spec).scan(loc); + if( ! reg.is_ok()) + { + return err(make_syntax_error("toml::parse_null: " + "invalid null: null must be lowercase. ", + syntax::null_value(spec), loc)); + } + + // ---------------------------------------------------------------------- + // it matches. gen value + + // ---------------------------------------------------------------------- + // no format info for boolean + + return ok(basic_value(detail::none_t{}, std::move(reg))); +} + +/* ============================================================================ + * _ __ + * | |/ /___ _ _ + * | ' +result::key_type, error_info> +parse_simple_key(location& loc, const context& ctx) +{ + using key_type = typename basic_value::key_type; + const auto& spec = ctx.toml_spec(); + + if(loc.current() == '\"') + { + auto str_res = parse_basic_string_only(loc, ctx); + if(str_res.is_ok()) + { + return ok(std::move(str_res.unwrap().first)); + } + else + { + return err(std::move(str_res.unwrap_err())); + } + } + else if(loc.current() == '\'') + { + auto str_res = parse_literal_string_only(loc, ctx); + if(str_res.is_ok()) + { + return ok(std::move(str_res.unwrap().first)); + } + else + { + return err(std::move(str_res.unwrap_err())); + } + } + + // bare key. + + if(const auto bare = syntax::unquoted_key(spec).scan(loc)) + { + return ok(string_conv(bare.as_string())); + } + else + { + std::string postfix; + if(spec.v1_1_0_allow_non_english_in_bare_keys) + { + postfix = "Hint: Not all Unicode characters are allowed as bare key.\n"; + } + else + { + postfix = "Hint: non-ASCII scripts are allowed in toml v1.1.0, but not in v1.0.0.\n"; + } + return err(make_syntax_error("toml::parse_simple_key: " + "invalid key: key must be \"quoted\", 'quoted-literal', or bare key.", + syntax::unquoted_key(spec), loc, postfix)); + } +} + +// dotted key become vector of keys +template +result::key_type>, region>, error_info> +parse_key(location& loc, const context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + using key_type = typename basic_value::key_type; + std::vector keys; + while( ! loc.eof()) + { + auto key = parse_simple_key(loc, ctx); + if( ! key.is_ok()) + { + return err(key.unwrap_err()); + } + keys.push_back(std::move(key.unwrap())); + + auto reg = syntax::dot_sep(spec).scan(loc); + if( ! reg.is_ok()) + { + break; + } + } + if(keys.empty()) + { + auto src = source_location(region(first)); + return err(make_error_info("toml::parse_key: expected a new key, " + "but got nothing", std::move(src), "reached EOF")); + } + + return ok(std::make_pair(std::move(keys), region(first, loc))); +} + +// ============================================================================ + +// forward-decl to implement parse_array and parse_table +template +result, error_info> +parse_value(location&, context& ctx); + +template +result::key_type>, region>, + basic_value + >, error_info> +parse_key_value_pair(location& loc, context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto key_res = parse_key(loc, ctx); + if(key_res.is_err()) + { + loc = first; + return err(key_res.unwrap_err()); + } + + if( ! syntax::keyval_sep(spec).scan(loc).is_ok()) + { + auto e = make_syntax_error("toml::parse_key_value_pair: " + "invalid key value separator `=`", syntax::keyval_sep(spec), loc); + loc = first; + return err(std::move(e)); + } + + auto v_res = parse_value(loc, ctx); + if(v_res.is_err()) + { + // loc = first; + return err(v_res.unwrap_err()); + } + return ok(std::make_pair(std::move(key_res.unwrap()), std::move(v_res.unwrap()))); +} + +/* ============================================================================ + * __ _ _ _ _ _ __ _ _ _ + * / _` | '_| '_/ _` | || | + * \__,_|_| |_| \__,_|\_, | + * |__/ + */ + +// array(and multiline inline table with `{` and `}`) has the following format. +// `[` +// (ws|newline|comment-line)? (value) (ws|newline|comment-line)? `,` +// (ws|newline|comment-line)? (value) (ws|newline|comment-line)? `,` +// ... +// (ws|newline|comment-line)? (value) (ws|newline|comment-line)? (`,`)? +// (ws|newline|comment-line)? `]` +// it skips (ws|newline|comment-line) and returns the token. +template +struct multiline_spacer +{ + using comment_type = typename TC::comment_type; + bool newline_found; + indent_char indent_type; + std::int32_t indent; + comment_type comments; +}; +template +std::ostream& operator<<(std::ostream& os, const multiline_spacer& sp) +{ + os << "{newline=" << sp.newline_found << ", "; + os << "indent_type=" << sp.indent_type << ", "; + os << "indent=" << sp.indent << ", "; + os << "comments=" << sp.comments.size() << "}"; + return os; +} + +template +cxx::optional> +skip_multiline_spacer(location& loc, context& ctx, const bool newline_found = false) +{ + const auto& spec = ctx.toml_spec(); + + multiline_spacer spacer; + spacer.newline_found = newline_found; + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer.comments.clear(); + + bool spacer_found = false; + while( ! loc.eof()) + { + if(auto comm = sequence(syntax::comment(spec), syntax::newline(spec)).scan(loc)) + { + spacer.newline_found = true; + auto comment = comm.as_string(); + if( ! comment.empty() && comment.back() == '\n') + { + comment.pop_back(); + if (!comment.empty() && comment.back() == '\r') + { + comment.pop_back(); + } + } + + spacer.comments.push_back(std::move(comment)); + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer_found = true; + } + else if(auto nl = syntax::newline(spec).scan(loc)) + { + spacer.newline_found = true; + spacer.comments.clear(); + spacer.indent_type = indent_char::none; + spacer.indent = 0; + spacer_found = true; + } + else if(auto sp = repeat_at_least(1, character(cxx::bit_cast(' '))).scan(loc)) + { + spacer.indent_type = indent_char::space; + spacer.indent = static_cast(sp.length()); + spacer_found = true; + } + else if(auto tabs = repeat_at_least(1, character(cxx::bit_cast('\t'))).scan(loc)) + { + spacer.indent_type = indent_char::tab; + spacer.indent = static_cast(tabs.length()); + spacer_found = true; + } + else + { + break; // done + } + } + if( ! spacer_found) + { + return cxx::make_nullopt(); + } + return spacer; +} + +// not an [[array.of.tables]]. It parses ["this", "type"] +template +result, error_info> +parse_array(location& loc, context& ctx) +{ + const auto num_errors = ctx.errors().size(); + + const auto first = loc; + + if(loc.eof() || loc.current() != '[') + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_array: " + "The next token is not an array", std::move(src), "here")); + } + loc.advance(); + + typename basic_value::array_type val; + + array_format_info fmt; + fmt.fmt = array_format::oneline; + fmt.indent_type = indent_char::none; + + auto spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value() && spacer.value().newline_found) + { + fmt.fmt = array_format::multiline; + } + + bool comma_found = true; + while( ! loc.eof()) + { + if(loc.current() == location::char_type(']')) + { + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.closing_indent = spacer.value().indent; + } + break; + } + + if( ! comma_found) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_array: " + "expected value-separator `,` or closing `]`", + std::move(src), "here")); + } + + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + + if(auto elem_res = parse_value(loc, ctx)) + { + auto elem = std::move(elem_res.unwrap()); + + if(spacer.has_value()) // copy previous comments to value + { + elem.comments() = std::move(spacer.value().comments); + } + + // parse spaces between a value and a comma + // array = [ + // 42 , # the answer + // ^^^^ + // 3.14 # pi + // , 2.71 ^^^^ + // ^^ + spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value()) + { + for(std::size_t i=0; i( + std::move(val), std::move(fmt), {}, region(first, loc) + )); +} + +/* ============================================================================ + * _ _ _ _ _ _ + * (_)_ _ | (_)_ _ ___ | |_ __ _| |__| |___ + * | | ' \| | | ' \/ -_) | _/ _` | '_ \ / -_) + * |_|_||_|_|_|_||_\___| \__\__,_|_.__/_\___| + */ + +// ---------------------------------------------------------------------------- +// insert_value is the most complicated part of the toml spec. +// +// To parse a toml file correctly, we sometimes need to check an exising value +// is appendable or not. +// +// For example while parsing an inline array of tables, +// +// ```toml +// aot = [ +// {a = "foo"}, +// {a = "bar", b = "baz"}, +// ] +// ``` +// +// this `aot` is appendable until parser reaches to `]`. After that, it becomes +// non-appendable. +// +// On the other hand, a normal array of tables, such as +// +// ```toml +// [[aot]] +// a = "foo" +// +// [[aot]] +// a = "bar" +// b = "baz" +// ``` +// This `[[aot]]` is appendable until the parser reaches to the EOF. +// +// +// It becomes a bit more difficult in case of dotted keys. +// In TOML, it is allowed to append a key-value pair to a table that is +// *implicitly* defined by a subtable definitino. +// +// ```toml +// [x.y.z] +// w = 123 +// +// [x] +// a = "foo" # OK. x is defined implicitly by `[x.y.z]`. +// ``` +// +// But if the table is defined by a dotted keys, it is not appendable. +// +// ```toml +// [x] +// y.z.w = 123 +// +// [x.y] +// # ERROR. x.y is already defined by a dotted table in the previous table. +// ``` +// +// Also, reopening a table using dotted keys is invalid. +// +// ```toml +// [x.y.z] +// w = 123 +// +// [x] +// y.z.v = 42 # ERROR. [x.y.z] is already defined. +// ``` +// +// +// ```toml +// [a] +// b.c = "foo" +// b.d = "bar" +// ``` +// +// +// ```toml +// a.b = "foo" +// [a] +// c = "bar" # ERROR +// ``` +// +// In summary, +// - a table must be defined only once. +// - assignment to an exising table is possible only when: +// - defining a subtable [x.y] to an existing table [x]. +// - defining supertable [x] explicitly after [x.y]. +// - adding dotted keys in the same table. + +enum class inserting_value_kind : std::uint8_t +{ + std_table, // insert [standard.table] + array_table, // insert [[array.of.tables]] + dotted_keys // insert a.b.c = "this" +}; + +template +result*, error_info> +insert_value(const inserting_value_kind kind, + typename basic_value::table_type* current_table_ptr, + const std::vector::key_type>& keys, region key_reg, + basic_value val) +{ + using value_type = basic_value; + using array_type = typename basic_value::array_type; + using table_type = typename basic_value::table_type; + + auto key_loc = source_location(key_reg); + + assert( ! keys.empty()); + + // dotted key can insert to dotted key tables defined at the same level. + // dotted key can NOT reopen a table even if it is implcitly-defined one. + // + // [x.y.z] # define x and x.y implicitly. + // a = 42 + // + // [x] # reopening implcitly defined table + // r.s.t = 3.14 # VALID r and r.s are new tables. + // r.s.u = 2.71 # VALID r and r.s are dotted-key tables. valid. + // + // y.z.b = "foo" # INVALID x.y.z are multiline table, not a dotted key. + // y.c = "bar" # INVALID x.y is implicit multiline table, not a dotted key. + + // a table cannot reopen dotted-key tables. + // + // [t1] + // t2.t3.v = 0 + // [t1.t2] # INVALID t1.t2 is defined as a dotted-key table. + + for(std::size_t i=0; i{}, key_reg)); + + assert(current_table.at(key).is_table()); + current_table_ptr = std::addressof(current_table.at(key).as_table()); + } + else if (found->second.is_table()) + { + const auto fmt = found->second.as_table_fmt().fmt; + if(fmt == table_format::oneline || fmt == table_format::multiline_oneline) + { + // foo = {bar = "baz"} or foo = { \n bar = "baz" \n } + return err(make_error_info("toml::insert_value: " + "failed to insert a value: inline table is immutable", + key_loc, "inserting this", + found->second.location(), "to this table")); + } + // dotted key cannot reopen a table. + if(kind ==inserting_value_kind::dotted_keys && fmt != table_format::dotted) + { + return err(make_error_info("toml::insert_value: " + "reopening a table using dotted keys", + key_loc, "dotted key cannot reopen a table", + found->second.location(), "this table is already closed")); + } + assert(found->second.is_table()); + current_table_ptr = std::addressof(found->second.as_table()); + } + else if(found->second.is_array_of_tables()) + { + // aot = [{this = "type", of = "aot"}] # cannot be reopened + if(found->second.as_array_fmt().fmt != array_format::array_of_tables) + { + return err(make_error_info("toml::insert_value:" + "inline array of tables are immutable", + key_loc, "inserting this", + found->second.location(), "inline array of tables")); + } + // appending to [[aot]] + + if(kind == inserting_value_kind::dotted_keys) + { + // [[array.of.tables]] + // [array.of] # reopening supertable is okay + // tables.x = "foo" # appending `x` to the first table + return err(make_error_info("toml::insert_value:" + "dotted key cannot reopen an array-of-tables", + key_loc, "inserting this", + found->second.location(), "to this array-of-tables.")); + } + + // insert_value_by_dotkeys::std_table + // [[array.of.tables]] + // [array.of.tables.subtable] # appending to the last aot + // + // insert_value_by_dotkeys::array_table + // [[array.of.tables]] + // [[array.of.tables.subtable]] # appending to the last aot + auto& current_array_table = found->second.as_array().back(); + + assert(current_array_table.is_table()); + current_table_ptr = std::addressof(current_array_table.as_table()); + } + else + { + return err(make_error_info("toml::insert_value: " + "failed to insert a value, value already exists", + key_loc, "while inserting this", + found->second.location(), "non-table value already exists")); + } + } + else // this is the last key. insert a new value. + { + switch(kind) + { + case inserting_value_kind::dotted_keys: + { + if(current_table.find(key) != current_table.end()) + { + return err(make_error_info("toml::insert_value: " + "failed to insert a value, value already exists", + key_loc, "inserting this", + current_table.at(key).location(), "but value already exists")); + } + current_table.emplace(key, std::move(val)); + return ok(std::addressof(current_table.at(key))); + } + case inserting_value_kind::std_table: + { + // defining a new table or reopening supertable + auto found = current_table.find(key); + if(found == current_table.end()) // define a new aot + { + current_table.emplace(key, std::move(val)); + return ok(std::addressof(current_table.at(key))); + } + else // the table is already defined, reopen it + { + // assigning a [std.table]. it must be an implicit table. + auto& target = found->second; + if( ! target.is_table() || // could be an array-of-tables + target.as_table_fmt().fmt != table_format::implicit) + { + return err(make_error_info("toml::insert_value: " + "failed to insert a table, table already defined", + key_loc, "inserting this", + target.location(), "this table is explicitly defined")); + } + + // merge table + for(const auto& kv : val.as_table()) + { + if(target.contains(kv.first)) + { + // [x.y.z] + // w = "foo" + // [x] + // y = "bar" + return err(make_error_info("toml::insert_value: " + "failed to insert a table, table keys conflict to each other", + key_loc, "inserting this table", + kv.second.location(), "having this value", + target.at(kv.first).location(), "already defined here")); + } + else + { + target[kv.first] = kv.second; + } + } + // change implicit -> explicit + target.as_table_fmt().fmt = table_format::multiline; + // change definition region + change_region_of_value(target, val); + + return ok(std::addressof(current_table.at(key))); + } + } + case inserting_value_kind::array_table: + { + auto found = current_table.find(key); + if(found == current_table.end()) // define a new aot + { + array_format_info fmt; + fmt.fmt = array_format::array_of_tables; + fmt.indent_type = indent_char::none; + + current_table.emplace(key, value_type( + array_type{ std::move(val) }, std::move(fmt), + std::vector{}, std::move(key_reg) + )); + + assert( ! current_table.at(key).as_array().empty()); + return ok(std::addressof(current_table.at(key).as_array().back())); + } + else // the array is already defined, append to it + { + if( ! found->second.is_array_of_tables()) + { + return err(make_error_info("toml::insert_value: " + "failed to insert an array of tables, value already exists", + key_loc, "while inserting this", + found->second.location(), "non-table value already exists")); + } + if(found->second.as_array_fmt().fmt != array_format::array_of_tables) + { + return err(make_error_info("toml::insert_value: " + "failed to insert a table, inline array of tables is immutable", + key_loc, "while inserting this", + found->second.location(), "this is inline array-of-tables")); + } + found->second.as_array().push_back(std::move(val)); + assert( ! current_table.at(key).as_array().empty()); + return ok(std::addressof(current_table.at(key).as_array().back())); + } + } + default: {assert(false);} + } + } + } + return err(make_error_info("toml::insert_key: no keys found", + std::move(key_loc), "here")); +} + +// ---------------------------------------------------------------------------- + +template +result, error_info> +parse_inline_table(location& loc, context& ctx) +{ + using table_type = typename basic_value::table_type; + + const auto num_errors = ctx.errors().size(); + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + if(loc.eof() || loc.current() != '{') + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "The next token is not an inline table", std::move(src), "here")); + } + loc.advance(); + + table_type table; + table_format_info fmt; + fmt.fmt = table_format::oneline; + fmt.indent_type = indent_char::none; + + cxx::optional> spacer(cxx::make_nullopt()); + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value() && spacer.value().newline_found) + { + fmt.fmt = table_format::multiline_oneline; + } + } + else + { + skip_whitespace(loc, ctx); + } + + bool still_empty = true; + bool comma_found = false; + while( ! loc.eof()) + { + // closing! + if(loc.current() == '}') + { + if(comma_found && ! spec.v1_1_0_allow_trailing_comma_in_inline_tables) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: trailing " + "comma is not allowed in TOML-v1.0.0)", std::move(src), "here")); + } + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.closing_indent = spacer.value().indent; + } + } + break; + } + + // if we already found a value and didn't found `,` nor `}`, error. + if( ! comma_found && ! still_empty) + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "expected value-separator `,` or closing `}`", + std::move(src), "here")); + } + + // parse indent. + if(spacer.has_value() && spacer.value().newline_found && + spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + + still_empty = false; // parsing a value... + if(auto kv_res = parse_key_value_pair(loc, ctx)) + { + auto keys = std::move(kv_res.unwrap().first.first); + auto key_reg = std::move(kv_res.unwrap().first.second); + auto val = std::move(kv_res.unwrap().second); + + auto ins_res = insert_value(inserting_value_kind::dotted_keys, + std::addressof(table), keys, std::move(key_reg), std::move(val)); + if(ins_res.is_err()) + { + ctx.report_error(std::move(ins_res.unwrap_err())); + // we need to skip until the next value (or end of the table) + // because we don't have valid kv pair. + while( ! loc.eof()) + { + const auto c = loc.current(); + if(c == ',' || c == '\n' || c == '}') + { + comma_found = (c == ','); + break; + } + loc.advance(); + } + continue; + } + + // if comment line follows immediately(without newline) after `,`, then + // the comment is for the elem. we need to check if comment follows `,`. + // + // (key) = (val) (ws|newline|comment-line)? `,` (ws)? (comment)? + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + if(spacer.has_value()) // copy previous comments to value + { + for(std::size_t i=0; icomments().push_back(spacer.value().comments.at(i)); + } + } + spacer = skip_multiline_spacer(loc, ctx); + if(spacer.has_value()) + { + for(std::size_t i=0; icomments().push_back(spacer.value().comments.at(i)); + } + if(spacer.value().newline_found) + { + fmt.fmt = table_format::multiline_oneline; + if(spacer.value().indent_type != indent_char::none) + { + fmt.indent_type = spacer.value().indent_type; + fmt.body_indent = spacer.value().indent; + } + } + } + } + else + { + skip_whitespace(loc, ctx); + } + + comma_found = character(',').scan(loc).is_ok(); + + if(spec.v1_1_0_allow_newlines_in_inline_tables) + { + auto com_res = parse_comment_line(loc, ctx); + if(com_res.is_err()) + { + ctx.report_error(com_res.unwrap_err()); + } + const bool comment_found = com_res.is_ok() && com_res.unwrap().has_value(); + if(comment_found) + { + fmt.fmt = table_format::multiline_oneline; + ins_res.unwrap()->comments().push_back(com_res.unwrap().value()); + } + if(comma_found) + { + spacer = skip_multiline_spacer(loc, ctx, comment_found); + if(spacer.has_value() && spacer.value().newline_found) + { + fmt.fmt = table_format::multiline_oneline; + } + } + } + else + { + skip_whitespace(loc, ctx); + } + } + else + { + ctx.report_error(std::move(kv_res.unwrap_err())); + while( ! loc.eof()) + { + if(loc.current() == '}') + { + break; + } + if( ! spec.v1_1_0_allow_newlines_in_inline_tables && loc.current() == '\n') + { + break; + } + loc.advance(); + } + break; + } + } + + if(loc.current() != '}') + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_inline_table: " + "missing closing bracket `}`", + std::move(src), "expected `}`, reached line end")); + } + else + { + loc.advance(); // skip } + } + + // any error reported from this function + if(num_errors < ctx.errors().size()) + { + assert(ctx.has_error()); // already reported + return err(ctx.pop_last_error()); + } + + basic_value retval( + std::move(table), std::move(fmt), {}, region(first, loc)); + + return ok(std::move(retval)); +} + +/* ============================================================================ + * _ + * __ ____ _| |_ _ ___ + * \ V / _` | | || / -_) + * \_/\__,_|_|\_,_\___| + */ + +template +result +guess_number_type(const location& first, const context& ctx) +{ + const auto& spec = ctx.toml_spec(); + location loc = first; + + if(syntax::offset_datetime(spec).scan(loc).is_ok()) + { + return ok(value_t::offset_datetime); + } + loc = first; + + if(syntax::local_datetime(spec).scan(loc).is_ok()) + { + const auto curr = loc.current(); + // if offset_datetime contains bad offset, it syntax::offset_datetime + // fails to scan it. + if(curr == '+' || curr == '-') + { + return err(make_syntax_error("bad offset: must be [+-]HH:MM or Z", + syntax::time_offset(spec), loc, std::string( + "Hint: valid : +09:00, -05:30\n" + "Hint: invalid: +9:00, -5:30\n"))); + } + return ok(value_t::local_datetime); + } + loc = first; + + if(syntax::local_date(spec).scan(loc).is_ok()) + { + // bad time may appear after this. + + if( ! loc.eof()) + { + const auto c = loc.current(); + if(c == 'T' || c == 't') + { + loc.advance(); + + return err(make_syntax_error("bad time: must be HH:MM:SS.subsec", + syntax::local_time(spec), loc, std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + if(c == ' ') + { + // A space is allowed as a delimiter between local time. + // But there is a case where bad time follows a space. + // - invalid: 2019-06-16 7:00:00 + // - valid : 2019-06-16 07:00:00 + loc.advance(); + if( ! loc.eof() && ('0' <= loc.current() && loc.current() <= '9')) + { + return err(make_syntax_error("bad time: must be HH:MM:SS.subsec", + syntax::local_time(spec), loc, std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + } + if('0' <= c && c <= '9') + { + return err(make_syntax_error("bad datetime: missing T or space", + character_either{'T', 't', ' '}, loc, std::string( + "Hint: valid : 1979-05-27T07:32:00, 1979-05-27 07:32:00.999999\n" + "Hint: invalid: 1979-05-27T7:32:00, 1979-05-27 17:32\n"))); + } + } + return ok(value_t::local_date); + } + loc = first; + + if(syntax::local_time(spec).scan(loc).is_ok()) + { + return ok(value_t::local_time); + } + loc = first; + + if(syntax::floating(spec).scan(loc).is_ok()) + { + if( ! loc.eof() && loc.current() == '_') + { + if(spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) + { + return ok(value_t::floating); + } + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: `_` must be surrounded by digits", + std::move(src), "invalid underscore", + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n")); + } + return ok(value_t::floating); + } + loc = first; + + if(spec.ext_hex_float) + { + if(syntax::hex_floating(spec).scan(loc).is_ok()) + { + if( ! loc.eof() && loc.current() == '_') + { + if(spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) + { + return ok(value_t::floating); + } + auto src = source_location(region(loc)); + return err(make_error_info( + "bad float: `_` must be surrounded by digits", + std::move(src), "invalid underscore", + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n")); + } + return ok(value_t::floating); + } + loc = first; + } + + if(auto int_reg = syntax::integer(spec).scan(loc)) + { + if( ! loc.eof()) + { + const auto c = loc.current(); + if(c == '_') + { + if(spec.ext_num_suffix && syntax::num_suffix(spec).scan(loc).is_ok()) + { + return ok(value_t::integer); + } + + if(int_reg.length() <= 2 && (int_reg.as_string() == "0" || + int_reg.as_string() == "-0" || int_reg.as_string() == "+0")) + { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad integer: leading zero is not allowed in decimal int", + std::move(src), "leading zero", + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n")); + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info( + "bad integer: `_` must be surrounded by digits", + std::move(src), "invalid underscore", + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n")); + } + } + if('0' <= c && c <= '9') + { + if(loc.current() == '0') + { + loc.retrace(); + return err(make_error_info( + "bad integer: leading zero", + source_location(region(loc)), "leading zero is not allowed", + std::string("Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n") + )); + } + else // invalid digits, especially in oct/bin ints. + { + return err(make_error_info( + "bad integer: invalid digit after an integer", + source_location(region(loc)), "this digit is not allowed", + std::string("Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n") + )); + } + } + if(c == ':' || c == '-') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad datetime: invalid format", + std::move(src), "here", + std::string("Hint: valid : 1979-05-27T07:32:00-07:00, 1979-05-27 07:32:00.999999Z\n" + "Hint: invalid: 1979-05-27T7:32:00-7:00, 1979-05-27 7:32-00:30") + )); + } + if(c == '.' || c == 'e' || c == 'E') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad float: invalid format", + std::move(src), "here", std::string( + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n"))); + } + } + return ok(value_t::integer); + } + if( ! loc.eof() && loc.current() == '.') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad float: integer part is required before decimal point", + std::move(src), "missing integer part", std::string( + "Hint: valid : +1.0, -2e-2, 3.141_592_653_589, inf, nan\n" + "Hint: invalid: .0, 1., _1.0, 1.0_, 1_.0, 1.0__0\n") + )); + } + if( ! loc.eof() && loc.current() == '_') + { + auto src = source_location(region(loc)); + return err(make_error_info("bad number: `_` must be surrounded by digits", + std::move(src), "digits required before `_`", std::string( + "Hint: valid : -42, 1_000, 1_2_3_4_5, 0xC0FFEE, 0b0010, 0o755\n" + "Hint: invalid: _42, 1__000, 0123\n") + )); + } + + auto src = source_location(region(loc)); + return err(make_error_info("bad format: unknown value appeared", + std::move(src), "here")); +} + +template +result +guess_value_type(const location& loc, const context& ctx) +{ + const auto& sp = ctx.toml_spec(); + location inner(loc); + + switch(loc.current()) + { + case '"' : {return ok(value_t::string); } + case '\'': {return ok(value_t::string); } + case '[' : {return ok(value_t::array); } + case '{' : {return ok(value_t::table); } + case 't' : + { + return ok(value_t::boolean); + } + case 'f' : + { + return ok(value_t::boolean); + } + case 'T' : // invalid boolean. + { + return err(make_syntax_error("toml::parse_value: " + "`true` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::boolean(sp), inner)); + } + case 'F' : + { + return err(make_syntax_error("toml::parse_value: " + "`false` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::boolean(sp), inner)); + } + case 'i' : // inf or string without quotes(syntax error). + { + if(literal("inf").scan(inner).is_ok()) + { + return ok(value_t::floating); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "`inf` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + case 'I' : // Inf or string without quotes(syntax error). + { + return err(make_syntax_error("toml::parse_value: " + "`inf` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + case 'n' : // nan or null-extension + { + if(sp.ext_null_value) + { + if(literal("nan").scan(inner).is_ok()) + { + return ok(value_t::floating); + } + else if(literal("null").scan(inner).is_ok()) + { + return ok(value_t::empty); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "Both `nan` and `null` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + else // must be nan. + { + if(literal("nan").scan(inner).is_ok()) + { + return ok(value_t::floating); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "`nan` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + } + case 'N' : // nan or null-extension + { + if(sp.ext_null_value) + { + return err(make_syntax_error("toml::parse_value: " + "Both `nan` and `null` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + else + { + return err(make_syntax_error("toml::parse_value: " + "`nan` must be in lowercase. " + "A string must be surrounded by quotes.", + syntax::floating(sp), inner)); + } + } + default : + { + return guess_number_type(loc, ctx); + } + } +} + +template +result, error_info> +parse_value(location& loc, context& ctx) +{ + const auto ty_res = guess_value_type(loc, ctx); + if(ty_res.is_err()) + { + return err(ty_res.unwrap_err()); + } + + switch(ty_res.unwrap()) + { + case value_t::empty: + { + if(ctx.toml_spec().ext_null_value) + { + return parse_null(loc, ctx); + } + else + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_value: unknown value appeared", + std::move(src), "here")); + } + } + case value_t::boolean : {return parse_boolean (loc, ctx);} + case value_t::integer : {return parse_integer (loc, ctx);} + case value_t::floating : {return parse_floating (loc, ctx);} + case value_t::string : {return parse_string (loc, ctx);} + case value_t::offset_datetime: {return parse_offset_datetime(loc, ctx);} + case value_t::local_datetime : {return parse_local_datetime (loc, ctx);} + case value_t::local_date : {return parse_local_date (loc, ctx);} + case value_t::local_time : {return parse_local_time (loc, ctx);} + case value_t::array : {return parse_array (loc, ctx);} + case value_t::table : {return parse_inline_table (loc, ctx);} + default: + { + auto src = source_location(region(loc)); + return err(make_error_info("toml::parse_value: unknown value appeared", + std::move(src), "here")); + } + } +} + +/* ============================================================================ + * _____ _ _ + * |_ _|_ _| |__| |___ + * | |/ _` | '_ \ / -_) + * |_|\__,_|_.__/_\___| + */ + +template +result::key_type>, region>, error_info> +parse_table_key(location& loc, context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::std_table(spec).scan(loc); + if(!reg.is_ok()) + { + return err(make_syntax_error("toml::parse_table_key: invalid table key", + syntax::std_table(spec), loc)); + } + + loc = first; + loc.advance(); // skip [ + skip_whitespace(loc, ctx); + + auto keys_res = parse_key(loc, ctx); + if(keys_res.is_err()) + { + return err(std::move(keys_res.unwrap_err())); + } + + skip_whitespace(loc, ctx); + loc.advance(); // ] + + return ok(std::make_pair(std::move(keys_res.unwrap().first), std::move(reg))); +} + +template +result::key_type>, region>, error_info> +parse_array_table_key(location& loc, context& ctx) +{ + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + auto reg = syntax::array_table(spec).scan(loc); + if(!reg.is_ok()) + { + return err(make_syntax_error("toml::parse_array_table_key: invalid array-of-tables key", + syntax::array_table(spec), loc)); + } + + loc = first; + loc.advance(); // [ + loc.advance(); // [ + skip_whitespace(loc, ctx); + + auto keys_res = parse_key(loc, ctx); + if(keys_res.is_err()) + { + return err(std::move(keys_res.unwrap_err())); + } + + skip_whitespace(loc, ctx); + loc.advance(); // ] + loc.advance(); // ] + + return ok(std::make_pair(std::move(keys_res.unwrap().first), std::move(reg))); +} + +// called after reading [table.keys] and comments around it. +// Since table may already contain a subtable ([x.y.z] can be defined before [x]), +// the table that is being parsed is passed as an argument. +template +result +parse_table(location& loc, context& ctx, basic_value& table) +{ + assert(table.is_table()); + + const auto num_errors = ctx.errors().size(); + const auto& spec = ctx.toml_spec(); + + // clear indent info + table.as_table_fmt().indent_type = indent_char::none; + + bool newline_found = true; + while( ! loc.eof()) + { + const auto start = loc; + + auto sp = skip_multiline_spacer(loc, ctx, newline_found); + + // if reached to EOF, the table ends here. return. + if(loc.eof()) + { + break; + } + // if next table is comming, return. + if(sequence(syntax::ws(spec), character('[')).scan(loc).is_ok()) + { + loc = start; + break; + } + // otherwise, it should be a key-value pair. + newline_found = newline_found || (sp.has_value() && sp.value().newline_found); + if( ! newline_found) + { + return err(make_error_info("toml::parse_table: " + "newline (LF / CRLF) or EOF is expected", + source_location(region(loc)), "here")); + } + if(sp.has_value() && sp.value().indent_type != indent_char::none) + { + table.as_table_fmt().indent_type = sp.value().indent_type; + table.as_table_fmt().body_indent = sp.value().indent; + } + + newline_found = false; // reset + if(auto kv_res = parse_key_value_pair(loc, ctx)) + { + auto keys = std::move(kv_res.unwrap().first.first); + auto key_reg = std::move(kv_res.unwrap().first.second); + auto val = std::move(kv_res.unwrap().second); + + if(sp.has_value()) + { + for(const auto& com : sp.value().comments) + { + val.comments().push_back(com); + } + } + + if(auto com_res = parse_comment_line(loc, ctx)) + { + if(auto com_opt = com_res.unwrap()) + { + val.comments().push_back(com_opt.value()); + newline_found = true; // comment includes newline at the end + } + } + else + { + ctx.report_error(std::move(com_res.unwrap_err())); + } + + auto ins_res = insert_value(inserting_value_kind::dotted_keys, + std::addressof(table.as_table()), + keys, std::move(key_reg), std::move(val)); + if(ins_res.is_err()) + { + ctx.report_error(std::move(ins_res.unwrap_err())); + } + } + else + { + ctx.report_error(std::move(kv_res.unwrap_err())); + skip_key_value_pair(loc, ctx); + } + } + + if(num_errors < ctx.errors().size()) + { + assert(ctx.has_error()); // already reported + return err(ctx.pop_last_error()); + } + return ok(); +} + +template +result, std::vector> +parse_file(location& loc, context& ctx) +{ + using value_type = basic_value; + using table_type = typename value_type::table_type; + + const auto first = loc; + const auto& spec = ctx.toml_spec(); + + if(loc.eof()) + { + return ok(value_type(table_type(), table_format_info{}, {}, region(loc))); + } + + value_type root(table_type(), table_format_info{}, {}, region(loc)); + root.as_table_fmt().fmt = table_format::multiline; + root.as_table_fmt().indent_type = indent_char::none; + + // parse top comment. + // + // ```toml + // # this is a comment for the top-level table. + // + // key = "the first value" + // ``` + // + // ```toml + // # this is a comment for "the first value". + // key = "the first value" + // ``` + while( ! loc.eof()) + { + if(auto com_res = parse_comment_line(loc, ctx)) + { + if(auto com_opt = com_res.unwrap()) + { + root.comments().push_back(std::move(com_opt.value())); + } + else // no comment found. + { + // if it is not an empty line, clear the root comment. + if( ! sequence(syntax::ws(spec), syntax::newline(spec)).scan(loc).is_ok()) + { + loc = first; + root.comments().clear(); + } + break; + } + } + else + { + ctx.report_error(std::move(com_res.unwrap_err())); + skip_comment_block(loc, ctx); + } + } + + // parse root table + { + const auto res = parse_table(loc, ctx, root); + if(res.is_err()) + { + ctx.report_error(std::move(res.unwrap_err())); + skip_until_next_table(loc, ctx); + } + } + + // parse tables + + while( ! loc.eof()) + { + auto sp = skip_multiline_spacer(loc, ctx, /*newline_found=*/true); + + if(auto key_res = parse_array_table_key(loc, ctx)) + { + auto key = std::move(std::get<0>(key_res.unwrap())); + auto reg = std::move(std::get<1>(key_res.unwrap())); + + std::vector com; + if(sp.has_value()) + { + for(std::size_t i=0; i(table_type()); + auto res = parse_table(loc, ctx, tmp); + if(res.is_err()) + { + ctx.report_error(res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + continue; + } + + auto tab_ptr = inserted.unwrap(); + assert(tab_ptr); + + const auto tab_res = parse_table(loc, ctx, *tab_ptr); + if(tab_res.is_err()) + { + ctx.report_error(tab_res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + + // parse_table first clears `indent_type`. + // to keep header indent info, we must store it later. + if(sp.has_value() && sp.value().indent_type != indent_char::none) + { + tab_ptr->as_table_fmt().indent_type = sp.value().indent_type; + tab_ptr->as_table_fmt().name_indent = sp.value().indent; + } + continue; + } + if(auto key_res = parse_table_key(loc, ctx)) + { + auto key = std::move(std::get<0>(key_res.unwrap())); + auto reg = std::move(std::get<1>(key_res.unwrap())); + + std::vector com; + if(sp.has_value()) + { + for(std::size_t i=0; i(table_type()); + auto res = parse_table(loc, ctx, tmp); + if(res.is_err()) + { + ctx.report_error(res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + continue; + } + + auto tab_ptr = inserted.unwrap(); + assert(tab_ptr); + + const auto tab_res = parse_table(loc, ctx, *tab_ptr); + if(tab_res.is_err()) + { + ctx.report_error(tab_res.unwrap_err()); + skip_until_next_table(loc, ctx); + } + if(sp.has_value() && sp.value().indent_type != indent_char::none) + { + tab_ptr->as_table_fmt().indent_type = sp.value().indent_type; + tab_ptr->as_table_fmt().name_indent = sp.value().indent; + } + continue; + } + + // does not match array_table nor std_table. report an error. + const auto keytop = loc; + const auto maybe_array_of_tables = literal("[[").scan(loc).is_ok(); + loc = keytop; + + if(maybe_array_of_tables) + { + ctx.report_error(make_syntax_error("toml::parse_file: invalid array-table key", + syntax::array_table(spec), loc)); + } + else + { + ctx.report_error(make_syntax_error("toml::parse_file: invalid table key", + syntax::std_table(spec), loc)); + } + skip_until_next_table(loc, ctx); + } + + if( ! ctx.errors().empty()) + { + return err(std::move(ctx.errors())); + } + return ok(std::move(root)); +} + +template +result, std::vector> +parse_impl(std::vector cs, std::string fname, const spec& s) +{ + using value_type = basic_value; + using table_type = typename value_type::table_type; + + // an empty file is a valid toml file. + if(cs.empty()) + { + auto src = std::make_shared>(std::move(cs)); + location loc(std::move(src), std::move(fname)); + return ok(value_type(table_type(), table_format_info{}, std::vector{}, region(loc))); + } + + // to simplify parser, add newline at the end if there is no LF. + // But, if it has raw CR, the file is invalid (in TOML, CR is not a valid + // newline char). if it ends with CR, do not add LF and report it. + if(cs.back() != '\n' && cs.back() != '\r') + { + cs.push_back('\n'); + } + + auto src = std::make_shared>(std::move(cs)); + + location loc(std::move(src), std::move(fname)); + + // skip BOM if found + if(loc.source()->size() >= 3) + { + auto first = loc.get_location(); + + const auto c0 = loc.current(); loc.advance(); + const auto c1 = loc.current(); loc.advance(); + const auto c2 = loc.current(); loc.advance(); + + const auto bom_found = (c0 == 0xEF) && (c1 == 0xBB) && (c2 == 0xBF); + if( ! bom_found) + { + loc.set_location(first); + } + } + + context ctx(s); + + return parse_file(loc, ctx); +} + +} // detail + +// ----------------------------------------------------------------------------- +// parse(byte array) + +template +result, std::vector> +try_parse(std::vector content, std::string filename, + spec s = spec::default_version()) +{ + return detail::parse_impl(std::move(content), std::move(filename), std::move(s)); +} +template +basic_value +parse(std::vector content, std::string filename, + spec s = spec::default_version()) +{ + auto res = try_parse(std::move(content), std::move(filename), std::move(s)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +// ----------------------------------------------------------------------------- +// parse(istream) + +template +result, std::vector> +try_parse(std::istream& is, std::string fname = "unknown file", spec s = spec::default_version()) +{ + const auto beg = is.tellg(); + is.seekg(0, std::ios::end); + const auto end = is.tellg(); + const auto fsize = end - beg; + is.seekg(beg); + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize), '\0'); + is.read(reinterpret_cast(letters.data()), static_cast(fsize)); + + return detail::parse_impl(std::move(letters), std::move(fname), std::move(s)); +} + +template +basic_value parse(std::istream& is, std::string fname = "unknown file", spec s = spec::default_version()) +{ + auto res = try_parse(is, std::move(fname), std::move(s)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +// ----------------------------------------------------------------------------- +// parse(filename) + +template +result, std::vector> +try_parse(std::string fname, spec s = spec::default_version()) +{ + std::ifstream ifs(fname, std::ios_base::binary); + if(!ifs.good()) + { + std::vector e; + e.push_back(error_info("toml::parse: Error opening file \"" + fname + "\"", {})); + return err(std::move(e)); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return try_parse(ifs, std::move(fname), std::move(s)); +} + +template +basic_value parse(std::string fname, spec s = spec::default_version()) +{ + std::ifstream ifs(fname, std::ios_base::binary); + if(!ifs.good()) + { + throw file_io_error("toml::parse: error opening file", fname); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return parse(ifs, std::move(fname), std::move(s)); +} + +template +result, std::vector> +try_parse(const char (&fname)[N], spec s = spec::default_version()) +{ + return try_parse(std::string(fname), std::move(s)); +} + +template +basic_value parse(const char (&fname)[N], spec s = spec::default_version()) +{ + return parse(std::string(fname), std::move(s)); +} + +// ---------------------------------------------------------------------------- +// parse_str + +template +result, std::vector> +try_parse_str(std::string content, spec s = spec::default_version(), + cxx::source_location loc = cxx::source_location::current()) +{ + std::istringstream iss(std::move(content)); + std::string name("internal string" + cxx::to_string(loc)); + return try_parse(iss, std::move(name), std::move(s)); +} + +template +basic_value parse_str(std::string content, spec s = spec::default_version(), + cxx::source_location loc = cxx::source_location::current()) +{ + auto res = try_parse_str(std::move(content), std::move(s), std::move(loc)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +// ---------------------------------------------------------------------------- +// filesystem + +#if defined(TOML11_HAS_FILESYSTEM) + +template +cxx::enable_if_t::value, + result, std::vector>> +try_parse(const FSPATH& fpath, spec s = spec::default_version()) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + std::vector e; + e.push_back(error_info("toml::parse: Error opening file \"" + fpath.string() + "\"", {})); + return err(std::move(e)); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return try_parse(ifs, fpath.string(), std::move(s)); +} + +template +cxx::enable_if_t::value, + basic_value> +parse(const FSPATH& fpath, spec s = spec::default_version()) +{ + std::ifstream ifs(fpath, std::ios_base::binary); + if(!ifs.good()) + { + throw file_io_error("toml::parse: error opening file", fpath.string()); + } + ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); + + return parse(ifs, fpath.string(), std::move(s)); +} +#endif + +// ----------------------------------------------------------------------------- +// FILE* + +template +result, std::vector> +try_parse(FILE* fp, std::string filename, spec s = spec::default_version()) +{ + const long beg = std::ftell(fp); + if (beg == -1L) + { + return err(std::vector{error_info( + std::string("Failed to access: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + } + + const int res_seekend = std::fseek(fp, 0, SEEK_END); + if (res_seekend != 0) + { + return err(std::vector{error_info( + std::string("Failed to seek: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + } + + const long end = std::ftell(fp); + if (end == -1L) + { + return err(std::vector{error_info( + std::string("Failed to access: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + } + + const auto fsize = end - beg; + + const auto res_seekbeg = std::fseek(fp, beg, SEEK_SET); + if (res_seekbeg != 0) + { + return err(std::vector{error_info( + std::string("Failed to seek: \"") + filename + + "\", errno = " + std::to_string(errno), {} + )}); + + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + const auto actual = std::fread(letters.data(), sizeof(char), static_cast(fsize), fp); + if(actual != static_cast(fsize)) + { + return err(std::vector{error_info( + std::string("File size changed: \"") + filename + + std::string("\" make sure that FILE* is in binary mode " + "to avoid LF <-> CRLF conversion"), {} + )}); + } + + return detail::parse_impl(std::move(letters), std::move(filename), std::move(s)); +} + +template +basic_value +parse(FILE* fp, std::string filename, spec s = spec::default_version()) +{ + const long beg = std::ftell(fp); + if (beg == -1L) + { + throw file_io_error(errno, "Failed to access", filename); + } + + const int res_seekend = std::fseek(fp, 0, SEEK_END); + if (res_seekend != 0) + { + throw file_io_error(errno, "Failed to seek", filename); + } + + const long end = std::ftell(fp); + if (end == -1L) + { + throw file_io_error(errno, "Failed to access", filename); + } + + const auto fsize = end - beg; + + const auto res_seekbeg = std::fseek(fp, beg, SEEK_SET); + if (res_seekbeg != 0) + { + throw file_io_error(errno, "Failed to seek", filename); + } + + // read whole file as a sequence of char + assert(fsize >= 0); + std::vector letters(static_cast(fsize)); + const auto actual = std::fread(letters.data(), sizeof(char), static_cast(fsize), fp); + if(actual != static_cast(fsize)) + { + throw file_io_error(errno, "File size changed; make sure that " + "FILE* is in binary mode to avoid LF <-> CRLF conversion", filename); + } + + auto res = detail::parse_impl(std::move(letters), std::move(filename), std::move(s)); + if(res.is_ok()) + { + return res.unwrap(); + } + else + { + std::string msg; + for(const auto& err : res.unwrap_err()) + { + msg += format_error(err); + } + throw syntax_error(std::move(msg), std::move(res.unwrap_err())); + } +} + +} // namespace toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; + +extern template result, std::vector> try_parse(std::vector, std::string, spec); +extern template result, std::vector> try_parse(std::istream&, std::string, spec); +extern template result, std::vector> try_parse(std::string, spec); +extern template result, std::vector> try_parse(FILE*, std::string, spec); +extern template result, std::vector> try_parse_str(std::string, spec, cxx::source_location); + +extern template basic_value parse(std::vector, std::string, spec); +extern template basic_value parse(std::istream&, std::string, spec); +extern template basic_value parse(std::string, spec); +extern template basic_value parse(FILE*, std::string, spec); +extern template basic_value parse_str(std::string, spec, cxx::source_location); + +extern template result, std::vector> try_parse(std::vector, std::string, spec); +extern template result, std::vector> try_parse(std::istream&, std::string, spec); +extern template result, std::vector> try_parse(std::string, spec); +extern template result, std::vector> try_parse(FILE*, std::string, spec); +extern template result, std::vector> try_parse_str(std::string, spec, cxx::source_location); + +extern template basic_value parse(std::vector, std::string, spec); +extern template basic_value parse(std::istream&, std::string, spec); +extern template basic_value parse(std::string, spec); +extern template basic_value parse(FILE*, std::string, spec); +extern template basic_value parse_str(std::string, spec, cxx::source_location); + +#if defined(TOML11_HAS_FILESYSTEM) +extern template cxx::enable_if_t::value, result, std::vector>> try_parse(const std::filesystem::path&, spec); +extern template cxx::enable_if_t::value, result, std::vector>> try_parse(const std::filesystem::path&, spec); +extern template cxx::enable_if_t::value, basic_value > parse (const std::filesystem::path&, spec); +extern template cxx::enable_if_t::value, basic_value > parse (const std::filesystem::path&, spec); +#endif // filesystem + +} // toml +#endif // TOML11_COMPILE_SOURCES + +#endif // TOML11_PARSER_HPP +#ifndef TOML11_LITERAL_HPP +#define TOML11_LITERAL_HPP + +#ifndef TOML11_LITERAL_FWD_HPP +#define TOML11_LITERAL_FWD_HPP + + +namespace toml +{ + +namespace detail +{ +// implementation +::toml::value literal_internal_impl(location loc); +} // detail + +inline namespace literals +{ +inline namespace toml_literals +{ + +::toml::value operator"" _toml(const char* str, std::size_t len); + +#if defined(TOML11_HAS_CHAR8_T) +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +::toml::value operator"" _toml(const char8_t* str, std::size_t len); +#endif + +} // toml_literals +} // literals +} // toml +#endif // TOML11_LITERAL_FWD_HPP + +#if ! defined(TOML11_COMPILE_SOURCES) +#ifndef TOML11_LITERAL_IMPL_HPP +#define TOML11_LITERAL_IMPL_HPP + + +namespace toml +{ + +namespace detail +{ +// implementation +TOML11_INLINE ::toml::value literal_internal_impl(location loc) +{ + const auto s = ::toml::spec::default_version(); + context ctx(s); + + const auto front = loc; + + // ------------------------------------------------------------------------ + // check if it is a raw value. + + // skip empty lines and comment lines + auto sp = skip_multiline_spacer(loc, ctx); + if(loc.eof()) + { + ::toml::value val; + if(sp.has_value()) + { + for(std::size_t i=0; i(str), + reinterpret_cast(str + len), + c.begin()); + if( ! c.empty() && c.back()) + { + c.push_back('\n'); // to make it easy to parse comment, we add newline + } + + return literal_internal_impl(::toml::detail::location( + std::make_shared(std::move(c)), + "TOML literal encoded in a C++ code")); +} + +#if defined(__cpp_char8_t) +# if __cpp_char8_t >= 201811L +# define TOML11_HAS_CHAR8_T 1 +# endif +#endif + +#if defined(TOML11_HAS_CHAR8_T) +// value of u8"" literal has been changed from char to char8_t and char8_t is +// NOT compatible to char +TOML11_INLINE ::toml::value +operator"" _toml(const char8_t* str, std::size_t len) +{ + if(len == 0) + { + return ::toml::value{}; + } + + ::toml::detail::location::container_type c(len); + std::copy(reinterpret_cast(str), + reinterpret_cast(str + len), + c.begin()); + if( ! c.empty() && c.back()) + { + c.push_back('\n'); // to make it easy to parse comment, we add newline + } + + return literal_internal_impl(::toml::detail::location( + std::make_shared(std::move(c)), + "TOML literal encoded in a C++ code")); +} +#endif + +} // toml_literals +} // literals +} // toml +#endif // TOML11_LITERAL_IMPL_HPP +#endif + +#endif // TOML11_LITERAL_HPP +#ifndef TOML11_SERIALIZER_HPP +#define TOML11_SERIALIZER_HPP + + +#include +#include +#include + +#include +#include + +namespace toml +{ + +struct serialization_error final : public ::toml::exception +{ + public: + explicit serialization_error(std::string what_arg, source_location loc) + : what_(std::move(what_arg)), loc_(std::move(loc)) + {} + ~serialization_error() noexcept override = default; + + const char* what() const noexcept override {return what_.c_str();} + source_location const& location() const noexcept {return loc_;} + + private: + std::string what_; + source_location loc_; +}; + +namespace detail +{ +template +class serializer +{ + public: + + using value_type = basic_value; + + using key_type = typename value_type::key_type ; + using comment_type = typename value_type::comment_type ; + using boolean_type = typename value_type::boolean_type ; + using integer_type = typename value_type::integer_type ; + using floating_type = typename value_type::floating_type ; + using string_type = typename value_type::string_type ; + using local_time_type = typename value_type::local_time_type ; + using local_date_type = typename value_type::local_date_type ; + using local_datetime_type = typename value_type::local_datetime_type ; + using offset_datetime_type = typename value_type::offset_datetime_type; + using array_type = typename value_type::array_type ; + using table_type = typename value_type::table_type ; + + using char_type = typename string_type::value_type; + + public: + + explicit serializer(const spec& sp) + : spec_(sp), force_inline_(false), current_indent_(0) + {} + + string_type operator()(const std::vector& ks, const value_type& v) + { + for(const auto& k : ks) + { + this->keys_.push_back(k); + } + return (*this)(v); + } + + string_type operator()(const key_type& k, const value_type& v) + { + this->keys_.push_back(k); + return (*this)(v); + } + + string_type operator()(const value_type& v) + { + switch(v.type()) + { + case value_t::boolean : {return (*this)(v.as_boolean (), v.as_boolean_fmt (), v.location());} + case value_t::integer : {return (*this)(v.as_integer (), v.as_integer_fmt (), v.location());} + case value_t::floating : {return (*this)(v.as_floating (), v.as_floating_fmt (), v.location());} + case value_t::string : {return (*this)(v.as_string (), v.as_string_fmt (), v.location());} + case value_t::offset_datetime: {return (*this)(v.as_offset_datetime(), v.as_offset_datetime_fmt(), v.location());} + case value_t::local_datetime : {return (*this)(v.as_local_datetime (), v.as_local_datetime_fmt (), v.location());} + case value_t::local_date : {return (*this)(v.as_local_date (), v.as_local_date_fmt (), v.location());} + case value_t::local_time : {return (*this)(v.as_local_time (), v.as_local_time_fmt (), v.location());} + case value_t::array : + { + return (*this)(v.as_array(), v.as_array_fmt(), v.comments(), v.location()); + } + case value_t::table : + { + string_type retval; + if(this->keys_.empty()) // it might be the root table. emit comments here. + { + retval += format_comments(v.comments(), v.as_table_fmt().indent_type); + } + if( ! retval.empty()) // we have comment. + { + retval += char_type('\n'); + } + + retval += (*this)(v.as_table(), v.as_table_fmt(), v.comments(), v.location()); + return retval; + } + case value_t::empty: + { + if(this->spec_.ext_null_value) + { + return string_conv("null"); + } + break; + } + default: + { + break; + } + } + throw serialization_error(format_error( + "[error] toml::serializer: toml::basic_value " + "does not have any valid type.", v.location(), "here"), v.location()); + } + + private: + + string_type operator()(const boolean_type& b, const boolean_format_info&, const source_location&) // {{{ + { + if(b) + { + return string_conv("true"); + } + else + { + return string_conv("false"); + } + } // }}} + + string_type operator()(const integer_type i, const integer_format_info& fmt, const source_location& loc) // {{{ + { + std::ostringstream oss; + this->set_locale(oss); + + const auto insert_spacer = [&fmt](std::string s) -> std::string { + if(fmt.spacer == 0) {return s;} + + std::string sign; + if( ! s.empty() && (s.at(0) == '+' || s.at(0) == '-')) + { + sign += s.at(0); + s.erase(s.begin()); + } + + std::string spaced; + std::size_t counter = 0; + for(auto iter = s.rbegin(); iter != s.rend(); ++iter) + { + if(counter != 0 && counter % fmt.spacer == 0) + { + spaced += '_'; + } + spaced += *iter; + counter += 1; + } + if(!spaced.empty() && spaced.back() == '_') {spaced.pop_back();} + + s.clear(); + std::copy(spaced.rbegin(), spaced.rend(), std::back_inserter(s)); + return sign + s; + }; + + std::string retval; + if(fmt.fmt == integer_format::dec) + { + oss << std::setw(static_cast(fmt.width)) << std::dec << i; + retval = insert_spacer(oss.str()); + + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + retval += '_'; + retval += fmt.suffix; + } + } + else + { + if(i < 0) + { + throw serialization_error(format_error("binary, octal, hexadecimal " + "integer does not allow negative value", loc, "here"), loc); + } + switch(fmt.fmt) + { + case integer_format::hex: + { + oss << std::noshowbase + << std::setw(static_cast(fmt.width)) + << std::setfill('0') + << std::hex; + if(fmt.uppercase) + { + oss << std::uppercase; + } + else + { + oss << std::nouppercase; + } + oss << i; + retval = std::string("0x") + insert_spacer(oss.str()); + break; + } + case integer_format::oct: + { + oss << std::setw(static_cast(fmt.width)) << std::setfill('0') << std::oct << i; + retval = std::string("0o") + insert_spacer(oss.str()); + break; + } + case integer_format::bin: + { + integer_type x{i}; + std::string tmp; + std::size_t bits(0); + while(x != 0) + { + if(fmt.spacer != 0) + { + if(bits != 0 && (bits % fmt.spacer) == 0) {tmp += '_';} + } + if(x % 2 == 1) { tmp += '1'; } else { tmp += '0'; } + x >>= 1; + bits += 1; + } + for(; bits < fmt.width; ++bits) + { + if(fmt.spacer != 0) + { + if(bits != 0 && (bits % fmt.spacer) == 0) {tmp += '_';} + } + tmp += '0'; + } + for(auto iter = tmp.rbegin(); iter != tmp.rend(); ++iter) + { + oss << *iter; + } + retval = std::string("0b") + oss.str(); + break; + } + default: + { + throw serialization_error(format_error( + "none of dec, hex, oct, bin: " + to_string(fmt.fmt), + loc, "here"), loc); + } + } + } + return string_conv(retval); + } // }}} + + string_type operator()(const floating_type f, const floating_format_info& fmt, const source_location&) // {{{ + { + using std::isnan; + using std::isinf; + using std::signbit; + + std::ostringstream oss; + this->set_locale(oss); + + if(isnan(f)) + { + if(signbit(f)) + { + oss << '-'; + } + oss << "nan"; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_'; + oss << fmt.suffix; + } + return string_conv(oss.str()); + } + + if(isinf(f)) + { + if(signbit(f)) + { + oss << '-'; + } + oss << "inf"; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_'; + oss << fmt.suffix; + } + return string_conv(oss.str()); + } + + switch(fmt.fmt) + { + case floating_format::defaultfloat: + { + if(fmt.prec != 0) + { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << f; + // since defaultfloat may omit point, we need to add it + std::string s = oss.str(); + if (s.find('.') == std::string::npos && + s.find('e') == std::string::npos && + s.find('E') == std::string::npos ) + { + s += ".0"; + } + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + s += '_'; + s += fmt.suffix; + } + return string_conv(s); + } + case floating_format::fixed: + { + if(fmt.prec != 0) + { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << std::fixed << f; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + case floating_format::scientific: + { + if(fmt.prec != 0) + { + oss << std::setprecision(static_cast(fmt.prec)); + } + oss << std::scientific << f; + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + case floating_format::hex: + { + if(this->spec_.ext_hex_float) + { + oss << std::hexfloat << f; + // suffix is only for decimal numbers. + return string_conv(oss.str()); + } + else // no hex allowed. output with max precision. + { + oss << std::setprecision(std::numeric_limits::max_digits10) + << std::scientific << f; + // suffix is only for decimal numbers. + return string_conv(oss.str()); + } + } + default: + { + if(this->spec_.ext_num_suffix && ! fmt.suffix.empty()) + { + oss << '_' << fmt.suffix; + } + return string_conv(oss.str()); + } + } + } // }}} + + string_type operator()(string_type s, const string_format_info& fmt, const source_location& loc) // {{{ + { + string_type retval; + switch(fmt.fmt) + { + case string_format::basic: + { + retval += char_type('"'); + retval += this->escape_basic_string(s); + retval += char_type('"'); + return retval; + } + case string_format::literal: + { + if(std::find(s.begin(), s.end(), char_type('\n')) != s.end()) + { + throw serialization_error(format_error("toml::serializer: " + "(non-multiline) literal string cannot have a newline", + loc, "here"), loc); + } + retval += char_type('\''); + retval += s; + retval += char_type('\''); + return retval; + } + case string_format::multiline_basic: + { + retval += string_conv("\"\"\""); + if(fmt.start_with_newline) + { + retval += char_type('\n'); + } + + retval += this->escape_ml_basic_string(s); + + retval += string_conv("\"\"\""); + return retval; + } + case string_format::multiline_literal: + { + retval += string_conv("'''"); + if(fmt.start_with_newline) + { + retval += char_type('\n'); + } + retval += s; + retval += string_conv("'''"); + return retval; + } + default: + { + throw serialization_error(format_error( + "[error] toml::serializer::operator()(string): " + "invalid string_format value", loc, "here"), loc); + } + } + } // }}} + + string_type operator()(const local_date_type& d, const local_date_format_info&, const source_location&) // {{{ + { + std::ostringstream oss; + oss << d; + return string_conv(oss.str()); + } // }}} + + string_type operator()(const local_time_type& t, const local_time_format_info& fmt, const source_location&) // {{{ + { + return this->format_local_time(t, fmt.has_seconds, fmt.subsecond_precision); + } // }}} + + string_type operator()(const local_datetime_type& dt, const local_datetime_format_info& fmt, const source_location&) // {{{ + { + std::ostringstream oss; + oss << dt.date; + switch(fmt.delimiter) + { + case datetime_delimiter_kind::upper_T: { oss << 'T'; break; } + case datetime_delimiter_kind::lower_t: { oss << 't'; break; } + case datetime_delimiter_kind::space: { oss << ' '; break; } + default: { oss << 'T'; break; } + } + return string_conv(oss.str()) + + this->format_local_time(dt.time, fmt.has_seconds, fmt.subsecond_precision); + } // }}} + + string_type operator()(const offset_datetime_type& odt, const offset_datetime_format_info& fmt, const source_location&) // {{{ + { + std::ostringstream oss; + oss << odt.date; + switch(fmt.delimiter) + { + case datetime_delimiter_kind::upper_T: { oss << 'T'; break; } + case datetime_delimiter_kind::lower_t: { oss << 't'; break; } + case datetime_delimiter_kind::space: { oss << ' '; break; } + default: { oss << 'T'; break; } + } + oss << string_conv(this->format_local_time(odt.time, fmt.has_seconds, fmt.subsecond_precision)); + oss << odt.offset; + return string_conv(oss.str()); + } // }}} + + string_type operator()(const array_type& a, const array_format_info& fmt, const comment_type& com, const source_location& loc) // {{{ + { + array_format f = fmt.fmt; + if(fmt.fmt == array_format::default_format) + { + // [[in.this.form]], you cannot add a comment to the array itself + // (but you can add a comment to each table). + // To keep comments, we need to avoid multiline array-of-tables + // if array itself has a comment. + if( ! this->keys_.empty() && + ! a.empty() && + com.empty() && + std::all_of(a.begin(), a.end(), [](const value_type& e) {return e.is_table();})) + { + f = array_format::array_of_tables; + } + else + { + f = array_format::oneline; + + // check if it becomes long + std::size_t approx_len = 0; + for(const auto& e : a) + { + // have a comment. cannot be inlined + if( ! e.comments().empty()) + { + f = array_format::multiline; + break; + } + // possibly long types ... + if(e.is_array() || e.is_table() || e.is_offset_datetime() || e.is_local_datetime()) + { + f = array_format::multiline; + break; + } + else if(e.is_boolean()) + { + approx_len += (*this)(e.as_boolean(), e.as_boolean_fmt(), e.location()).size(); + } + else if(e.is_integer()) + { + approx_len += (*this)(e.as_integer(), e.as_integer_fmt(), e.location()).size(); + } + else if(e.is_floating()) + { + approx_len += (*this)(e.as_floating(), e.as_floating_fmt(), e.location()).size(); + } + else if(e.is_string()) + { + if(e.as_string_fmt().fmt == string_format::multiline_basic || + e.as_string_fmt().fmt == string_format::multiline_literal) + { + f = array_format::multiline; + break; + } + approx_len += 2 + (*this)(e.as_string(), e.as_string_fmt(), e.location()).size(); + } + else if(e.is_local_date()) + { + approx_len += 10; // 1234-56-78 + } + else if(e.is_local_time()) + { + approx_len += 15; // 12:34:56.789012 + } + + if(approx_len > 60) // key, ` = `, `[...]` < 80 + { + f = array_format::multiline; + break; + } + approx_len += 2; // `, ` + } + } + } + if(this->force_inline_ && f == array_format::array_of_tables) + { + f = array_format::multiline; + } + if(a.empty() && f == array_format::array_of_tables) + { + f = array_format::oneline; + } + + // -------------------------------------------------------------------- + + if(f == array_format::array_of_tables) + { + if(this->keys_.empty()) + { + throw serialization_error("array of table must have its key. " + "use format(key, v)", loc); + } + string_type retval; + for(const auto& e : a) + { + assert(e.is_table()); + + this->current_indent_ += e.as_table_fmt().name_indent; + retval += this->format_comments(e.comments(), e.as_table_fmt().indent_type); + retval += this->format_indent(e.as_table_fmt().indent_type); + this->current_indent_ -= e.as_table_fmt().name_indent; + + retval += string_conv("[["); + retval += this->format_keys(this->keys_).value(); + retval += string_conv("]]\n"); + + retval += this->format_ml_table(e.as_table(), e.as_table_fmt()); + } + return retval; + } + else if(f == array_format::oneline) + { + // ignore comments. we cannot emit comments + string_type retval; + retval += char_type('['); + for(const auto& e : a) + { + this->force_inline_ = true; + retval += (*this)(e); + retval += string_conv(", "); + } + if( ! a.empty()) + { + retval.pop_back(); // ` ` + retval.pop_back(); // `,` + } + retval += char_type(']'); + this->force_inline_ = false; + return retval; + } + else + { + assert(f == array_format::multiline); + + string_type retval; + retval += string_conv("[\n"); + + for(const auto& e : a) + { + this->current_indent_ += fmt.body_indent; + retval += this->format_comments(e.comments(), fmt.indent_type); + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.body_indent; + + this->force_inline_ = true; + retval += (*this)(e); + retval += string_conv(",\n"); + } + this->force_inline_ = false; + + this->current_indent_ += fmt.closing_indent; + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.closing_indent; + + retval += char_type(']'); + return retval; + } + } // }}} + + string_type operator()(const table_type& t, const table_format_info& fmt, const comment_type& com, const source_location& loc) // {{{ + { + if(this->force_inline_) + { + if(fmt.fmt == table_format::multiline_oneline) + { + return this->format_ml_inline_table(t, fmt); + } + else + { + return this->format_inline_table(t, fmt); + } + } + else + { + if(fmt.fmt == table_format::multiline) + { + string_type retval; + // comment is emitted inside format_ml_table + if(auto k = this->format_keys(this->keys_)) + { + this->current_indent_ += fmt.name_indent; + retval += this->format_comments(com, fmt.indent_type); + retval += this->format_indent(fmt.indent_type); + this->current_indent_ -= fmt.name_indent; + retval += char_type('['); + retval += k.value(); + retval += string_conv("]\n"); + } + // otherwise, its the root. + + retval += this->format_ml_table(t, fmt); + return retval; + } + else if(fmt.fmt == table_format::oneline) + { + return this->format_inline_table(t, fmt); + } + else if(fmt.fmt == table_format::multiline_oneline) + { + return this->format_ml_inline_table(t, fmt); + } + else if(fmt.fmt == table_format::dotted) + { + std::vector keys; + if(this->keys_.empty()) + { + throw serialization_error(format_error("toml::serializer: " + "dotted table must have its key. use format(key, v)", + loc, "here"), loc); + } + keys.push_back(this->keys_.back()); + + const auto retval = this->format_dotted_table(t, fmt, loc, keys); + keys.pop_back(); + return retval; + } + else + { + assert(fmt.fmt == table_format::implicit); + + string_type retval; + for(const auto& kv : t) + { + const auto& k = kv.first; + const auto& v = kv.second; + + if( ! v.is_table() && ! v.is_array_of_tables()) + { + throw serialization_error(format_error("toml::serializer: " + "an implicit table cannot have non-table value.", + v.location(), "here"), v.location()); + } + if(v.is_table()) + { + if(v.as_table_fmt().fmt != table_format::multiline && + v.as_table_fmt().fmt != table_format::implicit) + { + throw serialization_error(format_error("toml::serializer: " + "an implicit table cannot have non-multiline table", + v.location(), "here"), v.location()); + } + } + else + { + assert(v.is_array()); + for(const auto& e : v.as_array()) + { + if(e.as_table_fmt().fmt != table_format::multiline && + v.as_table_fmt().fmt != table_format::implicit) + { + throw serialization_error(format_error("toml::serializer: " + "an implicit table cannot have non-multiline table", + e.location(), "here"), e.location()); + } + } + } + + keys_.push_back(k); + retval += (*this)(v); + keys_.pop_back(); + } + return retval; + } + } + } // }}} + + private: + + string_type escape_basic_string(const string_type& s) const // {{{ + { + string_type retval; + for(const char_type c : s) + { + switch(c) + { + case char_type('\\'): {retval += string_conv("\\\\"); break;} + case char_type('\"'): {retval += string_conv("\\\""); break;} + case char_type('\b'): {retval += string_conv("\\b" ); break;} + case char_type('\t'): {retval += string_conv("\\t" ); break;} + case char_type('\f'): {retval += string_conv("\\f" ); break;} + case char_type('\n'): {retval += string_conv("\\n" ); break;} + case char_type('\r'): {retval += string_conv("\\r" ); break;} + default : + { + if(c == char_type(0x1B) && spec_.v1_1_0_add_escape_sequence_e) + { + retval += string_conv("\\e"); + } + else if((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) + { + if(spec_.v1_1_0_add_escape_sequence_x) + { + retval += string_conv("\\x"); + } + else + { + retval += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + retval += static_cast('0' + c1); + if(c2 < 10) + { + retval += static_cast('0' + c2); + } + else // 10 <= c2 + { + retval += static_cast('A' + (c2 - 10)); + } + } + else + { + retval += c; + } + } + } + } + return retval; + } // }}} + + string_type escape_ml_basic_string(const string_type& s) // {{{ + { + string_type retval; + for(const char_type c : s) + { + switch(c) + { + case char_type('\\'): {retval += string_conv("\\\\"); break;} + case char_type('\b'): {retval += string_conv("\\b" ); break;} + case char_type('\t'): {retval += string_conv("\\t" ); break;} + case char_type('\f'): {retval += string_conv("\\f" ); break;} + case char_type('\n'): {retval += string_conv("\n" ); break;} + case char_type('\r'): {retval += string_conv("\\r" ); break;} + default : + { + if(c == char_type(0x1B) && spec_.v1_1_0_add_escape_sequence_e) + { + retval += string_conv("\\e"); + } + else if((char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) + { + if(spec_.v1_1_0_add_escape_sequence_x) + { + retval += string_conv("\\x"); + } + else + { + retval += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + retval += static_cast('0' + c1); + if(c2 < 10) + { + retval += static_cast('0' + c2); + } + else // 10 <= c2 + { + retval += static_cast('A' + (c2 - 10)); + } + } + else + { + retval += c; + } + } + } + } + // Only 1 or 2 consecutive `"`s are allowed in multiline basic string. + // 3 consecutive `"`s are considered as a closing delimiter. + // We need to check if there are 3 or more consecutive `"`s and insert + // backslash to break them down into several short `"`s like the `str6` + // in the following example. + // ```toml + // str4 = """Here are two quotation marks: "". Simple enough.""" + // # str5 = """Here are three quotation marks: """.""" # INVALID + // str5 = """Here are three quotation marks: ""\".""" + // str6 = """Here are fifteen quotation marks: ""\"""\"""\"""\"""\".""" + // ``` + auto found_3_quotes = retval.find(string_conv("\"\"\"")); + while(found_3_quotes != string_type::npos) + { + retval.replace(found_3_quotes, 3, string_conv("\"\"\\\"")); + found_3_quotes = retval.find(string_conv("\"\"\"")); + } + return retval; + } // }}} + + string_type format_local_time(const local_time_type& t, const bool has_seconds, const std::size_t subsec_prec) // {{{ + { + std::ostringstream oss; + oss << std::setfill('0') << std::setw(2) << static_cast(t.hour); + oss << ':'; + oss << std::setfill('0') << std::setw(2) << static_cast(t.minute); + if(has_seconds) + { + oss << ':'; + oss << std::setfill('0') << std::setw(2) << static_cast(t.second); + if(subsec_prec != 0) + { + std::ostringstream subsec; + subsec << std::setfill('0') << std::setw(3) << static_cast(t.millisecond); + subsec << std::setfill('0') << std::setw(3) << static_cast(t.microsecond); + subsec << std::setfill('0') << std::setw(3) << static_cast(t.nanosecond); + std::string subsec_str = subsec.str(); + oss << '.' << subsec_str.substr(0, subsec_prec); + } + } + return string_conv(oss.str()); + } // }}} + + string_type format_ml_table(const table_type& t, const table_format_info& fmt) // {{{ + { + const auto format_later = [](const value_type& v) -> bool { + + const bool is_ml_table = v.is_table() && + v.as_table_fmt().fmt != table_format::oneline && + v.as_table_fmt().fmt != table_format::multiline_oneline && + v.as_table_fmt().fmt != table_format::dotted ; + + const bool is_ml_array_table = v.is_array_of_tables() && + v.as_array_fmt().fmt != array_format::oneline && + v.as_array_fmt().fmt != array_format::multiline; + + return is_ml_table || is_ml_array_table; + }; + + string_type retval; + this->current_indent_ += fmt.body_indent; + for(const auto& kv : t) + { + const auto& key = kv.first; + const auto& val = kv.second; + if(format_later(val)) + { + continue; + } + this->keys_.push_back(key); + + retval += format_comments(val.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + if(val.is_table() && val.as_table_fmt().fmt == table_format::dotted) + { + retval += (*this)(val); + } + else + { + retval += format_key(key); + retval += string_conv(" = "); + retval += (*this)(val); + retval += char_type('\n'); + } + this->keys_.pop_back(); + } + this->current_indent_ -= fmt.body_indent; + + if( ! retval.empty()) + { + retval += char_type('\n'); // for readability, add empty line between tables + } + for(const auto& kv : t) + { + if( ! format_later(kv.second)) + { + continue; + } + // must be a [multiline.table] or [[multiline.array.of.tables]]. + // comments will be generated inside it. + this->keys_.push_back(kv.first); + retval += (*this)(kv.second); + this->keys_.pop_back(); + } + return retval; + } // }}} + + string_type format_inline_table(const table_type& t, const table_format_info&) // {{{ + { + // comments are ignored because we cannot write without newline + string_type retval; + retval += char_type('{'); + for(const auto& kv : t) + { + this->force_inline_ = true; + retval += this->format_key(kv.first); + retval += string_conv(" = "); + retval += (*this)(kv.second); + retval += string_conv(", "); + } + if( ! t.empty()) + { + retval.pop_back(); // ' ' + retval.pop_back(); // ',' + } + retval += char_type('}'); + this->force_inline_ = false; + return retval; + } // }}} + + string_type format_ml_inline_table(const table_type& t, const table_format_info& fmt) // {{{ + { + string_type retval; + retval += string_conv("{\n"); + this->current_indent_ += fmt.body_indent; + for(const auto& kv : t) + { + this->force_inline_ = true; + retval += format_comments(kv.second.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + retval += kv.first; + retval += string_conv(" = "); + + this->force_inline_ = true; + retval += (*this)(kv.second); + + retval += string_conv(",\n"); + } + if( ! t.empty()) + { + retval.pop_back(); // '\n' + retval.pop_back(); // ',' + } + this->current_indent_ -= fmt.body_indent; + this->force_inline_ = false; + + this->current_indent_ += fmt.closing_indent; + retval += format_indent(fmt.indent_type); + this->current_indent_ -= fmt.closing_indent; + + retval += char_type('}'); + return retval; + } // }}} + + string_type format_dotted_table(const table_type& t, const table_format_info& fmt, // {{{ + const source_location&, std::vector& keys) + { + // lets say we have: `{"a": {"b": {"c": {"d": "foo", "e": "bar"} } }` + // and `a` and `b` are `dotted`. + // + // - in case if `c` is `oneline`: + // ```toml + // a.b.c = {d = "foo", e = "bar"} + // ``` + // + // - in case if and `c` is `dotted`: + // ```toml + // a.b.c.d = "foo" + // a.b.c.e = "bar" + // ``` + + string_type retval; + + for(const auto& kv : t) + { + const auto& key = kv.first; + const auto& val = kv.second; + + keys.push_back(key); + + // format recursive dotted table? + if (val.is_table() && + val.as_table_fmt().fmt != table_format::oneline && + val.as_table_fmt().fmt != table_format::multiline_oneline) + { + retval += this->format_dotted_table(val.as_table(), val.as_table_fmt(), val.location(), keys); + } + else // non-table or inline tables. format normally + { + retval += format_comments(val.comments(), fmt.indent_type); + retval += format_indent(fmt.indent_type); + retval += format_keys(keys).value(); + retval += string_conv(" = "); + this->force_inline_ = true; // sub-table must be inlined + retval += (*this)(val); + retval += char_type('\n'); + this->force_inline_ = false; + } + keys.pop_back(); + } + return retval; + } // }}} + + string_type format_key(const key_type& key) // {{{ + { + if(key.empty()) + { + return string_conv("\"\""); + } + + // check the key can be a bare (unquoted) key + auto loc = detail::make_temporary_location(string_conv(key)); + auto reg = detail::syntax::unquoted_key(this->spec_).scan(loc); + if(reg.is_ok() && loc.eof()) + { + return key; + } + + //if it includes special characters, then format it in a "quoted" key. + string_type formatted = string_conv("\""); + for(const char_type c : key) + { + switch(c) + { + case char_type('\\'): {formatted += string_conv("\\\\"); break;} + case char_type('\"'): {formatted += string_conv("\\\""); break;} + case char_type('\b'): {formatted += string_conv("\\b" ); break;} + case char_type('\t'): {formatted += string_conv("\\t" ); break;} + case char_type('\f'): {formatted += string_conv("\\f" ); break;} + case char_type('\n'): {formatted += string_conv("\\n" ); break;} + case char_type('\r'): {formatted += string_conv("\\r" ); break;} + default : + { + // ASCII ctrl char + if( (char_type(0x00) <= c && c <= char_type(0x08)) || + (char_type(0x0A) <= c && c <= char_type(0x1F)) || + c == char_type(0x7F)) + { + if(spec_.v1_1_0_add_escape_sequence_x) + { + formatted += string_conv("\\x"); + } + else + { + formatted += string_conv("\\u00"); + } + const auto c1 = c / 16; + const auto c2 = c % 16; + formatted += static_cast('0' + c1); + if(c2 < 10) + { + formatted += static_cast('0' + c2); + } + else // 10 <= c2 + { + formatted += static_cast('A' + (c2 - 10)); + } + } + else + { + formatted += c; + } + break; + } + } + } + formatted += string_conv("\""); + return formatted; + } // }}} + cxx::optional format_keys(const std::vector& keys) // {{{ + { + if(keys.empty()) + { + return cxx::make_nullopt(); + } + + string_type formatted; + for(const auto& ky : keys) + { + formatted += format_key(ky); + formatted += char_type('.'); + } + formatted.pop_back(); // remove the last dot '.' + return formatted; + } // }}} + + string_type format_comments(const discard_comments&, const indent_char) const // {{{ + { + return string_conv(""); + } // }}} + string_type format_comments(const preserve_comments& comments, const indent_char indent_type) const // {{{ + { + string_type retval; + for(const auto& c : comments) + { + if(c.empty()) {continue;} + retval += format_indent(indent_type); + if(c.front() != '#') {retval += char_type('#');} + retval += string_conv(c); + if(c.back() != '\n') {retval += char_type('\n');} + } + return retval; + } // }}} + + string_type format_indent(const indent_char indent_type) const // {{{ + { + const auto indent = static_cast((std::max)(0, this->current_indent_)); + if(indent_type == indent_char::space) + { + return string_conv(make_string(indent, ' ')); + } + else if(indent_type == indent_char::tab) + { + return string_conv(make_string(indent, '\t')); + } + else + { + return string_type{}; + } + } // }}} + + std::locale set_locale(std::ostream& os) const + { + return os.imbue(std::locale::classic()); + } + + private: + + spec spec_; + bool force_inline_; // table inside an array without fmt specification + std::int32_t current_indent_; + std::vector keys_; +}; +} // detail + +template +typename basic_value::string_type +format(const basic_value& v, const spec s = spec::default_version()) +{ + detail::serializer ser(s); + return ser(v); +} +template +typename basic_value::string_type +format(const typename basic_value::key_type& k, + const basic_value& v, + const spec s = spec::default_version()) +{ + detail::serializer ser(s); + return ser(k, v); +} +template +typename basic_value::string_type +format(const std::vector::key_type>& ks, + const basic_value& v, + const spec s = spec::default_version()) +{ + detail::serializer ser(s); + return ser(ks, v); +} + +template +std::ostream& operator<<(std::ostream& os, const basic_value& v) +{ + os << format(v); + return os; +} + +} // toml + +#if defined(TOML11_COMPILE_SOURCES) +namespace toml +{ +struct type_config; +struct ordered_type_config; + +extern template typename basic_value::string_type +format(const basic_value&, const spec); + +extern template typename basic_value::string_type +format(const typename basic_value::key_type& k, + const basic_value& v, const spec); + +extern template typename basic_value::string_type +format(const std::vector::key_type>& ks, + const basic_value& v, const spec s); + +extern template typename basic_value::string_type +format(const basic_value&, const spec); + +extern template typename basic_value::string_type +format(const typename basic_value::key_type& k, + const basic_value& v, const spec); + +extern template typename basic_value::string_type +format(const std::vector::key_type>& ks, + const basic_value& v, const spec s); + +namespace detail +{ +extern template class serializer<::toml::type_config>; +extern template class serializer<::toml::ordered_type_config>; +} // detail +} // toml +#endif // TOML11_COMPILE_SOURCES + + +#endif // TOML11_SERIALIZER_HPP +#ifndef TOML11_TOML_HPP +#define TOML11_TOML_HPP + +// The MIT License (MIT) +// +// Copyright (c) 2017-now Toru Niina +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// IWYU pragma: begin_exports +// IWYU pragma: end_exports + +#endif// TOML11_TOML_HPP diff --git a/desktop/main.cpp b/desktop/main.cpp new file mode 100644 index 0000000..601b9a2 --- /dev/null +++ b/desktop/main.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include + +#include "config/config.hpp" +#include "datalake/database.hpp" +#include "datalake/indexer.hpp" + +int main(int argc, char* argv[]) { + QQuickStyle::setStyle(QStringLiteral("org.kde.breeze")); + QGuiApplication app(argc, argv); + app.setApplicationDisplayName("Koto"); + app.setDesktopFileName("com.github.joshstrobl.koto.desktop"); + + QQmlApplicationEngine engine; + + engine.loadFromModule("com.github.joshstrobl.koto", "Main"); + + if (engine.rootObjects().isEmpty()) { return -1; } + + std::thread([]() { + KotoConfig::create(); + KotoDatabase::create(); + + KotoDatabase::instance().connect(); + + // If we needed to bootstrap, index all libraries, otherwise load the database + if (KotoDatabase::instance().requiredBootstrap()) { + indexAllLibraries(); + } else { + KotoDatabase::instance().load(); + std::cout << "===== Summary =====" << std::endl; + for (auto artist : Cartographer::instance().getArtists()) { + std::cout << "Artist: " << artist->getName().toStdString() << std::endl; + for (auto album : artist->getAlbums()) { + std::cout << " Album: " << album->getTitle().toStdString() << std::endl; + for (auto track : album->getTracks()) { std::cout << " Track: " << track->getTitle().toStdString() << std::endl; } + } + } + std::cout << "===== Tracks without albums and/or artists =====" << std::endl; + for (auto track : Cartographer::instance().getTracks()) { + if (track->album_uuid.has_value()) continue; + std::cout << "Track: " << track->getTitle().toStdString() << std::endl; + } + } + }).detach(); + + return app.exec(); +} diff --git a/desktop/qml/HomePage.qml b/desktop/qml/HomePage.qml new file mode 100644 index 0000000..5810f7e --- /dev/null +++ b/desktop/qml/HomePage.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import org.kde.kirigami as Kirigami +import com.github.joshstrobl.koto + +Kirigami.ScrollablePage { + Component { + id: listDelegate + + Controls.ItemDelegate { + required property string name + + text: name + width: ListView.view.width + } + } + ListView { + Layout.fillHeight: true + Layout.fillWidth: true + delegate: listDelegate + model: Cartographer.artists + } +} diff --git a/desktop/qml/Main.qml b/desktop/qml/Main.qml new file mode 100644 index 0000000..6f729b3 --- /dev/null +++ b/desktop/qml/Main.qml @@ -0,0 +1,20 @@ +import org.kde.kirigami as Kirigami + +Kirigami.ApplicationWindow { + id: root + + height: 600 + title: "Koto" + visible: true + width: 1000 + + footer: PlayerBar { + } + globalDrawer: PrimaryNavigation { + windowRef: root + } + + // TODO: Implement an onboarding page + pageStack.initialPage: Root { + } +} diff --git a/desktop/qml/PlayerBar/PlayerBar.qml b/desktop/qml/PlayerBar/PlayerBar.qml new file mode 100644 index 0000000..5072116 --- /dev/null +++ b/desktop/qml/PlayerBar/PlayerBar.qml @@ -0,0 +1,74 @@ +import QtQuick.Controls as Controls +import QtQuick.Layouts +import org.kde.kirigami as Kirigami + +ColumnLayout { + id: playerBar + + Layout.fillWidth: true + Layout.leftMargin: Kirigami.Units.largeSpacing + Layout.rightMargin: Kirigami.Units.largeSpacing + spacing: 4 + + RowLayout { + Layout.fillWidth: true + Layout.margins: Kirigami.Units.largeSpacing + + Controls.Slider { + id: seekSlider + + Layout.fillWidth: true + } + } + RowLayout { + Layout.fillWidth: true + Layout.margins: Kirigami.Units.largeSpacing + Layout.maximumWidth: parent.width - 2 * Kirigami.Units.largeSpacing + Layout.minimumWidth: parent.width - 2 * Kirigami.Units.largeSpacing + + RowLayout { + anchors.left: parent.left + + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.small + icon.name: "media-seek-backward" + } + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.medium + icon.name: "media-playback-start" + icon.width: Kirigami.Units.iconSizes.medium + } + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.small + icon.name: "media-seek-forward" + } + } + RowLayout { + anchors.right: parent.right + + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.small + icon.name: "media-playlist-repeat" + } + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.small + icon.name: "media-playlist-shuffle" + } + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.small + icon.name: "playlist-symbolic" + } + Controls.Button { + flat: true + icon.height: Kirigami.Units.iconSizes.small + icon.name: "audio-volume-medium" + } + } + } +} \ No newline at end of file diff --git a/desktop/qml/PrimaryNavigation.qml b/desktop/qml/PrimaryNavigation.qml new file mode 100644 index 0000000..a689981 --- /dev/null +++ b/desktop/qml/PrimaryNavigation.qml @@ -0,0 +1,105 @@ +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import org.kde.kirigami as Kirigami + +Kirigami.GlobalDrawer { + id: primaryNavigation + + property Kirigami.ApplicationWindow windowRef + + function isMobile(width) { + return width < 800; + } + function onWindowSizeChanged(width) { + const mobile = isMobile(width); + drawerOpen = !mobile; + modal = mobile; + height = mobile ? windowRef.height : windowRef.height - windowRef.footer.height; + } + + collapseButtonVisible: false + drawerOpen: !isMobile() + edge: Qt.LeftEdge + height: parent.height - windowRef.footer.height + modal: false + + actions: [ + Kirigami.Action { + icon.name: "go-home" + text: "Home" + + onTriggered: console.log("Home triggered") + }, + Kirigami.Action { + expandible: true + icon.name: "bookmark" + text: "Audiobooks" + + onTriggered: console.log("Audiobooks triggered") + }, + Kirigami.Action { + expandible: true + icon.name: "emblem-music-symbolic" + text: "Music" + + children: [ + Kirigami.Action { + text: "Local Library" + + onTriggered: console.log("Music Local Library triggered") + }, + Kirigami.Action { + text: "Radio" + + onTriggered: console.log("Music Radio triggered") + } + ] + }, + Kirigami.Action { + expandible: true + icon.name: "application-rss+xml-symbolic" + text: "Podcasts" + + children: [ + Kirigami.Action { + text: "Library" + + onTriggered: console.log("Podcasts Library triggered") + }, + Kirigami.Action { + text: "Find new podcasts" + + onTriggered: console.log("Podcasts Find new podcasts triggered") + } + ] + }, + Kirigami.Action { + expandible: true + icon.name: "music-playlist-symbolic" + text: "Playlists" + + children: [ + Kirigami.Action { + text: "Library" + + onTriggered: console.log("Playlists Library triggered") + } + ] + // TODO: Generate list of playlists + } + ] + header: Kirigami.SearchField { + id: searchEntry + + Layout.topMargin: Kirigami.Units.largeSpacing + placeholderText: qsTr("Search") + } + + Component.onCompleted: { + if (Kirigami.Settings.isMobile) + return; + if (windowRef) + windowRef.onWidthChanged.connect(() => onWindowSizeChanged(windowRef.width)); + } +} diff --git a/desktop/qml/Root.qml b/desktop/qml/Root.qml new file mode 100644 index 0000000..96d72ea --- /dev/null +++ b/desktop/qml/Root.qml @@ -0,0 +1,24 @@ +import QtQuick +import QtQuick.Controls as Controls +import QtQuick.Layouts +import org.kde.kirigami as Kirigami + +Kirigami.Page { + id: rootPage + + ColumnLayout { + id: rootLayout + + anchors.fill: parent + + Controls.StackView { + id: rootStack + + Layout.fillHeight: true + Layout.fillWidth: true + + initialItem: HomePage { + } + } + } +} \ No newline at end of file diff --git a/jsc.cfg b/jsc.cfg deleted file mode 100644 index e2ae868..0000000 --- a/jsc.cfg +++ /dev/null @@ -1,2625 +0,0 @@ -# Uncrustify_d-0.71.0-250-9c62bb00 - -# -# General options -# - -# The type of line endings. -# -# Default: auto -newlines = auto # lf/crlf/cr/auto - -# The original size of tabs in the input. -# -# Default: 8 -input_tab_size = 4 # unsigned number - -# The size of tabs in the output (only used if align_with_tabs=true). -# -# Default: 8 -output_tab_size = 4 # unsigned number - -# The ASCII value of the string escape char, usually 92 (\) or (Pawn) 94 (^). -# -# Default: 92 -string_escape_char = 92 # unsigned number - -# Alternate string escape char (usually only used for Pawn). -# Only works right before the quote char. -string_escape_char2 = 0 # unsigned number - -# Replace tab characters found in string literals with the escape sequence \t -# instead. -string_replace_tab_chars = false # true/false - -# Allow interpreting '>=' and '>>=' as part of a template in code like -# 'void f(list>=val);'. If true, 'assert(x<0 && y>=3)' will be broken. -# Improvements to template detection may make this option obsolete. -tok_split_gte = false # true/false - -# Disable formatting of NL_CONT ('\\n') ended lines (e.g. multiline macros) -disable_processing_nl_cont = false # true/false - -# Specify the marker used in comments to disable processing of part of the -# file. -# The comment should be used alone in one line. -# -# Default: *INDENT-OFF* -disable_processing_cmt = " *INDENT-OFF*" # string - -# Specify the marker used in comments to (re)enable processing in a file. -# The comment should be used alone in one line. -# -# Default: *INDENT-ON* -enable_processing_cmt = " *INDENT-ON*" # string - -# Enable parsing of digraphs. -enable_digraphs = false # true/false - -# Add or remove the UTF-8 BOM (recommend 'remove'). -utf8_bom = ignore # ignore/add/remove/force - -# If the file contains bytes with values between 128 and 255, but is not -# UTF-8, then output as UTF-8. -utf8_byte = false # true/false - -# Force the output encoding to UTF-8. -utf8_force = false # true/false - -# Add or remove space between 'do' and '{'. -sp_do_brace_open = force # ignore/add/remove/force - -# Add or remove space between '}' and 'while'. -sp_brace_close_while = force # ignore/add/remove/force - -# Add or remove space between 'while' and '('. -sp_while_paren_open = force # ignore/add/remove/force - -# -# Spacing options -# - -# Add or remove space around non-assignment symbolic operators ('+', '/', '%', -# '<<', and so forth). -sp_arith = force # ignore/add/remove/force - -# Add or remove space around arithmetic operators '+' and '-'. -# -# Overrides sp_arith. -sp_arith_additive = force # ignore/add/remove/force - -# Add or remove space around assignment operator '=', '+=', etc. -sp_assign = force # ignore/add/remove/force - -# Add or remove space around assignment operator '=' in a prototype. -# -# If set to ignore, use sp_assign. -sp_assign_default = ignore # ignore/add/remove/force - -# Add or remove space in 'NS_ENUM ('. -sp_enum_paren = ignore # ignore/add/remove/force - -# Add or remove space around assignment '=' in enum. -sp_enum_assign = ignore # ignore/add/remove/force - -# Add or remove space before assignment '=' in enum. -# -# Overrides sp_enum_assign. -sp_enum_before_assign = ignore # ignore/add/remove/force - -# Add or remove space after assignment '=' in enum. -# -# Overrides sp_enum_assign. -sp_enum_after_assign = ignore # ignore/add/remove/force - -# Add or remove space around assignment ':' in enum. -sp_enum_colon = ignore # ignore/add/remove/force - -# Add or remove space around preprocessor '##' concatenation operator. -# -# Default: add -sp_pp_concat = add # ignore/add/remove/force - -# Add or remove space after preprocessor '#' stringify operator. -# Also affects the '#@' charizing operator. -sp_pp_stringify = ignore # ignore/add/remove/force - -# Add or remove space before preprocessor '#' stringify operator -# as in '#define x(y) L#y'. -sp_before_pp_stringify = add # ignore/add/remove/force - -# Add or remove space around boolean operators '&&' and '||'. -sp_bool = force # ignore/add/remove/force - -# Add or remove space around compare operator '<', '>', '==', etc. -sp_compare = force # ignore/add/remove/force - -# Add or remove space inside '(' and ')'. -sp_inside_paren = ignore # ignore/add/remove/force - -# Add or remove space between nested parentheses, i.e. '((' vs. ') )'. -sp_paren_paren = remove # ignore/add/remove/force - -# Add or remove space between back-to-back parentheses, i.e. ')(' vs. ') ('. -sp_cparen_oparen = force # ignore/add/remove/force - -# Whether to balance spaces inside nested parentheses. -sp_balance_nested_parens = false # true/false - -# Add or remove space between ')' and '{'. -sp_paren_brace = force # ignore/add/remove/force - -# Add or remove space between nested braces, i.e. '{{' vs '{ {'. -sp_brace_brace = ignore # ignore/add/remove/force - -# Add or remove space before pointer star '*'. -sp_before_ptr_star = force # ignore/add/remove/force - -# Add or remove space before pointer star '*' that isn't followed by a -# variable name. If set to ignore, sp_before_ptr_star is used instead. -sp_before_unnamed_ptr_star = remove # ignore/add/remove/force - -# Add or remove space between pointer stars '*'. -sp_between_ptr_star = remove # ignore/add/remove/force - -# Add or remove space after pointer star '*', if followed by a word. -# -# Overrides sp_type_func. -sp_after_ptr_star = force # ignore/add/remove/force - -# Add or remove space after pointer caret '^', if followed by a word. -sp_after_ptr_block_caret = ignore # ignore/add/remove/force - -# Add or remove space after pointer star '*', if followed by a qualifier. -sp_after_ptr_star_qualifier = force # ignore/add/remove/force - -# Add or remove space after a pointer star '*', if followed by a function -# prototype or function definition. -# -# Overrides sp_after_ptr_star and sp_type_func. -sp_after_ptr_star_func = force # ignore/add/remove/force - -# Add or remove space after a pointer star '*', if followed by an open -# parenthesis, as in 'void* (*)(). -sp_ptr_star_paren = force # ignore/add/remove/force - -# Add or remove space before a pointer star '*', if followed by a function -# prototype or function definition. -sp_before_ptr_star_func = force # ignore/add/remove/force - -# Add or remove space before a reference sign '&'. -sp_before_byref = ignore # ignore/add/remove/force - -# Add or remove space before a reference sign '&' that isn't followed by a -# variable name. If set to ignore, sp_before_byref is used instead. -sp_before_unnamed_byref = ignore # ignore/add/remove/force - -# Add or remove space after reference sign '&', if followed by a word. -# -# Overrides sp_type_func. -sp_after_byref = remove # ignore/add/remove/force - -# Add or remove space after a reference sign '&', if followed by a function -# prototype or function definition. -# -# Overrides sp_after_byref and sp_type_func. -sp_after_byref_func = remove # ignore/add/remove/force - -# Add or remove space before a reference sign '&', if followed by a function -# prototype or function definition. -sp_before_byref_func = remove # ignore/add/remove/force - -# Add or remove space between type and word. In cases where total removal of -# whitespace would be a syntax error, a value of 'remove' is treated the same -# as 'force'. -# -# This also affects some other instances of space following a type that are -# not covered by other options; for example, between the return type and -# parenthesis of a function type template argument, between the type and -# parenthesis of an array parameter, or between 'decltype(...)' and the -# following word. -# -# Default: force -sp_after_type = force # ignore/add/remove/force - -# Add or remove space between 'decltype(...)' and word. -# -# Overrides sp_after_type. -sp_after_decltype = ignore # ignore/add/remove/force - -# Add or remove space between 'template' and '<'. -# If set to ignore, sp_before_angle is used. -sp_template_angle = ignore # ignore/add/remove/force - -# Add or remove space before '<'. -sp_before_angle = ignore # ignore/add/remove/force - -# Add or remove space inside '<' and '>'. -sp_inside_angle = ignore # ignore/add/remove/force - -# Add or remove space inside '<>'. -sp_inside_angle_empty = ignore # ignore/add/remove/force - -# Add or remove space between '>' and ':'. -sp_angle_colon = ignore # ignore/add/remove/force - -# Add or remove space after '>'. -sp_after_angle = ignore # ignore/add/remove/force - -# Add or remove space between '>' and '(' as found in 'new List(foo);'. -sp_angle_paren = remove # ignore/add/remove/force - -# Add or remove space between '>' and '()' as found in 'new List();'. -sp_angle_paren_empty = remove # ignore/add/remove/force - -# Add or remove space between '>' and a word as in 'List m;' or -# 'template static ...'. -sp_angle_word = force # ignore/add/remove/force - -# Add or remove space between '>' and '>' in '>>' (template stuff). -# -# Default: add -sp_angle_shift = add # ignore/add/remove/force - -# (C++11) Permit removal of the space between '>>' in 'foo >'. Note -# that sp_angle_shift cannot remove the space without this option. -sp_permit_cpp11_shift = false # true/false - -# Add or remove space before '(' of control statements ('if', 'for', 'switch', -# 'while', etc.). -sp_before_sparen = force # ignore/add/remove/force - -# Add or remove space inside '(' and ')' of control statements. -sp_inside_sparen = remove # ignore/add/remove/force - -# Add or remove space after '(' of control statements. -# -# Overrides sp_inside_sparen. -sp_inside_sparen_open = remove # ignore/add/remove/force - -# Add or remove space before ')' of control statements. -# -# Overrides sp_inside_sparen. -sp_inside_sparen_close = remove # ignore/add/remove/force - -# Add or remove space after ')' of control statements. -sp_after_sparen = force # ignore/add/remove/force - -# Add or remove space between ')' and '{' of of control statements. -sp_sparen_brace = force # ignore/add/remove/force - -# Add or remove space before empty statement ';' on 'if', 'for' and 'while'. -sp_special_semi = ignore # ignore/add/remove/force - -# Add or remove space before ';'. -# -# Default: remove -sp_before_semi = remove # ignore/add/remove/force - -# Add or remove space before ';' in non-empty 'for' statements. -sp_before_semi_for = remove # ignore/add/remove/force - -# Add or remove space before a semicolon of an empty part of a for statement. -sp_before_semi_for_empty = ignore # ignore/add/remove/force - -# Add or remove space after ';', except when followed by a comment. -# -# Default: add -sp_after_semi = add # ignore/add/remove/force - -# Add or remove space after ';' in non-empty 'for' statements. -# -# Default: force -sp_after_semi_for = force # ignore/add/remove/force - -# Add or remove space after the final semicolon of an empty part of a for -# statement, as in 'for ( ; ; )'. -sp_after_semi_for_empty = ignore # ignore/add/remove/force - -# Add or remove space before '[' (except '[]'). -sp_before_square = ignore # ignore/add/remove/force - -# Add or remove space before '[' for a variable definition. -# -# Default: remove -sp_before_vardef_square = remove # ignore/add/remove/force - -# Add or remove space before '[' for asm block. -sp_before_square_asm_block = ignore # ignore/add/remove/force - -# Add or remove space before '[]'. -sp_before_squares = remove # ignore/add/remove/force - -# Add or remove space inside a non-empty '[' and ']'. -sp_inside_square = remove # ignore/add/remove/force - -# Add or remove space inside '[]'. -sp_inside_square_empty = remove # ignore/add/remove/force - -# Add or remove space after ',', i.e. 'a,b' vs. 'a, b'. -sp_after_comma = force # ignore/add/remove/force - -# Add or remove space before ','. -# -# Default: remove -sp_before_comma = remove # ignore/add/remove/force - -# Add or remove space between an open parenthesis and comma, -# i.e. '(,' vs. '( ,'. -# -# Default: force -sp_paren_comma = force # ignore/add/remove/force - -# Add or remove space before the variadic '...' when preceded by a -# non-punctuator. -sp_before_ellipsis = ignore # ignore/add/remove/force - -# Add or remove space between a type and '...'. -sp_type_ellipsis = ignore # ignore/add/remove/force - -# Add or remove space between ')' and '...'. -sp_paren_ellipsis = ignore # ignore/add/remove/force - -# Add or remove space between ')' and a qualifier such as 'const'. -sp_paren_qualifier = ignore # ignore/add/remove/force - -# Add or remove space between ')' and 'noexcept'. -sp_paren_noexcept = ignore # ignore/add/remove/force - -# Add or remove space after class ':'. -sp_after_class_colon = ignore # ignore/add/remove/force - -# Add or remove space before class ':'. -sp_before_class_colon = ignore # ignore/add/remove/force - -# Add or remove space after class constructor ':'. -sp_after_constr_colon = ignore # ignore/add/remove/force - -# Add or remove space before class constructor ':'. -sp_before_constr_colon = ignore # ignore/add/remove/force - -# Add or remove space before case ':'. -# -# Default: remove -sp_before_case_colon = remove # ignore/add/remove/force - -# Add or remove space between 'operator' and operator sign. -sp_after_operator = ignore # ignore/add/remove/force - -# Add or remove space between the operator symbol and the open parenthesis, as -# in 'operator ++('. -sp_after_operator_sym = force # ignore/add/remove/force - -# Overrides sp_after_operator_sym when the operator has no arguments, as in -# 'operator *()'. -sp_after_operator_sym_empty = ignore # ignore/add/remove/force - -# Add or remove space after C/D cast, i.e. 'cast(int)a' vs. 'cast(int) a' or -# '(int)a' vs. '(int) a'. -sp_after_cast = force # ignore/add/remove/force - -# Add or remove spaces inside cast parentheses. -sp_inside_paren_cast = remove # ignore/add/remove/force - -# Add or remove space between 'sizeof' and '('. -sp_sizeof_paren = remove # ignore/add/remove/force - -# Add or remove space between 'sizeof' and '...'. -sp_sizeof_ellipsis = ignore # ignore/add/remove/force - -# Add or remove space between 'sizeof...' and '('. -sp_sizeof_ellipsis_paren = remove # ignore/add/remove/force - -# Add or remove space between 'decltype' and '('. -sp_decltype_paren = force # ignore/add/remove/force - -# Add or remove space inside enum '{' and '}'. -sp_inside_braces_enum = force # ignore/add/remove/force - -# Add or remove space inside struct/union '{' and '}'. -sp_inside_braces_struct = force # ignore/add/remove/force - -# Add or remove space after open brace in an unnamed temporary -# direct-list-initialization. -sp_after_type_brace_init_lst_open = ignore # ignore/add/remove/force - -# Add or remove space before close brace in an unnamed temporary -# direct-list-initialization. -sp_before_type_brace_init_lst_close = ignore # ignore/add/remove/force - -# Add or remove space inside an unnamed temporary direct-list-initialization. -sp_inside_type_brace_init_lst = ignore # ignore/add/remove/force - -# Add or remove space inside '{' and '}'. -sp_inside_braces = force # ignore/add/remove/force - -# Add or remove space inside '{}'. -sp_inside_braces_empty = force # ignore/add/remove/force - -# Add or remove space around trailing return operator '->'. -sp_trailing_return = remove # ignore/add/remove/force - -# Add or remove space between return type and function name. A minimum of 1 -# is forced except for pointer return types. -sp_type_func = force # ignore/add/remove/force - -# Add or remove space between type and open brace of an unnamed temporary -# direct-list-initialization. -sp_type_brace_init_lst = ignore # ignore/add/remove/force - -# Add or remove space between function name and '(' on function declaration. -sp_func_proto_paren = remove # ignore/add/remove/force - -# Add or remove space between function name and '()' on function declaration -# without parameters. -sp_func_proto_paren_empty = remove # ignore/add/remove/force - -# Add or remove space between function name and '(' with a typedef specifier. -sp_func_type_paren = remove # ignore/add/remove/force - -# Add or remove space between alias name and '(' of a non-pointer function type typedef. -sp_func_def_paren = ignore # ignore/add/remove/force - -# Add or remove space between function name and '()' on function definition -# without parameters. -sp_func_def_paren_empty = remove # ignore/add/remove/force - -# Add or remove space inside empty function '()'. -# Overrides sp_after_angle unless use_sp_after_angle_always is set to true. -sp_inside_fparens = remove # ignore/add/remove/force - -# Add or remove space inside function '(' and ')'. -sp_inside_fparen = ignore # ignore/add/remove/force - -# Add or remove space inside the first parentheses in a function type, as in -# 'void (*x)(...)'. -sp_inside_tparen = ignore # ignore/add/remove/force - -# Add or remove space between the ')' and '(' in a function type, as in -# 'void (*x)(...)'. -sp_after_tparen_close = force # ignore/add/remove/force - -# Add or remove space between ']' and '(' when part of a function call. -sp_square_fparen = force # ignore/add/remove/force - -# Add or remove space between ')' and '{' of function. -sp_fparen_brace = force # ignore/add/remove/force - -# Add or remove space between ')' and '{' of a function call in object -# initialization. -# -# Overrides sp_fparen_brace. -sp_fparen_brace_initializer = force # ignore/add/remove/force - -# Add or remove space between function name and '(' on function calls. -sp_func_call_paren = remove # ignore/add/remove/force - -# Add or remove space between function name and '()' on function calls without -# parameters. If set to ignore (the default), sp_func_call_paren is used. -sp_func_call_paren_empty = remove # ignore/add/remove/force - -# Add or remove space between the user function name and '(' on function -# calls. You need to set a keyword to be a user function in the config file, -# like: -# set func_call_user tr _ i18n -sp_func_call_user_paren = ignore # ignore/add/remove/force - -# Add or remove space inside user function '(' and ')'. -sp_func_call_user_inside_fparen = remove # ignore/add/remove/force - -# Add or remove space between nested parentheses with user functions, -# i.e. '((' vs. '( ('. -sp_func_call_user_paren_paren = remove # ignore/add/remove/force - -# Add or remove space between a constructor/destructor and the open -# parenthesis. -sp_func_class_paren = remove # ignore/add/remove/force - -# Add or remove space between a constructor without parameters or destructor -# and '()'. -sp_func_class_paren_empty = remove # ignore/add/remove/force - -# Add or remove space between 'return' and '('. -sp_return_paren = force # ignore/add/remove/force - -# Add or remove space between 'return' and '{'. -sp_return_brace = force # ignore/add/remove/force - -# Add or remove space between '__attribute__' and '('. -sp_attribute_paren = ignore # ignore/add/remove/force - -# Add or remove space between 'defined' and '(' in '#if defined (FOO)'. -sp_defined_paren = force # ignore/add/remove/force - -# Add or remove space between 'throw' and '(' in 'throw (something)'. -sp_throw_paren = force # ignore/add/remove/force - -# Add or remove space between 'throw' and anything other than '(' as in -# '@throw [...];'. -sp_after_throw = force # ignore/add/remove/force - -# Add or remove space between 'catch' and '(' in 'catch (something) { }'. -# If set to ignore, sp_before_sparen is used. -sp_catch_paren = force # ignore/add/remove/force - -# Add or remove space between 'super' and '(' in 'super (something)'. -# -# Default: remove -sp_super_paren = remove # ignore/add/remove/force - -# Add or remove space between 'this' and '(' in 'this (something)'. -# -# Default: remove -sp_this_paren = remove # ignore/add/remove/force - -# Add or remove space between a macro name and its definition. -sp_macro = ignore # ignore/add/remove/force - -# Add or remove space between a macro function ')' and its definition. -sp_macro_func = ignore # ignore/add/remove/force - -# Add or remove space between 'else' and '{' if on the same line. -sp_else_brace = force # ignore/add/remove/force - -# Add or remove space between '}' and 'else' if on the same line. -sp_brace_else = force # ignore/add/remove/force - -# Add or remove space between '}' and the name of a typedef on the same line. -sp_brace_typedef = ignore # ignore/add/remove/force - -# Add or remove space before the '{' of a 'catch' statement, if the '{' and -# 'catch' are on the same line, as in 'catch (decl) {'. -sp_catch_brace = force # ignore/add/remove/force - -# Add or remove space between '}' and 'catch' if on the same line. -sp_brace_catch = force # ignore/add/remove/force - -# Add or remove space between 'finally' and '{' if on the same line. -sp_finally_brace = force # ignore/add/remove/force - -# Add or remove space between '}' and 'finally' if on the same line. -sp_brace_finally = force # ignore/add/remove/force - -# Add or remove space between 'try' and '{' if on the same line. -sp_try_brace = force # ignore/add/remove/force - -# Add or remove space between get/set and '{' if on the same line. -sp_getset_brace = force # ignore/add/remove/force - -# Add or remove space between a variable and '{' for a namespace. -# -# Default: add -sp_word_brace_ns = force # ignore/add/remove/force - -# Add or remove space before the '::' operator. -sp_before_dc = ignore # ignore/add/remove/force - -# Add or remove space after the '::' operator. -sp_after_dc = ignore # ignore/add/remove/force - -# Add or remove space after the '!' (not) unary operator. -# -# Default: remove -sp_not = remove # ignore/add/remove/force - -# Add or remove space after the '~' (invert) unary operator. -# -# Default: remove -sp_inv = remove # ignore/add/remove/force - -# Add or remove space after the '&' (address-of) unary operator. This does not -# affect the spacing after a '&' that is part of a type. -# -# Default: remove -sp_addr = remove # ignore/add/remove/force - -# Add or remove space around the '.' or '->' operators. -# -# Default: remove -sp_member = remove # ignore/add/remove/force - -# Add or remove space after the '*' (dereference) unary operator. This does -# not affect the spacing after a '*' that is part of a type. -# -# Default: remove -sp_deref = remove # ignore/add/remove/force - -# Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. -# -# Default: remove -sp_sign = remove # ignore/add/remove/force - -# Add or remove space between '++' and '--' the word to which it is being -# applied, as in '(--x)' or 'y++;'. -# -# Default: remove -sp_incdec = remove # ignore/add/remove/force - -# Add or remove space before a backslash-newline at the end of a line. -# -# Default: add -sp_before_nl_cont = add # ignore/add/remove/force - -# Add or remove space around the ':' in 'b ? t : f'. -sp_cond_colon = force # ignore/add/remove/force - -# Add or remove space around the '?' in 'b ? t : f'. -sp_cond_question = force # ignore/add/remove/force - -# Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make -# sense here. -sp_case_label = force # ignore/add/remove/force - -# Add or remove space between #else or #endif and a trailing comment. -sp_endif_cmt = remove # ignore/add/remove/force - -# Add or remove space after 'new', 'delete' and 'delete[]'. -sp_after_new = ignore # ignore/add/remove/force - -# Add or remove space between 'new' and '(' in 'new()'. -sp_between_new_paren = remove # ignore/add/remove/force - -# Add or remove space between ')' and type in 'new(foo) BAR'. -sp_after_newop_paren = force # ignore/add/remove/force - -# Add or remove space inside parenthesis of the new operator -# as in 'new(foo) BAR'. -sp_inside_newop_paren = remove # ignore/add/remove/force - -# Add or remove space before a trailing or embedded comment. -sp_before_tr_emb_cmt = force # ignore/add/remove/force - -# Number of spaces before a trailing or embedded comment. -sp_num_before_tr_emb_cmt = 1 # unsigned number - -# If true, vbrace tokens are dropped to the previous token and skipped. -sp_skip_vbrace_tokens = false # true/false - -# Add or remove space after 'noexcept'. -sp_after_noexcept = ignore # ignore/add/remove/force - -# Add or remove space after '_'. -sp_vala_after_translation = ignore # ignore/add/remove/force - -# If true, a is inserted after #define. -force_tab_after_define = false # true/false - -# -# Indenting options -# - -# The number of columns to indent per level. Usually 2, 3, 4, or 8. -# -# Default: 8 -indent_columns = 4 # unsigned number - -# The continuation indent. If non-zero, this overrides the indent of '(', '[' -# and '=' continuation indents. Negative values are OK; negative value is -# absolute and not increased for each '(' or '[' level. -# -# For FreeBSD, this is set to 4. -indent_continue = 0 # number - -# The continuation indent, only for class header line(s). If non-zero, this -# overrides the indent of 'class' continuation indents. -indent_continue_class_head = 0 # unsigned number - -# Whether to indent empty lines (i.e. lines which contain only spaces before -# the newline character). -indent_single_newlines = false # true/false - -# The continuation indent for func_*_param if they are true. If non-zero, this -# overrides the indent. -indent_param = 0 # unsigned number - -# How to use tabs when indenting code. -# -# 0: Spaces only -# 1: Indent with tabs to brace level, align with spaces (default) -# 2: Indent and align with tabs, using spaces when not on a tabstop -# -# Default: 1 -indent_with_tabs = 2 # unsigned number - -# Whether to indent comments that are not at a brace level with tabs on a -# tabstop. Requires indent_with_tabs=2. If false, will use spaces. -indent_cmt_with_tabs = false # true/false - -# Whether to indent strings broken by '\' so that they line up. -indent_align_string = false # true/false - -# The number of spaces to indent multi-line XML strings. -# Requires indent_align_string=true. -indent_xml_string = 0 # unsigned number - -# Spaces to indent '{' from level. -indent_brace = 0 # unsigned number - -# Whether braces are indented to the body level. -indent_braces = false # true/false - -# Whether to disable indenting function braces if indent_braces=true. -indent_braces_no_func = false # true/false - -# Whether to disable indenting class braces if indent_braces=true. -indent_braces_no_class = false # true/false - -# Whether to disable indenting struct braces if indent_braces=true. -indent_braces_no_struct = false # true/false - -# Whether to indent based on the size of the brace parent, -# i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. -indent_brace_parent = false # true/false - -# Whether to indent based on the open parenthesis instead of the open brace -# in '({\n'. -indent_paren_open_brace = false # true/false - -# Whether to indent the body of a 'namespace'. -indent_namespace = true # true/false - -# Whether to indent only the first namespace, and not any nested namespaces. -# Requires indent_namespace=true. -indent_namespace_single_indent = false # true/false - -# The number of spaces to indent a namespace block. -# If set to zero, use the value indent_columns -indent_namespace_level = 0 # unsigned number - -# If the body of the namespace is longer than this number, it won't be -# indented. Requires indent_namespace=true. 0 means no limit. -indent_namespace_limit = 0 # unsigned number - -# Whether the 'extern "C"' body is indented. -indent_extern = false # true/false - -# Whether the 'class' body is indented. -indent_class = true # true/false - -# Whether to indent the stuff after a leading base class colon. -indent_class_colon = false # true/false - -# Whether to indent based on a class colon instead of the stuff after the -# colon. Requires indent_class_colon=true. -indent_class_on_colon = false # true/false - -# Whether to indent the stuff after a leading class initializer colon. -indent_constr_colon = false # true/false - -# Virtual indent from the ':' for member initializers. -# -# Default: 2 -indent_ctor_init_leading = 2 # unsigned number - -# Additional indent for constructor initializer list. -# Negative values decrease indent down to the first column. -indent_ctor_init = 0 # number - -# Whether to indent 'if' following 'else' as a new block under the 'else'. -# If false, 'else\nif' is treated as 'else if' for indenting purposes. -indent_else_if = false # true/false - -# Amount to indent variable declarations after a open brace. -# -# <0: Relative -# >=0: Absolute -indent_var_def_blk = 0 # number - -# Whether to indent continued variable declarations instead of aligning. -indent_var_def_cont = false # true/false - -# Whether to indent continued shift expressions ('<<' and '>>') instead of -# aligning. Set align_left_shift=false when enabling this. -indent_shift = false # true/false - -# Whether to force indentation of function definitions to start in column 1. -indent_func_def_force_col1 = false # true/false - -# Whether to indent continued function call parameters one indent level, -# rather than aligning parameters under the open parenthesis. -indent_func_call_param = false # true/false - -# Whether to indent continued function definition parameters one indent level, -# rather than aligning parameters under the open parenthesis. -indent_func_def_param = true # true/false - -# for function definitions, only if indent_func_def_param is false -# Allows to align params when appropriate and indent them when not -# behave as if it was true if paren position is more than this value -# if paren position is more than the option value -indent_func_def_param_paren_pos_threshold = 0 # unsigned number - -# Whether to indent continued function call prototype one indent level, -# rather than aligning parameters under the open parenthesis. -indent_func_proto_param = true # true/false - -# Whether to indent continued function call declaration one indent level, -# rather than aligning parameters under the open parenthesis. -indent_func_class_param = true # true/false - -# Whether to indent continued class variable constructors one indent level, -# rather than aligning parameters under the open parenthesis. -indent_func_ctor_var_param = false # true/false - -# Whether to indent continued template parameter list one indent level, -# rather than aligning parameters under the open parenthesis. -indent_template_param = false # true/false - -# Double the indent for indent_func_xxx_param options. -# Use both values of the options indent_columns and indent_param. -indent_func_param_double = false # true/false - -# Indentation column for standalone 'const' qualifier on a function -# prototype. -indent_func_const = 0 # unsigned number - -# Indentation column for standalone 'throw' qualifier on a function -# prototype. -indent_func_throw = 0 # unsigned number - -# How to indent within a macro followed by a brace on the same line -# This allows reducing the indent in macros that have (for example) -# `do { ... } while (0)` blocks bracketing them. -# -# true: add an indent for the brace on the same line as the macro -# false: do not add an indent for the brace on the same line as the macro -# -# Default: true -indent_macro_brace = true # true/false - -# The number of spaces to indent a continued '->' or '.'. -# Usually set to 0, 1, or indent_columns. -indent_member = 0 # unsigned number - -# Whether lines broken at '.' or '->' should be indented by a single indent. -# The indent_member option will not be effective if this is set to true. -indent_member_single = false # true/false - -# Spaces to indent single line ('//') comments on lines before code. -indent_sing_line_comments = 0 # unsigned number - -# When opening a paren for a control statement (if, for, while, etc), increase -# the indent level by this value. Negative values decrease the indent level. -indent_sparen_extra = 0 # number - -# Whether to indent trailing single line ('//') comments relative to the code -# instead of trying to keep the same absolute column. -indent_relative_single_line_comments = false # true/false - -# Spaces to indent 'case' from 'switch'. Usually 0 or indent_columns. -indent_switch_case = 4 # unsigned number - -# indent 'break' with 'case' from 'switch'. -indent_switch_break_with_case = false # true/false - -# Whether to indent preprocessor statements inside of switch statements. -# -# Default: true -indent_switch_pp = true # true/false - -# Spaces to shift the 'case' line, without affecting any other lines. -# Usually 0. -indent_case_shift = 0 # unsigned number - -# Spaces to indent '{' from 'case'. By default, the brace will appear under -# the 'c' in case. Usually set to 0 or indent_columns. Negative values are OK. -indent_case_brace = 0 # number - -# Whether to indent comments found in first column. -indent_col1_comment = false # true/false - -# Whether to indent multi string literal in first column. -indent_col1_multi_string_literal = false # true/false - -# How to indent goto labels. -# -# >0: Absolute column where 1 is the leftmost column -# <=0: Subtract from brace indent -# -# Default: 1 -indent_label = 1 # number - -# How to indent access specifiers that are followed by a -# colon. -# -# >0: Absolute column where 1 is the leftmost column -# <=0: Subtract from brace indent -# -# Default: 1 -indent_access_spec = 1 # number - -# Whether to indent the code after an access specifier by one level. -# If true, this option forces 'indent_access_spec=0'. -indent_access_spec_body = false # true/false - -# If an open parenthesis is followed by a newline, whether to indent the next -# line so that it lines up after the open parenthesis (not recommended). -indent_paren_nl = false # true/false - -# How to indent a close parenthesis after a newline. -# -# 0: Indent to body level (default) -# 1: Align under the open parenthesis -# 2: Indent to the brace level -indent_paren_close = 2 # unsigned number - -# Whether to indent the open parenthesis of a function definition, -# if the parenthesis is on its own line. -indent_paren_after_func_def = false # true/false - -# Whether to indent the open parenthesis of a function declaration, -# if the parenthesis is on its own line. -indent_paren_after_func_decl = false # true/false - -# Whether to indent the open parenthesis of a function call, -# if the parenthesis is on its own line. -indent_paren_after_func_call = false # true/false - -# Whether to indent a comma when inside a parenthesis. -# If true, aligns under the open parenthesis. -indent_comma_paren = false # true/false - -# Whether to indent a Boolean operator when inside a parenthesis. -# If true, aligns under the open parenthesis. -indent_bool_paren = false # true/false - -# Whether to indent a semicolon when inside a for parenthesis. -# If true, aligns under the open for parenthesis. -indent_semicolon_for_paren = false # true/false - -# Whether to align the first expression to following ones -# if indent_bool_paren=true. -indent_first_bool_expr = false # true/false - -# Whether to align the first expression to following ones -# if indent_semicolon_for_paren=true. -indent_first_for_expr = false # true/false - -# If an open square is followed by a newline, whether to indent the next line -# so that it lines up after the open square (not recommended). -indent_square_nl = false # true/false - -# (ESQL/C) Whether to preserve the relative indent of 'EXEC SQL' bodies. -indent_preserve_sql = false # true/false - -# Whether to align continued statements at the '='. If false or if the '=' is -# followed by a newline, the next line is indent one tab. -# -# Default: true -indent_align_assign = true # true/false - -# If true, the indentation of the chunks after a '=' sequence will be set at -# LHS token indentation column before '='. -indent_off_after_assign = false # true/false - -# Whether to align continued statements at the '('. If false or the '(' is -# followed by a newline, the next line indent is one tab. -# -# Default: true -indent_align_paren = true # true/false - -# When indenting after virtual brace open and newline add further spaces to -# reach this minimum indent. -indent_min_vbrace_open = 0 # unsigned number - -# Whether to add further spaces after regular indent to reach next tabstop -# when indenting after virtual brace open and newline. -indent_vbrace_open_on_tabstop = false # true/false - -# How to indent after a brace followed by another token (not a newline). -# true: indent all contained lines to match the token -# false: indent all contained lines to match the brace -# -# Default: true -indent_token_after_brace = true # true/false - -# How to indent compound literals that are being returned. -# true: add both the indent from return & the compound literal open brace (ie: -# 2 indent levels) -# false: only indent 1 level, don't add the indent for the open brace, only add -# the indent for the return. -# -# Default: true -indent_compound_literal_return = true # true/false - -# How to indent the continuation of ternary operator. -# -# 0: Off (default) -# 1: When the `if_false` is a continuation, indent it under `if_false` -# 2: When the `:` is a continuation, indent it under `?` -indent_ternary_operator = 0 # unsigned number - -# Whether to indent the statments inside ternary operator. -indent_inside_ternary_operator = false # true/false - -# If true, the indentation of the chunks after a `return` sequence will be set at return indentation column. -indent_off_after_return = false # true/false - -# If true, the indentation of the chunks after a `return new` sequence will be set at return indentation column. -indent_off_after_return_new = false # true/false - -# If true, the tokens after return are indented with regular single indentation. By default (false) the indentation is after the return token. -indent_single_after_return = false # true/false - -# Whether to ignore indent and alignment for 'asm' blocks (i.e. assume they -# have their own indentation). -indent_ignore_asm_block = false # true/false - -# Don't indent the close parenthesis of a function definition, -# if the parenthesis is on its own line. -donot_indent_func_def_close_paren = false # true/false - -# -# Newline adding and removing options -# - -# Whether to collapse empty blocks between '{' and '}'. -# If true, overrides nl_inside_empty_func -nl_collapse_empty_body = false # true/false - -# Don't split one-line braced assignments, as in 'foo_t f = { 1, 2 };'. -nl_assign_leave_one_liners = false # true/false - -# Don't split one-line braced statements inside a 'class xx { }' body. -nl_class_leave_one_liners = true # true/false - -# Don't split one-line enums, as in 'enum foo { BAR = 15 };' -nl_enum_leave_one_liners = false # true/false - -# Don't split one-line get or set functions. -nl_getset_leave_one_liners = false # true/false - -# Don't split one-line function definitions, as in 'int foo() { return 0; }'. -# might modify nl_func_type_name -nl_func_leave_one_liners = false # true/false - -# Don't split one-line if/else statements, as in 'if(...) b++;'. -nl_if_leave_one_liners = false # true/false - -# Don't split one-line while statements, as in 'while(...) b++;'. -nl_while_leave_one_liners = false # true/false - -# Don't split one-line for statements, as in 'for(...) b++;'. -nl_for_leave_one_liners = false # true/false - -# Add or remove newlines at the start of the file. -nl_start_of_file = remove # ignore/add/remove/force - -# The minimum number of newlines at the start of the file (only used if -# nl_start_of_file is 'add' or 'force'). -nl_start_of_file_min = 0 # unsigned number - -# Add or remove newline at the end of the file. -nl_end_of_file = ignore # ignore/add/remove/force - -# The minimum number of newlines at the end of the file (only used if -# nl_end_of_file is 'add' or 'force'). -nl_end_of_file_min = 0 # unsigned number - -# Add or remove newline between '=' and '{'. -nl_assign_brace = ignore # ignore/add/remove/force - -# Add or remove newline between '[]' and '{'. -nl_tsquare_brace = ignore # ignore/add/remove/force - -# Add or remove newline between a function call's ')' and '{', as in -# 'list_for_each(item, &list) { }'. -nl_fcall_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'enum' and '{'. -nl_enum_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'enum' and 'class'. -nl_enum_class = remove # ignore/add/remove/force - -# Add or remove newline between 'enum class' and the identifier. -nl_enum_class_identifier = remove # ignore/add/remove/force - -# Add or remove newline between 'enum class' type and ':'. -nl_enum_identifier_colon = remove # ignore/add/remove/force - -# Add or remove newline between 'enum class identifier :' and type. -nl_enum_colon_type = remove # ignore/add/remove/force - -# Add or remove newline between 'struct and '{'. -nl_struct_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'union' and '{'. -nl_union_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'if' and '{'. -nl_if_brace = remove # ignore/add/remove/force - -# Add or remove newline between '}' and 'else'. -nl_brace_else = remove # ignore/add/remove/force - -# Add or remove newline between 'else if' and '{'. If set to ignore, -# nl_if_brace is used instead. -nl_elseif_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'else' and '{'. -nl_else_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'else' and 'if'. -nl_else_if = remove # ignore/add/remove/force - -# Add or remove newline before '{' opening brace -nl_before_opening_brace_func_class_def = remove # ignore/add/remove/force - -# Add or remove newline before 'if'/'else if' closing parenthesis. -nl_before_if_closing_paren = remove # ignore/add/remove/force - -# Add or remove newline between '}' and 'finally'. -nl_brace_finally = remove # ignore/add/remove/force - -# Add or remove newline between 'finally' and '{'. -nl_finally_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'try' and '{'. -nl_try_brace = remove # ignore/add/remove/force - -# Add or remove newline between get/set and '{'. -nl_getset_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'for' and '{'. -nl_for_brace = remove # ignore/add/remove/force - -# Add or remove newline before the '{' of a 'catch' statement, as in -# 'catch (decl) {'. -nl_catch_brace = remove # ignore/add/remove/force - -# Add or remove newline between '}' and 'catch'. -nl_brace_catch = remove # ignore/add/remove/force - -# Add or remove newline between '}' and ']'. -nl_brace_square = remove # ignore/add/remove/force - -# Add or remove newline between '}' and ')' in a function invocation. -nl_brace_fparen = remove # ignore/add/remove/force - -# Add or remove newline between 'while' and '{'. -nl_while_brace = remove # ignore/add/remove/force - -# Add or remove newline between two open or close braces. Due to general -# newline/brace handling, REMOVE may not work. -nl_brace_brace = ignore # ignore/add/remove/force - -# Add or remove newline between 'do' and '{'. -nl_do_brace = remove # ignore/add/remove/force - -# Add or remove newline between '}' and 'while' of 'do' statement. -nl_brace_while = remove # ignore/add/remove/force - -# Add or remove newline between 'switch' and '{'. -nl_switch_brace = remove # ignore/add/remove/force - -# Add or remove newline between 'synchronized' and '{'. -nl_synchronized_brace = remove # ignore/add/remove/force - -# Add a newline between ')' and '{' if the ')' is on a different line than the -# if/for/etc. -# -# Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and -# nl_catch_brace. -nl_multi_line_cond = false # true/false - -# Add a newline after '(' if an if/for/while/switch condition spans multiple -# lines -nl_multi_line_sparen_open = force # ignore/add/remove/force - -# Add a newline before ')' if an if/for/while/switch condition spans multiple -# lines. Overrides nl_before_if_closing_paren if both are specified. -nl_multi_line_sparen_close = force # ignore/add/remove/force - -# Force a newline in a define after the macro name for multi-line defines. -nl_multi_line_define = false # true/false - -# Whether to add a newline before 'case', and a blank line before a 'case' -# statement that follows a ';' or '}'. -nl_before_case = false # true/false - -# Whether to add a newline after a 'case' statement. -nl_after_case = false # true/false - -# Add or remove newline between a case ':' and '{'. -# -# Overrides nl_after_case. -nl_case_colon_brace = remove # ignore/add/remove/force - -# Add or remove newline between ')' and 'throw'. -nl_before_throw = remove # ignore/add/remove/force - -# Add or remove newline between 'namespace' and '{'. -nl_namespace_brace = remove # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template class. -nl_template_class = remove # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template class declaration. -# -# Overrides nl_template_class. -nl_template_class_decl = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<>' of a specialized class declaration. -# -# Overrides nl_template_class_decl. -nl_template_class_decl_special = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template class definition. -# -# Overrides nl_template_class. -nl_template_class_def = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<>' of a specialized class definition. -# -# Overrides nl_template_class_def. -nl_template_class_def_special = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template function. -nl_template_func = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template function -# declaration. -# -# Overrides nl_template_func. -nl_template_func_decl = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<>' of a specialized function -# declaration. -# -# Overrides nl_template_func_decl. -nl_template_func_decl_special = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template function -# definition. -# -# Overrides nl_template_func. -nl_template_func_def = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<>' of a specialized function -# definition. -# -# Overrides nl_template_func_def. -nl_template_func_def_special = ignore # ignore/add/remove/force - -# Add or remove newline after 'template<...>' of a template variable. -nl_template_var = ignore # ignore/add/remove/force - -# Add or remove newline between 'template<...>' and 'using' of a templated -# type alias. -nl_template_using = ignore # ignore/add/remove/force - -# Add or remove newline between 'class' and '{'. -nl_class_brace = remove # ignore/add/remove/force - -# Add or remove newline before or after (depending on pos_class_comma, -# may not be IGNORE) each',' in the base class list. -nl_class_init_args = ignore # ignore/add/remove/force - -# Add or remove newline after each ',' in the constructor member -# initialization. Related to nl_constr_colon, pos_constr_colon and -# pos_constr_comma. -nl_constr_init_args = ignore # ignore/add/remove/force - -# Add or remove newline before first element, after comma, and after last -# element, in 'enum'. -nl_enum_own_lines = force # ignore/add/remove/force - -# Add or remove newline between return type and function name in a function -# definition. -# might be modified by nl_func_leave_one_liners -nl_func_type_name = remove # ignore/add/remove/force - -# Add or remove newline between return type and function name inside a class -# definition. If set to ignore, nl_func_type_name or nl_func_proto_type_name -# is used instead. -nl_func_type_name_class = ignore # ignore/add/remove/force - -# Add or remove newline between class specification and '::' -# in 'void A::f() { }'. Only appears in separate member implementation (does -# not appear with in-line implementation). -nl_func_class_scope = ignore # ignore/add/remove/force - -# Add or remove newline between function scope and name, as in -# 'void A :: f() { }'. -nl_func_scope_name = ignore # ignore/add/remove/force - -# Add or remove newline between return type and function name in a prototype. -nl_func_proto_type_name = ignore # ignore/add/remove/force - -# Add or remove newline between a function name and the opening '(' in the -# declaration. -nl_func_paren = remove # ignore/add/remove/force - -# Overrides nl_func_paren for functions with no parameters. -nl_func_paren_empty = remove # ignore/add/remove/force - -# Add or remove newline between a function name and the opening '(' in the -# definition. -nl_func_def_paren = remove # ignore/add/remove/force - -# Overrides nl_func_def_paren for functions with no parameters. -nl_func_def_paren_empty = remove # ignore/add/remove/force - -# Add or remove newline between a function name and the opening '(' in the -# call. -nl_func_call_paren = ignore # ignore/add/remove/force - -# Overrides nl_func_call_paren for functions with no parameters. -nl_func_call_paren_empty = ignore # ignore/add/remove/force - -# Add or remove newline after '(' in a function declaration. -nl_func_decl_start = force # ignore/add/remove/force - -# Add or remove newline after '(' in a function definition. -nl_func_def_start = force # ignore/add/remove/force - -# Overrides nl_func_decl_start when there is only one parameter. -nl_func_decl_start_single = remove # ignore/add/remove/force - -# Overrides nl_func_def_start when there is only one parameter. -nl_func_def_start_single = remove # ignore/add/remove/force - -# Whether to add a newline after '(' in a function declaration if '(' and ')' -# are in different lines. If false, nl_func_decl_start is used instead. -nl_func_decl_start_multi_line = false # true/false - -# Whether to add a newline after '(' in a function definition if '(' and ')' -# are in different lines. If false, nl_func_def_start is used instead. -nl_func_def_start_multi_line = true # true/false - -# Add or remove newline after each ',' in a function declaration. -nl_func_decl_args = force # ignore/add/remove/force - -# Add or remove newline after each ',' in a function definition. -nl_func_def_args = force # ignore/add/remove/force - -# Add or remove newline after each ',' in a function call. -nl_func_call_args = remove # ignore/add/remove/force - -# Whether to add a newline after each ',' in a function declaration if '(' -# and ')' are in different lines. If false, nl_func_decl_args is used instead. -nl_func_decl_args_multi_line = true # true/false - -# Whether to add a newline after each ',' in a function definition if '(' -# and ')' are in different lines. If false, nl_func_def_args is used instead. -nl_func_def_args_multi_line = true # true/false - -# Add or remove newline before the ')' in a function declaration. -nl_func_decl_end = force # ignore/add/remove/force - -# Add or remove newline before the ')' in a function definition. -nl_func_def_end = force # ignore/add/remove/force - -# Overrides nl_func_decl_end when there is only one parameter. -nl_func_decl_end_single = remove # ignore/add/remove/force - -# Overrides nl_func_def_end when there is only one parameter. -nl_func_def_end_single = remove # ignore/add/remove/force - -# Whether to add a newline before ')' in a function declaration if '(' and ')' -# are in different lines. If false, nl_func_decl_end is used instead. -nl_func_decl_end_multi_line = true # true/false - -# Whether to add a newline before ')' in a function definition if '(' and ')' -# are in different lines. If false, nl_func_def_end is used instead. -nl_func_def_end_multi_line = false # true/false - -# Add or remove newline between '()' in a function declaration. -nl_func_decl_empty = remove # ignore/add/remove/force - -# Add or remove newline between '()' in a function definition. -nl_func_def_empty = remove # ignore/add/remove/force - -# Add or remove newline between '()' in a function call. -nl_func_call_empty = remove # ignore/add/remove/force - -# Whether to add a newline after '(' in a function call, -# has preference over nl_func_call_start_multi_line. -nl_func_call_start = ignore # ignore/add/remove/force - -# Whether to add a newline before ')' in a function call. -nl_func_call_end = ignore # ignore/add/remove/force - -# Whether to add a newline after '(' in a function call if '(' and ')' are in -# different lines. -nl_func_call_start_multi_line = true # true/false - -# Whether to add a newline after each ',' in a function call if '(' and ')' -# are in different lines. -nl_func_call_args_multi_line = true # true/false - -# Whether to add a newline before ')' in a function call if '(' and ')' are in -# different lines. -nl_func_call_end_multi_line = false # true/false - -# Whether to respect nl_func_call_XXX option incase of closure args. -nl_func_call_args_multi_line_ignore_closures = false # true/false - -# Whether to add a newline after '<' of a template parameter list. -nl_template_start = false # true/false - -# Whether to add a newline after each ',' in a template parameter list. -nl_template_args = false # true/false - -# Whether to add a newline before '>' of a template parameter list. -nl_template_end = false # true/false - -# Add or remove newline between function signature and '{'. -nl_fdef_brace = ignore # ignore/add/remove/force - -# Add or remove newline between function signature and '{', -# if signature ends with ')'. Overrides nl_fdef_brace. -nl_fdef_brace_cond = ignore # ignore/add/remove/force - -# Add or remove newline between 'return' and the return expression. -nl_return_expr = remove # ignore/add/remove/force - -# Whether to add a newline after semicolons, except in 'for' statements. -nl_after_semicolon = true # true/false - -# Whether to add a newline after the type in an unnamed temporary -# direct-list-initialization. -nl_type_brace_init_lst = ignore # ignore/add/remove/force - -# Whether to add a newline after the open brace in an unnamed temporary -# direct-list-initialization. -nl_type_brace_init_lst_open = ignore # ignore/add/remove/force - -# Whether to add a newline before the close brace in an unnamed temporary -# direct-list-initialization. -nl_type_brace_init_lst_close = ignore # ignore/add/remove/force - -# Whether to add a newline after '{'. This also adds a newline before the -# matching '}'. -nl_after_brace_open = true # true/false - -# Whether to add a newline between the open brace and a trailing single-line -# comment. Requires nl_after_brace_open=true. -nl_after_brace_open_cmt = false # true/false - -# Whether to add a newline after a virtual brace open with a non-empty body. -# These occur in un-braced if/while/do/for statement bodies. -nl_after_vbrace_open = true # true/false - -# Whether to add a newline after a virtual brace open with an empty body. -# These occur in un-braced if/while/do/for statement bodies. -nl_after_vbrace_open_empty = false # true/false - -# Whether to add a newline after '}'. Does not apply if followed by a -# necessary ';'. -nl_after_brace_close = true # true/false - -# Whether to add a newline after a virtual brace close, -# as in 'if (foo) a++; return;'. -nl_after_vbrace_close = false # true/false - -# Add or remove newline between the close brace and identifier, -# as in 'struct { int a; } b;'. Affects enumerations, unions and -# structures. If set to ignore, uses nl_after_brace_close. -nl_brace_struct_var = ignore # ignore/add/remove/force - -# Whether to alter newlines in '#define' macros. -nl_define_macro = false # true/false - -# Whether to alter newlines between consecutive parenthesis closes. The number -# of closing parentheses in a line will depend on respective open parenthesis -# lines. -nl_squeeze_paren_close = false # true/false - -# Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and -# '#endif'. Does not affect top-level #ifdefs. -nl_squeeze_ifdef = false # true/false - -# Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well. -nl_squeeze_ifdef_top_level = false # true/false - -# Add or remove blank line before 'if'. -nl_before_if = ignore # ignore/add/remove/force - -# Add or remove blank line after 'if' statement. Add/Force work only if the -# next token is not a closing brace. -nl_after_if = ignore # ignore/add/remove/force - -# Add or remove blank line before 'for'. -nl_before_for = ignore # ignore/add/remove/force - -# Add or remove blank line after 'for' statement. -nl_after_for = ignore # ignore/add/remove/force - -# Add or remove blank line before 'while'. -nl_before_while = ignore # ignore/add/remove/force - -# Add or remove blank line after 'while' statement. -nl_after_while = ignore # ignore/add/remove/force - -# Add or remove blank line before 'switch'. -nl_before_switch = ignore # ignore/add/remove/force - -# Add or remove blank line after 'switch' statement. -nl_after_switch = ignore # ignore/add/remove/force - -# Add or remove blank line before 'synchronized'. -nl_before_synchronized = ignore # ignore/add/remove/force - -# Add or remove blank line after 'synchronized' statement. -nl_after_synchronized = ignore # ignore/add/remove/force - -# Add or remove blank line before 'do'. -nl_before_do = ignore # ignore/add/remove/force - -# Add or remove blank line after 'do/while' statement. -nl_after_do = ignore # ignore/add/remove/force - -# Whether to put a blank line before 'return' statements, unless after an open -# brace. -nl_before_return = false # true/false - -# Whether to put a blank line after 'return' statements, unless followed by a -# close brace. -nl_after_return = false # true/false - -# Whether to put a blank line before a member '.' or '->' operators. -nl_before_member = ignore # ignore/add/remove/force - -# Whether to double-space commented-entries in 'struct'/'union'/'enum'. -nl_ds_struct_enum_cmt = false # true/false - -# Whether to force a newline before '}' of a 'struct'/'union'/'enum'. -# (Lower priority than eat_blanks_before_close_brace.) -nl_ds_struct_enum_close_brace = false # true/false - -# Add or remove newline before or after (depending on pos_class_colon) a class -# colon, as in 'class Foo : public Bar'. -nl_class_colon = ignore # ignore/add/remove/force - -# Add or remove newline around a class constructor colon. The exact position -# depends on nl_constr_init_args, pos_constr_colon and pos_constr_comma. -nl_constr_colon = ignore # ignore/add/remove/force - -# Whether to collapse a two-line namespace, like 'namespace foo\n{ decl; }' -# into a single line. If true, prevents other brace newline rules from turning -# such code into four lines. -nl_namespace_two_to_one_liner = false # true/false - -# Whether to remove a newline in simple unbraced if statements, turning them -# into one-liners, as in 'if(b)\n i++;' => 'if(b) i++;'. -nl_create_if_one_liner = false # true/false - -# Whether to remove a newline in simple unbraced for statements, turning them -# into one-liners, as in 'for (...)\n stmt;' => 'for (...) stmt;'. -nl_create_for_one_liner = false # true/false - -# Whether to remove a newline in simple unbraced while statements, turning -# them into one-liners, as in 'while (expr)\n stmt;' => 'while (expr) stmt;'. -nl_create_while_one_liner = false # true/false - -# Whether to collapse a function definition whose body (not counting braces) -# is only one line so that the entire definition (prototype, braces, body) is -# a single line. -nl_create_func_def_one_liner = false # true/false - -# Whether to collapse a function definition whose body (not counting braces) -# is only one line so that the entire definition (prototype, braces, body) is -# a single line. -nl_create_list_one_liner = false # true/false - -# Whether to split one-line simple unbraced if statements into two lines by -# adding a newline, as in 'if(b) i++;'. -nl_split_if_one_liner = false # true/false - -# Whether to split one-line simple unbraced for statements into two lines by -# adding a newline, as in 'for (...) stmt;'. -nl_split_for_one_liner = false # true/false - -# Whether to split one-line simple unbraced while statements into two lines by -# adding a newline, as in 'while (expr) stmt;'. -nl_split_while_one_liner = false # true/false - -# -# Blank line options -# - -# The maximum number of consecutive newlines (3 = 2 blank lines). -nl_max = 0 # unsigned number - -# The maximum number of consecutive newlines in a function. -nl_max_blank_in_func = 0 # unsigned number - -# The number of newlines inside an empty function body. -# This option is overridden by nl_collapse_empty_body=true -nl_inside_empty_func = 0 # unsigned number - -# The number of newlines before a function prototype. -nl_before_func_body_proto = 0 # unsigned number - -# The number of newlines before a multi-line function definition. -nl_before_func_body_def = 0 # unsigned number - -# The number of newlines before a class constructor/destructor prototype. -nl_before_func_class_proto = 0 # unsigned number - -# The number of newlines before a class constructor/destructor definition. -nl_before_func_class_def = 0 # unsigned number - -# The number of newlines after a function prototype. -nl_after_func_proto = 2 # unsigned number - -# The number of newlines after a function prototype, if not followed by -# another function prototype. -nl_after_func_proto_group = 0 # unsigned number - -# The number of newlines after a class constructor/destructor prototype. -nl_after_func_class_proto = 0 # unsigned number - -# The number of newlines after a class constructor/destructor prototype, -# if not followed by another constructor/destructor prototype. -nl_after_func_class_proto_group = 0 # unsigned number - -# Whether one-line method definitions inside a class body should be treated -# as if they were prototypes for the purposes of adding newlines. -# -# Requires nl_class_leave_one_liners=true. Overrides nl_before_func_body_def -# and nl_before_func_class_def for one-liners. -nl_class_leave_one_liner_groups = false # true/false - -# The number of newlines after '}' of a multi-line function body. -nl_after_func_body = 0 # unsigned number - -# The number of newlines after '}' of a multi-line function body in a class -# declaration. Also affects class constructors/destructors. -# -# Overrides nl_after_func_body. -nl_after_func_body_class = 0 # unsigned number - -# The number of newlines after '}' of a single line function body. Also -# affects class constructors/destructors. -# -# Overrides nl_after_func_body and nl_after_func_body_class. -nl_after_func_body_one_liner = 0 # unsigned number - -# The number of blank lines after a block of variable definitions at the top -# of a function body. -# -# 0: No change (default). -nl_func_var_def_blk = 0 # unsigned number - -# The number of newlines before a block of typedefs. If nl_after_access_spec -# is non-zero, that option takes precedence. -# -# 0: No change (default). -nl_typedef_blk_start = 0 # unsigned number - -# The number of newlines after a block of typedefs. -# -# 0: No change (default). -nl_typedef_blk_end = 0 # unsigned number - -# The maximum number of consecutive newlines within a block of typedefs. -# -# 0: No change (default). -nl_typedef_blk_in = 0 # unsigned number - -# The number of newlines before a block of variable definitions not at the top -# of a function body. If nl_after_access_spec is non-zero, that option takes -# precedence. -# -# 0: No change (default). -nl_var_def_blk_start = 0 # unsigned number - -# The number of newlines after a block of variable definitions not at the top -# of a function body. -# -# 0: No change (default). -nl_var_def_blk_end = 0 # unsigned number - -# The maximum number of consecutive newlines within a block of variable -# definitions. -# -# 0: No change (default). -nl_var_def_blk_in = 0 # unsigned number - -# The minimum number of newlines before a multi-line comment. -# Doesn't apply if after a brace open or another multi-line comment. -nl_before_block_comment = 0 # unsigned number - -# The minimum number of newlines before a single-line C comment. -# Doesn't apply if after a brace open or other single-line C comments. -nl_before_c_comment = 0 # unsigned number - -# Whether to force a newline after a multi-line comment. -nl_after_multiline_comment = false # true/false - -# Whether to force a newline after a label's colon. -nl_after_label_colon = false # true/false - -# The number of newlines after '}' or ';' of a struct/enum/union definition. -nl_after_struct = 1 # unsigned number - -# The number of newlines before a class definition. -nl_before_class = 0 # unsigned number - -# The number of newlines after '}' or ';' of a class definition. -nl_after_class = 1 # unsigned number - -# The number of newlines before a namespace. -nl_before_namespace = 0 # unsigned number - -# The number of newlines after '{' of a namespace. This also adds newlines -# before the matching '}'. -# -# 0: Apply eat_blanks_after_open_brace or eat_blanks_before_close_brace if -# applicable, otherwise no change. -# -# Overrides eat_blanks_after_open_brace and eat_blanks_before_close_brace. -nl_inside_namespace = 0 # unsigned number - -# The number of newlines after '}' of a namespace. -nl_after_namespace = 0 # unsigned number - -# The number of newlines before an access specifier label. This also includes -# the Qt-specific 'signals:' and 'slots:'. Will not change the newline count -# if after a brace open. -# -# 0: No change (default). -nl_before_access_spec = 0 # unsigned number - -# The number of newlines after an access specifier label. This also includes -# the Qt-specific 'signals:' and 'slots:'. Will not change the newline count -# if after a brace open. -# -# 0: No change (default). -# -# Overrides nl_typedef_blk_start and nl_var_def_blk_start. -nl_after_access_spec = 0 # unsigned number - -# The number of newlines between a function definition and the function -# comment, as in '// comment\n void foo() {...}'. -# -# 0: No change (default). -nl_comment_func_def = 0 # unsigned number - -# The number of newlines after a try-catch-finally block that isn't followed -# by a brace close. -# -# 0: No change (default). -nl_after_try_catch_finally = 0 # unsigned number - -# Whether to remove blank lines after '{'. -eat_blanks_after_open_brace = false # true/false - -# Whether to remove blank lines before '}'. -eat_blanks_before_close_brace = false # true/false - -# How aggressively to remove extra newlines not in preprocessor. -# -# 0: No change (default) -# 1: Remove most newlines not handled by other config -# 2: Remove all newlines and reformat completely by config -nl_remove_extra_newlines = 0 # unsigned number - -# The number of newlines before a whole-file #ifdef. -# -# 0: No change (default). -nl_before_whole_file_ifdef = 0 # unsigned number - -# The number of newlines after a whole-file #ifdef. -# -# 0: No change (default). -nl_after_whole_file_ifdef = 0 # unsigned number - -# The number of newlines before a whole-file #endif. -# -# 0: No change (default). -nl_before_whole_file_endif = 0 # unsigned number - -# The number of newlines after a whole-file #endif. -# -# 0: No change (default). -nl_after_whole_file_endif = 0 # unsigned number - -# -# Positioning options -# - -# The position of arithmetic operators in wrapped expressions. -pos_arith = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of assignment in wrapped expressions. Do not affect '=' -# followed by '{'. -pos_assign = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of Boolean operators in wrapped expressions. -pos_bool = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of comparison operators in wrapped expressions. -pos_compare = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of conditional operators, as in the '?' and ':' of -# 'expr ? stmt : stmt', in wrapped expressions. -pos_conditional = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of the comma in wrapped expressions. -pos_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of the comma in enum entries. -pos_enum_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of the comma in the base class list if there is more than one -# line. Affects nl_class_init_args. -pos_class_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of the comma in the constructor initialization list. -# Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon. -pos_constr_comma = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of trailing/leading class colon, between class and base class -# list. Affects nl_class_colon. -pos_class_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of colons between constructor and member initialization. -# Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma. -pos_constr_colon = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# The position of shift operators in wrapped expressions. -pos_shift = ignore # ignore/break/force/lead/trail/join/lead_break/lead_force/trail_break/trail_force - -# -# Line splitting options -# - -# Try to limit code width to N columns. -code_width = 0 # unsigned number - -# Whether to fully split long 'for' statements at semi-colons. -ls_for_split_full = false # true/false - -# Whether to fully split long function prototypes/calls at commas. -# The option ls_code_width has priority over the option ls_func_split_full. -ls_func_split_full = false # true/false - -# Whether to split lines as close to code_width as possible and ignore some -# groupings. -# The option ls_code_width has priority over the option ls_func_split_full. -ls_code_width = false # true/false - -# -# Code alignment options (not left column spaces/tabs) -# - -# Whether to keep non-indenting tabs. -align_keep_tabs = false # true/false - -# Whether to use tabs for aligning. -align_with_tabs = false # true/false - -# Whether to bump out to the next tab when aligning. -align_on_tabstop = false # true/false - -# Whether to right-align numbers. -align_number_right = false # true/false - -# Whether to keep whitespace not required for alignment. -align_keep_extra_space = false # true/false - -# Whether to align variable definitions in prototypes and functions. -align_func_params = false # true/false - -# The span for aligning parameter definitions in function on parameter name. -# -# 0: Don't align (default). -align_func_params_span = 0 # unsigned number - -# The threshold for aligning function parameter definitions. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_func_params_thresh = 0 # number - -# The gap for aligning function parameter definitions. -align_func_params_gap = 0 # unsigned number - -# The span for aligning constructor value. -# -# 0: Don't align (default). -align_constr_value_span = 0 # unsigned number - -# The threshold for aligning constructor value. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_constr_value_thresh = 0 # number - -# The gap for aligning constructor value. -align_constr_value_gap = 0 # unsigned number - -# Whether to align parameters in single-line functions that have the same -# name. The function names must already be aligned with each other. -align_same_func_call_params = false # true/false - -# The span for aligning function-call parameters for single line functions. -# -# 0: Don't align (default). -align_same_func_call_params_span = 0 # unsigned number - -# The threshold for aligning function-call parameters for single line -# functions. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_same_func_call_params_thresh = 0 # number - -# The span for aligning variable definitions. -# -# 0: Don't align (default). -align_var_def_span = 0 # unsigned number - -# How to consider (or treat) the '*' in the alignment of variable definitions. -# -# 0: Part of the type 'void * foo;' (default) -# 1: Part of the variable 'void *foo;' -# 2: Dangling 'void *foo;' -# Dangling: the '*' will not be taken into account when aligning. -align_var_def_star_style = 0 # unsigned number - -# How to consider (or treat) the '&' in the alignment of variable definitions. -# -# 0: Part of the type 'long & foo;' (default) -# 1: Part of the variable 'long &foo;' -# 2: Dangling 'long &foo;' -# Dangling: the '&' will not be taken into account when aligning. -align_var_def_amp_style = 1 # unsigned number - -# The threshold for aligning variable definitions. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_var_def_thresh = 0 # number - -# The gap for aligning variable definitions. -align_var_def_gap = 0 # unsigned number - -# Whether to align the colon in struct bit fields. -align_var_def_colon = false # true/false - -# The gap for aligning the colon in struct bit fields. -align_var_def_colon_gap = 0 # unsigned number - -# Whether to align any attribute after the variable name. -align_var_def_attribute = false # true/false - -# Whether to align inline struct/enum/union variable definitions. -align_var_def_inline = false # true/false - -# The span for aligning on '=' in assignments. -# -# 0: Don't align (default). -align_assign_span = 0 # unsigned number - -# The span for aligning on '=' in function prototype modifier. -# -# 0: Don't align (default). -align_assign_func_proto_span = 0 # unsigned number - -# The threshold for aligning on '=' in assignments. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_assign_thresh = 0 # number - -# How to apply align_assign_span to function declaration "assignments", i.e. -# 'virtual void foo() = 0' or '~foo() = {default|delete}'. -# -# 0: Align with other assignments (default) -# 1: Align with each other, ignoring regular assignments -# 2: Don't align -align_assign_decl_func = 0 # unsigned number - -# The span for aligning on '=' in enums. -# -# 0: Don't align (default). -align_enum_equ_span = 0 # unsigned number - -# The threshold for aligning on '=' in enums. -# Use a negative number for absolute thresholds. -# -# 0: no limit (default). -align_enum_equ_thresh = 0 # number - -# The span for aligning class member definitions. -# -# 0: Don't align (default). -align_var_class_span = 0 # unsigned number - -# The threshold for aligning class member definitions. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_var_class_thresh = 0 # number - -# The gap for aligning class member definitions. -align_var_class_gap = 0 # unsigned number - -# The span for aligning struct/union member definitions. -# -# 0: Don't align (default). -align_var_struct_span = 0 # unsigned number - -# The threshold for aligning struct/union member definitions. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_var_struct_thresh = 0 # number - -# The gap for aligning struct/union member definitions. -align_var_struct_gap = 0 # unsigned number - -# The span for aligning struct initializer values. -# -# 0: Don't align (default). -align_struct_init_span = 0 # unsigned number - -# The span for aligning single-line typedefs. -# -# 0: Don't align (default). -align_typedef_span = 0 # unsigned number - -# The minimum space between the type and the synonym of a typedef. -align_typedef_gap = 0 # unsigned number - -# How to align typedef'd functions with other typedefs. -# -# 0: Don't mix them at all (default) -# 1: Align the open parenthesis with the types -# 2: Align the function type name with the other type names -align_typedef_func = 0 # unsigned number - -# How to consider (or treat) the '*' in the alignment of typedefs. -# -# 0: Part of the typedef type, 'typedef int * pint;' (default) -# 1: Part of type name: 'typedef int *pint;' -# 2: Dangling: 'typedef int *pint;' -# Dangling: the '*' will not be taken into account when aligning. -align_typedef_star_style = 0 # unsigned number - -# How to consider (or treat) the '&' in the alignment of typedefs. -# -# 0: Part of the typedef type, 'typedef int & intref;' (default) -# 1: Part of type name: 'typedef int &intref;' -# 2: Dangling: 'typedef int &intref;' -# Dangling: the '&' will not be taken into account when aligning. -align_typedef_amp_style = 0 # unsigned number - -# The span for aligning comments that end lines. -# -# 0: Don't align (default). -align_right_cmt_span = 0 # unsigned number - -# Minimum number of columns between preceding text and a trailing comment in -# order for the comment to qualify for being aligned. Must be non-zero to have -# an effect. -align_right_cmt_gap = 0 # unsigned number - -# If aligning comments, whether to mix with comments after '}' and #endif with -# less than three spaces before the comment. -align_right_cmt_mix = false # true/false - -# Whether to only align trailing comments that are at the same brace level. -align_right_cmt_same_level = false # true/false - -# Minimum column at which to align trailing comments. Comments which are -# aligned beyond this column, but which can be aligned in a lesser column, -# may be "pulled in". -# -# 0: Ignore (default). -align_right_cmt_at_col = 0 # unsigned number - -# The span for aligning function prototypes. -# -# 0: Don't align (default). -align_func_proto_span = 0 # unsigned number - -# The threshold for aligning function prototypes. -# Use a negative number for absolute thresholds. -# -# 0: No limit (default). -align_func_proto_thresh = 0 # number - -# Minimum gap between the return type and the function name. -align_func_proto_gap = 0 # unsigned number - -# Whether to align function prototypes on the 'operator' keyword instead of -# what follows. -align_on_operator = false # true/false - -# Whether to mix aligning prototype and variable declarations. If true, -# align_var_def_XXX options are used instead of align_func_proto_XXX options. -align_mix_var_proto = false # true/false - -# Whether to align single-line functions with function prototypes. -# Uses align_func_proto_span. -align_single_line_func = false # true/false - -# Whether to align the open brace of single-line functions. -# Requires align_single_line_func=true. Uses align_func_proto_span. -align_single_line_brace = false # true/false - -# Gap for align_single_line_brace. -align_single_line_brace_gap = 0 # unsigned number - -# Whether to align macros wrapped with a backslash and a newline. This will -# not work right if the macro contains a multi-line comment. -align_nl_cont = false # true/false - -# Whether to align macro functions and variables together. -align_pp_define_together = false # true/false - -# The span for aligning on '#define' bodies. -# -# =0: Don't align (default) -# >0: Number of lines (including comments) between blocks -align_pp_define_span = 0 # unsigned number - -# The minimum space between label and value of a preprocessor define. -align_pp_define_gap = 0 # unsigned number - -# Whether to align lines that start with '<<' with previous '<<'. -# -# Default: true -align_left_shift = true # true/false - -# Whether to align comma-separated statements following '<<' (as used to -# initialize Eigen matrices). -align_eigen_comma_init = false # true/false - -# Whether to align text after 'asm volatile ()' colons. -align_asm_colon = false # true/false - -# -# Comment modification options -# - -# Try to wrap comments at N columns. -cmt_width = 0 # unsigned number - -# How to reflow comments. -# -# 0: No reflowing (apart from the line wrapping due to cmt_width) (default) -# 1: No touching at all -# 2: Full reflow -cmt_reflow_mode = 0 # unsigned number - -# Whether to convert all tabs to spaces in comments. If false, tabs in -# comments are left alone, unless used for indenting. -cmt_convert_tab_to_spaces = false # true/false - -# Whether to apply changes to multi-line comments, including cmt_width, -# keyword substitution and leading chars. -# -# Default: true -cmt_indent_multi = true # true/false - -# Whether to group c-comments that look like they are in a block. -cmt_c_group = false # true/false - -# Whether to put an empty '/*' on the first line of the combined c-comment. -cmt_c_nl_start = false # true/false - -# Whether to add a newline before the closing '*/' of the combined c-comment. -cmt_c_nl_end = false # true/false - -# Whether to put a star on subsequent comment lines. -cmt_star_cont = false # true/false - -# The number of spaces to insert at the start of subsequent comment lines. -cmt_sp_before_star_cont = 0 # unsigned number - -# The number of spaces to insert after the star on subsequent comment lines. -cmt_sp_after_star_cont = 0 # unsigned number - -# For multi-line comments with a '*' lead, remove leading spaces if the first -# and last lines of the comment are the same length. -# -# Default: true -cmt_multi_check_last = true # true/false - -# For multi-line comments with a '*' lead, remove leading spaces if the first -# and last lines of the comment are the same length AND if the length is -# bigger as the first_len minimum. -# -# Default: 4 -cmt_multi_first_len_minimum = 4 # unsigned number - -# Path to a file that contains text to insert at the beginning of a file if -# the file doesn't start with a C/C++ comment. If the inserted text contains -# '$(filename)', that will be replaced with the current file's name. -cmt_insert_file_header = "" # string - -# Path to a file that contains text to insert at the end of a file if the -# file doesn't end with a C/C++ comment. If the inserted text contains -# '$(filename)', that will be replaced with the current file's name. -cmt_insert_file_footer = "" # string - -# Path to a file that contains text to insert before a function definition if -# the function isn't preceded by a C/C++ comment. If the inserted text -# contains '$(function)', '$(javaparam)' or '$(fclass)', these will be -# replaced with, respectively, the name of the function, the javadoc '@param' -# and '@return' stuff, or the name of the class to which the member function -# belongs. -cmt_insert_func_header = "" # string - -# Path to a file that contains text to insert before a class if the class -# isn't preceded by a C/C++ comment. If the inserted text contains '$(class)', -# that will be replaced with the class name. -cmt_insert_class_header = "" # string - -# Path to a file that contains text to insert before an Objective-C message -# specification, if the method isn't preceded by a C/C++ comment. If the -# inserted text contains '$(message)' or '$(javaparam)', these will be -# replaced with, respectively, the name of the function, or the javadoc -# '@param' and '@return' stuff. -cmt_insert_oc_msg_header = "" # string - -# Whether a comment should be inserted if a preprocessor is encountered when -# stepping backwards from a function name. -# -# Applies to cmt_insert_oc_msg_header, cmt_insert_func_header and -# cmt_insert_class_header. -cmt_insert_before_preproc = false # true/false - -# Whether a comment should be inserted if a function is declared inline to a -# class definition. -# -# Applies to cmt_insert_func_header. -# -# Default: true -cmt_insert_before_inlines = true # true/false - -# Whether a comment should be inserted if the function is a class constructor -# or destructor. -# -# Applies to cmt_insert_func_header. -cmt_insert_before_ctor_dtor = false # true/false - -# -# Code modifying options (non-whitespace) -# - -# Add or remove braces on a single-line 'do' statement. -mod_full_brace_do = ignore # ignore/add/remove/force - -# Add or remove braces on a single-line 'for' statement. -mod_full_brace_for = ignore # ignore/add/remove/force - -# Add or remove braces on a single-line 'if' statement. Braces will not be -# removed if the braced statement contains an 'else'. -mod_full_brace_if = ignore # ignore/add/remove/force - -# Whether to enforce that all blocks of an 'if'/'else if'/'else' chain either -# have, or do not have, braces. If true, braces will be added if any block -# needs braces, and will only be removed if they can be removed from all -# blocks. -# -# Overrides mod_full_brace_if. -mod_full_brace_if_chain = false # true/false - -# Whether to add braces to all blocks of an 'if'/'else if'/'else' chain. -# If true, mod_full_brace_if_chain will only remove braces from an 'if' that -# does not have an 'else if' or 'else'. -mod_full_brace_if_chain_only = false # true/false - -# Add or remove braces on single-line 'while' statement. -mod_full_brace_while = ignore # ignore/add/remove/force - -# Add or remove braces on single-line 'using ()' statement. -mod_full_brace_using = ignore # ignore/add/remove/force - -# Don't remove braces around statements that span N newlines -mod_full_brace_nl = 0 # unsigned number - -# Whether to prevent removal of braces from 'if'/'for'/'while'/etc. blocks -# which span multiple lines. -# -# Affects: -# mod_full_brace_for -# mod_full_brace_if -# mod_full_brace_if_chain -# mod_full_brace_if_chain_only -# mod_full_brace_while -# mod_full_brace_using -# -# Does not affect: -# mod_full_brace_do -# mod_full_brace_function -mod_full_brace_nl_block_rem_mlcond = false # true/false - -# Add or remove unnecessary parenthesis on 'return' statement. -mod_paren_on_return = ignore # ignore/add/remove/force´ - -# Whether to fully parenthesize Boolean expressions in 'while' and 'if' -# statement, as in 'if (a && b > c)' => 'if (a && (b > c))'. -mod_full_paren_if_bool = false # true/false - -# Whether to remove superfluous semicolons. -mod_remove_extra_semicolon = false # true/false - -# If a function body exceeds the specified number of newlines and doesn't have -# a comment after the close brace, a comment will be added. -mod_add_long_function_closebrace_comment = 0 # unsigned number - -# If a namespace body exceeds the specified number of newlines and doesn't -# have a comment after the close brace, a comment will be added. -mod_add_long_namespace_closebrace_comment = 0 # unsigned number - -# If a class body exceeds the specified number of newlines and doesn't have a -# comment after the close brace, a comment will be added. -mod_add_long_class_closebrace_comment = 0 # unsigned number - -# If a switch body exceeds the specified number of newlines and doesn't have a -# comment after the close brace, a comment will be added. -mod_add_long_switch_closebrace_comment = 0 # unsigned number - -# If an #ifdef body exceeds the specified number of newlines and doesn't have -# a comment after the #endif, a comment will be added. -mod_add_long_ifdef_endif_comment = 0 # unsigned number - -# If an #ifdef or #else body exceeds the specified number of newlines and -# doesn't have a comment after the #else, a comment will be added. -mod_add_long_ifdef_else_comment = 0 # unsigned number - -# Whether to take care of the case by the mod_sort_xx options. -mod_sort_case_sensitive = false # true/false - -# Whether to sort consecutive single-line 'import' statements. -mod_sort_import = false # true/false - -# Whether to sort consecutive single-line '#include' statements (C/C++) and -# '#import' statements (Objective-C). Be aware that this has the potential to -# break your code if your includes/imports have ordering dependencies. -mod_sort_include = false # true/false - -# Whether to prioritize '#include' and '#import' statements that contain -# filename without extension when sorting is enabled. -mod_sort_incl_import_prioritize_filename = false # true/false - -# Whether to prioritize '#include' and '#import' statements that does not -# contain extensions when sorting is enabled. -mod_sort_incl_import_prioritize_extensionless = false # true/false - -# Whether to prioritize '#include' and '#import' statements that contain -# angle over quotes when sorting is enabled. -mod_sort_incl_import_prioritize_angle_over_quotes = false # true/false - -# Whether to ignore file extension in '#include' and '#import' statements -# for sorting comparison. -mod_sort_incl_import_ignore_extension = false # true/false - -# Whether to group '#include' and '#import' statements when sorting is enabled. -mod_sort_incl_import_grouping_enabled = false # true/false - -# Whether to move a 'break' that appears after a fully braced 'case' before -# the close brace, as in 'case X: { ... } break;' => 'case X: { ... break; }'. -mod_move_case_break = false # true/false - -# Add or remove braces around a fully braced case statement. Will only remove -# braces if there are no variable declarations in the block. -mod_case_brace = ignore # ignore/add/remove/force - -# Whether to remove a void 'return;' that appears as the last statement in a -# function. -mod_remove_empty_return = false # true/false - -# Add or remove the comma after the last value of an enumeration. -mod_enum_last_comma = ignore # ignore/add/remove/force - -# -# Preprocessor options -# - -# Add or remove indentation of preprocessor directives inside #if blocks -# at brace level 0 (file-level). -pp_indent = ignore # ignore/add/remove/force - -# Whether to indent #if/#else/#endif at the brace level. If false, these are -# indented from column 1. -pp_indent_at_level = false # true/false - -# Specifies the number of columns to indent preprocessors per level -# at brace level 0 (file-level). If pp_indent_at_level=false, also specifies -# the number of columns to indent preprocessors per level -# at brace level > 0 (function-level). -# -# Default: 1 -pp_indent_count = 1 # unsigned number - -# Add or remove space after # based on pp_level of #if blocks. -pp_space = ignore # ignore/add/remove/force - -# Sets the number of spaces per level added with pp_space. -pp_space_count = 0 # unsigned number - -# The indent for '#region' and '#endregion' in C# and '#pragma region' in -# C/C++. Negative values decrease indent down to the first column. -pp_indent_region = 0 # number - -# Whether to indent the code between #region and #endregion. -pp_region_indent_code = false # true/false - -# If pp_indent_at_level=true, sets the indent for #if, #else and #endif when -# not at file-level. Negative values decrease indent down to the first column. -# -# =0: Indent preprocessors using output_tab_size -# >0: Column at which all preprocessors will be indented -pp_indent_if = 0 # number - -# Whether to indent the code between #if, #else and #endif. -pp_if_indent_code = false # true/false - -# Whether to indent '#define' at the brace level. If false, these are -# indented from column 1. -pp_define_at_level = false # true/false - -# Whether to ignore the '#define' body while formatting. -pp_ignore_define_body = false # true/false - -# Whether to indent case statements between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the case statements -# directly inside of. -# -# Default: true -pp_indent_case = true # true/false - -# Whether to indent whole function definitions between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the function definition -# is directly inside of. -# -# Default: true -pp_indent_func_def = true # true/false - -# Whether to indent extern C blocks between #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the extern block is -# directly inside of. -# -# Default: true -pp_indent_extern = true # true/false - -# Whether to indent braces directly inside #if, #else, and #endif. -# Only applies to the indent of the preprocesser that the braces are directly -# inside of. -# -# Default: true -pp_indent_brace = true # true/false - -# -# Sort includes options -# - -# The regex for include category with priority 0. -include_category_0 = "" # string - -# The regex for include category with priority 1. -include_category_1 = "" # string - -# The regex for include category with priority 2. -include_category_2 = "" # string - -# -# Use or Do not Use options -# - -# true: indent_func_call_param will be used (default) -# false: indent_func_call_param will NOT be used -# -# Default: true -use_indent_func_call_param = true # true/false - -# The value of the indentation for a continuation line is calculated -# differently if the statement is: -# - a declaration: your case with QString fileName ... -# - an assignment: your case with pSettings = new QSettings( ... -# -# At the second case the indentation value might be used twice: -# - at the assignment -# - at the function call (if present) -# -# To prevent the double use of the indentation value, use this option with the -# value 'true'. -# -# true: indent_continue will be used only once -# false: indent_continue will be used every time (default) -use_indent_continue_only_once = false # true/false - -# Whether sp_after_angle takes precedence over sp_inside_fparen. This was the -# historic behavior, but is probably not the desired behavior, so this is off -# by default. -use_sp_after_angle_always = false # true/false - -# Whether to apply special formatting for Qt SIGNAL/SLOT macros. Essentially, -# this tries to format these so that they match Qt's normalized form (i.e. the -# result of QMetaObject::normalizedSignature), which can slightly improve the -# performance of the QObject::connect call, rather than how they would -# otherwise be formatted. -# -# See options_for_QT.cpp for details. -# -# Default: true -use_options_overriding_for_qt_macros = true # true/false - -# If true: the form feed character is removed from the list -# of whitespace characters. -# See https://en.cppreference.com/w/cpp/string/byte/isspace -use_form_feed_no_more_as_whitespace_character = false # true/false - -# -# Warn levels - 1: error, 2: warning (default), 3: note -# - -# Limit the number of loops. -# Used by uncrustify.cpp to exit from infinite loop. -# 0: no limit. -debug_max_number_of_loops = 0 # number - -# Set the number of the line to protocol; -# Used in the function prot_the_line if the 2. parameter is zero. -# 0: nothing protocol. -debug_line_number_to_protocol = 0 # number - -# Set the number of second(s) before terminating formatting the current file, -# 0: no timeout. -# only for linux -debug_timeout = 0 # number - -# Meaning of the settings: -# Ignore - do not do any changes -# Add - makes sure there is 1 or more space/brace/newline/etc -# Force - makes sure there is exactly 1 space/brace/newline/etc, -# behaves like Add in some contexts -# Remove - removes space/brace/newline/etc -# -# -# - Token(s) can be treated as specific type(s) with the 'set' option: -# `set tokenType tokenString [tokenString...]` -# -# Example: -# `set BOOL __AND__ __OR__` -# -# tokenTypes are defined in src/token_enum.h, use them without the -# 'CT_' prefix: 'CT_BOOL' => 'BOOL' -# -# -# - Token(s) can be treated as type(s) with the 'type' option. -# `type tokenString [tokenString...]` -# -# Example: -# `type int c_uint_8 Rectangle` -# -# This can also be achieved with `set TYPE int c_uint_8 Rectangle` -# -# -# To embed whitespace in tokenStrings use the '\' escape character, or quote -# the tokenStrings. These quotes are supported: "'` -# -# -# - Support for the auto detection of languages through the file ending can be -# added using the 'file_ext' command. -# `file_ext langType langString [langString..]` -# -# Example: -# `file_ext CPP .ch .cxx .cpp.in` -# -# langTypes are defined in uncrusify_types.h in the lang_flag_e enum, use -# them without the 'LANG_' prefix: 'LANG_CPP' => 'CPP' -# -# -# - Custom macro-based indentation can be set up using 'macro-open', -# 'macro-else' and 'macro-close'. -# `(macro-open | macro-else | macro-close) tokenString` -# -# Example: -# `macro-open BEGIN_TEMPLATE_MESSAGE_MAP` -# `macro-open BEGIN_MESSAGE_MAP` -# `macro-close END_MESSAGE_MAP` -# -# -# option(s) with 'not default' value: 0 -# diff --git a/meson.build b/meson.build deleted file mode 100644 index 6a7169c..0000000 --- a/meson.build +++ /dev/null @@ -1,35 +0,0 @@ -project('koto', 'c', - version: '0.1.0', - meson_version: '>= 0.57.0', - default_options: [ - 'c_std=gnu11', - 'warning_level=2', - 'werror=true', - ], -) - -i18n = import('i18n') -gnome = import('gnome') - -config_h = configuration_data() -config_h.set_quoted('PACKAGE_VERSION', meson.project_version()) -config_h.set_quoted('GETTEXT_PACKAGE', 'koto') -config_h.set_quoted('LOCALEDIR', join_paths(get_option('prefix'), get_option('localedir'))) -configure_file( - output: 'koto-config.h', - configuration: config_h, -) - -c = meson.get_compiler('c') -toml_dep = c.find_library('toml', required: true) - -subdir('theme') -subdir('data') -subdir('src') -subdir('po') - -gnome.post_install( - glib_compile_schemas: true, - gtk_update_icon_cache: false -) -meson.add_install_script('build-aux/meson/postinstall.py') diff --git a/po/LINGUAS b/po/LINGUAS deleted file mode 100644 index e69de29..0000000 diff --git a/po/POTFILES b/po/POTFILES deleted file mode 100644 index 2839995..0000000 --- a/po/POTFILES +++ /dev/null @@ -1,7 +0,0 @@ -data/com.github.joshstrobl.koto.desktop.in -data/com.github.joshstrobl.koto.appdata.xml.in -data/com.github.joshstrobl.koto.gschema.xml -src/koto-window.ui -src/main.c -src/koto-window.c - diff --git a/po/meson.build b/po/meson.build deleted file mode 100644 index fe55b6a..0000000 --- a/po/meson.build +++ /dev/null @@ -1 +0,0 @@ -i18n.gettext('koto', preset: 'glib') diff --git a/src/components/action-bar.c b/src/components/action-bar.c deleted file mode 100644 index 698777c..0000000 --- a/src/components/action-bar.c +++ /dev/null @@ -1,423 +0,0 @@ -/* action-bar.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../config/config.h" -#include "../db/cartographer.h" -#include "../indexer/album-playlist-funcs.h" -#include "../pages/music/music-local.h" -#include "../playlist/add-remove-track-popover.h" -#include "../playlist/current.h" -#include "../playback/engine.h" -#include "../koto-utils.h" -#include "../koto-window.h" -#include "action-bar.h" -#include "button.h" - -extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; -extern KotoCartographer * koto_maps; -extern KotoConfig * config; -extern KotoCurrentPlaylist * current_playlist; -extern KotoPageMusicLocal * music_local_page; -extern KotoPlaybackEngine * playback_engine; -extern KotoWindow * main_window; - -enum { - SIGNAL_CLOSED, - N_SIGNALS -}; - -static guint actionbar_signals[N_SIGNALS] = { - 0 -}; - -struct _KotoActionBar { - GObject parent_instance; - - GtkActionBar * main; - - GtkWidget * center_box_content; - GtkWidget * start_box_content; - GtkWidget * stop_box_content; - - KotoButton * close_button; - GtkWidget * go_to_artist; - GtkWidget * playlists; - GtkWidget * play_track; - GtkWidget * remove_from_playlist; - - GList * current_list; - gchar * current_album_uuid; - gchar * current_playlist_uuid; - KotoActionBarRelative relative; -}; - -struct _KotoActionBarClass { - GObjectClass parent_class; - - void (* closed) (KotoActionBar * self); -}; - -G_DEFINE_TYPE(KotoActionBar, koto_action_bar, G_TYPE_OBJECT); - -KotoActionBar * action_bar; - -static void koto_action_bar_class_init(KotoActionBarClass * c) { - GObjectClass * gobject_class = G_OBJECT_CLASS(c); - - actionbar_signals[SIGNAL_CLOSED] = g_signal_new( - "closed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoActionBarClass, closed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); -} - -static void koto_action_bar_init(KotoActionBar * self) { - self->main = GTK_ACTION_BAR(gtk_action_bar_new()); // Create a new action bar - self->current_list = NULL; - - self->close_button = koto_button_new_with_icon(NULL, "window-close-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - self->go_to_artist = gtk_button_new_with_label("Go to Artist"); - self->playlists = gtk_button_new_with_label("Playlists"); - self->play_track = gtk_button_new_with_label("Play"); - self->remove_from_playlist = gtk_button_new_with_label("Remove from Playlist"); - - gtk_widget_add_css_class(self->playlists, "suggested-action"); - gtk_widget_add_css_class(self->play_track, "suggested-action"); - gtk_widget_add_css_class(self->remove_from_playlist, "destructive-action"); - - gtk_widget_set_size_request(self->play_track, 160, -1); - - self->center_box_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - self->start_box_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - self->stop_box_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 20); - - gtk_box_prepend(GTK_BOX(self->start_box_content), GTK_WIDGET(self->go_to_artist)); - - gtk_box_prepend(GTK_BOX(self->center_box_content), GTK_WIDGET(self->play_track)); - - gtk_box_prepend(GTK_BOX(self->stop_box_content), GTK_WIDGET(self->playlists)); - gtk_box_append(GTK_BOX(self->stop_box_content), GTK_WIDGET(self->remove_from_playlist)); - gtk_box_append(GTK_BOX(self->stop_box_content), GTK_WIDGET(self->close_button)); - - gtk_action_bar_pack_start(self->main, self->start_box_content); - gtk_action_bar_pack_end(self->main, self->stop_box_content); - gtk_action_bar_set_center_widget(self->main, self->center_box_content); - - // Hide all the widgets by default - gtk_widget_hide(GTK_WIDGET(self->go_to_artist)); - gtk_widget_hide(GTK_WIDGET(self->playlists)); - gtk_widget_hide(GTK_WIDGET(self->play_track)); - gtk_widget_hide(GTK_WIDGET(self->remove_from_playlist)); - - // Set up bindings - - koto_button_add_click_handler(self->close_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_action_bar_handle_close_button_clicked), self); - g_signal_connect(self->go_to_artist, "clicked", G_CALLBACK(koto_action_bar_handle_go_to_artist_button_clicked), self); - g_signal_connect(self->playlists, "clicked", G_CALLBACK(koto_action_bar_handle_playlists_button_clicked), self); - g_signal_connect(self->play_track, "clicked", G_CALLBACK(koto_action_bar_handle_play_track_button_clicked), self); - g_signal_connect(self->remove_from_playlist, "clicked", G_CALLBACK(koto_action_bar_handle_remove_from_playlist_button_clicked), self); - - koto_action_bar_toggle_reveal(self, FALSE); // Hide by default -} - -void koto_action_bar_close(KotoActionBar * self) { - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - gtk_widget_hide(GTK_WIDGET(koto_add_remove_track_popup)); // Hide the Add / Remove Track Playlists popover - koto_action_bar_toggle_reveal(self, FALSE); // Hide the action bar - - g_signal_emit( - self, - actionbar_signals[SIGNAL_CLOSED], - 0, - NULL - ); -} - -GtkActionBar * koto_action_bar_get_main(KotoActionBar * self) { - if (!KOTO_IS_ACTION_BAR(self)) { - return NULL; - } - - return self->main; -} - -void koto_action_bar_handle_close_button_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - koto_action_bar_close(data); -} - -void koto_action_bar_handle_go_to_artist_button_clicked( - GtkButton * button, - gpointer data -) { - (void) button; - KotoActionBar * self = data; - - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - if (self->current_list == NULL || (g_list_length(self->current_list) != 1)) { // Not a list or not exactly one item - return; - } - - KotoTrack * selected_track = g_list_nth_data(self->current_list, 0); // Get the first item - - if (!KOTO_IS_TRACK(selected_track)) { // Not a track - return; - } - - gchar * artist_uuid = NULL; - - g_object_get( - selected_track, - "artist-uuid", - &artist_uuid, - NULL - ); - - koto_page_music_local_go_to_artist_by_uuid(music_local_page, artist_uuid); // Go to the artist - koto_window_go_to_page(main_window, "music.local"); // Navigate to the local music stack so we can see the substack page - koto_action_bar_close(self); // Close the action bar -} -void koto_action_bar_handle_playlists_button_clicked( - GtkButton * button, - gpointer data -) { - (void) button; - KotoActionBar * self = data; - - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - if (self->current_list == NULL || (g_list_length(self->current_list) == 0)) { // Not a list or no content - return; - } - - koto_add_remove_track_popover_set_tracks(koto_add_remove_track_popup, self->current_list); // Set the popover tracks - koto_add_remove_track_popover_set_pointing_to_widget(koto_add_remove_track_popup, GTK_WIDGET(self->playlists), GTK_POS_TOP); // Show the popover above the button - gtk_widget_show(GTK_WIDGET(koto_add_remove_track_popup)); -} - -void koto_action_bar_handle_play_track_button_clicked( - GtkButton * button, - gpointer data -) { - (void) button; - KotoActionBar * self = data; - - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - if (self->current_list == NULL || (g_list_length(self->current_list) != 1)) { // Not a list or not exactly 1 item selected - goto doclose; - } - - KotoTrack * track = g_list_nth_data(self->current_list, 0); // Get the first track - - if (!KOTO_IS_TRACK(track)) { // Not a track - goto doclose; - } - - KotoPlaylist * playlist = NULL; - - if (self->relative == KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE) { // Relative to a playlist - playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->current_playlist_uuid); - } else if (self->relative == KOTO_ACTION_BAR_IS_ALBUM_RELATIVE) { // Relative to an Album - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->current_album_uuid); // Get the Album - - if (KOTO_IS_ALBUM(album)) { // Have an Album - playlist = koto_album_get_playlist(album); // Create our playlist dynamically for the Album - } - } - - if (KOTO_IS_PLAYLIST(playlist)) { // Is a playlist - koto_current_playlist_set_playlist(current_playlist, playlist, FALSE, FALSE); // Update our playlist to the one associated with the track we are playing - koto_playlist_set_track_as_current(playlist, koto_track_get_uuid(track)); // Get this track as the current track in the position - } - - gboolean continue_playback = FALSE; - g_object_get(config, "playback-continue-on-playlist", &continue_playback, NULL); - koto_playback_engine_set_track_by_uuid(playback_engine, koto_track_get_uuid(track), continue_playback); // Set the track to play - -doclose: - koto_action_bar_close(self); -} - -void koto_action_bar_handle_remove_from_playlist_button_clicked( - GtkButton * button, - gpointer data -) { - (void) button; - KotoActionBar * self = data; - - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - if (self->current_list == NULL || (g_list_length(self->current_list) == 0)) { // Not a list or no content - goto doclose; - } - - if (!koto_utils_string_is_valid(self->current_playlist_uuid)) { // Not valid UUID - goto doclose; - } - - KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->current_playlist_uuid); - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - goto doclose; - } - - GList * cur_list; - - for (cur_list = self->current_list; cur_list != NULL; cur_list = cur_list->next) { // For each KotoTrack - KotoTrack * track = cur_list->data; - koto_playlist_remove_track_by_uuid(playlist, koto_track_get_uuid(track)); // Remove this track - } - -doclose: - koto_action_bar_close(self); -} - -void koto_action_bar_set_tracks_in_album_selection( - KotoActionBar * self, - gchar * album_uuid, - GList * tracks -) { - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - if (koto_utils_string_is_valid(self->current_album_uuid)) { // Album UUID currently set - g_free(self->current_album_uuid); - } - - if (koto_utils_string_is_valid(self->current_playlist_uuid)) { // Playlist UUID currently set - g_free(self->current_playlist_uuid); - } - - self->current_playlist_uuid = NULL; - self->current_album_uuid = g_strdup(album_uuid); - self->relative = KOTO_ACTION_BAR_IS_ALBUM_RELATIVE; - - g_list_free(self->current_list); - self->current_list = g_list_copy(tracks); - - koto_add_remove_track_popover_clear_tracks(koto_add_remove_track_popup); // Clear the current popover contents - koto_add_remove_track_popover_set_tracks(koto_add_remove_track_popup, self->current_list); // Set the associated tracks to remove - - koto_action_bar_toggle_go_to_artist_visibility(self, FALSE); - koto_action_bar_toggle_play_button_visibility(self, g_list_length(self->current_list) == 1); - - gtk_widget_show(GTK_WIDGET(self->playlists)); - gtk_widget_hide(GTK_WIDGET(self->remove_from_playlist)); -} - -void koto_action_bar_set_tracks_in_playlist_selection( - KotoActionBar * self, - gchar * playlist_uuid, - GList * tracks -) { - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - if (koto_utils_string_is_valid(self->current_album_uuid)) { // Album UUID currently set - g_free(self->current_album_uuid); - } - - if (koto_utils_string_is_valid(self->current_playlist_uuid)) { // Playlist UUID currently set - g_free(self->current_playlist_uuid); - } - - self->current_album_uuid = NULL; - self->current_playlist_uuid = g_strdup(playlist_uuid); - self->relative = KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE; - - g_list_free(self->current_list); - self->current_list = g_list_copy(tracks); - - gboolean single_selected = g_list_length(tracks) == 1; - - koto_action_bar_toggle_go_to_artist_visibility(self, single_selected); - koto_action_bar_toggle_play_button_visibility(self, single_selected); - gtk_widget_hide(GTK_WIDGET(self->playlists)); - gtk_widget_show(GTK_WIDGET(self->remove_from_playlist)); -} - -void koto_action_bar_toggle_go_to_artist_visibility( - KotoActionBar * self, - gboolean visible -) { - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - (visible) ? gtk_widget_show(GTK_WIDGET(self->go_to_artist)) : gtk_widget_hide(GTK_WIDGET(self->go_to_artist)); -} - -void koto_action_bar_toggle_play_button_visibility( - KotoActionBar * self, - gboolean visible -) { - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - (visible) ? gtk_widget_show(GTK_WIDGET(self->play_track)) : gtk_widget_hide(GTK_WIDGET(self->play_track)); -} - -void koto_action_bar_toggle_reveal( - KotoActionBar * self, - gboolean state -) { - if (!KOTO_IS_ACTION_BAR(self)) { - return; - } - - gtk_action_bar_set_revealed(self->main, state); -} - -KotoActionBar * koto_action_bar_new() { - return g_object_new( - KOTO_TYPE_ACTION_BAR, - NULL - ); -} \ No newline at end of file diff --git a/src/components/action-bar.h b/src/components/action-bar.h deleted file mode 100644 index d52281b..0000000 --- a/src/components/action-bar.h +++ /dev/null @@ -1,105 +0,0 @@ -/* action-bar.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../indexer/structs.h" - -G_BEGIN_DECLS - -typedef enum { - KOTO_ACTION_BAR_IS_ALBUM_RELATIVE = 1, - KOTO_ACTION_BAR_IS_PLAYLIST_RELATIVE = 2 -} KotoActionBarRelative; - -#define KOTO_TYPE_ACTION_BAR (koto_action_bar_get_type()) -#define KOTO_IS_ACTION_BAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ACTION_BAR)) - -typedef struct _KotoActionBar KotoActionBar; -typedef struct _KotoActionBarClass KotoActionBarClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_action_bar_get_type(void) G_GNUC_CONST; - -/** - * Action Bar Functions - **/ - -KotoActionBar * koto_action_bar_new(void); - -void koto_action_bar_close(KotoActionBar * self); - -GtkActionBar * koto_action_bar_get_main(KotoActionBar * self); - -void koto_action_bar_handle_close_button_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_action_bar_handle_go_to_artist_button_clicked( - GtkButton * button, - gpointer data -); - -void koto_action_bar_handle_playlists_button_clicked( - GtkButton * button, - gpointer data -); - -void koto_action_bar_handle_play_track_button_clicked( - GtkButton * button, - gpointer data -); - -void koto_action_bar_handle_remove_from_playlist_button_clicked( - GtkButton * button, - gpointer data -); - -void koto_action_bar_set_tracks_in_album_selection( - KotoActionBar * self, - gchar * album_uuid, - GList * tracks -); - -void koto_action_bar_set_tracks_in_playlist_selection( - KotoActionBar * self, - gchar * playlist_uuid, - GList * tracks -); - -void koto_action_bar_toggle_go_to_artist_visibility( - KotoActionBar * self, - gboolean visible -); - -void koto_action_bar_toggle_play_button_visibility( - KotoActionBar * self, - gboolean visible -); - -void koto_action_bar_toggle_reveal( - KotoActionBar * self, - gboolean state -); - -G_END_DECLS \ No newline at end of file diff --git a/src/components/album-info.c b/src/components/album-info.c deleted file mode 100644 index 94f0eab..0000000 --- a/src/components/album-info.c +++ /dev/null @@ -1,289 +0,0 @@ -/* album-info.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../config/config.h" -#include "../db/cartographer.h" -#include "../indexer/structs.h" -#include "../koto-utils.h" -#include "album-info.h" - -extern KotoCartographer * koto_maps; -extern KotoConfig * config; - -enum { - PROP_0, - PROP_TYPE, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -struct _KotoAlbumInfo { - GtkBox parent_instance; - KotoAlbumInfoType type; - - KotoAlbum * album; - - GtkWidget * name_year_box; - GtkWidget * genres_tags_list; - - GtkWidget * name_label; - GtkWidget * year_badge; - GtkWidget * narrator_label; - - GtkWidget * description_label; -}; - -struct _KotoAlbumInfoClass { - GtkBoxClass parent_class; -}; - -G_DEFINE_TYPE(KotoAlbumInfo, koto_album_info, GTK_TYPE_BOX); - -static void koto_album_info_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_album_info_class_init(KotoAlbumInfoClass * c) { - GObjectClass * gobject_class; - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_album_info_set_property; - - props[PROP_TYPE] = g_param_spec_string( - "type", - "Type of AlbumInfo component", - "Type of AlbumInfo component", - "album", - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_album_info_init(KotoAlbumInfo * self) { - gtk_widget_add_css_class(GTK_WIDGET(self), "album-info"); - self->type = KOTO_ALBUM_INFO_TYPE_ALBUM; // Default to Album here - - self->name_year_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create out name box for the name and year - gtk_widget_add_css_class(self->name_year_box, "album-title-year-combo"); - self->name_label = gtk_label_new(NULL); // Create our name label - gtk_widget_set_halign(self->name_label, GTK_ALIGN_START); - gtk_widget_set_valign(self->name_label, GTK_ALIGN_START); - gtk_widget_add_css_class(self->name_label, "album-title"); - - self->year_badge = gtk_label_new(NULL); // Create our year badge - gtk_widget_add_css_class(self->year_badge, "album-year"); - gtk_widget_add_css_class(self->year_badge, "label-badge"); - gtk_widget_set_valign(self->year_badge, GTK_ALIGN_CENTER); // Center vertically align the year badge - - self->narrator_label = gtk_label_new(NULL); // Create our narrator label - gtk_widget_add_css_class(self->narrator_label, "album-narrator"); - gtk_widget_set_halign(self->narrator_label, GTK_ALIGN_START); - - self->description_label = gtk_label_new(NULL); // Create our description label - gtk_widget_add_css_class(self->description_label, "album-description"); - gtk_widget_set_halign(self->description_label, GTK_ALIGN_START); - - gtk_box_append(GTK_BOX(self->name_year_box), self->name_label); // Add the name label to the name + year box - gtk_box_append(GTK_BOX(self->name_year_box), self->year_badge); // Add the year badge to the name + year box - - gtk_box_append(GTK_BOX(self), self->name_year_box); - gtk_box_append(GTK_BOX(self), self->narrator_label); - gtk_box_append(GTK_BOX(self), self->description_label); - - g_signal_connect(config, "notify::ui-info-show-description", G_CALLBACK(koto_album_info_apply_configuration_state), self); - g_signal_connect(config, "notify::ui-info-show-genres", G_CALLBACK(koto_album_info_apply_configuration_state), self); - g_signal_connect(config, "notify::ui-info-show-narrator", G_CALLBACK(koto_album_info_apply_configuration_state), self); - g_signal_connect(config, "notify::ui-info-show-year", G_CALLBACK(koto_album_info_apply_configuration_state), self); -} - -static void koto_album_info_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoAlbumInfo * self = KOTO_ALBUM_INFO(obj); - - switch (prop_id) { - case PROP_TYPE: - koto_album_info_set_type(self, g_value_get_string(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_album_info_apply_configuration_state( - KotoConfig * c, - guint prop_id, - KotoAlbumInfo * self -) { - (void) c; - (void) prop_id; - - gboolean show_description = TRUE; - gboolean show_genres = TRUE; - gboolean show_narrator = TRUE; - gboolean show_year = TRUE; - - g_object_get( - config, - "ui-album-info-show-description", - &show_description, - "ui-album-info-show-genres", - &show_genres, - "ui-album-info-show-narrator", - &show_narrator, - "ui-album-info-show-year", - &show_year, - NULL - ); - - if (self->type != KOTO_ALBUM_INFO_TYPE_AUDIOBOOK) { // If the type is NOT an audiobook - show_narrator = FALSE; // Narrator should never be shown. Only really applicable to audiobook - } - - if (self->type == KOTO_ALBUM_INFO_TYPE_PODCAST) { // If the type is podcast - show_year = FALSE; // Year isn't really applicable to podcast, at least not the "global" year - } - - if (koto_utils_string_is_valid(koto_album_get_description(self->album)) && show_description) { // Have description to show in the first place, and should show it - gtk_widget_show(self->description_label); - } else { // Don't have content, just hide it - gtk_widget_hide(self->description_label); - } - - if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album defined - return; - } - - if (GTK_IS_FLOW_BOX(self->genres_tags_list)) { - if ((g_list_length(koto_album_get_genres(self->album)) > 0) && show_genres) { // Have genres to show in the first place, and should show it - gtk_widget_show(self->genres_tags_list); - } else { - gtk_widget_hide(self->genres_tags_list); - } - } - - if (koto_utils_string_is_valid(koto_album_get_narrator(self->album)) && show_narrator) { // Have narrator and should show it - gtk_widget_show(self->narrator_label); - } else { - gtk_widget_hide(self->narrator_label); - } - - if ((koto_album_get_year(self->album) != 0) && show_year) { // Have year and should show it - gtk_widget_show(self->year_badge); - } else { - gtk_widget_hide(self->year_badge); - } -} - -void koto_album_info_set_album_uuid( - KotoAlbumInfo * self, - gchar * album_uuid -) { - if (!KOTO_IS_ALBUM_INFO(self)) { - return; - } - - if (!koto_utils_string_is_valid(album_uuid)) { - return; - } - - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Get the album - - if (!KOTO_IS_ALBUM(album)) { // Is not an album - return; - } - - self->album = album; - - gtk_label_set_text(GTK_LABEL(self->name_label), koto_album_get_name(self->album)); // Set the name label text to the album - gtk_label_set_text(GTK_LABEL(self->year_badge), g_strdup_printf("%li", koto_album_get_year(self->album))); // Set the year label text to any year for the album - - gchar * narrator = koto_album_get_narrator(self->album); - - if (koto_utils_string_is_valid(narrator)) { // Have a narrator - gtk_label_set_text(GTK_LABEL(self->narrator_label), g_strdup_printf("Narrated by %s", koto_album_get_narrator(self->album))); // Set narrated by string - } - - gchar * description = koto_album_get_description(self->album); - - if (koto_utils_string_is_valid(description)) { // Have a description - gtk_label_set_markup(GTK_LABEL(self->description_label), description); // Set the markup so we treat it more like HTML and let pango do its thing - } - - if (GTK_IS_BOX(self->genres_tags_list)) { // Genres Flow - gtk_box_remove(GTK_BOX(self), self->genres_tags_list); // Remove the genres flow from the box - } - - self->genres_tags_list = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our flow box for the genres - gtk_widget_add_css_class(self->genres_tags_list, "genres-tag-list"); - - GList * album_genres = koto_album_get_genres(self->album); - if (g_list_length(album_genres) != 0) { // Have genres - GList * cur_list; - for (cur_list = album_genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre - GtkWidget * genre_label = gtk_label_new(koto_utils_string_title(cur_list->data)); - gtk_widget_add_css_class(genre_label, "label-badge"); // Add label badge styling - gtk_box_append(GTK_BOX(self->genres_tags_list), genre_label); // Append to the genre tags list - } - } else { - gtk_widget_hide(self->genres_tags_list); // Hide the genres tag list - } - - gtk_box_insert_child_after(GTK_BOX(self), self->genres_tags_list, self->name_year_box); // Add after the name+year flowbox - - koto_album_info_apply_configuration_state(NULL, 0, self); // Apply our configuration state immediately so we know what to show and hide for this album based on the config -} - -void koto_album_info_set_type( - KotoAlbumInfo * self, - const gchar * type -) { - if (!KOTO_IS_ALBUM_INFO(self)) { - return; - } - - if (g_strcmp0(type, "audiobook") == 0) { // If this is an audiobook - self->type = KOTO_ALBUM_INFO_TYPE_AUDIOBOOK; - } else if (g_strcmp0(type, "podcast") == 0) { // If this is a podcast - self->type = KOTO_ALBUM_INFO_TYPE_PODCAST; - } else { - self->type = KOTO_ALBUM_INFO_TYPE_ALBUM; - } -} - -KotoAlbumInfo * koto_album_info_new(gchar * type) { - return g_object_new( - KOTO_TYPE_ALBUM_INFO, - "orientation", - GTK_ORIENTATION_VERTICAL, - "type", - type, - NULL - ); -} \ No newline at end of file diff --git a/src/components/album-info.h b/src/components/album-info.h deleted file mode 100644 index fe2e86c..0000000 --- a/src/components/album-info.h +++ /dev/null @@ -1,52 +0,0 @@ -/* album-info.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../config/config.h" - -G_BEGIN_DECLS - -typedef enum { - KOTO_ALBUM_INFO_TYPE_ALBUM, - KOTO_ALBUM_INFO_TYPE_AUDIOBOOK, - KOTO_ALBUM_INFO_TYPE_PODCAST -} KotoAlbumInfoType; - -#define KOTO_TYPE_ALBUM_INFO (koto_album_info_get_type()) -G_DECLARE_FINAL_TYPE(KotoAlbumInfo, koto_album_info, KOTO, ALBUM_INFO, GtkBox); -#define KOTO_IS_ALBUM_INFO(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ALBUM_INFO)) - -void koto_album_info_apply_configuration_state( - KotoConfig * c, - guint prop_id, - KotoAlbumInfo * self -); - -void koto_album_info_set_album_uuid( - KotoAlbumInfo * self, - gchar * album_uuid -); - -void koto_album_info_set_type( - KotoAlbumInfo * self, - const gchar * type -); - -KotoAlbumInfo * koto_album_info_new(gchar * type); \ No newline at end of file diff --git a/src/components/button.c b/src/components/button.c deleted file mode 100644 index 779128a..0000000 --- a/src/components/button.c +++ /dev/null @@ -1,818 +0,0 @@ -/* koto-button.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "config/config.h" -#include "button.h" -#include "../koto-window.h" -#include "../koto-utils.h" - -extern KotoWindow * main_window; - -struct _PixbufSize { - guint size; -}; - -typedef struct _PixbufSize PixbufSize; - -static PixbufSize * pixbuf_sizes = NULL; -static guint pixbuf_sizes_allocated = 0; - -static void init_pixbuf_sizes() { - if (pixbuf_sizes == NULL) { - pixbuf_sizes = g_new(PixbufSize, NUM_BUILTIN_SIZES); - - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_INVALID].size = 0; - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_TINY].size = 16; - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_SMALL].size = 24; - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_NORMAL].size = 32; - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_LARGE].size = 64; - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_MASSIVE].size = 96; - pixbuf_sizes[KOTO_BUTTON_PIXBUF_SIZE_GODLIKE].size = 128; - pixbuf_sizes_allocated = NUM_BUILTIN_SIZES; - } -} - -guint koto_get_pixbuf_size(KotoButtonPixbufSize s) { - init_pixbuf_sizes(); - - return pixbuf_sizes[s].size; -} - -enum { - PROP_BTN_0, - PROP_PIX_SIZE, - PROP_TEXT, - PROP_BADGE_TEXT, - PROP_USE_FROM_FILE, - PROP_IMAGE_FILE_PATH, - PROP_ICON_NAME, - PROP_ALT_ICON_NAME, - PROP_RESOURCE_PATH, - N_BTN_PROPERTIES -}; - -static GParamSpec * btn_props[N_BTN_PROPERTIES] = { - NULL, -}; - -struct _KotoButton { - GtkBox parent_instance; - guint pix_size; - - gpointer arbitrary_data; - - GtkWidget * button_pic; - GtkWidget * badge_label; - GtkWidget * button_label; - - GtkGesture * left_click_gesture; - GtkGesture * right_click_gesture; - - gchar * image_file_path; - gchar * badge_text; - gchar * icon_name; - gchar * alt_icon_name; - gchar * resource_path; - gchar * text; - - KotoButtonImagePosition image_position; - gboolean use_from_file; - gboolean currently_showing_alt; -}; - -struct _KotoButtonClass { - GtkBoxClass parent_class; -}; - -G_DEFINE_TYPE(KotoButton, koto_button, GTK_TYPE_BOX); - -static void koto_button_constructed(GObject * obj); - -static void koto_button_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_button_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_button_class_init(KotoButtonClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->constructed = koto_button_constructed; - gobject_class->set_property = koto_button_set_property; - gobject_class->get_property = koto_button_get_property; - - btn_props[PROP_PIX_SIZE] = g_param_spec_uint( - "pixbuf-size", - "Pixbuf Size", - "Size of the pixbuf", - koto_get_pixbuf_size(KOTO_BUTTON_PIXBUF_SIZE_TINY), - koto_get_pixbuf_size(KOTO_BUTTON_PIXBUF_SIZE_GODLIKE), - koto_get_pixbuf_size(KOTO_BUTTON_PIXBUF_SIZE_SMALL), - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_TEXT] = g_param_spec_string( - "button-text", - "Button Text", - "Text of Button", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_BADGE_TEXT] = g_param_spec_string( - "badge-text", - "Badge Text", - "Text of Badge", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_USE_FROM_FILE] = g_param_spec_boolean( - "use-from-file", - "Use from a file / file name", - "Use from a file / file name", - FALSE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_IMAGE_FILE_PATH] = g_param_spec_string( - "image-file-path", - "File path to image", - "File path to image", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_ICON_NAME] = g_param_spec_string( - "icon-name", - "Icon Name", - "Name of Icon", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_ALT_ICON_NAME] = g_param_spec_string( - "alt-icon-name", - "Name of an Alternate Icon", - "Name of an Alternate Icon", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - btn_props[PROP_RESOURCE_PATH] = g_param_spec_string( - "resource-path", - "Resource Path to an Icon", - "Resource Path to an Icon", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_BTN_PROPERTIES, btn_props); -} - -static void koto_button_init(KotoButton * self) { - self->currently_showing_alt = FALSE; - self->image_position = KOTO_BUTTON_IMAGE_POS_LEFT; - - self->left_click_gesture = gtk_gesture_click_new(); // Set up our left click gesture - self->right_click_gesture = gtk_gesture_click_new(); // Set up our right click gesture - - self->resource_path = NULL; - - gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->left_click_gesture), (int) KOTO_BUTTON_CLICK_TYPE_PRIMARY); // Only allow left clicks on left click gesture - gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(self->right_click_gesture), (int) KOTO_BUTTON_CLICK_TYPE_SECONDARY); // Only allow right clicks on right click gesture - - gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(self->left_click_gesture)); // Add our left click gesture - gtk_widget_add_controller(GTK_WIDGET(self), GTK_EVENT_CONTROLLER(self->right_click_gesture)); // Add our right click gesture - - GtkEventController * motion = gtk_event_controller_motion_new(); // Create a new motion controller - g_signal_connect(motion, "enter", G_CALLBACK(koto_button_handle_mouse_enter), self); // Handle mouse enter on motion - g_signal_connect(motion, "leave", G_CALLBACK(koto_button_handle_mouse_leave), self); // Handle mouse leave on motion - gtk_widget_add_controller(GTK_WIDGET(self), motion); -} - -static void koto_button_constructed(GObject * obj) { - KotoButton * self = KOTO_BUTTON(obj); - GtkStyleContext * style = gtk_widget_get_style_context(GTK_WIDGET(self)); - - gtk_style_context_add_class(style, "koto-button"); - - G_OBJECT_CLASS(koto_button_parent_class)->constructed(obj); -} - -static void koto_button_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoButton * self = KOTO_BUTTON(obj); - - switch (prop_id) { - case PROP_IMAGE_FILE_PATH: - g_value_set_string(val, self->image_file_path); - break; - case PROP_USE_FROM_FILE: - g_value_set_boolean(val, self->use_from_file); - break; - case PROP_PIX_SIZE: - g_value_set_uint(val, self->pix_size); - break; - case PROP_TEXT: - g_value_set_string(val, self->text); - break; - case PROP_BADGE_TEXT: - g_value_set_string(val, self->badge_text); - break; - case PROP_ICON_NAME: - g_value_set_string(val, self->icon_name); - break; - case PROP_ALT_ICON_NAME: - g_value_set_string(val, self->alt_icon_name); - break; - case PROP_RESOURCE_PATH: - g_value_set_string(val, self->resource_path); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_button_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoButton * self = KOTO_BUTTON(obj); - - switch (prop_id) { - case PROP_PIX_SIZE: - koto_button_set_pixbuf_size(self, g_value_get_uint(val)); - break; - case PROP_TEXT: - if (koto_utils_string_is_valid(g_value_get_string(val))) { - koto_button_set_text(self, (gchar*) g_value_get_string(val)); - } - - break; - case PROP_BADGE_TEXT: - koto_button_set_badge_text(self, g_strdup(g_value_get_string(val))); - break; - case PROP_USE_FROM_FILE: - self->use_from_file = g_value_get_boolean(val); - break; - case PROP_IMAGE_FILE_PATH: - koto_button_set_file_path(self, (gchar*) g_value_get_string(val)); - break; - case PROP_ICON_NAME: - koto_button_set_icon_name(self, g_strdup(g_value_get_string(val)), FALSE); - if (!self->currently_showing_alt) { // Not showing alt - koto_button_show_image(self, FALSE); - } - break; - case PROP_ALT_ICON_NAME: - koto_button_set_icon_name(self, g_strdup(g_value_get_string(val)), TRUE); - if (self->currently_showing_alt) { // Currently showing the alt image - koto_button_show_image(self, TRUE); - } - break; - case PROP_RESOURCE_PATH: - koto_button_set_resource_path(self, g_strdup(g_value_get_string(val))); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_button_add_click_handler( - KotoButton * self, - KotoButtonClickType button, - GCallback handler, - gpointer user_data -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if ((button != KOTO_BUTTON_CLICK_TYPE_PRIMARY) && (button != KOTO_BUTTON_CLICK_TYPE_SECONDARY)) { // Not valid type - return; - } - - g_signal_connect((button == KOTO_BUTTON_CLICK_TYPE_PRIMARY) ? self->left_click_gesture : self->right_click_gesture, "pressed", handler, user_data); -} - -void koto_button_flip(KotoButton * self) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - koto_button_show_image(self, !self->currently_showing_alt); -} - -gpointer koto_button_get_data(KotoButton * self) { - return self->arbitrary_data; -} - -void koto_button_global_page_nav_callback( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - - KotoButton * btn = KOTO_BUTTON(user_data); // Cast our user data as a button - - if (!KOTO_IS_BUTTON(btn)) { // Not a button - return; - } - - gchar * btn_nav_uuid = (gchar*) koto_button_get_data(btn); // Get the data - - if (!koto_utils_string_is_valid(btn_nav_uuid)) { // Not a valid string - return; - } - - koto_window_go_to_page(main_window, btn_nav_uuid); -} - -void koto_button_handle_mouse_enter( - GtkEventControllerMotion * controller, - double x, - double y, - gpointer user_data -) { - (void) controller; - (void) x; - (void) y; - - KotoButton * self = user_data; - - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - koto_button_set_pseudoactive_styling(self); // Set to be pseudoactive in styling -} - -void koto_button_handle_mouse_leave( - GtkEventControllerMotion * controller, - gpointer user_data -) { - (void) controller; - - KotoButton * self = user_data; - - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - koto_button_unset_pseudoactive_styling(self); // Unset the pseudoactive styling -} - -void koto_button_hide_image(KotoButton * self) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - if (GTK_IS_WIDGET(self->button_pic)) { // Is a widget - gtk_widget_hide(self->button_pic); - } -} - -void koto_button_set_data( - KotoButton * self, - gpointer data -) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - self->arbitrary_data = data; -} - -void koto_button_set_pseudoactive_styling(KotoButton * self) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - if (gtk_widget_has_css_class(GTK_WIDGET(self), "pseudoactive")) { // Already is pseudoactive - return; - } - - gtk_widget_add_css_class(GTK_WIDGET(self), "pseudoactive"); // Set to pseudoactive -} - -void koto_button_set_badge_text( - KotoButton * self, - gchar * text -) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - if (koto_utils_string_is_valid(self->badge_text)) { // Have existing text - g_free(self->badge_text); - } - - if (!koto_utils_string_is_valid(text)) { // If the text is empty - self->badge_text = NULL; - - if (GTK_IS_LABEL(self->badge_label)) { // If badge label already exists - gtk_widget_hide(self->badge_label); // Hide the label - } - - return; - } - - self->badge_text = g_strdup(text); - - if (GTK_IS_LABEL(self->badge_label)) { // If badge label already exists - gtk_label_set_text(GTK_LABEL(self->badge_label), self->badge_text); - } else { - self->badge_label = gtk_label_new(self->badge_text); // Create our label - gtk_box_append(GTK_BOX(self), self->badge_label); - } - - g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_BADGE_TEXT]); -} - -void koto_button_set_file_path( - KotoButton * self, - gchar * file_path -) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - if (!koto_utils_string_is_valid(file_path)) { // file path is invalid - return; - } - - if (g_strcmp0(self->image_file_path, file_path) == 0) { // Request setting as same file - return; - } - - if (koto_utils_string_is_valid(self->image_file_path)) { // image file path is valid - g_free(self->image_file_path); - } - - self->use_from_file = TRUE; - self->image_file_path = g_strdup(file_path); - koto_button_show_image(self, FALSE); -} - -void koto_button_set_icon_name( - KotoButton * self, - gchar * icon_name, - gboolean for_alt -) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - if (!koto_utils_string_is_valid(icon_name)) { // Not a valid icon - return; - } - - gchar * copied_icon_name = g_strdup(icon_name); - - if (for_alt) { // Is for the alternate icon - if ((self->alt_icon_name != NULL) && strcmp(icon_name, self->alt_icon_name) != 0) { // If the icons are different - g_free(self->alt_icon_name); - } - - self->alt_icon_name = copied_icon_name; - } else { - if ((self->icon_name != NULL) && strcmp(icon_name, self->icon_name) != 0) { - g_free(self->icon_name); - } - - self->icon_name = copied_icon_name; - } - - gboolean hide_image = FALSE; - - if (for_alt && self->currently_showing_alt && ((self->alt_icon_name == NULL) || strcmp(self->alt_icon_name, "") == 0)) { // For alt, alt is currently showing, and no longer have alt - hide_image = TRUE; - } else if (!for_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Not for alt, no icon - hide_image = TRUE; - } - - if (GTK_IS_IMAGE(self->button_pic)) { // If we already have a button image - if (hide_image) { // Should hide the image - gtk_widget_hide(self->button_pic); // Hide - } else { // Should be showing the image - gtk_image_set_from_icon_name(GTK_IMAGE(self->button_pic), self->icon_name); // Just update the existing image immediately - } - } - - g_object_notify_by_pspec(G_OBJECT(self), for_alt ? btn_props[PROP_ALT_ICON_NAME] : btn_props[PROP_ICON_NAME]); -} - -void koto_button_set_image_position( - KotoButton * self, - KotoButtonImagePosition pos -) { - if (!KOTO_IS_BUTTON(self)) { // Not a button - return; - } - - if (self->image_position == pos) { // Is a different position that currently - return; - } - - if (GTK_IS_WIDGET(self->button_pic)) { // Button is already defined - if (pos == KOTO_BUTTON_IMAGE_POS_RIGHT) { // If we want to move the image to the right - gtk_box_reorder_child_after(GTK_BOX(self), self->button_pic, self->button_label); // Move image to after label - } else { // Moving image to left - gtk_box_reorder_child_after(GTK_BOX(self), self->button_label, self->button_pic); // Move label to after image - } - } - - self->image_position = pos; -} - -void koto_button_set_pixbuf_size( - KotoButton * self, - guint size -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (size == self->pix_size) { - return; - } - - self->pix_size = size; - gtk_widget_set_size_request(GTK_WIDGET(self), self->pix_size, self->pix_size); - - g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_PIX_SIZE]); -} - -void koto_button_set_resource_path( - KotoButton * self, - gchar * resource_path -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (!koto_utils_string_is_valid(resource_path)) { // Not a valid string - return; - } - - if (koto_utils_string_is_valid(self->resource_path)) { // Have a resource path already - g_free(self->resource_path); // Free it - } - - self->resource_path = g_strdup(resource_path); - - if (GTK_IS_IMAGE(self->button_pic)) { // Already have a button image - gtk_image_set_from_resource(GTK_IMAGE(self->button_pic), self->resource_path); - } else { - self->button_pic = gtk_image_new_from_resource(self->resource_path); // Create a new image from the resource - gtk_image_set_pixel_size(GTK_IMAGE(self->button_pic), self->pix_size); - gtk_image_set_icon_size(GTK_IMAGE(self->button_pic), GTK_ICON_SIZE_INHERIT); // Inherit height of parent widget - gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box - } -} - -void koto_button_set_text( - KotoButton * self, - gchar * text -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (!koto_utils_string_is_valid(text)) { // Invalid text - return; - } - - if (koto_utils_string_is_valid(self->text)) { // Text defined - g_free(self->text); // Free existing text - } - - self->text = g_strdup(text); - - if (GTK_IS_LABEL(self->button_label)) { // If we have a button label - if (koto_utils_string_is_valid(self->text)) { // Have text set - gtk_label_set_text(GTK_LABEL(self->button_label), self->text); - gtk_widget_show(self->button_label); // Show the label - } else { // Have a label but no longer text - gtk_box_remove(GTK_BOX(self), self->button_label); - g_free(self->button_label); - } - } else { // If we do not have a button label - if (koto_utils_string_is_valid(self->text)) { // If we have text - self->button_label = gtk_label_new(self->text); // Create our label - gtk_widget_add_css_class(self->button_label, "button-label"); - gtk_widget_set_hexpand(self->button_label, TRUE); - gtk_label_set_xalign(GTK_LABEL(self->button_label), 0); - - if (GTK_IS_IMAGE(self->button_pic)) { // If we have an image - gtk_box_insert_child_after(GTK_BOX(self), self->button_label, self->button_pic); - } else { - gtk_box_prepend(GTK_BOX(self), GTK_WIDGET(self->button_label)); - } - } - } - - g_object_notify_by_pspec(G_OBJECT(self), btn_props[PROP_TEXT]); -} - -void koto_button_set_text_justify( - KotoButton * self, - GtkJustification j -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (!GTK_IS_LABEL(self->button_label)) { // If we do not have a button label - return; - } - - if (j == GTK_JUSTIFY_CENTER) { // Center text - gtk_label_set_xalign(GTK_LABEL(self->button_label), 0.5); - } else if (j == GTK_JUSTIFY_RIGHT) { // Right align - gtk_label_set_xalign(GTK_LABEL(self->button_label), 1.0); - } else { - gtk_label_set_xalign(GTK_LABEL(self->button_label), 0); - } - - gtk_label_set_justify(GTK_LABEL(self->button_label), j); -} - -void koto_button_set_text_wrap( - KotoButton * self, - gboolean wrap -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (!GTK_IS_LABEL(self->button_label)) { // If we do not have a button label - return; - } - - gtk_label_set_single_line_mode(GTK_LABEL(self->button_label), !wrap); - gtk_label_set_wrap(GTK_LABEL(self->button_label), wrap); -} - -void koto_button_show_image( - KotoButton * self, - gboolean use_alt -) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (self->use_from_file) { // Use from a file instead of icon name - if (!koto_utils_string_is_valid(self->image_file_path)) { // Not set - return; - } - - if (GTK_IS_IMAGE(self->button_pic)) { // Already have an icon - gtk_image_set_from_file(GTK_IMAGE(self->button_pic), self->image_file_path); - } else { // Don't have an image yet - self->button_pic = gtk_image_new_from_file(self->image_file_path); // Create a new image from the file - gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box - } - } else { // From icon name - if (use_alt && ((self->alt_icon_name == NULL) || (strcmp(self->alt_icon_name, "") == 0))) { // Don't have an alt icon set - return; - } else if (!use_alt && ((self->icon_name == NULL) || (strcmp(self->icon_name, "") == 0))) { // Don't have icon set - return; - } - - self->currently_showing_alt = use_alt; - gchar * name = use_alt ? self->alt_icon_name : self->icon_name; - - if (GTK_IS_IMAGE(self->button_pic)) { - gtk_image_set_from_icon_name(GTK_IMAGE(self->button_pic), name); // Just update the existing image - } else { // Not an image - self->button_pic = gtk_image_new_from_icon_name(name); // Get our new image - gtk_box_prepend(GTK_BOX(self), self->button_pic); // Prepend to the box - } - } - - gtk_image_set_pixel_size(GTK_IMAGE(self->button_pic), self->pix_size); - gtk_image_set_icon_size(GTK_IMAGE(self->button_pic), GTK_ICON_SIZE_INHERIT); // Inherit height of parent widget - gtk_widget_show(self->button_pic); // Ensure we actually are showing the image -} - -void koto_button_unflatten(KotoButton * self) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - gtk_widget_remove_css_class(GTK_WIDGET(self), "flat"); -} - -void koto_button_unset_pseudoactive_styling(KotoButton * self) { - if (!KOTO_IS_BUTTON(self)) { - return; - } - - if (!gtk_widget_has_css_class(GTK_WIDGET(self), "pseudoactive")) { // Don't have the CSS class - return; - } - - gtk_widget_remove_css_class(GTK_WIDGET(self), "pseudoactive"); // Remove pseudoactive class -} - -KotoButton * koto_button_new_plain(gchar * label) { - return g_object_new( - KOTO_TYPE_BUTTON, - "button-text", - label, - NULL - ); -} - -KotoButton * koto_button_new_with_icon( - gchar * label, - gchar * icon_name, - gchar * alt_icon_name, - KotoButtonPixbufSize size -) { - return g_object_new( - KOTO_TYPE_BUTTON, - "button-text", - label, - "icon-name", - icon_name, - "alt-icon-name", - alt_icon_name, - "pixbuf-size", - koto_get_pixbuf_size(size), - NULL - ); -} - -KotoButton * koto_button_new_with_file( - gchar * label, - gchar * file_path, - KotoButtonPixbufSize size -) { - return g_object_new( - KOTO_TYPE_BUTTON, - "button-text", - label, - "use-from-file", - TRUE, - "image-file-path", - file_path, - "pixbuf-size", - koto_get_pixbuf_size(size), - NULL - ); -} - -KotoButton * koto_button_new_with_resource ( - gchar * resource_path, - KotoButtonPixbufSize size -) { - return g_object_new( - KOTO_TYPE_BUTTON, - "resource-path", - resource_path, - "pixbuf-size", - koto_get_pixbuf_size(size), - NULL - ); -} diff --git a/src/components/button.h b/src/components/button.h deleted file mode 100644 index 70648bc..0000000 --- a/src/components/button.h +++ /dev/null @@ -1,174 +0,0 @@ -/* button.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include - -G_BEGIN_DECLS - -typedef enum { - KOTO_BUTTON_CLICK_TYPE_PRIMARY = 1, - KOTO_BUTTON_CLICK_TYPE_SECONDARY = 3 -} KotoButtonClickType; - -typedef enum { - KOTO_BUTTON_PIXBUF_SIZE_INVALID, - KOTO_BUTTON_PIXBUF_SIZE_TINY, - KOTO_BUTTON_PIXBUF_SIZE_SMALL, - KOTO_BUTTON_PIXBUF_SIZE_NORMAL, - KOTO_BUTTON_PIXBUF_SIZE_LARGE, - KOTO_BUTTON_PIXBUF_SIZE_MASSIVE, - KOTO_BUTTON_PIXBUF_SIZE_GODLIKE -} KotoButtonPixbufSize; - -typedef enum { - KOTO_BUTTON_IMAGE_POS_LEFT, - KOTO_BUTTON_IMAGE_POS_RIGHT -} KotoButtonImagePosition; - -#define NUM_BUILTIN_SIZES 7 - -#define KOTO_TYPE_BUTTON (koto_button_get_type()) -G_DECLARE_FINAL_TYPE(KotoButton, koto_button, KOTO, BUTTON, GtkBox) -#define KOTO_IS_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_BUTTON)) - -guint koto_get_pixbuf_size(KotoButtonPixbufSize size); - -KotoButton * koto_button_new_plain(gchar * label); - -KotoButton * koto_button_new_with_icon( - gchar * label, - gchar * icon_name, - gchar * alt_icon_name, - KotoButtonPixbufSize size -); - -KotoButton * koto_button_new_with_file( - gchar * label, - gchar * file_path, - KotoButtonPixbufSize size -); - -KotoButton * koto_button_new_with_resource( - gchar * resource_path, - KotoButtonPixbufSize size -); - -void koto_button_add_click_handler( - KotoButton * self, - KotoButtonClickType button, - GCallback handler, - gpointer user_data -); - -void koto_button_flip(KotoButton * self); - -gpointer koto_button_get_data(KotoButton * self); - -void koto_button_global_page_nav_callback( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_button_handle_mouse_enter( - GtkEventControllerMotion * controller, - double x, - double y, - gpointer user_data -); - -void koto_button_handle_mouse_leave( - GtkEventControllerMotion * controller, - gpointer user_data -); - -void koto_button_hide_image(KotoButton * self); - -void koto_button_set_data( - KotoButton * self, - gpointer data -); - -void koto_button_set_pseudoactive_styling(KotoButton * self); - -void koto_button_set_badge_text( - KotoButton * self, - gchar * text -); - -void koto_button_set_file_path( - KotoButton * self, - gchar * file_path -); - -void koto_button_set_icon_name( - KotoButton * self, - gchar * icon_name, - gboolean for_alt -); - -void koto_button_set_image_position( - KotoButton * self, - KotoButtonImagePosition pos -); - -void koto_button_set_resource_path( - KotoButton * self, - gchar * resource_path -); - -void koto_button_set_pixbuf( - KotoButton * self, - GdkPixbuf * pix -); - -void koto_button_set_pixbuf_size( - KotoButton * self, - guint size -); - -void koto_button_set_text( - KotoButton * self, - gchar * text -); - -void koto_button_set_text_justify( - KotoButton * self, - GtkJustification j -); - -void koto_button_set_text_wrap( - KotoButton * self, - gboolean wrap -); - -void koto_button_show_image( - KotoButton * self, - gboolean use_alt -); - -void koto_button_unflatten(KotoButton * self); - -void koto_button_unset_pseudoactive_styling(KotoButton * self); - -G_END_DECLS diff --git a/src/components/cover-art-button.c b/src/components/cover-art-button.c deleted file mode 100644 index e94c2fd..0000000 --- a/src/components/cover-art-button.c +++ /dev/null @@ -1,269 +0,0 @@ -/* cover-art-button.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../koto-utils.h" -#include "button.h" -#include "cover-art-button.h" - -struct _KotoCoverArtButton { - GObject parent_instance; - - GtkWidget * art; - GtkWidget * main; - GtkWidget * revealer; - KotoButton * play_button; - - gchar * art_path; - - guint height; - guint width; -}; - -G_DEFINE_TYPE(KotoCoverArtButton, koto_cover_art_button, G_TYPE_OBJECT); - -enum { - PROP_0, - PROP_DESIRED_HEIGHT, - PROP_DESIRED_WIDTH, - PROP_ART_PATH, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; -static void koto_cover_art_button_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_cover_art_button_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_cover_art_button_class_init(KotoCoverArtButtonClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->get_property = koto_cover_art_button_get_property; - gobject_class->set_property = koto_cover_art_button_set_property; - - props[PROP_DESIRED_HEIGHT] = g_param_spec_uint( - "desired-height", - "Desired height", - "Desired height", - 0, - G_MAXUINT, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - props[PROP_DESIRED_WIDTH] = g_param_spec_uint( - "desired-width", - "Desired width", - "Desired width", - 0, - G_MAXUINT, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - props[PROP_ART_PATH] = g_param_spec_string( - "art-path", - "Path to art", - "Path to art", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_cover_art_button_init(KotoCoverArtButton * self) { - self->main = gtk_overlay_new(); // Create our overlay container - gtk_widget_add_css_class(self->main, "cover-art-button"); - self->revealer = gtk_revealer_new(); // Create a new revealer - gtk_revealer_set_transition_type(GTK_REVEALER(self->revealer), GTK_REVEALER_TRANSITION_TYPE_CROSSFADE); - gtk_revealer_set_transition_duration(GTK_REVEALER(self->revealer), 400); - - GtkWidget * controls = gtk_center_box_new(); // Create a center box for the controls - - self->play_button = koto_button_new_with_icon("", "media-playback-start-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - gtk_center_box_set_center_widget(GTK_CENTER_BOX(controls), GTK_WIDGET(self->play_button)); - - gtk_revealer_set_child(GTK_REVEALER(self->revealer), controls); - koto_cover_art_button_hide_overlay_controls(NULL, self); // Hide by default - gtk_overlay_add_overlay(GTK_OVERLAY(self->main), self->revealer); // Add our revealer as the overlay - - GtkEventController * motion_controller = gtk_event_controller_motion_new(); // Create our new motion event controller to track mouse leave and enter - - g_signal_connect(motion_controller, "enter", G_CALLBACK(koto_cover_art_button_show_overlay_controls), self); - g_signal_connect(motion_controller, "leave", G_CALLBACK(koto_cover_art_button_hide_overlay_controls), self); - gtk_widget_add_controller(self->main, motion_controller); - - self->art_path = NULL; -} - -static void koto_cover_art_button_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - (void) val; - - switch (prop_id) { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_cover_art_button_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoCoverArtButton * self = KOTO_COVER_ART_BUTTON(obj); - - switch (prop_id) { - case PROP_ART_PATH: - koto_cover_art_button_set_art_path(self, (gchar*) g_value_get_string(val)); // Get the value and call our set_art_path with it - break; - case PROP_DESIRED_HEIGHT: - koto_cover_art_button_set_dimensions(self, g_value_get_uint(val), 0); - break; - case PROP_DESIRED_WIDTH: - koto_cover_art_button_set_dimensions(self, 0, g_value_get_uint(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_cover_art_button_hide_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -) { - (void) controller; - KotoCoverArtButton * self = data; - - gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), FALSE); -} - -KotoButton * koto_cover_art_button_get_button(KotoCoverArtButton * self) { - if (!KOTO_IS_COVER_ART_BUTTON(self)) { - return NULL; - } - - return self->play_button; -} - -GtkWidget * koto_cover_art_button_get_main(KotoCoverArtButton * self) { - if (!KOTO_IS_COVER_ART_BUTTON(self)) { - return NULL; - } - - return self->main; -} - -void koto_cover_art_button_set_art_path( - KotoCoverArtButton * self, - gchar * art_path -) { - if (!KOTO_IS_COVER_ART_BUTTON(self)) { - return; - } - - gboolean defined_artwork = koto_utils_string_is_valid(art_path); - - if (GTK_IS_IMAGE(self->art)) { // Already have an image - if (!defined_artwork) { // No art path or empty string - gtk_image_set_from_icon_name(GTK_IMAGE(self->art), "audio-x-generic-symbolic"); - } else { // Have an art path - if (g_strcmp0(self->art_path, art_path) != 0) { - self->art_path = g_strdup(art_path); // Set our art path - gtk_image_set_from_file(GTK_IMAGE(self->art), self->art_path); // Set from the file - } - } - } else { // If we don't have an image - self->art = koto_utils_create_image_from_filepath(defined_artwork ? g_strdup(art_path) : NULL, "audio-x-generic-symbolic", self->width, self->height); - gtk_overlay_set_child(GTK_OVERLAY(self->main), self->art); // Set the child - } -} - -void koto_cover_art_button_set_dimensions( - KotoCoverArtButton * self, - guint height, - guint width -) { - if (!KOTO_IS_COVER_ART_BUTTON(self)) { - return; - } - - if (height != 0) { - self->height = height; - } - - if (width != 0) { - self->width = width; - } - - if ((self->height != 0) && (self->width != 0)) { // Both height and width set - gtk_widget_set_size_request(self->main, self->width, self->height); // Update our widget - - if (GTK_IS_IMAGE(self->art)) { // Art is defined - gtk_widget_set_size_request(self->art, self->width, self->height); // Update our image as well - } - } -} - -void koto_cover_art_button_show_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -) { - (void) controller; - KotoCoverArtButton * self = data; - - gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), TRUE); -} - -KotoCoverArtButton * koto_cover_art_button_new( - guint height, - guint width, - gchar * art_path -) { - return g_object_new( - KOTO_TYPE_COVER_ART_BUTTON, - "desired-height", - height, - "desired-width", - width, - "art-path", - art_path, - NULL - ); -} \ No newline at end of file diff --git a/src/components/cover-art-button.h b/src/components/cover-art-button.h deleted file mode 100644 index d1f7d44..0000000 --- a/src/components/cover-art-button.h +++ /dev/null @@ -1,63 +0,0 @@ -/* cover-art-button.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "button.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_COVER_ART_BUTTON (koto_cover_art_button_get_type()) -G_DECLARE_FINAL_TYPE(KotoCoverArtButton, koto_cover_art_button, KOTO, COVER_ART_BUTTON, GObject); -#define KOTO_IS_COVER_ART_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_COVER_ART_BUTTON)) - -/** - * Cover Art Functions - **/ - -KotoCoverArtButton * koto_cover_art_button_new( - guint height, - guint width, - gchar * art_path -); - -KotoButton * koto_cover_art_button_get_button(KotoCoverArtButton * self); - -GtkWidget * koto_cover_art_button_get_main(KotoCoverArtButton * self); - -void koto_cover_art_button_hide_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -); - -void koto_cover_art_button_set_art_path( - KotoCoverArtButton * self, - gchar * art_path -); - -void koto_cover_art_button_set_dimensions( - KotoCoverArtButton * self, - guint height, - guint width -); - -void koto_cover_art_button_show_overlay_controls( - GtkEventControllerFocus * controller, - gpointer data -); - -G_END_DECLS \ No newline at end of file diff --git a/src/components/track-item.c b/src/components/track-item.c deleted file mode 100644 index 368e79f..0000000 --- a/src/components/track-item.c +++ /dev/null @@ -1,158 +0,0 @@ -/* track-item.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "indexer/structs.h" -#include "playlist/add-remove-track-popover.h" -#include "button.h" -#include "track-item.h" - -extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; - -struct _KotoTrackItem { - GtkBox parent_instance; - KotoTrack * track; - - GtkWidget * track_label; -}; - -struct _KotoTrackItemClass { - GtkBoxClass parent_class; -}; - -enum { - PROP_0, - PROP_TRACK, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -G_DEFINE_TYPE(KotoTrackItem, koto_track_item, GTK_TYPE_BOX); - -static void koto_track_item_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_track_item_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_track_item_class_init(KotoTrackItemClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_track_item_set_property; - gobject_class->get_property = koto_track_item_get_property; - - props[PROP_TRACK] = g_param_spec_object( - "track", - "Track", - "Track", - KOTO_TYPE_TRACK, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_track_item_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoTrackItem * self = KOTO_TRACK_ITEM(obj); - - switch (prop_id) { - case PROP_TRACK: - g_value_set_object(val, self->track); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_track_item_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoTrackItem * self = KOTO_TRACK_ITEM(obj); - - switch (prop_id) { - case PROP_TRACK: - koto_track_item_set_track(self, (KotoTrack*) g_value_get_object(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_track_item_init(KotoTrackItem * self) { - self->track_label = gtk_label_new(NULL); // Create with no track name - gtk_label_set_xalign(GTK_LABEL(self->track_label), 0.0); - - gtk_widget_add_css_class(GTK_WIDGET(self), "track-item"); - gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE); - gtk_widget_set_hexpand(GTK_WIDGET(self->track_label), TRUE); - - gtk_box_prepend(GTK_BOX(self), self->track_label); -} - -KotoTrack * koto_track_item_get_track(KotoTrackItem * self) { - if (!KOTO_IS_TRACK_ITEM(self)) { - return NULL; - } - - return self->track; -} - -void koto_track_item_set_track( - KotoTrackItem * self, - KotoTrack * track -) { - if (track == NULL) { // Not a track - return; - } - - self->track = track; - gchar * track_name; - - g_object_get(self->track, "parsed-name", &track_name, NULL); - gtk_label_set_text(GTK_LABEL(self->track_label), track_name); // Update the text -} - -KotoTrackItem * koto_track_item_new(KotoTrack * track) { - return g_object_new( - KOTO_TYPE_TRACK_ITEM, - "track", - track, - NULL - ); -} diff --git a/src/components/track-item.h b/src/components/track-item.h deleted file mode 100644 index 762cf44..0000000 --- a/src/components/track-item.h +++ /dev/null @@ -1,46 +0,0 @@ -/* track-item.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_TRACK_ITEM (koto_track_item_get_type()) - -G_DECLARE_FINAL_TYPE(KotoTrackItem, koto_track_item, KOTO, TRACK_ITEM, GtkBox) - -KotoTrackItem* koto_track_item_new(KotoTrack * track); -void koto_track_item_handle_add_to_playlist_button_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -KotoTrack * koto_track_item_get_track(KotoTrackItem * self); - -void koto_track_item_set_track( - KotoTrackItem * self, - KotoTrack * track -); - -G_END_DECLS diff --git a/src/components/track-table.c b/src/components/track-table.c deleted file mode 100644 index 0907c08..0000000 --- a/src/components/track-table.c +++ /dev/null @@ -1,540 +0,0 @@ -/* track-table.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../db/cartographer.h" -#include "../playback/engine.h" -#include "../playlist/current.h" -#include "../playlist/playlist.h" -#include "../koto-utils.h" -#include "../koto-window.h" -#include "action-bar.h" -#include "button.h" -#include "track-table.h" - -extern KotoActionBar * action_bar; -extern KotoCartographer * koto_maps; -extern KotoCurrentPlaylist * current_playlist; -extern KotoPlaybackEngine * playback_engine; -extern KotoWindow * main_window; - -struct _KotoTrackTable { - GObject parent_instance; - gchar * uuid; - KotoPlaylist * playlist; - - GtkWidget * main; - - GtkListItemFactory * item_factory; - GListModel * model; - GtkSelectionModel * selection_model; - - GtkWidget * track_list_content; - GtkWidget * track_list_header; - GtkWidget * track_list_view; - - KotoButton * track_album_button; - KotoButton * track_artist_button; - KotoButton * track_num_button; - KotoButton * track_title_button; - - GtkSizeGroup * track_pos_size_group; - GtkSizeGroup * track_name_size_group; - GtkSizeGroup * track_album_size_group; - GtkSizeGroup * track_artist_size_group; -}; - -struct _KotoTrackTableClass { - GObjectClass parent_class; -}; - -G_DEFINE_TYPE(KotoTrackTable, koto_track_table, G_TYPE_OBJECT); - -static void koto_track_table_class_init(KotoTrackTableClass * c) { - (void) c; -} - -static void koto_track_table_init(KotoTrackTable * self) { - self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - self->track_name_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->track_pos_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->track_album_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - self->track_artist_size_group = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL); - - self->item_factory = gtk_signal_list_item_factory_new(); // Create a new signal list item factory - g_signal_connect(self->item_factory, "setup", G_CALLBACK(koto_track_table_setup_track_item), self); - g_signal_connect(self->item_factory, "bind", G_CALLBACK(koto_track_table_bind_track_item), self); - - self->track_list_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class((self->track_list_content), "track-list-content"); - gtk_widget_set_hexpand(self->track_list_content, TRUE); // Expand horizontally - gtk_widget_set_vexpand(self->track_list_content, TRUE); // Expand vertically - - self->track_list_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->track_list_header, "track-list-header"); - koto_track_table_create_tracks_header(self); // Create our tracks header content - - self->track_list_view = gtk_list_view_new(NULL, self->item_factory); // Create our list view with no model yet - gtk_widget_add_css_class(self->track_list_view, "track-list-columned"); - gtk_widget_set_hexpand(self->track_list_view, TRUE); // Expand horizontally - gtk_widget_set_vexpand(self->track_list_view, TRUE); // Expand vertically - - gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_header); - gtk_box_append(GTK_BOX(self->track_list_content), self->track_list_view); - - gtk_box_append(GTK_BOX(self->main), self->track_list_content); - - g_signal_connect(action_bar, "closed", G_CALLBACK(koto_track_table_handle_action_bar_closed), self); // Handle closed action bar -} - -void koto_track_table_bind_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - KotoTrackTable * self -) { - (void) factory; - - GtkWidget * box = gtk_list_item_get_child(item); - GtkWidget * track_position_label = gtk_widget_get_first_child(box); - GtkWidget * track_name_label = gtk_widget_get_next_sibling(track_position_label); - GtkWidget * track_album_label = gtk_widget_get_next_sibling(track_name_label); - GtkWidget * track_artist_label = gtk_widget_get_next_sibling(track_album_label); - - KotoTrack * track = gtk_list_item_get_item(item); // Get the track UUID from our model - - if (!KOTO_IS_TRACK(track)) { - return; - } - - gchar * track_name = koto_track_get_name(track); - gchar * album_uuid = NULL; - gchar * artist_uuid = NULL; - - g_object_get( - track, - "album-uuid", - &album_uuid, - "artist-uuid", - &artist_uuid, - NULL - ); - - guint track_position = koto_playlist_get_position_of_track(self->playlist, track) + 1; - - gtk_label_set_label(GTK_LABEL(track_position_label), g_strdup_printf("%u", track_position)); // Set the track position - gtk_label_set_label(GTK_LABEL(track_name_label), track_name); // Set our track name - - if (koto_utils_string_is_valid(album_uuid)) { // Is associated with an album - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); - - if (KOTO_IS_ALBUM(album)) { - gtk_label_set_label(GTK_LABEL(track_album_label), koto_album_get_name(album)); // Get the name of the album and set it to the label - } - } - - if (koto_utils_string_is_valid(artist_uuid)) { // Is associated with an artist - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); - - if (KOTO_IS_ARTIST(artist)) { - gtk_label_set_label(GTK_LABEL(track_artist_label), koto_artist_get_name(artist)); // Get the name of the artist and set it to the label - } - } - - GList * data = NULL; - data = g_list_append(data, self); // Reference self first - data = g_list_append(data, koto_track_get_uuid(track)); // Next reference the track UUID string - - g_object_set_data(G_OBJECT(box), "data", data); // Bind our list data -} - -void koto_track_table_create_tracks_header(KotoTrackTable * self) { - self->track_num_button = koto_button_new_with_icon("#", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL); - koto_button_add_click_handler(self->track_num_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_num_clicked), self); - koto_button_set_image_position(self->track_num_button, KOTO_BUTTON_IMAGE_POS_RIGHT); // Move the image to the right - gtk_size_group_add_widget(self->track_pos_size_group, GTK_WIDGET(self->track_num_button)); - - self->track_title_button = koto_button_new_plain("Title"); - koto_button_add_click_handler(self->track_title_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_name_clicked), self); - gtk_size_group_add_widget(self->track_name_size_group, GTK_WIDGET(self->track_title_button)); - - self->track_album_button = koto_button_new_plain("Album"); - - gtk_widget_set_margin_start(GTK_WIDGET(self->track_album_button), 50); - koto_button_add_click_handler(self->track_album_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_album_clicked), self); - gtk_size_group_add_widget(self->track_album_size_group, GTK_WIDGET(self->track_album_button)); - - self->track_artist_button = koto_button_new_plain("Artist"); - - gtk_widget_set_margin_start(GTK_WIDGET(self->track_artist_button), 50); - koto_button_add_click_handler(self->track_artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_track_table_handle_track_artist_clicked), self); - gtk_size_group_add_widget(self->track_artist_size_group, GTK_WIDGET(self->track_artist_button)); - - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_num_button)); - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_title_button)); - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_album_button)); - gtk_box_append(GTK_BOX(self->track_list_header), GTK_WIDGET(self->track_artist_button)); -} - -GtkWidget * koto_track_table_get_main(KotoTrackTable * self) { - return self->main; -} - -void koto_track_table_handle_action_bar_closed ( - KotoActionBar * bar, - gpointer data -) { - (void) bar; - KotoTrackTable * self = data; - - if (!KOTO_IS_TRACK_TABLE(self)) { // Self is not a track table - return; - } - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set - return; - } - - gtk_selection_model_unselect_all(self->selection_model); - gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window -} - -void koto_track_table_handle_track_album_clicked ( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoTrackTable * self = user_data; - - gtk_widget_add_css_class(GTK_WIDGET(self->track_album_button), "active"); - koto_button_hide_image(self->track_num_button); // Go back to hiding the image - koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM); -} - -void koto_track_table_handle_track_artist_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoTrackTable * self = user_data; - - gtk_widget_add_css_class(GTK_WIDGET(self->track_artist_button), "active"); - koto_button_hide_image(self->track_num_button); // Go back to hiding the image - koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST); -} - -void koto_track_table_item_handle_clicked( - GtkGesture * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) x; - (void) y; - - if (n_press != 2) { // Not a double click or tap - return; - } - - GObject * track_item_as_object = G_OBJECT(user_data); - - if (!G_IS_OBJECT(track_item_as_object)) { // Not a GObject - return; - } - - GList * data = g_object_get_data(track_item_as_object, "data"); - - KotoTrackTable * self = g_list_nth_data(data, 0); - gchar * track_uuid = g_list_nth_data(data, 1); - - if (!koto_utils_string_is_valid(track_uuid)) { // Not a valid string - return; - } - - gtk_selection_model_unselect_all(self->selection_model); - gtk_widget_grab_focus(GTK_WIDGET(main_window)); // Focus on the window - koto_action_bar_toggle_reveal(action_bar, FALSE); - koto_action_bar_close(action_bar); // Close the action bar - koto_current_playlist_set_playlist(current_playlist, self->playlist, FALSE, FALSE); // Set the current playlist to the artist's playlist but do not play immediately - koto_playlist_set_track_as_current(self->playlist, track_uuid); // Set this track as the current one for the playlist - koto_playback_engine_set_track_by_uuid(playback_engine, track_uuid, FALSE); // Tell our playback engine to start playing at this track -} - -void koto_track_table_handle_track_name_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoTrackTable * self = user_data; - - gtk_widget_add_css_class(GTK_WIDGET(self->track_title_button), "active"); - koto_button_hide_image(self->track_num_button); // Go back to hiding the image - koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME); -} - -void koto_track_table_handle_track_num_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoTrackTable * self = user_data; - - KotoPreferredPlaylistSortType current_model = koto_playlist_get_current_model(self->playlist); - - if (current_model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) { // Set to newest currently - koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST); // Sort reversed (oldest) - koto_button_show_image(self->track_num_button, TRUE); // Use inverted value (pan-up-symbolic) - } else { - koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT); // Sort newest - koto_button_show_image(self->track_num_button, FALSE); // Use pan down default - } -} - -void koto_track_table_handle_tracks_selected( - GtkSelectionModel * model, - guint position, - guint n_items, - gpointer user_data -) { - (void) position; - KotoTrackTable * self = user_data; - - if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table - return; - } - - if (n_items == 0) { // No items selected - koto_action_bar_toggle_reveal(action_bar, FALSE); // Hide the action bar - return; - } - - GtkBitset * selected_items_bitset = gtk_selection_model_get_selection(model); // Get the selected items as a GtkBitSet - GtkBitsetIter iter; - GList * selected_tracks = NULL; - GList * selected_tracks_pos = NULL; - - guint first_track_pos; - - if (!gtk_bitset_iter_init_first(&iter, selected_items_bitset, &first_track_pos)) { // Failed to get the first item - return; - } - - selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(first_track_pos)); - - gboolean have_more_items = TRUE; - - while (have_more_items) { // While we are able to get selected items - guint track_pos; - have_more_items = gtk_bitset_iter_next(&iter, &track_pos); - if (have_more_items) { // Got the next track - selected_tracks_pos = g_list_append(selected_tracks_pos, GUINT_TO_POINTER(track_pos)); - } - } - - GList * cur_pos_list; - - for (cur_pos_list = selected_tracks_pos; cur_pos_list != NULL; cur_pos_list = cur_pos_list->next) { // Iterate over every position that we accumulated - KotoTrack * selected_track = g_list_model_get_item(self->model, GPOINTER_TO_UINT(cur_pos_list->data)); // Get the KotoTrack in the GListModel for this current position - selected_tracks = g_list_append(selected_tracks, selected_track); // Add to selected tracks - } - - koto_action_bar_set_tracks_in_playlist_selection(action_bar, koto_playlist_get_uuid(self->playlist), selected_tracks); // Set the tracks for the playlist selection - koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the items -} - -void koto_track_table_set_model( - KotoTrackTable * self, - KotoPreferredPlaylistSortType model -) { - if (!KOTO_IS_TRACK_TABLE(self)) { - return; - } - - koto_playlist_apply_model(self->playlist, model); // Apply our new model - self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel - - if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently - gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active"); - } - - if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently - gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active"); - } - - if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name - gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active"); - } - - self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model)); - g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_track_table_handle_tracks_selected), self); // Bind to our selection changed - - gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model -} - -void koto_track_table_set_playlist_model ( - KotoTrackTable * self, - KotoPreferredPlaylistSortType model -) { - if (!KOTO_IS_TRACK_TABLE(self)) { // Not a track table - return; - } - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // Don't have a playlist yet - return; - } - - koto_playlist_apply_model(self->playlist, model); // Apply our new model - self->model = G_LIST_MODEL(koto_playlist_get_store(self->playlist)); // Get the latest generated model / store and cast it as a GListModel - - if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Not sorting by album currently - gtk_widget_remove_css_class(GTK_WIDGET(self->track_album_button), "active"); - } - - if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Not sorting by artist currently - gtk_widget_remove_css_class(GTK_WIDGET(self->track_artist_button), "active"); - } - - if (model != KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Not sorting by track name - gtk_widget_remove_css_class(GTK_WIDGET(self->track_title_button), "active"); - } - - self->selection_model = GTK_SELECTION_MODEL(gtk_multi_selection_new(self->model)); - g_signal_connect(self->selection_model, "selection-changed", G_CALLBACK(koto_track_table_handle_tracks_selected), self); // Bind to our selection changed - - gtk_list_view_set_model(GTK_LIST_VIEW(self->track_list_view), self->selection_model); // Set our multi selection model to our provided model - - KotoPreferredPlaylistSortType current_model = koto_playlist_get_current_model(self->playlist); // Get the current model - - if (current_model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST) { - koto_button_show_image(self->track_num_button, TRUE); // Immediately use pan-up-symbolic - } -} - -void koto_track_table_set_playlist( - KotoTrackTable * self, - KotoPlaylist * playlist -) { - if (!KOTO_IS_TRACK_TABLE(self)) { - return; - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - return; - } - - self->playlist = playlist; - koto_track_table_set_playlist_model(self, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT); // TODO: Enable this to be changed -} - -void koto_track_table_setup_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - gpointer user_data -) { - (void) factory; - KotoTrackTable * self = user_data; - - if (!KOTO_IS_TRACK_TABLE(self)) { - return; - } - - GtkWidget * item_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Have a horizontal box for our content - - gtk_widget_add_css_class(item_content, "track-list-columned-item"); - - GtkWidget * track_number = gtk_label_new(NULL); // Our track number - - gtk_label_set_xalign(GTK_LABEL(track_number), 0); - gtk_widget_add_css_class(track_number, "track-column-number"); - gtk_widget_set_halign(track_number, GTK_ALIGN_END); - gtk_widget_set_hexpand(track_number, FALSE); - gtk_widget_set_size_request(track_number, 150, -1); - gtk_box_append(GTK_BOX(item_content), track_number); - gtk_size_group_add_widget(self->track_pos_size_group, track_number); - - GtkWidget * track_name = gtk_label_new(NULL); // Our track name - - gtk_label_set_xalign(GTK_LABEL(track_name), 0); - gtk_widget_add_css_class(track_name, "track-column-name"); - gtk_widget_set_halign(track_name, GTK_ALIGN_START); - gtk_widget_set_hexpand(track_name, FALSE); - gtk_widget_set_size_request(track_name, 350, -1); - gtk_box_append(GTK_BOX(item_content), track_name); - gtk_size_group_add_widget(self->track_name_size_group, track_name); - - GtkWidget * track_album = gtk_label_new(NULL); // Our track album - - gtk_label_set_xalign(GTK_LABEL(track_album), 0); - gtk_widget_add_css_class(track_album, "track-column-album"); - gtk_widget_set_halign(track_album, GTK_ALIGN_START); - gtk_widget_set_hexpand(track_album, FALSE); - gtk_widget_set_margin_start(track_album, 50); - gtk_widget_set_size_request(track_album, 350, -1); - gtk_box_append(GTK_BOX(item_content), track_album); - gtk_size_group_add_widget(self->track_album_size_group, track_album); - - GtkWidget * track_artist = gtk_label_new(NULL); // Our track artist - - gtk_label_set_xalign(GTK_LABEL(track_artist), 0); - gtk_widget_add_css_class(track_artist, "track-column-artist"); - gtk_widget_set_halign(track_artist, GTK_ALIGN_START); - gtk_widget_set_hexpand(track_artist, TRUE); - gtk_widget_set_margin_start(track_artist, 50); - gtk_widget_set_size_request(track_artist, 350, -1); - gtk_box_append(GTK_BOX(item_content), track_artist); - gtk_size_group_add_widget(self->track_artist_size_group, track_artist); - - gtk_list_item_set_child(item, item_content); - - GtkGesture * double_click_gesture = gtk_gesture_click_new(); // Create our new GtkGesture for double-click handling - gtk_widget_add_controller(item_content, GTK_EVENT_CONTROLLER(double_click_gesture)); // Have our item handle double clicking - g_signal_connect(double_click_gesture, "released", G_CALLBACK(koto_track_table_item_handle_clicked), item_content); -} - -KotoTrackTable * koto_track_table_new() { - return g_object_new( - KOTO_TYPE_TRACK_TABLE, - NULL - ); -} \ No newline at end of file diff --git a/src/components/track-table.h b/src/components/track-table.h deleted file mode 100644 index 613cbf3..0000000 --- a/src/components/track-table.h +++ /dev/null @@ -1,122 +0,0 @@ -/* track-table.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../playlist/playlist.h" -#include "action-bar.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_TRACK_TABLE koto_track_table_get_type() -#define KOTO_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_TRACK_TABLE, KotoTrackTable)) -typedef struct _KotoTrackTable KotoTrackTable; -typedef struct _KotoTrackTableClass KotoTrackTableClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_track_type_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_TRACK_TABLE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_TRACK_TABLE)) - -void koto_track_table_bind_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - KotoTrackTable * self -); - -void koto_track_table_create_tracks_header(KotoTrackTable * self); - -GtkWidget * koto_track_table_get_main(KotoTrackTable * self); - -void koto_track_table_handle_action_bar_closed( - KotoActionBar * bar, - gpointer data -); - -void koto_track_table_handle_track_album_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_track_table_handle_track_artist_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_track_table_handle_track_name_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_track_table_handle_track_name_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_track_table_handle_track_num_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_track_table_handle_tracks_selected( - GtkSelectionModel * model, - guint position, - guint n_items, - gpointer user_data -); - -void koto_track_table_set_model( - KotoTrackTable * self, - KotoPreferredPlaylistSortType model -); - -void koto_track_table_set_playlist_model( - KotoTrackTable * self, - KotoPreferredPlaylistSortType model -); - -void koto_track_table_set_playlist( - KotoTrackTable * self, - KotoPlaylist * playlist -); - -void koto_track_table_setup_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - gpointer user_data -); - -KotoTrackTable * koto_track_table_new(); - -G_END_DECLS \ No newline at end of file diff --git a/src/config/config.c b/src/config/config.c deleted file mode 100644 index e31b4d2..0000000 --- a/src/config/config.c +++ /dev/null @@ -1,768 +0,0 @@ -/* config.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include - -#include "../db/cartographer.h" -#include "../playback/engine.h" -#include "../koto-paths.h" -#include "../koto-utils.h" - -#include "config.h" - -extern int errno; -extern const gchar * koto_config_template; -extern KotoCartographer * koto_maps; -extern KotoPlaybackEngine * playback_engine; - -enum { - PROP_0, - PROP_PLAYBACK_CONTINUE_ON_PLAYLIST, - PROP_PLAYBACK_LAST_USED_VOLUME, - PROP_PLAYBACK_MAINTAIN_SHUFFLE, - PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT, - PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT, - PROP_PREFERRED_ALBUM_SORT_TYPE, - PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION, - PROP_UI_ALBUM_INFO_SHOW_GENRES, - PROP_UI_ALBUM_INFO_SHOW_NARRATOR, - PROP_UI_ALBUM_INFO_SHOW_YEAR, - PROP_UI_THEME_DESIRED, - PROP_UI_THEME_OVERRIDE, - N_PROPS, -}; - -static GParamSpec * config_props[N_PROPS] = { - 0 -}; - -struct _KotoConfig { - GObject parent_instance; - toml_table_t * toml_ref; - - GFile * config_file; - GFileMonitor * config_file_monitor; - - gchar * path; - gboolean finalized; - - /* Library Attributes */ - - // These are useful for when we need to determine separately if we need to index initial builtin folders that did not exist previously (during load) - gboolean has_type_audiobook; - gboolean has_type_music; - gboolean has_type_podcast; - - /* Playback Settings */ - - gboolean playback_continue_on_playlist; - gdouble playback_last_used_volume; - gboolean playback_maintain_shuffle; - guint playback_jump_backwards_increment; - guint playback_jump_forwards_increment; - - /* Misc Prefs */ - - KotoPreferredAlbumSortType preferred_album_sort_type; - - /* UI Settings */ - - gboolean ui_album_info_show_description; - gboolean ui_album_info_show_genres; - gboolean ui_album_info_show_narrator; - gboolean ui_album_info_show_year; - - gchar * ui_theme_desired; - gboolean ui_theme_override; -}; - -struct _KotoConfigClass { - GObjectClass parent_class; -}; - -G_DEFINE_TYPE(KotoConfig, koto_config, G_TYPE_OBJECT); - -KotoConfig * config; - -static void koto_config_constructed(GObject * obj); - -static void koto_config_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_config_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_config_class_init(KotoConfigClass * c) { - GObjectClass * gobject_class; - gobject_class = G_OBJECT_CLASS(c); - gobject_class->constructed = koto_config_constructed; - gobject_class->get_property = koto_config_get_property; - gobject_class->set_property = koto_config_set_property; - - config_props[PROP_PLAYBACK_CONTINUE_ON_PLAYLIST] = g_param_spec_boolean( - "playback-continue-on-playlist", - "Continue Playback of Playlist", - "Continue playback of a Playlist after playing a specific track in the playlist", - FALSE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_PLAYBACK_LAST_USED_VOLUME] = g_param_spec_double( - "playback-last-used-volume", - "Last Used Volume", - "Last Used Volume", - 0, - 1, - 0.5, // 50% - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_PLAYBACK_MAINTAIN_SHUFFLE] = g_param_spec_boolean( - "playback-maintain-shuffle", - "Maintain Shuffle on Playlist Change", - "Maintain shuffle setting when changing playlists", - TRUE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT] = g_param_spec_uint( - "playback-jump-backwards-increment", - "Jump Backwards Increment", - "Jump Backwards Increment", - 5, // 5s - 90, // 1min30s - 10, // 10s - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT] = g_param_spec_uint( - "playback-jump-forwards-increment", - "Jump Forwards Increment", - "Jump Forwards Increment", - 5, // 5s - 90, // 1min30s - 30, // 30s - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_PREFERRED_ALBUM_SORT_TYPE] = g_param_spec_string( - "artist-preferred-album-sort-type", - "Preferred album sort type (chronological or alphabetical-only)", - "Preferred album sort type (chronological or alphabetical-only)", - "default", - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION] = g_param_spec_boolean( - "ui-album-info-show-description", - "Show Description in Album Info", - "Show Description in Album Info", - TRUE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_UI_ALBUM_INFO_SHOW_GENRES] = g_param_spec_boolean( - "ui-album-info-show-genres", - "Show Genres in Album Info", - "Show Genres in Album Info", - TRUE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_UI_ALBUM_INFO_SHOW_NARRATOR] = g_param_spec_boolean( - "ui-album-info-show-narrator", - "Show Narrator in Album Info", - "Show Narrator in Album Info", - TRUE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_UI_ALBUM_INFO_SHOW_YEAR] = g_param_spec_boolean( - "ui-album-info-show-year", - "Show Year in Album Info", - "Show Year in Album Info", - TRUE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_UI_THEME_DESIRED] = g_param_spec_string( - "ui-theme-desired", - "Desired Theme", - "Desired Theme", - "dark", // Like my soul - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - config_props[PROP_UI_THEME_OVERRIDE] = g_param_spec_boolean( - "ui-theme-override", - "Override built-in theming", - "Override built-in theming", - FALSE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPS, config_props); -} - -static void koto_config_init(KotoConfig * self) { - self->finalized = FALSE; - self->has_type_audiobook = FALSE; - self->has_type_music = FALSE; - self->has_type_podcast = FALSE; -} - -static void koto_config_constructed(GObject * obj) { - KotoConfig * self = KOTO_CONFIG(obj); - self->finalized = TRUE; -} - -static void koto_config_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoConfig * self = KOTO_CONFIG(obj); - - switch (prop_id) { - case PROP_PLAYBACK_CONTINUE_ON_PLAYLIST: - g_value_set_boolean(val, self->playback_continue_on_playlist); - break; - case PROP_PLAYBACK_LAST_USED_VOLUME: - g_value_set_double(val, self->playback_last_used_volume); - break; - case PROP_PLAYBACK_MAINTAIN_SHUFFLE: - g_value_set_boolean(val, self->playback_maintain_shuffle); - break; - case PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT: - g_value_set_uint(val, self->playback_jump_backwards_increment); - break; - case PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT: - g_value_set_uint(val, self->playback_jump_forwards_increment); - break; - case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION: - g_value_set_boolean(val, self->ui_album_info_show_description); - break; - case PROP_UI_ALBUM_INFO_SHOW_GENRES: - g_value_set_boolean(val, self->ui_album_info_show_genres); - break; - case PROP_UI_ALBUM_INFO_SHOW_NARRATOR: - g_value_set_boolean(val, self->ui_album_info_show_narrator); - break; - case PROP_UI_ALBUM_INFO_SHOW_YEAR: - g_value_set_boolean(val, self->ui_album_info_show_year); - break; - case PROP_UI_THEME_DESIRED: - g_value_set_string(val, g_strdup(self->ui_theme_desired)); - break; - case PROP_UI_THEME_OVERRIDE: - g_value_set_boolean(val, self->ui_theme_override); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_config_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoConfig * self = KOTO_CONFIG(obj); - - switch (prop_id) { - case PROP_PLAYBACK_CONTINUE_ON_PLAYLIST: - self->playback_continue_on_playlist = g_value_get_boolean(val); - break; - case PROP_PLAYBACK_LAST_USED_VOLUME: - self->playback_last_used_volume = g_value_get_double(val); - break; - case PROP_PLAYBACK_MAINTAIN_SHUFFLE: - self->playback_maintain_shuffle = g_value_get_boolean(val); - break; - case PROP_PLAYBACK_JUMP_BACKWARDS_INCREMENT: - self->playback_jump_backwards_increment = g_value_get_uint(val); - break; - case PROP_PLAYBACK_JUMP_FORWARDS_INCREMENT: - self->playback_jump_forwards_increment = g_value_get_uint(val); - break; - case PROP_PREFERRED_ALBUM_SORT_TYPE: - self->preferred_album_sort_type = g_strcmp0(g_value_get_string(val), "alphabetical") ? KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL : KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT; - break; - case PROP_UI_ALBUM_INFO_SHOW_DESCRIPTION: - self->ui_album_info_show_description = g_value_get_boolean(val); - break; - case PROP_UI_ALBUM_INFO_SHOW_GENRES: - self->ui_album_info_show_genres = g_value_get_boolean(val); - break; - case PROP_UI_ALBUM_INFO_SHOW_NARRATOR: - self->ui_album_info_show_narrator = g_value_get_boolean(val); - break; - case PROP_UI_ALBUM_INFO_SHOW_YEAR: - self->ui_album_info_show_year = g_value_get_boolean(val); - break; - case PROP_UI_THEME_DESIRED: - self->ui_theme_desired = g_strdup(g_value_get_string(val)); - break; - case PROP_UI_THEME_OVERRIDE: - self->ui_theme_override = g_value_get_boolean(val); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } - - if (self->finalized) { // Loaded the config - g_object_notify_by_pspec(obj, config_props[prop_id]); // Notify that a change happened - } -} - -toml_table_t * koto_config_get_library( - KotoConfig * self, - gchar * library_uuid -) { - toml_array_t * libraries = toml_array_in(self->toml_ref, "library"); // Get the array of tables - for (int i = 0; i < toml_array_nelem(libraries); i++) { // For each library - toml_table_t * library = toml_table_at(libraries, i); // Get this library - toml_datum_t uuid_datum = toml_string_in(library, "uuid"); // Get the datum for the uuid - - gchar * lib_uuid = (uuid_datum.ok) ? (gchar*) uuid_datum.u.s : NULL; - - if (koto_utils_string_is_valid(lib_uuid) && (g_strcmp0(library_uuid, lib_uuid) == 0)) { // Is a valid string and the libraries match - return library; - } - } - - return NULL; -} - -/** - * Load our TOML file from the specified path into our KotoConfig - **/ -void koto_config_load( - KotoConfig * self, - gchar * path -) { - if (!koto_utils_string_is_valid(path)) { // Path is not valid - return; - } - - self->path = g_strdup(path); - - self->config_file = g_file_new_for_path(path); - gboolean config_file_exists = g_file_query_exists(self->config_file, NULL); - - if (!config_file_exists) { // File does not exist - GError * create_err; - GFileOutputStream * stream = g_file_create( - self->config_file, - G_FILE_CREATE_PRIVATE, - NULL, - &create_err - ); - - if (create_err != NULL) { - if (create_err->code != G_IO_ERROR_EXISTS) { // Not an error indicating the file already exists - g_warning("Failed to create or open file: %s", create_err->message); - return; - } - } - - g_object_unref(stream); - } - - GError * file_info_query_err; - - GFileInfo * file_info = g_file_query_info( - // Get the size of our TOML file - self->config_file, - G_FILE_ATTRIBUTE_STANDARD_SIZE, - G_FILE_QUERY_INFO_NONE, - NULL, - &file_info_query_err - ); - - if (file_info != NULL) { // Got info - goffset size = g_file_info_get_size(file_info); // Get the size from info - g_object_unref(file_info); // Unref immediately - - if (size == 0) { // If we don't have any file contents (new file), skip parsing - goto monitor; - } - } else { // Failed to get the info - g_warning("Failed to get size info of %s: %s", self->path, file_info_query_err->message); - } - - FILE * file; - file = fopen(self->path, "r"); // Open the file as read only - - if (file == NULL) { // Failed to get the file - /** Handle error checking here*/ - return; - } - - char errbuf[200]; - toml_table_t * conf = toml_parse_file(file, errbuf, sizeof(errbuf)); - fclose(file); // Close the file - - if (!conf) { - g_error("Failed to read our config file. %s", errbuf); - return; - } - - self->toml_ref = conf; - - /** Supplemental Libraries (Excludes Built-in) */ - - toml_array_t * libraries = toml_array_in(conf, "library"); // Get all of our libraries - if (libraries) { // If we have libraries already - for (int i = 0; i < toml_array_nelem(libraries); i++) { // Iterate over each library - toml_table_t * lib = toml_table_at(libraries, i); // Get the library datum - KotoLibrary * koto_lib = koto_library_new_from_toml_table(lib); // Get the library based on the TOML data for this specific type - - if (!KOTO_IS_LIBRARY(koto_lib)) { // Something wrong with it, not a library - continue; - } - - koto_cartographer_add_library(koto_maps, koto_lib); // Add library to Cartographer - KotoLibraryType lib_type = koto_library_get_lib_type(koto_lib); // Get the type - - if (lib_type == KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Is an audiobook lib - self->has_type_audiobook = TRUE; - } else if (lib_type == KOTO_LIBRARY_TYPE_MUSIC) { // Is a music lib - self->has_type_music = TRUE; - } else if (lib_type == KOTO_LIBRARY_TYPE_PODCAST) { // Is a podcast lib - self->has_type_podcast = TRUE; - } - } - } - - /** Playback Section */ - - toml_table_t * playback_section = toml_table_in(conf, "playback"); - - if (playback_section) { // Have playback section - toml_datum_t continue_on_playlist = toml_bool_in(playback_section, "continue-on-playlist"); - toml_datum_t jump_backwards_increment = toml_int_in(playback_section, "jump-backwards-increment"); - toml_datum_t jump_forwards_increment = toml_int_in(playback_section, "jump-forwards-increment"); - toml_datum_t last_used_volume = toml_double_in(playback_section, "last-used-volume"); - toml_datum_t maintain_shuffle = toml_bool_in(playback_section, "maintain-shuffle"); - - if (continue_on_playlist.ok && (self->playback_continue_on_playlist != continue_on_playlist.u.b)) { // If we have a continue-on-playlist set and they are different - g_object_set(self, "playback-continue-on-playlist", continue_on_playlist.u.b, NULL); - } - - if (jump_backwards_increment.ok && (self->playback_jump_backwards_increment != jump_backwards_increment.u.i)) { // If we have a jump-backwards-increment set and it is different - g_object_set(self, "playback-jump-backwards-increment", (guint) jump_backwards_increment.u.i, NULL); - } - - if (jump_forwards_increment.ok && (self->playback_jump_forwards_increment != jump_forwards_increment.u.i)) { // If we have a jump-backwards-increment set and it is different - g_object_set(self, "playback-jump-forwards-increment", (guint) jump_forwards_increment.u.i, NULL); - } - - if (last_used_volume.ok && (self->playback_last_used_volume != last_used_volume.u.d)) { // If we have last-used-volume set and they are different - g_object_set(self, "playback-last-used-volume", last_used_volume.u.d, NULL); - } - - if (maintain_shuffle.ok && (self->playback_maintain_shuffle != maintain_shuffle.u.b)) { // If we have a "maintain shuffle set" and they are different - g_object_set(self, "playback-maintain-shuffle", maintain_shuffle.u.b, NULL); - } - } - - /* UI Section */ - - toml_table_t * ui_section = toml_table_in(conf, "ui"); - - if (ui_section) { // Have UI section - toml_datum_t album_info_show_description = toml_bool_in(ui_section, "album-info-show-description"); - - if (album_info_show_description.ok && (album_info_show_description.u.b != self->ui_album_info_show_description)) { // Changed if we are disabling description - g_object_set(self, "ui-album-info-show-description", album_info_show_description.u.b, NULL); - } - - toml_datum_t album_info_show_genres = toml_bool_in(ui_section, "album-info-show-genres"); - - if (album_info_show_genres.ok && (album_info_show_genres.u.b != self->ui_album_info_show_genres)) { // Changed if we are disabling description - g_object_set(self, "ui-album-info-show-genres", album_info_show_genres.u.b, NULL); - } - - toml_datum_t album_info_show_narrator = toml_bool_in(ui_section, "album-info-show-narrator"); - - if (album_info_show_narrator.ok && (album_info_show_narrator.u.b != self->ui_album_info_show_narrator)) { // Changed if we are disabling description - g_object_set(self, "ui-album-info-show-narrator", album_info_show_description.u.b, NULL); - } - - toml_datum_t album_info_show_year = toml_bool_in(ui_section, "album-info-show-year"); - - if (album_info_show_year.ok && (album_info_show_year.u.b != self->ui_album_info_show_year)) { // Changed if we are disabling description - g_object_set(self, "ui-album-info-show-year", album_info_show_year.u.b, NULL); - } - - toml_datum_t name = toml_string_in(ui_section, "theme-desired"); - - if (name.ok && (g_strcmp0(name.u.s, self->ui_theme_desired) != 0)) { // Have a name specified and they are different - g_object_set(self, "ui-theme-desired", g_strdup(name.u.s), NULL); - free(name.u.s); - } - - toml_datum_t override_app = toml_bool_in(ui_section, "theme-override"); - - if (override_app.ok && (override_app.u.b != self->ui_theme_override)) { // Changed if we are overriding theme - g_object_set(self, "ui-theme-override", override_app.u.b, NULL); - } - } - -monitor: - if (self->config_file_monitor != NULL) { // If we already have a file monitor for the file - return; - } - - self->config_file_monitor = g_file_monitor_file( - self->config_file, - G_FILE_MONITOR_NONE, - NULL, - NULL - ); - - g_signal_connect(self->config_file_monitor, "changed", G_CALLBACK(koto_config_monitor_handle_changed), self); // Monitor changes to our config file - - if (!config_file_exists) { // File did not originally exist - koto_config_save(self); // Save immediately - } -} - -void koto_config_load_libs(KotoConfig * self) { - gchar * home_dir = g_strdup(g_get_home_dir()); // Get the home directory - - if (!self->has_type_audiobook) { // If we do not have a KotoLibrary for Audiobooks - gchar * audiobooks_path = g_build_path(G_DIR_SEPARATOR_S, home_dir, "Audiobooks", NULL); - koto_utils_mkdir(audiobooks_path); // Create the directory just in case - KotoLibrary * lib = koto_library_new(KOTO_LIBRARY_TYPE_AUDIOBOOK, NULL, audiobooks_path); // Audiobooks relative to home directory - - if (KOTO_IS_LIBRARY(lib)) { // Created built-in audiobooks lib successfully - koto_cartographer_add_library(koto_maps, lib); - koto_config_save(config); - koto_library_index(lib); // Index this library - } - - g_free(audiobooks_path); - } - - if (!self->has_type_music) { // If we do not have a KotoLibrary for Music - KotoLibrary * lib = koto_library_new(KOTO_LIBRARY_TYPE_MUSIC, NULL, g_get_user_special_dir(G_USER_DIRECTORY_MUSIC)); // Create a library using the user's MUSIC directory defined - - if (KOTO_IS_LIBRARY(lib)) { // Created built-in music lib successfully - koto_cartographer_add_library(koto_maps, lib); - koto_config_save(config); - koto_library_index(lib); // Index this library - } - } - - if (!self->has_type_podcast) { // If we do not have a KotoLibrary for Podcasts - gchar * podcasts_path = g_build_path(G_DIR_SEPARATOR_S, home_dir, "Podcasts", NULL); - koto_utils_mkdir(podcasts_path); // Create the directory just in case - KotoLibrary * lib = koto_library_new(KOTO_LIBRARY_TYPE_PODCAST, NULL, podcasts_path); // Podcasts relative to home dir - - if (KOTO_IS_LIBRARY(lib)) { // Created built-in podcasts lib successfully - koto_cartographer_add_library(koto_maps, lib); - koto_config_save(config); - koto_library_index(lib); // Index this library - } - - g_free(podcasts_path); - } - - g_free(home_dir); - g_thread_exit(0); -} - -void koto_config_monitor_handle_changed( - GFileMonitor * monitor, - GFile * file, - GFile * other_file, - GFileMonitorEvent ev, - gpointer user_data -) { - (void) monitor; - (void) file; - (void) other_file; - KotoConfig * config = user_data; - - if ( - (ev == G_FILE_MONITOR_EVENT_ATTRIBUTE_CHANGED) || // Attributes changed - (ev == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) // Changes done - ) { - koto_config_refresh(config); // Refresh the config - } -} - -KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self) { - return self->preferred_album_sort_type; -} - -/** - * Refresh will handle any FS notify change on our Koto config file and call load - **/ -void koto_config_refresh(KotoConfig * self) { - koto_config_load(self, self->path); -} - -/** - * Save will write our config back out - **/ -void koto_config_save(KotoConfig * self) { - GStrvBuilder * root_builder = g_strv_builder_new(); // Create a new strv builder - - /* Iterate over our libraries */ - - GList * libs = koto_cartographer_get_libraries(koto_maps); // Get our libraries - GList * current_libs; - - for (current_libs = libs; current_libs != NULL; current_libs = current_libs->next) { // Iterate over our libraries - KotoLibrary * lib = current_libs->data; - gchar * lib_config = koto_library_to_config_string(lib); // Get the config string - g_strv_builder_add(root_builder, lib_config); // Add the config to our string builder - g_free(lib_config); - } - - GParamSpec ** props_list = g_object_class_list_properties(G_OBJECT_GET_CLASS(self), NULL); // Get the propreties associated with our settings - - GHashTable * sections_to_prop_keys = g_hash_table_new(g_str_hash, g_str_equal); // Create our section to hold our various sections based on props - - /* Section Hashes*/ - - gchar * playback_hash = g_strdup("playback"); - gchar * ui_hash = g_strdup("ui"); - - gdouble current_playback_volume = 1.0; - - if (KOTO_IS_PLAYBACK_ENGINE(playback_engine)) { // Have a playback engine (useful since it may not be initialized before the config performs saving on first application load) - current_playback_volume = koto_playback_engine_get_volume(playback_engine); // Get the last used volume in the playback engine - } - - self->playback_last_used_volume = current_playback_volume; // Update our value so we have it during save - - int i; - for (i = 0; i < N_PROPS; i++) { // For each property - GParamSpec * spec = props_list[i]; // Get the prop - - if (!G_IS_PARAM_SPEC(spec)) { // Not a spec - continue; // Skip - } - - const gchar * prop_name = g_param_spec_get_name(spec); - - gpointer respective_prop = NULL; - - if (g_str_has_prefix(prop_name, "playback")) { // Is playback - respective_prop = playback_hash; - } else if (g_str_has_prefix(prop_name, "ui")) { // Is UI - respective_prop = ui_hash; - } - - if (respective_prop == NULL) { // No property - continue; - } - - GList * keys; - - if (g_hash_table_contains(sections_to_prop_keys, respective_prop)) { // Already has list - keys = g_hash_table_lookup(sections_to_prop_keys, respective_prop); // Get the list - } else { // Don't have list - keys = NULL; - } - - keys = g_list_append(keys, g_strdup(prop_name)); // Add the name in full - g_hash_table_insert(sections_to_prop_keys, respective_prop, keys); // Replace list (or add it) - } - - GHashTableIter iter; - gpointer section_name, section_props; - - g_hash_table_iter_init(&iter, sections_to_prop_keys); - - while (g_hash_table_iter_next(&iter, §ion_name, §ion_props)) { - GStrvBuilder * section_builder = g_strv_builder_new(); // Make our string builder - g_strv_builder_add(section_builder, g_strdup_printf("[%s]", (gchar*) section_name)); // Add section as [section] - - GList * current_section_keyname; - for (current_section_keyname = section_props; current_section_keyname != NULL; current_section_keyname = current_section_keyname->next) { // Iterate over property names - GValue prop_val_raw = G_VALUE_INIT; // Initialize our GValue - g_object_get_property(G_OBJECT(self), current_section_keyname->data, &prop_val_raw); - gchar * prop_val = g_strdup_value_contents(&prop_val_raw); - - if ((g_strcmp0(prop_val, "TRUE") == 0) || (g_strcmp0(prop_val, "FALSE") == 0)) { // TRUE or FALSE from a boolean type - prop_val = g_utf8_strdown(prop_val, -1); // Change it to be lowercased - } - - gchar * key_name = g_strdup(current_section_keyname->data); - gchar * key_name_replaced = koto_utils_string_replace_all(key_name, g_strdup_printf("%s-", (gchar*) section_name), ""); // Remove SECTIONNAME- - - const gchar * line = g_strdup_printf("\t%s = %s", key_name_replaced, prop_val); - - g_strv_builder_add(section_builder, line); // Add the line - g_free(key_name_replaced); - g_free(key_name); - } - - GStrv lines = g_strv_builder_end(section_builder); // Get all the lines as a GStrv which is a gchar ** - gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline - g_strfreev(lines); // Free our lines - - g_strv_builder_add(root_builder, content); // Add section content to root builder - g_strv_builder_unref(section_builder); // Unref our builder - } - - g_hash_table_unref(sections_to_prop_keys); // Free our hash table - - GStrv lines = g_strv_builder_end(root_builder); // Get all the lines as a GStrv which is a gchar ** - gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline - g_strfreev(lines); // Free our lines - - g_strv_builder_unref(root_builder); // Unref our root builder - - ulong file_content_length = g_utf8_strlen(content, -1); - - g_file_replace_contents( - self->config_file, - content, - file_content_length, - NULL, - FALSE, - G_FILE_CREATE_PRIVATE, - NULL, - NULL, - NULL - ); -} - -KotoConfig * koto_config_new() { - return g_object_new(KOTO_TYPE_CONFIG, NULL); -} \ No newline at end of file diff --git a/src/config/config.h b/src/config/config.h deleted file mode 100644 index 5aa5f02..0000000 --- a/src/config/config.h +++ /dev/null @@ -1,55 +0,0 @@ -/* config.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include "../indexer/misc-types.h" - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_CONFIG (koto_config_get_type()) - -G_DECLARE_FINAL_TYPE(KotoConfig, koto_config, KOTO, CONFIG, GObject) - -KotoConfig* koto_config_new(); -void koto_config_load( - KotoConfig * self, - gchar * path -); - -void koto_config_load_libs(KotoConfig * self); - -void koto_config_monitor_handle_changed( - GFileMonitor * monitor, - GFile * file, - GFile * other_file, - GFileMonitorEvent ev, - gpointer user_data -); - -KotoPreferredAlbumSortType koto_config_get_preferred_album_sort_type(KotoConfig * self); - -void koto_config_refresh(KotoConfig * self); - -void koto_config_save(KotoConfig * self); - -G_END_DECLS \ No newline at end of file diff --git a/src/db/cartographer.c b/src/db/cartographer.c deleted file mode 100644 index 829a5c6..0000000 --- a/src/db/cartographer.c +++ /dev/null @@ -1,910 +0,0 @@ -/* cartographer.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "../koto-utils.h" -#include "cartographer.h" - -enum { - SIGNAL_ALBUM_ADDED, - SIGNAL_ALBUM_REMOVED, - SIGNAL_ARTIST_ADDED, - SIGNAL_ARTIST_REMOVED, - SIGNAL_LIBRARY_ADDED, - SIGNAL_LIBRARY_REMOVED, - SIGNAL_PLAYLIST_ADDED, - SIGNAL_PLAYLIST_REMOVED, - SIGNAL_TRACK_ADDED, - SIGNAL_TRACK_REMOVED, - N_SIGNALS -}; - -static guint cartographer_signals[N_SIGNALS] = { - 0 -}; - -struct _KotoCartographer { - GObject parent_instance; - - GHashTable * albums; - GHashTable * artists; - GHashTable * artists_name_to_uuid; - GHashTable * libraries; - GHashTable * playlists; - GHashTable * tracks; - GHashTable * tracks_by_uniqueish_key; -}; - -struct _KotoCartographerClass { - GObjectClass parent_class; - - void (* album_added) ( - KotoCartographer * cartographer, - KotoAlbum * album - ); - void (* album_removed) ( - KotoCartographer * cartographer, - KotoAlbum * album - ); - void (* artist_added) ( - KotoCartographer * cartographer, - KotoArtist * artist - ); - void (* artist_removed) ( - KotoCartographer * cartographer, - KotoArtist * artist - ); - void (* library_added) ( - KotoCartographer * cartographer, - KotoLibrary * library - ); - void (* library_removed) ( - KotoCartographer * cartographer, - KotoLibrary * library - ); - void (* playlist_added) ( - KotoCartographer * cartographer, - KotoPlaylist * playlist - ); - void (* playlist_removed) ( - KotoCartographer * cartographer, - KotoPlaylist * playlist - ); - void (* track_added) ( - KotoCartographer * cartographer, - KotoTrack * track - ); - void (* track_removed) ( - KotoCartographer * cartographer, - KotoTrack * track - ); -}; - -G_DEFINE_TYPE(KotoCartographer, koto_cartographer, G_TYPE_OBJECT); - -KotoCartographer * koto_maps = NULL; - -static void koto_cartographer_class_init(KotoCartographerClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - - cartographer_signals[SIGNAL_ALBUM_ADDED] = g_signal_new( - "album-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, album_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_ALBUM - ); - - cartographer_signals[SIGNAL_ALBUM_REMOVED] = g_signal_new( - "album-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, album_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_CHAR - ); - - cartographer_signals[SIGNAL_ARTIST_ADDED] = g_signal_new( - "artist-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, artist_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_ARTIST - ); - - cartographer_signals[SIGNAL_ARTIST_REMOVED] = g_signal_new( - "artist-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, artist_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 2, - G_TYPE_CHAR, - G_TYPE_CHAR - ); - - cartographer_signals[SIGNAL_LIBRARY_ADDED] = g_signal_new( - "library-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, library_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_LIBRARY - ); - - cartographer_signals[SIGNAL_LIBRARY_ADDED] = g_signal_new( - "library-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, library_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_LIBRARY - ); - - cartographer_signals[SIGNAL_PLAYLIST_ADDED] = g_signal_new( - "playlist-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, playlist_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_PLAYLIST - ); - - cartographer_signals[SIGNAL_PLAYLIST_REMOVED] = g_signal_new( - "playlist-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, playlist_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_CHAR - ); - - cartographer_signals[SIGNAL_TRACK_ADDED] = g_signal_new( - "track-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, track_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_TRACK - ); - - cartographer_signals[SIGNAL_TRACK_REMOVED] = g_signal_new( - "track-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCartographerClass, track_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_CHAR - ); -} - -static void koto_cartographer_init(KotoCartographer * self) { - self->albums = g_hash_table_new(g_str_hash, g_str_equal); - self->artists = g_hash_table_new(g_str_hash, g_str_equal); - self->artists_name_to_uuid = g_hash_table_new(g_str_hash, g_str_equal); - self->libraries = g_hash_table_new(g_str_hash, g_str_equal); - self->playlists = g_hash_table_new(g_str_hash, g_str_equal); - self->tracks = g_hash_table_new(g_str_hash, g_str_equal); - self->tracks_by_uniqueish_key = g_hash_table_new(g_str_hash, g_str_equal); -} - -void koto_cartographer_add_album( - KotoCartographer * self, - KotoAlbum * album -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_ALBUM(album)) { - return; - } - - gchar * album_uuid = koto_album_get_uuid(album); // Get the album UUID - - if (!koto_utils_string_is_valid(album_uuid) || koto_cartographer_has_album_by_uuid(self, album_uuid)) { // Have the album or invalid UUID - return; - } - - g_hash_table_replace(self->albums, album_uuid, album); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_ALBUM_ADDED], - 0, - album - ); -} - -void koto_cartographer_add_artist( - KotoCartographer * self, - KotoArtist * artist -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - gchar * artist_uuid = koto_artist_get_uuid(artist); - - if (!koto_utils_string_is_valid(artist_uuid) || koto_cartographer_has_artist_by_uuid(self, artist_uuid)) { // Have the artist or invalid UUID - return; - } - - g_hash_table_replace(self->artists_name_to_uuid, koto_artist_get_name(artist), artist_uuid); // Add the UUID as a value with the key being the name of the artist - g_hash_table_replace(self->artists, artist_uuid, artist); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_ARTIST_ADDED], - 0, - artist - ); -} - -void koto_cartographer_add_library( - KotoCartographer * self, - KotoLibrary * library -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_LIBRARY(library)) { // Not a library - return; - } - - gchar * library_uuid = koto_library_get_uuid(library); - - if (!koto_utils_string_is_valid(library_uuid) || koto_cartographer_has_library_by_uuid(self, library_uuid)) { // Have the library or invalid UUID - return; - } - - g_hash_table_replace(self->libraries, library_uuid, library); // Add the library - g_signal_emit( - // Emit our library added signal - self, - cartographer_signals[SIGNAL_LIBRARY_ADDED], - 0, - library - ); -} - -void koto_cartographer_add_playlist( - KotoCartographer * self, - KotoPlaylist * playlist -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - return; - } - - gchar * playlist_uuid = koto_playlist_get_uuid(playlist); - - if (!koto_playlist_get_uuid(playlist) || koto_cartographer_has_playlist_by_uuid(self, playlist_uuid)) { // Have the playlist or invalid UUID - return; - } - - g_hash_table_replace(self->playlists, playlist_uuid, playlist); - - if (koto_playlist_get_is_finalized(playlist)) { // Already finalized - koto_cartographer_emit_playlist_added(playlist, self); // Emit playlist-added immediately - } else { // Not finalized - g_signal_connect(playlist, "track-load-finalized", G_CALLBACK(koto_cartographer_emit_playlist_added), self); - } -} - -void koto_cartographer_emit_playlist_added( - KotoPlaylist * playlist, - KotoCartographer * self -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - g_signal_emit( - self, - cartographer_signals[SIGNAL_PLAYLIST_ADDED], - 0, - playlist - ); -} - -void koto_cartographer_add_track( - KotoCartographer * self, - KotoTrack * track -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - gchar * track_uuid = koto_track_get_uuid(track); - - if (!koto_utils_string_is_valid(track_uuid) || koto_cartographer_has_track_by_uuid(self, track_uuid)) { // Have the track or invalid UUID - return; - } - - g_hash_table_replace(self->tracks, track_uuid, track); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_TRACK_ADDED], - 0, - track - ); -} - -KotoAlbum * koto_cartographer_get_album_by_uuid( - KotoCartographer * self, - gchar * album_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - return g_hash_table_lookup(self->albums, album_uuid); -} - -GHashTable * koto_cartographer_get_artists(KotoCartographer * self) { - return self->artists; -} - -KotoArtist * koto_cartographer_get_artist_by_name( - KotoCartographer * self, - gchar * artist_name -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - if (!koto_utils_string_is_valid(artist_name)) { // Not a valid name - return NULL; - } - - return koto_cartographer_get_artist_by_uuid(self, g_hash_table_lookup(self->artists_name_to_uuid, artist_name)); -} - -KotoArtist * koto_cartographer_get_artist_by_uuid( - KotoCartographer * self, - gchar * artist_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - if (!koto_utils_string_is_valid(artist_uuid)) { - return NULL; - } - - return g_hash_table_lookup(self->artists, artist_uuid); -} - -KotoLibrary * koto_cartographer_get_library_by_uuid( - KotoCartographer * self, - gchar * library_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - if (!koto_utils_string_is_valid(library_uuid)) { // Not a valid string - return NULL; - } - - return g_hash_table_lookup(self->libraries, library_uuid); -} - -KotoLibrary * koto_cartographer_get_library_containing_path( - KotoCartographer * self, - gchar * relative_path -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - if (!koto_utils_string_is_valid(relative_path)) { // Not a valid string - return NULL; - } - - GList * libs = koto_cartographer_get_libraries(self); // Get all the libraries, sorted based on priority - GList * current; - - for (current = libs; current != NULL; current = current->next) { // For each library - KotoLibrary * lib = (KotoLibrary*) current->data; - gchar * lib_path = koto_library_get_path(lib); // Get the path for the library - - GFile * track_file = g_file_new_build_filename(lib_path, relative_path, NULL); // Build a path from storage to file - - if (g_file_query_exists(track_file, NULL)) { // If this library contains this file - g_object_unref(track_file); - return lib; - } - - g_object_unref(track_file); - } - - return NULL; -} - -GList * koto_cartographer_get_libraries_for_storage_uuid( - KotoCartographer * self, - gchar * storage_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - GList * libraries = NULL; // Initialize our list - - if (!koto_utils_string_is_valid(storage_uuid)) { // Not a valid storage UUID string - return libraries; - } - - // TODO: Implement koto_cartographer_get_libraries_for_storage_uuid - return libraries; -} - -GList * koto_cartographer_get_libraries(KotoCartographer * self) { - GList * libraries = g_hash_table_get_values(self->libraries); - // TODO: Implement priority mechanism - return libraries; -} - -GHashTable * koto_cartographer_get_playlists(KotoCartographer * self) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - return self->playlists; -} - -KotoPlaylist * koto_cartographer_get_playlist_by_uuid( - KotoCartographer * self, - gchar * playlist_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - return g_hash_table_lookup(self->playlists, playlist_uuid); -} - -KotoTrack * koto_cartographer_get_track_by_uuid( - KotoCartographer * self, - gchar * track_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - if (!koto_utils_string_is_valid(track_uuid)) { - return NULL; - } - - return g_hash_table_lookup(self->tracks, track_uuid); -} - -KotoTrack * koto_cartographer_get_track_by_uniqueish_key( - KotoCartographer * self, - gchar * key -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return NULL; - } - - if (!koto_utils_string_is_valid(key)) { - return NULL; - } - - return g_hash_table_lookup(self->tracks_by_uniqueish_key, key); -} - -gboolean koto_cartographer_has_album( - KotoCartographer * self, - KotoAlbum * album -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!KOTO_IS_ALBUM(album)) { - return FALSE; - } - - gchar * album_uuid = NULL; - - g_object_get(album, "uuid", &album_uuid, NULL); - return koto_cartographer_has_album_by_uuid(self, album_uuid); -} - -gboolean koto_cartographer_has_album_by_uuid( - KotoCartographer * self, - gchar * album_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!koto_utils_string_is_valid(album_uuid)) { // Not a valid UUID - return FALSE; - } - - return g_hash_table_contains(self->albums, album_uuid); -} - -gboolean koto_cartographer_has_artist( - KotoCartographer * self, - KotoArtist * artist -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!KOTO_IS_ARTIST(artist)) { - return FALSE; - } - - return koto_cartographer_has_artist_by_uuid(self, koto_artist_get_uuid(artist)); -} - -gboolean koto_cartographer_has_artist_by_uuid( - KotoCartographer * self, - gchar * artist_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!koto_utils_string_is_valid(artist_uuid)) { // Not a valid UUID - return FALSE; - } - - return g_hash_table_contains(self->artists, artist_uuid); -} - -gboolean koto_cartographer_has_library( - KotoCartographer * self, - KotoLibrary * library -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!KOTO_IS_LIBRARY(library)) { - return FALSE; - } - - // TODO: return koto_cartographer_has_library_by_uuid(self, koto_library_get_uuid(library)) -- Need to implement get uuid func - return FALSE; -} - -gboolean koto_cartographer_has_library_by_uuid( - KotoCartographer * self, - gchar * library_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!koto_utils_string_is_valid(library_uuid)) { // Not a valid UUID - return FALSE; - } - - return g_hash_table_contains(self->libraries, library_uuid); -} - -gboolean koto_cartographer_has_playlist( - KotoCartographer * self, - KotoPlaylist * playlist -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - return FALSE; - } - - return koto_cartographer_has_playlist_by_uuid(self, koto_playlist_get_uuid(playlist)); -} - -gboolean koto_cartographer_has_playlist_by_uuid( - KotoCartographer * self, - gchar * playlist_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid UUID - return FALSE; - } - - return g_hash_table_contains(self->playlists, playlist_uuid); -} - -gboolean koto_cartographer_has_track( - KotoCartographer * self, - KotoTrack * track -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return FALSE; - } - - return koto_cartographer_has_album_by_uuid(self, koto_track_get_uuid(track)); -} - -gboolean koto_cartographer_has_track_by_uuid( - KotoCartographer * self, - gchar * track_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return FALSE; - } - - if (!koto_utils_string_is_valid(track_uuid)) { // Not a valid UUID - return FALSE; - } - - return g_hash_table_contains(self->tracks, track_uuid); -} - -void koto_cartographer_remove_album( - KotoCartographer * self, - KotoAlbum * album -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_ALBUM(album)) { // Not an album - return; - } - - koto_cartographer_remove_album_by_uuid(self, koto_album_get_uuid(album)); -} - -void koto_cartographer_remove_album_by_uuid( - KotoCartographer * self, - gchar * album_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!koto_utils_string_is_valid(album_uuid)) { - return; - } - - if (!g_hash_table_contains(self->albums, album_uuid)) { // Album does not exist in albums - return; - } - - g_hash_table_remove(self->albums, album_uuid); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_ALBUM_REMOVED], - 0, - album_uuid - ); -} - -void koto_cartographer_remove_artist( - KotoCartographer * self, - KotoArtist * artist -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - gchar * artist_uuid = koto_artist_get_uuid(artist); - gchar * artist_name = koto_artist_get_name(artist); - - g_hash_table_remove(self->artists_name_to_uuid, artist_name); // Add the UUID as a value with the key being the name of the artist - g_hash_table_remove(self->artists, artist_uuid); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_ARTIST_REMOVED], - 0, - artist_uuid, - artist_name - ); -} - -void koto_cartographer_remove_artist_by_uuid( - KotoCartographer * self, - gchar * artist_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!koto_utils_string_is_valid(artist_uuid)) { // Artist UUID not valid - return; - } - - if (!g_hash_table_contains(self->artists, artist_uuid)) { // Not in hash table - return; - } - - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(self, artist_uuid); - - if (!KOTO_IS_ARTIST(artist)) { - return; - } - - gchar * artist_name = koto_artist_get_name(artist); - - g_hash_table_remove(self->artists_name_to_uuid, artist_name); // Add the UUID as a value with the key being the name of the artist - g_hash_table_remove(self->artists, artist_uuid); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_ARTIST_REMOVED], - 0, - artist_uuid, - artist_name - ); -} - -void koto_cartographer_remove_playlist( - KotoCartographer * self, - KotoPlaylist * playlist -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_PLAYLIST(playlist)) { - return; - } - koto_cartographer_remove_playlist_by_uuid(self, koto_playlist_get_uuid(playlist)); -} - -void koto_cartographer_remove_playlist_by_uuid( - KotoCartographer * self, - gchar * playlist_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid playlist UUID string - return; - } - - if (!g_hash_table_contains(self->playlists, playlist_uuid)) { // Not in hash table - return; - } - - g_hash_table_remove(self->playlists, playlist_uuid); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_PLAYLIST_REMOVED], - 0, - playlist_uuid - ); -} - -void koto_cartographer_remove_track( - KotoCartographer * self, - KotoTrack * track -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - koto_cartographer_remove_track_by_uuid(self, koto_track_get_uuid(track)); -} - -void koto_cartographer_remove_track_by_uuid( - KotoCartographer * self, - gchar * track_uuid -) { - if (!KOTO_IS_CARTOGRAPHER(self)) { - return; - } - - if (!koto_utils_string_is_valid(track_uuid)) { - return; - } - - if (!g_hash_table_contains(self->tracks, track_uuid)) { // Not in hash table - return; - } - - g_hash_table_remove(self->tracks, track_uuid); - - g_signal_emit( - self, - cartographer_signals[SIGNAL_TRACK_REMOVED], - 0, - track_uuid - ); -} - -KotoCartographer * koto_cartographer_new() { - return g_object_new(KOTO_TYPE_CARTOGRAPHER, NULL); -} diff --git a/src/db/cartographer.h b/src/db/cartographer.h deleted file mode 100644 index 8b752bc..0000000 --- a/src/db/cartographer.h +++ /dev/null @@ -1,221 +0,0 @@ -/* cartographer.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include "../indexer/structs.h" -#include "../playlist/playlist.h" - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_CARTOGRAPHER koto_cartographer_get_type() - -typedef struct _KotoCartographer KotoCartographer; -typedef struct _KotoCartographerClass KotoCartographerClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_cartographer_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_CARTOGRAPHER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CARTOGRAPHER)) - -/** - * Cartographer Functions - **/ - -KotoCartographer * koto_cartographer_new(); - -void koto_cartographer_add_album( - KotoCartographer * self, - KotoAlbum * album -); - -void koto_cartographer_add_artist( - KotoCartographer * self, - KotoArtist * artist -); - -void koto_cartographer_add_library( - KotoCartographer * self, - KotoLibrary * library -); - -void koto_cartographer_add_playlist( - KotoCartographer * self, - KotoPlaylist * playlist -); - -void koto_cartographer_add_track( - KotoCartographer * self, - KotoTrack * track -); - -void koto_cartographer_emit_playlist_added( - KotoPlaylist * playlist, - KotoCartographer * self -); - -KotoAlbum * koto_cartographer_get_album_by_uuid( - KotoCartographer * self, - gchar * album_uuid -); - -GHashTable * koto_cartographer_get_artists(KotoCartographer * self); - -KotoArtist * koto_cartographer_get_artist_by_name( - KotoCartographer * self, - gchar * artist_name -); - -KotoArtist * koto_cartographer_get_artist_by_uuid( - KotoCartographer * self, - gchar * artist_uuid -); - -KotoLibrary * koto_cartographer_get_library_by_uuid( - KotoCartographer * self, - gchar * library_uuid -); - -KotoLibrary * koto_cartographer_get_library_containing_path( - KotoCartographer * self, - gchar * path -); - -GList * koto_cartographer_get_libraries(KotoCartographer * self); - -GList * koto_cartographer_get_libraries_for_storage_uuid( - KotoCartographer * self, - gchar * storage_uuid -); - -KotoPlaylist * koto_cartographer_get_playlist_by_uuid( - KotoCartographer * self, - gchar * playlist_uuid -); - -GHashTable * koto_cartographer_get_playlists(KotoCartographer * self); - -KotoTrack * koto_cartographer_get_track_by_uuid( - KotoCartographer * self, - gchar * track_uuid -); - -KotoTrack * koto_cartographer_get_track_by_uniqueish_key( - KotoCartographer * self, - gchar * key -); - -gboolean koto_cartographer_has_album( - KotoCartographer * self, - KotoAlbum * album -); - -gboolean koto_cartographer_has_album_by_uuid( - KotoCartographer * self, - gchar * album_uuid -); - -gboolean koto_cartographer_has_artist( - KotoCartographer * self, - KotoArtist * artist -); - -gboolean koto_cartographer_has_artist_by_uuid( - KotoCartographer * self, - gchar * artist_uuid -); - -gboolean koto_cartographer_has_library( - KotoCartographer * self, - KotoLibrary * library -); - -gboolean koto_cartographer_has_library_by_uuid( - KotoCartographer * self, - gchar * library_uuid -); - -gboolean koto_cartographer_has_playlist( - KotoCartographer * self, - KotoPlaylist * playlist -); - -gboolean koto_cartographer_has_playlist_by_uuid( - KotoCartographer * self, - gchar * playlist_uuid -); - -gboolean koto_cartographer_has_track( - KotoCartographer * self, - KotoTrack * track -); - -gboolean koto_cartographer_has_track_by_uuid( - KotoCartographer * self, - gchar * track_uuid -); - -void koto_cartographer_remove_album( - KotoCartographer * self, - KotoAlbum * album -); - -void koto_cartographer_remove_album_by_uuid( - KotoCartographer * self, - gchar * album_uuid -); - -void koto_cartographer_remove_artist( - KotoCartographer * self, - KotoArtist * artist -); - -void koto_cartographer_remove_artist_by_uuid( - KotoCartographer * self, - gchar * artist_uuid -); - -void koto_cartographer_remove_library( - KotoCartographer * self, - KotoLibrary * library -); - -void koto_cartographer_remove_playlist( - KotoCartographer * self, - KotoPlaylist * playlist -); - -void koto_cartographer_remove_playlist_by_uuid( - KotoCartographer * self, - gchar * playlist_uuid -); - -void koto_cartographer_remove_track( - KotoCartographer * self, - KotoTrack * track -); - -void koto_cartographer_remove_track_by_uuid( - KotoCartographer * self, - gchar * track_uuid -); - -G_END_DECLS diff --git a/src/db/db.c b/src/db/db.c deleted file mode 100644 index 045d067..0000000 --- a/src/db/db.c +++ /dev/null @@ -1,110 +0,0 @@ -/* db.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include "db.h" -#include "../koto-paths.h" - -extern gchar * koto_path_to_db; - -int KOTO_DB_SUCCESS = 0; -int KOTO_DB_NEW = 1; -int KOTO_DB_FAIL = 2; - -sqlite3 * koto_db = NULL; -gchar * db_filepath = NULL; -gboolean created_new_db = FALSE; - -void close_db() { - sqlite3_close(koto_db); -} - -int create_db_tables() { - gchar * tables_creation_queries = "CREATE TABLE IF NOT EXISTS artists(id string UNIQUE PRIMARY KEY, name string, art_path string);" - "CREATE TABLE IF NOT EXISTS albums(id string UNIQUE PRIMARY KEY, artist_id string, name string, description string, narrator string, art_path string, genres string, year int, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" - "CREATE TABLE IF NOT EXISTS tracks(id string UNIQUE PRIMARY KEY, artist_id string, album_id string, name string, disc int, position int, duration int, genres string, FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" - "CREATE TABLE IF NOT EXISTS libraries_albums(id string, album_id string, path string, PRIMARY KEY (id, album_id) FOREIGN KEY(album_id) REFERENCES albums(id) ON DELETE CASCADE);" - "CREATE TABLE IF NOT EXISTS libraries_artists(id string, artist_id string, path string, PRIMARY KEY(id, artist_id) FOREIGN KEY(artist_id) REFERENCES artists(id) ON DELETE CASCADE);" - "CREATE TABLE IF NOT EXISTS libraries_tracks(id string, track_id string, path string, PRIMARY KEY(id, track_id) FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);" - "CREATE TABLE IF NOT EXISTS playlist_meta(id string UNIQUE PRIMARY KEY, name string, art_path string, preferred_model int, album_id string, track_id string, playback_position_of_track int);" - "CREATE TABLE IF NOT EXISTS playlist_tracks(position INTEGER PRIMARY KEY AUTOINCREMENT, playlist_id string, track_id string, FOREIGN KEY(playlist_id) REFERENCES playlist_meta(id), FOREIGN KEY(track_id) REFERENCES tracks(id) ON DELETE CASCADE);"; - - return (new_transaction(tables_creation_queries, "Failed to create required tables", TRUE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL; -} - -int enable_foreign_keys() { - gchar * commit_op = g_strdup("PRAGMA foreign_keys = ON;"); - const gchar * transaction_err_msg = "Failed to enable foreign key support. Ensure your sqlite3 is compiled with neither SQLITE_OMIT_FOREIGN_KEY or SQLITE_OMIT_TRIGGER defined"; - - return (new_transaction(commit_op, transaction_err_msg, FALSE) == SQLITE_OK) ? KOTO_DB_SUCCESS : KOTO_DB_FAIL; -} - -int have_existing_db() { - struct stat db_stat; - int success = stat(koto_path_to_db, &db_stat); - return ((success == 0) && S_ISREG(db_stat.st_mode)) ? 0 : 1; -} - -int new_transaction( - gchar * operation, - const gchar * transaction_err_msg, - gboolean fatal -) { - gchar * commit_op_errmsg = NULL; - int rc = sqlite3_exec(koto_db, operation, 0, 0, &commit_op_errmsg); - - if (rc != SQLITE_OK) { - (fatal) ? g_critical("%s: %s", transaction_err_msg, commit_op_errmsg) : g_warning("%s: %s", transaction_err_msg, commit_op_errmsg); - } - - if (commit_op_errmsg == NULL) { - g_free(commit_op_errmsg); - } - - return rc; -} - -int open_db() { - int ret = KOTO_DB_SUCCESS; // Default to last return being SUCCESS - - if (have_existing_db() == 1) { // If we do not have an existing DB - ret = KOTO_DB_NEW; - } - - if (sqlite3_open(koto_path_to_db, &koto_db) != KOTO_DB_SUCCESS) { // If we failed to open the database file - g_critical("Failed to open or create database: %s", sqlite3_errmsg(koto_db)); - return KOTO_DB_FAIL; - } - - if (enable_foreign_keys() != KOTO_DB_SUCCESS) { // If we failed to enable foreign keys - return KOTO_DB_FAIL; - } - - if (create_db_tables() != KOTO_DB_SUCCESS) { // Failed to create our database tables - return KOTO_DB_FAIL; - } - - if (ret == KOTO_DB_NEW) { - created_new_db = TRUE; - } - - return ret; -} diff --git a/src/db/db.h b/src/db/db.h deleted file mode 100644 index 9ff7b74..0000000 --- a/src/db/db.h +++ /dev/null @@ -1,41 +0,0 @@ -/* db.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include - -extern int KOTO_DB_SUCCESS; -extern int KOTO_DB_NEW; -extern int KOTO_DB_FAIL; - -void close_db(); - -int create_db_tables(); - -gchar * get_db_path(); - -int enable_foreign_keys(); - -int have_existing_db(); - -int new_transaction( - gchar * operation, - const gchar * transaction_err_msg, - gboolean fatal -); - -int open_db(); diff --git a/src/db/loaders.c b/src/db/loaders.c deleted file mode 100644 index ebbee80..0000000 --- a/src/db/loaders.c +++ /dev/null @@ -1,392 +0,0 @@ -/* loaders.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "cartographer.h" -#include "db.h" -#include "loaders.h" -#include "../indexer/album-playlist-funcs.h" -#include "../indexer/structs.h" -#include "../koto-utils.h" - -extern KotoCartographer * koto_maps; -extern sqlite3 * koto_db; - -int process_artists( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - (void) data; - (void) num_columns; - (void) column_names; // Don't need any of the params - - gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[0])); // First column is UUID - gchar * artist_name = g_strdup(koto_utils_string_unquote(fields[1])); // Second column is artist name - - KotoArtist * artist = koto_artist_new_with_uuid(artist_uuid); // Create our artist with the UUID - - g_object_set( - artist, - "name", - artist_name, // Set name - NULL); - - int artist_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM libraries_artists WHERE artist_id=\"%s\"", artist_uuid), process_artist_paths, artist, NULL); // Process all the paths for this given artist - - if (artist_paths != SQLITE_OK) { // Failed to get our artists_paths - g_critical("Failed to read our paths for this artist: %s", sqlite3_errmsg(koto_db)); - return 1; - } - - koto_cartographer_add_artist(koto_maps, artist); // Add the artist to our global cartographer - - int albums_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM albums WHERE artist_id=\"%s\"", artist_uuid), process_albums, artist, NULL); // Process our albums - - if (albums_rc != SQLITE_OK) { // Failed to get our albums - g_critical("Failed to read our albums: %s", sqlite3_errmsg(koto_db)); - return 1; - } - - koto_artist_set_as_finalized(artist); // Indicate it is finalized - - int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE artist_id=\"%s\" AND album_id=''", artist_uuid), process_tracks, NULL, NULL); // Load all tracks for an artist that are NOT in an album (e.g. artists without albums) - - if (tracks_rc != SQLITE_OK) { // Failed to get our tracks - g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db)); - return 1; - } - - g_free(artist_uuid); - g_free(artist_name); - - return 0; -} - -int process_artist_paths( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - (void) num_columns; - (void) column_names; // Don't need these - - KotoArtist * artist = (KotoArtist*) data; - - gchar * library_uuid = g_strdup(koto_utils_string_unquote(fields[0])); - gchar * relative_path = g_strdup(koto_utils_string_unquote(fields[2])); - - KotoLibrary * lib = koto_cartographer_get_library_by_uuid(koto_maps, library_uuid); // Get the library for this artist - - if (!KOTO_IS_LIBRARY(lib)) { // Failed to get the library for this UUID - return 0; - } - - koto_artist_set_path(artist, lib, relative_path, FALSE); // Add the relative path from the db for this artist and lib to the Artist, do not commit - - return 0; -} - -int process_albums( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - (void) num_columns; - (void) column_names; // Don't need these - - KotoArtist * artist = (KotoArtist*) data; - - gchar * album_uuid = g_strdup(koto_utils_string_unquote(fields[0])); - gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[1])); - gchar * album_name = g_strdup(koto_utils_string_unquote(fields[2])); - gchar * album_description = (fields[3] != NULL) ? g_strdup(koto_utils_string_unquote(fields[3])) : NULL; - gchar * album_narrator = (fields[4] != NULL) ? g_strdup(koto_utils_string_unquote(fields[4])) : NULL; - gchar * album_art = (fields[5] != NULL) ? g_strdup(koto_utils_string_unquote(fields[5])) : NULL; - gchar * album_genres = (fields[6] != NULL) ? g_strdup(koto_utils_string_unquote(fields[6])) : NULL; - guint64 * album_year = (guint64*) g_ascii_strtoull(fields[7], NULL, 10); - - KotoAlbum * album = koto_album_new_with_uuid(artist, album_uuid); // Create our album - - g_object_set( - album, - "name", - album_name, // Set name - "description", - album_description, - "narrator", - album_narrator, - "art-path", - album_art, // Set art path if any - "preparsed-genres", - album_genres, - "year", - album_year, - NULL - ); - - koto_cartographer_add_album(koto_maps, album); // Add the album to our global cartographer - koto_artist_add_album(artist, album); // Add the album - - int tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM tracks WHERE album_id=\"%s\"", album_uuid), process_tracks, NULL, NULL); // Process all the tracks for this specific album - - if (tracks_rc != SQLITE_OK) { // Failed to get our tracks - g_critical("Failed to read our tracks: %s", sqlite3_errmsg(koto_db)); - return 1; - } - - koto_album_mark_as_finalized(album); // Mark the album as finalized now that all tracks have been loaded, allowing our internal album playlist to re-sort itself - - g_free(album_uuid); - g_free(artist_uuid); - g_free(album_name); - g_free(album_genres); - - if (album_art != NULL) { - g_free(album_art); - } - - return 0; -} - -int process_playlists( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - (void) data; - (void) num_columns; - (void) column_names; // Don't need any of the params - - gchar * playlist_uuid = g_strdup(koto_utils_string_unquote(fields[0])); // First column is UUID - gchar * playlist_name = g_strdup(koto_utils_string_unquote(fields[1])); // Second column is playlist name - gchar * playlist_art_path = g_strdup(koto_utils_string_unquote(fields[2])); // Third column is any art path - guint64 playlist_preferred_sort = g_ascii_strtoull(koto_utils_string_unquote(fields[3]), NULL, 10); // Fourth column is preferred model which is an int - gchar * playlist_album_id = g_strdup(koto_utils_string_unquote(fields[4])); // Fifth column is any album ID - gchar * playlist_current_track_id = g_strdup(koto_utils_string_unquote(fields[5])); // Sixth column is any track ID - guint64 playlist_track_current_playback_pos = g_ascii_strtoull(koto_utils_string_unquote(fields[6]), NULL, 10); // Seventh column is any playback position for the track - - KotoPreferredPlaylistSortType sort_type = (KotoPreferredPlaylistSortType) playlist_preferred_sort; - - KotoPlaylist * playlist = NULL; - - gboolean for_album = FALSE; - - if (koto_utils_string_is_valid(playlist_album_id)) { // Album UUID is set - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, playlist_album_id); // Get the album based on the album ID set for the playlist - - if (KOTO_IS_ALBUM(album)) { // Is an album - playlist = koto_album_get_playlist(album); // Get the playlist - for_album = TRUE; - } - } else { - playlist = koto_playlist_new_with_uuid(playlist_uuid); // Create a playlist using the existing UUID - koto_playlist_apply_model(playlist, sort_type); // Set the model based on what is stored - koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist yet, in this scenario it is likely the album no longer exists - goto free; - } - - g_object_set( - playlist, - "name", - playlist_name, - "art-path", - playlist_art_path, - "ephemeral", - for_album, - NULL - ); - - if (!for_album) { // Isn't for an album - int playlist_tracks_rc = sqlite3_exec(koto_db, g_strdup_printf("SELECT * FROM playlist_tracks WHERE playlist_id=\"%s\" ORDER BY position ASC", playlist_uuid), process_playlists_tracks, playlist, NULL); // Process our playlist tracks - - if (playlist_tracks_rc != SQLITE_OK) { // Failed to get our playlist tracks - g_critical("Failed to read our playlist tracks: %s", sqlite3_errmsg(koto_db)); - goto free; - } - } - - if (koto_utils_string_is_valid(playlist_current_track_id)) { // If we have a track UUID (probably) - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, playlist_current_track_id); // Get the track UUID - - if (KOTO_IS_TRACK(track)) { // If this is a track - koto_track_set_playback_position(track, playlist_track_current_playback_pos); // Set the playback position of the track - koto_playlist_set_track_as_current(playlist, playlist_current_track_id); // Ensure we have this track set as the current one in the playlist - } - } - - koto_playlist_mark_as_finalized(playlist); // Mark as finalized since loading should be complete - -free: - g_free(playlist_uuid); - g_free(playlist_name); - g_free(playlist_art_path); - - return 0; -} - -int process_playlists_tracks( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - (void) data; - (void) num_columns; - (void) column_names; // Don't need these - - gchar * playlist_uuid = g_strdup(koto_utils_string_unquote(fields[1])); - gchar * track_uuid = g_strdup(koto_utils_string_unquote(fields[2])); - - KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); // Get the playlist - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track - - if (!KOTO_IS_PLAYLIST(playlist)) { - goto freeforret; - } - - koto_playlist_add_track(playlist, track, FALSE, FALSE); // Add the track to the playlist but don't re-commit to the table - -freeforret: - g_free(playlist_uuid); - g_free(track_uuid); - - return 0; -} - -int process_tracks( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - (void) data; - (void) num_columns; - (void) column_names; // Don't need these - - gchar * track_uuid = g_strdup(koto_utils_string_unquote(fields[0])); - - KotoTrack * existing_track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); - - if (KOTO_IS_TRACK(existing_track)) { // Already have track - g_free(track_uuid); - return 0; - } - - gchar * artist_uuid = g_strdup(koto_utils_string_unquote(fields[1])); - gchar * album_uuid = g_strdup(koto_utils_string_unquote(fields[2])); - gchar * name = g_strdup(koto_utils_string_unquote(fields[3])); - guint * disc_num = (guint*) g_ascii_strtoull(fields[4], NULL, 10); - guint64 * position = (guint64*) g_ascii_strtoull(fields[5], NULL, 10); - guint64 * duration = (guint64*) g_ascii_strtoull(fields[6], NULL, 10); - gchar * genres = g_strdup(koto_utils_string_unquote(fields[7])); - - KotoTrack * track = koto_track_new_with_uuid(track_uuid); // Create our file - - g_object_set( - track, - "artist-uuid", - artist_uuid, - "album-uuid", - album_uuid, - "parsed-name", - name, - "cd", - disc_num, - "position", - position, - "duration", - duration, - "preparsed-genres", - genres, - NULL - ); - - g_free(name); - - int track_paths = sqlite3_exec(koto_db, g_strdup_printf("SELECT id, path FROM libraries_tracks WHERE track_id=\"%s\"", track_uuid), process_track_paths, track, NULL); // Process all pathes associated with the track - - if (track_paths != SQLITE_OK) { // Failed to read the paths - g_warning("Failed to read paths associated with track %s: %s", track_uuid, sqlite3_errmsg(koto_db)); - g_free(track_uuid); - g_free(artist_uuid); - g_free(album_uuid); - return 1; - } - - koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary - - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Get the artist - koto_artist_add_track(artist, track); // Add the track for the artist - - if (koto_utils_string_is_valid(album_uuid)) { // If we have an album UUID - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); // Attempt to get album - - if (KOTO_IS_ALBUM(album)) { // This is an album - koto_album_add_track(album, track); // Add the track - } - } - - g_free(track_uuid); - g_free(artist_uuid); - g_free(album_uuid); - - return 0; -} - -int process_track_paths( - void * data, - int num_columns, - char ** fields, - char ** column_names -) { - KotoTrack * track = (KotoTrack*) data; - (void) num_columns; - (void) column_names; // Don't need these - - KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, koto_utils_string_unquote(fields[0])); - - if (!KOTO_IS_LIBRARY(library)) { // Not a library - return 1; - } - - koto_track_set_path(track, library, koto_utils_string_unquote(fields[1])); - return 0; -} - - -void read_from_db() { - int artists_rc = sqlite3_exec(koto_db, "SELECT * FROM artists", process_artists, NULL, NULL); // Process our artists - - if (artists_rc != SQLITE_OK) { // Failed to get our artists - g_critical("Failed to read our artists: %s", sqlite3_errmsg(koto_db)); - return; - } - - int playlist_rc = sqlite3_exec(koto_db, "SELECT * FROM playlist_meta", process_playlists, NULL, NULL); // Process our playlists - - if (playlist_rc != SQLITE_OK) { // Failed to get our playlists - g_critical("Failed to read our playlists: %s", sqlite3_errmsg(koto_db)); - return; - } -} \ No newline at end of file diff --git a/src/db/loaders.h b/src/db/loaders.h deleted file mode 100644 index 07c90d7..0000000 --- a/src/db/loaders.h +++ /dev/null @@ -1,67 +0,0 @@ -/* loaders.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -int process_artists( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -int process_artist_paths( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -int process_albums( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -int process_playlists( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -int process_playlists_tracks( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -int process_tracks( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -int process_track_paths( - void * data, - int num_columns, - char ** fields, - char ** column_names -); - -void read_from_db(); \ No newline at end of file diff --git a/src/indexer/album-playlist-funcs.h b/src/indexer/album-playlist-funcs.h deleted file mode 100644 index 3a795cc..0000000 --- a/src/indexer/album-playlist-funcs.h +++ /dev/null @@ -1,25 +0,0 @@ -/* album-playlist-funcs.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "../playlist/playlist.h" -#include "structs.h" - -G_BEGIN_DECLS - -KotoPlaylist * koto_album_get_playlist(KotoAlbum * self); - -G_END_DECLS \ No newline at end of file diff --git a/src/indexer/album.c b/src/indexer/album.c deleted file mode 100644 index 0011601..0000000 --- a/src/indexer/album.c +++ /dev/null @@ -1,851 +0,0 @@ -/* album.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include "../db/cartographer.h" -#include "../db/db.h" -#include "../playlist/current.h" -#include "../playlist/playlist.h" -#include "../koto-utils.h" -#include "album-playlist-funcs.h" -#include "track-helpers.h" - -extern KotoCartographer * koto_maps; -extern KotoCurrentPlaylist * current_playlist; -extern magic_t magic_cookie; -extern sqlite3 * koto_db; - -enum { - PROP_0, - PROP_UUID, - PROP_DO_INITIAL_INDEX, - PROP_NAME, - PROP_ART_PATH, - PROP_ARTIST_UUID, - PROP_ALBUM_PREPARED_GENRES, - PROP_DESCRIPTION, - PROP_NARRATOR, - PROP_YEAR, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -enum { - SIGNAL_TRACK_ADDED, - SIGNAL_TRACK_REMOVED, - N_SIGNALS -}; - -static guint album_signals[N_SIGNALS] = { - 0 -}; - -struct _KotoAlbum { - GObject parent_instance; - gchar * uuid; - - gchar * name; - guint64 year; - gchar * description; - gchar * narrator; - gchar * art_path; - gchar * artist_uuid; - - GList * genres; - - KotoPlaylist * playlist; - GHashTable * paths; - - gboolean has_album_art; - gboolean do_initial_index; - gboolean finalized; -}; - -struct _KotoAlbumClass { - GObjectClass parent_class; - - void (* track_added) ( - KotoAlbum * album, - KotoTrack * track - ); - void (* track_removed) ( - KotoAlbum * album, - KotoTrack * track - ); -}; - -G_DEFINE_TYPE(KotoAlbum, koto_album, G_TYPE_OBJECT); - -static void koto_album_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_album_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_album_class_init(KotoAlbumClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_album_set_property; - gobject_class->get_property = koto_album_get_property; - - props[PROP_UUID] = g_param_spec_string( - "uuid", - "UUID to Album in database", - "UUID to Album in database", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_DO_INITIAL_INDEX] = g_param_spec_boolean( - "do-initial-index", - "Do an initial indexing operating instead of pulling from the database", - "Do an initial indexing operating instead of pulling from the database", - FALSE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_NAME] = g_param_spec_string( - "name", - "Name", - "Name of Album", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ART_PATH] = g_param_spec_string( - "art-path", - "Path to Artwork", - "Path to Artwork", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ARTIST_UUID] = g_param_spec_string( - "artist-uuid", - "UUID of Artist associated with Album", - "UUID of Artist associated with Album", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ALBUM_PREPARED_GENRES] = g_param_spec_string( - "preparsed-genres", - "Preparsed Genres", - "Preparsed Genres", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - props[PROP_DESCRIPTION] = g_param_spec_string( - "description", - "Description of Album, typically for an audiobook or podcast", - "Description of Album, typically for an audiobook or podcast", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - props[PROP_NARRATOR] = g_param_spec_string( - "narrator", - "Narrator of audiobook", - "Narrator of audiobook", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - props[PROP_YEAR] = g_param_spec_uint64( - "year", - "Year", - "Year of release", - 0, - G_MAXSHORT, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); - - album_signals[SIGNAL_TRACK_ADDED] = g_signal_new( - "track-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoAlbumClass, track_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_TRACK - ); - - album_signals[SIGNAL_TRACK_REMOVED] = g_signal_new( - "track-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoAlbumClass, track_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_TRACK - ); -} - -static void koto_album_init(KotoAlbum * self) { - self->description = NULL; - self->genres = NULL; - self->has_album_art = FALSE; - self->narrator = NULL; - self->paths = g_hash_table_new(g_str_hash, g_str_equal); - - self->playlist = koto_playlist_new_with_uuid(NULL); // Create a playlist, with UUID set to NULL temporarily (until we know the album UUID) - koto_playlist_apply_model(self->playlist, KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS); // Sort by track position - koto_playlist_set_album_uuid(self->playlist, self->uuid); // Set the playlist UUID to be the same as the album - g_object_set(self->playlist, "ephemeral", TRUE, NULL); // Set as ephemeral / temporary - koto_cartographer_add_playlist(koto_maps, self->playlist); // Add to cartographer - - self->year = 0; -} - -void koto_album_add_track( - KotoAlbum * self, - KotoTrack * track -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - if (koto_playlist_get_position_of_track(self->playlist, track) != -1) { // Have added it already - return; - } - - koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary - - GList * track_genres = koto_track_get_genres(track); // Get the genres for the track - GList * current_genre_list; - - gchar * existing_genres_as_string = koto_utils_join_string_list(self->genres, ";"); - - for (current_genre_list = track_genres; current_genre_list != NULL; current_genre_list = current_genre_list->next) { // Iterate over each item in the track genres - gchar * track_genre = current_genre_list->data; // Get this genre - - if (g_strcmp0(track_genre, "") == 0) { - continue; - } - - if (koto_utils_string_contains_substring(existing_genres_as_string, track_genre)) { // Genres list contains this genre - continue; - } - - self->genres = g_list_append(self->genres, g_strdup(track_genre)); // Duplicate the genre and add it to our list - } - - if (self->year == 0) { // Don't have a year set yet - guint64 track_year = koto_track_get_year(track); // Get the year from this track - - if (track_year > 0) { // Have a probably valid year set - self->year = track_year; // Set our track - } - } - - if (!koto_utils_string_is_valid(self->narrator)) { // No narrator set yet - gchar * track_narrator = koto_track_get_narrator(track); // Get the narrator for the track - - if (koto_utils_string_is_valid(track_narrator)) { // If this track has a narrator - self->narrator = g_strdup(track_narrator); - g_free(track_narrator); - } - } - - koto_playlist_add_track(self->playlist, track, FALSE, FALSE); // Add the track to our internal playlist - - g_signal_emit( - self, - album_signals[SIGNAL_TRACK_ADDED], - 0, - track - ); -} - -void koto_album_commit(KotoAlbum * self) { - if (self->art_path == NULL) { // If art_path isn't defined when committing - koto_album_set_album_art(self, ""); // Set to an empty string - } - - gchar * genres_string = koto_utils_join_string_list(self->genres, ";"); - - gchar * commit_op = g_strdup_printf( - "INSERT INTO albums(id, artist_id, name, description, narrator, art_path, genres, year)" - "VALUES('%s', '%s', quote(\"%s\"), quote(\"%s\"), quote(\"%s\"), quote(\"%s\"), '%s', %ld)" - "ON CONFLICT(id) DO UPDATE SET artist_id=excluded.artist_id, name=excluded.name, description=excluded.description, narrator=excluded.narrator, art_path=excluded.art_path, genres=excluded.genres, year=excluded.year;", - self->uuid, - self->artist_uuid, - koto_utils_string_get_valid(self->name), - koto_utils_string_get_valid(self->description), - koto_utils_string_get_valid(self->narrator), - koto_utils_string_get_valid(self->art_path), - koto_utils_string_get_valid(genres_string), - self->year - ); - - new_transaction(commit_op, "Failed to write our album to the database", FALSE); - - g_free(genres_string); - - GHashTableIter paths_iter; - g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths - gpointer lib_uuid_ptr, album_rel_path_ptr; - while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &album_rel_path_ptr)) { - gchar * lib_uuid = lib_uuid_ptr; - gchar * album_rel_path = album_rel_path_ptr; - - gchar * commit_op = g_strdup_printf( - "INSERT INTO libraries_albums(id, album_id, path)" - "VALUES ('%s', '%s', quote(\"%s\"))" - "ON CONFLICT(id, album_id) DO UPDATE SET path=excluded.path;", - lib_uuid, - self->uuid, - album_rel_path - ); - - new_transaction(commit_op, "Failed to add this path for the album", FALSE); - } -} - -void koto_album_find_album_art(KotoAlbum * self) { - if (self->has_album_art) { // If we already have album art - return; - } - - gchar * optimal_album_path = koto_album_get_path(self); - DIR * dir = opendir(optimal_album_path); // Attempt to open our directory - - if (dir == NULL) { - return; - } - - struct dirent * entry; - - while ((entry = readdir(dir))) { - if (entry->d_type != DT_REG) { // Not a regular file - continue; // SKIP - } - - if (g_str_has_prefix(entry->d_name, ".")) { // Reference to parent dir, self, or a hidden item - continue; // Skip - } - - gchar * full_path = g_strdup_printf("%s%s%s", optimal_album_path, G_DIR_SEPARATOR_S, entry->d_name); - - const char * mime_type = magic_file(magic_cookie, full_path); - - if ( - (mime_type == NULL) || // Failed to get the mimetype - ((mime_type != NULL) && !g_str_has_prefix(mime_type, "image/")) // Got the mimetype but it is not an image - ) { - g_free(full_path); - continue; // Skip - } - - gchar * album_art_no_ext = g_strdup(koto_utils_get_filename_without_extension(entry->d_name)); // Get the name of the file without the extension - - gchar * lower_art = g_strdup(g_utf8_strdown(album_art_no_ext, -1)); // Lowercase - g_free(album_art_no_ext); - - gboolean should_set = (g_strrstr(lower_art, "Small") == NULL) && (g_strrstr(lower_art, "back") == NULL); // Not back or small - - g_free(lower_art); - - if (should_set) { - koto_album_set_album_art(self, full_path); - g_free(full_path); - break; - } - - g_free(full_path); - } - - closedir(dir); -} - -static void koto_album_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoAlbum * self = KOTO_ALBUM(obj); - - switch (prop_id) { - case PROP_UUID: - g_value_set_string(val, self->uuid); - break; - case PROP_DO_INITIAL_INDEX: - g_value_set_boolean(val, self->do_initial_index); - break; - case PROP_NAME: - g_value_set_string(val, self->name); - break; - case PROP_ART_PATH: - g_value_set_string(val, koto_album_get_art(self)); - break; - case PROP_ARTIST_UUID: - g_value_set_string(val, self->artist_uuid); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_album_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoAlbum * self = KOTO_ALBUM(obj); - - switch (prop_id) { - case PROP_UUID: - koto_album_set_uuid(self, g_value_get_string(val)); - break; - case PROP_DO_INITIAL_INDEX: - self->do_initial_index = g_value_get_boolean(val); - break; - case PROP_NAME: // Name of album - koto_album_set_album_name(self, g_value_get_string(val)); - break; - case PROP_ART_PATH: // Path to art - koto_album_set_album_art(self, g_value_get_string(val)); - break; - case PROP_ARTIST_UUID: - koto_album_set_artist_uuid(self, g_value_get_string(val)); - break; - case PROP_ALBUM_PREPARED_GENRES: - koto_album_set_preparsed_genres(self, g_strdup(g_value_get_string(val))); - break; - case PROP_DESCRIPTION: - koto_album_set_description(self, g_value_get_string(val)); - break; - case PROP_NARRATOR: - koto_album_set_narrator(self, g_value_get_string(val)); - break; - case PROP_YEAR: - koto_album_set_year(self, g_value_get_uint64(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -gchar * koto_album_get_art(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return g_strdup(""); - } - - return g_strdup((self->has_album_art && koto_utils_string_is_valid(self->art_path)) ? self->art_path : ""); -} - -gchar * koto_album_get_artist_uuid(KotoAlbum * self) { - return KOTO_IS_ALBUM(self) ? self->artist_uuid : NULL; -} - -gchar * koto_album_get_description(KotoAlbum * self) { - return KOTO_IS_ALBUM(self) ? self->description : NULL; -} - -GList * koto_album_get_genres(KotoAlbum * self) { - return KOTO_IS_ALBUM(self) ? self->genres : NULL; -} - -KotoLibraryType koto_album_get_lib_type(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return KOTO_LIBRARY_TYPE_UNKNOWN; - } - - return koto_artist_get_lib_type(koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid)); // Get the lib type for the artist. If artist isn't valid, we just return UNKNOWN -} - -gchar * koto_album_get_name(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return NULL; - } - - if (!koto_utils_string_is_valid(self->name)) { // Not set - return NULL; - } - - return self->name; // Return name of the album -} - -gchar * koto_album_get_narrator(KotoAlbum * self) { - return KOTO_IS_ALBUM(self) ? self->narrator : NULL; -} - -gchar * koto_album_get_path(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self) || (KOTO_IS_ALBUM(self) && (g_list_length(g_hash_table_get_keys(self->paths)) == 0))) { // If this is not an album or is but we have no paths associated with it - return NULL; - } - - GList * libs = koto_cartographer_get_libraries(koto_maps); // Get all of our libraries - GList * cur_lib_list; - - for (cur_lib_list = libs; cur_lib_list != NULL; cur_lib_list = libs->next) { // Iterate over our libraries - KotoLibrary * cur_library = libs->data; // Get this as a KotoLibrary - gchar * library_relative_path = g_hash_table_lookup(self->paths, koto_library_get_uuid(cur_library)); // Get any relative path in our paths based on the current UUID - - if (!koto_utils_string_is_valid(library_relative_path)) { // Not a valid path - continue; - } - - return g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(cur_library), library_relative_path, NULL)); // Build our full library path using library's path and our file relative path - } - - return NULL; -} - -KotoPlaylist * koto_album_get_playlist(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return NULL; - } - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // Not a playlist - return NULL; - } - - return self->playlist; -} - -GListStore * koto_album_get_store(KotoAlbum * self) { - KotoPlaylist * playlist = koto_album_get_playlist(self); - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - return NULL; - } - - return koto_playlist_get_store(playlist); // Return the store from the playlist -} - -gchar * koto_album_get_uuid(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return NULL; - } - - return self->uuid; // Return the UUID -} - -guint64 koto_album_get_year(KotoAlbum * self) { - return KOTO_IS_ALBUM(self) ? self->year : 0; -} - -void koto_album_mark_as_finalized(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (self->finalized) { // Already finalized - return; - } - - self->finalized = TRUE; - koto_playlist_mark_as_finalized(self->playlist); - //koto_playlist_apply_model(self->playlist, self->model); // Resort our playlist -} - -void koto_album_remove_track( - KotoAlbum * self, - KotoTrack * track -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - koto_playlist_remove_track_by_uuid( - self->playlist, - koto_track_get_uuid(track) - ); - - g_signal_emit( - self, - album_signals[SIGNAL_TRACK_REMOVED], - 0, - track - ); -} - -void koto_album_set_album_name( - KotoAlbum * self, - const gchar * album_name -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (album_name == NULL) { // Not valid album name - return; - } - - if (self->name != NULL) { - g_free(self->name); - } - - self->name = g_strdup(album_name); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NAME]); -} - -void koto_album_set_artist_uuid( - KotoAlbum * self, - const gchar * artist_uuid -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (artist_uuid == NULL) { - return; - } - - if (self->artist_uuid != NULL) { - g_free(self->artist_uuid); - } - - self->artist_uuid = g_strdup(artist_uuid); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]); -} - -void koto_album_set_album_art( - KotoAlbum * self, - const gchar * album_art -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (album_art == NULL) { // Not valid album art - return; - } - - if (self->art_path != NULL) { - g_free(self->art_path); - } - - self->art_path = g_strdup(album_art); - - self->has_album_art = TRUE; - - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ART_PATH]); -} - -void koto_album_set_as_current_playlist(KotoAlbum * self) { - if (!KOTO_IS_ALBUM(self)) { - return; - } - - if (!KOTO_IS_CURRENT_PLAYLIST(current_playlist)) { - return; - } - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // Don't have a playlist for the album for some reason - return; - } - - koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE, FALSE); // Set our new current playlist and start playing immediately -} - -void koto_album_set_description( - KotoAlbum * self, - const gchar * description -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (!koto_utils_string_is_valid(description)) { - return; - } - - self->description = g_strdup(description); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]); -} - -void koto_album_set_narrator( - KotoAlbum * self, - const gchar * narrator -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (!koto_utils_string_is_valid(narrator)) { - return; - } - - self->narrator = g_strdup(narrator); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NARRATOR]); -} - -void koto_album_set_path( - KotoAlbum * self, - KotoLibrary * lib, - const gchar * fixed_path -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path - gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library - - gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path - g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one - - koto_album_set_album_name(self, g_path_get_basename(relative_path)); // Update our album name based on the base name - - if (!self->do_initial_index) { // Not doing our initial index - return; - } - - koto_album_find_album_art(self); // Update our path for the album art - self->do_initial_index = FALSE; -} - -void koto_album_set_preparsed_genres( - KotoAlbum * self, - gchar * genrelist -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (!koto_utils_string_is_valid(genrelist)) { // If it is an empty string - return; - } - - GList * preparsed_genres_list = koto_utils_string_to_string_list(genrelist, ";"); - - if (g_list_length(preparsed_genres_list) == 0) { // No genres - g_list_free(preparsed_genres_list); - return; - } - - // TODO: Do a pass on in first memory optimization phase to ensure string elements are freed. - g_list_free_full(self->genres, NULL); // Free the existing genres list - self->genres = preparsed_genres_list; -}; - -void koto_album_set_uuid( - KotoAlbum * self, - const gchar * uuid -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (!koto_utils_string_is_valid(uuid)) { - return; - } - - self->uuid = g_strdup(uuid); - g_object_set( - self->playlist, - "album-uuid", - self->uuid, - "uuid", - self->uuid, // Ensure the playlist has the same UUID as the album - NULL - ); - - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]); -} - -void koto_album_set_year( - KotoAlbum * self, - guint64 year -) { - if (!KOTO_IS_ALBUM(self)) { // Not an album - return; - } - - if (year <= 0) { - return; - } - - self->year = year; - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_YEAR]); -} - -KotoAlbum * koto_album_new(gchar * artist_uuid) { - if (!koto_utils_string_is_valid(artist_uuid)) { // Invalid artist UUID provided - return NULL; - } - - KotoAlbum * album = g_object_new( - KOTO_TYPE_ALBUM, - "artist-uuid", - artist_uuid, - "uuid", - g_strdup(g_uuid_string_random()), - "do-initial-index", - TRUE, - NULL - ); - - return album; -} - -KotoAlbum * koto_album_new_with_uuid( - KotoArtist * artist, - const gchar * uuid -) { - gchar * artist_uuid = koto_artist_get_uuid(artist); - - return g_object_new( - KOTO_TYPE_ALBUM, - "artist-uuid", - artist_uuid, - "uuid", - g_strdup(uuid), - "do-initial-index", - FALSE, - NULL - ); -} diff --git a/src/indexer/artist-playlist-funcs.h b/src/indexer/artist-playlist-funcs.h deleted file mode 100644 index 2c9677f..0000000 --- a/src/indexer/artist-playlist-funcs.h +++ /dev/null @@ -1,25 +0,0 @@ -/* artist-playlist-funcs.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "../playlist/playlist.h" -#include "structs.h" - -G_BEGIN_DECLS - -KotoPlaylist * koto_artist_get_playlist(KotoArtist * self); - -G_END_DECLS \ No newline at end of file diff --git a/src/indexer/artist.c b/src/indexer/artist.c deleted file mode 100644 index 0bf30e4..0000000 --- a/src/indexer/artist.c +++ /dev/null @@ -1,606 +0,0 @@ -/* artist.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../config/config.h" -#include "../db/db.h" -#include "../db/cartographer.h" -#include "../playlist/playlist.h" -#include "../koto-utils.h" -#include "artist-playlist-funcs.h" -#include "structs.h" -#include "track-helpers.h" - -extern KotoCartographer * koto_maps; -extern KotoConfig * config; - -enum { - PROP_0, - PROP_UUID, - PROP_ARTIST_NAME, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -enum { - SIGNAL_ALBUM_ADDED, - SIGNAL_ALBUM_REMOVED, - SIGNAL_HAS_NO_ALBUMS, - SIGNAL_TRACK_ADDED, - SIGNAL_TRACK_REMOVED, - N_SIGNALS -}; - -static guint artist_signals[N_SIGNALS] = { - 0 -}; - -struct _KotoArtist { - GObject parent_instance; - gchar * uuid; - - KotoPlaylist * content_playlist; - - gboolean finalized; - gboolean has_artist_art; - gchar * artist_name; - GList * tracks; - GHashTable * paths; - KotoLibraryType type; - - GQueue * albums; - GListStore * albums_store; -}; - -struct _KotoArtistClass { - GObjectClass parent_class; - - void (* album_added) ( - KotoArtist * artist, - KotoAlbum * album - ); - void (* album_removed) ( - KotoArtist * artist, - KotoAlbum * album - ); - void (* has_no_albums) (KotoArtist * artist); - void (* track_added) ( - KotoArtist * artist, - KotoTrack * track - ); - void (* track_removed) ( - KotoArtist * artist, - KotoTrack * track - ); -}; - -G_DEFINE_TYPE(KotoArtist, koto_artist, G_TYPE_OBJECT); - -static void koto_artist_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_artist_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_artist_class_init(KotoArtistClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_artist_set_property; - gobject_class->get_property = koto_artist_get_property; - - artist_signals[SIGNAL_ALBUM_ADDED] = g_signal_new( - "album-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoArtistClass, album_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_ALBUM - ); - - artist_signals[SIGNAL_ALBUM_REMOVED] = g_signal_new( - "album-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoArtistClass, album_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_ALBUM - ); - - artist_signals[SIGNAL_HAS_NO_ALBUMS] = g_signal_new( - "has-no-albums", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoArtistClass, has_no_albums), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - artist_signals[SIGNAL_TRACK_ADDED] = g_signal_new( - "track-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoArtistClass, track_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_TRACK - ); - - artist_signals[SIGNAL_TRACK_REMOVED] = g_signal_new( - "track-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoArtistClass, track_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - KOTO_TYPE_TRACK - ); - - props[PROP_UUID] = g_param_spec_string( - "uuid", - "UUID to Artist in database", - "UUID to Artist in database", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ARTIST_NAME] = g_param_spec_string( - "name", - "Name", - "Name of Artist", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_artist_init(KotoArtist * self) { - self->albums = g_queue_new(); // Create a new GQueue - self->albums_store = g_list_store_new(KOTO_TYPE_ALBUM); // Create our GListStore of type KotoAlbum - - self->content_playlist = koto_playlist_new(); // Create our playlist - g_object_set( - self->content_playlist, - "ephemeral", // Indicate that it is temporary - TRUE, - NULL - ); - - self->finalized = FALSE; // Indicate we not finalized - self->has_artist_art = FALSE; - self->paths = g_hash_table_new(g_str_hash, g_str_equal); - self->tracks = NULL; - self->type = KOTO_LIBRARY_TYPE_UNKNOWN; -} - -void koto_artist_commit(KotoArtist * self) { - if ((self->uuid == NULL) || strcmp(self->uuid, "")) { // UUID not set - self->uuid = g_strdup(g_uuid_string_random()); - } - - // TODO: Support multiple types instead of just local music artist - gchar * commit_op = g_strdup_printf( - "INSERT INTO artists(id , name, art_path)" - "VALUES ('%s', quote(\"%s\"), NULL)" - "ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path;", - self->uuid, - koto_utils_string_get_valid(self->artist_name) - ); - - new_transaction(commit_op, "Failed to write our artist to the database", FALSE); - - GHashTableIter paths_iter; - g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths - gpointer lib_uuid_ptr, artist_rel_path_ptr; - while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &artist_rel_path_ptr)) { - gchar * lib_uuid = lib_uuid_ptr; - gchar * artist_rel_path = artist_rel_path_ptr; - - gchar * commit_op = g_strdup_printf( - "INSERT INTO libraries_artists(id, artist_id, path)" - "VALUES ('%s', '%s', quote(\"%s\"))" - "ON CONFLICT(id, artist_id) DO UPDATE SET path=excluded.path;", - lib_uuid, - self->uuid, - artist_rel_path - ); - - new_transaction(commit_op, "Failed to add this path for the artist", FALSE); - } -} - -static void koto_artist_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoArtist * self = KOTO_ARTIST(obj); - - switch (prop_id) { - case PROP_UUID: - g_value_set_string(val, self->uuid); - break; - case PROP_ARTIST_NAME: - g_value_set_string(val, self->artist_name); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_artist_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoArtist * self = KOTO_ARTIST(obj); - - switch (prop_id) { - case PROP_UUID: - self->uuid = g_strdup(g_value_get_string(val)); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]); - break; - case PROP_ARTIST_NAME: - koto_artist_set_artist_name(self, (gchar*) g_value_get_string(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_artist_add_album( - KotoArtist * self, - KotoAlbum * album -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - if (!KOTO_IS_ALBUM(album)) { // Album provided is not an album - return; - } - - GList * found_albums = g_queue_find(self->albums, album); // Try finding the album - - if (found_albums != NULL) { // Already has been added - g_list_free(found_albums); - return; - } - - g_list_free(found_albums); - - g_queue_push_tail(self->albums, album); // Add the album to end of albums GQueue - g_list_store_append(self->albums_store, album); // Add the album to th estore as well - - if (self->finalized) { // Is already finalized - koto_artist_apply_model(self, koto_config_get_preferred_album_sort_type(config)); // Apply our model to sort our albums gqueue and store - } - - g_signal_emit( - self, - artist_signals[SIGNAL_ALBUM_ADDED], - 0, - album - ); -} - -void koto_artist_add_track( - KotoArtist * self, - KotoTrack * track -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - gchar * track_uuid = koto_track_get_uuid(track); - - if (g_list_index(self->tracks, track_uuid) != -1) { // If we have already added the track - return; - } - - koto_cartographer_add_track(koto_maps, track); // Add the track to cartographer if necessary - self->tracks = g_list_insert_sorted_with_data(self->tracks, track_uuid, koto_track_helpers_sort_tracks_by_uuid, NULL); - - koto_playlist_add_track(self->content_playlist, track, FALSE, FALSE); // Add this new track for the artist to its playlist - - g_signal_emit( - self, - artist_signals[SIGNAL_TRACK_ADDED], - 0, - track - ); -} - -void koto_artist_apply_model( - KotoArtist * self, - KotoPreferredAlbumSortType model -) { - if (!KOTO_IS_ARTIST(self)) { - return; - } - - g_queue_sort(self->albums, koto_artist_model_sort_albums, &model); - g_list_store_sort(self->albums_store, koto_artist_model_sort_albums, &model); -} - -GQueue * koto_artist_get_albums(KotoArtist * self) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return NULL; - } - - return self->albums; -} - -GListStore * koto_artist_get_albums_store(KotoArtist * self) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return NULL; - } - - return self->albums_store; -} - -KotoAlbum * koto_artist_get_album_by_name( - KotoArtist * self, - gchar * album_name -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return NULL; - } - - KotoAlbum * album = NULL; - - GList * cur_list_iter; - for (cur_list_iter = self->albums->head; cur_list_iter != NULL; cur_list_iter = cur_list_iter->next) { // Iterate through our albums by their UUIDs - KotoAlbum * this_album = cur_list_iter->data; - - if (!KOTO_IS_ALBUM(this_album)) { // Not an album - continue; - } - - if (g_strcmp0(koto_album_get_name(this_album), album_name) == 0) { // These album names match - album = this_album; - break; - } - } - - return album; -} - -gchar * koto_artist_get_name(KotoArtist * self) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return g_strdup(""); - } - - return g_strdup(koto_utils_string_is_valid(self->artist_name) ? self->artist_name : ""); // Return artist name if set -} - -KotoPlaylist * koto_artist_get_playlist(KotoArtist * self) { - if (!KOTO_IS_ARTIST(self)) { - return NULL; - } - - return self->content_playlist; -} - -GList * koto_artist_get_tracks(KotoArtist * self) { - return KOTO_IS_ARTIST(self) ? self->tracks : NULL; -} - -KotoLibraryType koto_artist_get_lib_type(KotoArtist * self) { - return KOTO_IS_ARTIST(self) ? self->type : KOTO_LIBRARY_TYPE_UNKNOWN; -} - -gchar * koto_artist_get_uuid(KotoArtist * self) { - return KOTO_IS_ARTIST(self) ? self->uuid : NULL; -} - -void koto_artist_remove_album( - KotoArtist * self, - KotoAlbum * album -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - if (!KOTO_ALBUM(album)) { // No album defined - return; - } - - g_queue_remove(self->albums, album); // Remove the album - - guint position = 0; - if (g_list_store_find(self->albums_store, album, &position)) { // Found the album in the list store - g_list_store_remove(self->albums_store, position); // Remove from the list store - } - - g_signal_emit( - self, - artist_signals[SIGNAL_ALBUM_REMOVED], - 0, - album - ); -} - -void koto_artist_remove_track( - KotoArtist * self, - KotoTrack * track -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - gchar * track_uuid = koto_track_get_uuid(track); - self->tracks = g_list_remove(self->tracks, koto_track_get_uuid(track)); - - koto_playlist_remove_track_by_uuid(self->content_playlist, track_uuid); // Remove the track from our playlist - - g_signal_emit( - self, - artist_signals[SIGNAL_TRACK_ADDED], - 0, - track - ); -} - -void koto_artist_set_artist_name( - KotoArtist * self, - gchar * artist_name -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - if (!koto_utils_string_is_valid(artist_name)) { // No artist name - return; - } - - if (koto_utils_string_is_valid(self->artist_name)) { // Has artist name - g_free(self->artist_name); - } - - self->artist_name = g_strdup(artist_name); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_NAME]); -} - -void koto_artist_set_as_finalized(KotoArtist * self) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - self->finalized = TRUE; - - if (g_queue_get_length(self->albums) == 0) { // Have no albums - g_signal_emit_by_name(self, "has-no-albums"); - } -} - -void koto_artist_set_path( - KotoArtist * self, - KotoLibrary * lib, - const gchar * fixed_path, - gboolean should_commit -) { - if (!KOTO_IS_ARTIST(self)) { // Not an artist - return; - } - - gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path - gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library - - gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path - g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one - - if (should_commit) { // Should commit to the DB - koto_artist_commit(self); // Save the artist - } - - self->type = koto_library_get_lib_type(lib); // Define our artist type as the type from the library -} - -gint koto_artist_model_sort_albums( - gconstpointer first_item, - gconstpointer second_item, - gpointer user_data -) { - KotoPreferredAlbumSortType * preferred_model = (KotoPreferredAlbumSortType*) user_data; - - KotoAlbum * first_album = KOTO_ALBUM(first_item); - KotoAlbum * second_album = KOTO_ALBUM(second_item); - - if (KOTO_IS_ALBUM(first_album) && !KOTO_IS_ALBUM(second_album)) { // First album is valid, second isn't - return -1; - } else if (!KOTO_IS_ALBUM(first_album) && KOTO_IS_ALBUM(second_album)) { // Second album is valid, first isn't - return 1; - } - - if (preferred_model == KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT) { // Sort chronological before alphabetical - guint64 fa_year = koto_album_get_year(first_album); - guint64 sa_year = koto_album_get_year(second_album); - - if (fa_year > sa_year) { // First album is newer than second - return -1; - } else if (fa_year < sa_year) { // Second album is newer than first - return 1; - } - } - - return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album)); -} - -KotoArtist * koto_artist_new(gchar * artist_name) { - KotoArtist * artist = g_object_new( - KOTO_TYPE_ARTIST, - "uuid", - g_uuid_string_random(), - "name", - artist_name, - NULL - ); - - return artist; -} - -KotoArtist * koto_artist_new_with_uuid(const gchar * uuid) { - return g_object_new( - KOTO_TYPE_ARTIST, - "uuid", - g_strdup(uuid), - NULL - ); -} diff --git a/src/indexer/file-indexer.c b/src/indexer/file-indexer.c deleted file mode 100644 index 9bd9e34..0000000 --- a/src/indexer/file-indexer.c +++ /dev/null @@ -1,239 +0,0 @@ -/* file-indexer.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include "../db/cartographer.h" -#include "../koto-utils.h" -#include "structs.h" -#include "track-helpers.h" - -extern KotoCartographer * koto_maps; -extern magic_t magic_cookie; - -void index_folder( - KotoLibrary * self, - gchar * path, - guint depth -) { - depth++; - - DIR * dir = opendir(path); // Attempt to open our directory - - if (dir == NULL) { - return; - } - - struct dirent * entry; - - while ((entry = readdir(dir))) { - if (g_str_has_prefix(entry->d_name, ".")) { // A reference to parent dir, self, or a hidden item - continue; - } - - gchar * full_path = g_strdup_printf("%s%s%s", path, G_DIR_SEPARATOR_S, entry->d_name); - - if (entry->d_type == DT_DIR) { // Directory - if (depth == 1) { // If we are following (ARTIST,AUTHOR,PODCAST)/ALBUM then this would be artist - KotoArtist * artist = koto_artist_new(entry->d_name); // Attempt to get the artist - - if (KOTO_IS_ARTIST(artist)) { - koto_artist_set_path(artist, self, full_path, TRUE); // Add the path for this library on this Artist and commit immediately - koto_cartographer_add_artist(koto_maps, artist); // Add the artist to cartographer - index_folder(self, full_path, depth); // Index this directory - koto_artist_set_as_finalized(artist); // Indicate it is finalized - } - } else if (depth == 2) { // If we are following FOLDER/ARTIST/ALBUM then this would be album - gchar * artist_name = g_path_get_basename(path); // Get the last entry from our path which is probably the artist - KotoArtist * artist = koto_cartographer_get_artist_by_name(koto_maps, artist_name); - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - continue; - } - - gchar * artist_uuid = koto_artist_get_uuid(artist); // Get the artist's UUID - - KotoAlbum * album = koto_album_new(artist_uuid); - - koto_album_set_path(album, self, full_path); - - koto_cartographer_add_album(koto_maps, album); // Add our album to the cartographer - koto_artist_add_album(artist, album); // Add the album - - index_folder(self, full_path, depth); // Index inside the album - koto_album_commit(album); // Save to database immediately - g_free(artist_name); - } else if (depth == 3) { // Possibly CD within album - gchar ** split = g_strsplit(full_path, G_DIR_SEPARATOR_S, -1); - guint split_len = g_strv_length(split); - - if (split_len < 4) { - g_strfreev(split); - continue; - } - - gchar * album_name = g_strdup(split[split_len - 2]); - gchar * artist_name = g_strdup(split[split_len - 3]); - g_strfreev(split); - - if (!koto_utils_string_is_valid(album_name)) { - g_free(album_name); - continue; - } - - if (!koto_utils_string_is_valid(artist_name)) { - g_free(album_name); - g_free(artist_name); - continue; - } - - KotoArtist * artist = koto_cartographer_get_artist_by_name(koto_maps, artist_name); - g_free(artist_name); - - if (!KOTO_IS_ARTIST(artist)) { - continue; - } - - KotoAlbum * album = koto_artist_get_album_by_name(artist, album_name); // Get the album - g_free(album_name); - - if (!KOTO_IS_ALBUM(album)) { - continue; - } - - index_folder(self, full_path, depth); // Index inside the album - } - } else if ((entry->d_type == DT_REG)) { // Is a file in artist folder or lower in FS hierarchy - index_file(self, full_path); // Index this audio file or weird ogg thing - } - - g_free(full_path); - } - - closedir(dir); // Close the directory -} - -void index_file( - KotoLibrary * lib, - const gchar * path -) { - const char * mime_type = magic_file(magic_cookie, path); - - if (mime_type == NULL) { // Failed to get the mimetype - return; - } - - if (!g_str_has_prefix(mime_type, "audio/") && !g_str_has_prefix(mime_type, "video/ogg")) { // Is not an audio file or ogg - return; - } - - gboolean for_audiobook = (koto_library_get_lib_type(lib) == KOTO_LIBRARY_TYPE_AUDIOBOOK); - - gchar * relative_path_to_file = koto_library_get_relative_path_to_file(lib, g_strdup(path)); // Strip out library path so we have a relative path to the file - gchar * file_basename = g_path_get_basename(relative_path_to_file); - - gchar ** split_on_relative_slashes = g_strsplit(relative_path_to_file, G_DIR_SEPARATOR_S, -1); // Split based on separator (e.g. / ) - guint slash_sep_count = g_strv_length(split_on_relative_slashes); - - gchar * artist_author_podcast_name = g_strdup(split_on_relative_slashes[0]); // No matter what, artist should be first - gchar * album_or_audiobook_name = NULL; - guint cd = (guint) 0; - - if (slash_sep_count >= 3) { // If this it is at least "artist" + "album" + "file" (or with CD) - album_or_audiobook_name = g_strdup(split_on_relative_slashes[1]); // Duplicate the second item as the album or audiobook name - } - - // #region CD parsing logic - - if ((slash_sep_count == 4)) { // If is at least "artist" + "album" + "cd" + "file" - gchar * cd_str = g_strdup(g_strstrip(koto_utils_string_replace_all(g_utf8_strdown(split_on_relative_slashes[2], -1), "cd", ""))); // Replace a lowercased version of our CD ("cd") and trim any whitespace - - cd = (guint) g_ascii_strtoull(cd_str, NULL, 10); // Attempt to convert} - } - - if (for_audiobook && (cd == 0)) { // No CD yet and is for an audiobook - cd = koto_track_helpers_get_cd_based_on_file_name(file_basename); // Base on file name - } else { - cd = 1; - } - - // #endregion - - gchar * file_name = NULL; - - if (for_audiobook) { // If this is for an audiobook library - guint64 pos = koto_track_helpers_get_position_based_on_file_name(file_basename); - - if (cd != 1) { // On a non "Part 1" audiobook - file_name = g_strdup_printf("%s - Part %u - %lu", album_or_audiobook_name, cd, pos); - } else { // Part 1 / CD 1 - file_name = g_strdup_printf("%s - %lu", album_or_audiobook_name, pos); - } - } - - if (!koto_utils_string_is_valid(file_name)) { // No valid file name yet - file_name = koto_track_helpers_get_name_for_file(path, artist_author_podcast_name); // Get the name of the file - } - - g_strfreev(split_on_relative_slashes); - g_free(file_basename); - - gchar * sorta_uniqueish_key = NULL; - - if (koto_utils_string_is_valid(album_or_audiobook_name)) { // Have audiobook or album name - sorta_uniqueish_key = g_strdup_printf("%s-%s-%s", artist_author_podcast_name, album_or_audiobook_name, file_name); - } else { // No audiobook or album name - sorta_uniqueish_key = g_strdup_printf("%s-%s", artist_author_podcast_name, file_name); - } - - KotoTrack * track = koto_cartographer_get_track_by_uniqueish_key(koto_maps, sorta_uniqueish_key); // Attempt to get any existing KotoTrack - - if (KOTO_IS_TRACK(track)) { // Got a track already - koto_track_set_path(track, lib, relative_path_to_file); // Add this path, which will determine the associated library within that function - } else { // Don't already have a track for this file - KotoArtist * artist = koto_cartographer_get_artist_by_name(koto_maps, artist_author_podcast_name); // Get the possible artist - - if (!KOTO_IS_ARTIST(artist)) { // Have an artist for this already - return; - } - - KotoAlbum * album = NULL; - - if (koto_utils_string_is_valid(album_or_audiobook_name)) { // Have an album or audiobook name - KotoAlbum * possible_album = koto_artist_get_album_by_name(artist, album_or_audiobook_name); - album = KOTO_IS_ALBUM(possible_album) ? possible_album : NULL; - } - - gchar * album_uuid = KOTO_IS_ALBUM(album) ? koto_album_get_uuid(album) : NULL; - - track = koto_track_new(koto_artist_get_uuid(artist), album_uuid, file_name, cd); - koto_track_set_path(track, lib, relative_path_to_file); // Immediately add the path to this file, for this Library - koto_artist_add_track(artist, track); // Add the track to the artist in the event this is a podcast (no album) or the track is directly in the artist directory - - if (KOTO_IS_ALBUM(album)) { // Have an album - koto_album_add_track(album, track); // Add this track since we haven't yet - } - - koto_cartographer_add_track(koto_maps, track); // Add to our cartographer tracks hashtable - } - - if (KOTO_IS_TRACK(track)) { // Is a track - koto_track_commit(track); // Save the track immediately - } -} \ No newline at end of file diff --git a/src/indexer/library.c b/src/indexer/library.c deleted file mode 100644 index 1365e8d..0000000 --- a/src/indexer/library.c +++ /dev/null @@ -1,511 +0,0 @@ -/* library.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "../config/config.h" -#include "../koto-utils.h" -#include "structs.h" - -extern KotoConfig * config; -extern GVolumeMonitor * volume_monitor; - -enum { - PROP_0, - PROP_TYPE, - PROP_UUID, - PROP_STORAGE_UUID, - PROP_CONSTRUCTION_PATH, - PROP_NAME, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -enum { - SIGNAL_NOW_AVAILABLE, - SIGNAL_NOW_UNAVAILABLE, - N_SIGNALS -}; - -static guint library_signals[N_SIGNALS] = { - 0 -}; - -struct _KotoLibrary { - GObject parent_instance; - gchar * uuid; - - KotoLibraryType type; - gchar * directory; - gchar * storage_uuid; - - GMount * mount; - gulong mount_unmounted_handler; - gchar * mount_path; - - gboolean should_index; - - gchar * path; - gchar * relative_path; - gchar * name; -}; - -struct _KotoLibraryClass { - GObjectClass parent_class; - - void (* now_available) (KotoLibrary * library); - - void (* now_unavailable) (KotoLibrary * library); -}; - -G_DEFINE_TYPE(KotoLibrary, koto_library, G_TYPE_OBJECT); - -static void koto_library_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_library_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_library_class_init(KotoLibraryClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_library_set_property; - gobject_class->get_property = koto_library_get_property; - - library_signals[SIGNAL_NOW_AVAILABLE] = g_signal_new( - "now-available", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoLibraryClass, now_available), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - library_signals[SIGNAL_NOW_UNAVAILABLE] = g_signal_new( - "now-unavailable", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoLibraryClass, now_unavailable), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - props[PROP_UUID] = g_param_spec_string( - "uuid", - "UUID of Library", - "UUID of Library", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_TYPE] = g_param_spec_string( - "type", - "Type of Library", - "Type of Library", - koto_library_type_to_string(KOTO_LIBRARY_TYPE_MUSIC), - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_STORAGE_UUID] = g_param_spec_string( - "storage-uuid", - "Storage UUID to associated Mount of Library", - "Storage UUID to associated Mount of Library", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_CONSTRUCTION_PATH] = g_param_spec_string( - "construction-path", - "Construction Path", - "Path to this library during construction", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - props[PROP_NAME] = g_param_spec_string( - "name", - "Name of the Library", - "Name of the Library", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_library_init(KotoLibrary * self) { - (void) self; -} - -static void koto_library_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoLibrary * self = KOTO_LIBRARY(obj); - - switch (prop_id) { - case PROP_NAME: - g_value_set_string(val, g_strdup(self->name)); - break; - case PROP_UUID: - g_value_set_string(val, self->uuid); - break; - case PROP_STORAGE_UUID: - g_value_set_string(val, self->storage_uuid); - break; - case PROP_TYPE: - g_value_set_string(val, koto_library_type_to_string(self->type)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_library_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoLibrary * self = KOTO_LIBRARY(obj); - - switch (prop_id) { - case PROP_UUID: - self->uuid = g_strdup(g_value_get_string(val)); - break; - case PROP_TYPE: - self->type = koto_library_type_from_string(g_strdup(g_value_get_string(val))); - break; - case PROP_STORAGE_UUID: - koto_library_set_storage_uuid(self, g_strdup(g_value_get_string(val))); - break; - case PROP_CONSTRUCTION_PATH: - koto_library_set_path(self, g_strdup(g_value_get_string(val))); - break; - case PROP_NAME: - koto_library_set_name(self, g_strdup(g_value_get_string(val))); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -gchar * koto_library_get_path(KotoLibrary * self) { - if (!KOTO_IS_LIBRARY(self)) { - return NULL; - } - - if (G_IS_MOUNT(self->mount)) { - - } - - return self->path; -} - -gchar * koto_library_get_relative_path_to_file( - KotoLibrary * self, - gchar * full_path -) { - if (!KOTO_IS_LIBRARY(self)) { - return NULL; - } - - gchar * appended_slash_to_library_path = g_str_has_suffix(self->path, G_DIR_SEPARATOR_S) ? g_strdup(self->path) : g_strdup_printf("%s%s", g_strdup(self->path), G_DIR_SEPARATOR_S); - gchar * cleaned_path = koto_utils_string_replace_all(full_path, appended_slash_to_library_path, ""); // Replace the full path - - g_free(appended_slash_to_library_path); - return cleaned_path; -} - -gchar * koto_library_get_storage_uuid(KotoLibrary * self) { - if (!KOTO_IS_LIBRARY(self)) { - return NULL; - } - - return self->storage_uuid; -} - -KotoLibraryType koto_library_get_lib_type(KotoLibrary * self) { - if (!KOTO_IS_LIBRARY(self)) { - return KOTO_LIBRARY_TYPE_UNKNOWN; - } - - return self->type; -} - -gchar * koto_library_get_uuid(KotoLibrary * self) { - if (!KOTO_IS_LIBRARY(self)) { - return NULL; - } - - return self->uuid; -} - -void koto_library_index(KotoLibrary * self) { - if (!KOTO_IS_LIBRARY(self) || !self->should_index) { // Not a library or should not index - return; - } - - index_folder(self, self->path, 0); // Start index operation at the top -} - -gboolean koto_library_is_available(KotoLibrary * self) { - if (!KOTO_IS_LIBRARY(self)) { - return FALSE; - } - - return FALSE; -} - -void koto_library_set_name( - KotoLibrary * self, - gchar * library_name -) { - if (!KOTO_IS_LIBRARY(self)) { - return; - } - - if (!koto_utils_string_is_valid(library_name)) { // Not a string - return; - } - - if (koto_utils_string_is_valid(self->name)) { // Name already set - g_free(self->name); // Free the existing value - } - - self->name = g_strdup(library_name); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NAME]); -} - -void koto_library_set_path( - KotoLibrary * self, - gchar * path -) { - if (!KOTO_IS_LIBRARY(self)) { - return; - } - - if (!koto_utils_string_is_valid(path)) { // Not a valid string - return; - } - - if (koto_utils_string_is_valid(self->path)) { - g_free(self->path); - } - - self->relative_path = g_path_is_absolute(path) ? koto_utils_string_replace_all(path, self->mount_path, "") : path; // Ensure path is relative to our mount, even if the mount is really our own system partition - self->path = g_build_path(G_DIR_SEPARATOR_S, self->mount_path, self->relative_path, NULL); // Ensure our path is to whatever the current path of the mount + relative path is -} - -void koto_library_set_storage_uuid( - KotoLibrary * self, - gchar * storage_uuid -) { - if (!KOTO_IS_LIBRARY(self)) { - return; - } - - if (G_IS_MOUNT(self->mount)) { // Already have a mount - g_signal_handler_disconnect(self->mount, self->mount_unmounted_handler); // Stop listening to the unmounted signal for this existing mount - g_object_unref(self->mount); // Dereference the mount - g_free(self->mount_path); - } - - if (!koto_utils_string_is_valid(storage_uuid)) { // Not a valid string, which actually is allowed for built-ins - self->mount = NULL; - self->mount_path = g_strdup_printf("%s%s", g_get_home_dir(), G_DIR_SEPARATOR_S); // Set mount path to user's home directory - self->storage_uuid = NULL; - return; - } - - GMount * mount = g_volume_monitor_get_mount_for_uuid(volume_monitor, storage_uuid); // Attempt to get the mount by this UUID - - if (!G_IS_MOUNT(mount)) { - g_warning("Failed to get mount for UUID: %s", storage_uuid); - self->mount = NULL; - return; - } - - if (g_mount_is_shadowed(mount)) { // Is shadowed and should not use - g_warning("This mount is considered \"shadowed\" and will not be used."); - return; - } - - GFile * mount_file = g_mount_get_default_location(mount); // Get the file for the entry location of the mount - - self->mount = mount; - self->mount_path = g_strdup(g_file_get_path(mount_file)); // Set the mount path to the path defined for the Mount File - self->storage_uuid = g_strdup(storage_uuid); -} - -gchar * koto_library_to_config_string(KotoLibrary * self) { - GStrvBuilder * lib_builder = g_strv_builder_new(); // Create new strv builder - g_strv_builder_add(lib_builder, g_strdup("[[library]]")); // Add our library array header - - g_strv_builder_add(lib_builder, g_strdup_printf("\tdirectory=\"%s\"", self->relative_path)); // Add the directory - - if (koto_utils_string_is_valid(self->name)) { // Have a library name - g_strv_builder_add(lib_builder, g_strdup_printf("\tname=\"%s\"", self->name)); // Add the name - } - - if (koto_utils_string_is_valid(self->storage_uuid)) { // Have a storage UUID (not applicable to built-ins) - g_strv_builder_add(lib_builder, g_strdup_printf("\tstorage_uuid=\"%s\"", self->storage_uuid)); // Add the storage UUID - } - - g_strv_builder_add(lib_builder, g_strdup_printf("\ttype=\"%s\"", koto_library_type_to_string(self->type))); // Add the type - g_strv_builder_add(lib_builder, g_strdup_printf("\tuuid=\"%s\"", self->uuid)); - - GStrv lines = g_strv_builder_end(lib_builder); // Get all the lines as a GStrv which is a gchar ** - gchar * content = g_strjoinv("\n", lines); // Separate all lines with newline - g_strfreev(lines); // Free our lines - - g_strv_builder_unref(lib_builder); // Unref our builder - return g_strdup(content); -} - -KotoLibrary * koto_library_new( - KotoLibraryType type, - const gchar * storage_uuid, - const gchar * path -) { - KotoLibrary * lib = g_object_new( - KOTO_TYPE_LIBRARY, - "type", - koto_library_type_to_string(type), - "uuid", - g_uuid_string_random(), // Create a new Library with a new UUID - "storage-uuid", - storage_uuid, - "construction-path", - path, - NULL - ); - - lib->should_index = TRUE; - return lib; -} - -KotoLibrary * koto_library_new_from_toml_table(toml_table_t * lib_datum) { - toml_datum_t uuid_datum = toml_string_in(lib_datum, "uuid"); // Get the library UUID - - if (!uuid_datum.ok) { // No UUID defined - g_warning("No UUID set for this library. Ignoring"); - return NULL; - } - - gchar * uuid = g_strdup(uuid_datum.u.s); // Duplicate our UUID - - toml_datum_t type_datum = toml_string_in(lib_datum, "type"); - - if (!type_datum.ok) { // No type defined - g_warning("Unknown type for library with UUID of %s", uuid); - return NULL; - } - - gchar * lib_type_as_str = g_strdup(type_datum.u.s); - KotoLibraryType lib_type = koto_library_type_from_string(lib_type_as_str); // Get the library's type - - if (lib_type == KOTO_LIBRARY_TYPE_UNKNOWN) { // Known type - return NULL; - } - - toml_datum_t dir_datum = toml_string_in(lib_datum, "directory"); - - if (!dir_datum.ok) { - g_critical("Failed to get directory path for library with UUID of %s", uuid); - return NULL; - } - - gchar * path = g_strdup(dir_datum.u.s); // Duplicate the path string - - toml_datum_t storage_uuid_datum = toml_string_in(lib_datum, "storage_uuid"); // Get the datum for the storage UUID - gchar * storage_uuid = g_strdup((storage_uuid_datum.ok) ? storage_uuid_datum.u.s : ""); - - toml_datum_t name_datum = toml_string_in(lib_datum, "name"); // Get the datum for the name - gchar * name = g_strdup((name_datum.ok) ? name_datum.u.s : ""); - - KotoLibrary * lib = g_object_new( - KOTO_TYPE_LIBRARY, - "type", - lib_type_as_str, - "uuid", - uuid, - "storage-uuid", - storage_uuid, - "construction-path", - path, - "name", - name, - NULL - ); - - lib->should_index = FALSE; - return lib; -} - -KotoLibraryType koto_library_type_from_string(gchar * t) { - if ( - (g_strcmp0(t, "audiobooks") == 0) || - (g_strcmp0(t, "audiobook") == 0) - ) { - return KOTO_LIBRARY_TYPE_AUDIOBOOK; - } else if (g_strcmp0(t, "music") == 0) { - return KOTO_LIBRARY_TYPE_MUSIC; - } else if ( - (g_strcmp0(t, "podcasts") == 0) || - (g_strcmp0(t, "podcast") == 0) - ) { - return KOTO_LIBRARY_TYPE_PODCAST; - } - - - g_warning("Invalid type provided for koto_library_type_from_string: %s", t); - return KOTO_LIBRARY_TYPE_UNKNOWN; -} - -gchar * koto_library_type_to_string(KotoLibraryType t) { - switch (t) { - case KOTO_LIBRARY_TYPE_AUDIOBOOK: - return g_strdup("audiobook"); - case KOTO_LIBRARY_TYPE_MUSIC: - return g_strdup("music"); - case KOTO_LIBRARY_TYPE_PODCAST: - return g_strdup("podcast"); - default: - return g_strdup("UNKNOWN"); - } -} \ No newline at end of file diff --git a/src/indexer/misc-types.h b/src/indexer/misc-types.h deleted file mode 100644 index e3963e6..0000000 --- a/src/indexer/misc-types.h +++ /dev/null @@ -1,22 +0,0 @@ -/* misc-types.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -typedef enum { - KOTO_PREFERRED_ALBUM_SORT_TYPE_DEFAULT, // Chronological is considered default - KOTO_PREFERRED_ALBUM_ALWAYS_ALPHABETICAL, // Prefer sorting alphabetically -} KotoPreferredAlbumSortType; \ No newline at end of file diff --git a/src/indexer/structs.h b/src/indexer/structs.h deleted file mode 100644 index 786e4c7..0000000 --- a/src/indexer/structs.h +++ /dev/null @@ -1,425 +0,0 @@ -/* structs.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include -#include -#include "misc-types.h" - -typedef enum { - KOTO_LIBRARY_TYPE_AUDIOBOOK = 1, - KOTO_LIBRARY_TYPE_MUSIC = 2, - KOTO_LIBRARY_TYPE_PODCAST = 3, - KOTO_LIBRARY_TYPE_UNKNOWN = 4 -} KotoLibraryType; - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_LIBRARY koto_library_get_type() -#define KOTO_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_LIBRARY, KotoLibrary)) -typedef struct _KotoLibrary KotoLibrary; -typedef struct _KotoLibraryClass KotoLibraryClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_library_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_LIBRARY)) - -#define KOTO_TYPE_ARTIST koto_artist_get_type() -#define KOTO_ARTIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_ARTIST, KotoArtist)) -typedef struct _KotoArtist KotoArtist; -typedef struct _KotoArtistClass KotoArtistClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_artist_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_ARTIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ARTIST)) - -#define KOTO_TYPE_ALBUM koto_album_get_type() -#define KOTO_ALBUM(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_ALBUM, KotoAlbum)) -typedef struct _KotoAlbum KotoAlbum; -typedef struct _KotoAlbumClass KotoAlbumClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_album_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_ALBUM(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ALBUM)) - -#define KOTO_TYPE_TRACK koto_track_get_type() -G_DECLARE_FINAL_TYPE(KotoTrack, koto_track, KOTO, TRACK, GObject); -#define KOTO_IS_TRACK(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_TRACK)) - -/** - * Library Functions - **/ - -KotoLibrary * koto_library_new( - KotoLibraryType type, - const gchar * storage_uuid, - const gchar * path -); - -KotoLibrary * koto_library_new_from_toml_table(toml_table_t * lib_datum); - -gchar * koto_library_get_path(KotoLibrary * self); - -gchar * koto_library_get_relative_path_to_file( - KotoLibrary * self, - gchar * full_path -); - -gchar * koto_library_get_storage_uuid(KotoLibrary * self); - -KotoLibraryType koto_library_get_lib_type(KotoLibrary * self); - -gchar * koto_library_get_uuid(KotoLibrary * self); - -void koto_library_index(KotoLibrary * self); - -gboolean koto_library_is_available(KotoLibrary * self); - -gchar * koto_library_get_storage_uuid(KotoLibrary * self); - -void koto_library_set_name( - KotoLibrary * self, - gchar * library_name -); - -void koto_library_set_path( - KotoLibrary * self, - gchar * path -); - -void koto_library_set_storage_uuid( - KotoLibrary * self, - gchar * uuid -); - -gchar * koto_library_to_config_string(KotoLibrary * self); - -KotoLibraryType koto_library_type_from_string(gchar * t); - -gchar * koto_library_type_to_string(KotoLibraryType t); - -void index_folder( - KotoLibrary * self, - gchar * path, - guint depth -); - -void index_file( - KotoLibrary * lib, - const gchar * path -); - -/** - * Artist Functions - **/ - -KotoArtist * koto_artist_new(gchar * artist_name); - -KotoArtist * koto_artist_new_with_uuid(const gchar * uuid); - -void koto_artist_add_album( - KotoArtist * self, - KotoAlbum * album -); - -void koto_artist_add_track( - KotoArtist * self, - KotoTrack * track -); - -void koto_artist_apply_model( - KotoArtist * self, - KotoPreferredAlbumSortType model -); - -void koto_artist_commit(KotoArtist * self); - -GQueue * koto_artist_get_albums(KotoArtist * self); - -GListStore * koto_artist_get_albums_store(KotoArtist * self); - -KotoAlbum * koto_artist_get_album_by_name( - KotoArtist * self, - gchar * album_name -); - -gchar * koto_artist_get_name(KotoArtist * self); - -gchar * koto_artist_get_path(KotoArtist * self); - -GList * koto_artist_get_tracks(KotoArtist * self); - -gchar * koto_artist_get_uuid(KotoArtist * self); - -KotoLibraryType koto_artist_get_lib_type(KotoArtist * self); - -gint koto_artist_model_sort_albums( - gconstpointer first_item, - gconstpointer second_item, - gpointer user_data -); - -void koto_artist_remove_album( - KotoArtist * self, - KotoAlbum * album -); - -void koto_artist_remove_track( - KotoArtist * self, - KotoTrack * track -); - -void koto_artist_set_artist_name( - KotoArtist * self, - gchar * artist_name -); - -void koto_artist_set_as_finalized(KotoArtist * self); - -void koto_artist_set_path( - KotoArtist * self, - KotoLibrary * lib, - const gchar * fixed_path, - gboolean should_commit -); - -/** - * Album Functions - **/ - -KotoAlbum * koto_album_new(gchar * artist_uuid); - -KotoAlbum * koto_album_new_with_uuid( - KotoArtist * artist, - const gchar * uuid -); - -void koto_album_add_track( - KotoAlbum * self, - KotoTrack * track -); - -void koto_album_commit(KotoAlbum * self); - -void koto_album_find_album_art(KotoAlbum * self); - -gchar * koto_album_get_art(KotoAlbum * self); - -gchar * koto_album_get_artist_uuid(KotoAlbum * self); - -gchar * koto_album_get_description(KotoAlbum * self); - -GList * koto_album_get_genres(KotoAlbum * self); - -KotoLibraryType koto_album_get_lib_type(KotoAlbum * self); - -gchar * koto_album_get_name(KotoAlbum * self); - -gchar * koto_album_get_narrator(KotoAlbum * self); - -gchar * koto_album_get_path(KotoAlbum * self); - -GListStore * koto_album_get_store(KotoAlbum * self); - -gchar * koto_album_get_uuid(KotoAlbum * self); - -guint64 koto_album_get_year(KotoAlbum * self); - -void koto_album_mark_as_finalized(KotoAlbum * self); - -void koto_album_remove_track( - KotoAlbum * self, - KotoTrack * track -); - -void koto_album_set_album_art( - KotoAlbum * self, - const gchar * album_art -); - -void koto_album_set_album_name( - KotoAlbum * self, - const gchar * album_name -); - -void koto_album_set_artist_uuid( - KotoAlbum * self, - const gchar * artist_uuid -); - -void koto_album_set_description( - KotoAlbum * self, - const char * description -); - -void koto_album_set_as_current_playlist(KotoAlbum * self); - -void koto_album_set_narrator( - KotoAlbum * self, - const char * narrator -); - -void koto_album_set_path( - KotoAlbum * self, - KotoLibrary * lib, - const gchar * fixed_path -); - -void koto_album_set_preparsed_genres( - KotoAlbum * self, - gchar * genrelist -); - -void koto_album_set_uuid( - KotoAlbum * self, - const gchar * uuid -); - -void koto_album_set_year( - KotoAlbum * self, - guint64 year -); - -/** - * File / Track Functions - **/ - -KotoTrack * koto_track_new( - const gchar * artist_uuid, - const gchar * album_uuid, - const gchar * parsed_name, - guint cd -); - -KotoTrack * koto_track_new_with_uuid(const gchar * uuid); - -void koto_track_commit(KotoTrack * self); - -gchar * koto_track_get_description(KotoTrack * self); - -guint koto_track_get_disc_number(KotoTrack * self); - -guint64 koto_track_get_duration(KotoTrack * self); - -GList * koto_track_get_genres(KotoTrack * self); - -GVariant * koto_track_get_metadata_vardict(KotoTrack * self); - -gchar * koto_track_get_path(KotoTrack * self); - -gchar * koto_track_get_name(KotoTrack * self); - -gchar * koto_track_get_narrator(KotoTrack * self); - -guint64 koto_track_get_playback_position(KotoTrack * self); - -guint64 koto_track_get_position(KotoTrack * self); - -gchar * koto_track_get_uniqueish_key(KotoTrack * self); - -gchar * koto_track_get_uuid(KotoTrack * self); - -guint64 koto_track_get_year(KotoTrack * self); - -void koto_track_remove_from_playlist( - KotoTrack * self, - gchar * playlist_uuid -); - -void koto_track_set_album_uuid( - KotoTrack * self, - const gchar * album_uuid -); - -void koto_track_save_to_playlist( - KotoTrack * self, - gchar * playlist_uuid -); - -void koto_track_set_cd( - KotoTrack * self, - guint cd -); - -void koto_track_set_description( - KotoTrack * self, - const gchar * description -); - -void koto_track_set_duration( - KotoTrack * self, - guint64 duration -); - -void koto_track_set_file_name( - KotoTrack * self, - gchar * new_file_name -); - -void koto_track_set_genres( - KotoTrack * self, - char * genrelist -); - -void koto_track_set_narrator( - KotoTrack * self, - const gchar * narrator -); - -void koto_track_set_parsed_name( - KotoTrack * self, - gchar * new_parsed_name -); - -void koto_track_set_path( - KotoTrack * self, - KotoLibrary * lib, - gchar * fixed_path -); - -void koto_track_set_playback_position( - KotoTrack * self, - guint64 position -); - -void koto_track_set_position( - KotoTrack * self, - guint64 pos -); - -void koto_track_set_preparsed_genres( - KotoTrack * self, - gchar * genrelist -); - -void koto_track_set_year( - KotoTrack * self, - guint64 year -); - -void koto_track_update_metadata(KotoTrack * self); - -G_END_DECLS diff --git a/src/indexer/track-helpers.c b/src/indexer/track-helpers.c deleted file mode 100644 index 076212e..0000000 --- a/src/indexer/track-helpers.c +++ /dev/null @@ -1,243 +0,0 @@ -/* track-helpers.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../components/track-item.h" -#include "../db/cartographer.h" -#include "../koto-utils.h" -#include "structs.h" - -extern KotoCartographer * koto_maps; - -GHashTable * genre_replacements; - -void koto_track_helpers_init() { - genre_replacements = g_hash_table_new(g_str_hash, g_str_equal); - - gchar * corrected_genre_hiphop = g_strdup("hip-hop"); - gchar * correct_genre_indie = g_strdup("indie"); - gchar * correct_genre_scifi = g_strdup("sci-fi"); - - g_hash_table_insert(genre_replacements, g_strdup("alternative/indie"), correct_genre_indie); // Change Alternative/Indie (lowercased) to indie - g_hash_table_insert(genre_replacements, g_strdup("rap-&-hip-hop"), corrected_genre_hiphop); // Change rap-&-hip-hop to just hip-hop - g_hash_table_insert(genre_replacements, g_strdup("science-fiction"), correct_genre_scifi); // Change science-fiction to sci-fi -} - -gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre) { - gchar * lookedup_genre = g_hash_table_lookup(genre_replacements, original_genre); // Look up the genre - return koto_utils_string_is_valid(lookedup_genre) ? lookedup_genre : original_genre; -} - -guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name) { - gchar ** part_split = g_strsplit(file_name, "Part", -1); - gchar * part_str = NULL; - - for (guint i = 0; i < g_strv_length(part_split); i++) { // Iterate on the parts - gchar * stripped_part = g_strdup(g_strstrip(part_split[i])); // Trim the whitespace around this part - gchar ** split = g_regex_split_simple("^([\\d]+)", stripped_part, G_REGEX_JAVASCRIPT_COMPAT, 0); - - g_free(stripped_part); // Free the stripped part - - if (g_strv_length(split) > 1) { // Has positional info at the beginning of the string - part_str = g_strdup(split[1]); - g_strfreev(split); - break; - } else { - g_strfreev(split); - } - } - - guint64 cd = 0; - - if (koto_utils_string_is_valid(part_str)) { // Have a valid string for the part - cd = g_ascii_strtoull(part_str, NULL, 10); - g_free(part_str); - } - - if (cd == 0) { - cd = 1; // Should be first CD, not 0 - } - - return cd; -} - -gchar * koto_track_helpers_get_name_for_file( - const gchar * path, - gchar * optional_artist_name -) { - gchar * file_name = NULL; - TagLib_File * t_file = taglib_file_new(path); // Get a taglib file for this file - - if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid - TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag - file_name = g_strdup(taglib_tag_title(tag)); // Get the tag title and duplicate it - } - - taglib_tag_free_strings(); // Free strings - taglib_file_free(t_file); // Free the file - - if (koto_utils_string_is_valid(file_name)) { // File name not set yet - return file_name; - } - - gchar * name_without_hyphen_surround = koto_utils_string_replace_all(koto_utils_get_filename_without_extension((gchar*) path), " - ", ""); // Remove - surrounded by spaces - - gchar * name_without_hyphen = koto_utils_string_replace_all(name_without_hyphen_surround, "-", ""); // Remove just - - g_free(name_without_hyphen_surround); - - file_name = koto_utils_string_replace_all(name_without_hyphen, "_", " "); // Replace underscore with whitespace - - if (koto_utils_string_is_valid(optional_artist_name)) { // Was provided an optional artist name - gchar * replaced_artist = koto_utils_string_replace_all(file_name, optional_artist_name, ""); // Remove the artist - g_free(file_name); - file_name = g_strdup(replaced_artist); - g_free(replaced_artist); - } - - gchar ** split = g_regex_split_simple("^([\\d]+)", file_name, G_REGEX_JAVASCRIPT_COMPAT, 0); // Split based on any possible position - if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file name - g_free(file_name); // Free the prior name - file_name = g_strdup(split[2]); // Set to our second item which is the rest of the song name without the prefixed numbers - } - - g_strfreev(split); - return g_strdup(g_strstrip(file_name)); -} - -guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name) { - GRegex * num_pat = g_regex_new("^([\\d]+)", G_REGEX_JAVASCRIPT_COMPAT, 0, NULL); - gchar ** split = g_regex_split(num_pat, file_name, 0); - - if (g_strv_length(split) > 1) { // Has positional info at the beginning of the file - gchar * num = g_strdup(split[1]); - g_strfreev(split); - - if ((strcmp(num, "0") != 0) && (strcmp(num, "00") != 0)) { // Is not zero - guint64 potential_pos = g_ascii_strtoull(num, NULL, 10); // Attempt to convert - - if (potential_pos != 0) { // Got a legitimate position - g_free(num_pat); // Free our regex - return potential_pos; // Return this position - } - } - } - - gchar * fn_no_ext = koto_utils_get_filename_without_extension((gchar*) file_name); // Get the filename without the extension - split = g_strsplit(fn_no_ext, ".", -1); // Split every time we see . - - guint len_of_extension_split = g_strv_length(split); - - gchar * fn_last_split_on_period = g_strdup(split[len_of_extension_split - 1]); - g_free(fn_no_ext); - - g_strfreev(split); // Free our split - - gchar ** whitespace_split = g_strsplit(fn_last_split_on_period, " ", -1); // Split on whitespace - g_free(fn_last_split_on_period); - - gchar * last_item = koto_utils_string_replace_all(whitespace_split[g_strv_length(split) - 1], "#", ""); // Get last item, removing any # from it - g_strfreev(whitespace_split); - - gchar ** hyphen_split = g_strsplit(last_item, "-", -1); // Split on hyphen - g_free(last_item); - - gchar * position_str = NULL; - for (guint i = 0; i < g_strv_length(hyphen_split); i++) { // Iterate over each item - gchar * pos_str = hyphen_split[i]; - - if (!g_regex_match(num_pat, pos_str, 0, NULL)) { // Is not a number - continue; - } - - position_str = g_strdup(pos_str); - break; - } - - g_strfreev(hyphen_split); - - guint64 position = 0; - - if (position_str != NULL) { // If we have a string defined - if (g_regex_match(num_pat, position_str, 0, NULL)) { // Matches being a number - position = g_ascii_strtoull(position_str, NULL, 10); // Attempt to convert - } - - g_free(position_str); - } - - g_free(num_pat); - return position; -} - -gint koto_track_helpers_sort_tracks( - gconstpointer track1, - gconstpointer track2, - gpointer user_data -) { - (void) user_data; - KotoTrack * track1_real = (KotoTrack*) track1; - KotoTrack * track2_real = (KotoTrack*) track2; - - if (!KOTO_IS_TRACK(track1_real) && !KOTO_IS_TRACK(track2_real)) { // Neither tracks actually exist - return 0; - } else if (KOTO_IS_TRACK(track1_real) && !KOTO_IS_TRACK(track2_real)) { // Only track2 does not exist - return -1; - } else if (!KOTO_IS_TRACK(track1_real) && KOTO_IS_TRACK(track2_real)) { // Only track1 does not exist - return 1; - } - - guint track1_disc = koto_track_get_disc_number(track1_real); - guint track2_disc = koto_track_get_disc_number(track2_real); - - if (track1_disc < track2_disc) { // Track 2 is in a later CD / Disc - return -1; - } else if (track1_disc > track2_disc) { // Track1 is later - return 1; - } - - guint track1_pos = koto_track_get_position(track1_real); - guint track2_pos = koto_track_get_position(track2_real); - - if (track1_pos == track2_pos) { // Identical positions (like reported as 0) - return g_utf8_collate(koto_track_get_name(track1_real), koto_track_get_name(track2_real)); - } else if (track1_pos < track2_pos) { - return -1; - } else { - return 1; - } -} - -gint koto_track_helpers_sort_track_items( - gconstpointer track1_item, - gconstpointer track2_item, - gpointer user_data -) { - KotoTrack * track1 = koto_track_item_get_track((KotoTrackItem*) track1_item); - KotoTrack * track2 = koto_track_item_get_track((KotoTrackItem*) track2_item); - return koto_track_helpers_sort_tracks(track1, track2, user_data); -} - -gint koto_track_helpers_sort_tracks_by_uuid( - gconstpointer track1_uuid, - gconstpointer track2_uuid, - gpointer user_data -) { - KotoTrack * track1 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track1_uuid); - KotoTrack * track2 = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) track2_uuid); - return koto_track_helpers_sort_tracks(track1, track2, user_data); -} \ No newline at end of file diff --git a/src/indexer/track-helpers.h b/src/indexer/track-helpers.h deleted file mode 100644 index e918766..0000000 --- a/src/indexer/track-helpers.h +++ /dev/null @@ -1,49 +0,0 @@ -/* track-helpers.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -void koto_track_helpers_init(); - -guint64 koto_track_helpers_get_cd_based_on_file_name(const gchar * file_name); - -gchar * koto_track_helpers_get_corrected_genre(gchar * original_genre); - -gchar * koto_track_helpers_get_name_for_file( - const gchar * path, - gchar * optional_artist_name -); - -guint64 koto_track_helpers_get_position_based_on_file_name(const gchar * file_name); - -gint koto_track_helpers_sort_track_items( - gconstpointer track1_item, - gconstpointer track2_item, - gpointer user_data -); - -gint koto_track_helpers_sort_tracks( - gconstpointer track1, - gconstpointer track2, - gpointer user_data -); - -gint koto_track_helpers_sort_tracks_by_uuid( - gconstpointer track1_uuid, - gconstpointer track2_uuid, - gpointer user_data -); \ No newline at end of file diff --git a/src/indexer/track.c b/src/indexer/track.c deleted file mode 100644 index f5eb42b..0000000 --- a/src/indexer/track.c +++ /dev/null @@ -1,879 +0,0 @@ -/* track.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include "../db/db.h" -#include "../db/cartographer.h" -#include "structs.h" -#include "track-helpers.h" -#include "koto-utils.h" - -extern KotoCartographer * koto_maps; -extern sqlite3 * koto_db; - -struct _KotoTrack { - GObject parent_instance; - gchar * artist_uuid; - gchar * album_uuid; - gchar * uuid; - - GHashTable * paths; - - gchar * parsed_name; - guint cd; - guint64 position; - guint64 duration; - gchar * description; - gchar * narrator; - guint64 playback_position; - guint64 year; - - GList * genres; - - gboolean do_initial_index; -}; - -G_DEFINE_TYPE(KotoTrack, koto_track, G_TYPE_OBJECT); - -enum { - PROP_0, - PROP_ARTIST_UUID, - PROP_ALBUM_UUID, - PROP_UUID, - PROP_DO_INITIAL_INDEX, - PROP_PARSED_NAME, - PROP_CD, - PROP_POSITION, - PROP_DURATION, - PROP_DESCRIPTION, - PROP_NARRATOR, - PROP_YEAR, - PROP_PLAYBACK_POSITION, - PROP_PREPARSED_GENRES, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL -}; - -static void koto_track_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_track_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_track_class_init(KotoTrackClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_track_set_property; - gobject_class->get_property = koto_track_get_property; - - props[PROP_ARTIST_UUID] = g_param_spec_string( - "artist-uuid", - "UUID to Artist associated with the File", - "UUID to Artist associated with the File", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ALBUM_UUID] = g_param_spec_string( - "album-uuid", - "UUID to Album associated with the File", - "UUID to Album associated with the File", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_UUID] = g_param_spec_string( - "uuid", - "UUID to File in database", - "UUID to File in database", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_DO_INITIAL_INDEX] = g_param_spec_boolean( - "do-initial-index", - "Do an initial indexing operating instead of pulling from the database", - "Do an initial indexing operating instead of pulling from the database", - FALSE, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_PARSED_NAME] = g_param_spec_string( - "parsed-name", - "Parsed Name of File", - "Parsed Name of File", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_CD] = g_param_spec_uint( - "cd", - "CD the Track belongs to", - "CD the Track belongs to", - 0, - G_MAXUINT16, - 1, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_POSITION] = g_param_spec_uint64( - "position", - "Position in Audiobook, Album, etc.", - "Position in Audiobook, Album, etc.", - 0, - G_MAXUINT64, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_DURATION] = g_param_spec_uint64( - "duration", - "Duration of Track", - "Duration of Track", - 0, - G_MAXUINT64, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_DESCRIPTION] = g_param_spec_string( - "description", - "Description of Track, typically show notes for a podcast episode", - "Description of Track, typically show notes for a podcast episode", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_NARRATOR] = g_param_spec_string( - "narrator", - "Narrator, typically of an Audiobook", - "Narrator, typically of an Audiobook", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_YEAR] = g_param_spec_uint64( - "year", - "Year", - "Year", - 0, - G_MAXUINT64, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_PLAYBACK_POSITION] = g_param_spec_uint64( - "playback-position", - "Current playback position", - "Current playback position", - 0, - G_MAXUINT64, - 0, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_PREPARSED_GENRES] = g_param_spec_string( - "preparsed-genres", - "Preparsed Genres", - "Preparsed Genres", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_WRITABLE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_track_init(KotoTrack * self) { - self->description = NULL; // Initialize our description - self->duration = 0; // Initialize our duration - self->genres = NULL; // Initialize our genres list - self->narrator = NULL, // Initialize our narrator - self->paths = g_hash_table_new(g_str_hash, g_str_equal); // Create our hash table of paths - self->position = 0; // Initialize our duration - self->year = 0; // Initialize our year -} - -static void koto_track_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoTrack * self = KOTO_TRACK(obj); - - switch (prop_id) { - case PROP_ARTIST_UUID: - g_value_set_string(val, self->artist_uuid); - break; - case PROP_ALBUM_UUID: - g_value_set_string(val, self->album_uuid); - break; - case PROP_UUID: - g_value_set_string(val, self->uuid); - break; - case PROP_PARSED_NAME: - g_value_set_string(val, self->parsed_name); - break; - case PROP_CD: - g_value_set_uint(val, self->cd); - break; - case PROP_DESCRIPTION: - g_value_set_string(val, self->description); - break; - case PROP_NARRATOR: - g_value_set_string(val, self->narrator); - break; - case PROP_YEAR: - g_value_set_uint64(val, self->year); - break; - case PROP_POSITION: - g_value_set_uint64(val, self->position); - break; - case PROP_PLAYBACK_POSITION: - g_value_set_uint64(val, koto_track_get_playback_position(self)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_track_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoTrack * self = KOTO_TRACK(obj); - - switch (prop_id) { - case PROP_ARTIST_UUID: - self->artist_uuid = g_strdup(g_value_get_string(val)); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ARTIST_UUID]); - break; - case PROP_ALBUM_UUID: - koto_track_set_album_uuid(self, g_value_get_string(val)); - break; - case PROP_UUID: - self->uuid = g_strdup(g_value_get_string(val)); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_UUID]); - break; - case PROP_DO_INITIAL_INDEX: - self->do_initial_index = g_value_get_boolean(val); - break; - case PROP_PARSED_NAME: - koto_track_set_parsed_name(self, g_strdup(g_value_get_string(val))); - break; - case PROP_CD: - koto_track_set_cd(self, g_value_get_uint(val)); - break; - case PROP_POSITION: - koto_track_set_position(self, g_value_get_uint64(val)); - break; - case PROP_PLAYBACK_POSITION: - koto_track_set_playback_position(self, g_value_get_uint64(val)); - break; - case PROP_DURATION: - koto_track_set_duration(self, g_value_get_uint64(val)); - break; - case PROP_DESCRIPTION: - koto_track_set_description(self, g_value_get_string(val)); - break; - case PROP_NARRATOR: - koto_track_set_narrator(self, g_strdup(g_value_get_string(val))); - break; - case PROP_YEAR: - koto_track_set_year(self, g_value_get_uint64(val)); - break; - case PROP_PREPARSED_GENRES: - koto_track_set_preparsed_genres(self, g_strdup(g_value_get_string(val))); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_track_commit(KotoTrack * self) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid(self->artist_uuid)) { // No valid required artist UUID - return; - } - - gchar * commit_msg = "INSERT INTO tracks(id, artist_id, album_id, name, disc, position, duration, genres)" \ - "VALUES('%s', '%s', '%s', quote(\"%s\"), %d, %d, %d, '%s')" \ - "ON CONFLICT(id) DO UPDATE SET album_id=excluded.album_id, artist_id=excluded.artist_id, name=excluded.name, disc=excluded.disc, position=excluded.position, duration=excluded.duration, genres=excluded.genres;"; - - // Combine our list items into a semi-colon separated string - gchar * genres = koto_utils_join_string_list(self->genres, ";"); // Join our GList of strings into a single - - gchar * commit_op = g_strdup_printf( - commit_msg, - self->uuid, - self->artist_uuid, - koto_utils_string_get_valid(self->album_uuid), - g_strescape(self->parsed_name, NULL), - (int) self->cd, - (int) self->position, - (int) self->duration, - genres - ); - - if (new_transaction(commit_op, "Failed to write our file to the database", FALSE) != SQLITE_OK) { - return; - } - - g_free(genres); // Free the genres string - - GHashTableIter paths_iter; - g_hash_table_iter_init(&paths_iter, self->paths); // Create an iterator for our paths - gpointer lib_uuid_ptr, track_rel_path_ptr; - while (g_hash_table_iter_next(&paths_iter, &lib_uuid_ptr, &track_rel_path_ptr)) { - gchar * lib_uuid = lib_uuid_ptr; - gchar * track_rel_path = track_rel_path_ptr; - - gchar * commit_op = g_strdup_printf( - "INSERT INTO libraries_tracks(id, track_id, path)" - "VALUES ('%s', '%s', quote(\"%s\"))" - "ON CONFLICT(id, track_id) DO UPDATE SET path=excluded.path;", - lib_uuid, - self->uuid, - track_rel_path - ); - - new_transaction(commit_op, "Failed to add this path for the track", FALSE); - } -} - -gchar * koto_track_get_description(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? g_strdup(self->description) : NULL; -} - -guint koto_track_get_disc_number(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? self->cd : 1; -} - -guint64 koto_track_get_duration(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? self->duration : 0; -} - -GList * koto_track_get_genres(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? self->genres : NULL; -} - -GVariant * koto_track_get_metadata_vardict(KotoTrack * self) { - if (!KOTO_IS_TRACK(self)) { - return NULL; - } - - GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid); - gchar * artist_name = koto_artist_get_name(artist); - - if (koto_utils_string_is_valid(self->album_uuid)) { // Have an album associated - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); - - if (KOTO_IS_ALBUM(album)) { - gchar * album_art_path = koto_album_get_art(album); - gchar * album_name = koto_album_get_name(album); - - if (koto_utils_string_is_valid(album_art_path)) { // Valid album art path - album_art_path = g_strconcat("file://", album_art_path, NULL); // Prepend with file:// - g_variant_builder_add(builder, "{sv}", "mpris:artUrl", g_variant_new_string(album_art_path)); - } - - g_variant_builder_add(builder, "{sv}", "xesam:album", g_variant_new_string(album_name)); - } - } else { - } // TODO: Implement artist artwork fetching here - - g_variant_builder_add(builder, "{sv}", "mpris:trackid", g_variant_new_string(self->uuid)); - - if (koto_utils_string_is_valid(artist_name)) { // Valid artist name - GVariant * artist_name_variant; - GVariantBuilder * artist_list_builder = g_variant_builder_new(G_VARIANT_TYPE("as")); - g_variant_builder_add(artist_list_builder, "s", artist_name); - artist_name_variant = g_variant_new("as", artist_list_builder); - g_variant_builder_unref(artist_list_builder); - - g_variant_builder_add(builder, "{sv}", "xesam:artist", artist_name_variant); - g_variant_builder_add(builder, "{sv}", "playbackengine:artist", g_variant_new_string(artist_name)); // Add a sort of "meta" string val for our playback engine so we don't need to mess about with the array - } - - g_variant_builder_add(builder, "{sv}", "xesam:discNumber", g_variant_new_uint64(self->cd)); - g_variant_builder_add(builder, "{sv}", "xesam:title", g_variant_new_string(self->parsed_name)); - g_variant_builder_add(builder, "{sv}", "xesam:url", g_variant_new_string(koto_track_get_path(self))); - g_variant_builder_add(builder, "{sv}", "xesam:trackNumber", g_variant_new_uint64(self->position)); - - GVariant * metadata_ret = g_variant_builder_end(builder); - - return metadata_ret; -} - -gchar * koto_track_get_name(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? g_strdup(self->parsed_name) : NULL; -} - -gchar * koto_track_get_narrator(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? g_strdup(self->narrator) : NULL; -} - -gchar * koto_track_get_path(KotoTrack * self) { - if (!KOTO_IS_TRACK(self) || (KOTO_IS_TRACK(self) && (g_list_length(g_hash_table_get_keys(self->paths)) == 0))) { // If this is not a track or is but we have no paths associated with it - return NULL; - } - - GHashTableIter iter; - - g_hash_table_iter_init(&iter, self->paths); // Create an iterator for our paths - gpointer uuidptr; - gpointer relpathptr; - - gchar * path = NULL; - - while (g_hash_table_iter_next(&iter, &uuidptr, &relpathptr)) { // Iterate over all the paths for this file - KotoLibrary * library = koto_cartographer_get_library_by_uuid(koto_maps, (gchar*) uuidptr); - - if (KOTO_IS_LIBRARY(library)) { - path = g_strdup(g_build_path(G_DIR_SEPARATOR_S, koto_library_get_path(library), koto_library_get_relative_path_to_file(library, (gchar*) relpathptr), NULL)); // Build our full library path using library's path and our file relative path - break; - } - } - - return path; -} - -guint64 koto_track_get_playback_position(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? self->playback_position : 0; -} - -guint64 koto_track_get_position(KotoTrack * self) { - return KOTO_IS_TRACK(self) ? self->position : 0; -} - -gchar * koto_track_get_uniqueish_key(KotoTrack * self) { - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, self->artist_uuid); // Get the artist associated with this track - - if (!KOTO_IS_ARTIST(artist)) { // Don't have an artist - return g_strdup(self->parsed_name); // Just return the name of the file, which is very likely not unique - } - - gchar * artist_name = koto_artist_get_name(artist); // Get the artist name - - if (koto_utils_string_is_valid(self->album_uuid)) { // If we have an album associated with this track (not necessarily guaranteed) - KotoAlbum * possible_album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); - - if (KOTO_IS_ALBUM(possible_album)) { // Album exists - gchar * album_name = koto_album_get_name(possible_album); // Get the name of the album - - if (koto_utils_string_is_valid(album_name)) { - return g_strdup_printf("%s-%s-%s", artist_name, album_name, self->parsed_name); // Create a key of (ARTIST/WRITER)-(ALBUM/AUDIOBOOK)-(CHAPTER/TRACK) - } - } - } - - return g_strdup_printf("%s-%s", artist_name, self->parsed_name); // Create a key of just (ARTIST/WRITER)-(CHAPTER/TRACK) -} - -gchar * koto_track_get_uuid(KotoTrack * self) { - if (!KOTO_IS_TRACK(self)) { - return NULL; - } - - return self->uuid; // Do not return a duplicate since otherwise comparison refs fail due to pointer positions being different -} - -guint64 koto_track_get_year(KotoTrack * self) { - if (!KOTO_IS_TRACK(self)) { - return 0; - } - - return self->year; -} - -void koto_track_remove_from_playlist( - KotoTrack * self, - gchar * playlist_uuid -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - gchar * commit_op = g_strdup_printf( - "DELETE FROM playlist_tracks WHERE track_id='%s' AND playlist_id='%s'", - self->uuid, - playlist_uuid - ); - - new_transaction(commit_op, "Failed to remove track from playlist", FALSE); -} - -void koto_track_set_album_uuid( - KotoTrack * self, - const gchar * album_uuid -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (album_uuid == NULL) { - return; - } - - gchar * uuid = g_strdup(album_uuid); - - if (!koto_utils_string_is_valid(uuid)) { // If this is not a valid string - return; - } - - self->album_uuid = uuid; - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_ALBUM_UUID]); -} - -void koto_track_save_to_playlist( - KotoTrack * self, - gchar * playlist_uuid -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - gchar * commit_op = g_strdup_printf( - "INSERT INTO playlist_tracks(playlist_id, track_id)" - "VALUES('%s', '%s')", - playlist_uuid, - self->uuid - ); - - new_transaction(commit_op, "Failed to save track to playlist", FALSE); -} - -void koto_track_set_cd( - KotoTrack * self, - guint cd -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (cd == 0) { // No change really - return; - } - - self->cd = cd; - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_CD]); -} - -void koto_track_set_description( - KotoTrack * self, - const gchar * description -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid(description)) { - return; - } - - if (g_strcmp0(self->description, description) == 0) { // Same description - return; - } - - if (koto_utils_string_is_valid(self->description)) { - g_free(self->description); // Free the existing narrator - } - - self->description = g_strdup(description); // Duplicate our description - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DESCRIPTION]); -} - -void koto_track_set_duration( - KotoTrack * self, - guint64 duration -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (duration == 0) { - return; - } - - self->duration = duration; - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_DURATION]); -} - -void koto_track_set_genres( - KotoTrack * self, - char * genrelist -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid((gchar*) genrelist)) { // If it is an empty string - return; - } - - gchar ** genres = g_strsplit(genrelist, ";", -1); // Split on semicolons for each genre, e.g. Electronic;Rock - guint len = g_strv_length(genres); - - if (len == 0) { // No genres - g_strfreev(genres); // Free the list - return; - } - - for (guint i = 0; i < len; i++) { // Iterate over each item - gchar * genre = genres[i]; // Get the genre - gchar * lowercased_genre = g_utf8_strdown(g_strstrip(genre), -1); // Lowercase the genre - - gchar * lowercased_hyphenated_genre = koto_utils_string_replace_all(lowercased_genre, " ", "-"); - g_free(lowercased_genre); // Free the lowercase genre string since we no longer need it - - gchar * corrected_genre = koto_track_helpers_get_corrected_genre(lowercased_hyphenated_genre); // Get any corrected genre - - if (g_list_index(self->genres, corrected_genre) == -1) { // Don't have this genre added - self->genres = g_list_append(self->genres, g_strdup(corrected_genre)); // Add the genre to our list - } - - g_free(lowercased_hyphenated_genre); // Free our remaining string - } - - g_strfreev(genres); // Free the list of genres locally -} - -void koto_track_set_narrator( - KotoTrack * self, - const gchar * narrator -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid(narrator)) { - return; - } - - if (g_strcmp0(self->narrator, narrator) == 0) { // Same narrator - return; - } - - if (koto_utils_string_is_valid(self->narrator)) { - g_free(self->narrator); // Free the existing narrator - } - - self->narrator = g_strdup(narrator); // Duplicate our narrator - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_NARRATOR]); -} - -void koto_track_set_parsed_name( - KotoTrack * self, - gchar * new_parsed_name -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid(new_parsed_name)) { - return; - } - - gboolean have_existing_name = koto_utils_string_is_valid(self->parsed_name); - - if (have_existing_name && (strcmp(self->parsed_name, new_parsed_name) == 0)) { // Have existing name that matches one provided - return; // Don't do anything - } - - if (have_existing_name) { - g_free(self->parsed_name); - } - - self->parsed_name = g_strdup(new_parsed_name); - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_PARSED_NAME]); -} - -void koto_track_set_path( - KotoTrack * self, - KotoLibrary * lib, - gchar * fixed_path -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid(fixed_path)) { // Not a valid path - return; - } - - gchar * path = g_strdup(fixed_path); // Duplicate our fixed_path - gchar * relative_path = koto_library_get_relative_path_to_file(lib, path); // Get the relative path to the file for the given library - - gchar * library_uuid = koto_library_get_uuid(lib); // Get the library for this path - g_hash_table_replace(self->paths, library_uuid, relative_path); // Replace any existing value or add this one - - if (self->do_initial_index) { - koto_track_update_metadata(self); // Attempt to get ID3 info - } -} - -void koto_track_set_playback_position( - KotoTrack * self, - guint64 position -) { - if (!KOTO_IS_TRACK(self)) { // Not a track - return; - } - - self->playback_position = position; -} - -void koto_track_set_position( - KotoTrack * self, - guint64 pos -) { - if (!KOTO_IS_TRACK(self)) { // Not a track - return; - } - - if (pos == 0) { // No position change really - return; - } - - self->position = pos; - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_POSITION]); -} - -void koto_track_set_year( - KotoTrack * self, - guint64 year -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - self->year = year; - g_object_notify_by_pspec(G_OBJECT(self), props[PROP_YEAR]); -} - -void koto_track_update_metadata(KotoTrack * self) { - if (!KOTO_IS_TRACK(self)) { // Not a track - return; - } - - gchar * optimal_track_path = koto_track_get_path(self); // Check all the libraries associated with this track, based on priority, return a built path using lib path + relative file path - - if (!koto_utils_string_is_valid(optimal_track_path)) { // Not a valid string - return; - } - - TagLib_File * t_file = taglib_file_new(optimal_track_path); // Get a taglib file for this file - - if ((t_file != NULL) && taglib_file_is_valid(t_file)) { // If we got the taglib file and it is valid - TagLib_Tag * tag = taglib_file_tag(t_file); // Get our tag - koto_track_set_genres(self, taglib_tag_genre(tag)); // Set our genres to any genres listed for the track - koto_track_set_position(self, (uint) taglib_tag_track(tag)); // Get the track, convert to uint and cast as a pointer - koto_track_set_year(self, (guint64) taglib_tag_year(tag)); // Get the track year and convert it to guint64 - const TagLib_AudioProperties * tag_props = taglib_file_audioproperties(t_file); // Get the audio properties of the file - koto_track_set_duration(self, taglib_audioproperties_length(tag_props)); // Get the length of the track and set it as our duration - } - - if (self->position == 0) { // Failed to get tag info or got the tag info but position is zero - guint64 position = koto_track_helpers_get_position_based_on_file_name(g_path_get_basename(optimal_track_path)); // Get the likely position - koto_track_set_position(self, position); // Set our position - } - - taglib_tag_free_strings(); // Free strings - taglib_file_free(t_file); // Free the file - g_free(optimal_track_path); -} - -void koto_track_set_preparsed_genres( - KotoTrack * self, - gchar * genrelist -) { - if (!KOTO_IS_TRACK(self)) { - return; - } - - if (!koto_utils_string_is_valid(genrelist)) { // If it is an empty string - return; - } - - GList * preparsed_genres_list = koto_utils_string_to_string_list(genrelist, ";"); - - if (g_list_length(preparsed_genres_list) == 0) { // No genres - g_list_free(preparsed_genres_list); - return; - } - - // TODO: Do a pass on in first memory optimization phase to ensure string elements are freed. - g_list_free_full(self->genres, NULL); // Free the existing genres list - self->genres = preparsed_genres_list; -} - -KotoTrack * koto_track_new( - const gchar * artist_uuid, - const gchar * album_uuid, - const gchar * parsed_name, - guint cd -) { - KotoTrack * track = g_object_new( - KOTO_TYPE_TRACK, - "artist-uuid", - artist_uuid, - "album-uuid", - album_uuid, - "do-initial-index", - TRUE, - "uuid", - g_uuid_string_random(), - "cd", - cd, - "parsed-name", - parsed_name, - NULL - ); - - return track; -} - -KotoTrack * koto_track_new_with_uuid(const gchar * uuid) { - return g_object_new( - KOTO_TYPE_TRACK, - "uuid", - g_strdup(uuid), - NULL - ); -} diff --git a/src/koto-dialog-container.c b/src/koto-dialog-container.c deleted file mode 100644 index fb7c2ac..0000000 --- a/src/koto-dialog-container.c +++ /dev/null @@ -1,111 +0,0 @@ -/* koto-dialog-container.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "components/button.h" -#include "koto-dialog-container.h" - -struct _KotoDialogContainer { - GtkBox parent_instance; - KotoButton * close_button; - GtkWidget * dialogs; -}; - -G_DEFINE_TYPE(KotoDialogContainer, koto_dialog_container, GTK_TYPE_BOX); - -static void koto_dialog_container_class_init(KotoDialogContainerClass * c) { - (void) c; -} - -static void koto_dialog_container_init(KotoDialogContainer * self) { - gtk_widget_add_css_class(GTK_WIDGET(self), "koto-dialog-container"); - - g_object_set(GTK_WIDGET(self), "hexpand", TRUE, "vexpand", TRUE, NULL); - - self->close_button = koto_button_new_with_icon(NULL, "window-close-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_LARGE); - gtk_widget_set_halign(GTK_WIDGET(self->close_button), GTK_ALIGN_END); - gtk_box_prepend(GTK_BOX(self), GTK_WIDGET(self->close_button)); // Add our close button - - self->dialogs = gtk_stack_new(); - gtk_stack_set_transition_duration(GTK_STACK(self->dialogs), 0); // No transition timing - gtk_stack_set_transition_type(GTK_STACK(self->dialogs), GTK_STACK_TRANSITION_TYPE_NONE); // No transition - gtk_widget_set_halign(self->dialogs, GTK_ALIGN_CENTER); - gtk_widget_set_hexpand(self->dialogs, TRUE); - gtk_widget_set_valign(self->dialogs, GTK_ALIGN_CENTER); - gtk_widget_set_vexpand(self->dialogs, TRUE); - - gtk_box_append(GTK_BOX(self), self->dialogs); // Add the dialogs stack - - koto_button_add_click_handler(self->close_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_dialog_container_handle_close_click), self); - gtk_widget_hide(GTK_WIDGET(self)); // Hide by default -} - -void koto_dialog_container_add_dialog( - KotoDialogContainer * self, - gchar * dialog_name, - GtkWidget * dialog -) { - if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container - return; - } - - gtk_stack_add_named(GTK_STACK(self->dialogs), dialog, dialog_name); // Add the dialog to the stack -} - -void koto_dialog_container_handle_close_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - koto_dialog_container_hide((KotoDialogContainer*) user_data); -} - -void koto_dialog_container_hide(KotoDialogContainer * self) { - if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container - return; - } - - gtk_widget_hide(GTK_WIDGET(self)); -} - -void koto_dialog_container_show_dialog( - KotoDialogContainer * self, - gchar * dialog_name -) { - if (!KOTO_IS_DIALOG_CONTAINER(self)) { // Not a dialog container - return; - } - - gtk_stack_set_visible_child_name(GTK_STACK(self->dialogs), dialog_name); // Set to the dialog name - gtk_widget_show(GTK_WIDGET(self)); // Ensure we show self -} - -KotoDialogContainer * koto_dialog_container_new() { - return g_object_new( - KOTO_TYPE_DIALOG_CONTAINER, - "orientation", - GTK_ORIENTATION_VERTICAL, - NULL - ); -} diff --git a/src/koto-dialog-container.h b/src/koto-dialog-container.h deleted file mode 100644 index 886be2c..0000000 --- a/src/koto-dialog-container.h +++ /dev/null @@ -1,59 +0,0 @@ -/* koto-dialog-container.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_DIALOG_CONTAINER koto_dialog_container_get_type() -G_DECLARE_FINAL_TYPE(KotoDialogContainer, koto_dialog_container, KOTO, DIALOG_CONTAINER, GtkBox); -#define KOTO_IS_DIALOG_CONTAINER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_DIALOG_CONTAINER)) - -/** - * Functions - **/ - -KotoDialogContainer * koto_dialog_container_new(); - -void koto_dialog_container_add_dialog( - KotoDialogContainer * self, - gchar * dialog_name, - GtkWidget * dialog -); - -void koto_dialog_container_handle_close_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_dialog_container_hide(KotoDialogContainer * self); - -void koto_dialog_container_show_dialog( - KotoDialogContainer * self, - gchar * dialog_name -); - -G_END_DECLS diff --git a/src/koto-expander.c b/src/koto-expander.c deleted file mode 100644 index f95af87..0000000 --- a/src/koto-expander.c +++ /dev/null @@ -1,323 +0,0 @@ -/* koto-expander.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "components/button.h" -#include "config/config.h" -#include "koto-expander.h" -#include "koto-utils.h" - -enum { - PROP_EXP_0, - PROP_HEADER_ICON_NAME, - PROP_HEADER_LABEL, - PROP_HEADER_SECONDARY_BUTTON, - PROP_CONTENT, - N_EXP_PROPERTIES -}; - -static GParamSpec * expander_props[N_EXP_PROPERTIES] = { - NULL, -}; - -struct _KotoExpander { - GtkBox parent_instance; - gboolean constructed; - GtkWidget * header; - KotoButton * header_button; - - gchar * icon_name; - gchar * label; - - KotoButton * header_secondary_button; - KotoButton * header_expand_button; - - GtkWidget * revealer; - GtkWidget * content; -}; - -struct _KotoExpanderClass { - GtkBoxClass parent_class; -}; - -G_DEFINE_TYPE(KotoExpander, koto_expander, GTK_TYPE_BOX); - -static void koto_expander_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_expander_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_expander_class_init(KotoExpanderClass * c) { - GObjectClass * gobject_class = G_OBJECT_CLASS(c); - - gobject_class->set_property = koto_expander_set_property; - gobject_class->get_property = koto_expander_get_property; - - expander_props[PROP_HEADER_ICON_NAME] = g_param_spec_string( - "icon-name", - "Icon Name", - "Name of the icon to use in the Expander", - "emblem-favorite-symbolic", - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - expander_props[PROP_HEADER_LABEL] = g_param_spec_string( - "label", - "Label", - "Label for the Expander", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - expander_props[PROP_HEADER_SECONDARY_BUTTON] = g_param_spec_object( - "secondary-button", - "Secondary Button", - "Secondary Button to be placed next to Expander button", - KOTO_TYPE_BUTTON, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - expander_props[PROP_CONTENT] = g_param_spec_object( - "content", - "Content", - "Content inside the Expander", - GTK_TYPE_WIDGET, - G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_EXP_PROPERTIES, expander_props); -} - -static void koto_expander_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoExpander * self = KOTO_EXPANDER(obj); - - switch (prop_id) { - case PROP_HEADER_ICON_NAME: - g_value_set_string(val, self->icon_name); - break; - case PROP_HEADER_LABEL: - g_value_set_string(val, self->label); - break; - case PROP_HEADER_SECONDARY_BUTTON: - g_value_set_object(val, (GObject*) self->header_secondary_button); - break; - case PROP_CONTENT: - g_value_set_object(val, (GObject*) self->content); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_expander_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoExpander * self = KOTO_EXPANDER(obj); - - switch (prop_id) { - case PROP_HEADER_ICON_NAME: - koto_expander_set_icon_name(self, g_strdup(g_value_get_string(val))); - break; - case PROP_HEADER_LABEL: - g_return_if_fail(GTK_IS_WIDGET(self->header_button)); - koto_button_set_text(self->header_button, g_strdup(g_value_get_string(val))); - break; - case PROP_HEADER_SECONDARY_BUTTON: - koto_expander_set_secondary_button(self, (KotoButton*) g_value_get_object(val)); - break; - case PROP_CONTENT: - koto_expander_set_content(self, (GtkWidget*) g_value_get_object(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_expander_init(KotoExpander * self) { - GtkStyleContext * style = gtk_widget_get_style_context(GTK_WIDGET(self)); - - gtk_style_context_add_class(style, "expander"); - gtk_widget_set_hexpand((GTK_WIDGET(self)), TRUE); - - self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10); - - GtkStyleContext * header_style = gtk_widget_get_style_context(self->header); - - gtk_style_context_add_class(header_style, "expander-header"); - - self->revealer = gtk_revealer_new(); - gtk_revealer_set_reveal_child(GTK_REVEALER(self->revealer), TRUE); // Set to be revealed by default - gtk_revealer_set_transition_type(GTK_REVEALER(self->revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); - - KotoButton * new_button = koto_button_new_with_icon(NULL, "emblem-favorite-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL); - - if (KOTO_IS_BUTTON(new_button)) { // Created our widget successfully - self->header_button = new_button; - gtk_widget_set_hexpand(GTK_WIDGET(self->header_button), TRUE); - gtk_box_prepend(GTK_BOX(self->header), GTK_WIDGET(self->header_button)); - } - - self->header_expand_button = koto_button_new_with_icon("", "pan-down-symbolic", "pan-up-symbolic", KOTO_BUTTON_PIXBUF_SIZE_SMALL); - gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->header_expand_button)); - - gtk_box_prepend(GTK_BOX(self), self->header); - gtk_box_append(GTK_BOX(self), self->revealer); - - self->constructed = TRUE; - - koto_button_add_click_handler(self->header_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_expander_toggle_content), self); - koto_button_add_click_handler(self->header_expand_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_expander_toggle_content), self); -} - -// koto_expander_set_icon_name will set the icon for our inner KotoButton for this Expander -void koto_expander_set_icon_name( - KotoExpander * self, - gchar * icon -) { - if (!KOTO_IS_EXPANDER(self)) { // Not a KotoExpander - return; - } - - if (!koto_utils_string_is_valid(icon)) { // Not a valid string - return; - } - - if (!KOTO_IS_BUTTON(self->header_button)) { // Don't have a header button for whatever reason - return; - } - - koto_button_set_icon_name(self->header_button, icon, FALSE); -} - -void koto_expander_set_secondary_button( - KotoExpander * self, - KotoButton * new_button -) { - if (!self->constructed) { - return; - } - - if (!GTK_IS_WIDGET(new_button)) { - return; - } - - if (GTK_IS_WIDGET(self->header_secondary_button)) { // Already have a button - gtk_box_remove(GTK_BOX(self->header), GTK_WIDGET(self->header_secondary_button)); - } - - self->header_secondary_button = new_button; - gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->header_secondary_button)); - - g_object_notify_by_pspec(G_OBJECT(self), expander_props[PROP_HEADER_SECONDARY_BUTTON]); -} - -void koto_expander_set_content( - KotoExpander * self, - GtkWidget * new_content -) { - if (!self->constructed) { - return; - } - - if (GTK_IS_WIDGET(self->content)) { // Already have content - gtk_revealer_set_child(GTK_REVEALER(self->revealer), NULL); - g_object_unref(self->content); - } - - self->content = new_content; - gtk_revealer_set_child(GTK_REVEALER(self->revealer), self->content); - - g_object_notify_by_pspec(G_OBJECT(self), expander_props[PROP_CONTENT]); -} - -GtkWidget * koto_expander_get_content(KotoExpander * self) { - return self->content; -} - -void koto_expander_toggle_content( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoExpander * self = data; - - koto_button_flip(KOTO_BUTTON(self->header_expand_button)); - GtkRevealer * rev = GTK_REVEALER(self->revealer); - - gtk_revealer_set_reveal_child(rev, !gtk_revealer_get_reveal_child(rev)); // Invert our values -} - -KotoExpander * koto_expander_new( - gchar * primary_icon_name, - gchar * primary_label_text -) { - return g_object_new( - KOTO_TYPE_EXPANDER, - "orientation", - GTK_ORIENTATION_VERTICAL, - "icon-name", - primary_icon_name, - "label", - primary_label_text, - NULL - ); -} - -KotoExpander * koto_expander_new_with_button( - gchar * primary_icon_name, - gchar * primary_label_text, - KotoButton * secondary_button -) { - return g_object_new( - KOTO_TYPE_EXPANDER, - "orientation", - GTK_ORIENTATION_VERTICAL, - "icon-name", - primary_icon_name, - "label", - primary_label_text, - "secondary-button", - secondary_button, - NULL - ); -} diff --git a/src/koto-expander.h b/src/koto-expander.h deleted file mode 100644 index 558ebcf..0000000 --- a/src/koto-expander.h +++ /dev/null @@ -1,66 +0,0 @@ -/* koto-expander.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include "components/button.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_EXPANDER (koto_expander_get_type()) -G_DECLARE_FINAL_TYPE(KotoExpander, koto_expander, KOTO, EXPANDER, GtkBox) -#define KOTO_IS_EXPANDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_EXPANDER)) - -KotoExpander * koto_expander_new(gchar * primary_icon_name, gchar * primary_label_text); -KotoExpander * koto_expander_new_with_button( - gchar * primary_icon_name, - gchar * primary_label_text, - KotoButton * secondary_button -); - -GtkWidget * koto_expander_get_content(KotoExpander * self); - -void koto_expander_set_icon_name( - KotoExpander * self, - gchar * icon -); - -void koto_expander_set_label( - KotoExpander * self, - const gchar * label -); - -void koto_expander_set_secondary_button( - KotoExpander * self, - KotoButton * new_button -); - -void koto_expander_set_content( - KotoExpander * self, - GtkWidget * new_content -); - -void koto_expander_toggle_content( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -G_END_DECLS diff --git a/src/koto-headerbar.ui b/src/koto-headerbar.ui deleted file mode 100644 index 6bad467..0000000 --- a/src/koto-headerbar.ui +++ /dev/null @@ -1,40 +0,0 @@ -?xml version="1.0" encoding="UTF-8"?> - - - - - False - audio-headphones - 3 - - - diff --git a/src/koto-nav.c b/src/koto-nav.c deleted file mode 100644 index 7c67785..0000000 --- a/src/koto-nav.c +++ /dev/null @@ -1,318 +0,0 @@ -/* koto-nav.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "components/button.h" -#include "config/config.h" -#include "db/cartographer.h" -#include "indexer/structs.h" -#include "playlist/playlist.h" -#include "koto-expander.h" -#include "koto-nav.h" -#include "koto-utils.h" -#include "koto-window.h" - -extern KotoCartographer * koto_maps; -extern KotoWindow * main_window; - -struct _KotoNav { - GObject parent_instance; - GtkWidget * win; - GtkWidget * content; - - KotoButton * home_button; - KotoExpander * audiobook_expander; - KotoExpander * music_expander; - KotoExpander * podcast_expander; - KotoExpander * playlists_expander; - - // Audiobooks - - KotoButton * audiobooks_local; - KotoButton * audiobooks_audible; - KotoButton * audiobooks_librivox; - - // Music - - KotoButton * music_local; - KotoButton * music_radio; - - // Playlists - - GHashTable * playlist_buttons; - - // Podcasts - - KotoButton * podcasts_local; - KotoButton * podcasts_discover; -}; - -struct _KotoNavClass { - GObjectClass parent_class; -}; - -G_DEFINE_TYPE(KotoNav, koto_nav, G_TYPE_OBJECT); - -static void koto_nav_class_init(KotoNavClass * c) { - (void) c; -} - -static void koto_nav_init(KotoNav * self) { - self->playlist_buttons = g_hash_table_new(g_str_hash, g_str_equal); - self->win = gtk_scrolled_window_new(); - gtk_widget_set_hexpand_set(self->win, TRUE); // using hexpand-set works, hexpand seems to break it by causing it to take up way too much space - gtk_widget_set_size_request(self->win, 300, -1); - gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(self->win), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); - gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->win), 300); - gtk_scrolled_window_set_max_content_width(GTK_SCROLLED_WINDOW(self->win), 300); - gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->win), TRUE); - - self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 10); - - gtk_widget_add_css_class(self->win, "primary-nav"); - gtk_widget_set_vexpand(self->win, TRUE); - - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->win), self->content); - - KotoButton * h_button = koto_button_new_with_icon("Home", "user-home-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL); - - if (h_button != NULL) { - self->home_button = h_button; - gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->home_button)); - } - - koto_nav_create_audiobooks_section(self); - koto_nav_create_music_section(self); - koto_nav_create_podcasts_section(self); - koto_nav_create_playlist_section(self); -} - -void koto_nav_create_audiobooks_section(KotoNav * self) { - KotoExpander * a_expander = koto_expander_new("ephy-bookmarks-symbolic", "Audiobooks"); - - self->audiobook_expander = a_expander; - gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->audiobook_expander)); - - GtkWidget * new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - koto_expander_set_content(a_expander, new_content); - - self->audiobooks_local = koto_button_new_plain("Library"); - koto_button_set_data(self->audiobooks_local, &"audiobooks.library"); - - self->audiobooks_audible = koto_button_new_plain("Audible"); - self->audiobooks_librivox = koto_button_new_plain("LibriVox"); - - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_local)); - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_audible)); - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->audiobooks_librivox)); - - koto_button_add_click_handler(self->audiobooks_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), self->audiobooks_local); - -} - -void koto_nav_create_music_section(KotoNav * self) { - KotoExpander * m_expander = koto_expander_new("emblem-music-symbolic", "Music"); - - self->music_expander = m_expander; - gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->music_expander)); - - GtkWidget * new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - self->music_local = koto_button_new_plain("Library"); - koto_button_set_data(self->music_local, &"music.library"); - - self->music_radio = koto_button_new_plain("Radio"); - - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_local)); - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->music_radio)); - - koto_expander_set_content(m_expander, new_content); - koto_button_add_click_handler(self->music_local, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), self->music_local); -} - -void koto_nav_create_playlist_section(KotoNav * self) { - KotoButton * playlist_add_button = koto_button_new_with_icon("", "list-add-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_SMALL); - KotoExpander * pl_expander = koto_expander_new_with_button("playlist-symbolic", "Playlists", playlist_add_button); - - self->playlists_expander = pl_expander; - gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->playlists_expander)); - - // TODO: Turn into ListBox to sort playlists - GtkWidget * playlist_list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - koto_expander_set_content(self->playlists_expander, playlist_list); - koto_button_add_click_handler(playlist_add_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_nav_handle_playlist_add_click), NULL); - - g_signal_connect(koto_maps, "playlist-added", G_CALLBACK(koto_nav_handle_playlist_added), self); - g_signal_connect(koto_maps, "playlist-removed", G_CALLBACK(koto_nav_handle_playlist_removed), self); -} - -void koto_nav_create_podcasts_section(KotoNav * self) { - KotoExpander * p_expander = koto_expander_new("microphone-sensitivity-high-symbolic", "Podcasts"); - - self->podcast_expander = p_expander; - gtk_box_append(GTK_BOX(self->content), GTK_WIDGET(self->podcast_expander)); - - GtkWidget * new_content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - self->podcasts_local = koto_button_new_plain("Library"); - self->podcasts_discover = koto_button_new_plain("Find New Podcasts"); - - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_local)); - gtk_box_append(GTK_BOX(new_content), GTK_WIDGET(self->podcasts_discover)); - - koto_expander_set_content(p_expander, new_content); -} - -void koto_nav_handle_playlist_add_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) user_data; - koto_window_show_dialog(main_window, "create-modify-playlist"); -} - -void koto_nav_handle_playlist_added( - KotoCartographer * carto, - KotoPlaylist * playlist, - gpointer user_data -) { - (void) carto; - if (!KOTO_IS_PLAYLIST(playlist)) { - return; - } - - if (koto_playlist_get_is_hidden(playlist)) { // Should be hidden from nav - return; - } - - KotoNav * self = user_data; - - if (!KOTO_IS_NAV(self)) { - return; - } - - gchar * playlist_uuid = koto_playlist_get_uuid(playlist); // Get the UUID for a playlist - - if (g_hash_table_contains(self->playlist_buttons, playlist_uuid)) { // Already added button - g_free(playlist_uuid); - return; - } - - gchar * playlist_name = koto_playlist_get_name(playlist); - gchar * playlist_art_path = koto_playlist_get_artwork(playlist); // Get any file path for it - KotoButton * playlist_button = NULL; - - if (koto_utils_string_is_valid(playlist_art_path)) { // Have a file associated - playlist_button = koto_button_new_with_file(playlist_name, playlist_art_path, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - } else { // No file associated - playlist_button = koto_button_new_with_icon(playlist_name, "audio-x-generic-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - } - - if (!KOTO_IS_BUTTON(playlist_button)) { // Failed to create the playlist button - return; - } - - koto_button_set_data(playlist_button, playlist_uuid); // Set our data to the playlist UUID string - g_hash_table_insert(self->playlist_buttons, playlist_uuid, playlist_button); // Add the button - - // TODO: Make this a ListBox and sort the playlists alphabetically - GtkBox * playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander)); - - if (GTK_IS_BOX(playlist_expander_content)) { - gtk_box_append(playlist_expander_content, GTK_WIDGET(playlist_button)); - - koto_button_add_click_handler(playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), playlist_button); - koto_window_handle_playlist_added(koto_maps, playlist, main_window); // TODO: MOVE THIS - g_signal_connect(playlist, "modified", G_CALLBACK(koto_nav_handle_playlist_modified), self); - } -} - -void koto_nav_handle_playlist_modified( - KotoPlaylist * playlist, - gpointer user_data -) { - if (!KOTO_IS_PLAYLIST(playlist)) { - return; - } - - KotoNav * self = user_data; - - if (!KOTO_IS_NAV(self)) { - return; - } - - gchar * playlist_uuid = koto_playlist_get_uuid(playlist); // Get the UUID for a playlist - - KotoButton * playlist_button = g_hash_table_lookup(self->playlist_buttons, playlist_uuid); - - if (!KOTO_IS_BUTTON(playlist_button)) { - return; - } - - gchar * artwork = koto_playlist_get_artwork(playlist); // Get the artwork - - if (koto_utils_string_is_valid(artwork)) { // Have valid artwork - koto_button_set_file_path(playlist_button, artwork); // Update the artwork path - } - - gchar * name = koto_playlist_get_name(playlist); // Get the name - - if (koto_utils_string_is_valid(name)) { // Have valid name - koto_button_set_text(playlist_button, name); // Update the button text - } -} - -void koto_nav_handle_playlist_removed( - KotoCartographer * carto, - gchar * playlist_uuid, - gpointer user_data -) { - (void) carto; - KotoNav * self = user_data; - - if (!g_hash_table_contains(self->playlist_buttons, playlist_uuid)) { // Does not contain this - return; - } - - KotoButton * playlist_btn = g_hash_table_lookup(self->playlist_buttons, playlist_uuid); // Get the playlist button - - if (!KOTO_IS_BUTTON(playlist_btn)) { // Not a playlist button - return; - } - - GtkBox * playlist_expander_content = GTK_BOX(koto_expander_get_content(self->playlists_expander)); - - gtk_box_remove(playlist_expander_content, GTK_WIDGET(playlist_btn)); // Remove the button - g_hash_table_remove(self->playlist_buttons, playlist_uuid); // Remove from the playlist buttons hash table -} - -GtkWidget * koto_nav_get_nav(KotoNav * self) { - return self->win; -} - -KotoNav * koto_nav_new(void) { - return g_object_new(KOTO_TYPE_NAV, NULL); -} diff --git a/src/koto-nav.h b/src/koto-nav.h deleted file mode 100644 index 6583eb4..0000000 --- a/src/koto-nav.h +++ /dev/null @@ -1,65 +0,0 @@ -/* koto-nav.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include "db/cartographer.h" -#include "indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_NAV (koto_nav_get_type()) - -G_DECLARE_FINAL_TYPE(KotoNav, koto_nav, KOTO, NAV, GObject) - -KotoNav* koto_nav_new(void); -void koto_nav_create_audiobooks_section(KotoNav * self); - -void koto_nav_create_music_section(KotoNav * self); - -void koto_nav_create_playlist_section(KotoNav * self); - -void koto_nav_create_podcasts_section(KotoNav * self); - -void koto_nav_handle_playlist_add_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_nav_handle_playlist_added( - KotoCartographer * carto, - KotoPlaylist * playlist, - gpointer user_data -); - -void koto_nav_handle_playlist_modified( - KotoPlaylist * playlist, - gpointer user_data -); - -void koto_nav_handle_playlist_removed( - KotoCartographer * carto, - gchar * playlist_uuid, - gpointer user_data -); - -GtkWidget * koto_nav_get_nav(KotoNav * self); - -G_END_DECLS diff --git a/src/koto-paths.c b/src/koto-paths.c deleted file mode 100644 index 07eed76..0000000 --- a/src/koto-paths.c +++ /dev/null @@ -1,46 +0,0 @@ -/* koto-paths.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include "koto-paths.h" -#include "koto-utils.h" - -gchar * koto_rev_dns; -gchar * koto_path_cache; -gchar * koto_path_config; - -gchar * koto_path_to_conf; -gchar * koto_path_to_db; - -void koto_paths_setup() { - koto_rev_dns = "com.github.joshstrobl.koto"; - gchar * user_cache_dir = g_strdup(g_get_user_data_dir()); - gchar * user_config_dir = g_strdup(g_get_user_config_dir()); - - koto_path_cache = g_build_path(G_DIR_SEPARATOR_S, user_cache_dir, koto_rev_dns, NULL); - koto_path_config = g_build_path(G_DIR_SEPARATOR_S, user_config_dir, koto_rev_dns, NULL); - koto_path_to_conf = g_build_filename(koto_path_config, "config.toml", NULL); - koto_path_to_db = g_build_filename( koto_path_cache, "db", NULL); - - koto_utils_mkdir(user_cache_dir); - koto_utils_mkdir(user_config_dir); - koto_utils_mkdir(koto_path_cache); - koto_utils_mkdir(koto_path_config); -} \ No newline at end of file diff --git a/src/koto-paths.h b/src/koto-paths.h deleted file mode 100644 index a791c88..0000000 --- a/src/koto-paths.h +++ /dev/null @@ -1,20 +0,0 @@ -/* koto-paths.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -void koto_paths_setup(); \ No newline at end of file diff --git a/src/koto-playerbar.c b/src/koto-playerbar.c deleted file mode 100644 index be1d5ce..0000000 --- a/src/koto-playerbar.c +++ /dev/null @@ -1,809 +0,0 @@ -/* koto-playerbar.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "components/button.h" -#include "config/config.h" -#include "db/cartographer.h" -#include "playlist/add-remove-track-popover.h" -#include "playlist/current.h" -#include "playlist/playlist.h" -#include "playback/engine.h" -#include "config/config.h" -#include "koto-playerbar.h" -#include "koto-utils.h" - -extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; -extern KotoCartographer * koto_maps; -extern KotoConfig * config; -extern KotoCurrentPlaylist * current_playlist; -extern KotoPlaybackEngine * playback_engine; - -struct _KotoPlayerBar { - GObject parent_instance; - GtkWidget * main; - GtkWidget * controls; - - /* Sections */ - GtkWidget * playback_section; - GtkWidget * primary_controls_section; - GtkWidget * secondary_controls_section; - - /* Primary Buttons */ - KotoButton * back_button; - KotoButton * play_pause_button; - KotoButton * forward_button; - KotoButton * repeat_button; - KotoButton * shuffle_button; - KotoButton * playlist_button; - KotoButton * eq_button; - GtkWidget * volume_button; - - GtkWidget * progress_bar; - - /* Selected Playback Section */ - - GtkWidget * playback_details_section; - GtkWidget * artwork; - GtkWidget * playback_title; - GtkWidget * playback_album; - GtkWidget * playback_artist; - - /* Advanced Controls (Jump Back / Forwards, Speed) */ - GtkWidget * advanced_controls_section; - KotoButton * backwards_jump_button; - KotoButton * forwards_jump_button; - GtkWidget * playback_speed_input; - - /* Misc Widgets */ - GtkWidget * playback_position_label; - - gint64 last_recorded_duration; - - gboolean is_progressbar_seeking; -}; - -struct _KotoPlayerBarClass { - GObjectClass parent_class; -}; - -G_DEFINE_TYPE(KotoPlayerBar, koto_playerbar, G_TYPE_OBJECT); - -static void koto_playerbar_constructed(GObject * obj); - -static void koto_playerbar_class_init(KotoPlayerBarClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - - gobject_class->constructed = koto_playerbar_constructed; -} - -static void koto_playerbar_constructed(GObject * obj) { - KotoPlayerBar * self = KOTO_PLAYERBAR(obj); - - self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->main, "player-bar"); - - self->progress_bar = gtk_scale_new_with_range(GTK_ORIENTATION_HORIZONTAL, 0, 120, 1); // Default to 120 as random max - - gtk_scale_set_draw_value(GTK_SCALE(self->progress_bar), FALSE); - gtk_scale_set_digits(GTK_SCALE(self->progress_bar), 0); - gtk_range_set_increments(GTK_RANGE(self->progress_bar), 1, 1); - gtk_range_set_round_digits(GTK_RANGE(self->progress_bar), 1); - - GtkGesture * press_controller = gtk_gesture_click_new(); // Create a new GtkGestureLongPress - - gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(press_controller), 1); // Set to left click - - g_signal_connect(press_controller, "begin", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_begin), self); - g_signal_connect(press_controller, "end", G_CALLBACK(koto_playerbar_handle_progressbar_gesture_end), self); - g_signal_connect(press_controller, "pressed", G_CALLBACK(koto_playerbar_handle_progressbar_pressed), self); - - gtk_widget_add_controller(GTK_WIDGET(self->progress_bar), GTK_EVENT_CONTROLLER(press_controller)); - - g_signal_connect(GTK_RANGE(self->progress_bar), "value-changed", G_CALLBACK(koto_playerbar_handle_progressbar_value_changed), self); - - self->controls = gtk_center_box_new(); - gtk_center_box_set_baseline_position(GTK_CENTER_BOX(self->controls), GTK_BASELINE_POSITION_CENTER); - - self->primary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->primary_controls_section, "playerbar-primary-controls"); - - self->playback_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->playback_section, "playerbar-info"); - - self->secondary_controls_section = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->secondary_controls_section, "playerbar-secondary-controls"); - - gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->primary_controls_section)); - gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->playback_section)); - gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->controls), GTK_WIDGET(self->secondary_controls_section)); - - koto_playerbar_create_playback_details(self); - koto_playerbar_create_primary_controls(self); - koto_playerbar_create_secondary_controls(self); - - gtk_box_prepend(GTK_BOX(self->main), self->progress_bar); - gtk_box_append(GTK_BOX(self->main), self->controls); - - gtk_widget_set_hexpand(GTK_WIDGET(self->main), TRUE); - - // Set up our volume and other playback state bits from our config - - koto_playerbar_apply_configuration_state(config, 0, self); - - // Set up the bindings - - g_signal_connect(config, "notify::playback-last-used-volume", G_CALLBACK(koto_playerbar_apply_configuration_state), self); // Bind onto the playback last used volume config option - g_signal_connect(playback_engine, "is-playing", G_CALLBACK(koto_playerbar_handle_is_playing), self); - g_signal_connect(playback_engine, "is-paused", G_CALLBACK(koto_playerbar_handle_is_paused), self); - g_signal_connect(playback_engine, "tick-duration", G_CALLBACK(koto_playerbar_handle_tick_duration), self); - g_signal_connect(playback_engine, "tick-track", G_CALLBACK(koto_playerbar_handle_tick_track), self); - g_signal_connect(playback_engine, "track-changed", G_CALLBACK(koto_playerbar_update_track_info), self); - g_signal_connect(playback_engine, "track-repeat-changed", G_CALLBACK(koto_playerbar_handle_track_repeat), self); - g_signal_connect(playback_engine, "track-shuffle-changed", G_CALLBACK(koto_playerbar_handle_track_shuffle), self); -} - -static void koto_playerbar_init(KotoPlayerBar * self) { - self->last_recorded_duration = 0; - self->is_progressbar_seeking = FALSE; -} - -KotoPlayerBar * koto_playerbar_new(void) { - return g_object_new(KOTO_TYPE_PLAYERBAR, NULL); -} - -void koto_playerbar_apply_configuration_state( - KotoConfig * config, - guint prop_id, - KotoPlayerBar * self -) { - (void) prop_id; - gdouble config_last_used_volume = 0; - g_object_get( - config, - "playback-last-used-volume", - &config_last_used_volume, - NULL - ); - - gtk_scale_button_set_value(GTK_SCALE_BUTTON(self->volume_button), config_last_used_volume * 100); -} - -void koto_playerbar_create_playback_details(KotoPlayerBar * self) { - self->playback_details_section = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - GtkIconTheme * default_icon_theme = gtk_icon_theme_get_for_display(gdk_display_get_default()); // Get the icon theme for this display - - if (default_icon_theme != NULL) { - gint scale_factor = gtk_widget_get_scale_factor(GTK_WIDGET(self->main)); - GtkIconPaintable * audio_paintable = gtk_icon_theme_lookup_icon(default_icon_theme, "audio-x-generic-symbolic", NULL, 96, scale_factor, GTK_TEXT_DIR_NONE, GTK_ICON_LOOKUP_PRELOAD); - - if (GTK_IS_ICON_PAINTABLE(audio_paintable)) { - if (GTK_IS_IMAGE(self->artwork)) { - gtk_image_set_from_paintable(GTK_IMAGE(self->artwork), GDK_PAINTABLE(audio_paintable)); - } else { // Not an image - self->artwork = gtk_image_new_from_paintable(GDK_PAINTABLE(audio_paintable)); - gtk_widget_add_css_class(self->artwork, "circular"); - gtk_widget_set_size_request(self->artwork, 96, 96); - gtk_box_append(GTK_BOX(self->playback_section), self->artwork); - } - } - } - - self->playback_title = gtk_label_new("Title"); - gtk_label_set_xalign(GTK_LABEL(self->playback_title), 0); - - self->playback_album = gtk_label_new("Album"); - gtk_label_set_xalign(GTK_LABEL(self->playback_album), 0); - - self->playback_artist = gtk_label_new("Artist"); - gtk_label_set_xalign(GTK_LABEL(self->playback_artist), 0); - - gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_title)); - gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_album)); - gtk_box_append(GTK_BOX(self->playback_details_section), GTK_WIDGET(self->playback_artist)); - - gtk_box_append(GTK_BOX(self->playback_section), GTK_WIDGET(self->playback_details_section)); -} - -void koto_playerbar_create_primary_controls(KotoPlayerBar * self) { - self->back_button = koto_button_new_with_icon("", "media-skip-backward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - self->play_pause_button = koto_button_new_with_icon("", "media-playback-start-symbolic", "media-playback-pause-symbolic", KOTO_BUTTON_PIXBUF_SIZE_LARGE); // TODO: Have this take in a state and switch to a different icon if necessary - self->forward_button = koto_button_new_with_icon("", "media-skip-forward-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - - if (KOTO_IS_BUTTON(self->back_button)) { - gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->back_button)); - koto_button_add_click_handler(self->back_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_backwards), self); - } - - if (KOTO_IS_BUTTON(self->play_pause_button)) { - gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->play_pause_button)); - koto_button_add_click_handler(self->play_pause_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_play_pause), self); - } - - if (KOTO_IS_BUTTON(self->forward_button)) { - gtk_box_append(GTK_BOX(self->primary_controls_section), GTK_WIDGET(self->forward_button)); - koto_button_add_click_handler(self->forward_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_go_forwards), self); - } - - self->advanced_controls_section = gtk_center_box_new(); - gtk_widget_add_css_class(self->advanced_controls_section, "playerbar-advanced-controls"); - - self->backwards_jump_button = koto_button_new_with_resource("/com/github/joshstrobl/koto/multimedia-backwards-jump", KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - koto_button_add_click_handler(self->backwards_jump_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_jump_backwards), self); - - self->forwards_jump_button = koto_button_new_with_resource("/com/github/joshstrobl/koto/multimedia-forwards-jump", KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - koto_button_add_click_handler(self->forwards_jump_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_jump_forwards), self); - - self->playback_speed_input = gtk_entry_new_with_buffer(gtk_entry_buffer_new("1.0", -1)); // Create an input that defaults to 1.0 - gtk_entry_set_max_length(GTK_ENTRY(self->playback_speed_input), 4); // Max of 4 characters (e.g. "0.25" but allow for "2" or "2.0") - gtk_editable_set_max_width_chars(GTK_EDITABLE(self->playback_speed_input), 4); // Set to be as wide as 4 characters - gtk_widget_set_valign(self->playback_speed_input, GTK_ALIGN_CENTER); // Only align center, don't vexpand - - gtk_center_box_set_start_widget(GTK_CENTER_BOX(self->advanced_controls_section), GTK_WIDGET(self->backwards_jump_button)); - gtk_center_box_set_center_widget(GTK_CENTER_BOX(self->advanced_controls_section), GTK_WIDGET(self->playback_speed_input)); - gtk_center_box_set_end_widget(GTK_CENTER_BOX(self->advanced_controls_section), GTK_WIDGET(self->forwards_jump_button)); - - gtk_box_append(GTK_BOX(self->primary_controls_section), self->advanced_controls_section); - - gtk_entry_set_input_hints( - GTK_ENTRY(self->playback_speed_input), - GTK_INPUT_HINT_NONE | GTK_INPUT_HINT_NO_SPELLCHECK | GTK_INPUT_HINT_NO_EMOJI // No fanciness please - ); - - gtk_entry_set_input_purpose( - GTK_ENTRY(self->playback_speed_input), - GTK_INPUT_PURPOSE_NUMBER // Numbers allow . so use that - ); - - gtk_entry_set_placeholder_text(GTK_ENTRY(self->playback_speed_input), "1.0"); - - g_signal_connect(self->playback_speed_input, "activate", G_CALLBACK(koto_playerbar_handle_speed_input_activate), self); // Handle our activate (enter key on entry) -} - -void koto_playerbar_create_secondary_controls(KotoPlayerBar * self) { - self->playback_position_label = gtk_label_new(NULL); // Create our label to communicate position and duration of a track - - self->repeat_button = koto_button_new_with_icon("", "media-playlist-repeat-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - self->shuffle_button = koto_button_new_with_icon("", "media-playlist-shuffle-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - self->playlist_button = koto_button_new_with_icon("", "playlist-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - self->eq_button = koto_button_new_with_icon("", "multimedia-equalizer-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - - self->volume_button = gtk_volume_button_new(); // Have this take in a state and switch to a different icon if necessary - - if (GTK_IS_LABEL(self->playback_position_label)) { - gtk_box_append(GTK_BOX(self->secondary_controls_section), self->playback_position_label); - } - - if (KOTO_IS_BUTTON(self->repeat_button)) { - gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->repeat_button)); - koto_button_add_click_handler(self->repeat_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_track_repeat), self); - } - - if (KOTO_IS_BUTTON(self->shuffle_button)) { - gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->shuffle_button)); - koto_button_add_click_handler(self->shuffle_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_toggle_playlist_shuffle), self); - } - - if (KOTO_IS_BUTTON(self->playlist_button)) { - gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->playlist_button)); - koto_button_add_click_handler(self->playlist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playerbar_handle_playlist_button_clicked), self); - } - - if (KOTO_IS_BUTTON(self->eq_button)) { - gtk_box_append(GTK_BOX(self->secondary_controls_section), GTK_WIDGET(self->eq_button)); - } - - if (GTK_IS_VOLUME_BUTTON(self->volume_button)) { - GtkAdjustment * granular_volume_change = gtk_adjustment_new(50.0, 0, 100.0, 1.0, 1.0, 1.0); - g_object_set(self->volume_button, "use-symbolic", TRUE, NULL); - gtk_scale_button_set_adjustment(GTK_SCALE_BUTTON(self->volume_button), granular_volume_change); // Set our adjustment - gtk_box_append(GTK_BOX(self->secondary_controls_section), self->volume_button); - - g_signal_connect(GTK_SCALE_BUTTON(self->volume_button), "value-changed", G_CALLBACK(koto_playerbar_handle_volume_button_change), self); - } -} - -void koto_playerbar_go_backwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - - koto_playback_engine_backwards(playback_engine); -} - -void koto_playerbar_go_forwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - - koto_playback_engine_forwards(playback_engine); -} - -void koto_playerbar_handle_speed_input_activate( - GtkEntry * playback_speed_input, - gpointer user_data -) { - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - GtkEntryBuffer * entry_buffer = gtk_entry_get_buffer(playback_speed_input); // Get the entry buffer - const gchar * text = gtk_entry_buffer_get_text(entry_buffer); // Get the text for the buffer - - gdouble rate = g_strtod(text, NULL); // Attempt to convert - gdouble fixed_rate = koto_playback_engine_get_sane_playback_rate(rate); - - gchar * conv = g_strdup_printf("%f", fixed_rate); - gtk_entry_buffer_set_text(entry_buffer, g_utf8_substring(conv, 0, 4), -1); // Set our entry value to the converted string of the new rate, or if it is something like 2 then it sets it to 2.0 - g_free(conv); - - koto_playback_engine_set_playback_rate(playback_engine, fixed_rate); -} - -void koto_playerbar_handle_is_playing( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - koto_button_show_image(self->play_pause_button, TRUE); // Set to TRUE to show pause as the next action -} - -void koto_playerbar_handle_is_paused( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - koto_button_show_image(self->play_pause_button, FALSE); // Set to FALSE to show play as the next action -} - -void koto_playerbar_handle_playlist_button_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlayerBar * self = data; - - if (!KOTO_IS_PLAYERBAR(self)) { // Not a playerbar - return; - } - - koto_add_remove_track_popover_set_pointing_to_widget(koto_add_remove_track_popup, GTK_WIDGET(self->playlist_button), GTK_POS_TOP); // Position above the playlist button - gtk_widget_show(GTK_WIDGET(koto_add_remove_track_popup)); -} - -void koto_playerbar_handle_progressbar_gesture_begin( - GtkGesture * gesture, - GdkEventSequence * seq, - gpointer data -) { - (void) gesture; - (void) seq; - KotoPlayerBar * self = data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - self->is_progressbar_seeking = TRUE; -} - -void koto_playerbar_handle_progressbar_gesture_end( - GtkGesture * gesture, - GdkEventSequence * seq, - gpointer data -) { - (void) gesture; - (void) seq; - KotoPlayerBar * self = data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - self->is_progressbar_seeking = FALSE; -} - -void koto_playerbar_handle_progressbar_pressed( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlayerBar * self = data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - self->is_progressbar_seeking = TRUE; -} - -void koto_playerbar_handle_progressbar_value_changed( - GtkRange * progress_bar, - gpointer data -) { - KotoPlayerBar * self = data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (!self->is_progressbar_seeking) { - return; - } - - int desired_position = (int) gtk_range_get_value(progress_bar); - - koto_playback_engine_set_position(playback_engine, desired_position); // Update our position -} - -void koto_playerbar_handle_tick_duration( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - koto_playerbar_set_progressbar_duration(self, koto_playback_engine_get_duration(engine)); -} - -void koto_playerbar_handle_tick_track( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (self->is_progressbar_seeking) { // Currently seeking - return; - } - - KotoTrack * current_track = koto_playback_engine_get_current_track(engine); - - gtk_label_set_text( - GTK_LABEL(self->playback_position_label), - g_strdup_printf( - "%s / %s", - koto_utils_seconds_to_time_format(koto_track_get_playback_position(current_track)), - koto_utils_seconds_to_time_format(koto_track_get_duration(current_track)) - )); - - koto_playerbar_set_progressbar_value(self, koto_playback_engine_get_progress(engine)); -} - -void koto_playerbar_handle_track_repeat( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (koto_playback_engine_get_track_repeat(engine)) { // Is repeating - gtk_widget_add_css_class(GTK_WIDGET(self->repeat_button), "active"); // Add active CSS class - } else { // Is not repeating - gtk_widget_remove_css_class(GTK_WIDGET(self->repeat_button), "active"); // Remove active CSS class - } -} - -void koto_playerbar_handle_track_shuffle( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (koto_playback_engine_get_track_shuffle(engine)) { // Is repeating - gtk_widget_add_css_class(GTK_WIDGET(self->shuffle_button), "active"); // Add active CSS class - } else { // Is not repeating - gtk_widget_remove_css_class(GTK_WIDGET(self->shuffle_button), "active"); // Remove active CSS class - } -} - -void koto_playerbar_handle_volume_button_change( - GtkScaleButton * button, - double value, - gpointer user_data -) { - (void) button; - (void) user_data; - koto_playback_engine_set_volume(playback_engine, (double) value / 100); -} - -void koto_playerbar_jump_backwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - - koto_playback_engine_jump_backwards(playback_engine); -} - -void koto_playerbar_jump_forwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - - koto_playback_engine_jump_forwards(playback_engine); -} - -void koto_playerbar_reset_progressbar(KotoPlayerBar * self) { - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (!GTK_IS_RANGE(self->progress_bar)) { - return; - } - - gtk_range_set_range(GTK_RANGE(self->progress_bar), 0, 0); // Reset range - gtk_range_set_value(GTK_RANGE(self->progress_bar), 0); // Set value to 0 -} - -void koto_playerbar_set_progressbar_duration( - KotoPlayerBar * self, - gint64 duration -) { - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (!GTK_IS_RANGE(self->progress_bar)) { - return; - } - - if (duration <= 0) { - return; - } - - if (duration != self->last_recorded_duration) { // Duration is different than what we recorded - self->last_recorded_duration = duration; - gtk_range_set_range(GTK_RANGE(self->progress_bar), 0, self->last_recorded_duration); - } -} - -void koto_playerbar_set_progressbar_value( - KotoPlayerBar * self, - double progress -) { - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - if (!GTK_IS_RANGE(self->progress_bar)) { - return; - } - - gtk_range_set_value(GTK_RANGE(self->progress_bar), progress); -} - -void koto_playerbar_toggle_play_pause( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - - koto_playback_engine_toggle(playback_engine); -} - -void koto_playerbar_toggle_playlist_shuffle( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - - koto_playback_engine_toggle_track_shuffle(playback_engine); // Call our playback engine's toggle track shuffle function -} - -void koto_playerbar_toggle_track_repeat( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - (void) data; - koto_playback_engine_toggle_track_repeat(playback_engine); // Call our playback engine's toggle track repeat function -} - -void koto_playerbar_update_track_info( - KotoPlaybackEngine * engine, - gpointer user_data -) { - if (!KOTO_IS_PLAYBACK_ENGINE(engine)) { - return; - } - - KotoPlayerBar * self = user_data; - - if (!KOTO_IS_PLAYERBAR(self)) { - return; - } - - KotoTrack * current_track = koto_playback_engine_get_current_track(playback_engine); // Get the current track from the playback engine - - if (!KOTO_IS_TRACK(current_track)) { - return; - } - - gchar * track_name = NULL; - gchar * artist_uuid = NULL; - gchar * album_uuid = NULL; - - g_object_get(current_track, "parsed-name", &track_name, "artist-uuid", &artist_uuid, "album-uuid", &album_uuid, NULL); - - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); - - g_free(artist_uuid); - - if (koto_utils_string_is_valid(track_name)) { // Have a track name - gtk_label_set_text(GTK_LABEL(self->playback_title), track_name); // Set the label - } - - if (KOTO_IS_ARTIST(artist)) { - gchar * artist_name = NULL; - g_object_get(artist, "name", &artist_name, NULL); - - if ((artist_name != NULL) && (strcmp(artist_name, "") != 0)) { // Have an artist name - gtk_label_set_text(GTK_LABEL(self->playback_artist), artist_name); - gtk_widget_show(self->playback_artist); - } else { // Don't have an artist name somehow - gtk_widget_hide(self->playback_artist); - } - } - - gchar * art_path = NULL; - - gboolean set_album_label = FALSE; - if (koto_utils_string_is_valid(album_uuid)) { // Have a valid album UUID - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, album_uuid); - g_free(album_uuid); - - if (KOTO_IS_ALBUM(album)) { - gchar * album_name = NULL; - g_object_get(album, "name", &album_name, "art-path", &art_path, NULL); // Get album name and art path - - if ((album_name != NULL) && (strcmp(album_name, "") != 0)) { // Have an album name - gtk_label_set_text(GTK_LABEL(self->playback_album), album_name); - set_album_label = TRUE; - gtk_widget_show(self->playback_album); - } - } - } - - (set_album_label) ? gtk_widget_show(self->playback_album) : gtk_widget_hide(self->playback_album); - - if ((art_path != NULL) && g_path_is_absolute(art_path)) { // Have an album artist path - gtk_image_set_from_file(GTK_IMAGE(self->artwork), art_path); // Update the art - } else { - gtk_image_set_from_icon_name(GTK_IMAGE(self->artwork), "audio-x-generic-symbolic"); // Use generic instead - } -} - -GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self) { - return self->main; -} diff --git a/src/koto-playerbar.h b/src/koto-playerbar.h deleted file mode 100644 index 8abaeb7..0000000 --- a/src/koto-playerbar.h +++ /dev/null @@ -1,196 +0,0 @@ -/* koto-playerbar.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include "playback/engine.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_PLAYERBAR (koto_playerbar_get_type()) -G_DECLARE_FINAL_TYPE(KotoPlayerBar, koto_playerbar, KOTO, PLAYERBAR, GObject) -#define KOTO_IS_PLAYERBAR(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYERBAR)) - -KotoPlayerBar * koto_playerbar_new(void); -GtkWidget * koto_playerbar_get_main(KotoPlayerBar * self); - -void koto_playerbar_apply_configuration_state( - KotoConfig * config, - guint prop_id, - KotoPlayerBar * self -); - -void koto_playerbar_create_playback_details(KotoPlayerBar * self); - -void koto_playerbar_create_primary_controls(KotoPlayerBar * self); - -void koto_playerbar_create_secondary_controls(KotoPlayerBar * self); - -void koto_playerbar_go_backwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_go_forwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_handle_is_playing( - KotoPlaybackEngine * engine, - gpointer user_data -); - -void koto_playerbar_handle_is_paused( - KotoPlaybackEngine * engine, - gpointer user_data -); - -void koto_playerbar_handle_playlist_button_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_handle_progressbar_scroll_begin( - GtkEventControllerScroll * controller, - gpointer data -); - -void koto_playerbar_handle_progressbar_gesture_begin( - GtkGesture * gesture, - GdkEventSequence * seq, - gpointer data -); - -void koto_playerbar_handle_progressbar_gesture_end( - GtkGesture * gesture, - GdkEventSequence * seq, - gpointer data -); - -void koto_playerbar_handle_progressbar_pressed( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_handle_progressbar_value_changed( - GtkRange * progress_bar, - gpointer data -); - -void koto_playerbar_handle_speed_input_activate( - GtkEntry * playback_speed_input, - gpointer user_data -); - -void koto_playerbar_handle_tick_duration( - KotoPlaybackEngine * engine, - gpointer user_data -); - -void koto_playerbar_handle_tick_track( - KotoPlaybackEngine * engine, - gpointer user_data -); - -void koto_playerbar_handle_track_repeat( - KotoPlaybackEngine * engine, - gpointer user_data -); - -void koto_playerbar_handle_track_shuffle( - KotoPlaybackEngine * engine, - gpointer user_data -); - -void koto_playerbar_handle_volume_button_change( - GtkScaleButton * button, - double value, - gpointer user_data -); - -void koto_playerbar_jump_backwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_jump_forwards( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_reset_progressbar(KotoPlayerBar * self); - -void koto_playerbar_set_progressbar_duration( - KotoPlayerBar * self, - gint64 duration -); - -void koto_playerbar_set_progressbar_value( - KotoPlayerBar * self, - gdouble progress -); - -void koto_playerbar_toggle_play_pause( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_toggle_playlist_shuffle( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_toggle_track_repeat( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_playerbar_update_track_info( - KotoPlaybackEngine * engine, - gpointer user_data -); - -G_END_DECLS diff --git a/src/koto-utils.c b/src/koto-utils.c deleted file mode 100644 index 3a536f5..0000000 --- a/src/koto-utils.c +++ /dev/null @@ -1,254 +0,0 @@ -/* koto-utils.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include "koto-utils.h" - -extern GtkWindow * main_window; - -GtkFileChooserNative * koto_utils_create_image_file_chooser(gchar * file_chooser_label) { - GtkFileChooserNative * chooser = gtk_file_chooser_native_new( - file_chooser_label, - main_window, - GTK_FILE_CHOOSER_ACTION_OPEN, - "Choose", - "Cancel" - ); - - GtkFileFilter * image_filter = gtk_file_filter_new(); // Create our file filter - - gtk_file_filter_add_mime_type(image_filter, "image/*"); // Only allow for images - gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(chooser), image_filter); // Only allow picking images - gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(chooser), FALSE); - - return chooser; -} - -GtkWidget * koto_utils_create_image_from_filepath( - gchar * filepath, - gchar * fallback_icon, - guint width, - guint height -) { - GtkWidget * image = NULL; - - if ((filepath != NULL) && (strcmp(filepath, "") != 0)) { // If we have a filepath - if (g_file_test(filepath, G_FILE_TEST_EXISTS)) { // File exists - image = gtk_image_new_from_file(filepath); // Load from the filepath - } - } - - if (!GTK_IS_IMAGE(image)) { // If we failed to get the image or never passed a valid filepath to begin with - image = gtk_image_new_from_icon_name(fallback_icon); // Set to the fallback icon - } - - gtk_image_set_icon_size(GTK_IMAGE(image), GTK_ICON_SIZE_INHERIT); - gtk_image_set_pixel_size(GTK_IMAGE(image), width); - gtk_widget_set_size_request(image, width, height); - - return image; -} - -gchar * koto_utils_gboolean_to_string(gboolean b) { - return g_strdup(b ? "true" : "false"); -} - -gchar * koto_utils_get_filename_without_extension(gchar * filename) { - gchar * trimmed_file_name = g_strdup(g_path_get_basename(filename)); // Ensure the filename provided is the base name of any possible path and duplicate it - gchar ** split = g_strsplit(trimmed_file_name, ".", -1); // Split every time we see . - - g_free(trimmed_file_name); - guint len_of_extension_split = g_strv_length(split); - - if (len_of_extension_split == 2) { // Only have two elements - trimmed_file_name = g_strdup(split[0]); // Get the first element - } else { - gchar * new_parsed_name = ""; - for (guint i = 0; i < len_of_extension_split - 1; i++) { // Iterate over everything except the last item - if (g_strcmp0(new_parsed_name, "") == 0) { // Currently empty - new_parsed_name = g_strdup(split[i]); // Just duplicate this string - } else { - gchar * tmp_copy = g_strdup(new_parsed_name); - g_free(new_parsed_name); // Free the old - new_parsed_name = g_strjoin(".", tmp_copy, split[i], NULL); // Join the two strings with a . again and duplicate it, setting it to our new_parsed_name - g_free(tmp_copy); // Free our temporary copy - } - } - - trimmed_file_name = g_strdup(new_parsed_name); - g_free(new_parsed_name); - } - - gchar * stripped_file_name = g_strstrip(g_strdup(trimmed_file_name)); // Strip leading and trailing whitespace - - g_free(trimmed_file_name); - g_strfreev(split); - return stripped_file_name; -} - -gchar * koto_utils_join_string_list ( - GList * list, - gchar * sep -) { - gchar * liststring = NULL; - GList * cur_list; - for (cur_list = list; cur_list != NULL; cur_list = cur_list->next) { // For each item in the list - gchar * current_item = cur_list->data; - if (!koto_utils_string_is_valid(current_item)) { // Not a valid string - continue; - } - - gchar * item_plus_sep = g_strdup_printf("%s%s", current_item, sep); - - if (koto_utils_string_is_valid(liststring)) { // Is a valid string - gchar * new_string = g_strconcat(liststring, item_plus_sep, NULL); - g_free(liststring); - liststring = new_string; - } else { // Don't have any content yet - liststring = item_plus_sep; - } - } - - return (liststring == NULL) ? g_strdup("") : liststring; -} -void koto_utils_mkdir(gchar * path) { - mkdir(path, 0755); - chown(path, getuid(), getgid()); -} - -void koto_utils_push_queue_element_to_store( - gpointer data, - gpointer user_data -) { - g_list_store_append(G_LIST_STORE(user_data), data); -} - -gchar * koto_utils_seconds_to_time_format(guint64 seconds) { - GDateTime * date = g_date_time_new_from_unix_utc(seconds); // Add our seconds after UTC - gchar * date_string = g_date_time_format(date, (g_date_time_get_hour(date) != 0) ? "%H:%M:%S" : "%M:%S"); - g_date_time_unref(date); - return date_string; -} - -gboolean koto_utils_string_contains_substring( - gchar * s, - gchar * sub -) { - gchar ** separated_string = g_strsplit(s, sub, -1); // Split on our substring - gboolean contains = (g_strv_length(separated_string) > 1); - g_strfreev(separated_string); - return contains; -} - -gchar * koto_utils_string_get_valid(gchar * str) { - return koto_utils_string_is_valid(str) ? str : g_strdup(""); // Return string if a string, otherwise return an empty string -} - -gboolean koto_utils_string_is_valid(const gchar * str) { - return ((str != NULL) && (g_strcmp0(str, "") != 0)); -} - -gchar * koto_utils_string_replace_all( - gchar * str, - gchar * find, - gchar * repl -) { - if (!koto_utils_string_is_valid(str)) { // Not a valid string - return g_strdup(""); - } - - gchar ** split = g_strsplit(str, find, -1); // Split on find - - guint split_len = g_strv_length(split); - - if (split_len == 1) { // Only one item - g_strfreev(split); - return g_strdup(str); // Just set to the string we were provided - } - - return g_strdup(g_strjoinv(repl, split)); -} - -GList * koto_utils_string_to_string_list( - gchar * s, - gchar * sep -) { - GList * list = NULL; - if (!koto_utils_string_is_valid(s)) { // Provided string is not valid - return list; - } - - gchar ** separated_strings = g_strsplit(s, sep, -1); // Split on separator for the string - - for (guint i = 0; i < g_strv_length(separated_strings); i++) { // Iterate over each item - gchar * item = separated_strings[i]; - if (g_strcmp0(item, "") != 0) { // Not an empty string - list = g_list_append(list, g_strdup(item)); - } - } - - g_strfreev(separated_strings); // Free our strings - return list; -} - -gchar * koto_utils_string_title(const gchar * s) { - if (!koto_utils_string_is_valid(s)) { // Not a valid string - return NULL; - } - - glong len = g_utf8_strlen(s, -1); - - if (len == 0) { // Empty string - return g_strdup(s); // Just duplicate itself - } else if (len == 1) { // One char - return g_utf8_strup(s, -1); // Uppercase all relevant cases - } else { - gchar * first_char = g_utf8_substring(s, 0, 1); - gchar * rest_of_string = g_utf8_substring(s, 1, len); // Rest of string - gchar * titled_string = g_strdup_printf("%s%s", g_utf8_strup(first_char, -1), rest_of_string); - - g_free(first_char); - g_free(rest_of_string); - return titled_string; - } -} - -gchar * koto_utils_string_unquote(gchar * s) { - gchar * new_s = NULL; - - if (!koto_utils_string_is_valid(s)) { // Not a valid string - new_s = g_strdup(""); - return new_s; - } - - if (g_str_has_prefix(s, "'") && g_str_has_suffix(s, "'")) { // Begins and ends with ' - new_s = g_utf8_substring(s, 1, g_utf8_strlen(s, -1) - 1); // Start at 1 and end at n-1 - } else { - new_s = g_strdup(s); - } - - gchar ** split_on_double_single = g_strsplit(new_s, "''", -1); // Split on instances of '' - - new_s = g_strjoinv("'", split_on_double_single); // Rejoin as ' - g_strfreev(split_on_double_single); // Free our array - - return new_s; -} diff --git a/src/koto-utils.h b/src/koto-utils.h deleted file mode 100644 index 131fe36..0000000 --- a/src/koto-utils.h +++ /dev/null @@ -1,75 +0,0 @@ -/* koto-utils.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -G_BEGIN_DECLS - -GtkFileChooserNative * koto_utils_create_image_file_chooser(gchar * file_chooser_label); - -GtkWidget * koto_utils_create_image_from_filepath( - gchar * filepath, - gchar * fallback_icon, - guint width, - guint height -); - -gchar * koto_utils_gboolean_to_string(gboolean b); - -gchar * koto_utils_get_filename_without_extension(gchar * filename); - -gchar * koto_utils_join_string_list( - GList * list, - gchar * sep -); - -void koto_utils_mkdir(gchar * path); - -void koto_utils_push_queue_element_to_store( - gpointer data, - gpointer user_data -); - -gchar * koto_utils_seconds_to_time_format(guint64 seconds); - -gboolean koto_utils_string_contains_substring( - gchar * s, - gchar * sub -); - -gchar * koto_utils_string_get_valid(gchar * str); - -gboolean koto_utils_string_is_valid(const gchar * str); - -gchar * koto_utils_string_replace_all( - gchar * str, - gchar * find, - gchar * repl -); - -GList * koto_utils_string_to_string_list( - gchar * s, - gchar * sep -); - -gchar * koto_utils_string_title(const gchar * s); - -gchar * koto_utils_string_unquote(gchar * s); - -G_END_DECLS diff --git a/src/koto-window.c b/src/koto-window.c deleted file mode 100644 index dcf014d..0000000 --- a/src/koto-window.c +++ /dev/null @@ -1,332 +0,0 @@ -/* koto-window.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "components/action-bar.h" -#include "db/cartographer.h" -#include "indexer/structs.h" -#include "pages/audiobooks/library.h" -#include "pages/music/music-local.h" -#include "pages/playlist/list.h" -#include "playback/engine.h" -#include "playlist/add-remove-track-popover.h" -#include "playlist/create-modify-dialog.h" -#include "config/config.h" -#include "koto-dialog-container.h" -#include "koto-nav.h" -#include "koto-playerbar.h" -#include "koto-paths.h" -#include "koto-window.h" - -extern KotoActionBar * action_bar; -extern KotoAddRemoveTrackPopover * koto_add_remove_track_popup; -extern KotoAudiobooksLibraryPage * audiobooks_library_page; -extern KotoCartographer * koto_maps; -extern KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog; -extern KotoConfig * config; -extern KotoPageMusicLocal * music_local_page; - -extern gchar * koto_rev_dns; - -struct _KotoWindow { - GtkApplicationWindow parent_instance; - KotoLibrary * library; - - KotoDialogContainer * dialogs; - - GtkCssProvider * provider; - - GtkWidget * overlay; - GtkWidget * header_bar; - GtkWidget * menu_button; - GtkWidget * search_entry; - - GtkWidget * primary_layout; - GtkWidget * content_layout; - - KotoNav * nav; - GtkWidget * pages; - KotoPlayerBar * player_bar; -}; - -G_DEFINE_TYPE(KotoWindow, koto_window, GTK_TYPE_APPLICATION_WINDOW) - -static void koto_window_class_init (KotoWindowClass * klass) { - (void) klass; -} - -static void koto_window_init (KotoWindow * self) { - koto_window_manage_style(config, 0, self); // Immediately apply the theme - g_signal_connect(config, "notify::ui-theme-desired", G_CALLBACK(koto_window_manage_style), self); // Handle changes to desired theme - g_signal_connect(config, "notify::ui-theme-override", G_CALLBACK(koto_window_manage_style), self); // Handle changes to theme overriding - - create_new_headerbar(self); // Create our headerbar - - self->overlay = gtk_overlay_new(); // Create our overlay - self->dialogs = koto_dialog_container_new(); // Create our dialog container - - self->primary_layout = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->primary_layout, "primary-layout"); - gtk_widget_set_hexpand(self->primary_layout, TRUE); - gtk_widget_set_vexpand(self->primary_layout, TRUE); - - playlist_create_modify_dialog = koto_create_modify_playlist_dialog_new(); // Create our Create Playlist dialog - koto_dialog_container_add_dialog(self->dialogs, "create-modify-playlist", GTK_WIDGET(playlist_create_modify_dialog)); - - gtk_overlay_set_child(GTK_OVERLAY(self->overlay), self->primary_layout); // Add our primary layout to the overlay - gtk_overlay_add_overlay(GTK_OVERLAY(self->overlay), GTK_WIDGET(self->dialogs)); // Add the stack as our overlay - - self->content_layout = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->content_layout, "content-layout"); - gtk_widget_set_hexpand(self->content_layout, TRUE); - gtk_widget_set_vexpand(self->content_layout, TRUE); - - self->nav = koto_nav_new(); - - if (self->nav != NULL) { - gtk_box_prepend(GTK_BOX(self->content_layout), koto_nav_get_nav(self->nav)); - } - - self->pages = gtk_stack_new(); // New stack to hold our pages - - if (GTK_IS_STACK(self->pages)) { // Created our stack successfully - gtk_stack_set_transition_type(GTK_STACK(self->pages), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT); - gtk_box_append(GTK_BOX(self->content_layout), self->pages); - gtk_widget_set_hexpand(self->pages, TRUE); - gtk_widget_set_vexpand(self->pages, TRUE); - } - - gtk_box_prepend(GTK_BOX(self->primary_layout), self->content_layout); - - koto_add_remove_track_popup = koto_add_remove_track_popover_new(); // Create our popover for adding and removing tracks - action_bar = koto_action_bar_new(); // Create our Koto Action Bar - - if (KOTO_IS_ACTION_BAR(action_bar)) { // Is an action bar - GtkActionBar * bar = koto_action_bar_get_main(action_bar); - - if (GTK_IS_ACTION_BAR(bar)) { - gtk_box_append(GTK_BOX(self->primary_layout), GTK_WIDGET(bar)); // Add the action - } - } - - self->player_bar = koto_playerbar_new(); - - if (KOTO_IS_PLAYERBAR(self->player_bar)) { // Is a playerbar - GtkWidget * playerbar_main = koto_playerbar_get_main(self->player_bar); - gtk_box_append(GTK_BOX(self->primary_layout), playerbar_main); - } - - gtk_window_set_child(GTK_WINDOW(self), self->overlay); -#ifdef GDK_WINDOWING_X11 - set_optimal_default_window_size(self); -#endif - gtk_window_set_title(GTK_WINDOW(self), "Koto"); - gtk_window_set_icon_name(GTK_WINDOW(self), "audio-headphones"); - gtk_window_set_startup_id(GTK_WINDOW(self), koto_rev_dns); - - gtk_widget_queue_draw(self->content_layout); - - audiobooks_library_page = koto_audiobooks_library_page_new(); // Create our audiobooks library page - music_local_page = koto_page_music_local_new(); - - // TODO: Remove and do some fancy state loading - koto_window_add_page(self, "audiobooks.library", GTK_WIDGET(koto_audiobooks_library_page_get_main(audiobooks_library_page))); - koto_window_add_page(self, "music.library", GTK_WIDGET(music_local_page)); - koto_window_go_to_page(self, "audiobooks.library"); - gtk_widget_show(self->pages); // Do not remove this. Will cause sporadic hiding of the local page content otherwise. -} - -void koto_window_add_page( - KotoWindow * self, - gchar * page_name, - GtkWidget * page -) { - if (!KOTO_IS_WINDOW(self)) { - return; - } - - if (!GTK_IS_STACK(self->pages)) { - return; - } - - if (!GTK_IS_WIDGET(page)) { - return; - } - - gtk_stack_add_named(GTK_STACK(self->pages), page, page_name); -} - -void koto_window_manage_style( - KotoConfig * c, - guint prop_id, - KotoWindow * self -) { - (void) prop_id; - - if (!KOTO_IS_WINDOW(self)) { // Not a Koto Window - return; - } - - gchar * desired_theme = NULL; - gboolean overriding_theme = FALSE; - g_object_get( - c, - "ui-theme-desired", - &desired_theme, - "ui-theme-override", - &overriding_theme, - NULL - ); - - if (!koto_utils_string_is_valid(desired_theme)) { // Theme not valid - desired_theme = "dark"; - } - - if (!GTK_IS_CSS_PROVIDER(self->provider)) { // Don't have a CSS provider yet - self->provider = gtk_css_provider_new(); - } - - gtk_css_provider_load_from_resource(self->provider, g_strdup_printf("/com/github/joshstrobl/koto/koto-builtin-%s.css", desired_theme)); - - if (!overriding_theme) { // If we are not overriding the theme - gtk_style_context_add_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(self->provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - - GList * themes = NULL; - themes = g_list_append(themes, "dark"); - themes = g_list_append(themes, "gruvbox"); - themes = g_list_append(themes, "light"); - - GList * themes_head; - - for (themes_head = themes; themes_head != NULL; themes_head = themes_head->next) { // For each theme - gchar * theme_class_name = g_strdup_printf("koto-theme-%s", (gchar*) themes->data); // Get the theme - - if (g_strcmp0((gchar*) themes->data, desired_theme) == 0) { // If we are using this theme - gtk_widget_add_css_class(GTK_WIDGET(self), theme_class_name); // Add the CSS class - } else { - gtk_widget_remove_css_class(GTK_WIDGET(self), theme_class_name); // Remove the CSS class - } - - g_free(theme_class_name); // Free the CSS class - } - - g_list_free(themes_head); - g_list_free(themes); - } else { // Overriding the built-in theme - gtk_style_context_remove_provider_for_display(gdk_display_get_default(), GTK_STYLE_PROVIDER(self->provider)); // Remove the provider - } -} - -void koto_window_go_to_page( - KotoWindow * self, - gchar * page_name -) { - gtk_stack_set_visible_child_name(GTK_STACK(self->pages), page_name); -} - -void koto_window_handle_playlist_added( - KotoCartographer * carto, - KotoPlaylist * playlist, - gpointer user_data -) { - (void) carto; - - if (!KOTO_IS_PLAYLIST(playlist)) { - return; - } - - KotoWindow * self = user_data; - - gchar * playlist_uuid = koto_playlist_get_uuid(playlist); - KotoPlaylistPage * playlist_page = koto_playlist_page_new(playlist_uuid); // Create our new Playlist Page - - koto_window_add_page(self, playlist_uuid, koto_playlist_page_get_main(playlist_page)); // Get the GtkScrolledWindow "main" content of the playlist page and add that as a page to our stack by the playlist UUID -} - -void koto_window_hide_dialogs(KotoWindow * self) { - koto_dialog_container_hide(self->dialogs); // Hide the dialog container -} - -void koto_window_remove_page( - KotoWindow * self, - gchar * page_name -) { - GtkWidget * page = gtk_stack_get_child_by_name(GTK_STACK(self->pages), page_name); - - if (GTK_IS_WIDGET(page)) { - gtk_stack_remove(GTK_STACK(self->pages), page); - } -} - -void koto_window_show_dialog( - KotoWindow * self, - gchar * dialog_name -) { - koto_dialog_container_show_dialog(self->dialogs, dialog_name); -} - -void create_new_headerbar(KotoWindow * self) { - self->header_bar = gtk_header_bar_new(); - gtk_widget_add_css_class(self->header_bar, "hdr"); - g_return_if_fail(GTK_IS_HEADER_BAR(self->header_bar)); - - self->menu_button = gtk_button_new_from_icon_name("audio-headphones"); - - self->search_entry = gtk_search_entry_new(); - gtk_widget_add_css_class(self->menu_button, "flat"); - gtk_widget_set_can_focus(self->search_entry, TRUE); - gtk_widget_set_size_request(self->search_entry, 400, -1); // Have 400px width - g_object_set(self->search_entry, "placeholder-text", "Search...", NULL); - - gtk_header_bar_pack_start(GTK_HEADER_BAR(self->header_bar), self->menu_button); - gtk_header_bar_set_show_title_buttons(GTK_HEADER_BAR(self->header_bar), TRUE); - gtk_header_bar_set_title_widget(GTK_HEADER_BAR(self->header_bar), self->search_entry); - - gtk_window_set_titlebar(GTK_WINDOW(self), self->header_bar); -} - -void set_optimal_default_window_size(KotoWindow * self) { - GdkDisplay * default_display = gdk_display_get_default(); - - if (!GDK_IS_X11_DISPLAY(default_display)) { // Not an X11 display - return; - } - - GdkMonitor * default_monitor = gdk_x11_display_get_primary_monitor(GDK_X11_DISPLAY(default_display)); // Get primary monitor for the X11 - - if (!GDK_IS_X11_MONITOR(default_monitor)) { // Not an X11 Monitor - return; - } - - GdkRectangle workarea = { - 0 - }; - - gdk_monitor_get_geometry(default_monitor, &workarea); - - if (workarea.width <= 1280) { // Honestly how do you even get anything done? - gtk_widget_set_size_request(GTK_WIDGET(self), 1200, 675); - } else if ((workarea.width > 1280) && (workarea.width <= 1600)) { // Plebian monitor resolution - gtk_widget_set_size_request(GTK_WIDGET(self), 1300, 709); - } else if ((workarea.width > 1600) && (workarea.width <= 1920)) { // Something slightly normal - gtk_widget_set_size_request(GTK_WIDGET(self), 1600, 900); - } else if ((workarea.width > 1920) && (workarea.width <= 2560)) { // Well aren't you hot stuff? - gtk_widget_set_size_request(GTK_WIDGET(self), 1920, 1080); - } else { // Now you're just flexing - gtk_widget_set_size_request(GTK_WIDGET(self), 2560, 1440); - } -} diff --git a/src/koto-window.h b/src/koto-window.h deleted file mode 100644 index 30896a2..0000000 --- a/src/koto-window.h +++ /dev/null @@ -1,74 +0,0 @@ -/* koto-window.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include "config/config.h" -#include "db/cartographer.h" -#include "playlist/playlist.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_WINDOW (koto_window_get_type()) - -G_DECLARE_FINAL_TYPE(KotoWindow, koto_window, KOTO, WINDOW, GtkApplicationWindow) - -void koto_window_add_page( - KotoWindow * self, - gchar * page_name, - GtkWidget * page -); - -void koto_window_go_to_page( - KotoWindow * self, - gchar * page_name -); - -void koto_window_handle_playlist_added( - KotoCartographer * carto, - KotoPlaylist * playlist, - gpointer user_data -); - -void koto_window_manage_style( - KotoConfig * config, - guint prop_id, - KotoWindow * self -); - -void koto_window_hide_dialogs(KotoWindow * self); - -void koto_window_remove_page( - KotoWindow * self, - gchar * page_name -); - -void koto_window_show_dialog( - KotoWindow * self, - gchar * dialog_name -); - -void create_new_headerbar(KotoWindow * self); - -void handle_album_added(); - -void load_library(KotoWindow * self); - -void set_optimal_default_window_size(KotoWindow * self); - -G_END_DECLS diff --git a/src/koto.gresource.xml b/src/koto.gresource.xml deleted file mode 100644 index daa9ffe..0000000 --- a/src/koto.gresource.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - ../data/genres/business-and-personal-finance.png - ../data/genres/foreign-languages.png - ../data/vectors/multimedia-backwards-jump.svg - ../data/vectors/multimedia-forwards-jump.svg - ../data/genres/mystery-and-thriller.png - ../data/genres/sci-fi.png - ../data/genres/travel.png - ../theme/koto-builtin-dark.css - ../theme/koto-builtin-gruvbox.css - ../theme/koto-builtin-light.css - - diff --git a/src/main.c b/src/main.c deleted file mode 100644 index 01e9637..0000000 --- a/src/main.c +++ /dev/null @@ -1,131 +0,0 @@ -/* main.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include -#include -#include -#include -#include "config/config.h" -#include "db/cartographer.h" -#include "db/db.h" -#include "db/loaders.h" -#include "indexer/track-helpers.h" -#include "playback/engine.h" -#include "playback/media-keys.h" -#include "playback/mimes.h" -#include "playback/mpris.h" -#include "paths.h" -#include "playlist/current.h" - -#include "config/config.h" -#include "koto-paths.h" -#include "koto-window.h" - -extern KotoConfig * config; - -extern guint mpris_bus_id; -extern GDBusNodeInfo * introspection_data; - -extern KotoPlaybackEngine * playback_engine; -extern KotoCartographer * koto_maps; -extern KotoCurrentPlaylist * current_playlist; -extern sqlite3 * koto_db; - -extern GHashTable * supported_mimes_hash; -extern GList * supported_mimes; - -extern gchar * koto_path_to_conf; -extern gchar * koto_rev_dns; - -extern gboolean created_new_db; - -GVolumeMonitor * volume_monitor = NULL; -GtkApplication * app = NULL; -GtkWindow * main_window; -magic_t magic_cookie; - -static void on_activate (GtkApplication * app) { - g_assert(GTK_IS_APPLICATION(app)); - - main_window = gtk_application_get_active_window(app); - if (main_window == NULL) { - main_window = g_object_new(KOTO_TYPE_WINDOW, "application", app, "default-width", 1200, "default-height", 675, NULL); - setup_mpris_interfaces(); // Set up our MPRIS interfaces - setup_mediakeys_interface(); // Set up our media key support - - if (!created_new_db) { - read_from_db(); // Read the database, allowing us to propagate the UI with various data such as artists and playlist navigation elements - } - } - - gtk_window_present(main_window); -} - -static void on_shutdown(GtkApplication * app) { - (void) app; - koto_current_playlist_save_playlist_state(current_playlist); // Save the current playlist state if necessary before closure - koto_config_save(config); // Save our config - close_db(); // Close the database - g_bus_unown_name(mpris_bus_id); - g_dbus_node_info_unref(introspection_data); -} - -int main ( - int argc, - char * argv[] -) { - int ret; - - gtk_init(); - gst_init(&argc, &argv); - - koto_track_helpers_init(); // Init our track helpers (primarily our genre replacement hashtable) - koto_paths_setup(); // Set up our required paths - - supported_mimes_hash = g_hash_table_new(g_str_hash, g_str_equal); - supported_mimes = NULL; // Ensure our mimes GList is initialized - koto_playback_engine_get_supported_mimetypes(supported_mimes); - - koto_maps = koto_cartographer_new(); // Create our new cartographer and their collection of maps - - volume_monitor = g_volume_monitor_get(); // Get a VolumeMonitor - - config = koto_config_new(); // Set our config - koto_config_load(config, koto_path_to_conf); - playback_engine = koto_playback_engine_new(); // Initialize the engine now that the config is available, since it listens on various config signals - - taglib_id3v2_set_default_text_encoding(TagLib_ID3v2_UTF8); // Ensure our id3v2 text encoding is UTF-8 - magic_cookie = magic_open(MAGIC_MIME); - - if (magic_cookie == NULL) { // Failed to open - g_critical("Failed to allocate a cookie pointer from libmagic."); - } - - if (magic_load(magic_cookie, NULL) != 0) { // Failed to load data - magic_close(magic_cookie); - g_critical("Failed to load the system magic database."); - } - - open_db(); // Open our database - g_thread_new("indexing-any-necessary-libs", (void*) koto_config_load_libs, config); // Load our libraries, now that our database is set up. Note that read_from_db is called in koto-window.c - - app = gtk_application_new(koto_rev_dns, G_APPLICATION_FLAGS_NONE); - g_signal_connect(app, "activate", G_CALLBACK(on_activate), NULL); - g_signal_connect(app, "shutdown", G_CALLBACK(on_shutdown), NULL); - ret = g_application_run(G_APPLICATION(app), argc, argv); - - return ret; -} diff --git a/src/meson.build b/src/meson.build deleted file mode 100644 index 01ac94b..0000000 --- a/src/meson.build +++ /dev/null @@ -1,75 +0,0 @@ -add_global_arguments([ - '-I' + meson.current_build_dir(), - '-Db_sanitize=address', - '-Dwerror=true', -], language: 'c') - -koto_sources = [ - 'components/album-info.c', - 'components/action-bar.c', - 'components/button.c', - 'components/cover-art-button.c', - 'components/track-item.c', - 'components/track-table.c', - 'config/config.c', - 'db/cartographer.c', - 'db/db.c', - 'db/loaders.c', - 'indexer/album.c', - 'indexer/artist.c', - 'indexer/file-indexer.c', - 'indexer/library.c', - 'indexer/track-helpers.c', - 'indexer/track.c', - 'pages/audiobooks/audiobook-view.c', - 'pages/audiobooks/genres-banner.c', - 'pages/audiobooks/genre-button.c', - 'pages/audiobooks/library.c', - 'pages/audiobooks/writer-page.c', - 'pages/music/album-view.c', - 'pages/music/artist-view.c', - 'pages/music/disc-view.c', - 'pages/music/music-local.c', - 'pages/playlist/list.c', - 'playback/engine.c', - 'playback/media-keys.c', - 'playback/mimes.c', - 'playback/mpris.c', - 'playlist/add-remove-track-popover.c', - 'playlist/create-modify-dialog.c', - 'playlist/current.c', - 'playlist/playlist.c', - 'main.c', - 'koto-dialog-container.c', - 'koto-expander.c', - 'koto-nav.c', - 'koto-playerbar.c', - 'koto-paths.c', - 'koto-utils.c', - 'koto-window.c', -] - -koto_deps = [ - dependency('glib-2.0', version: '>= 2.66'), - dependency('gio-2.0', version: '>= 2.66'), - dependency('gstreamer-1.0', version: '>= 1.18'), - dependency('gstreamer-player-1.0', version: '>= 1.18'), - dependency('gtk4', version: '>= 4.0'), - dependency('libmagic', version: '>=5.39'), - dependency('sqlite3', version: '>=3.34'), - dependency('taglib_c', version: '>=1.11'), - toml_dep, -] - -gnome = import('gnome') - -koto_sources += gnome.compile_resources('koto-resources', - 'koto.gresource.xml', - dependencies: themes, - c_name: 'koto', -) - -executable('com.github.joshstrobl.koto', koto_sources, - dependencies: koto_deps, - install: true, -) diff --git a/src/pages/audiobooks/audiobook-view.c b/src/pages/audiobooks/audiobook-view.c deleted file mode 100644 index be2b1bc..0000000 --- a/src/pages/audiobooks/audiobook-view.c +++ /dev/null @@ -1,255 +0,0 @@ -/* audiobook-view.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/album-info.h" -#include "../../components/button.h" -#include "../../components/track-item.h" -#include "../../db/cartographer.h" -#include "../../indexer/album-playlist-funcs.h" -#include "../../indexer/structs.h" -#include "../../playlist/current.h" -#include "../../koto-utils.h" -#include "../../koto-window.h" -#include "audiobook-view.h" - -extern KotoCartographer * koto_maps; -extern KotoCurrentPlaylist * current_playlist; -extern KotoWindow * main_window; - -struct _KotoAudiobookView { - GtkBox parent_instance; - - KotoAlbum * album; // Album associated with this view - - GtkWidget * side_info; // Our side info (artwork, playback button, position) - GtkWidget * info_contents; // Our info and contents vertical box - - KotoAlbumInfo * album_info; // Our "Album" (Audiobook) info - - GtkWidget * audiobook_art; // Our GtkImage for the audiobook art - GtkWidget * playback_button; // Our GtkButton for playback - GtkWidget * chapter_info; // Our GtkLabel of the position, effectively - GtkWidget * playback_position; // Our GtkLabel for the track playback position - - GtkWidget * tracks_list; // GtkListBox of tracks -}; - -struct _KotoAudiobookViewClass { - GtkBoxClass parent_class; -}; - -G_DEFINE_TYPE(KotoAudiobookView, koto_audiobook_view, GTK_TYPE_BOX); - -static void koto_audiobook_view_class_init(KotoAudiobookViewClass * c) { - (void) c; -} - -static void koto_audiobook_view_init(KotoAudiobookView * self) { - gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-view"); - - self->side_info = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->side_info, "side-info"); - gtk_widget_set_size_request(self->side_info, 220, -1); // Match audiobook art size - - self->info_contents = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - self->audiobook_art = gtk_image_new_from_icon_name("audio-x-generic-symbolic"); // Create an image with a symbolic icon - gtk_widget_set_size_request(self->audiobook_art, 220, 220); // Set to 220 like our cover art button - - self->playback_button = gtk_button_new_with_label("Play"); // Default to our button saying Play instead of continue - gtk_widget_add_css_class(self->playback_button, "suggested-action"); - g_signal_connect(self->playback_button, "clicked", G_CALLBACK(koto_audiobook_view_handle_play_clicked), self); - - self->chapter_info = gtk_label_new(NULL); - self->playback_position = gtk_label_new(NULL); - - gtk_widget_set_halign(self->chapter_info, GTK_ALIGN_START); - gtk_widget_set_halign(self->playback_position, GTK_ALIGN_START); - - gtk_box_append(GTK_BOX(self->side_info), self->audiobook_art); // Add our audiobook art to the side info - gtk_box_append(GTK_BOX(self->side_info), GTK_WIDGET(self->playback_button)); // Add our playback button to the side info - gtk_box_append(GTK_BOX(self->side_info), self->chapter_info); - gtk_box_append(GTK_BOX(self->side_info), self->playback_position); - - self->album_info = koto_album_info_new("audiobook"); // Create an "audiobook" type KotoAlbumInfo - gtk_box_append(GTK_BOX(self->info_contents), GTK_WIDGET(self->album_info)); // Add the album info the info contents - - GtkWidget * chapters_label = gtk_label_new("Chapters"); - gtk_widget_add_css_class(chapters_label, "chapters-label"); - gtk_widget_set_halign(chapters_label, GTK_ALIGN_START); // Align to the start - - gtk_box_append(GTK_BOX(self->info_contents), chapters_label); - - self->tracks_list = gtk_list_box_new(); // Create our list of our tracks - gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->tracks_list), FALSE); - gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->tracks_list), GTK_SELECTION_MULTIPLE); - gtk_widget_add_css_class(self->tracks_list, "track-list"); - gtk_box_append(GTK_BOX(self->info_contents), self->tracks_list); // Add our listbox to the info contents - - gtk_box_append(GTK_BOX(self), self->side_info); // Add our side info to the box - gtk_box_append(GTK_BOX(self), self->info_contents); // Add our info contents to the box -} - -GtkWidget * koto_audiobook_view_create_track_item( - gpointer item, - gpointer user_data -) { - (void) user_data; - - if (!KOTO_IS_TRACK(item)) { // Not actually a track - return NULL; - } - - KotoTrack * track = KOTO_TRACK(item); // Cast our item as a track - KotoTrackItem * track_item = koto_track_item_new(track); // Create our track item - - if (!KOTO_IS_TRACK_ITEM(track_item)) { // Not a track item - return NULL; - } - - return GTK_WIDGET(track_item); // Cast as a widget and return the track item -} - -void koto_audiobook_view_handle_play_clicked( - GtkButton * button, - gpointer user_data -) { - (void) button; - KotoAudiobookView * self = user_data; - - if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { - return; - } - - if (!KOTO_IS_ALBUM(self->album)) { // Don't have an album associated with this view - return; - } - - koto_playlist_commit(koto_album_get_playlist(self->album)); // Ensure we commit the current playlist to the database. This is needed to ensure we are able to save the playlist state going forward - koto_current_playlist_set_playlist(current_playlist, koto_album_get_playlist(self->album), TRUE, TRUE); // Set this playlist to be played immediately, play current track -} - -void koto_audiobook_view_handle_playlist_updated( - KotoPlaylist * playlist, - gpointer user_data -) { - (void) playlist; - - KotoAudiobookView * self = user_data; - - if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { - return; - } - - koto_audiobook_view_update_side_info(self); // Update the side info based on the playlist modification -} - -void koto_audiobook_view_set_album( - KotoAudiobookView * self, - KotoAlbum * album -) { - if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { - return; - } - - if (!KOTO_IS_ALBUM(album)) { // Not an album - return; - } - - self->album = album; - - gchar * album_art_path = koto_album_get_art(album); // Get any artwork - - if (koto_utils_string_is_valid(album_art_path)) { // Have album art - gtk_image_set_from_file(GTK_IMAGE(self->audiobook_art), album_art_path); // Set our album art - } - - koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Apply our album info - - gtk_list_box_bind_model( - // Apply our binding for the GtkListBox - GTK_LIST_BOX(self->tracks_list), - G_LIST_MODEL(koto_album_get_store(album)), - koto_audiobook_view_create_track_item, - NULL, - koto_audiobook_view_destroy_associated_user_data - ); - - - KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist - if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist - return; - } - - g_signal_connect(album_playlist, "modified", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle modifications of a playlist - g_signal_connect(album_playlist, "track-load-finalized", G_CALLBACK(koto_audiobook_view_handle_playlist_updated), self); // Handle when a playlist is finalized - koto_audiobook_view_update_side_info(self); // Update our side info -} - -void koto_audiobook_view_update_side_info(KotoAudiobookView * self) { - if (!KOTO_IS_AUDIOBOOK_VIEW(self)) { - return; - } - - if (!KOTO_IS_ALBUM(self->album)) { // Not an album - return; - } - - KotoPlaylist * album_playlist = koto_album_get_playlist(self->album); // Get the album playlist - if (!KOTO_IS_PLAYLIST(album_playlist)) { // Not a playlist - return; - } - - gint playlist_position = koto_playlist_get_current_position(album_playlist); // Get the current position in the playlist - gint playlist_length = koto_playlist_get_length(album_playlist); // Get the length of the playlist - - KotoTrack * current_track = koto_playlist_get_current_track(album_playlist); // Get the track for the playlist - - if (!KOTO_IS_TRACK(current_track)) { // Not a track - return; - } - - guint playback_position = koto_track_get_playback_position(current_track); - guint duration = koto_track_get_duration(current_track); - gboolean continuing = (playlist_position >= 1) || ((playlist_position <= 0) && (playback_position != 0)); - - gtk_button_set_label(GTK_BUTTON(self->playback_button), continuing ? "Resume" : "Play"); - - if (continuing) { // Have been playing track - gtk_label_set_text(GTK_LABEL(self->chapter_info), g_strdup_printf("Track %i of %i", (playlist_position <= 0) ? 1 : (playlist_position + 1), playlist_length)); - gtk_label_set_text(GTK_LABEL(self->playback_position), g_strdup_printf("%s / %s", koto_utils_seconds_to_time_format(playback_position), koto_utils_seconds_to_time_format(duration))); - gtk_widget_show(self->chapter_info); - gtk_widget_show(self->playback_position); - } else { - gtk_widget_hide(self->chapter_info); // Hide by default - gtk_widget_hide(self->playback_position); // Hide by default - } -} - -void koto_audiobook_view_destroy_associated_user_data(gpointer user_data) { - (void) user_data; -} - -KotoAudiobookView * koto_audiobook_view_new() { - return g_object_new( - KOTO_TYPE_AUDIOBOOK_VIEW, - "orientation", - GTK_ORIENTATION_HORIZONTAL, - NULL - ); -} \ No newline at end of file diff --git a/src/pages/audiobooks/audiobook-view.h b/src/pages/audiobooks/audiobook-view.h deleted file mode 100644 index fe5cee4..0000000 --- a/src/pages/audiobooks/audiobook-view.h +++ /dev/null @@ -1,56 +0,0 @@ -/* audiobook-view.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_AUDIOBOOK_VIEW (koto_audiobook_view_get_type()) -G_DECLARE_FINAL_TYPE(KotoAudiobookView, koto_audiobook_view, KOTO, AUDIOBOOK_VIEW, GtkBox); -#define KOTO_IS_AUDIOBOOK_VIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOK_VIEW)) - -void koto_audiobook_view_set_album( - KotoAudiobookView * self, - KotoAlbum * album -); - -GtkWidget * koto_audiobook_view_create_track_item( - gpointer item, - gpointer user_data -); - -void koto_audiobook_view_handle_play_clicked( - GtkButton * button, - gpointer user_data -); - -void koto_audiobook_view_handle_playlist_updated( - KotoPlaylist * playlist, - gpointer user_data -); - -void koto_audiobook_view_update_side_info(KotoAudiobookView * self); - -void koto_audiobook_view_destroy_associated_user_data(gpointer user_data); - -KotoAudiobookView * koto_audiobook_view_new(); - -G_END_DECLS \ No newline at end of file diff --git a/src/pages/audiobooks/genre-button.c b/src/pages/audiobooks/genre-button.c deleted file mode 100644 index a706a5e..0000000 --- a/src/pages/audiobooks/genre-button.c +++ /dev/null @@ -1,216 +0,0 @@ -/* genres-button.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/button.h" -#include "../../koto-utils.h" -#include "genre-button.h" - -enum { - PROP_0, - PROP_GENRE_ID, - PROP_GENRE_NAME, - N_PROPS -}; - -static GParamSpec * genre_button_props[N_PROPS] = { - NULL, -}; - -struct _KotoAudiobooksGenreButton { - GtkBox parent_instance; - - gchar * genre_id; - gchar * genre_name; - - GtkWidget * bg_image; - KotoButton * button; -}; - -struct _KotoAudiobooksGenreButtonClass { - GtkBoxClass parent_class; -}; - -G_DEFINE_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, GTK_TYPE_BOX); - -static void koto_audiobooks_genre_button_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_audiobooks_genre_button_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_audiobooks_genre_button_class_init(KotoAudiobooksGenreButtonClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_audiobooks_genre_button_set_property; - gobject_class->get_property = koto_audiobooks_genre_button_get_property; - - genre_button_props[PROP_GENRE_ID] = g_param_spec_string( - "genre-id", - "Genre ID", - "Genre ID", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - genre_button_props[PROP_GENRE_NAME] = g_param_spec_string( - "genre-name", - "Genre Name", - "Genre Name", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPS, genre_button_props); -} - -static void koto_audiobooks_genre_button_init(KotoAudiobooksGenreButton * self) { - gtk_widget_add_css_class(GTK_WIDGET(self), "audiobook-genre-button"); - gtk_widget_set_hexpand(GTK_WIDGET(self), FALSE); - gtk_widget_set_vexpand(GTK_WIDGET(self), FALSE); - - gtk_widget_set_size_request(GTK_WIDGET(self), 260, 120); - GtkWidget * overlay = gtk_overlay_new(); // Create our overlay - self->bg_image = gtk_picture_new(); // Create our new image - - self->button = koto_button_new_plain(NULL); // Create a new button - koto_button_set_text_wrap(self->button, TRUE); // Enable text wrapping for long genre lines - gtk_widget_set_valign(GTK_WIDGET(self->button), GTK_ALIGN_END); // Align towards bottom of button - - gtk_widget_set_hexpand(overlay, TRUE); - - gtk_overlay_set_child(GTK_OVERLAY(overlay), self->bg_image); - gtk_overlay_add_overlay(GTK_OVERLAY(overlay), GTK_WIDGET(self->button)); - - gtk_box_append(GTK_BOX(self), overlay); // Add the overlay to self -} - -static void koto_audiobooks_genre_button_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj); - - switch (prop_id) { - case PROP_GENRE_ID: - g_value_set_string(val, self->genre_id); - break; - case PROP_GENRE_NAME: - g_value_set_string(val, self->genre_name); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_audiobooks_genre_button_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoAudiobooksGenreButton * self = KOTO_AUDIOBOOKS_GENRE_BUTTON(obj); - - switch (prop_id) { - case PROP_GENRE_ID: - koto_audiobooks_genre_button_set_id(self, g_strdup(g_value_get_string(val))); - break; - case PROP_GENRE_NAME: - koto_audiobooks_genre_button_set_name(self, g_strdup(g_value_get_string(val))); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self) { - return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->button : NULL; -} - -gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self) { - return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_id : NULL; -} - -gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self) { - return KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self) ? self->genre_name : NULL; -} - -void koto_audiobooks_genre_button_set_id( - KotoAudiobooksGenreButton * self, - gchar * genre_id -) { - if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) { - return; - } - - if (!koto_utils_string_is_valid(genre_id)) { - return; - } - - self->genre_id = genre_id; - - gtk_picture_set_resource(GTK_PICTURE(self->bg_image), g_strdup_printf("/com/github/joshstrobl/koto/%s.png", genre_id)); - gtk_widget_set_size_request(self->bg_image, 260, 120); -} - -void koto_audiobooks_genre_button_set_name( - KotoAudiobooksGenreButton * self, - gchar * genre_name -) { - if (!KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(self)) { - return; - } - - if (!koto_utils_string_is_valid(genre_name)) { - return; - } - - if (koto_utils_string_is_valid(self->genre_name)) { // Already have a genre name - g_free(self->genre_name); - } - - self->genre_name = genre_name; - koto_button_set_text(self->button, genre_name); -} - -KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new( - gchar * genre_id, - gchar * genre_name -) { - return g_object_new( - KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON, - "genre-id", - genre_id, - "genre-name", - genre_name, - NULL - ); -} \ No newline at end of file diff --git a/src/pages/audiobooks/genre-button.h b/src/pages/audiobooks/genre-button.h deleted file mode 100644 index dc98a4b..0000000 --- a/src/pages/audiobooks/genre-button.h +++ /dev/null @@ -1,49 +0,0 @@ -/* genres-button.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/button.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON koto_audiobooks_genre_button_get_type() -G_DECLARE_FINAL_TYPE(KotoAudiobooksGenreButton, koto_audiobooks_genre_button, KOTO, AUDIOBOOKS_GENRE_BUTTON, GtkBox) -#define KOTO_IS_AUDIOBOOKS_GENRE_BUTTON(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BUTTON)) - -KotoButton * koto_audiobooks_genre_button_get_button(KotoAudiobooksGenreButton * self); - -gchar * koto_audiobooks_genre_button_get_id(KotoAudiobooksGenreButton * self); - -gchar * koto_audiobooks_genre_button_get_name(KotoAudiobooksGenreButton * self); - -void koto_audiobooks_genre_button_set_id( - KotoAudiobooksGenreButton * self, - gchar * genre_id -); - -void koto_audiobooks_genre_button_set_name( - KotoAudiobooksGenreButton * self, - gchar * genre_name -); - -KotoAudiobooksGenreButton * koto_audiobooks_genre_button_new( - gchar * genre_id, - gchar * genre_name -); - -G_END_DECLS \ No newline at end of file diff --git a/src/pages/audiobooks/genres-banner.c b/src/pages/audiobooks/genres-banner.c deleted file mode 100644 index 72d6374..0000000 --- a/src/pages/audiobooks/genres-banner.c +++ /dev/null @@ -1,153 +0,0 @@ -/* genres-banner.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/button.h" -#include "../../koto-utils.h" -#include "genre-button.h" -#include "genres-banner.h" - -enum { - SIGNAL_GENRE_CLICKED, - N_SIGNALS -}; - -static guint banner_signals[N_SIGNALS] = { - 0 -}; - -struct _KotoAudiobooksGenresBanner { - GObject parent_instance; - - GtkWidget * main; // Our main content - - GtkWidget * banner_revealer; // Our GtkRevealer for the strip - GtkWidget * banner_viewport; // Our banner viewport - GtkWidget * banner_content; // Our banner content as a horizontal box - - GtkWidget * strip_revealer; // Our GtkRevealer for the strip - GtkWidget * strip_viewport; // Our strip viewport - GtkWidget * strip_content; // Our strip content - - GHashTable * genre_ids_to_names; // Our genre "ids" to the name - GHashTable * genre_ids_to_ptrs; // Our HashTable of genre IDs to our GPtrArray with pointers to banner and strip buttons -}; - -struct _KotoAudiobooksGenresBannerClass { - GObjectClass parent_instance; - void (* genre_clicked) (gchar * genre); -}; - -G_DEFINE_TYPE(KotoAudiobooksGenresBanner, koto_audiobooks_genres_banner, G_TYPE_OBJECT); - -static void koto_audiobooks_genres_banner_class_init(KotoAudiobooksGenresBannerClass * c) { - GObjectClass * gobject_class = G_OBJECT_CLASS(c); - - banner_signals[SIGNAL_GENRE_CLICKED] = g_signal_new( - "genre-clicked", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoAudiobooksGenresBannerClass, genre_clicked), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_CHAR - ); -} - -static void koto_audiobooks_genres_banner_init(KotoAudiobooksGenresBanner * self) { - self->genre_ids_to_ptrs = g_hash_table_new(g_str_hash, g_str_equal); - self->genre_ids_to_names = g_hash_table_new(g_str_hash, g_str_equal); - g_hash_table_insert(self->genre_ids_to_names, g_strdup("hip-hop"), g_strdup("Hip-hop")); - g_hash_table_insert(self->genre_ids_to_names, g_strdup("indie"), g_strdup("Indie")); - g_hash_table_insert(self->genre_ids_to_names, g_strdup("sci-fi"), g_strdup("Science Fiction")); - - self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create the main box as a vertical box - gtk_widget_add_css_class(self->main, "genres-banner"); - gtk_widget_set_halign(self->main, GTK_ALIGN_START); - gtk_widget_set_vexpand(self->main, FALSE); - - self->banner_revealer = gtk_revealer_new(); // Create our revealer for the banner - self->strip_revealer = gtk_revealer_new(); // Create a revealer for the strip - - gtk_revealer_set_transition_type(GTK_REVEALER(self->strip_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_DOWN); - gtk_revealer_set_transition_type(GTK_REVEALER(self->banner_revealer), GTK_REVEALER_TRANSITION_TYPE_SLIDE_UP); - - self->banner_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the banner content to be scrollable - self->strip_viewport = gtk_viewport_new(NULL, NULL); // Create our viewport for enabling the strip content to be scrollable - - self->banner_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create our horizontal box for the content - gtk_widget_add_css_class(self->banner_content, "large-banner"); - - self->strip_content = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create the horizontal box for the strip content - gtk_widget_add_css_class(self->strip_content, "strip"); - - gtk_revealer_set_child(GTK_REVEALER(self->banner_revealer), self->banner_viewport); // Set the viewport to be the content which gets revealed - gtk_revealer_set_child(GTK_REVEALER(self->strip_revealer), self->strip_viewport); // Set the viewport to be the cont ent which gets revealed - gtk_revealer_set_reveal_child(GTK_REVEALER(self->banner_revealer), TRUE); // Show the banner by default - - gtk_viewport_set_child(GTK_VIEWPORT(self->banner_viewport), self->banner_content); // Set the banner content for this viewport - gtk_viewport_set_child(GTK_VIEWPORT(self->strip_viewport), self->strip_content); // Set the strip content for this viewport - - gtk_box_append(GTK_BOX(self->main), self->strip_revealer); - gtk_box_append(GTK_BOX(self->main), self->banner_revealer); -} - -void koto_audiobooks_genres_banner_add_genre( - KotoAudiobooksGenresBanner * self, - gchar * genre_id -) { - if (!KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self)) { // Not a banner - return; - } - - if (!koto_utils_string_is_valid(genre_id)) { // Not a valid genre ID - return; - } - - if (g_hash_table_contains(self->genre_ids_to_ptrs, genre_id)) { // Already have added this - return; - } - - gchar * name = g_hash_table_lookup(self->genre_ids_to_names, genre_id); // Get the named equivelant for this ID - - if (name == NULL) { // Didn't find a name equivelant - name = g_strdup(genre_id); // Just duplicate the genre ID for now - } - - KotoButton * genre_strip_button = koto_button_new_plain(name); // Create a new button with the name - gtk_box_append(GTK_BOX(self->strip_content), GTK_WIDGET(genre_strip_button)); // Add our KotoButton to the strip content - KotoAudiobooksGenreButton * genre_banner_button = koto_audiobooks_genre_button_new(genre_id, name); // Create our big button - gtk_box_append(GTK_BOX(self->banner_content), GTK_WIDGET(genre_banner_button)); - - GPtrArray * buttons = g_ptr_array_new(); - g_ptr_array_add(buttons, (gpointer) genre_strip_button); // Add our KotoButton as a gpointer to our GPtrArray as the first item - g_ptr_array_add(buttons, (gpointer) genre_banner_button); // Add our KotoAudiobooksGenresButton as a gpointer to our GPtrArray as the second item - - g_hash_table_replace(self->genre_ids_to_ptrs, genre_id, buttons); // Add our GPtrArray to our genre_ids_to_ptrs -} - -GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self) { - return KOTO_IS_AUDIOBOOKS_GENRES_BANNER(self) ? self->main : NULL; -} - -KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new() { - return g_object_new(KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, NULL); -} \ No newline at end of file diff --git a/src/pages/audiobooks/genres-banner.h b/src/pages/audiobooks/genres-banner.h deleted file mode 100644 index 7a4e1b7..0000000 --- a/src/pages/audiobooks/genres-banner.h +++ /dev/null @@ -1,44 +0,0 @@ -/* genres-banner.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include - -G_BEGIN_DECLS - -#define KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER koto_audiobooks_genres_banner_get_type() -#define KOTO_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER, KotoAudiobooksGenresBanner)) -typedef struct _KotoAudiobooksGenresBanner KotoAudiobooksGenresBanner; -typedef struct _KotoAudiobooksGenresBannerClass KotoAudiobooksGenresBannerClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_audiobooks_genres_banner_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_AUDIOBOOKS_GENRES_BANNER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_GENRE_BANNER)) - -void koto_audiobooks_genres_banner_add_genre( - KotoAudiobooksGenresBanner * self, - gchar * genre_id -); - -GtkWidget * koto_audiobooks_genres_banner_get_main(KotoAudiobooksGenresBanner * self); - -KotoAudiobooksGenresBanner * koto_audiobooks_genres_banner_new(); - -G_END_DECLS \ No newline at end of file diff --git a/src/pages/audiobooks/library.c b/src/pages/audiobooks/library.c deleted file mode 100644 index b6c9864..0000000 --- a/src/pages/audiobooks/library.c +++ /dev/null @@ -1,213 +0,0 @@ -/* library.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/button.h" -#include "../../db/cartographer.h" -#include "../../indexer/structs.h" -#include "../../koto-utils.h" -#include "../../koto-window.h" -#include "genres-banner.h" -#include "library.h" -#include "writer-page.h" - -extern KotoCartographer * koto_maps; -extern KotoWindow * main_window; - -struct _KotoAudiobooksLibraryPage { - GObject parent_instance; - - GtkWidget * main; // Our main content, contains banner and scrolled window - GtkWidget * content_scroll; // Our Scrolled Window - GtkWidget * content; // Content inside scrolled window - - KotoAudiobooksGenresBanner * banner; - - GtkWidget * writers_flow; - GHashTable * writers_to_buttons; // HashTable of UUIDs of "Artists" to their KotoButton - GHashTable * writers_to_pages; // HashTable of UUIDs of "Artists" to their KotoAudiobooksWritersPage -}; - -struct _KotoAudiobooksLibraryPageClass { - GObjectClass parent_instance; -}; - -G_DEFINE_TYPE(KotoAudiobooksLibraryPage, koto_audiobooks_library_page, G_TYPE_OBJECT); - -KotoAudiobooksLibraryPage * audiobooks_library_page; - -static void koto_audiobooks_library_page_init(KotoAudiobooksLibraryPage * self) { - self->main = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->main, "audiobook-library"); - self->content_scroll = gtk_scrolled_window_new(); // Create our GtkScrolledWindow - self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_set_vexpand(self->content, TRUE); // Ensure content expands vertically to allow flowboxchild to take up as much space as it requests - gtk_widget_add_css_class(self->content, "content"); - - self->banner = koto_audiobooks_genres_banner_new(); // Create our banner - - self->writers_flow = gtk_flow_box_new(); // Create our flow box - //gtk_flow_box_set_homogeneous(GTK_FLOW_BOX(self->writers_flow), TRUE); - gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->writers_flow), 100); // Set to a random amount that is not realistic, however GTK sets a default to 7 which is too small. - gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->writers_flow), GTK_SELECTION_NONE); - gtk_widget_add_css_class(self->writers_flow, "writers-button-flow"); - - self->writers_to_buttons = g_hash_table_new(g_str_hash, g_str_equal); - self->writers_to_pages = g_hash_table_new(g_str_hash, g_str_equal); - - g_signal_connect(koto_maps, "album-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_album), self); // Notify when we have a new Album - g_signal_connect(koto_maps, "artist-added", G_CALLBACK(koto_audiobooks_library_page_handle_add_artist), self); // Notify when we have a new Artist - - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->content_scroll), self->content); // Add our content to the content scroll - gtk_box_append(GTK_BOX(self->main), koto_audiobooks_genres_banner_get_main(self->banner)); // Add the banner to the content - gtk_box_append(GTK_BOX(self->main), self->content_scroll); // Add our scroll window to the main content - gtk_box_append(GTK_BOX(self->content), self->writers_flow); // Add our flowbox to the content -} - -static void koto_audiobooks_library_page_class_init(KotoAudiobooksLibraryPageClass * c) { - (void) c; -} - -void koto_audiobooks_library_page_create_artist_ux( - KotoAudiobooksLibraryPage * self, - KotoArtist * artist -) { - if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - gchar * artist_uuid = koto_artist_get_uuid(artist); // Get the artist UUID - - if (!g_hash_table_contains(self->writers_to_pages, artist_uuid)) { // Don't have the page - KotoWriterPage * writers_page = koto_writer_page_new(artist); - koto_window_add_page(main_window, artist_uuid, koto_writer_page_get_main(writers_page)); // Add the page to the stack - } - - if (!g_hash_table_contains(self->writers_to_buttons, artist_uuid)) { // Don't have the button for this - KotoButton * artist_button = koto_button_new_plain(koto_artist_get_name(artist)); // Create a KotoButton for this artist - koto_button_set_data(artist_button, artist_uuid); // Set the artist_uuid as the data for the button - - koto_button_set_text_justify(artist_button, GTK_JUSTIFY_CENTER); // Center the text - koto_button_set_text_wrap(artist_button, TRUE); - gtk_widget_add_css_class(GTK_WIDGET(artist_button), "writer-button"); - gtk_widget_set_size_request(GTK_WIDGET(artist_button), 260, 120); - - koto_button_add_click_handler(artist_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_button_global_page_nav_callback), artist_button); - - g_hash_table_replace(self->writers_to_buttons, artist_uuid, artist_button); // Add the button to the Writers to Buttons hashtable - gtk_flow_box_insert(GTK_FLOW_BOX(self->writers_flow), GTK_WIDGET(artist_button), -1); // Append the button to our flowbox - - GtkWidget * button_parent = gtk_widget_get_parent(GTK_WIDGET(artist_button)); // This is the GtkFlowboxChild that the button is in - gtk_widget_set_halign(button_parent, GTK_ALIGN_START); - } -} - -void koto_audiobooks_library_page_handle_add_album( - KotoCartographer * carto, - KotoAlbum * album, - KotoAudiobooksLibraryPage * self -) { - - if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer - return; - } - - if (!KOTO_IS_ALBUM(album)) { // Not an album - return; - } - - if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage - return; - } - - gchar * artist_uuid = koto_album_get_artist_uuid(album); // Get the Album's artist UUID - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); - - if (!KOTO_IS_ARTIST(artist)) { // Failed to get artist - return; - } - - if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library - return; - } - - koto_audiobooks_library_page_add_genres(self, koto_album_get_genres(album)); // Add all the genres necessary for this album -} - -void koto_audiobooks_library_page_handle_add_artist( - KotoCartographer * carto, - KotoArtist * artist, - KotoAudiobooksLibraryPage * self -) { - if (!KOTO_IS_CARTOGRAPHER(carto)) { // Not cartographer - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage - return; - } - - if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_AUDIOBOOK) { // Not in an Audiobook library - return; - } - - koto_audiobooks_library_page_create_artist_ux(self, artist); // Create the UX for the artist if necessary -} - -void koto_audiobooks_library_page_add_genres( - KotoAudiobooksLibraryPage * self, - GList * genres -) { - if (!KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self)) { // Not a AudiobooksLibraryPage - return; - } - - if (g_list_length(genres) == 0) { // No contents - return; - } - - GList * cur_list; - for (cur_list = genres; cur_list != NULL; cur_list = cur_list->next) { // Iterate over each genre - gchar * genre = (gchar*) cur_list->data; - if (!koto_utils_string_is_valid(genre)) { - continue; - } - - if (g_strcmp0(genre, "audiobook") == 0) { // Is generic - continue; - } - - koto_audiobooks_genres_banner_add_genre(self->banner, genre); // Add this genre - } -} - -GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self) { - return KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(self) ? self->main : NULL; -} - -KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new() { - return g_object_new(KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, NULL); -} \ No newline at end of file diff --git a/src/pages/audiobooks/library.h b/src/pages/audiobooks/library.h deleted file mode 100644 index 88e633f..0000000 --- a/src/pages/audiobooks/library.h +++ /dev/null @@ -1,56 +0,0 @@ -/* library.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../db/cartographer.h" -#include "../../indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE koto_audiobooks_library_page_get_type() -#define KOTO_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE, KotoAudiobooksLibraryPage)) -typedef struct _KotoAudiobooksLibraryPage KotoAudiobooksLibraryPage; -typedef struct _KotoAudiobooksLibraryPageClass KotoAudiobooksLibraryPageClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_audiobooks_library_page_get_type(void) G_GNUC_CONST; - -#define KOTO_IS_AUDIOBOOKS_LIBRARY_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_AUDIOBOOKS_LIBRARY_PAGE)) - -void koto_audiobooks_library_page_add_genres( - KotoAudiobooksLibraryPage * self, - GList * genres -); - -void koto_audiobooks_library_page_handle_add_album( - KotoCartographer * carto, - KotoAlbum * album, - KotoAudiobooksLibraryPage * self -); - -void koto_audiobooks_library_page_handle_add_artist( - KotoCartographer * carto, - KotoArtist * artist, - KotoAudiobooksLibraryPage * self -); - -GtkWidget * koto_audiobooks_library_page_get_main(KotoAudiobooksLibraryPage * self); - -KotoAudiobooksLibraryPage * koto_audiobooks_library_page_new(); \ No newline at end of file diff --git a/src/pages/audiobooks/writer-page.c b/src/pages/audiobooks/writer-page.c deleted file mode 100644 index f0944ce..0000000 --- a/src/pages/audiobooks/writer-page.c +++ /dev/null @@ -1,206 +0,0 @@ -/* writers-page.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../db/cartographer.h" -#include "../../indexer/structs.h" -#include "../../koto-utils.h" -#include "../../koto-window.h" -#include "audiobook-view.h" -#include "writer-page.h" - -extern KotoCartographer * koto_maps; -extern KotoWindow * main_window; - -enum { - PROP_0, - PROP_ARTIST, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -struct _KotoWriterPage { - GObject parent_instance; - - KotoArtist * artist; - - GtkWidget * main; // Our main content will be a scrolled Scrolled Window - GtkWidget * content; // Content inside scrolled window - - GtkWidget * writers_header; // Header GtkLabel for the writer - GListModel * model; - - GtkWidget * audiobooks_flow; -}; - -struct _KotoWriterPageClass { - GObjectClass parent_class; -}; - -G_DEFINE_TYPE(KotoWriterPage, koto_writer_page, G_TYPE_OBJECT); - -static void koto_writer_page_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_writer_page_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_writer_page_class_init(KotoWriterPageClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_writer_page_set_property; - gobject_class->get_property = koto_writer_page_get_property; - - props[PROP_ARTIST] = g_param_spec_object( - "artist", - "Artist", - "Artist", - KOTO_TYPE_ARTIST, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_writer_page_init(KotoWriterPage * self) { - self->main = gtk_scrolled_window_new(); // Set main to be a scrolled window - self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->content, "writer-page"); - - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->main), self->content); // Set content to be the child of the scrolled window - - self->writers_header = gtk_label_new(NULL); // Create an empty label - gtk_widget_add_css_class(self->writers_header, "writer-header"); - gtk_widget_set_halign(self->writers_header, GTK_ALIGN_START); - - self->audiobooks_flow = gtk_flow_box_new(); // Create our flowbox of the audiobooks views - gtk_widget_add_css_class(self->audiobooks_flow, "audiobooks-flow"); - - gtk_flow_box_set_max_children_per_line(GTK_FLOW_BOX(self->audiobooks_flow), 2); // Allow 2 to ensure adequate spacing for description - gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->audiobooks_flow), GTK_SELECTION_NONE); // Do not allow selection - - gtk_widget_set_hexpand(self->audiobooks_flow, TRUE); // Expand horizontally - gtk_widget_set_vexpand(self->audiobooks_flow, TRUE); // Expand vertically - - gtk_box_append(GTK_BOX(self->content), self->writers_header); - gtk_box_append(GTK_BOX(self->content), self->audiobooks_flow); // Add the audiobooks flow to the content -} - -static void koto_writer_page_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoWriterPage * self = KOTO_WRITER_PAGE(obj); - - switch (prop_id) { - case PROP_ARTIST: - g_value_set_object(val, self->artist); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_writer_page_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoWriterPage * self = KOTO_WRITER_PAGE(obj); - - switch (prop_id) { - case PROP_ARTIST: - koto_writer_page_set_artist(self, (KotoArtist*) g_value_get_object(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -GtkWidget * koto_writer_page_create_item( - gpointer item, - gpointer user_data -) { - (void) user_data; - - KotoAlbum * album = KOTO_ALBUM(item); // Cast the model data as an album - - if (!KOTO_IS_ALBUM(album)) { // Fetched item from list is not album - return NULL; - } - - KotoAudiobookView * view = koto_audiobook_view_new(); // Create our KotoAudiobookView - koto_audiobook_view_set_album(view, album); // Set the item for this set up audiobook view - return GTK_WIDGET(view); -} - -GtkWidget * koto_writer_page_get_main(KotoWriterPage * self) { - return self->main; -} - -void koto_writer_page_set_artist( - KotoWriterPage * self, - KotoArtist * artist -) { - if (!KOTO_IS_WRITER_PAGE(self)) { // Not a writers page - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - self->artist = artist; - gtk_label_set_text(GTK_LABEL(self->writers_header), koto_artist_get_name(self->artist)); // Get the label for the writers header - - self->model = G_LIST_MODEL(koto_artist_get_albums_store(artist)); // Get the store and cast it as a list model for our model - - gtk_flow_box_bind_model( - GTK_FLOW_BOX(self->audiobooks_flow), - self->model, - koto_writer_page_create_item, - NULL, - NULL - ); -} - -KotoWriterPage * koto_writer_page_new(KotoArtist * artist) { - return g_object_new( - KOTO_TYPE_WRITER_PAGE, - "artist", - artist, - NULL - ); -} \ No newline at end of file diff --git a/src/pages/audiobooks/writer-page.h b/src/pages/audiobooks/writer-page.h deleted file mode 100644 index 2a2a913..0000000 --- a/src/pages/audiobooks/writer-page.h +++ /dev/null @@ -1,41 +0,0 @@ -/* writers-page.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -G_BEGIN_DECLS - -#define KOTO_TYPE_WRITER_PAGE (koto_writer_page_get_type()) -G_DECLARE_FINAL_TYPE(KotoWriterPage, koto_writer_page, KOTO, WRITER_PAGE, GObject) - -GtkWidget* koto_writer_page_create_item( - gpointer item, - gpointer user_data -); - -GtkWidget * koto_writer_page_get_main(KotoWriterPage * self); - -void koto_writer_page_set_artist( - KotoWriterPage * self, - KotoArtist * artist -); - -KotoWriterPage * koto_writer_page_new(KotoArtist * artist); - -G_END_DECLS \ No newline at end of file diff --git a/src/pages/music/album-view.c b/src/pages/music/album-view.c deleted file mode 100644 index 678f23b..0000000 --- a/src/pages/music/album-view.c +++ /dev/null @@ -1,297 +0,0 @@ -/* album-view.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/album-info.h" -#include "../../components/button.h" -#include "../../components/cover-art-button.h" -#include "../../db/cartographer.h" -#include "../../indexer/structs.h" -#include "album-view.h" -#include "disc-view.h" -#include "config/config.h" -#include "koto-utils.h" - -extern KotoCartographer * koto_maps; - -struct _KotoAlbumView { - GObject parent_instance; - KotoAlbum * album; - GtkWidget * main; - GtkWidget * album_tracks_box; - GtkWidget * discs; - - KotoCoverArtButton * album_cover; - - KotoAlbumInfo * album_info; - GHashTable * cd_to_disc_views; -}; - -G_DEFINE_TYPE(KotoAlbumView, koto_album_view, G_TYPE_OBJECT); - -enum { - PROP_0, - PROP_ALBUM, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; -static void koto_album_view_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_album_view_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_album_view_class_init(KotoAlbumViewClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_album_view_set_property; - gobject_class->get_property = koto_album_view_get_property; - - props[PROP_ALBUM] = g_param_spec_object( - "album", - "Album", - "Album", - KOTO_TYPE_ALBUM, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_album_view_init(KotoAlbumView * self) { - self->cd_to_disc_views = g_hash_table_new(g_str_hash, g_str_equal); - self->main = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->main, "album-view"); - gtk_widget_set_can_focus(self->main, FALSE); - self->album_tracks_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - self->album_info = koto_album_info_new(KOTO_ALBUM_INFO_TYPE_ALBUM); // Create an Album-type Album Info - gtk_box_prepend(GTK_BOX(self->album_tracks_box), GTK_WIDGET(self->album_info)); // Add our Album Info to the album_tracks box - - self->discs = gtk_list_box_new(); // Create our list of our tracks - gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->discs), GTK_SELECTION_NONE); - gtk_list_box_set_show_separators(GTK_LIST_BOX(self->discs), FALSE); - gtk_list_box_set_sort_func(GTK_LIST_BOX(self->discs), koto_album_view_sort_discs, NULL, NULL); // Ensure we can sort our discs - gtk_widget_add_css_class(self->discs, "discs-list"); - gtk_widget_set_can_focus(self->discs, FALSE); - gtk_widget_set_focusable(self->discs, FALSE); - gtk_widget_set_size_request(self->discs, 600, -1); - - gtk_box_append(GTK_BOX(self->main), self->album_tracks_box); // Add the tracks box to the art info combo box - gtk_box_append(GTK_BOX(self->album_tracks_box), self->discs); // Add the discs list box to the albums tracks box - - self->album_cover = koto_cover_art_button_new(220, 220, NULL); - GtkWidget * album_cover_main = koto_cover_art_button_get_main(self->album_cover); - - gtk_widget_set_valign(album_cover_main, GTK_ALIGN_START); - - gtk_box_prepend(GTK_BOX(self->main), album_cover_main); - KotoButton * cover_art_button = koto_cover_art_button_get_button(self->album_cover); // Get the button for the cover art - koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_album_view_toggle_album_playback), self); -} - -GtkWidget * koto_album_view_get_main(KotoAlbumView * self) { - return self->main; -} - -static void koto_album_view_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoAlbumView * self = KOTO_ALBUM_VIEW(obj); - - switch (prop_id) { - case PROP_ALBUM: - g_value_set_object(val, self->album); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_album_view_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoAlbumView * self = KOTO_ALBUM_VIEW(obj); - - switch (prop_id) { - case PROP_ALBUM: - koto_album_view_set_album(self, (KotoAlbum*) g_value_get_object(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_album_view_add_track( - KotoAlbumView * self, - KotoTrack * track -) { - if (!KOTO_IS_ALBUM_VIEW(self)) { // Not an album view - return; - } - - if (!KOTO_IS_ALBUM(self->album)) { // Somehow don't have an album set - return; - } - - if (!KOTO_IS_TRACK(track)) { // Track doesn't exist - return; - } - - guint disc_number = koto_track_get_disc_number(track); // Get the disc number - gchar * disc_num_as_str = g_strdup_printf("%u", disc_number); - - KotoDiscView * disc_view; - - if (g_hash_table_contains(self->cd_to_disc_views, disc_num_as_str)) { // Already have this added this disc and its disc view - disc_view = g_hash_table_lookup(self->cd_to_disc_views, disc_num_as_str); // Get the disc view - } else { - disc_view = koto_disc_view_new(self->album, disc_number); // Build a new disc view - gtk_list_box_append(GTK_LIST_BOX(self->discs), GTK_WIDGET(disc_view)); // Add the Disc View to the List Box - g_hash_table_replace(self->cd_to_disc_views, disc_num_as_str, disc_view); // Add the new Disc View to our hash table - } - - if (!KOTO_IS_DISC_VIEW(disc_view)) { // If this is not a Disc View - return; - } - - koto_disc_view_add_track(disc_view, track); // Add the track to the disc view - koto_album_view_update_disc_labels(self); // Update our disc labels -} - -void koto_album_view_handle_track_added( - KotoAlbum * album, - KotoTrack * track, - gpointer user_data -) { - if (!KOTO_IS_ALBUM(album)) { // If not an album - return; - } - - if (!KOTO_IS_TRACK(track)) { // If not a track - return; - } - - KotoAlbumView * self = KOTO_ALBUM_VIEW(user_data); // Define as an album view - - if (!KOTO_IS_ALBUM_VIEW(self)) { - return; - } - - koto_album_view_add_track(self, track); // Add the track -} - -void koto_album_view_set_album( - KotoAlbumView * self, - KotoAlbum * album -) { - if (!KOTO_IS_ALBUM_VIEW(self)) { - return; - } - - if (!KOTO_IS_ALBUM(album)) { - return; - } - - self->album = album; - - gchar * album_art = koto_album_get_art(self->album); // Get the art for the album - koto_cover_art_button_set_art_path(self->album_cover, album_art); - koto_album_info_set_album_uuid(self->album_info, koto_album_get_uuid(album)); // Set the album we should be displaying info about on the Album Info - g_signal_connect(self->album, "track-added", G_CALLBACK(koto_album_view_handle_track_added), self); // Handle track added on our album -} - -int koto_album_view_sort_discs( - GtkListBoxRow * disc1, - GtkListBoxRow * disc2, - gpointer user_data -) { - (void) user_data; - KotoDiscView * disc1_item = KOTO_DISC_VIEW(gtk_list_box_row_get_child(disc1)); - KotoDiscView * disc2_item = KOTO_DISC_VIEW(gtk_list_box_row_get_child(disc2)); - - guint disc1_num; - guint disc2_num; - - g_object_get(disc1_item, "disc", &disc1_num, NULL); - g_object_get(disc2_item, "disc", &disc2_num, NULL); - - if (disc1_num == disc2_num) { // Identical positions (like reported as 0) - return 0; - } else if (disc1_num < disc2_num) { - return -1; - } else { - return 1; - } -} - -void koto_album_view_toggle_album_playback( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoAlbumView * self = data; - - koto_album_set_as_current_playlist(self->album); // Set as the current playlist -} - -void koto_album_view_update_disc_labels(KotoAlbumView * self) { - gboolean show_disc_labels = g_hash_table_size(self->cd_to_disc_views) > 1; - gpointer disc_num_ptr; - gpointer disc_view_ptr; - - GHashTableIter disc_view_iter; - g_hash_table_iter_init(&disc_view_iter, self->cd_to_disc_views); - - while (g_hash_table_iter_next(&disc_view_iter, &disc_num_ptr, &disc_view_ptr)) { - (void) disc_num_ptr; - KotoDiscView * view = (KotoDiscView*) disc_view_ptr; - if (KOTO_IS_DISC_VIEW(view)) { - koto_disc_view_set_disc_label_visible(view, show_disc_labels); - } - } -} - -KotoAlbumView * koto_album_view_new(KotoAlbum * album) { - return g_object_new(KOTO_TYPE_ALBUM_VIEW, "album", album, NULL); -} diff --git a/src/pages/music/album-view.h b/src/pages/music/album-view.h deleted file mode 100644 index cc55195..0000000 --- a/src/pages/music/album-view.h +++ /dev/null @@ -1,65 +0,0 @@ -/* album-view.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_ALBUM_VIEW (koto_album_view_get_type()) - -G_DECLARE_FINAL_TYPE(KotoAlbumView, koto_album_view, KOTO, ALBUM_VIEW, GObject) - -KotoAlbumView* koto_album_view_new(KotoAlbum * album); -GtkWidget * koto_album_view_get_main(KotoAlbumView * self); - -void koto_album_view_add_track( - KotoAlbumView * self, - KotoTrack * track -); - -void koto_album_view_handle_track_added( - KotoAlbum * album, - KotoTrack * track, - gpointer user_data -); - -void koto_album_view_set_album( - KotoAlbumView * self, - KotoAlbum * album -); - -int koto_album_view_sort_discs( - GtkListBoxRow * track1, - GtkListBoxRow * track2, - gpointer user_data -); - -void koto_album_view_toggle_album_playback( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -void koto_album_view_update_disc_labels(KotoAlbumView * self); - -G_END_DECLS diff --git a/src/pages/music/artist-view.c b/src/pages/music/artist-view.c deleted file mode 100644 index d0c4215..0000000 --- a/src/pages/music/artist-view.c +++ /dev/null @@ -1,336 +0,0 @@ -/* artist-view.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/cover-art-button.h" -#include "../../components/track-table.h" -#include "../../db/cartographer.h" -#include "../../indexer/artist-playlist-funcs.h" -#include "../../indexer/structs.h" -#include "../../playlist/current.h" -#include "album-view.h" -#include "artist-view.h" -#include "config/config.h" -#include "koto-utils.h" - -extern KotoCurrentPlaylist * current_playlist; -extern KotoCartographer * koto_maps; - -struct _KotoArtistView { - GObject parent_instance; - KotoArtist * artist; - GtkWidget * scrolled_window; - GtkWidget * content; - GtkWidget * album_list; - - GtkWidget * no_albums_view; - GtkWidget * no_albums_artist_header; - KotoCoverArtButton * no_albums_artist_button; - GtkWidget * no_albums_artist_label; - - KotoTrackTable * table; - GHashTable * albums_to_component; -}; - -G_DEFINE_TYPE(KotoArtistView, koto_artist_view, G_TYPE_OBJECT); - -enum { - PROP_0, - PROP_ARTIST, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -static void koto_artist_view_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_artist_view_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_artist_view_class_init(KotoArtistViewClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_artist_view_set_property; - gobject_class->get_property = koto_artist_view_get_property; - - props[PROP_ARTIST] = g_param_spec_object( - "artist", - "Artist", - "Artist", - KOTO_TYPE_ARTIST, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_artist_view_init(KotoArtistView * self) { - self->artist = NULL; - self->albums_to_component = g_hash_table_new(g_str_hash, g_str_equal); - - self->scrolled_window = gtk_scrolled_window_new(); // Create our scrolled window - self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); // Create our content as a GtkBox - - gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE); - gtk_scrolled_window_set_propagate_natural_width(GTK_SCROLLED_WINDOW(self->scrolled_window), TRUE); - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->content); // Add the content as the widget for the scrolled window - gtk_widget_add_css_class(GTK_WIDGET(self->scrolled_window), "artist-view"); - gtk_widget_add_css_class(GTK_WIDGET(self->content), "artist-view-content"); - - self->album_list = gtk_flow_box_new(); // Create our list of our albums - gtk_flow_box_set_selection_mode(GTK_FLOW_BOX(self->album_list), GTK_SELECTION_NONE); - gtk_widget_add_css_class(self->album_list, "album-list"); - - self->no_albums_view = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->no_albums_view, "no-albums-view"); - - self->no_albums_artist_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_add_css_class(self->no_albums_artist_header, "no-albums-view-header"), - - self->no_albums_artist_button = koto_cover_art_button_new(220, 220, NULL); - KotoButton * cover_art_button = koto_cover_art_button_get_button(self->no_albums_artist_button); // Get the button for the KotoCoverArt - koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_artist_view_toggle_playback), self); // Handle clicking the button - - self->no_albums_artist_label = gtk_label_new(NULL); // Create a label without any artist name - - gtk_box_append(GTK_BOX(self->no_albums_artist_header), koto_cover_art_button_get_main(self->no_albums_artist_button)); // Add our button to the header - gtk_box_append(GTK_BOX(self->no_albums_artist_header), self->no_albums_artist_label); - - gtk_box_append(GTK_BOX(self->no_albums_view), self->no_albums_artist_header); // Add the header to the no albums view - - self->table = koto_track_table_new(); //Create our track table - gtk_box_append(GTK_BOX(self->no_albums_view), koto_track_table_get_main(self->table)); // Add the table to the no albums view - - gtk_box_append(GTK_BOX(self->content), self->album_list); // Add the album flowbox - gtk_box_append(GTK_BOX(self->content), self->no_albums_view); // Add the no albums view just in case we do not have any albums - - gtk_widget_set_hexpand(GTK_WIDGET(self->album_list), TRUE); - gtk_widget_set_hexpand(GTK_WIDGET(self->no_albums_view), TRUE); -} - -static void koto_artist_view_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoArtistView * self = KOTO_ARTIST_VIEW(obj); - - switch (prop_id) { - case PROP_ARTIST: - g_value_set_object(val, self->artist); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_artist_view_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoArtistView * self = KOTO_ARTIST_VIEW(obj); - - switch (prop_id) { - case PROP_ARTIST: - koto_artist_view_set_artist(self, (KotoArtist*) g_value_get_object(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_artist_view_add_album( - KotoArtistView * self, - KotoAlbum * album -) { - if (!KOTO_IS_ARTIST_VIEW(self)) { - return; - } - - if (!KOTO_IS_ALBUM(album)) { - return; - } - - gchar * album_uuid = koto_album_get_uuid(album); // Get the album UUID - - if (g_hash_table_contains(self->albums_to_component, album_uuid)) { // Already added this album - return; - } - - KotoAlbumView * album_view = koto_album_view_new(album); // Create our new album view - GtkWidget * album_view_main = koto_album_view_get_main(album_view); - - gtk_flow_box_insert(GTK_FLOW_BOX(self->album_list), album_view_main, -1); // Append the album view to the album list - g_hash_table_replace(self->albums_to_component, album_uuid, album_view_main); - - gtk_widget_hide(self->no_albums_view); // Hide the no album view - gtk_widget_show(self->album_list); // Show the album list now that it has albums -} - -GtkWidget * koto_artist_view_get_main(KotoArtistView * self) { - return self->scrolled_window; -} - -void koto_artist_view_handle_album_added( - KotoArtist * artist, - KotoAlbum * album, - gpointer user_data -) { - KotoArtistView * self = user_data; - - if (!KOTO_IS_ARTIST_VIEW(self)) { - return; - } - - if (!KOTO_IS_ARTIST(artist)) { - return; - } - - if (!KOTO_IS_ALBUM(album)) { - return; - } - - if (g_strcmp0(koto_artist_get_uuid(self->artist), koto_artist_get_uuid(artist)) != 0) { // Not the same artist - return; - } - - koto_artist_view_add_album(self, album); // Add the album if necessary -} - -void koto_artist_view_handle_album_removed( - KotoArtist * artist, - gchar * album_uuid, - gpointer user_data -) { - KotoArtistView * self = user_data; - - if (!KOTO_IS_ARTIST_VIEW(self)) { - return; - } - - if (g_strcmp0(koto_artist_get_uuid(self->artist), koto_artist_get_uuid(artist)) != 0) { // Not the same artist - return; - } - - GtkWidget * album_view = g_hash_table_lookup(self->albums_to_component, album_uuid); // Get the album view if it exists - - if (!GTK_IS_WIDGET(album_view)) { // Not a widget - return; - } - - gtk_flow_box_remove(GTK_FLOW_BOX(self->album_list), album_view); // Remove the album - g_hash_table_remove(self->albums_to_component, album_uuid); // Remove the album from our hash table -} - -void koto_artist_view_handle_artist_name_changed( - KotoArtist * artist, - guint prop_id, - KotoArtistView * self -) { - (void) prop_id; - gtk_label_set_text(GTK_LABEL(self->no_albums_artist_label), koto_artist_get_name(artist)); // Update the label for the artist now that it has changed -} - -void koto_artist_view_handle_has_no_albums( - KotoArtist * artist, - gpointer user_data -) { - (void) artist; - KotoArtistView * self = user_data; - - if (!KOTO_IS_ARTIST_VIEW(self)) { - return; - } - - gtk_widget_hide(self->album_list); // Hide our album list flowbox - gtk_widget_show(self->no_albums_view); // Show the no albums view -} - -void koto_artist_view_set_artist( - KotoArtistView * self, - KotoArtist * artist -) { - if (!KOTO_IS_ARTIST_VIEW(self)) { // Not an Artist view - return; - } - - if (!KOTO_IS_ARTIST(artist)) { - return; - } - - self->artist = artist; - koto_track_table_set_playlist(self->table, koto_artist_get_playlist(self->artist)); // Set our track table to the artist's playlist - gtk_label_set_text(GTK_LABEL(self->no_albums_artist_label), koto_artist_get_name(self->artist)); // Update our label with the name of the artist - - g_signal_connect(artist, "album-added", G_CALLBACK(koto_artist_view_handle_album_added), self); - g_signal_connect(artist, "album-removed", G_CALLBACK(koto_artist_view_handle_album_removed), self); - g_signal_connect(artist, "has-no-albums", G_CALLBACK(koto_artist_view_handle_has_no_albums), self); - g_signal_connect(artist, "notify::name", G_CALLBACK(koto_artist_view_handle_artist_name_changed), self); -} - -void koto_artist_view_toggle_playback( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoArtistView * self = data; - - if (!KOTO_IS_ARTIST_VIEW(self)) { - return; - } - - KotoPlaylist * artist_playlist = koto_artist_get_playlist(self->artist); - - if (!KOTO_IS_PLAYLIST(artist_playlist)) { // Failed our is playlist check for the artist playlist - return; - } - - koto_current_playlist_set_playlist(current_playlist, artist_playlist, TRUE, FALSE); // Set our playlist to the one associated with the Artist and start playback immediately -} - -KotoArtistView * koto_artist_view_new(KotoArtist * artist) { - return g_object_new( - KOTO_TYPE_ARTIST_VIEW, - "artist", - artist, - NULL - ); -} diff --git a/src/pages/music/artist-view.h b/src/pages/music/artist-view.h deleted file mode 100644 index e5c6baa..0000000 --- a/src/pages/music/artist-view.h +++ /dev/null @@ -1,75 +0,0 @@ -/* artist-view.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_ARTIST_VIEW (koto_artist_view_get_type()) - -G_DECLARE_FINAL_TYPE(KotoArtistView, koto_artist_view, KOTO, ARTIST_VIEW, GObject) - -KotoArtistView* koto_artist_view_new(KotoArtist * artist); - -void koto_artist_view_add_album( - KotoArtistView * self, - KotoAlbum * album -); - -GtkWidget * koto_artist_view_get_main(KotoArtistView * self); - -void koto_artist_view_handle_album_added( - KotoArtist * artist, - KotoAlbum * album, - gpointer user_data -); - -void koto_artist_view_handle_album_removed( - KotoArtist * artist, - gchar * album_uuid, - gpointer user_data -); - -void koto_artist_view_handle_artist_name_changed( - KotoArtist * artist, - guint prop_id, - KotoArtistView * self -); - -void koto_artist_view_handle_has_no_albums( - KotoArtist * artist, - gpointer user_data -); - -void koto_artist_view_set_artist( - KotoArtistView * self, - KotoArtist * artist -); - -void koto_artist_view_toggle_playback( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer data -); - -G_END_DECLS diff --git a/src/pages/music/disc-view.c b/src/pages/music/disc-view.c deleted file mode 100644 index 01a8971..0000000 --- a/src/pages/music/disc-view.c +++ /dev/null @@ -1,326 +0,0 @@ -/* disc-view.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "../../components/action-bar.h" -#include "../../components/track-item.h" -#include "../../db/cartographer.h" -#include "../../indexer/track-helpers.h" -#include "../../indexer/structs.h" -#include "../../koto-utils.h" -#include "disc-view.h" - -extern KotoActionBar * action_bar; -extern KotoCartographer * koto_maps; - -struct _KotoDiscView { - GtkBox parent_instance; - KotoAlbum * album; - GtkWidget * header; - GtkWidget * label; - GtkWidget * list; - - GHashTable * tracks_to_items; - - guint disc_number; -}; - -G_DEFINE_TYPE(KotoDiscView, koto_disc_view, GTK_TYPE_BOX); - -enum { - PROP_0, - PROP_DISC, - PROP_ALBUM, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -static void koto_disc_view_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_disc_view_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_disc_view_class_init(KotoDiscViewClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_disc_view_set_property; - gobject_class->get_property = koto_disc_view_get_property; - - props[PROP_DISC] = g_param_spec_uint( - "disc", - "Disc", - "Disc", - 0, - G_MAXUINT16, - 1, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ALBUM] = g_param_spec_object( - "album", - "Album", - "Album", - KOTO_TYPE_ALBUM, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_disc_view_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoDiscView * self = KOTO_DISC_VIEW(obj); - - switch (prop_id) { - case PROP_DISC: - g_value_set_uint(val, GPOINTER_TO_UINT(self->disc_number)); - break; - case PROP_ALBUM: - g_value_set_object(val, self->album); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_disc_view_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoDiscView * self = KOTO_DISC_VIEW(obj); - - switch (prop_id) { - case PROP_DISC: - koto_disc_view_set_disc_number(self, g_value_get_uint(val)); - break; - case PROP_ALBUM: - koto_disc_view_set_album(self, (KotoAlbum*) g_value_get_object(val)); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_disc_view_init(KotoDiscView * self) { - self->tracks_to_items = g_hash_table_new(g_str_hash, g_str_equal); - - gtk_widget_add_css_class(GTK_WIDGET(self), "disc-view"); - gtk_widget_set_can_focus(GTK_WIDGET(self), FALSE); - self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); - gtk_widget_set_hexpand(self->header, TRUE); - gtk_widget_set_size_request(self->header, 16, -1); - - GtkWidget * ico = gtk_image_new_from_icon_name("drive-optical-symbolic"); - - gtk_box_prepend(GTK_BOX(self->header), ico); - - self->label = gtk_label_new(NULL); // Create an empty label - gtk_widget_set_halign(self->label, GTK_ALIGN_START); - gtk_widget_set_valign(self->label, GTK_ALIGN_CENTER); - gtk_box_append(GTK_BOX(self->header), self->label); - - gtk_box_append(GTK_BOX(self), self->header); - - self->list = gtk_list_box_new(); // Create our list of our tracks - gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->list), FALSE); - gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list), GTK_SELECTION_MULTIPLE); - gtk_list_box_set_sort_func(GTK_LIST_BOX(self->list), koto_disc_view_sort_list_box_rows, self, NULL); - gtk_widget_add_css_class(self->list, "track-list"); - gtk_widget_set_can_focus(self->list, FALSE); - gtk_widget_set_focusable(self->list, FALSE); - gtk_widget_set_size_request(self->list, 600, -1); - gtk_box_append(GTK_BOX(self), self->list); - - g_signal_connect(self->list, "selected-rows-changed", G_CALLBACK(koto_disc_view_handle_selected_rows_changed), self); -} - -void koto_disc_view_add_track( - KotoDiscView * self, - KotoTrack * track -) { - if (!KOTO_IS_DISC_VIEW(self)) { // If this is not a disc view - return; - } - - if (!KOTO_IS_TRACK(track)) { // If this is not a track - return; - } - - if (self->disc_number != koto_track_get_disc_number(track)) { // Not the same disc - return; - } - - gchar * track_uuid = koto_track_get_uuid(track); // Get the track UUID - - if (g_hash_table_contains(self->tracks_to_items, track_uuid)) { // Already have added this track - return; - } - - KotoTrackItem * track_item = koto_track_item_new(track); // Create our new track item - - if (!KOTO_IS_TRACK_ITEM(track_item)) { // Failed to create a track item for this track - return; - } - - gtk_list_box_append(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Add to our tracks list box - g_hash_table_replace(self->tracks_to_items, track_uuid, track_item); // Add to our hash table so we know we have added this -} - -void koto_disc_view_handle_selected_rows_changed( - GtkListBox * box, - gpointer user_data -) { - KotoDiscView * self = user_data; - - if (!KOTO_IS_DISC_VIEW(self)) { - return; - } - - gchar * album_uuid = koto_album_get_uuid(self->album); // Get the UUID - - if (!koto_utils_string_is_valid(album_uuid)) { // Not set - return; - } - - GList * selected_rows = gtk_list_box_get_selected_rows(box); // Get the selected rows - - if (g_list_length(selected_rows) == 0) { // No rows selected - koto_action_bar_toggle_reveal(action_bar, FALSE); // Close the action bar - return; - } - - GList * selected_tracks = NULL; // Create our list of KotoTracks - GList * cur_selected_rows; - - for (cur_selected_rows = selected_rows; cur_selected_rows != NULL; cur_selected_rows = cur_selected_rows->next) { // Iterate over the rows - KotoTrackItem * track_item = (KotoTrackItem*) gtk_list_box_row_get_child(cur_selected_rows->data); - selected_tracks = g_list_append(selected_tracks, koto_track_item_get_track(track_item)); // Add the KotoTrack to our list - } - - g_list_free(cur_selected_rows); - - koto_action_bar_set_tracks_in_album_selection(action_bar, album_uuid, selected_tracks); // Set our album selection - koto_action_bar_toggle_reveal(action_bar, TRUE); // Show the action bar -} - -void koto_disc_view_remove_track( - KotoDiscView * self, - gchar * track_uuid -) { - if (!KOTO_IS_DISC_VIEW(self)) { // If this is not a disc view - return; - } - - if (!koto_utils_string_is_valid(track_uuid)) { // If our UUID is not a valid - return; - } - - KotoTrackItem * track_item = g_hash_table_lookup(self->tracks_to_items, track_uuid); // Get the track item - - if (!KOTO_IS_TRACK_ITEM(track_item)) { // Track item not valid - return; - } - - gtk_list_box_remove(GTK_LIST_BOX(self->list), GTK_WIDGET(track_item)); // Remove the item from the listbox - g_hash_table_remove(self->tracks_to_items, track_uuid); // Remove the track from the hashtable -} - -void koto_disc_view_set_album( - KotoDiscView * self, - KotoAlbum * album -) { - if (album == NULL) { - return; - } - - if (self->album != NULL) { - g_free(self->album); - } - - self->album = album; -} - -void koto_disc_view_set_disc_number( - KotoDiscView * self, - guint disc_number -) { - if (disc_number == 0) { - return; - } - - self->disc_number = disc_number; - - gchar * disc_label = g_strdup_printf("Disc %u", disc_number); - - gtk_label_set_text(GTK_LABEL(self->label), disc_label); // Set the label - - g_free(disc_label); -} - -void koto_disc_view_set_disc_label_visible( - KotoDiscView * self, - gboolean visible -) { - (visible) ? gtk_widget_show(self->header) : gtk_widget_hide(self->header); -} - -gint koto_disc_view_sort_list_box_rows( - GtkListBoxRow * row1, - GtkListBoxRow * row2, - gpointer user_data -) { - KotoTrackItem * track1_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(row1)); - KotoTrackItem * track2_item = KOTO_TRACK_ITEM(gtk_list_box_row_get_child(row2)); - return koto_track_helpers_sort_track_items(track1_item, track2_item, user_data); -} - -KotoDiscView * koto_disc_view_new( - KotoAlbum * album, - guint disc_number -) { - return g_object_new( - KOTO_TYPE_DISC_VIEW, - "disc", - disc_number, - "album", - album, - "orientation", - GTK_ORIENTATION_VERTICAL, - NULL - ); -} diff --git a/src/pages/music/disc-view.h b/src/pages/music/disc-view.h deleted file mode 100644 index 35e7bcd..0000000 --- a/src/pages/music/disc-view.h +++ /dev/null @@ -1,66 +0,0 @@ -/* disc-view.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../indexer/structs.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_DISC_VIEW (koto_disc_view_get_type()) - -G_DECLARE_FINAL_TYPE(KotoDiscView, koto_disc_view, KOTO, DISC_VIEW, GtkBox) - -KotoDiscView* koto_disc_view_new( - KotoAlbum * album, - guint disc -); - -void koto_disc_view_add_track( - KotoDiscView * self, - KotoTrack * track -); - -void koto_disc_view_handle_selected_rows_changed( - GtkListBox * box, - gpointer user_data -); - -void koto_disc_view_set_album( - KotoDiscView * self, - KotoAlbum * album -); - -void koto_disc_view_set_disc_label_visible( - KotoDiscView * self, - gboolean visible -); - -void koto_disc_view_set_disc_number( - KotoDiscView * self, - guint disc_number -); - -gint koto_disc_view_sort_list_box_rows( - GtkListBoxRow * row1, - GtkListBoxRow * row2, - gpointer user_data -); - -G_END_DECLS diff --git a/src/pages/music/music-local.c b/src/pages/music/music-local.c deleted file mode 100644 index b979817..0000000 --- a/src/pages/music/music-local.c +++ /dev/null @@ -1,252 +0,0 @@ -/* music-local.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/button.h" -#include "../../db/cartographer.h" -#include "../../indexer/structs.h" -#include "config/config.h" -#include "../../koto-utils.h" -#include "music-local.h" - -extern KotoCartographer * koto_maps; - -struct _KotoPageMusicLocal { - GtkBox parent_instance; - GtkWidget * scrolled_window; - GtkWidget * artist_list; - GtkWidget * stack; - GHashTable * artist_name_to_buttons; - - gboolean constructed; -}; - -struct _KotoPageMusicLocalClass { - GtkBoxClass parent_class; -}; - -G_DEFINE_TYPE(KotoPageMusicLocal, koto_page_music_local, GTK_TYPE_BOX); - -KotoPageMusicLocal * music_local_page; - -static void koto_page_music_local_constructed(GObject * obj); - -static void koto_page_music_local_class_init(KotoPageMusicLocalClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->constructed = koto_page_music_local_constructed; -} - -static void koto_page_music_local_init(KotoPageMusicLocal * self) { - self->constructed = FALSE; - self->artist_name_to_buttons = g_hash_table_new(g_str_hash, g_str_equal); - - gtk_widget_add_css_class(GTK_WIDGET(self), "page-music-local"); - gtk_widget_set_hexpand(GTK_WIDGET(self), TRUE); - gtk_widget_set_vexpand(GTK_WIDGET(self), TRUE); - - self->scrolled_window = gtk_scrolled_window_new(); - gtk_scrolled_window_set_min_content_width(GTK_SCROLLED_WINDOW(self->scrolled_window), 300); - gtk_scrolled_window_set_propagate_natural_height(GTK_SCROLLED_WINDOW(self->scrolled_window), FALSE); - gtk_widget_add_css_class(self->scrolled_window, "artist-list"); - gtk_widget_set_vexpand(self->scrolled_window, TRUE); // Expand our scrolled window - gtk_box_prepend(GTK_BOX(self), self->scrolled_window); - - self->artist_list = gtk_list_box_new(); // Create our artist list - g_signal_connect(GTK_LIST_BOX(self->artist_list), "row-activated", G_CALLBACK(koto_page_music_local_handle_artist_click), self); - gtk_list_box_set_activate_on_single_click(GTK_LIST_BOX(self->artist_list), TRUE); - gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->artist_list), GTK_SELECTION_BROWSE); - gtk_list_box_set_sort_func(GTK_LIST_BOX(self->artist_list), koto_page_music_local_sort_artists, NULL, NULL); // Add our sort function - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->scrolled_window), self->artist_list); - - self->stack = gtk_stack_new(); // Create a new stack - gtk_stack_set_transition_duration(GTK_STACK(self->stack), 400); - gtk_stack_set_transition_type(GTK_STACK(self->stack), GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT); - gtk_widget_set_hexpand(self->stack, TRUE); - gtk_widget_set_vexpand(self->stack, TRUE); - gtk_box_append(GTK_BOX(self), self->stack); - - g_signal_connect(koto_maps, "artist-added", G_CALLBACK(koto_page_music_local_handle_artist_added), self); - g_signal_connect(koto_maps, "artist-removed", G_CALLBACK(koto_page_music_local_handle_artist_removed), self); -} - -static void koto_page_music_local_constructed(GObject * obj) { - KotoPageMusicLocal * self = KOTO_PAGE_MUSIC_LOCAL(obj); - - G_OBJECT_CLASS(koto_page_music_local_parent_class)->constructed(obj); - self->constructed = TRUE; -} - -void koto_page_music_local_add_artist( - KotoPageMusicLocal * self, - KotoArtist * artist -) { - if (!KOTO_IS_PAGE_MUSIC_LOCAL(self)) { // Not the local page - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - gchar * artist_name = koto_artist_get_name(artist); // Get the artist name - - if (!GTK_IS_STACK(self->stack)) { - return; - } - - GtkWidget * stack_child = gtk_stack_get_child_by_name(GTK_STACK(self->stack), artist_name); - - if (GTK_IS_WIDGET(stack_child)) { // Already have a page for this artist - g_free(artist_name); - return; - } - - KotoButton * artist_button = koto_button_new_plain(artist_name); - gtk_list_box_prepend(GTK_LIST_BOX(self->artist_list), GTK_WIDGET(artist_button)); - g_hash_table_replace(self->artist_name_to_buttons, artist_name, artist_button); // Add the KotoButton for this artist to the hash table, that way we can reference and remove it when we remove the artist - - KotoArtistView * artist_view = koto_artist_view_new(artist); // Create our new artist view - - gtk_stack_add_named(GTK_STACK(self->stack), koto_artist_view_get_main(artist_view), artist_name); - g_free(artist_name); -} - -void koto_page_music_local_go_to_artist_by_name( - KotoPageMusicLocal * self, - gchar * artist_name -) { - gtk_stack_set_visible_child_name(GTK_STACK(self->stack), artist_name); -} - -void koto_page_music_local_go_to_artist_by_uuid( - KotoPageMusicLocal * self, - gchar * artist_uuid -) { - KotoArtist * artist = koto_cartographer_get_artist_by_uuid(koto_maps, artist_uuid); // Get the artist - - if (!KOTO_IS_ARTIST(artist)) { // No artist for this UUID - return; - } - - gchar * artist_name = NULL; - - g_object_get( - artist, - "name", - &artist_name, - NULL - ); - - if (!koto_utils_string_is_valid(artist_name)) { // Failed to get the artist name - return; - } - - koto_page_music_local_go_to_artist_by_name(self, artist_name); -} - -void koto_page_music_local_handle_artist_click( - GtkListBox * box, - GtkListBoxRow * row, - gpointer data -) { - (void) box; - KotoPageMusicLocal * self = (KotoPageMusicLocal*) data; - KotoButton * btn = KOTO_BUTTON(gtk_list_box_row_get_child(row)); - - gchar * artist_name; - - g_object_get(btn, "button-text", &artist_name, NULL); - koto_page_music_local_go_to_artist_by_name(self, artist_name); -} - -void koto_page_music_local_handle_artist_added( - KotoCartographer * carto, - KotoArtist * artist, - gpointer user_data -) { - (void) carto; - KotoPageMusicLocal * self = user_data; - - if (!KOTO_IS_PAGE_MUSIC_LOCAL(self)) { // Not the page - return; - } - - if (!KOTO_IS_ARTIST(artist)) { // Not an artist - return; - } - - if (koto_artist_get_lib_type(artist) != KOTO_LIBRARY_TYPE_MUSIC) { // Not in our music library - return; - } - - koto_page_music_local_add_artist(self, artist); // Add the artist if needed -} - -void koto_page_music_local_handle_artist_removed( - KotoCartographer * carto, - gchar * artist_uuid, - gchar * artist_name, - gpointer user_data -) { - (void) carto; - (void) artist_uuid; - - KotoPageMusicLocal * self = user_data; - - GtkWidget * existing_artist_page = gtk_stack_get_child_by_name(GTK_STACK(self->stack), artist_name); - - // TODO: Navigate away from artist if we are currently looking at it - if (GTK_IS_WIDGET(existing_artist_page)) { // Page exists - gtk_stack_remove(GTK_STACK(self->stack), existing_artist_page); // Remove the artist page - } - - KotoButton * btn = g_hash_table_lookup(self->artist_name_to_buttons, artist_name); - - if (KOTO_IS_BUTTON(btn)) { // If we have a button for this artist - gtk_list_box_remove(GTK_LIST_BOX(self->artist_list), GTK_WIDGET(btn)); // Remove the button - } -} - -int koto_page_music_local_sort_artists( - GtkListBoxRow * artist1, - GtkListBoxRow * artist2, - gpointer user_data -) { - (void) user_data; - KotoButton * artist1_btn = KOTO_BUTTON(gtk_list_box_row_get_child(artist1)); - KotoButton * artist2_btn = KOTO_BUTTON(gtk_list_box_row_get_child(artist2)); - - gchar * artist1_text; - gchar * artist2_text; - - g_object_get(artist1_btn, "button-text", &artist1_text, NULL); - g_object_get(artist2_btn, "button-text", &artist2_text, NULL); - - return g_utf8_collate(artist1_text, artist2_text); -} - -KotoPageMusicLocal * koto_page_music_local_new() { - return g_object_new( - KOTO_TYPE_PAGE_MUSIC_LOCAL, - "orientation", - GTK_ORIENTATION_HORIZONTAL, - NULL - ); -} diff --git a/src/pages/music/music-local.h b/src/pages/music/music-local.h deleted file mode 100644 index 1bef6d5..0000000 --- a/src/pages/music/music-local.h +++ /dev/null @@ -1,72 +0,0 @@ -/* music-local.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../indexer/structs.h" -#include "artist-view.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_PAGE_MUSIC_LOCAL (koto_page_music_local_get_type()) - -G_DECLARE_FINAL_TYPE(KotoPageMusicLocal, koto_page_music_local, KOTO, PAGE_MUSIC_LOCAL, GtkBox) - -KotoPageMusicLocal* koto_page_music_local_new(); -void koto_page_music_local_add_artist( - KotoPageMusicLocal * self, - KotoArtist * artist -); - -void koto_page_music_local_handle_artist_click( - GtkListBox * box, - GtkListBoxRow * row, - gpointer data -); - -void koto_page_music_local_handle_artist_added( - KotoCartographer * carto, - KotoArtist * artist, - gpointer user_data -); - -void koto_page_music_local_handle_artist_removed( - KotoCartographer * carto, - gchar * artist_uuid, - gchar * artist_name, - gpointer user_data -); - -void koto_page_music_local_go_to_artist_by_name( - KotoPageMusicLocal * self, - gchar * artist_name -); - -void koto_page_music_local_go_to_artist_by_uuid( - KotoPageMusicLocal * self, - gchar * artist_uuid -); - -int koto_page_music_local_sort_artists( - GtkListBoxRow * artist1, - GtkListBoxRow * artist2, - gpointer user_data -); - -G_END_DECLS diff --git a/src/pages/playlist/list.c b/src/pages/playlist/list.c deleted file mode 100644 index b49b330..0000000 --- a/src/pages/playlist/list.c +++ /dev/null @@ -1,324 +0,0 @@ -/* list.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../../components/action-bar.h" -#include "../../components/button.h" -#include "../../components/cover-art-button.h" -#include "../../components/track-table.h" -#include "../../db/cartographer.h" -#include "../../indexer/structs.h" -#include "../../playlist/current.h" -#include "../../playlist/playlist.h" -#include "../../koto-window.h" -#include "../../playlist/create-modify-dialog.h" -#include "list.h" - -extern KotoActionBar * action_bar; -extern KotoCartographer * koto_maps; -extern KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog; -extern KotoCurrentPlaylist * current_playlist; -extern KotoWindow * main_window; - -enum { - PROP_0, - PROP_PLAYLIST_UUID, - N_PROPERTIES -}; - -static GParamSpec * props[N_PROPERTIES] = { - NULL, -}; - -struct _KotoPlaylistPage { - GObject parent_instance; - KotoPlaylist * playlist; - gchar * uuid; - - GtkWidget * main; // Our Scrolled Window - GtkWidget * content; // Content inside scrolled window - GtkWidget * header; - - KotoCoverArtButton * playlist_image; - GtkWidget * name_label; - GtkWidget * tracks_count_label; - GtkWidget * type_label; - KotoButton * favorite_button; - KotoButton * edit_button; - - KotoTrackTable * table; -}; - -struct _KotoPlaylistPageClass { - GObjectClass parent_class; -}; - -G_DEFINE_TYPE(KotoPlaylistPage, koto_playlist_page, G_TYPE_OBJECT); - -static void koto_playlist_page_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_playlist_page_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_playlist_page_class_init(KotoPlaylistPageClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->get_property = koto_playlist_page_get_property; - gobject_class->set_property = koto_playlist_page_set_property; - - props[PROP_PLAYLIST_UUID] = g_param_spec_string( - "uuid", - "UUID of associated Playlist", - "UUID of associated Playlist", - NULL, - G_PARAM_CONSTRUCT_ONLY | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); -} - -static void koto_playlist_page_init(KotoPlaylistPage * self) { - self->main = gtk_scrolled_window_new(); // Create our scrolled window - self->content = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - gtk_widget_add_css_class(self->content, "playlist-page"); - - gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(self->main), self->content); - - self->header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); // Create a horizontal box - gtk_widget_add_css_class(self->header, "playlist-page-header"); - gtk_box_prepend(GTK_BOX(self->content), self->header); - - self->playlist_image = koto_cover_art_button_new(220, 220, NULL); // Create our Cover Art Button with no art by default - KotoButton * cover_art_button = koto_cover_art_button_get_button(self->playlist_image); // Get the button for the cover art - - koto_button_add_click_handler(cover_art_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_cover_art_clicked), self); - - GtkWidget * info_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); - - gtk_widget_set_size_request(info_box, -1, 220); - gtk_widget_add_css_class(info_box, "playlist-page-header-info"); - gtk_widget_set_hexpand(info_box, TRUE); - - self->type_label = gtk_label_new(NULL); // Create our type label - gtk_widget_set_halign(self->type_label, GTK_ALIGN_START); - - self->name_label = gtk_label_new(NULL); - gtk_widget_set_halign(self->name_label, GTK_ALIGN_START); - - self->tracks_count_label = gtk_label_new(NULL); - gtk_widget_set_halign(self->tracks_count_label, GTK_ALIGN_START); - gtk_widget_set_valign(self->tracks_count_label, GTK_ALIGN_END); - - gtk_box_append(GTK_BOX(info_box), self->type_label); - gtk_box_append(GTK_BOX(info_box), self->name_label); - gtk_box_append(GTK_BOX(info_box), self->tracks_count_label); - - self->favorite_button = koto_button_new_with_icon(NULL, "emblem-favorite-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - self->edit_button = koto_button_new_with_icon(NULL, "emblem-system-symbolic", NULL, KOTO_BUTTON_PIXBUF_SIZE_NORMAL); - koto_button_add_click_handler(self->edit_button, KOTO_BUTTON_CLICK_TYPE_PRIMARY, G_CALLBACK(koto_playlist_page_handle_edit_button_clicked), self); // Set up our binding - - gtk_box_append(GTK_BOX(self->header), koto_cover_art_button_get_main(self->playlist_image)); // Add the Cover Art Button main overlay - gtk_box_append(GTK_BOX(self->header), info_box); // Add our info box - gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->favorite_button)); // Add the favorite button - gtk_box_append(GTK_BOX(self->header), GTK_WIDGET(self->edit_button)); // Add the edit button - - self->table = koto_track_table_new(); // Create our new KotoTrackTable - GtkWidget * track_main = koto_track_table_get_main(self->table); - gtk_box_append(GTK_BOX(self->content), track_main); -} - -static void koto_playlist_page_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoPlaylistPage * self = KOTO_PLAYLIST_PAGE(obj); - - switch (prop_id) { - case PROP_PLAYLIST_UUID: - g_value_set_string(val, self->uuid); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_playlist_page_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoPlaylistPage * self = KOTO_PLAYLIST_PAGE(obj); - - switch (prop_id) { - case PROP_PLAYLIST_UUID: - koto_playlist_page_set_playlist_uuid(self, g_strdup(g_value_get_string(val))); // Call to our playlist UUID set function - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self) { - return self->main; -} - -void koto_playlist_page_handle_cover_art_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlaylistPage * self = user_data; - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // No playlist set - return; - } - - koto_current_playlist_set_playlist(current_playlist, self->playlist, TRUE, FALSE); // Switch to this playlist and start playing immediately -} - -void koto_playlist_page_handle_edit_button_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - KotoPlaylistPage * self = user_data; - - koto_create_modify_playlist_dialog_set_playlist_uuid(playlist_create_modify_dialog, koto_playlist_get_uuid(self->playlist)); - koto_window_show_dialog(main_window, "create-modify-playlist"); -} - -void koto_playlist_page_handle_playlist_modified( - KotoPlaylist * playlist, - gpointer user_data -) { - if (!KOTO_IS_PLAYLIST(playlist)) { - return; - } - - KotoPlaylistPage * self = user_data; - - if (!KOTO_IS_PLAYLIST_PAGE(self)) { - return; - } - - gchar * artwork = koto_playlist_get_artwork(playlist); // Get the artwork - - if (koto_utils_string_is_valid(artwork)) { // Have valid artwork - koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our Koto Cover Art Button - } - - gchar * name = koto_playlist_get_name(playlist); // Get the name - - if (koto_utils_string_is_valid(name)) { // Have valid name - gtk_label_set_label(GTK_LABEL(self->name_label), name); // Update the name label - } -} - -void koto_playlist_page_set_playlist_uuid( - KotoPlaylistPage * self, - gchar * playlist_uuid -) { - if (!KOTO_IS_PLAYLIST_PAGE(self)) { - return; - } - - if (!koto_utils_string_is_valid(playlist_uuid)) { // Provided UUID string is not valid - return; - } - - if (!koto_cartographer_has_playlist_by_uuid(koto_maps, playlist_uuid)) { // If we don't have this playlist - return; - } - - self->uuid = g_strdup(playlist_uuid); // Duplicate the playlist UUID - KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->uuid); - - self->playlist = playlist; - koto_playlist_page_update_header(self); // Update our header - - g_signal_connect(playlist, "modified", G_CALLBACK(koto_playlist_page_handle_playlist_modified), self); // Handle playlist modification - koto_track_table_set_playlist(self->table, playlist); // Set our track table -} - -void koto_playlist_page_update_header(KotoPlaylistPage * self) { - if (!KOTO_IS_PLAYLIST_PAGE(self)) { - return; - } - - if (!KOTO_IS_PLAYLIST(self->playlist)) { // Not a playlist - return; - } - - gboolean ephemeral = TRUE; - - g_object_get( - self->playlist, - "ephemeral", - &ephemeral, - NULL - ); - - gtk_label_set_text(GTK_LABEL(self->type_label), ephemeral ? "Generated playlist" : "Curated playlist"); - - gtk_label_set_text(GTK_LABEL(self->name_label), koto_playlist_get_name(self->playlist)); // Set the name label to our playlist name - guint track_count = koto_playlist_get_length(self->playlist); // Get the number of tracks - - gtk_label_set_text(GTK_LABEL(self->tracks_count_label), g_strdup_printf(track_count != 1 ? "%u tracks" : "%u track", track_count)); // Set the text to "N tracks" where N is the number - - gchar * artwork = koto_playlist_get_artwork(self->playlist); - - if (koto_utils_string_is_valid(artwork)) { // Have artwork - koto_cover_art_button_set_art_path(self->playlist_image, artwork); // Update our artwork - } -} - -KotoPlaylistPage * koto_playlist_page_new(gchar * playlist_uuid) { - return g_object_new( - KOTO_TYPE_PLAYLIST_PAGE, - "uuid", - playlist_uuid, - NULL - ); -} diff --git a/src/pages/playlist/list.h b/src/pages/playlist/list.h deleted file mode 100644 index 6822a26..0000000 --- a/src/pages/playlist/list.h +++ /dev/null @@ -1,87 +0,0 @@ -/* list.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once - -#include -#include -#include "../../components/action-bar.h" -#include "../../playlist/playlist.h" -#include "../../koto-utils.h" - -G_BEGIN_DECLS - -#define KOTO_TYPE_PLAYLIST_PAGE koto_playlist_page_get_type() -G_DECLARE_FINAL_TYPE(KotoPlaylistPage, koto_playlist_page, KOTO, PLAYLIST_PAGE, GObject); -#define KOTO_IS_PLAYLIST_PAGE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST_PAGE)) - -KotoPlaylistPage * koto_playlist_page_new(gchar * playlist_uuid); - -void koto_playlist_page_add_track( - KotoPlaylistPage * self, - const gchar * track_uuid -); - -void koto_playlist_page_create_tracks_header(KotoPlaylistPage * self); - -void koto_playlist_page_bind_track_item( - GtkListItemFactory * factory, - GtkListItem * item, - KotoPlaylistPage * self -); - -void koto_playlist_page_remove_track( - KotoPlaylistPage * self, - const gchar * track_uuid -); - -GtkWidget * koto_playlist_page_get_main(KotoPlaylistPage * self); - - -void koto_playlist_page_handle_cover_art_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_playlist_page_handle_edit_button_clicked( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_playlist_page_handle_playlist_modified( - KotoPlaylist * playlist, - gpointer user_data -); - -void koto_playlist_page_hide_popover(KotoPlaylistPage * self); - -void koto_playlist_page_set_playlist_uuid( - KotoPlaylistPage * self, - gchar * playlist_uuid -); - -void koto_playlist_page_show_popover(KotoPlaylistPage * self); - -void koto_playlist_page_update_header(KotoPlaylistPage * self); - -G_END_DECLS diff --git a/src/playback/engine.c b/src/playback/engine.c deleted file mode 100644 index 510f2bf..0000000 --- a/src/playback/engine.c +++ /dev/null @@ -1,866 +0,0 @@ -/* engine.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include "../config/config.h" -#include "../db/cartographer.h" -#include "../playlist/current.h" -#include "../indexer/structs.h" -#include "../koto-paths.h" -#include "../koto-utils.h" -#include "engine.h" -#include "mpris.h" - -enum { - SIGNAL_IS_PLAYING, - SIGNAL_IS_PAUSED, - SIGNAL_PLAY_STATE_CHANGE, - SIGNAL_TICK_DURATION, - SIGNAL_TICK_TRACK, - SIGNAL_TRACK_CHANGE, - SIGNAL_TRACK_REPEAT_CHANGE, - SIGNAL_TRACK_SHUFFLE_CHANGE, - N_SIGNALS -}; - -extern gchar * koto_rev_dns; - -static guint playback_engine_signals[N_SIGNALS] = { - 0 -}; - -extern KotoCartographer * koto_maps; -extern KotoConfig * config; -extern KotoCurrentPlaylist * current_playlist; - -KotoPlaybackEngine * playback_engine; - -struct _KotoPlaybackEngine { - GObject parent_class; - GstElement * player; - GstElement * playbin; - GstElement * suppress_video; - GstBus * monitor; - - GstQuery * duration_query; - GstQuery * position_query; - - KotoTrack * current_track; - - gboolean is_muted; - gboolean is_repeat_enabled; - - gboolean is_playing; - gboolean is_playing_specific_track; - gboolean is_shuffle_enabled; - - gboolean tick_duration_timer_running; - gboolean tick_track_timer_running; - gboolean tick_update_playlist_state; - - gboolean via_config_continue_on_playlist; // Pulls from our Koto Config - gboolean via_config_maintain_shuffle; // Pulls from our Koto Config - - guint playback_position; - gint requested_playback_position; - - gdouble rate; - gdouble volume; -}; - -struct _KotoPlaybackEngineClass { - GObjectClass parent_class; - - void (* is_playing) (KotoPlaybackEngine * engine); - void (* is_paused) (KotoPlaybackEngine * engine); - void (* play_state_changed) (KotoPlaybackEngine * engine); - void (* tick_duration) (KotoPlaybackEngine * engine); - void (* tick_track) (KotoPlaybackEngine * engine); - void (* track_changed) (KotoPlaybackEngine * engine); - void (* track_repeat_changed) (KotoPlaybackEngine * engine); - void (* track_shuffle_changed) (KotoPlaybackEngine * engine); -}; - -G_DEFINE_TYPE(KotoPlaybackEngine, koto_playback_engine, G_TYPE_OBJECT); - -static void koto_playback_engine_class_init(KotoPlaybackEngineClass * c) { - c->play_state_changed = NULL; - GObjectClass * gobject_class; - gobject_class = G_OBJECT_CLASS(c); - - playback_engine_signals[SIGNAL_IS_PLAYING] = g_signal_new( - "is-playing", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_IS_PAUSED] = g_signal_new( - "is-paused", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE] = g_signal_new( - "play-state-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, play_state_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_TICK_DURATION] = g_signal_new( - "tick-duration", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, tick_duration), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_TICK_TRACK] = g_signal_new( - "tick-track", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, tick_track), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_TRACK_CHANGE] = g_signal_new( - "track-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, track_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_TRACK_REPEAT_CHANGE] = g_signal_new( - "track-repeat-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, track_repeat_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playback_engine_signals[SIGNAL_TRACK_SHUFFLE_CHANGE] = g_signal_new( - "track-shuffle-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaybackEngineClass, track_shuffle_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); -} - -static void koto_playback_engine_init(KotoPlaybackEngine * self) { - self->current_track = NULL; - current_playlist = koto_current_playlist_new(); // Ensure our global current playlist is set - - self->player = gst_pipeline_new("player"); - self->playbin = gst_element_factory_make("playbin", NULL); - self->suppress_video = gst_element_factory_make("fakesink", "suppress-video"); - - g_object_set(self->playbin, "video-sink", self->suppress_video, NULL); - self->rate = 1.0; - self->volume = 0.5; - koto_playback_engine_set_volume(self, 0.5); - - gst_bin_add(GST_BIN(self->player), self->playbin); - self->monitor = gst_bus_new(); // Get the bus for the playbin - - self->duration_query = gst_query_new_duration(GST_FORMAT_TIME); // Create our re-usable query for querying the duration - self->position_query = gst_query_new_position(GST_FORMAT_TIME); // Create our re-usable query for querying the position - - if (GST_IS_BUS(self->monitor)) { - gst_bus_add_watch(self->monitor, koto_playback_engine_monitor_changed, self); - gst_element_set_bus(self->player, self->monitor); // Set our bus to monitor changes - } - - self->is_muted = FALSE; - self->is_playing = FALSE; - self->is_playing_specific_track = FALSE; - self->is_repeat_enabled = FALSE; - self->is_shuffle_enabled = FALSE; - self->requested_playback_position = -1; - self->tick_duration_timer_running = FALSE; - self->tick_track_timer_running = FALSE; - self->tick_update_playlist_state = FALSE; - self->via_config_continue_on_playlist = FALSE; - self->via_config_maintain_shuffle = TRUE; - - koto_playback_engine_apply_configuration_state(NULL, 0, self); // Apply configuration immediately - - g_signal_connect(config, "notify::playback-continue-on-playlist", G_CALLBACK(koto_playback_engine_apply_configuration_state), self); // Handle changes to our config - g_signal_connect(config, "notify::last-used-volume", G_CALLBACK(koto_playback_engine_apply_configuration_state), self); // Handle changes to our config - g_signal_connect(config, "notify::maintain-shuffle", G_CALLBACK(koto_playback_engine_apply_configuration_state), self); // Handle changes to our config - - if (KOTO_IS_CURRENT_PLAYLIST(current_playlist)) { - g_signal_connect(current_playlist, "playlist-changed", G_CALLBACK(koto_playback_engine_handle_current_playlist_changed), self); - } -} - -void koto_playback_engine_apply_configuration_state( - KotoConfig * c, - guint prop_id, - KotoPlaybackEngine * self -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine - return; - } - - (void) c; - (void) prop_id; - - gboolean config_continue_on_playlist = self->via_config_continue_on_playlist; - gdouble config_last_used_volume = 0.5; - gboolean config_maintain_shuffle = self->via_config_maintain_shuffle; - - g_object_get( - config, - "playback-continue-on-playlist", - &config_continue_on_playlist, - "playback-last-used-volume", - &config_last_used_volume, - "playback-maintain-shuffle", - &config_maintain_shuffle, - NULL - ); - - self->via_config_continue_on_playlist = config_continue_on_playlist; - self->via_config_maintain_shuffle = config_maintain_shuffle; - - if (self->volume != config_last_used_volume) { // Not the same volume - koto_playback_engine_set_volume(self, config_last_used_volume); - } -} - -void koto_playback_engine_backwards(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine - return; - } - - KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist - - if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently - return; - } - - if (self->is_repeat_enabled || self->is_playing_specific_track) { // Repeat enabled or playing a specific track - return; - } - - koto_playback_engine_set_track_playback_position(self, 0); // Reset the current track position to 0 - koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_previous(playlist), FALSE); -} - -void koto_playback_engine_handle_current_playlist_changed( - KotoCurrentPlaylist * current_pl, - KotoPlaylist * playlist, - gboolean play_immediately, - gboolean play_current, - gpointer user_data -) { - (void) current_pl; - KotoPlaybackEngine * self = user_data; - - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - if (self->is_shuffle_enabled) { // If shuffle is enabled - koto_playback_engine_set_track_shuffle(self, self->via_config_maintain_shuffle); // Set to our maintain shuffle value - } - - if (play_immediately) { // Should play immediately - gchar * play_uuid = NULL; - - if (play_current) { // Play the current track - KotoTrack * track = koto_playlist_get_current_track(playlist); // Get the current track - - if (KOTO_IS_TRACK(track)) { - play_uuid = koto_track_get_uuid(track); - } - } - - if (!koto_utils_string_is_valid(play_uuid)) { - play_uuid = koto_playlist_go_to_next(playlist); - } - - koto_playback_engine_set_track_by_uuid(self, play_uuid, FALSE); // Go to "next" which is the first track - } -} - -void koto_playback_engine_forwards(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine - return; - } - - KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); // Get the current playlist - - if (!KOTO_IS_PLAYLIST(playlist)) { // If we do not have a playlist currently - return; - } - - if (self->is_repeat_enabled) { // Is repeat enabled - koto_playback_engine_set_position(self, 0); // Set position back to 0 to repeat the track - } else { // If repeat is not enabled - if ( - (self->via_config_continue_on_playlist && self->is_playing_specific_track) || // Playing a specific track and wanting to continue on the playlist - (!self->is_playing_specific_track) // Not playing a specific track - ) { - koto_playback_engine_set_track_playback_position(self, 0); // Reset the current track position to 0 - koto_playback_engine_set_track_by_uuid(self, koto_playlist_go_to_next(playlist), FALSE); - } - } -} - -KotoTrack * koto_playback_engine_get_current_track(KotoPlaybackEngine * self) { - return KOTO_IS_PLAYBACK_ENGINE(self) ? self->current_track : NULL; -} - -gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine - return 0; - } - - guint64 track_duration = koto_track_get_duration(self->current_track); // Get the current track's duration - - if (track_duration != 0) { // Have a duration stored - return (gint64) track_duration; // Trust what we got from the ID3 data - } - - gint64 duration = 0; - - if (gst_element_query(self->player, self->duration_query)) { // Able to query our duration - gst_query_parse_duration(self->duration_query, NULL, &duration); // Get the duration") - duration = duration / GST_SECOND; // Divide by NS to get seconds - } - - return duration; -} - -gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine - return 0; - } - - gdouble progress = 0.0; - gint64 gstprog = 0; - - if (gst_element_query(self->playbin, self->position_query)) { // Able to get our position - gst_query_parse_position(self->position_query, NULL, &gstprog); // Get the progress - - if (gstprog < 1) { // Less than a second - return 0.0; - } - - progress = gstprog / GST_SECOND; // Divide by GST_SECOND - } - - return progress; -} - -gdouble koto_playback_engine_get_sane_playback_rate(gdouble rate) { - if ((rate < 0.25) && (rate != 0)) { // 0.25 is probably the most reasonable limit - return 0.25; - } else if (rate > 2.5) { // Anything greater than 2.5 - return 2.5; - } else if (rate == 0) { // Possible conversion failure - return 1.0; - } else { - return rate; - } -} - -GstState koto_playback_engine_get_state(KotoPlaybackEngine * self) { - return GST_STATE(self->player); -} - -gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self) { - return KOTO_IS_PLAYBACK_ENGINE(self) ? self->is_repeat_enabled : FALSE; -} - -gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self) { - return KOTO_IS_PLAYBACK_ENGINE(self) ? self->is_shuffle_enabled : FALSE; -} - -gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self) { - return KOTO_IS_PLAYBACK_ENGINE(self) ? self->volume : 0; -} - -void koto_playback_engine_jump_backwards(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not an engine - return; - } - - if (!self->is_playing) { // Is not playing - return; - } - - gdouble cur_pos = koto_playback_engine_get_progress(self); // Get the current position - guint backwards_increment = 10; - g_object_get( - config, - "playback-jump-backwards-increment", // Get our backwards increment from our settings - &backwards_increment, - NULL - ); - - gint new_pos = (gint) cur_pos - backwards_increment; - - if (new_pos < 0) { // Before the beginning of time - new_pos = 0; - } - - koto_playback_engine_set_position(self, new_pos); -} - -void koto_playback_engine_jump_forwards(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not an engine - return; - } - - if (!self->is_playing) { // Is not playing - return; - } - - gdouble cur_pos = koto_playback_engine_get_progress(self); // Get the current position - guint forwards_increment = 30; - g_object_get( - config, - "playback-jump-forwards-increment", // Get our forward increment from our settings - &forwards_increment, - NULL - ); - - gint new_pos = (gint) cur_pos + forwards_increment; - koto_playback_engine_set_position(self, new_pos); -} - -gboolean koto_playback_engine_monitor_changed( - GstBus * bus, - GstMessage * msg, - gpointer user_data -) { - (void) bus; - KotoPlaybackEngine * self = user_data; - - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return FALSE; - } - - switch (GST_MESSAGE_TYPE(msg)) { - case GST_MESSAGE_DURATION_CHANGED: { // Duration changed - koto_playback_engine_tick_duration(self); - koto_playback_engine_tick_track(self); - break; - } - case GST_MESSAGE_STATE_CHANGED: { // State changed - GstState old_state; - GstState new_state; - GstState pending_state; - gst_message_parse_state_changed(msg, &old_state, &new_state, &pending_state); - - gboolean possibly_buffered = (old_state == GST_STATE_READY) && (new_state == GST_STATE_PAUSED) && (pending_state == GST_STATE_PLAYING); - - if (possibly_buffered && (self->requested_playback_position != -1)) { // Have a requested playback position - koto_playback_engine_set_position(self, self->requested_playback_position); // Set the position - self->requested_playback_position = -1; - koto_playback_engine_play(self); // Start playback - return TRUE; - } - - if (new_state == GST_STATE_PLAYING) { // Now playing - koto_playback_engine_tick_duration(self); - g_signal_emit(self, playback_engine_signals[SIGNAL_IS_PLAYING], 0); // Emit our is playing state signal - } else if (new_state == GST_STATE_PAUSED) { // Now paused - g_signal_emit(self, playback_engine_signals[SIGNAL_IS_PAUSED], 0); // Emit our is paused state signal - } - - if (new_state != GST_STATE_VOID_PENDING) { - g_signal_emit(self, playback_engine_signals[SIGNAL_PLAY_STATE_CHANGE], 0); // Emit our play state signal - } - - break; - } - case GST_MESSAGE_EOS: { // Reached end of stream - koto_playback_engine_forwards(self); // Go to the next track - break; - } - default: - break; - } - return TRUE; -} - -void koto_playback_engine_play(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - self->is_playing = TRUE; - gst_element_set_state(self->player, GST_STATE_PLAYING); // Set our state to play - - if (!self->tick_duration_timer_running) { - self->tick_duration_timer_running = TRUE; - g_timeout_add(1000, koto_playback_engine_tick_duration, self); // Create a 1s duration tick - } - - if (!self->tick_track_timer_running) { - self->tick_track_timer_running = TRUE; - g_timeout_add(100, koto_playback_engine_tick_track, self); // Create a 100ms track tick - } - - if (!self->tick_update_playlist_state) { - self->tick_update_playlist_state = TRUE; - g_timeout_add(60000, koto_playback_engine_tick_update_playlist_state, self); // Update the current playlist state every minute - } - - koto_update_mpris_playback_state(GST_STATE_PLAYING); -} - -void koto_playback_engine_pause(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - self->is_playing = FALSE; - gst_element_change_state(self->player, GST_STATE_CHANGE_PLAYING_TO_PAUSED); - koto_update_mpris_playback_state(GST_STATE_PAUSED); -} - -void koto_playback_engine_set_playback_rate( - KotoPlaybackEngine * self, - gdouble rate -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { // Not a playback engine - return; - } - - self->rate = koto_playback_engine_get_sane_playback_rate(rate); // Get a fixed rate and set our current rate to it - gst_element_set_state(self->player, GST_STATE_PAUSED); // Set our state to paused - koto_playback_engine_set_position(self, koto_playback_engine_get_progress(self)); // Basically creates a new seek event - gst_element_set_state(self->player, GST_STATE_PLAYING); // Set our state to play -} - -void koto_playback_engine_set_position( - KotoPlaybackEngine * self, - int position -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - gst_element_seek( - self->playbin, - self->rate, - GST_FORMAT_TIME, - GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, - GST_SEEK_TYPE_SET, - position * GST_SECOND, - GST_SEEK_TYPE_NONE, - -1 - ); -} - -void koto_playback_engine_set_track_playback_position( - KotoPlaybackEngine * self, - guint64 pos -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - if (!KOTO_IS_TRACK(self->current_track) || self->is_repeat_enabled) { // If we do not have a track or repeating - return; - } - - koto_track_set_playback_position(self->current_track, pos); // Set the playback position of the track -} - -void koto_playback_engine_set_track_repeat( - KotoPlaybackEngine * self, - gboolean enable_repeat -) { - self->is_repeat_enabled = enable_repeat; - g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_REPEAT_CHANGE], 0); // Emit our track repeat changed event -} - -void koto_playback_engine_set_track_shuffle( - KotoPlaybackEngine * self, - gboolean enable_shuffle -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); - - if (!KOTO_IS_PLAYLIST(playlist)) { // Don't have a playlist currently - return; - } - - self->is_shuffle_enabled = enable_shuffle; - g_object_set(playlist, "is-shuffle-enabled", self->is_shuffle_enabled, NULL); // Set the is-shuffle-enabled on any existing playlist - g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_SHUFFLE_CHANGE], 0); // Emit our track shuffle changed event -} - -void koto_playback_engine_set_track_by_uuid( - KotoPlaybackEngine * self, - gchar * track_uuid, - gboolean playing_specific_track -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - if (!koto_utils_string_is_valid(track_uuid)) { // If this is not a valid track uuid string - return; - } - - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track from cartographer - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - self->current_track = track; - - gchar * track_file_path = koto_track_get_path(self->current_track); // Get the most optimal path for the track given the libraries it is in - - koto_playback_engine_stop(self); // Stop current track - - self->is_playing_specific_track = playing_specific_track; - - gchar * gst_filename = gst_filename_to_uri(track_file_path, NULL); // Get the gst supported file naem - - g_object_set(self->playbin, "uri", gst_filename, NULL); - g_free(gst_filename); // Free the filename - - self->requested_playback_position = koto_track_get_playback_position(self->current_track); // Set our requested playback position - koto_playback_engine_play(self); // Play the new track - koto_playback_engine_set_volume(self, self->volume); // Re-enforce our volume on the updated playbin - - GVariant * metadata = koto_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata - GVariantDict * metadata_dict = g_variant_dict_new(metadata); - - g_signal_emit(self, playback_engine_signals[SIGNAL_TRACK_CHANGE], 0); // Emit our track change signal - koto_update_mpris_info_for_track(self->current_track); - - GVariant * track_name_var = g_variant_dict_lookup_value(metadata_dict, "xesam:title", NULL); // Get the GVariant for the name of the track - const gchar * track_name = g_variant_get_string(track_name_var, NULL); // Get the string of the track name - - GVariant * album_name_var = g_variant_dict_lookup_value(metadata_dict, "xesam:album", NULL); // Get the GVariant for the album name - gchar * album_name = (album_name_var != NULL) ? g_strdup(g_variant_get_string(album_name_var, NULL)) : NULL; // Get the string for the album name - - GVariant * artist_name_var = g_variant_dict_lookup_value(metadata_dict, "playbackengine:artist", NULL); // Get the GVariant for the name of the artist - gchar * artist_name = g_strdup(g_variant_get_string(artist_name_var, NULL)); // Get the string for the artist name - - gchar * artist_album_combo = koto_utils_string_is_valid(album_name) ? g_strjoin(" - ", artist_name, album_name, NULL) : artist_name; // Join artist and album name separated by " - " - - gchar * icon_name = "audio-x-generic-symbolic"; - - if (g_variant_dict_contains(metadata_dict, "mpris:artUrl")) { // If we have artwork specified - GVariant * art_url_var = g_variant_dict_lookup_value(metadata_dict, "mpris:artUrl", NULL); // Get the GVariant for the art URL - const gchar * art_uri = g_variant_get_string(art_url_var, NULL); // Get the string for the artwork - icon_name = koto_utils_string_replace_all(g_strdup(art_uri), "file://", ""); - } - - // Super important note: We are not using libnotify directly because the synchronous nature of notify_notification_send seems to result in dbus timeouts - if (g_find_program_in_path("notify-send") != NULL) { // Have notify-send - char * argv[14] = { - "notify-send", - "-a", - "Koto", - "-i", - icon_name, - "-c", - "x-gnome.music", - "-t", - "5000", - "-h", - g_strdup_printf("string:desktop-entry:%s", koto_rev_dns), - g_strdup(track_name), - artist_album_combo, - NULL - }; - - g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, NULL); - } -} - -void koto_playback_engine_set_volume( - KotoPlaybackEngine * self, - gdouble volume -) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - self->volume = volume; - g_object_set(self->playbin, "volume", self->volume, NULL); -} - -void koto_playback_engine_stop(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - gst_element_set_state(self->player, GST_STATE_NULL); - GstPad * pad = gst_element_get_static_pad(self->player, "sink"); // Get the static pad of the audio element - - if (!GST_IS_PAD(pad)) { - return; - } - - gst_pad_set_offset(pad, 0); // Change offset - koto_update_mpris_playback_state(GST_STATE_NULL); -} - -void koto_playback_engine_toggle(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - if (self->is_playing) { // Currently playing - koto_playback_engine_pause(self); // Pause - } else { - koto_playback_engine_play(self); // Play - } - - koto_current_playlist_save_playlist_state(current_playlist); // Save the playlist state during pause and play in case we are doing that remotely -} - -gboolean koto_playback_engine_tick_duration(gpointer user_data) { - KotoPlaybackEngine * self = user_data; - - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return FALSE; - } - - if (self->is_playing) { // Is playing - g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_DURATION], 0); // Emit our 1s track tick - } else { // Not playing so exiting timer - self->tick_duration_timer_running = FALSE; - } - - return self->is_playing; -} - -gboolean koto_playback_engine_tick_track(gpointer user_data) { - KotoPlaybackEngine * self = user_data; - - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return FALSE; - } - - if (self->is_playing) { // Is playing - KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); - - if (KOTO_IS_PLAYLIST(playlist)) { - koto_playlist_emit_modified(playlist); // Emit modified on the playlist to update UI in realtime (ish, okay? every 100ms is enough geez) - } - - g_signal_emit(self, playback_engine_signals[SIGNAL_TICK_TRACK], 0); // Emit our 100ms track tick - } else { - self->tick_track_timer_running = FALSE; - } - - koto_playback_engine_update_track_position(self); // Update track position if needed - - return self->is_playing; -} - -gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data) { - KotoPlaybackEngine * self = user_data; - - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return FALSE; - } - - if (self->is_playing) { // Is playing - koto_current_playlist_save_playlist_state(current_playlist); // Save the state of the current playlist - } else { - self->tick_update_playlist_state = FALSE; - } - - return self->is_playing; -} - -void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - koto_playback_engine_set_track_repeat(self, !self->is_repeat_enabled); -} - -void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - koto_playback_engine_set_track_shuffle(self, !self->is_shuffle_enabled); // Invert the currently shuffling vale -} - -void koto_playback_engine_update_track_position(KotoPlaybackEngine * self) { - if (!KOTO_IS_PLAYBACK_ENGINE(self)) { - return; - } - - koto_playback_engine_set_track_playback_position(self, (guint64) koto_playback_engine_get_progress(self)); // Set to current progress -} - -KotoPlaybackEngine * koto_playback_engine_new() { - return g_object_new(KOTO_TYPE_PLAYBACK_ENGINE, NULL); -} diff --git a/src/playback/engine.h b/src/playback/engine.h deleted file mode 100644 index 8d10a8d..0000000 --- a/src/playback/engine.h +++ /dev/null @@ -1,151 +0,0 @@ -/* engine.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include -#include "../config/config.h" -#include "../playlist/current.h" - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_PLAYBACK_ENGINE (koto_playback_engine_get_type()) -#define KOTO_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_PLAYBACK_ENGINE, KotoPlaybackEngine)) -#define KOTO_IS_PLAYBACK_ENGINE(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYBACK_ENGINE)) - -typedef struct _KotoPlaybackEngine KotoPlaybackEngine; -typedef struct _KotoPlaybackEngineClass KotoPlaybackEngineClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_playback_engine_get_type(void) G_GNUC_CONST; - -/** - * Playback Engine Functions - **/ - -KotoPlaybackEngine * koto_playback_engine_new(); - -void koto_playback_engine_apply_configuration_state( - KotoConfig * config, - guint prop_id, - KotoPlaybackEngine * self -); - -void koto_playback_engine_backwards(KotoPlaybackEngine * self); - -void koto_playback_engine_handle_current_playlist_changed( - KotoCurrentPlaylist * current_pl, - KotoPlaylist * playlist, - gboolean play_immediately, - gboolean play_current, - gpointer user_data -); - -void koto_playback_engine_forwards(KotoPlaybackEngine * self); - -KotoTrack * koto_playback_engine_get_current_track(KotoPlaybackEngine * self); - -gint64 koto_playback_engine_get_duration(KotoPlaybackEngine * self); - -gdouble koto_playback_engine_get_progress(KotoPlaybackEngine * self); - -; - -gdouble koto_playback_engine_get_sane_playback_rate(gdouble rate); - -GstState koto_playback_engine_get_state(KotoPlaybackEngine * self); - -gboolean koto_playback_engine_get_track_repeat(KotoPlaybackEngine * self); - -gboolean koto_playback_engine_get_track_shuffle(KotoPlaybackEngine * self); - -gdouble koto_playback_engine_get_volume(KotoPlaybackEngine * self); - -void koto_playback_engine_jump_backwards(KotoPlaybackEngine * self); - -void koto_playback_engine_jump_forwards(KotoPlaybackEngine * self); - -void koto_playback_engine_mute(KotoPlaybackEngine * self); - -gboolean koto_playback_engine_monitor_changed( - GstBus * bus, - GstMessage * msg, - gpointer user_data -); - -void koto_playback_engine_pause(KotoPlaybackEngine * self); - -void koto_playback_engine_play(KotoPlaybackEngine * self); - -void koto_playback_engine_toggle(KotoPlaybackEngine * self); - -void koto_playback_engine_set_playback_rate( - KotoPlaybackEngine * self, - gdouble rate -); - -void koto_playback_engine_set_position( - KotoPlaybackEngine * self, - int position -); - -void koto_playback_engine_set_track_playback_position( - KotoPlaybackEngine * self, - guint64 pos -); - -void koto_playback_engine_set_track_repeat( - KotoPlaybackEngine * self, - gboolean enable_repeat -); - -void koto_playback_engine_set_track_shuffle( - KotoPlaybackEngine * self, - gboolean enable_shuffle -); - -void koto_playback_engine_set_track_by_uuid( - KotoPlaybackEngine * self, - gchar * track_uuid, - gboolean playing_specific_track -); - -void koto_playback_engine_set_volume( - KotoPlaybackEngine * self, - gdouble volume -); - -void koto_playback_engine_stop(KotoPlaybackEngine * self); - -void koto_playback_engine_toggle_track_repeat(KotoPlaybackEngine * self); - -void koto_playback_engine_toggle_track_shuffle(KotoPlaybackEngine * self); - -void koto_playback_engine_update_duration(KotoPlaybackEngine * self); - -void koto_playback_engine_update_track_position(KotoPlaybackEngine * self); - -gboolean koto_playback_engine_tick_duration(gpointer user_data); - -gboolean koto_playback_engine_tick_track(gpointer user_data); - -gboolean koto_playback_engine_tick_update_playlist_state(gpointer user_data); diff --git a/src/playback/media-keys.c b/src/playback/media-keys.c deleted file mode 100644 index 05248d9..0000000 --- a/src/playback/media-keys.c +++ /dev/null @@ -1,199 +0,0 @@ -/* media-keys.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include "engine.h" -#include "media-keys.h" -#include "../koto-paths.h" - -extern GtkWindow * main_window; -extern KotoPlaybackEngine * playback_engine; -extern gchar * koto_rev_dns; - -GDBusConnection * media_keys_dbus_conn = NULL; -GDBusProxy * media_keys_proxy = NULL; -GDBusNodeInfo * media_keys_introspection_data = NULL; - -static const gchar introspection_xml[] = - "" - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - ""; - -void grab_media_keys() { - if (media_keys_proxy == NULL) { // No connection - return; - } - - g_dbus_proxy_call( - media_keys_proxy, - "GrabMediaPlayerKeys", - g_variant_new("(su)", koto_rev_dns, 0), - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - handle_media_keys_async_done, - NULL - ); -} - -void handle_media_keys_async_done( - GObject * source_object, - GAsyncResult * res, - gpointer user_data -) { - (void) user_data; - g_dbus_proxy_call_finish(G_DBUS_PROXY(source_object), res, NULL); // Ensure we finish our call -} - -void handle_media_keys_signal( - GDBusProxy * proxy, - const gchar * sender_name, - const gchar * signal_name, - GVariant * parameters, - gpointer user_data -) { - (void) proxy; - (void) sender_name; - (void) user_data; - if (g_strcmp0(signal_name, "MediaPlayerKeyPressed") != 0) { // Not MediaPlayerKeyPressed - return; - } - - gchar * application_name = NULL; - gchar * key = NULL; - - g_variant_get(parameters, "(ss)", &application_name, &key); - - if (g_strcmp0(application_name, koto_rev_dns) != 0) { // Not for Koto - return; - } - - if (g_strcmp0(key, "Play") == 0) { - koto_playback_engine_play(playback_engine); - } else if (g_strcmp0(key, "Pause") == 0) { - koto_playback_engine_pause(playback_engine); - } else if (g_strcmp0(key, "Stop") == 0) { - koto_playback_engine_stop(playback_engine); - } else if (g_strcmp0(key, "Previous") == 0) { - koto_playback_engine_backwards(playback_engine); - } else if (g_strcmp0(key, "Next") == 0) { - koto_playback_engine_forwards(playback_engine); - } else if (g_strcmp0(key, "Repeat") == 0) { - koto_playback_engine_toggle_track_repeat(playback_engine); - } else if (g_strcmp0(key, "Shuffle") == 0) { - koto_playback_engine_toggle_track_shuffle(playback_engine); - } -} - -void handle_window_enter( - GtkEventControllerFocus * controller, - gpointer user_data -) { - (void) controller; - (void) user_data; - grab_media_keys(); // Grab our media keys -} - -void handle_window_leave( - GtkEventControllerFocus * controller, - gpointer user_data -) { - (void) controller; - (void) user_data; - release_media_keys(); // Release our media keys -} - -void release_media_keys() { - if (media_keys_dbus_conn == NULL) { // No connection - return; - } - - GVariant * params = g_variant_new_string(g_strdup(koto_rev_dns)); - - g_dbus_proxy_call( - media_keys_proxy, - "ReleaseMediaPlayerKeys", - params, - G_DBUS_CALL_FLAGS_NONE, - -1, - NULL, - handle_media_keys_async_done, - NULL - ); -} - -void setup_mediakeys_interface() { - media_keys_introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); - g_assert(media_keys_introspection_data != NULL); - - GDBusConnection * bus; - GError * error = NULL; - - bus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, &error); - - if (bus == NULL) { // Failed to get session bus - g_printerr("Failed to get our session bus: %s\n", error->message); - g_error_free(error); - return; - } - - media_keys_proxy = g_dbus_proxy_new_sync( - bus, - G_DBUS_PROXY_FLAGS_NONE, - NULL, - "org.gnome.SettingsDaemon.MediaKeys", - "/org/gnome/SettingsDaemon/MediaKeys", - "org.gnome.SettingsDaemon.MediaKeys", - NULL, - &error - ); - - if (media_keys_proxy == NULL) { - g_printerr("Failed to get a proxy to GNOME Settings Daemon Media-Keys: %s\n", error->message); - g_error_free(error); - return; - } - - g_signal_connect_object( - media_keys_proxy, - "g-signal", - G_CALLBACK(handle_media_keys_signal), - NULL, - 0 - ); - - GtkEventController * focus_controller = gtk_event_controller_focus_new(); // Create a new focus controller - - g_signal_connect(focus_controller, "enter", G_CALLBACK(handle_window_enter), NULL); - g_signal_connect(focus_controller, "leave", G_CALLBACK(handle_window_leave), NULL); - gtk_widget_add_controller(GTK_WIDGET(main_window), focus_controller); -} diff --git a/src/playback/media-keys.h b/src/playback/media-keys.h deleted file mode 100644 index 20eee0c..0000000 --- a/src/playback/media-keys.h +++ /dev/null @@ -1,54 +0,0 @@ -/* media-keys.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include - -G_BEGIN_DECLS - -void grab_media_keys(); - -void handle_media_keys_async_done( - GObject * source_object, - GAsyncResult * res, - gpointer user_data -); - -void handle_media_keys_signal( - GDBusProxy * proxy, - const gchar * sender_name, - const gchar * signal_name, - GVariant * parameters, - gpointer user_data -); - -void handle_window_enter( - GtkEventControllerFocus * controller, - gpointer user_data -); - -void handle_window_leave( - GtkEventControllerFocus * controller, - gpointer user_data -); - -void release_media_keys(); - -void setup_mediakeys_interface(); - -G_END_DECLS diff --git a/src/playback/mimes.c b/src/playback/mimes.c deleted file mode 100644 index 8c3f0ea..0000000 --- a/src/playback/mimes.c +++ /dev/null @@ -1,75 +0,0 @@ -/* mimes.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../koto-utils.h" - -GHashTable * supported_mimes_hash = NULL; -GList * supported_mimes = NULL; - -gboolean koto_playback_engine_gst_caps_iter( - GstCapsFeatures * features, - GstStructure * structure, - gpointer user_data -) { - (void) features; - (void) user_data; - gchar * caps_name = (gchar*) gst_structure_get_name(structure); // Get the name, typically a mimetype - - if (g_str_has_prefix(caps_name, "unknown")) { // Is unknown - return TRUE; - } - - if (g_hash_table_contains(supported_mimes_hash, caps_name)) { // Found in list already - return TRUE; - } - - g_hash_table_add(supported_mimes_hash, g_strdup(caps_name)); - supported_mimes = g_list_prepend(supported_mimes, g_strdup(caps_name)); - supported_mimes = g_list_prepend(supported_mimes, g_strdup(koto_utils_string_replace_all(caps_name, "x-", ""))); - - return TRUE; -} - -void koto_playback_engine_gst_pad_iter( - gpointer list_data, - gpointer user_data -) { - (void) user_data; - GstStaticPadTemplate * templ = list_data; - - if (templ->direction == GST_PAD_SINK) { // Is a sink pad - GstCaps * capabilities = gst_static_pad_template_get_caps(templ); // Get the capabilities - gst_caps_foreach(capabilities, koto_playback_engine_gst_caps_iter, NULL); // Iterate over and add to mimes - gst_caps_unref(capabilities); - } -} - -void koto_playback_engine_get_supported_mimetypes() { - // Credit for code goes to https://github.com/mopidy/mopidy/issues/812#issuecomment-75868363 - // These are GstElementFactory - GList * elements = gst_element_factory_list_get_elements(GST_ELEMENT_FACTORY_TYPE_DEPAYLOADER | GST_ELEMENT_FACTORY_TYPE_DEMUXER | GST_ELEMENT_FACTORY_TYPE_PARSER | GST_ELEMENT_FACTORY_TYPE_DECODER | GST_ELEMENT_FACTORY_TYPE_MEDIA_AUDIO, GST_RANK_NONE); - - GList * ele; - - for (ele = elements; ele != NULL; ele = ele->next) { // For each of the elements - // GList of GstStaticPadTemplate - GList * static_pads = (GList*) gst_element_factory_get_static_pad_templates(ele->data); // Get the pads - g_list_foreach(static_pads, koto_playback_engine_gst_pad_iter, NULL); - } -} diff --git a/src/playback/mimes.h b/src/playback/mimes.h deleted file mode 100644 index f5010f7..0000000 --- a/src/playback/mimes.h +++ /dev/null @@ -1,37 +0,0 @@ -/* mimes.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -G_BEGIN_DECLS - -gboolean koto_bplayback_engine_gst_caps_iter( - GstCapsFeatures * features, - GstStructure * structure, - gpointer user_data -); - -void koto_playback_engine_gst_pad_iter( - gpointer list_data, - gpointer user_data -); - -void koto_playback_engine_get_supported_mimetypes(GList * mimes); - -G_END_DECLS diff --git a/src/playback/mpris.c b/src/playback/mpris.c deleted file mode 100644 index 1d726c1..0000000 --- a/src/playback/mpris.c +++ /dev/null @@ -1,404 +0,0 @@ -/* mpris.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Huge thanks to the GLib folks for providing an example server test - -#include -#include -#include -#include "../db/cartographer.h" -#include "../playlist/current.h" -#include "../playlist/playlist.h" -#include "../koto-paths.h" -#include "../koto-utils.h" -#include "engine.h" -#include "mimes.h" -#include "mpris.h" - -extern gchar * koto_rev_dns; -extern KotoCartographer * koto_maps; -extern KotoCurrentPlaylist * current_playlist; -extern GtkApplication * app; -extern GtkWindow * main_window; -extern KotoPlaybackEngine * playback_engine; -extern GList * supported_mimes; - -GDBusConnection * dbus_conn = NULL; -guint mpris_bus_id = 0; -GDBusNodeInfo * introspection_data = NULL; - -static const gchar introspection_xml[] = - "" - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - " " - ""; - -void handle_method_call( - GDBusConnection * connection, - const gchar * sender, - const gchar * object_path, - const gchar * interface_name, - const gchar * method_name, - GVariant * parameters, - GDBusMethodInvocation * invocation, - gpointer user_data -) { - (void) connection; - (void) sender; - (void) object_path; - (void) parameters; - (void) invocation; - (void) user_data; - - if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2") == 0) { // Root mediaplayer2 interface - if (g_strcmp0(method_name, "Raise") == 0) { // Raise the window - gtk_window_unminimize(main_window); // Ensure we unminimize the window - gtk_window_present(main_window); // Present our main window - return; - } - - if (g_strcmp0(method_name, "Quit") == 0) { // Quit the application - gtk_application_remove_window(app, main_window); // Remove the window, thereby closing the app - return; - } - } else if (g_strcmp0(interface_name, "org.mpris.MediaPlayer2.Player") == 0) { // Root mediaplayer2 interface - if (g_strcmp0(method_name, "Next") == 0) { // Next track - koto_playback_engine_forwards(playback_engine); - return; - } - - if (g_strcmp0(method_name, "Previous") == 0) { // Previous track - koto_playback_engine_backwards(playback_engine); - return; - } - - if (g_strcmp0(method_name, "Pause") == 0) { // Explicit pause - koto_playback_engine_pause(playback_engine); - return; - } - - if (g_strcmp0(method_name, "PlayPause") == 0) { // Explicit playpause - koto_playback_engine_toggle(playback_engine); - return; - } - - if (g_strcmp0(method_name, "Stop") == 0) { // Explicit stop - koto_playback_engine_stop(playback_engine); - return; - } - - if (g_strcmp0(method_name, "Play") == 0) { // Explicit play - koto_playback_engine_play(playback_engine); - return; - } - } -} - -GVariant * handle_get_property( - GDBusConnection * connection, - const gchar * sender, - const gchar * object_path, - const gchar * interface_name, - const gchar * property_name, - GError ** error, - gpointer user_data -) { - (void) connection; - (void) sender; - (void) object_path; - (void) interface_name; - (void) error; - (void) user_data; - GVariant * ret; - - ret = NULL; - - if (g_strcmp0(property_name, "CanQuit") == 0) { // If property is CanQuit - ret = g_variant_new_boolean(TRUE); // Allow quitting. You can escape Hotel California for now. - } - - if (g_strcmp0(property_name, "CanRaise") == 0) { // If property is CanRaise - ret = g_variant_new_boolean(TRUE); // Allow raising. - } - - if (g_strcmp0(property_name, "HasTrackList") == 0) { // If property is HasTrackList - KotoPlaylist * playlist = koto_current_playlist_get_playlist(current_playlist); - if (KOTO_IS_PLAYLIST(playlist)) { - ret = g_variant_new_boolean(koto_playlist_get_length(playlist) > 0); - } else { // Don't have a playlist - ret = g_variant_new_boolean(FALSE); - } - } - - if (g_strcmp0(property_name, "Identity") == 0) { // Want identity - ret = g_variant_new_string("Koto"); // Not Jason Bourne but close enough - } - - if (g_strcmp0(property_name, "DesktopEntry") == 0) { // Desktop Entry - ret = g_variant_new_string(koto_rev_dns); - } - - if (g_strcmp0(property_name, "SupportedUriSchemas") == 0) { // Supported URI Schemas - GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE("as")); // Array of strings - g_variant_builder_add(builder, "s", "file"); - ret = g_variant_new("as", builder); - g_variant_builder_unref(builder); // Unref builder since we no longer need it - } - - if (g_strcmp0(property_name, "SupportedMimeTypes") == 0) { // Supported mimetypes - GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE("as")); // Array of strings - GList * mimes; - mimes = NULL; - - for (mimes = supported_mimes; mimes != NULL; mimes = mimes->next) { // For each mimetype - g_variant_builder_add(builder, "s", mimes->data); // Add the mime as a string - } - - ret = g_variant_new("as", builder); - g_variant_builder_unref(builder); - g_list_free(mimes); - } - - if (g_strcmp0(property_name, "Metadata") == 0) { // Metadata - KotoTrack * current_track = koto_playback_engine_get_current_track(playback_engine); - - if (KOTO_IS_TRACK(current_track)) { // Currently playing a track - ret = koto_track_get_metadata_vardict(current_track); - } else { // No track - GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE_VARDICT); // Create an empty builder - ret = g_variant_builder_end(builder); // return the vardict - } - } - - if ( - (g_strcmp0(property_name, "CanPlay") == 0) || - (g_strcmp0(property_name, "CanPause") == 0) - ) { - KotoTrack * current_track = koto_playback_engine_get_current_track(playback_engine); - ret = g_variant_new_boolean(KOTO_IS_TRACK(current_track)); - } - - if (g_strcmp0(property_name, "CanSeek") == 0) { // Can control position over mpris - ret = g_variant_new_boolean(FALSE); - } - - if (g_strcmp0(property_name, "CanGoNext") == 0) { // Can Go Next - // TODO: Add some actual logic here - ret = g_variant_new_boolean(TRUE); - } - - if (g_strcmp0(property_name, "CanGoPrevious") == 0) { // Can Go Previous - // TODO: Add some actual logic here - ret = g_variant_new_boolean(TRUE); - } - - if (g_strcmp0(property_name, "CanControl") == 0) { // Can Control - ret = g_variant_new_boolean(TRUE); - } - - if (g_strcmp0(property_name, "PlaybackStatus") == 0) { // Get our playback status - GstState current_state = koto_playback_engine_get_state(playback_engine); // Get the current state - - if (current_state == GST_STATE_PLAYING) { - ret = g_variant_new_string("Playing"); - } else if (current_state == GST_STATE_PAUSED) { - ret = g_variant_new_string("Paused"); - } else { - ret = g_variant_new_string("Stopped"); - } - } - - return ret; -} - -gboolean handle_set_property( - GDBusConnection * connection, - const gchar * sender, - const gchar * object_path, - const gchar * interface_name, - const gchar * property_name, - GVariant * value, - GError ** error, - gpointer user_data -) { - (void) connection; - (void) sender; - (void) interface_name; - (void) object_path; - (void) error; - (void) user_data; - - if (g_strcmp0(property_name, "LoopStatus") == 0) { // Changing LoopStatus - koto_playback_engine_set_track_repeat(playback_engine, g_variant_get_boolean(value)); // Set the loop status state - return TRUE; - } - - if (g_strcmp0(property_name, "Shuffle") == 0) { // Changing Shuffle - koto_playback_engine_set_track_shuffle(playback_engine, g_variant_get_boolean(value)); // Set the shuffle state - return TRUE; - } - - return FALSE; -} - -void koto_update_mpris_playback_state(GstState state) { - GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); - - if (state == GST_STATE_PLAYING) { - g_variant_builder_add(builder, "{sv}", "PlaybackStatus", g_variant_new_string("Playing")); - } else if (state == GST_STATE_PAUSED) { - g_variant_builder_add(builder, "{sv}", "PlaybackStatus", g_variant_new_string("Paused")); - } else { - g_variant_builder_add(builder, "{sv}", "PlaybackStatus", g_variant_new_string("Stopped")); - } - - g_dbus_connection_emit_signal( - dbus_conn, - NULL, - "/org/mpris/MediaPlayer2", - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", builder, NULL), - NULL - ); -} - -void koto_update_mpris_info_for_track(KotoTrack * track) { - if (!KOTO_IS_TRACK(track)) { - return; - } - - GVariant * metadata = koto_track_get_metadata_vardict(track); // Get the GVariantBuilder variable dict for the metadata - - koto_update_mpris_info_for_track_with_metadata(track, metadata); -} - -void koto_update_mpris_info_for_track_with_metadata( - KotoTrack * track, - GVariant * metadata -) { - if (!KOTO_IS_TRACK(track)) { - return; - } - - GVariantBuilder * builder = g_variant_builder_new(G_VARIANT_TYPE_ARRAY); - - g_variant_builder_add(builder, "{sv}", "Metadata", metadata); - - g_dbus_connection_emit_signal( - dbus_conn, - NULL, - "/org/mpris/MediaPlayer2", - "org.freedesktop.DBus.Properties", - "PropertiesChanged", - g_variant_new("(sa{sv}as)", "org.mpris.MediaPlayer2.Player", builder, NULL), - NULL - ); -} - -static const GDBusInterfaceVTable main_mpris_interface_vtable = { - handle_method_call, - handle_get_property, - handle_set_property, - { 0 } -}; - -void on_main_mpris_bus_acquired( - GDBusConnection * connection, - const gchar * name, - gpointer user_data -) { - (void) name; - (void) user_data; - dbus_conn = connection; - g_dbus_connection_register_object( - dbus_conn, - "/org/mpris/MediaPlayer2", - introspection_data->interfaces[0], - &main_mpris_interface_vtable, - NULL, - NULL, - NULL - ); - - g_dbus_connection_register_object( - dbus_conn, - "/org/mpris/MediaPlayer2", - introspection_data->interfaces[1], - &main_mpris_interface_vtable, - NULL, - NULL, - NULL - ); -} - -void setup_mpris_interfaces() { - introspection_data = g_dbus_node_info_new_for_xml(introspection_xml, NULL); - g_assert(introspection_data != NULL); - - mpris_bus_id = g_bus_own_name( - G_BUS_TYPE_SESSION, - "org.mpris.MediaPlayer2.koto", - G_BUS_NAME_OWNER_FLAGS_NONE, - on_main_mpris_bus_acquired, - NULL, - NULL, - NULL, - NULL - ); - - g_assert(mpris_bus_id > 0); -} diff --git a/src/playback/mpris.h b/src/playback/mpris.h deleted file mode 100644 index 6662a7b..0000000 --- a/src/playback/mpris.h +++ /dev/null @@ -1,71 +0,0 @@ -/* mpris.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include "../indexer/structs.h" - -void koto_update_mpris_playback_state(GstState state); - -void koto_update_mpris_info_for_track(KotoTrack * track); - -void koto_update_mpris_info_for_track_with_metadata( - KotoTrack * track, - GVariant * metadata -); - -void handle_method_call( - GDBusConnection * connection, - const gchar * sender, - const gchar * object_path, - const gchar * interface_name, - const gchar * method_name, - GVariant * parameters, - GDBusMethodInvocation * invocation, - gpointer user_data -); - -GVariant * handle_get_property( - GDBusConnection * connection, - const gchar * sender, - const gchar * object_path, - const gchar * interface_name, - const gchar * property_name, - GError ** error, - gpointer user_data -); - -gboolean handle_set_property( - GDBusConnection * connection, - const gchar * sender, - const gchar * object_path, - const gchar * interface_name, - const gchar * property_name, - GVariant * value, - GError ** error, - gpointer user_data -); - -void on_main_mpris_bus_acquired( - GDBusConnection * connection, - const gchar * name, - gpointer user_data -); - -void setup_mpris_interfaces(); diff --git a/src/playlist/add-remove-track-popover.c b/src/playlist/add-remove-track-popover.c deleted file mode 100644 index 01c08a6..0000000 --- a/src/playlist/add-remove-track-popover.c +++ /dev/null @@ -1,275 +0,0 @@ -/* add-remove-track-popover.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include "../db/cartographer.h" -#include "../playback/engine.h" -#include "../playlist/playlist.h" -#include "add-remove-track-popover.h" - -extern KotoCartographer * koto_maps; - -struct _KotoAddRemoveTrackPopover { - GtkPopover parent_instance; - GtkWidget * list_box; - GHashTable * checkbox_to_playlist_uuid; - GHashTable * playlist_uuid_to_checkbox; - GList * tracks; - - GHashTable * checkbox_to_signal_ids; -}; - -G_DEFINE_TYPE(KotoAddRemoveTrackPopover, koto_add_remove_track_popover, GTK_TYPE_POPOVER); - -KotoAddRemoveTrackPopover * koto_add_remove_track_popup = NULL; - -static void koto_add_remove_track_popover_class_init(KotoAddRemoveTrackPopoverClass * c) { - (void) c; -} - -static void koto_add_remove_track_popover_init(KotoAddRemoveTrackPopover * self) { - self->list_box = gtk_list_box_new(); // Create our new GtkListBox - gtk_list_box_set_selection_mode(GTK_LIST_BOX(self->list_box), GTK_SELECTION_NONE); - - self->checkbox_to_playlist_uuid = g_hash_table_new(g_str_hash, g_str_equal); - self->playlist_uuid_to_checkbox = g_hash_table_new(g_str_hash, g_str_equal); - self->checkbox_to_signal_ids = g_hash_table_new(g_str_hash, g_str_equal), - self->tracks = NULL; // Initialize our tracks - - gtk_popover_set_autohide(GTK_POPOVER(self), TRUE); // Ensure autohide is enabled - gtk_popover_set_child(GTK_POPOVER(self), self->list_box); - - g_signal_connect(koto_maps, "playlist-added", G_CALLBACK(koto_add_remove_track_popover_handle_playlist_added), self); - g_signal_connect(koto_maps, "playlist-removed", G_CALLBACK(koto_add_remove_track_popover_handle_playlist_removed), self); -} - -void koto_add_remove_track_popover_add_playlist( - KotoAddRemoveTrackPopover * self, - KotoPlaylist * playlist -) { - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - return; - } - - gchar * playlist_uuid = koto_playlist_get_uuid(playlist); // Get the UUID of the playlist - - if (GTK_IS_CHECK_BUTTON(g_hash_table_lookup(self->playlist_uuid_to_checkbox, playlist_uuid))) { // Already have a check button for this - g_free(playlist_uuid); - return; - } - - GtkWidget * playlist_button = gtk_check_button_new_with_label(koto_playlist_get_name(playlist)); // Create our GtkCheckButton - - g_hash_table_insert(self->checkbox_to_playlist_uuid, playlist_button, playlist_uuid); - g_hash_table_insert(self->playlist_uuid_to_checkbox, playlist_uuid, playlist_button); - - gulong playlist_sig_id = g_signal_connect(playlist_button, "toggled", G_CALLBACK(koto_add_remove_track_popover_handle_checkbutton_toggle), self); - - g_hash_table_insert(self->checkbox_to_signal_ids, playlist_button, GUINT_TO_POINTER(playlist_sig_id)); // Add our GSignal handler ID - - gtk_list_box_append(GTK_LIST_BOX(self->list_box), playlist_button); // Add the playlist to the list box -} - -void koto_add_remove_track_popover_clear_tracks(KotoAddRemoveTrackPopover * self) { - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - if (self->tracks != NULL) { // Is a list - g_list_free(self->tracks); - self->tracks = NULL; - } -} - -void koto_add_remove_track_popover_remove_playlist( - KotoAddRemoveTrackPopover * self, - gchar * playlist_uuid -) { - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - if (!g_hash_table_contains(self->playlist_uuid_to_checkbox, playlist_uuid)) { // Playlist not added - return; - } - - GtkCheckButton * btn = GTK_CHECK_BUTTON(g_hash_table_lookup(self->playlist_uuid_to_checkbox, playlist_uuid)); // Get the check button - - if (GTK_IS_CHECK_BUTTON(btn)) { // Is a check button - g_hash_table_remove(self->checkbox_to_playlist_uuid, btn); // Remove uuid based on btn - gtk_list_box_remove(GTK_LIST_BOX(self->list_box), GTK_WIDGET(btn)); // Remove the button from the list box - } - - g_hash_table_remove(self->playlist_uuid_to_checkbox, playlist_uuid); -} - -void koto_add_remove_track_popover_handle_checkbutton_toggle( - GtkCheckButton * btn, - gpointer user_data -) { - KotoAddRemoveTrackPopover * self = user_data; - - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - gboolean should_add = gtk_check_button_get_active(btn); // Get the now active state - gchar * playlist_uuid = g_hash_table_lookup(self->checkbox_to_playlist_uuid, btn); // Get the playlist UUID for this button - - KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); // Get the playlist - - if (!KOTO_IS_PLAYLIST(playlist)) { // Failed to get the playlist - return; - } - - GList * pos; - - for (pos = self->tracks; pos != NULL; pos = pos->next) { // Iterate over our KotoTracks - KotoTrack * track = pos->data; - - if (!KOTO_TRACK(track)) { // Not a track - continue; // Skip this - } - - if (should_add) { // Should be adding - koto_playlist_add_track(playlist, track, FALSE, TRUE); // Add the track to the playlist - } else { // Should be removing - gchar * track_uuid = koto_track_get_uuid(track); // Get the track - koto_playlist_remove_track_by_uuid(playlist, track_uuid); // Remove the track from the playlist - } - } - - gtk_popover_popdown(GTK_POPOVER(self)); // Temporary to hopefully prevent a bork -} - -void koto_add_remove_track_popover_handle_playlist_added( - KotoCartographer * carto, - KotoPlaylist * playlist, - gpointer user_data -) { - (void) carto; - KotoAddRemoveTrackPopover * self = user_data; - - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - koto_add_remove_track_popover_add_playlist(self, playlist); -} - -void koto_add_remove_track_popover_handle_playlist_removed( - KotoCartographer * carto, - gchar * playlist_uuid, - gpointer user_data -) { - (void) carto; - KotoAddRemoveTrackPopover * self = user_data; - - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - koto_add_remove_track_popover_remove_playlist(self, playlist_uuid); -} - -void koto_add_remove_track_popover_set_pointing_to_widget( - KotoAddRemoveTrackPopover * self, - GtkWidget * widget, - GtkPositionType pos -) { - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - if (!GTK_IS_WIDGET(widget)) { // Not a widget - return; - } - - GtkWidget * existing_parent = gtk_widget_get_parent(GTK_WIDGET(self)); - - if (existing_parent != NULL) { - g_object_ref(GTK_WIDGET(self)); // Increment widget ref since unparent will do an unref - gtk_widget_unparent(GTK_WIDGET(self)); // Unparent the popup - } - - gtk_widget_insert_after(GTK_WIDGET(self), widget, gtk_widget_get_last_child(widget)); - gtk_popover_set_position(GTK_POPOVER(self), pos); -} - -void koto_add_remove_track_popover_set_tracks( - KotoAddRemoveTrackPopover * self, - GList * tracks -) { - if (!KOTO_JS_ADD_REMOVE_TRACK_POPOVER(self)) { - return; - } - - gint tracks_len = g_list_length(tracks); - - if (tracks_len == 0) { // No tracks - return; - } - - self->tracks = g_list_copy(tracks); - GHashTable * playlists = koto_cartographer_get_playlists(koto_maps); // Get our playlists - GHashTableIter playlists_iter; - gpointer uuid, playlist_ptr; - - g_hash_table_iter_init(&playlists_iter, playlists); // Init our HashTable iterator - - while (g_hash_table_iter_next(&playlists_iter, &uuid, &playlist_ptr)) { // While we are iterating through our playlists - KotoPlaylist * playlist = playlist_ptr; - gboolean should_be_checked = FALSE; - - if (tracks_len > 1) { // More than one track - GList * pos; - for (pos = self->tracks; pos != NULL; pos = pos->next) { // Iterate over our tracks - should_be_checked = (koto_playlist_get_position_of_track(playlist, pos->data) != -1); - - if (!should_be_checked) { // Should not be checked - break; - } - } - } else { - KotoTrack * track = g_list_nth_data(self->tracks, 0); // Get the first track - - if (KOTO_IS_TRACK(track)) { - gint pos = koto_playlist_get_position_of_track(playlist, track); - should_be_checked = (pos != -1); - } - } - - GtkCheckButton * playlist_check = g_hash_table_lookup(self->playlist_uuid_to_checkbox, uuid); // Get the GtkCheckButton for this playlist - - if (GTK_IS_CHECK_BUTTON(playlist_check)) { // Is a checkbox - gpointer sig_id_ptr = g_hash_table_lookup(self->checkbox_to_signal_ids, playlist_check); - gulong check_button_sig_id = GPOINTER_TO_UINT(sig_id_ptr); - g_signal_handler_block(playlist_check, check_button_sig_id); // Temporary ignore toggled signal, since set_active calls toggled - gtk_check_button_set_active(playlist_check, should_be_checked); // Set active to our should_be_checked bool - g_signal_handler_unblock(playlist_check, check_button_sig_id); // Unblock the signal - } - } -} - -KotoAddRemoveTrackPopover * koto_add_remove_track_popover_new() { - return g_object_new(KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER, NULL); -} \ No newline at end of file diff --git a/src/playlist/add-remove-track-popover.h b/src/playlist/add-remove-track-popover.h deleted file mode 100644 index 481bcd5..0000000 --- a/src/playlist/add-remove-track-popover.h +++ /dev/null @@ -1,80 +0,0 @@ -/* add-remove-track-popover.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include "../db/cartographer.h" -#include "playlist.h" - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER koto_add_remove_track_popover_get_type() -G_DECLARE_FINAL_TYPE(KotoAddRemoveTrackPopover, koto_add_remove_track_popover, KOTO, ADD_REMOVE_TRACK_POPOVER, GtkPopover); -#define KOTO_JS_ADD_REMOVE_TRACK_POPOVER(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_ADD_REMOVE_TRACK_POPOVER)) - -/** - * Functions - **/ - -KotoAddRemoveTrackPopover * koto_add_remove_track_popover_new(); - -void koto_add_remove_track_popover_add_playlist( - KotoAddRemoveTrackPopover * self, - KotoPlaylist * playlist -); - -void koto_add_remove_track_popover_clear_tracks(KotoAddRemoveTrackPopover * self); - -void koto_add_remove_track_popover_remove_playlist( - KotoAddRemoveTrackPopover * self, - gchar * playlist_uuid -); - -void koto_add_remove_track_popover_handle_checkbutton_toggle( - GtkCheckButton * btn, - gpointer user_data -); - -void koto_add_remove_track_popover_handle_playlist_added( - KotoCartographer * carto, - KotoPlaylist * playlist, - gpointer user_data -); - -void koto_add_remove_track_popover_handle_playlist_removed( - KotoCartographer * carto, - gchar * playlist_uuid, - gpointer user_data -); - -void koto_add_remove_track_popover_set_pointing_to_widget( - KotoAddRemoveTrackPopover * self, - GtkWidget * widget, - GtkPositionType pos -); - -void koto_add_remove_track_popover_set_tracks( - KotoAddRemoveTrackPopover * self, - GList * tracks -); - -G_END_DECLS \ No newline at end of file diff --git a/src/playlist/create-modify-dialog.c b/src/playlist/create-modify-dialog.c deleted file mode 100644 index 32c2ee9..0000000 --- a/src/playlist/create-modify-dialog.c +++ /dev/null @@ -1,353 +0,0 @@ -/* create-modify-dialog.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include "../db/cartographer.h" -#include "../playlist/playlist.h" -#include "../koto-utils.h" -#include "../koto-window.h" -#include "create-modify-dialog.h" - -extern KotoCartographer * koto_maps; -extern KotoWindow * main_window; - -enum { - PROP_DIALOG_0, - PROP_PLAYLIST_UUID, - N_PROPS -}; - -static GParamSpec * dialog_props[N_PROPS] = { - NULL, -}; - -struct _KotoCreateModifyPlaylistDialog { - GtkBox parent_instance; - GtkWidget * playlist_image; - GtkWidget * name_entry; - - GtkWidget * create_button; - - gchar * playlist_image_path; - gchar * playlist_uuid; -}; - -G_DEFINE_TYPE(KotoCreateModifyPlaylistDialog, koto_create_modify_playlist_dialog, GTK_TYPE_BOX); - -KotoCreateModifyPlaylistDialog * playlist_create_modify_dialog; - -static void koto_create_modify_playlist_dialog_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -static void koto_create_modify_playlist_dialog_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_create_modify_playlist_dialog_class_init(KotoCreateModifyPlaylistDialogClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_create_modify_playlist_dialog_set_property; - gobject_class->get_property = koto_create_modify_playlist_dialog_get_property; - - dialog_props[PROP_PLAYLIST_UUID] = g_param_spec_string( - "playlist-uuid", - "Playlist UUID", - "Playlist UUID", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPS, dialog_props); -} - -static void koto_create_modify_playlist_dialog_init(KotoCreateModifyPlaylistDialog * self) { - self->playlist_image_path = NULL; - - gtk_widget_set_halign(GTK_WIDGET(self), GTK_ALIGN_CENTER); - gtk_widget_set_valign(GTK_WIDGET(self), GTK_ALIGN_CENTER); - - self->playlist_image = gtk_image_new_from_icon_name("insert-image-symbolic"); - gtk_image_set_pixel_size(GTK_IMAGE(self->playlist_image), 220); - gtk_widget_set_size_request(self->playlist_image, 220, 220); - gtk_box_append(GTK_BOX(self), self->playlist_image); // Add our image - - GtkDropTarget * target = gtk_drop_target_new(G_TYPE_FILE, GDK_ACTION_COPY); - - g_signal_connect(GTK_EVENT_CONTROLLER(target), "drop", G_CALLBACK(koto_create_modify_playlist_dialog_handle_drop), self); - gtk_widget_add_controller(self->playlist_image, GTK_EVENT_CONTROLLER(target)); - - GtkGesture * image_click_controller = gtk_gesture_click_new(); // Create a click gesture for the image clicking - - gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(image_click_controller), 1); // Only allow left click - g_signal_connect(GTK_EVENT_CONTROLLER(image_click_controller), "pressed", G_CALLBACK(koto_create_modify_playlist_dialog_handle_image_click), self); - - gtk_widget_add_controller(self->playlist_image, GTK_EVENT_CONTROLLER(image_click_controller)); - - self->name_entry = gtk_entry_new(); // Create our text entry for the name of the playlist - gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist"); - gtk_entry_set_input_purpose(GTK_ENTRY(self->name_entry), GTK_INPUT_PURPOSE_NAME); - gtk_entry_set_input_hints(GTK_ENTRY(self->name_entry), GTK_INPUT_HINT_SPELLCHECK & GTK_INPUT_HINT_NO_EMOJI & GTK_INPUT_HINT_PRIVATE); - - gtk_box_append(GTK_BOX(self), self->name_entry); - - self->create_button = gtk_button_new_with_label("Create"); - gtk_widget_add_css_class(self->create_button, "suggested-action"); - g_signal_connect(self->create_button, "clicked", G_CALLBACK(koto_create_modify_playlist_dialog_handle_create_click), self); - gtk_box_append(GTK_BOX(self), self->create_button); // Add the create button -} - -static void koto_create_modify_playlist_dialog_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoCreateModifyPlaylistDialog * self = KOTO_CREATE_MODIFY_PLAYLIST_DIALOG(obj); - - switch (prop_id) { - case PROP_PLAYLIST_UUID: - g_value_set_string(val, (self->playlist_uuid != NULL) ? g_strdup(self->playlist_uuid) : NULL); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_create_modify_playlist_dialog_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoCreateModifyPlaylistDialog * self = KOTO_CREATE_MODIFY_PLAYLIST_DIALOG(obj); - - (void) self; - - switch (prop_id) { - case PROP_PLAYLIST_UUID: - koto_create_modify_playlist_dialog_set_playlist_uuid(self, g_strdup(g_value_get_string(val))); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -void koto_create_modify_playlist_dialog_handle_chooser_response( - GtkNativeDialog * native, - int response, - gpointer user_data -) { - if (response != GTK_RESPONSE_ACCEPT) { // Not accept - g_object_unref(native); - return; - } - - KotoCreateModifyPlaylistDialog * self = user_data; - - if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) { - return; - } - - GFile * file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(native)); - gchar * file_path = g_file_get_path(file); // Get the absolute path - - if (file_path != NULL) { - self->playlist_image_path = g_strdup(file_path); - gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), self->playlist_image_path); // Set the file path - g_free(file_path); - } - - g_object_unref(file); - g_object_unref(native); -} - -void koto_create_modify_playlist_dialog_handle_create_click( - GtkButton * button, - gpointer user_data -) { - (void) button; - - KotoCreateModifyPlaylistDialog * self = user_data; - - if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) { - return; - } - - if (gtk_entry_get_text_length(GTK_ENTRY(self->name_entry)) == 0) { // No text - gtk_widget_grab_focus(GTK_WIDGET(self->name_entry)); // Select the name entry - return; - } - - KotoPlaylist * playlist = NULL; - gboolean modify_existing_playlist = koto_utils_string_is_valid(self->playlist_uuid); - - if (modify_existing_playlist) { // Modifying an existing playlist - playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, self->playlist_uuid); - } else { // Creating a new playlist - playlist = koto_playlist_new(); // Create a new playlist with a new UUID - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // If this isn't a playlist - return; - } - - koto_playlist_set_name(playlist, gtk_entry_buffer_get_text(gtk_entry_get_buffer(GTK_ENTRY(self->name_entry)))); // Set the name to the text we get from the entry buffer - koto_playlist_set_artwork(playlist, self->playlist_image_path); // Add the playlist path if any - koto_playlist_commit(playlist); // Save the playlist to the database - - if (!modify_existing_playlist) { // Not a new playlist - koto_cartographer_add_playlist(koto_maps, playlist); // Add to cartographer - koto_playlist_mark_as_finalized(playlist); // Ensure our tracks loaded finalized signal is emitted for the new playlist - } - - koto_create_modify_playlist_dialog_reset(self); - koto_window_hide_dialogs(main_window); // Hide the dialogs -} - -gboolean koto_create_modify_playlist_dialog_handle_drop( - GtkDropTarget * target, - const GValue * val, - double x, - double y, - gpointer user_data -) { - (void) target; - (void) x; - (void) y; - - if (!G_VALUE_HOLDS(val, G_TYPE_FILE)) { // Not a file - return FALSE; - } - - KotoCreateModifyPlaylistDialog * self = user_data; - - if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) { // No dialog - return FALSE; - } - - GFile * dropped_file = g_value_get_object(val); // Get the GValue - gchar * file_path = g_file_get_path(dropped_file); // Get the absolute path - - g_object_unref(dropped_file); // Unref the file - - if (file_path == NULL) { - return FALSE; // Failed to get the path so immediately return false - } - - magic_t magic_cookie = magic_open(MAGIC_MIME); - - if (magic_cookie == NULL) { - return FALSE; - } - - if (magic_load(magic_cookie, NULL) != 0) { - goto cookie_closure; - } - - const char * mime_type = magic_file(magic_cookie, file_path); - - if ((mime_type != NULL) && g_str_has_prefix(mime_type, "image/")) { // Is an image - self->playlist_image_path = g_strdup(file_path); - gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), self->playlist_image_path); // Set the file path - g_free(file_path); - return TRUE; - } - -cookie_closure: - magic_close(magic_cookie); - return FALSE; -} - -void koto_create_modify_playlist_dialog_handle_image_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -) { - (void) gesture; - (void) n_press; - (void) x; - (void) y; - - KotoCreateModifyPlaylistDialog * self = user_data; - - GtkFileChooserNative * chooser = koto_utils_create_image_file_chooser("Choose playlist image"); - - g_signal_connect(chooser, "response", G_CALLBACK(koto_create_modify_playlist_dialog_handle_chooser_response), self); - gtk_native_dialog_show(GTK_NATIVE_DIALOG(chooser)); // Show our file chooser -} - -void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog * self) { - if (!KOTO_IS_CURRENT_MODIFY_PLAYLIST(self)) { - return; - } - - gtk_entry_buffer_set_text(gtk_entry_get_buffer(GTK_ENTRY(self->name_entry)), "", -1); // Reset existing buffer to empty string - gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), "Name of playlist"); // Reset placeholder - gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image - gtk_button_set_label(GTK_BUTTON(self->create_button), "Create"); - self->playlist_uuid = NULL; // Reset playlist UUID -} - -void koto_create_modify_playlist_dialog_set_playlist_uuid( - KotoCreateModifyPlaylistDialog * self, - gchar * playlist_uuid -) { - if (!koto_utils_string_is_valid(playlist_uuid)) { // Not a valid playlist UUID string - return; - } - - KotoPlaylist * playlist = koto_cartographer_get_playlist_by_uuid(koto_maps, playlist_uuid); - - if (!KOTO_IS_PLAYLIST(playlist)) { - return; - } - - self->playlist_uuid = g_strdup(playlist_uuid); - gtk_entry_buffer_set_text(gtk_entry_get_buffer(GTK_ENTRY(self->name_entry)), koto_playlist_get_name(playlist), -1); // Update the input buffer - gtk_entry_set_placeholder_text(GTK_ENTRY(self->name_entry), ""); // Clear placeholder - - gchar * art = koto_playlist_get_artwork(playlist); - - if (!koto_utils_string_is_valid(art)) { // If art is not defined - gtk_image_set_from_icon_name(GTK_IMAGE(self->playlist_image), "insert-image-symbolic"); // Reset the image - } else { - gtk_image_set_from_file(GTK_IMAGE(self->playlist_image), art); - g_free(art); - } - - gtk_button_set_label(GTK_BUTTON(self->create_button), "Save"); -} - -KotoCreateModifyPlaylistDialog * koto_create_modify_playlist_dialog_new(char * playlist_uuid) { - (void) playlist_uuid; - - return g_object_new(KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG, "orientation", GTK_ORIENTATION_VERTICAL, "spacing", 40, NULL); -} \ No newline at end of file diff --git a/src/playlist/create-modify-dialog.h b/src/playlist/create-modify-dialog.h deleted file mode 100644 index 0d7d26b..0000000 --- a/src/playlist/create-modify-dialog.h +++ /dev/null @@ -1,72 +0,0 @@ -/* create-modify-dialog.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG koto_create_modify_playlist_dialog_get_type() -G_DECLARE_FINAL_TYPE(KotoCreateModifyPlaylistDialog, koto_create_modify_playlist_dialog, KOTO, CREATE_MODIFY_PLAYLIST_DIALOG, GtkBox); -#define KOTO_IS_CURRENT_MODIFY_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CREATE_MODIFY_PLAYLIST_DIALOG)) - -/** - * Functions - **/ - -KotoCreateModifyPlaylistDialog * koto_create_modify_playlist_dialog_new(); - -void koto_create_modify_playlist_dialog_handle_chooser_response( - GtkNativeDialog * native, - int response, - gpointer user_data -); - -void koto_create_modify_playlist_dialog_handle_create_click( - GtkButton * button, - gpointer user_data -); - -gboolean koto_create_modify_playlist_dialog_handle_drop( - GtkDropTarget * target, - const GValue * val, - double x, - double y, - gpointer user_data -); - -void koto_create_modify_playlist_dialog_handle_image_click( - GtkGestureClick * gesture, - int n_press, - double x, - double y, - gpointer user_data -); - -void koto_create_modify_playlist_dialog_reset(KotoCreateModifyPlaylistDialog * self); - -void koto_create_modify_playlist_dialog_set_playlist_uuid( - KotoCreateModifyPlaylistDialog * self, - gchar * playlist_uuid -); - -G_END_DECLS diff --git a/src/playlist/current.c b/src/playlist/current.c deleted file mode 100644 index ba27fa5..0000000 --- a/src/playlist/current.c +++ /dev/null @@ -1,133 +0,0 @@ -/* current.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "current.h" - -enum { - SIGNAL_PLAYLIST_CHANGED, - N_SIGNALS -}; - -static guint signals[N_SIGNALS] = { - 0 -}; - -KotoCurrentPlaylist * current_playlist = NULL; - -struct _KotoCurrentPlaylist { - GObject parent_class; - KotoPlaylist * current_playlist; -}; - -struct _KotoCurrentPlaylistClass { - GObjectClass parent_class; - - void (* playlist_changed) ( - KotoCurrentPlaylist * self, - KotoPlaylist * playlist, - gboolean play_immediately - ); -}; - -static void koto_current_playlist_class_init(KotoCurrentPlaylistClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - - signals[SIGNAL_PLAYLIST_CHANGED] = g_signal_new( - "playlist-changed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoCurrentPlaylistClass, playlist_changed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 3, - KOTO_TYPE_PLAYLIST, - G_TYPE_BOOLEAN, - G_TYPE_BOOLEAN - ); -} - -G_DEFINE_TYPE(KotoCurrentPlaylist, koto_current_playlist, G_TYPE_OBJECT); - -static void koto_current_playlist_init(KotoCurrentPlaylist * self) { - self->current_playlist = NULL; -} - -KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self) { - return KOTO_IS_CURRENT_PLAYLIST(self) ? self->current_playlist : NULL; -} - -void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self) { - if (!KOTO_IS_CURRENT_PLAYLIST(self)) { // Not a CurrentPlaylist - return; - } - - if (!KOTO_IS_PLAYLIST(self->current_playlist)) { // No playlist - return; - } - - koto_playlist_save_current_playback_state(self->current_playlist); // Save the current playlist state -} - -void koto_current_playlist_set_playlist( - KotoCurrentPlaylist * self, - KotoPlaylist * playlist, - gboolean play_immediately, - gboolean play_current -) { - if (!KOTO_IS_CURRENT_PLAYLIST(self)) { - return; - } - - if (!KOTO_IS_PLAYLIST(playlist)) { // Not a playlist - return; - } - - if (KOTO_IS_PLAYLIST(self->current_playlist)) { - koto_current_playlist_save_playlist_state(self); // Save the current playlist state if needed - - gboolean * is_temp = FALSE; - g_object_get(self->current_playlist, "ephemeral", &is_temp, NULL); // Get the current ephemeral value - - if (is_temp) { // Is a temporary playlist - koto_playlist_unmap(self->current_playlist); // Unmap the playlist if needed - } else { // Not a temporary playlist - koto_playlist_commit(self->current_playlist); // Save the current playlist - } - - g_object_unref(self->current_playlist); // Unreference - } - - self->current_playlist = playlist; - g_object_ref(playlist); // Increment the reference - g_signal_emit( - self, - signals[SIGNAL_PLAYLIST_CHANGED], - 0, - playlist, - play_immediately, - play_current - ); -} - -KotoCurrentPlaylist * koto_current_playlist_new() { - return g_object_new(KOTO_TYPE_CURRENT_PLAYLIST, NULL); -} diff --git a/src/playlist/current.h b/src/playlist/current.h deleted file mode 100644 index 57b8cd2..0000000 --- a/src/playlist/current.h +++ /dev/null @@ -1,55 +0,0 @@ -/* current.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include "playlist.h" - -G_BEGIN_DECLS - -/** - * Type Definition - **/ - -#define KOTO_TYPE_CURRENT_PLAYLIST koto_current_playlist_get_type() -#define KOTO_CURRENT_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_CURRENT_PLAYLIST, KotoCurrentPlaylist)) -#define KOTO_IS_CURRENT_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_CURRENT_PLAYLIST)) - -typedef struct _KotoCurrentPlaylist KotoCurrentPlaylist; -typedef struct _KotoCurrentPlaylistClass KotoCurrentPlaylistClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_current_playlist_get_type(void) G_GNUC_CONST; - -/** - * Current Playlist Functions - **/ - -KotoCurrentPlaylist * koto_current_playlist_new(); - -KotoPlaylist * koto_current_playlist_get_playlist(KotoCurrentPlaylist * self); - -void koto_current_playlist_save_playlist_state(KotoCurrentPlaylist * self); - -void koto_current_playlist_set_playlist( - KotoCurrentPlaylist * self, - KotoPlaylist * playlist, - gboolean play_immediately, - gboolean play_current -); - -G_END_DECLS diff --git a/src/playlist/playlist.c b/src/playlist/playlist.c deleted file mode 100644 index 7e3bb39..0000000 --- a/src/playlist/playlist.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* playlist.c - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include "../db/cartographer.h" -#include "../db/db.h" -#include "../koto-utils.h" -#include "playlist.h" - -extern KotoCartographer * koto_maps; -extern sqlite3 * koto_db; - -enum { - PROP_0, - PROP_UUID, - PROP_NAME, - PROP_ALBUM_UUID, - PROP_ART_PATH, - PROP_EPHEMERAL, - PROP_IS_SHUFFLE_ENABLED, - N_PROPERTIES, -}; - -enum { - SIGNAL_MODIFIED, - SIGNAL_TRACK_ADDED, - SIGNAL_TRACK_LOAD_FINALIZED, - SIGNAL_TRACK_REMOVED, - N_SIGNALS -}; - -struct _KotoPlaylist { - GObject parent_instance; - gchar * uuid; - gchar * name; - gchar * art_path; - gchar * album_uuid; - gint current_position; - gchar * current_uuid; - - KotoPreferredPlaylistSortType model; - - gboolean ephemeral; - gboolean is_shuffle_enabled; - gboolean finalized; - - GListStore * store; - GQueue * sorted_tracks; - - GQueue * tracks; // This is effectively our vanilla value that should never change - GQueue * played_tracks; -}; - -struct _KotoPlaylistClass { - GObjectClass parent_class; - - void (* modified) (KotoPlaylist * playlist); - void (* track_added) ( - KotoPlaylist * playlist, - gchar * track_uuid - ); - void (* track_load_finalized) (KotoPlaylist * playlist); - void (* track_removed) ( - KotoPlaylist * playlist, - gchar * track_uuid - ); -}; - -G_DEFINE_TYPE(KotoPlaylist, koto_playlist, G_TYPE_OBJECT); - -static GParamSpec * props[N_PROPERTIES] = { - NULL -}; -static guint playlist_signals[N_SIGNALS] = { - 0 -}; - -static void koto_playlist_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -); - -; -static void koto_playlist_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -); - -static void koto_playlist_class_init(KotoPlaylistClass * c) { - GObjectClass * gobject_class; - - gobject_class = G_OBJECT_CLASS(c); - gobject_class->set_property = koto_playlist_set_property; - gobject_class->get_property = koto_playlist_get_property; - - props[PROP_UUID] = g_param_spec_string( - "uuid", - "UUID of the Playlist", - "UUID of the Playlist", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_NAME] = g_param_spec_string( - "name", - "Name of the Playlist", - "Name of the Playlist", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ALBUM_UUID] = g_param_spec_string( - "album-uuid", - "UUID of an Album associated with this Playlist", - "UUID of an Album associated with this Playlist. Useful for Audiobooks.", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_ART_PATH] = g_param_spec_string( - "art-path", - "Path to any associated artwork of the Playlist", - "Path to any associated artwork of the Playlist", - NULL, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_EPHEMERAL] = g_param_spec_boolean( - "ephemeral", - "Is the playlist ephemeral (temporary)", - "Is the playlist ephemeral (temporary)", - FALSE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - props[PROP_IS_SHUFFLE_ENABLED] = g_param_spec_boolean( - "is-shuffle-enabled", - "Is shuffling enabled", - "Is shuffling enabled", - FALSE, - G_PARAM_CONSTRUCT | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_READWRITE - ); - - g_object_class_install_properties(gobject_class, N_PROPERTIES, props); - - playlist_signals[SIGNAL_MODIFIED] = g_signal_new( - "modified", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaylistClass, modified), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playlist_signals[SIGNAL_TRACK_ADDED] = g_signal_new( - "track-added", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaylistClass, track_added), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_CHAR - ); - - playlist_signals[SIGNAL_TRACK_LOAD_FINALIZED] = g_signal_new( - "track-load-finalized", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaylistClass, track_load_finalized), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 0 - ); - - playlist_signals[SIGNAL_TRACK_REMOVED] = g_signal_new( - "track-removed", - G_TYPE_FROM_CLASS(gobject_class), - G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, - G_STRUCT_OFFSET(KotoPlaylistClass, track_removed), - NULL, - NULL, - NULL, - G_TYPE_NONE, - 1, - G_TYPE_CHAR - ); -} - -static void koto_playlist_get_property( - GObject * obj, - guint prop_id, - GValue * val, - GParamSpec * spec -) { - KotoPlaylist * self = KOTO_PLAYLIST(obj); - - switch (prop_id) { - case PROP_UUID: - g_value_set_string(val, self->uuid); - break; - case PROP_NAME: - g_value_set_string(val, self->name); - break; - case PROP_ALBUM_UUID: - g_value_set_string(val, self->album_uuid); - break; - case PROP_ART_PATH: - g_value_set_string(val, self->art_path); - break; - case PROP_EPHEMERAL: - g_value_set_boolean(val, self->ephemeral); - break; - case PROP_IS_SHUFFLE_ENABLED: - g_value_set_boolean(val, self->is_shuffle_enabled); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_playlist_set_property( - GObject * obj, - guint prop_id, - const GValue * val, - GParamSpec * spec -) { - KotoPlaylist * self = KOTO_PLAYLIST(obj); - - switch (prop_id) { - case PROP_UUID: - koto_playlist_set_uuid(self, g_value_get_string(val)); - break; - case PROP_NAME: - koto_playlist_set_name(self, g_value_get_string(val)); - break; - case PROP_ALBUM_UUID: - koto_playlist_set_album_uuid(self, g_value_get_string(val)); - break; - case PROP_ART_PATH: - koto_playlist_set_artwork(self, g_value_get_string(val)); - break; - case PROP_EPHEMERAL: - self->ephemeral = g_value_get_boolean(val); - break; - case PROP_IS_SHUFFLE_ENABLED: - self->is_shuffle_enabled = g_value_get_boolean(val); - break; - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, prop_id, spec); - break; - } -} - -static void koto_playlist_init(KotoPlaylist * self) { - self->current_position = -1; // Default to -1 so first time incrementing puts it at 0 - self->current_uuid = NULL; - self->model = KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT; // Default to default model - self->is_shuffle_enabled = FALSE; - self->ephemeral = FALSE; - self->finalized = FALSE; - - self->tracks = g_queue_new(); // Set as an empty GQueue - self->played_tracks = g_queue_new(); // Set as an empty GQueue - self->sorted_tracks = g_queue_new(); // Set as an empty GQueue - self->store = g_list_store_new(KOTO_TYPE_TRACK); -} - -void koto_playlist_add_to_played_tracks( - KotoPlaylist * self, - gchar * uuid -) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); - - if (!KOTO_IS_TRACK(track)) { - return; - } - - if (koto_playlist_get_position_of_track(self, track) != -1) { // Already added - return; - } - - g_queue_push_tail(self->played_tracks, uuid); // Add to end -} - -void koto_playlist_add_track( - KotoPlaylist * self, - KotoTrack * track, - gboolean current, - gboolean commit_to_table -) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - if (!KOTO_IS_TRACK(track)) { - return; - } - - gchar * track_uuid = koto_track_get_uuid(track); - - if (koto_playlist_get_position_of_track(self, track) != -1) { // Found already - return; - } - - g_queue_push_tail(self->tracks, track_uuid); // Prepend the UUID to the tracks - g_queue_push_tail(self->sorted_tracks, track_uuid); // Also add to our sorted tracks - g_list_store_append(self->store, track); // Add to the store - - if (self->finalized) { // Is already finalized - koto_playlist_apply_model(self, self->model); // Re-apply our current model to ensure our "sorted tracks" is sorted, as is our GLIstStore used in the playlist page - } - - if (commit_to_table) { - koto_track_save_to_playlist(track, self->uuid); // Call to save the playlist to the track - } - - if (current && (g_queue_get_length(self->tracks) > 1)) { // Is current and NOT the first item - self->current_uuid = track_uuid; // Mark this as current UUID - koto_playlist_apply_model(self, self->model); // Re-apply our model to enforce mass sort - } - - g_signal_emit( - self, - playlist_signals[SIGNAL_TRACK_ADDED], - 0, - track_uuid - ); -} - -void koto_playlist_apply_model( - KotoPlaylist * self, - KotoPreferredPlaylistSortType preferred_model -) { - GList * sort_user_data = NULL; - - sort_user_data = g_list_prepend(sort_user_data, GUINT_TO_POINTER(preferred_model)); // Prepend our preferred model first - sort_user_data = g_list_prepend(sort_user_data, self); // Prepend ourself - - g_queue_sort(self->sorted_tracks, koto_playlist_model_sort_by_uuid, sort_user_data); // Sort tracks, which is by UUID - g_list_store_sort(self->store, koto_playlist_model_sort_by_track, sort_user_data); // Sort tracks by indexed tracks - - self->model = preferred_model; // Update our preferred model -} - -void koto_playlist_commit(KotoPlaylist * self) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - gboolean album_acceptable_type = FALSE; - - if (koto_utils_string_is_valid(self->album_uuid)) { - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); // Get the album - - if (KOTO_IS_ALBUM(album)) { // Got the album - KotoLibraryType album_type = koto_album_get_lib_type(album); - album_acceptable_type = ((album_type == KOTO_LIBRARY_TYPE_AUDIOBOOK) || (album_type == KOTO_LIBRARY_TYPE_PODCAST)); - } - } - - if (self->ephemeral && !album_acceptable_type) { // Is a temporary playlist and NOT an acceptable type - return; - } - - guint64 track_playback_pos = 0; - - if (koto_utils_string_is_valid(self->current_uuid)) { // Have a track UUID - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the track - - if (KOTO_IS_TRACK(track)) { // Is a track - track_playback_pos = koto_track_get_playback_position(track); // Get the track playback position and set it to our track_playback_pos - } - } - - gchar * commit_op = g_strdup_printf( - "INSERT INTO playlist_meta(id, name, art_path, preferred_model, album_id, track_id, playback_position_of_track)" - "VALUES('%s', quote(\"%s\"), quote(\"%s\"), %li, '%s', '%s', '%li')" - "ON CONFLICT(id) DO UPDATE SET name=excluded.name, art_path=excluded.art_path, preferred_model=excluded.preferred_model, album_id=excluded.album_id, track_id=excluded.track_id;", - self->uuid, - koto_utils_string_get_valid(self->name), - koto_utils_string_get_valid(self->art_path), - (guint64) self->model, - koto_utils_string_get_valid(self->album_uuid), - koto_utils_string_get_valid(self->current_uuid), - track_playback_pos - ); - - new_transaction(commit_op, "Failed to save playlist", FALSE); -} - -void koto_playlist_emit_modified(KotoPlaylist * self) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - g_signal_emit( - self, - playlist_signals[SIGNAL_MODIFIED], - 0 - ); -} - -gchar * koto_playlist_get_artwork(KotoPlaylist * self) { - return (KOTO_IS_PLAYLIST(self) && koto_utils_string_is_valid(self->art_path)) ? g_strdup(self->art_path) : NULL; // Return a duplicate of our art path -} - -KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self) { - return KOTO_IS_PLAYLIST(self) ? self->model : KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT; -} - -gint koto_playlist_get_current_position(KotoPlaylist * self) { - return KOTO_IS_PLAYLIST(self) ? self->current_position : -1; -} - -KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self) { - if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist - return NULL; - } - - return koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the current track -} - -gboolean koto_playlist_get_is_finalized(KotoPlaylist * self) { - return KOTO_IS_PLAYLIST(self) ? self->finalized : FALSE; -} - -gboolean koto_playlist_get_is_hidden(KotoPlaylist * self) { - return (self->ephemeral && koto_utils_string_is_valid(self->album_uuid)); // If the playlist is ephemeral and associated with an album, it should be hidden from nav -} - -guint koto_playlist_get_length(KotoPlaylist * self) { - return KOTO_IS_PLAYLIST(self) ? g_queue_get_length(self->tracks) : 0; // Get the length of the tracks -} - -gchar * koto_playlist_get_name(KotoPlaylist * self) { - return (KOTO_IS_PLAYLIST(self) && koto_utils_string_is_valid(self->name)) ? g_strdup(self->name) : NULL; -} - -gint koto_playlist_get_position_of_track( - KotoPlaylist * self, - KotoTrack * track -) { - if (!KOTO_IS_PLAYLIST(self)) { - return -1; - } - - if (!G_IS_LIST_STORE(self->store)) { - return -1; - } - - if (!KOTO_IS_TRACK(track)) { - return -1; - } - - gint position = -1; - guint found_pos = 0; - - if (g_list_store_find(self->store, track, &found_pos)) { // Found the item - position = (gint) found_pos; // Cast our found position from guint to gint - } - - return position; -} - -gchar * koto_playlist_get_random_track(KotoPlaylist * self) { - gchar * track_uuid = NULL; - guint tracks_len = g_queue_get_length(self->sorted_tracks); - - if (tracks_len == g_queue_get_length(self->played_tracks)) { // Played all tracks - track_uuid = g_list_nth_data(self->sorted_tracks->head, 0); // Get the first - g_queue_clear(self->played_tracks); // Clear our played tracks - } else { // Have not played all tracks - GRand * rando_calrissian = g_rand_new(); // Create a new RNG - guint attempt = 0; - - while (track_uuid == NULL) { // Haven't selected a track yet - attempt++; - gint32 selected_item = g_rand_int_range(rando_calrissian, 0, (gint32) tracks_len); - gchar * selected_track = g_queue_peek_nth(self->sorted_tracks, (guint) selected_item); // Get the UUID of the selected item - - if (g_queue_index(self->played_tracks, selected_track) == -1) { // Haven't played the track - self->current_position = (gint) selected_item; - track_uuid = selected_track; - break; - } else { // Failed to get the track - if (attempt > tracks_len / 2) { - break; // Give up - } - } - } - - g_rand_free(rando_calrissian); // Free rando - } - - return track_uuid; -} - -GListStore * koto_playlist_get_store(KotoPlaylist * self) { - return self->store; -} - -GQueue * koto_playlist_get_tracks(KotoPlaylist * self) { - return self->tracks; -} - -gchar * koto_playlist_get_uuid(KotoPlaylist * self) { - return g_strdup(self->uuid); -} - -gchar * koto_playlist_go_to_next(KotoPlaylist * self) { - if (!KOTO_IS_PLAYLIST(self)) { - return NULL; - } - - if (self->is_shuffle_enabled) { // Shuffling enabled - gchar * random_track_uuid = koto_playlist_get_random_track(self); // Get a random track - self->current_uuid = random_track_uuid; // Update our current UUID - koto_playlist_add_to_played_tracks(self, random_track_uuid); - - koto_playlist_emit_modified(self); - - return random_track_uuid; - } - - if (!koto_utils_string_is_valid(self->current_uuid)) { // No valid UUID yet - self->current_position++; - } else { // Have a UUID currently - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); - - if (!KOTO_IS_TRACK(track)) { - return NULL; - } - - gint pos_of_song = koto_playlist_get_position_of_track(self, track); // Get the position of the current track based on the current model - - if ((guint) pos_of_song == (g_queue_get_length(self->sorted_tracks) - 1)) { // At end - return NULL; - } - - self->current_position = pos_of_song + 1; // Increment our position based on position of song - } - - self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position); - koto_playlist_add_to_played_tracks(self, self->current_uuid); - koto_playlist_emit_modified(self); - - return self->current_uuid; -} - -gchar * koto_playlist_go_to_previous(KotoPlaylist * self) { - if (self->is_shuffle_enabled) { // Shuffling enabled - return koto_playlist_get_random_track(self); // Get a random track - } - - if (!koto_utils_string_is_valid(self->current_uuid)) { // No valid UUID - return NULL; - } - - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); - - if (!KOTO_IS_TRACK(track)) { - return NULL; - } - - gint pos_of_song = koto_playlist_get_position_of_track(self, track); // Get the position of the current track based on the current model - - if (pos_of_song == 0) { - return NULL; - } - - self->current_position = pos_of_song - 1; // Decrement our position based on position of song - self->current_uuid = g_queue_peek_nth(self->sorted_tracks, self->current_position); - - koto_playlist_emit_modified(self); - - return self->current_uuid; -} - -void koto_playlist_mark_as_finalized(KotoPlaylist * self) { - if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist - return; - } - - self->finalized = TRUE; - koto_playlist_apply_model(self, self->model); // Re-apply our model to enforce mass sort - - g_signal_emit( - self, - playlist_signals[SIGNAL_TRACK_LOAD_FINALIZED], - 0 - ); -} - -gint koto_playlist_model_sort_by_uuid( - gconstpointer first_item, - gconstpointer second_item, - gpointer data_list -) { - KotoTrack * first_track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) first_item); - KotoTrack * second_track = koto_cartographer_get_track_by_uuid(koto_maps, (gchar*) second_item); - - return koto_playlist_model_sort_by_track(first_track, second_track, data_list); -} - -gint koto_playlist_model_sort_by_track( - gconstpointer first_item, - gconstpointer second_item, - gpointer data_list -) { - KotoTrack * first_track = (KotoTrack*) first_item; - KotoTrack * second_track = (KotoTrack*) second_item; - - GList * ptr_list = data_list; - KotoPlaylist * self = g_list_nth_data(ptr_list, 0); // First item in the GPtrArray is a pointer to our playlist - KotoPreferredPlaylistSortType model = GPOINTER_TO_UINT(g_list_nth_data(ptr_list, 1)); // Second item in the GPtrArray is a pointer to our KotoPreferredPlaylistSortType - - if ( - (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) || // Newest first model - (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST) // Oldest first - ) { - gint first_track_pos = g_queue_index(self->tracks, koto_track_get_uuid(first_track)); - gint second_track_pos = g_queue_index(self->tracks, koto_track_get_uuid(second_track)); - - if (first_track_pos == -1) { // First track isn't in tracks - return 1; - } - - if (second_track_pos == -1) { // Second track isn't in tracks - return -1; - } - - if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT) { // Newest first - return (first_track_pos < second_track_pos) ? 1 : -1; // Display first at end, not beginning - } else { - return (first_track_pos < second_track_pos) ? -1 : 1; // Display at beginning, not end - } - } - - if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM) { // Sort by album name - gchar * first_album_uuid = NULL; - gchar * second_album_uuid = NULL; - - g_object_get( - first_track, - "album-uuid", - &first_album_uuid, - NULL - ); - - g_object_get( - second_track, - "album-uuid", - &second_album_uuid, - NULL - ); - - if (g_strcmp0(first_album_uuid, second_album_uuid) == 0) { // Identical albums - g_free(first_album_uuid); - g_free(second_album_uuid); - return 0; // Don't get too granular, just consider them equal - } - - KotoAlbum * first_album = koto_cartographer_get_album_by_uuid(koto_maps, first_album_uuid); - KotoAlbum * second_album = koto_cartographer_get_album_by_uuid(koto_maps, second_album_uuid); - - g_free(first_album_uuid); - g_free(second_album_uuid); - - if (!KOTO_IS_ALBUM(first_album) && !KOTO_IS_ALBUM(second_album)) { // Neither are valid albums - return 0; // Just consider them as equal - } - - return g_utf8_collate(koto_album_get_name(first_album), koto_album_get_name(second_album)); - } - - if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST) { // Sort by artist name - gchar * first_artist_uuid = NULL; - gchar * second_artist_uuid = NULL; - - g_object_get( - first_track, - "artist-uuid", - &first_artist_uuid, - NULL - ); - - g_object_get( - second_track, - "artist-uuid", - &second_artist_uuid, - NULL - ); - - KotoArtist * first_artist = koto_cartographer_get_artist_by_uuid(koto_maps, first_artist_uuid); - KotoArtist * second_artist = koto_cartographer_get_artist_by_uuid(koto_maps, second_artist_uuid); - - g_free(first_artist_uuid); - g_free(second_artist_uuid); - - if (!KOTO_IS_ARTIST(first_artist) && !KOTO_IS_ARTIST(second_artist)) { // Neither are valid artists - return 0; // Just consider them as equal - } - - return g_utf8_collate(koto_artist_get_name(first_artist), koto_artist_get_name(second_artist)); - } else if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME) { // Track name - return g_utf8_collate(koto_track_get_name(first_track), koto_track_get_name(second_track)); - } else if (model == KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS) { // Sort by track position - - guint first_track_disc = koto_track_get_disc_number(first_track); - guint second_track_disc = koto_track_get_disc_number(second_track); - - if (first_track_disc < second_track_disc) { // First is in an earlier CD / Disc / Part - return -1; - } else if (first_track_disc > second_track_disc) { // First is in later CD / Disc / Part - return 1; - } // Continue on to same CD - - guint64 first_track_pos = koto_track_get_position(first_track); - guint64 second_track_pos = koto_track_get_position(second_track); - - if (first_track_pos < second_track_pos) { - return -1; - } else if (first_track_pos > second_track_pos) { - return 1; - } - } - - return 0; -} - -void koto_playlist_remove_from_played_tracks( - KotoPlaylist * self, - gchar * uuid -) { - g_queue_remove(self->played_tracks, uuid); -} - -void koto_playlist_remove_track_by_uuid( - KotoPlaylist * self, - gchar * uuid -) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - gint file_index = g_queue_index(self->tracks, uuid); // Get the position of this uuid - - if (file_index != -1) { // Have in tracks - g_queue_pop_nth(self->tracks, file_index); // Remove nth where it is the file index - } - - gint file_index_in_sorted = g_queue_index(self->sorted_tracks, uuid); // Get position in sorted tracks - - if (file_index_in_sorted != -1) { // Have in sorted tracks - g_queue_pop_nth(self->sorted_tracks, file_index_in_sorted); // Remove nth where it is the index in sorted tracks - } - - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, uuid); // Get the track - - if (!KOTO_IS_TRACK(track)) { // Is not a track - return; - } - - guint position = 0; - - if (g_list_store_find(self->store, track, &position)) { // Got the position - g_list_store_remove(self->store, position); // Remove from the store - } - - koto_track_remove_from_playlist(track, self->uuid); - - g_signal_emit( - self, - playlist_signals[SIGNAL_TRACK_REMOVED], - 0, - uuid - ); -} - -void koto_playlist_save_current_playback_state(KotoPlaylist * self) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - if (!koto_utils_string_is_valid(self->album_uuid)) { // No album associated with this playlist - return; - } - - KotoAlbum * album = koto_cartographer_get_album_by_uuid(koto_maps, self->album_uuid); // Get the album - - if (!KOTO_IS_ALBUM(album)) { // Failed to get the album - return; - } - - KotoLibraryType album_type = koto_album_get_lib_type(album); - - if ((album_type == KOTO_LIBRARY_TYPE_MUSIC) || (album_type == KOTO_LIBRARY_TYPE_UNKNOWN)) { // Not an Audiobook or Podcast - return; - } - - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, self->current_uuid); // Get the track - - if (!KOTO_IS_TRACK(track) || !koto_utils_string_is_valid(self->current_uuid)) { // If we don't have a track we are currently playing - gchar * commit_op = g_strdup_printf("UPDATE playlist_meta SET track_id='', playback_position_of_track=0 WHERE id='%s';", self->uuid); - new_transaction(commit_op, "Failed to update our playlist meta", FALSE); - return; - } - - gchar * commit_op = g_strdup_printf( - "UPDATE playlist_meta SET track_id='%s', playback_position_of_track=%li WHERE id='%s';", - koto_utils_string_get_valid(self->current_uuid), - koto_track_get_playback_position(track), - self->uuid - ); - - new_transaction(commit_op, "Failed to update our playlist state", FALSE); - koto_playlist_emit_modified(self); -} - -void koto_playlist_set_album_uuid( - KotoPlaylist * self, - const gchar * album_uuid -) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - if (!koto_utils_string_is_valid(album_uuid)) { - return; - } - - self->album_uuid = g_strdup(album_uuid); // Update the album UUID - - if (self->finalized) { // Has already been loaded - koto_playlist_emit_modified(self); - } -} - -void koto_playlist_set_artwork( - KotoPlaylist * self, - const gchar * path -) { - if (!KOTO_IS_PLAYLIST(self)) { - return; - } - - if (path == NULL) { - return; - } - - magic_t cookie = magic_open(MAGIC_MIME); // Create our magic cookie so we can validate if what we are setting is an image - - if (cookie == NULL) { // Failed to allocate - return; - } - - if (magic_load(cookie, NULL) != 0) { // Failed to load our cookie - goto free_cookie; - } - - const gchar * mime_type = magic_file(cookie, path); // Get the mimetype for this file - - if ((mime_type == NULL) || !g_str_has_prefix(mime_type, "image/")) { // Failed to get our mimetype or not an image - goto free_cookie; - } - - if (self->art_path != NULL) { // art path is set - g_free(self->art_path); // Free the current path - } - - self->art_path = g_strdup(path); // Update our art path to a duplicate of provided path - - if (self->finalized) { // Has already been loaded - koto_playlist_emit_modified(self); - } - -free_cookie: - magic_close(cookie); // Close and free the cookie to the cookie monster -} - -void koto_playlist_set_name( - KotoPlaylist * self, - const gchar * name -) { - if (name == NULL) { - return; - } - - if (self->name != NULL) { // Have a name allocated already - g_free(self->name); // Free the name - } - - self->name = g_strdup(name); - - if (self->finalized) { // Has already been loaded - koto_playlist_emit_modified(self); - } -} - -void koto_playlist_set_position( - KotoPlaylist * self, - gint position -) { - if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist - return; - } - - self->current_position = position; - - if (self->finalized) { // Has already been loaded - koto_playlist_emit_modified(self); - } -} - -void koto_playlist_set_track_as_current( - KotoPlaylist * self, - gchar * track_uuid -) { - if (!KOTO_IS_PLAYLIST(self)) { // Not a playlist - return; - } - - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); // Get the track - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - gint position_of_track = koto_playlist_get_position_of_track(self, track); - - if (position_of_track == -1) { - return; - } - - self->current_uuid = track_uuid; - self->current_position = position_of_track; // Set accurate position -} - -void koto_playlist_set_uuid( - KotoPlaylist * self, - const gchar * uuid -) { - if (uuid == NULL) { // No actual UUID - return; - } - - if (self->uuid != NULL) { // Have a UUID allocated already - return; // Do not allow changing the UUID - } - - self->uuid = g_strdup(uuid); // Set the new UUID -} - -void koto_playlist_tracks_queue_push_to_store( - gpointer data, - gpointer user_data -) { - gchar * track_uuid = (gchar*) data; - KotoTrack * track = koto_cartographer_get_track_by_uuid(koto_maps, track_uuid); - - if (!KOTO_IS_TRACK(track)) { // Not a track - return; - } - - g_list_store_append(G_LIST_STORE(user_data), track); -} - -void koto_playlist_unmap(KotoPlaylist * self) { - koto_cartographer_remove_playlist_by_uuid(koto_maps, self->uuid); // Remove from our cartographer -} - -KotoPlaylist * koto_playlist_new() { - return g_object_new( - KOTO_TYPE_PLAYLIST, - "uuid", - g_uuid_string_random(), - NULL - ); -} - -KotoPlaylist * koto_playlist_new_with_uuid(const gchar * uuid) { - return g_object_new( - KOTO_TYPE_PLAYLIST, - "uuid", - uuid, - NULL - ); -} diff --git a/src/playlist/playlist.h b/src/playlist/playlist.h deleted file mode 100644 index 69f708e..0000000 --- a/src/playlist/playlist.h +++ /dev/null @@ -1,171 +0,0 @@ -/* playlist.h - * - * Copyright 2021 Joshua Strobl - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -#include -#include "../indexer/structs.h" - -G_BEGIN_DECLS - -typedef enum { - KOTO_PREFERRED_PLAYLIST_SORT_TYPE_DEFAULT, // Considered to be newest first - KOTO_PREFERRED_PLAYLIST_SORT_TYPE_OLDEST_FIRST, - KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ALBUM, - KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_ARTIST, - KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_NAME, - KOTO_PREFERRED_PLAYLIST_SORT_TYPE_SORT_BY_TRACK_POS -} KotoPreferredPlaylistSortType; - -/** - * Type Definition - **/ - -#define KOTO_TYPE_PLAYLIST koto_playlist_get_type() -#define KOTO_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), KOTO_TYPE_PLAYLIST, KotoPlaylist)) -#define KOTO_IS_PLAYLIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), KOTO_TYPE_PLAYLIST)) - -typedef struct _KotoPlaylist KotoPlaylist; -typedef struct _KotoPlaylistClass KotoPlaylistClass; - -GLIB_AVAILABLE_IN_ALL -GType koto_playlist_get_type(void) G_GNUC_CONST; - -/** - * Playlist Functions - **/ - -KotoPlaylist * koto_playlist_new(); - -KotoPlaylist * koto_playlist_new_with_uuid(const gchar * uuid); - -void koto_playlist_add_to_played_tracks( - KotoPlaylist * self, - gchar * uuid -); - -void koto_playlist_add_track( - KotoPlaylist * self, - KotoTrack * track, - gboolean current, - gboolean commit_to_table -); - -void koto_playlist_apply_model( - KotoPlaylist * self, - KotoPreferredPlaylistSortType preferred_model -); - -void koto_playlist_commit(KotoPlaylist * self); - -void koto_playlist_emit_modified(KotoPlaylist * self); - -gchar * koto_playlist_get_artwork(KotoPlaylist * self); - -KotoPreferredPlaylistSortType koto_playlist_get_current_model(KotoPlaylist * self); - -gint koto_playlist_get_current_position(KotoPlaylist * self); - -KotoTrack * koto_playlist_get_current_track(KotoPlaylist * self); - -guint koto_playlist_get_length(KotoPlaylist * self); - -gboolean koto_playlist_get_is_finalized(KotoPlaylist * self); - -gboolean koto_playlist_get_is_hidden(KotoPlaylist * self); - -gchar * koto_playlist_get_name(KotoPlaylist * self); - -gint koto_playlist_get_position_of_track( - KotoPlaylist * self, - KotoTrack * track -); - -GListStore * koto_playlist_get_store(KotoPlaylist * self); - -GQueue * koto_playlist_get_tracks(KotoPlaylist * self); - -gchar * koto_playlist_get_uuid(KotoPlaylist * self); - -gchar * koto_playlist_go_to_next(KotoPlaylist * self); - -gchar * koto_playlist_go_to_previous(KotoPlaylist * self); - -void koto_playlist_mark_as_finalized(KotoPlaylist * self); - -gint koto_playlist_model_sort_by_uuid( - gconstpointer first_item, - gconstpointer second_item, - gpointer data_list -); - -gint koto_playlist_model_sort_by_track( - gconstpointer first_item, - gconstpointer second_item, - gpointer model_ptr -); - -void koto_playlist_remove_from_played_tracks( - KotoPlaylist * self, - gchar * uuid -); - -void koto_playlist_remove_track_by_uuid( - KotoPlaylist * self, - gchar * uuid -); - -void koto_playlist_set_album_uuid( - KotoPlaylist * self, - const gchar * album_uuid -); - -void koto_playlist_set_artwork( - KotoPlaylist * self, - const gchar * path -); - -void koto_playlist_save_current_playback_state(KotoPlaylist * self); - -void koto_playlist_set_name( - KotoPlaylist * self, - const gchar * name -); - -void koto_playlist_set_position( - KotoPlaylist * self, - gint position -); - -void koto_playlist_set_track_as_current( - KotoPlaylist * self, - gchar * track_uuid -); - -void koto_playlist_set_uuid( - KotoPlaylist * self, - const gchar * uuid -); - -void koto_playlist_tracks_queue_push_to_store( - gpointer data, - gpointer user_data -); - -void koto_playlist_unmap(KotoPlaylist * self); - -G_END_DECLS diff --git a/theme/_button.scss b/theme/_button.scss deleted file mode 100644 index 3622206..0000000 --- a/theme/_button.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import "vars"; - -.koto-button { - border-width: 0; - - & > .button-label { - margin-left: 10px; - } - - &:not(.active) { - color: $text-color-bright; - } - - &.active > image { - color: $koto-primary-color; - } -} diff --git a/theme/_disc-view.scss b/theme/_disc-view.scss deleted file mode 100644 index dc3a487..0000000 --- a/theme/_disc-view.scss +++ /dev/null @@ -1,14 +0,0 @@ -@import "vars"; - -.discs-list { - background-color: transparent; - border-color: transparent; - border-width: 0; - - .disc-view { - & > box { // Horizontal box with image and disc label - color: $text-color-faded; - margin: 10px 0; - } - } -} \ No newline at end of file diff --git a/theme/_expander.scss b/theme/_expander.scss deleted file mode 100644 index c07efe7..0000000 --- a/theme/_expander.scss +++ /dev/null @@ -1,17 +0,0 @@ -@import 'vars'; - -.expander { - & > .expander-header { - & > label { - font-size: large; - } - } - - & > revealer > box { - & > .koto-button { - &, & > box { - min-height: 40px; - } - } - } -} diff --git a/theme/_main.scss b/theme/_main.scss deleted file mode 100644 index ea45551..0000000 --- a/theme/_main.scss +++ /dev/null @@ -1,52 +0,0 @@ -@import 'components/album-info'; -@import 'components/audiobook-view'; -@import 'components/badge'; -@import 'components/cover-art-button'; -@import 'components/gtk-overrides'; -@import 'components/track-list'; -@import 'components/track-table'; -@import 'components/writer-page'; -@import 'pages/audiobook-library'; -@import 'pages/artist-view'; -@import 'pages/music-local'; -@import 'pages/playlist-page'; - -@import 'button'; -@import 'disc-view'; -@import 'expander'; -@import 'player-bar'; -@import 'primary-nav'; -@import 'track-item'; - -window { - color: $text-color-bright; - background-color: $bg-primary; - - & > headerbar, & > headerbar:active { - background-color: $bg-secondary; - background-image: none; - border-bottom-width: 0; // Unset default styling for headerbar - - &:active windowcontrols button { // Minimize, Maximize, Close buttons when window is active - color: $text-color-bright; - text-decoration-color: $text-color-bright; - } - - &:not(:active) windowcontrols button { // Minimize, Maximize, Close buttons when window is inactive - color: $text-color-faded; - text-decoration-color: $text-color-faded; - } - } - - .koto-dialog-container { - background-color: transparentize($bg-secondary, 0.25); - padding: 20px; - } - - // All the classes we want consistent padding applied to for its primary content - .artist-view-content, // Has the albums - .playlist-page, // Individual playlists - .writer-page { // Writer page in Audiobook - padding: $padding; - } -} diff --git a/theme/_player-bar.scss b/theme/_player-bar.scss deleted file mode 100644 index 17580e6..0000000 --- a/theme/_player-bar.scss +++ /dev/null @@ -1,73 +0,0 @@ -@import 'vars'; - -.player-bar { - background-color: $bg-secondary; - background-image: none; - padding: $halvedpadding; - - .koto-button { - &:not(.toggled) { - color: $player-bar-icon-color; - } - - &.toggled { - color: $text-color-bright; - } - } - - .playerbar-primary-controls, // Primary - .playerbar-secondary-controls { // Secondary - & > .koto-button { // Direct descendents - margin: 0 $quarterpadding; - } - } - - .playerbar-primary-controls { // Primary Controls - .playerbar-advanced-controls { // Advanced controls - margin-left: $padding; - - & > entry { // Inner GtkEntry - margin: 0 $halvedpadding; - } - } - } - - .playerbar-info { // Central info section - & > box { // Info labels - margin-left: 2ex; - - & > image { - color: $text-color-faded; - } - - & > label { - margin-top: 6px; - margin-bottom: 6px; - - &:nth-child(1) { // Title - font-size: x-large; - font-weight: bold; - } - - &:not(:nth-child(1)) { // Album and Artist - font-size: large; - } - - &:nth-child(2) { // Album - - } - - &:nth-child(3) { // Artist - - } - } - } - } - - .playerbar-secondary-controls { // Secondary controls - label { // Inner playback position label - font-size: large; - margin-right: $halvedpadding; - } - } -} diff --git a/theme/_primary-nav.scss b/theme/_primary-nav.scss deleted file mode 100644 index 7c0c054..0000000 --- a/theme/_primary-nav.scss +++ /dev/null @@ -1,35 +0,0 @@ -.primary-nav { - padding: 10px 0; - - & > viewport > box { - & > .koto-button, // Direct buttons like Home - & > .expander > .expander-header { // Expander Headers - font-size: large; - padding-top: 10px; - padding-bottom: 10px; - } - - & > .koto-button, // Direct buttons like Home - & > .expander > .expander-header, // Expander Headers - & > .expander revealer .koto-button { // Expander revealer Buttons - margin-left: 10px; - margin-right: 10px; - padding-left: 10px; - padding-right: 10px; - } - - & > .koto-button, // Direct buttons like Home - & > .expander revealer .koto-button { // Expander revealer Buttons - &.pseudoactive { // When hovering or explicit request to show as active - background-color: $primary-nav-button-active-color; - border-radius: 10px; - } - } - - & > .expander { - & > .expander-header { - color: $text-color-faded; - } - } - } -} \ No newline at end of file diff --git a/theme/_track-item.scss b/theme/_track-item.scss deleted file mode 100644 index 7afbbb0..0000000 --- a/theme/_track-item.scss +++ /dev/null @@ -1,3 +0,0 @@ -.track-item { - padding: 10px; -} diff --git a/theme/_vars.scss b/theme/_vars.scss deleted file mode 100644 index 348f9e9..0000000 --- a/theme/_vars.scss +++ /dev/null @@ -1,9 +0,0 @@ -$midnight: #1d1d1d; -$darkgrey: #666666; -$green: #60E078; -$palewhite: #cccccc; -$red : #FF4652; - -$padding: 40px; -$halvedpadding: $padding / 2; -$quarterpadding: $padding / 4; \ No newline at end of file diff --git a/theme/components/_album-info.scss b/theme/components/_album-info.scss deleted file mode 100644 index f0c646c..0000000 --- a/theme/components/_album-info.scss +++ /dev/null @@ -1,30 +0,0 @@ -// This file contain the styling for the Album Info section - -.album-info { - .album-description, - .album-narrator, - .album-title-year-combo, - .genres-tag-list { - margin-bottom: $quarterpadding - } - - .album-title-year-combo { - padding-top: $halvedpadding; - } - - .album-title { // Title of album - color: $text-color-faded; - font-size: 2.5em; - font-weight: bold; - } - - .album-year { - margin-left: $halvedpadding; - } - - .genres-tag-list { // Genres Tag List - & .label-badge:not(:last-child) { - margin-right: $halvedpadding; - } - } -} \ No newline at end of file diff --git a/theme/components/_audiobook-view.scss b/theme/components/_audiobook-view.scss deleted file mode 100644 index dafa68a..0000000 --- a/theme/components/_audiobook-view.scss +++ /dev/null @@ -1,34 +0,0 @@ -// This is the styling for the Audiobook VIew - -@import '../vars'; - -.audiobook-view { - .side-info { // Side Info - margin-right: $halvedpadding; - - button, - image { - margin-bottom: $halvedpadding; - } - - button { // Play / Continue Playback button - font-size: large; - font-weight: bold; - } - - & > label { - font-size: large; - - &:last-child { - margin-bottom: $halvedpadding; - } - } - } - - .chapters-label { // Chapters label after album info - color: $text-color-faded; - font-size: x-large; - font-weight: bold; - padding: $halvedpadding 0; // Top / bottom padding - } -} \ No newline at end of file diff --git a/theme/components/_badge.scss b/theme/components/_badge.scss deleted file mode 100644 index 1f28024..0000000 --- a/theme/components/_badge.scss +++ /dev/null @@ -1,12 +0,0 @@ - // This file contains the styling for our label badge - -@import '../vars'; - -.label-badge { - color: $text-color-faded; - font-size: large; - font-weight: 900; - background-color: $bg-secondary; - border-radius: 10px; - padding: 5px 20px; -} \ No newline at end of file diff --git a/theme/components/_cover-art-button.scss b/theme/components/_cover-art-button.scss deleted file mode 100644 index 4bd5d20..0000000 --- a/theme/components/_cover-art-button.scss +++ /dev/null @@ -1,11 +0,0 @@ -.cover-art-button { - &:hover { - & > image { - opacity: 0.75; - } - - & > revealer > box { // Inner controls - background-color: transparentize($bg-secondary, 0.25); - } - } -} \ No newline at end of file diff --git a/theme/components/_gtk-overrides.scss b/theme/components/_gtk-overrides.scss deleted file mode 100644 index 0eccc9e..0000000 --- a/theme/components/_gtk-overrides.scss +++ /dev/null @@ -1,135 +0,0 @@ -@import '../vars'; - -@mixin selected-row-styling { - color: $selected-row-color-text; - background-color: $selected-row-color-bg; - border: 0; // Don't have a border - border-image: none; // GTK uses an image which is weird - border-image-width: 0; - outline: none; - outline-offset: 0; - outline-style: none; -} - -button { - .text-button { - &:not(.destructive-action):not(.suggested-action) { // Is just a plain ol' normal text button - color: $button-normal-color-text; - background: $button-normal-color-bg; - border-color: $button-normal-color-border; - } - } - - &.destructive-action { - color: $button-destructive-color-text; - background-color: $button-destructive-color-bg; - background-image: none; - border-width: 0; - } - - &.suggested-action { // Adwaita makes it blue but we want it green - color: $button-suggested-color-text; - background-color: $button-suggested-color-bg; - background-image: none; - border-width: 0; - } -} - -checkbutton { - color: $text-color-bright; - - &:active { - background-color: transparent; - } -} - -check { - background-image: none; - - &:not(:checked) { // Not checked - color: $text-color-faded; - } - - &:active { - background-color: transparent; - } - - &:checked { // Checked but not actively pressing on it - color: $text-color-bright; - background-color: $koto-primary-color; - } -} - -entry { - color: $text-color-bright; - background: $input-background; - border-color: $border-color; - - placeholder { // Placeholder text - color: $text-color-faded; - } -} - -listview { - background-color: transparent; -} - -list:not(.discs-list), listview { - &:not(.track-list) > row { // Rows which are now in the track list - &:active, &:selected { // Active or selected - @include selected-row-styling; - } - } - - &.track-list > row { - &:selected { // Only selected rows - @include selected-row-styling; - } - } -} - -popover.background { - & > arrow, & > contents { - background-color: $bg-primary; - border-color: $border-color; - } - - & > contents { - color: $text-color-bright; - } -} - -range { - &.dragging { // Dragging a range - & > trough { - highlight { - border-color: $koto-primary-color; - } - - & > slider { - background-color: $koto-primary-color; - } - } - } -} - -scale { // Progress bar - highlight { - background-color: $koto-primary-color; - border-color: $koto-primary-color; - } - - slider { // Slider - outline-color: $koto-primary-color; - } -} - -scalebutton { - &, button, image { - color: $player-bar-icon-color; - } - - &:active, &:checked, &:hover, button:active, button:checked, button:hover { - background-color: transparent; - } -} \ No newline at end of file diff --git a/theme/components/_track-list.scss b/theme/components/_track-list.scss deleted file mode 100644 index 217089b..0000000 --- a/theme/components/_track-list.scss +++ /dev/null @@ -1,19 +0,0 @@ -// Track List styling - -@import '../vars'; - -.track-list { - & > row { - &:not(:active):not(:selected) { // Neither active nor selected, see gtk overrides - color: $text-color-bright; - - &:nth-child(odd):not(:hover) { - background-color: $bg-primary; - } - - &:nth-child(even), &:hover { - background-color: $bg-secondary; - } - } - } -} \ No newline at end of file diff --git a/theme/components/_track-table.scss b/theme/components/_track-table.scss deleted file mode 100644 index 59f2d8c..0000000 --- a/theme/components/_track-table.scss +++ /dev/null @@ -1,49 +0,0 @@ -.track-list-content { // Our Track List - & > .track-list-header, - .track-list-columned-item { - font-size: x-large; - padding: 3ex 2ex; - } - - & > .track-list-header { // Headers - font-weight: bold; - - .koto-button { // All Koto buttons in our header - &.active { // Is active - color: $green; - } - } - } - - & > .track-list-columned { // Column content - & > row { - &:not(:selected) { - color: $text-color-bright; - } - - &:nth-child(odd):not(:selected) { - background-color: $bg-secondary; - } - - & > .track-list-columned-item { // Track rows - font-size: x-large; - } - } - } - - .track-column-number { // Column section within header and track items - - } - - .track-column-name { // Name section within header and track items - - } - - .track-column-album { // Album section within headers and track items - - } - - .track-column-artist { // Artist section within headers and track items - - } -} \ No newline at end of file diff --git a/theme/components/_writer-page.scss b/theme/components/_writer-page.scss deleted file mode 100644 index 442201e..0000000 --- a/theme/components/_writer-page.scss +++ /dev/null @@ -1,12 +0,0 @@ -// This is the styling for the writer page - -@import '../vars'; - -.writer-page { - .writer-header { // Our writer / artist header label - color: $text-color-faded; - font-size: 4em; - font-weight: bold; - padding-bottom: $padding; - } -} \ No newline at end of file diff --git a/theme/koto-builtin-dark.scss b/theme/koto-builtin-dark.scss deleted file mode 100644 index f697902..0000000 --- a/theme/koto-builtin-dark.scss +++ /dev/null @@ -1,5 +0,0 @@ -$variant : 'dark'; - -@import 'vars'; // Root vars -@import 'variants/dark/vars'; // Dark variant vars -@import 'main'; \ No newline at end of file diff --git a/theme/koto-builtin-gruvbox.scss b/theme/koto-builtin-gruvbox.scss deleted file mode 100644 index f64fdf7..0000000 --- a/theme/koto-builtin-gruvbox.scss +++ /dev/null @@ -1,5 +0,0 @@ -$variant : 'gruvbox'; - -@import 'vars'; // Root vars -@import 'variants/gruvbox/vars'; // Grubox variant vars -@import 'main'; \ No newline at end of file diff --git a/theme/koto-builtin-light.scss b/theme/koto-builtin-light.scss deleted file mode 100644 index 8a33e8a..0000000 --- a/theme/koto-builtin-light.scss +++ /dev/null @@ -1,5 +0,0 @@ -$variant : 'light'; - -@import 'vars'; // Root vars -@import 'variants/light/vars'; // Light variant vars -@import 'main'; \ No newline at end of file diff --git a/theme/meson.build b/theme/meson.build deleted file mode 100644 index 1d6a97a..0000000 --- a/theme/meson.build +++ /dev/null @@ -1,22 +0,0 @@ -sassc = find_program('sassc', required: true) - -builtin_variants = [ - 'dark', - 'gruvbox', - 'light' -] - -themes = [] - -foreach variant: builtin_variants - themes += custom_target('@0@ theme generation'.format(variant), - input: 'koto-builtin-@0@.scss'.format(variant), - output: 'koto-builtin-@0@.css'.format(variant), - command: [ - sassc, - [ '-a', '-M', '-t', 'compact' ], - '@INPUT@', '@OUTPUT@', - ], - build_by_default: true, - ) -endforeach \ No newline at end of file diff --git a/theme/pages/_artist-view.scss b/theme/pages/_artist-view.scss deleted file mode 100644 index 5d07e44..0000000 --- a/theme/pages/_artist-view.scss +++ /dev/null @@ -1,14 +0,0 @@ -.artist-view { - .no-albums-view { // No Albums - .no-albums-view-header { // Header for artist when we have no albums - .cover-art-button { // Button to play all artist tracks - margin-right: 40px; - } - - & > label { // Artist Name - font-weight: 800; - font-size: 10ex; - } - } - } -} \ No newline at end of file diff --git a/theme/pages/_audiobook-library.scss b/theme/pages/_audiobook-library.scss deleted file mode 100644 index 987d2be..0000000 --- a/theme/pages/_audiobook-library.scss +++ /dev/null @@ -1,36 +0,0 @@ -// This file contains the styling for our Audiobook Library - -@import '../vars'; - -.audiobook-library { // Library page - .genres-banner { // Banner for genres list - .large-banner { // Large banner with art for each genre - padding: $padding; - - .audiobook-genre-button { // Genre buttons - .koto-button { - font-size: 2em; - margin: 0.5em; - } - } - } - } - - .writers-button-flow { // Flowbox of buttons for writers - padding: 0 $padding; // Horizontal padding of our standard item padding - - flowboxchild { - padding: 0; - - &:nth-child(even) { - margin: 0 0.5em; - } - - .writer-button { // Writer button - color: $text-color-bright; - font-size: 1.4em; - background-color: $bg-secondary; - } - } - } -} \ No newline at end of file diff --git a/theme/pages/_music-local.scss b/theme/pages/_music-local.scss deleted file mode 100644 index 337d5d6..0000000 --- a/theme/pages/_music-local.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import '../vars'; - -.page-music-local { - & > .artist-list { - &, & > viewport, & > viewport > list { - background-color: $artist-list-bg; - } - - & > viewport > list { - & > row { - padding: $halvedpadding; - } - } - } - - & > stack { - & > .artist-view { - & > viewport > .artist-view-content { - & > .album-list { - & > flowboxchild > .album-view { - & > overlay { - margin-right: $padding; - } - } - } - } - } - } -} diff --git a/theme/pages/_playlist-page.scss b/theme/pages/_playlist-page.scss deleted file mode 100644 index 9527357..0000000 --- a/theme/pages/_playlist-page.scss +++ /dev/null @@ -1,38 +0,0 @@ -@import '../vars'; - -.playlist-page { - .playlist-page-header { // Our header - & > .playlist-page-header-info { // Our info centerbox - margin-left: 40px; - - & > label { // All labels - color: $text-color-faded; - } - - & > label:nth-child(1) { // First item (type of playlist) - font-size: 3ex; - font-weight: 700; - margin-top: 30px; - margin-bottom: 10px; - } - - & > label:nth-child(2), - & > label:nth-child(3) { - font-weight: 800; - } - - & > label:nth-child(2) { // Second item (playlist name) - font-size: 10ex; - } - - & > label:nth-child(3) { // Third item (number of tracks) - font-size: 4ex; - margin-top: 40px; - } - } - - & > .koto-button { - - } - } -} \ No newline at end of file diff --git a/theme/variants/dark/_vars.scss b/theme/variants/dark/_vars.scss deleted file mode 100644 index 2748469..0000000 --- a/theme/variants/dark/_vars.scss +++ /dev/null @@ -1,27 +0,0 @@ -$koto-primary-color: $green; - -$bg-primary : #2e2e2e; -$bg-secondary : $midnight; - -$border-color: black; - -$text-color-bright: white; -$text-color-faded: $palewhite; - -$button-destructive-color-bg: $red; -$button-destructive-color-text: white; -$button-suggested-color-bg: $koto-primary-color; -$button-suggested-color-text: $midnight; -$button-normal-color-bg: $midnight; -$button-normal-color-text: $text-color-bright; -$button-normal-color-border: black; - -$input-background: $bg-primary; - -$primary-nav-button-active-color: $bg-secondary; - -$artist-list-bg: $bg-secondary; -$player-bar-icon-color: $darkgrey; - -$selected-row-color-bg : $koto-primary-color; -$selected-row-color-text : $midnight; \ No newline at end of file diff --git a/theme/variants/gruvbox/_vars.scss b/theme/variants/gruvbox/_vars.scss deleted file mode 100644 index db06e49..0000000 --- a/theme/variants/gruvbox/_vars.scss +++ /dev/null @@ -1,27 +0,0 @@ -$koto-primary-color: #ba5923; - -$bg-primary : #2C2826; -$bg-secondary : #211e1c; - -$border-color: black; - -$text-color-bright: white; -$text-color-faded: $palewhite; - -$button-destructive-color-bg: $red; -$button-destructive-color-text: white; -$button-suggested-color-bg: $koto-primary-color; -$button-suggested-color-text: $midnight; -$button-normal-color-bg: $midnight; -$button-normal-color-text: $text-color-bright; -$button-normal-color-border: black; - -$input-background: $bg-primary; - -$primary-nav-button-active-color: $bg-secondary; - -$artist-list-bg: $bg-secondary; -$player-bar-icon-color: #423C3A; - -$selected-row-color-bg : $koto-primary-color; -$selected-row-color-text : $midnight; \ No newline at end of file diff --git a/theme/variants/light/_vars.scss b/theme/variants/light/_vars.scss deleted file mode 100644 index 34fd53c..0000000 --- a/theme/variants/light/_vars.scss +++ /dev/null @@ -1,27 +0,0 @@ -$koto-primary-color: $green; - -$bg-primary : #fafafa; -$bg-secondary : $palewhite; - -$border-color: $darkgrey; - -$text-color-bright: $midnight; -$text-color-faded: $darkgrey; - -$button-destructive-color-bg: $red; -$button-destructive-color-text: white; -$button-suggested-color-bg: $koto-primary-color; -$button-suggested-color-text: $midnight; -$button-normal-color-bg: white; -$button-normal-color-text: $text-color-bright; -$button-normal-color-border: $darkgrey; - -$input-background: $bg-primary; - -$primary-nav-button-active-color: $bg-secondary; - -$artist-list-bg: $bg-secondary; -$player-bar-icon-color: $darkgrey; - -$selected-row-color-bg : $koto-primary-color; -$selected-row-color-text : $midnight; \ No newline at end of file