├── .clang-format ├── .gitattributes ├── .github └── workflows │ └── Build.yml ├── .gitignore ├── .gitmodules ├── CHANGES.md ├── CONTRIBUTORS.md ├── LICENSE.md ├── QDriverStation.pro ├── README.md ├── doc ├── QDriverStation-macOS.png ├── config │ ├── doxyfile │ ├── icon.png │ └── startpage.md └── project.png ├── etc ├── deploy │ ├── linux │ │ ├── qdriverstation.desktop │ │ └── qdriverstation.png │ ├── macOS │ │ ├── icon.icns │ │ └── info.plist │ └── windows │ │ ├── depends │ │ ├── SDL2.dll │ │ └── pthreadVC2.dll │ │ ├── nsis │ │ └── setup.nsi │ │ └── resources │ │ ├── icon.ico │ │ └── info.rc └── resources │ ├── fonts │ ├── FontAwesome.ttf │ ├── Ubuntu-Bold.ttf │ ├── Ubuntu-Regular.ttf │ └── UbuntuMono.ttf │ └── resources.qrc ├── qml ├── Dialogs │ ├── SettingsWindow.qml │ └── VirtualJoystickWindow.qml ├── Globals.js ├── MainWindow │ ├── About.qml │ ├── BatteryChart.qml │ ├── Charts.qml │ ├── Diagnostics.qml │ ├── JoystickItem.qml │ ├── Joysticks.qml │ ├── LeftTab.qml │ ├── MainWindow.qml │ ├── Messages.qml │ ├── Operator.qml │ ├── Preferences.qml │ ├── RightTab.qml │ ├── Status.qml │ └── VoltageGraph.qml ├── Widgets │ ├── Button.qml │ ├── Checkbox.qml │ ├── Combobox.qml │ ├── Icon.qml │ ├── Icons.qml │ ├── LED.qml │ ├── Label.qml │ ├── LineEdit.qml │ ├── ListViewer.qml │ ├── Panel.qml │ ├── Plot.qml │ ├── Progressbar.qml │ ├── Scrollbar.qml │ ├── Spinbox.qml │ └── TextEditor.qml ├── main.qml └── qml.qrc └── src ├── beeper.cpp ├── beeper.h ├── dashboards.cpp ├── dashboards.h ├── main.cpp ├── shortcuts.cpp ├── shortcuts.h ├── utilities.cpp ├── utilities.h └── versions.h /.clang-format: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2016 Olivier Goffart 2 | # 3 | # You may use this file under the terms of the 3-clause BSD license. 4 | # See the file LICENSE from this package for details. 5 | 6 | # This is the clang-format configuration style to be used by Qt, 7 | # based on the rules from https://wiki.qt.io/Qt_Coding_Style and 8 | # https://wiki.qt.io/Coding_Conventions 9 | 10 | --- 11 | # Webkit style was loosely based on the Qt style 12 | BasedOnStyle: WebKit 13 | 14 | Standard: Cpp11 15 | 16 | # Leave the line breaks up to the user. 17 | # Note that this may be changed at some point in the future. 18 | ColumnLimit: 0 19 | # How much weight do extra characters after the line length limit have. 20 | # PenaltyExcessCharacter: 4 21 | 22 | # Disable reflow of qdoc comments: indentation rules are different. 23 | # Translation comments are also excluded. 24 | CommentPragmas: "^!|^:" 25 | 26 | # We want a space between the type and the star for pointer types. 27 | PointerBindsToType: false 28 | 29 | # We use template< without space. 30 | SpaceAfterTemplateKeyword: false 31 | 32 | # We want to break before the operators, but not before a '='. 33 | BreakBeforeBinaryOperators: All 34 | 35 | # Braces are usually attached, but not after functions or class declarations. 36 | BreakBeforeBraces: Custom 37 | BraceWrapping: 38 | AfterClass: true 39 | AfterControlStatement: true 40 | AfterEnum: true 41 | AfterFunction: true 42 | AfterNamespace: true 43 | AfterObjCDeclaration: true 44 | AfterStruct: true 45 | AfterUnion: true 46 | BeforeCatch: true 47 | BeforeElse: true 48 | IndentBraces: false 49 | 50 | BreakBeforeTernaryOperators: true 51 | 52 | BreakConstructorInitializers: BeforeComma 53 | 54 | # Indent initializers by 3 spaces 55 | ConstructorInitializerIndentWidth: 3 56 | 57 | # No indentation for namespaces. 58 | NamespaceIndentation: None 59 | 60 | # Horizontally align arguments after an open bracket. 61 | # The coding style does not specify the following, but this is what gives 62 | # results closest to the existing code. 63 | AlignAfterOpenBracket: true 64 | AlwaysBreakTemplateDeclarations: true 65 | 66 | # Ideally we should also allow less short function in a single line, but 67 | # clang-format does not handle that. 68 | AllowShortFunctionsOnASingleLine: Inline 69 | 70 | # The coding style specifies some include order categories, but also tells to 71 | # separate categories with an empty line. It does not specify the order within 72 | # the categories. Since the SortInclude feature of clang-format does not 73 | # re-order includes separated by empty lines, the feature is not used. 74 | SortIncludes: false 75 | 76 | # macros for which the opening brace stays attached. 77 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ] 78 | 79 | IndentCaseLabels: true 80 | 81 | IndentPPDirectives: AfterHash 82 | 83 | AlignAfterOpenBracket: Align 84 | 85 | AccessModifierOffset: -3 86 | 87 | IndentWidth: 3 88 | 89 | #StatementMacros ['Q_OBJECT', 'Q_UNUSED'] 90 | 91 | ColumnLimit: 120 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | etc/* linguist-vendored 2 | doc/* linguist-vendored 3 | lib/* linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/workflows/Build.yml: -------------------------------------------------------------------------------- 1 | #-------------------------------------------------------------------------------- 2 | # Workflow configuration 3 | #-------------------------------------------------------------------------------- 4 | 5 | name: Build 6 | on: 7 | push: # Run on push 8 | paths-ignore: # File patterns to ignore 9 | - '**.md' # Ignore changes to *.md files 10 | 11 | pull_request: # Run on pull-request 12 | paths-ignore: # File-patterns to ignore 13 | - '**.md' # Ignore changes to *.md files 14 | 15 | workflow_dispatch: 16 | 17 | #-------------------------------------------------------------------------------- 18 | # Define application name & version 19 | #-------------------------------------------------------------------------------- 20 | 21 | env: 22 | VERSION: "21.04" 23 | EXECUTABLE: "QDriverStation" 24 | QMAKE_PROJECT: "QDriverStation.pro" 25 | QML_DIR_NIX: "qml" 26 | QML_DIR_WIN: "qml" 27 | 28 | #-------------------------------------------------------------------------------- 29 | # Workflow jobs (GNU/Linux, macOS & Windows) 30 | #-------------------------------------------------------------------------------- 31 | 32 | jobs: 33 | 34 | 35 | # GNU/Linux build (we run on Ubuntu 16.04 to generate AppImage) 36 | build-linux: 37 | runs-on: ubuntu-16.04 38 | name: '🐧 Ubuntu 16.04' 39 | steps: 40 | 41 | - name: '🧰 Checkout' 42 | uses: actions/checkout@v2 43 | with: 44 | submodules: recursive 45 | 46 | - name: '⚙️ Cache Qt' 47 | id: cache-qt 48 | uses: actions/cache@v1 49 | with: 50 | path: ../Qt 51 | key: ${{runner.os}}-QtCache 52 | 53 | - name: '⚙️ Install Qt' 54 | uses: jurplel/install-qt-action@v2 55 | with: 56 | modules: qtcharts 57 | cached: ${{steps.cache-qt.outputs.cache-hit}} 58 | 59 | # Install additional dependencies, stolen from: 60 | # https://github.com/mapeditor/tiled/blob/master/.github/workflows/packages.yml 61 | - name: '⚙️ Install dependencies' 62 | run: | 63 | sudo apt-get update 64 | sudo apt-get install libgl1-mesa-dev libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libzstd-dev libsdl2-dev 65 | 66 | - name: '🚧 Compile application' 67 | run: | 68 | qmake ${{env.QMAKE_PROJECT}} CONFIG+=release PREFIX=/usr 69 | make -j8 70 | 71 | - name: '📦 Create AppImage' 72 | run: | 73 | make INSTALL_ROOT=appdir install 74 | 75 | wget -c -nv "https://github.com/probonopd/linuxdeployqt/releases/download/continuous/linuxdeployqt-continuous-x86_64.AppImage" -O linuxdeployqt 76 | chmod a+x linuxdeployqt 77 | ./linuxdeployqt appdir/usr/share/applications/*.desktop -appimage -bundle-non-qt-libs -extra-plugins=imageformats/libqsvg.so -qmldir="${{env.QML_DIR_NIX}}" 78 | 79 | # Rename AppImage to match "%AppName%-%Version%-Linux.AppImage" format 80 | mv *.AppImage ${{env.EXECUTABLE}}-${{env.VERSION}}-Linux.AppImage 81 | 82 | - name: '📤 Upload artifact: AppImage' 83 | uses: actions/upload-artifact@v2 84 | with: 85 | name: ${{env.EXECUTABLE}}-${{env.VERSION}}-Linux.AppImage 86 | path: ${{env.EXECUTABLE}}-${{env.VERSION}}-Linux.AppImage 87 | 88 | 89 | # macOS build 90 | build-mac: 91 | runs-on: macos-latest 92 | name: '🍎 macOS' 93 | steps: 94 | 95 | - name: '🧰 Checkout' 96 | uses: actions/checkout@v2 97 | with: 98 | submodules: recursive 99 | 100 | - name: '⚙️ Cache Qt' 101 | id: cache-qt 102 | uses: actions/cache@v1 103 | with: 104 | path: ../Qt 105 | key: ${{runner.os}}-QtCache 106 | 107 | - name: '⚙️ Install Qt' 108 | uses: jurplel/install-qt-action@v2 109 | with: 110 | modules: qtcharts 111 | cached: ${{steps.cache-qt.outputs.cache-hit}} 112 | 113 | - name: '🚧 Compile application' 114 | run: | 115 | qmake ${{env.QMAKE_PROJECT}} CONFIG+=release 116 | make -j8 117 | 118 | - name: '📦 Package application (macdeployqt and zipfile)' 119 | run: | 120 | macdeployqt ${{env.EXECUTABLE}}.app -qmldir="${{env.QML_DIR_NIX}}" 121 | 122 | # ZIP application "%AppName%-%Version%-macOS.zip" 123 | # We use ditto instead of zip to use the same commands as Finder 124 | ditto -c -k --sequesterRsrc --keepParent "${{env.EXECUTABLE}}.app" ${{env.EXECUTABLE}}-${{env.VERSION}}-macOS.zip 125 | 126 | - name: '📤 Upload artifact: ZIP' 127 | uses: actions/upload-artifact@v2 128 | with: 129 | name: ${{env.EXECUTABLE}}-${{env.VERSION}}-macOS.zip 130 | path: ${{env.EXECUTABLE}}-${{env.VERSION}}-macOS.zip 131 | 132 | 133 | # Windows build 134 | build-windows: 135 | runs-on: windows-latest 136 | name: '🧊 Windows' 137 | steps: 138 | 139 | - name: '🧰 Checkout' 140 | uses: actions/checkout@v2 141 | with: 142 | submodules: recursive 143 | 144 | - name: '⚙️ Configure MSVC' 145 | uses: ilammy/msvc-dev-cmd@v1 146 | with: 147 | arch: x64 148 | spectre: true 149 | 150 | - name: '⚙️ Cache Qt' 151 | id: cache-qt 152 | uses: actions/cache@v1 153 | with: 154 | path: ../Qt 155 | key: ${{runner.os}}-QtCache 156 | 157 | - name: '⚙️ Install Qt' 158 | uses: jurplel/install-qt-action@v2 159 | with: 160 | modules: qtcharts 161 | cached: ${{steps.cache-qt.outputs.cache-hit}} 162 | 163 | - name: '⚙️ Install NSIS' 164 | run: | 165 | Invoke-Expression (New-Object System.Net.WebClient).DownloadString('https://get.scoop.sh') 166 | scoop bucket add extras 167 | scoop install nsis 168 | 169 | - name: '🚧 Compile application' 170 | run: | 171 | qmake ${{env.QMAKE_PROJECT}} CONFIG+=release 172 | nmake 173 | 174 | # Copy Qt DLLs, compiler runtime & application icon 175 | - name: '📦 Package application (windeployqt)' 176 | run: | 177 | mkdir bin 178 | move release/${{env.EXECUTABLE}}.exe bin 179 | windeployqt bin/${{env.EXECUTABLE}}.exe -qmldir="${{env.QML_DIR_WIN}}" --compiler-runtime 180 | mkdir "${{env.EXECUTABLE}}" 181 | move bin "${{env.EXECUTABLE}}" 182 | xcopy etc\deploy\windows\resources\icon.ico "${{env.EXECUTABLE}}" 183 | xcopy etc\deploy\windows\depends\*.dll "${{env.EXECUTABLE}}\bin" 184 | 185 | - name: '📦 Make NSIS installer' 186 | run: | 187 | move "${{env.EXECUTABLE}}" etc\deploy\windows\nsis\ 188 | cd etc\deploy\windows\nsis 189 | makensis /X"SetCompressor /FINAL lzma" setup.nsi 190 | ren *.exe ${{env.EXECUTABLE}}-${{env.VERSION}}-Windows.exe 191 | 192 | - name: '📤 Upload artifact: NSIS installer' 193 | uses: actions/upload-artifact@v2 194 | with: 195 | name: ${{env.EXECUTABLE}}-${{env.VERSION}}-Windows.exe 196 | path: etc/deploy/windows/nsis/${{env.EXECUTABLE}}-${{env.VERSION}}-Windows.exe 197 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # General 3 | .DS_Store 4 | .AppleDouble 5 | .LSOverride 6 | 7 | # Icon must end with two \r 8 | Icon 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | 29 | # Qt project file 30 | *.pro.user 31 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/QJoysticks"] 2 | path = lib/QJoysticks 3 | url = https://github.com/alex-spataru/QJoysticks 4 | [submodule "lib/LibDS"] 5 | path = lib/LibDS 6 | url = https://github.com/FRC-Utilities/LibDS 7 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changes 2 | 3 | ### QDriverStation 21.04 4 | 5 | - Use GitHub actions for building the project 6 | 7 | ### QDriverStation 20.10 8 | 9 | - Add QJoysticks & LibDS as submodules 10 | - Change code-formating scripts to `clang-format` 11 | - Add support for 2020 protocol (WIP) 12 | - Minor fixes for Qt 5.15 13 | 14 | ### QDriverStation 17.05 15 | 16 | - Minor UI changes 17 | - Remove QSimpleUpdater module 18 | - Fix CPU usage code on GNU/Linux 19 | - Improve memory usage of LibDS 20 | 21 | ### QDriverStation 17.01.1 22 | 23 | - Fix issue in which joystick input was not detected 24 | 25 | ### QDriverStation 17.01 26 | 27 | - Transition to LibDS-C, which does not depend on Qt and is more efficient 28 | - Re-implement logging system 29 | - Avoid crashes when inputting invalid network addresses 30 | - Change settings dialog buttons to be more user friendly 31 | - Minor QML UI improvements/changes 32 | 33 | ### QDriverStation 16.08 34 | 35 | - Fix issue where we the QDriverStation could not detect robot code with 2009/2014 robots 36 | 37 | ### QDriverStation 16.07 38 | 39 | - Add some CLI options 40 | - Add integrated mDNS responder 41 | - Save logs as JSON files 42 | - Allow user to set custom radio & FMS IPs 43 | - Improve handling of FMS packets 44 | - Minor improvements in code structure 45 | - Add code to interpret 2014 FMS packets 46 | - Implement logs window (netconsole + app logs) 47 | - Fix communication issues 48 | 49 | ### QDriverStation 16.06.2 50 | 51 | - Minor UI improvements 52 | - Add automatic DNS/host lookup 53 | - Improve code for obtaining CPU usage & Battery status 54 | - Fix docked windows for Linux 55 | - Save blacklisted joysticks through sessions 56 | - Put blacklisted joysticks at the bottom of the joystick list 57 | 58 | ### QDriverStation 16.06.1 59 | 60 | - Fix an issue that did not allow the DS to connect to the robot in some networks 61 | 62 | ### QDriverStation 16.06 63 | 64 | - Support for 2014 protocol 65 | - Implement parallel sockets to detect the robot a LOT faster 66 | - Better robot voltage reading code 67 | - Dynamic control of the watchdog 68 | - Fix crash issue on Mac OS X 69 | - Implement a logger 70 | - Fix some minor issues in the UI 71 | - Add base framework for implementing FMS communications 72 | - Redesign UI in QML 73 | - New application icon 74 | - FMS support for 2015 protocol (UNTESTED) 75 | - Allow enabling/disabling joysticks 76 | - Add voltage charts 77 | - Rewrite of LibDS to be more extensible and efficient (if a protocol requires constant comms. with radio or runs on TCP, we've got it!) 78 | 79 | ### QDriverStation 0.14: 80 | 81 | - Support for 2016 protocol 82 | - POV/Hat support for joysticks 83 | - Re-write of most of the code base 84 | - Map E-STOP to SHIFT and SPACE keys 85 | - 2015 protocol gets a lot of improvements 86 | - UI is written from ground-up in a more modular way 87 | - Implement global event filter to avoid issues with virtual joystick or E-STOP trigger 88 | - Implement built-in mDNS support for better cross-platform operation 89 | - Do not use XInput for joystick reading, it only makes everything worse 90 | - Improvements in socket programming for the LibDS 91 | - Scalable UI to any pixel density 92 | - Implement UI sound effects 93 | - More options in the preferences window 94 | 95 | ### QDriverStation 0.13: 96 | 97 | - Map E-STOP to SHIFT key 98 | - Implement auto-updater 99 | - Remove Gamepad API from joystick reader 100 | 101 | ### QDriverStation 0.12: 102 | 103 | - Initial release 104 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors 2 | 3 | While [Alex](https://github.com/alex-spataru) does most of the development of this project, the QDriverStation has received contributions and help from many people. If you have contributed to this project and you are not in this list, please [e-mail me](mailto:alex_spataru@outlook.com) so that I can add you to this list (or make a pull request). 4 | 5 | - [Jessica Creighton](https://github.com/jcreigh), for providing [excelent information](https://github.com/jcreigh/FRCDriverStation/wiki) regarding the 2015 protocol and thus solving numerous issues in the 2015 protocol implementation. 6 | - [Thomas Clark](https://github.com/ThomasJClark), for providing the pseudo-code needed to detect user code on the roboRIO and providing information about the "extra" data structures in the roboRIO->DS packets. 7 | - [Ray Stubbs](https://github.com/raystubbs), for providing the much-needed help regarding joystick encoding for the 2015 protocol. If you are good with Java (which I am not), you can contribute to his [DS library](https://github.com/raystubbs/RioComAPI). 8 | - [Ben Wolsieffer](https://github.com/lopsided98), for creating an [ArchLinux package](https://aur.archlinux.org/packages/qdriverstation-git/) for the QDriverStation, providing essential information and support for implementing the 2014 protocol and improving the dashboard initialization code. 9 | - [Boomaa23](https://github.com/boomaa23), for implementing the 2020 FRC Comm. protocol. 10 | - [Tyler Veness](https://github.com/calcmogul), for improving the code used to get CPU usage under GNU/Linux. 11 | - [Simon Andrews](https://github.com/simon-andrews), for improving the Linux launcher of the QDriverStation. 12 | - [Thomas Ross](https://github.com/thomassross), for fixing the Travis CI build script. 13 | - [Dakota Keeler](https://github.com/BearzRobotics), for building [Debian Packages](https://drive.google.com/file/d/0BwmIj7Fz03lXZ1JjYnhLVVdRR0E/view?usp=sharing) of the application, providing feedback and making a video tutorial to compile and run the QDriverStation on Linux systems. 14 | - [Jelomite](https://github.com/jelomite), for creating the application icon. 15 | - [Peter Mitrano](https://github.com/PeterMitrano), for contributing to the .gitignore configuration and thus avoiding possible compilation issues. 16 | - [SoftwareBug2.0](http://www.chiefdelphi.com/forums/member.php?u=7765), for helping fellow GNU/Linux users using Ubuntu to compile the application. 17 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2017 Alex Spataru 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /QDriverStation.pro: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2015-2021 Alex Spataru 3 | # 4 | # Permission is hereby granted, free of charge, to any person obtaining a copy 5 | # of this software and associated documentation files (the "Software"), to deal 6 | # in the Software without restriction, including without limitation the rights 7 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | # copies of the Software, and to permit persons to whom the Software is 9 | # furnished to do so, subject to the following conditions: 10 | # 11 | # The above copyright notice and this permission notice shall be included in 12 | # all copies or substantial portions of the Software. 13 | # 14 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | # THE SOFTWARE. 21 | 22 | #----------------------------------------------------------------------------------------- 23 | # Make options 24 | #----------------------------------------------------------------------------------------- 25 | 26 | UI_DIR = uic 27 | MOC_DIR = moc 28 | RCC_DIR = qrc 29 | OBJECTS_DIR = obj 30 | 31 | CONFIG += c++11 32 | 33 | isEmpty(PREFIX) { 34 | PREFIX = /usr 35 | } 36 | 37 | #------------------------------------------------------------------------------- 38 | # Deploy configuration 39 | #------------------------------------------------------------------------------- 40 | 41 | TEMPLATE = app 42 | TARGET = QDriverStation 43 | CONFIG += resources_big 44 | CONFIG += qtquickcompiler 45 | 46 | QTPLUGIN += qsvg 47 | 48 | QT += xml 49 | QT += sql 50 | QT += svg 51 | QT += core 52 | QT += quick 53 | QT += widgets 54 | 55 | #------------------------------------------------------------------------------- 56 | # Compiler options 57 | #------------------------------------------------------------------------------- 58 | 59 | *g++*: { 60 | QMAKE_CXXFLAGS_RELEASE -= -O 61 | QMAKE_CXXFLAGS_RELEASE *= -O3 62 | } 63 | 64 | *msvc*: { 65 | QMAKE_CXXFLAGS_RELEASE -= /O 66 | QMAKE_CXXFLAGS_RELEASE *= /O2 67 | } 68 | 69 | #------------------------------------------------------------------------------- 70 | # Deploy configuration 71 | #------------------------------------------------------------------------------- 72 | 73 | win32* { 74 | LIBS += -lPdh -lgdi32 # pthread + gdi 75 | RC_FILE = etc/deploy/windows/resources/info.rc # Set applicaiton icon 76 | OTHER_FILES += etc/deploy/windows/nsis/setup.nsi # Setup script 77 | } 78 | 79 | macx* { 80 | ICON = etc/deploy/macOS/icon.icns # icon file 81 | RC_FILE = etc/deploy/macOS/icon.icns # icon file 82 | QMAKE_INFO_PLIST = etc/deploy/macOS/info.plist # Add info.plist file 83 | CONFIG += sdk_no_version_check # Avoid warnings with Big Sur 84 | } 85 | 86 | linux:!android { 87 | TARGET = qdriverstation 88 | target.path = $$PREFIX/bin 89 | icon.path = $$PREFIX/share/pixmaps # icon instalation path 90 | desktop.path = $$PREFIX/share/applications # *.desktop instalation path 91 | icon.files += etc/deploy/linux/*.png # Add application icon 92 | desktop.files += etc/deploy/linux/*.desktop # Add *.desktop file 93 | INSTALLS += target desktop icon # make install targets 94 | } 95 | 96 | #------------------------------------------------------------------------------- 97 | # Include other libraries 98 | #------------------------------------------------------------------------------- 99 | 100 | include ($$PWD/lib/QJoysticks/QJoysticks.pri) 101 | include ($$PWD/lib/LibDS/wrappers/Qt/LibDS-Qt.pri) 102 | 103 | #------------------------------------------------------------------------------- 104 | # Import source code and QML 105 | #------------------------------------------------------------------------------- 106 | 107 | SOURCES += \ 108 | $$PWD/src/main.cpp \ 109 | $$PWD/src/utilities.cpp \ 110 | $$PWD/src/beeper.cpp \ 111 | $$PWD/src/dashboards.cpp \ 112 | $$PWD/src/shortcuts.cpp 113 | 114 | HEADERS += \ 115 | $$PWD/src/utilities.h \ 116 | $$PWD/src/beeper.h \ 117 | $$PWD/src/dashboards.h \ 118 | $$PWD/src/versions.h \ 119 | $$PWD/src/shortcuts.h 120 | 121 | RESOURCES += \ 122 | $$PWD/qml/qml.qrc \ 123 | $$PWD/etc/resources/resources.qrc 124 | 125 | OTHER_FILES += \ 126 | $$PWD/qml/*.qml \ 127 | $$PWD/qml/*.js \ 128 | $$PWD/qml/Dialogs/*.qml \ 129 | $$PWD/qml/Widgets/*.qml \ 130 | $$PWD/qml/MainWindow/*.qml 131 | 132 | #------------------------------------------------------------------------------- 133 | # Deploy files 134 | #------------------------------------------------------------------------------- 135 | 136 | OTHER_FILES += \ 137 | deploy/linux/* \ 138 | deploy/macOS/* \ 139 | deploy/windows/nsis/* \ 140 | deploy/windows/resources/* 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QDriverStation 2 | 3 | 4 | 5 | 6 | 7 | [![Build Status](https://github.com/FRC-Utilities/QDriverStation/workflows/Build/badge.svg)](https://github.com/FRC-Utilities/QDriverStation/actions) 8 | [![Github All Releases](https://img.shields.io/github/downloads/FRC-Utilities/QDriverStation/total.svg)](https://github.com/FRC-Utilities/QDriverStation/releases/) 9 | 10 | The QDriverStation is a cross-platform and open-source alternative to the FRC Driver Station. It allows you to operate FRC robots with the major operating systems (Windows, Mac OSX and GNU/Linux). The QDriverStation is able to operate both 2009-2014 robots and 2015-2017 robots, support for 2020 robots is on the way. 11 | 12 | The actual code that operates a FRC robot is found in a [separate repository](https://github.com/FRC-Utilities/LibDS), which is written in C and can be used for your own projects or change it to support more communication protocols (such as [ROS](https://github.com/FRC-Utilities/QDriverStation/issues/21)). 13 | 14 | You can find the online documentation of the QDriverStation and its sub-projects [here](http://frc-utilities.github.io/documentation/). 15 | 16 | ![macOS Screenshot](doc/QDriverStation-macOS.png) 17 | 18 | ### Install notes 19 | 20 | You can download the QDriverStation from [GitHub](http://github.com/FRC-Utilities/QDriverStation/releases). 21 | 22 | Once you finish installing the software, you can launch it and begin driving your robot. Just be sure to input your team number and to verify that the joysticks are working correctly. 23 | 24 | Mac users will be prompted to download an additional driver for Xbox 360 controllers to work. 25 | 26 | ###### Note for Linux users 27 | 28 | For convenience, Linux releases are now handled with AppImages. To run the AppImage, simply download the latest release, make it executable and run it. 29 | 30 | Terminal commands below: 31 | 32 | cd Downloads 33 | chmod +x QDriverStation*.AppImage 34 | ./QDriverStation*.AppImage 35 | 36 | More info can be found here: [https://appimage.org/](https://appimage.org/). 37 | 38 | ###### Warnings 39 | 40 | If you are on Linux, the QDriverStation may detect some devices as a joystick ([more info...](https://gist.github.com/denilsonsa/978f1d842cf5430f57f6#file-51-these-are-not-joysticks-rules)). If that happens, just disable the faulty device by clicking on the power button next to its name. 41 | 42 | ### Build instructions 43 | 44 | ###### Requirements 45 | 46 | The only requirement to compile the application is to have [Qt](http://www.qt.io/download-open-source/) installed in your system. The desktop application will compile with Qt 5.15 or greater. 47 | 48 | - If you are using Linux, make sure that you have installed the following packages: 49 | - `libsdl2-dev` 50 | 51 | The project already contains the compiled SDL libraries for Windows and Mac. 52 | 53 | ### Cloning this repository 54 | 55 | This repository makes use of [`git submodule`](https://git-scm.com/docs/git-submodule). In order to clone it, you have two options: 56 | 57 | One-liner: 58 | 59 | git clone --recursive https://github.com/FRC-Utilities/QDriverStation/ 60 | 61 | Normal procedure: 62 | 63 | git clone https://github.com/FRC-Utilities/QDriverStation/ 64 | cd QDriverStation 65 | git submodule init 66 | git submodule update 67 | 68 | ###### Compiling the application 69 | 70 | Once you have Qt installed, open *QDriverStation.pro* in Qt Creator and click the "Run" button. 71 | 72 | Alternatively, you can also use the following commands: 73 | - qmake 74 | - make 75 | - **Optional:** sudo make install 76 | 77 | ### Credits 78 | 79 | This application was created by [Alex Spataru](http://github.com/alex-spataru). 80 | 81 | Of course, many people contributed in different ways to this project, you can find more details in the [contributors list](CONTRIBUTORS.md). Finally, we want to thank you for trying this little project, we sincerely hope that you enjoy our application and we would love some of your feedback. 82 | 83 | ### License 84 | 85 | This project is released under the MIT License. For more information, [click here](LICENSE.md). 86 | -------------------------------------------------------------------------------- /doc/QDriverStation-macOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/doc/QDriverStation-macOS.png -------------------------------------------------------------------------------- /doc/config/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/doc/config/icon.png -------------------------------------------------------------------------------- /doc/config/startpage.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The QDriverStation is a cross-platform and open-source alternative to the FRC Driver Station. It allows you to operate FRC robots with the major operating systems (Windows, Mac OSX and GNU/Linux). The QDriverStation is able to operate both 2009-2014 robots and 2015-2016 robots. 4 | 5 | The QDriverStation uses LibDS to handle computer-to-robot and computer-to-FMS communications efficiently. 6 | 7 | ### Install notes 8 | 9 | You can download the QDriverStation from both SourceForge and GitHub. We recommend you to download QDriverStation from GitHub, since SourceForge is known for serving misleading advertisements. 10 | 11 | Once you finish installing the software, you can launch it and begin driving your robot. Just be sure to input your team number and to verify that the joysticks are working correctly. 12 | 13 | Mac users will be prompted to download an additional driver for Xbox 360 controllers to work. 14 | 15 | ### Build instructions 16 | 17 | #### Requirements 18 | 19 | The only requirement to compile the application is to have Qt installed in your system. The desktop application will compile with Qt 5.2 or greater. 20 | 21 | - If you are using Linux, make sure that you have installed the following packages: 22 | - libsdl2-dev 23 | 24 | The project already contains the compiled SDL libraries for Windows and Mac. 25 | 26 | #### Compiling the application 27 | 28 | Once you have Qt installed, open *QDriverStation.pro* in Qt Creator and click the "Run" button. 29 | 30 | Alternatively, you can also use the following commands: 31 | - qmake 32 | - make 33 | - **Optional:** sudo make install 34 | 35 | ### License 36 | 37 | The QDriverStation is released under the MIT license. 38 | -------------------------------------------------------------------------------- /doc/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/doc/project.png -------------------------------------------------------------------------------- /etc/deploy/linux/qdriverstation.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=QDriverStation 3 | Comment=Cross-platform clone of the FRC Driver Station 4 | Exec=qdriverstation 5 | Terminal=false 6 | Type=Application 7 | StartupNotify=true 8 | Categories=Development;Education;Qt;Robotics;Utility; 9 | Icon=qdriverstation 10 | -------------------------------------------------------------------------------- /etc/deploy/linux/qdriverstation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/deploy/linux/qdriverstation.png -------------------------------------------------------------------------------- /etc/deploy/macOS/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/deploy/macOS/icon.icns -------------------------------------------------------------------------------- /etc/deploy/macOS/info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | QDriverStation 9 | CFBundleGetInfoString 10 | Copyright © 2015-2021 Alex Spataru 11 | CFBundleIconFile 12 | icon 13 | CFBundleIdentifier 14 | org.frc-utilities.qdriverstation 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | QDriverStation 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | 21.04 23 | LSHasLocalizedDisplayName 24 | 25 | NSPrincipalClass 26 | NSApplication 27 | NSSupportsSuddenTermination 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /etc/deploy/windows/depends/SDL2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/deploy/windows/depends/SDL2.dll -------------------------------------------------------------------------------- /etc/deploy/windows/depends/pthreadVC2.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/deploy/windows/depends/pthreadVC2.dll -------------------------------------------------------------------------------- /etc/deploy/windows/nsis/setup.nsi: -------------------------------------------------------------------------------- 1 | ; 2 | ; Copyright (c) 2021 Alex Spataru 3 | ; 4 | ; This program is free software; you can redistribute it and/or modify 5 | ; it under the terms of the GNU General Public License as published by 6 | ; the Free Software Foundation; either version 3 of the License, or 7 | ; (at your option) any later version. 8 | ; 9 | ; This program is distributed in the hope that it will be useful, 10 | ; but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | ; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | ; GNU General Public License for more details. 13 | ; 14 | ; You should have received a copy of the GNU General Public License 15 | ; along with this program; if not, write to the Free Software 16 | ; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 17 | ; USA 18 | ; 19 | 20 | Unicode True 21 | 22 | !include "MUI2.nsh" 23 | !include "FileFunc.nsh" 24 | !include "LogicLib.nsh" 25 | 26 | !define APPNAME "QDriverStation" 27 | !define EXECNAME "QDriverStation" 28 | !define COMPANYNAME "Alex Spataru" 29 | !define DESCRIPTION "Cross-platform clone of the FRC Driver Station" 30 | !define VERSIONMAJOR 21 31 | !define VERSIONMINOR 04 32 | !define VERSIONBUILD 0 33 | !define MUI_ABORTWARNING 34 | !define INSTALL_DIR "$PROGRAMFILES64\${APPNAME}" 35 | !define MUI_FINISHPAGE_RUN 36 | !define MUI_FINISHPAGE_RUN_TEXT "Run ${APPNAME}" 37 | !define MUI_FINISHPAGE_RUN_FUNCTION "RunApplication" 38 | !define MUI_FINISHPAGE_LINK "Visit project website" 39 | !define MUI_FINISHPAGE_LINK_LOCATION "http://frc-utilities.github.io/" 40 | !define MUI_WELCOMEPAGE_TITLE "Welcome to the ${APPNAME} installer!" 41 | 42 | !insertmacro MUI_PAGE_WELCOME 43 | !insertmacro MUI_PAGE_DIRECTORY 44 | !insertmacro MUI_PAGE_INSTFILES 45 | !insertmacro MUI_PAGE_FINISH 46 | !insertmacro MUI_UNPAGE_WELCOME 47 | !insertmacro MUI_UNPAGE_CONFIRM 48 | !insertmacro MUI_UNPAGE_INSTFILES 49 | !insertmacro MUI_LANGUAGE "English" 50 | 51 | !macro VerifyUserIsAdmin 52 | UserInfo::GetAccountType 53 | pop $0 54 | ${If} $0 != "admin" 55 | messageBox mb_iconstop "Administrator rights required!" 56 | setErrorLevel 740 57 | quit 58 | ${EndIf} 59 | !macroend 60 | 61 | Name "${APPNAME}" 62 | ManifestDPIAware true 63 | InstallDir "${INSTALL_DIR}" 64 | RequestExecutionLevel admin 65 | OutFile "${EXECNAME}-${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}-Windows.exe" 66 | 67 | Function .onInit 68 | setShellVarContext all 69 | !insertmacro VerifyUserIsAdmin 70 | FunctionEnd 71 | 72 | Section "${APPNAME} (required)" SecDummy 73 | SectionIn RO 74 | SetOutPath "${INSTALL_DIR}" 75 | File /r "${APPNAME}\*" 76 | 77 | ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 78 | IntFmt $0 "0x%08X" $0 79 | 80 | DeleteRegKey HKCU "Software\${COMPANYNAME}\${APPNAME}" 81 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" 82 | 83 | WriteUninstaller "${INSTALL_DIR}\uninstall.exe" 84 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayName" "${APPNAME}" 85 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "UninstallString" "${INSTALL_DIR}\uninstall.exe" 86 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "InstallLocation" "${INSTALL_DIR}" 87 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "Publisher" "${COMPANYNAME}" 88 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayIcon" "${INSTALL_DIR}\icon.ico" 89 | WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "DisplayVersion" ${VERSIONMAJOR}.${VERSIONMINOR}${VERSIONBUILD} 90 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "VersionMajor" ${VERSIONMAJOR} 91 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "VersionMinor" ${VERSIONMINOR} 92 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "NoModify" 1 93 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "NoRepair" 1 94 | WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" "EstimatedSize" "$0" 95 | SectionEnd 96 | 97 | Section "Start Menu Shortcuts" 98 | CreateShortCut "$SMPROGRAMS\${APPNAME}.lnk" "${INSTALL_DIR}\bin\${EXECNAME}.exe" "" "${INSTALL_DIR}\bin\${EXECNAME}.exe" 0 99 | SectionEnd 100 | 101 | Section "Install Visual C++ Redistributable" 102 | ExecWait "${INSTALL_DIR}\bin\vc_redist.x64.exe /quiet /norestart" 103 | Delete "${INSTALL_DIR}\bin\vc_redist.x64.exe" 104 | SectionEnd 105 | 106 | Function RunApplication 107 | ExecShell "" "${INSTALL_DIR}\bin\${EXECNAME}.exe" 108 | FunctionEnd 109 | 110 | Function un.onInit 111 | SetShellVarContext all 112 | MessageBox MB_OKCANCEL|MB_ICONQUESTION "Are you sure that you want to uninstall ${APPNAME}?" IDOK next 113 | Abort 114 | next: 115 | !insertmacro VerifyUserIsAdmin 116 | FunctionEnd 117 | 118 | Section "Uninstall" 119 | RMDir /r "${INSTALL_DIR}" 120 | RMDir /r "$SMPROGRAMS\${APPNAME}.lnk" 121 | DeleteRegKey HKCU "Software\${COMPANYNAME}\${APPNAME}" 122 | DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANYNAME} ${APPNAME}" 123 | SectionEnd 124 | -------------------------------------------------------------------------------- /etc/deploy/windows/resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/deploy/windows/resources/icon.ico -------------------------------------------------------------------------------- /etc/deploy/windows/resources/info.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "icon.ico" 2 | -------------------------------------------------------------------------------- /etc/resources/fonts/FontAwesome.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/resources/fonts/FontAwesome.ttf -------------------------------------------------------------------------------- /etc/resources/fonts/Ubuntu-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/resources/fonts/Ubuntu-Bold.ttf -------------------------------------------------------------------------------- /etc/resources/fonts/Ubuntu-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/resources/fonts/Ubuntu-Regular.ttf -------------------------------------------------------------------------------- /etc/resources/fonts/UbuntuMono.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FRC-Utilities/QDriverStation/09d57b7a4979e1809bf17c6d9677471a7b11d256/etc/resources/fonts/UbuntuMono.ttf -------------------------------------------------------------------------------- /etc/resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | fonts/FontAwesome.ttf 4 | fonts/Ubuntu-Bold.ttf 5 | fonts/UbuntuMono.ttf 6 | fonts/Ubuntu-Regular.ttf 7 | 8 | 9 | -------------------------------------------------------------------------------- /qml/Dialogs/SettingsWindow.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Window 2.0 25 | import QtQuick.Layouts 1.0 26 | import Qt.labs.settings 1.0 27 | 28 | import "../Widgets" 29 | import "../Globals.js" as Globals 30 | 31 | Window { 32 | id: window 33 | visible: false 34 | width: minimumWidth 35 | height: minimumHeight 36 | title: qsTr ("Settings") 37 | minimumWidth: Globals.scale (420) 38 | maximumWidth: Globals.scale (420) 39 | minimumHeight: Globals.scale (340) 40 | maximumHeight: Globals.scale (340) 41 | color: Globals.Colors.WindowBackground 42 | 43 | // 44 | // Used to obtain the placeholder of each custom IP box 45 | // 46 | function getPlaceholder (ip) { 47 | return ip === "" ? qsTr ("Auto") : ip 48 | } 49 | 50 | // 51 | // Changes the placeholder text of each custom IP box 52 | // 53 | function updatePlaceholders() { 54 | fmsAddress.placeholder = getPlaceholder (CppDS.defaultFMSAddress) 55 | radioAddress.placeholder = getPlaceholder (CppDS.defaultRadioAddress) 56 | robotAddress.placeholder = getPlaceholder (CppDS.defaultRobotAddress) 57 | } 58 | 59 | // 60 | // Default window position 61 | // 62 | x: (Screen.width - width) / 4 63 | y: (Screen.height - height) / 4 64 | 65 | // 66 | // Set window flags 67 | // 68 | flags: Qt.Window | 69 | Qt.WindowTitleHint | 70 | Qt.WindowSystemMenuHint | 71 | Qt.WindowCloseButtonHint | 72 | Qt.WindowMinimizeButtonHint 73 | 74 | // 75 | // Applies the UI settings... 76 | // 77 | function apply() { 78 | updatePlaceholders() 79 | CppBeeper.setEnabled (enableSoundEffects.checked) 80 | CppUtilities.setAutoScaleEnabled (autoScale.checked) 81 | 82 | CppDS.customFMSAddress = fmsAddress.text 83 | CppDS.customRadioAddress = radioAddress.text 84 | CppDS.customRobotAddress = robotAddress.text 85 | } 86 | 87 | // 88 | // Apply settings on launch 89 | // 90 | Component.onCompleted: { 91 | apply() 92 | CppBeeper.setEnabled (enableSoundEffects.checked) 93 | } 94 | 95 | // 96 | // Configure DS on init 97 | // 98 | Connections { 99 | target: CppDS 100 | function onProtocolChanged() { 101 | apply() 102 | } 103 | 104 | function onTeamNumberChanged() { 105 | updatePlaceholders() 106 | } 107 | } 108 | 109 | // 110 | // Save and load the settings 111 | // 112 | Settings { 113 | category: "SettingsWindow" 114 | property alias x: window.x 115 | property alias y: window.y 116 | property alias address: robotAddress.text 117 | property alias autoScale: autoScale.checked 118 | property alias enableSoundEffects: enableSoundEffects.checked 119 | } 120 | 121 | // 122 | // All the widgets of this window are placed in a column 123 | // 124 | ColumnLayout { 125 | anchors.fill: parent 126 | anchors.margins: Globals.spacing 127 | 128 | // 129 | // All the controls that indicate or change preferences 130 | // 131 | Panel { 132 | Layout.fillWidth: true 133 | Layout.fillHeight: true 134 | 135 | ColumnLayout { 136 | anchors.fill: parent 137 | spacing: Globals.spacing 138 | anchors.margins: Globals.spacing * 1.5 139 | 140 | // 141 | // Network settings label 142 | // 143 | Label { 144 | font.bold: true 145 | text: qsTr ("Custom network addresses") + ":" 146 | } 147 | 148 | // 149 | // Network settings controls 150 | // 151 | RowLayout { 152 | Layout.fillWidth: true 153 | spacing: Globals.spacing 154 | 155 | // 156 | // Networking icon 157 | // 158 | Icon { 159 | name: icons.fa_laptop 160 | size: Globals.scale (48) 161 | Layout.minimumWidth: 2 * size 162 | } 163 | 164 | // 165 | // Network settings checkbox & line edit 166 | // 167 | GridLayout { 168 | columns: 2 169 | rowSpacing: Globals.spacing 170 | columnSpacing: Globals.spacing 171 | 172 | Label { 173 | text: qsTr ("FMS") + ":" 174 | } 175 | 176 | LineEdit { 177 | id: fmsAddress 178 | Layout.fillWidth: true 179 | } 180 | 181 | Label { 182 | text: qsTr ("Radio") + ":" 183 | } 184 | 185 | LineEdit { 186 | id: radioAddress 187 | Layout.fillWidth: true 188 | } 189 | 190 | Label { 191 | text: qsTr ("Robot") + ":" 192 | } 193 | 194 | LineEdit { 195 | id: robotAddress 196 | Layout.fillWidth: true 197 | } 198 | } 199 | } 200 | 201 | Item { 202 | height: Globals.spacing * 2 203 | } 204 | 205 | // 206 | // "Other Settings" label 207 | // 208 | Label { 209 | font.bold: true 210 | text: qsTr ("Other Settings") + ":" 211 | } 212 | 213 | // 214 | // "Other settings" controls 215 | // 216 | RowLayout { 217 | Layout.fillWidth: true 218 | spacing: Globals.spacing 219 | 220 | // 221 | // Gears icon 222 | // 223 | Icon { 224 | name: icons.fa_sliders 225 | size: Globals.scale (48) 226 | Layout.minimumWidth: 2 * size 227 | } 228 | 229 | // 230 | // Misc. settings 231 | // 232 | ColumnLayout { 233 | spacing: Globals.spacing 234 | 235 | Checkbox { 236 | checked: true 237 | id: enableSoundEffects 238 | text: qsTr ("Enable UI sound effects") 239 | } 240 | 241 | Checkbox { 242 | checked: true 243 | id: autoScale 244 | text: qsTr ("Auto-scale text and UI items") 245 | } 246 | } 247 | } 248 | 249 | Item { 250 | Layout.fillHeight: true 251 | } 252 | } 253 | } 254 | 255 | // 256 | // Dialog buttons 257 | // 258 | RowLayout { 259 | Layout.fillWidth: true 260 | 261 | Item { 262 | Layout.fillWidth: true 263 | } 264 | 265 | Button { 266 | text: qsTr ("OK") 267 | onClicked: { 268 | apply() 269 | close() 270 | } 271 | } 272 | 273 | Button { 274 | text: qsTr ("Apply") 275 | onClicked: apply() 276 | } 277 | } 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /qml/Dialogs/VirtualJoystickWindow.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Window 2.0 25 | import QtQuick.Layouts 1.0 26 | import Qt.labs.settings 1.0 27 | 28 | import "../Widgets" 29 | import "../Globals.js" as Globals 30 | 31 | Window { 32 | id: window 33 | 34 | // 35 | // Window size 36 | // 37 | width: minimumWidth 38 | height: minimumHeight 39 | minimumWidth: Globals.scale (460) 40 | maximumWidth: Globals.scale (460) 41 | minimumHeight: Globals.scale (320) 42 | maximumHeight: Globals.scale (320) 43 | 44 | // 45 | // Default window position 46 | // 47 | x: (Screen.width - width) / 4 48 | y: (Screen.height - height) / 4 49 | 50 | // 51 | // Window properties 52 | // 53 | visible: false 54 | color: Globals.Colors.WindowBackground 55 | title: qsTr ("Virtual Joystick Options") 56 | 57 | // 58 | // Set window flags 59 | // 60 | flags: Qt.Window | 61 | Qt.WindowTitleHint | 62 | Qt.WindowSystemMenuHint | 63 | Qt.WindowCloseButtonHint | 64 | Qt.WindowMinimizeButtonHint 65 | 66 | // 67 | // Save settings 68 | // 69 | Settings { 70 | category: "VirtualJoystick" 71 | property alias x: window.x 72 | property alias y: window.y 73 | property alias range: spin.value 74 | property alias enabled: checkbox.checked 75 | } 76 | 77 | // 78 | // Apply saved settings into QJoysticks 79 | // 80 | Component.onCompleted: { 81 | QJoysticks.setVirtualJoystickRange (spin.value / 100) 82 | QJoysticks.setVirtualJoystickEnabled (checkbox.checked) 83 | } 84 | 85 | // 86 | // Window controls 87 | // 88 | ColumnLayout { 89 | anchors.fill: parent 90 | spacing: Globals.spacing 91 | anchors.margins: Globals.spacing 92 | 93 | Label { 94 | text: qsTr ("Keyboard assignments") + ":" 95 | } 96 | 97 | // 98 | // Keyboard assignments tables 99 | // 100 | RowLayout { 101 | Layout.fillWidth: true 102 | Layout.fillHeight: true 103 | spacing: Globals.spacing 104 | 105 | // 106 | // Function table 107 | // 108 | TextEditor { 109 | enabled: false 110 | Layout.fillWidth: true 111 | Layout.fillHeight: true 112 | 113 | Component.onCompleted: { 114 | editor.append (qsTr ("Joystick Buttons")) 115 | editor.append (qsTr ("Primary Thumb (axes 0 and 1)")) 116 | editor.append (qsTr ("Left Trigger (axis 2)")) 117 | editor.append (qsTr ("Right Trigger (axis 3)")) 118 | editor.append (qsTr ("Secondary Thumb (axes 4 and 5)")) 119 | editor.append (qsTr ("POV Hat")) 120 | } 121 | } 122 | 123 | // 124 | // Keyboard keys table 125 | // 126 | TextEditor { 127 | enabled: false 128 | Layout.fillWidth: true 129 | Layout.fillHeight: true 130 | 131 | Component.onCompleted: { 132 | editor.append (qsTr ("0,1,2,3,4,5,6,7,8,9")) 133 | editor.append (qsTr ("W,A,S,D")) 134 | editor.append (qsTr ("Q & E")) 135 | editor.append (qsTr ("U & O")) 136 | editor.append (qsTr ("I,J,K,L")) 137 | editor.append (qsTr ("Arrows")) 138 | } 139 | } 140 | } 141 | 142 | Label { 143 | text: qsTr ("Axis range") + ":" 144 | } 145 | 146 | // 147 | // Axis range control 148 | // 149 | Spinbox { 150 | id: spin 151 | value: 100 152 | minimumValue: 0 153 | maximumValue: 100 154 | Layout.fillWidth: true 155 | onValueChanged: QJoysticks.setVirtualJoystickRange (value / 100) 156 | } 157 | 158 | // 159 | // Close button & enable virtual joystick checkbox 160 | // 161 | RowLayout { 162 | Layout.fillWidth: true 163 | spacing: Globals.spacing 164 | 165 | // 166 | // Enables or disables the virtual joystick 167 | // 168 | Checkbox { 169 | id: checkbox 170 | checked: false 171 | Layout.fillWidth: true 172 | text: qsTr ("Use my keyboard as a joystick") 173 | backgroundColor: Globals.Colors.WidgetBackground 174 | onCheckedChanged: QJoysticks.setVirtualJoystickEnabled (checked) 175 | } 176 | 177 | // 178 | // Close button 179 | // 180 | Button { 181 | onClicked: close() 182 | text: qsTr ("Close") 183 | } 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /qml/Globals.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | // 24 | // All the colors used by the application are defined here 25 | // 26 | var Colors = { 27 | 'Foreground' : "#FFFFFF", 28 | 'Background' : "#4B4B4B", 29 | 'WidgetBorder' : "#282828", 30 | 'WidgetBackground' : "#4B4B4B", 31 | 'WidgetForeground' : "#DEDEDE", 32 | 'WindowBackground' : "#2A2A2A", 33 | 'WidgetBackgroundSelected' : "#3B3B3B", 34 | 'WidgetForegroundSelected' : "#DEDEDE", 35 | 'WidgetBackgroundHighlight' : "#8B8B8B", 36 | 'WidgetForegroundHighlight' : "#DEDEDE", 37 | 'TextAreaBackground' : "#D4D4D4", 38 | 'TextAreaForeground' : "#000000", 39 | 'IndicatorGood' : "#64FF00", 40 | 'IndicatorError' : "#FF1400", 41 | 'IndicatorWarning' : "#FFAA22", 42 | 'HighlightColor' : "#52D000", 43 | 'IconColor' : "#969696", 44 | 'PanelBackground' : "#464646", 45 | 'AlternativeHighlight' : "#01D3FF", 46 | 'EnableButtonSelected' : "#2EDC00", 47 | 'DisableButtonSelected' : "#FF0000", 48 | 'EnableButtonUnselected' : "#002F00", 49 | 'DisableButtonUnselected' : "#880000", 50 | 'CPUProgress' : "#DCAE00", 51 | 'PacketLoss' : "#DC9F00", 52 | 'LowBattery' : "#C81600", 53 | } 54 | 55 | // 56 | // Global-wide animation speeds 57 | // 58 | var slowAnimation = 200 59 | var fastAnimation = 100 60 | 61 | // 62 | // Global-wide layout spacing value 63 | // 64 | var spacing = scale (10) 65 | 66 | // 67 | // Used in all text-based status indicators (such as voltage indicator) 68 | // 69 | var invalidStr = "--.--" 70 | 71 | // 72 | // Specified the UI and monospace fonts used by the application 73 | // 74 | var uiFont = "Ubuntu" 75 | var monoFont = "Ubuntu Mono" 76 | 77 | // 78 | // Returns the adjusted input for the screen size and density 79 | // 80 | function scale (input) { 81 | return Math.round (input * CppUtilities.scaleRatio); 82 | } 83 | 84 | // 85 | // A quick beep, some speakers may have trouble reproducing this 86 | // 87 | function fastBeep() { 88 | beep (220, 50) 89 | } 90 | 91 | // 92 | // A medium-length beep, used when clicking/interacting with UI elements (such as buttons) 93 | // 94 | function normalBeep() { 95 | beep (220, 100) 96 | } 97 | 98 | // 99 | // Only used to get the user's attention (e.g. when we cannot enable the robot) 100 | // 101 | function longBeep() { 102 | beep (220, 200) 103 | } 104 | 105 | // 106 | // Beep with a custom frequency (in HZ) and time (in milliseconds) 107 | // 108 | function beep (frequency, time) { 109 | CppBeeper.beep (frequency, time) 110 | } 111 | 112 | // 113 | // Logs the given message to the console/log dump 114 | // 115 | function log (message) { 116 | console.log ("qml: " + message) 117 | } 118 | 119 | // 120 | // Parses the input text (already formatted in morse) and generates 121 | // the appropriate sounds. This is used in the status widget to generate 122 | // several sound tones. 123 | // 124 | function morse (input, frequency) { 125 | for (var i = 0; i < input.length; ++i) { 126 | var time = 0 127 | var base = 50 128 | var character = input.charAt (i) 129 | 130 | if (character === ".") 131 | time = base 132 | else if (character === "-") 133 | time = base * 3 134 | else if (character === " ") 135 | time = base * 3 136 | else if (character === "/") 137 | time = base * 7 138 | 139 | beep (frequency, time) 140 | beep (0, base) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /qml/MainWindow/About.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | 26 | import "../Widgets" 27 | import "../Globals.js" as Globals 28 | 29 | ColumnLayout { 30 | spacing: Globals.spacing 31 | 32 | // 33 | // The icon of the button 34 | // 35 | Icon { 36 | color: "#fff" 37 | name: icons.fa_cogs 38 | size: Globals.scale (64) 39 | Layout.alignment: Qt.AlignHCenter 40 | } 41 | 42 | // 43 | // A large label displaying the application name and version 44 | // 45 | Label { 46 | size: large 47 | font.bold: true 48 | Layout.alignment: Qt.AlignHCenter 49 | text: CppAppDspName + " " + CppAppVersion 50 | } 51 | 52 | // 53 | // A medium label displaying the LibDS version 54 | // 55 | Label { 56 | size: medium 57 | Layout.alignment: Qt.AlignHCenter 58 | text: "LibDS " 59 | + CppDS.libDSVersion 60 | + " (built on " 61 | + CppDS.buildDate 62 | + " at " 63 | + CppDS.buildTime 64 | + ")" 65 | } 66 | 67 | // 68 | // Website and report bug buttons 69 | // 70 | RowLayout { 71 | Layout.fillWidth: true 72 | spacing: Globals.scale (-1) 73 | Layout.alignment: Qt.AlignHCenter 74 | 75 | Button { 76 | text: qsTr ("Visit Website") 77 | onClicked: Qt.openUrlExternally (CppAppWebsite) 78 | } 79 | 80 | Button { 81 | text: qsTr ("Report Bug") 82 | onClicked: Qt.openUrlExternally (CppAppRepBugs) 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /qml/MainWindow/BatteryChart.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Controls.Styles 1.0 4 | 5 | import "../Globals.js" as Globals 6 | 7 | Item { 8 | property var diodeHeight: height * 0.20 9 | property var backgroundColor: Globals.Colors.IconColor 10 | 11 | // 12 | // Background rectangle 13 | // 14 | Rectangle { 15 | id: cover 16 | height: Globals.scale (4) 17 | color: parent.backgroundColor 18 | 19 | anchors { 20 | top: parent.top 21 | left: parent.left 22 | right: parent.right 23 | topMargin: parent.diodeHeight 24 | } 25 | } 26 | 27 | // 28 | // Left diode 29 | // 30 | Rectangle { 31 | width: parent.width * 0.2 32 | color: parent.backgroundColor 33 | height: parent.diodeHeight - Globals.scale (2) 34 | 35 | anchors { 36 | top: parent.top 37 | left: parent.left 38 | leftMargin: cover.height 39 | } 40 | } 41 | 42 | // 43 | // Right diode 44 | // 45 | Rectangle { 46 | width: parent.width * 0.2 47 | color: parent.backgroundColor 48 | height: parent.diodeHeight - Globals.scale (2) 49 | 50 | anchors { 51 | top: parent.top 52 | right: parent.right 53 | rightMargin: cover.height 54 | } 55 | } 56 | 57 | // 58 | // Graph 59 | // 60 | Rectangle { 61 | color: parent.backgroundColor 62 | 63 | anchors { 64 | fill: parent 65 | leftMargin: Globals.scale (2) 66 | rightMargin: Globals.scale (2) 67 | topMargin: parent.diodeHeight + (cover.height / 2) 68 | } 69 | 70 | VoltageGraph { 71 | border.color: parent.color 72 | Component.onCompleted: setSpeed (24) 73 | color: Globals.Colors.WindowBackground 74 | noCommsColor: Globals.Colors.WindowBackground 75 | 76 | anchors { 77 | fill: parent 78 | topMargin: 0 79 | margins: Globals.scale (2) 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /qml/MainWindow/Charts.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Window 2.0 25 | import QtQuick.Layouts 1.0 26 | import Qt.labs.settings 1.0 27 | 28 | import "../Widgets" 29 | import "../Globals.js" as Globals 30 | 31 | Item { 32 | // 33 | // Changes the horizontal refresh rate of the charts 34 | // 35 | function updateGraphTimes (seconds) { 36 | loss.clear() 37 | voltage.clear() 38 | loss.setSpeed (seconds) 39 | voltage.setSpeed (seconds) 40 | } 41 | 42 | // 43 | // Make sure that the "1 min" button is checked on start-up 44 | // 45 | Component.onCompleted: oneMin.checked = true 46 | 47 | // 48 | // Displays all the widgets in a vertical layout 49 | // 50 | ColumnLayout { 51 | anchors.fill: parent 52 | spacing: Globals.spacing 53 | 54 | // 55 | // Show the refresh speed buttons horizontally 56 | // 57 | RowLayout { 58 | spacing: Globals.scale (-1) 59 | 60 | // 61 | // Packet loss label 62 | // 63 | Label { 64 | text: qsTr ("Packet Loss %") + ":" 65 | } 66 | 67 | // 68 | // Horizontal spacer 69 | // 70 | Item { 71 | Layout.fillWidth: true 72 | } 73 | 74 | // 75 | // "12 s" button 76 | // 77 | Button { 78 | id: twelveSecs 79 | checkable: true 80 | text: qsTr ("12 s") 81 | width: Globals.scale (48) 82 | 83 | onCheckedChanged: { 84 | if (checked) { 85 | oneMin.checked = false 86 | fiveMin.checked = false 87 | twelveSecs.checked = true 88 | 89 | updateGraphTimes (12) 90 | } 91 | 92 | if (!oneMin.checked && !fiveMin.checked && !twelveSecs.checked) 93 | checked = true 94 | } 95 | } 96 | 97 | // 98 | // "1 m" button 99 | // 100 | Button { 101 | id: oneMin 102 | checkable: true 103 | text: qsTr ("1 m") 104 | width: Globals.scale (48) 105 | 106 | onCheckedChanged: { 107 | if (checked) { 108 | oneMin.checked = true 109 | fiveMin.checked = false 110 | twelveSecs.checked = false 111 | 112 | updateGraphTimes (60) 113 | } 114 | 115 | if (!oneMin.checked && !fiveMin.checked && !twelveSecs.checked) 116 | checked = true 117 | } 118 | } 119 | 120 | // 121 | // "5 m" button 122 | // 123 | Button { 124 | id: fiveMin 125 | checkable: true 126 | text: qsTr ("5 m") 127 | width: Globals.scale (48) 128 | 129 | onCheckedChanged: { 130 | if (checked) { 131 | oneMin.checked = false 132 | fiveMin.checked = true 133 | twelveSecs.checked = false 134 | 135 | updateGraphTimes (300) 136 | } 137 | 138 | if (!oneMin.checked && !fiveMin.checked && !twelveSecs.checked) 139 | checked = true 140 | } 141 | } 142 | } 143 | 144 | // 145 | // Packet loss chart 146 | // 147 | Plot { 148 | id: loss 149 | value: 0 150 | minimumValue: 0 151 | maximumValue: 100 152 | Layout.fillWidth: true 153 | Layout.fillHeight: true 154 | barColor: Globals.Colors.PacketLoss 155 | onRefreshed: value = Math.max (1, CppDS.robotPacketLoss) 156 | Component.onCompleted: value = Math.max (1, CppDS.robotPacketLoss) 157 | } 158 | 159 | // 160 | // Robot voltage label 161 | // 162 | Label { 163 | text: qsTr ("Robot Voltage") + ":" 164 | } 165 | 166 | // 167 | // Robot voltage chart 168 | // 169 | VoltageGraph { 170 | id: voltage 171 | Layout.fillWidth: true 172 | Layout.fillHeight: true 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /qml/MainWindow/Diagnostics.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | 26 | import "../Widgets" 27 | import "../Globals.js" as Globals 28 | 29 | RowLayout { 30 | spacing: Globals.spacing 31 | 32 | // 33 | // Network diagnostics items 34 | // 35 | ColumnLayout { 36 | Layout.fillHeight: true 37 | spacing: Globals.spacing 38 | Layout.maximumWidth: Globals.scale (240) 39 | 40 | Label { 41 | font.bold: true 42 | text: qsTr ("Network Diagnostics") + ":" 43 | } 44 | 45 | ColumnLayout { 46 | spacing: Globals.scale (1) 47 | 48 | Checkbox { 49 | enabled: false 50 | text: qsTr ("FMS") 51 | checked: CppDS.connectedToFMS 52 | } 53 | 54 | Checkbox { 55 | enabled: false 56 | text: qsTr ("Robot") 57 | checked: CppDS.connectedToRobot 58 | } 59 | 60 | Checkbox { 61 | enabled: false 62 | text: qsTr ("Bridge/Radio") 63 | checked: CppDS.connectedToRadio 64 | } 65 | } 66 | 67 | Item { 68 | Layout.fillHeight: true 69 | } 70 | 71 | ColumnLayout { 72 | spacing: Globals.scale (-1) 73 | 74 | Button { 75 | Layout.fillWidth: true 76 | text: qsTr ("Reboot RIO") 77 | onClicked: CppDS.rebootRobot() 78 | } 79 | 80 | Button { 81 | Layout.fillWidth: true 82 | text: qsTr ("Restart Code") 83 | onClicked: CppDS.restartRobotCode() 84 | } 85 | } 86 | } 87 | 88 | // 89 | // Horizontal spacer 90 | // 91 | Item { 92 | Layout.fillWidth: true 93 | } 94 | 95 | // 96 | // Robot information items (labels & indicators) 97 | // 98 | ColumnLayout { 99 | Layout.fillHeight: true 100 | spacing: Globals.spacing 101 | 102 | Label { 103 | font.bold: true 104 | text: qsTr ("Robot Information") + ":" 105 | } 106 | 107 | Grid { 108 | columns: 2 109 | Layout.fillHeight: true 110 | rowSpacing: Globals.scale (1) 111 | columnSpacing: Globals.spacing 112 | 113 | Label { 114 | text: qsTr ("CPU Usage") 115 | } 116 | 117 | Label { 118 | text: CppDS.connectedToRobot ? CppDS.cpuUsage + " %" : Globals.invalidStr 119 | } 120 | 121 | Label { 122 | text: qsTr ("RAM Usage") 123 | } 124 | 125 | Label { 126 | text: CppDS.connectedToRobot ? CppDS.ramUsage + " %" : Globals.invalidStr 127 | } 128 | 129 | Label { 130 | text: qsTr ("Disk Usage") 131 | } 132 | 133 | Label { 134 | text: CppDS.connectedToRobot ? CppDS.diskUsage+ " %" : Globals.invalidStr 135 | } 136 | 137 | Label { 138 | text: qsTr ("CAN Utilization") 139 | } 140 | 141 | Label { 142 | text: CppDS.connectedToRobot ? CppDS.canUsage+ " %" : Globals.invalidStr 143 | } 144 | } 145 | } 146 | 147 | // 148 | // Another spacer 149 | // 150 | Item { 151 | Layout.fillWidth: true 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /qml/MainWindow/JoystickItem.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | 26 | import "../Widgets" 27 | import "../Globals.js" as Globals 28 | 29 | Rectangle { 30 | // 31 | // The joystick that this item represents 32 | // 33 | property int jsIndex: 0 34 | 35 | // 36 | // The joystick that is currently selected by the user 37 | // 38 | property int currentJS: 0 39 | 40 | // 41 | // Holds the title/name of the joystick 42 | // 43 | property string jsName 44 | 45 | // 46 | // Emitted when the item is clicked, this is used by the 47 | // Joystick tab to redraw the joystick indicators to the 48 | // selected joystick 49 | // 50 | signal selected 51 | 52 | // 53 | // Emitted when the user enables or disables this joystick. 54 | // This is used by the Joysticks tab to update the blacklist 55 | // status of the selected joystick and redraw the controls 56 | // 57 | signal blacklistedChanged 58 | 59 | // 60 | // The size of the control 61 | // 62 | height: text.implicitHeight * 1.5 63 | anchors { 64 | left: parent.left 65 | right: parent.right 66 | } 67 | 68 | // 69 | // If the control is selected, then its background will be 70 | // a dark grey, if the control is hovered by the mouse, then 71 | // it will be light grey. 72 | // 73 | // If the control is not selected and not hovered, it will have 74 | // no specific background color 75 | // 76 | color: { 77 | if (mouse.containsMouse) 78 | return Qt.lighter (Globals.Colors.WidgetBackground, 1.2) 79 | 80 | else if (currentJS === jsIndex) 81 | return Globals.Colors.WidgetBackground 82 | 83 | return "transparent" 84 | } 85 | 86 | // 87 | // Displays the joystick name 88 | // 89 | Label { 90 | id: text 91 | maximumLineCount: 1 92 | text: " " + jsName 93 | elide: Text.ElideRight 94 | 95 | anchors { 96 | right: icon.left 97 | left: parent.left 98 | verticalCenter: parent.verticalCenter 99 | } 100 | } 101 | 102 | // 103 | // This is the principal mouse area, if it is clicked, then 104 | // the selected joystick will change to this joystick 105 | // 106 | MouseArea { 107 | id: mouse 108 | hoverEnabled: true 109 | 110 | anchors { 111 | top: parent.top 112 | right: icon.left 113 | left: parent.left 114 | bottom: parent.bottom 115 | } 116 | 117 | onClicked: { 118 | selected() 119 | Globals.normalBeep() 120 | } 121 | } 122 | 123 | // 124 | // The blacklist icon/button. When clicked, the joystick 125 | // blacklist status will be toggled 126 | // 127 | Rectangle { 128 | id: icon 129 | width: Globals.scale (32) 130 | 131 | anchors { 132 | top: parent.top 133 | right: parent.right 134 | bottom: parent.bottom 135 | } 136 | 137 | color: { 138 | if (blacklistMouse.containsMouse 139 | || (mouse.containsMouse && currentJS !== jsIndex)) 140 | return Qt.lighter (Globals.Colors.WidgetBackground, 1.2) 141 | 142 | else if (currentJS === jsIndex) 143 | return Globals.Colors.WidgetBackground 144 | 145 | return "transparent" 146 | } 147 | 148 | // 149 | // The power icon. 150 | // When the joystick is disabled, it will be red (off). 151 | // When the joystick is enabled, it will be green (on). 152 | // 153 | Icon { 154 | name: icons.fa_power_off 155 | anchors.centerIn: parent 156 | color: QJoysticks.isBlacklisted (jsIndex) ? 157 | Globals.Colors.IndicatorError : 158 | Globals.Colors.HighlightColor 159 | } 160 | 161 | // 162 | // Toggles the blacklist state of the joystick 163 | // 164 | MouseArea { 165 | id: blacklistMouse 166 | hoverEnabled: true 167 | anchors.fill: parent 168 | onClicked: { 169 | Globals.normalBeep() 170 | blacklistedChanged() 171 | } 172 | } 173 | 174 | // 175 | // Allows the button to gently change its background color 176 | // 177 | Behavior on color { 178 | ColorAnimation { 179 | duration: Globals.slowAnimation 180 | } 181 | } 182 | } 183 | 184 | // 185 | // Allows the control to gently change its background color 186 | // 187 | Behavior on color { 188 | ColorAnimation { 189 | duration: Globals.slowAnimation 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /qml/MainWindow/LeftTab.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | 26 | import "../Widgets" 27 | import "../Globals.js" as Globals 28 | 29 | RowLayout { 30 | id: tab 31 | spacing: Globals.spacing 32 | 33 | // 34 | // Notifiers 35 | // 36 | signal flashStatusIndicators 37 | signal windowModeChanged (var isDocked) 38 | 39 | // 40 | // Contains the tab switcher buttons 41 | // 42 | Column { 43 | Layout.fillHeight: true 44 | spacing: Globals.scale (-1) 45 | 46 | Button { 47 | icon: icons.fa_dashboard 48 | caption.font.bold: true 49 | width: Globals.scale (36) 50 | height: Globals.scale (36) 51 | onClicked: leftTab.showOperator() 52 | textColor: operator.visible ? Globals.Colors.AlternativeHighlight : 53 | Globals.Colors.Foreground 54 | } 55 | 56 | Button { 57 | icon: icons.fa_stethoscope 58 | caption.font.bold: true 59 | width: Globals.scale (36) 60 | height: Globals.scale (36) 61 | onClicked: leftTab.showDiagnostics() 62 | textColor: diagnostics.visible ? Globals.Colors.AlternativeHighlight : 63 | Globals.Colors.Foreground 64 | } 65 | 66 | Button { 67 | icon: icons.fa_wrench 68 | caption.font.bold: true 69 | width: Globals.scale (36) 70 | height: Globals.scale (36) 71 | onClicked: leftTab.showPreferences() 72 | textColor: preferences.visible ? Globals.Colors.AlternativeHighlight : 73 | Globals.Colors.Foreground 74 | } 75 | 76 | Button { 77 | icon: icons.fa_usb 78 | caption.font.bold: true 79 | width: Globals.scale (36) 80 | height: Globals.scale (36) 81 | onClicked: leftTab.showJoysticks() 82 | textColor: joysticks.visible ? Globals.Colors.AlternativeHighlight : 83 | Globals.Colors.Foreground 84 | } 85 | } 86 | 87 | // 88 | // Contains the actual controls 89 | // 90 | Panel { 91 | id: leftTab 92 | Layout.fillWidth: true 93 | Layout.fillHeight: true 94 | 95 | function updateWidth() { 96 | if (joysticks.opacity > 0 && Layout.minimumWidth > 1) 97 | Layout.minimumWidth = joysticks.getMinimumWidth() * 1.2 98 | else 99 | Layout.minimumWidth = 0 100 | } 101 | 102 | function hideWidgets() { 103 | operator.opacity = 0 104 | joysticks.opacity = 0 105 | preferences.opacity = 0 106 | diagnostics.opacity = 0 107 | } 108 | 109 | function showOperator() { 110 | hideWidgets() 111 | updateWidth() 112 | operator.opacity = 1 113 | } 114 | 115 | function showJoysticks() { 116 | hideWidgets() 117 | updateWidth() 118 | joysticks.opacity = 1 119 | } 120 | 121 | function showPreferences() { 122 | hideWidgets() 123 | updateWidth() 124 | preferences.opacity = 1 125 | } 126 | 127 | function showDiagnostics() { 128 | hideWidgets() 129 | updateWidth() 130 | diagnostics.opacity = 1 131 | } 132 | 133 | Connections { 134 | target: CppDS 135 | function onJoystickCountChanged() { 136 | leftTab.updateWidth() 137 | } 138 | } 139 | 140 | Component.onCompleted: showOperator() 141 | 142 | Operator { 143 | opacity: 1 144 | id: operator 145 | visible: opacity > 0 146 | anchors.fill: parent 147 | anchors.margins: Globals.spacing 148 | onWindowModeChanged: tab.windowModeChanged (isDocked) 149 | } 150 | 151 | Diagnostics { 152 | opacity: 0 153 | id: diagnostics 154 | visible: opacity > 0 155 | anchors.fill: parent 156 | anchors.margins: Globals.spacing 157 | } 158 | 159 | Preferences { 160 | opacity: 0 161 | id: preferences 162 | visible: opacity > 0 163 | anchors.fill: parent 164 | anchors.margins: Globals.spacing 165 | } 166 | 167 | Joysticks { 168 | opacity: 0 169 | id: joysticks 170 | visible: opacity > 0 171 | anchors.fill: parent 172 | anchors.margins: Globals.spacing 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /qml/MainWindow/MainWindow.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Window 2.0 25 | import QtQuick.Layouts 1.0 26 | import QtQuick.Controls 1.0 27 | import Qt.labs.settings 1.0 28 | 29 | import "../Globals.js" as Globals 30 | 31 | Window { 32 | id: mw 33 | 34 | // 35 | // Gets the \c x value used in the initial launch 36 | // 37 | function getDefaultX() { 38 | return (Screen.desktopAvailableWidth - getMinimumWidth()) / 2 39 | } 40 | 41 | // 42 | // Gets the \c y value used in the initial launch 43 | // 44 | function getDefaultY() { 45 | return Screen.desktopAvailableHeight - (getMinimumHeight() * 1.2) 46 | } 47 | 48 | // 49 | // If set to true, the window will dock to the bottom of the screen 50 | // 51 | property bool docked: false 52 | 53 | // 54 | // Holds the old values of x and y 55 | // 56 | property int oldX: getDefaultX() 57 | property int oldY: getDefaultY() 58 | 59 | // 60 | // Calculates the minimum width of the window 61 | // 62 | function getMinimumWidth() { 63 | return layout.implicitWidth + (2 * layout.x) 64 | } 65 | 66 | // 67 | // Calculates the minimum height of the window 68 | // 69 | function getMinimumHeight() { 70 | return Math.max(205, layout.implicitHeight + (2 * layout.y)) 71 | } 72 | 73 | // 74 | // The actual docking procedures 75 | // 76 | function updateWindowMode() { 77 | if (!visible) 78 | return 79 | 80 | if (docked) { 81 | if (CppIsUnix) { 82 | x = 0 83 | width = Screen.desktopAvailableWidth 84 | minimumWidth = Screen.desktopAvailableWidth 85 | } 86 | 87 | else { 88 | showMaximized() 89 | } 90 | 91 | y = Screen.desktopAvailableHeight - height 92 | } 93 | 94 | else { 95 | showNormal() 96 | width = getMinimumWidth() 97 | minimumWidth = getMinimumWidth() 98 | 99 | x = oldX 100 | y = oldY 101 | } 102 | } 103 | 104 | // 105 | // Initial position 106 | // 107 | x: getDefaultX() 108 | y: getDefaultY() 109 | 110 | // 111 | // Update custom variables 112 | // 113 | onDockedChanged: updateWindowMode() 114 | onXChanged: oldX = !docked ? x : oldX 115 | onYChanged: oldY = !docked ? y : oldY 116 | 117 | // 118 | // Window geometry hacks 119 | // 120 | height: getMinimumHeight() 121 | minimumWidth: getMinimumWidth() 122 | minimumHeight: getMinimumHeight() 123 | maximumHeight: getMinimumHeight() 124 | onHeightChanged: height = getMinimumHeight() 125 | 126 | // 127 | // To hell with you X11 128 | // 129 | flags: Qt.Dialog | Qt.WindowCloseButtonHint | Qt.WindowTitleHint 130 | 131 | // 132 | // Misc. properties 133 | // 134 | visible: false 135 | color: Globals.Colors.WindowBackground 136 | title: CppAppDspName + " - " + qsTr ("Version") + " " + CppAppVersion 137 | 138 | // 139 | // Save window geometry 140 | // 141 | Settings { 142 | category: "MainWindow" 143 | property alias x: mw.x 144 | property alias y: mw.y 145 | property alias oldX: mw.oldX 146 | property alias oldY: mw.oldY 147 | property alias docked: mw.docked 148 | } 149 | 150 | // 151 | // Animate window position when dock mode changes 152 | // 153 | Behavior on x { NumberAnimation { duration: Globals.fastAnimation }} 154 | Behavior on y { NumberAnimation { duration: Globals.slowAnimation }} 155 | 156 | // 157 | // Display the left tab, status controls and right tab horizontally 158 | // 159 | RowLayout { 160 | id: layout 161 | x: Globals.spacing 162 | y: Globals.spacing 163 | width: Math.max (implicitWidth, mw.width - (2 * x)) 164 | height: Math.max (implicitHeight, mw.height - (2 * y)) 165 | 166 | LeftTab { 167 | Layout.fillWidth: false 168 | Layout.fillHeight: true 169 | Layout.minimumWidth: Globals.scale (440) 170 | onWindowModeChanged: mw.docked = isDocked 171 | onFlashStatusIndicators: status.flashStatusIndicators() 172 | } 173 | 174 | Status { 175 | id: status 176 | Layout.fillWidth: false 177 | Layout.fillHeight: true 178 | } 179 | 180 | RightTab { 181 | Layout.fillWidth: true 182 | Layout.fillHeight: true 183 | Layout.minimumWidth: Globals.scale (420) 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /qml/MainWindow/Messages.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | import QtQuick.Controls 1.4 26 | import Qt.labs.settings 1.0 27 | 28 | import "../Widgets" 29 | import "../Globals.js" as Globals 30 | 31 | ColumnLayout { 32 | spacing: Globals.spacing 33 | 34 | // 35 | // Save protocol value between runs 36 | // 37 | Settings { 38 | property alias protocolVersion: protocol.currentIndex 39 | } 40 | 41 | // 42 | // Write to the console every time the DS receives a message 43 | // 44 | Connections { 45 | target: CppDS 46 | function onNewMessage(message) { 47 | messages.editor.append (message) 48 | } 49 | } 50 | 51 | // 52 | // Logs menu 53 | // 54 | Menu { 55 | id: logsContextMenu 56 | 57 | MenuItem { 58 | text: qsTr ("Open Application Logs") 59 | onTriggered: CppDSLogger.openLogsPath() 60 | } 61 | 62 | MenuItem { 63 | text: qsTr ("Open Current Log") 64 | onTriggered: CppDSLogger.openCurrentLog() 65 | } 66 | } 67 | 68 | // 69 | // Draw the action buttons 70 | // 71 | RowLayout { 72 | Layout.fillWidth: true 73 | spacing: Globals.scale (-1) 74 | 75 | Button { 76 | text: qsTr ("Logs") + "..." 77 | onClicked: logsContextMenu.popup() 78 | } 79 | 80 | Item { 81 | width: Globals.spacing 82 | height: Globals.spacing 83 | } 84 | 85 | Button { 86 | icon: icons.fa_copy 87 | width: Globals.scale (48) 88 | height: Globals.scale (24) 89 | iconSize: Globals.scale (12) 90 | 91 | onClicked: { 92 | messages.copy() 93 | messages.editor.append ("** " 94 | + qsTr ("Information") 95 | + ": " 96 | + qsTr ("Console output copied to clipboard") 97 | + "") 98 | } 99 | } 100 | 101 | Button { 102 | icon: icons.fa_trash 103 | width: Globals.scale (48) 104 | height: Globals.scale (24) 105 | iconSize: Globals.scale (12) 106 | onClicked: messages.text = "" 107 | } 108 | 109 | Item { 110 | Layout.fillWidth: true 111 | } 112 | 113 | Combobox { 114 | width: 92 115 | id: protocol 116 | alignRight: true 117 | model: CppDS.protocols 118 | Layout.fillHeight: true 119 | onCurrentIndexChanged: CppDS.setProtocol (currentIndex) 120 | } 121 | } 122 | 123 | // 124 | // Draw the console 125 | // 126 | TextEditor { 127 | id: messages 128 | editor.readOnly: true 129 | Layout.fillWidth: true 130 | Layout.fillHeight: true 131 | editor.textFormat: Text.RichText 132 | editor.font.family: Globals.monoFont 133 | editor.font.pixelSize: Globals.scale (13) 134 | foregroundColor: Globals.Colors.WidgetForeground 135 | backgroundColor: Globals.Colors.WindowBackground 136 | editor.wrapMode: TextEdit.WrapAtWordBoundaryOrAnywhere 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /qml/MainWindow/Operator.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | import Qt.labs.settings 1.0 26 | 27 | import DriverStation 1.0 28 | 29 | import "../Widgets" 30 | import "../Globals.js" as Globals 31 | 32 | RowLayout { 33 | spacing: Globals.spacing 34 | signal windowModeChanged (var isDocked) 35 | 36 | // 37 | // Control modes & enable/disable buttons 38 | // 39 | ColumnLayout { 40 | Layout.fillHeight: true 41 | Layout.minimumWidth: Globals.scale (160) 42 | Layout.maximumWidth: Globals.scale (160) 43 | 44 | // 45 | // Control mode selector 46 | // 47 | ColumnLayout { 48 | Layout.fillWidth: true 49 | Layout.fillHeight: true 50 | spacing: Globals.scale (-1) 51 | 52 | Button { 53 | Layout.fillWidth: true 54 | text: " " + qsTr ("Teleoperated") 55 | caption.horizontalAlignment: Text.AlignLeft 56 | checked: CppDS.controlMode === LibDS.ControlTeleoperated 57 | onClicked: CppDS.controlMode = LibDS.ControlTeleoperated 58 | } 59 | 60 | Button { 61 | Layout.fillWidth: true 62 | text: " " + qsTr ("Autonomous") 63 | caption.horizontalAlignment: Text.AlignLeft 64 | checked: CppDS.controlMode === LibDS.ControlAutonomous 65 | onClicked: CppDS.controlMode = LibDS.ControlAutonomous 66 | } 67 | 68 | Button { 69 | enabled: false 70 | Layout.fillWidth: true 71 | text: " " + qsTr ("Practice") 72 | caption.horizontalAlignment: Text.AlignLeft 73 | } 74 | 75 | Button { 76 | Layout.fillWidth: true 77 | text: " " + qsTr ("Test") 78 | caption.horizontalAlignment: Text.AlignLeft 79 | checked: CppDS.controlMode === LibDS.ControlTest 80 | onClicked: CppDS.controlMode = LibDS.ControlTest 81 | } 82 | } 83 | 84 | // 85 | // Vertical spacer 86 | // 87 | Item { 88 | Layout.fillHeight: true 89 | } 90 | 91 | // 92 | // Enable/Disable buttons 93 | // 94 | RowLayout { 95 | Layout.fillWidth: true 96 | spacing: Globals.scale (-1) 97 | 98 | Button { 99 | checked: CppDS.enabled 100 | text: qsTr ("Enable") 101 | Layout.fillWidth: true 102 | Layout.fillHeight: true 103 | caption.font.bold: true 104 | onClicked: CppDS.enabled = CppDS.canBeEnabled 105 | caption.font.pixelSize: Globals.scale (16) 106 | caption.color: checked ? Globals.Colors.EnableButtonSelected : 107 | Globals.Colors.EnableButtonUnselected 108 | } 109 | 110 | Button { 111 | checked: !CppDS.enabled 112 | text: qsTr ("Disable") 113 | Layout.fillWidth: true 114 | Layout.fillHeight: true 115 | caption.font.bold: true 116 | onClicked: CppDS.enabled = false 117 | caption.font.pixelSize: Globals.scale (16) 118 | caption.color: checked ? Globals.Colors.DisableButtonSelected : 119 | Globals.Colors.DisableButtonUnselected 120 | } 121 | } 122 | } 123 | 124 | // 125 | // Small sepatator between core operator and information indicators 126 | // 127 | Rectangle { 128 | Layout.fillHeight: true 129 | width: Globals.scale (1) 130 | color: Globals.Colors.WidgetBorder 131 | } 132 | 133 | // 134 | // Extra information indicators 135 | // 136 | GridLayout { 137 | columns: 2 138 | Layout.fillWidth: true 139 | Layout.fillHeight: true 140 | rowSpacing: Globals.scale (4) 141 | columnSpacing: Globals.spacing 142 | 143 | // 144 | // Elapsed time label 145 | // 146 | Label { 147 | Layout.fillWidth: true 148 | text: qsTr ("Elapsed Time") 149 | horizontalAlignment: Text.AlignRight 150 | } 151 | 152 | // 153 | // Elapsed time indicator 154 | // 155 | Label { 156 | font.bold: true 157 | Layout.fillWidth: true 158 | size: Globals.scale (18) 159 | text: CppDS.elapsedTime 160 | Layout.rightMargin: Globals.spacing 161 | horizontalAlignment: Text.AlignRight 162 | } 163 | 164 | // 165 | // Spacer (for both columns) 166 | // 167 | Item { 168 | Layout.fillHeight: true 169 | } 170 | Item { 171 | Layout.fillHeight: true 172 | } 173 | 174 | // 175 | // PC Battery label 176 | // 177 | RowLayout { 178 | Layout.fillWidth: true 179 | 180 | Icon { 181 | name: icons.fa_plug 182 | Layout.fillWidth: true 183 | size: Globals.scale (10) 184 | horizontalAlignment: Text.AlignRight 185 | opacity: CppUtilities.connectedToAC ? 1 : 0 186 | Behavior on opacity { NumberAnimation{} } 187 | } 188 | 189 | Label { 190 | text: qsTr ("PC Battery") 191 | horizontalAlignment: Text.AlignRight 192 | } 193 | } 194 | 195 | // 196 | // Battery progressbar 197 | // 198 | Progressbar { 199 | text: "" 200 | Layout.fillWidth: true 201 | value: CppUtilities.batteryLevel 202 | barColor: { 203 | if (value > 60) 204 | return Globals.Colors.HighlightColor 205 | 206 | else if (value > 25) 207 | return Globals.Colors.CPUProgress 208 | 209 | return Globals.Colors.LowBattery 210 | } 211 | } 212 | 213 | // 214 | // PC CPU label 215 | // 216 | Label { 217 | Layout.fillWidth: true 218 | text: qsTr ("PC CPU") + " %" 219 | horizontalAlignment: Text.AlignRight 220 | } 221 | 222 | // 223 | // CPU prgoressbar 224 | // 225 | Progressbar { 226 | text: "" 227 | Layout.fillWidth: true 228 | value: CppUtilities.cpuUsage 229 | barColor: Globals.Colors.CPUProgress 230 | } 231 | 232 | // 233 | // Spacer (for both columns) 234 | // 235 | Item { 236 | Layout.fillHeight: true 237 | } 238 | Item { 239 | Layout.fillHeight: true 240 | } 241 | 242 | // 243 | // Window type label 244 | // 245 | Label { 246 | Layout.fillWidth: true 247 | text: qsTr ("Window") 248 | horizontalAlignment: Text.AlignRight 249 | } 250 | 251 | // 252 | // Dock/Normal buttons 253 | // 254 | RowLayout { 255 | Layout.fillWidth: true 256 | spacing: Globals.scale (-1) 257 | 258 | Button { 259 | id: normal 260 | checked: !mw.docked 261 | Layout.fillWidth: true 262 | icon: icons.fa_mail_reply 263 | width: Globals.scale (48) 264 | iconSize: Globals.scale (12) 265 | onClicked: { 266 | normal.checked = true 267 | docked.checked = false 268 | windowModeChanged (false) 269 | } 270 | } 271 | 272 | Button { 273 | id: docked 274 | checked: mw.docked 275 | icon: icons.fa_expand 276 | Layout.fillWidth: true 277 | width: Globals.scale (48) 278 | iconSize: Globals.scale (12) 279 | onClicked: { 280 | docked.checked = true 281 | normal.checked = false 282 | windowModeChanged (true) 283 | } 284 | } 285 | } 286 | 287 | // 288 | // Spacer (for both columns) 289 | // 290 | Item { 291 | Layout.fillHeight: true 292 | } 293 | Item { 294 | Layout.fillHeight: true 295 | } 296 | 297 | // 298 | // Team station label 299 | // 300 | Label { 301 | Layout.fillWidth: true 302 | text: qsTr ("Team Station") 303 | horizontalAlignment: Text.AlignRight 304 | } 305 | 306 | // 307 | // Team station selector 308 | // 309 | Combobox { 310 | id: stations 311 | model: CppDS.stations 312 | Layout.fillWidth: true 313 | onCurrentIndexChanged: CppDS.station = currentIndex 314 | } 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /qml/MainWindow/Preferences.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | import Qt.labs.settings 1.0 26 | 27 | import "../Widgets" 28 | import "../Globals.js" as Globals 29 | 30 | RowLayout { 31 | spacing: Globals.spacing 32 | 33 | Settings { 34 | property alias delay: delay.value 35 | property alias teleop: teleop.value 36 | property alias team: teamNumber.value 37 | property alias endGame: endGame.value 38 | property alias gameDataStr: gameData.text 39 | property alias countdown: countdown.value 40 | property alias autonomous: autonomous.value 41 | property alias dashbaord: dashboard.currentIndex 42 | } 43 | 44 | // 45 | // Open the dashboard on application launch 46 | // 47 | Component.onCompleted: { 48 | CppDashboard.openDashboard (dashboard.currentIndex) 49 | } 50 | 51 | // 52 | // Team number, dashboard & game data 53 | // 54 | ColumnLayout { 55 | spacing: Globals.scale (5) 56 | Layout.maximumWidth: parent.width * 0.5 57 | 58 | // 59 | // Team number label 60 | // 61 | Label { 62 | font.bold: true 63 | text: qsTr ("Team Number") + ":" 64 | } 65 | 66 | // 67 | // Team number control 68 | // 69 | Spinbox { 70 | id: teamNumber 71 | minimumValue: 0 72 | maximumValue: 9999 73 | Layout.fillWidth: true 74 | value: CppDS.teamNumber 75 | onValueChanged: CppDS.teamNumber = value 76 | } 77 | 78 | // 79 | // Dashboard label 80 | // 81 | Label { 82 | font.bold: true 83 | text: qsTr ("Dashboard Type") + ":" 84 | } 85 | 86 | // 87 | // Dashboard selector 88 | // 89 | Combobox { 90 | id: dashboard 91 | Layout.fillWidth: true 92 | model: CppDashboard.dashboardList() 93 | onCurrentIndexChanged: CppDashboard.openDashboard (dashboard.currentIndex) 94 | } 95 | 96 | // 97 | // Game data label 98 | // 99 | Label { 100 | font.bold: true 101 | text: qsTr ("Game Data") + ":" 102 | } 103 | 104 | // 105 | // Game data field 106 | // 107 | LineEdit { 108 | id: gameData 109 | text: CppDS.gameData 110 | Layout.fillWidth: true 111 | onTextChanged: CppDS.setGameData(text) 112 | } 113 | 114 | // 115 | // Vertical spacer 116 | // 117 | Item { 118 | Layout.fillHeight: true 119 | } 120 | } 121 | 122 | // 123 | // Horizontal spacer 124 | // 125 | Item { 126 | Layout.fillWidth: true 127 | } 128 | 129 | // 130 | // Practice timings 131 | // 132 | ColumnLayout { 133 | Layout.fillWidth: true 134 | spacing: Globals.spacing 135 | Layout.maximumWidth: Globals.scale (150) 136 | 137 | // 138 | // Title label 139 | // 140 | Label { 141 | font.bold: true 142 | text: qsTr ("Practice Timings") + ":" 143 | } 144 | 145 | // 146 | // Practice timings controls & labels 147 | // 148 | GridLayout { 149 | columns: 2 150 | rowSpacing: Globals.scale (1) 151 | columnSpacing: Globals.spacing 152 | 153 | Label { 154 | Layout.fillWidth: true 155 | text: qsTr ("Countdown") 156 | } 157 | 158 | // 159 | // Countdown control 160 | // 161 | Spinbox { 162 | value: 5 163 | id: countdown 164 | minimumValue: 0 165 | maximumValue: 150 166 | Layout.minimumWidth: Globals.scale (36) 167 | } 168 | 169 | Label { 170 | Layout.fillWidth: true 171 | text: qsTr ("Autonomous") 172 | } 173 | 174 | // 175 | // Autonomous period 176 | // 177 | Spinbox { 178 | value: 15 179 | id: autonomous 180 | minimumValue: 0 181 | maximumValue: 150 182 | Layout.minimumWidth: Globals.scale (36) 183 | } 184 | 185 | Label { 186 | Layout.fillWidth: true 187 | text: qsTr ("Delay") 188 | } 189 | 190 | // 191 | // Delay timings 192 | // 193 | Spinbox { 194 | value: 1 195 | id: delay 196 | minimumValue: 0 197 | maximumValue: 150 198 | Layout.minimumWidth: Globals.scale (36) 199 | } 200 | 201 | Label { 202 | Layout.fillWidth: true 203 | text: qsTr ("Teleoperated") 204 | } 205 | 206 | // 207 | // Teleop period 208 | // 209 | Spinbox { 210 | value: 100 211 | id: teleop 212 | minimumValue: 0 213 | maximumValue: 150 214 | Layout.minimumWidth: Globals.scale (36) 215 | } 216 | 217 | Label { 218 | Layout.fillWidth: true 219 | text: qsTr ("End Game") 220 | } 221 | 222 | // 223 | // End game warning 224 | // 225 | Spinbox { 226 | value: 20 227 | id: endGame 228 | minimumValue: 0 229 | maximumValue: 150 230 | Layout.minimumWidth: Globals.scale (36) 231 | } 232 | } 233 | 234 | // 235 | // Put a vertical spacer to compact the controls 236 | // 237 | Item { 238 | Layout.fillHeight: true 239 | } 240 | } 241 | 242 | // 243 | // Yet another horizontal spacer to keep things tidy 244 | // 245 | Item { 246 | Layout.fillWidth: true 247 | } 248 | } 249 | -------------------------------------------------------------------------------- /qml/MainWindow/RightTab.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | 26 | import "../Widgets" 27 | import "../Globals.js" as Globals 28 | 29 | RowLayout { 30 | spacing: Globals.spacing 31 | 32 | // 33 | // Contains the actual tab controls/buttons 34 | // 35 | Panel { 36 | id: rightTab 37 | Layout.fillWidth: true 38 | Layout.fillHeight: true 39 | 40 | function hideWidgets() { 41 | about.opacity = 0 42 | charts.opacity = 0 43 | messages.opacity = 0 44 | } 45 | 46 | function showMessages() { 47 | hideWidgets() 48 | messages.opacity = 1 49 | } 50 | 51 | function showCharts() { 52 | hideWidgets() 53 | charts.opacity = 1 54 | } 55 | 56 | function showAbout() { 57 | hideWidgets() 58 | about.opacity = 1 59 | } 60 | 61 | Component.onCompleted: showMessages() 62 | 63 | Messages { 64 | opacity: 1 65 | id: messages 66 | visible: opacity > 0 67 | anchors.fill: parent 68 | anchors.margins: Globals.spacing 69 | } 70 | 71 | Charts { 72 | opacity: 0 73 | id: charts 74 | visible: opacity > 0 75 | anchors.fill: parent 76 | anchors.margins: Globals.spacing 77 | } 78 | 79 | About { 80 | id: about 81 | opacity: 0 82 | visible: opacity > 0 83 | anchors.fill: parent 84 | anchors.margins: Globals.spacing 85 | } 86 | } 87 | 88 | // 89 | // Contains the tab switcher buttons 90 | // 91 | ColumnLayout { 92 | Layout.fillHeight: true 93 | spacing: Globals.scale (-1) 94 | Layout.minimumWidth: Globals.scale (36) 95 | Layout.maximumWidth: Globals.scale (36) 96 | 97 | Button { 98 | icon: icons.fa_envelope 99 | caption.font.bold: true 100 | width: Globals.scale (36) 101 | height: Globals.scale (36) 102 | onClicked: rightTab.showMessages() 103 | textColor: messages.visible ? Globals.Colors.AlternativeHighlight : 104 | Globals.Colors.Foreground 105 | } 106 | 107 | Button { 108 | caption.font.bold: true 109 | icon: icons.fa_area_chart 110 | width: Globals.scale (36) 111 | height: Globals.scale (36) 112 | onClicked: rightTab.showCharts() 113 | textColor: charts.visible ? Globals.Colors.AlternativeHighlight : 114 | Globals.Colors.Foreground 115 | } 116 | 117 | Button { 118 | icon: icons.fa_info 119 | caption.font.bold: true 120 | width: Globals.scale (36) 121 | height: Globals.scale (36) 122 | onClicked: rightTab.showAbout() 123 | textColor: about.visible ? Globals.Colors.AlternativeHighlight : 124 | Globals.Colors.Foreground 125 | } 126 | 127 | Item { 128 | Layout.fillWidth: true 129 | Layout.fillHeight: true 130 | Layout.minimumHeight: Globals.spacing 131 | } 132 | 133 | Button { 134 | icon: icons.fa_cog 135 | caption.font.bold: true 136 | width: Globals.scale (36) 137 | height: Globals.scale (36) 138 | onClicked: app.showSettingsWindow() 139 | } 140 | 141 | Button { 142 | icon: icons.fa_keyboard_o 143 | caption.font.bold: true 144 | width: Globals.scale (36) 145 | height: Globals.scale (36) 146 | onClicked: app.showVirtualJoystickWindow() 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /qml/MainWindow/Status.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Layouts 1.0 25 | 26 | import "../Widgets" 27 | import "../Globals.js" as Globals 28 | 29 | ColumnLayout { 30 | id: status 31 | 32 | // 33 | // Defines the separation between the different components of the widget 34 | // 35 | property double spacerHeight: Globals.spacing / 2 36 | 37 | // 38 | // Layout/geometry options 39 | // 40 | spacing: 0 41 | Layout.margins: 0 42 | Layout.leftMargin: Globals.spacing 43 | Layout.rightMargin: Globals.spacing 44 | Layout.minimumWidth: Globals.scale (144) 45 | Layout.preferredWidth: Globals.scale (144) 46 | 47 | // 48 | // Team number indicator 49 | // 50 | RowLayout { 51 | spacing: Globals.spacing 52 | 53 | Label { 54 | size: large 55 | font.bold: true 56 | Layout.fillWidth: true 57 | text: qsTr ("Team") + " #" 58 | verticalAlignment: Qt.AlignCenter 59 | } 60 | 61 | Label { 62 | size: large 63 | font.bold: true 64 | text: CppDS.teamNumber 65 | verticalAlignment: Qt.AlignVCenter 66 | horizontalAlignment: Qt.AlignHCenter 67 | } 68 | } 69 | 70 | // 71 | // Spacer 72 | // 73 | Item { 74 | Layout.fillHeight: true 75 | Layout.minimumHeight: spacerHeight 76 | } 77 | 78 | // 79 | // Voltage indicator & plot 80 | // 81 | RowLayout { 82 | spacing: Globals.spacing 83 | Layout.minimumHeight: Globals.scale (42) 84 | 85 | // 86 | // Robot battery graph 87 | // 88 | BatteryChart { 89 | width: Globals.scale (53) 90 | height: Globals.scale (32) 91 | } 92 | 93 | // 94 | // Horizontal spacer 95 | // 96 | Item { 97 | Layout.fillWidth: true 98 | } 99 | 100 | // 101 | // Voltage text (finally) 102 | // 103 | Label { 104 | size: large 105 | font.bold: true 106 | verticalAlignment: Text.AlignVCenter 107 | horizontalAlignment: Text.AlignHCenter 108 | text: CppDS.connectedToRobot ? CppDS.voltageString : Globals.invalidStr 109 | } 110 | } 111 | 112 | // 113 | // Another spacer 114 | // 115 | Item { 116 | Layout.fillHeight: true 117 | Layout.minimumHeight: spacerHeight 118 | } 119 | 120 | // 121 | // Status LEDs 122 | // 123 | Column { 124 | Layout.fillWidth: true 125 | spacing: Globals.scale (3) 126 | 127 | LED { 128 | id: commsLed 129 | leftToRight: true 130 | anchors.right: parent.right 131 | text: qsTr ("Communications") 132 | checked: CppDS.connectedToRobot 133 | } 134 | 135 | LED { 136 | id: codeLed 137 | leftToRight: true 138 | text: qsTr ("Robot Code") 139 | anchors.right: parent.right 140 | checked: CppDS.robotCode 141 | } 142 | 143 | LED { 144 | leftToRight: true 145 | text: qsTr ("Joysticks") 146 | anchors.right: parent.right 147 | checked: QJoysticks.nonBlacklistedCount 148 | } 149 | } 150 | 151 | // 152 | // (Yet) Another spacer 153 | // 154 | Item { 155 | Layout.fillHeight: true 156 | Layout.minimumHeight: spacerHeight 157 | } 158 | 159 | // 160 | // Robot status 161 | // 162 | Label { 163 | size: large 164 | font.bold: true 165 | text: CppDS.status 166 | Layout.fillWidth: true 167 | Layout.fillHeight: true 168 | verticalAlignment: Qt.AlignVCenter 169 | horizontalAlignment: Qt.AlignHCenter 170 | Layout.minimumHeight: Globals.scale (42) 171 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /qml/MainWindow/VoltageGraph.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | 25 | import "../Widgets" 26 | import "../Globals.js" as Globals 27 | 28 | Plot { 29 | // 30 | // The graph will be black when there are no communications with the robot 31 | // 32 | property string noCommsColor: "#000" 33 | 34 | // 35 | // Gets current voltage and changes plot settings accordingly 36 | // 37 | function update() { 38 | value = CppDS.voltage 39 | 40 | if (!CppDS.connectedToRobot) { 41 | barColor = noCommsColor 42 | value = maximumValue * 0.95 43 | } 44 | 45 | else if (getLevel() > 0.80) 46 | barColor = Globals.Colors.IndicatorGood 47 | else if (getLevel() > 0.70) 48 | barColor = Globals.Colors.IndicatorWarning 49 | else 50 | barColor = Globals.Colors.IndicatorError 51 | } 52 | 53 | // 54 | // Refresh the graph values from time to time 55 | // 56 | onRefreshed: update() 57 | barColor: Globals.Colors.TextAreaBackground 58 | 59 | // 60 | // Start graphing from origin, not from the middle or some other place 61 | // 62 | minimumValue: 0 63 | Component.onCompleted: update() 64 | maximumValue: CppDS.maximumBatteryVoltage 65 | } 66 | -------------------------------------------------------------------------------- /qml/Widgets/Button.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Rectangle { 27 | // 28 | // Holds the text displayed by the button 29 | // 30 | property string text 31 | 32 | // 33 | // Holds the icon displayed by the button 34 | // 35 | property string icon 36 | 37 | // 38 | // Misc. properties 39 | // 40 | property bool enabled: true 41 | property bool checked: false 42 | property bool checkable: false 43 | 44 | // 45 | // Gives direct access to the text control 46 | // 47 | property alias caption: label 48 | 49 | // 50 | // Holds the list of the icon names 51 | // 52 | property alias icons: awesome.icons 53 | 54 | // 55 | // These properties control the size of the text and the icon 56 | // 57 | property alias textSize: label.size 58 | property alias iconSize: awesome.size 59 | 60 | // 61 | // These properties control the colors of the text and the icon 62 | // 63 | property color textColor: Globals.Colors.Foreground 64 | property color baseColor: Globals.Colors.WidgetBackground 65 | 66 | // 67 | // Emitted when the button is clicked, you can control what to 68 | // do when the button is clicked with the onClicked: property 69 | // 70 | signal clicked 71 | 72 | // 73 | // Change the background color when the button is pressed or checked 74 | // 75 | color: { 76 | if (checked || mouse.pressed) 77 | return Qt.darker (baseColor, 1.7) 78 | 79 | return baseColor 80 | } 81 | 82 | // 83 | // Border size and color 84 | // 85 | border.width: Globals.scale (1) 86 | border.color: Globals.Colors.WidgetBorder 87 | 88 | // 89 | // You may need to re-define these values when dealing with layouts 90 | // 91 | height: Globals.scale (24) 92 | width: Math.max (label.implicitWidth + Globals.scale (12), Globals.scale (120)) 93 | 94 | // 95 | // Perform a simple animation when the background color is changed 96 | // 97 | Behavior on baseColor { 98 | ColorAnimation { 99 | duration: Globals.slowAnimation 100 | } 101 | } 102 | 103 | // 104 | // Perform a simple animation when the foreground color is changed 105 | // 106 | Behavior on textColor { 107 | ColorAnimation { 108 | duration: Globals.slowAnimation 109 | } 110 | } 111 | 112 | // 113 | // The actual label/caption/text of the button 114 | // 115 | Label { 116 | id: label 117 | color: textColor 118 | text: parent.text 119 | anchors.fill: parent 120 | opacity: parent.enabled ? 1 : 0.5 121 | anchors.margins: Globals.scale (1) 122 | verticalAlignment: Text.AlignVCenter 123 | horizontalAlignment: Text.AlignHCenter 124 | } 125 | 126 | // 127 | // The icon of the button 128 | // 129 | Icon { 130 | id: awesome 131 | size: large 132 | color: textColor 133 | name: parent.icon 134 | anchors.fill: parent 135 | anchors.margins: Globals.scale (1) 136 | } 137 | 138 | // 139 | // Allows the Button to know if the mouse is over the control 140 | // or pressing the control... 141 | // 142 | MouseArea { 143 | id: mouse 144 | hoverEnabled: true 145 | anchors.fill: parent 146 | enabled: parent.enabled 147 | 148 | onClicked: { 149 | Globals.normalBeep() 150 | parent.clicked() 151 | 152 | if (parent.checkable) 153 | parent.checked = !parent.checked 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /qml/Widgets/Checkbox.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Item { 27 | // 28 | // Holds the text/caption of the control 29 | // 30 | property string text 31 | 32 | // 33 | // Misc. properties of the checkbox 34 | // 35 | property bool enabled: true 36 | property bool checked: false 37 | property bool leftToRight: false 38 | property alias backgroundColor: checkbox.color 39 | 40 | // 41 | // Gives direct access to the caption/label control 42 | // 43 | property alias caption: label 44 | 45 | // 46 | // Resize the control to fit the text length and height 47 | // 48 | height: Math.max (checkbox.height, label.height) 49 | width: checkbox.implicitWidth + label.implicitWidth + (2 * Globals.spacing) 50 | 51 | // 52 | // Holds the actual checkbox, because the rectangle is like a box...Ba Dum Tss! 53 | // 54 | Rectangle { 55 | id: checkbox 56 | border.width: Globals.scale (1) 57 | implicitWidth: Globals.scale (16) 58 | implicitHeight: Globals.scale (16) 59 | color: Globals.Colors.WindowBackground 60 | border.color: Globals.Colors.WidgetBorder 61 | 62 | anchors { 63 | verticalCenter: parent.verticalCenter 64 | left: leftToRight ? label.right : parent.left 65 | leftMargin: leftToRight ? Globals.spacing : 0 66 | } 67 | 68 | Icon { 69 | name: icons.fa_check 70 | anchors.centerIn: parent 71 | color: checked ? Globals.Colors.AlternativeHighlight : parent.color 72 | } 73 | } 74 | 75 | /// 76 | /// The text of the control 77 | /// 78 | Label { 79 | id: label 80 | text: parent.text 81 | 82 | anchors { 83 | margins: Globals.spacing * 0.75 84 | verticalCenter: parent.verticalCenter 85 | left: leftToRight ? parent.left : checkbox.right 86 | } 87 | } 88 | 89 | // 90 | // Inverts the check-state of the widget when clicked 91 | // 92 | MouseArea { 93 | anchors.fill: parent 94 | enabled: parent.enabled 95 | onClicked: { 96 | Globals.normalBeep() 97 | parent.checked = !parent.checked 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /qml/Widgets/Combobox.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Controls.Styles 1.0 4 | 5 | import "../Globals.js" as Globals 6 | 7 | ComboBox { 8 | id: combo 9 | 10 | // 11 | // Moves the current label to the right if set to true 12 | // 13 | property bool alignRight: false 14 | 15 | // 16 | // We can do a combo-box from scratch, but I'm lazy 17 | // 18 | style: ComboBoxStyle { 19 | background: Rectangle { 20 | border.width: Globals.scale (1) 21 | color: Globals.Colors.WidgetBackground 22 | border.color: Globals.Colors.WidgetBorder 23 | 24 | Icon { 25 | size: Globals.scale (12) 26 | name: icons.fa_chevron_down 27 | 28 | anchors { 29 | right: parent.right 30 | margins: Globals.spacing 31 | verticalCenter: parent.verticalCenter 32 | } 33 | } 34 | } 35 | 36 | label: Text { 37 | id: _label 38 | smooth: true 39 | text: control.currentText 40 | font.family: Globals.uiFont 41 | color: Globals.Colors.Foreground 42 | font.pixelSize: Globals.scale (12) 43 | verticalAlignment: Text.AlignVCenter 44 | horizontalAlignment: alignRight ? Text.AlignRight : Text.AlignLeft 45 | 46 | anchors { 47 | verticalCenter: parent.verticalCenter 48 | left: alignRight ? undefined: parent.left 49 | right: alignRight ? parent.right : undefined 50 | margins: alignRight ? Globals.spacing + Globals.scale(12) : 0 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /qml/Widgets/Icon.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | 25 | Label { 26 | id: label 27 | 28 | property var icons: Icons {} 29 | property alias name: label.text 30 | 31 | font.family: "FontAwesome" 32 | verticalAlignment: Text.AlignVCenter 33 | horizontalAlignment: Text.AlignHCenter 34 | } 35 | -------------------------------------------------------------------------------- /qml/Widgets/LED.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Item { 27 | // 28 | // Holds the text/caption/label of the control 29 | // 30 | property string text 31 | 32 | // 33 | // Misc. properties... 34 | // 35 | property bool enabled: false 36 | property bool checked: false 37 | property bool showBorder: false 38 | property bool leftToRight: false 39 | property string textColor: Globals.Colors.Foreground 40 | 41 | // 42 | // LED on/off colors 43 | // 44 | property string poweredColor: Globals.Colors.IndicatorGood 45 | property string unpoweredColor: Globals.Colors.IndicatorError 46 | 47 | // 48 | // Gives direct access to the caption control 49 | // 50 | property alias caption: label 51 | 52 | // 53 | // Gives access to the LED size 54 | // 55 | property int ledWidth: Globals.scale (28) 56 | property int ledHeight: Globals.scale (12) 57 | 58 | // 59 | // Control sizes 60 | // 61 | implicitHeight: Math.max (ledHeight, label.height) 62 | implicitWidth: ledWidth + label.implicitWidth + (2 * Globals.spacing) 63 | 64 | /// 65 | /// This is the actual LED... 66 | /// 67 | Rectangle { 68 | id: led 69 | implicitWidth: ledWidth 70 | implicitHeight: ledHeight 71 | border.color: Globals.Colors.WidgetBorder 72 | color: checked ? poweredColor : unpoweredColor 73 | border.width: Globals.scale (showBorder ? 1 : 0) 74 | 75 | Behavior on color { 76 | ColorAnimation { 77 | duration: Globals.slowAnimation 78 | } 79 | } 80 | 81 | anchors { 82 | margins: Globals.spacing 83 | verticalCenter: parent.verticalCenter 84 | left: leftToRight ? label.right : parent.left 85 | } 86 | } 87 | 88 | // 89 | // The text of the LED/checkbox 90 | // 91 | Label { 92 | id: label 93 | text: parent.text 94 | color: parent.textColor 95 | 96 | anchors { 97 | margins: Globals.spacing 98 | verticalCenter: parent.verticalCenter 99 | left: leftToRight ? parent.left : led.right 100 | } 101 | } 102 | 103 | // 104 | // Inverts the checked state of the widget when clicked 105 | // 106 | MouseArea { 107 | anchors.fill: parent 108 | enabled: parent.enabled 109 | onClicked: { 110 | Globals.normalBeep() 111 | parent.checked = !parent.checked 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /qml/Widgets/Label.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Text { 27 | property int size: normal 28 | readonly property int small: Globals.scale (10) 29 | readonly property int large: Globals.scale (16) 30 | readonly property int normal: Globals.scale (12) 31 | readonly property int medium: Globals.scale (14) 32 | 33 | smooth: true 34 | font.pixelSize: size 35 | font.family: Globals.uiFont 36 | color: Globals.Colors.Foreground 37 | } 38 | -------------------------------------------------------------------------------- /qml/Widgets/LineEdit.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Item { 27 | // 28 | // Gives direct access to the line edit 29 | // 30 | property alias editor: input 31 | 32 | // 33 | // Holds the text of the line edit 34 | // 35 | property alias text: input.text 36 | 37 | // 38 | // Holds the placeholder text of the control, which is 39 | // shown when the text is empty 40 | // 41 | property string placeholder 42 | 43 | // 44 | // The colors of the base rectangle and the text 45 | // 46 | property string foregroundColor: Globals.Colors.TextAreaForeground 47 | property string backgroundColor: Globals.Colors.TextAreaBackground 48 | 49 | // 50 | // Control size 51 | // 52 | height: Globals.scale (24) 53 | implicitWidth: Globals.scale (128) 54 | implicitHeight: Globals.scale (24) 55 | 56 | // 57 | // Base rectangle of the line edit 58 | // 59 | Rectangle { 60 | anchors.fill: parent 61 | color: backgroundColor 62 | border.width: Globals.scale (1) 63 | opacity: parent.enabled ? 1 :0.5 64 | border.color: Globals.Colors.WidgetBorder 65 | 66 | Behavior on opacity { 67 | NumberAnimation { 68 | duration: Globals.slowAnimation 69 | } 70 | } 71 | } 72 | 73 | // 74 | // Placeholder text edit 75 | // 76 | TextInput { 77 | opacity: 0.5 78 | readOnly: true 79 | text: placeholder 80 | anchors.fill: parent 81 | color: foregroundColor 82 | visible: input.text === "" 83 | font.family: Globals.uiFont 84 | font.pixelSize: Globals.scale (12) 85 | anchors.margins: Globals.scale (5) 86 | } 87 | 88 | // 89 | // The actual line edit 90 | // 91 | TextInput { 92 | id: input 93 | anchors.fill: parent 94 | color: foregroundColor 95 | readOnly: !parent.enabled 96 | font.family: Globals.uiFont 97 | font.pixelSize: Globals.scale (12) 98 | anchors.margins: Globals.scale (5) 99 | selectionColor: Globals.Colors.HighlightColor 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /qml/Widgets/ListViewer.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Rectangle { 27 | property alias list: view 28 | property alias model: view.model 29 | property alias delegate: view.delegate 30 | 31 | border.width: Globals.scale (1) 32 | color: Globals.Colors.WindowBackground 33 | border.color: Globals.Colors.WidgetBorder 34 | 35 | // 36 | // Allows the scrollbar to show/hide automatically 37 | // 38 | MouseArea { 39 | id: mouse 40 | anchors.fill: parent 41 | } 42 | 43 | // 44 | // Contains the list in a scrollable area 45 | // 46 | Flickable { 47 | id: flick 48 | clip: true 49 | interactive: true 50 | contentWidth: parent.width 51 | contentHeight: view.contentHeight 52 | flickableDirection: Flickable.VerticalFlick 53 | 54 | anchors { 55 | fill: parent 56 | margins: Globals.scale (5) 57 | rightMargin: scroll.width + Globals.scale (8) 58 | } 59 | 60 | // 61 | // The actual list 62 | // 63 | ListView { 64 | id: view 65 | anchors.fill: parent 66 | anchors.rightMargin: flick.anchors.rightMargin 67 | } 68 | } 69 | 70 | // 71 | // The scrollbar, it is smart and it will show and hide automatically, 72 | // it will also go back in time and kill your younger self 73 | // 74 | Scrollbar { 75 | id: scroll 76 | mouseArea: mouse 77 | scrollArea: flick 78 | height: parent.height 79 | width: opacity > 0 ? Globals.scale (8) : 0 80 | 81 | Behavior on width {NumberAnimation{}} 82 | 83 | anchors { 84 | top: parent.top 85 | right: parent.right 86 | bottom: parent.bottom 87 | margins: Globals.scale (6) 88 | } 89 | } 90 | 91 | // 92 | // Animate when changing size 93 | // 94 | Behavior on width { 95 | NumberAnimation { 96 | duration: Globals.slowAnimation 97 | } 98 | } Behavior on height { 99 | NumberAnimation { 100 | duration: Globals.slowAnimation 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /qml/Widgets/Panel.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | // 27 | // This item is the ultimate expression of my lazyness 28 | // 29 | Rectangle { 30 | radius: Globals.scale (5) 31 | border.width: Globals.scale (1) 32 | color: Globals.Colors.PanelBackground 33 | border.color: Globals.Colors.WidgetBorder 34 | 35 | Behavior on color { 36 | ColorAnimation { 37 | duration: Globals.slowAnimation 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /qml/Widgets/Plot.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Rectangle { 27 | id: plot 28 | 29 | // 30 | // Defines the refresh interval (in milliseconds) of the graph 31 | // 32 | property int refreshInterval: 50 33 | 34 | // 35 | // Defines the width of each bar generated during each refresh 36 | // 37 | property double rectWidth: Globals.scale (2) 38 | 39 | // 40 | // Gives direct access to the canvas object 41 | // 42 | property alias canvasObject: canvas 43 | 44 | // 45 | // Defines the color to use to draw the lines 46 | // 47 | property string barColor: Globals.Colors.HighlightColor 48 | 49 | // 50 | // Display options 51 | // 52 | property double value: 0 53 | property double minimumValue: 0 54 | property double maximumValue: 100 55 | 56 | // 57 | // Emitted when the timer expires and a canvas repaint is done 58 | // 59 | signal refreshed 60 | 61 | // 62 | // Calculates the ratio between the current value and the maxinum value 63 | // 64 | function getLevel() { 65 | return Math.max (value / maximumValue, minimumValue / maximumValue) 66 | } 67 | 68 | // 69 | // Changes the time in which the graph resets itself 70 | // 71 | function setSpeed (seconds) { 72 | /* Get current time required to reset the graph */ 73 | var pixelsPerSec = rectWidth * (1000 / refreshInterval) 74 | var resetTime = width / pixelsPerSec 75 | 76 | /* Do a rule of three to obtain new refresh interval */ 77 | var newInterval = (seconds * refreshInterval) / resetTime 78 | 79 | /* Apply obtained interval */ 80 | refreshInterval = newInterval 81 | timer.interval = refreshInterval 82 | } 83 | 84 | // 85 | // Forces the canvas to clear its plot 86 | // 87 | function clear() { 88 | timer.currentPos = canvas.width / rectWidth 89 | } 90 | 91 | // 92 | // Rectangle color & border 93 | // 94 | border.width: Globals.scale (1) 95 | color: Globals.Colors.WindowBackground 96 | border.color: Globals.Colors.WidgetBorder 97 | 98 | // 99 | // Refreshes the graph on real-time 100 | // 101 | Timer { 102 | id: timer 103 | interval: refreshInterval 104 | Component.onCompleted: start() 105 | 106 | property int currentPos: 0 107 | 108 | onTriggered: { 109 | if (plot.visible) 110 | ++currentPos 111 | 112 | canvas.requestPaint() 113 | parent.refreshed() 114 | } 115 | } 116 | 117 | // 118 | // The actual canvas used to draw the graph 119 | // 120 | Canvas { 121 | id: canvas 122 | anchors.fill: parent 123 | renderStrategy: Canvas.Threaded 124 | anchors.margins: parent.border.width 125 | 126 | onPaint: { 127 | /* Lazy is good sometimes */ 128 | if (plot.visible) { 129 | /* Get drawing context */ 130 | var context = getContext('2d') 131 | 132 | /* Set the bar color */ 133 | context.fillStyle = barColor 134 | 135 | /* Calculate X and Y coordinates */ 136 | var yOffset = (1 - getLevel()) * height 137 | var xOffset = timer.currentPos * rectWidth 138 | 139 | /* Reset the graph if it is greater than the width */ 140 | if (xOffset > canvas.width) { 141 | xOffset = 0 142 | timer.currentPos = 0 143 | context.clearRect (0, 0, canvas.width, canvas.height) 144 | } 145 | 146 | /* Draw a single bar */ 147 | context.fillRect (xOffset, yOffset, rectWidth, height) 148 | } 149 | 150 | /* Restart the timer */ 151 | timer.restart() 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /qml/Widgets/Progressbar.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Rectangle { 27 | // 28 | // Holds the relative value of the progressbar 29 | // 30 | property int value: 0 31 | 32 | // 33 | // The values hold the 'range' of the progressbar 34 | // 35 | property int minimumValue: 0 36 | property int maximumValue: 100 37 | 38 | // 39 | // Default caption will display current relative value of progressbar 40 | // 41 | property string text: value + "%" 42 | 43 | // 44 | // Define the colors of the progressbar 45 | // 46 | property string barColor: Globals.Colors.HighlightColor 47 | property string textColor: Globals.Colors.TextAreaForeground 48 | 49 | // 50 | // Set height of widget and width of border 51 | // 52 | height: Globals.scale (14) 53 | border.width: Globals.scale (1) 54 | 55 | // 56 | // Set background color and border color 57 | // 58 | color: Globals.Colors.WindowBackground 59 | border.color: Globals.Colors.WidgetBorder 60 | 61 | // 62 | // Animate the progrssbar when it changes its value 63 | // 64 | Behavior on value { 65 | NumberAnimation { 66 | duration: Globals.slowAnimation 67 | } 68 | } 69 | 70 | // 71 | // The 'foreground' part of the progress bar that displays its value 72 | // 73 | Rectangle { 74 | y: 0 75 | x: 0 76 | color: barColor 77 | radius: parent.radius 78 | height: parent.height 79 | border.width: parent.border.width 80 | border.color: parent.border.color 81 | width: parent.width * ((minimumValue + value) / maximumValue) 82 | } 83 | 84 | // 85 | // The text/caption of the progressbar 86 | // 87 | Label { 88 | text: parent.text 89 | anchors.centerIn: parent 90 | opacity: font.pixelSize > Globals.scale (8) 91 | font.pixelSize: Math.min (Globals.scale (12), parent.height * (2/3)) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /qml/Widgets/Scrollbar.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Item { 27 | id: container 28 | opacity: 0 29 | 30 | property variant mouseArea 31 | property variant scrollArea 32 | property variant orientation: Qt.Vertical 33 | 34 | // 35 | // Shows the control and enables the 'watchdog' timer 36 | // 37 | function showControl() { 38 | if (scroll.height < container.height) 39 | opacity = 1 40 | 41 | else 42 | opacity = 0 43 | 44 | timer.start() 45 | } 46 | 47 | // 48 | // Used to calculate the position that the scrollbar 49 | // should have to stay in reference with the height/width of 50 | // the parent flickable 51 | // 52 | function calculatePosition() 53 | { 54 | var ny = 0; 55 | 56 | if (container.orientation == Qt.Vertical) 57 | ny = scrollArea.visibleArea.yPosition * container.height; 58 | 59 | else 60 | ny = scrollArea.visibleArea.xPosition * container.width; 61 | 62 | if (ny > Globals.scale (2)) 63 | return ny; 64 | 65 | else return Globals.scale (2); 66 | } 67 | 68 | // 69 | // Used to calculate the size that the scrollbar should have 70 | // 71 | function calculateSize() 72 | { 73 | var nh, ny; 74 | 75 | if (container.orientation == Qt.Vertical) 76 | nh = scrollArea.visibleArea.heightRatio * container.height; 77 | else 78 | nh = scrollArea.visibleArea.widthRatio * container.width; 79 | 80 | if (container.orientation == Qt.Vertical) 81 | ny = scrollArea.visibleArea.yPosition * container.height; 82 | else 83 | ny = scrollArea.visibleArea.xPosition * container.width; 84 | 85 | if (ny > Globals.scale (3)) { 86 | var t; 87 | 88 | if (container.orientation == Qt.Vertical) 89 | t = Math.ceil (container.height - ny); 90 | 91 | else 92 | t = Math.ceil (container.width - ny); 93 | 94 | if (nh > t) 95 | return t 96 | 97 | else 98 | return nh 99 | } 100 | 101 | else return nh + ny; 102 | } 103 | 104 | // 105 | // Enables a smooth fade when the control is hovered 106 | // or changes its size 107 | // 108 | Behavior on opacity { 109 | NumberAnimation { 110 | duration: Globals.slowAnimation 111 | } 112 | } 113 | 114 | // 115 | // Hides the control after certain amount of time without 116 | // any activity 117 | // 118 | Timer { 119 | id: timer 120 | interval: 800 121 | onTriggered: { 122 | if (opacity > 0 && !mouseArea.containsMouse) 123 | opacity = 0 124 | } 125 | } 126 | 127 | // 128 | // The actual iOS-like scrollbar 129 | // 130 | Rectangle { 131 | id: scroll 132 | opacity: 0.5 133 | radius: Globals.scale (5) 134 | onHeightChanged: showControl() 135 | color: Globals.Colors.TextAreaBackground 136 | width: container.orientation == Qt.Vertical ? container.width : calculateSize() 137 | height: container.orientation == Qt.Vertical ? calculateSize() : container.height 138 | x: container.orientation == Qt.Vertical ? Globals.scale (2) : calculatePosition() 139 | y: container.orientation == Qt.Vertical ? calculatePosition() : Globals.scale (2) 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /qml/Widgets/Spinbox.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import QtQuick.Controls 1.0 25 | import QtQuick.Controls.Styles 1.4 26 | 27 | import "../Globals.js" as Globals 28 | 29 | SpinBox { 30 | width: Globals.scale (64) 31 | height: Globals.scale (24) 32 | 33 | // 34 | // Assign a custom style to the control 35 | // 36 | style: SpinBoxStyle { 37 | background: Rectangle { 38 | implicitWidth: parent.width 39 | implicitHeight: parent.height 40 | border.width: Globals.scale (1) 41 | opacity: control.enabled ? 1 : 0.5 42 | color: Globals.Colors.TextAreaBackground 43 | border.color: Globals.Colors.WidgetBorder 44 | 45 | Behavior on opacity { 46 | NumberAnimation { 47 | duration: Globals.slowAnimation 48 | } 49 | } 50 | } 51 | 52 | textColor: Globals.Colors.TextAreaForeground 53 | selectionColor: Globals.Colors.HighlightColor 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /qml/Widgets/TextEditor.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | import "../Globals.js" as Globals 25 | 26 | Item { 27 | // 28 | // Gives us direct access to the text editor 29 | // 30 | property alias editor: edit 31 | 32 | // 33 | // Gives us access to the text of the text editor 34 | // 35 | property alias text: edit.text 36 | 37 | // 38 | // If set to true, the text will automatically scroll down when 39 | // its changed (like a terminal emulator does) 40 | // 41 | property bool autoscroll: true 42 | 43 | // 44 | // Define the colors of the control 45 | // 46 | property string foregroundColor: height > Globals.scale (140) ? Globals.Colors.WidgetForeground : 47 | Globals.Colors.TextAreaForeground 48 | property string backgroundColor: height > Globals.scale (140) ? Globals.Colors.WidgetBackground : 49 | Globals.Colors.TextAreaBackground 50 | 51 | // 52 | // Copies the text to the system clipboard 53 | // 54 | function copy() { 55 | /* Get current text */ 56 | var html = editor.getFormattedText (0, editor.length) 57 | 58 | /* Remove HTML codes */ 59 | html = html.replace(//gi, ''); 60 | html = html.replace(//gi, ''); 61 | html = html.replace(/<\/div>/ig, '\n'); 62 | html = html.replace(/<\/li>/ig, '\n'); 63 | html = html.replace(/
  • /ig, ' * '); 64 | html = html.replace(/<\/ul>/ig, '\n'); 65 | html = html.replace(/<\/p>/ig, '\n'); 66 | html = html.replace(//gi, "\n"); 67 | html = html.replace(/<[^>]+>/ig, ''); 68 | 69 | /* Copy result to system clipboard */ 70 | CppUtilities.copy (html) 71 | } 72 | 73 | // 74 | // Define a standard size for the widget 75 | // 76 | width: Globals.scale (250) 77 | height: Globals.scale (120) 78 | 79 | // 80 | // The widget drawing (e.g. background, border, etc) 81 | // 82 | Rectangle { 83 | anchors.fill: parent 84 | color: backgroundColor 85 | border.width: Globals.scale (1) 86 | opacity: parent.enabled ? 1 :0.5 87 | border.color: Globals.Colors.WidgetBorder 88 | 89 | Behavior on opacity { 90 | NumberAnimation { 91 | duration: Globals.slowAnimation 92 | } 93 | } 94 | } 95 | 96 | // 97 | // A flickable item allows us to implement scrolling for 98 | // the text editor. 99 | // 100 | Flickable { 101 | id: flick 102 | clip: true 103 | interactive: true 104 | contentWidth: parent.width 105 | contentHeight: edit.contentHeight 106 | flickableDirection: Flickable.VerticalFlick 107 | 108 | anchors { 109 | fill: parent 110 | margins: Globals.spacing 111 | } 112 | 113 | // 114 | // This code was written by a Russian guy, original post: 115 | // Link: http://www.cyberforum.ru/qt/thread578187.html 116 | // 117 | function ensureVisible (r) { 118 | if (autoscroll) { 119 | if (contentX >= r.x) 120 | contentX = r.x; 121 | else if (contentX + width <= r.x + r.width) 122 | contentX = r.x + r.width - width; 123 | if (contentY >= r.y) 124 | contentY = r.y; 125 | else if (contentY + height <= r.y + r.height) 126 | contentY = r.y + r.height - height; 127 | } 128 | } 129 | 130 | // 131 | // The actual text editor 132 | // 133 | TextEdit { 134 | id: edit 135 | selectByMouse: true 136 | color: foregroundColor 137 | readOnly: !parent.enabled 138 | font.family: Globals.uiFont 139 | font.pixelSize: Globals.scale (12) 140 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 141 | selectionColor: Globals.Colors.HighlightColor 142 | onCursorRectangleChanged: flick.ensureVisible (cursorRectangle) 143 | 144 | anchors { 145 | left: parent.left 146 | right: parent.right 147 | margins: Globals.scale (5) 148 | } 149 | } 150 | } 151 | 152 | // 153 | // The scrollbar 154 | // 155 | Scrollbar { 156 | id: scroll 157 | mouseArea: mouse 158 | scrollArea: flick 159 | height: parent.height 160 | width: Globals.scale (8) 161 | 162 | anchors { 163 | top: parent.top 164 | right: parent.right 165 | bottom: parent.bottom 166 | margins: Globals.scale (6) 167 | } 168 | } 169 | 170 | // 171 | // Used to show the scrollbar when mouse is over control 172 | // 173 | MouseArea { 174 | id: mouse 175 | hoverEnabled: true 176 | anchors.fill: parent 177 | onContainsMouseChanged: scroll.showControl() 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /qml/main.qml: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | import QtQuick 2.0 24 | 25 | import "Dialogs" 26 | import "MainWindow" 27 | import "Globals.js" as Globals 28 | 29 | Item { 30 | id: app 31 | visible: false 32 | 33 | // 34 | // Display the virtual joystick window (from anywhere in the app) 35 | // 36 | function showVirtualJoystickWindow() { 37 | virtualJoystickWindow.show() 38 | } 39 | 40 | // 41 | // Display the settings window (from anywhere in the app) 42 | // 43 | function showSettingsWindow() { 44 | settingsWindow.show() 45 | } 46 | 47 | // 48 | // Load the fonts used by the application 49 | // 50 | FontLoader { source: Qt.resolvedUrl ("qrc:/fonts/UbuntuMono.ttf") } 51 | FontLoader { source: Qt.resolvedUrl ("qrc:/fonts/FontAwesome.ttf") } 52 | FontLoader { source: Qt.resolvedUrl ("qrc:/fonts/Ubuntu-Bold.ttf") } 53 | FontLoader { source: Qt.resolvedUrl ("qrc:/fonts/Ubuntu-Regular.ttf") } 54 | 55 | // 56 | // Initialize the DS engine when the application starts 57 | // 58 | Component.onCompleted: { 59 | if (!mainwindow.visible) { 60 | mainwindow.visible = true 61 | mainwindow.updateWindowMode() 62 | } 63 | 64 | Globals.beep (440, 100) 65 | Globals.beep (220, 100) 66 | } 67 | 68 | 69 | // 70 | // The Main window 71 | // 72 | MainWindow { 73 | id: mainwindow 74 | onVisibleChanged: { 75 | if (!visible) 76 | Qt.quit() 77 | } 78 | } 79 | 80 | // 81 | // The settings window 82 | // 83 | SettingsWindow { 84 | id: settingsWindow 85 | } 86 | 87 | // 88 | // The virtual joystick window 89 | // 90 | VirtualJoystickWindow { 91 | id: virtualJoystickWindow 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /qml/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Globals.js 4 | main.qml 5 | Dialogs/SettingsWindow.qml 6 | Dialogs/VirtualJoystickWindow.qml 7 | MainWindow/About.qml 8 | MainWindow/Charts.qml 9 | MainWindow/Diagnostics.qml 10 | MainWindow/JoystickItem.qml 11 | MainWindow/Joysticks.qml 12 | MainWindow/LeftTab.qml 13 | MainWindow/MainWindow.qml 14 | MainWindow/Messages.qml 15 | MainWindow/Operator.qml 16 | MainWindow/Preferences.qml 17 | MainWindow/RightTab.qml 18 | MainWindow/Status.qml 19 | MainWindow/VoltageGraph.qml 20 | Widgets/Button.qml 21 | Widgets/Checkbox.qml 22 | Widgets/Combobox.qml 23 | Widgets/Icon.qml 24 | Widgets/Icons.qml 25 | Widgets/Label.qml 26 | Widgets/LED.qml 27 | Widgets/LineEdit.qml 28 | Widgets/ListViewer.qml 29 | Widgets/Panel.qml 30 | Widgets/Plot.qml 31 | Widgets/Progressbar.qml 32 | Widgets/Scrollbar.qml 33 | Widgets/Spinbox.qml 34 | Widgets/TextEditor.qml 35 | MainWindow/BatteryChart.qml 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/beeper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #include "beeper.h" 24 | 25 | /* Used for generating the sine wave and various operations */ 26 | #include 27 | #include 28 | 29 | /* Used for generating the sounds */ 30 | #include 31 | #include 32 | 33 | /** 34 | * \brief Holds information regarding a beep/wave sound 35 | */ 36 | struct BeepObject 37 | { 38 | qreal frequency; 39 | int samplesLeft; 40 | }; 41 | 42 | /* Think of this as the 'volume' of the sound wave */ 43 | const int AMPLITUDE = 16000; 44 | 45 | /* Corresponds to the freq. used in phones, we do not need more than that */ 46 | const int SAMPLING_FREQ = 8000; 47 | 48 | /* Holds the beep objects to be processed */ 49 | QQueue BEEPS; 50 | 51 | /** 52 | * Calls the beeper when and generates the audio 53 | */ 54 | void AUDIO_CALLBACK(void *beeper, quint8 *stream, int length) 55 | { 56 | Beeper *object = static_cast(beeper); 57 | object->generateSamples((qint16 *)stream, length / 2); 58 | } 59 | 60 | /** 61 | * Configures the audio spec 62 | */ 63 | Beeper::Beeper() 64 | { 65 | m_angle = 0; 66 | m_enabled = false; 67 | 68 | SDL_LockAudio(); 69 | 70 | /* Generate the audio configuration */ 71 | SDL_AudioSpec desiredSpec; 72 | desiredSpec.channels = 1; 73 | desiredSpec.samples = 1024; 74 | desiredSpec.userdata = this; 75 | desiredSpec.freq = SAMPLING_FREQ; 76 | desiredSpec.format = AUDIO_S16SYS; 77 | desiredSpec.callback = AUDIO_CALLBACK; 78 | 79 | /* Open the audio device */ 80 | SDL_AudioSpec obtainedSpec; 81 | SDL_OpenAudio(&desiredSpec, &obtainedSpec); 82 | 83 | /* Forces to initialize the data for the callback function */ 84 | SDL_PauseAudio(0); 85 | } 86 | 87 | /** 88 | * Stop using the SDL audio when destroying this class 89 | */ 90 | Beeper::~Beeper() 91 | { 92 | SDL_CloseAudio(); 93 | SDL_UnlockAudio(); 94 | } 95 | 96 | void Beeper::generateSamples(qint16 *stream, int length) 97 | { 98 | int i = 0; 99 | while (i < length) 100 | { 101 | 102 | /* Beeps object is empty, ensure that stream has neutral values */ 103 | if (BEEPS.empty()) 104 | { 105 | for (; i < length; ++i) 106 | stream[i] = 0; 107 | 108 | return; 109 | } 110 | 111 | /* Get the beep object */ 112 | BeepObject &beep = BEEPS.front(); 113 | int samplesToDo = qMin(i + beep.samplesLeft, length); 114 | beep.samplesLeft -= samplesToDo - i; 115 | 116 | /* Generate the sound */ 117 | for (; i < samplesToDo; ++i) 118 | { 119 | m_angle += beep.frequency; 120 | stream[i] = AMPLITUDE * qSin((m_angle * 2 * M_PI) / SAMPLING_FREQ); 121 | } 122 | 123 | /* Go to next beep object */ 124 | if (beep.samplesLeft == 0) 125 | BEEPS.pop_front(); 126 | } 127 | } 128 | 129 | /** 130 | * Enables or disables the sound output 131 | */ 132 | void Beeper::setEnabled(bool enabled) 133 | { 134 | m_enabled = enabled; 135 | } 136 | 137 | /** 138 | * Generates a beep of the given \a frequency & \a duration (in milliseconds). 139 | * \note The request will be ignored if the beeper is disabled 140 | */ 141 | void Beeper::beep(qreal frequency, int duration) 142 | { 143 | if (m_enabled) 144 | { 145 | BeepObject beep_object; 146 | beep_object.frequency = frequency; 147 | beep_object.samplesLeft = duration * SAMPLING_FREQ / 1000; 148 | 149 | BEEPS.append(beep_object); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/beeper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * This code is based on the answer provided at: 3 | * http://stackoverflow.com/questions/10110905/simple-wave-generator-with-sdl-in-c 4 | */ 5 | 6 | #ifndef _QDS_BEEPER_H 7 | #define _QDS_BEEPER_H 8 | 9 | #include 10 | 11 | /** 12 | * \brief Uses SDL to generate telephone-like sound tones on the fly 13 | */ 14 | class Beeper : public QObject 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit Beeper(); 20 | ~Beeper(); 21 | 22 | void generateSamples(qint16 *stream, int length); 23 | 24 | public slots: 25 | void setEnabled(bool enabled); 26 | void beep(qreal frequency, int duration); 27 | 28 | private: 29 | bool m_enabled; 30 | double m_angle; 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/dashboards.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "dashboards.h" 28 | 29 | // *INDENT-OFF* 30 | #if defined Q_OS_WIN 31 | # include 32 | # define IS_64_BIT true 33 | # if _WIN32 34 | # undef IS_64_BIT 35 | # define IS_64_BIT GetProcAddress(GetModuleHandle(TEXT("kernel32")), "IsWow64Process") 36 | # endif 37 | # define PROGRAM_FILES IS_64_BIT ? "C:/Program Files (x86)" : "C:/Program Files" 38 | #endif 39 | // *INDENT-ON* 40 | 41 | /* Dashboard open commands */ 42 | const QString LVD_COMMAND = "\"%1/FRC Dashboard/Dashboard.exe\""; 43 | const QString SFX_COMMAND = "java -jar \"%1/wpilib/tools/sfx.jar\""; 44 | const QString SBD_COMMAND = "java -jar \"%1/wpilib/tools/SmartDashboard.jar\""; 45 | const QString SHB_COMMAND = "java -jar \"%1/wpilib/tools/shuffleboard.jar\""; 46 | 47 | /** 48 | * Configures the application to close the dashboard when it quits 49 | */ 50 | Dashboards::Dashboards() 51 | { 52 | connect(qApp, SIGNAL(aboutToQuit()), &m_process, SLOT(kill())); 53 | } 54 | 55 | /** 56 | * Returns a list with the available dashboards. 57 | * \note This list may differ from operating system to operating system 58 | */ 59 | QStringList Dashboards::dashboardList() 60 | { 61 | QStringList list; 62 | list.append(tr("None")); 63 | list.append(tr("SFX Dashboard")); 64 | list.append(tr("SmartDashboard")); 65 | list.append(tr("Shuffleboard")); 66 | 67 | #if defined Q_OS_WIN 68 | list.append(tr("LabVIEW Dashboard")); 69 | #endif 70 | 71 | return list; 72 | } 73 | 74 | /** 75 | * Opens the given \a dashboard process 76 | */ 77 | void Dashboards::openDashboard(int dashboard) 78 | { 79 | m_process.kill(); 80 | QString command = ""; 81 | 82 | switch (dashboard) 83 | { 84 | case kSFXDashboard: 85 | command = SFX_COMMAND.arg(QDir::homePath()); 86 | break; 87 | case kSmartDashboard: 88 | command = SBD_COMMAND.arg(QDir::homePath()); 89 | break; 90 | case kShuffleboard: 91 | command = SHB_COMMAND.arg(QDir::homePath()); 92 | break; 93 | #if defined Q_OS_WIN 94 | case kLabVIEWDashboard: 95 | command = LVD_COMMAND.arg(PROGRAM_FILES); 96 | break; 97 | #endif 98 | } 99 | 100 | if (!command.isEmpty()) 101 | { 102 | m_process.start(command, QStringList(), QIODevice::ReadOnly); 103 | qDebug() << "Dashboard command set to:" << command.toStdString().c_str(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/dashboards.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #ifndef _QDS_DASHBOARDS_H 24 | #define _QDS_DASHBOARDS_H 25 | 26 | #include 27 | 28 | /** 29 | * \brief Opens and closes the available FRC Dashboards 30 | */ 31 | class Dashboards : public QObject 32 | { 33 | Q_OBJECT 34 | Q_ENUMS(DashboardTypes) 35 | 36 | public: 37 | explicit Dashboards(); 38 | 39 | enum DashboardTypes 40 | { 41 | kNone = 0, 42 | kSFXDashboard = 1, 43 | kSmartDashboard = 2, 44 | kShuffleboard = 3, 45 | kLabVIEWDashboard = 4, 46 | }; 47 | 48 | Q_INVOKABLE QStringList dashboardList(); 49 | 50 | public slots: 51 | void openDashboard(int dashboard); 52 | 53 | private: 54 | QProcess m_process; 55 | }; 56 | 57 | #endif 58 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | //------------------------------------------------------------------------------ 24 | // Qt includes 25 | //------------------------------------------------------------------------------ 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #ifdef Q_OS_WIN 35 | # include 36 | #endif 37 | 38 | //------------------------------------------------------------------------------ 39 | // Library includes 40 | //------------------------------------------------------------------------------ 41 | 42 | #include 43 | #include 44 | 45 | #include 46 | #include 47 | #include 48 | 49 | //------------------------------------------------------------------------------ 50 | // Application includes 51 | //------------------------------------------------------------------------------ 52 | 53 | #include "beeper.h" 54 | #include "versions.h" 55 | #include "shortcuts.h" 56 | #include "utilities.h" 57 | #include "dashboards.h" 58 | 59 | //------------------------------------------------------------------------------ 60 | // CLI messages 61 | //------------------------------------------------------------------------------ 62 | 63 | const QString WEBS = "We instructed your web browser to navigate to: \n" 64 | " %1 \n" 65 | "If nothing happens, please navigate to that URL. "; 66 | 67 | const QString HELP = "Usage: qdriverstation [ options ... ] \n" 68 | " \n" 69 | "Options include: \n" 70 | " -b, --bug Report a bug \n" 71 | " -h, --help Show this message \n" 72 | " -r, --reset Reset/clear the settings \n" 73 | " -c, --contact Contact the lead developer \n" 74 | " -v, --version Display the application version \n" 75 | " -w, --website Open a web site of this project \n"; 76 | 77 | //------------------------------------------------------------------------------ 78 | // Download joystick drivers if needed 79 | //------------------------------------------------------------------------------ 80 | 81 | static void WelcomeMessages() 82 | { 83 | QSettings settings(APP_COMPANY, APP_DSPNAME); 84 | if (settings.value("FirstLaunch", true).toBool()) 85 | { 86 | // Download Xbox drivers on Mac 87 | #ifdef Q_OS_MAC 88 | QMessageBox xboxDrivers; 89 | xboxDrivers.setIcon(QMessageBox::Question); 90 | xboxDrivers.setStandardButtons(QMessageBox::Yes | QMessageBox::No); 91 | xboxDrivers.setDefaultButton(QMessageBox::Yes); 92 | xboxDrivers.setWindowTitle(QObject::tr("Download Joystick Drivers")); 93 | xboxDrivers.setText(QObject::tr("Do you want to install a driver " 94 | "for Xbox joysticks?")); 95 | 96 | xboxDrivers.setInformativeText(QObject::tr("Clicking \"Yes\" will open a web " 97 | "browser to download the drivers")); 98 | 99 | if (xboxDrivers.exec() == QMessageBox::Yes) 100 | QDesktopServices::openUrl(QUrl("https://github.com/360Controller/" 101 | "360Controller/releases/latest")); 102 | #endif 103 | 104 | settings.setValue("FirstLaunch", false); 105 | } 106 | } 107 | 108 | //------------------------------------------------------------------------------ 109 | // Utility functions 110 | //------------------------------------------------------------------------------ 111 | 112 | static void showHelp() 113 | { 114 | qDebug() << HELP.toStdString().c_str(); 115 | } 116 | 117 | static void resetSettings() 118 | { 119 | QSettings(APP_COMPANY, APP_DSPNAME).clear(); 120 | qDebug() << "QDriverStation settings cleared!"; 121 | } 122 | 123 | static void contact() 124 | { 125 | QString url = "mailto:" + CONTACT_URL; 126 | QDesktopServices::openUrl(QUrl(url)); 127 | qDebug() << WEBS.arg(url).toStdString().c_str(); 128 | } 129 | 130 | static void reportBug() 131 | { 132 | QDesktopServices::openUrl(QUrl(APP_REPBUGS)); 133 | qDebug() << WEBS.arg(APP_REPBUGS).toStdString().c_str(); 134 | } 135 | 136 | static void openWebsite() 137 | { 138 | QDesktopServices::openUrl(QUrl(APP_WEBSITE)); 139 | qDebug() << WEBS.arg(APP_WEBSITE).toStdString().c_str(); 140 | } 141 | 142 | static void showVersion() 143 | { 144 | QString appver = APP_DSPNAME + " version " + APP_VERSION; 145 | QString author = "Written by Alex Spataru <" + CONTACT_URL + ">"; 146 | 147 | qDebug() << appver.toStdString().c_str(); 148 | qDebug() << author.toStdString().c_str(); 149 | } 150 | 151 | //------------------------------------------------------------------------------ 152 | // Application init 153 | //------------------------------------------------------------------------------ 154 | 155 | int main(int argc, char *argv[]) 156 | { 157 | // Fix console output on Windows (https://stackoverflow.com/a/41701133) 158 | // This code will only execute if the application is started from the comamnd prompt 159 | #ifdef _WIN32 160 | if (AttachConsole(ATTACH_PARENT_PROCESS)) 161 | { 162 | // Open the console's active buffer 163 | (void)freopen("CONOUT$", "w", stdout); 164 | (void)freopen("CONOUT$", "w", stderr); 165 | 166 | // Force print new-line (to avoid printing text over user commands) 167 | printf("\n"); 168 | } 169 | #endif 170 | 171 | /* Fix scalling issues on Windows */ 172 | #ifndef Q_OS_WIN 173 | QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 174 | #else 175 | QApplication::setAttribute(Qt::AA_DisableHighDpiScaling); 176 | #endif 177 | 178 | /* Set application info */ 179 | QApplication::setApplicationName(APP_DSPNAME); 180 | QApplication::setOrganizationName(APP_COMPANY); 181 | QApplication::setApplicationVersion(APP_VERSION); 182 | QApplication::setOrganizationDomain(APP_WEBSITE); 183 | 184 | /* Initialize application */ 185 | QString arguments; 186 | QApplication app(argc, argv); 187 | 188 | /* Read command line arguments */ 189 | if (app.arguments().count() >= 2) 190 | arguments = app.arguments().at(1); 191 | 192 | /* We have some arguments, read them */ 193 | if (!arguments.isEmpty() && arguments.startsWith("-")) 194 | { 195 | if (arguments == "-b" || arguments == "--bug") 196 | reportBug(); 197 | 198 | else if (arguments == "-r" || arguments == "--reset") 199 | resetSettings(); 200 | 201 | else if (arguments == "-c" || arguments == "--contact") 202 | contact(); 203 | 204 | else if (arguments == "-v" || arguments == "--version") 205 | showVersion(); 206 | 207 | else if (arguments == "-w" || arguments == "--website") 208 | openWebsite(); 209 | 210 | else 211 | showHelp(); 212 | 213 | return EXIT_SUCCESS; 214 | } 215 | 216 | /* Start the initialization time clock */ 217 | QElapsedTimer timer; 218 | timer.start(); 219 | 220 | /* Initialize OS variables */ 221 | bool isMac = false; 222 | bool isUnx = false; 223 | bool isWin = false; 224 | 225 | /* Let QML know the operating system */ 226 | #if defined Q_OS_MAC 227 | isMac = true; 228 | #elif defined Q_OS_WIN 229 | isWin = true; 230 | #else 231 | isUnx = true; 232 | #endif 233 | 234 | /* Install the LibDS event logger */ 235 | DSEventLogger *CppDSLogger = DSEventLogger::getInstance(); 236 | qInstallMessageHandler(CppDSLogger->messageHandler); 237 | 238 | /* Initialize application modules */ 239 | Beeper beeper; 240 | Utilities utilities; 241 | Shortcuts shortcuts; 242 | Dashboards dashboards; 243 | QJoysticks *qjoysticks = QJoysticks::getInstance(); 244 | DriverStation *driverstation = DriverStation::getInstance(); 245 | 246 | /* Set virtual joystick axis range */ 247 | qjoysticks->setVirtualJoystickAxisSensibility(0); 248 | 249 | /* Configure the shortcuts handler and start the DS */ 250 | app.installEventFilter(&shortcuts); 251 | driverstation->declareQML(); 252 | driverstation->start(); 253 | 254 | /* Load the QML interface */ 255 | QQmlApplicationEngine engine; 256 | engine.rootContext()->setContextProperty("CppIsMac", isMac); 257 | engine.rootContext()->setContextProperty("CppIsUnix", isUnx); 258 | engine.rootContext()->setContextProperty("CppIsWindows", isWin); 259 | engine.rootContext()->setContextProperty("CppBeeper", &beeper); 260 | engine.rootContext()->setContextProperty("QJoysticks", qjoysticks); 261 | engine.rootContext()->setContextProperty("CppUtilities", &utilities); 262 | engine.rootContext()->setContextProperty("CppDashboard", &dashboards); 263 | engine.rootContext()->setContextProperty("CppAppDspName", APP_DSPNAME); 264 | engine.rootContext()->setContextProperty("CppAppVersion", APP_VERSION); 265 | engine.rootContext()->setContextProperty("CppAppWebsite", APP_WEBSITE); 266 | engine.rootContext()->setContextProperty("CppAppRepBugs", APP_REPBUGS); 267 | engine.rootContext()->setContextProperty("CppDSLogger", CppDSLogger); 268 | engine.rootContext()->setContextProperty("CppDS", driverstation); 269 | engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); 270 | 271 | /* QML loading failed, exit the application */ 272 | if (engine.rootObjects().isEmpty()) 273 | return EXIT_FAILURE; 274 | 275 | /* Tell user how much time was needed to initialize the app */ 276 | qDebug() << "Initialized in " << timer.elapsed() << "milliseconds"; 277 | 278 | /* Warn first-timers to download the xbox drivers on macOS */ 279 | WelcomeMessages(); 280 | 281 | /* Run normally */ 282 | return app.exec(); 283 | } 284 | -------------------------------------------------------------------------------- /src/shortcuts.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #include "shortcuts.h" 24 | #include 25 | #include 26 | 27 | bool Shortcuts::eventFilter(QObject *object, QEvent *event) 28 | { 29 | Q_UNUSED(object); 30 | 31 | if (event->type() == QEvent::KeyPress) 32 | { 33 | switch (static_cast(event)->key()) 34 | { 35 | case Qt::Key_Space: 36 | DriverStation::getInstance()->setEmergencyStopped(true); 37 | break; 38 | case Qt::Key_Enter: 39 | DriverStation::getInstance()->setEnabled(true); 40 | break; 41 | case Qt::Key_F1: 42 | QJoysticks::getInstance()->updateInterfaces(); 43 | break; 44 | } 45 | } 46 | 47 | return false; 48 | } 49 | -------------------------------------------------------------------------------- /src/shortcuts.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #ifndef _QDS_SHORTCUTS_H 24 | #define _QDS_SHORTCUTS_H 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | /** 31 | * \brief Listens for keyboard events and acts if a shortcut has been activated 32 | * 33 | * This allows us to implement safety features, such as triggering an emergency 34 | * stop when pressing the spacebar, or disabling the robot when pressing enter. 35 | */ 36 | class Shortcuts : public QObject 37 | { 38 | Q_OBJECT 39 | 40 | private: 41 | bool eventFilter(QObject *object, QEvent *event); 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /src/utilities.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #include "utilities.h" 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | //------------------------------------------------------------------------------ 33 | // Windows hacks 34 | //------------------------------------------------------------------------------ 35 | 36 | #if defined Q_OS_WIN 37 | # include 38 | # include 39 | # include 40 | # include 41 | 42 | static PDH_HQUERY cpuQuery; 43 | static PDH_HCOUNTER cpuTotal; 44 | static SYSTEM_POWER_STATUS power; 45 | #endif 46 | 47 | //------------------------------------------------------------------------------ 48 | // Mac OS hacks 49 | //------------------------------------------------------------------------------ 50 | 51 | #if defined Q_OS_MAC 52 | static const QString CPU_CMD = "bash -c \"ps -A -o %cpu | " 53 | "awk '{s+=$1} END {print s}'\""; 54 | static const QString BTY_CMD = "pmset -g batt"; 55 | static const QString PWR_CMD = "pmset -g batt"; 56 | #endif 57 | 58 | //------------------------------------------------------------------------------ 59 | // Linux hacks 60 | //------------------------------------------------------------------------------ 61 | 62 | #if defined Q_OS_LINUX 63 | # include 64 | # include 65 | 66 | static const QString BTY_CMD = "bash -c \"upower -i " 67 | "$(upower -e | grep 'BAT') | " 68 | "grep -E 'state|to\\ full|percentage'\""; 69 | static const QString PWR_CMD = "bash -c \"upower -i " 70 | "$(upower -e | grep 'BAT') | " 71 | "grep -E 'state|to\\ full|percentage'\""; 72 | #endif 73 | 74 | //------------------------------------------------------------------------------ 75 | // Ensure that application compiles even if OS is not supported 76 | //------------------------------------------------------------------------------ 77 | 78 | #if !defined Q_OS_WIN && !defined Q_OS_MAC && !defined Q_OS_LINUX 79 | static const QString CPU_CMD = ""; 80 | static const QString BTY_CMD = ""; 81 | static const QString PWR_CMD = ""; 82 | #endif 83 | 84 | //------------------------------------------------------------------------------ 85 | // Start class code 86 | //------------------------------------------------------------------------------ 87 | 88 | /** 89 | * Configures the class and nitializes the CPU querying process under Windows. 90 | */ 91 | Utilities::Utilities() 92 | { 93 | m_ratio = 0; 94 | m_cpuUsage = 0; 95 | m_batteryLevel = 0; 96 | m_connectedToAC = 0; 97 | 98 | m_settings = new QSettings(qApp->organizationName(), qApp->applicationName()); 99 | 100 | /* Read process data when they finish */ 101 | connect(&m_cpuProcess, SIGNAL(finished(int)), this, SLOT(readCpuUsageProcess(int))); 102 | connect(&m_batteryLevelProcess, SIGNAL(finished(int)), this, SLOT(readBatteryLevelProcess(int))); 103 | connect(&m_connectedToACProcess, SIGNAL(finished(int)), this, SLOT(readConnectedToACProcess(int))); 104 | 105 | /* Kill the probing processes when application quits */ 106 | connect(qApp, SIGNAL(aboutToQuit()), &m_cpuProcess, SLOT(kill())); 107 | connect(qApp, SIGNAL(aboutToQuit()), &m_batteryLevelProcess, SLOT(kill())); 108 | connect(qApp, SIGNAL(aboutToQuit()), &m_connectedToACProcess, SLOT(kill())); 109 | 110 | /* Configure Windows */ 111 | #if defined Q_OS_WIN 112 | PdhOpenQuery(0, 0, &cpuQuery); 113 | PdhAddCounter(cpuQuery, L"\\Processor(_Total)\\% Processor Time", 0, &cpuTotal); 114 | PdhCollectQueryData(cpuQuery); 115 | #endif 116 | 117 | /* Start loop */ 118 | updateCpuUsage(); 119 | updateBatteryLevel(); 120 | updateConnectedToAC(); 121 | } 122 | 123 | /** 124 | * Returns the auto-calculates scale ratio 125 | */ 126 | qreal Utilities::scaleRatio() 127 | { 128 | if (m_ratio < 1) 129 | calculateScaleRatio(); 130 | 131 | return m_ratio; 132 | } 133 | 134 | /** 135 | * Returns the current CPU usage (from 0 to 100) 136 | */ 137 | int Utilities::cpuUsage() 138 | { 139 | m_cpuUsage = abs(m_cpuUsage); 140 | 141 | if (m_cpuUsage <= 100) 142 | return m_cpuUsage; 143 | 144 | return 0; 145 | } 146 | 147 | /** 148 | * Returns the current battery level (from 0 to 100) 149 | */ 150 | int Utilities::batteryLevel() 151 | { 152 | m_batteryLevel = abs(m_batteryLevel); 153 | 154 | if (m_batteryLevel <= 100) 155 | return m_batteryLevel; 156 | 157 | return 0; 158 | } 159 | 160 | /** 161 | * Returns \c true if the computer is connected to a power source or the 162 | * battery is not discharging. 163 | */ 164 | bool Utilities::isConnectedToAC() 165 | { 166 | return m_connectedToAC; 167 | } 168 | 169 | /** 170 | * Copies the given \a data to the system clipboard 171 | */ 172 | void Utilities::copy(const QVariant &data) 173 | { 174 | qApp->clipboard()->setText(data.toString(), QClipboard::Clipboard); 175 | } 176 | 177 | /** 178 | * Enables or disables the autoscale feature. 179 | * \note The application must be restarted for changes to take effect 180 | */ 181 | void Utilities::setAutoScaleEnabled(const bool enabled) 182 | { 183 | m_settings->setValue("AutoScale", enabled); 184 | } 185 | 186 | /** 187 | * Queries for the current CPU usage 188 | */ 189 | void Utilities::updateCpuUsage() 190 | { 191 | #if defined Q_OS_WIN 192 | PDH_FMT_COUNTERVALUE counterVal; 193 | PdhCollectQueryData(cpuQuery); 194 | PdhGetFormattedCounterValue(cpuTotal, PDH_FMT_DOUBLE, 0, &counterVal); 195 | m_cpuUsage = static_cast(counterVal.doubleValue); 196 | emit cpuUsageChanged(); 197 | #elif defined Q_OS_MAC 198 | m_cpuProcess.terminate(); 199 | m_cpuProcess.start(CPU_CMD, QIODevice::ReadOnly); 200 | #elif defined Q_OS_LINUX 201 | auto cpuJiffies = getCpuJiffies(); 202 | 203 | m_cpuUsage = (cpuJiffies.first - m_pastCpuJiffies.first) * 100 / (cpuJiffies.second - m_pastCpuJiffies.second); 204 | 205 | m_pastCpuJiffies = cpuJiffies; 206 | emit cpuUsageChanged(); 207 | #endif 208 | 209 | QTimer::singleShot(1000, Qt::PreciseTimer, this, SLOT(updateCpuUsage())); 210 | } 211 | 212 | /** 213 | * Queries for the current battery level 214 | */ 215 | void Utilities::updateBatteryLevel() 216 | { 217 | #if defined Q_OS_WIN 218 | GetSystemPowerStatus(&power); 219 | m_batteryLevel = static_cast(power.BatteryLifePercent); 220 | emit batteryLevelChanged(); 221 | #else 222 | m_batteryLevelProcess.terminate(); 223 | m_batteryLevelProcess.start(BTY_CMD, QIODevice::ReadOnly); 224 | #endif 225 | 226 | QTimer::singleShot(1000, Qt::PreciseTimer, this, SLOT(updateBatteryLevel())); 227 | } 228 | 229 | /** 230 | * Queries for the current AC power source status 231 | */ 232 | void Utilities::updateConnectedToAC() 233 | { 234 | #if defined Q_OS_WIN 235 | GetSystemPowerStatus(&power); 236 | m_connectedToAC = (power.ACLineStatus != 0); 237 | emit connectedToACChanged(); 238 | #else 239 | m_connectedToACProcess.terminate(); 240 | m_connectedToACProcess.start(PWR_CMD, QIODevice::ReadOnly); 241 | #endif 242 | 243 | QTimer::singleShot(1000, Qt::PreciseTimer, this, SLOT(updateConnectedToAC())); 244 | } 245 | 246 | /** 247 | * Calculates the scale factor to apply to the UI. 248 | * \note This function uses different procedures depending on the OS 249 | */ 250 | void Utilities::calculateScaleRatio() 251 | { 252 | bool enabled = m_settings->value("AutoScale", true).toBool(); 253 | 254 | /* Get scale factor using OS-specific code */ 255 | #if defined Q_OS_WIN 256 | HDC screen = GetDC(Q_NULLPTR); 257 | m_ratio = (qreal)GetDeviceCaps(screen, LOGPIXELSX) / 96; 258 | ReleaseDC(Q_NULLPTR, screen); 259 | #elif defined Q_OS_LINUX 260 | m_ratio = qApp->primaryScreen()->physicalDotsPerInch() / 120; 261 | #endif 262 | 263 | /* Ensure that values between x.40 and x.65 round down to x.40 */ 264 | qreal decimals = m_ratio - (int)m_ratio; 265 | if (decimals >= 0.40 && decimals <= 0.65) 266 | m_ratio -= (decimals - 0.40); 267 | 268 | /* Ratio is too small to be useful to us */ 269 | if (!enabled || m_ratio < 1.2) 270 | m_ratio = 1; 271 | 272 | /* Brag about the obtained result */ 273 | qDebug() << "Scale factor set to:" << m_ratio; 274 | } 275 | 276 | /** 277 | * Reads the output of the process launched to get the CPU usage 278 | */ 279 | void Utilities::readCpuUsageProcess(int exit_code) 280 | { 281 | if (exit_code == EXIT_FAILURE) 282 | return; 283 | 284 | #if defined Q_OS_MAC 285 | m_cpuUsage = 0; 286 | m_cpuProcess.terminate(); 287 | QByteArray data = m_cpuProcess.readAll(); 288 | 289 | if (!data.isEmpty() && data.length() >= 2) 290 | { 291 | /* Parse the digits of the percentage */ 292 | int t = data.at(0) - '0'; // Tens 293 | int u = data.at(1) - '0'; // Units 294 | 295 | /* Check if process data is invalid */ 296 | if (t < 0) 297 | t = 0; 298 | if (u < 0) 299 | u = 0; 300 | 301 | /* Update information */ 302 | m_cpuUsage = (t * 10) + u; 303 | emit cpuUsageChanged(); 304 | } 305 | #endif 306 | } 307 | 308 | /** 309 | * Reads the output of the process launched to get the battery level 310 | */ 311 | void Utilities::readBatteryLevelProcess(int exit_code) 312 | { 313 | if (exit_code == EXIT_FAILURE) 314 | return; 315 | 316 | #if defined Q_OS_MAC || defined Q_OS_LINUX 317 | m_batteryLevel = 0; 318 | m_batteryLevelProcess.terminate(); 319 | QByteArray data = m_batteryLevelProcess.readAll(); 320 | 321 | if (!data.isEmpty()) 322 | { 323 | /* Parse the digits of the percentage */ 324 | int h = data.at(data.indexOf("%") - 3) - '0'; // Hundreds 325 | int t = data.at(data.indexOf("%") - 2) - '0'; // Tens 326 | int u = data.at(data.indexOf("%") - 1) - '0'; // Units 327 | 328 | /* Check if process data is invalid */ 329 | if (h < 0) 330 | h = 0; 331 | if (t < 0) 332 | t = 0; 333 | if (u < 0) 334 | u = 0; 335 | 336 | /* Update information */ 337 | m_batteryLevel = (h * 100) + (t * 10) + u; 338 | emit batteryLevelChanged(); 339 | } 340 | #endif 341 | } 342 | 343 | /** 344 | * Reads the output of the process launched to get the AC power source status 345 | */ 346 | void Utilities::readConnectedToACProcess(int exit_code) 347 | { 348 | if (exit_code == EXIT_FAILURE) 349 | return; 350 | 351 | #if defined Q_OS_MAC || defined Q_OS_LINUX 352 | m_connectedToAC = false; 353 | m_connectedToACProcess.terminate(); 354 | QByteArray data = m_connectedToACProcess.readAll(); 355 | 356 | if (!data.isEmpty()) 357 | { 358 | m_connectedToAC = !data.contains("discharging"); 359 | emit connectedToACChanged(); 360 | } 361 | #endif 362 | } 363 | 364 | #if defined Q_OS_LINUX 365 | /** 366 | * Reads the current count of CPU jiffies from /proc/stat and return a pair 367 | * consisting of non-idle jiffies and total jiffies 368 | */ 369 | QPair Utilities::getCpuJiffies() 370 | { 371 | quint64 totalJiffies = 0; 372 | quint64 nonIdleJiffies = 0; 373 | 374 | QFile file("/proc/stat"); 375 | if (file.open(QFile::ReadOnly)) 376 | { 377 | QString line = file.readLine(); 378 | QStringList jiffies = line.replace("cpu ", "").split(" "); 379 | 380 | if (jiffies.count() > 3) 381 | { 382 | nonIdleJiffies = jiffies.at(0).toInt() + jiffies.at(2).toInt(); 383 | totalJiffies = nonIdleJiffies + jiffies.at(3).toInt(); 384 | } 385 | 386 | file.close(); 387 | } 388 | 389 | return qMakePair(nonIdleJiffies, totalJiffies); 390 | } 391 | #endif 392 | -------------------------------------------------------------------------------- /src/utilities.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #ifndef _QDS_UTILITIES_H 24 | #define _QDS_UTILITIES_H 25 | 26 | #include 27 | 28 | #if defined Q_OS_LINUX 29 | # include 30 | #endif 31 | 32 | class QSettings; 33 | 34 | /** 35 | * \brief Provides CPU and Battery information to the QML interface 36 | */ 37 | class Utilities : public QObject 38 | { 39 | Q_OBJECT 40 | Q_PROPERTY(int cpuUsage READ cpuUsage NOTIFY cpuUsageChanged) 41 | Q_PROPERTY(int batteryLevel READ batteryLevel NOTIFY batteryLevelChanged) 42 | Q_PROPERTY(bool connectedToAC READ isConnectedToAC NOTIFY connectedToACChanged) 43 | Q_PROPERTY(qreal scaleRatio READ scaleRatio CONSTANT) 44 | 45 | signals: 46 | void cpuUsageChanged(); 47 | void batteryLevelChanged(); 48 | void connectedToACChanged(); 49 | 50 | public: 51 | explicit Utilities(); 52 | 53 | int cpuUsage(); 54 | int batteryLevel(); 55 | qreal scaleRatio(); 56 | bool isConnectedToAC(); 57 | 58 | public slots: 59 | void copy(const QVariant &data); 60 | void setAutoScaleEnabled(const bool enabled); 61 | 62 | private slots: 63 | void updateCpuUsage(); 64 | void updateBatteryLevel(); 65 | void updateConnectedToAC(); 66 | void calculateScaleRatio(); 67 | void readCpuUsageProcess(int exit_code = 0); 68 | void readBatteryLevelProcess(int exit_code = 0); 69 | void readConnectedToACProcess(int exit_code = 0); 70 | 71 | private: 72 | #if defined(Q_OS_LINUX) 73 | QPair getCpuJiffies(); 74 | QPair m_pastCpuJiffies { 0, 0 }; 75 | #endif 76 | 77 | qreal m_ratio; 78 | int m_cpuUsage; 79 | int m_batteryLevel; 80 | bool m_connectedToAC; 81 | 82 | QSettings *m_settings; 83 | QProcess m_cpuProcess; 84 | QProcess m_batteryLevelProcess; 85 | QProcess m_connectedToACProcess; 86 | }; 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /src/versions.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015-2020 Alex Spataru 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | #ifndef _QDS_VERSIONS_H 24 | #define _QDS_VERSIONS_H 25 | 26 | #include 27 | 28 | // *INDENT-OFF* 29 | static const QString APP_VERSION = "21.04"; 30 | static const QString APP_COMPANY = "FRC Utilities"; 31 | static const QString APP_DSPNAME = "QDriverStation"; 32 | static const QString CONTACT_URL = "https://github.com/alex-spataru"; 33 | static const QString APP_WEBSITE = "http://frc-utilities.github.io/"; 34 | static const QString APP_REPBUGS = "http://github.com/FRC-Utilities/QDriverStation/issues"; 35 | // *INDENT-ON* 36 | 37 | #endif 38 | --------------------------------------------------------------------------------