mirror of
				https://github.com/gtalent/sc9k.git
				synced 2025-10-27 05:09:09 -05:00 
			
		
		
		
	Compare commits
	
		
			30 Commits
		
	
	
		
			release-0.
			...
			release-1.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f4e0b5ab9f | |||
| bb825f947e | |||
| b71a64ca33 | |||
| 048b06db97 | |||
| 344cc4f819 | |||
| ae580a0d58 | |||
| ac7bb9c585 | |||
| 56f98eed60 | |||
| 9171a080a7 | |||
| 8a4989923c | |||
| 77df4257c5 | |||
| 87997e8f18 | |||
| 8bdad3ed68 | |||
| b7aba1180d | |||
| ed01f44b91 | |||
| b119268396 | |||
| bc342a290f | |||
| 814cef4a2e | |||
| a9ad077d55 | |||
| 2817d543e9 | |||
| 8f3693a1e5 | |||
| 877f894511 | |||
| 0a28e80455 | |||
| 35bf654246 | |||
| 2bfd466da1 | |||
| 160977110e | |||
| 29cdc499e6 | |||
| a8327b2b67 | |||
| 6c00d960bd | |||
| 6b81eb4137 | 
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,10 +1,13 @@ | ||||
| .cache | ||||
| .clangd | ||||
| .conanbuild | ||||
| .current_build | ||||
| __pycache__ | ||||
| CMakeLists.txt.user | ||||
| Session.vim | ||||
| build | ||||
| compile_commands.json | ||||
| cmake-build-debug | ||||
| dist | ||||
| graph_info.json | ||||
| tags | ||||
|   | ||||
							
								
								
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.idea/.gitignore
									
									
									
										generated
									
									
										vendored
									
									
								
							| @@ -1,8 +0,0 @@ | ||||
| # Default ignored files | ||||
| /shelf/ | ||||
| /workspace.xml | ||||
| # Datasource local storage ignored files | ||||
| /dataSources/ | ||||
| /dataSources.local.xml | ||||
| # Editor-based HTTP Client requests | ||||
| /httpRequests/ | ||||
							
								
								
									
										16
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								.idea/misc.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,17 +1,7 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="CompDBSettings"> | ||||
|     <option name="linkedExternalProjectsSettings"> | ||||
|       <CompDBProjectSettings> | ||||
|         <option name="externalProjectPath" value="$PROJECT_DIR$" /> | ||||
|         <option name="modules"> | ||||
|           <set> | ||||
|             <option value="$PROJECT_DIR$" /> | ||||
|           </set> | ||||
|         </option> | ||||
|       </CompDBProjectSettings> | ||||
|     </option> | ||||
|   <component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" /> | ||||
|   <component name="CompDBWorkspace"> | ||||
|     <contentRoot DIR="$PROJECT_DIR$" /> | ||||
|   </component> | ||||
|   <component name="CompDBWorkspace" PROJECT_DIR="$PROJECT_DIR$" /> | ||||
|   <component name="ExternalStorageConfigurationManager" enabled="true" /> | ||||
| </project> | ||||
							
								
								
									
										2
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="VcsDirectoryMappings"> | ||||
|     <mapping directory="$PROJECT_DIR$" vcs="Git" /> | ||||
|     <mapping directory="" vcs="Git" /> | ||||
|   </component> | ||||
| </project> | ||||
| @@ -2,7 +2,7 @@ | ||||
| source: | ||||
| - . | ||||
| copyright_notice: |- | ||||
|   Copyright 2021 gary@drinkingtea.net | ||||
|   Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  | ||||
|   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 | ||||
|   | ||||
							
								
								
									
										373
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,373 @@ | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
|  | ||||
| 1. Definitions | ||||
| -------------- | ||||
|  | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
|  | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
|  | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
|  | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
|  | ||||
|     (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. | ||||
|  | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
|  | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
|  | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
|  | ||||
| 2.1. Grants | ||||
|  | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
|  | ||||
| (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 | ||||
|  | ||||
| (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. | ||||
|  | ||||
| 2.2. Effective Date | ||||
|  | ||||
| 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. | ||||
|  | ||||
| 2.3. Limitations on Grant Scope | ||||
|  | ||||
| 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: | ||||
|  | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
|  | ||||
| (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 | ||||
|  | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
|  | ||||
| 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). | ||||
|  | ||||
| 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 http://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. | ||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @@ -14,4 +14,4 @@ run: install | ||||
| 	${ENV_RUN} ${PROJECT_EXECUTABLE} | ||||
| .PHONY: debug | ||||
| debug: install | ||||
| 	${ENV_RUN} gdb --args ${PROJECT_EXECUTABLE} | ||||
| 	${DEBUGGER} ${PROJECT_EXECUTABLE} | ||||
|   | ||||
							
								
								
									
										19
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| # Slide Controller 9000 | ||||
|  | ||||
| ## Build Prerequisites | ||||
|  | ||||
| * Install GCC, Clang, or Visual Studio with C++20 support | ||||
| * Install Python 3 | ||||
| * Install Ninja, Make, and CMake | ||||
| * [Qt6](https://www.qt.io/download-qt-installer-oss) (Network and Widgets) | ||||
| * Consider also installing ccache for faster subsequent build times | ||||
|  | ||||
| ## Build | ||||
|  | ||||
| Build options: release, debug, asan | ||||
|  | ||||
| 	make purge configure-{release,debug,asan} install | ||||
|  | ||||
| ## Run | ||||
|  | ||||
| 	make run | ||||
							
								
								
									
										6
									
								
								deps/buildcore/base.cmake
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								deps/buildcore/base.cmake
									
									
									
									
										vendored
									
									
								
							| @@ -14,6 +14,7 @@ set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) | ||||
| set(CMAKE_CXX_STANDARD 20) | ||||
| set(CMAKE_CXX_STANDARD_REQUIRED ON) | ||||
| set(CMAKE_CXX_EXTENSIONS OFF) | ||||
|  | ||||
| # enable ccache | ||||
| if(NOT DEFINED ENV{BUILDCORE_SUPPRESS_CCACHE}) | ||||
| 	find_program(CCACHE_PROGRAM ccache) | ||||
| @@ -44,12 +45,13 @@ else() | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wmissing-field-initializers") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor") | ||||
| 	#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnull-dereference") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-null-dereference") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Woverloaded-virtual") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpedantic") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-compare") | ||||
| 	#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused") | ||||
| 	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused-variable") | ||||
| 	# release build options | ||||
|   | ||||
							
								
								
									
										63
									
								
								deps/buildcore/base.mk
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										63
									
								
								deps/buildcore/base.mk
									
									
									
									
										vendored
									
									
								
							| @@ -1,11 +1,15 @@ | ||||
| # | ||||
| #  Copyright 2016 - 2021 gary@drinkingtea.net | ||||
| #  Copyright 2016 - 2023 gary@drinkingtea.net | ||||
| # | ||||
| #  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 http://mozilla.org/MPL/2.0/. | ||||
| # | ||||
|  | ||||
| ifndef USE_CONAN | ||||
| 	USE_CONAN=0 | ||||
| endif | ||||
|  | ||||
| ifeq (${OS},Windows_NT) | ||||
| 	SHELL := powershell.exe | ||||
| 	.SHELLFLAGS := -NoProfile -Command | ||||
| @@ -24,7 +28,7 @@ ifneq ($(shell which docker 2> /dev/null),) | ||||
| 	endif | ||||
| endif | ||||
|  | ||||
| ifneq ($(shell which python3 2> /dev/null),) | ||||
| ifneq ($(shell ${ENV_RUN} which python3 2> /dev/null),) | ||||
| 	PYTHON3=python3 | ||||
| else | ||||
| 	ifeq ($(shell ${ENV_RUN} python -c 'import sys; print(sys.version_info[0])'),3) | ||||
| @@ -36,8 +40,16 @@ SCRIPTS=${BUILDCORE_PATH}/scripts | ||||
| SETUP_BUILD=${PYTHON3} ${SCRIPTS}/setup-build.py | ||||
| PYBB=${PYTHON3} ${SCRIPTS}/pybb.py | ||||
| CMAKE_BUILD=${PYBB} cmake-build | ||||
| GET_ENV=${PYBB} getenv | ||||
| CTEST=${PYBB} ctest-all | ||||
| RM_RF=${PYBB} rm | ||||
| HOST=$(shell ${PYBB} hostname) | ||||
| BUILDCORE_HOST_SPECIFIC_BUILDPATH=$(shell ${GET_ENV} BUILDCORE_HOST_SPECIFIC_BUILDPATH) | ||||
| ifneq (${BUILDCORE_HOST_SPECIFIC_BUILDPATH},) | ||||
| BUILD_PATH=build/${HOST} | ||||
| else | ||||
| BUILD_PATH=build | ||||
| endif | ||||
| ifdef USE_VCPKG | ||||
| 	ifndef VCPKG_DIR_BASE | ||||
| 		VCPKG_DIR_BASE=.vcpkg | ||||
| @@ -54,31 +66,33 @@ else | ||||
| endif | ||||
|  | ||||
| VCPKG_DIR=$(VCPKG_DIR_BASE)/$(VCPKG_VERSION)-$(HOST_ENV) | ||||
| CURRENT_BUILD=$(HOST_ENV)-$(shell ${PYBB} cat .current_build) | ||||
| CURRENT_BUILD=$(HOST_ENV)-$(shell ${ENV_RUN} ${PYBB} cat .current_build) | ||||
|  | ||||
| .PHONY: build | ||||
| build: | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} build | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} | ||||
| .PHONY: install | ||||
| install: | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} build install | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} install | ||||
| .PHONY: clean | ||||
| clean: | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} build clean | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} clean | ||||
| .PHONY: purge | ||||
| purge: | ||||
| 	${ENV_RUN} ${RM_RF} .current_build | ||||
| 	${ENV_RUN} ${RM_RF} build | ||||
| 	${ENV_RUN} ${RM_RF} ${BUILD_PATH} | ||||
| 	${ENV_RUN} ${RM_RF} dist | ||||
| 	${ENV_RUN} ${RM_RF} compile_commands.json | ||||
| .PHONY: test | ||||
| test: build | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} build test | ||||
| 	${ENV_RUN} mypy ${SCRIPTS} | ||||
| 	${ENV_RUN} ${CMAKE_BUILD} ${BUILD_PATH} test | ||||
| .PHONY: test-verbose | ||||
| test-verbose: build | ||||
| 	${ENV_RUN} ${CTEST} build --output-on-failure | ||||
| 	${ENV_RUN} ${CTEST} ${BUILD_PATH} --output-on-failure | ||||
| .PHONY: test-rerun-verbose | ||||
| test-rerun-verbose: build | ||||
| 	${ENV_RUN} ${CTEST} build --rerun-failed --output-on-failure | ||||
| 	${ENV_RUN} ${CTEST} ${BUILD_PATH} --rerun-failed --output-on-failure | ||||
|  | ||||
| .PHONY: devenv-image | ||||
| devenv-image: | ||||
| @@ -120,6 +134,8 @@ else | ||||
| 	${ENV_RUN} ${VCPKG_DIR}/bootstrap-vcpkg.bat | ||||
| endif | ||||
|  | ||||
| endif | ||||
|  | ||||
| .PHONY: vcpkg-install | ||||
| vcpkg-install: | ||||
| ifneq (${OS},windows) | ||||
| @@ -128,37 +144,30 @@ else | ||||
| 	${VCPKG_DIR}/vcpkg install --triplet x64-windows ${VCPKG_PKGS} | ||||
| endif | ||||
|  | ||||
| else ifdef USE_CONAN # USE_VCPKG ################################################ | ||||
|  | ||||
| .PHONY: setup-conan | ||||
| ifeq (${USE_CONAN},1) # USE_CONAN ################################################ | ||||
| .PHONY: conan-config | ||||
| conan-config: | ||||
| 	${ENV_RUN} conan profile new ${PROJECT_NAME} --detect --force | ||||
| ifeq ($(OS),linux) | ||||
| 	${ENV_RUN} conan profile update settings.compiler.libcxx=libstdc++11 ${PROJECT_NAME} | ||||
| else | ||||
| 	${ENV_RUN} conan profile update settings.compiler.cppstd=20 ${PROJECT_NAME} | ||||
| ifeq ($(OS),windows) | ||||
| 	${ENV_RUN} conan profile update settings.compiler.runtime=static ${PROJECT_NAME} | ||||
| endif | ||||
| endif | ||||
| 	${ENV_RUN} conan profile detect -f --name ${PROJECT_NAME} | ||||
| .PHONY: conan | ||||
| conan: | ||||
| 	${ENV_RUN} ${PYBB} conan-install ${PROJECT_NAME} | ||||
| endif # USE_VCPKG ############################################### | ||||
| endif # USE_CONAN ############################################### | ||||
|  | ||||
| ifeq (${OS},darwin) | ||||
| .PHONY: configure-xcode | ||||
| configure-xcode: | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_tool=xcode --current_build=0 --build_root=${BUILD_PATH} --use_conan=${USE_CONAN} | ||||
| endif | ||||
|  | ||||
| .PHONY: configure-release | ||||
| configure-release: | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=release | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=release --build_root=${BUILD_PATH} --use_conan=${USE_CONAN} | ||||
|  | ||||
| .PHONY: configure-debug | ||||
| configure-debug: | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=debug --build_root=${BUILD_PATH} --use_conan=${USE_CONAN} | ||||
|  | ||||
| .PHONY: configure-asan | ||||
| configure-asan: | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan | ||||
| 	${ENV_RUN} ${SETUP_BUILD} ${VCPKG_TOOLCHAIN} --build_type=asan --build_root=${BUILD_PATH} --use_conan=${USE_CONAN} | ||||
|  | ||||
|   | ||||
							
								
								
									
										83
									
								
								deps/buildcore/scripts/pybb.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								deps/buildcore/scripts/pybb.py
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| #! /usr/bin/env python3 | ||||
|  | ||||
| # | ||||
| #  Copyright 2016 - 2021 gary@drinkingtea.net | ||||
| #  Copyright 2016 - 2023 gary@drinkingtea.net | ||||
| # | ||||
| #  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 | ||||
| @@ -12,43 +12,24 @@ | ||||
| #                     don't translate well to that other operating system | ||||
|  | ||||
| import os | ||||
| import platform | ||||
| import shutil | ||||
| import subprocess | ||||
| import sys | ||||
| from typing import List, Optional | ||||
|  | ||||
|  | ||||
| def cat(paths: [str]) -> int: | ||||
|     for path in paths: | ||||
|         try: | ||||
|             with open(path) as f: | ||||
|                 data = f.read() | ||||
|                 sys.stdout.write(data) | ||||
|         except FileNotFoundError: | ||||
|             sys.stderr.write('cat: {}: no such file or directory\n'.format(path)) | ||||
|             return 1 | ||||
|     sys.stdout.write('\n') | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def mkdir(path: str) -> int: | ||||
| def mkdir(path: str): | ||||
|     if not os.path.exists(path): | ||||
|         try: | ||||
|         os.mkdir(path) | ||||
|         except: | ||||
|             return 1 | ||||
|         return 0 | ||||
|     if os.path.isdir(path): | ||||
|         return 0 | ||||
|     return 1 | ||||
|  | ||||
|  | ||||
| # this exists because Windows is utterly incapable of providing a proper rm -rf | ||||
| def rm(path: str) -> int: | ||||
| def rm(path: str): | ||||
|     if (os.path.exists(path) or os.path.islink(path)) and not os.path.isdir(path): | ||||
|         os.remove(path) | ||||
|     elif os.path.isdir(path): | ||||
|         shutil.rmtree(path) | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def ctest_all() -> int: | ||||
| @@ -66,12 +47,15 @@ def ctest_all() -> int: | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def cmake_build(base_path: str, target: str) -> int: | ||||
| def cmake_build(base_path: str, target: Optional[str]) -> int: | ||||
|     if not os.path.isdir(base_path): | ||||
|         # nothing to build | ||||
|         return 0 | ||||
|     for d in os.listdir(base_path): | ||||
|         args = ['cmake', '--build', os.path.join(base_path, d)] | ||||
|         path = os.path.join(base_path, d) | ||||
|         if not os.path.isdir(path): | ||||
|             continue | ||||
|         args = ['cmake', '--build', path] | ||||
|         if target is not None: | ||||
|             args.extend(['--target', target]) | ||||
|         err = subprocess.run(args).returncode | ||||
| @@ -83,10 +67,14 @@ def cmake_build(base_path: str, target: str) -> int: | ||||
| def conan() -> int: | ||||
|     project_name = sys.argv[2] | ||||
|     conan_dir = '.conanbuild' | ||||
|     err = mkdir(conan_dir) | ||||
|     err = 0 | ||||
|     try: | ||||
|         mkdir(conan_dir) | ||||
|     except: | ||||
|         return 1 | ||||
|     if err != 0: | ||||
|         return err | ||||
|     args = ['conan', 'install', '../', '--build=missing', '-pr', project_name] | ||||
|     args = ['conan', 'install', '../', '-of', '.', '--build=missing', '-pr', project_name] | ||||
|     os.chdir(conan_dir) | ||||
|     err = subprocess.run(args).returncode | ||||
|     if err != 0: | ||||
| @@ -94,10 +82,38 @@ def conan() -> int: | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def main(): | ||||
| def cat(paths: List[str]) -> int: | ||||
|     for path in paths: | ||||
|         try: | ||||
|             with open(path) as f: | ||||
|                 data = f.read() | ||||
|                 sys.stdout.write(data) | ||||
|         except FileNotFoundError: | ||||
|             sys.stderr.write('cat: {}: no such file or directory\n'.format(path)) | ||||
|             return 1 | ||||
|     sys.stdout.write('\n') | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def get_env(var_name: str) -> int: | ||||
|     if var_name not in os.environ: | ||||
|         return 1 | ||||
|     sys.stdout.write(os.environ[var_name]) | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def hostname() -> int: | ||||
|     sys.stdout.write(platform.node()) | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| def main() -> int: | ||||
|     err = 0 | ||||
|     if sys.argv[1] == 'mkdir': | ||||
|         err = mkdir(sys.argv[2]) | ||||
|         try: | ||||
|             mkdir(sys.argv[2]) | ||||
|         except: | ||||
|             err = 1 | ||||
|     elif sys.argv[1] == 'rm': | ||||
|         for i in range(2, len(sys.argv)): | ||||
|             rm(sys.argv[i]) | ||||
| @@ -109,6 +125,10 @@ def main(): | ||||
|         err = cmake_build(sys.argv[2], sys.argv[3] if len(sys.argv) > 3 else None) | ||||
|     elif sys.argv[1] == 'cat': | ||||
|         err = cat(sys.argv[2:]) | ||||
|     elif sys.argv[1] == 'getenv': | ||||
|         err = get_env(sys.argv[2]) | ||||
|     elif sys.argv[1] == 'hostname': | ||||
|         err = hostname() | ||||
|     else: | ||||
|         sys.stderr.write('Command not found\n') | ||||
|         err = 1 | ||||
| @@ -117,7 +137,6 @@ def main(): | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     try: | ||||
|         err = main() | ||||
|         sys.exit(err) | ||||
|         sys.exit(main()) | ||||
|     except KeyboardInterrupt: | ||||
|         sys.exit(1) | ||||
|   | ||||
							
								
								
									
										23
									
								
								deps/buildcore/scripts/setup-build.py
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								deps/buildcore/scripts/setup-build.py
									
									
									
									
										vendored
									
									
								
							| @@ -1,7 +1,7 @@ | ||||
