├── .gitignore ├── .travis.yml ├── INSTALL.md ├── LICENSE ├── README.md ├── appveyor.yml ├── resources ├── default_filters │ ├── README.md │ ├── filters │ │ ├── WebDataConnector.json │ │ └── hyper-queries.json │ ├── install_all.bat │ └── install_all.sh ├── images │ ├── branch-closed.png │ ├── branch-end.png │ ├── branch-end_black.png │ ├── branch-more.png │ ├── branch-more_black.png │ ├── branch-open.png │ ├── branch-vline.png │ ├── branch-vline_black.png │ ├── ctx-add.png │ ├── ctx-book.png │ ├── ctx-clear.png │ ├── ctx-copy.png │ ├── ctx-datetime.png │ ├── ctx-diff.png │ ├── ctx-down.png │ ├── ctx-find.png │ ├── ctx-hide.png │ ├── ctx-highlight.png │ ├── ctx-livecapture.png │ ├── ctx-newtab.png │ ├── ctx-open-file-tab.png │ ├── ctx-open-merge.png │ ├── ctx-open.png │ ├── ctx-refresh.png │ ├── ctx-summary.png │ ├── ctx-time.png │ ├── ctx-up.png │ ├── darktheme │ │ ├── ctx-add.png │ │ ├── ctx-book.png │ │ ├── ctx-clear.png │ │ ├── ctx-copy.png │ │ ├── ctx-datetime.png │ │ ├── ctx-diff.png │ │ ├── ctx-down.png │ │ ├── ctx-find.png │ │ ├── ctx-hide.png │ │ ├── ctx-highlight.png │ │ ├── ctx-livecapture.png │ │ ├── ctx-newtab.png │ │ ├── ctx-open-file-tab.png │ │ ├── ctx-open-merge.png │ │ ├── ctx-open.png │ │ ├── ctx-refresh.png │ │ ├── ctx-summary.png │ │ ├── ctx-time.png │ │ ├── ctx-up.png │ │ ├── palette.html │ │ ├── tab-sync-thin.png │ │ ├── tab-sync.png │ │ ├── value-next.png │ │ └── value-previous.png │ ├── live.png │ ├── tab-close-red.png │ ├── tab-close.png │ ├── tab-sync-thin.png │ ├── tab-sync.png │ ├── tlv.icns │ ├── tlv.ico │ ├── value-next.png │ └── value-previous.png ├── query-graphs │ ├── query-graphs.css │ ├── query-graphs.min.js │ └── query-graphs.tlv.html ├── sqlsyntax.css └── workbooks │ ├── QueryInfo.twb │ └── Timeline.twb ├── script ├── before_install_linux.sh └── before_install_osx.sh └── src ├── colorlibrary.cpp ├── colorlibrary.h ├── column.h ├── filtertab.cpp ├── filtertab.h ├── filtertab.ui ├── finddlg.cpp ├── finddlg.h ├── finddlg.ui ├── highlightdlg.cpp ├── highlightdlg.h ├── highlightdlg.ui ├── highlightoptions.cpp ├── highlightoptions.h ├── logtab.cpp ├── logtab.h ├── logtab.ui ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── options.cpp ├── options.h ├── optionsdlg.cpp ├── optionsdlg.h ├── optionsdlg.ui ├── pathhelper.cpp ├── pathhelper.h ├── processevent.cpp ├── processevent.h ├── qjsonutils.cpp ├── qjsonutils.h ├── resources.qrc ├── savefilterdialog.cpp ├── savefilterdialog.h ├── savefilterdialog.ui ├── searchopt.cpp ├── searchopt.h ├── statusbar.cpp ├── statusbar.h ├── tableau-log-viewer.pro ├── theme.cpp ├── theme.h ├── themeutils.cpp ├── themeutils.h ├── tokenizer.cpp ├── tokenizer.h ├── treeitem.cpp ├── treeitem.h ├── treemodel.cpp ├── treemodel.h ├── valuedlg.cpp ├── valuedlg.h ├── valuedlg.ui ├── zoomabletreeview.cpp └── zoomabletreeview.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | # QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | 36 | # QtCtreator CMake 37 | CMakeLists.txt.user* 38 | 39 | # Vim 40 | *.swp 41 | 42 | # Misc OS 43 | [Tt]humbs.db 44 | .DS_Store 45 | Desktop.ini 46 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | dist: trusty 3 | 4 | os: 5 | - linux 6 | #- osx 7 | 8 | addons: 9 | apt: 10 | sources: 11 | - ubuntu-toolchain-r-test 12 | packages: 13 | - freeglut3-dev 14 | - gcc-5 15 | - g++-5 16 | 17 | env: 18 | - BUILDDIR=build-release 19 | 20 | before_install: 21 | - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo script/before_install_linux.sh; export QTDIR=/opt/qt57; export QMAKECC=gcc-5; export QMAKECXX=g++-5; fi 22 | - if [ "$TRAVIS_OS_NAME" = "osx" ]; then script/before_install_osx.sh; export QTDIR=/usr/local/Cellar/qt57; export QMAKECC=clang; export QMAKECXX=clang++; fi 23 | 24 | script: 25 | - echo $QTDIR 26 | - mkdir $BUILDDIR 27 | - cd $BUILDDIR 28 | - source $QTDIR/bin/qt57-env.sh 29 | - $QTDIR/bin/qmake -spec linux-g++-64 QMAKE_CC=$QMAKECC QMAKE_CXX=$QMAKECXX -Wall ../src/tableau-log-viewer.pro 30 | - make 31 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Using a pre-built version of Tableau Log Viewer 2 | 3 | We provide [pre-built releases](https://github.com/tableau/tableau-log-viewer/releases) of Tableau Log Viewer. 4 | No need to setup a dev-environment if all you really want to do is inspecting your lovely log files. 5 | 6 | Download the ZIP file of the [latest release](https://github.com/tableau/tableau-log-viewer/releases/latest) for your operating system (Windows and Mac OS available) and extract the ZIP file. Finally, launch the application (tlv.exe for Windows and tlv.app for Mac). 7 | 8 | # Building Tableau Log Viewer 9 | 10 | TLV is built using C++ and Qt libraries and can be build in different platforms. 11 | 12 | The easiest way to install the Qt libraries is through [aqtinstall](https://github.com/miurahr/aqtinstall): 13 | 14 | ``` 15 | pip install aqtinstall 16 | # On OSX 17 | aqt install-qt mac desktop 6.4.0 clang_64 -m qtwebview qtwebengine qtpositioning qtwebchannel 18 | # On Windows 19 | python -m aqt install-qt windows desktop 6.4.0 win64_msvc2019_64 20 | ``` 21 | 22 | **Note:** When installing Qt make sure you get the following components: `Qt WebView` and `Qt WebEngine`. 23 | 24 | ## Building on Mac (using command line) 25 | The following instructions use Qt 6.4 and the clang compiler to compile a 64-bit binary. 26 | Building TLV with a different Qt version or compiler should be fairly similar. 27 | 28 | ```bash 29 | # Start by cloning the repository 30 | git clone https://github.com/tableau/tableau-log-viewer.git 31 | cd tableau-log-viewer 32 | 33 | # Find out where the Qt libraries are stored in your disk drive, and add the directory to the PATH 34 | export PATH="~/Qt/6.4.0/macos/bin":$PATH 35 | 36 | # Create a directory to compile 37 | mkdir build-release && cd build-release 38 | 39 | # Build 40 | qmake -spec macx-clang -Wall ../src/tableau-log-viewer.pro 41 | make 42 | 43 | # Make a self-contained app 44 | macdeployqt tlv.app 45 | 46 | # Run the application 47 | open tlv.app 48 | ``` 49 | 50 | ## Building on Linux 51 | 52 | Steps for Ubuntu 22.04 53 | 54 | ### Install Qt 6.2 components 55 | 56 | ```bash 57 | sudo apt install qt6-webengine-dev qt6-base-dev qmake6 58 | ``` 59 | 60 | ### Install OpenGL components 61 | 62 | ```bash 63 | sudo apt-get install -y freeglut3-dev 64 | ``` 65 | 66 | ### Build 67 | 68 | ```bash 69 | mkdir -p build-release 70 | cd build-release 71 | qmake6 -spec linux-g++-64 QMAKE_CC=gcc QMAKE_CXX=g++ -Wall ../src/tableau-log-viewer.pro 72 | make -j 8 73 | tlv 74 | ``` 75 | 76 | ## Building on Windows (using command line) 77 | 78 | The following instructions use Qt 6.4 and the Visual Studio 2022 compiler to compile a 64-bit binary. 79 | Building TLV with a different Qt version or compiler should be fairly similar. 80 | 81 | You can install the MSVC compiler by downloading "Build Tools for Visual Studio 2022" from [https://visualstudio.microsoft.com/downloads/#other] 82 | and selecting "Desktop development with C++" from the installer. 83 | A full-blown Visual Studio installation is not necessary. 84 | 85 | The following instructions use the **Visual Studio Command Prompt** that you get from a Visual Studio installation. 86 | You can find it from the Windows menu, it is called "x64 Native Tools Command Prompt for VS 2022". 87 | 88 | **From the Visual Studio command prompt:** 89 | ```cmd 90 | # Start by cloning the repository 91 | cd where\ever\you\store\your\code 92 | git clone https://github.com/tableau/tableau-log-viewer.git 93 | 94 | 95 | # Make build directory and run QMake 96 | mkdir build-release 97 | cd build-release 98 | C:\Qt\6.4.0\msvc2019_64\bin\qmake -spec win32-msvc -Wall ../src/tableau-log-viewer.pro 99 | 100 | # Build it 101 | nmake 102 | 103 | # Run it 104 | .\release\tlv.exe 105 | ``` 106 | 107 | The binary will appear under build-release/release. 108 | The executable needs the Qt libraries to run. The Git Bash command prompt already had the Qt libraries on the PATH. 109 | 110 | **Back to the Git Bash:** 111 | ```bash 112 | # Run the executable 113 | cd release 114 | ./tlv.exe 115 | ``` 116 | 117 | ## Buiding using Qt Creator 118 | 119 | ** This section is outdated. If you successfully built on Linux, please update this section. ** 120 | 121 | Start by cloning the repository 122 | ```bash 123 | git clone https://github.com/tableau/tableau-log-viewer.git 124 | ``` 125 | 126 | 1. Launch Qt Creator and open the project file, it should be under *src/tableau-log-viewer.pro* 127 | 2. Configure the new project. The default configuration might be good enough, if you have problems see the section "Fixing configuration" 128 | 3. Click on the Build button (the one with the Hammer) 129 | 4. Click on the Run button 130 | 131 | If everything went well, TLV should be up and running! 132 | 133 | ### Fixing configuration 134 | The project can be configured with different compiler and targets. 135 | 136 | To change the configuration: 137 | 138 | 1. Click on the Projects button (the one with a wrench) 139 | 2. On the "Build & Run" section, click on "Manage kits..." 140 | 141 | We have only tried a handful of configurations. Here are some that are known to work (strings as displayed by Qt creator) 142 | 143 | #### Windows, Visual Studio 2013 compiler, 64 bit 144 | * Compiler: Microsoft Visual C++ Compiler 12.0 (amd64) 145 | * Qt Version: Qt 5.X.X MSVC2013 64bit 146 | * [Optional but recommended] Add a 64 bit debugger 147 | 1. Switch to the "Debuggers" tab. 148 | 2. Click on the "Add" button 149 | 3. Give a name you can recognize later like "CDB 64 bit" 150 | 4. Find the 64 bit binary. It should be under `C:\debuggers\x64\cdb.exe` or similar 151 | 5. Save the changes clicking the "Apply" button 152 | 6. Go back to the "Kits" tab and choose the debugger configuration just created 153 | 154 | #### Mac OS X, clang compiler, 64 bit 155 | * Compiler: Clang (x86 64 bit in /usr/bin) 156 | * Debugger: System LLDB at /usr/bin/lldb 157 | * Qt Version: Qt 5.X.X clang 64bit 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tableau 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tableau Log Viewer 2 | [![Community Supported](https://img.shields.io/badge/Support%20Level-Community%20Supported-457387.svg)](https://www.tableau.com/support-levels-it-and-developer-tools) 3 | 4 | **Master branch** | [![Travis-CI](https://img.shields.io/travis/tableau/tableau-log-viewer/master.svg?label=Linux%20build)](https://travis-ci.org/tableau/tableau-log-viewer) | [![AppVeyor](https://img.shields.io/appveyor/ci/tableau/tableau-log-viewer/master.svg?label=Windows%20build)](https://ci.appveyor.com/project/tableau/tableau-log-viewer/branch/master) 5 | :--|---|--- 6 | **Dev branch** | [![Travis-CI](https://img.shields.io/travis/tableau/tableau-log-viewer/dev.svg?label=Linux%20build)](https://travis-ci.org/tableau/tableau-log-viewer) | [![AppVeyor](https://img.shields.io/appveyor/ci/tableau/tableau-log-viewer/dev.svg?label=Windows%20build)](https://ci.appveyor.com/project/tableau/tableau-log-viewer/branch/dev) 7 | 8 | Tableau Log Viewer is a cross-platform tool with a simple interface that has a single purpose of making it easy to quickly glance over Tableau log files. 9 | 10 | ![TLV Screenshot](https://user-images.githubusercontent.com/1087437/45051694-a6a11880-b039-11e8-8028-969eb68e7c2b.png "TLV running on Windows 10") 11 | 12 | Overview 13 | --------------- 14 | * Tableau log file representation in easy to read columnar format 15 | * Live capture from Tableau's log files 16 | * Advanced event search, filtering and highlighting 17 | * Query syntax highlighting 18 | * Compatible with Windows, Mac and, most likely, Linux 19 | 20 | How do I use Tableau Log Viewer? 21 | --------------- 22 | You can get the latest release in the [Releases Section](https://github.com/tableau/tableau-log-viewer/releases). Download the ZIP file of the latest release for your operating system (Windows and Mac OS available) and extract the ZIP file. Finally, launch the application (tlv.exe for Windows and tlv.app for Mac). 23 | 24 | To view logs, drag'n'drop a Tableau log file into the application. Both Tableau Desktop and [most] Tableau Server log files are supported. 25 | Take a look at our [wiki](https://github.com/tableau/tableau-log-viewer/wiki) to get more details on the features and usage of TLV. 26 | 27 | **Using Pre-Built Tableau Log Viewer on M1 Mac** 28 | If you are trying to open a downloaded pre-built version of Tableau Log Viewer on an M1 Mac, this may fail as the application is not code-signed. 29 | You can code-sign the application yourself using `sudo codesign --force --deep -s - /path/to/tlv.app` and then launch it for the first time via `CTRL + `. 30 | 31 | 32 | How do I build Tableau Log Viewer? 33 | --------------- 34 | See [INSTALL.md](INSTALL.md) 35 | 36 | Is Tableau Log Viewer supported? 37 | --------------- 38 | Tableau Log Viewer is supported by the community. This is intended to be a self service tool and includes a user guide. Any requests or issues discovered should be filed in the [Issue Tracker](https://github.com/tableau/tableau-log-viewer/issues). 39 | 40 | How can I contribute to Tableau Log Viewer? 41 | --------------- 42 | Code contributions & improvements by the community are welcomed & encouraged! See the [LICENSE](LICENSE) file for current open-source licensing and use information. Also, if you are new to GitHub - read [this handy guide](https://guides.github.com/activities/contributing-to-open-source/) on how to get started! 43 | 44 | A Word About Licenses 45 | --------------- 46 | Tableau Log Viewer is released under MIT license however it relies on several other components: 47 | * [Qt](https://www.qt.io/). We are using Qt's components that are licensed under [LGPLv3 license](https://www.qt.io/licensing-comparison/). Qt source code is located [here](http://code.qt.io/cgit/qt/qtbase.git/tree/) and instructions for obtaining it are located [here](https://wiki.qt.io/Building_Qt_5_from_Git#Getting_the_source_code). We will be keeping these links up to date and making sure they provide clear directions for obtaining Qt's source code in case you need it. 48 | * [Query Graphs](https://github.com/tableau/query-graphs). This is our side project that is used for query tree visualization. It is released under the MIT license and uses the d3js library, released under the BSD license. 49 | * [Solarized theme](https://github.com/altercation/solarized). This a collection of colors and guidelines for UI applications designed by Ethan Schoonover. The "Solarized Light" and "Solarized Dark" themes in TLV are using these color schemes. It is released under the MIT license. 50 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | install: 3 | - set QTHOME=C:\Qt\6.4.0\msvc2019_64 4 | - set BUILDDIR=build-release 5 | - set PATH=%QTHOME%\bin;%PATH% 6 | build_script: 7 | - '"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x86_amd64' 8 | - mkdir %BUILDDIR% 9 | - cd /d %BUILDDIR% 10 | - C:\Qt\6.3.0\msvc2019_64\bin\qmake -spec win32-msvc -Wall ..\src\tableau-log-viewer.pro 11 | - nmake 12 | -------------------------------------------------------------------------------- /resources/default_filters/README.md: -------------------------------------------------------------------------------- 1 | # Saved Filters 2 | 3 | Share your filters with other users and give better default options to new users. The json files in the filters folder will be copied into the saved filter location and accessed from the Load Filters menu. 4 | 5 | ### How To Install Filters 6 | On Windows, run the `install_all.bat` batch script. On Mac, run the `install_all.sh` script. 7 | 8 | ### Submit your own filter 9 | To submit a filter of your own, find the filter in the ` %LOCALAPPDATA%\Tableau\TLV\filters\` folder on windows and `~/Library/Preferences/Tableau/TLV/filters` on Mac. Copy that filter json file into the `./default_filters/filters` folder and submit a pull request to github. 10 | -------------------------------------------------------------------------------- /resources/default_filters/filters/WebDataConnector.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "backgroundColor": [ 4 | 191, 5 | 228, 6 | 243 7 | ], 8 | "keys": [ 9 | "Key" 10 | ], 11 | "matchCase": false, 12 | "useRegex": false, 13 | "value": "web-data-connector" 14 | }, 15 | { 16 | "backgroundColor": [ 17 | 242, 18 | 236, 19 | 153 20 | ], 21 | "keys": [ 22 | "Value" 23 | ], 24 | "matchCase": false, 25 | "useRegex": true, 26 | "value": "^(http.*)" 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /resources/default_filters/filters/hyper-queries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "backgroundColor": [ 4 | 191, 5 | 228, 6 | 243 7 | ], 8 | "keys": [ 9 | "Key" 10 | ], 11 | "matchCase": false, 12 | "mode": "equals", 13 | "value": "startup-info" 14 | }, 15 | { 16 | "backgroundColor": [ 17 | 242, 18 | 236, 19 | 153 20 | ], 21 | "keys": [ 22 | "Key" 23 | ], 24 | "matchCase": false, 25 | "mode": "equals", 26 | "value": "connection-startup" 27 | }, 28 | { 29 | "backgroundColor": [ 30 | 198, 31 | 202, 32 | 248 33 | ], 34 | "keys": [ 35 | "Key" 36 | ], 37 | "matchCase": false, 38 | "mode": "contains", 39 | "value": "query-begin" 40 | }, 41 | { 42 | "backgroundColor": [ 43 | 153, 44 | 148, 45 | 241 46 | ], 47 | "keys": [ 48 | "Key" 49 | ], 50 | "matchCase": false, 51 | "mode": "contains", 52 | "value": "query-end" 53 | }, 54 | { 55 | "backgroundColor": [ 56 | 191, 57 | 233, 58 | 104 59 | ], 60 | "keys": [ 61 | "Key" 62 | ], 63 | "matchCase": false, 64 | "mode": "equals", 65 | "value": "query-plan" 66 | } 67 | ] 68 | -------------------------------------------------------------------------------- /resources/default_filters/install_all.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | for /r %%i in (filters\*.json) do ( 3 | SET _dest=%LOCALAPPDATA%\Tableau\TLV\filters\ 4 | xcopy /i /e %%i %_dest% 5 | ) -------------------------------------------------------------------------------- /resources/default_filters/install_all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # find the location the filters live 4 | BASEDIR=$(dirname "$0") 5 | FILES=/filters/* 6 | FILTERS=$BASEDIR$FILES 7 | 8 | # make the filters directory if it doesn't exist 9 | DEST=~/Library/Preferences/Tableau/TLV/filters/ 10 | mkdir -p $DEST 11 | 12 | # go through and copy all the filters 13 | for f in $FILTERS 14 | do 15 | cp "$f" "$DEST" 16 | echo "$f" 17 | done -------------------------------------------------------------------------------- /resources/images/branch-closed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-closed.png -------------------------------------------------------------------------------- /resources/images/branch-end.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-end.png -------------------------------------------------------------------------------- /resources/images/branch-end_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-end_black.png -------------------------------------------------------------------------------- /resources/images/branch-more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-more.png -------------------------------------------------------------------------------- /resources/images/branch-more_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-more_black.png -------------------------------------------------------------------------------- /resources/images/branch-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-open.png -------------------------------------------------------------------------------- /resources/images/branch-vline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-vline.png -------------------------------------------------------------------------------- /resources/images/branch-vline_black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/branch-vline_black.png -------------------------------------------------------------------------------- /resources/images/ctx-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-add.png -------------------------------------------------------------------------------- /resources/images/ctx-book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-book.png -------------------------------------------------------------------------------- /resources/images/ctx-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-clear.png -------------------------------------------------------------------------------- /resources/images/ctx-copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-copy.png -------------------------------------------------------------------------------- /resources/images/ctx-datetime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-datetime.png -------------------------------------------------------------------------------- /resources/images/ctx-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-diff.png -------------------------------------------------------------------------------- /resources/images/ctx-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-down.png -------------------------------------------------------------------------------- /resources/images/ctx-find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-find.png -------------------------------------------------------------------------------- /resources/images/ctx-hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-hide.png -------------------------------------------------------------------------------- /resources/images/ctx-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-highlight.png -------------------------------------------------------------------------------- /resources/images/ctx-livecapture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-livecapture.png -------------------------------------------------------------------------------- /resources/images/ctx-newtab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-newtab.png -------------------------------------------------------------------------------- /resources/images/ctx-open-file-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-open-file-tab.png -------------------------------------------------------------------------------- /resources/images/ctx-open-merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-open-merge.png -------------------------------------------------------------------------------- /resources/images/ctx-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-open.png -------------------------------------------------------------------------------- /resources/images/ctx-refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-refresh.png -------------------------------------------------------------------------------- /resources/images/ctx-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-summary.png -------------------------------------------------------------------------------- /resources/images/ctx-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-time.png -------------------------------------------------------------------------------- /resources/images/ctx-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/ctx-up.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-add.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-book.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-clear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-clear.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-copy.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-datetime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-datetime.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-diff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-diff.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-down.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-find.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-hide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-hide.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-highlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-highlight.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-livecapture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-livecapture.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-newtab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-newtab.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-open-file-tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-open-file-tab.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-open-merge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-open-merge.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-open.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-refresh.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-summary.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-time.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-time.png -------------------------------------------------------------------------------- /resources/images/darktheme/ctx-up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/ctx-up.png -------------------------------------------------------------------------------- /resources/images/darktheme/palette.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 59 | 60 | 61 |
62 |

