qmidictl-1.0.1/PaxHeaders/CMakeLists.txt0000644000000000000000000000013214675771705015160 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/CMakeLists.txt0000644000175000001440000000746114675771705015160 0ustar00rncbcuserscmake_minimum_required (VERSION 3.15) project (QmidiCtl VERSION 1.0.1 DESCRIPTION "A MIDI Remote Controller via UDP/IP Multicast" HOMEPAGE_URL "https://qmidictl.sourceforge.io" LANGUAGES C CXX) set (PROJECT_TITLE "${PROJECT_NAME}") string (TOLOWER "${PROJECT_TITLE}" PROJECT_NAME) set (PROJECT_COPYRIGHT "Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved.") set (PROJECT_DOMAIN "rncbc.org") execute_process ( COMMAND git describe --tags --dirty --abbrev=6 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_DESCRIBE_OUTPUT RESULT_VARIABLE GIT_DESCRIBE_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) if (GIT_DESCRIBE_RESULT EQUAL 0) set (GIT_VERSION "${GIT_DESCRIBE_OUTPUT}") string (REGEX REPLACE "^[^0-9]+" "" GIT_VERSION "${GIT_VERSION}") string (REGEX REPLACE "-g" "git." GIT_VERSION "${GIT_VERSION}") string (REGEX REPLACE "[_|-]" "." GIT_VERSION "${GIT_VERSION}") execute_process ( COMMAND git rev-parse --abbrev-ref HEAD WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE GIT_REVPARSE_OUTPUT RESULT_VARIABLE GIT_REVPARSE_RESULT OUTPUT_STRIP_TRAILING_WHITESPACE) if (GIT_REVPARSE_RESULT EQUAL 0 AND NOT GIT_REVPARSE_OUTPUT STREQUAL "main") set (GIT_VERSION "${GIT_VERSION} [${GIT_REVPARSE_OUTPUT}]") endif () set (PROJECT_VERSION "${GIT_VERSION}") endif () if (CMAKE_BUILD_TYPE MATCHES "Debug") set (CONFIG_DEBUG 1) set (CONFIG_BUILD_TYPE "debug") else () set (CONFIG_DEBUG 0) set (CONFIG_BUILD_TYPE "release") set (CMAKE_BUILD_TYPE "Release") endif () set (CONFIG_PREFIX "${CMAKE_INSTALL_PREFIX}") include (GNUInstallDirs) set (CONFIG_BINDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_BINDIR}") set (CONFIG_LIBDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_LIBDIR}") set (CONFIG_DATADIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_DATADIR}") set (CONFIG_MANDIR "${CONFIG_PREFIX}/${CMAKE_INSTALL_MANDIR}") # Enable Network IPv6 support option. option (CONFIG_IPV6 "Enable Network IPv6 support (default=yes)" 1) # Enable Multi-touch support option. option (CONFIG_MULTI_TOUCH "Enable multi-touch support (default=yes)" 1) # Enable Wayland support option. option (CONFIG_WAYLAND "Enable Wayland support (EXPERIMENTAL) (default=yes)" 1) # Enable Qt6 build preference. option (CONFIG_QT6 "Enable Qt6 build (default=yes)" 1) # Fix for new CMAKE_REQUIRED_LIBRARIES policy. if (POLICY CMP0075) cmake_policy (SET CMP0075 NEW) endif () # Check for Qt... if (CONFIG_QT6) find_package (Qt6 QUIET) if (NOT Qt6_FOUND) set (CONFIG_QT6 0) endif () endif () if (CONFIG_QT6) find_package (QT QUIET NAMES Qt6) else () find_package (QT QUIET NAMES Qt5) endif () find_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Widgets Svg) if (CONFIG_IPV6) find_package (Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Network) endif () #find_package (Qt${QT_VERSION_MAJOR}LinguistTools) include (CheckIncludeFile) include (CheckIncludeFiles) include (CheckIncludeFileCXX) include (CheckFunctionExists) include (CheckLibraryExists) # Checks for header files. if (UNIX AND NOT APPLE) check_include_files ("fcntl.h;unistd.h;signal.h" HAVE_SIGNAL_H) endif () add_subdirectory (src) # Finally check whether Qt is statically linked. if (QT_FEATURE_static) set(QT_VERSION "${QT_VERSION}-static") endif () # Configuration status macro (SHOW_OPTION text value) if (${value}) message ("${text}: yes") else () message ("${text}: no") endif () endmacro () message ("\n ${PROJECT_TITLE} ${PROJECT_VERSION} (Qt ${QT_VERSION})") message ("\n Build target . . . . . . . . . . . . . . . . . . .: ${CONFIG_BUILD_TYPE}\n") show_option (" Network IPv6 support . . . . . . . . . . . . . . ." CONFIG_IPV6) show_option (" Multi-touch support . . . . . . . . . . . . . . ." CONFIG_MULTI_TOUCH) message ("\n Install prefix . . . . . . . . . . . . . . . . . .: ${CONFIG_PREFIX}\n") qmidictl-1.0.1/PaxHeaders/ChangeLog0000644000000000000000000000013214675771705014172 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/ChangeLog0000644000175000001440000001664114675771705014172 0ustar00rncbcusersQmidiCtl - A MIDI Remote Controller via UDP/IP Multicast -------------------------------------------------------- ChangeLog 1.0.1 2024-09-28 An Early-Fall'24 Release. - Target platform is now Android 14 (in compliance to Google Play Developer Program policies). - Multi-touch/finger support is now an option. 1.0.0 2024-06-19 An Unthinkable Release. - Making up the unthinkable (aka. v1.0.0) 0.9.91 2024-05-01 A Spring'24 Release Candidate 2. - Prepping the unthinkable (aka. v1.0.0-rc2) - Updated to latest framework level (Qt >= 6.7) 0.9.90 2024-04-10 A Spring'24 Release Candidate. - Prepping the unthinkable (aka. v1.0.0-rc1) 0.9.12 2024-01-24 A Winter'24 Release. - Updated copyright headers into the New Year (2024). 0.9.11 2023-09-09 An End-of-Summer'23 Release. - Preppings to next development cycle (Qt >= 6.6) 0.9.10 2023-06-01 A Spring'23 Release. - Prepping into the next development cycle (with Qt >= 6.5). 0.9.9 2023-03-23 An Early-Spring'23 Release. - Bumping copyright headers to the brand new year. 0.9.8 2022-12-28 And End-of-Year'22 Release. - Just bumping into the next develop cycle/season. 0.9.7 2022-10-03 An Early-Autumn'22 Release. - Bumped version number into the next release season. 0.9.6 2022-04-02 A Spring'22 Release. - Main application icon is now presented in scalable format (SVG). - Migrated command line parsing to QCommandLineParser/Option (Qt >= 5.2) 0.9.5 2022-01-09 A Winter'22 Release. - Dropped autotools (autoconf, automake, etc.) build system. 0.9.4 2021-07-04 An Early-Summer'21 Release. - All builds default to Qt6 (Qt >= 6.1) where available. - CMake is now the official build system. 0.9.3 2021-05-11 A Spring'21 Release. - All packaging builds switching to CMake. 0.9.2 2021-03-14 An End-of-Winter'21 Release. - Bumped version micro/dot number into the next develop cycle. 0.9.1 2021-02-07 A Winter'21 Release. - Early preparations for the New Year develop(ment) cycle. 0.9.0 2020-12-17 A Winter'20 Release. - Early fixing to build for Qt >= 6.0.0 and comply with C++17 standard. 0.6.3 2020-07-31 A Summer'20 Release. - Early fixing to build for Qt >= 5.15.0. 0.6.2 2020-03-24 A Spring'20 Release. - A scalable (.svg) icon version has been added. - Make man page compression reproducible (after request by Jelle van der Waa, while on the Vee-Ones, thanks). - Updated to build Android App Bundles (Qt >= 5.14.0). - Avoid some touch-events while tracking a swipe gesture. - Bumped copyright headers into the New Year (2020). 0.6.1 2019-12-22 The Winter'19 Release. - Provide a default IPv6 address, and feedback to the user in the options dialog (after a merge request by plcl aka. Pedro López-Cabanillas, while on qmidinet). - When using ./configure --with-qt=... it is also necessary to adjust the PKG_CONFIG_PATH environment variable (also by plcl aka. Pedro López-Cabanillas, while on qmidinet). 0.6.0 2019-10-17 An Autumn'19 Release. - Populate automatically the network interface combo-box with detected interface names (after merge request by plcl aka. Pedro López-Cabanillas, while on qmidinet). - Complete rewrite of all the basic network interface code, while using the Qt5 framework as far as needed to support IPv4 and IPv6 seamless and interchangeably. - Added alternate yet non-official CMake build option. - Fix HiDPI display screen effective support (Qt >= 5.6). - Added left/right swipe gestures to navigate over mixer strip pages. - Make sure compiler flags comply to c++11 as standard. 0.5.5 2019-07-12 A Summer'19 Release. - Multi-touch/finger support has been introduced. - Updated for the newer Qt5 development tools (>= 5.13). - Configure updated to check for qtchooser availability. - Minor update to Debian packaging control file. 0.5.4 2019-04-11 A Spring-Break'19 Release - Fix build on linux with musl (according to POSIX.1-2001, POSIX.1-2008; PR#3 by Andreas Müller aka. schnitzeltony, thanks). - HiDPI display screen support (Qt >= 5.6). 0.5.3 2019-01-08 An Early Winter'19 Release - Added proper display names to Command and Control Type entry fields on Configure dialog (ie. MIDI controller assignments dialog). - Old deprecated Qt4 build support is no more. - AppData/AppStream metadata is now settled under an all permisssive license (FSFAP); also updated to be the most compliant with latest freedesktop.org specification and recommendation. 0.5.2 2018-06-30 An Early Summer'18 release. - Fixed all mixer strips for a larger width on main layout. - Fixed for some g++ >= 8.1.1 warnings and quietness. - Yes, Android GA is now official. 0.5.1 2018-01-11 An Early Winter'18 release. - A little hardening on the configure (autoconf) macro side. - A rather naive attempt to port to the Android-Qt platform; also introducing an (Android) Action Bar menu look-a-like. 0.5.0 2017-12-16 End of Autum'17 release. - Desktop entry specification file is now finally independent from build/configure template chains. - Updated target path for freedesktop.org's AppStream metainfo file (formerly AppData). 0.4.3 2017-04-27 Pre-LAC2017 release frenzy. - Added French man page (by Olivier Humbert, thanks). - Make builds reproducible byte for byte, by getting rid of the configure build date and time stamps. 0.4.2 2016-11-14 A Fall'16 release. - Almost complete overhaul on the configure script command line options, wrt. installation directories specification, eg. --prefix, --bindir, --libdir, --datadir and --mandir. 0.4.1 2016-09-14 End of Summer'16 release. - Dropped the --enable-qt5 from configure as found redundant given that's the build default anyway (suggestion by Guido Scholz, while for Qtractor, thanks). 0.4.0 2016-04-05 Spring'16 release frenzy. - Added application keywords to freedesktop.org's AppData. 0.3.0 2015-09-21 Summer'15 release frenzy. - Prefer Qt5 over Qt4 by default with configure script. - Complete rewrite of Qt4 vs. Qt5 configure builds. - Fixed for some strict tests for Qt4 vs. Qt5 configure builds. 0.2.0 2015-03-23 Pre-LAC2015 pre-season release. - Reset (to network defaults) button added to options dialog, which also gets some layout reform. - Added application description as freedesktop.org's AppData. - Previously hard-coded UDP/IP multicast address (225.0.0.37) is now an user configurable option. - A man page has beed added. - Allow the build system to include an user specified LDFLAGS. 0.1.1 2013-12-31 A fifth of a Jubilee. - More preparations for Qt5 configure build. - Preparations for Qt5 migration. - Make(ing) -jN parallel builds now available for the masses. - Fixed Makefile.in handling of installation directories to the configure script eg. --datadir. - Symbian^3 port (by Pedro Lopez-Cabanillas). - Added translatable macro contextualizers. 0.1.0 2010-09-03 Second public release. - Standard desktop icon fixing (Fremantle/Hildon). - MIDI note and controller names (GM) added. - Logarithmic scale configure option added. - Message box title confidence fix (whatever it be:) - MIDI controller assignment, any other than mostly MMC, is now user configurable for all widgets, buttons, sliders and knobs, trough the novel "Configure..." menu item. - Dropped the "About Qt..." menu item. - Use standard dialog buttons as provided by QDialogButtonBox. - Attempt to comply with maemo.org extras-testing promotion. 0.0.1 2010-03-07 First public release. qmidictl-1.0.1/PaxHeaders/LICENSE0000644000000000000000000000013214675771705013425 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/LICENSE0000644000175000001440000004310314675771705013416 0ustar00rncbcusers GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. qmidictl-1.0.1/PaxHeaders/README0000644000000000000000000000013214675771705013300 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/README0000644000175000001440000000601614675771705013273 0ustar00rncbcusersQmidiCtl - A MIDI Remote Controller via UDP/IP Multicast -------------------------------------------------------- QmidiCtl is a MIDI remote controller application that sends MIDI data over the network, using UDP/IP multicast. Inspired by multimidicast [3] and designed to be compatible with ipMIDI for Windows [4]. QmidiCt [1] has been primarily designed for the Maemo [5] enabled handheld devices, namely the Nokia N900 [6] and also being promoted to the Maemo Package [7] repositories. Nevertheless, QmidiCtl [1] may still be found effective as a regular desktop and, ultimately, as an Android application as well [8]. Website: https://qmidictl.sourceforge.io https://qmidictl.sourceforge.net Project page: https://sourceforge.net/projects/qmidictl Weblog: https://www.rncbc.org QmidiNet is free, open-source software, distributed under the terms of the GNU General Public License (GPL) [2] version 2 or later. Requirements ------------ The software requirements for build and runtime are listed as follows: Mandatory: [1] Qt framework, C++ class library and tools for cross-platform application and UI development https://qt.io/ Installation ------------ Unpack the tarball and in the extracted source directory: cmake [-DCMAKE_INSTALL_PREFIX=] -B build cmake --build build [--parallel ] and optionally, as root: [sudo] cmake --install build Note that the default installation path () is /usr/local . Configuration ------------- QmidiCtl holds its settings and configuration state per user, in a file located as $HOME/.config/rncbc.org/QmidiCtl.conf . Normally, there's no need to edit this file, as it is recreated and rewritten everytime qmidictl is run. Bugs ---- Probably plenty still, QjackCtl maybe considered on beta stage already. Support ------- QmidiCtl is open source free software. For bug reports, feature requests, discussion forums, mailling lists, or any other matter related to the development of this piece of software, please use the Sourceforge project page (https://sourceforge.net/projects/qmidictl). You can also find timely and closer contact information on my personal web site (https://www.rncbc.org). References ---------- [1] Qt framework, C++ class library and tools for cross-platform application and UI development https://qt.io/ [2] GNU General Public License https://www.gnu.org/copyleft/gpl.html [3] multimidicast - sends and receives MIDI from ALSA sequencers over network https://llg.cubic.org/tools/multimidicast [4] ipMIDI - MIDI over Ethernet ports - send MIDI over your LAN https://nerds.de [5] Maemo.org - Home of the Maemo community http://www.maemo.org [6] Maemo.org Wiki - Nokia N900 http://wiki.maemo.org/Nokia_N900 [7] Maemo.org - Downloads: QmidiCtl http://maemo.org/downloads/product/Maemo5/qmidictl [8] Get it on Google Play Store https://play.google.com/store/apps/details?id=org.rncbc.qmidictl Enjoy. rncbc aka Rui Nuno Capela rncbc at rncbc dot org https://www.rncbc.org qmidictl-1.0.1/PaxHeaders/src0000644000000000000000000000013214675771705013132 xustar0030 mtime=1727525829.433779736 30 atime=1727525829.430779736 30 ctime=1727525829.433779736 qmidictl-1.0.1/src/0000755000175000001440000000000014675771705013177 5ustar00rncbcusersqmidictl-1.0.1/src/PaxHeaders/qmidictlOptionsForm.cpp0000644000000000000000000000013214675771705017721 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlOptionsForm.cpp0000644000175000001440000002140214675771705017710 0ustar00rncbcusers// qmidictlOptionsForm.cpp // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlAbout.h" #include "qmidictlOptionsForm.h" #include "qmidictlOptions.h" #include #if defined(CONFIG_IPV6) #include #endif #if defined(Q_OS_ANDROID) #include "qmidictlActionBar.h" #include #include #endif //---------------------------------------------------------------------------- // qmidictlOptionsForm -- UI wrapper form. // Constructor. qmidictlOptionsForm::qmidictlOptionsForm ( QWidget *pParent ) : QDialog(pParent) { // Setup UI struct... m_ui.setupUi(this); #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) QDialog::setWindowIcon(QIcon(":/images/qmidictl.png")); #endif #if defined(Q_OS_ANDROID) // Custom dialog font size... const QFont& font = QDialog::font(); QDialog::setFont(QFont(font.family(), font.pointSize() + 1)); // Special actions for the android stuff. m_pBackAction = new QAction(QIcon(":/images/actionBack.png"), tr("Back"), this); m_pResetAction = new QAction(QIcon(":/images/actionReset.png"), tr("Reset"), this); m_pAcceptAction = new QAction(QIcon(":/images/actionAccept.png"), tr("Done"), this); m_pCancelAction = new QAction(QIcon(":/images/actionCancel.png"), tr("Cancel"), this); QObject::connect(m_pBackAction, SIGNAL(triggered(bool)), this, SLOT(reject())); QObject::connect(m_pResetAction, SIGNAL(triggered(bool)), this, SLOT(reset())); QObject::connect(m_pAcceptAction, SIGNAL(triggered(bool)), this, SLOT(accept())); QObject::connect(m_pCancelAction, SIGNAL(triggered(bool)), this, SLOT(reject())); // Special action-bar for the android stuff. m_pActionBar = new qmidictlActionBar(); m_pActionBar->setIcon(QDialog::windowIcon()); m_pActionBar->setTitle(QDialog::windowTitle()); // Action-bar back-button... m_pActionBar->addMenuItem(m_pBackAction); // Action-bar right-overflow button items... m_pActionBar->addButton(m_pResetAction); m_pActionBar->addButton(m_pAcceptAction); m_pActionBar->addButton(m_pCancelAction); // Make it at the top... m_ui.MainCentralLayout->insertWidget(0, m_pActionBar); m_ui.DialogButtonBox->hide(); #endif // Initialize the dialog widgets with deafult settings... m_sDefInterface = tr("(Any)"); m_ui.InterfaceComboBox->clear(); m_ui.InterfaceComboBox->addItem(m_sDefInterface); #if defined(CONFIG_IPV6) foreach (const QNetworkInterface& iface, QNetworkInterface::allInterfaces()) { if (iface.isValid() && iface.flags().testFlag(QNetworkInterface::CanMulticast) && iface.flags().testFlag(QNetworkInterface::IsUp) && iface.flags().testFlag(QNetworkInterface::IsRunning) && !iface.flags().testFlag(QNetworkInterface::IsLoopBack)) { m_ui.InterfaceComboBox->addItem(iface.name()); } } #else m_ui.InterfaceComboBox->addItem("wlan0"); m_ui.InterfaceComboBox->addItem("eth0"); #endif #if defined(Q_OS_ANDROID) #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) // m_ui.InterfaceComboBox->setMinimumWidth(240); m_ui.InterfaceComboBox->setMinimumHeight(128); m_ui.InterfaceComboBox->lineEdit()->setMinimumHeight(96); #endif #endif m_ui.UdpAddrComboBox->clear(); m_ui.UdpAddrComboBox->addItem(QMIDICTL_UDP_IPV4_ADDR); #if defined(CONFIG_IPV6) m_ui.UdpAddrComboBox->addItem(QMIDICTL_UDP_IPV6_ADDR); #endif #if defined(Q_OS_ANDROID) #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) // m_ui.UdpAddrComboBox->setMinimumWidth(240); m_ui.UdpAddrComboBox->setMinimumHeight(128); m_ui.UdpAddrComboBox->lineEdit()->setMinimumHeight(96); #endif #endif m_ui.UdpPortSpinBox->setValue(QMIDICTL_UDP_PORT); // Populate dialog widgets with current settings... qmidictlOptions *pOptions = qmidictlOptions::getInstance(); if (pOptions) { if (pOptions->sInterface.isEmpty()) m_ui.InterfaceComboBox->setCurrentIndex(0); else m_ui.InterfaceComboBox->setEditText(pOptions->sInterface); if (pOptions->sUdpAddr.isEmpty()) m_ui.UdpAddrComboBox->setCurrentIndex(0); else m_ui.UdpAddrComboBox->setEditText(pOptions->sUdpAddr); m_ui.UdpPortSpinBox->setValue(pOptions->iUdpPort); m_ui.MmcDeviceSpinBox->setValue(pOptions->iMmcDevice); } #if defined(Q_OS_SYMBIAN) || defined(Q_OS_WINDOWS) m_ui.InterfaceComboBox->setEnabled(false); #endif // Start clean. m_iDirtyCount = 0; // Try to fix window geometry. #if defined(Q_OS_ANDROID) || defined(Q_OS_SYMBIAN) showMaximized(); #else adjustSize(); #endif // UI signal/slot connections... QObject::connect(m_ui.InterfaceComboBox, SIGNAL(editTextChanged(const QString&)), SLOT(change())); QObject::connect(m_ui.UdpAddrComboBox, SIGNAL(editTextChanged(const QString&)), SLOT(change())); QObject::connect(m_ui.UdpPortSpinBox, SIGNAL(valueChanged(int)), SLOT(change())); QObject::connect(m_ui.MmcDeviceSpinBox, SIGNAL(valueChanged(int)), SLOT(change())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(accepted()), SLOT(accept())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(rejected()), SLOT(reject())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(clicked(QAbstractButton *)), SLOT(buttonClick(QAbstractButton *))); } // Destructor. qmidictlOptionsForm::~qmidictlOptionsForm (void) { #if defined(Q_OS_ANDROID) // No need for special android stuff anymore. delete m_pCancelAction; delete m_pAcceptAction; delete m_pResetAction; delete m_pBackAction; delete m_pActionBar; #endif } // Change settings (anything else slot). void qmidictlOptionsForm::change (void) { m_iDirtyCount++; } // Accept settings (OK button slot). void qmidictlOptionsForm::accept (void) { // Save options... if (m_iDirtyCount > 0) { qmidictlOptions *pOptions = qmidictlOptions::getInstance(); if (pOptions) { // Display options... pOptions->sInterface = m_ui.InterfaceComboBox->currentText(); pOptions->sUdpAddr = m_ui.UdpAddrComboBox->currentText(); pOptions->iUdpPort = m_ui.UdpPortSpinBox->value(); pOptions->iMmcDevice = m_ui.MmcDeviceSpinBox->value(); // Take care of some translatable adjustments... if (pOptions->sInterface == m_sDefInterface) pOptions->sInterface.clear(); // Save/commit to disk. pOptions->saveOptions(); // Clean all dirt... m_iDirtyCount = 0; } } // Just go with dialog acceptance QDialog::accept(); } // Reject options (Cancel button slot). void qmidictlOptionsForm::reject (void) { // Check if there's any pending changes... if (m_iDirtyCount > 0) { switch (QMessageBox::warning(this, QDialog::windowTitle(), tr("Some settings have been changed.\n\n" "Do you want to apply the changes?"), QMessageBox::Apply | QMessageBox::Discard | QMessageBox::Cancel)) { case QMessageBox::Discard: break; case QMessageBox::Apply: accept(); default: return; } } // Just go with dialog rejection... QDialog::reject(); } // Reset settings (action button slot). void qmidictlOptionsForm::buttonClick ( QAbstractButton *pButton ) { #ifdef CONFIG_DEBUG qDebug("qmidictlOptionsForm::buttonClick(%p)", pButton); #endif QDialogButtonBox::ButtonRole role = m_ui.DialogButtonBox->buttonRole(pButton); if ((role & QDialogButtonBox::ResetRole) == QDialogButtonBox::ResetRole) reset(); } // Reset options (generic button slot). void qmidictlOptionsForm::reset (void) { #ifdef CONFIG_DEBUG qDebug("qmidictlOptionsForm::reset()"); #endif if (QMessageBox::warning(this, QDialog::windowTitle(), tr("All settings will be reset to the original default.\n\n" "Are you sure to apply the changes?"), QMessageBox::Reset | QMessageBox::Cancel) == QMessageBox::Cancel) return; m_ui.MmcDeviceSpinBox->setValue(QMIDICTL_MMC_DEVICE); m_ui.InterfaceComboBox->setCurrentIndex(0); #if defined(CONFIG_IPV6) const QString& sUdpAddr = m_ui.UdpAddrComboBox->currentText(); QHostAddress addr; if (addr.setAddress(sUdpAddr) && addr.protocol() == QAbstractSocket::IPv6Protocol) m_ui.UdpAddrComboBox->setEditText(QMIDICTL_UDP_IPV6_ADDR); else #endif m_ui.UdpAddrComboBox->setEditText(QMIDICTL_UDP_IPV4_ADDR); m_ui.UdpPortSpinBox->setValue(QMIDICTL_UDP_PORT); } // end of qmidictlOptionsForm.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictlUdpDevice.h0000644000000000000000000000013214675771705016757 xustar0030 mtime=1727525829.433779736 30 atime=1727525829.433779736 30 ctime=1727525829.433779736 qmidictl-1.0.1/src/qmidictlUdpDevice.h0000644000175000001440000000564514675771705016761 0ustar00rncbcusers// qmidictlUdpDevice.h // /**************************************************************************** Copyright (C) 2010-2020, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlUdpDevice_h #define __qmidictlUdpDevice_h #include "qmidictlAbout.h" #include #if !defined(CONFIG_IPV6) #if defined(__SYMBIAN32__) #include #include #else #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) #include #else #include #include #endif #endif #endif // !CONFIG_IPV6 #include #include #if defined(CONFIG_IPV6) #include #include #endif //---------------------------------------------------------------------------- // qmidictlUdpDevice -- Network interface device (UDP/IP). class qmidictlUdpDevice : public QObject { Q_OBJECT public: // Constructor. qmidictlUdpDevice(QObject *pParent = nullptr); // Destructor. ~qmidictlUdpDevice(); // Kind of singleton reference. static qmidictlUdpDevice *getInstance(); // Device initialization method. bool open(const QString& sInterface, const QString& sUdpAddr, int iUdpPort); // Device termination method. void close(); // Data transmission methods. bool sendData(unsigned char *data, unsigned short len) const; void recvData(unsigned char *data, unsigned short len); signals: // Received data signal. void received(QByteArray data); #if defined(CONFIG_IPV6) protected slots: // Process incoming datagrams. void readPendingDatagrams(); #else protected: // Get interface address from supplied name. static bool get_address(int sock, struct in_addr *iaddr, const char *ifname); #endif // !CONFIG_IPV6 private: // Instance variables, #if defined(CONFIG_IPV6) QUdpSocket *m_sockin; QUdpSocket *m_sockout; QHostAddress m_udpaddr; int m_udpport; #else int m_sockin; int m_sockout; struct sockaddr_in m_addrout; // Network receiver thread. class qmidictlUdpDeviceThread *m_pRecvThread; #endif // !CONFIG_IPV6 // Kind-of singleton reference. static qmidictlUdpDevice *g_pDevice; }; #endif // __qmidictlUdpDevice_h // end of qmidictlUdpDevice.h qmidictl-1.0.1/src/PaxHeaders/qmidictlMidiControl.h0000644000000000000000000000013214675771705017332 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMidiControl.h0000644000175000001440000001322414675771705017324 0ustar00rncbcusers// qmidictlMidiControl.h // /**************************************************************************** Copyright (C) 2010-2019, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlMidiControl_h #define __qmidictlMidiControl_h #include // Forward declarations. class QSettings; //---------------------------------------------------------------------- // qmidictlMidiControl -- MIDI control map (singleton). // class qmidictlMidiControl { public: // Controller types. enum ControlType { MMC = 1, NOTE_ON = 2, NOTE_OFF = 3, KEY_PRESS = 4, CONTROLLER = 5, PGM_CHANGE = 6, CHAN_PRESS = 7, PITCH_BEND = 8 }; // Command types. enum Command { RST = 1, REW = 2, STOP = 3, PLAY = 4, REC = 5, FFWD = 6, TRACK_SOLO = 7, TRACK_MUTE = 8, TRACK_REC = 9, TRACK_VOL = 10, JOG_WHEEL = 11 }; // Key param masks (wildcard flags). enum { TrackParam = 0x4000, TrackParamMask = 0x3fff }; // MIDI control map key. class MapKey { public: // Constructor. MapKey(ControlType ctype = MMC, unsigned short iChannel = 0, unsigned short iParam = 0, bool bLogarithmic = false) : m_ctype(ctype), m_iChannel(iChannel), m_iParam(iParam), m_bLogarithmic(bLogarithmic) {} // Type accessors. void setType(ControlType ctype) { m_ctype = ctype; } ControlType type() const { return m_ctype; } // Channel accessors. void setChannel(unsigned short iChannel) { m_iChannel = iChannel; } unsigned short channel() const { return m_iChannel; } bool isChannel() const { return ((m_iChannel & TrackParamMask) == m_iChannel); } bool isChannelTrack() const { return (m_iChannel & TrackParam); } // Parameter accessors. void setParam (unsigned short iParam) { m_iParam = iParam; } unsigned short param() const { return m_iParam; } bool isParam() const { return ((m_iParam & TrackParamMask) == m_iParam); } bool isParamTrack() const { return (m_iParam & TrackParam); } // Logarithmic payload accessors. void setLogarithmic (bool bLogarithmic) { m_bLogarithmic = bLogarithmic; } bool isLogarithmic() const { return m_bLogarithmic; } // Generic key matcher. bool match (ControlType ctype, unsigned short iChannel, unsigned short iParam) const { return (type() == ctype && (isChannelTrack() || channel() == iChannel) && (isParamTrack() || param() == iParam)); } // Hash key comparator. bool operator== ( const MapKey& key ) const { return (key.m_ctype == m_ctype) && (key.m_iChannel == m_iChannel) && (key.m_iParam == m_iParam); } private: // Instance (key) member variables. ControlType m_ctype; unsigned short m_iChannel; unsigned short m_iParam; bool m_bLogarithmic; }; // MIDI command/control map type. typedef QHash CommandMap; typedef QHash ControlMap; // Constructor. qmidictlMidiControl(); // Destructor. ~qmidictlMidiControl(); static qmidictlMidiControl *getInstance(); // Clear control/command map (reset to default). void clear(); // Map accessors. const CommandMap& commandMap() const; const ControlMap& controlMap() const; // Insert new controller mappings. void mapCommand(Command command, ControlType ctype, unsigned short iChannel, unsigned short iParam, bool bLogarithmic = false); // Remove existing controller mapping. void unmapCommand(Command command); // Check if given command is currently mapped. bool isCommandMapped(Command command) const; // Check if given channel, controller pair is currently mapped. bool isChannelParamMapped(ControlType ctype, unsigned short iChannel, unsigned short iParam) const; // Lookup the command mapping... ControlMap::ConstIterator find(ControlType ctype, unsigned short iChannel, unsigned short iParam) const; // Load from/save into global settings. void load(QSettings& settings); void save(QSettings& settings); // Textual helpers. static ControlType typeFromText(const QString& sText); static QString textFromType(ControlType ctype); static unsigned short keyFromText(const QString& sText); static QString textFromKey(unsigned short iKey); static const QString& controllerName(unsigned short iParam); static const QString noteName(unsigned short iParam, bool fDrums = false); protected: static Command commandFromText(const QString& sText); static QString textFromCommand(Command command); private: // Command/control map. CommandMap m_commandMap; ControlMap m_controlMap; // Pseudo-singleton instance. static qmidictlMidiControl *g_pMidiControl; }; // Hash key functions. inline uint qHash ( const qmidictlMidiControl::Command& command ) { return qHash(uint(command)); } inline uint qHash ( const qmidictlMidiControl::MapKey& key ) { return qHash(uint(key.type()) ^ key.channel() ^ key.param()); } #endif // __qmidictlMidiControl_h // end of qmidictlMidiControl.h qmidictl-1.0.1/src/PaxHeaders/qmidictlUdpDevice.cpp0000644000000000000000000000013214675771705017312 xustar0030 mtime=1727525829.433779736 30 atime=1727525829.433779736 30 ctime=1727525829.433779736 qmidictl-1.0.1/src/qmidictlUdpDevice.cpp0000644000175000001440000003052414675771705017306 0ustar00rncbcusers// qmidictlUdpDevice.cpp // /**************************************************************************** Copyright (C) 2010-2020, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlUdpDevice.h" #if defined(CONFIG_IPV6) #include #include #include #else #include #include #if defined(Q_OS_SYMBIAN) #include #include #include inline void closesocket(int s) { ::close(s); } #else #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) static WSADATA g_wsaData; typedef int socklen_t; #else #include #include #include inline void closesocket(int s) { ::close(s); } #endif #endif #include #include //---------------------------------------------------------------------------- // qmidictlUdpDeviceThread -- Network listener thread. // class qmidictlUdpDeviceThread : public QThread { public: // Constructor. qmidictlUdpDeviceThread(int sockin); // Run-state accessors. void setRunState(bool bRunState); bool runState() const; protected: // The main thread executive. void run(); private: // The listener socket. int m_sockin; // Whether the thread is logically running. volatile bool m_bRunState; }; // Constructor. qmidictlUdpDeviceThread::qmidictlUdpDeviceThread ( int sockin ) : QThread(), m_sockin(sockin), m_bRunState(false) { } // Run-state accessors. void qmidictlUdpDeviceThread::setRunState ( bool bRunState ) { m_bRunState = bRunState; } bool qmidictlUdpDeviceThread::runState (void) const { return m_bRunState; } // The main thread executive. void qmidictlUdpDeviceThread::run (void) { m_bRunState = true; while (m_bRunState) { // Wait for an network event... fd_set fds; FD_ZERO(&fds); FD_SET(m_sockin, &fds); int fdmax = m_sockin; // Set timeout period (1 second)... struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; int s = ::select(fdmax + 1, &fds, nullptr, nullptr, &tv); if (s < 0) { ::perror("select"); break; } if (s == 0) { // Timeout! continue; } // A Network event if (FD_ISSET(m_sockin, &fds)) { // Read from network... unsigned char buf[1024]; struct sockaddr_in sender; socklen_t slen = sizeof(sender); int r = ::recvfrom(m_sockin, (char *) buf, sizeof(buf), 0, (struct sockaddr *) &sender, &slen); if (r > 0) qmidictlUdpDevice::getInstance()->recvData(buf, r); else if (r < 0) ::perror("recvfrom"); } } } #endif // !CONFIG_IPV6 //---------------------------------------------------------------------------- // qmidictlUdpDevice -- Network interface device (UDP/IP). // qmidictlUdpDevice *qmidictlUdpDevice::g_pDevice = nullptr; // Constructor. qmidictlUdpDevice::qmidictlUdpDevice ( QObject *pParent ) : QObject(pParent), #if defined(CONFIG_IPV6) m_sockin(nullptr), m_sockout(nullptr), m_udpport(-1) #else m_sockin(-1), m_sockout(-1), m_pRecvThread(nullptr) #endif // !CONFIG_IPV6 { #if !defined(CONFIG_IPV6) #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) WSAStartup(MAKEWORD(1, 1), &g_wsaData); #endif #endif // !CONFIG_IPV6 g_pDevice = this; } // Destructor. qmidictlUdpDevice::~qmidictlUdpDevice (void) { close(); g_pDevice = nullptr; #if !defined(CONFIG_IPV6) #if defined(__WIN32__) || defined(_WIN32) || defined(WIN32) WSACleanup(); #endif #endif // !CONFIG_IPV6 } // Kind of singleton reference. qmidictlUdpDevice *qmidictlUdpDevice::getInstance (void) { return g_pDevice; } // Device initialization method. bool qmidictlUdpDevice::open ( const QString& sInterface, const QString& sUdpAddr, int iUdpPort ) { // Close if already open. close(); #if defined(CONFIG_IPV6) // Setup host address for udp multicast... m_udpaddr.setAddress(sUdpAddr); // Check whether is real for udp multicast... #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) if (!m_udpaddr.isMulticast()) { qWarning() << "open(udpaddr):" << sUdpAddr << "not an udp multicast address"; return false; } #endif // Check whether protocol is IPv4 or IPv6... const bool ipv6_protocol = (m_udpaddr.protocol() != QAbstractSocket::IPv4Protocol); // Setup network interface... QNetworkInterface iface; if (!sInterface.isEmpty()) iface = QNetworkInterface::interfaceFromName(sInterface); // Allocate sockets and addresses... m_sockin = new QUdpSocket(); m_sockout = new QUdpSocket(); m_udpport = iUdpPort; // Bind input socket... if (!m_sockin->bind(ipv6_protocol ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4, iUdpPort, QUdpSocket::ShareAddress)) { qWarning() << "open(sockin):" << "udp socket error" << m_sockin->error() << m_sockin->errorString(); return false; } #if defined(Q_OS_WIN) m_sockin->setSocketOption( QAbstractSocket::MulticastLoopbackOption, 0); #endif bool joined = false; if (iface.isValid()) joined = m_sockin->joinMulticastGroup(m_udpaddr, iface); else joined = m_sockin->joinMulticastGroup(m_udpaddr); if (!joined) { qWarning() << "open(sockin):" << "udp socket error" << m_sockin->error() << m_sockin->errorString(); } QObject::connect(m_sockin, SIGNAL(readyRead()), SLOT(readPendingDatagrams())); // Bind output socket... if (!m_sockout->bind(ipv6_protocol ? QHostAddress::AnyIPv6 : QHostAddress::AnyIPv4, m_sockout->localPort())) { qWarning() << "open(sockout):" << "udp socket error" << m_sockout->error() << m_sockout->errorString(); return false; } m_sockout->setSocketOption( QAbstractSocket::MulticastTtlOption, 1); #if defined(Q_OS_UNIX) m_sockout->setSocketOption( QAbstractSocket::MulticastLoopbackOption, 0); #endif if (iface.isValid()) m_sockout->setMulticastInterface(iface); #else // Setup network protocol... int protonum = 0; #if 0 struct protoent *proto = ::getprotobyname("IP"); if (proto) protonum = proto->p_proto; #endif // Stable interface name... const char *ifname = nullptr; const QByteArray aInterface = sInterface.toLocal8Bit(); if (!aInterface.isEmpty()) ifname = aInterface.constData(); const char *udp_addr = nullptr; const QByteArray aUdpAddr = sUdpAddr.toLocal8Bit(); if (!aUdpAddr.isEmpty()) udp_addr = aUdpAddr.constData(); // Input socket stuff... // m_sockin = ::socket(PF_INET, SOCK_DGRAM, protonum); if (m_sockin < 0) { ::perror("socket(in)"); return false; } struct sockaddr_in addrin; ::memset(&addrin, 0, sizeof(addrin)); addrin.sin_family = AF_INET; addrin.sin_addr.s_addr = htonl(INADDR_ANY); addrin.sin_port = htons(iUdpPort); if (::bind(m_sockin, (struct sockaddr *) (&addrin), sizeof(addrin)) < 0) { ::perror("bind"); return false; } // Will Hall, 2007 // INADDR_ANY will bind to default interface, // specify alternate interface nameon which to bind... struct in_addr if_addr_in; if (ifname) { if (!get_address(m_sockin, &if_addr_in, ifname)) { fprintf(stderr, "socket(in): could not find interface address for %s\n", ifname); return false; } if (::setsockopt(m_sockin, IPPROTO_IP, IP_MULTICAST_IF, (char *) &if_addr_in, sizeof(if_addr_in))) { ::perror("setsockopt(IP_MULTICAST_IF)"); return false; } } else { if_addr_in.s_addr = htonl(INADDR_ANY); } struct ip_mreq mreq; mreq.imr_multiaddr.s_addr = ::inet_addr(udp_addr); mreq.imr_interface.s_addr = if_addr_in.s_addr; if(::setsockopt (m_sockin, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char *) &mreq, sizeof(mreq)) < 0) { ::perror("setsockopt(IP_ADD_MEMBERSHIP)"); fprintf(stderr, "socket(in): your kernel is probably missing multicast support.\n"); return false; } // Output socket... // m_sockout = ::socket(AF_INET, SOCK_DGRAM, protonum); if (m_sockout < 0) { ::perror("socket(out)"); return false; } // Will Hall, Oct 2007 if (!sInterface.isEmpty()) { const QByteArray aInterface = sInterface.toLocal8Bit(); const char *ifname = aInterface.constData(); struct in_addr if_addr_out; if (!get_address(m_sockout, &if_addr_out, ifname)) { ::fprintf(stderr, "socket(out): could not find interface address for %s\n", ifname); return false; } if (::setsockopt(m_sockout, IPPROTO_IP, IP_MULTICAST_IF, (char *) &if_addr_out, sizeof(if_addr_out))) { ::perror("setsockopt(IP_MULTICAST_IF)"); return false; } } ::memset(&m_addrout, 0, sizeof(m_addrout)); m_addrout.sin_family = AF_INET; m_addrout.sin_addr.s_addr = ::inet_addr(udp_addr); m_addrout.sin_port = htons(iUdpPort); // Turn off loopback... int loop = 0; if (::setsockopt(m_sockout, IPPROTO_IP, IP_MULTICAST_LOOP, (char *) &loop, sizeof (loop)) < 0) { ::perror("setsockopt(IP_MULTICAST_LOOP)"); return false; } // Start listener thread... m_pRecvThread = new qmidictlUdpDeviceThread(m_sockin); m_pRecvThread->start(); #endif // !CONFIG_IPV6 // Done. return true; } // Device termination method. void qmidictlUdpDevice::close (void) { #if defined(CONFIG_IPV6) if (m_sockin) { delete m_sockin; m_sockin = nullptr; } if (m_sockout) { delete m_sockout; m_sockout = nullptr; } m_udpaddr.clear(); m_udpport = -1; #else if (m_sockin >= 0) ::closesocket(m_sockin); if (m_sockout >= 0) ::closesocket(m_sockout); if (m_pRecvThread) { if (m_pRecvThread->isRunning()) { m_pRecvThread->setRunState(false); // m_pRecvThread->terminate(); m_pRecvThread->wait(1200); // Timeout>1sec. } delete m_pRecvThread; m_pRecvThread = nullptr; } #endif // !CONFIG_IPV6 } // Data transmission methods. bool qmidictlUdpDevice::sendData ( unsigned char *data, unsigned short len ) const { #if defined(CONFIG_IPV6) if (m_sockout == nullptr) return false; if (!m_sockout->isValid() || m_sockout->state() != QAbstractSocket::BoundState) { qWarning() << "sendData(sockout):" << "udp socket has invalid state" << m_sockout->state(); return false; } QByteArray datagram((const char *) data, len); if (m_sockout->writeDatagram(datagram, m_udpaddr, m_udpport) < len) { qWarning() << "sendData(sockout):" << "udp socket error" << m_sockout->error() << " " << m_sockout->errorString(); return false; } #else if (m_sockout < 0) return false; if (::sendto(m_sockout, (char *) data, len, 0, (struct sockaddr*) &m_addrout, sizeof(m_addrout)) < 0) { ::perror("sendto"); return false; } #endif // !CONFIG_IPV6 return true; } #if defined(CONFIG_IPV6) // Process incoming datagrams. void qmidictlUdpDevice::readPendingDatagrams (void) { if (m_sockin == nullptr) return; while (m_sockin->hasPendingDatagrams()) { QByteArray datagram; int nread = m_sockin->pendingDatagramSize(); datagram.resize(nread); nread = m_sockin->readDatagram(datagram.data(), datagram.size()); if (nread > 0) { datagram.resize(nread); emit received(datagram); } } } #else void qmidictlUdpDevice::recvData ( unsigned char *data, unsigned short len ) { emit received(QByteArray((const char *) data, len)); } // Get interface address from supplied name. bool qmidictlUdpDevice::get_address ( int sock, struct in_addr *inaddr, const char *ifname ) { #if !defined(__WIN32__) && !defined(_WIN32) && !defined(WIN32) && !defined(Q_OS_SYMBIAN) struct ifreq ifr; ::strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name) - 1); if (::ioctl(sock, SIOCGIFFLAGS, (char *) &ifr)) { ::perror("ioctl(SIOCGIFFLAGS)"); return false; } if ((ifr.ifr_flags & IFF_UP) == 0) { fprintf(stderr, "interface %s is down\n", ifname); return false; } if (::ioctl(sock, SIOCGIFADDR, (char *) &ifr)) { ::perror("ioctl(SIOCGIFADDR)"); return false; } struct sockaddr_in sa; ::memcpy(&sa, &ifr.ifr_addr, sizeof(struct sockaddr_in)); inaddr->s_addr = sa.sin_addr.s_addr; return true; #else return false; #endif // !WIN32 && !SYMBIAN } #endif // !CONFIG_IPV6 // end of qmidictlUdpDevice.h qmidictl-1.0.1/src/PaxHeaders/images0000644000000000000000000000013214675771705014377 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/0000755000175000001440000000000014675771705014444 5ustar00rncbcusersqmidictl-1.0.1/src/images/PaxHeaders/actionBack.png0000644000000000000000000000013214675771705017220 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionBack.png0000644000175000001440000000554114675771705017215 0ustar00rncbcusersPNG  IHDR``w8 (IDATx]ML~4}mɆ͑%ŽIJB Hl")JlDJd,'c<kx9d2c3!UÛ꿙%z{ޫ^W-la [>Ǝx1$ cĐ`C3.t oL̃ 9"X #' 8LiII? 2 f@ Vp ' :7ȑ~ց VH0 >N9 ]~Z! Qn(YȐ#m< X Iz=,O@tW'ShFz@ͻ}}DYͪ Jގ={;Tj3~@TpWC +F2677Z[[EکS>w"D M=z"hYmP1T**z4h "Y4zSl@`c_Fummm}@1p4zՏ,o`fר ~S2?;7"c 濠ƚd;::$TH3<˞ =ώ łHf~N[O<յkJHBP y:?L~B/xv1ѣ_ߺu+:I ,W_~oW}@.$@ҏ+++OW}# ~csDL?xv,{*])B@A X, up~g('{``J:՘*D,U?oN$nGDL///SU˥^>Y?X֬L,//~pp0Q;iY?H:'MS fh{!T^We|^hJ$1!2lUf^ZZC<O Uc{142vOfy\|v.@ qhhh]U:6L^YIUy^PU[;s:|䜧ݻgq)7>o%V] j1BBn/ſl.uuvv4FGG8*U|ymZLj36>ި…>BR=f33333ccc;?[XժFs/̈*l#CG|vT DT=9LOO@= 5>X1u:𥚤yXmzz_:Փl6355`bb9DM%P;vG_šUJm*Ht?hgb;wLNN~M@:=L\˼)3!;"dUe޿"(ƊՁgJNȄfx< ܹ|OU)?SWSCRv'-iPdWP唤>p؈ISƒ]Fn RIaqb|uUyEhtz}{{I)70 zZeN-% fO$ɸ(sXX{rr+8|=쵰SJ;~*XƀD"1 RNg'&&vH*S :+maY`ues !ƃt8pǷ ?Ά 2g>X S8FIF?6 ) QG GgH1YDUB.٬ X%dr/΄*# vlĔnRkqq/###kpX@BSw"I? "HL[9+ y!,q_$5$`$MYU z CY\>y<O@~1nglz+ T4$TOfq*lXɮX$syHp ķ|:c2qZQD5X͛A@p nr=Z9Y..$Hoo/%AAIr厔D *IBT3p_R񒼩(XA@!{sss $@W9&a!abnn# ڃr䊨 ?IwIc5~aaF Q&.0"7nܘ{n vMdG?IxkAj$ ׯ_R RIHE[&pa`2"W^HE@)$fYJMF燫H\!!mf(ԩ&Xŋ߄kç! HrgcvS{JK%ڄ /_bgg TE9J---?spbH7" j,L0wwwZZZ.8q'O :bU/p8֕݁~nO@Iݾj@zD, %>jdP+]Z膖`UEn5~Pp2^^eXTuZH= :<نWB_J*%[-la [?Ax̜IENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionAccept.png0000644000000000000000000000013214675771705017557 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionAccept.png0000644000175000001440000000472614675771705017560 0ustar00rncbcusersPNG  IHDR``w8 IDATx]hT>g2_M5$ z/I|AAA7mtb$X*BK р/Ay"ۤ8$gfv<;Y|L^LkЦM6mڴiӦM6m֒1hf\ P>kVKp-gs-@}k\qܳZoU)pk A@ΏZ9|&o!9T"4|Z/I>cr ?5/| ޴Y5 a $1 L 9OV_1x=h]"gώ&"F3IǏsGM_>s0o.ح!c*D54|):Pѣ5779g`ׯ_jV`hRA$@T055-[|W39D"d:5 ?@Çm]qH$>55 //^xq{CNvB5 ~͛===KsT*Ov?ZBUJm66m]sD~Pv]ѯt(R#>O&NBZ@D7^744 ]>܏r8PBNU?MZu|re%_@|aE { ? ,RqcI'-F*/և=wsAn ҡ9)uI~:B|45I:ZY+ "}QĤWTU-)D1tYʐN.v2TYb4 g,s W)ܬ(B ]R೶ՂeߗO@ʕ+sssH&۬Jseh9$V𳥄c@ҥKMG`Fc8H$lj4]TDQrW h_lwcǎ]0Ms x-,A}|^,|/\zȑ#Ci6 +0 J߸qzZ"%9㫦inL9)xM&QØX Y᝝;9_du}833s)>!( e^G+֛39111hF"@"D]SĪp/ˈM.`\LOOݻwD"QDIk>QBX8p;w9 (Uo-'\Q crv@hY-D:ƐbM"+J"Yk^RGΗ.'nxӂ֭[aFвɓ3hu^2VK%_vѵk~}񗨖/Hf4rng#ͰyB8'f\Ji8,͑M! A$eDrH,PHvANug܁YuDopz*S ~%p뜩=[`+J d\!T@u.w7SWZBx\WSb[5WSB_UEB]6mڴiӦM6mڴiӦ"^ k IENDB`qmidictl-1.0.1/src/images/PaxHeaders/formForward.png0000644000000000000000000000013214675771705017452 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/formForward.png0000644000175000001440000000031614675771705017442 0ustar00rncbcusersPNG  IHDRasRGBIDAT8˭ 0C_o%$34t| mUԟmYaUj@v҈- 8`q ^ :q Hwo~HwӀ2<1+;y<1~&R7~*1sV[IENDB`qmidictl-1.0.1/src/images/PaxHeaders/qmidictl.svg0000644000000000000000000000013214675771705017003 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/qmidictl.svg0000644000175000001440000020103514675771705016774 0ustar00rncbcusers qmidictl-1.0.1/src/images/PaxHeaders/formRecord.png0000644000000000000000000000013214675771705017264 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/formRecord.png0000644000175000001440000000046014675771705017254 0ustar00rncbcusersPNG  IHDRasRGBIDAT8ҽMP)`L (i2= ,%H@8"Lgxc2W{gvr9W9U}x,&ĨbgټHV-f--q13E6`?ƀ?î ZUE֛~汃 P%j@0op" yrӳp ^jN[@w5(3:gp䀓EI"FJOVIENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionAbout.png0000644000000000000000000000013214675771705017432 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionAbout.png0000644000175000001440000001101114675771705017414 0ustar00rncbcusersPNG  IHDR``w8IDATx]}LG==̇ܝNl9?X,M N'vouhv$$ʒn,78 #b|2q3_]OϨMU Z*TիWU_~EmW:N*MhO'$RG$x|tc6#"x#[@@ݐ*DV|'qlI?pC&C:YI|yy8$H6ox ب)( )4޽{ݻw"Hi%%%>OUUu/#(NIx<;uOBb ~!zVξiړR[B<_~ʕkrP(~|(!H$r귈%ZJpqN R=TNJդ/x饗IU-x$/СEbG@Z̙354Mk J]׿~w 3,1TS{\ |ĉ=zɓ|Ji,';::.={vH/L8Ϟ={W_WTUHqEyiGIIÞͅ`e|rT(]vuByW{왮نBxyS;I.Fz?;wœp-'_?~|1 0uSY`x&"FjJX z{{˻,!uii/Q%*<)]o5;;a3:8Kq'1N29bȼL}p evvE3J;ׁr/Zl@_744[;a|k >̋qTa&{y'2o"@ IKK|@޽{4\ ')W}>>d[[۟$>\D{ob7?|駿X- =gϞD3M$LUt< $vY@ '''UEÓ􅶶qd* ۷7EEE;EQh}~ 0Jm|zzE&''e== uq+y%UWWľz]קLS3)qRgSSsb[`~0)g]eᜮS.}}}e$=klH|F$?R=$qQFR'=3'`S5!!mCCCId >Y ^TTT>_b$ӧOt0!XRK\zdZ}{pDmӧRJl=,p\!aܹ-lڴ鷦ǖŋv۶m* @<O?w8X(Sۯ͛7_,++{%rii_"_q#|w㥯o?‹K.Z[E/`Y$M\s@!fdK. /b%ܯy+ +?& Z<_F"AH&/|gmw}PG9??6t^Ψw߭ٹsA0|Cĵk=capԴ_;4?}x"𢂜6a$TUH4n+kkk7i9(!r6X(Rrrrr) +9A/UAĥ$ZHMھ"wi0 I&0?Q@y./5d.dU60 ;Xlarj#(0 ѭi-f,3 J o~U˺+FRz_dWhq[ {r ar} 3od3.]@ R"9na/Œ,2*H{'A0"'@n,kV-0xK r^jKfOІ,ˊpf!@"erɜ63^G"lX V"6θ 8@l U%U8X%KHȦUUUuk(r7@P\\\駹^V(J@$ˌtw݌ϧ:gH6br}_!eX;|I_[*$fff`5iZsoo>SEMJ 6%A(%q(iX\qVNNƑd2#G~dOg&M)UCsI-e}"rȑc'L.666a  L/Q@4M188XHu:e9}/.>/i#]4Mք ×!Џɣk65 ^0 #F0b|`0# Pdž]`ooo?a`0x]ཬc===333\扉;RuE'f( `t~F&&&XcΗȆ=06G ǟ !.B" ޽L { 촎JT \d޽|B; n#!1G+Rj$ر)|2::19)Hd[,'H$r9mvܰ#a5T a ymJ@JKKi$I*]IԖ'ssppplX$gXBm%?z_͜ed+^kqTAf;Qa,Ǐ;-d2쨂鰎G,p9akBu`M,a9Ɖ󁁁kͲ,;|+s12WȲ|;XϜ9Sn3./6g6G}嗟 XeKyv\2*ZCt]6H̡}iZ 'Zt}vhJh;Ӳhz7'KJJ.Ց@ ̴_ "R/޾}{ԩSs]-Qv$u]RUY]o}l>vsy| ]_ /x_6*/wCB!!x& aoaIxbs`+7YeHp"c-oc崪/nc"VFnN7q[W oFv6Op3R<#%?B HJw3!rQb,jsHYeLTM$O7(ռX%oNwIENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionReset.png0000644000000000000000000000013214675771705017442 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionReset.png0000644000175000001440000001004714675771705017434 0ustar00rncbcusersPNG  IHDR``w8IDATx]{l[νM;iҦըMmRUEMicӔ0ӤODFӤH*!M+ D+Uj^RҐYGy=ܗMґk|sP?G~ԏQ?Gl~/0 X}?& uM#`p^'^s tR# 3ǥaJWNHhG6% a_C%F=!ʒο.؁%]G:[Cs^$ A:ZSF hF@5"9qmk~Y4Aijρ)]Ps|4LBXWY[[C3Y뵜uMem39X4B| x JЊd29#ziBLA'4H'4Fq7<#G}ݿu=9E!4AGӄOpb!\wU ,exÒ1>>ŋXIlMfB>_>D 3,پ}oZQp 0dT Gz|]6|<0dn"9vƍ71fpΙ"kF^\+gv7E |M~#16}gq"iQ?Gv$P.yRToBLa>?zp8.k"/8܆df.^A"8ڌ8ER1 ̣~ؑ#GB\͠Iiɩ<1888c;bG|$0Gv|||F 5dUi2"-@3Ƹiٹ7x㕁iGD`2;cʶ4Y۱cG?o3,!ݻwڵ뒥 s i䚺vKljZUН1411h{{򍊛D"OI6U*S^o~wÆ * Ƙ9==_ S4/iTF$&"o?3D WLč vTswNrbbѶJ MMMAJZmW*"@LY9kg 4C6@m94f˗urr򬈂.-C^122"wc!yx5G(LMM_ܴa}R%,ܶh2hi|Xl0;;Qgg,-FLƮ!}Aʔ XHB$&>}zORIpnBUo*0C~i=މzEʕ+t!({9U7VEKgDe Ӧ-7Qz N퉮PK?I|lq=@g8p`m<_4I%[^cP@7 ߹ kټyO5U dl]ATĨiϥ:@ulIW^>[U<7XU$7cL&3ֶׄEWܮ$9,XfPSL:a~g޷jժ iLjDo?waPwH^\QV S P̀8HMTڱcG֭[-_[7kf8!S43N Y;"5FI$T"qر;8笣cqsssG,4 Ԧ*D4t Z-ye>;}{Gh,,ɬP}4n}U $p!nm( !D5;[Hn%H2;E===Cy[G*`#=H275.L~gb<\H4MS'lFjebU@$EVx|?\oMT#ub!dw r+*WTk &c SN=HכR " 0A^I#ObW!HG,84\Q)[`I,cU~mȹXZ#<qMD\ԃ *ksqPf{_|ţvbŏvm=".<d]xI:Ov\𹁁KPڪ ;J $~zEenɭ7S\nھ؇.\|```ʷ F޼ 惂,hgΜ9tsÅB!d.i""E2::zy```7lFVE.^iE#8341m,y&K/Mcli~sCA./ :=@mWPSu]{ٹ7p(wTD,59we'M6ph "yiNFd@Ms^ZK*B'ds)2H`Aʌ2{d !͵>k˱ۓAu̒s>S&({WLRQ.JsA[/WZ] j|@b5jVežf]-HX\?G~ԏx6@BIENDB`qmidictl-1.0.1/src/images/PaxHeaders/formPlay.png0000644000000000000000000000013214675771705016753 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/formPlay.png0000644000175000001440000000030614675771705016742 0ustar00rncbcusersPNG  IHDRasRGBIDAT8˭ Dmt ]C:~BJ%ɻpm7-)Y0"BFxQfwwF^c4 \$gci1u6 5ؿDXk#3]L 6^LBIENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionMenu.png0000644000000000000000000000013214675771705017264 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionMenu.png0000755000175000001440000000362714675771705017267 0ustar00rncbcusersPNG  IHDR``w8^IDATx]ME~fHqDXʬ"-"W Hp B,f` Pqc*ͫww=v}U*TRJ*UTRJ*U7> }(ari`EJ]R@I)3Q9c,Q<c+` Q 䠩H4vTaGr MWع"lGc! {|1`;*?(ފc0I}!|J_og L (=.7+0a&H뱲vZ#1( ֣ (w>'s[Ql6i!"5${#CDBD׶?Ǐ.|wP&^sq@{+( t1^\\ѣoW<S THSdg&:a6r {ul6{x%,$A`s}ψQ|?9*yQV݄C_[2 e&!FDѐ1y)8-j/REBwx ̝̊1n8wCcbn~,(5i~wҎD8 ңDLq’3S)R\3؂c87GÐrXCΐ|a$0!Ej"%{,GX12 b'_hf`@(q±d\~4}m)~`8%i׶;A\Fxx Y.4M$9C-Gֶmt:cX]b8 ⩵~bT9a` kb B!)/pCKN! A%LJ0EC0E'|97$_>@'\BIP0 ls|V\tT@Kƞ]uNxNXcZE|-E\ ZĊ(I˪_s1Md4 rlc.GKN8Kbt2OKbh#)_< f=ʄc.8! Ǩ  @%0@|]9am.OT7f&'V k|*]!N yo"^['\ |ҭ5!9=}L>=(b[:aVb16Ƹ/#9rnTķWf}1!bAxN8ZKrZ_xN @^1]N<%Wɔ̎ m8'lBABa"p3XFv'0T3A[zPoIv}c'|gZȝ0*)¥qFe'0yskiv5[z^ܻwStu"5NX&aǐ5kȉXZrWo]#7܅6m=ͻcH`rnW޾~0&r8H+Pvo>H;W"VWRJ*UTRJ*UEtpsFIENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionCancel.png0000644000000000000000000000013214675771705017545 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionCancel.png0000644000175000001440000000760414675771705017544 0ustar00rncbcusersPNG  IHDR``w8KIDATx]K?w|DoRڦؿ4)ں51&~u#f[AiP _ ӐB?%&ym;3Йoyo,8{{ @ $@ $@ $ څA2LH璧@@9$O {d'EU y>e"ә+y@4f@F$8ĥ3CtTx#y# 銫>ދG$ROߺbpz/0#`P FGZGD KY;pfD.XLҟWLB.7l*Pэ|ZgEt(1 F7td[t1>Y DA|4yΠ! ,c8n򆞚,*t=~ B@8͞jffC SD "E63-񷋺̴noofO1J3KTL[dLY[[{mAZ?N& $e n,i5_ Qz@}:nX4-}&V3=䵵7")!HӧO_IRؙ-ֲ/яzORO>}EfB!Z[[{վ* 2h H-@@!Ƹܹs񱱱t\y%|i;w.1n&D"oeQ-\35|H@1nL&f˯d2ygcDJ1( p1 :xw/r noo?WTHdsF*a@hw@FZGz,B_~eNBg&XExvv6#G:DQAB(L&g2S v+I$X,vd!!?φzzzj:!&aލ7􆆆ڢ ܆(h4ڦ(J&Q+x{+ÂÅoLOO'Ο?r :>23b>)"xMǎ;&b:Ap"A p;D%]煒!i}6^*)+0G ܺuK[ioo?JP2<@l dy7'''o13/դܜW G&u?nHooEIx]vuxx I)HS ȦͩF6A$X,̶!C_{{{f:N[Y?^!)L&SeL]7###lY%+Re2- PhbME.}|+Kk, ‚hCCC-}}}}$5rg^9R_JI@L& Vh F&B& Aqqq1ωzK.5_pWNSx|GLF5H8b;::B~ZZZY4q3ỳy}|*sm %$9󐪪?///bXY-"(ޚɲ| %QQ:;;_%B Đf6?䓉D"qUv$5X1Fc4f(1VW)@?*MLLGGGYs _-ܒv充];::oqcbX&J&[pUMV׊^~ȮU󄐟:;;axR(兊֏">zꯜWuxpSjiii TUE"L0,rd󉉉 N|REw8E:&////]]]ϛ.)#O.#H\4f5NzWXɑ)U rjkn,kU$ɓivgyy, 5Ler9tbMo< ,I䮮!Ól6csa(EN./]lf|wѿf1'N$բkUff*٪E4k־i*s$T­8+0JGG!IV,'jqcHK___,sD"q}~~ɓ'$I7c?~YWsP^#UHw|?,#ŔF퀪瓓~ퟚzQ OTv(2BIV:]ШeDLki&+#B'NdlրɈ"Q!̀*N#vt*[* "cAV;::a6/PRc^ᴼx {U~c>fM6'&&Lb$%I_1 %xV@vю53oi%j4=,Yx_V$j V|3]#t#N1%ek~~> V8E$~F&G(Ó^FY.#:p Nَ+tKďHݸ"'S૰kmXa-,KEvmNq~dE~Iu$,7_ "~"GNV{, FZuUP$8,*&AX5֍dAyV5܂A7ٴE 2|%ac:'IQu]gy fN&n"/~_>/tN? ᱱ: Z^N{i`w>/m߾}ߺu̢&*SeZ~T*{ Kı|@y_|w rGycb/,јt:rل$IM`s8e໵:.!;b{Q#QG=7n`3В`΄hkk?ggg |I#iS -M)%GӴ񡡡8M88T%d2>`J\$BP(SmjUI[6661;1u]055uehh.<-N;\(3~:x#dGCCCw֕1ܴ%T6oȷ~~H!Tu˗“q$ÎZIA}}=-|/pDQW_`Wz*\?¤J_#Vy`^H2܅_H H H 47IENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionOverflow.png0000644000000000000000000000013214675771705020163 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionOverflow.png0000644000175000001440000000424514675771705020160 0ustar00rncbcusersPNG  IHDR``w8lIDATx]n6p뤏 @_@>EA{mNȣѐ6Ŋk+Q#:ꨣ:T(YsR۟Sgܔ:U=&^so 6r@#RC7X-t8k':+ @J.N 1AFh{jB Jz9|a׫gZKqqb%|ӑ/A`jzA W0 P|8o{  Y>m+pO`5 q2^ W|ߥՔ]\Vj~ Do޼!Y" V?w8m>Jq~s1'uh74Z !x;/ƞ AKh!b_ !"jnhn'\BúPՐc=J qdnpGixƄ i}k+yE '8S eDīĪ! ¥^ 4EN83/(%"]Ѯ+D"T4eRHk8n8_X2;RB5s GB%PGﲀNUULs V%P \Q!$@K BcֱV64Dթ]J~Gaqm_~f8>Csnlfwmv]qRAg1===}¥ 4 ;"> cg:T S;q=ZQB EPm̸A`rH93*+bD$-e@LYf,N)vJ5¹xJ9g+Q)>P-gvh"bKk)uąy-xY7B5GIE  _+Zq5$Bi&DQQ)aUBO [X,%5%ÊEʊ8C+BvY9?*WVY=ׄA"uVDY 2 9%Vpɢ@eEV?tMp d64BeE@ZG~xx-"qxhIYii`EY_c+EyV]̀`fz(%wV9Q 50pW# CB}I~J1,TɆL+kûkKS9C`r?/.-QZIR6XS #,i,k C*HuDzFxeRX]iRڶSFxA0?xmQ;٨z9kM@u#UԪ۷oEyKmDϻ#G:,- VI;*: UЍzY 4NWZJ뙚uk-SODm֑@Y'BRP vD:f (Zʹ:d"L(GK/@ }YǷl%;C悦km *7|}`ƬR6E]@Ri^%R\C-QRl姀V8 )Z KsE.®vf3V8@,Yi) 3us̎clhlܠCـԱPYݻ_?f893HKˆ]{WN0oq2kiVVfIpKvvRaz͵/1 s.1e7O8 Ub튵vn{ʡTA<$v{bkU|ۚCG¹t֪sIu/=dL^iZiMQ^ߒyYR"W\P!Av7 Ắ==='fΜիvjy֟N~:odY-_ֽ'!5M{K.]RRRrr饥'Mjgee-pDYi9ejo$)q|i~򗍍'7nܸsܸqoܹx***SRRVtttkjj.ttO)7yLJ"9YuB?-%K~'))4M[`ͳt]tttt+-((-"-[,"z"!p;# }}gO=%;C-Wm뻥Wokkku].\X+##ݚƆ6`:{;PlP(OO&%%TI)..7JZZ344배jMJa팈HmmmĉW={ʕ+={|jժ""3fX DVLF @nk dGvZ6uIK,>>˗lzΜ9saʔ)x<=o튋[v 6cN_C< 'I3HLl,qqqF_nmbssss -[xCꚦNcǎCɝeeeeWJT Фi gDf͞IƔ'0*^Mtt4˗/߻iӦuUUUlذ vffvkLDĭ: bK}/]l¨رcu--tww,Q x+v ;gP/K)ȡ_[Mcc#F8𝴌 (//#!&??MN9:xKGs79Gٌh 8X>g3a,DDRX}sf1baYA<" ZC>b"I) hbXl,&>O|@CKԜ]e{Ngl b;7BhÖ0!#~v;NCBY]~BP#"jkց;*u݁⅋mTUCZ [B%a 'ٵӊxtjٚ``<=MI[|OhRJv̻ As8 ]62z1FZںp}\RV%t]nn 8x" xD~cc븺1q9x5IENDB`qmidictl-1.0.1/src/images/PaxHeaders/ledOff.png0000644000000000000000000000013214675771705016361 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/ledOff.png0000644000175000001440000000123114675771705016346 0ustar00rncbcusersPNG  IHDRabKGD pHYs  tIME,Q &IDATx͒?oP{vҦBRT))  P &>#_ LlCi&n:NJ-3ޫ{~:ҽs6x0 !mם}|h4φc .DY'Pn.\u%S.:\e/z'~t4gVq8493݃c 핺}|: 4@Aq0 ,..=gEGA3XR(%0+>\\Uw۝|fq5IDATx͒MkQ{g26 jlJSB#tD(n+Kw A]P]ٸ&&I?&ׅTk=s}x:ȟJ2fP K[d(6j_777EZ->'XXGgVM8V҅;N޻_b[٩z9^fۨ+DQvC'֯WLw-疲 E$@s^f8[q;j|[ƶT>%5!' óCu $JZ~=4Tv( %.XFLbԇ/;HR.8HD@a'R]bWā|>}Zq@]j6f)O4.EK @ M)˙K9k 5"ZFxl6_8m; |[ R|\pMmLwh<,__\.OW@ ,a8Eݶ?[u*~^IENDB`qmidictl-1.0.1/src/images/PaxHeaders/actionOptions.png0000644000000000000000000000013214675771705020013 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/actionOptions.png0000644000175000001440000001454114675771705020010 0ustar00rncbcusersPNG  IHDR``w8(IDATx]{PWN0E >*kԠލD4G<6fS{s fom5qss*eM$ݻ&|GDy*# tgΜ3]TL|9 pxW>S}?4aX'(t> Q@),|D9#TMa'O6E 78@ -0^O)59Uҙ#@` / a$4E~&|\jJ6Oi$,U Sd% A>p;6}D8`& i&-Dh? !]#w =4k4/Z l !xA}ϲ,헅}ss,AԂ p(rDQ q֭>o/R&8K*PFxu"7>:I!H[fQpC !D`X2`nK.'61^&IՓ,x_57?ϟ?(*(&)^@Or"Cbԓ$| M|DEE\ZmxpEɨWA2I\(Ҝ "x"xHG'13`8I؂7N!G>&'m:˲j]uuuJ G<G kRSS'%hd 2RJ9%Dn|.xYńaGN }i#)>PagIbҿ[!v*ެ4 D@999K3?g)ܒ>'xF:WiM[pAhH5Fc< aW"3hvc4CEInd544p޼y C2i. wC!attt˿Q) d̪XsW0!ʕ+ ,˥/sss/1!UUUI֭۠H3I`@6zcc  ;v,->>ޘQ qK ?Wpt:o[ޮs};v ˀ?BLБpvАpJQ:<<<\Vz}ypp>>&|yg?nذa9si 2cV[ѸRAp jl]~r,5,aڵoD?34 JZzzz_.//Ӿ?6mZ ɓ'k`f&@jlz nڦj ni1d;x`ƍ+VADDqQ2:.Ob92:;AOф4@q\(* V.qtttHUUhMMM\~~2fyY__ߴnݺ.'Fc62 %iXQ@>!rAJ5\IJ"%}@=.++8ݺu/<J^r666.%%l6ߐ"E6:juŋWQX{T *IIIO(h { /-[ꫫǏ)TXM*YYYYǏܯF6B%{9߮D2$S)O>555UUUq?<ƍK&\SS3z̙)))o^@ uww3s-~577?8 &d2͘1XaaakS$GiY'aV:F,K+n4b__ߵ۶m 1cF*m˟>=HYgϞ-&1&&&~߾}I555:M%~2s(pp1bccD3!r= 3<*Nveea f}TTTI!Eo3n|l:|$*T3cc֭[k܋?عsg?Fvٿw? %@|`a#fلk ^~yKG)m0;ȰrVYYI3B~~ Bb___sAAɾ@C}.]t5VX1.jKII+aY,I++9=k߱cѣG? =3gt~AΧw6و" 粲Nm̫qR||| MMM5R>_EWQf&Hf*7n BhhhK-KΝ;ob7k^;0^׻>g6YװHAqǎ)>"`pD Չ_o,rg$@-cwtCw=ŋ0]AS"5&0~WI5AW;| c>-bKKK BliKׯX⬒YN+HhBDSjXXvx Y}#FPzu,.qcs߄4?2̣6߹ۇߓ'T544d#2hf3qg{1hJ@"@qDY& A$ܵ1//Vфp_:ZM{}yJPD$T:|-MZiii^w 7aZo~hI%J gi (==}KA~t0g _WcHZJzzz2p)U3㸠^xa `D]KWivg\8 ƄpR{{{p=t)}Yf-;xCPI5 h?WLxxL &<FqѣG;t+4}}mmJ RRRu`4999ϔ}]AP FmgϞ]/5$EdQŋkkkbEo-Tj0> 3jۣ0 **dӦM/15AqTĘ .lF ϕUUUǎQ{R0&AR.\Nk/FK׿@4fϞ=kV!4@_؍DQk׮u?^rs/ T*݂ ¾nhhz;LBBӢ(mTS͜l\dz uaWjX1qԩSR^yеkILLL%ׂ&1{ii鑚ŋKLLLMG4UWW  KP[[;g;AP8־>bSS׫Vm۶_,Nn`Q_ܼǃc۷xqqq~8'\L@d qϟ? &(ާO,^8*W_5߿+" 9u녅+W>J\桤<Ϗdee}͈'xT#sLbk׮}HWTTԻ( @ .8eҥg+(2y|m 8i%I̿/Z+'2!ڢklttgJ+Epll~ϗ,YrX[QQn\y:{# @T;{[C_ӅTTT+,,f4wvv^ T.s h***Vʓ>=W]Wf(LWBCCӕq Byyĉ\"駟^#h:L&S|plĺ999( 8LzAv7""Wp./eJzƙ " RsX(r0#⦉i?Fc-[̻wrFѡV8.((H237o4ڵ .444X{9m]]?q'yyI' ݇wV>&HOB `)6MXƍWՖ-[,d[mmmC[nEOyyyIII3!`- Ldj\6IL, V.Zifwxxy~  ftvvv #M{c233c܎S0:::`Zo %x!__2U|V̏w6+%J;v,s…/#4l6N>ݼz69N>|̙SXrAZBhhh8xrLE"v===pСԇzhvLLL^ŝ^|y aݵs4\>Jy^]PPnݲF.M;:7jۦK۶m ]~1cFo"[(W߁{Uwy'ey3҂: CCC>|ܩ 8na'e_| JAcY`|YR~ ̜9Cܾ74μcXQ[67^swz)ב)n={ ܋-$%i40o+d.I x`8IY)R&N4EEE -[l)+-wai$A1)A H6#k!!!|qHOOwT&LĴDNƱWJZOf3a$8z[sP.]:^1ŸJTbGI@@8y)o&'^DQYE4;N<@]ƬԆ̄/&HVGEEm#R6!wyͻY*yOZU jq3`X.RN+u%d6kq۵KjB@ߺqƷpg.x-B4~" #7|DDII;aD իWOK,:q1eo$}[a "lGiIo* :|0[DxuxN-ȚH {V dj#?EWp1&6A.YwQz zɛ{){  V y"Fj%)%$8[_92)<|yR T/z=3@QGW kYßA4IENDB`qmidictl-1.0.1/src/images/PaxHeaders/formStop.png0000644000000000000000000000013214675771705016773 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/formStop.png0000644000175000001440000000023314675771705016761 0ustar00rncbcusersPNG  IHDRasRGBUIDAT8c`hK(]?ٳY{,CA&J0j ,sⴥ7` $X~*5pIENDB`qmidictl-1.0.1/src/images/PaxHeaders/qmidictl.xcf0000644000000000000000000000013214675771705016764 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/images/qmidictl.xcf0000644000175000001440000003006414675771705016757 0ustar00rncbcusersgimp xcf file@@BB'Bgimp-image-grid(style solid) (fgcolor (color-rgba 0.000000 0.000000 0.000000 1.000000)) (bgcolor (color-rgba 1.000000 1.000000 1.000000 1.000000)) (xspacing 10.000000) (yspacing 10.000000) (spacing-unit inches) (xoffset 0.000000) (yoffset 0.000000) (offset-unit inches) gamma0.45454999804496765 X (@@ Waves TR     b@@v@@) *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=) *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=) *+ 0+-/. ,/++*-/(((*+)+*6;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=8578/.-/2**),.''')*++05 W@@Waves TR Shadow     $ @@ @@ ( 5*?EE?- 2)r\B-07¬V/.)rضV.-2HQcصB+,EMJBBMbˌC*)rbMBD]̋C)7­]CB[ˋA()sضJ,Hˁ.(2KdˌG)HU&,EOUYWčG,[؈/&)r`DB[ˌIAU&7ضJ,H˄C]׆,&)rˌG)H]DB&2Jb̌G,[؉Bb\%,DIAB[ˌIBMMه%)rJ,H˄DcbB%7̌G)H`WيBb%)t͌G,[،YKQ%.a͌IBUeMI٧%#Y˂AbOL٧E3ss$(7.+YIK٧E3ss,)7)$)pp.#aڥD2ss,)7)$7ĕ8/us,)7)()pp))7)-)7)3U@@ Waves BL     %@@@@><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ *><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ *><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<:6++(+*(((/-*++/, ./.,1 ,+ * X50++*)'''.,)**2/-./8758 Drop Shadow     %'0DT)7( )7))pp)-)7),su/8ĕ7)7),ss2Dڸa#.pp))7),ss3E٩KIY+.8)*rs3E٩LObAߦY# ?اHMeUڋBIߨa. FQJڍY[,Gt) FbBW`ؓH)Gڗ7 ?يBbcĎH,Jr),LLڋBI̓[BAID,\bB[,GضbJ2 BًD]ؓH)G¨r),]ČH,Jؗ7U؊AI̓[BD`r)/[,GضcWYUOE,UؓH)G®dK2.̌H,J©s)B̓[BC]ؗ7Cض]DBMbr)CطbMBBJME,B¬cQH2 .Vør) /Vؗ7 -B\r) ,?EE?* @@Plug     @@@@K<9'3¹9{,3¹8~%3½K6,3iv6rOɪ6Okkɸ6Tuk5ukkmY[u2#ukgOϫmm"2#u=Цsm(1'A3ϤqU+1?Ϧ],03Ϟ[-1?ϢU033ϞmG+,?ϞmG{9,3ޚ`U]G+?nբϻ],3UGmӽ],?U3Ӻ]K-",mӽ[,?mӽ].,3mӽ]#.,?mӽ]''K,3mӽ['&K,?mӽm?9 .,3mU"-,?mн^3-,3mUmfmom-,?3mo$.3mUm$. ,?MUba^m/3mmr-GUm?, 33GUmU‰wtkK+,?3G?ӶV{+12,;,Ӷ9+#,13,G,Ӷ*7,29Ӷ)"23';u,?Ӷ(",23Mf]79Ӱ(-+,UU?b9I5:(0GMJb 9NMn)"9?+[,c93Jo)3,XbS ISP{ *jb m3/VUC+lC$ONwmOm.XADyǻOtk0l#dʼnV"2l*V}3bX},57 6R5M:9(1Ļ8}+1Ļ8'1ĿM6z+1b~n6tE}6E\\}6Hne\}3en\\~xh[]w1!enhYIѭhh#1!en=Ҩuh)1 81ѦsV,1@Ѩz_+01Ѡz]//@ѤzV1/1ѠzhH,)@ѠhH}z: )1zzbV_H(@zpפѽz_(1VHzhտz_(@V1ռz_M(#+hzտz]( @hzտz_ (+1hzտz_$'+@hzտz_(&M+1hzտz](%M+@hzտh@: %+1hzzzzV#$+@hzҿ`1$+1hzVhhqh #+@1zzhq%#1hzVzh%# +@OVdc`h#1hhzt#HVhzz@$ 11HVhVĄvihzM$+-@1H@zΩvQ}% 33+=+zzΩ4&$+31+Hz+Ω'9+3zz:Ωz'#31(=zw+@Ωv'#+31Oh_7:Τp'/,+VV@a:C28 '1HOR>r) Ea c1*JBD*GB%GNyhGc.;CΪgg!2EN6ϥrg'2)5ΣpT*1>Υy\.35ΝyZ,3>ΡyT/35ΝygF*/>ΝgFzy8.5ݘyy_T\F.>ymԡκy\.5TFygҼy\.>T5ҹy\J.!.gyҼyZ/>gyҼy\/.5gyҼy\"/.>gyҼy\&'J.5gyҼyZ&&J.>gyҼg>8 /.5gyyyyT!/.>gyϼ]5/.5gyTgegng..>5yygn#/5gyTyg#. .>MTa`]g/5ggyq.FTgyy>, 55FTgTE)JyJ+.+>5F>y|@z+01.;.yy|+".05.Fy.|*6.1yy8|)!15&;yt.>| (!.15Me\8|(,*.TT>8z')/FM 8F$*!8>$.{9)5.:  :]>+#t5j2-#,Fvg,#q 0Cxƺ,)J0:ă@!2q|4$&t.5G8(';:997777 3 2 2 2 2 100**))))))('&&%%$$$$$%%&'(((((()*+/0 2 4 68@@ Plug Shadow     )F@@)Z@@)j84 3,AIB/ 1,Sv{]: /Avf=! -JlG. ,B{Ġ}aH0 +/]ζ^: ) :fѸf=! ' lšhWE/ ' !G~й^: '.aηf< ' Hg< &0_×g< & ;kØg< ! G~Øh>!  .bÙlH3*&"   Lšj`[TE07mкaA'  $O͹uQ3 3a˭`:  :gؼf<  l Øg<  !H Øg< 1h Øg<  %X Øg< I Øg< 7n Øg<  %SØg< 9oØg<  $P×g< 2`×f:  :f”`2   h—f: & C|^pf8|QMqA\Gz)c! Aǵ^ xɒT B+E~ @QcAfPGg7/ ^'sB <\/(ʯ#iz0PKO BދhZyyA '}Ou|A)njw&H-!F./#xԩ#""]VcN͞=Ny sH-' G7@YNzlС:.cFaYuɓ'A=a)$2)zqL |D^t21fS1ILLꫯ&Y֚"/7( Ϗz!|Ph "b܏1FaÆMs8-..K}ݷȑ#E$yB0 f&(@JaM#V >[ TÚ MMM1|3]t:}]BVk2oovKJP)aq˖-MG՚F Iw?L>l2g@#_YY ~&X||#&(7z|x^Nbcc7m޼y͵k*X Nk-Wbi?@]2z3燭]6nV^^ŋcxW^iNHH[}ÇglqyyyUgΜ)̐$/QB X!Vg:ql~ڵk^x:. P]]}jԨQtmf ̸{yE3g3f+**P<\LL2&a;Zɬg_~uz>Fc|JJ/c{( H'̳z_^o_-0 6ldWpld^3|x>#} ky qrc>@B7F fcTSS/"x :g})SLҊ+D_~-\0\!UͶru~S7fHjM ұ/^5j 322҂﫳uşV,))1UUU;Ҿ;vVPNk=?E nYѷU0_^|k֬N L2ex6))kիs#""b{677HLLn4xSrV w|3X&dA Q#՚hѢYGѓO>y^6G~!666N͇ɓ3]/Cm=+osVk ::mí-9o޼(;=PqmmIR'ff̘1 SN=*I~rV3%P%+vh:t 4$qhmmmgBr4 dwfvGEEP_ѪBl`0O<9Biii'qIIIUchgϻ%INeK6+*f tT 8VVVHHHxbZqz`O|1otctt?I$7Uח8K4n*TxMޱLl~w  `5kV.=~O!znݺB, ~W@ |pl}.6YmM&Ӏݻw?^z?7ΝkgLJv{o ǺhG3|cά!̶ r\_F;fJ*_th'(|1(miӦXʼ ^]OgPS ~!'LƁ%7+p3NO25?L_`Zi~w 5?vuŒJEHxZnQ*+tٌaj2E5I;P|Z4,{7TjZB[2C-j`lhh/L$W󢝐;Xv[>.\@%Zm5A`CFbryH dmKwֺ:h믿>9lذ u\Dw޽g̙{ر#GF!5A 9V< BOm47<\=po՝8q"3%%e4 :GD@dhOV4UD1Nm|g~lr߯+cWZG])##@]]ENݞ-#\|QV\cv\*]B~3ȃ/NS!p\MC >|x0Ai0Ɲܓn5P~ik;Kw0T얞1EK}>]~T]]kf"x} 5l!] { PLZ9O>I6̓L`5yUу.]:kΝv23/,,'gp3Ca= E&| cvvy@t^gO?}Bp˃z |Ҝ ׀5eEz=qQ ٳ$۷oÌ3N(8N4fLJINob&ox<-߿]NN1JA!C ҈1LoQ]555%|סCVO6 n _k|h0VD@BYf5o9 P<|{챣5_ JX_ee qqqHs)GibbZ&ZmFuttfffyMI+ |bI׍~cN3s/ pSz}B8~G'N,a7))]jO8ؼfez$I~c#!W&g(_i".aй-^) nnSʬQqy;1z{f]բ#p8*=OS*8;D&H:Ϟ=;k4˝El۶m,YyRhwu|q,xU=immm5O.: 2|74Ç?(g>cU1v744,,,,z饗 اҩsA^_ SRR&- Mdo^߿˗?w…&L8($O^N ){,|J+++t:(C+77V=}`6ׁՂT{---y(cbb,P˨@g4wMZP:qo2 [u3Ck?k̋x@AdbcTJׂB8YU鿁WPVʊ7&Vc0$|}+Z Z" @I߬) Q7!~l8;acIENDB`qmidictl-1.0.1/src/PaxHeaders/qmidictlMidiControlForm.h0000644000000000000000000000013214675771705020156 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMidiControlForm.h0000644000175000001440000000413014675771705020144 0ustar00rncbcusers// qmidictlMidiControlForm.h // /**************************************************************************** Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlMidiControlForm_h #define __qmidictlMidiControlForm_h #include "ui_qmidictlMidiControlForm.h" // Forward declarations. #if defined(Q_OS_ANDROID) class qmidictlActionBar; class QAction; #endif //---------------------------------------------------------------------------- // qmidictlMidiControlForm -- UI wrapper form. class qmidictlMidiControlForm : public QDialog { Q_OBJECT public: // Constructor. qmidictlMidiControlForm(QWidget *pParent = nullptr); // Destructor. ~qmidictlMidiControlForm(); protected slots: void activateCommand(int); void activateControlType(int); void change(); void buttonClick(QAbstractButton *); void accept(); void reject(); void reset(); private: // The Qt-designer UI struct... Ui::qmidictlMidiControlForm m_ui; // Instance variables. int m_iDirtyCount; int m_iDirtySetup; #if defined(Q_OS_ANDROID) // Special action-bar for the android stuff. qmidictlActionBar *m_pActionBar; QAction *m_pBackAction; QAction *m_pResetAction; QAction *m_pAcceptAction; QAction *m_pCancelAction; #endif }; #endif // __qmidictlMidiControlForm_h // end of qmidictlMidiControlForm.h qmidictl-1.0.1/src/PaxHeaders/qmidictlOptionsForm.h0000644000000000000000000000013214675771705017366 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlOptionsForm.h0000644000175000001440000000404114675771705017355 0ustar00rncbcusers// qmidictlOptionsForm.h // /**************************************************************************** Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlOptionsForm_h #define __qmidictlOptionsForm_h #include "ui_qmidictlOptionsForm.h" // Forward declarations. #if defined(Q_OS_ANDROID) class qmidictlActionBar; class QAction; #endif //---------------------------------------------------------------------------- // qmidictlOptionsForm -- UI wrapper form. class qmidictlOptionsForm : public QDialog { Q_OBJECT public: // Constructor. qmidictlOptionsForm(QWidget *pParent = nullptr); // Destructor. ~qmidictlOptionsForm(); protected slots: void change(); void buttonClick(QAbstractButton *); void accept(); void reject(); void reset(); private: // The Qt-designer UI struct... Ui::qmidictlOptionsForm m_ui; // Instance variables. int m_iDirtyCount; // Default (translatable) interface name. QString m_sDefInterface; #if defined(Q_OS_ANDROID) // Special action-bar for the android stuff. qmidictlActionBar *m_pActionBar; QAction *m_pBackAction; QAction *m_pResetAction; QAction *m_pAcceptAction; QAction *m_pCancelAction; #endif }; #endif // __qmidictlOptionsForm_h // end of qmidictlOptionsForm.h qmidictl-1.0.1/src/PaxHeaders/appdata0000644000000000000000000000013214675771705014544 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/appdata/0000755000175000001440000000000014675771705014611 5ustar00rncbcusersqmidictl-1.0.1/src/appdata/PaxHeaders/org.rncbc.qmidictl.metainfo.xml0000644000000000000000000000013214675771705022626 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/appdata/org.rncbc.qmidictl.metainfo.xml0000644000175000001440000000305014675771705022614 0ustar00rncbcusers org.rncbc.qmidictl FSFAP GPL-2.0+ QmidiCtl A MIDI Remote Controller via UDP/IP Multicast

QmidiCtl is a MIDI remote controller application that sends MIDI data over the network, using UDP/IP multicast. Inspired by multimidicast (http\://llg.cubic.org/tools) and designed to be compatible with ipMIDI for Windows (http\://nerds.de).

org.rncbc.qmidictl.desktop qmidictl https://qmidictl.sourceforge.io/image/qmidictl-android-phone-1.png The main window showing the application in action MIDI Multicast Network UDP IP Qt https://qmidictl.sourceforge.io rncbc.org rncbc aka. Rui Nuno Capela rncbc@rncbc.org
qmidictl-1.0.1/src/appdata/PaxHeaders/org.rncbc.qmidictl.desktop0000644000000000000000000000013214675771705021676 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/appdata/org.rncbc.qmidictl.desktop0000644000175000001440000000065114675771705021670 0ustar00rncbcusers[Desktop Entry] Encoding=UTF-8 Type=Application Name=QmidiCtl Version=1.0 Comment=MIDI Remote Controller via UDP/IP Multicast Comment[fr]=Contrôleur MIDI distant via UDP/IP Multicast Exec=qmidictl Icon=org.rncbc.qmidictl Categories=Audio;AudioVideo;Midi;X-Alsa;Qt; Keywords=MIDI;Multicast;Network;UDP;IP;Qt; StartupWMClass=qmidictl X-Window-Icon=qmidictl X-HildonDesk-ShowInToolbar=true X-Osso-Type=application/x-executable qmidictl-1.0.1/src/PaxHeaders/qmidictlOptions.h0000644000000000000000000000013214675771705016542 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlOptions.h0000644000175000001440000000436214675771705016537 0ustar00rncbcusers// qmidictlOptions.h // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlOptions_h #define __qmidictlOptions_h #include #include // Some hard-coded default options.... #define QMIDICTL_MMC_DEVICE 127 #define QMIDICTL_UDP_IPV4_ADDR "225.0.0.37" #define QMIDICTL_UDP_IPV6_ADDR "ff12::37" #define QMIDICTL_UDP_PORT 21928 //------------------------------------------------------------------------- // qmidictlOptions - Prototype settings class (singleton). // class qmidictlOptions { public: // Constructor. qmidictlOptions(); // Default destructor. ~qmidictlOptions(); // The settings object accessor. QSettings& settings(); // Explicit I/O methods. void loadOptions(); void saveOptions(); // Command line arguments parser. bool parse_args(const QStringList& args); #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) void show_error(const QString& msg); #else // Command line usage helper. void print_usage(const QString& arg0); #endif // Network options... QString sInterface; QString sUdpAddr; int iUdpPort; // MIDI options... int iMmcDevice; // Singleton instance accessor. static qmidictlOptions *getInstance(); private: // Settings member variables. QSettings m_settings; // The singleton instance. static qmidictlOptions *g_pOptions; }; #endif // __qmidictlOptions_h // end of qmidictlOptions.h qmidictl-1.0.1/src/PaxHeaders/qmidictlMainForm.cpp0000644000000000000000000000013214675771705017152 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMainForm.cpp0000644000175000001440000007767614675771705017171 0ustar00rncbcusers// qmidictlMainForm.cpp // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlAbout.h" #include "qmidictlMainForm.h" #include "qmidictlOptionsForm.h" #include "qmidictlMidiControlForm.h" #include "qmidictlOptions.h" #include "qmidictlMidiControl.h" #include "qmidictlUdpDevice.h" #include "qmidictlDialStyle.h" #if defined(Q_OS_ANDROID) #include "qmidictlActionBar.h" #endif #include #include #include // Possible cubic-root optimization. // (borrowed from metamerist.com) #include static inline float cbrtf2 ( float x ) { #ifdef CONFIG_FLOAT32 // Avoid strict-aliasing optimization (gcc -O2). union { float f; int i; } u; u.f = x; u.i = (u.i / 3) + 710235478; return u.f; #else return cbrtf(x); #endif } static inline float cubef2 ( float x ) { return x * x * x; } //---------------------------------------------------------------------------- // qmidictlMainForm -- UI wrapper form. // qmidictlMainForm *qmidictlMainForm::g_pMainForm = nullptr; // Constructor. qmidictlMainForm::qmidictlMainForm ( QWidget *pParent ) : QMainWindow(pParent) { // Setup UI struct... m_ui.setupUi(this); #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) QMainWindow::setWindowIcon(QIcon(":/images/qmidictl.png")); #endif // The main network device. m_pUdpDevice = new qmidictlUdpDevice(this); // Strip page/states. m_iStripPages = 4; m_pStripStates = nullptr; m_iCurrentStripPage = 0; // Jog-wheel last known state. m_iJogWheelDelta = 0; m_iJogWheelValue = 0; // Activity LED counters. m_iMidiInLed = 0; m_iMidiOutLed = 0; // Hack the jog-wheel dial style and palette... m_pDialStyle = new qmidictlDialStyle(); m_ui.jogWheelDial->setStyle(m_pDialStyle); // Enable multi-gestures... #ifdef CONFIG_MULTI_TOUCH m_ui.MainCentralWidget->setAttribute(Qt::WA_AcceptTouchEvents); m_ui.MainCentralWidget->installEventFilter(this); #endif #if defined(Q_OS_ANDROID) // Special stuff for the android platform. m_ui.optionsAction->setIcon(QIcon(":/images/actionOptions.png")); m_ui.configureAction->setIcon(QIcon(":/images/actionConfigure.png")); m_ui.aboutAction->setIcon(QIcon(":/images/actionAbout.png")); // m_ui.exitAction->setIcon(QIcon(":/images/actionCancel.png")); #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) m_ui.prevStripPageButton->setMaximumSize(96, 48); m_ui.nextStripPageButton->setMaximumSize(96, 48); #endif // Special action-bar for the android stuff. m_pActionBar = new qmidictlActionBar(); m_pActionBar->setIcon(QIcon(":/images/qmidictl.png")); #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) m_pActionBar->setTitle(QApplication::applicationDisplayName()); #else m_pActionBar->setTitle(QMIDICTL_TITLE " - " + tr(QMIDICTL_SUBTITLE)); #endif #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) // Action-bar left-drop-down menu items... m_pActionBar->addMenuItem(m_ui.optionsAction); m_pActionBar->addMenuItem(m_ui.configureAction); m_pActionBar->addMenuItem(m_ui.aboutAction); // m_pActionBar->addMenuItem(m_ui.exitAction); #endif // Action-bar right-overflow button items... m_pActionBar->addButton(m_ui.optionsAction); m_pActionBar->addButton(m_ui.configureAction); m_pActionBar->addButton(m_ui.aboutAction); // m_pActionBar->addButton(m_ui.exitAction); // Make it at the top... m_ui.MainCentralLayout->insertWidget(0, m_pActionBar); #endif // Custom swipe gesture trackers. m_bSwipe = false; // Pseudo-singleton reference setup. g_pMainForm = this; // Initialize strip connections... initMixerStrips(); // Main menu action connections. QObject::connect(m_ui.optionsAction, SIGNAL(triggered(bool)), SLOT(optionsSlot())); QObject::connect(m_ui.configureAction, SIGNAL(triggered(bool)), SLOT(configureSlot())); QObject::connect(m_ui.aboutAction, SIGNAL(triggered(bool)), SLOT(aboutSlot())); QObject::connect(m_ui.exitAction, SIGNAL(triggered(bool)), SLOT(exitSlot())); // UI widgets signal/slot connections... QObject::connect(m_ui.prevStripPageButton, SIGNAL(clicked()), SLOT(prevStripPageSlot())); QObject::connect(m_ui.nextStripPageButton, SIGNAL(clicked()), SLOT(nextStripPageSlot())); QObject::connect(m_ui.jogWheelDial, SIGNAL(valueChanged(int)), SLOT(jogWheelSlot(int))); QObject::connect(m_ui.resetButton, SIGNAL(clicked()), SLOT(resetSlot())); QObject::connect(m_ui.rewindButton, SIGNAL(toggled(bool)), SLOT(rewindSlot(bool))); QObject::connect(m_ui.playButton, SIGNAL(toggled(bool)), SLOT(playSlot(bool))); QObject::connect(m_ui.stopButton, SIGNAL(clicked()), SLOT(stopSlot())); QObject::connect(m_ui.recordButton, SIGNAL(toggled(bool)), SLOT(recordSlot(bool))); QObject::connect(m_ui.forwardButton, SIGNAL(toggled(bool)), SLOT(forwardSlot(bool))); // Network signal/slot connections... QObject::connect(m_pUdpDevice, SIGNAL(received(QByteArray)), SLOT(receiveSlot(QByteArray))); } // Destructor. qmidictlMainForm::~qmidictlMainForm (void) { #if defined(Q_OS_ANDROID) // No need for special android stuff anymore. delete m_pActionBar; #endif // No need for fancy styling no more. delete m_pDialStyle; // No more strip states. delete [] m_pStripStates; // No more network device. delete m_pUdpDevice; // Pseudo-singleton reference shut-down. g_pMainForm = nullptr; } // Kind of singleton reference. qmidictlMainForm *qmidictlMainForm::getInstance (void) { return g_pMainForm; } // Mixer strip page accessors. void qmidictlMainForm::setCurrentStripPage ( int iStripPage ) { if (iStripPage < 0 || iStripPage > m_iStripPages - 1) return; if (iStripPage != m_iCurrentStripPage) saveStripPage(m_iCurrentStripPage); m_iCurrentStripPage = iStripPage; loadStripPage(m_iCurrentStripPage); stabilizeForm(); } int qmidictlMainForm::currentStripPage (void) const { return m_iCurrentStripPage; } // Setup method. void qmidictlMainForm::setup (void) { qmidictlOptions *pOptions = qmidictlOptions::getInstance(); if (pOptions == nullptr) return; if (!m_pUdpDevice->open( pOptions->sInterface, pOptions->sUdpAddr, pOptions->iUdpPort)) { QMessageBox::critical(this, tr("Network Inferface Error"), tr("The network interface could not be established.\n\n" "Please, make sure you have an on-line network connection " "and try again.")); } } // Initialize mixer strips. void qmidictlMainForm::initMixerStrips (void) { initStripStates(); initMixerStrip(m_ui.strip1); initMixerStrip(m_ui.strip2); initMixerStrip(m_ui.strip3); initMixerStrip(m_ui.strip4); setCurrentStripPage(0); } void qmidictlMainForm::initMixerStrip ( qmidictlMixerStrip *pStrip ) { QObject::connect(pStrip, SIGNAL(recordToggled(int, bool)), SLOT(stripRecordSlot(int, bool))); QObject::connect(pStrip, SIGNAL(muteToggled(int, bool)), SLOT(stripMuteSlot(int, bool))); QObject::connect(pStrip, SIGNAL(soloToggled(int, bool)), SLOT(stripSoloSlot(int, bool))); QObject::connect(pStrip, SIGNAL(sliderChanged(int, int)), SLOT(stripSliderSlot(int, int))); } // Strip states methods. void qmidictlMainForm::initStripStates (void) { const int iStrips = 4 * m_iStripPages; m_pStripStates = new StripState [iStrips]; for (int iStrip = 0; iStrip < iStrips; ++iStrip) { StripState *pState = &m_pStripStates[iStrip]; pState->strip = iStrip; pState->record = false; pState->mute = false; pState->solo = false; pState->slider = false; } } void qmidictlMainForm::saveStripState ( qmidictlMixerStrip *pStrip, int iStrip ) { StripState *pState = &m_pStripStates[iStrip]; pState->strip = pStrip->strip(); pState->record = pStrip->isRecord(); pState->mute = pStrip->isMute(); pState->solo = pStrip->isSolo(); pState->slider = pStrip->slider(); } void qmidictlMainForm::loadStripState ( qmidictlMixerStrip *pStrip, int iStrip ) { StripState *pState = &m_pStripStates[iStrip]; pStrip->setStrip(pState->strip, pState->record, pState->mute, pState->solo, pState->slider); } void qmidictlMainForm::saveStripPage ( int iStripPage ) { const int iStrip = (iStripPage * 4); saveStripState(m_ui.strip1, iStrip + 0); saveStripState(m_ui.strip2, iStrip + 1); saveStripState(m_ui.strip3, iStrip + 2); saveStripState(m_ui.strip4, iStrip + 3); } void qmidictlMainForm::loadStripPage ( int iStripPage ) { const int iStrip = (iStripPage * 4); loadStripState(m_ui.strip1, iStrip + 0); loadStripState(m_ui.strip2, iStrip + 1); loadStripState(m_ui.strip3, iStrip + 2); loadStripState(m_ui.strip4, iStrip + 3); } // Common form stabilizer. void qmidictlMainForm::stabilizeForm(void) { m_ui.prevStripPageButton->setEnabled(m_iCurrentStripPage > 0); m_ui.nextStripPageButton->setEnabled(m_iCurrentStripPage < m_iStripPages - 1); } // Mixer strip slots. void qmidictlMainForm::prevStripPageSlot (void) { setCurrentStripPage(currentStripPage() - 1); } void qmidictlMainForm::nextStripPageSlot (void) { setCurrentStripPage(currentStripPage() + 1); } // MMC Command dispatcher. void qmidictlMainForm::sendMmcCommand ( MmcCommand cmd, unsigned char *data, unsigned short len ) { // Build up the MMC sysex message... unsigned char *pSysex; unsigned short iSysex = 6; if (data && len > 0) iSysex += 1 + len; pSysex = new unsigned char [iSysex]; iSysex = 0; pSysex[iSysex++] = 0xf0; // Sysex header. pSysex[iSysex++] = 0x7f; // Realtime sysex. pSysex[iSysex++] = qmidictlOptions::getInstance()->iMmcDevice; pSysex[iSysex++] = 0x06; // MMC command mode. pSysex[iSysex++] = (unsigned char) cmd; // MMC command code. if (data && len > 0) { pSysex[iSysex++] = len; ::memcpy(&pSysex[iSysex], data, len); iSysex += len; } pSysex[iSysex++] = 0xf7; // Sysex trailer. // Go. sendData(pSysex, iSysex); // Done. delete [] pSysex; } // MMC dispatch special commands. void qmidictlMainForm::sendMmcMaskedWrite ( MmcSubCommand scmd, int iTrack, bool bOn ) { unsigned char data[4]; const int iMask = (1 << (iTrack < 2 ? iTrack + 5 : (iTrack - 2) % 7)); data[0] = scmd; data[1] = (unsigned char) (iTrack < 2 ? 0 : 1 + (iTrack - 2) / 7); data[2] = (unsigned char) iMask; data[3] = (unsigned char) (bOn ? iMask : 0); sendMmcCommand(MMC_MASKED_WRITE, data, sizeof(data)); } void qmidictlMainForm::sendMmcLocate ( unsigned long iLocate ) { unsigned char data[6]; data[0] = 0x01; data[1] = iLocate / (3600 * 30); iLocate -= (3600 * 30) * (int) data[1]; data[2] = iLocate / ( 60 * 30); iLocate -= ( 60 * 30) * (int) data[2]; data[3] = iLocate / ( 30); iLocate -= ( 30) * (int) data[3]; data[4] = iLocate; data[5] = 0; sendMmcCommand(MMC_LOCATE, data, sizeof(data)); } void qmidictlMainForm::sendMmcStep ( int iDelta ) { unsigned char data; if (iDelta < 0) { data = (-iDelta & 0x3f) | 0x40; } else { data = (+iDelta & 0x3f); } sendMmcCommand(MMC_STEP, &data, sizeof(data)); } // MIDI data transmitter helpers. void qmidictlMainForm::sendData3 ( unsigned char data1, unsigned char data2, unsigned char data3 ) { unsigned char data[3]; data[0] = data1; data[1] = data2; data[2] = data3; sendData(data, sizeof(data)); } void qmidictlMainForm::sendData2 ( unsigned char data1, unsigned char data2 ) { unsigned char data[2]; data[0] = data1; data[1] = data2; sendData(data, sizeof(data)); } void qmidictlMainForm::sendPitchBend ( unsigned short iChannel, int iValue ) { unsigned char data[3]; const unsigned short val = (unsigned short) (0x2000 + iValue); data[0] = 0xe0 + iChannel; data[1] = (val & 0x007f); data[2] = (val & 0x3f80) >> 7; sendData(data, sizeof(data)); } // Network transmitter. void qmidictlMainForm::sendData ( unsigned char *data, unsigned short len ) { #ifdef CONFIG_DEBUG fprintf(stderr, "sendData:"); for(int i = 0; i < len; ++i) fprintf(stderr, " %02X", data[i]); fprintf(stderr, "\n"); #endif if (m_pUdpDevice->sendData(data, len)) { if (++m_iMidiOutLed < 2) { m_ui.midiOutLedLabel->setPixmap(QPixmap(":/images/ledOn.png")); QTimer::singleShot(200, this, SLOT(timerSlot())); } } } // Generic command dispatcher. void qmidictlMainForm::sendCommand ( int iCommand, int iTrack, int iValue ) { qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (pMidiControl == nullptr) return; qmidictlMidiControl::Command command = qmidictlMidiControl::Command(iCommand); if (!pMidiControl->isCommandMapped(command)) return; const qmidictlMidiControl::MapKey& key = pMidiControl->commandMap().value(command); unsigned short iChannel = key.channel(); if (key.isChannelTrack()) { iChannel &= qmidictlMidiControl::TrackParamMask; iChannel += iTrack; } unsigned short iParam = key.param(); if (key.isParamTrack()) { iParam &= qmidictlMidiControl::TrackParamMask; iParam += iTrack; } if (key.isLogarithmic()) iValue = int(127.0f * cbrtf2(float(iValue) / 127.0f)); const bool bOn = (iValue > 0); switch (key.type()) { case qmidictlMidiControl::MMC: switch (command) { case qmidictlMidiControl::RST: sendMmcLocate(0); break; case qmidictlMidiControl::REW: sendMmcCommand(bOn ? MMC_REWIND : MMC_STOP); break; case qmidictlMidiControl::STOP: sendMmcCommand(MMC_STOP); break; case qmidictlMidiControl::PLAY: sendMmcCommand(bOn ? MMC_PLAY : MMC_PAUSE); break; case qmidictlMidiControl::REC: sendMmcCommand(bOn ? MMC_RECORD_STROBE : MMC_RECORD_EXIT); break; case qmidictlMidiControl::FFWD: sendMmcCommand(bOn ? MMC_FAST_FORWARD : MMC_STOP); break; case qmidictlMidiControl::JOG_WHEEL: sendMmcStep(iValue); break; case qmidictlMidiControl::TRACK_SOLO: sendMmcMaskedWrite(MMC_TRACK_SOLO, iTrack, bOn); break; case qmidictlMidiControl::TRACK_MUTE: sendMmcMaskedWrite(MMC_TRACK_MUTE, iTrack, bOn); break; case qmidictlMidiControl::TRACK_REC: sendMmcMaskedWrite(MMC_TRACK_RECORD, iTrack, bOn); break; case qmidictlMidiControl::TRACK_VOL: // Not handled here. default: break; } break; case qmidictlMidiControl::NOTE_OFF: sendData3(0x80 + iChannel, iParam, iValue); break; case qmidictlMidiControl::NOTE_ON: sendData3(0x90 + iChannel, iParam, iValue); break; case qmidictlMidiControl::KEY_PRESS: sendData3(0xa0 + iChannel, iParam, iValue); break; case qmidictlMidiControl::CONTROLLER: sendData3(0xb0 + iChannel, iParam, iValue); break; case qmidictlMidiControl::PGM_CHANGE: sendData2(0xc0 + iChannel, iParam); break; case qmidictlMidiControl::CHAN_PRESS: sendData2(0xd0 + iChannel, iValue); break; case qmidictlMidiControl::PITCH_BEND: sendPitchBend(iChannel, iValue); break; } } // Mixer strip slots. void qmidictlMainForm::stripRecordSlot ( int iStrip, bool bOn ) { // Update strip state... if (iStrip >= 0 && iStrip < (4 * m_iStripPages)) m_pStripStates[iStrip].record = bOn; sendCommand(qmidictlMidiControl::TRACK_REC, iStrip, int(bOn)); } void qmidictlMainForm::stripMuteSlot ( int iStrip, bool bOn ) { // Update strip state... if (iStrip >= 0 && iStrip < (4 * m_iStripPages)) m_pStripStates[iStrip].mute = bOn; sendCommand(qmidictlMidiControl::TRACK_MUTE, iStrip, int(bOn)); } void qmidictlMainForm::stripSoloSlot ( int iStrip, bool bOn ) { // Update strip state... if (iStrip >= 0 && iStrip < (4 * m_iStripPages)) m_pStripStates[iStrip].solo = bOn; sendCommand(qmidictlMidiControl::TRACK_SOLO, iStrip, int(bOn)); } void qmidictlMainForm::stripSliderSlot ( int iStrip, int iValue ) { // Update strip state... if (iStrip >= 0 && iStrip < (4 * m_iStripPages)) m_pStripStates[iStrip].slider = iValue; sendCommand(qmidictlMidiControl::TRACK_VOL, iStrip, (127 * iValue) / 100); } // Jog wheel slot. void qmidictlMainForm::jogWheelSlot ( int iValue ) { int iDelta = (iValue - m_iJogWheelValue); if ((iDelta * m_iJogWheelDelta) < 0) iDelta = -(m_iJogWheelDelta); sendCommand(qmidictlMidiControl::JOG_WHEEL, 0, iDelta); m_iJogWheelValue = iValue; m_iJogWheelDelta = iDelta; } // Reset action slot void qmidictlMainForm::resetSlot (void) { sendCommand(qmidictlMidiControl::RST); } // Rewind action slot void qmidictlMainForm::rewindSlot ( bool bOn ) { m_ui.forwardButton->setChecked(false); sendCommand(qmidictlMidiControl::REW, 0, int(bOn)); } // Start/Play action slot void qmidictlMainForm::playSlot ( bool bOn ) { sendCommand(qmidictlMidiControl::PLAY, 0, int(bOn)); } // Stop action slot void qmidictlMainForm::stopSlot (void) { m_ui.rewindButton->setChecked(false); m_ui.forwardButton->setChecked(false); m_ui.recordButton->setChecked(false); m_ui.playButton->setChecked(false); sendCommand(qmidictlMidiControl::STOP); } // Record action slot void qmidictlMainForm::recordSlot ( bool bOn ) { sendCommand(qmidictlMidiControl::REC, 0, int(bOn)); } // Forward action slot void qmidictlMainForm::forwardSlot ( bool bOn ) { m_ui.rewindButton->setChecked(false); sendCommand(qmidictlMidiControl::FFWD, 0, int(bOn)); } // Network listener/receiver slot. void qmidictlMainForm::receiveSlot ( QByteArray data ) { recvData((unsigned char *) data.constData(), data.length()); } void qmidictlMainForm::recvData ( unsigned char *data, unsigned short len ) { qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (pMidiControl == nullptr) return; #ifdef CONFIG_DEBUG fprintf(stderr, "recvData:"); for(int i = 0; i < len; ++i) fprintf(stderr, " %02X", data[i]); fprintf(stderr, "\n"); #endif // Flash the MIDI In LED... if (++m_iMidiInLed < 2) { m_ui.midiInLedLabel->setPixmap(QPixmap(":/images/ledOn.png")); QTimer::singleShot(200, this, SLOT(timerSlot())); } // Handle immediate incoming MIDI data... const int iTracks = (4 * m_iStripPages); int iTrack = -1; qmidictlMidiControl::ControlType ctype = qmidictlMidiControl::ControlType(0); unsigned short iChannel = 0; unsigned short iParam = 0; int iValue = 0; // SysEx (actually)... if (data[0] == 0xf0 && data[len - 1] == 0xf7) { // MMC command... const unsigned char mmcid = qmidictlOptions::getInstance()->iMmcDevice; if (data[1] == 0x7f && data[3] == 0x06 && (mmcid == 0x7f || data[2] == mmcid)) { ctype = qmidictlMidiControl::MMC; const MmcCommand cmd = MmcCommand(data[4]); switch (cmd) { case MMC_STOP: case MMC_RESET: iParam = int(qmidictlMidiControl::STOP); iValue = int(true); break; case MMC_PLAY: case MMC_DEFERRED_PLAY: iParam = int(qmidictlMidiControl::PLAY); iValue = int(true); break; case MMC_REWIND: iParam = int(qmidictlMidiControl::REW); iValue = int(true); break; case MMC_FAST_FORWARD: iParam = int(qmidictlMidiControl::FFWD); iValue = int(true); break; case MMC_RECORD_STROBE: iParam = int(qmidictlMidiControl::REC); iValue = int(true); break; case MMC_RECORD_EXIT: case MMC_RECORD_PAUSE: iParam = int(qmidictlMidiControl::REC); iValue = int(false); break; case MMC_PAUSE: iParam = int(qmidictlMidiControl::PLAY); iValue = int(false); break; case MMC_MASKED_WRITE: if (int(data[5]) > 3) { const MmcSubCommand scmd = MmcSubCommand(data[6]); iTrack = (data[7] > 0 ? (data[7] * 7) : 0) - 5; iValue = int(false); for (int i = 0; i < 7; ++i) { int iMask = (1 << i); if (data[8] & iMask) { iValue = (data[9] & iMask); break; } ++iTrack; } // Patch corresponding track/strip state... if (iTrack >= 0 && iTrack < iTracks) { switch (scmd) { case MMC_TRACK_RECORD: iParam = int(qmidictlMidiControl::TRACK_REC); break; case MMC_TRACK_MUTE: iParam = int(qmidictlMidiControl::TRACK_MUTE); break; case MMC_TRACK_SOLO: iParam = int(qmidictlMidiControl::TRACK_SOLO); break; default: break; } } } // Fall thru... default: break; } } } else { // Channel event... iChannel = (data[0] & 0x0f); iParam = data[1]; if (len > 2) iValue = data[2]; // Channel control type... switch (data[0] & 0xf0) { case 0x80: ctype = qmidictlMidiControl::NOTE_OFF; break; case 0x90: ctype = qmidictlMidiControl::NOTE_ON; break; case 0xa0: ctype = qmidictlMidiControl::KEY_PRESS; break; case 0xb0: ctype = qmidictlMidiControl::CONTROLLER; break; case 0xc0: ctype = qmidictlMidiControl::PGM_CHANGE; break; case 0xd0: ctype = qmidictlMidiControl::CHAN_PRESS; break; case 0xe0: ctype = qmidictlMidiControl::PITCH_BEND; iParam = 0; iValue = ((int(data[2]) << 7) | data[1]) - 0x2000; break; } } // Lookup the command mapping... qmidictlMidiControl::ControlMap::ConstIterator iter = pMidiControl->find(ctype, iChannel, iParam); if (iter != pMidiControl->controlMap().constEnd()) { const qmidictlMidiControl::MapKey& key = iter.key(); const qmidictlMidiControl::Command command = iter.value(); if (key.isChannelTrack()) iTrack = int(iChannel); else if (key.isParamTrack()) { iTrack = int(iParam); iTrack -= int(key.param() & qmidictlMidiControl::TrackParamMask); } if (key.isLogarithmic()) iValue = int(127.0f * cubef2(float(iValue) / 127.0f)); #ifdef CONFIG_DEBUG qDebug("recvData: Command=0x%04x Type=0x%04x Track=%d Value=%d", uint(command), uint(ctype), iTrack, iValue); #endif switch (command) { case qmidictlMidiControl::RST: // Nothing to do... break; case qmidictlMidiControl::REW: m_ui.rewindButton->setChecked(true); m_ui.forwardButton->setChecked(false); break; case qmidictlMidiControl::STOP: m_ui.rewindButton->setChecked(false); m_ui.playButton->setChecked(false); m_ui.recordButton->setChecked(false); m_ui.forwardButton->setChecked(false); break; case qmidictlMidiControl::PLAY: m_ui.playButton->setChecked(iValue > 0); break; case qmidictlMidiControl::REC: m_ui.recordButton->setChecked(iValue > 0); break; case qmidictlMidiControl::FFWD: m_ui.rewindButton->setChecked(false); m_ui.forwardButton->setChecked(true); break; case qmidictlMidiControl::JOG_WHEEL: // Nothing to do... break; case qmidictlMidiControl::TRACK_SOLO: if (iTrack >= 0 && iTrack < iTracks) m_pStripStates[iTrack].solo = bool(iValue > 0); break; case qmidictlMidiControl::TRACK_MUTE: if (iTrack >= 0 && iTrack < iTracks) m_pStripStates[iTrack].mute = bool(iValue > 0); break; case qmidictlMidiControl::TRACK_REC: if (iTrack >= 0 && iTrack < iTracks) m_pStripStates[iTrack].record = bool(iValue > 0); break; case qmidictlMidiControl::TRACK_VOL: if (iTrack >= 0 && iTrack < iTracks) m_pStripStates[iTrack].slider = (100 * iValue) / 127; break; } } // Update corresponding strip state, when currently visible... const int iStrip = (4 * m_iCurrentStripPage); if (iTrack >= iStrip && iTrack < iStrip + 4) { switch (iTrack % 4) { case 0: loadStripState(m_ui.strip1, iTrack); break; case 1: loadStripState(m_ui.strip2, iTrack); break; case 2: loadStripState(m_ui.strip3, iTrack); break; case 3: loadStripState(m_ui.strip4, iTrack); break; } } } // Timer slot funtion. void qmidictlMainForm::timerSlot (void) { // Handle pending incoming MIDI data... if (m_iMidiInLed > 0) { m_iMidiInLed = 0; m_ui.midiInLedLabel->setPixmap(QPixmap(":/images/ledOff.png")); } // Handle pending outgoing MIDI data... if (m_iMidiOutLed > 0) { m_iMidiOutLed = 0; m_ui.midiOutLedLabel->setPixmap(QPixmap(":/images/ledOff.png")); } } // Options action slot. void qmidictlMainForm::optionsSlot (void) { if (qmidictlOptionsForm(this).exec()) setup(); } // MIDI control configuration dialog. void qmidictlMainForm::configureSlot (void) { qmidictlMidiControlForm(this).exec(); } // About dialog. void qmidictlMainForm::aboutSlot (void) { // Stuff the about box text... QString sText = "

" QMIDICTL_TITLE "

\n"; sText += "

" + tr(QMIDICTL_SUBTITLE) + "
\n"; sText += "
\n"; sText += tr("Version") + ": " PROJECT_VERSION "
\n"; // sText += "" + tr("Build") + ": " CONFIG_BUILD_DATE "
\n"; #ifdef CONFIG_DEBUG sText += ""; sText += tr("Debugging option enabled."); sText += "\n"; #endif sText += "
\n"; sText += tr("Using: Qt %1").arg(qVersion()); #if defined(QT_STATIC) sText += "-static"; #endif sText += "
\n"; sText += "
\n"; sText += tr("Website") + ": " QMIDICTL_WEBSITE "
\n"; sText += "
\n"; sText += ""; sText += QMIDICTL_COPYRIGHT "
\n"; sText += "
\n"; sText += tr("This program is free software; you can redistribute it and/or modify it") + '\n'; sText += tr("under the terms of the GNU General Public License version 2 or later."); sText += "
"; sText += "
\n"; sText += "

\n"; QMessageBox::about(this, tr("About"), sText); } // Exit/quit action slot. void qmidictlMainForm::exitSlot (void) { close(); } // Provided for multi-touch support... bool qmidictlMainForm::eventFilter ( QObject *pObject, QEvent *pEvent ) { if (pObject == m_ui.MainCentralWidget) { if (pEvent->type() == QEvent::TouchBegin || pEvent->type() == QEvent::TouchUpdate || pEvent->type() == QEvent::TouchEnd || pEvent->type() == QEvent::TouchCancel) { QTouchEvent *pTouchEvent = static_cast (pEvent); if (pTouchEvent) return touchEvent(pTouchEvent); } } return QMainWindow::eventFilter(pObject, pEvent); } bool qmidictlMainForm::touchEvent ( QTouchEvent *pTouchEvent ) { // Make up with multi-touch stuff... // -- synthesize mouse events: int iTouched = 0; const QWidget *pCentralWidget = m_ui.MainCentralWidget; QEvent::Type etype; QMouseEvent *pMouseEvent; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QList& points = pTouchEvent->points(); #else const QList& points = pTouchEvent->touchPoints(); #endif switch (pTouchEvent->type()) { case QEvent::TouchBegin: // Begin... m_touched.clear(); if (points.size() == 1) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QEventPoint& point = points.at(0); #else const QTouchEvent::TouchPoint& point = points.at(0); #endif // Replay as first mouse-press... const int id = point.id(); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QPoint& pos = point.position().toPoint(); #else const QPoint& pos = point.pos().toPoint(); #endif const QList& children = pCentralWidget->findChildren (); foreach (QWidget *pWidget, children) { const QPoint& wpos = pWidget->mapFrom(pCentralWidget, pos); if (pWidget->rect().contains(wpos) && pWidget->childAt(wpos) == nullptr) { m_touched.insert(id, pWidget); etype = QEvent::MouseButtonPress; pMouseEvent = new QMouseEvent( etype, wpos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::postEvent(pWidget, pMouseEvent); ++iTouched; } } // Start tracking a swipe gesture... m_bSwipe = true; } // Fall thru... case QEvent::TouchUpdate: // Whether currently tracking a swipe gesture... if (m_bSwipe) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QEventPoint& point = points.at(0); const QPointF& p0 = point.globalPressPosition(); const QPointF& p1 = point.globalLastPosition(); const QPointF& p2 = point.globalPosition(); #else const QTouchEvent::TouchPoint& point = points.at(0); const QPointF& p0 = point.startScreenPos(); const QPointF& p1 = point.lastScreenPos(); const QPointF& p2 = point.screenPos(); #endif const qreal dx2 = (p2.x() - p1.x()) * (p1.x() - p0.x()); const qreal dy = p2.y() - p0.y(); if (dx2 < 0.0 || qAbs(dy) > 60.0 || points.size() > 1) { // And cancel swipe... m_bSwipe = false; } // Done with swipe tracking, for now... break; } // Assess all touch-point-ids... #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) foreach (QEventPoint point, points) { const QPoint& pos = point.position().toPoint(); #else foreach (QTouchEvent::TouchPoint point, points) { const QPoint& pos = point.pos().toPoint(); #endif const int id = point.id(); QWidget *pTouchedWidget = m_touched.value(id, nullptr); if (pTouchedWidget) { const QPoint& wpos = pTouchedWidget->mapFrom(pCentralWidget, pos); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) if (point.state() & QEventPoint::Released) { #else if (point.state() & Qt::TouchPointReleased) { #endif m_touched.remove(id); etype = QEvent::MouseButtonRelease; } else { etype = QEvent::MouseMove; } pMouseEvent = new QMouseEvent( etype, wpos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::postEvent(pTouchedWidget, pMouseEvent); ++iTouched; } else { const QList& children = pCentralWidget->findChildren (); foreach (QWidget *pWidget, children) { const QPoint& wpos = pWidget->mapFrom(pCentralWidget, pos); if (pWidget->rect().contains(wpos) && pWidget->childAt(wpos) == nullptr) { m_touched.insert(id, pWidget); etype = QEvent::MouseButtonPress; pMouseEvent = new QMouseEvent( etype, wpos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::postEvent(pWidget, pMouseEvent); ++iTouched; break; } } } } break; case QEvent::TouchEnd: // Whether currently tracking some swipe gesture... if (m_bSwipe) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QEventPoint& point = points.at(0); const QPointF& p0 = point.globalPressPosition(); const QPointF& p2 = point.globalPosition(); #else const QTouchEvent::TouchPoint& point = points.at(0); const QPointF& p0 = point.startScreenPos(); const QPointF& p2 = point.screenPos(); #endif const qreal dx = p2.x() - p0.x(); if (qAbs(dx) > 60.0) { // Perform swipe... if (dx < 0.0) nextStripPageSlot(); else prevStripPageSlot(); // Done swipe. } else { // Replay... #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const QPoint& pos = point.position().toPoint(); #else const QPoint& pos = point.pos().toPoint(); #endif const int id = point.id(); const QList& children = pCentralWidget->findChildren (); foreach (QWidget *pWidget, children) { const QPoint& wpos = pWidget->mapFrom(pCentralWidget, pos); if (pWidget->rect().contains(wpos) && pWidget->childAt(wpos) == nullptr) { m_touched.insert(id, pWidget); etype = QEvent::MouseButtonPress; pMouseEvent = new QMouseEvent( etype, wpos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::postEvent(pWidget, pMouseEvent); ++iTouched; } } } } // Release all remaining touch-point-ids... etype = QEvent::MouseButtonRelease; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) foreach (QEventPoint point, points) { const QPoint& pos = point.position().toPoint(); #else foreach (QTouchEvent::TouchPoint point, points) { const QPoint& pos = point.pos().toPoint(); #endif const int id = point.id(); QWidget *pTouchedWidget = m_touched.value(id, nullptr); if (pTouchedWidget) { const QPoint& wpos = pTouchedWidget->mapFrom(pCentralWidget, pos); pMouseEvent = new QMouseEvent( etype, wpos, pos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); QApplication::postEvent(pTouchedWidget, pMouseEvent); ++iTouched; } } // Fall thru... case QEvent::TouchCancel: m_touched.clear(); m_bSwipe = false; // Fall thru... default: break; } // Done with multi-touch stuff. return (iTouched > 0); } // end of qmidictlMainForm.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictlMixerStrip.cpp0000644000000000000000000000013214675771705017550 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMixerStrip.cpp0000644000175000001440000000750314675771705017545 0ustar00rncbcusers// qmidictlMixerStrip.cpp // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlMixerStrip.h" //---------------------------------------------------------------------------- // qmidictlMixerStrip -- UI wrapper form. // // Constructor. qmidictlMixerStrip::qmidictlMixerStrip ( QWidget *pParent ) : QWidget(pParent) { // Setup UI struct... m_ui.setupUi(this); // Strip number. m_iStrip = 0; m_iBusy = 0; #if defined(Q_OS_ANDROID) // Specific R/M/S color overlays... QPalette pal = QWidget::palette(); pal.setColor(QPalette::HighlightedText, Qt::black); pal.setColor(QPalette::Highlight, Qt::red); m_ui.recordButton->setPalette(pal); pal.setColor(QPalette::Highlight, Qt::yellow); m_ui.muteButton->setPalette(pal); pal.setColor(QPalette::Highlight, Qt::cyan); m_ui.soloButton->setPalette(pal); #endif #if defined(Q_OS_ANDROID) #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) m_ui.recordButton->setMaximumWidth(48); m_ui.muteButton->setMaximumWidth(48); m_ui.soloButton->setMaximumWidth(48); m_ui.recordButton->setMinimumHeight(32); m_ui.muteButton->setMinimumHeight(32); m_ui.soloButton->setMinimumHeight(32); m_ui.stripSlider->setMinimumWidth(32); #endif #endif // UI widgets signal/slot connections... QObject::connect(m_ui.recordButton, SIGNAL(toggled(bool)), SLOT(recordSlot(bool))); QObject::connect(m_ui.muteButton, SIGNAL(toggled(bool)), SLOT(muteSlot(bool))); QObject::connect(m_ui.soloButton, SIGNAL(toggled(bool)), SLOT(soloSlot(bool))); QObject::connect(m_ui.stripSlider, SIGNAL(valueChanged(int)), SLOT(sliderSlot(int))); } // Destructor. qmidictlMixerStrip::~qmidictlMixerStrip (void) { } // Mixer strip accessors. void qmidictlMixerStrip::setStrip ( int iStrip, bool bRecord, bool bMute, bool bSolo, int iSlider ) { ++m_iBusy; m_iStrip = iStrip; m_ui.stripLabel->setText(QString::number(m_iStrip + 1)); m_ui.recordButton->setChecked(bRecord); m_ui.muteButton->setChecked(bMute); m_ui.soloButton->setChecked(bSolo); m_ui.stripSlider->setValue(iSlider); --m_iBusy; } int qmidictlMixerStrip::strip (void) const { return m_iStrip; } bool qmidictlMixerStrip::isRecord (void) const { return m_ui.recordButton->isChecked(); } bool qmidictlMixerStrip::isMute (void) const { return m_ui.muteButton->isChecked(); } bool qmidictlMixerStrip::isSolo (void) const { return m_ui.soloButton->isChecked(); } int qmidictlMixerStrip::slider (void) const { return m_ui.stripSlider->value(); } // Mixer strip slots. void qmidictlMixerStrip::recordSlot ( bool bOn ) { if (m_iBusy > 0) return; emit recordToggled(m_iStrip, bOn); } void qmidictlMixerStrip::muteSlot ( bool bOn ) { if (m_iBusy > 0) return; emit muteToggled(m_iStrip, bOn); } void qmidictlMixerStrip::soloSlot ( bool bOn ) { if (m_iBusy > 0) return; emit soloToggled(m_iStrip, bOn); } void qmidictlMixerStrip::sliderSlot ( int iValue ) { if (m_iBusy > 0) return; emit sliderChanged(m_iStrip, iValue); } // end of qmidictlMixerStrip.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictlMidiControl.cpp0000644000000000000000000000013214675771705017665 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMidiControl.cpp0000644000175000001440000003564014675771705017665 0ustar00rncbcusers// qmidictlMidiControl.cpp // /**************************************************************************** Copyright (C) 2010-2019, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlMidiControl.h" #include #include // Translatable macro contextualizer. #undef _TR #define _TR(x) QT_TR_NOOP(x) //---------------------------------------------------------------------- // qmidictlMidiControl -- MIDI control map (singleton). // // Kind of singleton reference. qmidictlMidiControl* qmidictlMidiControl::g_pMidiControl = nullptr; // Constructor. qmidictlMidiControl::qmidictlMidiControl (void) { // Pseudo-singleton reference setup. g_pMidiControl = this; // Default controller mapping... clear(); } // Destructor. qmidictlMidiControl::~qmidictlMidiControl (void) { // Pseudo-singleton reference shut-down. g_pMidiControl = nullptr; } // Kind of singleton reference. qmidictlMidiControl *qmidictlMidiControl::getInstance (void) { return g_pMidiControl; } // Clear control map (reset to default). void qmidictlMidiControl::clear (void) { m_controlMap.clear(); mapCommand(RST, MMC, 0, int(RST)); mapCommand(REW, MMC, 0, int(REW)); mapCommand(STOP, MMC, 0, int(STOP)); mapCommand(PLAY, MMC, 0, int(PLAY)); mapCommand(REC, MMC, 0, int(REC)); mapCommand(FFWD, MMC, 0, int(FFWD)); mapCommand(TRACK_SOLO, MMC, 0, int(TRACK_SOLO)); mapCommand(TRACK_MUTE, MMC, 0, int(TRACK_MUTE)); mapCommand(TRACK_REC, MMC, 0, int(TRACK_REC)); mapCommand(TRACK_VOL, CONTROLLER, 15, TrackParam | 0x40); mapCommand(JOG_WHEEL, MMC, 0, int(JOG_WHEEL)); } // Map accessors. const qmidictlMidiControl::CommandMap& qmidictlMidiControl::commandMap (void) const { return m_commandMap; } const qmidictlMidiControl::ControlMap& qmidictlMidiControl::controlMap (void) const { return m_controlMap; } // Insert new controller mappings. void qmidictlMidiControl::mapCommand ( Command command, ControlType ctype, unsigned short iChannel, unsigned short iParam, bool bLogarithmic ) { MapKey key(ctype, iChannel, iParam, bLogarithmic); m_commandMap.insert(command, key); m_controlMap.insert(key, command); } // Remove existing controller mapping. void qmidictlMidiControl::unmapCommand ( Command command ) { if (isCommandMapped(command)) { const MapKey& key = m_commandMap.value(command); m_controlMap.remove(key); m_commandMap.remove(command); } } // Check if given command is currently mapped. bool qmidictlMidiControl::isCommandMapped ( Command command ) const { return m_commandMap.contains(command); } // Check if given channel, param pair is currently mapped. bool qmidictlMidiControl::isChannelParamMapped ( ControlType ctype, unsigned short iChannel, unsigned short iParam ) const { return m_controlMap.contains(MapKey(ctype, iChannel, iParam)); } // Lookup the command mapping... qmidictlMidiControl::ControlMap::ConstIterator qmidictlMidiControl::find ( ControlType ctype, unsigned short iChannel, unsigned short iParam ) const { #if 0 MapKey key(ctype, iChannel, iParam); ControlMap::ConstIterator iter = m_controlMap.constFind(key); if (iter == m_controlMap.constEnd()) { key.setChannel(TrackParam); iter = m_controlMap.constFind(key); if (iter == m_controlMap.constEnd()) { key.setChannel(iChannel); // Fallback to original channel. key.setParam(iParam | TrackParam); iter = m_controlMap.constFind(key); } } return iter; #else ControlMap::ConstIterator iter = m_controlMap.constBegin(); for ( ; iter != m_controlMap.constEnd(); ++iter) { const MapKey& key = iter.key(); if (key.match(ctype, iChannel, iParam)) break; } return iter; #endif } // Save into global settings. void qmidictlMidiControl::save ( QSettings& settings ) { settings.beginGroup("/MidiControl/Default"); CommandMap::ConstIterator iter = m_commandMap.constBegin(); for (; iter != m_commandMap.constEnd(); ++iter) { Command command = iter.key(); const MapKey& key = iter.value(); int iChannel = (key.isChannelTrack() ? 0 : key.channel() + 1); int iParam = key.param(); if (key.isParamTrack()) iParam &= qmidictlMidiControl::TrackParamMask; QString sValue = textFromType(key.type()); sValue += ',' + QString::number(iChannel); sValue += ',' + QString::number(iParam); sValue += ',' + QString::number(int(key.isParamTrack())); sValue += ',' + QString::number(int(key.isLogarithmic())); settings.setValue('/' + textFromCommand(command), sValue); } settings.endGroup(); settings.sync(); } // Load from global settings. void qmidictlMidiControl::load ( QSettings& settings ) { clear(); settings.beginGroup("/MidiControl/Default"); QStringList commands; CommandMap::ConstIterator iter = m_commandMap.constBegin(); for (; iter != m_commandMap.constEnd(); ++iter) commands.append(textFromCommand(iter.key())); QStringListIterator it(commands); while (it.hasNext()) { const QString& sCommand = it.next(); Command command = commandFromText(sCommand); if (!command) continue; const QString& sValue = settings.value('/' + sCommand).toString(); if (sValue.isEmpty()) continue; const QString& sControlType = sValue.section(',', 0, 0); ControlType ctype = typeFromText(sControlType); if (!ctype) continue; unmapCommand(command); unsigned short iChannel = sValue.section(',', 1, 1).toUInt(); if (iChannel == 0) iChannel |= TrackParam; else iChannel--; unsigned short iParam = sValue.section(',', 2, 2).toUInt(); if (sValue.section(',', 3, 3).toInt() > 0) iParam |= TrackParam; bool bLogarithmic = (sValue.section(',', 3, 3).toInt() > 0); mapCommand(command, ctype, iChannel, iParam, bLogarithmic); } settings.endGroup(); } // Textual helpers. unsigned short qmidictlMidiControl::keyFromText ( const QString& sText ) { if (sText == "TrackParam" || sText == "*" || sText.isEmpty()) return TrackParam; else return sText.toUShort(); } QString qmidictlMidiControl::textFromKey ( unsigned short iKey ) { if (iKey & TrackParam) return "*"; // "TrackParam"; else return QString::number(iKey); } qmidictlMidiControl::ControlType qmidictlMidiControl::typeFromText ( const QString& sText ) { if (sText == "MMC") return MMC; else if (sText == "NOTE_ON") return NOTE_ON; else if (sText == "NOTE_OFF") return NOTE_OFF; else if (sText == "KEY_PRESS") return KEY_PRESS; else if (sText == "CONTROLLER") return CONTROLLER; else if (sText == "PGM_CHANGE") return PGM_CHANGE; else if (sText == "CHAN_PRESS") return CHAN_PRESS; else if (sText == "PITCH_BEND") return PITCH_BEND; else return ControlType(0); } QString qmidictlMidiControl::textFromType ( qmidictlMidiControl::ControlType ctype ) { QString sText; switch (ctype) { case MMC: sText = "MMC"; break; case NOTE_ON: sText = "NOTE_ON"; break; case NOTE_OFF: sText = "NOTE_OFF"; break; case KEY_PRESS: sText = "KEY_PRESS"; break; case CONTROLLER: sText = "CONTROLLER"; break; case PGM_CHANGE: sText = "PGM_CHANGE"; break; case CHAN_PRESS: sText = "CHAN_PRESS"; break; case PITCH_BEND: sText = "PITCH_BEND"; break; } return sText; } qmidictlMidiControl::Command qmidictlMidiControl::commandFromText ( const QString& sText ) { if (sText == "RST") return RST; else if (sText == "REW") return REW; else if (sText == "STOP") return STOP; else if (sText == "PLAY") return PLAY; else if (sText == "REC") return REC; else if (sText == "FFWD") return FFWD; else if (sText == "TRACK_SOLO") return TRACK_SOLO; else if (sText == "TRACK_MUTE") return TRACK_MUTE; else if (sText == "TRACK_REC") return TRACK_REC; else if (sText == "TRACK_VOL") return TRACK_VOL; else if (sText == "JOG_WHEEL") return JOG_WHEEL; else return Command(0); } QString qmidictlMidiControl::textFromCommand ( qmidictlMidiControl::Command command ) { QString sText; switch (command) { case RST: sText = "RST"; break; case REW: sText = "REW"; break; case STOP: sText = "STOP"; break; case PLAY: sText = "PLAY"; break; case REC: sText = "REC"; break; case FFWD: sText = "FFWD"; break; case TRACK_SOLO: sText = "TRACK_SOLO"; break; case TRACK_MUTE: sText = "TRACK_MUTE"; break; case TRACK_REC: sText = "TRACK_REC"; break; case TRACK_VOL: sText = "TRACK_VOL"; break; case JOG_WHEEL: sText = "JOG_WHEEL"; break; } return sText; } //---------------------------------------------------------------------------- // MIDI Note Names - Default note names hash map. static struct { unsigned short note; const char *name; } g_aNoteNames[] = { // Diatonic note map... { 0, _TR("C") }, { 1, _TR("C#/Db") }, { 2, _TR("D") }, { 3, _TR("D#/Eb") }, { 4, _TR("E") }, { 5, _TR("F") }, { 6, _TR("F#/Gb") }, { 7, _TR("G") }, { 8, _TR("G#/Ab") }, { 9, _TR("A") }, { 10, _TR("A#/Bb") }, { 11, _TR("B") }, // GM Drum note map... { 35, _TR("Acoustic Bass Drum") }, { 36, _TR("Bass Drum 1") }, { 37, _TR("Side Stick") }, { 38, _TR("Acoustic Snare") }, { 39, _TR("Hand Clap") }, { 40, _TR("Electric Snare") }, { 41, _TR("Low Floor Tom") }, { 42, _TR("Closed Hi-Hat") }, { 43, _TR("High Floor Tom") }, { 44, _TR("Pedal Hi-Hat") }, { 45, _TR("Low Tom") }, { 46, _TR("Open Hi-Hat") }, { 47, _TR("Low-Mid Tom") }, { 48, _TR("Hi-Mid Tom") }, { 49, _TR("Crash Cymbal 1") }, { 50, _TR("High Tom") }, { 51, _TR("Ride Cymbal 1") }, { 52, _TR("Chinese Cymbal") }, { 53, _TR("Ride Bell") }, { 54, _TR("Tambourine") }, { 55, _TR("Splash Cymbal") }, { 56, _TR("Cowbell") }, { 57, _TR("Crash Cymbal 2") }, { 58, _TR("Vibraslap") }, { 59, _TR("Ride Cymbal 2") }, { 60, _TR("Hi Bongo") }, { 61, _TR("Low Bongo") }, { 62, _TR("Mute Hi Conga") }, { 63, _TR("Open Hi Conga") }, { 64, _TR("Low Conga") }, { 65, _TR("High Timbale") }, { 66, _TR("Low Timbale") }, { 67, _TR("High Agogo") }, { 68, _TR("Low Agogo") }, { 69, _TR("Cabasa") }, { 70, _TR("Maracas") }, { 71, _TR("Short Whistle") }, { 72, _TR("Long Whistle") }, { 73, _TR("Short Guiro") }, { 74, _TR("Long Guiro") }, { 75, _TR("Claves") }, { 76, _TR("Hi Wood Block") }, { 77, _TR("Low Wood Block") }, { 78, _TR("Mute Cuica") }, { 79, _TR("Open Cuica") }, { 80, _TR("Mute Triangle") }, { 81, _TR("Open Triangle") }, { 0, nullptr } }; static QHash g_noteNames; // Default note name map accessor. const QString qmidictlMidiControl::noteName ( unsigned short iParam, bool fDrums ) { if (fDrums) { // Pre-load drum-names hash table... if (g_noteNames.isEmpty()) { for (int i = 12; g_aNoteNames[i].name; ++i) { g_noteNames.insert(g_aNoteNames[i].note, QObject::tr(g_aNoteNames[i].name, "noteName")); } } // Check whether the drum note exists... QHash::ConstIterator iter = g_noteNames.constFind(iParam); if (iter != g_noteNames.constEnd()) return iter.value(); } return QObject::tr(g_aNoteNames[iParam % 12].name, "noteName") + QString::number((iParam / 12) - 2); } //---------------------------------------------------------------------------- // MIDI Controller Names - Default controller names hash map. static struct { unsigned short controller; const char *name; } g_aControllerNames[] = { { 0, _TR("Bank Select (coarse)") }, { 1, _TR("Modulation Wheel (coarse)") }, { 2, _TR("Breath Controller (coarse)") }, { 4, _TR("Foot Pedal (coarse)") }, { 5, _TR("Portamento Time (coarse)") }, { 6, _TR("Data Entry (coarse)") }, { 7, _TR("Volume (coarse)") }, { 8, _TR("Balance (coarse)") }, { 10, _TR("Pan Position (coarse)") }, { 11, _TR("Expression (coarse)") }, { 12, _TR("Effect Control 1 (coarse)") }, { 13, _TR("Effect Control 2 (coarse)") }, { 16, _TR("General Purpose Slider 1") }, { 17, _TR("General Purpose Slider 2") }, { 18, _TR("General Purpose Slider 3") }, { 19, _TR("General Purpose Slider 4") }, { 32, _TR("Bank Select (fine)") }, { 33, _TR("Modulation Wheel (fine)") }, { 34, _TR("Breath Controller (fine)") }, { 36, _TR("Foot Pedal (fine)") }, { 37, _TR("Portamento Time (fine)") }, { 38, _TR("Data Entry (fine)") }, { 39, _TR("Volume (fine)") }, { 40, _TR("Balance (fine)") }, { 42, _TR("Pan Position (fine)") }, { 43, _TR("Expression (fine)") }, { 44, _TR("Effect Control 1 (fine)") }, { 45, _TR("Effect Control 2 (fine)") }, { 64, _TR("Hold Pedal (on/off)") }, { 65, _TR("Portamento (on/off)") }, { 66, _TR("Sustenuto Pedal (on/off)") }, { 67, _TR("Soft Pedal (on/off)") }, { 68, _TR("Legato Pedal (on/off)") }, { 69, _TR("Hold 2 Pedal (on/off)") }, { 70, _TR("Sound Variation") }, { 71, _TR("Sound Timbre") }, { 72, _TR("Sound Release Time") }, { 73, _TR("Sound Attack Time") }, { 74, _TR("Sound Brightness") }, { 75, _TR("Sound Control 6") }, { 76, _TR("Sound Control 7") }, { 77, _TR("Sound Control 8") }, { 78, _TR("Sound Control 9") }, { 79, _TR("Sound Control 10") }, { 80, _TR("General Purpose Button 1 (on/off)") }, { 81, _TR("General Purpose Button 2 (on/off)") }, { 82, _TR("General Purpose Button 3 (on/off)") }, { 83, _TR("General Purpose Button 4 (on/off)") }, { 91, _TR("Effects Level") }, { 92, _TR("Tremulo Level") }, { 93, _TR("Chorus Level") }, { 94, _TR("Celeste Level") }, { 95, _TR("Phaser Level") }, { 96, _TR("Data Button Increment") }, { 97, _TR("Data Button Decrement") }, { 98, _TR("Non-Registered Parameter (fine)") }, { 99, _TR("Non-Registered Parameter (coarse)") }, { 100, _TR("Registered Parameter (fine)") }, { 101, _TR("Registered Parameter (coarse)") }, { 120, _TR("All Sound Off") }, { 121, _TR("All Controllers Off") }, { 122, _TR("Local Keyboard (on/off)") }, { 123, _TR("All Notes Off") }, { 124, _TR("Omni Mode Off") }, { 125, _TR("Omni Mode On") }, { 126, _TR("Mono Operation") }, { 127, _TR("Poly Operation") }, { 0, nullptr } }; static QHash g_controllerNames; // Default controller name accessor. const QString& qmidictlMidiControl::controllerName ( unsigned short iParam ) { if (g_controllerNames.isEmpty()) { // Pre-load controller-names hash table... for (int i = 0; g_aControllerNames[i].name; ++i) { g_controllerNames.insert(g_aControllerNames[i].controller, QObject::tr(g_aControllerNames[i].name, "controllerName")); } } return g_controllerNames[iParam]; } // end of qmidictlMidiControl.cpp qmidictl-1.0.1/src/PaxHeaders/android0000644000000000000000000000013214675771705014552 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/0000755000175000001440000000000014675771705014617 5ustar00rncbcusersqmidictl-1.0.1/src/android/PaxHeaders/res0000644000000000000000000000013214675771705015343 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/0000755000175000001440000000000014675771705015410 5ustar00rncbcusersqmidictl-1.0.1/src/android/res/PaxHeaders/drawable-mdpi0000644000000000000000000000013214675771705020053 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-mdpi/0000755000175000001440000000000014675771705020120 5ustar00rncbcusersqmidictl-1.0.1/src/android/res/drawable-mdpi/PaxHeaders/icon.png0000644000000000000000000000013214675771705021566 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-mdpi/icon.png0000644000175000001440000003511114675771705021557 0ustar00rncbcusersPNG  IHDR00W'fzTXtRaw profile type exifxڭin$rs^a9wY%u p dgc?-hb*5-bw~~?s6޿?3{{x~Yjxy~BK} |o߸FwΪzLC~깅\7f)܏e %~.UmE촃.[uwvMOj(lp!_B +;M߱{v7]V︘#|3-sO޹b\k֝1o[16Ns%Fr?nޗ+KoD ,.$-b܇+RABfm*qĽLq> BiX1&J RL)TRM-uC9KVJ,RJ-jZj[ %S˭Z{hz|އađFeFό3<ˬ; +*vP2;.!N8O9ӿ|VͽJ}j]^ g֌ѱE+@@{.FӚɊd,gq%tw~V^7\o3ZfVm ]' 56}' =rÄ|E-\&UYX%aT.26?fTnP7=6B(gH-f(VGy% jcfCnd&oǶW> e֣h;dJm_j`ČuOXO|;ﶬw2ÍXކ5O^gfYA \ kakz 'mu Bf}[ '_quҶoCC|IOHjM[O (F$wsL+z?F$%wHCe|43kq2Yf8TW(掓Dʑ3Kw}ΙAdhbAf85Ɂ8o /bg :|@Jl ;cQBa>6O0ٹ=941p}`p G*ImG@Ȁ< ZxX}{;f>^k))⒟̳pؚh5@uv'+FЧ&4,m^m^urD4;G| _0mb)&О81Z4B

\u,`$L=䜿]%Ob AS,j՝8Tu#Yp_z 0<@%l!TdL,@gcrv8=L1qF^no.t1?rbC&M$=*4MJ֠#4|`71'O?$oRuD8+%h*e n~fYme2\# Z 6@G[ .O` +A'EE-Y6mcrD05qbhQ, "U۔~CZP_""e Pq ̀t,c#h!Ձo̬a`gr~e`#Va[a(. Y}@j5Ӯ| p< \P"C)Q3HTU;by!P2nX@kRXaRHdۅgs3|숵άE8ղYFb4P5?" 'ID_nDNy[@Dcn9T=Go\)/fwU;TT{QKW$-~oYyP.!lX.B ( puIwk7z;h?>?\#}'H5W )dV7j AxK߬a!Nu<3, b4cՓ}F)Uo"&ӏ 8ʚOٕҦ1:oڳLvɘ/"C"UfaJmS gL-s5=Sѡaf!v/ѱDrRWڛqNETa ~bQ`LG(jҭTqLi3Qi:)ЉmnP;UY4p'̮#V*˫l#e6VSD*K@˳o`U\ H&U4:; nAr߉ `.Y>(C1̴h?e^BI4ŨL ";etXMރΉupЃ8I]t.dIN,=y)LLDlI;xE>:nYTb(XR%tja+V=(6¡" 7A6;@A|Wje{KuU_g -6VV>hbeJB/"%qx)曉MSi!+Nl$m)%BM/HBZUE di3㢰+1x'KкV.B2ꊞAtH\!V`@FNL2ITt-2`@PyI-%Q7^n8^ )EW rSB[PIk³IVtRPmҔI ni.H]TZڒr #HsAb& aF|,-lME1p9,KhZJpKG Y ՙl 递@#0.Dnb.$Ӷ<#% i6œu\+b x.!S1^{xSޚvؙ>=a9 wWNOԿf~eϦA& A'"{"KY]5^*|ݧw&iKRyl1S֖v$\xD%a:dsi}$̖#A^dRdTu@qC?%jym_j`&/H[Gt( 51@0hZFeBMu6h[нIArGV=O#Bdh[I dh1(x[e~=QF|ex5*ò VFϧUt6FENX<j]XsFvsx(Nvd`%ma]BGLt!Ꮤ9Ğ\8 2w_6jfG2Es %}E.J(aMLXJ٠.ӧCȠ=چ)v 2Vs+e"hg5*WB ^]W"B8^-`7&?V$ΣuBT:m-2*Nn8h) j%HQw$Akjwzʂ+ $.1d%ITB4yg@Ye=+Sd d c\r!%P/ XgYɩu#X[*JM 户'ݪO<͒AkԣX A)1xD~7ץn(SȉlRbj?2xEd4Er4x[qI*pCkM1xCryp4>q"(´|~8xts@ %& # n7J=餻EYJuVԦ5Niks# <ݔ/wM0-,UZ ڙ+;W8riU:BmH_Div}8j"=h%*`fANRp XѰꪑR#dYNh G9l9 J*2݆! J R~|,Υ2Ȑo]Uط\ɫ{B}|輆ƙbTT?bf1:P*__z7ox Y"S"tMPb({Q3{"AG;䅶72g@lH$~ 0w4G1z38ƃ6JQhBeclTf}DQQ޽Q8 gVks}.#Qn"7#LvZE6Bа ;&C`& nyԀUkXclF?J>8wJgBҙ^Y"UڭN6Im2 ZکIn -(:F.Ĕo/0ϕIVM7\O !q>=/H}JCڧ|ДvipW!CX5VX~ʒ)ԡ*ݺ.N ڡ_m(׋= ,eJݓ +6񨪏>ҡ%ńrw;%*^y}=s5D c u}-x^sb y} "w3'%`PFE `w]&weZw`H <Zr xk|[Y, R"ZܠfQrW۩Ft 0@ ﮤ GR(佅!%yނ5BAYՆv(rjS}a\ D틀⻨FDeEu$Rh]5vn[gCBΌEo3hjiIUBj@ Eh m$sQq+&tDvTqS(X: lXӱPK]7~V;j]!\|̧&,w3%8 ? ߨ!\P*,Z5o ($n*EafvB("boykًFq:D0셨xː7(iH5\_2US%HAnåj,Tl>XMgz7lE 5Xټho|T#λ^IY`d7jTG`r.bڢLcIQͺ^vkO-56i&reX q:ۦ|jYE$XG"JB| W"`\nw0a-^>E¼(!>EsMSsկ ő_tP U3IQ<҃?BPx06$XhFA}I`IY]+¿j.B1?”yqJ;6MA$k.^t[rvti;>rsprmd̫h^d6hJۗ[I$"VqS-d(O !״7Uv4 ԑ B: 2},2ũFU%֬󔚌{$=*ً:,/zQǾ}@}7~tḮ,rTԾǺnH!@*Os*Ou~YׂaJx_3W[uD iWXԲ~%Nbn)\S8GU4Iͫ]:,Uҿaz 2n~}`91|wwL^Od? bPx{kNp h{ν 4`Tqa:r.)E!cfڟUx\tKA=s]qSx$:8KO!TD-6Uyf5AjyK $D*\Pտ0gC5tCUvϕf ubE1upCLT)񱭩j56C u[<7xT虠vta>vp9r2[n[]8iZ I?LHC:nJtc?Ϯyeh0pK~ >_$tlAUO!qyЕCM7.NkpWt=6. 9]w$ Mgw`։\UĤ$}?!jԈFf!zށi*q gȏ:d};󿔮 DKgһښ=a"t]q/٘RN\UN?/.Pg̳OcD3Qj*Hry~X='^,.-E*v޻y^(VfL&QMH)DSǏ۟3%(-=rh=o 5U("e73 d|wR4}dq{bSgqUR4<؟P_G*5)@- t)ОJEp%EOP4ԱecUa~Oܰ 7/m2':n ۑJRVRL(][{YG\ęAtR47qWΜ>M4(1dvq:&'^Wz:IZ{lb,޿[6iGgC=hp6u/`%4㣣ulX4_VرaVVV?+Td}їEES5~\ R2=7JReSLQZ^"5(M'eOYiAyȤ M.GHm[6p;\o*45ֳaYfQ˱ˑ]ٴ'rh&ZZuM(薟ʪZTVRׅʍh D"45s.rz T\|vCG{+/gj:HО7{p SئE8Ra444`:FFfk`D*]p\RgiƧ9yv}XGUe%;[oswoZGeec7Z4hQ[SM|y뷦Lq'j_~9LayqoR RZZa465 #7hhWѭ呈+S<("$>Ȧu-`db PGO1|L{k 9,MCC=ޏ?DQUǟJ_aB; 1HqF@arlNþݯr;"R]D"I$6TR &Μ@t4;;_;)/+!gcl^AIqE(:a( pLISئEY李y''ٱ^> Ξ^Br38p*YS ]ƻvCScx,F4M MLLf,χ=ח uuzx 4 r C!JY\Xdb6<mė(FZJ* xƖN,[ioIgim&\^7ƦyB^8{ >ۏg˂\'EٽYXC A4cjr(,//H$KSK3Ř߶)**bl޵Ph@ KlkpO3Tu37;˥ioB CY:/ XvQ3|kDa9DQ ݛ8w4 K; F YXXBZZ[()++!$Ja*DW$+z[ 7]J`j6 "KU cOxm<2 B4vp)eRJ&EQ5 DZ`6H۶QTX"{QUcG15% qۼ{//?Tĕ؁v ?gh2&ޡrStwfΜ4DQUTEq̢ZdVף܆>"U4**XE*rՔLLah:@QNQWSSö(.. \t<EQx~XfZdC1p!kԳXUzؿS pbzLgYNQ: --QpЛH!IJd~G.F'З#'32poy7qrn^Z%cDMU9 7|$rҙi_af'.F./P58KKH!ogذq[6cv^{Ѭ R4@HeMV|n-vs{NKz"i֍afsp6`|)RYYIK{ ~XVbƶN4͸C<8T CN A^ D@*EU)+SO?Gg5;PQJ299 u+Dyϻxij̙C5&'nL$&XUc9;4elۨ**ea>hؐ4 $b+1tc>p#C寧}4|[پ}| Jjh3}nKt!9Y~s#/բ(X>݌2;=TTQUjFZ9<̯~k܊?Pic7 '87ީ n}챧69Ì pHs5/0X>.FFX^C7A477!eFFo05=8- ؁Q·6¹ Ij2|9}SOYx!y齦ĴlZ;qd46Mcc3.1=%6W5!;+V [X(EcK'C$P085mOK0=5Qlǹ~/ؼyK S'"U~}y_As/F<$(VqƶNa5Etl)J$'(,,0??'ۻڅkl'H0 oiGלWkGgxTW= SX.. cnjg}hvBeѷ!=5¼(==ض"K5%T/5 )r,^sJwc2I~DNk*U(.-C>/btd}+|?%IXr1h?*Vq_'S yM2;ts^ !#ﮫFIy< sSR\>o<P*jsعs'ǁsKG¤##.S Z3o rw<x,׻VUTBl;H:"RUDZ4ThTٓtEii9%ż_hބan %2m/ۈ!EY+HQ֐ykiq =̙ir+׈-]GU*#H`1]  2 SXcL WCT>V0L[@EaRf[C[=.Lp gyË?^7Y G\:( ";o 1NI&:ԸMnJXjZȝu_bMS eX;^]`E2'Nn`sCϧճ“39:g*,Y@~(躎bgء˂^7e<ƨY;0Z1]<pӟ"@BRr#2Yc {% qmidictl-1.0.1/src/android/res/PaxHeaders/drawable-ldpi0000644000000000000000000000013214675771705020052 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-ldpi/0000755000175000001440000000000014675771705020117 5ustar00rncbcusersqmidictl-1.0.1/src/android/res/drawable-ldpi/PaxHeaders/icon.png0000644000000000000000000000013214675771705021565 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-ldpi/icon.png0000644000175000001440000001211314675771705021553 0ustar00rncbcusersPNG  IHDR szz zTXtRaw profile type exifxڭi+ > \57%m3UzDf"q#ՋKZjn9{>&}\ >' 9bIu|77BL#'.u]wSYDSLx_?wGL3CO?8fhAB'\&kLp4H-X);Ƙ\Zf̼J`+y{LF!}xaźD`0F1 ś^aP/̕C×@9+eUIbne1!B!jBR!rI UY,RRn*:bn)>+*uR~4XY)))Qf-Ziw95\\/%RjiXS՚kڛHJj˭Z9{r];ow}Ȉ# yQG}"<ˬ;dŕʪFJn;nvIGO>Nb`-X˔=W>qg81IƋ185$Ɯq曐*,RE0 z‡/1o7'97-y{0;46aEka-q8® J'֚&mU4;$Iev CzIJSe-j٭gѐQ'mHBrG;19՞ įs& uGX>ܩKK/#.C'2+lsLaOdr?wva%Tj8 ;5gd,4f ;C_/fi}ǗG^~V{_TTԌ[7FRn#0&/Kم\QfQȚ:4f{fw&p\6B^V䁜%-s y'>@RDr@ړ YԦ=IJ:䰴fj/2"$&KӪtG`dx8 :ZZS ꜫ Yֶ2sJHr::}ߋ*foB}ҍ= o}o3'דAAgY 㻘~{1t\"ƊD!UXck}(?j@_Imo>ֺxt!ȗ0txIZj1? |NX]DQ@h"Pƺ+Km,9qnF uDTBf%ik`AN1]Dl=9tWvy˳}-ړc_ ox ]skF" zdo"6LM]ؕܺrl=?ۛ'CHEh(l{\$ O.i~O+'MQ"D dJQX>n☃296h xX|FIܯ"eC0PZe][Cvm{ N(i'D*BQcѐB2;VBQ,\m؃`pS" Plq4{:ktXF4NVlhYj|V0Qmediis3#$?q?(ګ}6oٝΥY'6{׌K)8CEmg}NGZhmwq2T(RH`v`=3nt]t#(yy`ʲ6=7~8@{O{}MͯKJ4Zؚw>cB%,} M SHa뚛mᵹ@a-E&Œ,~v2=Տ˾u.^j]ޞ=잻ًjsV("Y IDATXÍipo=Zjq1g+3Wp;*'WRW\8qbDZ`1B@ $$@3LԼy;_r)@@DB  HEJ9K@jckJ6`ضM-xi3f浘+ a\gFv(Ą c1``4I 8 4'%P4"hYu>|nLDd_Z*c#p@icq̝\좩 _Nw܌ >׿kFhmnj2I^[c0$R&4$HR!BIJUj":0ġfm kKfe!J)Dmsƒٹ[ ehtcpσx!*qJϝJ"x3MJaZt]cG1 K"}!4N-㪭xa` xg '_PDVi $R*iKsE *Y~c==9xHd(|LMAR idxk)Oe K ćzD& @)PWO|g7{=dl(ָӇE7>v{HJ}?DaRd ߈4t6ő9ÕD)2&z*ʊ9S8{cMm5 }ftkC_HYY0 `-U ())j6Sú[ofDzYj ,\L[GY>Ï=[#e: Mޏxh?0F`8}sRtMcnYkW.&s}o=l7UWs1++kƮu!TMZy.,*Vz:;xBYr2׬m( nV-tK F`ӓ.tK3/9 %ʥbVr#2r/S-u e kjcEjlrO]WAo>894]̯-'shSKV>p8f ~˗,]d%mvp3:`fesx+9!0< y }{<^P aCh /??j"I,_tGGqy!Yd\n Ӆa4 s@TZ9B ˖ RPOLii)ٵ%+ bak*]ISJ}LdZ9!cWݝ`dPLˣd"ºgj^"S1 U4ŷ4RN C?z(dX`X,ƪ5ؖ;ocμ*,GnလpJ9ͺc&ǏR lEIt^$;M^~>PC޻bnn"C*ndr" EZPTBVVM ĒaxXccsͣr 3-4\e񭚌0@JM9s1BIi04>"(X /H$‘FARv l²lloC6m+=< i000Dgw7e3좠 e1ϸо H&HFKJ,°,u)nlR흿4D<3h8\ϙV,[ƤXJ9bhp徦o%HJJ"IG"8 p\9|?&`[45FՂD8?Os` xqrNHSJ"(!h16o?HL>Ivcj @)třO\׶ @+nAڊH^#&yגM?ը?ziS b.* 5Q}<^-C8"mc[VvrQgKtMc̮^I{ۧ<;DdKPPdbi(eac$eaYHNy`J 4P 嫘>{X&t !Ym[N. JC[ZXU֧Nwl,–M N8m?_/# :GY7l+nAJ-m_JlJkk,OJ8*Q RT02OW,$}O)%x^? q?Iob0GFPBq1L ]JaK¯X'2I]Y)H \nv2FPTqSö-wߥ&>RS㙝1}v3IENDB`qmidictl-1.0.1/src/android/res/PaxHeaders/drawable-xxhdpi0000644000000000000000000000013214675771705020426 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-xxhdpi/0000755000175000001440000000000014675771705020473 5ustar00rncbcusersqmidictl-1.0.1/src/android/res/drawable-xxhdpi/PaxHeaders/icon.png0000644000000000000000000000013214675771705022141 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-xxhdpi/icon.png0000644000175000001440000011145314675771705022136 0ustar00rncbcusersPNG  IHDRF"zTXtRaw profile type exifxڭir$9r:vL7=$ݵLkLn*@?}].j'/||~ _߃/"??E_M_xɅP~@Mm|}iDiFӟvn2Ϥ>p?.'*_|5? okbH #p 1x3{%=SHY_FK=RL+Rv)~%wwށ/wy(f֊qE8PpVD[Kʭ O~ۊZ|Ywa0!_C*o1Z9F#)IB)q3ȘSĦGܛXx%~^TOI5i cCK)XijZUܰdيU3kmPVz=DI^{{F|z1fiYf6s,gUV]kλm8JSN=vg\romD-|E-H}5^5Ӆ%p(fD,@M b[9*r(Af?\0 ߱+rvk-;s C;闸!j[H^>U5eGހͨڭk3Kyr_6Ph:d0KTI6­1{qN햩u:!77uיN$ (VL\^ctSkz[ׄ^LJ)_/d>?~= f>\cZ=%̋iB'ڄ#k2l=[t瞍o>[Z1q'6N=KExnV~pE,?D` y'뇑aV7{\Z7SIBSpG_&d¦Ng#sظd,Ǹߨ]M9E^\iM9WT~[kFq;)68e(ۃͼmtM=t/y&hD;Rg+X-QNaf'vo 0xgygjK%7vLfYM}WVv(T!@Q eoozKeV[3Jr aRL? [q|B=ב>TwUBgu u%d#fxQn9p Sй.sz.UĴ`Py7aU 2 %7]Z2Zzdin>20ZLp7* Gz {Ack |{ [ZOfҎ IB8Ha 0 {"=R8Ht @:6g* R>/տb8ߣ0*Dg v@v5KهLM5rtC,Ov'W4لF<[nTL&JE0FrILKWB.Ԡ1Q滞5(~#ׁHtX6h'X/UU΃!nw3pȫ'EQ j ^CSet~tEX;$!0hpqBm zH|5H(v|&K=1<@ "JU*&Ø.%s-`:'gfLW* "$p>(VXdڄIjdoxoI SMr {.Y?-SA] 3zZ D,φP0DTYӢp2߉%& 3(]|"׋GA!XVX}%wkz2p8;N/UՉ i jE =Iȝ| 'Qrm8 3&zVX} JUY S2N륛@L;C. ࡨA j(o`D%~ 'hIM,in*HTQ p#EAE@tC}Fk5.^`}"6(X*Y*^^wr!ܐgUC/P,O#&WFhOxe2\M^CKf V<]U0=yp ?>69Ե'^Ð%& ^:D&uʢwr'?eej@`<$ dgI 3"bH0;n .cp:d-z{Ȉ*",&=-X <)l q'wC15!ES1@$a$DOJ˼~Z3e MļyrU1)5Sl/o&=԰!CRhAAR uaDsHukIRwN;rQPcPP(qzUԹk*j#nœOULA bԫ> rE͌ G"2!8ƙ pyBĉ`Tuf .Z$lQgj?I1pDjʃ=6M+68*m%VuG]I&G ӥ&Lr>~0$,,LnR{j~@;A_YxmBGt !2a;ᓞ:CѓK@(J1Qu3WY-S-&fj1, p3A}oN m bm t@?Y[oI ǒ Dj/ˢ*#GCz:@q֭"e!R\ٙJ E]V0DAZNWR+,E 3dSI]zOϫVqeD5Q}F  EZn Pc4*>$^6CvNw45&|ۣOsnKVj(H*i}_*KĀ&Ԕa> BG-#%]=) 9@`U"9"|k a !z$$eP jƙllsI̾F>&Lj~۪{O﷝ʇ/m'Rs՜[݇1#986L`CSȔxkhiloy %Dj/4Jno2mH #YݼcNӁWF CIєΌ9nDw$'ߧ|~9~~Nw!Vo6[uD!9ĄNFhy!pBHY 9h*h'g'Q$$0FcG*9yzF_eu$J;~sd7|zւ29ۯIv1u R" ʬ%W2a` r(m/ kέxun'jjPSdC>:^Nf,T HB}ՄbK#NU6 uNsDg:مKP?Kņ6ki~RXV *Msk&NtiD>F"p>;,#0wg*چ~  } ydH(= ],4hYv"O̠~SH8$kIh|z)Q-묇6Vi @Ļ%rP!i$DB4@xN* wE+1C1l=:^Ԉ.HBsWmEV‡Q!D)|UU]$*^RiжYg@mq Ӷg׉O'v,Jrh^@L{(!7}1dH] m-5_W-aV|}]S@PEtӔhm6m7MZ>5ߗ$V*`mBmL`iCF&T&έagLV;Xnk\ASjP<<E"FXQ&XRpdVTEƫAHUцoDAyG?U"D_x~t~:~& ޮUBKKd]#2ڔ@xiFŐ7\iS`Tm>Ki_tRzEJɩtw}p/6.CF!EU ZxAOQnisH6tmPPFܢ&!EhGtXhHŃRaᙝW"԰my]g;:TP0v:M?uN]ؑGD?7HF(#]KmrTAOU<JlZ<^#%LFzTւDא@-T$(ԏ*C>ʒkIXYeO~g} ^6Tg\ %VƾY#L4ӮpW)PrK )2ZAߒDf6nՙTYR퓩g.'U'kf#y<徉J*9%ڔ:l \I_rsGÿ>GuP[p1ZRWwau"j'^/e:߳ݰ:4%1\j:HEx$_QERA'BEܯW˅ 04(:Nzz+LiIQ/=u\ljw,N-d$_,t8.er#T (xS,o5z@v"6Ps]drl^B+=I!zIg2MD~7pB| }$j:âmȥcuW{=~!H2rkNT"Mt&pDя$1U!~yMtt4=+ G{ꪂo:%@D\kc&dx Ըk b+fQY )ѡRҽOspnWQ8\LjN|؎?t>6( 1_ʕQx {RM#QG)(*g}A>(_R\PzHJ^:__V l8ٮQ7*A UR C &RJW3^1~‚GEbF$TSVnq&+0s}Gw|Z-)QJPm"D^ց4 BfDB}G,:Y@tYO-E#{ؒ;;jWqUSۅ"`KWP+\<r-1NxAb&_Y)Յ1񂣐^CL=E%DKXų"-H.{ǣIapiSe-A6DFIJ(F-j3;jSzp-GtnX"mwЉu ҋLƌ,׷pD%l;rv kԱ h4hZ ̿&-G0L=1oyiߢ{HLbg>F&HY'S>G.yOK"|̵kאw{9}>D,`eo@ sRwNӯϜ#4Bc&n+ӟ#W&{8)AZHxל4$ҙaA|9C| 늌Vx$D)HF2&h4b5s* RVIIg71u% 0[gl2/<ũ5#;HyqI ܳK0Aiym( SVdbtHx#ˏHeƓX_Q{4HzуfnLsf<23"СLfa4"}e<]|$B,Yp#dLf(ZkZ\,?yܺquZ3=3_O&WʖB!D!-6卥stڞ&&"Ft:2cCbt"c/8$iymSA恎~&yH!.2.deH̡!&i",^$æH l"֣3u4>5<{OwǬ.>baaٹ9 Wx9A)@ /2<̓N'NsY/ӓhƁ 3ĉAf=N|#us򃀇pCn?ZBR*RH!"4*y):#Ds~cYtFi u:~bCxسg&GE-v$PQ~$E⭣7`EE9tb;# G",9f$aڝ^ݧ؎9V=eb y {:+ZMQR$['D&4ǕswwJZORf1ARa޽ސ~@fzvr/& ,t>Nqp>JfёNqZb2NhPx"/Ak|˛78ٽ#G00OksQVQJb6jB,SӔ^h&!AD^fX:sKӜO6*]Y/SDĺg:p-5 $׮]gccI eH}ܾ} utgo$HCDP!/P{x">CqU$Gebbಹťy)SLOOhٵ{Ϟpcò|&S,tCBgZ4Bzq $M"yF2)o݋=G9D"kN.a?<,E[-j 01`uu۷^R)aYJb~W X]^ܻ}wdc(LDڟSkW44d`ZfdMJdg6sXm(R)RYXe)litiSg?ビ!w-Ln~G/#n+FbbYO>;3%&'OXlXdמ}(e3,-w^vZ-lywBXdfzZFQ,/-g?|)Ο=ťŕϸy h-3ŒiO978C 4T=9OLEdeZ!Y6 cJkR ²,em$7>~>秀Bﷹuq|Ĕ=Ut3V~wDj8n_F IXbuy׮QԘqlFJe)=es ;س]-əN.jR B d\͘Bq7bv&El"DZu/55"8Kir":}s޷zimE Ý)m+96m8.+S,->bYW=$,:N}s,B3.| Ow#J F_0{B+>옙f<3lmvYY۠inc`scey^x ;樔ؖ98LQ,&Oc!(_"%bG\$nzʟ% 3IɊ:_3D$?7{)ڂVv PFY vP?z$߷+hfzsg&ɰ'9chPx"e"P Egv\r 8dHKuzVIVA鵷zd3L6ԫJJepU 2zrBXHx|Sޙ:# o6ZZ_`i=EtK45fjtp϶9^9r7UlqOTJlDŽ:G|p/pJ/r qjki=VU%?=8r/ɓǏ(W4&'dcmRA{};T FFF3 'ڹA{ᅦ8LNPTHmJ}N1N*Ɯ #"trO$eɒO`T %rA vNT ba~ov;y [>BITl˄:ɏÇ' _;x1+°6 m/&r_W?=ݨg_D LP*U9@sgݷu>;W**rFBRZ)S,,E>pjA>A\H# 94`\@?cvjczfd(e4": ڷsjW=;uޤ L,qܧ1me)z=G{cZ GϘ,[J^ON=E#7b-frj)pVVP^-E 1(4J"U-Pt]vDI~J )B1dLB0и +)S贛7lS+LNnR)ڱrm|@JR#C })^8zw{:}RÃ}/nH$ 8:;Iֶ2C٧U*ӓ `0=?plBV2QoP)y)gΞZk015!+{C|׾a`W$QĸQgֱhVdBNM-ZJ.~ׯߠRg& qk`gnvr<`,.] 3פ)>|JA{W~t]c/')c*R8y#o~7 aZ'NpVn>>BĮ{Rq9vOR-(ebl C|7 WJ*[Qbvz~q9>|g˟JR\R.Wg*bANsIsƛR>K$ iaHT۔+ˢr~S\"r;~!k5zW^O3iI޼#ٝRF W0!C F,cq J;l&2n3>?;kT`Ha+B4c)?lum"19`bjYؽ{7bsX*S,Vk‘%ө*) K<)OQuQ3^s)P~۶K?+7x(So~%/!DHJɍ[] ?s%Z[=:sLMO133܎9޽{̩)JTju*zªD7H]Q_9wIɵwv4S@ F>aǐyPٶFZ\ywsSX\BI/0?`ff:g_zy> ݁oJlLLF%r;,L1A3JKf% wo?>łM:*̾V5^k(|`Vo#P.ifw`j"@ZVDHoST)uge*rHmt%x_zo9STNaYhY6B*_Ε+9~%޾t{eGp˻($)8sG^|N~ddKLh0 FI*''t~{Yn !N'$nɇ0zV#CRW@t3uX^Zdyu2νص9vZ`fnFcRD0v <)$e351Νސ[(Q7VH+|XN yQ _1vNżV\ba_>ۯD䰼Bo裤kb3Y}Sncn{|kaGܸsoiDR9wqxZs$x?ߥ~;a6R\!2 ^H?zpΝabeeɐJ;,.Z}}2cTkU >no0@: Jͭ/~F6Al01&J=y,Y~I_BF(bohk >~xJJfgx㕗9E֚R(%QR6RY\ze^~WQ-{5)ZH]x52F$J\a`CrFw 10TRGinl8BJRVXRlnnIfv1srLPZP,Z( o5RJJ {ql>x,ˡZpy,Ujf[!}6F~&\*qüt`7wݣaHŬg+\x^?2};XYo/$GivzX63;o7'?dr!@XRJFշOhq#bl\DK ӳsTu޺VfNH(lKaI<[ZT8q?|jR8)MDHސ^O!bjz۶XY^gTb$IpS}Ft;"'};)vjTW0Y-p[& IY+:}S?f ./n畗T,S:F+Ha1*kV?81*d{BTU6E*}Eے}WPbaa7S8ߠH;qFEi2LvkRHf˜XC\Z吨,#Ldr&IDdө_[9M\ٙi<W^3codIp|rՊR ^98S*$ %W]~}?xxPZ9?8!tJyhHbɩj ߽Ez}vgVրM+U 0KIf0P(($]hw:wVǧOR6(W*TQ$tr䔉$u Cs㓧~:V驟yvl^;ƃ{wY؊R .||WPQ)p-sBJAg(;!,]ec/@wK}wmfhG-5b$}VWliw[![m) $\cgl -kt"4y>N0RPZSp ]sgN%jzX}cZ$N~L3^+ $Ckfu/OgpmSrnU!ǏDgH)L߼tG._H7\pvm} Y6JZq-#wDRq@ԑ&jHa ",q4&x]2jJjt,ed{J ZEbƐ3d9HW%%Q7n?{Gsl|n?Wq{#}ln!> ֎ya|J-؟ݹZdr5e ] Է~O1>QFLs* B@\ab_޸F8 ހP޶mJ2ʲ P줼a\CPni( LLL`Y*d5/jԛ.b#DNg,5d!ON FtYI-i9ܸrChlkD ;Q #Zto}KPͩ;CGS !$w>_P{w?RVH#ekj:>CIheHηq EV~v;.Oaz 啣/Mye0IVVVxc+W鉲 C#~ :m0k鴷ecdC&;s\۶l)̀Ȉa?!4Xl>59IV哏>|67x!3=?ǣwy.ai (9D'H|?SKK|~2a{`)ay{] :{㥵&=;a~H6FJ.obrMl6]Yq?ӌJ'2hFt=pVe\e}mgOH(kZ$KZA*E٤8R)jKYX.^Jʎu@'gvs.]8Ǖ r\tW>ڥ\)W.¹s?IJJB8[1" m3ζmkǸM[8mVJl|.??o79onZk)˧~J7 (;{veC0$Ϋ~Չ$OoCM( H#X3cgWd 2趻oeVS3 qjnCזGq#D?<ÇllnR(رs /‹/pAvTcifgi?wytwo}e;H(+(R ef"YO.\{?z󟝧^-377걗Y6k׿F>?cS\RIj%]pnv?;XNou<?~ĽNj"/cWa210;c'oi ÉX:+t-m5*00t FeY18M庱D77t eQm4_g߾}޽yL76µZ|ex7R(qlJiGû OkI=F'z˼G#FHCMlְ&''xkB,/^gΜQm1?`6G>g>dXEґE7ާƸ B-_+ٝϞ>H'P<~CA?$-%8tMXDAs: ,xQsQ<ؖEsɳǏYz:4[[;|`9fJF5!m=GL8X9v!c,Cf#8}saC8\u?̰޾{_z}n~`˜iriVs/n?r $Z>xh'˶qlǼ>_w"523TRdzu^Xu2n—R Nw=~E)-RZ-8Ebqԓ[Ry %OQ- /_!L7Vy-3^*?Wi48{N_7s؎eCὕmGNJQS^K7x/@#THq\=R(Cџ0Ɗ* iRkl?,;wZ(j5y|!KKlT5fgg27cvOR}crzhZt::. [mCR8I BQ i@#dy,--qVBP֘4O{kv1G^ǶxZB{͟ E+ ۵|5VRRձ7׬0Td(RrM~rvç1%daTqU㙕~%7z{²79AJ%CQt]_wvz$yOkQgV_f n:9$I0I* q8&mN=~,MPkY_]C{갲AV뱾Np :kTuvZ`~~y**P&A*,e6t`B6k=VA ׎{V79K}mtdd%EN{oW׈,wz!373 ,+f8[[[,lR#$_OFi<( OέRLZ̺QC^$2 Y)-h=wV Dzjy ևF5WTK1;:31t=WVRdQX,R*)KF=0-3eLḫ4hoc3y_nrt@k^=G.ЃImhO.}{}њ 3v4*$p}e:هoC͋GiD{@3eyztJ%2ᐍVn-S_žP(J,,A(k-*"h;ܑc-5R(Ĩ:1T(+KlC1IJI-գ|J(˼C&wn]KۑzKϧn#:;fg0$f~h@ &}j1+%0q ߇~&fFaM63'  o/lE Ơ~*j/ o>XnK "QnXFA0ci_Zkݽ3BP`h+ːr;Oҿi3q eSFpSĵ4/8Sض oXqhd$eVws )S'@jA#YƷ%vp!"lܷ7t^Tj\"=f8Z[8K&;,0=Iq`lPRwZb6B!6 R4 u477JjZɟ 6J%|#mBC2MA qA`tlB'eV#_=IijqB @%<%ՉUU'''c[2ݬ9dZSOz0ߥjDFkJ! Dh:,%\@`ݡ3ԉo2 C ¢̀P hm$<9\"<}`8d~ ?cuma"S`zn'B1naQ1em D)Pփ $Ba#q&Gإg 6$t|=rP-9IN JadpBhSY1ž}T-?pnh⏽J?ptX^[0afEht8;1"QUX4$mwÇk+{=67=Dz=R47x~4$[)exd RD*D*3ltNTe!G.S"(Y[W4elFejW̄xb`-GeY)C3;4$̲>bcugJ 6mC^B@ಾgg?X.s7ONh.OlD:  "Ʀb$%6>&ir67Eck+!2R|_ls ǰ[-+A*־3,B}A(FW!Nth3xbHUm'8R0bJDTX* XZ\飇h 3a; !4;d  A\1+$N tb_x0rkAgj:3S=zZ "9~_b۽. [l/lK4L+4+?_EW[0jl67jLoH`v=R~@ۣ벾ߧP,q쵷 utY?=޿z]6lSQ.]2cKңOtvI?ҙΙ(b[6ln'OeZTl:뛛 @qA5d#u] zL ?3JefyfNk gt{ ,ti6w{L̢4FJbR1|6#a6 SF)z"#唠I掠"m9ə0挕𡖫U@yۀTr{+5ʕx%(oa0b0j=Q36*F\Q$}ϣfyt=zA\AhC674זHxoPO("@郓d˜=H K#c);7 ̮l]F"gqz5 ]#u5-<}%:a"|Y4 "`l$M'pP2&`0lf( L#mS)ՙhPT(K%t:-RR0jW KFr}Pz1ɝ116zQ,)K1$B3"TL`8"6&^nЂ=ٳ{ɉ lb8l6 7`eq;)W*t:676Cš0ҡ#^BPV(ի8ceZ6V{H)9שX`|t*fĨ*E:-k;w;'Ⱥt ENn reH i۵W'/!R }j+K6Bπ&áDFB5 i5fZZN-eFk#s]׌T[*J"Y_[cme9&]TŲTSxz E ڈNP׾}"GC.ա]ʼx9SkgTju,; ݟD}w6{z6PT+eb)E8Um7nmq=nݺ Bsa 4&'(U*XaF{l}?$0 /H6ofVkgO8.S3X3Z$NRuICJv%|$ϗȘCh}BdMd60י(r)lGjqKV`&)Bs nޘ\cz6tC.R54~{]_sgdq_z؉EII~E EX>-mZ  `0[w_VeޛY=d*~ݛu/ٌJVH}"K{h19>99hx:!c*AH9(1 &caIl4nef1BxniZGBDyt2sEY8@ʚl 44,lKG!Oll|EƏd99b] G $I<3AAX"Qmh5t4O)h2Ieٌx|6' 62o=ץ񂧞{{{|)U*|՛FǸʸ{2z o=3('4yLJYWk+SQdrWp?}GΏHf}1 gA*RߜFFER& '̓i|;Tj^5d_p9bAV#vVK>)%јO>7z~߼sh/Fo鼖1K2cΈ' )El*ҰUhrmR J4/3O rܾ˯1Əh:[łx3(>ZrBR# q2rd_rf {J!խ v/qiwJ9dWl6=1q_ɇ -|MN* [Wuu."jJo$_~B (P;,z_S&<1кZNq];h4SZp@]F)>3t ,ƙRdp@CKPHdN!U ol_?O M/ GP G/N$2aj)eU)hA4??'ɖ~pǸc5T m6rl?+J<#L&nܸIZ͏ڽB\A7 0¸`M"Yau)s \ȢǠT+e\yM[)|H f1?@ AVhedXD C݉K(eXP(W+:f:yiǒ8sZduY(V Uk\s[[;גi5Yf`RT麛O$0J a܅X[u¾=7SYAQP?JW,(^KkSoR7_~C$~)"4M?d>] EY*C N4MJ!BvKWIRI#@C3HP0Ar ^nRtz)*~rӣ+t۩z 9*u%@yzT[9ZIX!oA(t$Ԗ'DCe+VpSiDIiFJr.ݭm^xU?*Ѳ;)_VS*TE RgިvڢPՀ>͘NޔVpnGM̦d^"=A<#`4`6zRL/N$FJ?tPNn)za]`,KDk<"Y,7)8.*v_0MV\Z*\nҫ5ɿrfE=b"Rm,bdM8Usig]v^ѥT.$ hh8bs~z)vw\K$(a8x`0;N%(5 COSyuĸ2%9Gޫi2wϖߍ&U#}&YTQ H& Vq_uN~ +3 ^x5xOkFWoʸwTIV ,3lBSAT+t]:.fT")Gh2h\ewJզT:Ápڀ^ 9#o18y&@{xYx6O$*QHG"dJ4YG5G>[mgI0 [;|孂`Ux_AKvsT0Tppp]E7^&/~7{8^'egQ͔zjbgkK/EQG d:e:3G$I6O<[ۛԪeJ%qkHN$rx.8:>oZ!lGc4κ(²I}V,1yQCP9sdЩNJ(%d{ J&2R)'@,bLSo LYh1 _6WR9krE2ɆS)lצXrd\Vg^(͟J}K/pVTU\C& )U,O#nRJNբ\ 'ł`{{\M=.=d pAOUe4>A q*v0$L]Pbe'4;άm`5 !2_a;_K*3Jf3U!+q8T¨pՊ@!_8Yo1a?zm˥2尌xHrG'f^TkH$Ƣrٹwױk^hs29%ΣV4f+REuҙD5_J"%JT&*:i e)M@j$ zG\eME2a63 99>>?dtRV2g mH"V-:q'1ј{uwާ?gJ(&:IfA-Ԗ):58IvǑ8n!iE*AAi# 'MXe~/aGF~aW aV cS)K?k~ SXWHa-KN58Xz^ZTjYdp_糮ȮE' li6[Mʥ2|q>p;|o9;̼R,d&Sd{d{Ӏ8g2HF8B1+߇(Νc\?ZkJ,Ba•8krW&TzخʙO/A(YiTP"i; +<C kTnܠ ~Rk4)*ͦx?p/gP~jj8I ~{cwQ*1[e.lX,R QeSkTi pN^=unJZu}XRou3;Xe?:Q_ͩ(^f*s`UvP@K3vن7C`Q;'=Et6aDӡTw$^P*f 7X" RMCyGht:E&CCvN!AEbuxH^Q*k2N-*~gڱCF&3ńycDsA$p`O(+4m|O+Zͪ&"^|/^W`jFkUdfs?5 ,kͫCcؖ²[*/[AT y0%xr\$ s)Wvo>À/b&R猧#J=_yx\/J0Gswsv888<sx Nj=΄pM%QNNklM$Zde' ̅-~`VhE&A}1|Mb8#itO{cAX'z$F)K)*$Y\QiDo0\J9Ŕ8wV n+wɤ?d~>,57fVQsvɍO>rV=M+(%0FL28ݷp\~F:eOt^`W;y5M&ybKj^!_A493*I]nrm~a2" -+:sGGxuS|6CTT xқ1 S}^yVsF4&Gk&$$ZDvE09=;{ܼ}z!F :_{ {i 8y0L뜈SR52NѣߵXG=CbSVwVHh8+eJS!4ʜSd!Dօ(lqP<_6aXל h84FLɎ=ޔzߖapx3EL%t)8Rz{l^9q}zk(xdSٺ7?Í.T+0ޯC~%’@](,Vx2E`6AEGvփ"n$ X*^6teT҉U^+pRx_x|ƛ #R %I͐O<s͙&:T7 <_m¯ޅwEHZT.1h( wc)nqOxsڳWw&>qdg̕] a:8#rwI(21tO׍ʡր S6aH* Xa k{0r_`4ݞ4,ؐzEVYGZ% Õ+IbN&g /x1߁j [j]'BF2 g@)Q2hgg=έfpg_xg{MfsI"Ñl`p-G'1Jz+Aʶ_{?x]˖ePEXu,b ʜ͔PVU+iYbn:,) (Za)5t޾d2T*M&,f\+%l3gA@ UE.qi6~(a)3aL!y~Ύϥpv=`<8 NjXRmsjt0;$響>aRlͦ@M/M[a6rNZr%bLfJ ߵ5Εw~RKM&)Oʌ2Q8JQQπ9%Wzhu.{.zZ~E2Ntlm7CA9bEI3F Nk^F eZh1!gqI|W4 JӉ(+TdP,OsUTKb oeI*aW{2PGgc^~ bT n/;I$fߕTQp'bpp,`ZGP6"r{ScJ^M:B;S*ᐰe&p|f,W#R?WXP*_ ݈\Lӫ5VQ-,xNHȧ+M 1/|x>l 2!Ibxl3'f ")s t T?phՐm4k ~Xg8̣li(e5\45}.vhu+rcD덙Ԛm.?5*5 ܣ vZs !TX^3=y]KIDAT5]w SVAK%ZJȒ)|6/tYDQںl.I/ɍw ǁV(= " MfBա].Z-` z[qUaN3 ZHڔJA#P fg{&2PawiOԫHnkՌ)LNnrxնq+x,'=@2Y/ۼ0(&N@.>O(LQA3Ͽ|뷸sЮH(FØryFlTkUjNTlT84)"V )DFWG{Z9 =h#5`ժ8[Dh;gdmoCYrW2@ yȗ;3\\5oU(F ANT+Xs+$Njn(ou+PʥhQR̛N\Q'.qOY qw4C2_/\e667p=KSGz>ÙM3Lp\=,*fq4_/D? QY< E,2FPT^<<*:h ~4]Q50FANW%. Jk%D*}F .FL iu7yyjG? >rzJzbrnejݢ^YO+ 5-"1z{wRq=?2Za|$} Ȣot1 ް͏ɏ2pElFhʢkf}]#\qXR[mk@Ҵ[^K%]u%JT |f)m~};B<+.1CjaOz qHRNz}OD=G\K)pm\(v8-*ʧ(.]v"WE&L4گ ^D¾ԓpa: tTBY qYm UVZEW1}W'g^JWJH5Cb.~B7WBT]mU5byHOq .-| sN41" Ƒ R q!" ̒'SF!=8<8ƍLgsx%.?ۻh6B8@싑hL^0/Pʻ*kn˹)O'#-f\MqOWfuk92yqG*gO !~mڲqVyfg_| ,FYl8/zFK:1C)$Vܸ)>ŝ{s.o.]H+{>ׅWX;liQV&uP΋ /(-bhy]z{_*mK jRNiT],9>]2Ӡ_U4mxfSnKG\ B:e S DTq_+[orx~OߤM.}<<+?zfE1(YE}=/+AdQXlЁ*rJןAjEҜ,l]dQo`{o'\p,kx @d]\O*l6%"8i*z^ .dȲ0=/EJl$i] (Ffy )A%zEYn氯L+ ZQԑوo*wܔn%slT}k˰4['(9x~H,24Y< C$V/(<_U@)F" 5f)YeZ&%#ڑ*T ƤT6, Sޜ.K ҅@pqdd]v?ȸOPL]ֽTA32HZmbeTq,[D@ .#sl\%YWa0aJ #v[ 2! U&)\ˆ1wpW(.P U PX]Äuއbi.^$\d=k2~>Ӹ-g~he%FEN$UP# @{7Jpm@؊SŇ1%ĖvZ3*#. ڢti^\O8T:rm g(e=c)+[ UXgX!U*C'T< f7wxVS^yQCu3} n* ٲUi͖Mv_4C.Ȩ̳4VzB V\P .`aѲ1F0&?ɘ0#3c{9sYǻ(<7#o4tG>ZH.d̦^䡈Ws+Ss+籽ywVjZYt\9t].귔Z;$ eʮ P)rhaݛFK̩UA$o]nHeiOV9ConG(&ϻ ^s +NgTf`v#ߛ ˽m$)ۻ9xfθ޳3n ^q `mQ?M'}Bn|<(×_3`qyyiT|}W0WC~縣4;y+v㚗skSO^إX;A畒ȣs\˄D9Posո*uׁ*,adgW\&i^R(挻,'IF5}nR4$CFW$!7Jcfݦ>!:<=2pf%ńz$H9K[mlf3y}+ ̄߾, MP+Wp_Kc-I7g ƦE[ d8Nn/Ѹa)kx,5NHRD]j%T {YBɲHPNB5nw qS< !\`EC]ƇZqY {ŽlU@=vp$XJ5$eDPؕZ8/xyK Y@‰^ xo"(cg2+*Iv;9"f _HkUEP!ZF 4wCg]O] YHmoQڔldKB@uHh!), ]ŢT( 8: t6̇]((\@*^/9SKt@uh=YJX!x$!}a0~Ht"Oc0Oe{p?BHa $@KaK1 DRb[їPX=;-()E 2 e7F@QȣSlE#H h$WV4 i+,өWnhc4 EJ`BDe?X/؆y#%)aqx gf2<.d-Xt@Ӣ4WUa wZ.@iǢKfz*egmm |.:Za$A HmGnFVʀ;(|VX0n] y%7" ɄA NZ[ -A\Qm&PrJcȁOJFCK?HZ"k.R2~R쒮GKNXK#1ҝ@i!0 ;HBׅJc~>& ύrO@{jI$}@ޓK=E1 *T4FJlxrF j;@ JMzm#4.gP6KaFc$9wǡ1U,Azag4O@afB0IPJ4'3 cBCK z XG2>h$ #xZ'"ʴBtn@KʵܫoƱnt&dڡ`]q+JӖj.5ogYu9 Ol4?q)N@Wmk4=<(6d0>|\HTD&ѼOfrTF!051.4Tgkg BVW+]#Z"aԭ=0r" `:-}.*K^_+I?I6e, b6iľiD)t 0' ك b,S|e@U'#C9k#Ypw ;LtGo;Fr%k=Z 8B7~Z ?œvP#c }Z0m 4T8lK=d$LW?ydڴWQ!"U14NYȈc2Jgʾ{q@XId}(Ftjǖw 2zjnf1#HՕq[ wDq!;Oe:@QIb- -}}:P"K9mOlgkuN~څՆZMY =X 0Пڅ (#h/H˒X;p&Ͳ#/Ay=j,ǨhflJlr p~XtlwOT߁ꬄ"򉣪LjS"H9fQˤszV_G6Q G;WL-^:z?.-9lCPԃBƅJR7 3]jd_veh |?EENYT®88w^jz rRxB+Y~lkY]|/ƬvO/$z$ov @2H4Rd$>DfQ8ߐ6ڽޏn}=%uN"&9bAppp5uw:@S- ''/:aLph"@`ceP$ц:Z=Ow7zQy}K.}ژ  ro τ !`NE*A7`ܺϐ 0\CWt(jB ų>{" $- 'FJwOس :&sRG7Xh,Iɠ:)M: Ӂ&WdꘝU=;wV:oA9Nq+eź7:82wp%I7PCP !Haz;m5cSBAYQG2FE !Y5/\'Oo ᩡωQtlh̠J^c ~2h.xdo`4.Y dRYRg?:LS#JY Om-3/_L \<@ k0%B:T^EVCf騊: X5Pa/5vL KNm{0)fMރy҇yS_=uwuxEk ỈC!>e4q/ǂݯuȃG" 'b~׻G7L䥬Vd:z }gQ쵁qYݲ&=«[pw-,N J֢(UIJ7c@[Ad£Al`%٘,h0|aɢR2"5HW/Xkj5}In&JѴTL0^Y֊b0cF)gTÅMFuV*%#p X]X%õ/!ˢX2 jM,*GNZ =Z XU!LY8mmZo%_.ӮREZ.@sLg}ފK8ȀWtP:{x@W\}8c{ dJAg!Z* XSm3op*8l}Z`$,cSjր7uPDF;{pøsNTk¢ mOHfGבtPx jMCqjd57 GE j@>6kIP}DBܻ.!P(ӬKKY :u.CmmW}/93K%[6xc~(?^8,,(= _+P&6*zuWim5N$oNn:0ĵ|+ cQYg )O D1X$Cw5@0lbۦJgE!j'wADT(sf-4PBvLKaC,L#,Yr`xEtE̱ؒN;4pYwo~A7r;:_Աo` ܙ(< 1|s?ײǛtA aΟڈ43~|&A,>aT ƏlOeۉ^Y`:FPW`(=ae\¦ +&"Tu}zX:AБثxϖS0_o'sicdAY'os=l+:W#5N8.q)$>kp2Z{bR"}wtĞ"Ij0gQ 'E}>8'9~Ki.tRHנ@NUz=kzW62x(NEQkYU-OЧHEɂM͓Bô5k~&os/KTC#$k|w?^ǽk4 IDATxڬWs%Y&9W H-*E(=j1.iF>Ќk|ޞ鞙|74ښ ;TȪJQZ) .p#9qB=0Ceeq?wO7$@?_? )I Az,㽮^J~/?$k/D$}¤y?f^oSW`r )iAo>ȿi/gˑP$uA&>}_e^z߷-Cץ/x(L {g#MG'|o+g])x ?H$p$8x.L_h `j 7[$p×66PxkJ?!A xNey$(u:)p0{f  ,xSFAn)ClqZ#&2Pţ0O hR,DHphR`}|} =et]818^9ukTju`D`@C=+2\!ÇHyjdkf'&Faޯp }&#%f nR$u.#9{ áŕ5/`ni km[y1}6"1Xu'X4g C"? l(4*4+2wk w2޿S'cmyDGFt:Ab[ )%c @fXe2L'ed*#FG$_#)l ,pʄNF!syH%/V5lFstʿ۶ob &[ongz:-+ /F kK4T/yHfLC 2!fF o!9nW`ye7^ƕKh601VHg2$ffӇ].c8ܛB^G*&nT^E"bP$Vd)V5'0D(JQ8h\ab٬QFB|B4BZoQ8^'3;5ekc`t4PQ{IF>a=2hha' $\(2KQQ"R @E )0;__opvb>'N̙39GùpU|%./#QX,&-u,R0ºsFCa #iP1 %v8qWY Yt)4LBpoPK)\7q:*~ُL&WOǫ'~1,p8q Lc%Ƹ 2IUe8dFi"dB܏ CGBL&UEp͵UgXIDh1S~W,IEQ &q( 4/2\$"O"¯f[[1;ٕMX88cH!PMOA¨ ӰzUz ..IԺ H^&g~[mlmml.RL&Ruw,ܻ{n߀7AmŽ%!1 . &TeP cePIL e< >QDsD ҈p>G>!\zIuX\^}{/191ɉqllW1= ;c,l6b}1k::6'ɤ%SZWq& II 2 Ť")4−EoEGV0*ᎆ  >97G1L&W*d^dU]͔:Z7wu#$r!`h.&$)? 2VV7uM[ױJ:*VWqlڨT*ԐNddPlڕ/A`6F&`Y_nPb] iYJ@ ѻ%e"ICP'Cҙ0&+*qg;dP4K,`xl)ƱXfۀ_>FVJE\KKc܉eNx.} '} *;.>[ߠ\$<1)Σlh\.qkRRl1bHlmv" e29۷/Bҙh;fud u Y& ¤IB1M"2/25Uj#A2 !6?g?|*:6##d2!KEO΀-$V$ Nӌ Da ǁ_@Wh%*=3sY:rBwYL=z6* j*2 R VVVp7DS)4F@dMRˤS\d~#Vk:[.‰l\'z [ŒF$($5#Q8aˆD`q-.g_>(!L#x85vgZS=%/*lDgpVcMJnYH3r +9G>jmV\.3b!"\ fiŅ9,?s=,ZMpΕpW*y8WJ/f - IB-s@IjhODvTzE%CF#$&& B-"c 1p˂eO1OO> 0Wn,zil# Kg{>J#+̈́=hetcR N3}k#ˡR6RGZ#bk quAD8T,qa !n}s ~xRwnݛxx ͵U)Pijc({W:B7#"*CR3ѤbُP1EșDPhj CI- qpۂ-X1pJ`,XőCɤiC3>'0pߣa:Z:!#dMJ$# #JFE@H2hb{w!\G>Z œ'OJZLVt:T:zՕ VnC-  W>F>4snSeaϯ`z!N?t#cS'>ffC@;7O%g\@݇1=@lB'A_*D- cHӰRܽ VfQ)PP@jc 8mz]H) 贷l>cUf8q9qȈC2ΒAB\5դZ(Cd[ ( f&Uctfԅe!9'#Ms\6FǏ[N#(f- 2c`\}Z<\z/~~{ ǎU8znиbs ,$s1렂#_i*DUvC L q}lm1}\gσ[̕ sx)6N Wt[`EH J |L)釨G!DZ2N h I-+(.Eʈi>#B|H=Ĵ,r@z$X7 ZΞ:w^;F%:Z [\)A=Xĸ-^hc6jSҕ}*$eb'PF@W0 c_rn\a \E\㺐2)3X]]B>GA AK9TE y9s9YiYIk.Xa8ZVPT!X#0N%GX3K/8rd|Ybҧ^22vz qJCĩCWK6D\ȥ9rr,r 24 ,424ҩ4ҩ`}GF!ATk )-IM&!>1RClx[H%#^ݡFB&#I R{u?#xͳs~2!:s39@mndqXd4"݆IU_S ZB9ۇI 7ɵ =}!߃L:ɽ{kb}uKk"0F:*{C|gxQ WS)8>2HK A2nw1u@gtA>IO9d;:~x Tir)Y XCݴ!$ tB!eY-֚tUjUku]8qP'a'æKp;$H4ò(4g&sAśјÞTΧ`wq-,-.q%U28QUOP1&HKWqjfY>~~y \DOԱS)h全\x Q0 *0Q(8 ,hKeSRHTאhz^k> 00>#kW>£x2u3O?'C.%ᤗ"]":66 Q6Pۘ^%Vt3D~)UVp=⃏.byi\ZW6Jw<9,7V(M_^z c/|b |uz->6e"gRI@BnG'BC[%SF-c{`6 fX|N@&czNbDžؼy' NT*`t677gbccƃpU*n];7Ƶxx 뫫m En1aqŦVp鎣أK=ba% ţ]"2tXr ٥5\r0={^ D?88ss6n?x@bL![PZ7%r:^ΫO$ cBgq!e Q bvܛ3bH(WjhmPȥ˦NY<*`l|5f[2i-R?`sLO=6WNg ~~ΰ=zQ-+ Q|n.Z'1Jpu:1 f6oh AU%u \t|K$ۉ#p~۫t$еVab _øw1E$cZ(.$DP>5!\'rۡ3,n^rl msby Qt@?soH)1 Z[m{JE4FGPW122QLLL`}ܻj3OR"B7xi>q2Qn/jHWdJ(z(APDx02ڞ@3 IDAT?3OKe_|p_jDP;8~MAdrܼ7U4#/|ϟǟ^{ pI$~ȠB(¦PHi?D | |1i٬rE+J )\ׁp%\IH6R`e}K+X^[VۨVkPQ.QUQ0::=i/` ],-V R[(@f^_iܟDTc iO_>&^߇P9}~3 $,%ǽbZOm q8c((SH\y!bBH-ӕp !%8,9`Dpn66 _*RX*"_( #"'TTJ%dY0" C},=Fjb(=% G3R7">|V(2AF + G0ܕ8~pF9ml`$3 2qC\v 'E>{iZ[>TL?wx+r/LaSOf}A$y=yTns\h bnHE_a#|O8k#E#WDC2){He.PQVAZAFipF-Ri0XZ\*X\Z@T'b'<,R jG|m(8EXeA#B:ߐB}4Ξ8JRF'Oل-{aZ֠e˰C_<{[F*c1|yk;=v.}%z}Gqtb A_7ޏ₿,K L`0곏|&b 9,<L!8`LK\ {]lncqaͭ.x؏=Z\ / 7 mR*^T,u]ܿ{K 5lbɯ}.dlNIJ}! ueɝ7kEGd0ҽk(&4{8Nݿs)8^;sozL W [eb7ŨqUvK?{wvχA>Ǭ߿wyuIփi Je G ɤ^x`vc|s!:!=8B esd36l+P*C2I!]ZM,maYT&0?#  Er9d2_N8gT Bl!瞣Z|J:23C V>97sUK O$ LB{d_mu"=Eo(|Ɔsn/=k/{z_@E <nŰG:lsI1@ GL~ СĂchIсd쒕ıB'b8`8pelS,H@ڢ]AF@< #02{P!\dYS)XZ5e:i}m#5jj0?R ƹC>K!8ѠGA Wcs6"J*Zo~x85V2b```VPe᫯o!c)8kg_R d aR|!'ҴдjO;hwgNp>Eb[ HZ^<''``BDZQ;CPa۶*bph(Z[ 8\)33NBVÈ%70eX*5 :Bύ  }3,(CnΌ қ']h gT%ydmO}N+^̽lo6q%Tsv$>K}P_Uok>@D_ֈkcbj$@"G?`v@XԖ>qԃptz6]nn=qAGfԞ2!`Y}a86h8Tr |D@Cl4(8T(|+հ $uɌZ$vTEQ## R@-98k&N [C^6g˘}gO5 w @\]铯:][2FbGAt5FrZw ाZoSd4Iῄ ;ʕ >rf'b=^Y])@W[N{F`YΟ9J>[)SM=5##lm%uC;za| L*C&+LNBI;~k*tPCnKQџ_J3F$vM*q(+lmD(O݇tK (s<\&t:N˲f`ٶj0 wY( cWzhZw{dҨ( b}u1 %?Xf 6s+M;KpE\ӥs/: 3K|gZ+r,yܹ}۽ Uyq"©W/8n2"Hsv$9x_B#<6PE,ѢA!ô4sQ.@ Y$0nRb /tt#t4p)&e[HY?̛,vK.r!I R#݉gOT*yHCeg(ʁ (,%cFPtA 4; /['C`1x2?O>RZ9z,7b(<|U4Fv aOUwɣE=;o۵,z dGd$-IyfU|(I09P h}8.$!!:zp5ьAL%t̠?y9g,6`s4FECہPTh4PTAp ,.̡6ҀeYNq4 I4T O !ihLBS?w#_"yfaHu58޸7Μ@=.~vkoDl I 6A?џ]A:WNa&;mGRZow{Cz} juCBaYl)wC,>]_3NT*jUA"Rl4Pi1*0N O"5NIYBM+IЪ`h>_D?s PxG'p~gc7ϓaGayxLLB)D` 8@?>PoN5+vh0" \JN cNv )JՃCe;sY6 b\"Ƅ)I"h9ߌfRG G&829JNn߿ %})@[3})>g'ht$ ڋrJ޿xIA}?@x6|޽/sC0 Q!vkE)QJՒeϧ%\,.b{\&v[]1#2erYضLo¡t:'>Ҍxp8fk n)FVC>_PS4Qβb"@(/i%"oND>0>ZǾ1=yoy ʆC=p.=R3'A痟yf>X́>!./ª@3%!S삹Q2vL%U U-ٝtŹ9<{ϓF$0SavȇCB !<\) HuAqu] ׅe:q%u<{( P,ԘP'Mo-ehqGYhZD )?Λ( |jWO&Uӑǘqef汼0gN <nڃs:|U47_ۻĞqEӭ -BSv3ݩB$2U!\,?;70 U%pC#(Rn[\?pbTn^٬b=8[RZ1ewn# pBXDxϼۋ7cP{S(pul{:Z$aoCm)X(Ï/bJ?p0ݾ&Hwyifqsc49 HW`}esg0dJ/ U43 l"ms EcRe.u: XBf/|8DPy&;m)(`f`ccU rJȥBPsP$pm0Ib9RbQQ2{ٓx[mE/P+ܯ3OXIgʈ`X_/Lç_~ ΃)7xRfOgF؂?%dtUDoEHRPIhڂK\_EjxBj:t:ҟCe[~"1F,R@#Qr~t\pB Je`"ɢ%,/bei+K XY\<籼9,/cqqK XY^DksL[pc`<‚97pWR6Ο9r.[սm t^cRjTU|ͳJͻ$B_(yfןV+xKFK=&4)z!?ӯM:԰LbyC\%`lXv v*ׯސՙHhASNLC=_3E3dH&hwlFʶC2\d`HqK3PC }R #+i<),-<~Sd>{ãxt)l4:씍L:OD2$&mBi}onTrHps{ G^I-rlĺ'2SϕO]b %,W* Mߩ$86G2~#U-j*ǒ^T(t0,{uS$ilq\p2"Xl>;v^W!Sy8p]t]=XB\B.s 5,/aeiN(Z[[lmv*6֖\[уҙ z.r<!&țnJM0Bn}s9O &ܡvV tVs)&G!UR /h.1IdB:-O6Z-jѣx+x8q=a=ػg&'&01>zLF8!f<³'JeQq3,rI(\ŗW3!ͼpN-gO'_\QeG#'&;<{xb8>"~M𥬍ɽ{vQW^v`Vv5mያ7<8nRhEڞqL)IZG9ТJX^\@ksk+K!o1A~K j3R7Aj̟Jkc  "dYdY&;Ĺ/2Sj%7p]|###;b`?&&b@TBXD\FZC\Va!>ڭ޽ t&~L.1> 6p/|2h?SGE(7?z'AgOؕ{Ϝ[Jd2 6)feQC T xMs䤹h͍&zNĻex.`(B?M»Uk];mEP8|(>cbbj( Hl}:*z *AP ܾh#l.wIq #Z}J׷͛8{K47ׯ_Ƕa)OsL"#"./bva՗ZxDvd>+ehF|]XZoZH\%6K?FDosH)+)ޠRzx(d&̲PX*!ayi=-QA!8g+;Dk *t 6Znooc-[[c}8s,N>#G`rr:A:p]4ֱe6Zjv^lu픍t* ˲8RP,P(¹G8L5?[m9}0 +G'_G-whf`:̐JKju.WWzC*Rp_}s[%' &GwA ss! %(22WLk&B̤B!1Cx㧈%" E =XV Kp5{(:dP';CbmN2OCZXYYApt:j=3|vk NG鹥\NI22YdiҶLTm¶C lò. 狽(ນ7+q0qx(/}]*|mwFhei 3˪c]"ػKNR.2`;K <~[6&=@,q>H1~NzadlUg*Q(;l.BOG 5ˣj3t \$0跱Tڂ#\/{J% ygXGWfAL#% zpff[ZH(W+Ur G U2=t1:'W` Q(e/Ttz]lo !1rBy7T8#5pGWW7ǻvn1O|E6@ +vw6.^ Nu\p3x9bt$M?֛MJohfI)=" \jb񣀌$"}KҪqMQ1.rso qإȔQ !! &NjB2 GD!̀[zR42,2rp5ZE'EPJeې{ʹ0j}0?_TxQXxD#p!\wW [x|z= -o)I -llP١~^HQBJ0O $O/ n2kĒa)ut4:Byv[(mo[6gQǪ;Cv2Ia hmnb~nK܄IgP,Q.֐b{k [8.Q1:H|h|,*kf<7y"tB[M=6|HqAn"XmnD])ܺu ]-P *6^?P>P+qg~;oZ+X-`WcEwYA*Z܆e~z! ZZI K&JT:B`[)j>O&в8ah CvHɈEGCAe,..jPP(SZ[X"Z-iX(8Ecm'oʜ;L'^AJؓd Cl`jj5Ҩ0Ph23Alfzt^3o@;AϞbtek)p`OcdW2R,O!\*]vG,.b\"Ul\HBkJ½ uMH 05ս ^a.c {o797_`fiFqK>?Zy*E N+VX|5 X?ksa֛]WB~EhJ޻G'Ծ2!G*O4=Cw~~A_RlDjr*e: ѻ? vIJB_k^r?$dW)a);l-=~k633FҒHrSA J"-](bWHp.EH1R1"*-#ː9gfNkO.wE!Ϝݾ?g 4T<3ww{hN4fo[|W;a Ad? 9Bʡ,qB)Z) Ja$OoQBOp0n(jG6"#q6Q&~W`kR`{@<IQ^㳂ʈ11P%͖Tv!s-fAj`^#""fͺ'%IdäÏ+dY?oE%:  -j'!|7}6<o?8;?ϭ)wcFtpy409ϱ CdJ'QH#c62t+hV6md 5/T󚨺򉢘_':&))|Lr|WRV`؄g(/yج<}rBFGx;sRKZ9Q|5yn hZ 2ݜUQ|CӚ?3?g㷹n!iNN633,Q(*7yzsy~s&+(t&D!a,<:_9Z$aOv2Inb׷95Qm@F]'$LjFɮoMsU)I3q@l?}ӧHUYY†)Ij*tyM*+ʢh'\ TPQ fpݭxJ>eQP ˀ;7c3qeXo|g2j=gůſŜxz~ewa)B5_T6= 0H 3i p=#5J `Bf5qT;U)Q]h(ƐAS Yn4 XPƫ||_>EA|_MP ob#Plg8A6{8)/"65<J^ QpctRP6-Q:.]]10heOڗ||^}&B'(v7`pǏ8D PZ4wNW {DIJ[<>=FȞfk]q&us<(٭VQQL ֛5el:e>5c7I}򻢝,I4MiicGj-FQE}xY񭷾^@qLFUef+^7`1ln#J2A;XZHа!z>U`gb{A[)|^.ۍAU^4KP|B%č-t%F 70ݯv4,xԻj}Q?է}&oV`LYiXUUmPKEiZu,uʽYy^;u zE2ɵi'ņ[tfeQly뭯k`{Y|S;b2[0IҔ*́Qʼnkc`Q/.?B^4w~7?GD1UookQ\.OK3UUկ}}xJq||/K~~ >O7}F`{Z4KSYt2k롖1-sV-CL&D}ykr.~To(EZ.;MT&ElF՟ຏ s+WnOmuw=Q:!^/qkIhxQ*6;C%H(N JR˩]ȭAچҕcs؞òu{QYN4LEUQn7y|q "dE4̐N7]tHrE&Ge}4M󭪪F( ʢ4Xﱼ@kBoS3GdGl6>}WDaۦf[ߘL1s'X .@]˼l%x"b%sy F!e;w 72IXt{ y3MyE=-)Ee&'r. zvd|UDqTT:2 ЕLfa舴z0n?PozX&ʥ&ϕUe8/2a&5TZ-$)i:KNK^sn=A/yw*N'xsqǑa`;Bmt;5qeaAF\56 p >t:'ԉ\9T꽬꾢nWJ6b4 % >u,K#;,}nS(&lF,Z|m lz! є͚z ~NyQڛNTY ÛZ.) >Tk3a)Kqx͜Taj4ҙx!^oqN"xݗ`yI{e&;%Ը否n)aD|ʢbڲM3C`n9jRUgC5 WVYTchN4MYMWlkK.+-ʲ2<6,)ast&XW6NF /F1w[\N 8_FT⺎-z\~DXـ̓c\|֓Szɭ!u.>aTۤ,96&3QfyUv仝} ^͆`ݲ15ɠRM@G3/p`洗5>q3N;%R4'q^W$W)͚f-Yn4*D hJOϗ?_sc;jzLh`nq6C'%[N75r);l1Laٔ"EqHu5nO"=J9|M%gD~sڨ C4FWLJx"TEn2zQD&Q2ϹCr]V>k;TgDQ$ۄ$6(X+,nv\nl6_ȄW؏w:͚zeϽ`Ld޼~W!]cᩆ1ϥKȐB? sQWV'0Soer1]}ʻmi4$SZU&#=-7|0mMV@ltv 3 DĨîVZ> RX;qj]mU%iP&K^W4iGplZs>0[FgMV-ь zJ9# bw~zax%7^:n E2hje^>^ Ay֟ڍy!bJdy~fb]7wQy']3-_#5I-ਓ]C ڑTVe XVl7[,kE:3!I3L؛ϙOL;P9}B8I8uCȔUi|zݠ E ոc%ƴ*:?BKg7KոO52SErpܷk֙NcAθ܋|Or\Mkx6("/HF< y;-:r?5tjuȳOyAT)(&cfUETyaVJٔtlv9?_@;cDۚ]wa6]jڑCzstv].~'ge `3C7U0些PSz)!2'L&3n?G7olc2DkJ%U %:?j:yAY-KʲV;#،#"Ty.ʦ\ʋ0 J!vhGQ>tF޼ExM@nP"&H4b=ԐA\SU1JW9U#'0󋻽ȸD4 *͙0wIJФfy i3WvC%f+Blܔ\)22vv,KAv IDAT<g[2/(_/ "|ȸys3SkiFũ5,!Yk ="FE.44{ !$8wrn_g=3]-z앳]:I [ϽPCyjyFDqJLPͭR3FuUquyffR%hg: ϓ l7vEW؟ 1ϔfVN(=B;w3 Hh8kPs0a@)ޤ#ykߧQa1/|#<~}mm)+M5wȷ >͆OZ.tE$6uf60ff#Mi$$1bHevٖ,9}|| 8ɓ'ʓVLw=e=9s\ø}YAG22MmfBk͢f0`U䋍s ҽtKRM7|hXZ#T9w(0ܡ(Z*uS3I7k3جV{zHC^""<~x̾gY7 k2^o D|DQv;i)2)v9͆Rk4ao )MeF,3PUH3 (C5 ℣ `$R\m6\e~<۱ݬ­;`,, 2x@ׁCQI2՘&G?_o+sT'~8U# ]hw7pm$yLgsb{&(!qe)"ۑmd-UQ>q0L& IteYR%햳S{'O È7nppxhZ[33 kh]ͰҠu(Kss9EY2tF2IςMo ]b\2` 3>oNWed[[^dr1w퀺?B՛HVW%ڙ,{6c`ƒ#!D(( &6W C$u()fV+[ppxH:tN&u\Jy I٬8C,s RhMqy`2:$tbΤX7JOqs}O;'ttCnp#8l>@9Po f8P=R#\"6%Nb^y㓈֤~)˜?Ώz/clyF1Qv|}NRe^^@.AԺeYfaݱl8??vD*& 7,yzrFff1 -ӺdJM7|]9P. u"u1IJQcUw% CNߡDhXwqZ ׌N9erIQjSc|!OLx/1>~.\)|32sMY$zbgc^I?_0͘qBYlH+*n 3'<(27MZ7|0Dp-f!'dY o}LLXMtXZu-WjOo,rq1iF C¬68Cl}y2$ #!heО ڈ'^~ "G_wNF*[JAK2 Kq0QıdF25}D6w LIelcn7Wkl7n֭[sܮPyZ999wݻܽ{QJQ#(6[>5%o9ZqbZ ~q:Ԇa[,=An٬KJ7\7Ah,KD/< ?38::qẾmLNǬW+6=Y-(f|YqP7z`eD92Y/5e{-r v"xt-Ud0vՏ2NLzQ uשez>- $Wߠ( ~G"rHgfb67M=^Za|M[u_Yd[e".3^EN?$v5M9<>bjvq[oUcgl6[q(''Wyeyq :Q4Vz &Ҹu뺔nk]>(x+DƸQN$u^T-{1@=sRGoի>egkF|`2=hSYYKEL&/ɫMC0|\˴oV*,*J65(K0|bs/~`l_ &=C*"kP %I:SCLRyC 7oA+AH[ء'Y}n]$}5}oDMQ#|lVjn|@g\FeK8첛(MlX@r, ;o~ AЀ e!̘ƕʎ\~fJ?*iid6c`:9ѭPE/TTh,{ֹi!I2M;loz?x_2Q3͙J5(׭ҁrF%c#n(ի;7AutuM4VgrX~D}Qo^V6 F8IjL/!+Գ~I :')oQX,I)􎪬5EQPV%͖* Ŝt:|t)a}n7Wk65½=S$ crzY1KX_ 'x/˔dv[AD^7NONQ~LY VM3m`JM891WCq-:.b@g| TxH`Qc.e`t*ʱ\k8!5XoN3:8%b3{WfZ yu܏ר&GZoPu*m6Cgm2`=f9q:v;KYVq 3O88Xp>Et&)QV3^A >Qj`um7e~r#! kʢ$!(q!T]m uѼGkܜ`3_j@a<鸁 &-PGl9(nD)~X5H8W?|7A=|Ԩ|.m}-hheQqZ!{{<<萣#qH+*kϲb6Og$?G_7LR( 4A(A͉cH D0 #4X^\N8=WBoy=P@Cevnkz8˾ r G~YgBG7n!^ M#+rP̳t;},4.}DKVIX{! Z;LxO! w RQ`[jgo-QJ,Mvh`؝feX,gXN'qghEf ! #& 7o֭DQl a!J-9\:N笖+'Uf u{O݈pMg.M`F0 *8gPԑ^Qܠ2vAjn֓c~H%unq37y6Q>x$qG^y?mD6eiSW-h<#N&)G e\./9=9bIUL,^iO<]^!D㓈Z  = DRJsޑm t)6(N°?)A-wc֧51Pg@kAKp𢌊f:t "㢚)WuE/=O97Y`B^m5.rpw_8۬q˯to|Bf2:%0BBb|P{D`V0 lJ=(ʒ]fԸO},>#DMԾc#D#TssXKWh]q~wWWdž2a#n&0vԆ~~?}s,B8 u xs[ƨ1.gt$\'oe-ç3MczNx3X,(t2;Qh/ojZoINeiD3N8>>9QhF#m7k.9=9'{}NN ClJ&ILŭL5ZOyu Z*٢TnQ@I'W,/wQ%oR SZa&uP5SXUj8QD2. Rww`LFG<'Aq@EW(&ˡ1>88#GgD*k؜j"gy7xˊ2/mw\.<{q]?|DUFYîM&B Lc^S/RxWbCh4Tj4("c6x9/A)ɌtfV<%zQX[||:3m;;cT֞CCNXMu;Yn? c[ܐ45f,Xwq=dz >Ik\ʯ Td1)MU@+c0JUF 62N%2)\P IDAT2|Çï~gHU1MZ]C(6u#иH+fP .z<xT|#Gl9"Z*DU~ |Ɍ(N?:Bu뵡iz Q/Suwu2Cy&W` ;dKKh_a~Z'z4J_[v (y"!>%♎6:p hR-*Y컎#{X'u/ :/̱dvej}]pM{KXmk9 'iK}N~ jASM]yL[*PͮYm<=;޽y1WWrb1mN{a6kXU(tiu H" #Ɍ[-E`_NLihzo~K$q>q]v{c??fe([қ MD& n㜌Ȩ4QuB>FGƼN8XJuO,A){m9ql}?G)+^@aڜ(K wj,9 xZyplFYU%5a1C3'ܰQ>bSDgh,3bEQQk0GRQ(+( +..V8ʼn^Sۇ.z7GiV4mY3d|X5^\%O'Qz[1VcS&>K[3_3ò8Iy}Dt徏uy>Q1˵^dyzSU{k^y7^9is]ZݎM3'lU!e<()wA؍x*D)ˊ ._sqqŊEr9rsyqysqYpvY^)ΌM(I$JIW>Fyg ilU<ȷ)F&MS)%ƸsLUF~0yH[ k;ДYC^R5z<7]Q >⃏O%`6O$$ 0/hP#Dۆ|R,MjSqrsd-%ϣ ;/e͍ Bo3WWPd8T`uw}J6!J)nÓIY!mvSjzU (*0=nviTz[aX>В $`Ԍ}♌ bғ2jս-.^4njW;%/5.{[ȷzt2ŕWbR=+koXܿ`Z2C2q#R[Z*Jz]IenxgpvW':U vEYeXQ8@RS);-g4zcίd:_P9q21吮A9EX5-q`5A{PVWn3_'^Zڋ+[_4[bgjPgL.lz.JtY|R1ʋvOCsoݷOҗ<#`6p|> ̼# ۮ2<˓ĖCO kOm. Z񚹥t 8G;dCF&2jՅv ]pa݊CnCߪyNV\'&%I~Ӝ?=e:0NfVEk!Yk"wjE"ZmII P/8> ރrS*DLRP-T Zqxt~x1=cٰ-&D<0r Lx'd {kyPD %WڛcBcԘvKM]CU*{(&[HFHF`LiTn02  =$ ˰EnСGG8(ɋ)C_NϱWW$k>vC%XD ,U.5 ^+F&+r6yffg&7l\,/x>ڿ[/ݸI:]PjX/ u| '&,brqqF-ڄovw<dR2Wρ0?`m =WaC`zX. ݍK4M?DD MUPsVN9Ni' >7Dm*iXٯSxDKa$_gy$M:r$xA`"S! +Kp\]E,O QH4ph>Bb_q|$Tx= ei!.( kvъ铧i UvNvE^YpyOL,WJ#.^aN?b|wOx;l%~ȳ|* g)-;%anu6}8肆]Y.ZuesN)ñ&c~7>{Όc(5eY)~( yUَYdFQ\8Vy$O>$O4{c"PxyZ%mŊZqsV/$f ύqEt=&2];?'C[r<+pa=ӈnx`yqfq~Y0Ad~d:GkfՆuis<+rM&8b2E 1,fmE~ݝٗu?@ںzpI2e(c$󒇼HQ!q ((%ҴqΙ3tw5e~ܠeQTj@ Q!1Opok6ʻENё+y`O(nho!;A5g:ILJƛ?|U%͆O;rKTe0H:m&WӘG7sSrd }GYL'<x\]VۘKmG 9I5Ŗ-p G[S !M\f£_pY^h1M3FQEI(`Rn]柸= `@6{i-Vky։kl-'QcZ&;ԁ9K)ej,랡[y{v[53KD)ңoO[=;Śz[6|hd!bqf ڕwJtHG͓9?xqьzm! Sa2J)A?zj=Q_r1ɐl4'Ύ W_0K&IC&iO&IPk^lUbSݮ9v◺lTIz(_>`.=W79"1uRԋ \-c*h XBa Mw^k#1vԸIQT&87ޢ \s_˧KNnN;y^eV  *izmDUI[P7ߠrsx=`O1yZSW5sv\$Gqm,\6U*[] dKo٨9F펒'FCA[;Be'[5[4oXESP UֈjDPm"*b""O ՖeYSPmfyRklqz _$|ZqEC$x/ߛ \\$y:.SQ9`Y-!Qsj* بVxO>5ʷ޵^hG"mboIcrOl]@`[F> _5T ~GAV)D9+F0eϟn_48I6Lf1iN6$θwvե0J\B}G6PCQN Gg-WϿ.CAk= 8ժN[v: g̩<@p "vٻ]8mO%'ֻ͆l˒僟lhՖm4SB*k֛ەRU B)8fSᯅ4#$Z- 6 嚛Y\VHr1Q2" ix¶\Sl7KDH''\_swoYa6XoYn]5~ETA=:zE(|h¦4жC딖Nb3;])CL\' w~ҘKXp}l|H4J$YY݈M>AJ[s(VaS5ܡ!?c.!uUP+uSJ-ʊm@,jX+r̅ӓFieD:"zz]r}US%7I:&(3 9< UX߰Y EyS|t6yc..><|\1><*ŒnN/uL?R./P$@al*ظDh'W0EM"c }yKB–EdO0C:ʾ@}8ܝ^@mc,yHlŷ7"[Y)[U>hFӕCͰd% U08%3FxQf;p~%ILf bbr{Pl,<}MqCfC&d,Y=JeHI7K7W]Fh겇w]Ci!5A}sBNng(qF;HB5镯۲`k~d-OPdU~ 6sJilNahLcFm8H1G!$'$!d|y>l6ߦap.n)w7CV!g&w68B%5I od3LΧ̎f&)Ɍxl6`FeM/RlVkno#.#.r./=gIƓ)E1{?aanmiQ bM$eɃ珹Z OmSRa @IP'PAVJ cdx2cvФslXPn\xL%Q3*57[noc&Lf7n׷sWsYR58DyOjMl٘]MC79ݫsc/dw R3E6d lqoOٖk5;RETsF)um,M$6VbYG082?15L*6 ѨjEK.6C& 4N)+eSl)ʪ MLgsF?E qЭy8Ӗzs_l&PW!2 ih D@dq8nA16[n@kuP5H#EsPm¼w$"*jΧR89{$N'|_賏yz%|TYFZ K n`(Ikɖ8bt :ɦRɤt'Qk ℛ++6ee]Dܭ8?,{>oK0Z_ɷ@^.Fhj[6Vk9%JOS{{tEfH6Tc~-J5 U1 0og F=ubFu`C VyJ2H8 Ij D٨"q"epy7װ^ՌdE:(Қa0f2Fyx4b4qOMn∲,;z[5*I奰F,W\\pu`m(d~ǧ$AE/?$.:l:2J\%xtk&^){FAˮ5$C,T5"#i6 4ʮ]FFPaE$IDAT-ՒO?#0Na- :&A" `o ,I $iƒx ?5lQkbA[bz%Woɔ4[K _ߡ]#ҹ[vO:k1^f$Ps{^N¥p0CRy _VKj %LW=o$qX=p|W h]I{#;3jthb^?O}B$8Sf9,^ܤH9`X).LoWLFSF!nP$åAn00N diF7$K8Bu_5d@[?b~xYqXs tM7AQv\v+b0rcfZ_"o\nq۾ӻ䭺JCBw gA ;gq 4$uد~Zw jRGNC;iS| ! 0ɅuqWb[łAwh6ujetsa6llے鐤%O^7a~| ͈Ƙ,o1B7[}?!z\,hUt4 ~a:ffO]fY4W`"H3y/u/rkA ^Vwش9b xDw&ppxk?xr">z%ijI LөP)Rn,/YchJOIp@]O :yF$uDyh4b53a]p}uEw[fmL-Q ЖaK@(/4)첦Ɂ=b]ڣtdsº ?@E81˚x'yS>xY 339gmVYbFM}ߚ(H?|0XӨ pYځ !y)dP1o9{"OHk(4 #T&+DM#uͦ(Y.WKw\_puuœOx)|j+?x7!go9v,P\ ,vΐAPEq+/}G5\ocâAr`c@(7e: 6_3?u04Nu1DuyWګ%'G#`b9|re#<#IjM;%7W7|#>yWϟs../y~Xx?'g88:a:7 $Ic?߲`wOfQik.g=yWh5mboB3X]An#ޖx}0M;nP=x[դ[XkLjr3W¼ _aQk&0I#ZxXId+DqS$|%[[ qʐmQTq9J¶XDqHAYF,/ⳇ>>|co8}ӗȲaccd.@.&Jѯ:,+vJŰ_k`*kETW[\ؤ cIjziѷk{[q'@&$ +-(n [ManK!IS͚r/~q\PSpPk%Uuf^-!^)E!,[=}_> ǜ{ӗ]&]A~"% CDM uG>7}NjxFPxwne?k0βTo3tpuɲъQ;ɕEs4s+qK^/FzZ5R1{x9'%[$V`:Z/Ȳ 3$RUM݊䗿 'CNfY |/'ֱ%4|Nz cf97>(xY"bhB4i3x/)={罚3ھŖԮ/_[.ԚXXݟ^H:sNg XTR-Q3Iч['قt &Ü$Yo녲ZW-6-7%Q4W%^ ^QGNg'3XV5ReQiI+ĉ☺J!ջEʹssN}S8籨:ԞzywMS $)eo,g+/563t'WǢQ&vj`=q֭N8bx&덌\S >nNJ7:78MB6PrMFR{;~QKE;֋j"u*VA-J w,S|f,Zqo/R O{5YvX؛ϴP+#*[|S ӹXw8i3K+(5qj٧Q \Lo&#ձaf[8W]+z~ṃ\#CMw Ow/&[ͶvNvw ޳tMC[)k뼗 @ԍ7O Z_sHz.| aţ-$<7riV`=l?D %BfzGjs固o-b mٕuK?03ġYL Eh(]X:e 'ں5F:qa. ߚT=*o#XP`mcA-N=-kvO^quY`SUj,240<@"T\H]CX8: _[3%g-P{ƻ>EGNja&Zv*xZ8on*M#ҵy:Pu$4ٔU+@U %91{I+1ZUϰVlaw \jP&.Tg*lv8:.Y67huO9|>U܄tuOo[TZ,Z7> . lm0T5x]@cCtwXeukf?򈤅%.3z·Y}oSw101х=[? {5nH#"wk*AIs3&pmkޕVCV-GpIEpgs%|+Hjֆ VZ@Я'7w_/]iFp,0+N\Yæpmп~$t2'z1uҀݑ+ sMU}Hs4՞u㻭7~'r}ͪ uJV*fݭ:ȤcbBq vKFw v)˜{@X{ȃ|g9mu,sXq+]^bghu BmBnc#6qsl ]׏G0~!AfcI"QW:MAe_OEkJL]J M֟ū>k Q| qܜ1e=z+B$$X342SCtO5gJ{vbdoۮ&q mn<+w^^SgXj) yڎG{pD0`&{ޡ4uD8kgMj:]ߚ=6k>D1=lǟԻNJ<~n#@OF͞KM+V{KzK/u&ŖYȫsD?g+:5fvԐv=[F pFߥǚ#IENDB`qmidictl-1.0.1/src/android/res/PaxHeaders/drawable-hdpi0000644000000000000000000000013214675771705020046 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-hdpi/0000755000175000001440000000000014675771705020113 5ustar00rncbcusersqmidictl-1.0.1/src/android/res/drawable-hdpi/PaxHeaders/icon.png0000644000000000000000000000013214675771705021561 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/res/drawable-hdpi/icon.png0000644000175000001440000004776614675771705021575 0ustar00rncbcusersPNG  IHDRHHUG+zTXtRaw profile type exifxڭir$9s8XE}d0jkD:#3"tw3Tߢ;_{,.J/O_{p??Śk__~}7oǿ(Xڟy P>^8({o+{}:~_~kb0C ۅn8a {Lʟ1hkjq`IպmkmcfYVh\y^x^wy(>|!hM26$ܯ}o~m-s9-{`.'Bڵ$qc | C XCH4h)Nv 77YaoqĵyO 1I'[tlVJC#[N9knንTr)ܨVS͵Z[u4kVZm6zFJ^z>ɍN?kn[o]kk)_U>"g)U;@@Go!ӞɊɬ [Ns9۷\tof׶pdw󊥏K\i;N?7kaX)ΊsSayWyXlMF8cevv^s-^lsU;r<%cRmvzӨdp2y? ?n Wsl}VM7;=6Qxn&h-RYbـswS@fOصG12&Dt20\g}΋͝<-UkAsN UyJZ6ؿ_WmN7òV [Oe2"r'=nmA:S.TV¦AZG0=F'kZ uSؗέ)+e=[!G[Z;=G53y.n)ǔs%Ƹ,;cܪ{x=i>ͩF{ rrU,Uu5VjD6+lc8Y<7B?3/+so)w/W reNKZ+>Squ'XMA"rs}הۊZcnl K圶ZY 檧axe;dl'd=؊/*%θ)Xnm456weU@]Ȅ@)NVK-&v#{G;Di6gB!{Yrl{wZy*gC  X3B#A{r]OyҎ$d@ p0=g>6a N7p.8Jy~"ty=yp 1%Wu!UP6E|$"B`ډ`,w _wI̓^n j6XrVXW]dR‹ ▬(͗JbmS@ Fd> f;VH V>nΓp rw4=]J3ܳo.jT02E!EhsҎq® thۜ<67ÁbV=ڙgP9di+n*;KuYԖw* i0䙢9lg!vc$ HOqkKbOT9` 3ڤ@7d8"$fЖu ; }jH;;!@stV/Vıd2;XԎZCqXIÚ}+iKb鵗<랹{.z8Յ 1/ T%2VmؚTQ#p`-Ud K!k.gX]l|d` {}`q|(Y2 X̡׉]icsjC)颖@5lπ`U\$A^d1YtV5E,^mhPnň|Gلls+M֊#s}O^l@b|Se&,E٬E{pwCWu#>r_YӮ&`!(Lg_!vh/؄J.=z 6nr%ۉu@\0wN agD&D ,H 0\qDbtIB;)YZ&*: e@m,erDòm>}ڳ-2TZ{Ul K74\^uH;HeD6%vV}GXܖIw@a_N8vY8O1C"|d53!.*c/\  5[f & PY/1ɮX؉{2l[Q;B 57Dҁ%7![!}X P`Ąw{YFgY M$,'cYtW $wq 6]"PX(r4Q%:3I.aR}U8mP`q[ 㝰T h${$uTC&@#J$,r(ofs{?&5$FoL:9ТI)a6gTMKw67 l!則ER.,UW(}<0b5\S4[Y= "&c%orvJxHrg{X Z&"B4vT92¬+Y9ƞ/  K)S٬M<_`1BB#s'C`͊MLlԭ2f*rTD$&^DaR !0\,;\G2dĮ*u_ŦBtLj1~^ e&X"^>0ۤFRHJ$k x/֖VB-A^Ccr+nF46)EV \#԰V9r-lVqf_(:o!Clk*Ycg>}!=hxC=Z\ƫ"&xfoH  a߀"+Tr T&AH#y+Ѥ%q?&`+&Bw:P#*d0bݬ aמa^C(~֪3@$n~hא*>!dPGSE? /U{OX@^ zT(BbdW8I< rbdQ5D:.x=c0?Pݴ ld+AN6CǡtT7oUш=n ,)V@"I|%Pڌ"!*+fYd@4z譽;=`qfkG|`=gyxzRv_e-1Ix,t.NLʕPV2ɻ>`>ukS ɝ_~m E&> $ikNfͺm'?䛞0A[P FxƎo̞4n-ljͤG@ (z1+:,됭놢Ĝgk`F~9/J l 1 A%0%%c!:Bf$[>)˃ \ś=x Э 82UrBѹ!,"Bdp cHGƃS[,"aX7:`&T'ܛ3J*n,VBHdHAd1:jAr@!d<" $0(|83P喕G/dPHbylF i:"Gq.]{SZ6/"q*b?w u" 1.9 W*3bQ6=$,m9+("֎*⎑`.O"<A!C?Q\SO D#X;X*,U7l}D3=H$xnpK^L^K ,4^uq|jV> Zr 7׺Hᆋ?Z Nձ@0 BVA 0)zCm;`T-rbh@6Oܗ)"MR<D$ :pJ)xC]w \&_Hpb`Pꛄ27IRq:xOTݕR 5xT+uR[:,tjE"gFA4L͝L /Ga#cAxU m}BX8RU K)CYgzY ^EGWa!ԏQ3 *`IOP|h:թ[1 MXbT9񲧐ߤQJ bu@fF*1h t`$ b -+K lXúK^U ;$ 4‹dV"&0=E>hm ';"FTy(k;P#j}4_QI*<-a ҒK2:W(q^]S+lʀ*/*K="!ylBA{c!҇ ޡt۽M|}DK[m -@zE?y$d;ߒxп#Y˖y-3) 8|fCR禺B{qOq@|oun16A2E\] xf,7qUJ=/"zƦ.Sy jFhH \k$(y9$mrHLߧj{qv-r̄, )T 5YH R ߰y9Q"@͋w T!P@ vuDy~dn*G[@20\e cmcW['rx$豣XR7|"EP$t\eAGƒ/0LGSE!dSC/:ELj_(ss"fՑĖ/GJ_F0Q՜]8,^zx8,H}pX8U2=ڇTKvQ-6tJ-^|'RbUHD#82ۄXwAnsH|ToVwbAA/3^4k2dBUjg}ǃRn<_rWdSK}Z>x"KպZe!^R9y|:DGIʽ9* [ǖL*.B7. f%lKḊ"r*_d]D9wuTAdW2UxÖԒVEtAklϾPѪTb϶gؽDȁD8# 6 hzڑbS%a b'I8xB]@S{Eͅ,6)X@a;OTKN *v Lܛ*w.䄛Υ&<ʲWģ};ď'x4όSɚmvdz%Lb|LTrQe:lgg;NP-Tx%\#ΠMKq*r&+ Biw#=Q$0Xs:#U!d4joCA#1ӣaPlܩ:XuȲFsF  :Ϩ9JX(aS d!b.nsSOԋ*Qk>M ½.G+pWa!0O&ę|)uQү[[ԓQMCLqUj߇xE)YWVmƣTD~}rzCd-?>#9@N$4@@9[3YMAwpCVdH>T͒Ĝǝhs}1U|1tPLQrQB׺Jh#V54Mug.^jP 5YS4ZO|Sz$xEԊr*fKAE>b0֓ $K6@D +f@ca!E)cAԕ CtdV440HhRfS."*!0rY)eRs(woƔ@`_Bכ$hG K|X-DbR@ h&#{-=I^G+W e_&_w>ﮣi戣^[ 3 ReLVv#gVId4VPia12UT6}z NvU6h:%X0BHBGSơbYhtY8 & A#]I ?n βs<(Z KB`HQx~(zC8Q>]C(AQ~À9;=E2~YTg#R%HY* Q6(2FUNA1"~`YX{&SsT [Ai0 |c~ٖp2)YΚ|J]l,S~5͕EZF#:")hL 幩AM6$A}i? ZlhP;?00DR~.ISGK#{&dʫ؁#Ӂ,Az:Ԧ*C"#WX*=h1]U#'6v#C@eLc_bjcwLtevȘ?ݯAn j>~Ǭ: {}?T-. WǷ/U jO$At%$|~5$_@V H+LՂLˤ) zi_ԤF[3Mvr#EΒk\`PA*vb&4:&= Ydđ np (JAՂdԀ$Űa3J,4j0X}Mۡ 󩚭jr}!ǣG <:*@|fo*%v 4%Ќ?$sW;}'oC"#pkͲce`)pjsy <#yf4pUUT*ɑj NasnjYI( .Sj[d|!YY P4ʨǰACY}МM9N1[gݷͬlA(ɝBfHSY0WJXf\VP4KZ>ewȭO5RCM1 M&d!ݍ\5z{(S$YrkhR6KD$hLhMrMPGb3q~W5*=;,KI*)OG(o$Ʉ u_i KtH4=X٪#osP`lU + yCip-hdɴ&x o\DR0*PȪ !ڳnޟdonqPn H%D:1^~tCK߆gZCXp5 aE]8៨Zίt fߥia" T)`_zPW}.5GC򼭩o!0T} h9쨁 262Iz @Ӑz2o X#~2W/099vxu 'VO' YPp Tci%[O :Y j58tU 7i^$NJ+T*)bA`D y*=[jfW@?1*mUW.$@@*кZcjT^Z ,) ̱TW@'7%aq?"BPW`&v{}}T}OQf2wa4j1ߟM:|4J4IYިDD=ؠtz}\IB[$֨aplO]lTF;jĐ&LGǑM;h~a2­M71 ل$&1*K'̾iPTV]`;j.Oo5 yWc? B=JMuT:5]N5CEMv6aJ͠\KD&D}LJ ݣZ y:nV[<\!5-Bq[mYbRSohHD0'˃RY\`ҀWcoӮ3۞:{gAFB/D#U”P974TE5 zT < HU!Az:?M&NN)̨Qt* ;}zsCU9M! @K1/Qh/&s|@^Hz4E^B\UA?܅Sc;tt8>aL*UnT[UL4kQ[G*S}}WؾDe.?gub ՜ɵS1Ȭ"\IEu MG@g!r oӢ:{'}%_e-#";ی`+t:ZFW4y:mL:ȿb4F^:3ڛz&8) :aEF`mR֩H|"8`|ӠNͮ5Myxpod 5Lbx:D`נPqҪ6RsU&PT\2D 5>tCԠ gb'Iݎ*sjZu|t Sv57A4Qל:Ty$ar:J X ݰMaDG/>DC77I<Qtw"jC:Ϡ"P!^jwoM%GPY9i0,BK ̐@uM/t~ $9ٿ$MUtl:1f3>t(,>GEGz@)"X[pRӓ'@ 8ZJk6T 4mV7ߖ5|A>JzHoV jE4YSPHaJcto ,׏Bo~&T2x>$:.m^jv kRyq !e>ہwyY\5-& )}̭c+H-\@E\!;~@ṆZS&_m؋CiItY[&QxS,(QP!33W9!#";_'a op+Ž'J~(ίA 2t߃Gc&y8{yK iן$f`ʬ IDATxڭgy{l ,r) *1Q$/0,QV鋿J|-O/pabX,9ԯ?tLwϞX.k؝O?6! ,  B` ` 9,Op.]~ asa,XkimUUȵ\kvkY!,@6i~U\!PgXKO{ljt]w Y{@jb8qm9;p p#Mb$,ڣK56׭`"9K}}5-VrN$!ݸPw^]%/\p3H{O} Y i>M l SOPbv:wGi_Zeau|BQTdYF$$yeLϑuÚ@pz͑ŏ_!/?4{˴_zP$.`[_uڠ%|¨ r{-ZjP%~q?S3߾O US%$ #k[JveY\](ˌx=o~VjrloHx5`iYK8%,Fq5MBXUj# 08rp/֯+O'(f(JTU,q-=~F, o|j[?qa|I~U=cx('"q~.ضk<bUt s) "r]XX{vgU$0P,W$%cZX[zȱcG~&w'9z|iy 6(Y_pNquE<_p v(Y`Y%_`|bW8͕+WE#tv y~J#+ xڕIaqqׯpǸr w'8q8O>$o/+0~}C;FȵPSsuJᵡm+0>9Ȁ*dI0[SiDĎ{qddYF yx@~$ˌec3|u{oB8rd0G\/D-HY5.fפ|Ќ;s"Y'cANˎvfD5Lfyt@0=|8HDͅ wާ;ILBBTv"r3(ׯ_\`t#C!PvC,ނH%ɷ%Nr,,5P7p(@/O?DkdyyKR f C$4Uđܸvt*+u-cu P ljj$i;dx4AFUPU H5^|]Fc!CXNM&2W&$fl2p`DN9d# bW']g>az 6Sid= ݍطLj d-y$I"df~MQ\^8KqD$ bL ˫I̶Az=JTb+$048,0c!K9p4'aͅ w&xhhdU>}F"$JUE_۵7|dESv4V q7&HD b0O> $FD" jU d=\| S#NŲf͍$"(Wʄ4hmIxM<C<3Ĺ vȡ? 2Kk)v F$=0Y&<#W%Ih3qwﳹc5YB¤DKt&C4%:ryjSNr[m00Uב%;bڇamrsܚb L#P9}+уA.Oph`Iؿ{7Ϝ4ܺ3ӏo"⅋+#ɍDڷ;s.Oa;WY"3sW2=󀍍4\>Uät&M$!c [I?~o*C;QtF "y0w&^O Քlo፷ޡ6;/ 8~p7g޿n9yNEQLӤbfn!50a(/ʳ1;N$dI"sgޡTfHme $bb|`0HKEU)dd8y1Ο=;Q5A|@Cݭ&j$i50g9|p?@=#xM7Y_[fݎ?D Vx1:;y7P"k1')>k$a 7$l 2 -  ) Ca:::P4JB:$pQ^C;P Eղ,ˆ@\Elܪo|$ {8u =m1{%LܸGB5AJL.e^ 9g@Q sB6(o^C@>pd Y쬤X.g cQUB4Z[[Q5RDr}]ؽgwoq̻\^8ǕqwzUU}TH:OkO_~@0HoXuC=IȲݽMjsHV-*2lJL< X`X,F2bsmΞn;ʉ'@v@@J{Z:b2Iau3 μ!`RG獷ϠLNSvDM4bdxb!ύhFC ܸ~L Wc †cvE,O70훤 T"Tb2H1 LQ0_d$77ɗKTUbm--A ޞo`H$(hZ4d=,˴'\yIi#hei$rS;7Oc!Z&}{WW7no8>H:]orK CN.r@TeVxhUڑ7eO<Ξ{4 Y,Y  4Q5CO:GQD TECUUUqJ8;F|3cF0.3͛|P8%w˯/ 0@U43hf tupeJ"qܢR˒LmIcrwh`0aB!TUWB$3{?Ğ={hokelQuUQRd!CD :;.D~?{.c!z@ 30C誝 )_@ko aȴwuHr% B*P, b $l@OHEQduh4J0ldBR晧?Ʈ]CR\6͇&0LEXX^ce% }PP(L `L``0nhh騚k#|=w+y!@%O|s9^zO) l)r,͑h/ޖ ,KX8He "G*ɃSDbqT-KHrsIaC[ZU;QUU7PEXm d7EBgj-Pr=vʾDS5Ī5m* e4EE6.kcƚF(r2;wUG};0L"ъja&b&pBCtT]GQT$nM㴨 t8?j;S᧜}q)UVbnvŹ =JUAXAQe4UK`YVu`0Xr^y&nOO&03=E0xMc5`!=R AF'WsIJ Ao_lEׯ]|ߤoɫdj5cVzz%6,1hO=$|j딫e ]Ǫ64\zH,S{^"Q.fd92[5ޞh`(du6Y`F mssk$"}[X\:NV)VB&'dܻwT!F즻P(ģqE hAۙ䭛  GPIX^eԬZ J³+[u€CG)r ΝBuJ*)aa*ItvuF("{Xp$L,'O K**]ܟD¨^4ZۢVC"'5c)+H4 k!%,"Ze+b3x,JRaiqS(L,h,,(i(L$"`S$ ;ɨV51rgY"#o0e$+K˔EFFhwZ=kiȦ3a {:z@r7k|kEk Kx?8e;h4NOfJ 6Q*P*iIT*t[ˋKˢAwz\O0l;nɿ?K b(~pxfԻ>,ū+IZ4N0%2,l&C>F҂X*R YflS.ٵ{'O2:: o~::y8{cJݕ G@H'S{~o 믧>fC Gc t#v-Rwgw7Hr$W&ayaLjsQ$k$S)2,` N&vV#58GMGWigVSúrbY.f4NO3?"#2s'L޾|E R-W7o04ESQ|Q\VW18m!6Sˇ4n HV%‘(C;}Vjc[e @7lBxPR)/8o1so]x!QcOʪn$?{kMm7dOStkS/ Y$TUhO}y~t&)njR~Q+(;tAʕ w\_ɏ=0e$чiE^@R\Lrj!4,jI؎e]!yV=~k E{>C؍>Eo~Y~_1p,֊b'L\ŢJ'Ng_/B>!`EU ~N!"4RXV)$~~共cݚJ6~LP"8,_r*pȷ+r8BFct0tjYBaIRPadx\>}]WJLH2xX"AnA[4YO@Kܾ1J~dY'CH>/0&|oɍ\;Rm72ebo';Y锽I-iA(b=eyyesYR$VJ< ҎQFILs,VhCQ:Ӝ>QLx|A[>@% ZcN%p$JO v^c4%/)y$%ҒlHw5Yi!ͱ>E&-CP,C{g#dTʧL׸=@~oyUQ9p8JnO\GX%tM\,cH"m22W$[(狴wt0l+㗖XO SQy΃{d'Y›)cc5b ;ñx'}+f8dDb8Xe*2BX Rxor`@D2JXLTX(ޢ/Jӿ!ϼeDv=5¡cO0ywt&ͣtv<Xo|K9T?q<9ߦ^uikZ/8ȓj?7dGȲLXXh[ƚZ d yyͷ)gN͉)VSd3Iw$̰U"E[g$/>?&؆QMh"D'tsk]}zYc<5hRgO3SwI #J* L Fv LL+UJK YZZRJ9MR&[6x4FOȞu,-ot:"(6`[]f !|whƎo´}M#-p8F{W3XE Wrg"P)ѦZtaxzw(\ު21>_X q4e'DZvs$Vd#2=A0AwSH_|n Oob7g5sߨy7t$ia=6) le,1j"K\"K1Z@[ЌP$0);5鷮2y?Mr3Kg0]6="(³c|UEIDATijlW (`W޼Gա- #L$whUA.3!0U~r6A,>Bo>0aD,/R?";]=xSZMwZh +>4M wr*!(*}C;w{B>URVr we@ʚb=ȑÇٵk'@; &ow1(#Z͑w:U rMG7 FӀhf+$()i3W_7Ȳ?xQ+.Qʑ頯P0L4Ee4MsC72xqVUuh4 ȃ"wŶU۸ȵMM@l{yF"ha'Q'~W8/I͹79< mA1(QIod9&&( >x]{ )~ٔuja5lDjQVkNv !KTG'%$+$Ory^b>hTffޠRZGRbH&篾F($ڔ_E8YV3㡙 cBS}wE9ިXqV+L4yOpY;6{A&_"S*JꌏwΝ0#QfUӑ1/?y[4$ 0r ߨ,80.m$n..9JB>e;ޡ#Ao{/U`m:FaZ; &a+㷄zmh/"YG z\WEsJ@X#)*$o!ODMݞQṏN#5͆67=xT nɼ]% ۘvYgjʲi9JOiJR,( E=TEo=7y5e_ˢ'gU?oC9_3ggx^,nx|BK|!|no\uoU;gs33ːI=_5*nvikλ`nq-~N7c~z?}P|3BoF^B +;M🱸{v7]Q︘+o}}δs޽֊qg9Ǣ6o.̕ v;K~b+|./Wv(;1]H.;[/+v1T{g} BiY1&J RL)TRM-uC9KVJ,RJ-jZj[ %S˭Z{hz|އađFeFό3<ˬ; +*vP2;.!N8O9?տ~vͽvߝgx]^ gѱE;@@{홭.FӞɊd,gq-tg~v7Zo3ںf?vm ݱ' 6}'}>K Z7_bs3Ll c鉗Bډtl^{aDԙnNuY;՝eVيQ_C:֡b3=' s54[&=es/ onMcLJq&ȼ>_m] |]|?`{s[)Td,o6EVy|V&ypj CeTvΓY {k~e+륺C{ʻG= Vz-?zQBQVXG6 02(\hs^Ʋk=,ɰ;8!]?}v{Y>4Dk5؉j+;g=QȦ[(l$ NpϮFb poV5DWB 8Q̡h7R6ja7HJ3%;}"m2S^G{̲)+J,WxObpć}ˆ?|M0EW2vXcN0erAƚф #fxFm{#y3[s8j6;29;)I*mplm1O%m民@x@YLC^Do:kTdW!%"c_0Kh+4 7;p0v΂ ̵y(vIy#ofo^´e  ںpV{dB²;|4`TgQB3d#`a%!-9ǭsL(E ^) 6& g"؋&ŧFz(,@=1LP<JniP n͌zsXyHKs&.Jf^Q77&ϬUøSΌ ,(t<--4Ag}?k\&6p8U( q &{ъ r6a0]t#l#gl&cꬃ Sƶ8u b-bw@TpX6D y嬍: ;MvP~ 05ۏs8 GK(ܲM@VNV~Vҁs260,yD-!86 Ռfg4raN D['$BLp ;(XvؕL6`ro] ɸ!գ㐈0Fm`PH%f4X j b'j]~Hޕq @ͥ 1 בų4&Г]VFe5WOS+DPQ8P{߈9HCM>7Dh&؝%)!'MH"S"ܦJ o , JTwA60C rI `<6'"4؋ki~k h^v0iA `6 !$/wӱ(DU6ƉLDbu{"`lP Gnbt;UdJE${#O E4@;hQ (:jmD9t\P{zG| _oU)1 ho  &B-X8V 6rRm$4ELKe>[,Ai9%m?9p@@ 8dG`nC4p2YU#Y*WP}3AFTrL05TJCJ]z}4+H֡:ߒ%̷*e IFwSp^/B`7RL=cD)QxBEb2R/Ztb% ɢ- jbj_D=1 lM/OB2Z,E '(D\u!Xt;I@NH&jLU(A05-iO/-_,<i'""+1۬ހ7K=Y6ַK@zdYⲋX=Dؙo$ Y*| %bRD`~o5r: \*}GdVtNܠ(vt <%*I7X+MZyU_r<\-b}p M>k.Tnܐ*4I$IE,U8Ïxm^یmFȓX:cYMځJE19<=Ipx؊yDbu0iq>>YsW OxĔV!Tmt$9N{ $8c\T2R9KyJX1;mnVK&d㐡!s!IFPyo_v&ӭḩoYEJ oDG̠bQh$/œQ`*ʞ4~Љ ]D(5@r_LnG5Ԥ9$5|"d(F߳9` #\Y(ӐBafD*嚰aXL:E]p8;tъn2TuWxz%H_)3S@An U >dClmVC\{)*&>OG4?ܒ4GdWtp?ZUzT[M9R fJua)(abg`²Q g`~5]]G<}3*/AFlFjG*&ѮB652F@ETDF$ZkvL8HY +;Η@ hY$G!]؀drH^8ŢP_rwI=',:u ܣ'#T7a Uv wBwl8R'5 b` k<,nތ"Z94"bWGWy^'Z#&a^Uk$:4D$ cNK"؆.! I,,BUbœ^G&¤L2PZMU[q0l;{pMF'n628.MOx#+FU_$ lP@e3o|>Meh<4`e=A爏+!670@\"ׂWq[v]vO)aa>%SbQV6=X:D9$E갏\>B%]:|;e٘xG5[Z&'{-[=o0<9``l+NJ4.Aov]LZU:y^ZjU`RNR ILK:t,d|Im?0Aޠ+:9u7 |ݡY'д <1rRN \lW~TQS36oUaUpf %'B4kb@i&#aN-@E$IҮD]$?ae͍Crc T"貃PT$__z|gWJ1!GGtK#hNaAk#bSsXh`p]R+)5_?XQ"JCD;BzYa26iI^1pG:-Gvd-Ȉ{ җd^=;̻V *C9˒Z1} 75W K |6ReR 617p3b :B an-}kچjY vAoYI^JʾCLjY"R aїM N} +iHDO?O5ZZ@((䙆k;P1!D kR.ȁ֐fS(6~.KĝSPClYqٛlUA^gZԎ kN$U$)ce8K-< #ى-JGM_l' &q uU%BL KKl_ݥD0+UaKP5ڂԶv=H 6s S8KsN;qQ<J]JBHlO:'.Vw-N!;QS! 'D,M4ʐ=^1 vkrI}x$[+MjK@>GP^c8rbCfNUПO;"0NrP~©-קkZ,(1x):naNG9t&H'o%? q):3Б!<=/\AzKB !FxU f?t 6 C@JJ%?=KR*+ j}Dш'@HM]9x,aCPLus_PfpP\yـhe“~8,攧ś؇|tvK=}NrY@@ϲi6(%k&]Fdp3L[K-ѿ'6X.א BJ3KR+.{I$M9^({[O~ڷZu~;w5m*nYsv җ2&HH!r @΃X|< !?`Y2a}hcǰTbܚ`Ӧ4'_FQTlqBPx\g+zRv[- `@.[Rh8r$*jzo,l˄ip u{f90,BZ0KlMt\` ר*,x9bSQg 70|(/Ł >#*d>Y ҋo\6EDjU+(^Timwb㖭BZmU/aȖMד(Wg̦ʠ}Nb O;Y &tTp ]d!E* j5`m @ΡuY 0%}{[5buNT(聏Sס8p 6#k$y ʋI'[j?AHݽuDc(7GZ >!.a}Bͣr>>^8ZZxT{EdTؖu%T˾O"{x<3u;RGzL  JXdIoWTa 1ge&f?nk4i%%QSVΗzlbe}U7\[ϗKBTE"XǵRuQ|2.mX)-!z^lFsn&I,4%IL[d2PqV5 zUeBG!RF k`xym.yI$vxGU1 g,un2S-$ -+lEzY]<\j̤|Z8nlEMN6zwԏ @y5=[9W9et10[JrïiDe<>)40hH.'0,27>4=v7?.j+7CZR3~3<_}OCzؿxe)WׇoCѿUvy[~H:|Pz^3[m'w `٪E-YTRt#4ڠSF ^UB"FaRjj"; Np.E^h9(?@THY:=Rl׊l5\2^T~&LYuurՃuEv^W1nm7y;*udHV%S$l^KxߊF-/Z!_I0mCsjA?ec]_w>_oTy-]' IDATxڽWuxwpm(_dL+V̨ER-ю&%ua=e#ZiIHMy US {/.pq9fy7 ?@x%ƺ_}_ҹ}tgZ{L#/JG;u_^x3Uw0PA q[9S+gUzWtv%/t^588߿ʃDDZlD6͓ܷ87wQIW{K L`Ȍu6o'(:u)ѝ@G);&#l蚢kg:liBPXpg:ZH$'~ᗙ޵)u4Mxڻj6\+;o*hBжt v[ݝ 31Dxu"B)B Ac}s/D;};J%h6[ c$BȠ 8 GF Qֹ3U(ŋGv3=1 66uw,Q Hާ%vwA UBm;_oU8NZooYۮ? &vbY&s0pUsʿZ;75S~79;Tu>k/>a<鱵-cX4@%B (wg;^.ǶY^ZD5ss󬯭322§?i&3pM:ͦ>DJ]ޖ8iuxp;Nה p3W~Xã~#Ix?Jfjض7l}/7kR8 /_xs{<GWYY^^.{WcfxMH*DpE0M4/:FLog)Gqvq<2Lt*w"fvNsl.foݢX NRE=Ep::( /mLgX[LNM08<;E9L&3gX\cm\ >ȧ }F\ P,[ 4 33c8H:ܯ9 ٵ/}/N A4crE;q!_C Cwqr};9y䷷x382RXYC==д7s?︳/vMofg:I,HLĴ"Vk起H' LB2qE !ځ; * ڗ!r!x),mFGIeӼVCIavL1=60qS{B*etl! }) z.N) ~%H tTSJ1d` #uk|uK5 0- \pGaf糆܉\rR!w"'m??XLR ;v,/2{F] @.`&@&#C rxiGRÅJ `X7 N<:=KTYGὬ,Y&iPk9q,vNɤG⇔j 4M:h)x/~_}kv*t$O{Aܾå129#7iژXD"wD(CãP&{ADЇ욫gߕ0 ӧ(h4[\vM< 栳58|p/YݼR0,4ID4&'&G84Z s\`dxT27 {7yi gvvޫi?o!4 )D7< qeů٩`Ҡ{3,DLI$R\xLOdɤH)7T+Urp:I2 J27?kV)l- Jws[E?9҇{ 0*H@:a]sqYD(;C\5aZν%F];>ay"nx|\(ʿ ~FHU~o0>"bzzJѐI76p% ''d9rrܽ}$SoLz2ohS!s{*K&h64l]70, ӊε;y35M&L(Sc\t 0MIFu&:Kέ HMGjZiQ3AEEyx_wwXec!5I])ZvJ:Z2νڻiecJD,8kK\|)ҙw A4fOo!Yy<}xBRnX[YЁ}f?3s 0:{};'Ḫ;wJ"o=i_z'N ;JsܹzE.%1"JרVKloYqa&IRZ.I(Tݛchdd:N*#HhCu9@49 :cvQ-Y^ca;ϳ'aFw' xe]7 >ǟ;koE7 y_FX(/|OOEA0K$'{(W$cxM䓿  !$FuбC\V042́Çr7]cbz'tN4?ALx&+DsVqlV3S}QOIJؿgwq]j-nJ;x|H2%Lh4s'a [R389>zh4y)޹flˌ $=|Go#Ɩ}+ a$6}XV#ܿ="e:MEL$XRiq,r'Ak+T+vE:bmn24:N* ( $X|iGџ V6ysK1FF{&brb,0 tsx3s>~覉\qO yf\e=qm_}+S! dy )ahdss,.?PPȗi6ԛM4! t6KդRdyiN`u}@Gp_t!T fYáh(nR7peÃFG00g?4A3|2/=v$Wg&BT]S{N1M 0t׌>P_,VC#p6fjLZnBHu!%mcgtFAXvl4`zzU>'3^@#;!,S(uMJ*A+?v#p'ݝզ $g{#~g:GuY@7L4.ʫCઐEu4/ sCtoَCZFeJ&csŐQ&b4snskv1 ^F֧~i /%8. GCL P,*T0L7-՚y]>@,:A#t{eu3>:{`RIE{:4.`y>EHBRp%._> H8 eӴm4) T&M^Z244D mi`Zgqil.DZcGy 7o`l|D"&=W;[Ǽ8Ggeӝlb*{Gid^GiaIeNON ݈! :&0x0wNXx+ѪSo6h5Zxušı ]'w[3GԈF"LNNRx!ׯp%f]fen\W>V [-8DST;nZ}/=ۋx}tbXġT$͍0LJ=חyaE] $By; Tm٦?W*l _~^)5jQ$NFJI" iRF)_\*2<:B"gs}{X]_#sG\tVI67v/!p ˦=8`0ҍ; R{Uy`nR8t`L pԐ9|,ƙ?LQ}!Qru6>a\M`fzxHH, l O$urVkq}v{򱏽'>2< ;ٱ).6hD2dWF7kyYRc ®qgn0*U081^;>i'_<ٙPM\~R 0)Jw~eE0-ir/ht$uc1M"˱0wRa'Pufk tHlƲ,"[۬1M^|e{y>$X]c{krLQG 2}n\Q161IR:kyıٵs.\ڰ1 w t^O$?wZF3L2|m\yô(|g:\>Cǧrd𞓚x;nPVpKk޷:JQeLDl]`# D֗k5\=aX:Ǐ꼖NxXfC78{Tgu6+[Hĕ> =-_B(Ow)#c puj͋Z&F iѰmt)X&\u|>d`x{L&Y]]ܝE2Ì322B.#j$"Q锋 un^S;twI!5E"ٛ7xK+ täR*p`Z&rO}9C 0MRQ];:R6 R)zS`6C^~x4F1#ܿGV H#e+얍l3{Cƴ,?z:n0==raҽ޿nY&BW/!4ͽ#MC :c)r][E9aR( E 4N 6{?ŒD0H |r-"V$nH!UsGѰPh ?45gZ1Ih6m*[yn\:7BE QS,X[^fz.<,j)V~wkq\~7RI$.o :xzbHiM6&cvAhw u"Lrr@^'L̰Gf,'P.LkH)h6j(#qJ4M \98*w"N>}aass\.mo;ʶQ&ɯʯ"yLX-Aj-E,RE\K4"};Զm6י B__o}O> W\:D]45UR>[*#ӬQC).ViX8 i`Y&8 G9([Bn{ 9ý9ϓNgU__!  ?q>ů W/,_ xu1kPԫt۵OФD!5GSyN0N_RJ 4 (DLڭ%bض+OL S@ T+Cucn]FRt# 6T$K?7 Uڊ / FoW A;sh\.A$H\6[4UJ2HA\T,0Mا\nj>q/AQ@0I[~W `= b wHN z?B'5PeE]±Gxů{[ t Ww5~HzMX"iˏwFѽAыבв[ZN9m9\niu2L }80IDATZu!eI$"ǰ&n\%@3 /oV,P`zENX:@)]ETG)!u:&H83ZhԻifFܹb5GSAAJ):zJLTn4bdJ:ju=s̋ L ÝP- 9PA_w ʠ,\9>' + 202c~-Q5F/:帋BJI\%*CP6ZRLa{ÛiD#q$|FKebquB(ӂ `c BHяhWNGa,]DO iQ)3{$-G4.#!0tw;mooxyb>O"djvb%=b{{r"J0990mnݸɮ9 !Sn EaqHe"x%TD<> 0wh}}*wf h#Mwum6.*ᚣFv#豣LLN244*.M[[lڦjb&CQ)P-R@`yt>I<@N!2P .TABr*oEq_ ? ;(ջrLKjOOPF |wp6r 511&dT&T+9fE\i٤R$&i*UVWVz*\=D7 4MRZ-t p[2wf$I<)P=2|{JT!WP@jk/: 184yQ[ۨPXxڨQVi4LJtx,!daeybHZ8 fR],0wg`x|{fSOMDw.:u`CMun4-?I[?'klmol) rd)+k@4R4eaZloH\fh Ǿ}{7./T7" bMb8P̗AX$rp~88A>g}murnr&k\bisWvHX[_%"MlFs42N 2]K#8u_`աk+K3C!Yi8HCNjxdyq'#M׈DMtCGy@ [ys&&"Nl6Y_]c{s4yge`@'^K)bCpϲ4++kd'*=MET7q|1/~djY4l@@p3?ՓhOiE9pJoY]~@CA*"H؂VAtTfI1gc.~xL۱)$. X E#>A%ФK̵Z-Q2t;:w`e˧P? T83*k~7&B dՕ@,"XH4Hbe:Q"EVg#._ի HM,h,nXhR"rMk͛q @c'@;0kB^2xEOUףiH]X{ܹqtSlIĦEUa0alxnP)ܼq7NF-ꪎ\,j54q̃DmRpGp-kgD="x](勉}YҩuCpMX='+}$Zp%tnKR)BKĉ]AYG7 FGE[Wkl巰EpIfV Jm1t# 1,R!CĊ08m* 8| AH.p-gAF;ytʧT=R^ r$iV1bR^yw' Dc;]c_8 :r֫q$nI!MDz tal`Kn\yH004 LVҩ0!:}r&=^F=h 7hR=ɕR^CdY\n1-˺IT*E")(kحmPQ~ӶiVluZ՚SiHaLдZަT^\^Zi5Z&݌ i<1n,r=؇Oh;r}= {W,zT/%iQR;_gssD\$$`IIReq#{6]cPrjbޡ@5[[,ϻmARٗ^`ttӈ<^JׯXZQo SY^:g|F^&4\p=牒 ֱ_Oh*ƨ.r} AωE~4)IgHR<|0OZr 4rL,ŊFmo01b36/kܻRRm֩+(DqPK%_]+l/r ={h;tʊpΰ*|hϩ1!S%5LhƏuJdZDR"^-Q,5  ř36(*W0uxhxտZx@$!k0mkzb|bmEY sQӃӚ F7?SJ/^tANgE+ &{N}HsXN*A!N*#[lo܎,KP2,jTH68L8$6b|iv+Worϳ7yJdm@Ubl4V-OsiNj/ *HtVO9iGAVSiusʕ괃R O$xjS7Ԛf!$$vdS;$#[)!He,ܹ͝b"7o1+elreX|\dj̀+2-|au]N=\,p W8}tS\Pv`d&˽۳6M3r!Iu kđzݽ&sYq[0)r "Vlj;Cv`-W/o Q}:8=ރl’p΁p:GenG&zΞq[H2فa.< pkUZr TIJ*TVޢIdSA6o'}06w Dh>̉:BX,,UyŎ=:y9z&7>S 8FG S8cT |}Kt̑Hg$i/>ߠ٨So4lm4q-4HZI+Ywb d fZX ;B```d<6A&gǞI!87ZoV)8B:;Af&@WG NCmn_UnV*6 [h- 5?׾u [xcl@8[[ȣ-}_LjI4TɬΙv67X^Lp%*? Ydiae;^J Gڕc3yר@6MaZ>p+e{kVvgοN"_wd9dd|];dl|Qlom1w{;=˸M?v3goaXfƾTb>ǃO痰P.75)$h*E.?iF޵ W6J9uR 1SHSaDi51aj.vd||0(D#Q{GTl MWc~ɔ~e=; `g{τ dP p #֦v(/Tp\ߊ(k zDF˰ݦp?4P%Fa_9{@WN U ̨aap[ vID4}T'Op 4u޸vBVfmR,Dksl!F,cpp\j^{A!飋',GTݓЅEyfERN50A|!R׉Dce4] I$JEξsΜaTO3'DcfkMD _ ՋbOL)|cBݦ}sW~t,IsGxWjzkBv664H<Ή>CE:ʱAjL+B,wu4tk3 ~g\8u#M"÷;+ P+ѷ턎@\p Fqz{t[Fg oinߠ퓠~և2@}F7s]/ۥ^X*_QO_Dn>OO}ҩK n^1;f],۰sj?nd} 5'bj !~A[=oroYUo@>kki: rb-"9}B }zp8'A*.ZihٹPv1J* w+ RהwI\-)zEP<УLCG\NW+{_@O3!~ۨ4PꎹCѠw7?CW*vNࠇ;|AXQ݋TN9_٧CK?UO/ETI~~z? }R=)*B 'TS pfM}dug&*,+ o3A'ģ)I\o>EfQ}ю So2'\F?J:C:IENDB`qmidictl-1.0.1/src/android/PaxHeaders/AndroidManifest.xml0000644000000000000000000000013214675771705020420 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/AndroidManifest.xml0000644000175000001440000000342714675771705020416 0ustar00rncbcusers qmidictl-1.0.1/src/android/PaxHeaders/build.gradle0000644000000000000000000000013214675771705017106 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/android/build.gradle0000644000175000001440000000415114675771705017077 0ustar00rncbcusersbuildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:7.4.1' } } repositories { google() mavenCentral() } apply plugin: 'com.android.application' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) } android { /******************************************************* * The following variables: * - androidBuildToolsVersion, * - androidCompileSdkVersion * - qtAndroidDir - holds the path to qt android files * needed to build any Qt application * on Android. * * are defined in gradle.properties file. This file is * updated by QtCreator and androiddeployqt tools. * Changing them manually might break the compilation! *******************************************************/ compileSdkVersion androidCompileSdkVersion.toInteger() buildToolsVersion androidBuildToolsVersion ndkVersion androidNdkVersion // Extract native libraries from the APK packagingOptions.jniLibs.useLegacyPackaging true sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] res.srcDirs = [qtAndroidDir + '/res', 'res'] resources.srcDirs = ['resources'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } } tasks.withType(JavaCompile) { options.incremental = true } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { abortOnError false } // Do not compress Qt binary resources file aaptOptions { noCompress 'rcc' } defaultConfig { resConfig "en" minSdkVersion qtMinSdkVersion targetSdkVersion qtTargetSdkVersion ndk.abiFilters = qtTargetAbiList.split(",") } } qmidictl-1.0.1/src/PaxHeaders/qmidictlMixerStrip.h0000644000000000000000000000013214675771705017215 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMixerStrip.h0000644000175000001440000000413714675771705017212 0ustar00rncbcusers// qmidictlMixerStrip.h // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlMixerStrip_h #define __qmidictlMixerStrip_h #include "ui_qmidictlMixerStrip.h" //---------------------------------------------------------------------------- // qmidictlMixerStrip -- UI wrapper form. class qmidictlMixerStrip : public QWidget { Q_OBJECT public: // Constructor. qmidictlMixerStrip(QWidget *pParent = nullptr); // Destructor. ~qmidictlMixerStrip(); // Strip accessors. void setStrip(int iStrip, bool bRecord, bool bMute, bool bSolo, int iSlider); int strip() const; bool isRecord() const; bool isMute() const; bool isSolo() const; int slider() const; signals: // Strip signals. void recordToggled(int iStrip, bool bOn); void muteToggled(int iStrip, bool bOn); void soloToggled(int iStrip, bool bOn); void sliderChanged(int iStrip, int iValue); protected slots: // Strip slots. void recordSlot(bool bOn); void muteSlot(bool bOn); void soloSlot(bool bOn); void sliderSlot(int iValue); private: // The Qt-designer UI struct... Ui::qmidictlMixerStrip m_ui; // Current mixer strip page. int m_iStrip; int m_iBusy; }; #endif // __qmidictlMixerStrip_h // end of qmidictlMixerStrip.h qmidictl-1.0.1/src/PaxHeaders/qmidictlOptions.cpp0000644000000000000000000000013214675771705017075 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlOptions.cpp0000644000175000001440000002014114675771705017063 0ustar00rncbcusers// qmidictlOptions.cpp // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlAbout.h" #include "qmidictlOptions.h" #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) #include #include #if defined(Q_OS_WINDOWS) #include #endif #endif //------------------------------------------------------------------------- // qmidictlOptions - Prototype settings structure (pseudo-singleton). // // Singleton instance pointer. qmidictlOptions *qmidictlOptions::g_pOptions = nullptr; // Singleton instance accessor (static). qmidictlOptions *qmidictlOptions::getInstance (void) { return g_pOptions; } // Constructor. qmidictlOptions::qmidictlOptions (void) : m_settings(QMIDICTL_DOMAIN, QMIDICTL_TITLE) { // Pseudo-singleton reference setup. g_pOptions = this; loadOptions(); } // Default Destructor. qmidictlOptions::~qmidictlOptions (void) { saveOptions(); // Pseudo-singleton reference shut-down. g_pOptions = nullptr; } // Explicit load method. void qmidictlOptions::loadOptions (void) { // And go into general options group. m_settings.beginGroup("/Options"); // Network specific options... m_settings.beginGroup("/Network"); sInterface = m_settings.value("/Interface").toString(); sUdpAddr = m_settings.value("/UdpAddr", QMIDICTL_UDP_IPV4_ADDR).toString(); iUdpPort = m_settings.value("/UdpPort", QMIDICTL_UDP_PORT).toInt(); m_settings.endGroup(); // MIDI specific options... m_settings.beginGroup("/Midi"); iMmcDevice = m_settings.value("/MmcDevice", 0x7f).toInt(); m_settings.endGroup(); m_settings.endGroup(); } // Explicit save method. void qmidictlOptions::saveOptions (void) { // And go into general options group. m_settings.beginGroup("/Options"); // Network specific options... m_settings.beginGroup("/Network"); m_settings.setValue("/Interface", sInterface); m_settings.setValue("/UdpAddr", sUdpAddr); m_settings.setValue("/UdpPort", iUdpPort); m_settings.endGroup(); // MIDI specific options... m_settings.beginGroup("/Midi"); m_settings.setValue("/MmcDevice", iMmcDevice); m_settings.endGroup(); m_settings.endGroup(); // Save/commit to disk. m_settings.sync(); } // Settings accessor. QSettings& qmidictlOptions::settings (void) { return m_settings; } #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) void qmidictlOptions::show_error( const QString& msg ) { #if defined(Q_OS_WINDOWS) QMessageBox::information(nullptr, QApplication::applicationName(), msg); #else const QByteArray tmp = msg.toUtf8() + '\n'; ::fputs(tmp.constData(), stderr); #endif } #else // Help about command line options. void qmidictlOptions::print_usage ( const QString& arg0 ) { QTextStream out(stderr); const QString sEot = "\n\t"; const QString sEol = "\n\n"; out << QObject::tr("Usage: %1 [options]").arg(arg0) + sEol; out << QMIDICTL_TITLE " - " << QObject::tr(QMIDICTL_SUBTITLE) + sEol; out << QObject::tr("Options:") + sEol; #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINDOWS) out << " -i, --interface=[interface]" + sEot + QObject::tr("Use specific network interface (default = %1)") .arg(sInterface.isEmpty() ? "all" : sInterface) + sEol; #endif out << " -u, --udp-addr=[addr]" + sEot + QObject::tr("Use specific network address (default = %1)") .arg(sUdpAddr) + sEol; out << " -p, --udp-port=[port]" + sEot + QObject::tr("Use specific network port (default = %1)") .arg(iUdpPort) + sEol; out << " -m, --mmc-device=[num]" + sEot + QObject::tr("Use specific MMC device number (default = %1)") .arg(iMmcDevice) + sEol; out << " -h, --help" + sEot + QObject::tr("Show help about command line options.") + sEol; out << " -v, --version" + sEot + QObject::tr("Show version information.") + sEol; } #endif // Parse command line arguments into m_settings. bool qmidictlOptions::parse_args ( const QStringList& args ) { #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) QCommandLineParser parser; parser.setApplicationDescription( QMIDICTL_TITLE " - " + QObject::tr(QMIDICTL_SUBTITLE)); #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINDOWS) parser.addOption({{"i", "interface"}, QObject::tr("Use specific network interface (default = %1)") .arg(sInterface.isEmpty() ? "all" : sInterface), "name"}); #endif parser.addOption({{"u", "udp-addr"}, QObject::tr("Use specific network address (default = %1)") .arg(sUdpAddr), "addr"}); parser.addOption({{"p", "udp-port"}, QObject::tr("Use specific network port (default = %1)") .arg(iUdpPort), "port"}); parser.addOption({{"m", "mmc-device"}, QObject::tr("Use specific MMC device number (default = %1)") .arg(iMmcDevice), "num"}); parser.addHelpOption(); parser.addVersionOption(); parser.process(args); #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINDOWS) if (parser.isSet("interface")) { sInterface = parser.value("interface"); // Maybe empty! } #endif if (parser.isSet("udp-addr")) { const QString& sVal = parser.value("udp-addr"); if (sVal.isEmpty()) { show_error(QObject::tr("Option -u requires an argument (addr).")); return false; } sUdpAddr = sVal; } if (parser.isSet("udp-port")) { bool bOK = false; const int iVal = parser.value("udp-port").toInt(&bOK); if (!bOK) { show_error(QObject::tr("Option -p requires an argument (port).")); return false; } iUdpPort = iVal; } if (parser.isSet("mmc-device")) { bool bOK = false; const int iVal = parser.value("mmc-device").toInt(&bOK); if (!bOK) { show_error(QObject::tr("Option -m requires an argument (num).")); return false; } iMmcDevice = iVal; } #else QTextStream out(stderr); const QString sEol = "\n\n"; const int argc = args.count(); for (int i = 1; i < argc; ++i) { QString sVal; QString sArg = args.at(i); const int iEqual = sArg.indexOf('='); if (iEqual >= 0) { sVal = sArg.right(sArg.length() - iEqual - 1); sArg = sArg.left(iEqual); } else if (i < argc - 1) { sVal = args.at(i + 1); if (sVal[0] == '-') sVal.clear(); } #if !defined(Q_OS_SYMBIAN) && !defined(Q_OS_WINDOWS) if (sArg == "-i" || sArg == "--interface") { sInterface = sVal; // Maybe empty! if (iEqual < 0) i++; } else #endif if (sArg == "-u" || sArg == sArg == "--udp-addr") { if (sVal.isEmpty()) { out << QObject::tr("Option -u requires an argument (addr).") + sEol; return false; } sUdpAddr = sVal; if (iEqual < 0) ++i; } else if (sArg == "-p" || sArg == "--udp-port") { if (sVal.isEmpty()) { out << QObject::tr("Option -p requires an argument (port).") + sEol; return false; } iUdpPort = sVal.toInt(); if (iEqual < 0) ++i; } else if (sArg == "-m" || sArg == "--mmc-device") { if (sVal.isEmpty()) { out << QObject::tr("Option -m requires an argument (num).") + sEol; return false; } iMmcDevice = sVal.toInt(); if (iEqual < 0) i++; } else if (sArg == "-h" || sArg == "--help") { print_usage(args.at(0)); return false; } else if (sArg == "-v" || sArg == "--version") { out << QString("Qt: %1").arg(qVersion()); #if defined(QT_STATIC) out << "-static"; #endif out << '\n'; out << QString("%1: %2\n") .arg(QMIDICTL_TITLE) .arg(PROJECT_VERSION); return false; } } #endif // Alright with argument parsing. return true; } // end of qmidictlOptions.cpp qmidictl-1.0.1/src/PaxHeaders/CMakeLists.txt0000644000000000000000000000013214675771705015747 xustar0030 mtime=1727525829.430779736 30 atime=1727525829.430779736 30 ctime=1727525829.430779736 qmidictl-1.0.1/src/CMakeLists.txt0000644000175000001440000001171514675771705015744 0ustar00rncbcusers# project (qmidictl) set (CMAKE_INCLUDE_CURRENT_DIR ON) set (CMAKE_AUTOUIC ON) set (CMAKE_AUTOMOC ON) set (CMAKE_AUTORCC ON) if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/config.h) file (REMOVE ${CMAKE_CURRENT_SOURCE_DIR}/config.h) endif () configure_file (config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) set (HEADERS qmidictlAbout.h qmidictlOptions.h qmidictlMidiControl.h qmidictlMidiControlForm.h qmidictlOptionsForm.h qmidictlUdpDevice.h qmidictlMixerStrip.h qmidictlDialStyle.h qmidictlActionBar.h qmidictlActionBarStyle.h qmidictlMainForm.h ) set (SOURCES qmidictl.cpp qmidictlOptions.cpp qmidictlOptionsForm.cpp qmidictlMidiControl.cpp qmidictlMidiControlForm.cpp qmidictlUdpDevice.cpp qmidictlMixerStrip.cpp qmidictlDialStyle.cpp qmidictlActionBar.cpp qmidictlActionBarStyle.cpp qmidictlMainForm.cpp ) set (FORMS qmidictlOptionsForm.ui qmidictlMidiControlForm.ui qmidictlMixerStrip.ui qmidictlMainForm.ui ) set (RESOURCES qmidictl.qrc ) if (WIN32) set (RC_FILE ${CMAKE_CURRENT_SOURCE_DIR}/win32/${PROJECT_NAME}.rc) set (RES_FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.res.obj) find_program (WINDRES_EXECUTABLE NAMES windres mingw32-windres i686-mingw32-windres) if (MINGW) exec_program (${WINDRES_EXECUTABLE} ARGS "-i ${RC_FILE} -o ${RES_FILE} --include-dir=${CMAKE_CURRENT_SOURCE_DIR}/images") list (APPEND SOURCES ${RES_FILE}) else () list (APPEND SOURCES ${RC_FILE}) endif () endif () if (APPLE) set (ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/${PROJECT_NAME}.icns) list (APPEND SOURCES ${ICON_FILE}) set (MACOSX_BUNDLE_ICON_FILE ${PROJECT_NAME}.icns) set_source_files_properties (${ICON_FILE} PROPERTIES MACOSX_PACKAGE_LOCATION Resources) endif () if (ANDROID AND QT_VERSION_MAJOR GREATER_EQUAL 6) qt_add_executable (${PROJECT_NAME} MANUAL_FINALIZATION ${HEADERS} ${SOURCES} ${FORMS} ${RESOURCES} ) else () if (ANDROID) add_library (${PROJECT_NAME} SHARED ${HEADERS} ${SOURCES} ${FORMS} ${RESOURCES} ) else () add_executable (${PROJECT_NAME} ${HEADERS} ${SOURCES} ${FORMS} ${RESOURCES} ) endif () endif () if (ANDROID) set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android) string (REPLACE "." "0" VERSION_CODE "0${PROJECT_VERSION}") if (ANDROID_ABI MATCHES "x86_64") set (VERSION_CODE "${VERSION_CODE}04") elseif (ANDROID_ABI MATCHES "x86") set (VERSION_CODE "${VERSION_CODE}03") elseif (ANDROID_ABI MATCHES "armeabi-v7a") set (VERSION_CODE "${VERSION_CODE}02") elseif (ANDROID_ABI MATCHES "arm64-v8a") set (VERSION_CODE "${VERSION_CODE}01") else () set (VERSION_CODE "${VERSION_CODE}00") endif () set (VERSION_NAME "${PROJECT_VERSION}.${ANDROID_ABI}") set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY QT_ANDROID_VERSION_CODE ${VERSION_CODE}) set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY QT_ANDROID_VERSION_NAME ${VERSION_NAME}) endif () # Add some debugger flags. if (CONFIG_DEBUG AND UNIX AND NOT APPLE AND NOT ANDROID) set (CONFIG_DEBUG_OPTIONS -g -fsanitize=address -fno-omit-frame-pointer) target_compile_options (${PROJECT_NAME} PRIVATE ${CONFIG_DEBUG_OPTIONS}) target_link_options (${PROJECT_NAME} PRIVATE ${CONFIG_DEBUG_OPTIONS}) endif () set_target_properties (${PROJECT_NAME} PROPERTIES CXX_STANDARD 17) if (WIN32) set_target_properties (${PROJECT_NAME} PROPERTIES WIN32_EXECUTABLE true) endif () if (APPLE) set_target_properties (${PROJECT_NAME} PROPERTIES MACOSX_BUNDLE true) endif () target_link_libraries (${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Svg) if (CONFIG_IPV6) target_link_libraries (${PROJECT_NAME} PRIVATE Qt${QT_VERSION_MAJOR}::Network) endif () if (UNIX AND NOT APPLE AND NOT ANDROID) install (TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install (FILES images/${PROJECT_NAME}.png RENAME org.rncbc.${PROJECT_NAME}.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/32x32/apps) install (FILES images/${PROJECT_NAME}.svg RENAME org.rncbc.${PROJECT_NAME}.svg DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps) install (FILES appdata/org.rncbc.${PROJECT_NAME}.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) install (FILES appdata/org.rncbc.${PROJECT_NAME}.metainfo.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/metainfo) install (FILES man1/${PROJECT_NAME}.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) install (FILES man1/${PROJECT_NAME}.fr.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/fr/man1 RENAME ${PROJECT_NAME}.1) endif () if (WIN32) install (TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) install (FILES ${QM_FILES} DESTINATION ${CMAKE_INSTALL_DATADIR}/${PROJECT_NAME}/translations) endif () if (ANDROID AND QT_VERSION_MAJOR GREATER_EQUAL 6) qt_finalize_executable (${PROJECT_NAME}) endif () qmidictl-1.0.1/src/PaxHeaders/qmidictlDialStyle.h0000644000000000000000000000013214675771705017001 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlDialStyle.h0000644000175000001440000000244414675771705016775 0ustar00rncbcusers// qmidictlDialStyle.h // /**************************************************************************** Copyright (C) 2010-2021, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlDialStyle_h #define __qmidictlDialStyle_h #include class qmidictlDialStyle : public QCommonStyle { public: void drawComplexControl( ComplexControl cc, const QStyleOptionComplex *optc, QPainter *p, const QWidget *widget = 0) const; }; #endif // __qmidictlDialStyle_h // end of qmidictlDialStyle.h qmidictl-1.0.1/src/PaxHeaders/qmidictl.qrc0000644000000000000000000000013214675771705015524 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/qmidictl.qrc0000644000175000001440000000136614675771705015522 0ustar00rncbcusers images/qmidictl.png images/qmidictl.svg images/actionMenu.png images/actionOverflow.png images/actionOptions.png images/actionConfigure.png images/actionAbout.png images/actionBack.png images/actionReset.png images/actionAccept.png images/actionCancel.png images/formReset.png images/formRewind.png images/formForward.png images/formPlay.png images/formStop.png images/formRecord.png images/ledOn.png images/ledOff.png qmidictl-1.0.1/src/PaxHeaders/qmidictlMixerStrip.ui0000644000000000000000000000013214675771705017403 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMixerStrip.ui0000644000175000001440000000664514675771705017406 0ustar00rncbcusers rncbc aka Rui Nuno Capela QmidiCtl - A MIDI remote controller via IP/UDP multicast. Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. qmidictlMixerStrip 0 0 80 220 80 220 4 8 100 Qt::Vertical QSlider::TicksBothSides 10 1 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter R true M true S true Qt::Vertical 20 40 stripSlider recordButton muteButton soloButton qmidictl-1.0.1/src/PaxHeaders/qmidictlActionBar.h0000644000000000000000000000013214675771705016751 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlActionBar.h0000644000175000001440000001744314675771705016752 0ustar00rncbcusers// qmidictlActionBar.h // /**************************************************************************** Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlActionBar_h #define __qmidictlActionBar_h /* This code is borrowed, stirred and adapted from actionbar.h * * https://github.com/mbnoimi/QtActionBar.git * * (c) 2015 by Muhammad Bashir Al-Noimi * (c) 2014 by Stefan Frings * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. */ #include #include // Forward decls. class QAction; class QHBoxLayout; class QToolButton; class QLabel; class QMenu; class QStyleOption; /** * Toolbar similar to Android's Action Bar, can also be used on Desktop OS. * The action bar shows an icon, a title and any number of action buttons. * Also the title can have a menu of navigation actions. *

* If the buttons do not fit into the window, then they are displayed * as an "overflow" menu on the right side. *

* See http://developer.android.com/design/patterns/actionbar.html * * To be used within a vertical box layout this way: *


 * MainWindow::MainWindow(QWidget *parent) : QWidget(parent) {
 *     QVBoxLayout *layout = new QVBoxLayout(this);
 *     layout->setMargin(0);
 *     layout->setSizeConstraint(QLayout::SetNoConstraint);
 *
 *     // Action bar
 *     ActionBar *actionBar=new ActionBar(this);
 *     actionBar->setTitle("My App",false);
 *     actionBar->addMenuItem(new QAction("News",this));
 *     actionBar->addMenuItem(new QAction("Weather",this));
 *     actionBar->addButton(new QAction(QIcon(":icons/search"),"Search",this));
 *     actionBar->addButton(new QAction(QIcon(":icons/call"),"Call",this));
 *     actionBar->addButton(new QAction(QIcon(":icons/settings"),"Settings",this));
 *     layout->addWidget(actionBar);
 *
 *     // Content of main window below the action bar
 *     layout->addWidget(new QLabel("Hello",this));
 *     layout->addStretch();
 * }
 * 
* The layout of the main window must use the option QLayout::SetNoConstraint, * to support screen rotation on mpbile devices. *

* The action bar needs two icons in the resource file: *

    *
  • QIcon(":icons/app") is used for the initial display. *
  • QIcon(":icons/app_up") is used when you enable "up" navigation by calling setTitle(). *
*/ class qmidictlActionBar : public QWidget { Q_OBJECT public: /** * Constructor, creates an Activity bar with icon and title but no action buttons. * The icon is loaded from the resource file via QIcon(":icons/app"). * The title is taken from QApplication::applicationDisplayName(). * * @param parent Points to the parent window. */ explicit qmidictlActionBar(QWidget *parent = 0); /** * Destructor. */ ~qmidictlActionBar(); /** * Adds an item to the navigation menu of the action bar. * @param action The action, containing at least a text and optionally an icon. The action emits signal triggered() when clicked. */ void addMenuItem(QAction *action); /** * Adds many items to the navigation menu of the action bar. * @param actions List of actions. * @see addAction() */ void addMenuItems(QList actions); /** * Removes an item from the navigation menu of the action bar. * @param action The action that had been added before. */ void removeMenuItem(QAction *action); /** * Set icon of the action bar. * @param icon Either the icon of the application or the current view within the application. */ void setIcon(const QIcon& icon); /** Get the current icon of the action bar. */ const QIcon& icon() const; /** * Set title of the action bar. * @param title Either the name of the application or title of the current view within the application. * @param showUpButton Enables "up" navigation. Then the action bar emits signal up() on click on the icon. */ void setTitle(const QString& title); /** Get the current title of the action bar. */ QString title() const; /** * Adds an action button (or overflow menu item) to the action bar. * @param action The action, containing at least a text and optinally an icon. The action emits signal triggered() when clicked. * @param position Insert before this position. 0=before first button, 1=second button. Default is -1=append to the end. */ void addButton(QAction *action, int position = -1); /** * Removes an action button (or overflow menu item) from the action bar. * @param action The action that had been added before. */ void removeButton(QAction *action); public slots: /** Can be used to open the overflow menu */ void openOverflowMenu(); protected: /** * Adjust the menu icon in the action bar. A menu with just * one single item will show its action icon. */ void adjustMenu(); /** * Adjust the number of buttons in the action bar. Buttons that don't * fit go into the overflow menu. You need to call this method after * adding, removing or changing the visibility of an action. */ void adjustContent(); /** * Overrides the paintEvent method to draw background color properly. */ void paintEvent(QPaintEvent* event); /** * Overrides the resizeEvent to adjust the content depending on the new size. */ void resizeEvent(QResizeEvent* event); protected slots: /** Listener for changes in action enabled/disabled status */ void actionChanged(); /** Listener for navigation menu open signals */ void aboutToShowAppMenu(); /** Listener for navigation menu close signals */ void aboutToHideAppMenu(); private: /** Horizontal layout of the navigation bar */ QHBoxLayout *m_layout; /** The application or current view icon. */ QIcon m_appIcon; /** The Button that contains the applications icon, used for "up" navigation. */ QToolButton *m_appButton; /** The menu that appears when the user clicks on the navigation menu button */ QMenu *m_appMenu; /** The label that contains the title. */ QLabel *m_appTitle; /** List of actions for the action buttons and overflow menu. */ QList m_buttonActions; /** List of action buttons, same order as buttonActions. */ QList m_actionButtons; /** Overflow button, is only visible when there are more buttons than available space. */ QToolButton *m_overflowButton; /** The menu that appears when the user clicks on the overflow button. */ QMenu *m_overflowMenu; }; #endif // __qmidictlActionBar_h // end of qmidictlActionBar.h qmidictl-1.0.1/src/PaxHeaders/qmidictlMidiControlForm.ui0000644000000000000000000000013214675771705020344 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMidiControlForm.ui0000644000175000001440000001324414675771705020340 0ustar00rncbcusers rncbc aka Rui Nuno Capela QmidiCtl - A MIDI remote controller via IP/UDP multicast. Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. qmidictlMidiControlForm 0 0 320 240 Configure :/images/qmidictl.svg 8 0 8 8 &Command: CommandComboBox &Type: ControlTypeComboBox Cha&nnel: ChannelSpinBox (Track) false 16 Qt::Horizontal 20 20 &Parameter: ParamComboBox &Track &Logarithmic Qt::Vertical 20 8 Qt::Vertical QDialogButtonBox::Reset|QDialogButtonBox::Cancel|QDialogButtonBox::Ok CommandComboBox ControlTypeComboBox ChannelSpinBox ParamComboBox ParamTrackCheckBox LogarithmicCheckBox DialogButtonBox qmidictl-1.0.1/src/PaxHeaders/qmidictlActionBar.cpp0000644000000000000000000000013214675771705017304 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlActionBar.cpp0000644000175000001440000002130614675771705017276 0ustar00rncbcusers// qmidictlActionBar.cpp // /**************************************************************************** Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ /* This code is borrowed, stirred and adapted from actionbar.cpp * * https://github.com/mbnoimi/QtActionBar.git * * (c) 2015 by Muhammad Bashir Al-Noimi * (c) 2014 by Stefan Frings * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. */ #include "qmidictlAbout.h" #include "qmidictlActionBar.h" #if QT_VERSION < QT_VERSION_CHECK(5, 1, 0) #include #endif #include #include #include #include #include #include #include #include #include #include #include qmidictlActionBar::qmidictlActionBar ( QWidget *parent ) : QWidget(parent) { #if defined(Q_OS_ANDROID) // Make up the action-bar colors... QPalette pal(QWidget::palette()); pal.setColor(QPalette::WindowText, pal.highlightedText().color()); pal.setColor(QPalette::Window, pal.highlight().color()); QWidget::setPalette(pal); #endif // Make sure the background is filled accordingly... QWidget::setAutoFillBackground(true); // Create layout m_layout = new QHBoxLayout(this); m_layout->setSpacing(16); m_layout->setContentsMargins(8, 8, 8, 8); m_layout->setSizeConstraint(QLayout::SetNoConstraint); // App Navigation/Menu Button m_appButton = new QToolButton(); m_appButton->setAutoRaise(true); m_appButton->setFocusPolicy(Qt::NoFocus); m_appButton->setPopupMode(QToolButton::InstantPopup); m_appMenu = new QMenu(m_appButton); QObject::connect(m_appMenu, SIGNAL(aboutToHide()), SLOT(aboutToHideAppMenu())); QObject::connect(m_appMenu, SIGNAL(aboutToShow()), SLOT(aboutToShowAppMenu())); m_layout->addWidget(m_appButton); // View Control Button m_appTitle = new QLabel(); const QFont& font = QWidget::font(); m_appTitle->setFont(QFont(font.family(), font.pointSize() + 1, QFont::Bold)); m_layout->addWidget(m_appTitle); // Spacer m_layout->addStretch(1); // Action Overflow Button m_overflowButton = new QToolButton(); m_overflowButton->setIcon(QIcon(":/images/actionOverflow.png")); m_overflowButton->setToolTip(tr("more")); m_overflowButton->setAutoRaise(true); m_overflowButton->setFocusPolicy(Qt::NoFocus); m_overflowButton->setPopupMode(QToolButton::InstantPopup); m_overflowMenu = new QMenu(m_overflowButton); m_overflowButton->setMenu(m_overflowMenu); m_layout->addWidget(m_overflowButton); } qmidictlActionBar::~qmidictlActionBar (void) { } void qmidictlActionBar::resizeEvent ( QResizeEvent *event ) { const int oldWidth = event->oldSize().width(); const int newWidth = event->size().width(); if (oldWidth != newWidth) adjustContent(); } void qmidictlActionBar::paintEvent ( QPaintEvent * ) { QStyleOption opt; opt.initFrom(this); QPainter painter(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); } void qmidictlActionBar::setIcon ( const QIcon& icon ) { m_appIcon = icon; } const QIcon& qmidictlActionBar::icon (void) const { return m_appIcon; } void qmidictlActionBar::setTitle ( const QString& title ) { m_appTitle->setText(title); } QString qmidictlActionBar::title (void) const { return m_appTitle->text(); } void qmidictlActionBar::adjustMenu (void) { QObject::disconnect(m_appButton, SIGNAL(clicked(bool)), nullptr, nullptr); if (!m_appMenu->isEmpty()) { const QList& actions = m_appMenu->actions(); if (actions.count() > 1) { m_appButton->setIcon(QIcon(":/images/actionMenu.png")); m_appButton->setMenu(m_appMenu); } else { QAction *action = actions.first(); m_appButton->setIcon(action->icon()); m_appButton->setMenu(nullptr); QObject::connect(m_appButton, SIGNAL(clicked(bool)), action, SLOT(trigger())); } } else { m_appButton->setIcon(m_appIcon); m_appButton->setMenu(nullptr); } } void qmidictlActionBar::adjustContent (void) { #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) const int screenWidth = QApplication::primaryScreen()->availableSize().width(); #else const int screenWidth = QApplication::desktop()->screen()->width(); #endif adjustMenu(); m_overflowButton->hide(); // Check if all action buttons fit into the available space. for (int i = 0; i < m_buttonActions.size(); ++i) { QAction *action = m_buttonActions.at(i); QToolButton *button = m_actionButtons.at(i); if (action->isVisible()) { button->show(); } } if (sizeHint().width() > screenWidth) { // The buttons don't fit, we need an overflow menu. m_overflowButton->show(); m_overflowMenu->clear(); } else { m_overflowButton->hide(); } // Show/Hide action buttons and overflow menu entries QAction *lastAction = 0; for (int i = m_buttonActions.size() - 1; i >= 0; --i) { QAction *action = m_buttonActions.at(i); QToolButton *button = m_actionButtons.at(i); if (action->isVisible()) { if (sizeHint().width() <= screenWidth) { // show as button button->show(); } else { // show as overflow menu entry button->hide(); m_overflowMenu->insertAction(lastAction, action); lastAction = action; } } } } void qmidictlActionBar::addMenuItem ( QAction *action ) { // QWidget::addAction(action); m_appMenu->addAction(action); adjustMenu(); } void qmidictlActionBar::addMenuItems ( QList actions ) { for (int i = 0; i < actions.size(); ++i) addMenuItem(actions.at(i)); } void qmidictlActionBar::removeMenuItem ( QAction *action ) { // QWidget::removeAction(action); m_appMenu->removeAction(action); adjustMenu(); } void qmidictlActionBar::addButton ( QAction *action, int position ) { if (position == -1) position = m_buttonActions.size(); m_buttonActions.insert(position, action); QToolButton *button = new QToolButton(); button->setText(action->text()); button->setToolTip(action->text()); const QIcon& icon = action->icon(); if (!icon.isNull()) { button->setIcon(icon); button->setToolButtonStyle(Qt::ToolButtonIconOnly); } button->setEnabled(action->isEnabled()); button->setFocusPolicy(Qt::NoFocus); button->setAutoRaise(true); QObject::connect(button, SIGNAL(clicked(bool)), action, SLOT(trigger())); QObject::connect(action, SIGNAL(changed()), SLOT(actionChanged())); m_actionButtons.insert(position, button); m_layout->insertWidget(position + 3, button); } void qmidictlActionBar::removeButton ( QAction* action ) { QToolButton *button = nullptr; for (int i = 0; i < m_buttonActions.size(); ++i) { if (m_buttonActions.at(i) == action) { button = m_actionButtons.at(i); break; } } if (button) { m_layout->removeWidget(button); m_actionButtons.removeOne(button); delete button; m_buttonActions.removeOne(action); } } void qmidictlActionBar::openOverflowMenu (void) { if (m_overflowButton->isVisible()) m_overflowButton->click(); } void qmidictlActionBar::actionChanged (void) { QAction *action = qobject_cast (sender()); const int index = m_buttonActions.indexOf(action); m_actionButtons[index]->setEnabled(action->isEnabled()); m_actionButtons[index]->setIcon(action->icon()); } void qmidictlActionBar::aboutToHideAppMenu (void) { m_appButton->setIcon(QIcon(":/images/actionMenu.png")); } void qmidictlActionBar::aboutToShowAppMenu (void) { m_appButton->setIcon(m_appIcon); } // end of qmidictlActionBar.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictlMainForm.ui0000644000000000000000000000013214675771705017005 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMainForm.ui0000644000175000001440000002302414675771705016776 0ustar00rncbcusers rncbc aka Rui Nuno Capela QmidiCtl - A MIDI remote controller via IP/UDP multicast. Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. qmidictlMainForm 0 0 480 240 :/images/qmidictl.svg 8 0 0 4 Qt::Horizontal 40 20 MIDI In Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter :/images/ledOff.png Out Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter :/images/ledOff.png 8 4 0 0 220 220 Qt::NoFocus 100 true RS&T :/images/formReset.png RE&W :/images/formRewind.png true &PLAY :/images/formPlay.png true &STOP :/images/formStop.png &REC :/images/formRecord.png true &FFWD :/images/formForward.png true 32 16 < Qt::Horizontal 20 8 32 16 > &Options &Configure &About E&xit qmidictlMixerStrip QWidget
qmidictlMixerStrip.h
prevStripPageButton nextStripPageButton jogWheelDial resetButton rewindButton playButton stopButton recordButton forwardButton
qmidictl-1.0.1/src/PaxHeaders/qmidictlMainForm.h0000644000000000000000000000013214675771705016617 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMainForm.h0000644000175000001440000001463014675771705016613 0ustar00rncbcusers// qmidictlMainForm.h // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlMainForm_h #define __qmidictlMainForm_h #include "ui_qmidictlMainForm.h" #include // Forward declarations. class qmidictlUdpDevice; class qmidictlMixerStrip; class qmidictlDialStyle; #if defined(Q_OS_ANDROID) class qmidictlActionBar; #endif class QTouchEvent; //---------------------------------------------------------------------------- // qmidictlMainForm -- UI wrapper form. class qmidictlMainForm : public QMainWindow { Q_OBJECT public: // Constructor. qmidictlMainForm(QWidget *pParent = nullptr); // Destructor. ~qmidictlMainForm(); // Kind of singleton reference. static qmidictlMainForm *getInstance(); // Strip bank accessors. void setCurrentStripPage(int iStripPage); int currentStripPage() const; // Setup method. void setup(); protected: // MMC Command Codes. enum MmcCommand { MMC_STOP = 0x01, MMC_PLAY = 0x02, MMC_DEFERRED_PLAY = 0x03, MMC_FAST_FORWARD = 0x04, MMC_REWIND = 0x05, MMC_RECORD_STROBE = 0x06, MMC_RECORD_EXIT = 0x07, MMC_RECORD_PAUSE = 0x08, MMC_PAUSE = 0x09, MMC_EJECT = 0x0a, MMC_CHASE = 0x0b, MMC_COMMAND_ERROR_RESET = 0x0c, MMC_RESET = 0x0d, MMC_JOG_START = 0x20, MMC_JOG_STOP = 0x21, MMC_WRITE = 0x40, MMC_MASKED_WRITE = 0x41, MMC_READ = 0x42, MMC_UPDATE = 0x43, MMC_LOCATE = 0x44, MMC_VARIABLE_PLAY = 0x45, MMC_SEARCH = 0x46, MMC_SHUTTLE = 0x47, MMC_STEP = 0x48, MMC_ASSIGN_SYSTEM_MASTER = 0x49, MMC_GENERATOR_COMMAND = 0x4a, MMC_MTC_COMMAND = 0x4b, MMC_MOVE = 0x4c, MMC_ADD = 0x4d, MMC_SUBTRACT = 0x4e, MMC_DROP_FRAME_ADJUST = 0x4f, MMC_PROCEDURE = 0x50, MMC_EVENT = 0x51, MMC_GROUP = 0x52, MMC_COMMAND_SEGMENT = 0x53, MMC_DEFERRED_VARIABLE_PLAY = 0x54, MMC_RECORD_STROBE_VARIABLE = 0x55, MMC_WAIT = 0x7c, MMC_RESUME = 0x7f }; // MMC Command dispatcher. void sendMmcCommand (MmcCommand cmd, unsigned char *data = nullptr, unsigned short len = 0); // MMC sub-command codes (as for MASKED_WRITE). enum MmcSubCommand { MMC_TRACK_NONE = 0x00, MMC_TRACK_RECORD = 0x4f, MMC_TRACK_MUTE = 0x62, MMC_TRACK_SOLO = 0x66 // Custom-implementation ;) }; void sendMmcMaskedWrite(MmcSubCommand scmd, int iTrack, bool bOn); // MMC dispatch special commands. void sendMmcLocate(unsigned long iLocate); void sendMmcStep(int iDelta); // MIDI data transmitter helpers. void sendData3( unsigned char data1, unsigned char data2, unsigned char data3); void sendData2( unsigned char data1, unsigned char data2); void sendPitchBend(unsigned short iChannel, int iValue); // Generic command dispatcher. void sendCommand(int iCommand, int iTrack = 0, int iValue = 0); // Network transmitter. void sendData(unsigned char *data, unsigned short len); void recvData(unsigned char *data, unsigned short len); // Initialize mixer strips. void initMixerStrips(); void initMixerStrip(qmidictlMixerStrip *pStrip); // Strip states methods. void initStripStates(); void saveStripState(qmidictlMixerStrip *pStrip, int iStrip); void loadStripState(qmidictlMixerStrip *pStrip, int iStrip); void saveStripPage(int iStripPage); void loadStripPage(int iStripPage); // Common form stabilizer. void stabilizeForm(); // Provided for multi-touch support... bool eventFilter(QObject *pObject, QEvent *pEvent); bool touchEvent(QTouchEvent *pTouchEvent); protected slots: // Mixer strip slots. void prevStripPageSlot(); void nextStripPageSlot(); void stripRecordSlot(int iStrip, bool bOn); void stripMuteSlot(int iStrip, bool bOn); void stripSoloSlot(int iStrip, bool bOn); void stripSliderSlot(int iStrip, int iValue); // Jog wheel slot. void jogWheelSlot(int iValue); // Transport slots. void resetSlot(); void rewindSlot(bool bOn); void playSlot(bool bOn); void stopSlot(); void recordSlot(bool bon); void forwardSlot(bool bOn); // Network listener/receiver slot. void receiveSlot(QByteArray data); // Timer slot funtion. void timerSlot(); // Main action slots. void optionsSlot(); void configureSlot(); void aboutSlot(); void exitSlot(); private: // The Qt-designer UI struct... Ui::qmidictlMainForm m_ui; // The main network device. qmidictlUdpDevice *m_pUdpDevice; // Number of strip pages. int m_iStripPages; // Strip state table. struct StripState { int strip; bool record; bool mute; bool solo; int slider; } *m_pStripStates; // Current mixer strip page. int m_iCurrentStripPage; // Jog wheel last known state. int m_iJogWheelDelta; int m_iJogWheelValue; // Activity LED counters. int m_iMidiInLed; int m_iMidiOutLed; // Special style for the jog wheel dial. qmidictlDialStyle *m_pDialStyle; #if defined(Q_OS_ANDROID) // Special action-bar for the android stuff. qmidictlActionBar *m_pActionBar; #endif // Current touch-point-id to widgets map. QHash m_touched; // Custom swipe gesture trackers. bool m_bSwipe; // Kind-of singleton reference. static qmidictlMainForm *g_pMainForm; }; #endif // __qmidictlMainForm_h // end of qmidictlMainForm.h qmidictl-1.0.1/src/PaxHeaders/config.h.cmake0000644000000000000000000000013214675771705015704 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/config.h.cmake0000644000175000001440000000270314675771705015676 0ustar00rncbcusers#ifndef CONFIG_H #define CONFIG_H /* Define to the title of this package. */ #cmakedefine PROJECT_TITLE "@PROJECT_TITLE@" /* Define to the name of this package. */ #cmakedefine PROJECT_NAME "@PROJECT_NAME@" /* Define to the version of this package. */ #cmakedefine PROJECT_VERSION "@PROJECT_VERSION@" /* Define to the description of this package. */ #cmakedefine PROJECT_DESCRIPTION "@PROJECT_DESCRIPTION@" /* Define to the homepage of this package. */ #cmakedefine PROJECT_HOMEPAGE_URL "@PROJECT_HOMEPAGE_URL@" /* Define to the copyright of this package. */ #cmakedefine PROJECT_COPYRIGHT "@PROJECT_COPYRIGHT@" /* Define to the domain of this package. */ #cmakedefine PROJECT_DOMAIN "@PROJECT_DOMAIN@" /* Default installation prefix. */ #cmakedefine CONFIG_PREFIX "@CONFIG_PREFIX@" /* Define to target installation dirs. */ #cmakedefine CONFIG_BINDIR "@CONFIG_BINDIR@" #cmakedefine CONFIG_LIBDIR "@CONFIG_LIBDIR@" #cmakedefine CONFIG_DATADIR "@CONFIG_DATADIR@" #cmakedefine CONFIG_MANDIR "@CONFIG_MANDIR@" /* Define if debugging is enabled. */ #cmakedefine CONFIG_DEBUG @CONFIG_DEBUG@ /* Define to 1 if you have the header file. */ #cmakedefine HAVE_SIGNAL_H @HAVE_SIGNAL_H@ /* Define if multi-touch is supported */ #cmakedefine CONFIG_MULTI_TOUCH @CONFIG_MULTI_TOUCH@ /* Define if IPv6 is supported */ #cmakedefine CONFIG_IPV6 @CONFIG_IPV6@ /* Define if Wayland is supported */ #cmakedefine CONFIG_WAYLAND @CONFIG_WAYLAND@ #endif /* CONFIG_H */ qmidictl-1.0.1/src/PaxHeaders/qmidictlActionBarStyle.cpp0000644000000000000000000000013214675771705020325 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlActionBarStyle.cpp0000644000175000001440000001222014675771705020312 0ustar00rncbcusers// qmidictlActionBarStyle.cpp // /**************************************************************************** Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ /* This code is borrowed, stirred and adapted from menustyle.cpp * * https://github.com/mbnoimi/QtActionBar.git * * (c) 2015 by Muhammad Bashir Al-Noimi * (c) 2014 by Stefan Frings * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. */ #include "qmidictlActionBarStyle.h" #include #include #include #include qmidictlActionBarStyle::qmidictlActionBarStyle ( QStyle *style ) : QProxyStyle(style) { } int qmidictlActionBarStyle::dpToPixels ( int dp ) { #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) return (dp * QApplication::primaryScreen()->physicalDotsPerInch()) / 160; #else return dp; #endif } void qmidictlActionBarStyle::drawComplexControl ( ComplexControl control, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget ) const { if (control == CC_ToolButton) { const QStyleOptionToolButton *opt = qstyleoption_cast (option); if (opt) { QStyleOptionToolButton newOption(*opt); // newOption.state = State_AutoRaise; // if (opt->state & State_Enabled) // newOption.state |= State_Enabled; if (opt->state | State_DownArrow) newOption.state &= ~State_DownArrow; if (opt->state & (State_Sunken | State_On)) { newOption.palette.setBrush( QPalette::ButtonText, newOption.palette.highlightedText()); newOption.palette.setBrush( QPalette::Button, newOption.palette.highlight()); } QProxyStyle::drawComplexControl(control, &newOption, painter, widget); #if defined(Q_OS_ANDROID) if (opt->state & (State_Sunken | State_On)) { QColor over = newOption.palette.highlight().color(); over.setAlpha(120); painter->save(); painter->fillRect(newOption.rect.adjusted(+8, +8, -8, -8), over); painter->restore(); } #endif return; } } QProxyStyle::drawComplexControl(control, option, painter, widget); } void qmidictlActionBarStyle::drawControl ( ControlElement control, const QStyleOption *option, QPainter *painter, const QWidget *widget ) const { if (control == CE_PushButton) { const QStyleOptionButton *opt = qstyleoption_cast (option); if (opt) { QStyleOptionButton newOption(*opt); if (opt->state | State_HasFocus) newOption.state &= ~State_HasFocus; if (opt->state | State_DownArrow) newOption.state &= ~State_DownArrow; if (opt->state & (State_Sunken | State_On)) { newOption.palette.setBrush( QPalette::ButtonText, newOption.palette.highlightedText()); newOption.palette.setBrush( QPalette::Button, newOption.palette.highlight()); } QProxyStyle::drawControl(control, &newOption, painter, widget); #if defined(Q_OS_ANDROID) if (opt->state & (State_Sunken | State_On)) { QColor over = newOption.palette.color(QPalette::Highlight); over.setAlpha(120); painter->save(); painter->fillRect(newOption.rect.adjusted(+8, +8, -8, -8), over); painter->restore(); } #endif return; } } QProxyStyle::drawControl(control, option, painter, widget); } int qmidictlActionBarStyle::pixelMetric ( PixelMetric metric, const QStyleOption* option, const QWidget* widget ) const { switch (metric) { case PM_ButtonIconSize: case PM_SmallIconSize: case PM_ToolBarIconSize: return dpToPixels(32); case PM_LargeIconSize: return dpToPixels(48); #if QT_VERSION >= QT_VERSION_CHECK(6, 1, 0) case PM_SliderThickness: case PM_SliderLength: return dpToPixels(32); #endif default: return QProxyStyle::pixelMetric(metric, option, widget); } } // end of qmidictlActionBarStyle.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictl.cpp0000644000000000000000000000013214675771705015521 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/qmidictl.cpp0000644000175000001440000002171714675771705015521 0ustar00rncbcusers// qmidictl.cpp // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlAbout.h" #include "qmidictlOptions.h" #include "qmidictlMidiControl.h" #include "qmidictlMainForm.h" #include #if defined(Q_OS_ANDROID) #include "qmidictlActionBarStyle.h" #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) #include #endif #endif #if defined(Q_OS_SYMBIAN) // to lock orientation in Symbian #include #include #include #include #endif #if defined(Q_OS_ANDROID) //------------------------------------------------------------------------- // update_palette() - Application palette settler. // static void update_palette ( QPalette& pal ) { pal.setColor(QPalette::Active, QPalette::Window, QColor(73, 78, 88)); pal.setColor(QPalette::Inactive, QPalette::Window, QColor(73, 78, 88)); pal.setColor(QPalette::Disabled, QPalette::Window, QColor(64, 68, 77)); pal.setColor(QPalette::Active, QPalette::WindowText, QColor(182, 193, 208)); pal.setColor(QPalette::Inactive, QPalette::WindowText, QColor(182, 193, 208)); pal.setColor(QPalette::Disabled, QPalette::WindowText, QColor(97, 104, 114)); pal.setColor(QPalette::Active, QPalette::Base, QColor(60, 64, 72)); pal.setColor(QPalette::Inactive, QPalette::Base, QColor(60, 64, 72)); pal.setColor(QPalette::Disabled, QPalette::Base, QColor(52, 56, 63)); pal.setColor(QPalette::Active, QPalette::AlternateBase, QColor(67, 71, 80)); pal.setColor(QPalette::Inactive, QPalette::AlternateBase, QColor(67, 71, 80)); pal.setColor(QPalette::Disabled, QPalette::AlternateBase, QColor(59, 62, 70)); pal.setColor(QPalette::Active, QPalette::ToolTipBase, QColor(182, 193, 208)); pal.setColor(QPalette::Inactive, QPalette::ToolTipBase, QColor(182, 193, 208)); pal.setColor(QPalette::Disabled, QPalette::ToolTipBase, QColor(182, 193, 208)); pal.setColor(QPalette::Active, QPalette::ToolTipText, QColor(42, 44, 48)); pal.setColor(QPalette::Inactive, QPalette::ToolTipText, QColor(42, 44, 48)); pal.setColor(QPalette::Disabled, QPalette::ToolTipText, QColor(42, 44, 48)); pal.setColor(QPalette::Active, QPalette::Text, QColor(210, 222, 240)); pal.setColor(QPalette::Inactive, QPalette::Text, QColor(210, 222, 240)); pal.setColor(QPalette::Disabled, QPalette::Text, QColor(99, 105, 115)); pal.setColor(QPalette::Active, QPalette::Button, QColor(82, 88, 99)); pal.setColor(QPalette::Inactive, QPalette::Button, QColor(82, 88, 99)); pal.setColor(QPalette::Disabled, QPalette::Button, QColor(72, 77, 87)); pal.setColor(QPalette::Active, QPalette::ButtonText, QColor(210, 222, 240)); pal.setColor(QPalette::Inactive, QPalette::ButtonText, QColor(210, 222, 240)); pal.setColor(QPalette::Disabled, QPalette::ButtonText, QColor(111, 118, 130)); pal.setColor(QPalette::Active, QPalette::BrightText, QColor(255, 255, 255)); pal.setColor(QPalette::Inactive, QPalette::BrightText, QColor(255, 255, 255)); pal.setColor(QPalette::Disabled, QPalette::BrightText, QColor(255, 255, 255)); pal.setColor(QPalette::Active, QPalette::Light, QColor(95, 101, 114)); pal.setColor(QPalette::Inactive, QPalette::Light, QColor(95, 101, 114)); pal.setColor(QPalette::Disabled, QPalette::Light, QColor(86, 92, 104)); pal.setColor(QPalette::Active, QPalette::Midlight, QColor(84, 90, 101)); pal.setColor(QPalette::Inactive, QPalette::Midlight, QColor(84, 90, 101)); pal.setColor(QPalette::Disabled, QPalette::Midlight, QColor(75, 81, 91)); pal.setColor(QPalette::Active, QPalette::Dark, QColor(40, 43, 49)); pal.setColor(QPalette::Inactive, QPalette::Dark, QColor(40, 43, 49)); pal.setColor(QPalette::Disabled, QPalette::Dark, QColor(35, 38, 43)); pal.setColor(QPalette::Active, QPalette::Mid, QColor(63, 68, 76)); pal.setColor(QPalette::Inactive, QPalette::Mid, QColor(63, 68, 76)); pal.setColor(QPalette::Disabled, QPalette::Mid, QColor(56, 59, 67)); pal.setColor(QPalette::Active, QPalette::Shadow, QColor(29, 31, 35)); pal.setColor(QPalette::Inactive, QPalette::Shadow, QColor(29, 31, 35)); pal.setColor(QPalette::Disabled, QPalette::Shadow, QColor(25, 27, 30)); pal.setColor(QPalette::Active, QPalette::Highlight, QColor(120, 136, 156)); pal.setColor(QPalette::Inactive, QPalette::Highlight, QColor(81, 90, 103)); pal.setColor(QPalette::Disabled, QPalette::Highlight, QColor(64, 68, 77)); pal.setColor(QPalette::Active, QPalette::HighlightedText, QColor(209, 225, 244)); pal.setColor(QPalette::Inactive, QPalette::HighlightedText, QColor(182, 193, 208)); pal.setColor(QPalette::Disabled, QPalette::HighlightedText, QColor(97, 104, 114)); pal.setColor(QPalette::Active, QPalette::Link, QColor(156, 212, 255)); pal.setColor(QPalette::Inactive, QPalette::Link, QColor(156, 212, 255)); pal.setColor(QPalette::Disabled, QPalette::Link, QColor(82, 102, 119)); pal.setColor(QPalette::Active, QPalette::LinkVisited, QColor(64, 128, 255)); pal.setColor(QPalette::Inactive, QPalette::LinkVisited, QColor(64, 128, 255)); pal.setColor(QPalette::Disabled, QPalette::LinkVisited, QColor(54, 76, 119)); // Dark themes grayed/disabled color group fix... if (pal.base().color().value() < 0x7f) { const QColor& color = pal.window().color(); const int iGroups = int(QPalette::Active | QPalette::Inactive) + 1; for (int i = 0; i < iGroups; ++i) { const QPalette::ColorGroup group = QPalette::ColorGroup(i); pal.setBrush(group, QPalette::Light, color.lighter(140)); pal.setBrush(group, QPalette::Midlight, color.lighter(100)); pal.setBrush(group, QPalette::Mid, color.lighter(90)); pal.setBrush(group, QPalette::Dark, color.darker(160)); pal.setBrush(group, QPalette::Shadow, color.darker(180)); } pal.setColorGroup(QPalette::Disabled, pal.windowText().color().darker(), pal.button(), pal.light(), pal.dark(), pal.mid(), pal.text().color().darker(), pal.text().color().lighter(), pal.base(), pal.window()); #if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) pal.setColor(QPalette::Disabled, QPalette::Highlight, pal.mid().color()); pal.setColor(QPalette::Disabled, QPalette::ButtonText, pal.mid().color()); #endif } } #endif // Q_OS_ANDROID //------------------------------------------------------------------------- // main - The main program trunk. // int main ( int argc, char *argv[] ) { Q_INIT_RESOURCE(qmidictl); #if !defined(Q_OS_ANDROID) #if defined(Q_OS_LINUX) && !defined(CONFIG_WAYLAND) ::setenv("QT_QPA_PLATFORM", "xcb", 0); #endif #if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif #endif #endif QApplication app(argc, argv); #if QT_VERSION >= QT_VERSION_CHECK(5, 1, 0) app.setApplicationName(QMIDICTL_TITLE); app.setApplicationDisplayName( QMIDICTL_TITLE " - " + QObject::tr(QMIDICTL_SUBTITLE)); #if QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) app.setDesktopFileName( QString("org.rncbc.%1").arg(PROJECT_NAME)); #endif QString sVersion(PROJECT_VERSION); sVersion += '\n'; sVersion += QString("Qt: %1").arg(qVersion()); #if defined(QT_STATIC) sVersion += "-static"; #endif QApplication::setApplicationVersion(sVersion); #endif qmidictlOptions opt; if (!opt.parse_args(app.arguments())) { app.quit(); return 1; } #if defined(Q_OS_ANDROID) // Custom color theme... QPalette pal(app.palette()); update_palette(pal); app.setPalette(pal); // Custom style theme.... #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) QStyle *pAndroidStyle = QStyleFactory::create("Android"); app.setStyle(new qmidictlActionBarStyle(pAndroidStyle)); #else app.setStyle(new qmidictlActionBarStyle()); #endif // Custom font size... const QFont& font = app.font(); app.setFont(QFont(font.family(), font.pointSize() - 1)); #endif qmidictlMidiControl ctl; ctl.load(opt.settings()); qmidictlMainForm w; #if defined(Q_OS_SYMBIAN) // Lock orientation to portrait in Symbian CAknAppUi* appUi = dynamic_cast (CEikonEnv::Static()->AppUi()); TRAP_IGNORE( if(appUi) { appUi->SetOrientationL(CAknAppUi::EAppUiOrientationLandscape); } ); #endif #if defined(Q_OS_ANDROID) || defined(Q_OS_SYMBIAN) w.showMaximized(); #else w.show(); #endif w.setup(); return app.exec(); } // end of qmidictl.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictlDialStyle.cpp0000644000000000000000000000013214675771705017334 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlDialStyle.cpp0000644000175000001440000002246314675771705017333 0ustar00rncbcusers// qmidictlDialStyle.cpp // /**************************************************************************** Copyright (C) 2010-2021, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ /* This code is borrowed from Qt 4.6 source code. * * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). * All rights reserved. * Contact: Nokia Corporation (qt-info@nokia.com) */ #include "qmidictlDialStyle.h" #include #include #include #include #define Q_PI M_PI #define qSin(x) qreal(::sin(x)) #define qCos(x) qreal(::cos(x)) static QString uniqueName ( const QString &key, const QStyleOption *option, const QSize &size ) { const QStyleOptionComplex *complexOption = qstyleoption_cast(option); QString tmp = QString::fromLatin1("%1-%2-%3-%4-%5-%6x%7").arg(key).arg(uint(option->state)).arg(option->direction) .arg(complexOption ? uint(complexOption->activeSubControls) : uint(0)) .arg(option->palette.cacheKey()).arg(size.width()).arg(size.height()); return tmp; } static int calcBigLineSize ( int radius ) { int bigLineSize = radius / 6; if (bigLineSize < 4) bigLineSize = 4; if (bigLineSize > radius / 2) bigLineSize = radius / 2; return bigLineSize; } static QPolygonF calcLines ( const QStyleOptionSlider *dial ) { QPolygonF poly; int width = dial->rect.width(); int height = dial->rect.height(); qreal r = qMin(width, height) / 2; int bigLineSize = calcBigLineSize(int(r)); qreal xc = width / 2 + 0.5; qreal yc = height / 2 + 0.5; int ns = dial->tickInterval; int notches = (dial->maximum + ns - 1 - dial->minimum) / ns; if (notches <= 0) return poly; if (dial->maximum < dial->minimum || dial->maximum - dial->minimum > 1000) { int maximum = dial->minimum + 1000; notches = (maximum + ns - 1 - dial->minimum) / ns; } poly.resize(2 + 2 * notches); int smallLineSize = bigLineSize / 2; for (int i = 0; i <= notches; ++i) { qreal angle = dial->dialWrapping ? Q_PI * 3 / 2 - i * 2 * Q_PI / notches : (Q_PI * 8 - i * 10 * Q_PI / notches) / 6; qreal s = qSin(angle); qreal c = qCos(angle); if (i == 0 || (((ns * i) % (dial->pageStep ? dial->pageStep : 1)) == 0)) { poly[2 * i] = QPointF(xc + (r - bigLineSize) * c, yc - (r - bigLineSize) * s); poly[2 * i + 1] = QPointF(xc + r * c, yc - r * s); } else { poly[2 * i] = QPointF(xc + (r - 1 - smallLineSize) * c, yc - (r - 1 - smallLineSize) * s); poly[2 * i + 1] = QPointF(xc + (r - 1) * c, yc -(r - 1) * s); } } return poly; } static QPointF calcRadialPos ( const QStyleOptionSlider *dial, qreal offset ) { const int width = dial->rect.width(); const int height = dial->rect.height(); const int r = qMin(width, height) / 2; const int currentSliderPosition = dial->upsideDown ? dial->sliderPosition : (dial->maximum - dial->sliderPosition); qreal a = 0; if (dial->maximum == dial->minimum) a = Q_PI / 2; else if (dial->dialWrapping) a = Q_PI * 3 / 2 - (currentSliderPosition - dial->minimum) * 2 * Q_PI / (dial->maximum - dial->minimum); else a = (Q_PI * 8 - (currentSliderPosition - dial->minimum) * 10 * Q_PI / (dial->maximum - dial->minimum)) / 6; qreal xc = width / 2.0; qreal yc = height / 2.0; qreal len = r - calcBigLineSize(r) - 3; qreal back = offset * len; QPointF pos(QPointF(xc + back * qCos(a), yc - back * qSin(a))); return pos; } static void drawDial ( const QStyleOptionSlider *option, QPainter *painter ) { QPalette pal = option->palette; QColor buttonColor = pal.button().color(); const int width = option->rect.width(); const int height = option->rect.height(); const bool enabled = option->state & QStyle::State_Enabled; qreal r = qMin(width, height) / 2; r -= r/50; const qreal penSize = r/20.0; painter->save(); painter->setRenderHint(QPainter::Antialiasing); // Draw notches if (option->subControls & QStyle::SC_DialTickmarks) { painter->setPen(option->palette.dark().color().darker(120)); painter->drawLines(calcLines(option)); } // Cache dial background QString a = QString::fromLatin1("qdial"); QRect rect = option->rect; QPixmap internalPixmapCache; QImage imageCache; QPainter *p = painter; QString unique = uniqueName((a), option, option->rect.size()); int txType = painter->deviceTransform().type() | painter->worldTransform().type(); bool doPixmapCache = txType <= QTransform::TxTranslate; if (doPixmapCache && QPixmapCache::find(unique, &internalPixmapCache)) { painter->drawPixmap(option->rect.topLeft(), internalPixmapCache); } else { if (doPixmapCache) { rect.setRect(0, 0, option->rect.width(), option->rect.height()); imageCache = QImage(option->rect.size(), QImage::Format_ARGB32_Premultiplied); imageCache.fill(0); p = new QPainter(&imageCache); } //--BEGIN_STYLE_PIXMAPCACHE(QString::fromLatin1("qdial")); p->setRenderHint(QPainter::Antialiasing); const qreal d_ = r / 6; const qreal dx = option->rect.x() + d_ + (width - 2 * r) / 2 + 1; const qreal dy = option->rect.y() + d_ + (height - 2 * r) / 2 + 1; QRectF br = QRectF(dx + 0.5, dy + 0.5, int(r * 2 - 2 * d_ - 2), int(r * 2 - 2 * d_ - 2)); buttonColor.setHsv(buttonColor .hue(), qMin(140, buttonColor .saturation()), qMax(180, buttonColor.value())); // QColor shadowColor(0, 0, 0, 20); if (enabled) { // Drop shadow qreal shadowSize = qMax(1.0, penSize/2.0); QRectF shadowRect= br.adjusted(-2*shadowSize, -2*shadowSize, 2*shadowSize, 2*shadowSize); QRadialGradient shadowGradient(shadowRect.center().x(), shadowRect.center().y(), shadowRect.width()/2.0, shadowRect.center().x(), shadowRect.center().y()); shadowGradient.setColorAt(qreal(0.91), QColor(0, 0, 0, 40)); shadowGradient.setColorAt(qreal(1.0), Qt::transparent); p->setBrush(shadowGradient); p->setPen(Qt::NoPen); p->translate(shadowSize, shadowSize); p->drawEllipse(shadowRect); p->translate(-shadowSize, -shadowSize); // Main gradient QRadialGradient gradient(br.center().x() - br.width()/3, dy, br.width()*1.3, br.center().x(), br.center().y() - br.height()/2); gradient.setColorAt(0, buttonColor.lighter(110)); gradient.setColorAt(qreal(0.5), buttonColor); gradient.setColorAt(qreal(0.501), buttonColor.darker(102)); gradient.setColorAt(1, buttonColor.darker(115)); p->setBrush(gradient); } else { p->setBrush(Qt::NoBrush); } p->setPen(QPen(buttonColor.darker(280))); p->drawEllipse(br); p->setBrush(Qt::NoBrush); p->setPen(buttonColor.lighter(110)); p->drawEllipse(br.adjusted(1, 1, -1, -1)); if (option->state & QStyle::State_HasFocus) { QColor highlight = pal.highlight().color(); highlight.setHsv(highlight.hue(), qMin(160, highlight.saturation()), qMax(230, highlight.value())); highlight.setAlpha(127); p->setPen(QPen(highlight, 2.0)); p->setBrush(Qt::NoBrush); p->drawEllipse(br.adjusted(-1, -1, 1, 1)); } //--END_STYLE_PIXMAPCACHE if (doPixmapCache) { p->end(); delete p; internalPixmapCache = QPixmap::fromImage(imageCache); painter->drawPixmap(option->rect.topLeft(), internalPixmapCache); QPixmapCache::insert(unique, internalPixmapCache); } } QPointF dp = calcRadialPos(option, qreal(0.70)); buttonColor = buttonColor.lighter(104); buttonColor.setAlphaF(qreal(0.8)); const qreal ds = r/qreal(7.0); QRectF dialRect(dp.x() - ds, dp.y() - ds, 2*ds, 2*ds); QRadialGradient dialGradient(dialRect.center().x() + dialRect.width()/2, dialRect.center().y() + dialRect.width(), dialRect.width()*2, dialRect.center().x(), dialRect.center().y()); dialGradient.setColorAt(1, buttonColor.darker(140)); dialGradient.setColorAt(qreal(0.4), buttonColor.darker(120)); dialGradient.setColorAt(0, buttonColor.darker(110)); if (penSize > 3.0) { painter->setPen(QPen(QColor(0, 0, 0, 25), penSize)); painter->drawLine(calcRadialPos(option, qreal(0.90)), calcRadialPos(option, qreal(0.96))); } painter->setBrush(dialGradient); painter->setPen(QColor(255, 255, 255, 150)); painter->drawEllipse(dialRect.adjusted(-1, -1, 1, 1)); painter->setPen(QColor(0, 0, 0, 80)); painter->drawEllipse(dialRect); painter->restore(); } void qmidictlDialStyle::drawComplexControl ( ComplexControl cc, const QStyleOptionComplex *optc, QPainter *painter, const QWidget *widget) const { if (cc == QStyle::CC_Dial) { const QStyleOptionSlider *option = qstyleoption_cast (optc); if (option) { drawDial(option, painter); return; } } QCommonStyle::drawComplexControl(cc, optc, painter, widget); } // end of qmidictlDialSkulptureStyle.cpp qmidictl-1.0.1/src/PaxHeaders/data0000644000000000000000000000013214675771705014043 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/0000755000175000001440000000000014675771705014110 5ustar00rncbcusersqmidictl-1.0.1/src/data/PaxHeaders/symbian0000644000000000000000000000013214675771705015505 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/symbian/0000755000175000001440000000000014675771705015552 5ustar00rncbcusersqmidictl-1.0.1/src/data/symbian/PaxHeaders/qmidictl.svg0000644000000000000000000000013214675771705020111 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/symbian/qmidictl.svg0000644000175000001440000001644014675771705020106 0ustar00rncbcusers qmidictl-1.0.1/src/data/PaxHeaders/48x480000644000000000000000000000013214675771705014642 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/48x48/0000755000175000001440000000000014675771705014707 5ustar00rncbcusersqmidictl-1.0.1/src/data/48x48/PaxHeaders/qmidictl.png0000644000000000000000000000013214675771705017233 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/48x48/qmidictl.png0000644000175000001440000000707714675771705017236 0ustar00rncbcusersPNG  IHDR00WsRGB IDAThytTu?.RCEE!`\P[@ZgFi`8=H=8n((0b*DT!PHBU*+O (Jc~s~?h"R!p(z<%jfjinwB*KUUe]~N/yXqxrzP5V6lHkll\o7l߾X,%O^o+[n[V_5G|vEgw7/1`0O]]]g }sK+d6{00/u/)*ϵ_} (JEUl{ƍyQQ?^~,1:}ƍ.[l $ w,:Q=\bӿ?,^D(~qΊFGYW[['|7ʪ ]&P.pon#~RvbՆ/$<@jժSN}t޽MfJg}vk4y|q)Yl`"P^̝"!0eL]͛7;]Оom۶\b333ӍFaxxŋ߿rgfffE7onѾ\ 04,vzWT"x6jnXQo]]5,w:~s1֭{Μ9;o^vEuvƌ ̙3KVkyQǼyjM&d2ElmoV'?ÁbuW{V#w 'OW_{E5OE+G4~kɦtv*?j>dJztzDQAxh_G7/N[&~Ψ\OWO>=7VWWO].W6bŊjjjjVx.[Q%@iغI̙7?;"-aQʒjiQ.BL|k>*ӹ;//ﱍ76ȲKMMhhhOD^~'S{zzJKKGEQZ[[ yd&bbq5~Qs8~B o'/x\z۲>+;ޙ |F=\H6rBx<Pjz v ׎]zT!̝7ұcǘQ5#筷,c?+zfUϼl5>R___Hi[;|SLgܹJDeeL brBFÚW՝ƙ}l}cޫ9p'q\䡇曉(BBsE͢5i &MKK[8ll6۔u 6N5I 'iNӐx(,+f$ 1g,,Xn:NwrMxw4={/X*gB??7noYt:l3S=@ ^LSx]+~߀*$qξFe"rŐUl*n>fp8?" jǚX$H܈ꉢpb `X49W1id;iL()٢G`"m!wB=7¤r$U{HNK%'7BTU%5- $ڋ~DBSDQ0"!3ұ&Y!-pz zi*|z9Ix;0j$} #猴fV3}dl6R316[&A7*AXbz5~$ W0Hz().G$b(zN3)INI忷OwO%==Wd+R54 nMJdAn\b:88ŽU1R=(b^yix1 "͐:^n#99P@FVdnXR=?{<0Y0Zʰ&'3-m8ohbl,m3bڞ}l$l;"3r(/-mp8HĤiS10&c~~-G"A)@bw.DNK99p$DEy9z^ ^RVJ+&c6\)!"!g6. :՚d7t$YNw X!O]==ƏbJfs==:5"ah;^'|n.%g˲gϜ8/ f7+d#n)&>nj㭷^;_:1%|C8_hX Bߙ3 [=M'A'T51)pP!|VZ;keI,^1=eu,+'w&GelX R 4(+XBDLTJLUHKR.(/O@s; <>8PJkcl9[dN :6nZ6w}t*C=;'r8UCn)%]_μ˹um̾zr$?d{ Up h0p`63j=0|B;}uHc?犡qE̺:''~ d\Nh7uB'vHjOX[*5ڦU`G~Կɲ|RӜ$iMFIZK>(S Er Z͏G hNiW,s:_@vlK:5Okr$x ?[9&-s:?4Ih> {4 61$Y)!ahj IENDB`qmidictl-1.0.1/src/data/PaxHeaders/64x640000644000000000000000000000013214675771705014636 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/64x64/0000755000175000001440000000000014675771705014703 5ustar00rncbcusersqmidictl-1.0.1/src/data/64x64/PaxHeaders/qmidictl.png0000644000000000000000000000013214675771705017227 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/64x64/qmidictl.png0000644000175000001440000001144114675771705017220 0ustar00rncbcusersPNG  IHDR@@iqsRGBIDATxyp\ՕnRbY,M3E$RdP$@ CT2S dX @b x,ZŖ,YK-nWTVju=w=6o\mrLdL@%miiziBOBtmo_L@ $4!-/Ǭ>ͬܺm =f4)@`F5x< #}؜y KȼDT`2q?yO|SkAxϛMWqy/6X|͉`|N;x PTz]^Y:u4 O^_g}@<FJ>Y?``-=pօ NeY2 Cz oi}*@ۿt;o7ͲEuL& رy՞ 9@Àhp!۪NWg GO ߞK?1%2:2ǐ;H6m(_T۷oo5 L]\988(pر`ٳa,ˊN (Jr]vȶZ(Km}I~dY^z *P* 7MEEEw@/m՟m"'AՔ"nY1gBBCYx_G5vnz ó/@ǑΏw_NJ}^@pf epȑiӦ}. cdI!#0ɽ];ڈLZ ;;fl6աNuV>9#&.`| VwȑҔ)SPo%FCʁ@)?usYb%u3g~CѲw?Ǹj2ϧ`X K|ݩS~u! nv9Ed SKY_X_M @M{xhzǘ%vjkkVMUi[nkf֜k 1plɒ%ϥ"놘ϋu}{jycm߾}apfq 1iGY@(76obXVvMqq1uuuL1 6m﹇us 6M47>4/Ydmccq^Fjjj֌{'fƌ"8Eh,bM [?)-S__里v^/xWysF|n~?=`My 478k׮fl@u`깃5k֝螴{? lB*KzQ+HmL,O4eѢED;hd(/''7cD"6gv3sYvIUp:EQL., iSnW^y.V+۶mn%8~_p=pϡvQ=k_@CM 4M|><8nbf+-g(|nN M1 j*^/w羚(7YH'2\Elyg G13}=뺡(ʘYSSF|18[NsŎ;Ƈi)A "#hL8Kf (6X4ޞ6[u7M7D$AUUdYF$z{{f(b؁C@HUկiq?@g__;n1X \E"G& L waJEbiۉblo٧gV}q:t:q\dggSUZk@44}hUUk\^ 6<EQZxoDגb򺁖t+9[ 0E^|"6g*C4X @V v~$Ick5*GlzP4o՟jsVux<{̖M~va)`w|ŇAHiN\=|kO)à| iƓmnf9_$`29s@X!OO=&DvLs,DQK[ɀI# bX'YOcm6IPɢoOA8A"*>OܗpZn] ]Su(cك  pMA'00$ I((( i$I>? $D!wt- roikk)-@UULfauբ5)EU(sg'H0~ xTxXYT+sީp0{(T~Ht]ӄю*w&GB"O/H#HmdZMH<~ZKHiB H8]YΔrK=Fg/4t+h|:F^ x—nlr%6 %3h0H0@!. QDb*d~u:\0Y=c6,Y86V\%Kƍٺ-d"#[31q^'Ey  tHv:[͉x\t0MMxj'̃"}~jgdxho#Y…r `=$n8%q!/9 #Cw>EiE9ib72B͆bQQuۍH.2]H<5@( FCx?{IC0 $InρD fNx?P H"\PlMJy)ZMÒx< R]M8fppmnާzsք ŝrXWg2>1Y@}}=6bGH$ͷ}Xs!R峂p[zXT &H選;3'"E&iI֮N*H_1 XU A81tv^Y:sf]XVlnȖrRʁ8Xw/&sU@,Dˍ;8ڵk | }ă!UcɐsRzn+_ GӖƥpfRPTH%U  .),k7sH2 d*6C$F$dA#gjtrrsP$8Kđ͡o!|\B.bǏoFOO@xP 0ȱi9&s)]}25e !JK8EAQ!&["?WJrt08?dA(`Gc^8lJfA8) 4h?qbYqi Mnr$m&GD" Ţtt'ʢ#0tt![5'.6rYױLk,su]-YB@heSx٢߉=QS3ZcN%"Q;805 S}0P0He&cIY,/s}_(`?^iF 76P@k{o.~1Da%`[*CE˂axTaŔ& g{-~:DҭR8&46AWsq`ŋ<Uc¾6ϥ@q?rzHAP% uTxqN]}S-f̞= Tk&>ml}a0I;i`"Jځ #/ρ]aeݳ)p$ MF@wK$}2mp(m70&*!";voVX j_$IB!ՕZ=<]N.0dDtޟ&^^}#&Q\]$?}q']ǎ># ?!Җɗa+!l+bԷ(⡙ssΦ0yt =n2z#e`|YNs3zc2|pN>et'(*!s; ;UXMJCR'YŬ +DEs#ͤʹu9 ta~1_&2m<R|p+D/z2_ |pf)B*snHRi}}A zIENDB`qmidictl-1.0.1/src/data/PaxHeaders/26x260000644000000000000000000000013214675771705014632 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/26x26/0000755000175000001440000000000014675771705014677 5ustar00rncbcusersqmidictl-1.0.1/src/data/26x26/PaxHeaders/qmidictl.png0000644000000000000000000000013214675771705017223 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/26x26/qmidictl.png0000644000175000001440000000300114675771705017205 0ustar00rncbcusersPNG  IHDRJLsRGBIDATHǭmlSuRvbqJFC&-t NiX`ItnYI64jZRmA%]ǚ 2ypH8}q}s bBQAQ('R0w>fQqaBLV D@ ]I*? E;ۇ nBwĉh|]QC1EQ4ljFJ, FCS<s޾'_CG#'Aǎ뮮~*L^9zlccc{ SWc뀟?%w)Uz3k󱚋"rjbb ,h|jt )9uqz;QUul SfYܹsw޻|\.sBn206{CKCuFZvP.-).۶U MMMɃaQsxvv oAŲ  ˩BoUZ2UdoLO)M뤤"]]]ÇJGۿwv&I1y7\?VJŅ)ׯgƍdޱp^\-444***^jmmm<{[Ng$I!U;>>p8jF6YH>xkj@0HaQv k6 vM=&ɎoVUU=qZZ#G5::xb.6^v|-[| ՗_~鍶o̔}ׂ T pӹJ>kM-?Fөpz ܌:?{Mmjj"pp%n•ѿb@%EgvQT,iy'g3Q PH *DQ j:⢟M55X Ȧb^ͷfʥ-&~ I)b(><D5v 텂jZ,_#2@ F $LA~>( E ?8wM^?HF@6 `Ec لRWĬ sU%( d*Z*ʿD2W,\nfsy @@oجV}~ >5Gilڏ1>[fS@~1IZQXMdzyYp&cpc6oFYXHsR4d9Y586\"2)9TGV@P;wTA+0 + ?*|5pIENDB`qmidictl-1.0.1/src/data/PaxHeaders/maemo0000644000000000000000000000013214675771705015141 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/maemo/0000755000175000001440000000000014675771705015206 5ustar00rncbcusersqmidictl-1.0.1/src/data/maemo/PaxHeaders/qmidictl.xpm0000644000000000000000000000013214675771705017552 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/data/maemo/qmidictl.xpm0000644000175000001440000001105014675771705017537 0ustar00rncbcusers/* XPM */ static char * qmidictl_xpm[] = { "26 26 196 2", " c None", ". c #4D4D4B", "+ c #F7F7F7", "@ c #F3F3F3", "# c #272826", "$ c #626161", "% c #80807F", "& c #9E9E9E", "* c #B8B8B8", "= c #F5F5F5", "- c #F6F6F6", "; c #EEEEEE", "> c #3E3E3D", ", c #535351", "' c #80766A", ") c #6D615B", "! c #FBFBFB", "~ c #CECECE", "{ c #8B8B8B", "] c #F8F8F8", "^ c #E7E7E7", "/ c #574B36", "( c #77684C", "_ c #AB9C8C", ": c #675D57", "< c #000100", "[ c #CCCCCC", "} c #FFFFFF", "| c #F9F9F9", "1 c #D8D8D8", "2 c #1A170F", "3 c #695A3F", "4 c #706855", "5 c #A3A19D", "6 c #858582", "7 c #4C4C4A", "8 c #ECECEC", "9 c #C9C9C9", "0 c #ADADAD", "a c #A7A7A7", "b c #F1F1F1", "c c #19140C", "d c #383530", "e c #A6A7A6", "f c #C6C8C5", "g c #8C8C89", "h c #585856", "i c #B6B6B6", "j c #E1E1E1", "k c #FEFEFE", "l c #A8A8A8", "m c #000000", "n c #030100", "o c #333130", "p c #A7A7A4", "q c #CACBC8", "r c #8D8D8A", "s c #61605F", "t c #EFEFEF", "u c #BABABA", "v c #D1D1D1", "w c #030000", "x c #332F2F", "y c #A9A9A7", "z c #C8C9C6", "A c #8E8D8B", "B c #A6A7A4", "C c #737270", "D c #313330", "E c #E0E0E0", "F c #C3C3C3", "G c #D9D9D9", "H c #EAEAEA", "I c #B0B0B0", "J c #343131", "K c #6A6A68", "L c #929290", "M c #C5C5C3", "N c #CACCC9", "O c #92928F", "P c #484846", "Q c #353735", "R c #E5E5E5", "S c #B4B4B4", "T c #BFBFBF", "U c #D0D0D0", "V c #616161", "W c #151312", "X c #565455", "Y c #B0B0AD", "Z c #E4E6E3", "` c #CBCDCA", " . c #989795", ".. c #313130", "+. c #9F9F9F", "@. c #0F110F", "#. c #010100", "$. c #1C1A1A", "%. c #615E5E", "&. c #AAAAA7", "*. c #E3E5E2", "=. c #CFD1CE", "-. c #807F7E", ";. c #3E3E3C", ">. c #000200", ",. c #010200", "'. c #1E1C1C", "). c #625E5E", "!. c #A8A9A6", "~. c #B0B1AE", "{. c #72706D", "]. c #3D3E3C", "^. c #1D1B1C", "/. c #615D5D", "(. c #8C8B89", "_. c #CECFCC", ":. c #AFAFAD", "<. c #73716F", "[. c #3E3F3D", "}. c #010000", "|. c #171515", "1. c #575555", "2. c #898785", "3. c #ADAEAC", "4. c #A4A5A2", "5. c #878785", "6. c #595957", "7. c #000101", "8. c #262524", "9. c #686664", "0. c #8A8886", "a. c #AEAEAC", "b. c #A4A5A0", "c. c #A2A29C", "d. c #7A7B7B", "e. c #030403", "f. c #313231", "g. c #403F3F", "h. c #949492", "i. c #A09E9C", "j. c #D5D4B8", "k. c #C9C141", "l. c #7A7343", "m. c #030506", "n. c #131312", "o. c #2D2D2E", "p. c #5B5A58", "q. c #6E6B69", "r. c #403D3D", "s. c #E4E5E3", "t. c #F7F8BF", "u. c #C7BD29", "v. c #403808", "w. c #101110", "x. c #313131", "y. c #565657", "z. c #494512", "A. c #040401", "B. c #4B4949", "C. c #DDDEE1", "D. c #E4E3A4", "E. c #A09412", "F. c #2F2A10", "G. c #1C1B1A", "H. c #2B2D35", "I. c #C79E09", "J. c #756B02", "K. c #020304", "L. c #484646", "M. c #726D58", "N. c #2F280F", "O. c #4F4A29", "P. c #111103", "Q. c #0E1011", "R. c #342408", "S. c #D29B00", "T. c #817803", "U. c #302E1A", "V. c #4B4635", "W. c #5E5528", "X. c #4A421E", "Y. c #100E0A", "Z. c #221900", "`. c #AD7C05", " + c #64675F", ".+ c #D4D7D5", "++ c #89857C", "@+ c #474328", "#+ c #1F1602", "$+ c #786D38", "%+ c #DFDDCE", "&+ c #D6D8D5", "*+ c #1A1B1D", "=+ c #403E15", "-+ c #5A5948", ";+ c #100E0D", " ", " . + @ ", " # $ % & * = - ; ", " > , ' ) ! + ~ { ] ^ ", " / ( _ : < [ } } | 1 ", " 2 3 4 5 6 7 8 9 0 ! a b ", " c d e f g h i j ] * k @ l ", " m n o p q r s t 0 - } u 9 v ", " m w x y z A B C D + b E F G H I 1 ", " m m J K L M N O P Q = R S T U V ", " m m m W X Y Z ` .F .. +. ", " @.m #.$.%.&.*.=.-.;.>. ", " < m ,.'.).!.Z ~.{.].m ", " m >.< m ^./.(._.:.<.[. ", " < < < }.|.1.2.3.4.5.6. ", " m < >.m 7.8.9.0.a.b.c.d. ", " m < >.e.f.g.h.i.j.k.l.m. ", " m m n.o.p.q.r.s.t.u.v. ", " m w.x.y.z.A.B.C.D.E.F. ", " m G.H.I.J.K.L.M.N.O.P. ", " < Q.R.S.T.U.V.W.X.Y. ", " m Z.`. +.+++@+m ", " #+$+%+&+*+ ", " =+-+;+m ", " ", " "}; qmidictl-1.0.1/src/PaxHeaders/man10000644000000000000000000000013214675771705013766 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/man1/0000755000175000001440000000000014675771705014033 5ustar00rncbcusersqmidictl-1.0.1/src/man1/PaxHeaders/qmidictl.10000644000000000000000000000013214675771705015733 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/man1/qmidictl.10000644000175000001440000000231014675771705015717 0ustar00rncbcusers.TH QMIDICTL "1" "June 17, 2014" .SH NAME qmidictl \- A MIDI Remote Controller via UDP/IP Multicast .SH SYNOPSIS .B qmidictl [\fIoptions\fR] .SH DESCRIPTION This manual page documents briefly the .B qmidictl command. .PP \fBQmidiCtl\fP is a MIDI remote controller application that sends MIDI data over the network, using UDP/IP multicast. Inspired by multimidicast (http://llg.cubic.org/tools) and designed to be compatible with ipMIDI for Windows (http://nerds.de). .SH OPTIONS .HP \fB\-i\fR, \fB\-\-interface\fR=[\fIinterface\fR] .IP Use specific network interface (default = all) .HP \fB\-u\fR, \fB\-\-address\fR, \fB\-\-udp\-addr\fR=[\fIaddress\fR] .IP Use specific network address (default = 225.0.0.37) .HP \fB\-p\fR, \fB\-\-port\fR, \fB\-\-udp\-port\fR=[\fIport\fR] .IP Use specific network port (default = 21928) .HP \fB\-m\fR, \fB\-\-mmc\-device\fR=[\fImmc-device\fR] .IP Use specific MMC device number (default = 127) .HP \fB\-h\fR, \fB\-\-help\fR .IP Show help about command line options .HP \fB\-v\fR, \fB\-\-version\fR .IP Show version information .SH FILES Configuration settings are stored in ~/.config/rncbc.org/QmidiCtl.conf .SH SEE ALSO .BR qmidinet (1) .SH AUTHOR QmidiCtl was written by Rui Nuno Capela. qmidictl-1.0.1/src/man1/PaxHeaders/qmidictl.fr.10000644000000000000000000000013214675771705016341 xustar0030 mtime=1727525829.431779736 30 atime=1727525829.431779736 30 ctime=1727525829.431779736 qmidictl-1.0.1/src/man1/qmidictl.fr.10000644000175000001440000000274514675771705016341 0ustar00rncbcusers.TH QMIDICTL "1" "Juin 17, 2014" .SH NOM qmidictl \- un contrôleur MIDI distant via UDP/IP Multicast .SH SYNOPSIS .B qmidictl [\fIoptions\fR] .SH DESCRIPTION Cette page de manuel documente brièvement la commande .B qmidictl . .PP \fBQmidiCtl\fP est une application de contrôleur distant MIDI qui envoie des données MIDI à travers le réseau, en utilisant UDP/IP multicast. .PP qu'application de bureau régulier. .SH OPTIONS .HP \fB\-i\fR, \fB\-\-interface\fR=[\fIinterface\fR] .IP Utilise une interface réseau spécifique (par défaut = all) .HP \fB\-u\fR, \fB\-\-address\fR, \fB\-\-udp\-addr\fR=[\fIaddresse\fR] .IP Utilise une adresse de réseau spécifique (par défaut = 225.0.0.37) .HP \fB\-p\fR, \fB\-\-port\fR, \fB\-\-udp\-port\fR=[\fIport\fR] .IP Utilise un port de réseau spécifique (par défaut = 21928) .HP \fB\-m\fR, \fB\-\-mmc\-device\fR=[\fIpériphérique-mmc\fR] .IP Utilise un numéro de périphérique MMC spécifique (par défaut = 127) .HP \fB\-h\fR, \fB\-\-help\fR .IP Affiche de l'aide à propos des options de ligne de commande .HP \fB\-v\fR, \fB\-\-version\fR .IP Affiche une information de version .SH FICHIERS Les paramètres de configuration sont stockés dans ~/.config/rncbc.org/QmidiCtl.conf .SH VOIR ÉGALEMENT .BR qmidinet (1) .SH AUTEUR qmidictl a été écrit par Rui Nuno Capela. .PP La version française a été traduite à partir de la page en anglais par Olivier Humbert , pour le projet LibraZiK (mais peut être utilisée par d'autres). qmidictl-1.0.1/src/PaxHeaders/qmidictlOptionsForm.ui0000644000000000000000000000013214675771705017554 xustar0030 mtime=1727525829.433779736 30 atime=1727525829.433779736 30 ctime=1727525829.433779736 qmidictl-1.0.1/src/qmidictlOptionsForm.ui0000644000175000001440000002011314675771705017541 0ustar00rncbcusers rncbc aka Rui Nuno Capela QmidiCtl - A MIDI remote controller via IP/UDP multicast. Copyright (C) 2010-2023, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. qmidictlOptionsForm 0 0 320 240 Options :/images/qmidictl.svg 8 0 8 8 75 true MIDI true 50 false MMC &Device: MmcDeviceSpinBox 50 false true 127 127 Qt::Vertical 20 8 75 true Network true 50 false &Interface: InterfaceComboBox 50 false true 50 false &UDP Address: UdpAddrComboBox 50 false true 50 false UDP &Port: UdpPortSpinBox 50 false true 65535 Qt::Vertical 20 8 Qt::Vertical QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset MmcDeviceSpinBox InterfaceComboBox UdpAddrComboBox UdpPortSpinBox DialogButtonBox qmidictl-1.0.1/src/PaxHeaders/qmidictlAbout.h0000644000000000000000000000013214675771705016161 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.431779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlAbout.h0000644000175000001440000000246514675771705016160 0ustar00rncbcusers// qmidictlAbout.h // /**************************************************************************** Copyright (C) 2010-2024, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlAbout_h #define __qmidictlAbout_h #include "config.h" #define QMIDICTL_TITLE PROJECT_TITLE #define QMIDICTL_SUBTITLE PROJECT_DESCRIPTION #define QMIDICTL_WEBSITE PROJECT_HOMEPAGE_URL #define QMIDICTL_COPYRIGHT PROJECT_COPYRIGHT #define QMIDICTL_DOMAIN PROJECT_DOMAIN #endif // __qmidictlAbout_h // end of qmidictlAbout.h qmidictl-1.0.1/src/PaxHeaders/qmidictlMidiControlForm.cpp0000644000000000000000000000013214675771705020511 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlMidiControlForm.cpp0000644000175000001440000003430214675771705020503 0ustar00rncbcusers// qmidictlMidiControlForm.cpp // /**************************************************************************** Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #include "qmidictlAbout.h" #include "qmidictlMidiControlForm.h" #include "qmidictlMidiControl.h" #include "qmidictlOptions.h" #include #if defined(Q_OS_ANDROID) #include "qmidictlActionBar.h" #include #endif //---------------------------------------------------------------------------- // qmidictlMidiControlForm -- UI wrapper form. // Constructor. qmidictlMidiControlForm::qmidictlMidiControlForm ( QWidget *pParent ) : QDialog(pParent) { // Setup UI struct... m_ui.setupUi(this); #if QT_VERSION < QT_VERSION_CHECK(6, 1, 0) QDialog::setWindowIcon(QIcon(":/images/qmidictl.png")); #endif #if defined(Q_OS_ANDROID) // Custom dialog font size... const QFont& font = QDialog::font(); QDialog::setFont(QFont(font.family(), font.pointSize() + 1)); // Special actions for the android stuff. m_pBackAction = new QAction(QIcon(":/images/actionBack.png"), tr("Back"), this); m_pResetAction = new QAction(QIcon(":/images/actionReset.png"), tr("Reset"), this); m_pAcceptAction = new QAction(QIcon(":/images/actionAccept.png"), tr("Done"), this); m_pCancelAction = new QAction(QIcon(":/images/actionCancel.png"), tr("Cancel"), this); QObject::connect(m_pBackAction, SIGNAL(triggered(bool)), this, SLOT(reject())); QObject::connect(m_pResetAction, SIGNAL(triggered(bool)), this, SLOT(reset())); QObject::connect(m_pAcceptAction, SIGNAL(triggered(bool)), this, SLOT(accept())); QObject::connect(m_pCancelAction, SIGNAL(triggered(bool)), this, SLOT(reject())); // Special action-bar for the android stuff. m_pActionBar = new qmidictlActionBar(); m_pActionBar->setIcon(QDialog::windowIcon()); m_pActionBar->setTitle(QDialog::windowTitle()); // Action-bar back-button... m_pActionBar->addMenuItem(m_pBackAction); // Action-bar right-overflow button items... m_pActionBar->addButton(m_pResetAction); m_pActionBar->addButton(m_pAcceptAction); m_pActionBar->addButton(m_pCancelAction); // Make it at the top... m_ui.MainCentralLayout->insertWidget(0, m_pActionBar); m_ui.DialogButtonBox->hide(); #endif // Populate command list. m_ui.CommandComboBox->addItem(QIcon(":/images/formReset.png"), tr("RST"), qmidictlMidiControl::RST); m_ui.CommandComboBox->addItem(QIcon(":/images/formRewind.png"), tr("REW"), qmidictlMidiControl::REW); m_ui.CommandComboBox->addItem(QIcon(":/images/formStop.png"), tr("STOP"), qmidictlMidiControl::STOP); m_ui.CommandComboBox->addItem(QIcon(":/images/formPlay.png"), tr("PLAY"), qmidictlMidiControl::PLAY); m_ui.CommandComboBox->addItem(QIcon(":/images/formRecord.png"), tr("REC"), qmidictlMidiControl::REC); m_ui.CommandComboBox->addItem(QIcon(":/images/formForward.png"), tr("FFWD"), qmidictlMidiControl::FFWD); m_ui.CommandComboBox->addItem( tr("Track Solo (S)"), qmidictlMidiControl::TRACK_SOLO); m_ui.CommandComboBox->addItem( tr("Track Mute (M)"), qmidictlMidiControl::TRACK_MUTE); m_ui.CommandComboBox->addItem( tr("Track Record (R)"), qmidictlMidiControl::TRACK_REC); m_ui.CommandComboBox->addItem( tr("Track Volume (fader)"), qmidictlMidiControl::TRACK_VOL); m_ui.CommandComboBox->addItem( tr("Jog Wheel"), qmidictlMidiControl::JOG_WHEEL); // Populate command list. m_ui.ControlTypeComboBox->addItem( tr("MMC"), qmidictlMidiControl::MMC); m_ui.ControlTypeComboBox->addItem( tr("Note On"), qmidictlMidiControl::NOTE_ON); m_ui.ControlTypeComboBox->addItem( tr("Note Off"), qmidictlMidiControl::NOTE_OFF); m_ui.ControlTypeComboBox->addItem( tr("Key Press"), qmidictlMidiControl::KEY_PRESS); m_ui.ControlTypeComboBox->addItem( tr("Controller"), qmidictlMidiControl::CONTROLLER); m_ui.ControlTypeComboBox->addItem( tr("Pgm Change"), qmidictlMidiControl::PGM_CHANGE); m_ui.ControlTypeComboBox->addItem( tr("Chan Press"), qmidictlMidiControl::CHAN_PRESS); m_ui.ControlTypeComboBox->addItem( tr("Pitch Bend"), qmidictlMidiControl::PITCH_BEND); // Start clean. m_iDirtyCount = 0; m_iDirtySetup = 0; // Populate param list. activateCommand(m_ui.CommandComboBox->currentIndex()); // Try to fix window geometry. #if defined(Q_OS_ANDROID) || defined(Q_OS_SYMBIAN) showMaximized(); #else adjustSize(); #endif // UI signal/slot connections... QObject::connect(m_ui.CommandComboBox, SIGNAL(activated(int)), SLOT(activateCommand(int))); QObject::connect(m_ui.ControlTypeComboBox, SIGNAL(activated(int)), SLOT(activateControlType(int))); QObject::connect(m_ui.ChannelSpinBox, SIGNAL(valueChanged(int)), SLOT(change())); QObject::connect(m_ui.ParamComboBox, SIGNAL(activated(int)), SLOT(change())); QObject::connect(m_ui.ParamTrackCheckBox, SIGNAL(toggled(bool)), SLOT(change())); QObject::connect(m_ui.LogarithmicCheckBox, SIGNAL(toggled(bool)), SLOT(change())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(clicked(QAbstractButton *)), SLOT(buttonClick(QAbstractButton *))); QObject::connect(m_ui.DialogButtonBox, SIGNAL(accepted()), SLOT(accept())); QObject::connect(m_ui.DialogButtonBox, SIGNAL(rejected()), SLOT(reject())); } // Destructor. qmidictlMidiControlForm::~qmidictlMidiControlForm (void) { #if defined(Q_OS_ANDROID) // No need for special android stuff anymore. delete m_pCancelAction; delete m_pAcceptAction; delete m_pResetAction; delete m_pBackAction; delete m_pActionBar; #endif } // List view item activation. void qmidictlMidiControlForm::activateCommand ( int iCommand ) { #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::activateCommand(%d)", iCommand); #endif m_iDirtySetup++; qmidictlMidiControl::Command command = qmidictlMidiControl::Command( m_ui.CommandComboBox->itemData(iCommand).toInt()); bool bEnabled = (command != qmidictlMidiControl::Command(0)); qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (bEnabled) bEnabled = (pMidiControl != nullptr); m_ui.ControlTypeTextLabel->setEnabled(bEnabled); m_ui.ControlTypeComboBox->setEnabled(bEnabled); if (bEnabled) bEnabled = pMidiControl->isCommandMapped(command); m_ui.ParamTextLabel->setEnabled(bEnabled); m_ui.ParamComboBox->setEnabled(bEnabled); m_ui.ChannelTextLabel->setEnabled(bEnabled); m_ui.ChannelSpinBox->setEnabled(bEnabled); m_ui.ParamTextLabel->setEnabled(bEnabled); m_ui.ParamComboBox->setEnabled(bEnabled); m_ui.ParamTrackCheckBox->setEnabled(bEnabled); m_ui.LogarithmicCheckBox->setEnabled(bEnabled); if (bEnabled) { const qmidictlMidiControl::MapKey& key = pMidiControl->commandMap().value(command); const int iControlType = m_ui.ControlTypeComboBox->findData(key.type()); m_ui.ControlTypeComboBox->setCurrentIndex(iControlType); activateControlType(iControlType); int iChannel = 0; if (m_ui.ChannelSpinBox->isEnabled()) iChannel = (key.isChannelTrack() ? 0 : key.channel() + 1); m_ui.ChannelSpinBox->setValue(iChannel); int iParam = key.param(); if (key.isParamTrack()) iParam &= qmidictlMidiControl::TrackParamMask; m_ui.ParamComboBox->setCurrentIndex(iParam); if (key.isChannelTrack()) { m_ui.ParamTrackCheckBox->setEnabled(false); m_ui.ParamTrackCheckBox->setChecked(false); } else { m_ui.ParamTrackCheckBox->setChecked(key.isParamTrack()); } m_ui.LogarithmicCheckBox->setChecked(key.isLogarithmic()); } else { m_ui.ControlTypeComboBox->setCurrentIndex(0); m_ui.ChannelSpinBox->setValue(0); m_ui.ParamComboBox->clear(); m_ui.ParamTrackCheckBox->setChecked(false); m_ui.LogarithmicCheckBox->setChecked(false); } m_iDirtySetup--; } void qmidictlMidiControlForm::activateControlType ( int iControlType ) { #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::activateControlType(%d)", iControlType); #endif qmidictlMidiControl::ControlType ctype = qmidictlMidiControl::ControlType( m_ui.ControlTypeComboBox->itemData(iControlType).toInt()); if (!ctype) return; m_ui.ParamComboBox->clear(); switch (ctype) { case qmidictlMidiControl::MMC: case qmidictlMidiControl::CHAN_PRESS: case qmidictlMidiControl::PITCH_BEND: m_ui.ChannelTextLabel->setEnabled(false); m_ui.ChannelSpinBox->setEnabled(false); m_ui.ParamTextLabel->setEnabled(false); m_ui.ParamComboBox->setEnabled(false); m_ui.ParamTrackCheckBox->setEnabled(false); break; case qmidictlMidiControl::NOTE_ON: case qmidictlMidiControl::NOTE_OFF: case qmidictlMidiControl::CONTROLLER: case qmidictlMidiControl::KEY_PRESS: case qmidictlMidiControl::PGM_CHANGE: m_ui.ChannelTextLabel->setEnabled(true); m_ui.ChannelSpinBox->setEnabled(true); m_ui.ParamTextLabel->setEnabled(true); m_ui.ParamComboBox->setEnabled(true); for (unsigned short i = 0; i < 128; ++i) { QString sText; switch (ctype) { case qmidictlMidiControl::NOTE_ON: case qmidictlMidiControl::NOTE_OFF: sText = QString("%1 (%2)") .arg(qmidictlMidiControl::noteName(i)).arg(i); break; case qmidictlMidiControl::CONTROLLER: sText = QString("%1 - %2") .arg(i).arg(qmidictlMidiControl::controllerName(i)); break; default: sText = QString::number(i); break; } m_ui.ParamComboBox->addItem(sText); } if (m_ui.ChannelSpinBox->value() == 0) { m_ui.ParamTrackCheckBox->setEnabled(false); m_ui.ParamTrackCheckBox->setChecked(false); } else { m_ui.ParamTrackCheckBox->setEnabled(true); } break; } // This is enabled by as long there's a value. m_ui.LogarithmicCheckBox->setEnabled( ctype != qmidictlMidiControl::MMC && ctype != qmidictlMidiControl::PGM_CHANGE); // Try make changes dirty. change(); } // Change settings (anything else slot). void qmidictlMidiControlForm::change (void) { if (m_iDirtySetup > 0) return; #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::change()"); #endif qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (pMidiControl == nullptr) return; const int iCommand = m_ui.CommandComboBox->currentIndex(); qmidictlMidiControl::Command command = qmidictlMidiControl::Command( m_ui.CommandComboBox->itemData(iCommand).toInt()); if (!command) return; const int iControlType = m_ui.ControlTypeComboBox->currentIndex(); qmidictlMidiControl::ControlType ctype = qmidictlMidiControl::ControlType( m_ui.ControlTypeComboBox->itemData(iControlType).toInt()); if (!ctype) return; unsigned short iChannel = 0; if (m_ui.ChannelSpinBox->isEnabled()) { iChannel = m_ui.ChannelSpinBox->value(); if (iChannel == 0) { iChannel |= qmidictlMidiControl::TrackParam; m_ui.ParamTrackCheckBox->setEnabled(false); m_ui.ParamTrackCheckBox->setChecked(false); } else { iChannel--; m_ui.ParamTrackCheckBox->setEnabled(true); } } unsigned short iParam = 0; if (m_ui.ParamComboBox->isEnabled()) { iParam = m_ui.ParamComboBox->currentIndex(); if (m_ui.ParamTrackCheckBox->isEnabled() && m_ui.ParamTrackCheckBox->isChecked()) iParam |= qmidictlMidiControl::TrackParam; } bool bLogarithmic = false; if (m_ui.LogarithmicCheckBox->isEnabled()) bLogarithmic = m_ui.LogarithmicCheckBox->isChecked(); pMidiControl->unmapCommand(command); pMidiControl->mapCommand(command, ctype, iChannel, iParam, bLogarithmic); m_iDirtyCount++; } // Reset settings (action button slot). void qmidictlMidiControlForm::buttonClick ( QAbstractButton *pButton ) { #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::buttonClick(%p)", pButton); #endif QDialogButtonBox::ButtonRole role = m_ui.DialogButtonBox->buttonRole(pButton); if ((role & QDialogButtonBox::ResetRole) == QDialogButtonBox::ResetRole) reset(); } // Accept settings (OK button slot). void qmidictlMidiControlForm::accept (void) { #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::accept()"); #endif // Save settings... if (m_iDirtyCount > 0) { qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (pMidiControl) { qmidictlOptions *pOptions = qmidictlOptions::getInstance(); if (pOptions) { pMidiControl->save(pOptions->settings()); m_iDirtyCount = 0; } } } // Just go with dialog acceptance... QDialog::accept(); } // Reject settings (Cancel button slot). void qmidictlMidiControlForm::reject (void) { #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::reject()"); #endif // Check if there's any pending changes... if (m_iDirtyCount > 0) { switch (QMessageBox::warning(this, QDialog::windowTitle(), tr("Some settings have been changed.\n\n" "Do you want to apply the changes?"), QMessageBox::Apply | QMessageBox::Discard | QMessageBox::Cancel)) { case QMessageBox::Discard: break; case QMessageBox::Apply: accept(); default: return; } // Reload settings... qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (pMidiControl) { qmidictlOptions *pOptions = qmidictlOptions::getInstance(); if (pOptions) { pMidiControl->load(pOptions->settings()); m_iDirtyCount = 0; } } } // Just go with dialog rejection... QDialog::reject(); } // Reset settings (Reset button slot). void qmidictlMidiControlForm::reset (void) { #ifdef CONFIG_DEBUG qDebug("qmidictlMidiControlForm::reset()"); #endif if (QMessageBox::warning(this, QDialog::windowTitle(), tr("All settings will be reset to the original default.\n\n" "Are you sure to apply the changes?"), QMessageBox::Reset | QMessageBox::Cancel) == QMessageBox::Cancel) return; qmidictlMidiControl *pMidiControl = qmidictlMidiControl::getInstance(); if (pMidiControl) { pMidiControl->clear(); m_iDirtyCount++; } activateCommand(m_ui.CommandComboBox->currentIndex()); } // end of qmidictlMidiControlForm.cpp qmidictl-1.0.1/src/PaxHeaders/qmidictlActionBarStyle.h0000644000000000000000000000013214675771705017772 xustar0030 mtime=1727525829.432779736 30 atime=1727525829.432779736 30 ctime=1727525829.432779736 qmidictl-1.0.1/src/qmidictlActionBarStyle.h0000644000175000001440000000606514675771705017771 0ustar00rncbcusers// qmidictlActionBarStyle.h // /**************************************************************************** Copyright (C) 2010-2022, rncbc aka Rui Nuno Capela. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. *****************************************************************************/ #ifndef __qmidictlActionBarStyle_h #define __qmidictlActionBarStyle_h /* This code is borrowed, stirred and adapted from menustyle.h * * https://github.com/mbnoimi/QtActionBar.git * * (c) 2015 by Muhammad Bashir Al-Noimi * (c) 2014 by Stefan Frings * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. */ #include // Forward decls. class QStyleOption; /** * This proxy style changes the size of icons relative to the font size. * I use it to give icons a proper size on high-res displays. */ class qmidictlActionBarStyle : public QProxyStyle { public: /** Constructor */ qmidictlActionBarStyle(QStyle *style = nullptr); /** Convert the provided dimension in dp (device-independent pixels) to the * corresponding number of actual pixels on the current display. */ static int dpToPixels(int dp); /** Always draw QToolButtons in the default flat style */ void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *option, QPainter *painter, const QWidget *widget = 0) const; /**Never show buttons with input focus as highlighted */ void drawControl(ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget = 0) const; /** Calculate the size of icons relative to the font size */ int pixelMetric(PixelMetric metric, const QStyleOption *option = 0, const QWidget *widget = 0) const; }; #endif // __qmidictlActionBarStyle_h // end of qmidictlActionBarStyle.h