| #! /usr/bin/env python3 | ||||
|  | ||||
| # | ||||
| #  Copyright 2016 - 2021 gary@drinkingtea.net | ||||
| #  Copyright 2016 - 2023 gary@drinkingtea.net | ||||
| # | ||||
| #  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 | ||||
| @@ -17,15 +17,18 @@ import sys | ||||
|  | ||||
| from pybb import mkdir, rm | ||||
|  | ||||
| os_name = os.uname().sysname.lower() | ||||
|  | ||||
| def main(): | ||||
| def main() -> int: | ||||
|     parser = argparse.ArgumentParser() | ||||
|     parser.add_argument('--target', help='Platform target', | ||||
|                         default='{:s}-{:s}'.format(sys.platform, platform.machine())) | ||||
|                         default='{:s}-{:s}'.format(os_name, platform.machine())) | ||||
|     parser.add_argument('--build_type', help='Build type (asan,debug,release)', default='release') | ||||
|     parser.add_argument('--build_tool', help='Build tool (default,xcode)', default='') | ||||
|     parser.add_argument('--build_root', help='Path to the root of build directories (must be in project dir)', default='build') | ||||
|     parser.add_argument('--toolchain', help='Path to CMake toolchain file', default='') | ||||
|     parser.add_argument('--current_build', help='Indicates whether or not to make this the active build', default=1) | ||||
|     parser.add_argument('--use_conan', help='Indicates whether or not should use .conanbuild/conan_toolchain.cmake', default='0') | ||||
|     args = parser.parse_args() | ||||
|  | ||||
|     if args.build_type == 'asan': | ||||
| @@ -39,7 +42,7 @@ def main(): | ||||
|         sanitizer_status = 'OFF' | ||||
|     else: | ||||
|         print('Error: Invalid build tool') | ||||
|         sys.exit(1) | ||||
|         return 1 | ||||
|  | ||||
|     if args.build_tool == 'xcode': | ||||
|         build_config = '{:s}-{:s}'.format(args.target, args.build_tool) | ||||
| @@ -60,12 +63,11 @@ def main(): | ||||
|         build_tool = '-GXcode' | ||||
|     else: | ||||
|         print('Error: Invalid build tool') | ||||
|         sys.exit(1) | ||||
|         return 1 | ||||
|  | ||||
|     project_dir = os.getcwd() | ||||
|     build_dir = '{:s}/build/{:s}'.format(project_dir, build_config) | ||||
|     build_dir = '{:s}/{:s}/{:s}'.format(project_dir, args.build_root, build_config) | ||||
|     rm(build_dir) | ||||
|     mkdir(build_dir) | ||||
|     cmake_cmd = [ | ||||
|         'cmake', '-S', project_dir, '-B', build_dir, build_tool, | ||||
|         '-DCMAKE_EXPORT_COMPILE_COMMANDS=ON', | ||||
| @@ -75,6 +77,8 @@ def main(): | ||||
|         '-DBUILDCORE_BUILD_CONFIG={:s}'.format(build_config), | ||||
|         '-DBUILDCORE_TARGET={:s}'.format(args.target), | ||||
|     ] | ||||
|     if args.use_conan != '0': | ||||
|         cmake_cmd.append('-DCMAKE_TOOLCHAIN_FILE={:s}'.format('.conanbuild/conan_toolchain.cmake')) | ||||
|     if qt_path != '': | ||||
|         cmake_cmd.append(qt_path) | ||||
|     if platform.system() == 'Windows': | ||||
| @@ -90,11 +94,12 @@ def main(): | ||||
|  | ||||
|     rm('compile_commands.json') | ||||
|     if platform.system() != 'Windows': | ||||
|         os.symlink('build/{:s}/compile_commands.json'.format(build_config), 'compile_commands.json') | ||||
|         os.symlink('{:s}/compile_commands.json'.format(build_dir), 'compile_commands.json') | ||||
|     return 0 | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     try: | ||||
|         main() | ||||
|         sys.exit(main()) | ||||
|     except KeyboardInterrupt: | ||||
|         sys.exit(1) | ||||
|   | ||||
| @@ -33,7 +33,7 @@ class RqstHandler(BaseHTTPRequestHandler): | ||||
|  | ||||
|  | ||||
| def run(name): | ||||
|     httpd = HTTPServer(('127.0.0.1', 9302), RqstHandler) | ||||
|     httpd = HTTPServer(('0.0.0.0', 9302), RqstHandler) | ||||
|     httpd.serve_forever() | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -3,15 +3,18 @@ set(CMAKE_AUTOMOC ON) | ||||
| set(CMAKE_AUTORCC ON) | ||||
| set(CMAKE_AUTOUIC ON) | ||||
|  | ||||
| find_package(QT NAMES Qt6 Qt5 COMPONENTS Network Widgets REQUIRED) | ||||
| find_package(QT NAMES Qt6 COMPONENTS Network Widgets REQUIRED) | ||||
| find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Network Widgets REQUIRED) | ||||
|  | ||||
| add_executable( | ||||
| 	SlideController MACOSX_BUNDLE WIN32 | ||||
| 		cameraclient.cpp | ||||
| 		main.cpp | ||||
| 		mainwindow.cpp | ||||
| 		obsclient.cpp | ||||
| 		openlpclient.cpp | ||||
| 		settingsdata.cpp | ||||
| 		settingsdialog.cpp | ||||
| 		slideview.cpp | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										114
									
								
								src/cameraclient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/cameraclient.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| /* | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #include <QNetworkReply> | ||||
| #include <QSettings> | ||||
| #include <string_view> | ||||
|  | ||||
| #include "settingsdata.hpp" | ||||
| #include "cameraclient.hpp" | ||||
|  | ||||
| CameraClient::CameraClient(QObject *parent): QObject(parent) { | ||||
| 	setBaseUrl(); | ||||
| 	m_pollTimer.start(1000); | ||||
| 	connect(&m_pollTimer, &QTimer::timeout, this, &CameraClient::poll); | ||||
| 	connect(m_pollingNam, &QNetworkAccessManager::finished, this, &CameraClient::handlePollResponse); | ||||
| } | ||||
|  | ||||
| void CameraClient::setPresetVC(int preset, VideoConfig const&vc) { | ||||
| 	if (preset > 0 && preset < MaxCameraPresets) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset)); | ||||
| 		setBrightness(vc.brightness); | ||||
| 		setSaturation(vc.saturation); | ||||
| 		setContrast(vc.contrast); | ||||
| 		setSharpness(vc.sharpness); | ||||
| 		setHue(vc.hue); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::setPreset(int preset) { | ||||
| 	if (preset > 0 && preset < MaxCameraPresets) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?ptzcmd&poscall&%1").arg(preset)); | ||||
| 		auto const vc = getVideoConfig()[preset - 1]; | ||||
| 		setBrightness(vc.brightness); | ||||
| 		setSaturation(vc.saturation); | ||||
| 		setContrast(vc.contrast); | ||||
| 		setSharpness(vc.sharpness); | ||||
| 		setHue(vc.hue); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::setBrightness(int val) { | ||||
| 	if (val > -1) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&bright&%1").arg(val)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::setSaturation(int val) { | ||||
| 	if (val > -1) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&saturation&%1").arg(val)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::setContrast(int val) { | ||||
| 	if (val > -1) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&contrast&%1").arg(val)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::setSharpness(int val) { | ||||
| 	if (val > -1) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&sharpness&%1").arg(val)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::setHue(int val) { | ||||
| 	if (val > -1) { | ||||
| 		get(QString("/cgi-bin/ptzctrl.cgi?post_image_value&hue&%1").arg(val)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void CameraClient::reboot() { | ||||
| 	post("/cgi-bin/param.cgi?post_reboot"); | ||||
| 	emit pollFailed(); | ||||
| } | ||||
|  | ||||
| void CameraClient::setBaseUrl() { | ||||
| 	auto const [host, port] = getCameraConnectionData(); | ||||
| 	m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port)); | ||||
| } | ||||
|  | ||||
| void CameraClient::get(QString const&urlExt) { | ||||
| 	QUrl url(QString(m_baseUrl) + urlExt); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	auto const reply = m_nam->get(rqst); | ||||
| 	connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater); | ||||
| } | ||||
|  | ||||
| void CameraClient::post(QString const&urlExt) { | ||||
| 	QUrl url(QString(m_baseUrl) + urlExt); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	auto const reply = m_nam->post(rqst, QByteArray{}); | ||||
| 	connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater); | ||||
| } | ||||
|  | ||||
| void CameraClient::poll() { | ||||
| 	QUrl url(QString(m_baseUrl) + "/cgi-bin/param.cgi?get_device_conf"); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	m_pollingNam->get(rqst); | ||||
| } | ||||
|  | ||||
| void CameraClient::handlePollResponse(QNetworkReply *reply) { | ||||
| 	reply->deleteLater(); | ||||
| 	if (reply->error()) { | ||||
| 		qDebug() << "CameraClient error response:" << reply->errorString(); | ||||
| 		emit pollFailed(); | ||||
| 		return; | ||||
| 	} | ||||
| 	emit pollUpdate(); | ||||
| } | ||||
							
								
								
									
										62
									
								
								src/cameraclient.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/cameraclient.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| /* | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QNetworkAccessManager> | ||||
| #include <QObject> | ||||
| #include <QTimer> | ||||
|  | ||||
| #include "consts.hpp" | ||||
|  | ||||
| class CameraClient: public QObject { | ||||
| 	Q_OBJECT | ||||
| 	private: | ||||
| 		QString m_baseUrl; | ||||
| 		QNetworkAccessManager *const m_nam = new QNetworkAccessManager(this); | ||||
| 		QNetworkAccessManager *const m_pollingNam = new QNetworkAccessManager(this); | ||||
| 		QTimer m_pollTimer; | ||||
|  | ||||
| 	public: | ||||
| 		explicit CameraClient(QObject *parent = nullptr); | ||||
|  | ||||
| 		void setPresetVC(int preset, struct VideoConfig const&vc); | ||||
|  | ||||
| 		void setPreset(int preset); | ||||
|  | ||||
| 		void setBrightness(int val); | ||||
|  | ||||
| 		void setSaturation(int val); | ||||
|  | ||||
| 		void setContrast(int val); | ||||
|  | ||||
| 		void setSharpness(int val); | ||||
|  | ||||
| 		void setHue(int val); | ||||
|  | ||||
| 		void reboot(); | ||||
|  | ||||
| 	public slots: | ||||
| 		void setBaseUrl(); | ||||
|  | ||||
| 	private: | ||||
| 		void get(QString const&url); | ||||
|  | ||||
| 		void post(QString const&url); | ||||
|  | ||||
| 		void poll(); | ||||
|  | ||||
| 		void handlePollResponse(QNetworkReply *reply); | ||||
|  | ||||
| 	signals: | ||||
| 		void pollUpdate(); | ||||
|  | ||||
| 		void pollFailed(); | ||||
|  | ||||
| }; | ||||
|  | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -8,4 +8,7 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| constexpr auto SlideHost = "127.0.0.1"; | ||||
| constexpr auto MaxCameraPresets = 9; | ||||
| constexpr auto MaxViews = 9; | ||||
| constexpr auto Version = "1.0-beta4"; | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -7,10 +7,12 @@ | ||||
|  */ | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QSettings> | ||||
|  | ||||
| #include "mainwindow.hpp" | ||||
|  | ||||
| int main(int argc, char *argv[]) { | ||||
| 	QSettings::setDefaultFormat(QSettings::Format::IniFormat); | ||||
| 	QApplication a(argc, argv); | ||||
| 	QApplication::setApplicationName(QObject::tr("Slide Controller 9000")); | ||||
| 	MainWindow w; | ||||
|   | ||||
| @@ -1,78 +1,237 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #include <QApplication> | ||||
| #include <QHBoxLayout> | ||||
| #include <QMenuBar> | ||||
| #include <QMessageBox> | ||||
| #include <QPushButton> | ||||
| #include <QStatusBar> | ||||
|  | ||||
| #include "settingsdialog.hpp" | ||||
| #include "slideview.hpp" | ||||
| #include "mainwindow.hpp" | ||||
|  | ||||
| MainWindow::MainWindow(QWidget *parent): QMainWindow(parent) { | ||||
| 	move(0, 0); | ||||
| 	setFixedSize(590, 555); | ||||
| 	setFixedSize(610, 555); | ||||
| 	setWindowTitle(tr("Slide Controller 9000")); | ||||
| 	const auto mainWidget = new QWidget(this); | ||||
| 	const auto rootLyt = new QVBoxLayout; | ||||
| 	const auto controlsLayout = new QGridLayout; | ||||
| 	setupMenu(); | ||||
| 	auto const mainWidget = new QWidget(this); | ||||
| 	m_rootLyt = new QVBoxLayout; | ||||
| 	auto const controlsLayout = new QGridLayout; | ||||
| 	m_slideView = new SlideView(this); | ||||
| 	setCentralWidget(mainWidget); | ||||
| 	mainWidget->setLayout(rootLyt); | ||||
| 	rootLyt->addWidget(m_slideView); | ||||
| 	rootLyt->addLayout(controlsLayout); | ||||
| 	mainWidget->setLayout(m_rootLyt); | ||||
| 	m_rootLyt->addWidget(m_slideView); | ||||
| 	m_rootLyt->addLayout(controlsLayout); | ||||
| 	// setup slide controls | ||||
| 	const auto showHideLyt = new QHBoxLayout; | ||||
| 	rootLyt->addLayout(showHideLyt); | ||||
| 	const auto btnPrevSong = new QPushButton(tr("Previous Song (Left)"), this); | ||||
| 	const auto btnPrevSlide = new QPushButton(tr("Previous Slide (Up)"), this); | ||||
| 	const auto btnNextSlide = new QPushButton(tr("Next Slide (Down)"), this); | ||||
| 	const auto btnNextSong = new QPushButton(tr("Next Song (Right)"), this); | ||||
| 	const auto btnHideSlides = new QPushButton(tr("Hide (1)"), this); | ||||
| 	const auto btnOpenLpShowSlides = new QPushButton(tr("Show in OpenLP Only (2)"), this); | ||||
| 	const auto btnShowSlides = new QPushButton(tr("Show (3)"), mainWidget); | ||||
| 	auto const btnPrevSong = new QPushButton(tr("Previous Song"), this); | ||||
| 	auto const btnPrevSlide = new QPushButton(tr("Previous Slide"), this); | ||||
| 	auto const btnNextSlide = new QPushButton(tr("Next Slide"), this); | ||||
| 	auto const btnNextSong = new QPushButton(tr("Next Song"), this); | ||||
| 	btnPrevSong->setToolTip(tr("Change to previous song (left arrow key)")); | ||||
| 	btnPrevSlide->setToolTip(tr("Change to previous slide (up arrow key)")); | ||||
| 	btnNextSong->setToolTip(tr("Change to next song (right arrow key)")); | ||||
| 	btnNextSlide->setToolTip(tr("Change to next slide (down arrow key)")); | ||||
| 	controlsLayout->addWidget(btnPrevSlide, 0, 1); | ||||
| 	controlsLayout->addWidget(btnNextSlide, 0, 2); | ||||
| 	controlsLayout->addWidget(btnPrevSong, 0, 0); | ||||
| 	controlsLayout->addWidget(btnNextSong, 0, 3); | ||||
| 	showHideLyt->addWidget(btnHideSlides); | ||||
| 	showHideLyt->addWidget(btnOpenLpShowSlides); | ||||
| 	showHideLyt->addWidget(btnShowSlides); | ||||
| 	controlsLayout->setSpacing(2); | ||||
| 	btnNextSong->setShortcut(Qt::Key_Right); | ||||
| 	btnPrevSong->setShortcut(Qt::Key_Left); | ||||
| 	btnNextSlide->setShortcut(Qt::Key_Down); | ||||
| 	btnPrevSlide->setShortcut(Qt::Key_Up); | ||||
| 	btnHideSlides->setShortcut(Qt::Key_1); | ||||
| 	btnOpenLpShowSlides->setShortcut(Qt::Key_2); | ||||
| 	btnHideSlides->setToolTip(tr("Also hides slides in OBS")); | ||||
| 	btnShowSlides->setShortcut(Qt::Key_3); | ||||
| 	btnNextSong->setFixedWidth(135); | ||||
| 	btnPrevSong->setFixedWidth(135); | ||||
| 	btnNextSlide->setFixedWidth(135); | ||||
| 	btnPrevSlide->setFixedWidth(135); | ||||
| 	setupViewControls(m_rootLyt); | ||||
| 	connect(btnNextSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSlide); | ||||
| 	connect(btnPrevSlide, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSlide); | ||||
| 	connect(btnNextSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::nextSong); | ||||
| 	connect(btnPrevSong, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::prevSong); | ||||
| 	connect(btnHideSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::blankScreen); | ||||
| 	connect(btnHideSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides); | ||||
| 	connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides); | ||||
| 	connect(btnShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::showSlides); | ||||
| 	connect(btnShowSlides, &QPushButton::clicked, &m_openlpClient, &OpenLPClient::showSlides); | ||||
| 	connect(&m_openlpClient, &OpenLPClient::pollUpdate, m_slideView, &SlideView::pollUpdate); | ||||
| 	connect(&m_openlpClient, &OpenLPClient::songListUpdate, m_slideView, &SlideView::songListUpdate); | ||||
| 	connect(&m_openlpClient, &OpenLPClient::slideListUpdate, m_slideView, &SlideView::slideListUpdate); | ||||
| 	connect(&m_openlpClient, &OpenLPClient::pollFailed, m_slideView, &SlideView::reset); | ||||
| 	connect(m_slideView, &SlideView::songChanged, &m_openlpClient, &OpenLPClient::changeSong); | ||||
| 	connect(m_slideView, &SlideView::slideChanged, &m_openlpClient, &OpenLPClient::changeSlide); | ||||
| 	// setup scene selector | ||||
| 	connect(btnOpenLpShowSlides, &QPushButton::clicked, &m_obsClient, &OBSClient::hideSlides); | ||||
| 	// setup status bar | ||||
| 	setStatusBar(new QStatusBar(this)); | ||||
| 	connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit); | ||||
| 	connect(&m_openlpClient, &OpenLPClient::songChanged, this, &MainWindow::refreshStatusBar); | ||||
| 	connect(&m_openlpClient, &OpenLPClient::pollUpdate, this, &MainWindow::openLpConnectionInit); | ||||
| 	connect(&m_obsClient, &OBSClient::pollUpdate, this, &MainWindow::obsConnectionInit); | ||||
| 	refreshStatusBar(); | ||||
| 	connect(statusBar(), &QStatusBar::messageChanged, this, [this](QStringView const&msg) { | ||||
| 		if (msg.empty()) { | ||||
| 			refreshStatusBar(); | ||||
| 		} | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| void MainWindow::setupMenu() { | ||||
| 	// file menu | ||||
| 	{ | ||||
| 		auto const menu = menuBar()->addMenu(tr("&File")); | ||||
| 		auto const settingsAct = new QAction(tr("&Settings"), this); | ||||
| 		auto const quitAct = new QAction(tr("E&xit"), this); | ||||
| 		settingsAct->setShortcuts(QKeySequence::Preferences); | ||||
| 		connect(settingsAct, &QAction::triggered, this, &MainWindow::openSettings); | ||||
| 		quitAct->setShortcuts(QKeySequence::Quit); | ||||
| 		quitAct->setStatusTip(tr("Exit application")); | ||||
| 		connect(quitAct, &QAction::triggered, &QApplication::quit); | ||||
| 		menu->addAction(settingsAct); | ||||
| 		menu->addAction(quitAct); | ||||
| 	} | ||||
| 	// slides menu | ||||
| 	{ | ||||
| 		auto const menu = menuBar()->addMenu(tr("&Slides")); | ||||
| 		auto const hideSlidesAct = new QAction(tr("&Hide Slides"), this); | ||||
| 		hideSlidesAct->setShortcut(Qt::CTRL | Qt::Key_1); | ||||
| 		connect(hideSlidesAct, &QAction::triggered, &m_openlpClient, &OpenLPClient::blankScreen); | ||||
| 		connect(hideSlidesAct, &QAction::triggered, &m_obsClient, &OBSClient::hideSlides); | ||||
| 		menu->addAction(hideSlidesAct); | ||||
| 		auto const showSlidesInOpenLpAct = new QAction(tr("Show in &OpenLP Only"), this); | ||||
| 		showSlidesInOpenLpAct->setShortcut(Qt::CTRL | Qt::Key_2); | ||||
| 		connect(showSlidesInOpenLpAct, &QAction::triggered, &m_openlpClient, &OpenLPClient::showSlides); | ||||
| 		connect(showSlidesInOpenLpAct, &QAction::triggered, &m_obsClient, &OBSClient::hideSlides); | ||||
| 		menu->addAction(showSlidesInOpenLpAct); | ||||
| 		auto const showSlidesAct = new QAction(tr("&Show Slides"), this); | ||||
| 		showSlidesAct->setShortcut(Qt::CTRL | Qt::Key_3); | ||||
| 		connect(showSlidesAct, &QAction::triggered, &m_obsClient, &OBSClient::showSlides); | ||||
| 		connect(showSlidesAct, &QAction::triggered, &m_openlpClient, &OpenLPClient::showSlides); | ||||
| 		menu->addAction(showSlidesAct); | ||||
| 	} | ||||
| 	// camera preset menu | ||||
| 	{ | ||||
| 		auto const menu = menuBar()->addMenu(tr("&Camera")); | ||||
| 		for (auto i = 0; i < std::min(9, MaxCameraPresets); ++i) { | ||||
| 			auto const cameraPresetAct = new QAction(tr("Camera Preset &%1").arg(i + 1), this); | ||||
| 			cameraPresetAct->setShortcut(Qt::ALT | static_cast<Qt::Key>(Qt::Key_1 + i)); | ||||
| 			connect(cameraPresetAct, &QAction::triggered, &m_cameraClient, [this, i] { | ||||
| 				m_cameraClient.setPreset(i + 1); | ||||
| 			}); | ||||
| 			menu->addAction(cameraPresetAct); | ||||
| 		} | ||||
| 		menu->addSeparator(); | ||||
| 		auto const rebootAct = new QAction(tr("&Reboot"), this); | ||||
| 		connect(rebootAct, &QAction::triggered, &m_cameraClient, [this] { | ||||
| 			QMessageBox confirm(this); | ||||
| 			confirm.setText(tr("Are you sure you want to reboot the camera? This will take about 20 seconds.")); | ||||
| 			confirm.addButton(tr("&No"), QMessageBox::ButtonRole::NoRole); | ||||
| 			confirm.addButton(tr("&Yes"), QMessageBox::ButtonRole::YesRole); | ||||
| 			if (confirm.exec()) { | ||||
| 				m_cameraClient.reboot(); | ||||
| 			} | ||||
| 		}); | ||||
| 		menu->addAction(rebootAct); | ||||
| 	} | ||||
| 	// help menu | ||||
| 	{ | ||||
| 		auto const menu = menuBar()->addMenu(tr("&Help")); | ||||
| 		auto const aboutAct = new QAction(tr("&About"), this); | ||||
| 		connect(aboutAct, &QAction::triggered, &m_cameraClient, [this] { | ||||
| 			QMessageBox about(this); | ||||
| 			about.setText(tr( | ||||
| R"(Slide Controller 9000 - %1 | ||||
| Build date: %2 | ||||
|  | ||||
| Copyright 2021 - 2024 Gary Talent (gary@drinkingtea.net) | ||||
| Slide Controller 9000 is released under the MPL 2.0 | ||||
| Built on Qt library under LGPL 2.0)").arg(Version, __DATE__)); | ||||
| 			about.exec(); | ||||
| 		}); | ||||
| 		menu->addAction(aboutAct); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MainWindow::setupViewControlButtons(QVector<View> const&views, QGridLayout *viewCtlLyt) { | ||||
| 	constexpr auto columns = 3; | ||||
| 	auto const parent = viewCtlLyt->parentWidget(); | ||||
| 	for (auto i = 0; auto const&view : views) { | ||||
| 		auto const x = i % columns; | ||||
| 		auto const y = i / columns; | ||||
| 		auto const name = QString("%1. %2").arg(i + 1).arg(view.name); | ||||
| 		auto const btn = new QPushButton(name, parent); | ||||
| 		btn->setShortcut(Qt::Key_1 + i); | ||||
| 		viewCtlLyt->addWidget(btn, y, x); | ||||
| 		auto const slides = view.slides; | ||||
| 		auto const obsSlides = view.obsSlides; | ||||
| 		auto const cameraPreset = view.cameraPreset; | ||||
| 		connect(btn, &QPushButton::clicked, this, [this, slides, obsSlides, cameraPreset] { | ||||
| 			m_cameraClient.setPreset(cameraPreset); | ||||
| 			m_openlpClient.setSlidesVisible(slides); | ||||
| 			m_obsClient.setSlidesVisible(obsSlides); | ||||
| 		}); | ||||
| 		++i; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MainWindow::setupViewControls(QVBoxLayout *rootLyt) { | ||||
| 	auto views = getViews(); | ||||
| 	if (!m_viewControlsParent) { | ||||
| 		m_viewControlsParent = new QWidget(rootLyt->parentWidget()); | ||||
| 		m_viewControlsParentLyt = new QHBoxLayout(m_viewControlsParent); | ||||
| 		m_viewControlsParentLyt->setContentsMargins(0, 0, 0, 0); | ||||
| 		rootLyt->addWidget(m_viewControlsParent); | ||||
| 	} | ||||
| 	delete m_viewControls; | ||||
| 	m_viewControls = new QWidget(m_viewControlsParent); | ||||
| 	m_viewControlsParentLyt->addWidget(m_viewControls); | ||||
| 	auto const viewCtlLyt = new QGridLayout(m_viewControls); | ||||
| 	viewCtlLyt->setSpacing(5); | ||||
| 	if (views.empty()) { | ||||
| 		views.emplace_back(View{ | ||||
| 				.name = tr("Hide"), | ||||
| 				.slides = false, | ||||
| 				.obsSlides = false, | ||||
| 		}); | ||||
| 		views.emplace_back(View{ | ||||
| 				.name = tr("Show in OpenLP Only"), | ||||
| 				.slides = true, | ||||
| 				.obsSlides = false, | ||||
| 		}); | ||||
| 		views.emplace_back(View{ | ||||
| 			.name = tr("Show"), | ||||
| 			.slides = false, | ||||
| 			.obsSlides = false, | ||||
| 		}); | ||||
| 	} | ||||
| 	setupViewControlButtons(views, viewCtlLyt); | ||||
| } | ||||
|  | ||||
| void MainWindow::openSettings() { | ||||
| 	SettingsDialog d(this); | ||||
| 	connect(&d, &SettingsDialog::previewPreset, &m_cameraClient, &CameraClient::setPresetVC); | ||||
| 	auto const result = d.exec(); | ||||
| 	if (result == QDialog::Accepted) { | ||||
| 		m_cameraClient.setBaseUrl(); | ||||
| 		m_obsClient.setBaseUrl(); | ||||
| 		m_openlpClient.setBaseUrl(); | ||||
| 		setupViewControls(m_rootLyt); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MainWindow::cameraConnectionInit() { | ||||
| 	disconnect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit); | ||||
| 	connect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost); | ||||
| 	m_cameraConnected = true; | ||||
| 	refreshStatusBar(); | ||||
| } | ||||
|  | ||||
| void MainWindow::cameraConnectionLost() { | ||||
| 	disconnect(&m_cameraClient, &CameraClient::pollFailed, this, &MainWindow::cameraConnectionLost); | ||||
| 	connect(&m_cameraClient, &CameraClient::pollUpdate, this, &MainWindow::cameraConnectionInit); | ||||
| 	m_cameraConnected = false; | ||||
| 	refreshStatusBar(); | ||||
| } | ||||
|  | ||||
| void MainWindow::openLpConnectionInit() { | ||||
| @@ -104,9 +263,10 @@ void MainWindow::obsConnectionLost() { | ||||
| } | ||||
|  | ||||
| void MainWindow::refreshStatusBar() { | ||||
| 	const auto openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected"); | ||||
| 	const auto obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected"); | ||||
| 	const auto nextSong = m_openlpClient.getNextSong(); | ||||
| 	const auto nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : ""; | ||||
| 	statusBar()->showMessage(openLpStatus + " | " + obsStatus + nextSongTxt); | ||||
| 	auto const cameraStatus = m_cameraConnected ? tr("Camera: Connected") : tr("Camera: Not Connected"); | ||||
| 	auto const openLpStatus = m_openLpConnected ? tr("OpenLP: Connected") : tr("OpenLP: Not Connected"); | ||||
| 	auto const obsStatus = m_obsConnected ? tr("OBS: Connected") : tr("OBS: Not Connected"); | ||||
| 	auto const nextSong = m_openlpClient.getNextSong(); | ||||
| 	auto const nextSongTxt = m_openLpConnected ? " | Next Song: " + nextSong : ""; | ||||
| 	statusBar()->showMessage(cameraStatus + " | " + openLpStatus + " | " + obsStatus + nextSongTxt); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -12,24 +12,44 @@ | ||||
|  | ||||
| #include <QMainWindow> | ||||
|  | ||||
| #include "cameraclient.hpp" | ||||
| #include "obsclient.hpp" | ||||
| #include "openlpclient.hpp" | ||||
| #include "settingsdata.hpp" | ||||
|  | ||||
| class MainWindow: public QMainWindow { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| 	private: | ||||
| 		CameraClient m_cameraClient; | ||||
| 		OBSClient m_obsClient; | ||||
| 		OpenLPClient m_openlpClient; | ||||
| 		class SlideView *m_slideView = nullptr; | ||||
| 		bool m_cameraConnected = false; | ||||
| 		bool m_openLpConnected = false; | ||||
| 		bool m_obsConnected = false; | ||||
| 		class QVBoxLayout *m_rootLyt = nullptr; | ||||
| 		class QHBoxLayout *m_viewControlsParentLyt = nullptr; | ||||
| 		class QWidget *m_viewControlsParent = nullptr; | ||||
| 		class QWidget *m_viewControls = nullptr; | ||||
|  | ||||
| 	public: | ||||
| 		explicit MainWindow(QWidget *parent = nullptr); | ||||
| 		~MainWindow() override = default; | ||||
|  | ||||
| 	private slots: | ||||
| 	private: | ||||
| 		void setupMenu(); | ||||
|  | ||||
| 		void setupViewControlButtons(QVector<View> const&views, class QGridLayout *rootLyt); | ||||
|  | ||||
| 		void setupViewControls(class QVBoxLayout *rootLyt); | ||||
|  | ||||
| 		void openSettings(); | ||||
|  | ||||
| 		void cameraConnectionInit(); | ||||
|  | ||||
| 		void cameraConnectionLost(); | ||||
|  | ||||
| 		void openLpConnectionInit(); | ||||
|  | ||||
| 		void openLpConnectionLost(); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -8,17 +8,20 @@ | ||||
|  | ||||
| #include <QNetworkReply> | ||||
| #include <QNetworkRequest> | ||||
| #include <QSettings> | ||||
| #include <QUrl> | ||||
|  | ||||
| #include "settingsdata.hpp" | ||||
| #include "obsclient.hpp" | ||||
|  | ||||
| OBSClient::OBSClient(QObject *parent): QObject(parent) { | ||||
| 	setBaseUrl(); | ||||
| 	m_pollTimer.start(1000); | ||||
| 	connect(&m_pollTimer, &QTimer::timeout, this, &OBSClient::poll); | ||||
| 	connect(m_pollingNam, &QNetworkAccessManager::finished, this, &OBSClient::handlePollResponse); | ||||
| } | ||||
|  | ||||
| void OBSClient::setScene(QString scene) { | ||||
| void OBSClient::setScene(QString const&scene) { | ||||
| 	get(QString("/Scene?name=%1").arg(scene)); | ||||
| } | ||||
|  | ||||
| @@ -30,7 +33,7 @@ void OBSClient::hideSlides() { | ||||
| 	setScene("SpeakerScene"); | ||||
| } | ||||
|  | ||||
| void OBSClient::setSlidesVisible(int state) { | ||||
| void OBSClient::setSlidesVisible(bool state) { | ||||
| 	if (state) { | ||||
| 		setScene("MusicScene"); | ||||
| 	} else { | ||||
| @@ -38,15 +41,20 @@ void OBSClient::setSlidesVisible(int state) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OBSClient::get(QString urlExt) { | ||||
| 	QUrl url(QString(BaseUrl) + urlExt); | ||||
| void OBSClient::setBaseUrl() { | ||||
| 	auto const [host, port] = getOBSConnectionData(); | ||||
| 	m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port)); | ||||
| } | ||||
|  | ||||
| void OBSClient::get(QString const&urlExt) { | ||||
| 	QUrl url(QString(m_baseUrl) + urlExt); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	auto reply = m_nam->get(rqst); | ||||
| 	connect(reply, &QIODevice::readyRead, reply, &QObject::deleteLater); | ||||
| } | ||||
|  | ||||
| void OBSClient::poll() { | ||||
| 	QUrl url(QString(BaseUrl) + "/ping"); | ||||
| 	QUrl url(QString(m_baseUrl) + "/ping"); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	m_pollingNam->get(rqst); | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -17,7 +17,7 @@ | ||||
| class OBSClient: public QObject { | ||||
| 	Q_OBJECT | ||||
| 	private: | ||||
| 		const QString BaseUrl = QString("http://") + SlideHost + ":9302"; | ||||
| 		QString m_baseUrl; | ||||
| 		QNetworkAccessManager *m_nam = new QNetworkAccessManager(this); | ||||
| 		QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this); | ||||
| 		QTimer m_pollTimer; | ||||
| @@ -26,16 +26,18 @@ class OBSClient: public QObject { | ||||
| 		explicit OBSClient(QObject *parent = nullptr); | ||||
|  | ||||
| 	public slots: | ||||
| 		void setScene(QString scene); | ||||
| 		void setScene(QString const&scene); | ||||
|  | ||||
| 		void showSlides(); | ||||
|  | ||||
| 		void hideSlides(); | ||||
|  | ||||
| 		void setSlidesVisible(int state); | ||||
| 		void setSlidesVisible(bool state); | ||||
|  | ||||
| 		void setBaseUrl(); | ||||
|  | ||||
| 	private: | ||||
| 		void get(QString url); | ||||
| 		void get(QString const&url); | ||||
|  | ||||
| 		void poll(); | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -11,10 +11,13 @@ | ||||
| #include <QJsonObject> | ||||
| #include <QJsonValueRef> | ||||
| #include <QNetworkReply> | ||||
| #include <QSettings> | ||||
|  | ||||
| #include "settingsdata.hpp" | ||||
| #include "openlpclient.hpp" | ||||
|  | ||||
| OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) { | ||||
| 	setBaseUrl(); | ||||
| 	poll(); | ||||
| 	m_pollTimer.start(250); | ||||
| 	connect(&m_pollTimer, &QTimer::timeout, this, &OpenLPClient::poll); | ||||
| @@ -25,8 +28,8 @@ OpenLPClient::OpenLPClient(QObject *parent): QObject(parent) { | ||||
| } | ||||
|  | ||||
| QString OpenLPClient::getNextSong() { | ||||
| 	const auto currentSong = m_songNameMap[m_currentSongId]; | ||||
| 	const auto songIdx = m_songList.indexOf(currentSong) + 1; | ||||
| 	auto const currentSong = m_songNameMap[m_currentSongId]; | ||||
| 	auto const songIdx = m_songList.indexOf(currentSong) + 1; | ||||
| 	if (songIdx < m_songList.size()) { | ||||
| 		return m_songList[songIdx]; | ||||
| 	} | ||||
| @@ -57,6 +60,14 @@ void OpenLPClient::showSlides() { | ||||
| 	get("/api/display/show"); | ||||
| } | ||||
|  | ||||
| void OpenLPClient::setSlidesVisible(bool value) { | ||||
| 	if (value) { | ||||
| 		showSlides(); | ||||
| 	} else { | ||||
| 		blankScreen(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void OpenLPClient::changeSong(int it) { | ||||
| 	auto n = QString::number(it); | ||||
| 	auto url = "/api/service/set?data=%7B%22request%22%3A+%7B%22id%22%3A+" + n + "%7D%7D&_=1627181837297"; | ||||
| @@ -69,26 +80,31 @@ void OpenLPClient::changeSlide(int slide) { | ||||
| 	get(url); | ||||
| } | ||||
|  | ||||
| void OpenLPClient::get(QString urlExt) { | ||||
| 	QUrl url(QString(BaseUrl) + urlExt); | ||||
| void OpenLPClient::setBaseUrl() { | ||||
| 	auto const [host, port] = getOpenLPConnectionData(); | ||||
| 	m_baseUrl = QString("http://%1:%2").arg(host, QString::number(port)); | ||||
| } | ||||
|  | ||||
| void OpenLPClient::get(QString const&urlExt) { | ||||
| 	QUrl url(m_baseUrl + urlExt); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	m_nam->get(rqst); | ||||
| } | ||||
|  | ||||
| void OpenLPClient::requestSongList() { | ||||
| 	QUrl url(QString(BaseUrl) + "/api/service/list?_=1626628079579"); | ||||
| 	QUrl url(m_baseUrl + "/api/service/list?_=1626628079579"); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	m_songListNam->get(rqst); | ||||
| } | ||||
|  | ||||
| void OpenLPClient::requestSlideList() { | ||||
| 	QUrl url(QString(BaseUrl) + "/api/controller/live/text?_=1626628079579"); | ||||
| 	QUrl url(m_baseUrl + "/api/controller/live/text?_=1626628079579"); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	m_slideListNam->get(rqst); | ||||
| } | ||||
|  | ||||
| void OpenLPClient::poll() { | ||||
| 	QUrl url(QString(BaseUrl) + "/api/poll?_=1626628079579"); | ||||
| 	QUrl url(m_baseUrl + "/api/poll?_=1626628079579"); | ||||
| 	QNetworkRequest rqst(url); | ||||
| 	m_pollingNam->get(rqst); | ||||
| } | ||||
| @@ -144,7 +160,7 @@ void OpenLPClient::handleSongListResponse(QNetworkReply *reply) { | ||||
| 	auto items = doc.object()["results"].toObject()["items"].toArray(); | ||||
| 	m_songNameMap.clear(); | ||||
| 	m_songList.clear(); | ||||
| 	for (const auto &item : items) { | ||||
| 	for (auto const &item : items) { | ||||
| 		auto song = item.toObject(); | ||||
| 		auto name = song["title"].toString(); | ||||
| 		auto id = song["id"].toString(); | ||||
| @@ -167,7 +183,7 @@ void OpenLPClient::handleSlideListResponse(QNetworkReply *reply) { | ||||
| 	QStringList tagList; | ||||
| 	auto doc = QJsonDocument::fromJson(data); | ||||
| 	auto items = doc.object()["results"].toObject()["slides"].toArray(); | ||||
| 	for (const auto &item : items) { | ||||
| 	for (auto const &item : items) { | ||||
| 		auto slide = item.toObject(); | ||||
| 		auto text = slide["text"].toString(); | ||||
| 		auto tag = slide["tag"].toString(); | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -19,11 +19,7 @@ class OpenLPClient: public QObject { | ||||
| 	Q_OBJECT | ||||
|  | ||||
| 	private: | ||||
| 		struct Song { | ||||
| 			QString name; | ||||
| 			QString id; | ||||
| 		}; | ||||
| 		const QString BaseUrl = QString("http://") + SlideHost + ":4316"; | ||||
| 		QString m_baseUrl; | ||||
| 		QNetworkAccessManager *m_nam = new QNetworkAccessManager(this); | ||||
| 		QNetworkAccessManager *m_pollingNam = new QNetworkAccessManager(this); | ||||
| 		QNetworkAccessManager *m_songListNam = new QNetworkAccessManager(this); | ||||
| @@ -53,12 +49,16 @@ class OpenLPClient: public QObject { | ||||
|  | ||||
| 		void showSlides(); | ||||
|  | ||||
| 		void setSlidesVisible(bool value); | ||||
|  | ||||
| 		void changeSong(int it); | ||||
|  | ||||
| 		void changeSlide(int slide); | ||||
|  | ||||
| 		void setBaseUrl(); | ||||
|  | ||||
| 	private: | ||||
| 		void get(QString url); | ||||
| 		void get(QString const&url); | ||||
|  | ||||
| 		void requestSongList(); | ||||
|  | ||||
|   | ||||
							
								
								
									
										188
									
								
								src/settingsdata.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										188
									
								
								src/settingsdata.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,188 @@ | ||||
| /* | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #include <QSettings> | ||||
|  | ||||
| #include "consts.hpp" | ||||
| #include "settingsdata.hpp" | ||||
|  | ||||
| void setVideoConfig(QSettings &settings, QVector<VideoConfig> const&vcList) { | ||||
| 	settings.beginGroup("Camera"); | ||||
| 	settings.beginWriteArray("VideoImageConfig"); | ||||
| 	for (auto i = 0; auto const&vc : vcList) { | ||||
| 		settings.setArrayIndex(i); | ||||
| 		settings.setValue("brightness", vc.brightness); | ||||
| 		settings.setValue("saturation", vc.saturation); | ||||
| 		settings.setValue("contrast", vc.contrast); | ||||
| 		settings.setValue("sharpness", vc.sharpness); | ||||
| 		settings.setValue("hue", vc.hue); | ||||
| 		++i; | ||||
| 	} | ||||
| 	settings.endArray(); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| void setVideoConfig(QVector<VideoConfig> const&vcList) { | ||||
| 	QSettings s; | ||||
| 	setVideoConfig(s, vcList); | ||||
| } | ||||
|  | ||||
| QVector<VideoConfig> getVideoConfig(QSettings &settings) { | ||||
| 	QVector<VideoConfig> vc(MaxCameraPresets); | ||||
| 	settings.beginGroup("Camera"); | ||||
| 	auto const size = std::min(settings.beginReadArray("VideoImageConfig"), MaxCameraPresets); | ||||
| 	for (auto i = 0; i < size; ++i) { | ||||
| 		settings.setArrayIndex(i); | ||||
| 		vc[i] = { | ||||
| 			.brightness = settings.value("brightness").toInt(), | ||||
| 			.saturation = settings.value("saturation").toInt(), | ||||
| 			.contrast = settings.value("contrast").toInt(), | ||||
| 			.sharpness = settings.value("sharpness").toInt(), | ||||
| 			.hue = settings.value("hue").toInt(), | ||||
| 		}; | ||||
| 	} | ||||
| 	settings.endArray(); | ||||
| 	settings.endGroup(); | ||||
| 	return vc; | ||||
| } | ||||
|  | ||||
| QVector<VideoConfig> getVideoConfig() { | ||||
| 	QSettings s; | ||||
| 	return getVideoConfig(s); | ||||
| } | ||||
|  | ||||
| void setCameraConnectionData(QSettings &settings, ConnectionData const&cd) { | ||||
| 	settings.beginGroup("CameraClient"); | ||||
| 	settings.setValue("Host", cd.host); | ||||
| 	settings.setValue("Port", cd.port); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| void setOpenLPConnectionData(QSettings &settings, ConnectionData const&cd) { | ||||
| 	settings.beginGroup("OpenLPClient"); | ||||
| 	settings.setValue("Host", cd.host); | ||||
| 	settings.setValue("Port", cd.port); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| void setOBSConnectionData(QSettings &settings, ConnectionData const&cd) { | ||||
| 	settings.beginGroup("OBSClient"); | ||||
| 	settings.setValue("Host", cd.host); | ||||
| 	settings.setValue("Port", cd.port); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| ConnectionData getCameraConnectionData(QSettings &settings) { | ||||
| 	ConnectionData out; | ||||
| 	settings.beginGroup("CameraClient"); | ||||
| 	out.host = settings.value("Host", "192.168.100.88").toString(); | ||||
| 	out.port = static_cast<uint16_t>(settings.value("Port", 80).toInt()); | ||||
| 	settings.endGroup(); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| ConnectionData getOpenLPConnectionData(QSettings &settings) { | ||||
| 	ConnectionData out; | ||||
| 	settings.beginGroup("OpenLPClient"); | ||||
| 	out.host = settings.value("Host", "127.0.0.1").toString(); | ||||
| 	out.port = static_cast<uint16_t>(settings.value("Port", 4316).toInt()); | ||||
| 	settings.endGroup(); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| ConnectionData getOBSConnectionData(QSettings &settings) { | ||||
| 	ConnectionData out; | ||||
| 	settings.beginGroup("OBSClient"); | ||||
| 	out.host = settings.value("Host", "127.0.0.1").toString(); | ||||
| 	out.port = static_cast<uint16_t>(settings.value("Port", 9302).toInt()); | ||||
| 	settings.endGroup(); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| void setCameraConnectionData(ConnectionData const&cd) { | ||||
| 	QSettings settings; | ||||
| 	settings.beginGroup("CameraClient"); | ||||
| 	settings.setValue("Host", cd.host); | ||||
| 	settings.setValue("Port", cd.port); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| void setOpenLPConnectionData(ConnectionData const&cd) { | ||||
| 	QSettings settings; | ||||
| 	settings.beginGroup("OpenLPClient"); | ||||
| 	settings.setValue("Host", cd.host); | ||||
| 	settings.setValue("Port", cd.port); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| void setOBSConnectionData(ConnectionData const&cd) { | ||||
| 	QSettings settings; | ||||
| 	settings.beginGroup("OBSClient"); | ||||
| 	settings.setValue("Host", cd.host); | ||||
| 	settings.setValue("Port", cd.port); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| ConnectionData getCameraConnectionData() { | ||||
| 	QSettings s; | ||||
| 	return getCameraConnectionData(s); | ||||
| } | ||||
|  | ||||
| ConnectionData getOpenLPConnectionData() { | ||||
| 	QSettings s; | ||||
| 	return getOpenLPConnectionData(s); | ||||
| } | ||||
|  | ||||
| ConnectionData getOBSConnectionData() { | ||||
| 	QSettings s; | ||||
| 	return getOBSConnectionData(s); | ||||
| } | ||||
|  | ||||
|  | ||||
| void setViews(QSettings &settings, QVector<View> const&views) { | ||||
| 	settings.beginGroup("Views"); | ||||
| 	settings.beginWriteArray("Views"); | ||||
| 	for (auto i = 0; auto const&view : views) { | ||||
| 		settings.setArrayIndex(i); | ||||
| 		settings.setValue("Name", view.name); | ||||
| 		settings.setValue("Slides", view.slides); | ||||
| 		settings.setValue("ObsSlides", view.obsSlides); | ||||
| 		settings.setValue("Preset", view.cameraPreset); | ||||
| 		++i; | ||||
| 	} | ||||
| 	settings.endArray(); | ||||
| 	settings.endGroup(); | ||||
| } | ||||
|  | ||||
| void setViews(QVector<View> const&views) { | ||||
| 	QSettings s; | ||||
| 	return setViews(s, views); | ||||
| } | ||||
|  | ||||
| QVector<View> getViews(QSettings &settings) { | ||||
| 	QVector<View> out; | ||||
| 	settings.beginGroup("Views"); | ||||
| 	auto const size = settings.beginReadArray("Views"); | ||||
| 	for (auto i = 0; i < size; ++i) { | ||||
| 		settings.setArrayIndex(i); | ||||
| 		out.emplace_back(View{ | ||||
| 			.name = settings.value("Name").toString(), | ||||
| 			.slides = settings.value("Slides").toBool(), | ||||
| 			.obsSlides = settings.value("ObsSlides").toBool(), | ||||
| 			.cameraPreset = settings.value("Preset").toInt(), | ||||
| 		}); | ||||
| 	} | ||||
| 	settings.endArray(); | ||||
| 	settings.endGroup(); | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QVector<View> getViews() { | ||||
| 	QSettings s; | ||||
| 	return getViews(s); | ||||
| } | ||||
							
								
								
									
										76
									
								
								src/settingsdata.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/settingsdata.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| /* | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <cstdint> | ||||
| #include <QString> | ||||
| #include <QVector> | ||||
|  | ||||
| struct VideoConfig { | ||||
| 	int brightness = 6; | ||||
| 	int saturation = 4; | ||||
| 	int contrast = 8; | ||||
| 	int sharpness = 3; | ||||
| 	int hue = 7; | ||||
| }; | ||||
|  | ||||
| void setVideoConfig(class QSettings &settings, QVector<VideoConfig> const&vc); | ||||
|  | ||||
| void setVideoConfig(QVector<VideoConfig> const&vc); | ||||
|  | ||||
| QVector<VideoConfig> getVideoConfig(class QSettings &settings); | ||||
|  | ||||
| QVector<VideoConfig> getVideoConfig(); | ||||
|  | ||||
| struct ConnectionData { | ||||
| 	QString host; | ||||
| 	uint16_t port = 0; | ||||
| }; | ||||
|  | ||||
| void setCameraConnectionData(class QSettings &settings, ConnectionData const&cd); | ||||
|  | ||||
| void setOpenLPConnectionData(class QSettings &settings, ConnectionData const&cd); | ||||
|  | ||||
| void setOBSConnectionData(class QSettings &settings, ConnectionData const&cd); | ||||
|  | ||||
| [[nodiscard]] | ||||
| ConnectionData getCameraConnectionData(class QSettings &settings); | ||||
|  | ||||
| [[nodiscard]] | ||||
| ConnectionData getOpenLPConnectionData(class QSettings &settings); | ||||
|  | ||||
| [[nodiscard]] | ||||
| ConnectionData getOBSConnectionData(class QSettings &settings); | ||||
|  | ||||
| [[nodiscard]] | ||||
| ConnectionData getCameraConnectionData(); | ||||
|  | ||||
| [[nodiscard]] | ||||
| ConnectionData getOpenLPConnectionData(); | ||||
|  | ||||
| [[nodiscard]] | ||||
| ConnectionData getOBSConnectionData(); | ||||
|  | ||||
|  | ||||
| struct View { | ||||
| 	QString name; | ||||
| 	bool slides = false; | ||||
| 	bool obsSlides = false; | ||||
| 	int cameraPreset = -1; | ||||
| }; | ||||
|  | ||||
| void setViews(class QSettings &settings, QVector<View> const&views); | ||||
|  | ||||
| void setViews(QVector<View> const&views); | ||||
|  | ||||
| [[nodiscard]] | ||||
| QVector<View> getViews(class QSettings &settings); | ||||
|  | ||||
| [[nodiscard]] | ||||
| QVector<View> getViews(); | ||||
							
								
								
									
										311
									
								
								src/settingsdialog.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								src/settingsdialog.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | ||||
| /* | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #include <QCheckBox> | ||||
| #include <QComboBox> | ||||
| #include <QFormLayout> | ||||
| #include <QHeaderView> | ||||
| #include <QIntValidator> | ||||
| #include <QLabel> | ||||
| #include <QLineEdit> | ||||
| #include <QPushButton> | ||||
| #include <QSettings> | ||||
| #include <QSpacerItem> | ||||
| #include <QSpinBox> | ||||
| #include <QTabWidget> | ||||
| #include <QTableWidget> | ||||
| #include <QVBoxLayout> | ||||
|  | ||||
| #include "consts.hpp" | ||||
| #include "settingsdialog.hpp" | ||||
|  | ||||
| enum ViewColumn { | ||||
| 	Name = 0, | ||||
| 	Slides, | ||||
| 	ObsSlides, | ||||
| 	CameraPreset, | ||||
| 	Count | ||||
| }; | ||||
|  | ||||
| SettingsDialog::SettingsDialog(QWidget *parent): QDialog(parent) { | ||||
| 	auto const lyt = new QVBoxLayout(this); | ||||
| 	auto const tabs = new QTabWidget(this); | ||||
| 	lyt->addWidget(tabs); | ||||
| 	tabs->addTab(setupViewConfig(tabs), tr("&Views")); | ||||
| 	tabs->addTab(setupImageConfig(tabs), tr("&Image")); | ||||
| 	tabs->addTab(setupNetworkInputs(tabs), tr("&Network")); | ||||
| 	lyt->addWidget(setupButtons(this)); | ||||
| 	setFixedSize(440, 440); | ||||
| } | ||||
|  | ||||
| QWidget *SettingsDialog::setupNetworkInputs(QWidget *parent) { | ||||
| 	auto const root = new QWidget(parent); | ||||
| 	auto const lyt = new QFormLayout(root); | ||||
| 	auto const portValidator = new QIntValidator(1, 65536, this); | ||||
| 	QSettings settings; | ||||
| 	// camera settings | ||||
| 	{ | ||||
| 		auto const c = getCameraConnectionData(settings); | ||||
| 		m_cameraHostLe = new QLineEdit(root); | ||||
| 		m_cameraPortLe = new QLineEdit(root); | ||||
| 		m_cameraHostLe->setText(c.host); | ||||
| 		m_cameraPortLe->setText(QString::number(c.port)); | ||||
| 		m_cameraPortLe->setValidator(portValidator); | ||||
| 		lyt->addRow(tr("Camera &Host:"), m_cameraHostLe); | ||||
| 		lyt->addRow(tr("Ca&mera Port:"), m_cameraPortLe); | ||||
| 	} | ||||
| 	// OpenLP settings | ||||
| 	{ | ||||
| 		auto const c = getOpenLPConnectionData(settings); | ||||
| 		m_openLpHostLe = new QLineEdit(root); | ||||
| 		m_openLpPortLe = new QLineEdit(root); | ||||
| 		m_openLpHostLe->setText(c.host); | ||||
| 		m_openLpPortLe->setText(QString::number(c.port)); | ||||
| 		m_openLpPortLe->setValidator(portValidator); | ||||
| 		lyt->addRow(tr("Op&enLP Host:"), m_openLpHostLe); | ||||
| 		lyt->addRow(tr("Open&LP Port:"), m_openLpPortLe); | ||||
| 	} | ||||
| 	// OBS settings | ||||
| 	{ | ||||
| 		auto const c = getOBSConnectionData(settings); | ||||
| 		m_obsHostLe = new QLineEdit(root); | ||||
| 		m_obsPortLe = new QLineEdit(root); | ||||
| 		m_obsHostLe->setText(c.host); | ||||
| 		m_obsPortLe->setText(QString::number(c.port)); | ||||
| 		m_obsPortLe->setValidator(portValidator); | ||||
| 		lyt->addRow(tr("O&BS Host:"), m_obsHostLe); | ||||
| 		lyt->addRow(tr("OB&S Port:"), m_obsPortLe); | ||||
| 	} | ||||
| 	return root; | ||||
| } | ||||
|  | ||||
| QWidget *SettingsDialog::setupImageConfig(QWidget *parent) { | ||||
| 	auto const root = new QWidget(parent); | ||||
| 	auto const lyt = new QVBoxLayout(root); | ||||
| 	{ | ||||
| 		auto const formRoot = new QWidget(parent); | ||||
| 		auto const formLyt = new QFormLayout(formRoot); | ||||
| 		lyt->addWidget(formRoot); | ||||
| 		m_videoConfig = getVideoConfig(); | ||||
| 		auto const mkSb = [parent, formLyt](QString const&lbl) { | ||||
| 			auto const s = new QSpinBox(parent); | ||||
| 			s->setAlignment(Qt::AlignRight); | ||||
| 			s->setRange(0, 14); | ||||
| 			formLyt->addRow(lbl, s); | ||||
| 			return s; | ||||
| 		}; | ||||
| 		auto const presetNo = new QComboBox(parent); | ||||
| 		connect(presetNo, &QComboBox::currentIndexChanged, this, &SettingsDialog::updateVidConfigPreset); | ||||
| 		for (auto i = 0; i < MaxCameraPresets; ++i) { | ||||
| 			presetNo->addItem(tr("Camera Preset %1").arg(i + 1)); | ||||
| 		} | ||||
| 		formLyt->addRow(presetNo); | ||||
| 		m_vidBrightness = mkSb(tr("&Brightness:")); | ||||
| 		m_vidSaturation = mkSb(tr("&Saturation:")); | ||||
| 		m_vidContrast = mkSb(tr("Con&trast:")); | ||||
| 		m_vidSharpness = mkSb(tr("Sharpn&ess:")); | ||||
| 		m_vidHue = mkSb(tr("&Hue:")); | ||||
| 		updateVidConfigPreset(0); | ||||
| 	} | ||||
| 	{ | ||||
| 		auto const btnRoot = new QWidget(parent); | ||||
| 		auto const btnLyt = new QHBoxLayout(btnRoot); | ||||
| 		lyt->addWidget(btnRoot); | ||||
| 		btnLyt->setAlignment(Qt::AlignRight); | ||||
| 		auto const previewBtn = new QPushButton(tr("&Preview"), btnRoot); | ||||
| 		btnLyt->addWidget(previewBtn); | ||||
| 		connect(previewBtn, &QPushButton::clicked, this, [this] { | ||||
| 			this->collectVideoConfig(); | ||||
| 			auto const &vc = m_videoConfig[m_vidCurrentPreset]; | ||||
| 			emit previewPreset(m_vidCurrentPreset + 1, vc); | ||||
| 		}); | ||||
| 	} | ||||
| 	return root; | ||||
| } | ||||
|  | ||||
| QWidget *SettingsDialog::setupViewConfig(QWidget *parent) { | ||||
| 	auto const root = new QWidget(parent); | ||||
| 	auto const lyt = new QVBoxLayout(root); | ||||
| 	auto const btnsRoot = new QWidget(root); | ||||
| 	m_viewTable = new QTableWidget(root); | ||||
| 	lyt->addWidget(btnsRoot); | ||||
| 	lyt->addWidget(m_viewTable); | ||||
| 	{ // table | ||||
| 		QStringList columns; | ||||
| 		columns.resize(ViewColumn::Count); | ||||
| 		columns[ViewColumn::Name] = tr("Name"); | ||||
| 		columns[ViewColumn::Slides] = tr("Slides"); | ||||
| 		columns[ViewColumn::ObsSlides] = tr("OBS Slides"); | ||||
| 		columns[ViewColumn::CameraPreset] = tr("Camera Preset"); | ||||
| 		m_viewTable->setColumnCount(static_cast<int>(columns.size())); | ||||
| 		m_viewTable->setHorizontalHeaderLabels(columns); | ||||
| 		m_viewTable->setSelectionMode(QAbstractItemView::SelectionMode::SingleSelection); | ||||
| 		m_viewTable->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows); | ||||
| 		auto const hdr = m_viewTable->horizontalHeader(); | ||||
| 		m_viewTable->setColumnWidth(1, 70); | ||||
| 		m_viewTable->setColumnWidth(2, 75); | ||||
| 		m_viewTable->setColumnWidth(3, 70); | ||||
| 		hdr->setStretchLastSection(true); | ||||
| 	} | ||||
| 	{ // add/removes buttons | ||||
| 		auto const btnsLyt = new QHBoxLayout(btnsRoot); | ||||
| 		auto const addBtn = new QPushButton("A&dd", btnsRoot); | ||||
| 		auto const rmBtn = new QPushButton("&Remove", btnsRoot); | ||||
| 		addBtn->setFixedWidth(70); | ||||
| 		rmBtn->setFixedWidth(70); | ||||
| 		rmBtn->setDisabled(true); | ||||
| 		btnsLyt->addWidget(addBtn); | ||||
| 		btnsLyt->addWidget(rmBtn); | ||||
| 		btnsLyt->setAlignment(Qt::AlignLeft); | ||||
| 		connect(addBtn, &QPushButton::clicked, this, [this, addBtn] { | ||||
| 			auto const row = m_viewTable->rowCount(); | ||||
| 			m_viewTable->setRowCount(row + 1); | ||||
| 			setupViewRow(row); | ||||
| 			addBtn->setEnabled(m_viewTable->rowCount() < MaxViews); | ||||
| 		}); | ||||
| 		connect(rmBtn, &QPushButton::clicked, this, [this, addBtn] { | ||||
| 			auto const row = m_viewTable->currentRow(); | ||||
| 			m_viewTable->removeRow(row); | ||||
| 			addBtn->setEnabled(m_viewTable->rowCount() < MaxViews); | ||||
| 		}); | ||||
| 		connect(m_viewTable, &QTableWidget::currentCellChanged, rmBtn, [this, addBtn, rmBtn] (int row) { | ||||
| 			rmBtn->setEnabled(row > -1 && row < m_viewTable->rowCount()); | ||||
| 			addBtn->setEnabled(m_viewTable->rowCount() < MaxViews); | ||||
| 		}); | ||||
| 		auto const views = getViews(); | ||||
| 		m_viewTable->setRowCount(static_cast<int>(views.size())); | ||||
| 		for (auto row = 0; auto const&view : views) { | ||||
| 			setupViewRow(row, view); | ||||
| 			++row; | ||||
| 		} | ||||
| 	} | ||||
| 	return root; | ||||
| } | ||||
|  | ||||
| QWidget *SettingsDialog::setupButtons(QWidget *parent) { | ||||
| 	auto const root = new QWidget(parent); | ||||
| 	auto const lyt = new QHBoxLayout(root); | ||||
| 	m_errLbl = new QLabel(root); | ||||
| 	auto const okBtn = new QPushButton(tr("&OK"), root); | ||||
| 	auto const applyBtn = new QPushButton(tr("&Apply"), root); | ||||
| 	auto const cancelBtn = new QPushButton(tr("&Cancel"), root); | ||||
| 	lyt->addWidget(m_errLbl); | ||||
| 	lyt->addSpacerItem(new QSpacerItem(1000, 0, QSizePolicy::Expanding, QSizePolicy::Ignored)); | ||||
| 	lyt->addWidget(okBtn); | ||||
| 	lyt->addWidget(applyBtn); | ||||
| 	lyt->addWidget(cancelBtn); | ||||
| 	connect(okBtn, &QPushButton::clicked, this, &SettingsDialog::handleOK); | ||||
| 	connect(applyBtn, &QPushButton::clicked, this, &SettingsDialog::handleApply); | ||||
| 	connect(cancelBtn, &QPushButton::clicked, this, &SettingsDialog::reject); | ||||
| 	return root; | ||||
| } | ||||
|  | ||||
| void SettingsDialog::handleApply() { | ||||
| 	QSettings settings; | ||||
| 	QVector<View> views; | ||||
| 	auto const viewsErr = collectViews(views); | ||||
| 	if (viewsErr) { | ||||
| 		return; | ||||
| 	} | ||||
| 	setViews(settings, views); | ||||
| 	setCameraConnectionData(settings, { | ||||
| 		.host = m_cameraHostLe->text(), | ||||
| 		.port = m_cameraPortLe->text().toUShort(), | ||||
| 	}); | ||||
| 	setOpenLPConnectionData(settings, { | ||||
| 		.host = m_openLpHostLe->text(), | ||||
| 		.port = m_openLpPortLe->text().toUShort(), | ||||
| 	}); | ||||
| 	setOBSConnectionData(settings, { | ||||
| 		.host = m_obsHostLe->text(), | ||||
| 		.port = m_obsPortLe->text().toUShort(), | ||||
| 	}); | ||||
| 	collectVideoConfig(); | ||||
| 	setVideoConfig(settings, m_videoConfig); | ||||
| } | ||||
|  | ||||
| void SettingsDialog::handleOK() { | ||||
| 	handleApply(); | ||||
| 	accept(); | ||||
| } | ||||
|  | ||||
| void SettingsDialog::setupViewRow(int row, View const&view) { | ||||
| 	// name | ||||
| 	auto const nameItem = new QTableWidgetItem(view.name); | ||||
| 	m_viewTable->setItem(row, ViewColumn::Name, nameItem); | ||||
| 	// slides | ||||
| 	auto const slidesCb = new QCheckBox(m_viewTable); | ||||
| 	slidesCb->setChecked(view.slides); | ||||
| 	m_viewTable->setCellWidget(row, ViewColumn::Slides, slidesCb); | ||||
| 	// obs slides | ||||
| 	auto const obsSlidesCb = new QCheckBox(m_viewTable); | ||||
| 	obsSlidesCb->setChecked(view.obsSlides); | ||||
| 	m_viewTable->setCellWidget(row, ViewColumn::ObsSlides, obsSlidesCb); | ||||
| 	// camera preset | ||||
| 	auto const presetItem = new QTableWidgetItem(QString::number(view.cameraPreset)); | ||||
| 	m_viewTable->setItem(row, ViewColumn::CameraPreset, presetItem); | ||||
| } | ||||
|  | ||||
| int SettingsDialog::collectViews(QVector<View> &views) const { | ||||
| 	for (auto row = 0; row < m_viewTable->rowCount(); ++row) { | ||||
| 		auto const viewNo = row + 1; | ||||
| 		bool ok = false; | ||||
| 		auto const name = m_viewTable->item(row, ViewColumn::Name)->text(); | ||||
| 		if (name.trimmed() == "") { | ||||
| 			m_errLbl->setText(tr("View %1 has no name.").arg(viewNo)); | ||||
| 			return 1; | ||||
| 		} | ||||
| 		auto const cameraPreset = m_viewTable->item(row, ViewColumn::CameraPreset)->text().toInt(&ok); | ||||
| 		if (!ok || cameraPreset < 1 || cameraPreset > MaxCameraPresets) { | ||||
| 			m_errLbl->setText(tr("View %1 has invalid preset (1-%2)").arg(viewNo).arg(MaxCameraPresets)); | ||||
| 			return 2; | ||||
| 		} | ||||
| 		views.emplace_back(View{ | ||||
| 			.name = name, | ||||
| 			.slides = dynamic_cast<QCheckBox*>(m_viewTable->cellWidget(row, ViewColumn::Slides))->isChecked(), | ||||
| 			.obsSlides = dynamic_cast<QCheckBox*>(m_viewTable->cellWidget(row, ViewColumn::ObsSlides))->isChecked(), | ||||
| 			.cameraPreset = cameraPreset, | ||||
| 		}); | ||||
| 	} | ||||
| 	return 0; | ||||
| } | ||||
|  | ||||
| void SettingsDialog::collectVideoConfig() { | ||||
| 	auto &vc = m_videoConfig[m_vidCurrentPreset]; | ||||
| 	auto constexpr getVal = [](int &val, QSpinBox *src) { | ||||
| 		if (src) { | ||||
| 			val = src->value(); | ||||
| 		} | ||||
| 	}; | ||||
| 	getVal(vc.brightness, m_vidBrightness); | ||||
| 	getVal(vc.saturation, m_vidSaturation); | ||||
| 	getVal(vc.contrast, m_vidContrast); | ||||
| 	getVal(vc.sharpness, m_vidSharpness); | ||||
| 	getVal(vc.hue, m_vidHue); | ||||
| } | ||||
|  | ||||
| void SettingsDialog::updateVidConfigPreset(int preset) { | ||||
| 	// update to new value | ||||
| 	auto constexpr setVal = [](int val, QSpinBox *dst) { | ||||
| 		if (dst) { | ||||
| 			dst->setValue(val); | ||||
| 		} | ||||
| 	}; | ||||
| 	auto const&vc = m_videoConfig[preset]; | ||||
| 	setVal(vc.brightness, m_vidBrightness); | ||||
| 	setVal(vc.saturation, m_vidSaturation); | ||||
| 	setVal(vc.contrast, m_vidContrast); | ||||
| 	setVal(vc.sharpness, m_vidSharpness); | ||||
| 	setVal(vc.hue, m_vidHue); | ||||
| 	m_vidCurrentPreset = preset; | ||||
| } | ||||
|  | ||||
| void SettingsDialog::updateVidConfigPresetCollect(int preset) { | ||||
| 	collectVideoConfig(); | ||||
| 	updateVidConfigPreset(preset); | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/settingsdialog.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/settingsdialog.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| /* | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <QDialog> | ||||
|  | ||||
| #include "consts.hpp" | ||||
| #include "settingsdata.hpp" | ||||
|  | ||||
| class SettingsDialog: public QDialog { | ||||
| 	Q_OBJECT | ||||
| 	private: | ||||
| 		QVector<VideoConfig> m_videoConfig = QVector<VideoConfig>(MaxCameraPresets); | ||||
| 		class QLabel *m_errLbl = nullptr; | ||||
| 		class QLineEdit *m_cameraHostLe = nullptr; | ||||
| 		class QLineEdit *m_cameraPortLe = nullptr; | ||||
| 		class QLineEdit *m_openLpHostLe = nullptr; | ||||
| 		class QLineEdit *m_openLpPortLe = nullptr; | ||||
| 		class QLineEdit *m_obsHostLe = nullptr; | ||||
| 		class QLineEdit *m_obsPortLe = nullptr; | ||||
| 		class QSpinBox *m_vidBrightness = nullptr; | ||||
| 		class QSpinBox *m_vidSaturation = nullptr; | ||||
| 		class QSpinBox *m_vidContrast = nullptr; | ||||
| 		class QSpinBox *m_vidSharpness = nullptr; | ||||
| 		class QSpinBox *m_vidHue = nullptr; | ||||
| 		int m_vidCurrentPreset = 0; | ||||
| 		class QTableWidget *m_viewTable = nullptr; | ||||
| 	public: | ||||
| 		explicit SettingsDialog(QWidget *parent); | ||||
| 	private: | ||||
| 		QWidget *setupNetworkInputs(QWidget *parent); | ||||
| 		QWidget *setupViewConfig(QWidget *parent); | ||||
| 		QWidget *setupImageConfig(QWidget *parent); | ||||
| 		QWidget *setupButtons(QWidget *parent); | ||||
| 		void handleApply(); | ||||
| 		void handleOK(); | ||||
| 		void setupViewRow(int row, View const&view = {}); | ||||
| 		/** | ||||
| 		 * Gets views from table. | ||||
| 		 * @return error code | ||||
| 		 */ | ||||
| 		[[nodiscard("Must check error code")]] | ||||
| 		int collectViews(QVector<View> &views) const; | ||||
| 		void collectVideoConfig(); | ||||
| 		void updateVidConfigPreset(int preset); | ||||
| 		void updateVidConfigPresetCollect(int preset); | ||||
| 	signals: | ||||
| 		void previewPreset(int, VideoConfig const&); | ||||
| }; | ||||
| @@ -1,12 +1,11 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 http://mozilla.org/MPL/2.0/. | ||||
|  */ | ||||
|  | ||||
| #include <QComboBox> | ||||
| #include <QDebug> | ||||
| #include <QHBoxLayout> | ||||
| #include <QHeaderView> | ||||
| @@ -35,17 +34,17 @@ SlideView::SlideView(QWidget *parent): QWidget(parent) { | ||||
| } | ||||
|  | ||||
| QString SlideView::getNextSong() const { | ||||
| 	const auto cnt = m_songSelector->count(); | ||||
| 	const auto idx = m_songSelector->currentRow() + 1; | ||||
| 	auto const cnt = m_songSelector->count(); | ||||
| 	auto const idx = m_songSelector->currentRow() + 1; | ||||
| 	if (idx < cnt) { | ||||
| 		return m_songSelector->currentItem()->text(); | ||||
| 	} | ||||
| 	return ""; | ||||
| } | ||||
|  | ||||
| void SlideView::pollUpdate(QString songName, int slide) { | ||||
| void SlideView::pollUpdate(QString const&songName, int slide) { | ||||
| 	auto songItems = m_songSelector->findItems(songName, Qt::MatchFixedString); | ||||
| 	if (songItems.size() < 1) { | ||||
| 	if (songItems.empty()) { | ||||
| 		return; | ||||
| 	} | ||||
| 	auto songItem = songItems.first(); | ||||
| @@ -60,17 +59,20 @@ void SlideView::pollUpdate(QString songName, int slide) { | ||||
| } | ||||
|  | ||||
| void SlideView::changeSong(int song) { | ||||
| 	auto songItem = m_songSelector->item(song); | ||||
| 	if (songItem->text() != m_currentSong) { | ||||
| 	if (song < 0) { | ||||
| 		return; | ||||
| 	} | ||||
| 	auto const songItem = m_songSelector->item(song); | ||||
| 	if (songItem && songItem->text() != m_currentSong) { | ||||
| 		emit songChanged(song); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void SlideView::slideListUpdate(QStringList tagList, QStringList slideList) { | ||||
| void SlideView::slideListUpdate(QStringList const&tagList, QStringList const&slideList) { | ||||
| 	m_currentSlide = 0; | ||||
| 	m_slideTable->setRowCount(slideList.size()); | ||||
| 	m_slideTable->setRowCount(static_cast<int>(slideList.size())); | ||||
| 	for (int i = 0; i < slideList.size(); ++i) { | ||||
| 		auto txt = slideList[i]; | ||||
| 		auto const& txt = slideList[i]; | ||||
| 		auto item = new QTableWidgetItem(txt); | ||||
| 		item->setFlags(item->flags() & ~Qt::ItemIsEditable); | ||||
| 		m_slideTable->setItem(i, 0, item); | ||||
| @@ -86,7 +88,7 @@ void SlideView::reset() { | ||||
| 	m_currentSlide = -1; | ||||
| } | ||||
|  | ||||
| void SlideView::songListUpdate(QStringList songList) { | ||||
| void SlideView::songListUpdate(QStringList const&songList) { | ||||
| 	// Is this replacing an existing song list or is it the initial song list? | ||||
| 	// We want to reset the song to 0 upon replacement, | ||||
| 	// but leave it alone upon initialization. | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| /* | ||||
|  * Copyright 2021 gary@drinkingtea.net | ||||
|  * Copyright 2021 - 2024 gary@drinkingtea.net | ||||
|  * | ||||
|  * 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 | ||||
| @@ -24,11 +24,11 @@ class SlideView: public QWidget { | ||||
| 		QString getNextSong() const; | ||||
|  | ||||
| 	public slots: | ||||
| 		void pollUpdate(QString songId, int slideNum); | ||||
| 		void pollUpdate(const QString& songId, int slideNum); | ||||
|  | ||||
| 		void songListUpdate(QStringList songList); | ||||
| 		void songListUpdate(QStringList const&songList); | ||||
|  | ||||
| 		void slideListUpdate(QStringList tagList, QStringList songList); | ||||
| 		void slideListUpdate(QStringList const&tagList, QStringList const&songList); | ||||
|  | ||||
| 		void reset(); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user