Solarized

63 |
64 |
65 |

Base05

66 |

#001419

67 |

Context menus

68 |
69 |
70 |

Base04

71 |

#001e26

72 |

Controls

73 |
74 |
75 |

Base03

76 |

#002b36

77 |

Main BG

78 |
79 |
80 |

Base02

81 |

#073642

82 |

Highlight, Control borders

83 |
84 |
85 |

Base01

86 |

#586e75

87 |

88 |
89 |
90 |

Base00

91 |

#657b83

92 |

93 |
94 |
95 |

Base0

96 |

#839496

97 |

Content

98 |
99 |
100 |

Base1

101 |

#93a1a1

102 |
103 |
104 |

Base2

105 |

#eee8d5

106 |
107 |
108 |

Base3

109 |

#fdf6e3

110 |
111 |
112 |

Base4

113 |

#e6ddc1

114 |
115 |
116 |
117 | 118 | -------------------------------------------------------------------------------- /resources/images/darktheme/tab-sync-thin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/tab-sync-thin.png -------------------------------------------------------------------------------- /resources/images/darktheme/tab-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/tab-sync.png -------------------------------------------------------------------------------- /resources/images/darktheme/value-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/value-next.png -------------------------------------------------------------------------------- /resources/images/darktheme/value-previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/darktheme/value-previous.png -------------------------------------------------------------------------------- /resources/images/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/live.png -------------------------------------------------------------------------------- /resources/images/tab-close-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/tab-close-red.png -------------------------------------------------------------------------------- /resources/images/tab-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/tab-close.png -------------------------------------------------------------------------------- /resources/images/tab-sync-thin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/tab-sync-thin.png -------------------------------------------------------------------------------- /resources/images/tab-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/tab-sync.png -------------------------------------------------------------------------------- /resources/images/tlv.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/tlv.icns -------------------------------------------------------------------------------- /resources/images/tlv.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/tlv.ico -------------------------------------------------------------------------------- /resources/images/value-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/value-next.png -------------------------------------------------------------------------------- /resources/images/value-previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tableau/tableau-log-viewer/f796449395ee53682eeb6a3eb8a561e531ebbac6/resources/images/value-previous.png -------------------------------------------------------------------------------- /resources/query-graphs/query-graphs.css: -------------------------------------------------------------------------------- 1 | .qg-tree-container { 2 | padding: 0px; 3 | width: 100%; 4 | height: 100%; 5 | background: #fff; 6 | margin: 0 auto; 7 | } 8 | 9 | .qg-info-card { 10 | position: absolute; 11 | top: 3px; 12 | right: 3px; 13 | background-color: rgba(255, 255, 255, 0.85); 14 | border: thin solid #ccc; 15 | border-radius: 5px; 16 | box-shadow: 2px 2px 2px #888; 17 | } 18 | 19 | .qg-tree-label { 20 | font-size: 13px; 21 | font-weight: bold; 22 | font-family: Monaco, Consolas, monospace; 23 | padding: 12px 12px 0 12px; 24 | } 25 | 26 | .qg-toolbar { 27 | float: right; 28 | display: inline-block; 29 | padding: 2px; 30 | } 31 | 32 | .qg-toolbar-button { 33 | position: relative; 34 | display: inline-block; 35 | vertical-align: bottom; 36 | border: thin solid hsl(0 0% 60%); 37 | border-width: 1px 1px 1px 0; 38 | padding: 3px; 39 | font-family: Monaco, Consolas, monospace; 40 | user-select: none; 41 | } 42 | 43 | .qg-toolbar-button:hover { 44 | background: #eee; 45 | cursor: pointer; 46 | } 47 | 48 | .qg-toolbar-button:first-child { 49 | border-width: 1px; 50 | } 51 | 52 | .qg-toolbar-tooltip { 53 | visibility: hidden; 54 | text-align: center; 55 | border-radius: 5px; 56 | padding: 8px; 57 | position: absolute; 58 | z-index: 1; 59 | top: 150%; 60 | right: 0; 61 | width: 125px; 62 | background: #ccc; 63 | font-size: 11px; 64 | } 65 | 66 | .qg-toolbar-tooltip::after { 67 | content: " "; 68 | top: -10px; 69 | right: 8px; 70 | border-color: transparent transparent #ccc transparent; 71 | border-width: 5px; 72 | position: absolute; 73 | border-style: solid; 74 | } 75 | 76 | .qg-toolbar-icon > svg { 77 | display: block; 78 | } 79 | 80 | .qg-toolbar-button:hover .qg-toolbar-tooltip { 81 | visibility: visible; 82 | } 83 | 84 | .qg-node { 85 | cursor: pointer; 86 | } 87 | 88 | .qg-overlay{ 89 | background-color: #fff; 90 | } 91 | 92 | /* 93 | Using a 2 color palette technique. When styling symbols use 94 | "fill: inherit" to use the 1st set of colors 95 | "fill: currentColor" to use the 2nd set of colors 96 | More info on techniques: http://tympanus.net/codrops/2015/07/16/styling-svg-use-content-css/ 97 | */ 98 | 99 | /* 100 | .expanded luminosity increased by (100 - collapsed luminosity) / 1.5 101 | */ 102 | 103 | .qg-expanded { 104 | stroke: hsl(0, 0%, 40%); 105 | stroke-width: 0.8; 106 | color: hsl(0, 0%, 84%); 107 | } 108 | 109 | .qg-collapsed { 110 | stroke: hsl(0, 0%, 40%); 111 | stroke-width: 0.8; 112 | color: hsl(0, 0%, 60%); 113 | } 114 | 115 | /* 116 | federated connection coloring 117 | 118 | db2 119 | excel-direct 120 | fedeval_dataengine_connection 121 | mysql 122 | oracle 123 | postgres 124 | sqlserver 125 | textscan 126 | */ 127 | 128 | .qg-db2 .qg-expanded { 129 | color: hsl(211, 36%, 83%) 130 | } 131 | 132 | .qg-db2 .qg-collapsed { 133 | color: hsl(211, 36%, 48%) 134 | } 135 | 136 | .qg-excel-direct .qg-expanded { 137 | color: hsl(30, 88%, 86%) 138 | } 139 | 140 | .qg-excel-direct .qg-collapsed { 141 | color: hsl(30, 88%, 56%) 142 | } 143 | 144 | .qg-fedeval_dataengine_connection .qg-expanded { 145 | color: hsl(113, 34%, 83%) 146 | } 147 | 148 | .qg-fedeval_dataengine_connection .qg-collapsed { 149 | color: hsl(113, 34%, 47%) 150 | } 151 | 152 | .qg-mysql .qg-expanded { 153 | color: hsl(47, 82%, 87%) 154 | } 155 | 156 | .qg-mysql .qg-collapsed { 157 | color: hsl(47, 82%, 61%) 158 | } 159 | 160 | .qg-oracle .qg-expanded { 161 | color: hsl(173, 30%, 88%) 162 | } 163 | 164 | .qg-oracle .qg-collapsed { 165 | color: hsl(173, 30%, 64%) 166 | } 167 | 168 | .qg-postgres .qg-expanded { 169 | color: hsl(359, 70%, 87%) 170 | } 171 | 172 | .qg-postgres .qg-collapsed { 173 | color: hsl(359, 70%, 61%) 174 | } 175 | 176 | .qg-sqlserver .qg-expanded { 177 | color: hsl(354, 100%, 94%) 178 | } 179 | 180 | .qg-sqlserver .qg-collapsed { 181 | color: hsl(354, 100%, 81%) 182 | } 183 | 184 | .qg-textscan .qg-expanded { 185 | color: hsl(317, 25%, 86%) 186 | } 187 | 188 | .qg-textscan .qg-collapsed { 189 | color: hsl(317, 25%, 58%) 190 | } 191 | 192 | .qg-node text { 193 | font-size: 9px; 194 | font-weight: bold; 195 | font-family: Monaco, Consolas, monospace; 196 | fill: #2b2a2a; 197 | } 198 | 199 | .qg-tooltip { 200 | font-size: 13px; 201 | font-weight: bold; 202 | font-family: Monaco, Consolas, monospace; 203 | background-color: rgba(255, 255, 255, 0.85); 204 | padding: 12px; 205 | border: thin solid #eee; 206 | border-radius: 5px; 207 | box-shadow: 2px 2px 2px #888; 208 | } 209 | 210 | .qg-prop-name { 211 | color: hsl(309, 84%, 36%); 212 | } 213 | 214 | .qg-prop-name2 { 215 | color: hsl(0, 0%, 50%); 216 | } 217 | 218 | .qg-prop-value { 219 | color: black; 220 | } 221 | 222 | .qg-link { 223 | fill: none; 224 | stroke: #ccc; 225 | stroke-width: 1; 226 | } 227 | 228 | .qg-crosslink { 229 | fill: none; 230 | stroke: #ccc; 231 | stroke-width: 0.5; 232 | stroke-dasharray: 4 4; 233 | } 234 | 235 | .qg-crosslink-highlighted { 236 | fill: none; 237 | stroke: hsl(309, 84%, 36%); 238 | stroke-width: 1.0; 239 | } 240 | 241 | .qg-link-and-arrow { 242 | marker-start: url(#arrow); 243 | stroke-dasharray: 5 3; 244 | } 245 | 246 | .qg-dotted-link { 247 | stroke-width: 1.5; 248 | stroke-dasharray: 1.5 2; 249 | } 250 | 251 | marker#arrow { 252 | fill: #ccc; 253 | } 254 | 255 | .qg-link-label { 256 | font-size: 7px; 257 | font-weight: bold; 258 | font-family: Monaco, Consolas, monospace; 259 | fill: #aaa; 260 | } 261 | 262 | /* Symbols */ 263 | 264 | .qg-symbol-fill-fg { 265 | fill: currentColor; 266 | } 267 | 268 | .qg-symbol-fill-bg { 269 | fill: #fff; 270 | } 271 | 272 | .qg-symbol-no-stroke { 273 | stroke: none; 274 | } 275 | 276 | .qg-symbol-stroke-only { 277 | stroke-width: 1.2; 278 | fill: none; 279 | } 280 | 281 | /* Run Query */ 282 | path.qg-run-query { 283 | fill: #fff; 284 | } 285 | 286 | /* Joins */ 287 | circle.qg-fill-join { 288 | fill: currentColor; 289 | } 290 | 291 | circle.qg-empty-join { 292 | stroke: none; 293 | fill: #fff; 294 | } 295 | 296 | circle.qg-only-stroke-join { 297 | fill: none; 298 | } 299 | 300 | /* Table */ 301 | rect.qg-table-background { 302 | fill: #fff; 303 | } 304 | 305 | rect.qg-table-header { 306 | fill: currentColor; 307 | } 308 | 309 | rect.qg-table-border { 310 | fill: none; 311 | } 312 | 313 | text.qg-table-text { 314 | text-anchor: middle; 315 | font-size: 7px; 316 | font-weight: bold; 317 | font-family: Monaco, Consolas, monospace; 318 | stroke: none; 319 | fill: hsl(0, 0%, 40%); 320 | } 321 | 322 | .qg-magnifier-handle { 323 | stroke-width: 1.5; 324 | stroke-linecap: round; 325 | } -------------------------------------------------------------------------------- /resources/query-graphs/query-graphs.tlv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /resources/sqlsyntax.css: -------------------------------------------------------------------------------- 1 | /* Whole document settings */ 2 | body{ 3 | font-family: Monaco, Consolas, Monospace, Courier; 4 | } 5 | 6 | /* Keyword */ 7 | .k{ 8 | color: #cb4b16; 9 | /*font-weight: bold;*/ 10 | } 11 | 12 | /* String */ 13 | .s{ 14 | color: #268bd2; 15 | } 16 | 17 | /* Tag */ 18 | .t{ 19 | color: #2aa198; 20 | } 21 | 22 | /* Whitespace */ 23 | .w{ 24 | } 25 | 26 | /* Identifier */ 27 | .i{ 28 | } 29 | 30 | /* Symbol */ 31 | .sy{ 32 | } 33 | 34 | /* Open Parenthesis */ 35 | .op{ 36 | color: #859900; 37 | } 38 | 39 | /* Close Parenthesis */ 40 | .cp{ 41 | color: #859900; 42 | } 43 | 44 | /* Operator */ 45 | .o{ 46 | 47 | } 48 | 49 | /* No Match */ 50 | .n{ 51 | 52 | } 53 | 54 | /* Integer */ 55 | .in{ 56 | 57 | } 58 | 59 | /* Float */ 60 | .f{ 61 | 62 | } 63 | -------------------------------------------------------------------------------- /script/before_install_linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | add-apt-repository -y ppa:beineri/opt-qt571-trusty 3 | apt-get update -y 4 | apt-get install -y \ 5 | qt57declarative \ 6 | qt57graphicaleffects \ 7 | qt57quickcontrols \ 8 | qt57svg \ 9 | qt57tools \ 10 | qt57webengine 11 | -------------------------------------------------------------------------------- /script/before_install_osx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | brew install qt5 --with-qtwebkit 3 | -------------------------------------------------------------------------------- /src/colorlibrary.cpp: -------------------------------------------------------------------------------- 1 | #include "colorlibrary.h" 2 | 3 | const static QVector DefaultColors = 4 | { 5 | QColor("#f2ec99"), 6 | QColor("#bfe4f3"), 7 | QColor("#bfe968"), 8 | QColor("#c6caf8"), 9 | QColor("#eec757"), 10 | QColor("#9994f1"), 11 | QColor("#edb0e8"), 12 | QColor("#cebff3"), 13 | QColor("#a5f0c6"), 14 | QColor("#f5b0b4") 15 | }; 16 | 17 | ColorLibrary::ColorLibrary() 18 | { 19 | AddColors(DefaultColors); 20 | } 21 | 22 | ColorLibrary::ColorLibrary(const QVector& usedColors) 23 | { 24 | Exclude(usedColors); 25 | } 26 | 27 | void ColorLibrary::Exclude(const QVector& usedColors) 28 | { 29 | m_colorLibrary.clear(); 30 | 31 | QVector colorsToAdd; 32 | QVector colorsToAddToEnd; 33 | for(const auto& color : DefaultColors) 34 | { 35 | if (!usedColors.contains(color)) 36 | colorsToAdd.push_back(color); 37 | else 38 | colorsToAddToEnd.push_back(color); 39 | } 40 | AddColors(colorsToAdd); 41 | AddColors(colorsToAddToEnd); 42 | } 43 | 44 | void ColorLibrary::AddColors(const QVector& colors) 45 | { 46 | for (const auto& color : colors) 47 | { 48 | m_colorLibrary.enqueue(color); 49 | } 50 | } 51 | 52 | QColor ColorLibrary::GetNextColor() 53 | { 54 | QColor result = m_colorLibrary.head(); 55 | auto color = m_colorLibrary.dequeue(); 56 | m_colorLibrary.enqueue(color); 57 | return result; 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/colorlibrary.h: -------------------------------------------------------------------------------- 1 | #ifndef COLORLIBRARY_H 2 | #define COLORLIBRARY_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | // The purpose of this class is to provide some default colors for highlight filters. 9 | // Default colors are stored in a queue and once a color is used, it is enqueued again. 10 | // If a color is being used from a loaded filter, it will be moved to the end of the queue. 11 | class ColorLibrary 12 | { 13 | public: 14 | ColorLibrary(); 15 | ColorLibrary(const QVector& usedColors); 16 | 17 | void Exclude(const QVector& usedColors); 18 | QColor GetNextColor(); 19 | 20 | private: 21 | QQueue m_colorLibrary; 22 | void AddColors(const QVector& colors); 23 | }; 24 | #endif // COLORLIBRARY_H 25 | -------------------------------------------------------------------------------- /src/column.h: -------------------------------------------------------------------------------- 1 | #ifndef COLUMN_H 2 | #define COLUMN_H 3 | 4 | enum COL : short { 5 | ID = 0, 6 | File, 7 | Time, 8 | Elapsed, 9 | PID, 10 | TID, 11 | Severity, 12 | Request, 13 | Session, 14 | Site, 15 | User, 16 | Key, 17 | ART, 18 | ErrorCode, 19 | Value 20 | }; 21 | 22 | inline const char* GetColumnName(COL column) { 23 | switch (column) { 24 | case ID: return "ID"; 25 | case File: return "File"; 26 | case Time: return "Time"; 27 | case Elapsed: return "Elapsed"; 28 | case PID: return "PID"; 29 | case TID: return "TID"; 30 | case Severity: return "Severity"; 31 | case Request: return "Request"; 32 | case Session: return "Session"; 33 | case Site: return "Site"; 34 | case User: return "User"; 35 | case Key: return "Key"; 36 | case ART: return "ART"; 37 | case ErrorCode: return "Error Code"; 38 | case Value: return "Value"; 39 | } 40 | return "unknown column"; 41 | } 42 | 43 | #endif // COLUMN_H 44 | -------------------------------------------------------------------------------- /src/filtertab.cpp: -------------------------------------------------------------------------------- 1 | #include "filtertab.h" 2 | #include "ui_filtertab.h" 3 | 4 | #include "highlightdlg.h" 5 | 6 | #include 7 | #include 8 | 9 | FilterTab::FilterTab(QWidget *parent, QString valueText, QColor backgroundColor) : 10 | QWidget(parent), 11 | ui(new Ui::FilterTab) 12 | { 13 | ui->setupUi(this); 14 | ui->checkBoxKey->setChecked(true); 15 | ui->checkBoxValue->setChecked(true); 16 | SetTitle(valueText); 17 | SetBackgroundColor(backgroundColor); 18 | 19 | ui->comboBoxMode->addItem("Contains",SearchMode::Contains); 20 | ui->comboBoxMode->addItem("Equals",SearchMode::Equals); 21 | ui->comboBoxMode->addItem("Starts with",SearchMode::StartsWith); 22 | ui->comboBoxMode->addItem("Ends with",SearchMode::EndsWith); 23 | ui->comboBoxMode->addItem("Regular expression",SearchMode::Regex); 24 | ui->comboBoxMode->setCurrentIndex(ui->comboBoxMode->findData(SearchMode::Contains)); 25 | 26 | // mapping of COL and checkBox widgets 27 | m_mapColToBox = std::map { 28 | {COL::Severity, ui->checkBoxSeverity}, 29 | {COL::TID, ui->checkBoxTID}, 30 | {COL::PID, ui->checkBoxPID}, 31 | {COL::Request, ui->checkBoxRequest}, 32 | {COL::Session, ui->checkBoxSession}, 33 | {COL::Site, ui->checkBoxSite}, 34 | {COL::User, ui->checkBoxUser}, 35 | {COL::Key, ui->checkBoxKey}, 36 | {COL::Value, ui->checkBoxValue}, 37 | {COL::File, ui->checkBoxFile}, 38 | {COL::ART, ui->checkBoxART}, 39 | {COL::ErrorCode, ui->checkBoxErrorCode} 40 | }; 41 | } 42 | 43 | FilterTab::~FilterTab() 44 | { 45 | delete ui; 46 | } 47 | 48 | void FilterTab::SetTitle(const QString& title) 49 | { 50 | ui->groupBoxValue->setTitle(title); 51 | } 52 | 53 | void FilterTab::SetFocusOnFilterLine() 54 | { 55 | ui->filterLineEdit->setFocus(); 56 | } 57 | 58 | void FilterTab::SetBackgroundColor(const QColor& backgroundColor) 59 | { 60 | m_backgroundColor = backgroundColor; 61 | ui->colorDisplay->setStyleSheet("background-color: " + m_backgroundColor.name()); 62 | } 63 | 64 | void FilterTab::HideColorWidget() 65 | { 66 | ui->colorWidget->hide(); 67 | } 68 | 69 | void FilterTab::SetSearchOptions(const SearchOpt& options) 70 | { 71 | ui->filterLineEdit->setText(options.m_value); 72 | ui->comboBoxMode->setCurrentIndex(ui->comboBoxMode->findData(options.m_mode)); 73 | ui->checkBoxCase->setChecked(options.m_matchCase); 74 | for (auto& colToBox : m_mapColToBox) 75 | { 76 | colToBox.second->setChecked(options.m_keys.contains(colToBox.first)); 77 | } 78 | } 79 | 80 | SearchOpt FilterTab::GetSearchOptions() 81 | { 82 | SearchOpt options; 83 | options.m_value = ui->filterLineEdit->text(); 84 | options.m_mode = static_cast(ui->comboBoxMode->currentData().toInt()); 85 | options.m_matchCase = ui->checkBoxCase->isChecked(); 86 | 87 | for (const auto& colToBox : m_mapColToBox) 88 | { 89 | if (colToBox.second->isChecked()) 90 | { 91 | options.m_keys.append(colToBox.first); 92 | } 93 | } 94 | options.m_backgroundColor = m_backgroundColor; 95 | return options; 96 | } 97 | 98 | void FilterTab::keyPressEvent(QKeyEvent * k) 99 | { 100 | QTabWidget *parentTab = (QTabWidget *)this->parentWidget(); 101 | HighlightDlg *parentDlg = (HighlightDlg *)parentTab->parentWidget(); 102 | parentDlg->keyPressEvent(k); 103 | } 104 | 105 | void FilterTab::on_filterLineEdit_textChanged(const QString &text) 106 | { 107 | filterValueChanged(text); 108 | } 109 | 110 | void FilterTab::on_buttonChangeColor_clicked() 111 | { 112 | QColor pickedColor = QColorDialog::getColor(/* initial = */m_backgroundColor, this); 113 | if (pickedColor.isValid()) 114 | { 115 | SetBackgroundColor(pickedColor); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/filtertab.h: -------------------------------------------------------------------------------- 1 | #ifndef FILTERTAB_H 2 | #define FILTERTAB_H 3 | 4 | #include "column.h" 5 | #include "searchopt.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace Ui { 13 | class FilterTab; 14 | } 15 | 16 | class FilterTab : public QWidget 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | explicit FilterTab(QWidget *parent = 0, QString valueText = "", QColor backgroundColor = QColor(255, 255, 255)); 22 | ~FilterTab(); 23 | void SetTitle(const QString& title); 24 | void SetFocusOnFilterLine(); 25 | void SetBackgroundColor(const QColor& backgroundColor); 26 | void HideColorWidget(); 27 | void SetSearchOptions(const SearchOpt& options); 28 | SearchOpt GetSearchOptions(); 29 | 30 | private slots: 31 | void keyPressEvent(QKeyEvent * k); 32 | void on_filterLineEdit_textChanged(const QString &text); 33 | void on_buttonChangeColor_clicked(); 34 | 35 | signals: 36 | void filterValueChanged(const QString& text); 37 | 38 | private: 39 | Ui::FilterTab *ui; 40 | QColor m_backgroundColor; 41 | std::map m_mapColToBox; 42 | }; 43 | 44 | #endif // FILTERTAB_H 45 | -------------------------------------------------------------------------------- /src/filtertab.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FilterTab 4 | 5 | 6 | 7 | 0 8 | 0 9 | 481 10 | 265 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Qt::Horizontal 38 | 39 | 40 | 41 | 40 42 | 20 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | false 51 | 52 | 53 | 54 | 55 | 56 | 57 | Qt::Horizontal 58 | 59 | 60 | 61 | 40 62 | 20 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | &Match case 71 | 72 | 73 | 74 | 75 | 76 | 77 | Qt::Horizontal 78 | 79 | 80 | 81 | 40 82 | 20 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 0 97 | 1 98 | 99 | 100 | 101 | Include these column values in the search: 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | Severit&y 110 | 111 | 112 | 113 | 114 | 115 | 116 | &Process ID 117 | 118 | 119 | 120 | 121 | 122 | 123 | &Thread ID 124 | 125 | 126 | 127 | 128 | 129 | 130 | &File 131 | 132 | 133 | 134 | 135 | 136 | 137 | Qt::Vertical 138 | 139 | 140 | 141 | 20 142 | 40 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | S&ession 155 | 156 | 157 | 158 | 159 | 160 | 161 | &Request 162 | 163 | 164 | 165 | 166 | 167 | 168 | &Site 169 | 170 | 171 | 172 | 173 | 174 | 175 | &ART 176 | 177 | 178 | 179 | 180 | 181 | 182 | Qt::Vertical 183 | 184 | 185 | 186 | 20 187 | 40 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | &User 200 | 201 | 202 | 203 | 204 | 205 | 206 | &Key 207 | 208 | 209 | 210 | 211 | 212 | 213 | &Value 214 | 215 | 216 | 217 | 218 | 219 | 220 | Err&or Code 221 | 222 | 223 | 224 | 225 | 226 | 227 | Qt::Vertical 228 | 229 | 230 | 231 | 20 232 | 40 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 0 247 | 248 | 249 | 0 250 | 251 | 252 | 0 253 | 254 | 255 | 0 256 | 257 | 258 | 259 | 260 | Qt::Horizontal 261 | 262 | 263 | 264 | 40 265 | 20 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | QFrame::StyledPanel 274 | 275 | 276 | 277 | 0 278 | 279 | 280 | 0 281 | 282 | 283 | 0 284 | 285 | 286 | 0 287 | 288 | 289 | 0 290 | 291 | 292 | 293 | 294 | 295 | 0 296 | 0 297 | 298 | 299 | 300 | 301 | 24 302 | 24 303 | 304 | 305 | 306 | background-color: green; 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | Change filter &color 317 | 318 | 319 | 320 | 321 | 322 | 323 | Qt::Horizontal 324 | 325 | 326 | 327 | 40 328 | 20 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | -------------------------------------------------------------------------------- /src/finddlg.cpp: -------------------------------------------------------------------------------- 1 | #include "finddlg.h" 2 | #include "ui_finddlg.h" 3 | 4 | #include "themeutils.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | FindDlg::FindDlg(QWidget *parent, SearchOpt findOpts) : 12 | QDialog(parent), 13 | ui(new Ui::FindDlg) 14 | { 15 | ui->setupUi(this); 16 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 17 | 18 | if(findOpts.m_keys.count() > 0 && findOpts.m_value != "") 19 | { 20 | ConstructTab(findOpts); 21 | } 22 | else 23 | { 24 | SearchOpt blankOpt; 25 | blankOpt.m_keys.append(COL::Key); 26 | blankOpt.m_keys.append(COL::Value); 27 | ConstructTab(blankOpt); 28 | } 29 | 30 | ui->prevButton->setText(""); 31 | ui->nextButton->setText(""); 32 | ui->prevButton->setIcon(QIcon(ThemeUtils::GetThemedIcon(":/value-previous.png"))); 33 | ui->nextButton->setIcon(QIcon(ThemeUtils::GetThemedIcon(":/value-next.png"))); 34 | ui->prevButton->setShortcut(QKeySequence(Qt::SHIFT | Qt::Key_F3)); 35 | ui->nextButton->setShortcut(QKeySequence(Qt::Key_F3)); 36 | 37 | connect(this, &QDialog::accepted, this, &FindDlg::accepted); 38 | } 39 | 40 | FindDlg::~FindDlg() 41 | { 42 | delete ui; 43 | } 44 | 45 | void FindDlg::ConstructTab(SearchOpt options) 46 | { 47 | // Populate the tab based on the given filter 48 | ui->filterTab->SetTitle("Find log event values that contains substring:"); 49 | ui->filterTab->SetSearchOptions(options); 50 | ui->filterTab->SetFocusOnFilterLine(); 51 | ui->filterTab->HideColorWidget(); 52 | } 53 | 54 | void FindDlg::UpdateFindOptions() 55 | { 56 | SearchOpt newSearchOpt = ui->filterTab->GetSearchOptions(); 57 | if(!newSearchOpt.m_value.isEmpty() && !newSearchOpt.m_keys.isEmpty()) 58 | { 59 | m_findOpts = newSearchOpt; 60 | } 61 | } 62 | 63 | void FindDlg::accepted() 64 | { 65 | UpdateFindOptions(); 66 | } 67 | 68 | void FindDlg::on_nextButton_clicked() 69 | { 70 | UpdateFindOptions(); 71 | emit next(); 72 | } 73 | 74 | void FindDlg::on_prevButton_clicked() 75 | { 76 | UpdateFindOptions(); 77 | emit prev(); 78 | } 79 | -------------------------------------------------------------------------------- /src/finddlg.h: -------------------------------------------------------------------------------- 1 | #ifndef FINDDLG_H 2 | #define FINDDLG_H 3 | 4 | #include "filtertab.h" 5 | #include "searchopt.h" 6 | 7 | #include 8 | 9 | namespace Ui { 10 | class FindDlg; 11 | } 12 | 13 | class FindDlg : public QDialog 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | FindDlg(QWidget *parent, SearchOpt findOpts); 19 | ~FindDlg(); 20 | 21 | SearchOpt m_findOpts; 22 | 23 | private slots: 24 | void accepted(); 25 | void on_prevButton_clicked(); 26 | void on_nextButton_clicked(); 27 | 28 | signals: 29 | void next(); 30 | void prev(); 31 | 32 | private: 33 | void ConstructTab(SearchOpt option); 34 | void UpdateFindOptions(); 35 | 36 | Ui::FindDlg *ui; 37 | }; 38 | 39 | #endif // FINDDLG_H 40 | -------------------------------------------------------------------------------- /src/finddlg.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FindDlg 4 | 5 | 6 | 7 | 0 8 | 0 9 | 375 10 | 260 11 | 12 | 13 | 14 | Find 15 | 16 | 17 | 18 | 3 19 | 20 | 21 | 3 22 | 23 | 24 | 3 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 9 33 | 34 | 35 | 9 36 | 37 | 38 | 39 | 40 | Find previous 41 | 42 | 43 | P 44 | 45 | 46 | 47 | 48 | 49 | 50 | Find next 51 | 52 | 53 | N 54 | 55 | 56 | 57 | 58 | 59 | 60 | Qt::Horizontal 61 | 62 | 63 | 64 | 40 65 | 20 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | QDialogButtonBox::Close|QDialogButtonBox::Ok 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | FilterTab 84 | QWidget 85 |
filtertab.h
86 | 1 87 |
88 |
89 | 90 | 91 | 92 | buttonBox 93 | accepted() 94 | FindDlg 95 | accept() 96 | 97 | 98 | 286 99 | 191 100 | 101 | 102 | 187 103 | 129 104 | 105 | 106 | 107 | 108 | buttonBox 109 | rejected() 110 | FindDlg 111 | reject() 112 | 113 | 114 | 286 115 | 191 116 | 117 | 118 | 187 119 | 129 120 | 121 | 122 | 123 | 124 |
125 | -------------------------------------------------------------------------------- /src/highlightdlg.cpp: -------------------------------------------------------------------------------- 1 | #include "highlightdlg.h" 2 | #include "ui_highlightdlg.h" 3 | #include "column.h" 4 | #include "themeutils.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | HighlightDlg::HighlightDlg(QWidget *parent, HighlightOptions highlightOpts, const ColorLibrary& colorLibrary) : 19 | QDialog(parent), 20 | m_colorLibrary(colorLibrary), 21 | ui(new Ui::HighlightDlg) 22 | { 23 | ui->setupUi(this); 24 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 25 | 26 | // Remove close button of the Help tab. 27 | ui->tabWidget->tabBar()->tabButton(0, QTabBar::RightSide)->resize(0, 0); 28 | 29 | // Create a button to Add new highlight filters 30 | QToolButton* newTabButton = new QToolButton(this); 31 | ui->tabWidget->setCornerWidget(newTabButton); 32 | newTabButton->setToolTip("Add filter"); 33 | newTabButton->setIcon(QIcon(ThemeUtils::GetThemedIcon(":/ctx-add.png"))); 34 | connect(newTabButton, &QToolButton::clicked, this, &HighlightDlg::AddNewTab); 35 | 36 | connect(this, &QDialog::accepted, this, &HighlightDlg::accepted); 37 | BuildTabs(highlightOpts); 38 | } 39 | 40 | void HighlightDlg::BuildTabs(const HighlightOptions& highlightOpts) 41 | { 42 | if(highlightOpts.count() == 0) 43 | { 44 | AddNewTab(); 45 | } 46 | for(auto option : highlightOpts) 47 | { 48 | ConstructTab(option); 49 | } 50 | } 51 | 52 | void HighlightDlg::keyPressEvent(QKeyEvent * k) 53 | { 54 | if((k->key() == Qt::Key_T || k->key() == Qt::Key_N) && (QApplication::keyboardModifiers() & Qt::ControlModifier)) 55 | { 56 | AddNewTab(); 57 | } 58 | else if((k->key() == Qt::Key_W || k->key() == Qt::Key_F4) && (QApplication::keyboardModifiers() & Qt::ControlModifier)) 59 | { 60 | CloseCurrentTab(); 61 | } 62 | else if(k->key() == Qt::Key_Enter || k->key() == Qt::Key_Return) 63 | { 64 | accept(); 65 | } 66 | else 67 | { 68 | QDialog::keyPressEvent(k); 69 | } 70 | } 71 | 72 | void HighlightDlg::ConstructTab(SearchOpt option) 73 | { 74 | // Populate the tab based on the given filter 75 | auto filterTab = new FilterTab(nullptr, m_filterTabValueLabel, option.m_backgroundColor); 76 | ui->tabWidget->addTab(filterTab, option.m_value.left(m_tabLabelSize)); 77 | ui->tabWidget->setCurrentIndex(ui->tabWidget->indexOf(filterTab)); 78 | filterTab->SetSearchOptions(option); 79 | connect(filterTab, &FilterTab::filterValueChanged, this, &HighlightDlg::TextChanged); 80 | } 81 | 82 | void HighlightDlg::AddNewTab() 83 | { 84 | auto newFilter = new FilterTab(nullptr, m_filterTabValueLabel); 85 | ui->tabWidget->addTab(newFilter, " "); 86 | ui->tabWidget->setCurrentIndex(ui->tabWidget->indexOf(newFilter)); 87 | newFilter->SetBackgroundColor(m_colorLibrary.GetNextColor()); 88 | 89 | connect(newFilter, &FilterTab::filterValueChanged, this, &HighlightDlg::TextChanged); 90 | } 91 | 92 | void HighlightDlg::TextChanged(const QString & text) 93 | { 94 | ui->tabWidget->setTabText(ui->tabWidget->currentIndex(), text.left(m_tabLabelSize)); 95 | } 96 | 97 | void HighlightDlg::CloseCurrentTab() 98 | { 99 | CloseTab(ui->tabWidget->currentIndex()); 100 | } 101 | 102 | void HighlightDlg::CloseTab(int index) 103 | { 104 | auto filterTab = dynamic_cast(ui->tabWidget->widget(index)); 105 | if (!filterTab) 106 | { 107 | return; 108 | } 109 | ui->tabWidget->removeTab(index); 110 | } 111 | 112 | void HighlightDlg::on_tabWidget_currentChanged(int index) 113 | { 114 | auto filterTab = dynamic_cast(ui->tabWidget->widget(index)); 115 | if (!filterTab) 116 | { 117 | return; 118 | } 119 | filterTab->SetFocusOnFilterLine(); 120 | } 121 | 122 | void HighlightDlg::on_tabWidget_tabCloseRequested(int index) 123 | { 124 | CloseTab(index); 125 | } 126 | 127 | void HighlightDlg::accepted() 128 | { 129 | m_highlightOpts.clear(); 130 | for (int i = 0; i < ui->tabWidget->count(); i++) 131 | { 132 | auto filterTab = dynamic_cast(ui->tabWidget->widget(i)); 133 | if (!filterTab) 134 | continue; 135 | SearchOpt newSearchOpt = filterTab->GetSearchOptions(); 136 | if(!newSearchOpt.m_value.isEmpty() && !newSearchOpt.m_keys.isEmpty()) 137 | { 138 | m_highlightOpts.append(newSearchOpt); 139 | } 140 | } 141 | } 142 | 143 | HighlightDlg::~HighlightDlg() 144 | { 145 | delete ui; 146 | } 147 | -------------------------------------------------------------------------------- /src/highlightdlg.h: -------------------------------------------------------------------------------- 1 | #ifndef HIGHLIGHTDLG_H 2 | #define HIGHLIGHTDLG_H 3 | 4 | #include "colorlibrary.h" 5 | #include "filtertab.h" 6 | #include "highlightoptions.h" 7 | #include "searchopt.h" 8 | 9 | #include 10 | #include 11 | 12 | namespace Ui { 13 | class HighlightDlg; 14 | } 15 | 16 | class HighlightDlg : public QDialog 17 | { 18 | Q_OBJECT 19 | 20 | public: 21 | HighlightDlg(QWidget *parent, HighlightOptions highlightOpts, const ColorLibrary& colorLibrary); 22 | ~HighlightDlg(); 23 | void keyPressEvent(QKeyEvent * k); 24 | 25 | HighlightOptions m_highlightOpts; 26 | ColorLibrary m_colorLibrary; 27 | 28 | private slots: 29 | void on_tabWidget_tabCloseRequested(int index); 30 | void on_tabWidget_currentChanged(int index); 31 | void TextChanged(const QString & text); 32 | 33 | private: 34 | void BuildTabs(const HighlightOptions& highlightOpts); 35 | void AddNewTab(); 36 | void ConstructTab(SearchOpt options); 37 | void CloseCurrentTab(); 38 | void CloseTab(int index); 39 | void accepted(); 40 | 41 | Ui::HighlightDlg *ui; 42 | const QString m_filterTabValueLabel = "Highlight &log event values that contains substring:"; 43 | const int m_tabLabelSize = 20; 44 | }; 45 | 46 | #endif // HIGHLIGHTDLG_H 47 | -------------------------------------------------------------------------------- /src/highlightdlg.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | HighlightDlg 4 | 5 | 6 | 7 | 0 8 | 0 9 | 440 10 | 300 11 | 12 | 13 | 14 | Highlight Filters 15 | 16 | 17 | 18 | 19 | 20 | QTabBar::close-button { 21 | image: url(:/tab-close.png); 22 | subcontrol-position: right; 23 | margin-bottom: -2px; 24 | } 25 | QTabBar::close-button:hover { 26 | image: url(:/tab-close-red.png); 27 | } 28 | QTabWidget::tab-bar { 29 | left: 0px; 30 | } 31 | 32 | 33 | QTabWidget::Rounded 34 | 35 | 36 | 0 37 | 38 | 39 | true 40 | 41 | 42 | true 43 | 44 | 45 | 46 | Help 47 | 48 | 49 | 50 | 51 | 52 | <html><head/><body><p><span style=" font-size:10pt;">- To add a new filter, press Ctrl/Cmd + T or Ctrl/Cmd + N</span></p><p><span style=" font-size:10pt;">- To delete a filter, press Ctrl/Cmd + W or Ctrl/Cmd + F4</span></p><p><span style=" font-size:10pt;">- Newer tabs (further right) have a higher priority</span></p><p><span style=" font-size:10pt;">- Drag and drop tabs to re-order and change filter priority</span></p></body></html> 53 | 54 | 55 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | Qt::Horizontal 67 | 68 | 69 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 70 | 71 | 72 | true 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | buttonBox 82 | accepted() 83 | HighlightDlg 84 | accept() 85 | 86 | 87 | 248 88 | 254 89 | 90 | 91 | 157 92 | 274 93 | 94 | 95 | 96 | 97 | buttonBox 98 | rejected() 99 | HighlightDlg 100 | reject() 101 | 102 | 103 | 316 104 | 260 105 | 106 | 107 | 286 108 | 274 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /src/highlightoptions.cpp: -------------------------------------------------------------------------------- 1 | #include "highlightoptions.h" 2 | 3 | HighlightOptions::HighlightOptions() 4 | { 5 | } 6 | 7 | HighlightOptions::HighlightOptions(const QJsonArray& json) 8 | { 9 | FromJson(json); 10 | } 11 | 12 | QJsonArray HighlightOptions::ToJson() 13 | { 14 | QJsonArray json; 15 | for (SearchOpt& highlightFilter : *this) 16 | { 17 | json.append(highlightFilter.ToJson()); 18 | } 19 | return json; 20 | } 21 | 22 | void HighlightOptions::FromJson(const QJsonArray& json) 23 | { 24 | clear(); 25 | for (const auto& filterJson : json) 26 | { 27 | SearchOpt highlightFilter; 28 | highlightFilter.FromJson(filterJson.toObject()); 29 | this->append(highlightFilter); 30 | } 31 | } 32 | 33 | QVector HighlightOptions::GetColors() 34 | { 35 | QVector result; 36 | for (const auto &searchOpt : *this) 37 | result.push_back(searchOpt.m_backgroundColor); 38 | return result; 39 | } 40 | -------------------------------------------------------------------------------- /src/highlightoptions.h: -------------------------------------------------------------------------------- 1 | #ifndef HIGHLIGHTOPTIONS_H 2 | #define HIGHLIGHTOPTIONS_H 3 | 4 | #include "searchopt.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class HighlightOptions : public QVector 11 | { 12 | public: 13 | HighlightOptions(); 14 | HighlightOptions(const QJsonArray& json); 15 | QJsonArray ToJson(); 16 | void FromJson(const QJsonArray& json); 17 | QVector GetColors(); 18 | }; 19 | 20 | #endif // HIGHLIGHTOPTIONS_H 21 | -------------------------------------------------------------------------------- /src/logtab.h: -------------------------------------------------------------------------------- 1 | #ifndef LOGTAB_H 2 | #define LOGTAB_H 3 | 4 | #include "options.h" 5 | #include "statusbar.h" 6 | #include "treemodel.h" 7 | #include "valuedlg.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | 14 | namespace Ui { 15 | class LogTab; 16 | } 17 | 18 | class LogTab : public QWidget 19 | { 20 | Q_OBJECT 21 | 22 | public: 23 | explicit LogTab(QWidget *parent, StatusBar *bar, const EventListPtr events); 24 | ~LogTab(); 25 | bool StartLiveCapture(); 26 | void EndLiveCapture(); 27 | QString GetTabPath() const; 28 | void SetTabPath(const QString& path); 29 | void UpdateStatusBar(); 30 | void CopyFullPath(); 31 | void ShowInFolder(); 32 | void RefilterTreeView(); 33 | TreeModel* GetTreeModel(); 34 | QTreeView* GetTreeView(); 35 | 36 | private: 37 | void keyPressEvent(QKeyEvent *event) override; 38 | 39 | void InitTreeView(const EventListPtr events); 40 | void InitMenus(); 41 | void InitOneRowMenu(); 42 | void InitTwoRowsMenu(); 43 | void InitMultipleRowsMenu(); 44 | void InitHeaderMenu(); 45 | void SetColumn(COL column, int width, bool isHidden); 46 | void ShowItemDetails(const QModelIndex&); 47 | void CopyItemDetails(bool textOnly, bool normalized) const; 48 | void CopyItemDetailsAsHtml() const; 49 | void CopyItemDetailsAsText() const; 50 | void CopyItemDetailsAsNormalizedText() const; 51 | void RowFindPrev(); 52 | void RowFindNext(); 53 | void RowFindImpl(int offset); 54 | void ShowDetails(const QModelIndex& idx, ValueDlg& valueDlg); 55 | void ReadFile(); 56 | void ReadDirectoryFiles(); 57 | void SetUpTimer(); 58 | void SetUpFile(std::shared_ptr file); 59 | void UpdateModelView(); 60 | void TrimEventCount(); 61 | bool StartFileLiveCapture(); 62 | void StartDirectoryLiveCapture(); 63 | QString GetDebugInfo() const; 64 | 65 | Ui::LogTab *ui; 66 | StatusBar *m_bar; 67 | TreeModel *m_treeModel; 68 | QMenu *m_oneRowMenu; 69 | QMenu *m_twoRowsMenu; 70 | QMenu *m_multipleRowsMenu; 71 | QMenu *m_headerMenu; 72 | ValueDlg *m_valueDlg; 73 | std::vector> m_tempFiles; 74 | QAction *m_hideSelectedEvent; 75 | QAction *m_hideSelectedType; 76 | QAction *m_highlightSelectedType; 77 | QMenu *m_highlightSelectedMenu; 78 | QMenu *m_exportToTabMenu; 79 | QAction *m_exportToTabAction; 80 | QAction *m_copyItemsHtmlAction; 81 | QAction *m_copyItemsTextAction; 82 | QAction *m_copyItemsNormalizedTextAction; 83 | QAction *m_showGlobalDateTime; 84 | QAction *m_showGlobalTime; 85 | QAction *m_showTimeDeltas; 86 | int m_openFileMenuIdx; 87 | int m_eventIndex; 88 | QFile m_logFile; 89 | QTimer m_timer; 90 | std::unique_ptr m_liveDirectory; 91 | QHash> m_directoryFiles; 92 | QList m_excludedFileNames; 93 | QString m_tabPath; 94 | 95 | private slots: 96 | void RowDoubleClicked(const QModelIndex& idx); 97 | void RowRightClicked(const QPoint& pos); 98 | void RowDiffEvents(); 99 | void RowHideSelected(); 100 | void RowHideSelectedType(); 101 | void RowFindNextSelectedType(); 102 | void RowFindPrevSelectedType(); 103 | void RowHighlightSelected(COL column); 104 | void RowShowGlobalDateTime(); 105 | void RowShowGlobalTime(); 106 | void RowShowTimeDeltas(); 107 | void HeaderRightClicked(const QPoint& pos); 108 | void HeaderItemSelected(QAction* action); 109 | void ChangeNextIndex(); 110 | void ChangePrevIndex(); 111 | void ExportToNewTab(); 112 | void ExportToNewTab(COL column); 113 | void OpenSelectedFile(); 114 | 115 | signals: 116 | void menuUpdateNeeded(); 117 | void exportToTab(QModelIndexList list, QString name); 118 | void openFile(QString); 119 | }; 120 | 121 | #endif // LOGTAB_H 122 | -------------------------------------------------------------------------------- /src/logtab.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | LogTab 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 31 | 32 | Qt::ActionsContextMenu 33 | 34 | 35 | QTreeView:focus { 36 | outline: none; 37 | } 38 | QTreeView::branch:has-siblings:!adjoins-item { 39 | border-image: url(:/branch-vline.png) 0; 40 | } 41 | QTreeView::branch:has-siblings:adjoins-item { 42 | border-image: url(:/branch-more.png) 0; 43 | } 44 | QTreeView::branch:!has-children:!has-siblings:adjoins-item { 45 | border-image: url(:/branch-end.png) 0; 46 | } 47 | QTreeView::branch:has-children:!has-siblings:closed, QTreeView::branch:closed:has-children:has-siblings { 48 | border-image: none; 49 | image: url(:/branch-closed.png); 50 | } 51 | QTreeView::branch:open:has-children:!has-siblings, QTreeView::branch:open:has-children:has-siblings { 52 | border-image: none; 53 | image: url(:/branch-open.png); 54 | } 55 | 56 | 57 | QFrame::NoFrame 58 | 59 | 60 | QFrame::Plain 61 | 62 | 63 | 1 64 | 65 | 66 | Qt::ScrollBarAlwaysOn 67 | 68 | 69 | Qt::ScrollBarAsNeeded 70 | 71 | 72 | true 73 | 74 | 75 | QAbstractItemView::NoEditTriggers 76 | 77 | 78 | true 79 | 80 | 81 | false 82 | 83 | 84 | false 85 | 86 | 87 | true 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | ZoomableTreeView 96 | QTreeWidget 97 |
zoomabletreeview.h
98 |
99 |
100 | 101 | 102 | 103 | RowRightClicked() 104 | 105 |
106 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "mainwindow.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | Q_INIT_RESOURCE(resources); 8 | 9 | QApplication app(argc, argv); 10 | app.setOrganizationName("Tableau"); 11 | app.setApplicationName("TLV"); 12 | app.setApplicationVersion(APP_VERSION); 13 | 14 | std::unique_ptr mainWin = std::make_unique(); 15 | mainWin->show(); 16 | return app.exec(); 17 | } 18 | -------------------------------------------------------------------------------- /src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include "logtab.h" 5 | #include "statusbar.h" 6 | #include "treemodel.h" 7 | #include "ui_mainwindow.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include // Is QQueue a thread safe queue? For that matter, check if we need one for the interactions between client and mainwindow 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | class MainWindow : public QMainWindow, private Ui::MainWindow 24 | { 25 | Q_OBJECT 26 | 27 | public: 28 | MainWindow(); 29 | ~MainWindow(); 30 | 31 | protected: 32 | void closeEvent(QCloseEvent *event) override; 33 | void dragEnterEvent(QDragEnterEvent *event) override; 34 | void dropEvent(QDropEvent *event) override; 35 | void keyPressEvent(QKeyEvent * k) override; 36 | bool eventFilter(QObject *watched, QEvent *event) override; 37 | 38 | private slots: 39 | void Recent_files_triggered(QAction * action); 40 | void UpdateTheme(); 41 | void UpdateMenuAndStatusBar(); 42 | void ExportEventsToTab(QModelIndexList list, QString name); 43 | bool LoadLogFile(QString); 44 | 45 | //Slots use underscores as per QT's automatic connection syntax 46 | //File 47 | void on_actionOpen_in_new_tab_triggered(); 48 | void on_actionOpen_log_txt_triggered(); 49 | void on_actionOpen_beta_log_txt_triggered(); 50 | void on_actionMerge_into_tab_triggered(); 51 | void on_actionRefresh_triggered(); 52 | void on_actionShow_summary_triggered(); 53 | void on_actionCreate_info_viz_triggered(); 54 | void on_actionSave_filters_triggered(); 55 | void on_menuLoad_filters_aboutToShow(); 56 | void on_menuLoad_filters_triggered(QAction * action); 57 | void on_actionClose_tab_triggered(); 58 | void on_actionClose_all_tabs_triggered(); 59 | void on_actionExit_triggered(); 60 | //Recent files 61 | void on_actionClear_Recent_Files_triggered(); 62 | //Highlight 63 | void on_actionHighlight_triggered(); 64 | void on_actionFind_next_highlighted_triggered(); 65 | void on_actionFind_previous_highlighted_triggered(); 66 | void on_actionHighlight_only_mode_triggered(); 67 | //Find 68 | void on_actionFind_triggered(); 69 | void on_actionFind_next_triggered(); 70 | void on_actionFind_previous_triggered(); 71 | 72 | void on_actionOptions_triggered(); 73 | void on_tabWidget_currentChanged(int index); 74 | void on_tabWidget_tabCloseRequested(int index); 75 | 76 | void on_actionTail_current_tab_triggered(); 77 | void on_actionClear_all_events_triggered(); 78 | 79 | void on_actionLog_directory_triggered(); 80 | void on_actionBeta_log_directory_triggered(); 81 | void on_actionChoose_directory_triggered(); 82 | 83 | private: 84 | void WriteSettings(); 85 | void ReadSettings(); 86 | 87 | EventListPtr GetEventsFromFile(QString path, int & skippedCount); 88 | 89 | TreeModel * GetCurrentTreeModel(); 90 | QTreeView * GetCurrentTreeView(); 91 | LogTab * GetCurrentLogTab(); 92 | LogTab * GetLogTab(int index); 93 | 94 | QString GetOpenDefaultFolder(); 95 | QStringList PickLogFilesToOpen(QString caption); 96 | void UpdateRecentFilesMenu(); 97 | void ClearRecentFileMenu(); 98 | void AddRecentFile(const QString& path); 99 | void RemoveRecentFile(const QString& path); 100 | 101 | void MergeLogFile(QString path); 102 | void FindPrev(); 103 | void FindNext(); 104 | void FindPrevH(); 105 | void FindNextH(); 106 | void FindImpl(int offset, bool findHighlight); 107 | 108 | void StartDirectoryLiveCapture(QString directoryPath, QString label); 109 | void FocusOpenedFile(QString path); 110 | LogTab* SetUpTab(EventListPtr events, bool isDirectory, QString path, QString label); 111 | 112 | Options& m_options = Options::GetInstance(); 113 | StatusBar * m_statusBar; 114 | QStringList m_recentFiles; 115 | QString m_lastOpenFolder; 116 | 117 | // m_liveFiles is used to store all the files that have been opened/are open in the MainWindow. 118 | // If a user opens a new tab, this structure is used to check the file path of the file being loaded 119 | // in a new tab. If the file is open in another tab, the new tab cannot be opened. 120 | QList m_allFiles; 121 | }; 122 | 123 | #endif // MAINWINDOW_H 124 | -------------------------------------------------------------------------------- /src/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 900 10 | 500 11 | 12 | 13 | 14 | true 15 | 16 | 17 | Tableau Log Viewer 18 | 19 | 20 | 21 | 18 22 | 18 23 | 24 | 25 | 26 | Qt::ToolButtonTextUnderIcon 27 | 28 | 29 | QTabWidget::Rounded 30 | 31 | 32 | 33 | 34 | 0 35 | 36 | 37 | 0 38 | 39 | 40 | 0 41 | 42 | 43 | 0 44 | 45 | 46 | 47 | 48 | Qt::CustomContextMenu 49 | 50 | 51 | QTabBar::close-button { 52 | image: url(:/tab-close.png); 53 | subcontrol-position: right; 54 | margin-bottom: -2px; 55 | } 56 | QTabBar::close-button:hover { 57 | image: url(:/tab-close-red.png); 58 | } 59 | QTabWidget::tab-bar { 60 | left: 0px; 61 | } 62 | 63 | 64 | 65 | QTabWidget::North 66 | 67 | 68 | QTabWidget::Rounded 69 | 70 | 71 | Qt::ElideRight 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | true 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 0 93 | 0 94 | 95 | 96 | 97 | false 98 | 99 | 100 | 101 | 102 | 103 | 0 104 | 0 105 | 900 106 | 21 107 | 108 | 109 | 110 | 111 | &File 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | &Recent files 129 | 130 | 131 | 132 | 133 | &Highlight 134 | 135 | 136 | 137 | Load &filters 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | Fin&d 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | Hel&p 160 | 161 | 162 | 163 | 164 | 165 | &Live capture 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | toolBar 186 | 187 | 188 | TopToolBarArea 189 | 190 | 191 | false 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | :/ctx-open.png:/ctx-open.png 205 | 206 | 207 | &Open in new tab... 208 | 209 | 210 | Open 211 | 212 | 213 | Ctrl+O 214 | 215 | 216 | 217 | 218 | false 219 | 220 | 221 | 222 | :/ctx-open-merge.png:/ctx-open-merge.png 223 | 224 | 225 | &Merge into tab... 226 | 227 | 228 | Ctrl+Shift+O 229 | 230 | 231 | 232 | 233 | false 234 | 235 | 236 | 237 | :/ctx-refresh.png:/ctx-refresh.png 238 | 239 | 240 | &Refresh 241 | 242 | 243 | F5 244 | 245 | 246 | 247 | 248 | false 249 | 250 | 251 | &Save filters... 252 | 253 | 254 | 255 | 256 | false 257 | 258 | 259 | &Close tab 260 | 261 | 262 | Ctrl+F4 263 | 264 | 265 | 266 | 267 | false 268 | 269 | 270 | Close &all tabs 271 | 272 | 273 | Ctrl+Shift+F4 274 | 275 | 276 | 277 | 278 | E&xit 279 | 280 | 281 | 282 | 283 | &Highlight... 284 | 285 | 286 | Ctrl+G 287 | 288 | 289 | 290 | 291 | false 292 | 293 | 294 | Find &next highlighted 295 | 296 | 297 | F4 298 | 299 | 300 | 301 | 302 | false 303 | 304 | 305 | Find &previous highlighted 306 | 307 | 308 | Shift+F4 309 | 310 | 311 | 312 | 313 | true 314 | 315 | 316 | false 317 | 318 | 319 | 320 | :/ctx-highlight.png:/ctx-highlight.png 321 | 322 | 323 | Highlight &only mode 324 | 325 | 326 | Ctrl+J 327 | 328 | 329 | 330 | 331 | 332 | :/ctx-find.png:/ctx-find.png 333 | 334 | 335 | &Find... 336 | 337 | 338 | Ctrl+F 339 | 340 | 341 | 342 | 343 | false 344 | 345 | 346 | Find &next 347 | 348 | 349 | F3 350 | 351 | 352 | 353 | 354 | false 355 | 356 | 357 | Find &previous 358 | 359 | 360 | Shift+F3 361 | 362 | 363 | 364 | 365 | &Options... 366 | 367 | 368 | 369 | 370 | Clear recent files 371 | 372 | 373 | 374 | 375 | placeholder 376 | 377 | 378 | 379 | 380 | true 381 | 382 | 383 | 384 | :/ctx-livecapture.png:/ctx-livecapture.png 385 | 386 | 387 | Live &mode 388 | 389 | 390 | Ctrl+Shift+L 391 | 392 | 393 | 394 | 395 | 396 | :/ctx-clear.png:/ctx-clear.png 397 | 398 | 399 | Clear all &events 400 | 401 | 402 | Clear events 403 | 404 | 405 | Ctrl+Shift+C 406 | 407 | 408 | 409 | 410 | &Open log.txt 411 | 412 | 413 | 414 | 415 | Open (beta) log.&txt 416 | 417 | 418 | 419 | 420 | 421 | :/ctx-summary.png:/ctx-summary.png 422 | 423 | 424 | Show &summary 425 | 426 | 427 | Ctrl+M 428 | 429 | 430 | 431 | 432 | &Log directory 433 | 434 | 435 | Ctrl+L 436 | 437 | 438 | 439 | 440 | &Beta log directory 441 | 442 | 443 | Ctrl+B 444 | 445 | 446 | 447 | 448 | Choose &directory... 449 | 450 | 451 | 452 | 453 | Create &info viz 454 | 455 | 456 | Create visualization workbook for query analysis 457 | 458 | 459 | Ctrl+I 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | -------------------------------------------------------------------------------- /src/options.cpp: -------------------------------------------------------------------------------- 1 | #include "options.h" 2 | #include "pathhelper.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | 12 | void Options::ReadSettings() 13 | { 14 | QString iniPath = PathHelper::GetConfigIniPath(); 15 | QSettings settings(iniPath, QSettings::IniFormat); 16 | settings.beginGroup("Options"); 17 | 18 | QStringList defaultSkip = {"dll-version-info", "ds-interpret-metadata"}; 19 | m_skippedText = settings.value("skippedText", defaultSkip).toStringList(); 20 | m_skippedState = settings.value("skippedState", QBitArray(m_skippedText.length(), true)).toBitArray(); 21 | m_visualizationServiceEnable = settings.value("visualizationServiceEnable", false).toBool(); 22 | m_visualizationServiceURL = settings.value("visualizationServiceURL", QString("")).toString(); 23 | auto defaultDiffToolPath = QSysInfo::productType() == "windows" ? QString("C:/Program Files (x86)/Beyond Compare 4/BCompare.exe") : QString("/usr/local/bin/bcomp"); 24 | m_diffToolPath = settings.value("diffToolPath", defaultDiffToolPath).toString(); 25 | m_futureTabsUnderLive = settings.value("enableLiveCapture").toBool(); 26 | m_defaultFilterName = settings.value("defaultHighlightFilter", "None").toString(); 27 | m_captureAllTextFiles = settings.value("liveCaptureAllTextFiles", true).toBool(); 28 | m_showArtDataInValue = settings.value("showArtDataInValue", false).toBool(); 29 | m_showErrorCodeInValue = settings.value("showErrorCodeInValue", false).toBool(); 30 | m_syntaxHighlightLimit = settings.value("syntaxHighlightLimit", 15000).toInt(); 31 | m_theme = settings.value("theme", "Native").toString(); 32 | m_notation = settings.value("notation", "YAML").toString(); 33 | 34 | settings.endGroup(); 35 | 36 | if(m_defaultFilterName == "None") 37 | { 38 | m_defaultHighlightOpts.clear(); 39 | } 40 | else 41 | { 42 | LoadHighlightFilter(m_defaultFilterName); 43 | } 44 | } 45 | 46 | void Options::WriteSettings() 47 | { 48 | QString iniPath = PathHelper::GetConfigIniPath(); 49 | QSettings settings(iniPath, QSettings::IniFormat); 50 | 51 | settings.beginGroup("Options"); 52 | settings.setValue("skippedText", m_skippedText); 53 | settings.setValue("skippedState", m_skippedState); 54 | settings.setValue("visualizationServiceEnable", m_visualizationServiceEnable); 55 | settings.setValue("visualizationServiceURL", m_visualizationServiceURL); 56 | settings.setValue("diffToolPath", m_diffToolPath); 57 | settings.setValue("enableLiveCapture", m_futureTabsUnderLive); 58 | settings.setValue("liveCaptureAllTextFiles", m_captureAllTextFiles); 59 | settings.setValue("showArtDataInValue", m_showArtDataInValue); 60 | settings.setValue("showErrorCodeInValue", m_showErrorCodeInValue); 61 | settings.setValue("defaultHighlightFilter", m_defaultFilterName); 62 | settings.setValue("syntaxHighlightLimit", m_syntaxHighlightLimit); 63 | settings.setValue("theme", m_theme); 64 | settings.setValue("notation", m_notation); 65 | settings.endGroup(); 66 | } 67 | 68 | QStringList Options::getSkippedText() const 69 | { 70 | return m_skippedText; 71 | } 72 | 73 | void Options::setSkippedText(const QStringList &skippedText) 74 | { 75 | m_skippedText = skippedText; 76 | } 77 | 78 | QBitArray Options::getSkippedState() const 79 | { 80 | return m_skippedState; 81 | } 82 | 83 | void Options::setSkippedState(const QBitArray &skippedState) 84 | { 85 | m_skippedState = skippedState; 86 | } 87 | 88 | bool Options::getVisualizationServiceEnable() const 89 | { 90 | return m_visualizationServiceEnable; 91 | } 92 | 93 | void Options::setVisualizationServiceEnable(const bool visualizationServiceEnable) 94 | { 95 | m_visualizationServiceEnable = visualizationServiceEnable; 96 | } 97 | 98 | QString Options::getVisualizationServiceURL() const 99 | { 100 | return m_visualizationServiceURL; 101 | } 102 | 103 | void Options::setVisualizationServiceURL(const QString &visualizationServiceURL) 104 | { 105 | m_visualizationServiceURL = visualizationServiceURL; 106 | } 107 | 108 | QString Options::getDiffToolPath() const 109 | { 110 | return m_diffToolPath; 111 | } 112 | 113 | void Options::setDiffToolPath(const QString &diffToolPath) 114 | { 115 | m_diffToolPath = diffToolPath; 116 | } 117 | 118 | bool Options::getFutureTabsUnderLive() const 119 | { 120 | return m_futureTabsUnderLive; 121 | } 122 | 123 | void Options::setFutureTabsUnderLive(const bool futureTabsUnderLive) 124 | { 125 | m_futureTabsUnderLive = futureTabsUnderLive; 126 | } 127 | 128 | bool Options::getShowArtDataInValue() const 129 | { 130 | return m_showArtDataInValue; 131 | } 132 | 133 | void Options::setShowArtDataInValue(const bool showArtDataInValue) 134 | { 135 | m_showArtDataInValue = showArtDataInValue; 136 | } 137 | 138 | bool Options::getShowErrorCodeInValue() const 139 | { 140 | return m_showErrorCodeInValue; 141 | } 142 | 143 | void Options::setShowErrorCodeInValue(const bool showErrorCodeInValue) 144 | { 145 | m_showErrorCodeInValue = showErrorCodeInValue; 146 | } 147 | 148 | bool Options::getCaptureAllTextFiles() const 149 | { 150 | return m_captureAllTextFiles; 151 | } 152 | 153 | void Options::setCaptureAllTextFiles(const bool captureAllTextFiles) 154 | { 155 | m_captureAllTextFiles = captureAllTextFiles; 156 | } 157 | 158 | QString Options::getDefaultFilterName() const 159 | { 160 | return m_defaultFilterName; 161 | } 162 | 163 | void Options::setDefaultFilterName(const QString &defaultFilterName) 164 | { 165 | m_defaultFilterName = defaultFilterName; 166 | } 167 | 168 | HighlightOptions Options::getDefaultHighlightOpts() 169 | { 170 | return m_defaultHighlightOpts; 171 | } 172 | 173 | void Options::LoadHighlightFilter(const QString& filterName) 174 | { 175 | QDir loadDir(PathHelper::GetFiltersConfigPath()); 176 | QFile loadFile(loadDir.filePath(filterName + ".json")); 177 | if (!loadFile.open(QIODevice::ReadOnly)) 178 | { 179 | qWarning("Couldn't open file."); 180 | return; 181 | } 182 | QByteArray filterData = loadFile.readAll(); 183 | QJsonDocument filtersDoc(QJsonDocument::fromJson(filterData)); 184 | m_defaultHighlightOpts.FromJson(filtersDoc.array()); 185 | // Currently this does not apply to already open tabs, but I think that's fine. 186 | } 187 | 188 | int Options::getSyntaxHighlightLimit() const 189 | { 190 | return m_syntaxHighlightLimit; 191 | } 192 | 193 | void Options::setSyntaxHighlightLimit(const int syntaxHighlightLimit) 194 | { 195 | m_syntaxHighlightLimit = syntaxHighlightLimit; 196 | } 197 | 198 | QString Options::getTheme() const 199 | { 200 | return m_theme; 201 | } 202 | 203 | void Options::setTheme(const QString &theme) 204 | { 205 | m_theme = theme; 206 | } 207 | 208 | QString Options::getNotation() const 209 | { 210 | return m_notation; 211 | } 212 | 213 | void Options::setNotation(const QString ¬ation) 214 | { 215 | m_notation = notation; 216 | } 217 | -------------------------------------------------------------------------------- /src/options.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "highlightoptions.h" 4 | 5 | #include 6 | 7 | class Options 8 | { 9 | private: 10 | Options(){ ReadSettings(); } 11 | Options(const Options&) = delete; 12 | Options& operator= (const Options&) = delete; 13 | 14 | QStringList m_skippedText; 15 | QBitArray m_skippedState; 16 | bool m_visualizationServiceEnable; 17 | QString m_visualizationServiceURL; 18 | QString m_diffToolPath; 19 | bool m_futureTabsUnderLive; 20 | bool m_captureAllTextFiles; 21 | bool m_showArtDataInValue; 22 | bool m_showErrorCodeInValue; 23 | QString m_defaultFilterName; 24 | HighlightOptions m_defaultHighlightOpts; 25 | int m_syntaxHighlightLimit; 26 | QString m_theme; 27 | QString m_notation; 28 | 29 | public: 30 | static Options& GetInstance() 31 | { 32 | static Options options; 33 | return options; 34 | } 35 | void ReadSettings(); 36 | void WriteSettings(); 37 | void LoadHighlightFilter(const QString& filterName); 38 | 39 | QStringList getSkippedText() const; 40 | void setSkippedText(const QStringList& skippedText); 41 | 42 | QBitArray getSkippedState() const; 43 | void setSkippedState(const QBitArray& skippedState); 44 | 45 | bool getVisualizationServiceEnable() const; 46 | void setVisualizationServiceEnable(const bool visualizationServiceEnable); 47 | 48 | QString getVisualizationServiceURL() const; 49 | void setVisualizationServiceURL(const QString& visualizationServiceURL); 50 | 51 | QString getDiffToolPath() const; 52 | void setDiffToolPath(const QString& diffToolPath); 53 | 54 | bool getFutureTabsUnderLive() const; 55 | void setFutureTabsUnderLive(const bool futureTabsUnderLive); 56 | 57 | bool getCaptureAllTextFiles() const; 58 | void setCaptureAllTextFiles(const bool captureAllTextFiles); 59 | 60 | bool getShowArtDataInValue() const; 61 | void setShowArtDataInValue(const bool showArtDataInValue); 62 | 63 | bool getShowErrorCodeInValue() const; 64 | void setShowErrorCodeInValue(const bool showErrorCodeInValue); 65 | 66 | QString getDefaultFilterName() const; 67 | void setDefaultFilterName(const QString& defaultFilterName); 68 | 69 | HighlightOptions getDefaultHighlightOpts(); 70 | 71 | int getSyntaxHighlightLimit() const; 72 | void setSyntaxHighlightLimit(const int syntaxHighlightLimit); 73 | 74 | QString getTheme() const; 75 | void setTheme(const QString& theme); 76 | 77 | QString getNotation() const; 78 | void setNotation(const QString& notation); 79 | }; 80 | -------------------------------------------------------------------------------- /src/optionsdlg.cpp: -------------------------------------------------------------------------------- 1 | #include "optionsdlg.h" 2 | #include "ui_optionsdlg.h" 3 | 4 | #include "options.h" 5 | #include "pathhelper.h" 6 | #include "qjsonutils.h" 7 | #include "themeutils.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | OptionsDlg::OptionsDlg(QWidget *parent) : 16 | QDialog(parent), 17 | ui(new Ui::OptionsDlg) 18 | { 19 | ui->setupUi(this); 20 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 21 | 22 | ReadSettings(); 23 | } 24 | 25 | OptionsDlg::~OptionsDlg() 26 | { 27 | delete ui; 28 | } 29 | 30 | void OptionsDlg::WriteSettings() 31 | { 32 | Options& options = Options::GetInstance(); 33 | 34 | QStringList skippedText; 35 | QBitArray skippedState(ui->listWidget->count()); 36 | for (int i = 0; i < ui->listWidget->count(); i++) 37 | { 38 | skippedText.append(ui->listWidget->item(i)->text()); 39 | skippedState[i] = ui->listWidget->item(i)->checkState() == Qt::Checked ? true : false; 40 | } 41 | options.setSkippedText(skippedText); 42 | options.setSkippedState(skippedState); 43 | 44 | options.setVisualizationServiceEnable(ui->serviceEnable->isChecked()); 45 | options.setVisualizationServiceURL(ui->serviceURLEdit->text()); 46 | options.setDiffToolPath(ui->diffToolPath->text()); 47 | options.setFutureTabsUnderLive(ui->startFutureLiveCapture->isChecked()); 48 | options.setCaptureAllTextFiles(ui->captureAllTextFiles->isChecked()); 49 | options.setShowArtDataInValue(ui->showArtDataInValue->isChecked()); 50 | options.setShowErrorCodeInValue(ui->showErrorCodeInValue->isChecked()); 51 | options.setDefaultFilterName(ui->defaultHighlightComboBox->currentText()); 52 | options.setSyntaxHighlightLimit(ui->syntaxHighlightLimitSpinBox->value()); 53 | options.setTheme(ui->themeComboBox->currentText()); 54 | options.setNotation(ui->notationComboBox->currentText()); 55 | 56 | // Persist the options 57 | options.WriteSettings(); 58 | } 59 | 60 | void OptionsDlg::ReadSettings() 61 | { 62 | Options& options = Options::GetInstance(); 63 | 64 | QStringList skippedText = options.getSkippedText(); 65 | QBitArray skippedState = options.getSkippedState(); 66 | for (int i = 0; i < skippedText.length(); i++) 67 | { 68 | QListWidgetItem *newItem = new QListWidgetItem(skippedText[i], ui->listWidget); 69 | newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); 70 | newItem->setCheckState(skippedState[i] ? Qt::Checked : Qt::Unchecked); 71 | } 72 | 73 | bool serviceEnable = options.getVisualizationServiceEnable(); 74 | ui->useEmbedded->setChecked(!serviceEnable); 75 | ui->serviceEnable->setChecked(serviceEnable); 76 | ui->serviceURLEdit->setEnabled(serviceEnable); 77 | 78 | ui->serviceURLEdit->setText(options.getVisualizationServiceURL()); 79 | ui->diffToolPath->setText(options.getDiffToolPath()); 80 | ui->startFutureLiveCapture->setChecked(options.getFutureTabsUnderLive()); 81 | ui->captureAllTextFiles->setChecked(options.getCaptureAllTextFiles()); 82 | ui->showArtDataInValue->setChecked(options.getShowArtDataInValue()); 83 | ui->showErrorCodeInValue->setChecked(options.getShowErrorCodeInValue()); 84 | ui->syntaxHighlightLimitSpinBox->setValue(options.getSyntaxHighlightLimit()); 85 | 86 | const auto& themeNames = ThemeUtils::GetThemeNames(); 87 | ui->themeComboBox->addItems(themeNames); 88 | QString themeName = options.getTheme(); 89 | if (!themeNames.contains(themeName)) 90 | { 91 | themeName = "Native"; 92 | } 93 | ui->themeComboBox->setCurrentText(themeName); 94 | 95 | const auto& notationNames = QJsonUtils::GetNotationNames(); 96 | ui->notationComboBox->addItems(notationNames); 97 | QString notationName = options.getNotation(); 98 | if (!notationNames.contains(notationName)) 99 | { 100 | notationName = "YAML"; 101 | } 102 | ui->notationComboBox->setCurrentText(notationName); 103 | 104 | // load all saved filters for default filters 105 | ui->defaultHighlightComboBox->addItem(QString("None")); 106 | QDir loadDir(PathHelper::GetFiltersConfigPath()); 107 | if (loadDir.exists()) 108 | { 109 | QString filterExtension = QStringLiteral("*.json"); 110 | QStringList filters; 111 | for (QString entry : loadDir.entryList(QStringList(filterExtension), QDir::Filter::Files)) 112 | { 113 | // Add an item with the filter file. Take out the ".json" at the end 114 | filters.push_back(entry.left(entry.length() - filterExtension.length() + 1)); 115 | } 116 | ui->defaultHighlightComboBox->addItems(filters); 117 | } 118 | int filterIdx = ui->defaultHighlightComboBox->findText(options.getDefaultFilterName()); 119 | ui->defaultHighlightComboBox->setCurrentIndex(filterIdx); 120 | } 121 | 122 | void OptionsDlg::on_listWidget_itemSelectionChanged() 123 | { 124 | qDebug() << "ui->listWidget->currentRow()" << ui->listWidget->currentRow(); 125 | ui->btnRemove->setEnabled(ui->listWidget->currentRow() != -1); 126 | } 127 | 128 | void OptionsDlg::on_btnAdd_clicked() 129 | { 130 | bool ok; 131 | QString text = QInputDialog::getText(this, "Add", "Add new event to skip on load:", QLineEdit::Normal, "", &ok); 132 | if (ok) 133 | { 134 | QListWidgetItem *newItem = new QListWidgetItem(text, ui->listWidget); 135 | newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); 136 | newItem->setCheckState(Qt::Checked); 137 | } 138 | } 139 | 140 | void OptionsDlg::on_btnRemove_clicked() 141 | { 142 | qDebug() << "removing" << ui->listWidget->currentItem(); 143 | delete ui->listWidget->takeItem(ui->listWidget->currentRow()); 144 | } 145 | 146 | void OptionsDlg::on_chooseDiffTool_clicked() 147 | { 148 | auto fileName = QFileDialog::getOpenFileName(this, "Select Diff Tool", "/home", ""); 149 | if(!fileName.isNull()) 150 | ui->diffToolPath->setText(fileName); 151 | } 152 | 153 | void OptionsDlg::on_useEmbedded_clicked() 154 | { 155 | ui->serviceURLEdit->setDisabled(true); 156 | } 157 | 158 | void OptionsDlg::on_serviceEnable_clicked() 159 | { 160 | ui->serviceURLEdit->setEnabled(true); 161 | } 162 | 163 | void OptionsDlg::on_themeComboBox_currentTextChanged(const QString &themeName) 164 | { 165 | // Only switch the theme after the dialog window is fully loaded (issue #59) 166 | if (this->isVisible()) 167 | { 168 | ThemeUtils::SwitchTheme(themeName, this->parentWidget()); 169 | } 170 | } 171 | 172 | void OptionsDlg::on_OptionsDlg_accepted() 173 | { 174 | WriteSettings(); 175 | } 176 | 177 | void OptionsDlg::on_OptionsDlg_rejected() 178 | { 179 | // Revert the theme back if the user cancels the dialog 180 | Options& options = Options::GetInstance(); 181 | auto themeNameSettings = options.getTheme(); 182 | if (!ThemeUtils::GetThemeNames().contains(themeNameSettings)) 183 | { 184 | themeNameSettings = "Native"; 185 | } 186 | 187 | auto themeNameUi = ui->themeComboBox->currentText(); 188 | // Only apply a theme change if necessary 189 | if (themeNameSettings != themeNameUi) 190 | { 191 | ThemeUtils::SwitchTheme(themeNameSettings, this->parentWidget()); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/optionsdlg.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTIONSDLG_H 2 | #define OPTIONSDLG_H 3 | 4 | #include 5 | #include 6 | 7 | namespace Ui { 8 | class OptionsDlg; 9 | } 10 | 11 | class OptionsDlg : public QDialog 12 | { 13 | Q_OBJECT 14 | 15 | public: 16 | explicit OptionsDlg(QWidget *parent = 0); 17 | ~OptionsDlg(); 18 | 19 | private slots: 20 | //Slots use underscores as per QT's automatic connection syntax 21 | void on_btnAdd_clicked(); 22 | void on_OptionsDlg_accepted(); 23 | void on_OptionsDlg_rejected(); 24 | void on_listWidget_itemSelectionChanged(); 25 | void on_btnRemove_clicked(); 26 | void on_chooseDiffTool_clicked(); 27 | void on_useEmbedded_clicked(); 28 | void on_serviceEnable_clicked(); 29 | void on_themeComboBox_currentTextChanged(const QString &themeName); 30 | 31 | private: 32 | void WriteSettings(); 33 | void ReadSettings(); 34 | 35 | Ui::OptionsDlg *ui; 36 | }; 37 | 38 | #endif // OPTIONSDLG_H 39 | -------------------------------------------------------------------------------- /src/optionsdlg.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | OptionsDlg 4 | 5 | 6 | 7 | 0 8 | 0 9 | 502 10 | 570 11 | 12 | 13 | 14 | Options 15 | 16 | 17 | false 18 | 19 | 20 | true 21 | 22 | 23 | 24 | 6 25 | 26 | 27 | 6 28 | 29 | 30 | 6 31 | 32 | 33 | 6 34 | 35 | 36 | 37 | 38 | 0 39 | 40 | 41 | 42 | 43 | Skipped event types 44 | 45 | 46 | 47 | 2 48 | 49 | 50 | 2 51 | 52 | 53 | 2 54 | 55 | 56 | 2 57 | 58 | 59 | 60 | 61 | QAbstractItemView::NoEditTriggers 62 | 63 | 64 | false 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | + 74 | 75 | 76 | false 77 | 78 | 79 | 80 | 81 | 82 | 83 | - 84 | 85 | 86 | 87 | 88 | 89 | 90 | Qt::Vertical 91 | 92 | 93 | 94 | 5 95 | 40 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Query visualization 109 | 110 | 111 | 112 | 113 | 114 | <html><head/><body><p>Select to enable the embedded query visualizer.</p></body></html> 115 | 116 | 117 | Use embedded visualizer 118 | 119 | 120 | false 121 | 122 | 123 | 124 | 125 | 126 | 127 | <html><head/><body><p>Select to enable a browser-based query visualizer service at the specified URL.</p></body></html> 128 | 129 | 130 | Use external visualizer service: 131 | 132 | 133 | 134 | 135 | 136 | 137 | true 138 | 139 | 140 | 141 | 142 | 143 | true 144 | 145 | 146 | Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter 147 | 148 | 149 | false 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | External diff tool 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | Choose file 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | General 183 | 184 | 185 | 186 | 187 | 188 | Open log files in live capture mode 189 | 190 | 191 | 192 | 193 | 194 | 195 | Include non-JSON format text files such as TDE and server logs in directory live capture 196 | 197 | 198 | Include all text files in directory live capture 199 | 200 | 201 | 202 | 203 | 204 | 205 | If ART data is available in an event, an "~art" attribute is added and displayed in the value 206 | 207 | 208 | Show ART data as part of value 209 | 210 | 211 | 212 | 213 | 214 | 215 | If Error Code is available in an event, an "~errorcode" attribute is added and displayed in the value 216 | 217 | 218 | Show Error Code as part of value 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | Theme 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | <html><head/><body><p>Changes the way the events are displayed in the main window and the detailed view. Note: you might need to 'Refresh' to see the changes take effect.<br/></p><p>- <span style=" font-weight:600;">Flat.</span> The original format in TLV. No identation, and easy to read on non-nested events. Difficult for machines to parse.</p><p>- <span style=" font-weight:600;">JSON.</span> Compliant with the JSON format. Nesting is clear but long strings are difficult to read.</p><p>- <span style=" font-weight:600;">YAML.</span> Compliant with the YAML format. Preserves nesting and human readability while still being computer readable.</p></body></html> 242 | 243 | 244 | Event notation 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | Character limit for syntax highlighting in the value dialog 259 | 260 | 261 | 262 | 263 | 264 | 265 | Set a smaller character limit for syntax highlighting, if it takes too long to open an event in value dialog. 266 | 267 | 268 | true 269 | 270 | 271 | 1000000 272 | 273 | 274 | 100 275 | 276 | 277 | 10000 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | Default highlight filter 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | Qt::Horizontal 302 | 303 | 304 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | buttonBox 316 | accepted() 317 | OptionsDlg 318 | accept() 319 | 320 | 321 | 255 322 | 283 323 | 324 | 325 | 157 326 | 274 327 | 328 | 329 | 330 | 331 | buttonBox 332 | rejected() 333 | OptionsDlg 334 | reject() 335 | 336 | 337 | 323 338 | 283 339 | 340 | 341 | 286 342 | 274 343 | 344 | 345 | 346 | 347 | 348 | -------------------------------------------------------------------------------- /src/pathhelper.cpp: -------------------------------------------------------------------------------- 1 | #include "pathhelper.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace PathHelper 7 | { 8 | QString GetConfigPath() 9 | { 10 | return QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation)[0]; 11 | } 12 | 13 | QString GetFiltersConfigPath() 14 | { 15 | return GetConfigPath() + "/" + QStringLiteral("filters"); 16 | } 17 | 18 | QString GetConfigIniPath() 19 | { 20 | return GetConfigPath() + "/" + QStringLiteral("tlv.ini"); 21 | } 22 | 23 | QString GetDocumentsPath() 24 | { 25 | return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); 26 | } 27 | 28 | QString GetTableauRepositoryPath(bool isBeta) 29 | { 30 | return GetDocumentsPath() + "/" + 31 | (isBeta ? QStringLiteral("My Tableau Repository (Beta)") : QStringLiteral("My Tableau Repository")); 32 | } 33 | 34 | QString GetTableauLogFolderPath(bool isBeta) 35 | { 36 | return GetTableauRepositoryPath(isBeta) + "/" + QStringLiteral("Logs"); 37 | } 38 | 39 | QString GetTableauLogFilePath(bool isBeta) 40 | { 41 | return GetTableauLogFolderPath(isBeta) + "/" + QStringLiteral("log.txt"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/pathhelper.h: -------------------------------------------------------------------------------- 1 | #ifndef PATHHELPER_H 2 | #define PATHHELPER_H 3 | 4 | #include 5 | 6 | namespace PathHelper 7 | { 8 | QString GetConfigPath(); 9 | QString GetConfigIniPath(); 10 | QString GetFiltersConfigPath(); 11 | QString GetDocumentsPath(); 12 | QString GetTableauRepositoryPath(bool isBeta = false); 13 | QString GetTableauLogFolderPath(bool isBeta = false); 14 | QString GetTableauLogFilePath(bool isBeta = false); 15 | } 16 | 17 | #endif // PATHHELPER_H 18 | -------------------------------------------------------------------------------- /src/processevent.cpp: -------------------------------------------------------------------------------- 1 | #include "processevent.h" 2 | 3 | #include "options.h" 4 | #include "pathhelper.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | namespace ProcessEvent 11 | { 12 | QJsonObject ProcessLogEventMessage(int index, QString message, const QString& fileName) 13 | { 14 | Options& options = Options::GetInstance(); 15 | QStringList m_SkippedText = options.getSkippedText(); 16 | QBitArray m_SkippedState = options.getSkippedState(); 17 | 18 | if (!message.startsWith("{")) 19 | { 20 | QJsonObject obj; 21 | obj["idx"] = index; 22 | obj["file"] = fileName; 23 | obj["k"] = ""; 24 | obj["v"] = message; 25 | return obj; 26 | } 27 | else 28 | { 29 | message.insert(1, QString("\"file\": \"%1\",").arg(fileName)); 30 | message.insert(1, QString("\"idx\": %1,").arg(index)); 31 | QJsonDocument jsonDoc = QJsonDocument::fromJson(message.toUtf8()); 32 | if (jsonDoc.object().contains("k") 33 | && m_SkippedText.contains(jsonDoc.object()["k"].toString()) 34 | && m_SkippedState[m_SkippedText.indexOf(jsonDoc.object()["k"].toString(), 0)]) 35 | { 36 | jsonDoc = QJsonDocument(); 37 | } 38 | return jsonDoc.object(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/processevent.h: -------------------------------------------------------------------------------- 1 | #ifndef PROCESSEVENT_H 2 | #define PROCESSEVENT_H 3 | 4 | #include 5 | #include 6 | 7 | namespace ProcessEvent 8 | { 9 | QJsonObject ProcessLogEventMessage(int index, QString message, const QString& fileName); 10 | } 11 | 12 | #endif // PROCESSEVENT_H 13 | -------------------------------------------------------------------------------- /src/qjsonutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class QString; 6 | class QJsonValue; 7 | 8 | namespace QJsonUtils 9 | { 10 | enum class Notation : unsigned int 11 | { 12 | Flat, 13 | JSON, 14 | YAML 15 | }; 16 | 17 | enum class LineFormat : unsigned int 18 | { 19 | SingleLine, 20 | Free 21 | }; 22 | 23 | QString Format(const QJsonValue& value, Notation format, LineFormat lineFormat = LineFormat::Free); 24 | 25 | QStringList GetNotationNames(); 26 | 27 | Notation GetNotationFromName(const QString& notationName); 28 | QString GetNameForNotation(Notation notation); 29 | 30 | bool IsStructured(const QJsonValue& value); 31 | } 32 | -------------------------------------------------------------------------------- /src/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | ../resources/sqlsyntax.css 4 | ../resources/images/branch-closed.png 5 | ../resources/images/branch-end.png 6 | ../resources/images/branch-more.png 7 | ../resources/images/branch-open.png 8 | ../resources/images/branch-vline.png 9 | ../resources/images/tab-close-red.png 10 | ../resources/images/tab-close.png 11 | ../resources/images/tab-sync.png 12 | ../resources/images/tab-sync-thin.png 13 | ../resources/images/ctx-open.png 14 | ../resources/images/ctx-open-merge.png 15 | ../resources/images/ctx-down.png 16 | ../resources/images/ctx-highlight.png 17 | ../resources/images/ctx-newtab.png 18 | ../resources/images/ctx-time.png 19 | ../resources/images/ctx-datetime.png 20 | ../resources/images/ctx-up.png 21 | ../resources/images/ctx-hide.png 22 | ../resources/images/ctx-diff.png 23 | ../resources/images/ctx-book.png 24 | ../resources/images/ctx-copy.png 25 | ../resources/images/ctx-open-file-tab.png 26 | ../resources/images/ctx-clear.png 27 | ../resources/images/ctx-refresh.png 28 | ../resources/images/ctx-find.png 29 | ../resources/images/ctx-summary.png 30 | ../resources/images/ctx-livecapture.png 31 | ../resources/images/ctx-add.png 32 | ../resources/images/value-next.png 33 | ../resources/images/value-previous.png 34 | ../resources/query-graphs/query-graphs.tlv.html 35 | ../resources/query-graphs/query-graphs.css 36 | ../resources/query-graphs/query-graphs.min.js 37 | ../resources/workbooks/QueryInfo.twb 38 | ../resources/workbooks/Timeline.twb 39 | 40 | 41 | ../resources/images/darktheme/tab-sync.png 42 | ../resources/images/darktheme/tab-sync-thin.png 43 | ../resources/images/darktheme/ctx-open.png 44 | ../resources/images/darktheme/ctx-open-merge.png 45 | ../resources/images/darktheme/ctx-down.png 46 | ../resources/images/darktheme/ctx-highlight.png 47 | ../resources/images/darktheme/ctx-newtab.png 48 | ../resources/images/darktheme/ctx-time.png 49 | ../resources/images/darktheme/ctx-datetime.png 50 | ../resources/images/darktheme/ctx-up.png 51 | ../resources/images/darktheme/ctx-hide.png 52 | ../resources/images/darktheme/ctx-diff.png 53 | ../resources/images/darktheme/ctx-book.png 54 | ../resources/images/darktheme/ctx-copy.png 55 | ../resources/images/darktheme/ctx-open-file-tab.png 56 | ../resources/images/darktheme/ctx-clear.png 57 | ../resources/images/darktheme/ctx-refresh.png 58 | ../resources/images/darktheme/ctx-find.png 59 | ../resources/images/darktheme/ctx-summary.png 60 | ../resources/images/darktheme/ctx-livecapture.png 61 | ../resources/images/darktheme/ctx-add.png 62 | ../resources/images/darktheme/value-next.png 63 | ../resources/images/darktheme/value-previous.png 64 | 65 | 66 | -------------------------------------------------------------------------------- /src/savefilterdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "savefilterdialog.h" 2 | #include "ui_savefilterdialog.h" 3 | #include "options.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | SaveFilterDialog::SaveFilterDialog(QWidget *parent, const HighlightOptions& filters) : 15 | QDialog(parent), 16 | ui(new Ui::SaveFilterDialog), 17 | m_filters(filters) 18 | { 19 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 20 | ui->setupUi(this); 21 | ui->lineEdit->setFocus(); 22 | } 23 | 24 | SaveFilterDialog::~SaveFilterDialog() 25 | { 26 | delete ui; 27 | } 28 | 29 | void SaveFilterDialog::on_buttonBox_clicked(QAbstractButton *button) 30 | { 31 | auto role = ui->buttonBox->standardButton(button); 32 | if (role == QDialogButtonBox::Save) 33 | { 34 | QString filename = ui->lineEdit->text(); 35 | 36 | bool success = SaveFilter(filename); 37 | if (success) 38 | { 39 | Options& options = Options::GetInstance(); 40 | if (options.getDefaultFilterName() == filename) 41 | { 42 | options.LoadHighlightFilter(filename); 43 | } 44 | accept(); 45 | } 46 | } 47 | } 48 | 49 | bool SaveFilterDialog::SaveFilter(const QString& filename) 50 | { 51 | qDebug() << "attempting to save..."; 52 | 53 | // Validate filename 54 | QRegularExpression regex("^[a-z0-9.\\-_ ]+$", QRegularExpression::CaseInsensitiveOption); 55 | if (!regex.match(filename).hasMatch()) 56 | { 57 | QMessageBox validationWarning( 58 | QMessageBox::Icon::Warning, 59 | QStringLiteral("Invalid name"), 60 | QStringLiteral("Only alphanumeric characters, underscores, dashes, periods, and spaces are allowed.")); 61 | validationWarning.exec(); 62 | return false; 63 | } 64 | 65 | // Get AppConfig folder, create it if it doesn't exists 66 | QDir saveFolder(QStandardPaths::standardLocations(QStandardPaths::AppConfigLocation)[0] + "/filters"); 67 | if (!saveFolder.exists()) 68 | { 69 | bool folderSuccess = saveFolder.mkpath("."); 70 | if (!folderSuccess) 71 | { 72 | QMessageBox configFolderWarning( 73 | QMessageBox::Icon::Warning, 74 | QStringLiteral("Error saving configuration"), 75 | QStringLiteral("Config folder didn't exist and couldn't be created.")); 76 | configFolderWarning.exec(); 77 | return false; 78 | } 79 | } 80 | 81 | // Open file in write mode 82 | QFile saveFile(saveFolder.filePath(filename + ".json")); 83 | qDebug() << saveFile.fileName(); 84 | if (!saveFile.open(QIODevice::WriteOnly)) 85 | { 86 | QMessageBox configFileWarning( 87 | QMessageBox::Icon::Warning, 88 | QStringLiteral("Error saving configuration"), 89 | QStringLiteral("Config file couldn't be opened.")); 90 | configFileWarning.exec(); 91 | return false; 92 | } 93 | 94 | QJsonDocument filtersDoc(m_filters.ToJson()); 95 | saveFile.write(filtersDoc.toJson()); 96 | saveFile.close(); 97 | return true; 98 | } 99 | -------------------------------------------------------------------------------- /src/savefilterdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef SAVEFILTERDIALOG_H 2 | #define SAVEFILTERDIALOG_H 3 | 4 | #include "highlightoptions.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace Ui { 10 | class SaveFilterDialog; 11 | } 12 | 13 | class SaveFilterDialog : public QDialog 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | explicit SaveFilterDialog(QWidget *parent, const HighlightOptions& filters); 19 | ~SaveFilterDialog(); 20 | 21 | private slots: 22 | void on_buttonBox_clicked(QAbstractButton *button); 23 | 24 | private: 25 | bool SaveFilter(const QString& filename); 26 | 27 | Ui::SaveFilterDialog *ui; 28 | HighlightOptions m_filters; 29 | }; 30 | 31 | #endif // SAVEFILTERDIALOG_H 32 | -------------------------------------------------------------------------------- /src/savefilterdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SaveFilterDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 330 10 | 126 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | 21 | 330 22 | 126 23 | 24 | 25 | 26 | 27 | 330 28 | 126 29 | 30 | 31 | 32 | Save Filters 33 | 34 | 35 | 36 | 37 | 20 38 | 80 39 | 291 40 | 32 41 | 42 | 43 | 44 | Qt::Horizontal 45 | 46 | 47 | QDialogButtonBox::Cancel|QDialogButtonBox::Save 48 | 49 | 50 | 51 | 52 | 53 | 20 54 | 40 55 | 291 56 | 21 57 | 58 | 59 | 60 | Use alphanumeric characters, underscores, dashes, periods, and spaces. 61 | 62 | 63 | 100 64 | 65 | 66 | 67 | 68 | 69 | 20 70 | 10 71 | 291 72 | 31 73 | 74 | 75 | 76 | Highlight filters collection name: 77 | 78 | 79 | Qt::AutoText 80 | 81 | 82 | 83 | 84 | 85 | 86 | buttonBox 87 | rejected() 88 | SaveFilterDialog 89 | reject() 90 | 91 | 92 | 316 93 | 260 94 | 95 | 96 | 286 97 | 274 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/searchopt.cpp: -------------------------------------------------------------------------------- 1 | #include "searchopt.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | SearchOpt::SearchOpt() : 9 | m_value(""), 10 | m_matchCase(false), 11 | m_mode(SearchMode::Contains), 12 | m_backgroundColor(QColor(255, 255, 255)) 13 | { 14 | } 15 | 16 | bool SearchOpt::HasMatch(QString value) 17 | { 18 | switch (m_mode) { 19 | case SearchMode::Equals: 20 | return value.compare(m_value, m_matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive) == 0; 21 | case SearchMode::Contains: 22 | return value.contains(m_value, m_matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive); 23 | case SearchMode::StartsWith: 24 | return value.startsWith(m_value, m_matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive); 25 | case SearchMode::EndsWith: 26 | return value.endsWith(m_value, m_matchCase ? Qt::CaseSensitive : Qt::CaseInsensitive); 27 | case SearchMode::Regex: 28 | QRegularExpression regex(m_value, m_matchCase ? QRegularExpression::NoPatternOption : QRegularExpression::CaseInsensitiveOption); 29 | return regex.isValid() && regex.match(value).hasMatch(); 30 | } 31 | return false; 32 | } 33 | 34 | static QMap mapColToString{ 35 | {COL::ID, "ID"}, 36 | {COL::File, "File"}, 37 | {COL::Time, "Time"}, 38 | {COL::PID, "PID"}, 39 | {COL::TID, "TID"}, 40 | {COL::Severity, "Severity"}, 41 | {COL::Request, "Request"}, 42 | {COL::Session, "Session"}, 43 | {COL::Site, "Site"}, 44 | {COL::Key, "Key"}, 45 | {COL::Value, "Value"}, 46 | {COL::ART, "ART"}, 47 | {COL::ErrorCode,"ErrorCode"} 48 | }; 49 | 50 | static QMap mapModeToString{ 51 | {SearchMode::Equals, "equals"}, 52 | {SearchMode::Contains, "contains"}, 53 | {SearchMode::StartsWith, "startswith"}, 54 | {SearchMode::EndsWith, "endswith"}, 55 | {SearchMode::Regex, "regex"}, 56 | }; 57 | 58 | QJsonObject SearchOpt::ToJson() 59 | { 60 | QJsonObject json; 61 | json["value"] = m_value; 62 | QJsonArray keysArray; 63 | for (COL columnKey : m_keys) 64 | { 65 | keysArray.append(mapColToString[columnKey]); 66 | } 67 | json["keys"] = keysArray; 68 | json["matchCase"] = m_matchCase; 69 | json["mode"] = mapModeToString[m_mode]; 70 | json["backgroundColor"] = QJsonArray{ 71 | m_backgroundColor.red(), 72 | m_backgroundColor.green(), 73 | m_backgroundColor.blue() 74 | }; 75 | return json; 76 | } 77 | 78 | static QMap mapStringToCol{ 79 | {"ID", COL::ID}, 80 | {"File", COL::File}, 81 | {"Time", COL::Time}, 82 | {"PID", COL::PID}, 83 | {"TID", COL::TID}, 84 | {"Severity", COL::Severity}, 85 | {"Request", COL::Request}, 86 | {"Session", COL::Session}, 87 | {"Site", COL::Site}, 88 | {"Key", COL::Key}, 89 | {"Value", COL::Value}, 90 | {"ART", COL::ART}, 91 | {"ErrorCode",COL::ErrorCode} 92 | }; 93 | 94 | static QMap mapStringToMode{ 95 | {"equals", SearchMode::Equals}, 96 | {"contains", SearchMode::Contains}, 97 | {"startswith", SearchMode::StartsWith}, 98 | {"endswith", SearchMode::EndsWith}, 99 | {"regex", SearchMode::Regex}, 100 | }; 101 | 102 | void SearchOpt::FromJson(const QJsonObject& json) 103 | { 104 | m_value = json["value"].toString(); 105 | QJsonArray keys = json["keys"].toArray(); 106 | m_keys.clear(); 107 | for (auto value : keys) 108 | { 109 | m_keys.append(mapStringToCol[value.toString()]); 110 | } 111 | m_matchCase = json["matchCase"].toBool(); 112 | if (json.contains("mode")) { 113 | m_mode=mapStringToMode[json["mode"].toString()]; 114 | } else if (json.contains("useRegex")) { 115 | // Logic for parsing legacy format. Remove this code path at some future point in time 116 | m_mode=json["mode"].toBool() ? SearchMode::Regex : SearchMode::Contains; 117 | } 118 | QJsonArray backgroundColors = json["backgroundColor"].toArray(); 119 | m_backgroundColor.setRed(backgroundColors[0].toInt()); 120 | m_backgroundColor.setGreen(backgroundColors[1].toInt()); 121 | m_backgroundColor.setBlue(backgroundColors[2].toInt()); 122 | } 123 | -------------------------------------------------------------------------------- /src/searchopt.h: -------------------------------------------------------------------------------- 1 | #ifndef SEARCHOPT_H 2 | #define SEARCHOPT_H 3 | 4 | #include "column.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | enum SearchMode : short { 12 | Equals, 13 | Contains, 14 | StartsWith, 15 | EndsWith, 16 | Regex 17 | }; 18 | 19 | class SearchOpt 20 | { 21 | public: 22 | SearchOpt(); 23 | bool HasMatch(QString value); 24 | QJsonObject ToJson(); 25 | void FromJson(const QJsonObject& json); 26 | 27 | QString m_value; 28 | QVector m_keys; 29 | bool m_matchCase; 30 | SearchMode m_mode; 31 | QColor m_backgroundColor; 32 | }; 33 | 34 | #endif // SEARCHOPT_H 35 | -------------------------------------------------------------------------------- /src/statusbar.cpp: -------------------------------------------------------------------------------- 1 | #include "statusbar.h" 2 | 3 | StatusBar::StatusBar(QMainWindow* parent) : 4 | m_qbar(parent->statusBar()), 5 | m_statusLabel(new QLabel(parent)) 6 | { 7 | m_statusLabel->setContentsMargins(0, 0, 8, 0); 8 | m_qbar->addPermanentWidget(m_statusLabel); 9 | } 10 | 11 | void StatusBar::ShowMessage(const QString& message, int timeout) 12 | { 13 | m_qbar->showMessage(message, timeout); 14 | } 15 | 16 | void StatusBar::SetRightLabelText(const QString& text) 17 | { 18 | m_statusLabel->setText(text); 19 | } 20 | -------------------------------------------------------------------------------- /src/statusbar.h: -------------------------------------------------------------------------------- 1 | #ifndef STATUSBAR_H 2 | #define STATUSBAR_H 3 | 4 | #include 5 | 6 | class StatusBar 7 | { 8 | public: 9 | StatusBar(QMainWindow* parent); 10 | void ShowMessage(const QString& message, int timeout); 11 | void SetRightLabelText(const QString& text); 12 | 13 | private: 14 | QStatusBar *m_qbar; 15 | QLabel *m_statusLabel; 16 | }; 17 | 18 | #endif // STATUSBAR_H 19 | -------------------------------------------------------------------------------- /src/tableau-log-viewer.pro: -------------------------------------------------------------------------------- 1 | QT += core gui 2 | QT += network 3 | QT += webenginewidgets 4 | QT += widgets 5 | 6 | TARGET = "tlv" 7 | TEMPLATE = app 8 | 9 | FORMS = \ 10 | filtertab.ui \ 11 | finddlg.ui \ 12 | highlightdlg.ui \ 13 | logtab.ui \ 14 | mainwindow.ui \ 15 | optionsdlg.ui \ 16 | savefilterdialog.ui \ 17 | valuedlg.ui 18 | 19 | HEADERS = \ 20 | colorlibrary.h \ 21 | column.h \ 22 | filtertab.h \ 23 | finddlg.h \ 24 | highlightdlg.h \ 25 | highlightoptions.h \ 26 | logtab.h \ 27 | mainwindow.h \ 28 | options.h \ 29 | optionsdlg.h \ 30 | pathhelper.h \ 31 | processevent.h \ 32 | savefilterdialog.h \ 33 | searchopt.h \ 34 | statusbar.h \ 35 | tokenizer.h \ 36 | treeitem.h \ 37 | treemodel.h \ 38 | valuedlg.h \ 39 | zoomabletreeview.h \ 40 | themeutils.h \ 41 | theme.h \ 42 | qjsonutils.h 43 | 44 | SOURCES = \ 45 | colorlibrary.cpp \ 46 | filtertab.cpp \ 47 | finddlg.cpp \ 48 | highlightdlg.cpp \ 49 | highlightoptions.cpp \ 50 | logtab.cpp \ 51 | main.cpp \ 52 | mainwindow.cpp \ 53 | options.cpp \ 54 | optionsdlg.cpp \ 55 | pathhelper.cpp \ 56 | processevent.cpp \ 57 | savefilterdialog.cpp \ 58 | searchopt.cpp \ 59 | statusbar.cpp \ 60 | tokenizer.cpp \ 61 | treeitem.cpp \ 62 | treemodel.cpp \ 63 | valuedlg.cpp \ 64 | zoomabletreeview.cpp \ 65 | themeutils.cpp \ 66 | theme.cpp \ 67 | qjsonutils.cpp 68 | 69 | RESOURCES = resources.qrc 70 | 71 | win32:RC_ICONS += ../resources/images/tlv.ico 72 | 73 | ICON = ../resources/images/tlv.icns 74 | 75 | CONFIG += c++17 76 | CONFIG += x86_64 77 | 78 | QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64 79 | 80 | VERSION = 1.3.0 81 | DEFINES += APP_VERSION=\\\"$$VERSION\\\" 82 | -------------------------------------------------------------------------------- /src/theme.cpp: -------------------------------------------------------------------------------- 1 | #include "theme.h" 2 | #include "themeutils.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace 13 | { 14 | 15 | std::unique_ptr GetNativePalette(QWidget* widget) 16 | { 17 | return std::make_unique(widget->style()->standardPalette()); 18 | } 19 | 20 | std::unique_ptr GetDarkFusionPalette(QWidget*) 21 | { 22 | // Theme by GitHub user QuantumCD, Dark Fusion Palette 23 | // https://gist.github.com/QuantumCD/6245215 24 | auto palette = std::make_unique(); 25 | palette->setColor(QPalette::Window, QColor(0x353535)); 26 | palette->setColor(QPalette::WindowText, QColor(0xf1f1f1)); 27 | palette->setColor(QPalette::Base, QColor(0x202020)); 28 | palette->setColor(QPalette::AlternateBase, QColor(0x353535)); 29 | palette->setColor(QPalette::ToolTipBase, QColor(0x353535)); 30 | palette->setColor(QPalette::ToolTipText, QColor(0xf1f1f1)); 31 | palette->setColor(QPalette::Text, QColor(0xf1f1f1)); 32 | palette->setColor(QPalette::Button, QColor(0x353535)); 33 | palette->setColor(QPalette::ButtonText, QColor(0xf1f1f1)); 34 | palette->setColor(QPalette::BrightText, Qt::red); 35 | palette->setColor(QPalette::Link, QColor(0x2a82da)); 36 | 37 | palette->setColor(QPalette::Highlight, QColor(0x2a82da)); 38 | palette->setColor(QPalette::HighlightedText, Qt::black); 39 | palette->setColor(QPalette::Light, QColor(0x303030)); 40 | 41 | palette->setColor(QPalette::Disabled, QPalette::Text, QColor(0x909090)); 42 | palette->setColor(QPalette::Disabled, QPalette::Button, QColor(0x202020)); 43 | palette->setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x909090)); 44 | palette->setColor(QPalette::Disabled, QPalette::Base, QColor(0x353535)); 45 | return palette; 46 | } 47 | 48 | std::unique_ptr GetSolarizedLightPalette(QWidget*) 49 | { 50 | // Colors from the Solarized Palette, light mode, by Ethan Schoonover 51 | // https://github.com/altercation/solarized 52 | auto palette = std::make_unique(); 53 | palette->setColor(QPalette::Window, QColor(0xe6ddc1)); 54 | palette->setColor(QPalette::WindowText, QColor(0x657b83)); 55 | palette->setColor(QPalette::Base, QColor(0xfdf6e3)); 56 | palette->setColor(QPalette::AlternateBase, QColor(0xeee8d5)); 57 | palette->setColor(QPalette::ToolTipBase, QColor(0xeee8d5)); 58 | palette->setColor(QPalette::ToolTipText, QColor(0x657b83)); 59 | palette->setColor(QPalette::Text, QColor(0x657b83)); 60 | palette->setColor(QPalette::Button, QColor(0xe6ddc1)); 61 | palette->setColor(QPalette::ButtonText, QColor(0x657b83)); 62 | palette->setColor(QPalette::BrightText, Qt::red); 63 | palette->setColor(QPalette::Link, QColor(0x2aa198)); 64 | 65 | palette->setColor(QPalette::Highlight, QColor(0x268bd2)); 66 | palette->setColor(QPalette::HighlightedText, Qt::black); 67 | palette->setColor(QPalette::Light, QColor(0xeee8d5)); 68 | 69 | palette->setColor(QPalette::Disabled, QPalette::Text, QColor(0x93a1a1)); 70 | palette->setColor(QPalette::Disabled, QPalette::Button, QColor(0xfdf6e3)); 71 | palette->setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x93a1a1)); 72 | palette->setColor(QPalette::Disabled, QPalette::Base, QColor(0xe6ddc1)); 73 | return palette; 74 | } 75 | 76 | std::unique_ptr GetSolarizedDarkPalette(QWidget*) 77 | { 78 | // Colors from the Solarized Palette, dark mode, by Ethan Schoonover 79 | // https://github.com/altercation/solarized 80 | auto palette = std::make_unique(); 81 | palette->setColor(QPalette::Window, QColor(0x001e26)); 82 | palette->setColor(QPalette::WindowText, QColor(0x839496)); 83 | palette->setColor(QPalette::Base, QColor(0x002b36)); 84 | palette->setColor(QPalette::AlternateBase, QColor(0x073642)); 85 | palette->setColor(QPalette::ToolTipBase, QColor(0x073642)); 86 | palette->setColor(QPalette::ToolTipText, QColor(0x839496)); 87 | palette->setColor(QPalette::Text, QColor(0x839496)); 88 | palette->setColor(QPalette::Button, QColor(0x001e26)); 89 | palette->setColor(QPalette::ButtonText, QColor(0x839496)); 90 | palette->setColor(QPalette::BrightText, Qt::red); 91 | palette->setColor(QPalette::Link, QColor(0x2aa198)); 92 | 93 | palette->setColor(QPalette::Highlight, QColor(0x268bd2)); 94 | palette->setColor(QPalette::HighlightedText, Qt::black); 95 | palette->setColor(QPalette::Light, QColor(0x073642)); 96 | 97 | palette->setColor(QPalette::Disabled, QPalette::Text, QColor(0x586e75)); 98 | palette->setColor(QPalette::Disabled, QPalette::Button, QColor(0x002b36)); 99 | palette->setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x586e75)); 100 | palette->setColor(QPalette::Disabled, QPalette::Base, QColor(0x001e26)); 101 | return palette; 102 | } 103 | 104 | std::unique_ptr GetEvergreenStatePalette(QWidget*) 105 | { 106 | auto palette = std::make_unique(); 107 | palette->setColor(QPalette::Window, QColor(0x4d6625)); 108 | palette->setColor(QPalette::WindowText, QColor(0xf1f1f1)); 109 | palette->setColor(QPalette::Base, QColor(0x2e3c18)); 110 | palette->setColor(QPalette::AlternateBase, QColor(0xffff7f)); 111 | palette->setColor(QPalette::ToolTipBase, QColor(0x4b3a29)); 112 | palette->setColor(QPalette::ToolTipText, QColor(0xf1f1f1)); 113 | palette->setColor(QPalette::Text, QColor(0xdadada)); 114 | palette->setColor(QPalette::Button, QColor(0x4d6625)); 115 | palette->setColor(QPalette::ButtonText, QColor(0xf1f1f1)); 116 | palette->setColor(QPalette::BrightText, Qt::red); 117 | palette->setColor(QPalette::Link, QColor(0x2a82da)); 118 | 119 | palette->setColor(QPalette::Highlight, QColor(0xccd970)); 120 | palette->setColor(QPalette::HighlightedText, Qt::black); 121 | palette->setColor(QPalette::Light, QColor(0x303030)); 122 | 123 | palette->setColor(QPalette::Disabled, QPalette::Text, QColor(0x909090)); 124 | palette->setColor(QPalette::Disabled, QPalette::Button, QColor(0x2e3c18)); 125 | palette->setColor(QPalette::Disabled, QPalette::ButtonText, QColor(0x909090)); 126 | palette->setColor(QPalette::Disabled, QPalette::Base, QColor(0x4d6625)); 127 | return palette; 128 | } 129 | 130 | using ThemeFunctionPtr = std::unique_ptr (*)(QWidget* widget); 131 | 132 | struct ThemeEntry 133 | { 134 | bool isFusionStyle; 135 | bool isDark; 136 | ThemeFunctionPtr function; 137 | }; 138 | 139 | static QMap themeMap = { 140 | {"Native", {false, false, GetNativePalette}}, 141 | {"Dark Fusion", {true, true, GetDarkFusionPalette}}, 142 | {"Solarized Light", {true, false, GetSolarizedLightPalette}}, 143 | {"Solarized Dark", {true, true, GetSolarizedDarkPalette}}, 144 | {"Evergreen State", {true, true, GetEvergreenStatePalette}}, 145 | }; 146 | 147 | } // namespace 148 | 149 | Theme::Theme(bool isFusionStyle, bool isDark, std::unique_ptr palette): 150 | m_isFusionStyle(isFusionStyle), 151 | m_isDark(isDark), 152 | m_palette(std::move(palette)) 153 | { 154 | } 155 | 156 | QString Theme::sm_defaultStyle; 157 | QString Theme::GetDefaultStyle() 158 | { 159 | if (sm_defaultStyle.isEmpty()) 160 | { 161 | // Extract the style name from the active style class name 162 | // Example: QWindowsVistaStyle -> WindowsVista 163 | QRegularExpression regExp("^.(.*)\\+?Style$"); 164 | sm_defaultStyle = QApplication::style()->metaObject()->className(); 165 | auto match = regExp.match(sm_defaultStyle); 166 | 167 | if (match.hasMatch()) 168 | { 169 | sm_defaultStyle = match.captured(1); 170 | } 171 | else 172 | { 173 | // If this fails (maybe in a OS that we didn't test), fallback to the Fusion style 174 | sm_defaultStyle = "Fusion"; 175 | } 176 | } 177 | return sm_defaultStyle; 178 | } 179 | 180 | QStringList Theme::GetThemeNames() 181 | { 182 | return themeMap.keys(); 183 | } 184 | 185 | std::unique_ptr Theme::ThemeFactory(const QString& themeName, QWidget* widget) 186 | { 187 | if (themeMap.contains(themeName)) 188 | { 189 | const ThemeEntry& entry = themeMap[themeName]; 190 | return std::make_unique(entry.isFusionStyle, entry.isDark, entry.function(widget)); 191 | } 192 | 193 | return nullptr; 194 | } 195 | 196 | void Theme::Activate() 197 | { 198 | QString defaultStyle = GetDefaultStyle(); 199 | QStyle* style = this->m_isFusionStyle ? 200 | QStyleFactory::create("Fusion") : 201 | QStyleFactory::create(defaultStyle); 202 | 203 | qApp->setStyle(style); 204 | qApp->setPalette(*m_palette); 205 | 206 | // setPalette() propagates evenly to all widgets. There are several Qt bugs 207 | // related to this issue. 208 | // The workaround is to call setPalette explicitly for every widget. (issue #59) 209 | for (QWidget *widget : qApp->allWidgets()) 210 | widget->setPalette(*m_palette); 211 | 212 | if (m_isDark) 213 | { 214 | ThemeUtils::SetDarkIconSet(); 215 | } 216 | else 217 | { 218 | ThemeUtils::SetLightIconSet(); 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/theme.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QPalette; 7 | class QString; 8 | class QWidget; 9 | 10 | class Theme { 11 | private: 12 | static QString GetDefaultStyle(); 13 | static QString sm_defaultStyle; 14 | 15 | bool m_isFusionStyle; 16 | bool m_isDark; 17 | std::unique_ptr m_palette; 18 | public: 19 | Theme(bool isFusionStyle, bool isDark, std::unique_ptr palette); 20 | static QStringList GetThemeNames(); 21 | static std::unique_ptr ThemeFactory(const QString& themeName, QWidget* widget); 22 | void Activate(); 23 | }; 24 | -------------------------------------------------------------------------------- /src/themeutils.cpp: -------------------------------------------------------------------------------- 1 | #include "theme.h" 2 | #include "themeutils.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void ThemeUtils::SwitchTheme(const QString& themeName, QWidget* widget) 12 | { 13 | auto theme = Theme::ThemeFactory(themeName, widget); 14 | if (theme) 15 | { 16 | theme->Activate(); 17 | } 18 | else 19 | { 20 | qDebug() << "Unsupported theme:" << themeName; 21 | } 22 | } 23 | 24 | QStringList ThemeUtils::GetThemeNames() 25 | { 26 | return Theme::GetThemeNames(); 27 | } 28 | 29 | double ThemeUtils::Luminance(QColor color) 30 | { 31 | // https://www.w3.org/TR/WCAG/#relativeluminancedef 32 | double r = color.redF(); 33 | double g = color.greenF(); 34 | double b = color.blueF(); 35 | r = (r <= 0.03928) ? r/12.93 : std::pow((r+0.055)/1.055, 2.4); 36 | g = (g <= 0.03928) ? g/12.93 : std::pow((g+0.055)/1.055, 2.4); 37 | b = (b <= 0.03928) ? b/12.93 : std::pow((b+0.055)/1.055, 2.4); 38 | return 0.2126 * r + 0.7152 * g + 0.0722 * b; 39 | } 40 | 41 | double ThemeUtils::ContrastRatio(QColor a, QColor b) 42 | { 43 | // https://www.w3.org/TR/WCAG/#contrast-ratiodef 44 | double luminance1 = ThemeUtils::Luminance(a); 45 | double luminance2 = ThemeUtils::Luminance(b); 46 | double light = std::max(luminance1, luminance2); 47 | double dark = std::min(luminance1, luminance2); 48 | return (light + 0.05) / (dark + 0.05); 49 | } 50 | 51 | namespace ThemeUtils { 52 | QFileSelector m_fileSelector; 53 | void SetLightIconSet() 54 | { 55 | QStringList extraSelectors; 56 | m_fileSelector.setExtraSelectors(extraSelectors); 57 | } 58 | void SetDarkIconSet() 59 | { 60 | QStringList extraSelectors; 61 | extraSelectors << "darktheme"; 62 | m_fileSelector.setExtraSelectors(extraSelectors); 63 | } 64 | 65 | QString GetThemedIcon(const QString& path) 66 | { 67 | if (m_fileSelector.extraSelectors().empty()) 68 | { 69 | SetLightIconSet(); 70 | } 71 | return m_fileSelector.select(path); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/themeutils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | class QColor; 5 | class QString; 6 | 7 | namespace ThemeUtils { 8 | QStringList GetThemeNames(); 9 | void SwitchTheme(const QString& themeName, QWidget* widget); 10 | double Luminance(QColor color); 11 | double ContrastRatio(QColor a, QColor b); 12 | void SetLightIconSet(); 13 | void SetDarkIconSet(); 14 | QString GetThemedIcon(const QString& path); 15 | } 16 | -------------------------------------------------------------------------------- /src/tokenizer.cpp: -------------------------------------------------------------------------------- 1 | #include "tokenizer.h" 2 | #include 3 | #include 4 | 5 | Tokenizer::Tokenizer() 6 | { 7 | } 8 | 9 | QList Tokenizer::Tokenize(QString script) 10 | { 11 | int i = 0; 12 | int length = script.length(); 13 | 14 | QRegularExpressionMatch match; 15 | 16 | QList results; 17 | 18 | while (i < length) 19 | { 20 | for (LexicalRule rule : m_rules) 21 | { 22 | match = rule.regExp.match(script, i); 23 | if (match.hasMatch()) 24 | { 25 | if (match.capturedLength() == 0) 26 | { 27 | qDebug() << "Regex match length is zero"; 28 | } 29 | 30 | //Add to results 31 | results.append(Token(i, match.capturedLength(), rule.type)); 32 | i += match.capturedLength(); 33 | break; 34 | } 35 | } 36 | } 37 | return results; 38 | } 39 | 40 | void Tokenizer::SetSQL() 41 | { 42 | m_rules = { 43 | LexicalRule(TokenType::String, QRegularExpression("\\G'([^']*)'")), 44 | LexicalRule(TokenType::Tag, QRegularExpression("\\G([a-zA-Z\\d_\\$-]+:\\s)")), 45 | LexicalRule(TokenType::Keyword, QRegularExpression("\\G(ON|COMMIT|PRESERVE|INTEGER|ROWS|LOCAL|TEMPORARY|ELSE|CASE|END|WHEN|THEN|CREATE|INSERT|DROP|DELETE|INDEX|TABLE|SELECT|WHERE|INTO|VALUES|ON|AS|FROM|AND|NOT|LEFT|RIGHT|INNER|OUTER|JOIN|ORDER|GROUP|BY|IN|OR|SUM)\\b", QRegularExpression::CaseInsensitiveOption)), 46 | LexicalRule(TokenType::Whitespace, QRegularExpression("\\G([\\s]+)")), 47 | LexicalRule(TokenType::Float, QRegularExpression("\\G[\\d]+\\.[\\d]+")), 48 | LexicalRule(TokenType::Integer, QRegularExpression("\\G[\\d]+")), 49 | LexicalRule(TokenType::Identifier, QRegularExpression("\\G([a-zA-Z_\\$][a-zA-Z_\\d\\$]*|(`([^`]*)`)|(\\[([^\\]]*)\\])|\\\"([^\\\"]*)\\\")")), 50 | LexicalRule(TokenType::Symbol, QRegularExpression("\\G[\\.,\\}\\{]+")), 51 | LexicalRule(TokenType::OpenParan, QRegularExpression("\\G\\(")), 52 | LexicalRule(TokenType::CloseParan, QRegularExpression("\\G\\)")), 53 | LexicalRule(TokenType::Operator, QRegularExpression("\\G((!=)|(==)|(<=)|(>=)|(<>)|(\\*\\*)|=|<|>|\\+|-)")), 54 | LexicalRule(TokenType::NoMatch, QRegularExpression("\\G.")) 55 | }; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /src/tokenizer.h: -------------------------------------------------------------------------------- 1 | #ifndef TOKENIZER_H 2 | #define TOKENIZER_H 3 | 4 | #include 5 | #include 6 | 7 | class Tokenizer 8 | { 9 | public: 10 | enum TokenType 11 | { 12 | String, 13 | Keyword, 14 | Tag, 15 | Whitespace, 16 | Identifier, 17 | Symbol, 18 | OpenParan, 19 | CloseParan, 20 | Operator, 21 | Integer, 22 | Float, 23 | NoMatch 24 | }; 25 | 26 | struct LexicalRule { 27 | TokenType type; 28 | QRegularExpression regExp; 29 | 30 | LexicalRule(TokenType type, QRegularExpression regExp) : 31 | type(type), 32 | regExp(regExp) 33 | {} 34 | }; 35 | 36 | struct Token { 37 | int start; 38 | int length; 39 | TokenType type; 40 | 41 | Token(int start, int length, TokenType type) : 42 | start(start), 43 | length(length), 44 | type(type) 45 | {} 46 | }; 47 | 48 | Tokenizer(); 49 | QList Tokenize(QString in); 50 | void SetSQL(); 51 | 52 | QList m_rules; 53 | }; 54 | 55 | #endif // TOKENIZER_H 56 | -------------------------------------------------------------------------------- /src/treeitem.cpp: -------------------------------------------------------------------------------- 1 | #include "treeitem.h" 2 | 3 | #include 4 | 5 | TreeItem::TreeItem(const QVector &data, TreeItem *parent) 6 | { 7 | m_parentItem = parent; 8 | m_itemData = data; 9 | } 10 | 11 | TreeItem::~TreeItem() 12 | { 13 | qDeleteAll(m_childItems); 14 | } 15 | 16 | TreeItem *TreeItem::Child(int number) 17 | { 18 | return m_childItems.value(number); 19 | } 20 | 21 | int TreeItem::ChildCount() const 22 | { 23 | return m_childItems.count(); 24 | } 25 | 26 | int TreeItem::ChildNumber() const 27 | { 28 | if (m_parentItem) 29 | return m_parentItem->m_childItems.indexOf(const_cast(this)); 30 | 31 | return 0; 32 | } 33 | 34 | int TreeItem::ColumnCount() const 35 | { 36 | return m_itemData.count(); 37 | } 38 | 39 | QVariant TreeItem::Data(int column) const 40 | { 41 | return m_itemData.value(column); 42 | } 43 | 44 | bool TreeItem::InsertChildren(int position, int count, int columns) 45 | { 46 | if (position < 0 || position > m_childItems.size()) 47 | return false; 48 | 49 | for (int row = 0; row < count; ++row) 50 | { 51 | QVector data(columns); 52 | TreeItem *item = new TreeItem(data, this); 53 | m_childItems.insert(position, item); 54 | } 55 | 56 | return true; 57 | } 58 | 59 | TreeItem * TreeItem::AddChild() 60 | { 61 | InsertChildren(ChildCount(), 1, ColumnCount()); 62 | return Child(ChildCount() - 1); 63 | } 64 | 65 | bool TreeItem::InsertColumns(int position, int columns) 66 | { 67 | if (position < 0 || position > m_itemData.size()) 68 | return false; 69 | 70 | for (int column = 0; column < columns; ++column) 71 | m_itemData.insert(position, QVariant()); 72 | 73 | foreach (TreeItem *child, m_childItems) 74 | child->InsertColumns(position, columns); 75 | 76 | return true; 77 | } 78 | 79 | TreeItem *TreeItem::Parent() 80 | { 81 | return m_parentItem; 82 | } 83 | 84 | bool TreeItem::RemoveChildren(int position, int count) 85 | { 86 | if (position < 0 || position + count > m_childItems.size()) 87 | return false; 88 | 89 | for (int row = 0; row < count; ++row) 90 | delete m_childItems.takeAt(position); 91 | 92 | return true; 93 | } 94 | 95 | bool TreeItem::RemoveColumns(int position, int columns) 96 | { 97 | if (position < 0 || position + columns > m_itemData.size()) 98 | return false; 99 | 100 | for (int column = 0; column < columns; ++column) 101 | m_itemData.remove(position); 102 | 103 | foreach (TreeItem *child, m_childItems) 104 | child->RemoveColumns(position, columns); 105 | 106 | return true; 107 | } 108 | 109 | bool TreeItem::SetData(int column, const QVariant &value) 110 | { 111 | if (column < 0 || column >= m_itemData.size()) 112 | return false; 113 | 114 | m_itemData[column] = value; 115 | return true; 116 | } 117 | -------------------------------------------------------------------------------- /src/treeitem.h: -------------------------------------------------------------------------------- 1 | #ifndef TREEITEM_H 2 | #define TREEITEM_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class TreeItem 9 | { 10 | public: 11 | explicit TreeItem(const QVector &Data, TreeItem *Parent = 0); 12 | ~TreeItem(); 13 | 14 | TreeItem *Child(int number); 15 | int ChildCount() const; 16 | int ColumnCount() const; 17 | QVariant Data(int column) const; 18 | TreeItem * AddChild(); 19 | bool InsertChildren(int position, int count, int columns); 20 | bool InsertColumns(int position, int columns); 21 | TreeItem *Parent(); 22 | bool RemoveChildren(int position, int count); 23 | bool RemoveColumns(int position, int columns); 24 | int ChildNumber() const; 25 | bool SetData(int column, const QVariant &value); 26 | 27 | private: 28 | QList m_childItems; 29 | QVector m_itemData; 30 | TreeItem * m_parentItem; 31 | }; 32 | 33 | #endif // TREEITEM_H 34 | -------------------------------------------------------------------------------- /src/treemodel.h: -------------------------------------------------------------------------------- 1 | #ifndef TREEMODEL_H 2 | #define TREEMODEL_H 3 | 4 | #include "colorlibrary.h" 5 | #include "highlightoptions.h" 6 | #include "searchopt.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | class TreeItem; 20 | typedef QHash ColumnKeys; 21 | typedef QList EventList; 22 | typedef std::shared_ptr EventListPtr; 23 | 24 | enum class TABTYPE { 25 | SingleFile = 0, 26 | Directory, 27 | ExportedEvents 28 | }; 29 | 30 | enum class TimeMode { 31 | GlobalDateTime, 32 | GlobalTime, 33 | TimeDeltas, 34 | }; 35 | 36 | class TreeModel : public QAbstractItemModel 37 | { 38 | Q_OBJECT 39 | 40 | public: 41 | TreeModel(const QStringList &headers, const EventListPtr events, QObject *parent = 0); 42 | ~TreeModel(); 43 | 44 | QVariant data(const QModelIndex &index, int role) const override; 45 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 46 | QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; 47 | QModelIndex parent(const QModelIndex &index) const override; 48 | 49 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 50 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 51 | 52 | Qt::ItemFlags flags(const QModelIndex &index) const override; 53 | bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; 54 | bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole) override; 55 | 56 | bool insertColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) override; 57 | bool removeColumns(int position, int columns, const QModelIndex &parent = QModelIndex()) override; 58 | bool insertRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override; 59 | bool removeRows(int position, int rows, const QModelIndex &parent = QModelIndex()) override; 60 | 61 | QString GetChildValueString(const QModelIndex &index, QString key) const; 62 | int MergeIntoModelData(const EventList& events); 63 | void AddToModelData(const EventList& events); 64 | bool ValidFindOpts(); 65 | void ClearAllEvents(); 66 | void SetTimeMode(TimeMode mode); 67 | TimeMode GetTimeMode() const; 68 | void ShowDeltas(qint64 delta); 69 | bool IsHighlightedRow(int row) const; 70 | QJsonObject GetEvent(QModelIndex idx) const; 71 | QJsonValue GetConsolidatedEventContent(QModelIndex idx) const; 72 | QString GetValueFullString(const QModelIndex& idx, bool singleLineFormat = false) const; 73 | TABTYPE TabType() const; 74 | void SetTabType(TABTYPE type); 75 | const HighlightOptions& GetHighlightFilters() const; 76 | void SetHighlightFilters(const HighlightOptions& highlightOpts); 77 | void AddHighlightFilter(const SearchOpt& filter); 78 | bool HasHighlightFilters() const; 79 | 80 | bool m_highlightOnlyMode; 81 | bool m_liveMode; 82 | ColorLibrary m_colorLibrary; 83 | SearchOpt m_findOpts; 84 | QList m_paths; 85 | 86 | private: 87 | void SetupModelData(TreeItem *parent); 88 | void SetupChild(TreeItem *parent, const QJsonObject & event); 89 | void AddChildren(QJsonObject &obj, TreeItem *parent); 90 | void AddChild(const QString& key, const QJsonValue& value, TreeItem* parent); 91 | void InsertChild(int position, const QJsonObject & event); 92 | QString JsonToString(const QJsonValue& json, const bool isSingleLine = true) const; 93 | QJsonValue ConsolidateValueAndActivity(const QJsonObject& event) const; 94 | QColor ItemHighlightColor(const QModelIndex& idx) const; 95 | QString GetDeltaMSecs(QDateTime dateTime) const; 96 | TreeItem *GetItem(const QModelIndex &index) const; 97 | 98 | TreeItem * m_rootItem; 99 | TimeMode m_timeMode = TimeMode::GlobalDateTime; 100 | qint64 m_deltaBase = 0; 101 | EventListPtr m_allEvents; 102 | TABTYPE m_fileType; 103 | HighlightOptions m_highlightOpts; 104 | mutable QHash m_highlightColorCache; 105 | }; 106 | 107 | #endif // TREEMODEL_H 108 | -------------------------------------------------------------------------------- /src/valuedlg.cpp: -------------------------------------------------------------------------------- 1 | #include "valuedlg.h" 2 | #include "ui_valuedlg.h" 3 | 4 | #include "logtab.h" 5 | #include "themeutils.h" 6 | #include "tokenizer.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | QByteArray ValueDlg::sm_savedGeometry { QByteArray() }; 23 | qreal ValueDlg::sm_savedFontPointSize { 0 }; 24 | bool ValueDlg::sm_savedWrapText { true }; 25 | QJsonUtils::Notation ValueDlg::sm_notation { QJsonUtils::Notation::YAML }; 26 | 27 | ValueDlg::ValueDlg(QWidget *parent) : 28 | QDialog(parent), 29 | ui(new Ui::ValueDlg) 30 | { 31 | ui->setupUi(this); 32 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); 33 | restoreGeometry(sm_savedGeometry); 34 | 35 | // Set a monospaced font. 36 | QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); 37 | fixedFont.setPointSizeF(sm_savedFontPointSize); 38 | ui->textEdit->setFont(fixedFont); 39 | 40 | auto prevKey = QKeySequence(Qt::CTRL | Qt::Key_Up); 41 | ui->prevButton->setShortcut(prevKey); 42 | ui->prevButton->setToolTip(QString("Go to previous event (%1)").arg(prevKey.toString(QKeySequence::NativeText))); 43 | auto nextKey = QKeySequence(Qt::CTRL | Qt::Key_Down); 44 | ui->nextButton->setShortcut(nextKey); 45 | ui->nextButton->setToolTip(QString("Go to next event (%1)").arg(nextKey.toString(QKeySequence::NativeText))); 46 | 47 | QShortcut *shortcutPlus = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Plus), this); 48 | connect(shortcutPlus, &QShortcut::activated, this, &ValueDlg::on_zoomIn); 49 | QShortcut *shortcutEqual = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Equal), this); 50 | connect(shortcutEqual, &QShortcut::activated, this, &ValueDlg::on_zoomIn); 51 | QShortcut *shortcutMinus = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key_Minus), this); 52 | connect(shortcutMinus, &QShortcut::activated, this, &ValueDlg::on_zoomOut); 53 | 54 | m_id = QString(""); 55 | m_key = QString(""); 56 | m_queryPlan = QString(""); 57 | Options& options = Options::GetInstance(); 58 | m_visualizationServiceEnable = options.getVisualizationServiceEnable(); 59 | m_visualizationServiceURL = options.getVisualizationServiceURL(); 60 | 61 | ui->prevButton->setIcon(QIcon(ThemeUtils::GetThemedIcon(":/value-previous.png"))); 62 | ui->nextButton->setIcon(QIcon(ThemeUtils::GetThemedIcon(":/value-next.png"))); 63 | ui->wrapTextCheck->setChecked(sm_savedWrapText); 64 | SetWrapping(sm_savedWrapText); 65 | 66 | { 67 | const QSignalBlocker blocker(ui->notationComboBox); 68 | ui->notationComboBox->addItems(QJsonUtils::GetNotationNames()); 69 | ui->notationComboBox->setCurrentText(QJsonUtils::GetNameForNotation(sm_notation)); 70 | } 71 | 72 | QFile sqlsyntaxcss(":/sqlsyntax.css"); 73 | sqlsyntaxcss.open(QIODevice::ReadOnly); 74 | QTextStream textStream(&sqlsyntaxcss); 75 | QString styleSheet = textStream.readAll(); 76 | sqlsyntaxcss.close(); 77 | 78 | QTextDocument * textDoc = new QTextDocument(ui->textEdit); 79 | textDoc->setDefaultStyleSheet(styleSheet); 80 | textDoc->setDefaultFont(fixedFont); 81 | ui->textEdit->setDocument(textDoc); 82 | } 83 | 84 | ValueDlg::~ValueDlg() 85 | { 86 | delete ui; 87 | } 88 | 89 | void ValueDlg::on_zoomIn() 90 | { 91 | ui->textEdit->zoomIn(1); 92 | } 93 | 94 | void ValueDlg::on_zoomOut() 95 | { 96 | ui->textEdit->zoomOut(1); 97 | } 98 | 99 | static QString MakeHTMLSafe(QString input) 100 | { 101 | auto output = input.toHtmlEscaped(); 102 | // Use HTML safe spacing 103 | output.replace(" ", " "); 104 | output.replace("\t", "    "); 105 | output.replace("\n", "
"); 106 | 107 | return output; 108 | } 109 | 110 | static QString HighlightSyntax(QString in) 111 | { 112 | in = in.replace("; ", "\n"); 113 | Tokenizer sqlLexer; 114 | sqlLexer.SetSQL(); 115 | QList tokens = sqlLexer.Tokenize(in); 116 | if (tokens.size() == 0) 117 | return in; 118 | 119 | QString out; 120 | 121 | QHash TokenTypeToCSSClass = { 122 | {Tokenizer::TokenType::Keyword, "k"}, 123 | {Tokenizer::TokenType::String, "s"}, 124 | {Tokenizer::TokenType::Tag, "t"}, 125 | {Tokenizer::TokenType::Whitespace, "w"}, 126 | {Tokenizer::TokenType::Identifier, "i"}, 127 | {Tokenizer::TokenType::Symbol, "sy"}, 128 | {Tokenizer::TokenType::OpenParan, "op"}, 129 | {Tokenizer::TokenType::CloseParan, "cp"}, 130 | {Tokenizer::TokenType::Operator, "o"}, 131 | {Tokenizer::TokenType::NoMatch, "n"}, 132 | {Tokenizer::TokenType::Integer, "in"}, 133 | {Tokenizer::TokenType::Float, "f"} 134 | }; 135 | 136 | // Build the html, grouping concurrent same token items in the same span. 137 | Tokenizer::TokenType prevType = tokens[0].type; 138 | out += ""; 139 | for (Tokenizer::Token token : tokens) 140 | { 141 | QString word = in.mid(token.start, token.length); 142 | word = MakeHTMLSafe(word); 143 | if(token.type != prevType) 144 | { 145 | out += ""; 146 | prevType = token.type; 147 | } 148 | out += word; 149 | } 150 | out += ""; 151 | return out; 152 | } 153 | 154 | void ValueDlg::SetContent(QString id, QString key, QJsonValue value) 155 | { 156 | m_id = id; 157 | m_key = key; 158 | m_value = value; 159 | 160 | setWindowTitle(QString("ID: %1 - Key: %2").arg(m_id, m_key)); 161 | 162 | UpdateValueBox(); 163 | 164 | // Recognize query plans 165 | m_queryPlan = ""; 166 | if (value.isObject()) { 167 | if (m_key.startsWith("logical-query") || 168 | m_key.startsWith("aql-table") || 169 | m_key == "federate-query" || 170 | m_key == "remote-query-planning" || 171 | m_key == "begin-query" || 172 | m_key == "begin-protocol.query" || 173 | m_key == "end-query" || 174 | m_key == "end-protocol.query") 175 | { 176 | // Assume that queries starting with < have query function trees or logical-query. 177 | // They normally start with "", "" or "" 178 | auto queryText = value.toObject()["query"].toString(); 179 | if (queryText.startsWith("<")) 180 | { 181 | m_queryPlan = queryText; 182 | } 183 | } 184 | else if (m_key.startsWith("query-plan") || m_key == "optimizer-step") 185 | { 186 | auto plan = value.toObject()["plan"]; 187 | if (plan.isObject()) { 188 | QJsonDocument doc(plan.toObject()); 189 | m_queryPlan = doc.toJson(QJsonDocument::Compact); 190 | } 191 | } 192 | } 193 | // Setup UI for query plan 194 | if (!m_queryPlan.isEmpty()) 195 | { 196 | ui->visualizeButton->setEnabled(true); 197 | ui->visualizeLabel->setText(""); 198 | } 199 | else 200 | { 201 | ui->visualizeButton->setEnabled(false); 202 | ui->visualizeLabel->setText("Nothing to visualize"); 203 | } 204 | 205 | ui->notationComboBox->setEnabled(QJsonUtils::IsStructured(value)); 206 | ui->notationComboBox->repaint(); 207 | } 208 | 209 | void ValueDlg::UpdateValueBox() { 210 | QString value = QJsonUtils::Format(m_value, sm_notation); 211 | 212 | int syntaxHighlightLimit = Options::GetInstance().getSyntaxHighlightLimit(); 213 | bool syntaxHighlight = (!m_key.isEmpty() && 214 | QJsonUtils::IsStructured(m_value) && 215 | syntaxHighlightLimit && 216 | value.size() <= syntaxHighlightLimit); 217 | if (syntaxHighlight) 218 | { 219 | QString htmlText(QString("") + HighlightSyntax(value) + QString("")); 220 | ui->textEdit->setHtml(htmlText); 221 | } 222 | else 223 | { 224 | // Toggling between "setHtml" and "setPlainText" still winds up formatting 225 | // the "plain text" based on the previous HTML contents. The simplest way 226 | // to keep it "plain" appears to be to just keep it HTML and omit the styling. 227 | // (alternatively, we could explicitly use setTextColor to make it black.. 228 | // but that might not play well with themes?) 229 | QString htmlText(QString("%1").arg(MakeHTMLSafe(value))); 230 | ui->textEdit->setHtml(htmlText); 231 | } 232 | ui->textEdit->moveCursor(QTextCursor::Start); 233 | ui->textEdit->ensureCursorVisible(); 234 | ui->textEdit->repaint(); 235 | } 236 | 237 | void ValueDlg::on_nextButton_clicked() 238 | { 239 | emit next(); 240 | } 241 | 242 | void ValueDlg::on_prevButton_clicked() 243 | { 244 | emit prev(); 245 | } 246 | 247 | void ValueDlg::on_wrapTextCheck_clicked() 248 | { 249 | SetWrapping(ui->wrapTextCheck->isChecked()); 250 | } 251 | 252 | void ValueDlg::on_notationComboBox_currentTextChanged(const QString& newValue) 253 | { 254 | sm_notation = QJsonUtils::GetNotationFromName(newValue); 255 | UpdateValueBox(); 256 | } 257 | 258 | void ValueDlg::SetWrapping(const bool wrapText) 259 | { 260 | sm_savedWrapText = wrapText; 261 | if (sm_savedWrapText) 262 | { 263 | ui->textEdit->setLineWrapMode(QTextEdit::WidgetWidth); 264 | } 265 | else 266 | { 267 | ui->textEdit->setLineWrapMode(QTextEdit::NoWrap); 268 | } 269 | } 270 | 271 | QUrl ValueDlg::GetUploadURL() 272 | { 273 | return QUrl(m_visualizationServiceURL); 274 | } 275 | 276 | QUrl ValueDlg::GetVisualizeURL(QNetworkReply * const reply) 277 | { 278 | // The URL to visualize the query should be in the response. 279 | // 280 | // Sample response: 281 | //http://localhost:3000/d3/query-graphs.html?upload=y&file=31480681.xml 282 | auto response = QString(reply->readAll()); 283 | qDebug() << "Response from visualization service:\n" << response; 284 | return QUrl(response); 285 | } 286 | 287 | void ValueDlg::UploadQuery() 288 | { 289 | QHttpMultiPart *multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType, this); 290 | QHttpPart filePart; 291 | filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"queryfile\"; filename=\"yeah.xml\"")); 292 | filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("text/xml")); 293 | 294 | filePart.setBody(m_queryPlan.toUtf8()); 295 | 296 | multipart->append(filePart); 297 | 298 | QNetworkRequest request(GetUploadURL()); 299 | QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); 300 | QNetworkReply *reply = networkManager->post(request, multipart); 301 | // Set the parent of the multipart to the reply, so that we delete the multiPart with the reply 302 | multipart->setParent(reply); 303 | 304 | connect(networkManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(on_uploadFinished(QNetworkReply*))); 305 | } 306 | 307 | void ValueDlg::on_visualizeButton_clicked() 308 | { 309 | if (m_visualizationServiceEnable) 310 | { 311 | UploadQuery(); 312 | } 313 | else 314 | { 315 | VisualizeQuery(); 316 | } 317 | } 318 | 319 | void ValueDlg::on_uploadFinished(QNetworkReply* reply) 320 | { 321 | if (reply->error() == QNetworkReply::NetworkError::NoError) 322 | { 323 | ui->visualizeLabel->setText("Visualization uploaded, check your browser..."); 324 | QDesktopServices::openUrl(GetVisualizeURL(reply)); 325 | } 326 | else 327 | { 328 | ui->visualizeLabel->setText(QString("Failed to upload to URL '%1'").arg(reply->url().toString())); 329 | qDebug() << "Failed to upload query"; 330 | } 331 | ui->visualizeButton->setEnabled(false); 332 | } 333 | 334 | void ValueDlg::VisualizeQuery() 335 | { 336 | // Conditionally enable QtWebEngine debugging via Chromium-based browser at http://localhost:9000 337 | #ifdef QT_DEBUG 338 | qputenv("QTWEBENGINE_REMOTE_DEBUGGING", "9000"); 339 | #endif 340 | 341 | m_view = new QWebEngineView(this); 342 | m_view->setAttribute(Qt::WA_DeleteOnClose); 343 | 344 | // Set Qt::Dialog rather than Qt::Window to disable zoom and avoid a Qt bug with unresponsive zoomed views on OSX 345 | m_view->setWindowFlags(Qt::Dialog | Qt::WindowMaximizeButtonHint | Qt::WindowCloseButtonHint); 346 | m_view->setWindowTitle(QString("Query Visualization - ID: %1 - Key: %2").arg(m_id, m_key)); 347 | 348 | QScreen* screen = this->windowHandle()->screen(); 349 | QRect screenRect = screen->availableGeometry(); 350 | int width = screenRect.width() - 400; 351 | int height = screenRect.height() - 200; 352 | int x = screenRect.left() + 200; 353 | int y = screenRect.top() + 80; 354 | m_view->resize(width, height); 355 | m_view->move(x, y); 356 | 357 | // Add keyboard shortcut for reloading the view 358 | QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+r"), m_view); 359 | QObject::connect(shortcut, &QShortcut::activated, m_view, &QWebEngineView::reload); 360 | 361 | // Reload the view one time to work around a Qt bug causing left justified alignment 362 | m_reload = true; 363 | connect(m_view, &QWebEngineView::loadFinished, this, &ValueDlg::on_loadFinished); 364 | m_view->load(QUrl("qrc:///query-graphs/query-graphs.tlv.html?inline=" + QUrl::toPercentEncoding(m_queryPlan))); 365 | } 366 | 367 | void ValueDlg::on_loadFinished(bool loaded) 368 | { 369 | if (!loaded) 370 | { 371 | ui->visualizeLabel->setText(QString("Failed to load query visualization")); 372 | qDebug() << "Failed to load query visualization"; 373 | } 374 | if (m_reload) 375 | { 376 | m_reload = false; 377 | m_view->reload(); 378 | } 379 | m_view->show(); 380 | } 381 | 382 | void ValueDlg::reject() 383 | { 384 | sm_savedGeometry = saveGeometry(); 385 | sm_savedFontPointSize = ui->textEdit->document()->defaultFont().pointSize(); 386 | QDialog::reject(); 387 | } 388 | 389 | // Persist the dialog state into QSettings. 390 | // 391 | // WriteSettings and ReadSettings should only be called once during app start and close, 392 | // so the dialog state between multiple instances will not interfere each other. 393 | // 394 | void ValueDlg::WriteSettings(QSettings& settings) 395 | { 396 | settings.beginGroup("ValueDialog"); 397 | settings.setValue("geometry", sm_savedGeometry); 398 | settings.setValue("fontPointSize", sm_savedFontPointSize); 399 | settings.setValue("wrapText", sm_savedWrapText); 400 | settings.setValue("notation", QJsonUtils::GetNameForNotation(sm_notation)); 401 | settings.endGroup(); 402 | } 403 | 404 | void ValueDlg::ReadSettings(QSettings& settings) 405 | { 406 | settings.beginGroup("ValueDialog"); 407 | sm_savedGeometry = settings.value("geometry").toByteArray(); 408 | sm_savedFontPointSize = settings.value("fontPointSize").toReal(); 409 | sm_savedWrapText = settings.value("wrapText").toBool(); 410 | sm_notation = QJsonUtils::GetNotationFromName(settings.value("notation").toString()); 411 | settings.endGroup(); 412 | } 413 | -------------------------------------------------------------------------------- /src/valuedlg.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "qjsonutils.h" 6 | class QNetworkReply; 7 | class QSettings; 8 | class QWebEngineView; 9 | 10 | namespace Ui { 11 | class ValueDlg; 12 | } 13 | 14 | class ValueDlg : public QDialog 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit ValueDlg(QWidget *parent = 0); 20 | ~ValueDlg(); 21 | void SetContent(QString id, QString key, QJsonValue value); 22 | 23 | static void WriteSettings(QSettings& settings); 24 | static void ReadSettings(QSettings& settings); 25 | 26 | protected: 27 | void UpdateValueBox(); 28 | void reject() override; 29 | 30 | private slots: 31 | void on_visualizeButton_clicked(); 32 | void on_wrapTextCheck_clicked(); 33 | void on_prevButton_clicked(); 34 | void on_notationComboBox_currentTextChanged(const QString& newValue); 35 | void on_nextButton_clicked(); 36 | void on_uploadFinished(QNetworkReply*); 37 | void on_loadFinished(bool); 38 | void on_zoomIn(); 39 | void on_zoomOut(); 40 | 41 | signals: 42 | void next(); 43 | void prev(); 44 | 45 | private: 46 | QUrl GetUploadURL(); 47 | QUrl GetVisualizeURL(QNetworkReply * const reply); 48 | void UploadQuery(); 49 | void VisualizeQuery(); 50 | void SetWrapping(const bool wrapText); 51 | 52 | Ui::ValueDlg *ui; 53 | QString m_id; 54 | QString m_key; 55 | QString m_queryPlan; 56 | QJsonValue m_value; 57 | bool m_visualizationServiceEnable; 58 | QString m_visualizationServiceURL; 59 | QWebEngineView *m_view; 60 | bool m_reload; 61 | 62 | static QByteArray sm_savedGeometry; 63 | static qreal sm_savedFontPointSize; 64 | static bool sm_savedWrapText; 65 | static QJsonUtils::Notation sm_notation; 66 | }; 67 | -------------------------------------------------------------------------------- /src/valuedlg.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ValueDlg 4 | 5 | 6 | 7 | 0 8 | 0 9 | 748 10 | 474 11 | 12 | 13 | 14 | 15 | 400 16 | 250 17 | 18 | 19 | 20 | log event value 21 | 22 | 23 | true 24 | 25 | 26 | true 27 | 28 | 29 | 30 | 31 | 32 | 33 | MS Shell Dlg 2 34 | 10 35 | 36 | 37 | 38 | Qt::ScrollBarAlwaysOn 39 | 40 | 41 | Qt::ScrollBarAlwaysOn 42 | 43 | 44 | QTextEdit::WidgetWidth 45 | 46 | 47 | true 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Wrap text 57 | 58 | 59 | true 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 10 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 🡱 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 10 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | :/+darktheme/value-next.png:/+darktheme/value-next.png 100 | 101 | 102 | 103 | 16 104 | 16 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | Qt::Horizontal 113 | 114 | 115 | 116 | 40 117 | 20 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | Nothing to visualize 126 | 127 | 128 | 129 | 130 | 131 | 132 | false 133 | 134 | 135 | 136 | 0 137 | 0 138 | 139 | 140 | 141 | Visualize Query 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 0 150 | 0 151 | 152 | 153 | 154 | Close 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | closeButton 168 | clicked() 169 | ValueDlg 170 | close() 171 | 172 | 173 | 688 174 | 438 175 | 176 | 177 | 367 178 | 229 179 | 180 | 181 | 182 | 183 | 184 | -------------------------------------------------------------------------------- /src/zoomabletreeview.cpp: -------------------------------------------------------------------------------- 1 | #include "zoomabletreeview.h" 2 | #include "options.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | qreal ZoomableTreeView::sm_savedFontPointSize { 0 }; 11 | 12 | ZoomableTreeView::ZoomableTreeView(QWidget *parent) 13 | :QTreeView(parent) 14 | { 15 | QFont fixedFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); 16 | if (sm_savedFontPointSize > 0) { 17 | fixedFont.setPointSizeF(sm_savedFontPointSize); 18 | } 19 | this->setFont(fixedFont); 20 | } 21 | 22 | void ZoomableTreeView::SetAutoResizeColumns(const std::vector& columns) 23 | { 24 | m_autoResizedColumns = columns; 25 | ResizeColumns(); 26 | } 27 | 28 | void ZoomableTreeView::ResizeColumns() 29 | { 30 | // Resize column width. Not using QHeaderView::setSectionResizeMode because 31 | // it does not allow users to change size and only looks at 1000 rows by default. 32 | for (int col : m_autoResizedColumns) 33 | { 34 | resizeColumnToContents(col); 35 | } 36 | } 37 | 38 | void ZoomableTreeView::ResizeFont(int delta) 39 | { 40 | QFont font = this->font(); 41 | 42 | // Set the font size, make sure it fits in a reasonable range 43 | int pointSize; 44 | if (delta == 0) 45 | { 46 | QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); 47 | pointSize = font.pointSize(); 48 | } 49 | else 50 | { 51 | pointSize = font.pointSize() + delta; 52 | pointSize = std::max(std::min(pointSize, MAX_FONT_SIZE), MIN_FONT_SIZE); 53 | } 54 | font.setPointSize(pointSize); 55 | this->setFont(font); 56 | sm_savedFontPointSize = pointSize; 57 | 58 | ResizeColumns(); 59 | } 60 | 61 | void ZoomableTreeView::wheelEvent(QWheelEvent *event) 62 | { 63 | if (event->modifiers() & Qt::ControlModifier) 64 | { 65 | // Zooom-in or Zoom-out if CTRL key is used (Command key on Mac) 66 | int fontChange = (event->pixelDelta().y() > 0) ? 1 : -1; 67 | ResizeFont(fontChange); 68 | event->accept(); 69 | } 70 | else 71 | { 72 | // Use default wheel event otherwise 73 | QTreeView::wheelEvent(event); 74 | } 75 | } 76 | 77 | void ZoomableTreeView::keyPressEvent(QKeyEvent* event) 78 | { 79 | if ((event->key() == Qt::Key_Plus || event->key() == Qt::Key_Equal) && 80 | (QApplication::keyboardModifiers() & Qt::ControlModifier)) 81 | { 82 | ResizeFont(1); 83 | } 84 | else if ((event->key() == Qt::Key_Minus) && (QApplication::keyboardModifiers() & Qt::ControlModifier)) 85 | { 86 | ResizeFont(-1); 87 | } 88 | else if ((event->key() == Qt::Key_0) && (QApplication::keyboardModifiers() & Qt::ControlModifier)) 89 | { 90 | ResizeFont(0); 91 | } 92 | else 93 | { 94 | QTreeView::keyPressEvent(event); 95 | } 96 | } 97 | 98 | void ZoomableTreeView::WriteSettings(QSettings& settings) 99 | { 100 | settings.beginGroup("TreeView"); 101 | settings.setValue("fontPointSize", sm_savedFontPointSize); 102 | settings.endGroup(); 103 | } 104 | 105 | void ZoomableTreeView::ReadSettings(QSettings& settings) 106 | { 107 | settings.beginGroup("TreeView"); 108 | sm_savedFontPointSize = settings.value("fontPointSize").toReal(); 109 | settings.endGroup(); 110 | } 111 | -------------------------------------------------------------------------------- /src/zoomabletreeview.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | class QSettings; 5 | 6 | class ZoomableTreeView: public QTreeView 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | ZoomableTreeView(QWidget *parent); 12 | void SetAutoResizeColumns(const std::vector& columns); 13 | void ResizeColumns(); 14 | 15 | static void WriteSettings(QSettings& settings); 16 | static void ReadSettings(QSettings& settings); 17 | 18 | protected: 19 | void wheelEvent(QWheelEvent *) override; 20 | void keyPressEvent(QKeyEvent *) override; 21 | 22 | private: 23 | void ResizeFont(int delta); 24 | 25 | std::vector m_autoResizedColumns; 26 | 27 | static qreal sm_savedFontPointSize; 28 | 29 | const int MIN_FONT_SIZE = 7; 30 | const int MAX_FONT_SIZE = 24; 31 | }; 32 | --------------------------------------------------------------------------------