diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4bf5e097 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ + +*.pro.user +*.autosave +/build* +.vscode +*.so \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 00000000..1b74b360 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,18 @@ +Copyright (c) 2020 Mahlet, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..ca931c85 --- /dev/null +++ b/README.md @@ -0,0 +1,26 @@ +## Building with Qt Creator and Qt 5.13+ +1. (Optional) Install any dev libraries required to get the full functionality +of the core plugins ([fftw3](http://www.fftw.org/download.html)) +2. Open the root project file, [src/hobbits.pro](src/hobbits.pro), in Qt Creator +3. Build and run the application + +## Using Hobbits +Read the user guide in the documentation and/or watch +[this walkthrough video](https://youtu.be/6ygkhze36qM) + +## Adding plugins +1. Read the plugin developer guide in the documentation and/or watch +[this plugin tutorial video](https://youtu.be/Dg3vknwLO74) +2. Run [wizard_installer.sh](wizards/wizard_installer.sh) from inside the +[wizards](wizards) directory to add Hobbits Plugin Qt Creator templates (restart +Qt Creator after doing this) +3. Add a plugin subproject to the appropriate plugins project and implement your +desired functionality + +## Viewing and Building the Documentation +1. Install the Python module `mkdocs` +2. Run `mkdocs serve` in the project root directory to serve the documentation +at `localhost:8000` +3. Run `mkdocs build` to build the documentation into static website files + +![Screenshot of the Hobbits GUI](docs/hobbits_screenshot.png) \ No newline at end of file diff --git a/docs/hobbits_screenshot.png b/docs/hobbits_screenshot.png new file mode 100644 index 00000000..6c6dc40e Binary files /dev/null and b/docs/hobbits_screenshot.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..b53433a3 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,211 @@ +# Overview + +Hobbits is a software platform for analyzing, processing, and visualizing bits. +The Hobbits GUI is the central tool of the platform, and will be the primary +focus of this document. However, the configurability and extensibility of the +analysis and processing parts of the platform make it an attractive option for +"headless" operation (e.g. a command line utility, or an analysis/processing +server.) + +## Why? + +Hobbits was developed to accelerate manual data analysis tasks that were +starting to burden Mahlet's hardware and software development. The de facto +solution involved the use of multiple tools with slow turnaround and improvised +integration (e.g. custom Python scripts and xxd.) Hobbits provided a fully +integrated analysis environment without sacrificing flexibility. As a result, it +has quickly accumulated success stories across the different development teams. + +There are tools that are similar to Hobbits, but none can claim to be as +extensible, portable, and flexible. GUI and plugin binaries can be easily +dropped, swapped, and executed on a variety of platforms without dependencies +or restrictions. This is a priceless advantage when developing analysis +tradecraft across a variety of communities, networks, or access levels. + +## What Now? + +The general architecture of Hobbits has proven to be highly effective and agile, +so the current development priorities are refining the GUI, and adding more +plugins. + +# Hobbits GUI + +The primary concern of Hobbits GUI is making machine-level data presentable to +humans so that they can analyze/fix/generate it easily. This usually involves +some level of processing, analysis, and visualization. One workflow could be +counting the number of bytes and looking at a hex dump. Another could be +performing a QAM remap, finding a data width, de-muxing, and then looking at a +bit raster. + +Hobbits simplifies the task of developing and repeating these workflows within a +single application. + +## Capabilities + +All analysis, processing, and visualization capabilities of Hobbits are provided +dynamically with plugins. (The plugin system is described in depth +[here](#plugin-system).) + +While the current collection of capabilities provides a uniquely fast, easy, and +portable experience, the true power of the platform lies in its ability add new +plugins that further customize and optimize desired workflows. Thoughtful +architectural planning has allowed several plugins to be developed within a few +hours - an example plugin was even created within a few minutes during a live +demo! + +The ease of extensibility provided by the plugin system will enable core feature +development to proceed rapidly, and even opens up the possibility of externally +developed plugin collections. + +### Displays + +Displays are critical because the information bandwidth of the average human +optical physiology is very high. Variety and configurability are necessary for +showing the most important charactaristics of different types of data. + + - Bit Raster: shows frames of bits as raster lines in which each pixel + represents a bit + - Byte Raster: same as the Bit Raster, but each pixel represents a byte + - Symbol Raster: same as the Bit Raster, but each pixel is a configurable + bit-length "symbol" with a correlating custom color + - Binary: shows frames of bits as lines of binary digits + - Hex: shows frames of bits as lines of hexadecimal digits + - ASCII: shows frames of bits as lines of decoded ASCII text + +### Analyzers + +Analyzers digest and decorate the data in a way that facilitates follow-on +processing and/or human evaluation. Analyzers are non-destructive. + + - Find: finds and navigates to bit, hex, octal, and ASCII patterns in the data + - Width Framer: frames the data to given bit-width. Includes an + auto-correlation function and width-picker graph. + - Header Framer: frames the data on a given constant header sequence + - Flexible Framer: frames data based on a regular-expression-like frame syntax + +### Operators + +Operators take some number of data inputs, and produce some number of data +outputs. Most operators are 1-in, 1-out, but the flexibility of the interface +enables several critical operations (e.g. data generators, muxes, demuxes.) +The output of operators will go into new containers so that the original data +can still be easily referenced. + + - Take Skip: performs common bitwise pre-processing operations using a + simple-yet-powerful "take, skip" syntax + - Bit Error: injects bit errors into the data + - LFSR: generates LFSR bits based on given parameters + - QAM Remapper: maps n-bit "symbols" to other n-bit "symbols" + +## Templates + +When a user develops a sequence of operations that they want to perform +frequently, they can save it as a "Template" and perform it later with a single +step. + +# Technical Architecture + +The rest of this document covers the technical architecture of the Hobbits +platform. + +## Challenges + +The architecture of Hobbits is best understood through the lens of the +challenges it faces in satisfying its user goals. + +### Required capabilities are unpredictable + +The problem space that can be approached with a "bitwise analysis, +processing, and visualization" tool is vast, even when scoped to a specific +domain like high-speed networks. Hobbits must be prepared for unexpected +features. + +### Required capabilities are numerous + +A large problem space also leads to a large number of capabilities being +integrated, and each newly integrated capability beckons adjacent capabilities +to be integrated, and so on. Hobbits must be prepared for a lot of features. + +### Required capabilities are performance-intensive + +When working with large amounts of data at the bit level, it is critical to use +processing cycles and memory efficiently. An effective interface and core +library needs to ensure that simple capabilities have efficient abstractions +available, while high-performance capabilities are unencumbered by abstraction. +Hobbits must provide lean interfaces and fast utilities. + +### Implemented capabilities need to work together + +Decoupling capabilities from one another in a plugin system makes it harder for +them to coordinate with each other and create the smoothest experience possible. +Hobbits must provide ways of orchestrating multiple plugins cleanly, and the +plugin interface must allow safe, opportunistic coordination between plugins. + +## Solutions + +### C++/Qt + +Using C++ and the Qt framework as the foundation for Hobbits makes it easier to +meet performance requirements, and trivializes the process of turning existing +C/C++ programs into plugins. This is useful because many performance-critical +data analysis and processing capabilities are written in C/C++. + +Qt enables rapid GUI design as well as a variety of well-documented convenience +tools, such as a plugin loading system. + +### Plugin System + +As mentioned above, all analysis, processing, and visualization capabilities of +Hobbits are provided dynamically with plugins. This makes it easy to add new +and unexpected capabilities without increasing the complexity of adding *more* +new and unexpected capabilities. This kind of development scaling can be +achieved without plugins, but plugins *guarantee* it. Furthemore, plugins enable +the program to be extended without any updates to the core program. + +The plugin interfaces grant a lot of implementation flexibility while +maintaining a narrow, inflexible coupling to the core program. The flexibility +enables the addition of unpredictable capabilities, while the clear coupling +enables useful integration between plugins. + +Another important consideration of the plugin system design was implementation +speed and ease. Adding new plugins needs to be fast and painless. The simplicity +of the essential interface components helps with this, but the core library +utilities and the plugin templates are also very important. + +### Core Library + +The core library's main purpose is to provide useful tools and abstractions for +the plugin implementations. This includes ways of accessing and operating on +packed bits, and partial implementations of common plugin types (e.g. displays +that show zoomable characters,) among other conveniences. + +The core library also contains useful capabilities for managing the integration +of different plugins in a sensible way. The Hobbits GUI relies heavily on these +default integration managers, and as a result is less than 1000 lines of code. +Other applications would be able to similarly leverage the core library, so +custom Hobbits command line utilities and processing servers could feasibly be +created (or added to existing programs) in a matter of hours rather than +days/weeks. + +### Templates + +Even with rigorous simplification of the plugin interfaces and useful +abstractions of the Qt plugin loader, there is still a small amount of +boilerplate code required when making a plugin. This boilerplate code can be +annoying to experienced developers, and it can be intimidating to developers +that are unfamiliar with the program. + +Templates, in the form of both Qt Creator wizards and Python cookiecutters, are +available to set up all of the boilerplate code required to make a functioning +plugin. This dramatically speeds up the development of new plugins, especially +simple plugins, and plugins that simply wrap existing C/C++ processing +capabilities. + +### Conan + +Hobbits currently uses the C/C++ package manager "Conan" in its building and +dependency management process. Conan works similarly to Python's pip, and +Node.js's npm in that it creates clear dependency plans that simplify the build +process when you are using external dependencies. It currently enables plugin +developers to write and build their plugins without needing to set up Qt or +Hobbits. \ No newline at end of file diff --git a/docs/labelled_hobbits_screenshot.png b/docs/labelled_hobbits_screenshot.png new file mode 100644 index 00000000..fb1639aa Binary files /dev/null and b/docs/labelled_hobbits_screenshot.png differ diff --git a/docs/plugin-developer-guide.md b/docs/plugin-developer-guide.md new file mode 100644 index 00000000..847452a8 --- /dev/null +++ b/docs/plugin-developer-guide.md @@ -0,0 +1,189 @@ +# Hobbits Plugin Development + +The Hobbits framework is designed to coordinate the capabilities of various +plugins into a smooth, cohesive experience. The plugins are where all of the +"actual work" gets done. + +There are currently three types of plugins in Hobbits: Displays, Analyzers, and +Operators. This document will cover general plugin implementation concepts, and +then walk through the development process for each type of plugin. + +## General Terms and Concepts + +### Qt Plugin API + +Hobbits leverages the low-level plugin API provided by Qt for its plugin system. +Refer to the following Qt documentation for details: + + - [Low-level Plugin API](https://doc.qt.io/qt-5/plugins-howto.html#the-low-level-api-extending-qt-applications) + - [Deploying Plugins](https://doc.qt.io/qt-5/deployment-plugins.html) + +### Bit Containers + +The `BitContainer` class is the primary means of storing data, and sharing it +between plugins. A `BitContainer` holds binary data, as well as metadata such as +how that data is framed. It also keeps a record of any changes that have been +made to it by Hobbits plugins. + +The binary data is held in a `BitArray`, which abstracts byte boundaries from +the user, and has a modest-but-growing assortment of performance efficiencies +and helper functions. + +General metadata is read and written with the `getMetadata` and `setMetadata` +methods, and it is stored as a simple map of strings to string lists. Plugins +can use container metadata as a general purpose bus for communicating with other +plugins, or even as a cache for their own purposes. For example, the "Find" +Analyzer plugin stores the results of a find operation in a container's +metadata so that when that container loses focus, the information will still be +available when the container comes back into focus. + +Highlights are a separate kind of metadata that deal with ranges of bits within +a bit container. They are read and written with the `getHighlights` and +`setHighlights` methods, and they are stored as a map of strings to range lists +(a range list is a list of start and end indices.) The "Find" Analyzer plugin +sets highlights in the ranges where it finds its search string, and several +Display plugins check for the existence of highlights with the "find" or +"find-focus"keys, and render visual highlights over those sections. + +Analyzer plugins do not have write access to bit containers, so they cannot use +the `setMetadata`, and `setHighlights` methods. As a result, they must include +the metadata and highlight entries they wish to add to the container in the +`AnalyzerResult`s that they return. The `AnalyzerResult` has `addRanges` and +`addMetadata` methods for this purpose. + +**Also, in order for an analyzer to set the frames of a bit container, it must** +**return an `AnalyzerResult` with the desired frames set via `addRanges` with** +**the "frames" key, e.g. `result.addRanges("frames", frameRanges)`, and,** +**ideally, with the maximum frame width set as well via the general metadata,** +**e.g. `result.addMetadata("max_frame_width", maxWidth)`**. As a result of this +special behavior, the "frames" range key, and the "max_frame_width" metadata +key should be considered reserved, and used only for this situation. + +### Common Plugin Interface Methods + +All of the plugin interfaces have a `getName` method that must return the name +of that plugin as it should appear to the user. The name must be unique. There +is also a common `createDefaultX` method (where X is the interface type) that +simply returns a default instance of the plugin. + +The Analyzer and Operator interfaces have several simple methods in common: + + - `applyToWidget` initializes the plugin's UI on a given widget + - `provideCallback` gives the plugin a `PluginCallback` instance that can be + used to request that it gets executed (this is useful because the central + application that triggers plugin execution is decoupled from the plugin's UI, + so in order for a plugin to trigger execution when, for example, the "Enter" + key is pressed in its UI form, it needs to relay this request through the + `PluginCallback` instance it is given.) + - `getStateFromUi` gets the plugin's current state/configuration based on how + its UI is filled out. If there are invalid or incomplete entries, the plugin + can return an empty state to indicate that it is not ready for execution. + - `canRecallPluginState` checks a plugin state to see if it can be used to + execute the plugin. If the state is empty or incomplete, it must return + false. + +#### Plugin State + +The plugin state contains parameters for how the plugin should run. For example, +the Take Skip operator plugin will require a "take_skip_string" parameter to +guide its execution. The state that it passed in can be provided by the plugin's +own `getStateFromUi` method, or it can come from a saved template that was +generated from a state returned by a plugin result (`OperatorResult` or +`AnalyzerResult`.) Plugin results will usually just return the state that was +passed in (e.g. `result->setPluginState(recallablePluginState)`,) but sometimes +that state needs to be enriched with execution details that were added by the +plugin, such as a random number seed, so that the execution can be duplicated +exactly with the returned state. + +#### Action Progress + +The `ActionProgress` parameter is passed to the primary methods of the operator +and analyzer interfaces. It allows a plugin to report its percentage progress as +it executes, and allows it to check if it has been cancelled so that it can halt +execution gracefully. + +## Specific Interfaces + +### Operator Interface + +Operators take some number of data inputs, and produce some number of data +outputs. Most operators are 1-in, 1-out, but the flexibility of the interface +enables several critical operations (e.g. data generators, muxes, demuxes.) +The output of operators will go into new containers so that the original data +can still be easily referenced. + +`operateOnContainers` takes a list of read-only bit containers, a plugin state, +and an `ActionProgress` instance. It returns an `OperatorResult` which contains +any new bit containers that have been created by the operator, and the plugin +state that will enable the operation to be duplicated exactly (see +[plugin state details above](#plugin-state).) **`operateOnContainers` might run +in a separate thread from the main GUI thread, so you cannot use Qt features +that depend on being in the main thread, e.g., showing a QMessageBox.** + +`getMinInputContainers` and `getMaxInputContainers` return the minimum and +maximum number of containers that the operator accepts as inputs. Operators that +take a single input and produce a single output would simply return 1 for both +of these functions. + +### Analyzer Interface + +Analyzers digest and decorate the data in a way that facilitates follow-on +processing and/or human evaluation. + +`analyzeBits` takes a read-only bit container, a plugin state, and an +`ActionProgress` instance. It returns an `AnalyzerResult` which contains new +general metadata and range entries for the container, and the plugin +state that will enable the operation to be duplicated exactly (see +[plugin state details above](#plugin-state).) **`analyzeBits` might run in a +separate thread from the main GUI thread, so you cannot use Qt features that +depend on being in the main thread, e.g., showing a QMessageBox.** + +`previewBits` takes a read-only bit container and it can be used to prepare the +plugin for execution or enhance the UI options it presents. The Width Framer +analyzer plugins generates auto-correlation data in the `previewBits` method, +which is used to display a width-selector graph with suggested widths. It is +fine to leave this method's implementation empty. + +### Display Interface + +Displays show the data in some sort of useful format (e.g. a bit raster, or a +hex dump.) They can use metadata and highlights in bit containers to augment a +depiction of the data, but they could also show *only* the metadata if that was +useful somehow (e.g. if there were some metrics worth presenting separately from +the plugin that generated them.) + +`getDisplayWidget` and `getControlsWidget` provide the display and (optional) +controls for a display plugin. They both take a `DisplayHandle` as their sole +parameter. + +The `DisplayHandle` provides access to a variety of things that a display might +need to access (e.g. the current bit container), or set (e.g. the bit that is +currently being hovered over by the mouse.) + +### Import/Export Interface + +Import/Export plugins import and/or export bit containers for use in Hobbits. +The simplest of these plugins is the "File Data" plugin that imports/exports +raw binary data from/to files. + +`canExport` and `canImport` return a boolean value indicating whether or not the +plugin is capable of importing or exporting (e.g. you might want a plugin that +can import data but not export it.) You can return `false` in both methods, but +that would be impolite. + +`importBits` and `exportBits` both receive a map of arguments (which might be +empty) and a pointer to a `QWidget` that should be used as the parent to any GUI +elements generated by the plugin (e.g. a file dialog.) + +`exportBits` also receives a bit container that should be exported. + +`importBits` returns a non-empty bit container if it was successful at importing +data. + +## Helpful Tools + +In order to simplify the process of developing plugins, there are Qt Creator +wizards and CMake/Conan Python cookiecutters that take care of most of the +boilerplate code. + +The wizards can be installed using the wizard_installer.sh script. \ No newline at end of file diff --git a/docs/user-guide.md b/docs/user-guide.md new file mode 100644 index 00000000..15391ede --- /dev/null +++ b/docs/user-guide.md @@ -0,0 +1,106 @@ +# Using the Hobbits GUI + +The Hobbits GUI is a powerful tool for manually evaluating data. It provides a +fully integrated set of tools for preprocessing, analyzing, and displaying data +in a variety of ways. This guide explains how to use the different features of +the Hobbits GUI, and then presents a few example workflows. + +## GUI Layout + +![The Hobbits GUI](labelled_hobbits_screenshot.png) + + 1. Bit Container Selection Panel + 2. Displays + 3. Analyzer Panel + 4. Controls for the Displays + 5. Operator Panel + +## Bit Containers + +Hobbits organizes imported data into "bit containers" that appear in a +collapsible panel on the left side of the GUI by default. The panel's visibility +can be toggled via: + + - The top menu at `View->Bit Containers` + - The `Ctrl-Shift-B` hotkey + - The `x` at the top right corner of the panel (only for hiding) + +Bit containers keep track of the data as well as metadata that can be extended, +modified, and read by plugins. An important part of this metadata is the +"frames" that the data is subdivided into. For example, when looking at +time-division multiplexed data in a bit raster, it is useful to have each +multiplexer frame on its own line so that constants, counters, and other +patterns can be easily identified. + +## Displays + +One of the most prominent features of the GUI is the variety of displays that it +provides for looking at data. Switching between displays is as simple as +selecting the tab of the display that you want to use. + +Your position in the data is preserved across displays, so you can navigate to a +section of your data in one display, and then switch tabs to see what that +section looks like in a different display. + +Displays can also be split so that you can look at more than one display at the +same time. Split views can be added or removed via: + + - The top menu in `View->Split View` + - The `Ctrl-Shift-V` hotkey adds a view to the right + - The `Ctrl-Shift-X` hotkey removes the rightmost view + +## Operators + +Operators are displayed in a collapsible panel that appears at the bottom of the +GUI by default. The panel's visibility can be toggled via: + + - The top menu at `View->Operator Plugins` + - The `Ctrl-Shift-O` hotkey + - The `x` at the top right corner of the panel (only for hiding) + +Operators can be used to modify, generate, combine, or separate data. A commonly +used operator is the Take Skip Operator, which allows you to quickly process +data with a simple filtering expression. For example, if you have data that is +uninteresting for the first 50 bits of every 256-bit frame, you can perform a +`s50t206` to skip 50 bits and then take 206 bits for each frame so that you are +only left with the last 206 bits of each frame for further evaluation. + +When an operator modifies data, the original data is still available to work +with. + +## Analyzers + +Analyzers appear in a collapsible panel on the right side of the GUI by default. +The panel's visibility can be toggled via: + + - The top menu at `View->Analyzer Plugins` + - The `Ctrl-A` hotkey + - The `x` at the top right corner of the panel (only for hiding) + +Analyzers provide useful information about the data, and can add that +information to the current bit container's metadata. For example, you can use +the Width Framer to discover appropriate frame widths for data, and then apply +one of those widths to the container. You can use the Find analyzer to find +instances of bit/byte/ASCII sequences in data, and then highlight and quickly +navigate to them. + +## Command line execution + +When running Hobbits from the command line, a variety of configuration and +data loading options are provided. Simply run it with the `--help` option to see +which options are available. + +## Plugin loading + +A standard Hobbits distributable binary comes with a set of core plugins that +should load without any user configuration. However, if you want to use a +plugin that is not part of the core distribution, you can either: + + - add it to the appropriate directory in the `plugins` folder of the + distribution folder or `~/.local/share/hobbits/plugins` +- or, use the `--extra-plugin-path` command line option to specify a folder with + non-core plugins in it (remember that a valid plugins folder has analyzers, + displays, and operators in their own sub-folders, e.g. `my-plugins/displays`.) +- or, specify your plugin path in your GUI config. In Linux, the config is an + ini file that can be found at `~/.config/Hobbits/Hobbits GUI.conf`, and the + plugin path configuration is the `path` variable in the `[Plugins]` section. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..bbfd4865 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,6 @@ +site_name: Hobbits +nav: + - Overview: index.md + - User Guide: user-guide.md + - Plugin Developer Guide: plugin-developer-guide.md +theme: readthedocs \ No newline at end of file diff --git a/src/hobbits-core/actionprogress.cpp b/src/hobbits-core/actionprogress.cpp new file mode 100644 index 00000000..8a6ddab1 --- /dev/null +++ b/src/hobbits-core/actionprogress.cpp @@ -0,0 +1,32 @@ +#include "actionprogress.h" +#include + +ActionProgress::ActionProgress(QObject *parent) : + QObject(parent), + m_progressPercent(0), + m_cancelled(0) +{ + +} + +void ActionProgress::setProgressPercent(int progressPercent) +{ + QMutexLocker locker(&m_mutex); + + m_progressPercent = progressPercent; + emit progressPercentChanged(m_progressPercent); +} + +void ActionProgress::setCancelled(bool cancelled) +{ + QMutexLocker locker(&m_mutex); + + m_cancelled = cancelled; +} + +bool ActionProgress::getCancelled() +{ + QMutexLocker locker(&m_mutex); + + return m_cancelled; +} diff --git a/src/hobbits-core/actionprogress.h b/src/hobbits-core/actionprogress.h new file mode 100644 index 00000000..bd326b02 --- /dev/null +++ b/src/hobbits-core/actionprogress.h @@ -0,0 +1,27 @@ +#ifndef ACTIONPROGRESS_H +#define ACTIONPROGRESS_H + +#include +#include + +class ActionProgress : public QObject +{ + Q_OBJECT + +public: + ActionProgress(QObject *parent = nullptr); + + void setProgressPercent(int progressPercent); + void setCancelled(bool cancelled); + bool getCancelled(); + +signals: + void progressPercentChanged(int); + +private: + int m_progressPercent; + QMutex m_mutex; + bool m_cancelled; +}; + +#endif // ACTIONPROGRESS_H diff --git a/src/hobbits-core/actionwatcher.h b/src/hobbits-core/actionwatcher.h new file mode 100644 index 00000000..e0501b5e --- /dev/null +++ b/src/hobbits-core/actionwatcher.h @@ -0,0 +1,41 @@ +#ifndef ACTIONWATCHER_H +#define ACTIONWATCHER_H + +#include "actionprogress.h" +#include +#include +#include + +template +class ActionWatcher +{ +public: + explicit ActionWatcher(QFuture future, QSharedPointer progress) : + m_progress(progress) + { + m_future = future; + m_futureWatcher.setFuture(future); + } + + T result() + { + return m_future.result(); + } + + QSharedPointer progress() + { + return m_progress; + } + + QFutureWatcher* watcher() + { + return &m_futureWatcher; + } + +private: + QFuture m_future; + QFutureWatcher m_futureWatcher; + QSharedPointer m_progress; +}; + +#endif // ACTIONWATCHER_H diff --git a/src/hobbits-core/analyzeractor.cpp b/src/hobbits-core/analyzeractor.cpp new file mode 100644 index 00000000..ddbe0b19 --- /dev/null +++ b/src/hobbits-core/analyzeractor.cpp @@ -0,0 +1,123 @@ +#include "analyzeractor.h" +#include "pluginaction.h" +#include "pluginactionlineage.h" +#include "pluginactionmanager.h" +#include "settingsmanager.h" + +AnalyzerActor::AnalyzerActor(PluginActionManager *manager, QSharedPointer pluginManager) : + QObject(), + m_pluginManager(pluginManager), + m_manager(manager) +{ +} + +QSharedPointer>> AnalyzerActor::analyzerFullAct( + QSharedPointer analyzer, + QSharedPointer container, + QJsonObject pluginState) +{ + + if (m_future.isRunning() || m_manager->isBusy()) { + return QSharedPointer>>(); + } + + if (pluginState.isEmpty()) { + pluginState = analyzer->getStateFromUi(); + if (pluginState.isEmpty() || pluginState.contains("error")) { + if (pluginState.contains("error")) { + emit reportError(QString("Plugin '%1' reported an error with its current state: '%2'").arg( + analyzer->getName()).arg(pluginState.value("error").toString())); + } + else if (pluginState.isEmpty()) { + emit reportError(QString( + "Plugin '%1' is in an invalid state and can't be executed. Double check the input fields.").arg( + analyzer->getName())); + } + return QSharedPointer>>(); + } + } + + QSharedPointer progress(new ActionProgress()); + + SettingsManager::getInstance().setPrivateSetting( + SettingsData::PLUGIN_RUNNING_KEY, + QVariant(m_pluginManager->getPluginLocation(analyzer->getName()))); + + m_future = QtConcurrent::run( + QThreadPool::globalInstance(), + AnalyzerActor::analyzerCall, + analyzer, + container, + pluginState, + progress); + + m_actionWatcher = QSharedPointer>>( + new ActionWatcher>( + m_future, + progress)); + m_analyzer = analyzer; + m_container = container; + m_pluginState = pluginState; + + connect(m_actionWatcher->watcher(), SIGNAL(finished()), this, SLOT(postProcess())); + + m_manager->registerAnalyzerWatcher(m_actionWatcher); + + return m_actionWatcher; +} + +void AnalyzerActor::postProcess() +{ + disconnect(m_actionWatcher->watcher(), SIGNAL(finished()), this, SLOT(postProcess())); + + QSharedPointer result = m_future.result(); + + if (result.isNull()) { + emit reportError(QString("Plugin '%1' failed to execute. Double check the input fields.").arg( + m_analyzer->getName())); + return; + } + + if (result->getPluginState().contains("error")) { + emit reportError(QString("Plugin '%1' reported an error with its processing: %2").arg(m_analyzer->getName()).arg( + result->getPluginState().value("error").toString())); + return; + } + + QSharedPointer action = + QSharedPointer( + new PluginAction( + PluginAction::Analyzer, + m_analyzer->getName(), + result->getPluginState())); + QSharedPointer lineage = PluginActionLineage::create(action)->setParent( + m_container->getActionLineage()); + + m_container->setActionLineage(lineage); + + for (auto key : result->getMetadata().keys()) { + m_container->setMetadata(key, result->getMetadata().value(key)); + } + + bool shouldFrame = false; + for (auto key : result->getRanges().keys()) { + m_container->setHighlights(key, result->getRanges().value(key)); + + if (key == "frames") { + shouldFrame = true; + } + } + + if (shouldFrame) { + m_container->frameViaHighlights(); + } +} + +QSharedPointer AnalyzerActor::analyzerCall( + QSharedPointer analyzer, + QSharedPointer bits, + QJsonObject pluginState, + QSharedPointer progressTracker) +{ + return analyzer->analyzeBits(bits, pluginState, progressTracker); +} diff --git a/src/hobbits-core/analyzeractor.h b/src/hobbits-core/analyzeractor.h new file mode 100644 index 00000000..7944346e --- /dev/null +++ b/src/hobbits-core/analyzeractor.h @@ -0,0 +1,50 @@ +#ifndef ANALYZERACTOR_H +#define ANALYZERACTOR_H + +#include "actionwatcher.h" +#include "analyzerresult.h" +#include "pluginmanager.h" +#include +#include +#include + +class PluginActionManager; + +class AnalyzerActor : public QObject +{ + Q_OBJECT + +public: + explicit AnalyzerActor(PluginActionManager *manager, QSharedPointer pluginManager); + + QSharedPointer>> analyzerFullAct( + QSharedPointer analyzer, + QSharedPointer container, + QJsonObject pluginState = QJsonObject()); + +signals: + void reportError(QString); + +public slots: +private slots: + void postProcess(); + +private: + static QSharedPointer analyzerCall( + QSharedPointer analyzer, + QSharedPointer bits, + QJsonObject pluginState, + QSharedPointer progressTracker); + + QFuture> m_future; + QSharedPointer m_analyzer; + QSharedPointer m_container; + QJsonObject m_pluginState; + QSharedPointer m_pluginManager; + + QSharedPointer>> m_actionWatcher; + + PluginActionManager *m_manager; +}; + +#endif // ANALYZERACTOR_H diff --git a/src/hobbits-core/analyzerinterface.h b/src/hobbits-core/analyzerinterface.h new file mode 100644 index 00000000..cdbd0d78 --- /dev/null +++ b/src/hobbits-core/analyzerinterface.h @@ -0,0 +1,39 @@ +#ifndef ANALYZERINTERFACE_H +#define ANALYZERINTERFACE_H + +#include +#include + +#include "actionprogress.h" +#include "analyzerresult.h" +#include "bitcontainer.h" +#include "bitcontainerpreview.h" +#include "plugincallback.h" + +class AnalyzerInterface +{ +public: + virtual ~AnalyzerInterface() + { + } + + virtual QString getName() = 0; + virtual AnalyzerInterface* createDefaultAnalyzer() = 0; + + virtual void applyToWidget(QWidget *widget) = 0; + virtual void provideCallback(QSharedPointer pluginCallback) = 0; + virtual QJsonObject getStateFromUi() = 0; + virtual bool canRecallPluginState(const QJsonObject &pluginState) = 0; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) = 0; + + virtual QSharedPointer analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) = 0; + virtual void previewBits(QSharedPointer container) = 0; + +}; + +Q_DECLARE_INTERFACE(AnalyzerInterface, "hobbits.AnalyzerInterface") + +#endif // ANALYZERINTERFACE_H diff --git a/src/hobbits-core/analyzerresult.cpp b/src/hobbits-core/analyzerresult.cpp new file mode 100644 index 00000000..d322ff1b --- /dev/null +++ b/src/hobbits-core/analyzerresult.cpp @@ -0,0 +1,50 @@ +#include "analyzerresult.h" + +AnalyzerResult::AnalyzerResult() +{ + +} + +AnalyzerResult* AnalyzerResult::addRanges(QString key, QList ranges) +{ + if (m_ranges.contains(key)) { + m_ranges.insert(key, m_ranges.value(key) + ranges); + } + else { + m_ranges.insert(key, ranges); + } + return this; +} + +const QMap> AnalyzerResult::getRanges() const +{ + return m_ranges; +} + +AnalyzerResult* AnalyzerResult::addMetadata(QString key, QString value) +{ + m_metadata.insert(key, (QStringList)(value)); + return this; +} + +AnalyzerResult* AnalyzerResult::addMetadata(QString key, QStringList value) +{ + m_metadata.insert(key, value); + return this; +} + +const QMap AnalyzerResult::getMetadata() const +{ + return m_metadata; +} + +AnalyzerResult* AnalyzerResult::setPluginState(QJsonObject pluginState) +{ + m_pluginState = pluginState; + return this; +} + +const QJsonObject AnalyzerResult::getPluginState() const +{ + return m_pluginState; +} diff --git a/src/hobbits-core/analyzerresult.h b/src/hobbits-core/analyzerresult.h new file mode 100644 index 00000000..426497eb --- /dev/null +++ b/src/hobbits-core/analyzerresult.h @@ -0,0 +1,30 @@ +#ifndef ANALYZERRESULT_H +#define ANALYZERRESULT_H + +#include "range.h" +#include +#include + +class AnalyzerResult +{ +public: + AnalyzerResult(); + + AnalyzerResult* addRanges(QString key, QList ranges); + const QMap> getRanges() const; + + AnalyzerResult* addMetadata(QString key, QString value); + AnalyzerResult* addMetadata(QString key, QStringList value); + const QMap getMetadata() const; + + AnalyzerResult* setPluginState(QJsonObject pluginState); + const QJsonObject getPluginState() const; + +private: + QMap> m_ranges; + QMap m_metadata; + + QJsonObject m_pluginState; +}; + +#endif // ANALYZERRESULT_H diff --git a/src/hobbits-core/bitarray.cpp b/src/hobbits-core/bitarray.cpp new file mode 100644 index 00000000..602e20f3 --- /dev/null +++ b/src/hobbits-core/bitarray.cpp @@ -0,0 +1,181 @@ +#include "bitarray.h" + +#include +#include + +static char BIT_MASKS[8] = { + -128, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +}; + +static char INVERSE_BIT_MASKS[8] = { + 0x7f, -65, -33, -17, -9, -5, -3, -2 +}; + +BitArray::BitArray() : + m_size(0) +{ + +} + +BitArray::BitArray(QByteArray bytes, int size) : + m_bytes(bytes), + m_size(size) +{ + Q_ASSERT_X(size >= 0, "Bit Array", QStringLiteral("Negative bit array size is not allowed").toLocal8Bit().data()); + Q_ASSERT_X( + size <= bytes.size() * 8, + "Bit Array", + QStringLiteral("Bit array size %1 is too big for the given byte array").arg(size).toLocal8Bit().data()); +} + +BitArray::BitArray(const BitArray &other) : + BitArray(other.getBytes(), other.size()) +{ +} + +bool BitArray::at(int i) const +{ + Q_ASSERT_X( + i < m_size, + "Bit Array", + QStringLiteral("Trying to access bit %1 of BitArray with size %2 bits").arg(i).arg( + m_size).toLocal8Bit().data()); + return m_bytes.at(i / 8) & BIT_MASKS[i % 8]; +} + +int BitArray::size() const +{ + return m_size; +} + +void BitArray::set(int i, bool value) +{ + if (value) { + m_bytes[i / 8] = m_bytes.at(i / 8) | BIT_MASKS[i % 8]; + } + else { + m_bytes[i / 8] = m_bytes.at(i / 8) & INVERSE_BIT_MASKS[i % 8]; + } +} + +void BitArray::setSize(int size) +{ + m_size = size; +} + +QByteArray BitArray::getBytes() const +{ + return m_bytes; +} + +BitArray BitArray::xorAt(int offset, const BitArray &mask) +{ + BitArray result(QByteArray(((mask.size() - 1) / 8) + 1, 0x00), mask.size()); + for (int i = 0; i < mask.size(); i++) { + result.set(i, at(offset + i) ^ mask.at(i)); + } + return result; +} + +BitArray BitArray::andAt(int offset, const BitArray &mask) +{ + BitArray result(QByteArray(((mask.size() - 1) / 8) + 1, 0x00), mask.size()); + for (int i = 0; i < mask.size(); i++) { + result.set(i, at(offset + i) & mask.at(i)); + } + return result; +} + +BitArray BitArray::orAt(int offset, const BitArray &mask) const +{ + BitArray result(QByteArray(((mask.size() - 1) / 8) + 1, 0x00), mask.size()); + for (int i = 0; i < mask.size(); i++) { + result.set(i, at(offset + i) | mask.at(i)); + } + return result; +} + +int BitArray::countOnes() +{ + int ones = 0; + for (int i = 0; i < m_size; i++) { + ones += at(i); + } + return ones; +} + +QString BitArray::toString() +{ + QString binString = ""; + for (int i = 0; i < m_size; i++) { + binString += at(i) ? "1" : "0"; + } + return binString; +} + +BitArray BitArray::fromString(QString bitArraySpec, QStringList parseErrors) +{ + int size = 0; + if (bitArraySpec.startsWith("0x")) { + QRegularExpression hexMatcher("^[0-9A-F]+$", QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch match = hexMatcher.match(bitArraySpec.mid(2)); + if (match.hasMatch()) { + QString evenSpec = bitArraySpec.mid(2); + if (evenSpec.size() % 2 == 1) { + evenSpec += "0"; + } + QByteArray bytes = QByteArray::fromHex(evenSpec.toLocal8Bit()); + size = qMin((bitArraySpec.length() - 2) * 4, bytes.size() * 8); + return BitArray(bytes, size); + } + else { + parseErrors.append(QString("Expected only hex digits in '0x'-prefixed data - got '%1'").arg(bitArraySpec)); + return BitArray(); + } + } + else if (bitArraySpec.startsWith("0o")) { + size = (bitArraySpec.length() - 2) * 3; + QByteArray bytes(size / 8 + 1, 0x00); + BitArray bits = BitArray(bytes, size); + bool parseOk; + for (int i = 2; i < bitArraySpec.size(); i++) { + parseOk = true; + int val = bitArraySpec.mid(i, 1).toInt(&parseOk, 8); + + if (!parseOk) { + parseErrors.append( + QString("Expected octal digit in '0o'-prefixed data - got %1").arg( + bitArraySpec.at( + i))); + continue; + } + + for (int bit = 0; bit < 3; bit++) { + bits.set((i - 2) * 3 + bit, val & BIT_MASKS[5 + bit]); + } + } + return bits; + } + else if (bitArraySpec.startsWith("0b")) { + size = bitArraySpec.length() - 2; + QByteArray bytes(size / 8 + 1, 0x00); + BitArray bits = BitArray(bytes, size); + for (int i = 2; i < bitArraySpec.size(); i++) { + if (bitArraySpec.at(i) == '1') { + bits.set(i - 2, true); + } + else if (bitArraySpec.at(i) != '0') { + parseErrors.append( + QString("Expected '1' or '0' in '0b'-prefixed data - got '%1'").arg( + bitArraySpec.at( + i))); + } + } + return bits; + } + else { + QByteArray bytes = bitArraySpec.toLocal8Bit(); + size = bitArraySpec.length() * 8; + return BitArray(bytes, size); + } +} diff --git a/src/hobbits-core/bitarray.h b/src/hobbits-core/bitarray.h new file mode 100644 index 00000000..ab26f9ba --- /dev/null +++ b/src/hobbits-core/bitarray.h @@ -0,0 +1,48 @@ +#ifndef BITARRAY_H +#define BITARRAY_H + +#include +#include + +class BitArray +{ +public: + BitArray(); + BitArray(QByteArray bytes, int size); + BitArray(const BitArray &other); + + bool at(int i) const; + int size() const; + + static BitArray fromString(QString bitArraySpec, QStringList parseErrors = QStringList()); + + void set(int i, bool value); + void setSize(int size); + + BitArray xorAt(int offset, const BitArray &mask); + BitArray andAt(int offset, const BitArray &mask); + BitArray orAt(int offset, const BitArray &mask) const; + + QByteArray getBytes() const; + + QString toString(); + + int countOnes(); + +private: + QByteArray m_bytes; + int m_size; + +}; + +inline bool operator==(const BitArray &b1, const BitArray &b2) +{ + return b1.getBytes() == b2.getBytes(); +} + +inline uint qHash(const BitArray &key, uint seed) +{ + return qHash(key.getBytes(), seed) ^ uint(key.size()); +} + +#endif // BITARRAY_H diff --git a/src/hobbits-core/bitcontainer.cpp b/src/hobbits-core/bitcontainer.cpp new file mode 100644 index 00000000..2ef0f2d6 --- /dev/null +++ b/src/hobbits-core/bitcontainer.cpp @@ -0,0 +1,374 @@ +#include "bitcontainer.h" +#include "pluginactionlineage.h" +#include "settingsmanager.h" +#include + +BitContainer::BitContainer(QObject *parent) : + QObject(parent), + m_name("Some Bits"), + m_id(QUuid::createUuid()), + m_lastFrameOffsetFocus(0), + m_lastBitOffsetFocus(0) +{ + +} + +QList BitContainer::getFrames() const +{ + return m_frames; +} + +int BitContainer::getMaxFrameWidth() const +{ + return m_maxFrameWidth; +} + +void BitContainer::setBytes(QByteArray bytes, int bitLen) +{ + if (bitLen < 0) { + bitLen = qMin(MAX_BIT_CONTAINER_SIZE, bytes.size() * 8); + } + m_bytes = bytes; + m_bits = QSharedPointer(new BitArray(m_bytes, bitLen)); + + + // Initialize frames + int frameWidth = 256; + QList frames; + + for (int i = 0; i < m_bits->size(); i += frameWidth) { + int width = qMin(frameWidth - 1, m_bits->size() - i - 1); + frames.append(Frame(m_bits, i, i + width)); + } + + this->setFrames(frames, frameWidth); +} + +void BitContainer::setFrames(QList frames, int maxFrameWidth) +{ + m_frames = frames; + + if (maxFrameWidth < 1) { + maxFrameWidth = 0; + for (Frame frame : m_frames) { + maxFrameWidth = qMax(maxFrameWidth, frame.size()); + } + } + m_maxFrameWidth = maxFrameWidth; + + emit framesChanged(this); + emit changed(this); +} + +QImage BitContainer::getRasterImage(int x, int y, int w, int h) const +{ + QColor trueColor = SettingsManager::getInstance().getUiSetting(SettingsData::ONE_COLOR_KEY).value(); + QColor falseColor = SettingsManager::getInstance().getUiSetting(SettingsData::ZERO_COLOR_KEY).value(); + QImage raster(w, h, QImage::Format_ARGB32); + raster.fill(qRgba(0x66, 0x66, 0x66, 0x66)); + + int frameOffset = y; + int bitOffset = x; + + if (frameOffset < 0) { + return raster; + } + + for (int i = 0; i < h; i++) { + if (i + frameOffset >= getFrames().size()) { + break; + } + Frame frame = getFrames().at(i + frameOffset); + + for (int ii = 0; ii < w; ii++) { + if (ii + bitOffset >= frame.size()) { + break; + } + + if (frame.at(ii + bitOffset)) { + raster.setPixel(ii, i, trueColor.rgba()); + } + else { + raster.setPixel(ii, i, falseColor.rgba()); + } + } + } + return raster; +} + +QImage BitContainer::getByteRasterImage(int x, int y, int w, int h) const +{ + QImage raster(w, h, QImage::Format_ARGB32); + raster.fill(qRgba(0x66, 0x66, 0x66, 0x66)); + + int frameOffset = y; + int byteOffset = x / 8; + int bitOffset = byteOffset * 8; + + if (frameOffset < 0) { + return raster; + } + + for (int i = 0; i < h; i++) { + if (i + frameOffset >= getFrames().size()) { + break; + } + Frame frame = getFrames().at(i + frameOffset); + + for (int ii = 0; ii < w * 8; ii += 8) { + if (ii + bitOffset + 8 >= frame.size()) { + break; + } + + quint8 byteVal = 0; + for (int bit = 0; bit < 8; bit++) { + if (frame.at(ii + bit + bitOffset)) { + byteVal |= 0x80 >> bit; + } + } + + QColor c = SettingsManager::getInstance().getUiSetting(SettingsData::BYTE_HUE_SAT_KEY).value(); + c.setHsl(c.hue(), c.saturation(), byteVal); + raster.setPixel(ii / 8, i, c.rgba()); + } + } + return raster; +} + +QString BitContainer::getName() const +{ + return m_name; +} + +QPixmap BitContainer::getThumbnail() +{ + return QPixmap::fromImage(this->getRasterImage(0, 0, 64, 64)); +} + +void BitContainer::setName(QString name) +{ + this->m_name = name; + emit changed(this); +} + +QSharedPointer BitContainer::getBaseBits() const +{ + return m_bits; +} + +QList BitContainer::getHighlights(QString type) const +{ + return m_highlightMap.value(type); +} + +void BitContainer::setHighlights(QString type, QList highlights) +{ + m_highlightMap.insert(type, highlights); + emit highlightsChanged(this); + emit changed(this); +} + +int BitContainer::getFrameOffsetContaining(Range target) const +{ + if (m_frames.isEmpty()) { + return -1; + } + int offset = m_frames.size() / 2; + int index = offset; + int countdown = 7; + while (true) { + unsigned int compare = target.compare(m_frames.at(index)); + + if (compare & Frame::Overlapping) { + return index; + } + + if (countdown < 1) { + return -1; + } + + offset /= 2; + if (offset < 1) { + offset = 1; + countdown--; + } + + if (compare & Frame::Before) { + index += offset; + } + else if (compare & Frame::After) { + index -= offset; + } + + if (index < 0 || index >= m_frames.size()) { + return -1; + } + } +} + +QSharedPointer BitContainer::getActionLineage() const +{ + return m_actionLineage; +} + +void BitContainer::setActionLineage(QSharedPointer lineage) +{ + m_actionLineage = lineage; +} + +QStringList BitContainer::getMetadata(QString key) const +{ + return m_metadata.value(key); +} + +void BitContainer::setMetadata(QString key, QStringList value) +{ + m_metadata.insert(key, value); +} + +int BitContainer::getLastBitOffsetFocus() const +{ + return m_lastBitOffsetFocus; +} + +int BitContainer::getLastFrameOffsetFocus() const +{ + return m_lastFrameOffsetFocus; +} + +void BitContainer::requestFocus(int bitOffset, int frameOffset) +{ + emit focusRequested(bitOffset, frameOffset); +} + +void BitContainer::recordFocus(int bitOffset, int frameOffset) +{ + m_lastFrameOffsetFocus = frameOffset; + m_lastBitOffsetFocus = bitOffset; +} + +void BitContainer::frameViaHighlights() +{ + QList frames; + for (auto key : m_highlightMap.keys()) { + if (key == "frames") { + for (Range range : m_highlightMap.value(key)) { + frames.append(Frame(getBaseBits(), range)); + } + } + } + + int maxFrameWidth = -1; + for (auto key : m_metadata.keys()) { + if (key == "max_frame_width") { + maxFrameWidth = m_metadata.value(key).at(0).toInt(); + } + } + + if (!frames.isEmpty()) { + setFrames(frames, maxFrameWidth); + } +} + +bool BitContainer::isRootContainer() const +{ + return m_parents.isEmpty(); +} + +QList BitContainer::getChildUuids() const +{ + return m_children; +} + +QList BitContainer::getParentUuids() const +{ + return m_parents; +} + +QUuid BitContainer::getId() const +{ + return m_id; +} + +void BitContainer::detachChild(QUuid childId) +{ + m_children.removeAll(childId); +} + +void BitContainer::detachParent(QUuid parentId) +{ + m_parents.removeAll(parentId); +} + +void BitContainer::addChild(QUuid childId) +{ + m_children.append(childId); +} + +void BitContainer::addParent(QUuid parentId) +{ + m_parents.append(parentId); +} + +QJsonDocument BitContainer::serializeJson() const +{ + QJsonObject root; + root.insert("size", m_bits->size()); + root.insert("data", QString(m_bits->getBytes().toBase64())); + return QJsonDocument(root); +} + +bool BitContainer::deserializeJson(QJsonDocument json) +{ + QJsonObject root = json.object(); + if (!(root.contains("size") + && root.value("size").isDouble() + && root.contains("data") + && root.value("data").isString())) { + return false; + } + + QByteArray data = QByteArray::fromBase64(root.value("data").toString().toLocal8Bit()); + int size = root.value("size").toInt(); + if (size > data.length() * 8) { + return false; + } + + setBytes(data, size); + return true; +} + +const QString VERSION_1 = "Really Good Hobbits Bit Container Serialization Format v1"; +const QString VERSION_2 = "Oh So Good Hobbits Bit Container Serialization Format v2"; +QDataStream& operator<<(QDataStream &stream, const BitContainer &container) +{ + stream << VERSION_2; + stream << container.m_highlightMap; + stream << container.m_metadata; + stream << container.getBaseBits()->size(); + stream << container.getBaseBits()->getBytes(); + + return stream; +} + +QDataStream& operator>>(QDataStream &stream, BitContainer &container) +{ + QString version; + stream >> version; + if (version == VERSION_1 || version == VERSION_2) { + stream >> container.m_highlightMap; + stream >> container.m_metadata; + int bitLength; + QByteArray bytes; + stream >> bitLength; + stream >> bytes; + container.setBytes(bytes, bitLength); + container.frameViaHighlights(); + } + else { + stream.setStatus(QDataStream::Status::ReadCorruptData); + return stream; + } + + return stream; +} diff --git a/src/hobbits-core/bitcontainer.h b/src/hobbits-core/bitcontainer.h new file mode 100644 index 00000000..ecc6a634 --- /dev/null +++ b/src/hobbits-core/bitcontainer.h @@ -0,0 +1,105 @@ +#ifndef BITCONTAINER_H +#define BITCONTAINER_H + +#include "frame.h" + +#include +#include +#include +#include +#include +#include +#include +#include +class PluginActionLineage; +class PluginAction; + +#define MAX_BIT_CONTAINER_SIZE (42 * 1000 * 1000 * 8) + +class BitContainer : public QObject +{ + Q_OBJECT + + // this lets OperatorActor set the container's ID when necessary + friend class OperatorActor; + +public: + explicit BitContainer(QObject *parent = nullptr); + + void setFrames(QList frames, int maxFrameWidth = -1); + + QList getFrames() const; + int getMaxFrameWidth() const; + + QList getHighlights(QString type) const; + void setHighlights(QString type, QList); + + QStringList getMetadata(QString) const; + void setMetadata(QString, QStringList); + + QString getName() const; + void setName(QString name); + + QPixmap getThumbnail(); + QImage getRasterImage(int x, int y, int w, int h) const; + QImage getByteRasterImage(int x, int y, int w, int h) const; + + QSharedPointer getBaseBits() const; + + int getFrameOffsetContaining(Range target) const; + + void setActionLineage(QSharedPointer lineage); + QSharedPointer getActionLineage() const; + + int getLastFrameOffsetFocus() const; + int getLastBitOffsetFocus() const; + + bool isRootContainer() const; + QList getChildUuids() const; + QList getParentUuids() const; + QUuid getId() const; + void detachChild(QUuid childId); + void detachParent(QUuid parentId); + void addChild(QUuid childId); + void addParent(QUuid parentId); + + QJsonDocument serializeJson() const; + bool deserializeJson(QJsonDocument json); + + friend QDataStream& operator<<(QDataStream&, const BitContainer&); + friend QDataStream& operator>>(QDataStream&, BitContainer&); + +private: + QString m_name; + QByteArray m_bytes; + QSharedPointer m_bits; + QList m_frames; + int m_maxFrameWidth; + QMap> m_highlightMap; + QMap m_metadata; + + QSharedPointer m_actionLineage; + + QUuid m_id; + QList m_children; + QList m_parents; + + int m_lastFrameOffsetFocus; + int m_lastBitOffsetFocus; + +signals: + void highlightsChanged(BitContainer *bitContainer); + void framesChanged(BitContainer *bitContainer); + void changed(BitContainer *bitContainer); + void focusRequested(int bitOffset, int frameOffset); + +public slots: + void setBytes(QByteArray bytes, int bitLen = -1); + void requestFocus(int bitOffset, int frameOffset); + void recordFocus(int bitOffset, int frameOffset); + + void frameViaHighlights(); + +}; + +#endif // BITCONTAINER_H diff --git a/src/hobbits-core/bitcontainerlistmodel.cpp b/src/hobbits-core/bitcontainerlistmodel.cpp new file mode 100644 index 00000000..bc4dd830 --- /dev/null +++ b/src/hobbits-core/bitcontainerlistmodel.cpp @@ -0,0 +1,76 @@ +#include "bitcontainerlistmodel.h" + +BitContainerListModel::BitContainerListModel(QObject *parent) : + QAbstractListModel(parent) +{ + +} + +QVariant BitContainerListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + Q_ASSERT(index.model() == this); + + if (role == Qt::DisplayRole) { + return QVariant(m_bitContainers.at(index.row())->getName()); + } + else if (role == Qt::DecorationRole) { + return QVariant(m_bitContainers.at(index.row())->getThumbnail()); + } + return QVariant(); +} + +int BitContainerListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + return m_bitContainers.size(); +} + +QVariant BitContainerListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) + return QVariant(); +} + +QModelIndex BitContainerListModel::addContainer(QSharedPointer bitContainer) +{ + beginInsertRows(QModelIndex(), rowCount(), rowCount()); + m_bitContainers.append(bitContainer); + endInsertRows(); + connect(bitContainer.data(), SIGNAL(changed(BitContainer*)), this, SLOT(updateAll())); + + return this->index(m_bitContainers.size() - 1); +} + +QSharedPointer BitContainerListModel::getContainer(const QModelIndex &index) const +{ + if (!index.isValid()) { + return QSharedPointer(); + } + return m_bitContainers.at(index.row()); +} + +void BitContainerListModel::updateAll() +{ + QModelIndex top = this->index(0); + QModelIndex bottom = this->index(m_bitContainers.size() - 1); + emit dataChanged(top, bottom, {Qt::DisplayRole, Qt::DecorationRole}); +} + +void BitContainerListModel::removeContainer(int row) +{ + beginRemoveRows(QModelIndex(), row, row); + m_bitContainers.removeAt(row); + endRemoveRows(); +} + +QList> BitContainerListModel::getContainers() const +{ + return m_bitContainers; +} diff --git a/src/hobbits-core/bitcontainerlistmodel.h b/src/hobbits-core/bitcontainerlistmodel.h new file mode 100644 index 00000000..2ffeb2ce --- /dev/null +++ b/src/hobbits-core/bitcontainerlistmodel.h @@ -0,0 +1,32 @@ +#ifndef BITCONTAINERLISTMODEL_H +#define BITCONTAINERLISTMODEL_H + +#include +#include + +class BitContainerListModel : public QAbstractListModel +{ + Q_OBJECT + +public: + explicit BitContainerListModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + QModelIndex addContainer(QSharedPointer bitContainer); + void removeContainer(int row); + QSharedPointer getContainer(const QModelIndex &index) const; + + QList> getContainers() const; + +private: + QList> m_bitContainers; + +public slots: + void updateAll(); + +}; + +#endif // BITCONTAINERLISTMODEL_H diff --git a/src/hobbits-core/bitcontainermanager.cpp b/src/hobbits-core/bitcontainermanager.cpp new file mode 100644 index 00000000..c7d83225 --- /dev/null +++ b/src/hobbits-core/bitcontainermanager.cpp @@ -0,0 +1,49 @@ +#include "bitcontainermanager.h" + +BitContainerManager::BitContainerManager(QObject *parent) : + QObject(parent), + m_bitContainerTreeModel(QSharedPointer(new BitContainerTreeModel())), + m_currSelectionModel(QSharedPointer(new QItemSelectionModel)) +{ + m_currSelectionModel->setModel(m_bitContainerTreeModel.data()); + connect( + m_currSelectionModel.data(), + SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)), + this, + SIGNAL(currSelectionChanged(const QItemSelection&,const QItemSelection&))); +} + +QSharedPointer BitContainerManager::getCurrentContainer() +{ + if (m_currSelectionModel->selection().isEmpty()) { + return QSharedPointer(); + } + return m_bitContainerTreeModel->getContainer(m_currSelectionModel->selection().indexes().first()); +} + +QSharedPointer BitContainerManager::getTreeModel() +{ + return m_bitContainerTreeModel; +} + +QSharedPointer BitContainerManager::getContainerById(QUuid id) +{ + return m_bitContainerTreeModel->getContainerById(id); +} + +QSharedPointer BitContainerManager::getCurrSelectionModel() +{ + return m_currSelectionModel; +} + +void BitContainerManager::deleteCurrentContainer() +{ + if (m_currSelectionModel->selection().indexes().isEmpty()) { + return; + } + QModelIndex index = m_currSelectionModel->selection().indexes().first(); + if (!index.isValid()) { + return; + } + m_bitContainerTreeModel->removeContainer(index); +} diff --git a/src/hobbits-core/bitcontainermanager.h b/src/hobbits-core/bitcontainermanager.h new file mode 100644 index 00000000..9a9b217d --- /dev/null +++ b/src/hobbits-core/bitcontainermanager.h @@ -0,0 +1,33 @@ +#ifndef BITCONTAINERMANAGER_H +#define BITCONTAINERMANAGER_H + +#include "bitcontainer.h" +#include "bitcontainertreemodel.h" +#include +#include + +class BitContainerManager : public QObject +{ + Q_OBJECT + +public: + explicit BitContainerManager(QObject *parent = nullptr); + + QSharedPointer getCurrentContainer(); + QSharedPointer getContainerById(QUuid id); + + QSharedPointer getTreeModel(); + QSharedPointer getCurrSelectionModel(); + + void deleteCurrentContainer(); + +public slots: +signals: + void currSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected); + +private: + QSharedPointer m_bitContainerTreeModel; + QSharedPointer m_currSelectionModel; +}; + +#endif // BITCONTAINERMANAGER_H diff --git a/src/hobbits-core/bitcontainerpreview.cpp b/src/hobbits-core/bitcontainerpreview.cpp new file mode 100644 index 00000000..e8af34f5 --- /dev/null +++ b/src/hobbits-core/bitcontainerpreview.cpp @@ -0,0 +1,89 @@ +#include "bitcontainerpreview.h" + +BitContainerPreview::BitContainerPreview(QSharedPointer bitContainer) : + m_bitContainer(bitContainer) +{ + connect( + m_bitContainer.data(), + SIGNAL(highlightsChanged(BitContainer*)), + this, + SIGNAL(highlightsChanged(BitContainer*))); + connect(m_bitContainer.data(), SIGNAL(framesChanged(BitContainer*)), this, SIGNAL(framesChanged(BitContainer*))); + connect(m_bitContainer.data(), SIGNAL(changed(BitContainer*)), this, SIGNAL(changed(BitContainer*))); + connect(m_bitContainer.data(), SIGNAL(focusRequested(int,int)), this, SIGNAL(focusRequested(int,int))); +} + +QList BitContainerPreview::getFrames() const +{ + return m_bitContainer->getFrames(); +} + +int BitContainerPreview::getMaxFrameWidth() const +{ + return m_bitContainer->getMaxFrameWidth(); +} + +QList BitContainerPreview::getHighlights(QString type) const +{ + return m_bitContainer->getHighlights(type); +} + +void BitContainerPreview::setHighlights(QString type, QList highlights) +{ + m_bitContainer->setHighlights(type, highlights); +} + +QStringList BitContainerPreview::getMetadata(QString type) const +{ + return m_bitContainer->getMetadata(type); +} + +void BitContainerPreview::setMetadata(QString type, QStringList metadata) +{ + m_bitContainer->setMetadata(type, metadata); +} + +QString BitContainerPreview::getName() const +{ + return m_bitContainer->getName(); +} + +void BitContainerPreview::setName(QString name) +{ + m_bitContainer->setName(name); +} + +QPixmap BitContainerPreview::getThumbnail() const +{ + return m_bitContainer->getThumbnail(); +} + +QImage BitContainerPreview::getRasterImage(int x, int y, int w, int h) const +{ + return m_bitContainer->getRasterImage(x, y, w, h); +} + +QSharedPointer BitContainerPreview::getBaseBits() const +{ + return m_bitContainer->getBaseBits(); +} + +int BitContainerPreview::getFrameOffsetContaining(Range target) const +{ + return m_bitContainer->getFrameOffsetContaining(target); +} + +int BitContainerPreview::getLastFrameOffsetFocus() const +{ + return m_bitContainer->getLastFrameOffsetFocus(); +} + +int BitContainerPreview::getLastBitOffsetFocus() const +{ + return m_bitContainer->getLastBitOffsetFocus(); +} + +void BitContainerPreview::requestFocus(int bitOffset, int frameOffset) +{ + m_bitContainer->requestFocus(bitOffset, frameOffset); +} diff --git a/src/hobbits-core/bitcontainerpreview.h b/src/hobbits-core/bitcontainerpreview.h new file mode 100644 index 00000000..18d71549 --- /dev/null +++ b/src/hobbits-core/bitcontainerpreview.h @@ -0,0 +1,50 @@ +#ifndef BITCONTAINERPREVIEW_H +#define BITCONTAINERPREVIEW_H + +#include "bitcontainer.h" +#include + +/// A proxy class for BitContainers that hides bit editing functionality +class BitContainerPreview : public QObject +{ + Q_OBJECT + +public: + explicit BitContainerPreview(QSharedPointer m_bitContainer); + + QList getFrames() const; + int getMaxFrameWidth() const; + + QList getHighlights(QString type) const; + void setHighlights(QString type, QList); + + QStringList getMetadata(QString) const; + void setMetadata(QString, QStringList); + + QString getName() const; + void setName(QString name); + + QPixmap getThumbnail() const; + QImage getRasterImage(int x, int y, int w, int h) const; + + QSharedPointer getBaseBits() const; + + int getFrameOffsetContaining(Range target) const; + + int getLastFrameOffsetFocus() const; + int getLastBitOffsetFocus() const; + +signals: + void highlightsChanged(BitContainer *bitContainer); + void framesChanged(BitContainer *bitContainer); + void changed(BitContainer *bitContainer); + void focusRequested(int bitOffset, int frameOffset); + +public slots: + void requestFocus(int bitOffset, int frameOffset); + +private: + QSharedPointer m_bitContainer; +}; + +#endif // BITCONTAINERPREVIEW_H diff --git a/src/hobbits-core/bitcontainertreemodel.cpp b/src/hobbits-core/bitcontainertreemodel.cpp new file mode 100644 index 00000000..6c0a0b9f --- /dev/null +++ b/src/hobbits-core/bitcontainertreemodel.cpp @@ -0,0 +1,243 @@ +#include "bitcontainertreemodel.h" + +BitContainerTreeModel::BitContainerTreeModel(QObject *parent) : + QAbstractItemModel(parent) +{ + +} + +QVariant BitContainerTreeModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + Q_ASSERT(index.model() == this); + + BitContainer *container = static_cast(index.internalPointer()); + if (role == Qt::DisplayRole) { + return QVariant(container->getName()); + } + else if (role == Qt::DecorationRole) { + return QVariant(container->getThumbnail()); + } + return QVariant(); +} + +Qt::ItemFlags BitContainerTreeModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + return QAbstractItemModel::flags(index); +} + +QVariant BitContainerTreeModel::headerData( + int section, + Qt::Orientation orientation, + int role) const +{ + Q_UNUSED(section) + Q_UNUSED(orientation) + Q_UNUSED(role) + return QVariant(); +} + +QModelIndex BitContainerTreeModel::index( + int row, + int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + if (!parent.isValid()) { + int rootContainers = 0; + for (QSharedPointer container : m_containerMap.values()) { + if (container->isRootContainer()) { + if (rootContainers == row) { + return createIndex(row, column, container.data()); + } + else { + rootContainers++; + } + } + } + } + else { + BitContainer *parentContainer = static_cast(parent.internalPointer()); + int currentChild = 0; + for (QUuid childUuid : parentContainer->getChildUuids()) { + if (m_containerMap.contains(childUuid)) { + if (currentChild == row) { + return createIndex(row, column, m_containerMap.value(childUuid).data()); + } + else { + currentChild++; + } + } + } + } + return QModelIndex(); +} + +QModelIndex BitContainerTreeModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + + BitContainer *childContainer = static_cast(index.internalPointer()); + + return getContainerParentIndex(childContainer); +} + +int BitContainerTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) { + return 0; + } + + if (!parent.isValid()) { + int rootContainers = 0; + for (QSharedPointer container : m_containerMap.values()) { + if (container->isRootContainer()) { + rootContainers++; + } + } + return rootContainers; + } + else { + BitContainer *parentContainer = static_cast(parent.internalPointer()); + // maybe needs to make sure they are there? + return parentContainer->getChildUuids().length(); + } +} + +int BitContainerTreeModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent) + return 1; +} + +void BitContainerTreeModel::updateAll() +{ + +} + +QModelIndex BitContainerTreeModel::addContainer(QSharedPointer bitContainer) +{ + m_containerMap.insert(bitContainer->getId(), bitContainer); + QModelIndex parentIndex = getContainerParentIndex(bitContainer.data()); + int row = getContainerRow(bitContainer.data()); + beginInsertRows(parentIndex, row, row); + endInsertRows(); + connect(bitContainer.data(), SIGNAL(changed(BitContainer*)), this, SLOT(updateAll())); + + return this->index(row, 0, parentIndex); +} + +void BitContainerTreeModel::removeContainer(const QModelIndex &index) +{ + BitContainer *container = static_cast(index.internalPointer()); + QModelIndex parentIndex = getContainerParentIndex(container); + int row = getContainerRow(container); + QList> orphanedContainers; + beginRemoveRows(parentIndex, row, row); + // Get a copy of the container as a QSharedPointer so it doesn't get deleted when it is removed from the container + QSharedPointer containerPtr = m_containerMap.value(container->getId()); + m_containerMap.remove(container->getId()); + QList detach; + for (QUuid childUuid : container->getChildUuids()) { + detach.append(childUuid); + } + for (QUuid childUuid : detach) { + if (m_containerMap.contains(childUuid)) { + m_containerMap.value(childUuid)->detachParent(container->getId()); + orphanedContainers.append(m_containerMap.value(childUuid)); + } + } + detach.clear(); + for (QUuid parentUuid : container->getParentUuids()) { + detach.append(parentUuid); + } + for (QUuid parentUuid : detach) { + if (m_containerMap.contains(parentUuid)) { + m_containerMap.value(parentUuid)->detachChild(container->getId()); + } + } + endRemoveRows(); + if (!orphanedContainers.isEmpty()) { + beginInsertRows(QModelIndex(), 0, rowCount() - 1); + endInsertRows(); + } +} + +QSharedPointer BitContainerTreeModel::getContainer(const QModelIndex &index) const +{ + if (!index.isValid()) { + return QSharedPointer(); + } + + BitContainer *indexContainer = static_cast(index.internalPointer()); + for (QSharedPointer container : m_containerMap.values()) { + if (container.data() == indexContainer) { + return container; + } + } + + return QSharedPointer(); +} + +QSharedPointer BitContainerTreeModel::getContainerById(QUuid id) const +{ + return m_containerMap.value(id); +} + +QList> BitContainerTreeModel::getContainers() const +{ + return m_containerMap.values(); +} + +int BitContainerTreeModel::getContainerRow(BitContainer *bitContainer) const +{ + if (bitContainer->isRootContainer()) { + int row = 0; + for (QSharedPointer container : m_containerMap.values()) { + if (container->isRootContainer()) { + if (container.data() == bitContainer) { + return row; + } + row++; + } + } + } + else { + for (QUuid parentUuid : bitContainer->getParentUuids()) { + if (m_containerMap.contains(parentUuid)) { + return m_containerMap.value(parentUuid)->getChildUuids().indexOf(bitContainer->getId()); + } + } + } + return -1; +} + +QModelIndex BitContainerTreeModel::getContainerParentIndex(BitContainer *childContainer) const +{ + if (childContainer->isRootContainer()) { + return QModelIndex(); + } + + for (QUuid parentUuid : childContainer->getParentUuids()) { + if (m_containerMap.contains(parentUuid)) { + BitContainer *parentContainer = m_containerMap.value(parentUuid).data(); + int row = getContainerRow(parentContainer); + if (row >= 0) { + return createIndex(getContainerRow(parentContainer), 0, parentContainer); + } + } + } + + return QModelIndex(); +} diff --git a/src/hobbits-core/bitcontainertreemodel.h b/src/hobbits-core/bitcontainertreemodel.h new file mode 100644 index 00000000..c53ef287 --- /dev/null +++ b/src/hobbits-core/bitcontainertreemodel.h @@ -0,0 +1,47 @@ +#ifndef BITCONTAINERTREEMODEL_H +#define BITCONTAINERTREEMODEL_H + +#include "bitcontainer.h" +#include + +class BitContainerTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit BitContainerTreeModel(QObject *parent = nullptr); + + QVariant data(const QModelIndex &index, int role) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + QVariant headerData( + int section, + Qt::Orientation orientation, + int role = Qt::DisplayRole) const override; + QModelIndex index( + int row, + int column, + const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + + QModelIndex addContainer(QSharedPointer bitContainer); + void removeContainer(const QModelIndex &index); + QSharedPointer getContainer(const QModelIndex &index) const; + + QList> getContainers() const; + QSharedPointer getContainerById(QUuid id) const; + +signals: +public slots: + void updateAll(); + +private: + int getContainerRow(BitContainer *bitContainer) const; + QModelIndex getContainerParentIndex(BitContainer *bitContainer) const; + + QMap> m_containerMap; +}; + +#endif // BITCONTAINERTREEMODEL_H diff --git a/src/hobbits-core/displaybase.cpp b/src/hobbits-core/displaybase.cpp new file mode 100644 index 00000000..00535551 --- /dev/null +++ b/src/hobbits-core/displaybase.cpp @@ -0,0 +1,525 @@ +#include "displaybase.h" +#include "settingsmanager.h" +#include +#include +#include +#include +#include +#include +#include + +DisplayBase::DisplayBase(QSharedPointer displayHandle, DisplayInterface *pluginRef, QWidget *parent) : + QWidget(parent), + m_displayHandle(displayHandle), + m_pluginRef(pluginRef) +{ + this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + this->setMouseTracking(true); + + connect(m_displayHandle.data(), SIGNAL(containerFramesChanged()), this, SLOT(adjustScrollbars())); + connect(m_displayHandle.data(), SIGNAL(containerHighlightsChanged()), this, SLOT(repaint())); + connect(m_displayHandle.data(), SIGNAL(newOffsets(int,int)), this, SLOT(repaint())); + connect( + m_displayHandle.data(), + SIGNAL(newFocusDisplays(QSet)), + this, + SLOT(checkFocus(QSet))); + connect(m_displayHandle.data(), SIGNAL(newBitContainer()), this, SLOT(bitContainerChanged())); + connect(this, SIGNAL(bitHover(bool,int,int)), m_displayHandle.data(), SLOT(setBitHover(bool,int,int))); + + this->setContextMenuPolicy(Qt::CustomContextMenu); + + connect( + this, + SIGNAL(customContextMenuRequested(const QPoint&)), + this, + SLOT(showContextMenu(const QPoint&))); +} + +void DisplayBase::wheelEvent(QWheelEvent *event) +{ + if (m_displayHandle.isNull()) { + return; + } + if (event->delta() > 0) { + m_displayHandle->setOffsets(m_displayHandle->getBitOffset(), m_displayHandle->getFrameOffset() - 1); + } + else if (event->delta() < 0) { + m_displayHandle->setOffsets(m_displayHandle->getBitOffset(), m_displayHandle->getFrameOffset() + 1); + } +} + +void DisplayBase::leaveEvent(QEvent *event) +{ + Q_UNUSED(event) + m_displayHandle->setBitHover(false, 0, 0); +} + +QPoint DisplayBase::getOffset(int x, int y, int xSize, int ySize, int xGrouping, int bitsPerX) +{ + int xDiff = x / xSize; + int yDiff = y / ySize; + + if (xGrouping > 1) { + int groupOffset = m_displayHandle->getBitOffset() % xGrouping; + int xGroups = (xDiff + groupOffset) / (xGrouping + 1); + xDiff -= xGroups; + } + xDiff *= bitsPerX; + + return QPoint(xDiff, yDiff); +} + +void DisplayBase::sendHoverUpdate(QMouseEvent *event, int xSize, int ySize, int xGrouping, int bitsPerX, QPoint offset) +{ + if (m_displayHandle->getContainer().isNull()) { + m_lastHover = QPoint(); + emit bitHover(false, 0, 0); + return; + } + + if (event->x() < offset.x() || event->y() < offset.y()) { + m_lastHover = QPoint(); + emit bitHover(false, 0, 0); + return; + } + + QPoint diff = getOffset(event->x() - offset.x(), event->y() - offset.y(), xSize, ySize, xGrouping, bitsPerX); + + if (diff.x() < 0 || diff.y() < 0) { + m_lastHover = QPoint(); + emit bitHover(false, 0, 0); + return; + } + + int frameOffset = m_displayHandle->getFrameOffset() + diff.y(); + int bitOffset = m_displayHandle->getBitOffset() + diff.x(); + if (frameOffset < 0 || frameOffset >= m_displayHandle->getContainer()->getFrames().size()) { + m_lastHover = QPoint(); + emit bitHover(false, 0, 0); + return; + } + if (bitOffset >= m_displayHandle->getContainer()->getFrames().at(frameOffset).size()) { + m_lastHover = QPoint(); + emit bitHover(false, 0, 0); + return; + } + m_lastHover = QPoint(bitOffset, frameOffset); + emit bitHover(true, bitOffset, frameOffset); +} + +void DisplayBase::bitContainerChanged() +{ + if (!m_displayHandle->getContainer().isNull()) { + this->adjustScrollbars(); + } + else { + this->repaint(); + } +} + +void DisplayBase::checkFocus(QSet displays) +{ + if (displays.contains(m_pluginRef)) { + this->adjustScrollbars(); + } +} + +void DisplayBase::adjustScrollbars() +{ + if (m_displayHandle->getCurrentFocusDisplays().contains(m_pluginRef)) { + if (m_displayHandle->getContainer().isNull()) { + m_displayHandle->getHScroll()->setVisible(false); + m_displayHandle->getVScroll()->setVisible(false); + } + else { + m_displayHandle->getHScroll()->setEnabled(true); + m_displayHandle->getHScroll()->setVisible(true); + m_displayHandle->getHScroll()->setMinimum(0); + m_displayHandle->getHScroll()->setMaximum(m_displayHandle->getContainer()->getMaxFrameWidth() - 1); + m_displayHandle->getHScroll()->setSingleStep(1); + m_displayHandle->getHScroll()->setPageStep(this->width()); + + m_displayHandle->getVScroll()->setEnabled(true); + m_displayHandle->getVScroll()->setVisible(true); + m_displayHandle->getVScroll()->setMinimum(0); + m_displayHandle->getVScroll()->setMaximum(m_displayHandle->getContainer()->getFrames().size() - 1); + m_displayHandle->getVScroll()->setSingleStep(1); + m_displayHandle->getVScroll()->setPageStep(this->height()); + } + + this->repaint(); + } +} + +QList DisplayBase::getHighlightSpots(QList highlights, int &highlightMinIndex, Frame frame) +{ + QList spots; + + unsigned int intersection = Frame::Before; + int highlightIndex = highlightMinIndex; + while (intersection & (Frame::Before | Frame::Overlapping)) { + if (highlightIndex >= highlights.size()) { + break; + } + Range highlight = highlights.at(highlightIndex); + intersection = frame.compare(highlight); + + if (intersection & Frame::Overlapping) { + spots.append(frame.getOverlap(highlight)); + } + else if (intersection & Frame::After) { + break; + } + + if ((!(intersection & Frame::After)) && highlightMinIndex == highlightIndex) { + highlightMinIndex++; + } + highlightIndex++; + } + return spots; +} + +void DisplayBase::drawHighlights( + QPainter *painter, + double colWidth, + double rowHeight, + int frameOffset, + int bitOffset, + int colCount, + int rowCount, + int colGroupSize, + int colGroupMargin) +{ + painter->setPen(Qt::transparent); + QVector highlightRects = getHighlightRects( + "find", + colWidth, + rowHeight, + frameOffset, + bitOffset, + colCount, + rowCount, + colGroupSize, + colGroupMargin); + if (!highlightRects.isEmpty()) { + painter->setBrush( + SettingsManager::getInstance().getUiSetting( + SettingsData::HIGHLIGHT_1_COLOR_KEY).value()); + painter->drawRects(highlightRects); + } + highlightRects = getHighlightRects( + "find-focus", + colWidth, + rowHeight, + frameOffset, + bitOffset, + colCount, + rowCount, + colGroupSize, + colGroupMargin); + if (!highlightRects.isEmpty()) { + painter->setBrush( + SettingsManager::getInstance().getUiSetting( + SettingsData::HIGHLIGHT_2_COLOR_KEY).value()); + painter->drawRects(highlightRects); + } + highlightRects = getHighlightRects( + "manual_highlights", + colWidth, + rowHeight, + frameOffset, + bitOffset, + colCount, + rowCount, + colGroupSize, + colGroupMargin); + if (!highlightRects.isEmpty()) { + painter->setBrush( + SettingsManager::getInstance().getUiSetting( + SettingsData::HIGHLIGHT_5_COLOR_KEY).value()); + painter->drawRects(highlightRects); + } +} + +QVector DisplayBase::getHighlightRects( + QString type, + double colWidth, + double rowHeight, + int frameOffset, + int bitOffset, + int colCount, + int rowCount, + int colGroupSize, + int colGroupMargin) +{ + if (m_displayHandle->getContainer().isNull()) { + return QVector(); + } + + QVector rects; + if (m_displayHandle->getContainer()->getHighlights(type).size() > 0) { + int lastHighlight = 0; + int rowOffset = -1; + for (int i = frameOffset; i < m_displayHandle->getContainer()->getFrames().size(); i++) { + rowOffset++; + if (rowOffset >= rowCount) { + break; + } + Frame frame = m_displayHandle->getContainer()->getFrames().at(i); + Frame displayFrame = + Frame( + m_displayHandle->getContainer()->getBaseBits(), + frame.start() + bitOffset, + qMin(frame.end(), frame.start() + bitOffset + colCount - 1)); + QList spots = getHighlightSpots( + m_displayHandle->getContainer()->getHighlights(type), + lastHighlight, + displayFrame); + + for (Range spot : spots) { + int displayStart = (spot.start() - displayFrame.start()); + double hx = getGroupedOffset(displayStart, colWidth, colGroupSize, bitOffset, colGroupMargin); + double hy = (i - frameOffset) * rowHeight; + int displayEnd = (spot.end() - displayFrame.start()); + double hw = + getGroupedOffset(displayEnd, colWidth, colGroupSize, bitOffset, colGroupMargin) + colWidth - hx; + double hh = rowHeight; + rects.append(QRectF(hx, hy, hw, hh)); + } + } + } + return rects; +} + +double DisplayBase::getGroupedOffset(int idx, double width, int groupSize, int offset, int groupMargin) +{ + if (groupSize > 1) { + int groupOffset = offset % groupSize; + int groups = (idx + groupOffset) / groupSize; + return width * ((groups * groupMargin) + idx); + } + else { + return width * idx; + } +} + +void DisplayBase::showContextMenu(const QPoint &point) +{ + if (m_lastHover.isNull()) { + return; + } + QMenu menu(this); + + Frame frame = m_displayHandle->getContainer()->getFrames().at(m_lastHover.y()); + + menu.addSection( + QString("Frame %1, Bit %2 of %3") + .arg(m_lastHover.y()) + .arg(m_lastHover.x()) + .arg(frame.size())); + + QMenu gotoMenu("Go to Location"); + gotoMenu.addAction( + tr("End of Frame"), + [this, frame]() { + this->m_displayHandle->getHScroll()->setValue(frame.size() - 8); + }); + gotoMenu.addAction( + tr("Start of frame"), + [this]() { + this->m_displayHandle->getHScroll()->setValue(0); + }); + gotoMenu.addAction( + tr("Next Bit Change in Frame"), + [this, frame]() { + bool value = frame.at(m_lastHover.x()); + for (int i = m_lastHover.x() + 1; i < frame.size(); i++) { + if (frame.at(i) != value) { + this->m_displayHandle->getHScroll()->setValue(i); + break; + } + } + }); + gotoMenu.addAction( + tr("Previous Bit Change in Frame"), + [this, frame]() { + bool value = frame.at(m_lastHover.x()); + for (int i = m_lastHover.x() - 1; i >= 0; i--) { + if (frame.at(i) != value) { + this->m_displayHandle->getHScroll()->setValue(i); + break; + } + } + }); + gotoMenu.addAction( + tr("Next Bit Change in Column"), + [this, frame]() { + bool value = frame.at(m_lastHover.x()); + QList frames = m_displayHandle->getContainer()->getFrames(); + for (int i = m_lastHover.y() + 1; i < frames.size(); i++) { + if (frames.at(i).at(m_lastHover.x()) != value) { + this->m_displayHandle->getVScroll()->setValue(i); + break; + } + } + }); + gotoMenu.addAction( + tr("Previous Bit Change in Column"), + [this, frame]() { + bool value = frame.at(m_lastHover.x()); + QList frames = m_displayHandle->getContainer()->getFrames(); + for (int i = m_lastHover.y() - 1; i >= 0; i--) { + if (frames.at(i).at(m_lastHover.x()) != value) { + this->m_displayHandle->getVScroll()->setValue(i); + break; + } + } + }); + gotoMenu.addAction( + tr("Next Constant(ish) Column"), + [this, frame]() { + QList frames = m_displayHandle->getContainer()->getFrames(); + for (int i = m_lastHover.x() + 1; i < frame.size(); i++) { + bool value = frame.at(i); + bool constantish = true; + for (int adjacent = qMax(0, m_lastHover.y() - 6); + adjacent <= qMin(frames.size() - 1, m_lastHover.y() + 6); + adjacent++) { + Frame adjFrame = frames.at(adjacent); + if (i >= adjFrame.size() + || adjFrame.at(i) != value) { + constantish = false; + break; + } + } + if (constantish) { + this->m_displayHandle->getHScroll()->setValue(i); + break; + } + } + }); + gotoMenu.addAction( + tr("Previous Constant(ish) Column"), + [this, frame]() { + QList frames = m_displayHandle->getContainer()->getFrames(); + for (int i = m_lastHover.x() - 1; i >= 0; i--) { + bool value = frame.at(i); + bool constantish = true; + for (int adjacent = qMax(0, m_lastHover.y() - 5); + adjacent <= qMin(frames.size() - 1, m_lastHover.y() + 5); + adjacent++) { + Frame adjFrame = frames.at(adjacent); + if (i >= adjFrame.size() + || adjFrame.at(i) != value) { + constantish = false; + break; + } + } + if (constantish) { + this->m_displayHandle->getHScroll()->setValue(i); + break; + } + } + }); + + menu.addMenu(&gotoMenu); + + menu.addSeparator(); + + menu.addAction( + tr("Set Marker"), + [this, frame]() { + int focusBit = frame.start() + m_lastHover.x(); + auto container = this->m_displayHandle->getContainer(); + auto markers = container->getMetadata("location_markers"); + + bool ok; + QString text = QInputDialog::getText( + this, + tr("Create New Marker"), + QString(tr("Marker name for bit %1:")).arg(focusBit), + QLineEdit::Normal, + QString("%1").arg(markers.size() + 1), + &ok); + if (!ok || text.isEmpty()) { + return; + } + + markers.append(QString("%1,%2").arg(focusBit).arg(text)); + container->setMetadata("location_markers", markers); + }); + + + auto container = this->m_displayHandle->getContainer(); + auto markers = container->getMetadata("location_markers"); + QMenu gotoMarkerMenu("Go to Marker"); + + for (auto marker : markers) { + int bit = marker.mid(0, marker.indexOf(",")).toInt(); + QString name = marker.mid(marker.indexOf(",") + 1); + gotoMarkerMenu.addAction( + QString("\"%1\"").arg(name), + [this, container, name, bit]() { + int frameOffset = container->getFrameOffsetContaining(Range(bit, bit)); + if (frameOffset < 0 || frameOffset >= container->getFrames().size()) { + return; + } + Frame frame = container->getFrames().at(frameOffset); + int bitOffset = bit - frame.start(); + this->m_displayHandle->setOffsets(bitOffset, frameOffset); + }); + } + + QAction *gotoMarkerMenuAction = menu.addMenu(&gotoMarkerMenu); + if (markers.isEmpty()) { + gotoMarkerMenuAction->setEnabled(false); + } + + menu.addSeparator(); + + menu.addAction( + tr("Add Highlight..."), + [this, frame]() { + int focusBit = frame.start() + m_lastHover.x(); + auto container = this->m_displayHandle->getContainer(); + auto manualHighlights = container->getHighlights("manual_highlights"); + + bool ok; + int length = QInputDialog::getInt( + this, + tr("Create New Highlight"), + QString(tr("Length of highlight in bits:")), + 8, + 1, + INT_MAX, + 1, + &ok); + if (!ok) { + return; + } + + manualHighlights.append(Range(focusBit, qMin(container->getBaseBits()->size() - 1, focusBit + length - 1))); + std::sort(manualHighlights.begin(), manualHighlights.end()); + container->setHighlights("manual_highlights", manualHighlights); + }); + + menu.addAction( + tr("Add Highlight to Frame"), + [this, frame]() { + auto container = this->m_displayHandle->getContainer(); + auto manualHighlights = container->getHighlights("manual_highlights"); + manualHighlights.append(frame); + std::sort(manualHighlights.begin(), manualHighlights.end()); + container->setHighlights("manual_highlights", manualHighlights); + }); + + menu.addAction( + tr("Clear All Highlights"), + [this, frame]() { + auto container = this->m_displayHandle->getContainer(); + container->setHighlights("manual_highlights", {}); + }); + + menu.exec(this->mapToGlobal(point)); +} diff --git a/src/hobbits-core/displaybase.h b/src/hobbits-core/displaybase.h new file mode 100644 index 00000000..511a310f --- /dev/null +++ b/src/hobbits-core/displaybase.h @@ -0,0 +1,69 @@ +#ifndef DISPLAYBASE_H +#define DISPLAYBASE_H + +#include "bitcontainer.h" +#include "displayhandle.h" +#include +#include + +class DisplayBase : public QWidget +{ + Q_OBJECT + +public: + explicit DisplayBase( + QSharedPointer displayHandle, + DisplayInterface *pluginRef, + QWidget *parent = nullptr); + +protected: + QSharedPointer m_displayHandle; + DisplayInterface *m_pluginRef; + + QPoint m_lastHover; + + void wheelEvent(QWheelEvent *event) override; + void leaveEvent(QEvent *event) override; + + QList getHighlightSpots(QList highlights, int &highlightMinIndex, Frame frame); + + QPoint getOffset(int x, int y, int xSize, int ySize, int xGrouping, int bitsPerX); + void sendHoverUpdate(QMouseEvent *event, int xSize, int ySize, int xGrouping, int bitsPerX, QPoint offset); + + double getGroupedOffset(int idx, double width, int groupSize, int offset, int groupMargin); + void drawHighlights( + QPainter *painter, + double colWidth, + double rowHeight, + int frameOffset, + int bitOffset, + int colCount, + int rowCount, + int colGroupSize, + int colGroupMargin = 1); + QVector getHighlightRects( + QString type, + double colWidth, + double rowHeight, + int frameOffset, + int bitOffset, + int colCount, + int rowCount, + int colGroupSize, + int colGroupMargin = 1); + +signals: + void newOffsets(int bitOffset, int frameOffset); + void bitHover(bool, int, int); + +public slots: + void bitContainerChanged(); + void checkFocus(QSet); + +protected slots: + virtual void adjustScrollbars(); + virtual void showContextMenu(const QPoint &point); + +}; + +#endif // DISPLAYBASE_H diff --git a/src/hobbits-core/displaybasetext.cpp b/src/hobbits-core/displaybasetext.cpp new file mode 100644 index 00000000..5ca3dad0 --- /dev/null +++ b/src/hobbits-core/displaybasetext.cpp @@ -0,0 +1,238 @@ +#include "displaybasetext.h" + +#include +#include + +#include "settingsmanager.h" +#include +#include +#include +#include + +DisplayBaseText::DisplayBaseText( + QSharedPointer displayHandle, + DisplayInterface *pluginRef, + int columnGrouping) : + DisplayBase(displayHandle, pluginRef), + m_fontSize(10), + m_columnGrouping(columnGrouping), + m_showFrameOffsets(true), + m_showColumnOffsets(true), + m_displayOffset(0, 0), + m_fontWidth(1), + m_fontHeight(1), + m_frameHeight(1) +{ +} + +void DisplayBaseText::paintEvent(QPaintEvent*) +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + prepareHeaders(); + + int w = qRound(double((this->width() - m_displayOffset.x()) * bitsPerChar()) / double(m_fontWidth)); + if (m_columnGrouping > 1) { + w = + qRound( + double((this->width() - m_displayOffset.x()) * bitsPerChar()) + / (double(m_fontWidth) * double(m_columnGrouping + 1) / double(m_columnGrouping))); + } + int h = (height() - m_displayOffset.y()) / m_frameHeight; + + int frameOffset = m_displayHandle->getFrameOffset(); + int charOffset = qRound(double(m_displayHandle->getBitOffset()) / double(bitsPerChar())); + + + if (frameOffset < 0) { + return; + } + + QPainter painter(this); + QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + font.setPointSize(m_fontSize); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + if (m_showFrameOffsets) { + painter.fillRect(0, 0, m_displayOffset.x() - m_fontWidth / 2, height(), Qt::lightGray); + for (int i = 0; i < h; i++) { + if (i + frameOffset >= m_displayHandle->getContainer()->getFrames().size()) { + break; + } + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + m_fontWidth / 2, + m_displayOffset.y() + i * m_frameHeight, + qRound(m_displayOffset.x() - (m_fontWidth * 1.5)), + m_frameHeight, + Qt::AlignRight, + QString("%1").arg(i + frameOffset)); + } + } + + if (m_showColumnOffsets) { + painter.fillRect(0, 0, width(), m_displayOffset.y() - m_fontWidth / 2, Qt::lightGray); + for (int i = 0; i < w; i += 2) { + if ((i + charOffset) * bitsPerChar() >= m_displayHandle->getContainer()->getMaxFrameWidth()) { + break; + } + + painter.save(); + painter.rotate(-90); + painter.setPen(Qt::darkGray); + + int xOffset = qRound(getGroupedOffset(i, m_fontWidth, m_columnGrouping, charOffset, 1)); + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + -1 * m_displayOffset.y() + m_fontWidth, + m_displayOffset.x() + xOffset - qFloor(double(m_fontWidth) / 2.5), + m_displayOffset.y(), + m_fontHeight, + Qt::AlignLeft, + QString("%1").arg((i + charOffset) * bitsPerChar())); + painter.restore(); + } + } + + painter.save(); + painter.translate(m_displayOffset); + for (int i = 0; i < h; i++) { + if (i + frameOffset >= m_displayHandle->getContainer()->getFrames().size()) { + break; + } + Frame frame = m_displayHandle->getContainer()->getFrames().at(i + frameOffset); + + QString frameString = ""; + QString nibString; + for (int ii = 0; ii < w; ii += bitsPerChar()) { + int bitStart = ii + charOffset * bitsPerChar(); + if (bitStart >= frame.size()) { + break; + } + + frameString += getDisplayChars(frame, bitStart); + + if (m_columnGrouping > 1) { + if (((charOffset * bitsPerChar() + ii) / bitsPerChar()) % m_columnGrouping == m_columnGrouping - 1) { + frameString += " "; + } + } + } + painter.setFont(font); + painter.setPen(Qt::black); + painter.drawText( + 0, + i * m_frameHeight, + (this->width() - m_displayOffset.x()), + m_frameHeight, + Qt::AlignLeft, + frameString); + } + + drawHighlights( + &painter, + double(m_fontWidth) / double(bitsPerChar()), + m_frameHeight, + frameOffset, + charOffset * bitsPerChar(), + w * bitsPerChar(), + h, + bitsPerChar() * m_columnGrouping, + m_columnGrouping > 1 ? bitsPerChar() : 0); + + painter.restore(); +} + +void DisplayBaseText::mouseMoveEvent(QMouseEvent *event) +{ + sendHoverUpdate( + event, + m_fontWidth, + m_frameHeight, + m_columnGrouping, + bitsPerChar(), + m_displayOffset); +} + +void DisplayBaseText::prepareHeaders() +{ + QPainter painter(this); + QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + font.setPointSize(m_fontSize); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + QFontMetrics fontMetrics = QFontMetrics(font, painter.device()); + m_fontWidth = fontMetrics.width("0"); + m_fontHeight = fontMetrics.height(); + m_frameHeight = m_fontHeight + 2; + + if (m_showFrameOffsets) { + int totalFrames = m_displayHandle->getContainer()->getFrames().size(); + int maxChars = qFloor(log10(totalFrames)) + 1; + m_displayOffset.setX(qRound(m_fontWidth * (maxChars + 1.5))); + } + else { + m_displayOffset.setX(0); + } + + if (m_showColumnOffsets) { + int maxWidth = m_displayHandle->getContainer()->getMaxFrameWidth(); + int maxChars = qCeil(log10(maxWidth)); + m_displayOffset.setY(qRound(m_fontWidth * (maxChars + 1.5))); + } + else { + m_displayOffset.setY(0); + } +} + +void DisplayBaseText::adjustScrollbars() +{ + if (!m_displayHandle->getCurrentFocusDisplays().contains(m_pluginRef)) { + return; + } + if (m_displayHandle->getContainer().isNull()) { + DisplayBase::adjustScrollbars(); + return; + } + + repaint(); + + int w = qRound(double((this->width() - m_displayOffset.x()) * bitsPerChar()) / double(m_fontWidth)); + if (m_columnGrouping > 1) { + w = + qRound( + double((this->width() - m_displayOffset.x()) * bitsPerChar()) + / (double(m_fontWidth) * double(m_columnGrouping + 1) / double(m_columnGrouping))); + } + int h = (height() - m_displayOffset.y()) / m_frameHeight; + + DisplayBase::adjustScrollbars(); + + if (m_displayHandle->getCurrentFocusDisplays().size() == 1) { + m_displayHandle->getVScroll()->setPageStep(h); + m_displayHandle->getHScroll()->setPageStep(w); + m_displayHandle->getHScroll()->setSingleStep(bitsPerChar()); + m_displayHandle->getHScroll()->setMaximum(m_displayHandle->getContainer()->getMaxFrameWidth() - bitsPerChar()); + } +} + +void DisplayBaseText::setFontSize(int fontSize) +{ + m_fontSize = fontSize; + adjustScrollbars(); +} + +void DisplayBaseText::setColumnGrouping(int columnGrouping) +{ + m_columnGrouping = columnGrouping; + adjustScrollbars(); +} + +void DisplayBaseText::setShowHeaders(bool showHeaders) +{ + m_showFrameOffsets = showHeaders; + m_showColumnOffsets = showHeaders; + adjustScrollbars(); +} diff --git a/src/hobbits-core/displaybasetext.h b/src/hobbits-core/displaybasetext.h new file mode 100644 index 00000000..31de1990 --- /dev/null +++ b/src/hobbits-core/displaybasetext.h @@ -0,0 +1,44 @@ +#ifndef DISPLAYBASETEXT_H +#define DISPLAYBASETEXT_H + +#include "displaybase.h" +#include + +class DisplayBaseText : public DisplayBase +{ + Q_OBJECT + +public: + DisplayBaseText(QSharedPointer displayHandle, DisplayInterface *pluginRef, int columnGrouping); + + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *event); + + virtual QString getDisplayChars(Frame frame, int offset) = 0; + virtual int bitsPerChar() = 0; + +public slots: + void setFontSize(int fontSize); + void setColumnGrouping(int columnGrouping); + void setShowHeaders(bool showHeaders); + +private: + void prepareHeaders(); + + int m_fontSize; + int m_columnGrouping; + bool m_showFrameOffsets; + bool m_showColumnOffsets; + + QPoint m_displayOffset; + + int m_fontWidth; + int m_fontHeight; + int m_frameHeight; + +protected slots: + void adjustScrollbars() override; + +}; + +#endif // DISPLAYBASETEXT_H diff --git a/src/hobbits-core/displayhandle.cpp b/src/hobbits-core/displayhandle.cpp new file mode 100644 index 00000000..e058db41 --- /dev/null +++ b/src/hobbits-core/displayhandle.cpp @@ -0,0 +1,94 @@ +#include "displayhandle.h" +#include + +DisplayHandle::DisplayHandle( + QSharedPointer bitManager, + QScrollBar *vScroll, + QScrollBar *hScroll, + QObject *parent) : + QObject(parent), + m_bitManager(bitManager), + m_vScroll(vScroll), + m_hScroll(hScroll) +{ + connect( + m_bitManager.data(), + SIGNAL(currSelectionChanged(const QItemSelection&,const QItemSelection&)), + this, + SLOT(bitContainerSelected())); + connect(m_vScroll, SIGNAL(valueChanged(int)), this, SLOT(offsetChanged())); + connect(m_hScroll, SIGNAL(valueChanged(int)), this, SLOT(offsetChanged())); +} + +QSharedPointer DisplayHandle::getContainer() const +{ + return m_bitManager->getCurrentContainer(); +} + +int DisplayHandle::getBitOffset() const +{ + return m_hScroll->value(); +} + +int DisplayHandle::getFrameOffset() const +{ + return m_vScroll->value(); +} + +QSet DisplayHandle::getCurrentFocusDisplays() const +{ + return m_focusDisplays; +} + +QScrollBar* DisplayHandle::getVScroll() +{ + return m_vScroll; +} + +QScrollBar* DisplayHandle::getHScroll() +{ + return m_hScroll; +} + +void DisplayHandle::setOffsets(int bitOffset, int frameOffset) +{ + m_vScroll->setValue(frameOffset); + m_hScroll->setValue(bitOffset); + offsetChanged(); +} + +void DisplayHandle::offsetChanged() +{ + emit newOffsets(getBitOffset(), getFrameOffset()); +} + +void DisplayHandle::setBitHover(bool hovering, int bitOffset, int frameOffset) +{ + emit newBitHover(hovering, bitOffset, frameOffset); +} + +void DisplayHandle::setFocusDisplays(QSet focusDisplays) +{ + m_focusDisplays = focusDisplays; + emit newFocusDisplays(m_focusDisplays); +} + +void DisplayHandle::bitContainerSelected() +{ + disconnect(this, SIGNAL(containerFramesChanged())); + disconnect(this, SIGNAL(containerHighlightsChanged())); + + emit newBitContainer(); + + if (!getContainer().isNull()) { + connect(getContainer().data(), SIGNAL(framesChanged(BitContainer*)), this, SIGNAL(containerFramesChanged())); + connect( + getContainer().data(), + SIGNAL(highlightsChanged(BitContainer*)), + this, + SIGNAL(containerHighlightsChanged())); + + emit containerFramesChanged(); + emit containerHighlightsChanged(); + } +} diff --git a/src/hobbits-core/displayhandle.h b/src/hobbits-core/displayhandle.h new file mode 100644 index 00000000..a45047ee --- /dev/null +++ b/src/hobbits-core/displayhandle.h @@ -0,0 +1,55 @@ +#ifndef DISPLAYHANDLE_H +#define DISPLAYHANDLE_H + +#include "bitcontainer.h" +#include "bitcontainermanager.h" +#include +class DisplayInterface; + +class QScrollBar; + +class DisplayHandle : public QObject +{ + Q_OBJECT + +public: + explicit DisplayHandle( + QSharedPointer bitManager, + QScrollBar *vScroll, + QScrollBar *hScroll, + QObject *parent = nullptr); + + QSharedPointer getContainer() const; + int getBitOffset() const; + int getFrameOffset() const; + QSet getCurrentFocusDisplays() const; + + QScrollBar* getVScroll(); + QScrollBar* getHScroll(); + +signals: + void newBitContainer(); + void newOffsets(int bitOffset, int frameOffset); + void newBitHover(bool, int, int); + void newFocusDisplays(QSet); + + void containerFramesChanged(); + void containerHighlightsChanged(); + +public slots: + void setOffsets(int bitOffset, int frameOffset); + void setBitHover(bool hovering, int bitOffset, int frameOffset); + void setFocusDisplays(QSet focusDisplays); + void bitContainerSelected(); + +private slots: + void offsetChanged(); + +private: + QSharedPointer m_bitManager; + QScrollBar *m_vScroll; + QScrollBar *m_hScroll; + QSet m_focusDisplays; +}; + +#endif // DISPLAYHANDLE_H diff --git a/src/hobbits-core/displayinterface.h b/src/hobbits-core/displayinterface.h new file mode 100644 index 00000000..78cbd8d8 --- /dev/null +++ b/src/hobbits-core/displayinterface.h @@ -0,0 +1,29 @@ +#ifndef DISPLAYINTERFACE_H +#define DISPLAYINTERFACE_H + +#include +#include + +#include "bitcontainer.h" + +#include "displayhandle.h" + +class DisplayInterface +{ +public: + virtual ~DisplayInterface() + { + } + + virtual DisplayInterface* createDefaultDisplay() = 0; + + virtual QString getName() = 0; + + virtual QWidget* getDisplayWidget(QSharedPointer displayHandle) = 0; + virtual QWidget* getControlsWidget(QSharedPointer displayHandle) = 0; + +}; + +Q_DECLARE_INTERFACE(DisplayInterface, "hobbits.DisplayInterface") + +#endif // DISPLAYINTERFACE_H diff --git a/src/hobbits-core/frame.cpp b/src/hobbits-core/frame.cpp new file mode 100644 index 00000000..158fb1e1 --- /dev/null +++ b/src/hobbits-core/frame.cpp @@ -0,0 +1,26 @@ +#include "frame.h" + +#include + +Frame::Frame(QSharedPointer bits, int start, int end) : + Range(start, end), + m_bits(bits) +{ + +} + +Frame::Frame(QSharedPointer bits, Range range) : + Range(range), + m_bits(bits) +{ + +} + +bool Frame::at(int i) const +{ + Q_ASSERT_X( + i >= 0 && i < this->size(), + "Frame", + QStringLiteral("Index %1 is not valid").arg(i).toLocal8Bit().data()); + return m_bits->at(this->start() + i); +} diff --git a/src/hobbits-core/frame.h b/src/hobbits-core/frame.h new file mode 100644 index 00000000..c31fb9eb --- /dev/null +++ b/src/hobbits-core/frame.h @@ -0,0 +1,21 @@ +#ifndef FRAME_H +#define FRAME_H + +#include "bitarray.h" +#include "range.h" +#include + +class Frame : public Range +{ +public: + Frame(QSharedPointer bits, int start, int end); + Frame(QSharedPointer bits, Range range); + Frame(const Frame &other) = default; + + bool at(int i) const; + +private: + QSharedPointer m_bits; +}; + +#endif // FRAME_H diff --git a/src/hobbits-core/hobbits-core.pro b/src/hobbits-core/hobbits-core.pro new file mode 100644 index 00000000..948547b9 --- /dev/null +++ b/src/hobbits-core/hobbits-core.pro @@ -0,0 +1,103 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-06-04T10:53:29 +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = hobbits-core +TEMPLATE = lib + +DEFINES += HOBBITSCORE_LIBRARY + +DEFINES += "HOBBITS_CORE_LIB_VERSION=\"\\\"Super Cool Developer Version\\\"\"" + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + actionprogress.cpp \ + analyzeractor.cpp \ + analyzerresult.cpp \ + # asciidisplay.cpp \ + # binarydisplay.cpp \ + bitarray.cpp \ + bitcontainer.cpp \ + bitcontainerlistmodel.cpp \ + bitcontainermanager.cpp \ + bitcontainerpreview.cpp \ + # bitraster.cpp \ + bitcontainertreemodel.cpp \ + displaybase.cpp \ + displaybasetext.cpp \ + displayhandle.cpp \ + frame.cpp \ + # hexdisplay.cpp \ + hobbitscoreinfo.cpp \ + mathparser.cpp \ + operatoractor.cpp \ + operatorresult.cpp \ + parseresult.cpp \ + pluginaction.cpp \ + pluginactionlineage.cpp \ + pluginactionmanager.cpp \ + plugincallback.cpp \ + pluginmanager.cpp \ + range.cpp \ + settingsdata.cpp \ + settingsmanager.cpp \ + templatefilehandler.cpp + +HEADERS += \ + actionprogress.h \ + actionwatcher.h \ + analyzeractor.h \ + analyzerinterface.h \ + analyzerresult.h \ +# asciidisplay.h \ + # binarydisplay.h \ + bitarray.h \ + bitcontainer.h \ + bitcontainerlistmodel.h \ + bitcontainermanager.h \ + bitcontainerpreview.h \ + # bitraster.h \ + bitcontainertreemodel.h \ + displaybase.h \ + displaybasetext.h \ + displayhandle.h \ + displayinterface.h \ + frame.h \ + # hexdisplay.h \ + hobbits-core_global.h \ + hobbitscoreinfo.h \ + importexportinterface.h \ + operatoractor.h \ + operatorinterface.h \ + mathparser.h \ + operatorresult.h \ + parseresult.h \ + pluginaction.h \ + pluginactionlineage.h \ + pluginactionmanager.h \ + plugincallback.h \ + pluginmanager.h \ + range.h \ + settingsdata.h \ + settingsmanager.h \ + templatefilehandler.h + +DISTFILES += + +INSTALLS = diff --git a/src/hobbits-core/hobbits-core_global.h b/src/hobbits-core/hobbits-core_global.h new file mode 100644 index 00000000..8f71142b --- /dev/null +++ b/src/hobbits-core/hobbits-core_global.h @@ -0,0 +1,12 @@ +#ifndef HOBBITSCORE_GLOBAL_H +#define HOBBITSCORE_GLOBAL_H + +#include + +#if defined (HOBBITSCORE_LIBRARY) +# define HOBBITSCORESHARED_EXPORT Q_DECL_EXPORT +#else +# define HOBBITSCORESHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // HOBBITSCORE_GLOBAL_H diff --git a/src/hobbits-core/hobbitscoreinfo.cpp b/src/hobbits-core/hobbitscoreinfo.cpp new file mode 100644 index 00000000..8f88caa6 --- /dev/null +++ b/src/hobbits-core/hobbitscoreinfo.cpp @@ -0,0 +1,14 @@ +#include "hobbitscoreinfo.h" + +namespace HobbitsCoreInfo +{ +QString getLibVersion() +{ +#ifdef HOBBITS_CORE_LIB_VERSION + return HOBBITS_CORE_LIB_VERSION; +#else + return "Spooky Unknown Version"; +#endif +} + +} diff --git a/src/hobbits-core/hobbitscoreinfo.h b/src/hobbits-core/hobbitscoreinfo.h new file mode 100644 index 00000000..55e99d05 --- /dev/null +++ b/src/hobbits-core/hobbitscoreinfo.h @@ -0,0 +1,13 @@ +#ifndef HOBBITSCOREINFO_H +#define HOBBITSCOREINFO_H + +#include + +namespace HobbitsCoreInfo +{ + +QString getLibVersion(); + +} + +#endif // HOBBITSCOREINFO_H diff --git a/src/hobbits-core/importexportinterface.h b/src/hobbits-core/importexportinterface.h new file mode 100644 index 00000000..1dbd12da --- /dev/null +++ b/src/hobbits-core/importexportinterface.h @@ -0,0 +1,31 @@ +#ifndef IMPORTEXPORTINTERFACE_H +#define IMPORTEXPORTINTERFACE_H + +#include "bitcontainer.h" +#include + +class ImportExportInterface +{ +public: + virtual ~ImportExportInterface() + { + } + + virtual ImportExportInterface* createDefaultImporterExporter() = 0; + + virtual QString getName() = 0; + + virtual bool canImport() = 0; + virtual bool canExport() = 0; + + virtual QSharedPointer importBits(QMap args, QWidget *parent) = 0; + virtual void exportBits( + QSharedPointer container, + QMap args, + QWidget *parent) = 0; + +}; + +Q_DECLARE_INTERFACE(ImportExportInterface, "hobbits.ImportExportInterface") + +#endif // IMPORTEXPORTINTERFACE_H diff --git a/src/hobbits-core/mathparser.cpp b/src/hobbits-core/mathparser.cpp new file mode 100644 index 00000000..65987f8c --- /dev/null +++ b/src/hobbits-core/mathparser.cpp @@ -0,0 +1,461 @@ +#include "mathparser.h" +#include + +/* |-------------------------------------------------| + * | Input format: [expression][unit] | + * | | + * | Expression consists of base 2,8,10,16 numbers | + * | separated by operations (+,-,*,/). | + * | | + * | Units: ["Bits"] or "Bytes" | + * | | + * | Hex digits should be lowercase | + * | | + * | Spaces ignored | + * |_________________________________________________| + */ + + +MathParser::MathParser() +{ +} + +QChar MathParser::tok() +{ + return (pos < this->tokens.length()) ? QChar(this->tokens[this->pos]) : *(new QChar(';')); +} + +void MathParser::next() +{ + this->pos++; +} + +void MathParser::prev() +{ + this->pos--; +} + +QChar MathParser::op() +{ + QString ops = "+-*/"; + if (ops.contains(this->tok())) { + return this->tok(); + } + return '\0'; +} + +QChar MathParser::bin() +{ + + if (this->tok() == "0" || this->tok() == "1") { + return this->tok(); + } + return '\0'; +} + +QChar MathParser::oct() +{ + QString octs = "234567"; + if (!bin().isNull()) { + return this->tok(); + } + if (octs.contains(this->tok())) { + return this->tok(); + } + return '\0'; +} + +QChar MathParser::dec() +{ + QString decs = "89"; + if (!oct().isNull()) { + return this->tok(); + } + if (decs.contains(this->tok())) { + return this->tok(); + } + return '\0'; +} + +QChar MathParser::hex() +{ + QString hexs = "abcdef"; + if (!dec().isNull()) { + return this->tok(); + } + if (hexs.contains(this->tok(), Qt::CaseSensitive)) { + return this->tok(); + } + return '\0'; +} + +ParseResult MathParser::wholebin(bool move_back = true) +{ + QString binaryString = ""; + bool ok; + if (this->tok() == "0") { + this->next(); + if (this->tok() == "b") { + this->next(); + if (!bin().isNull()) { + binaryString += this->tok(); + this->next(); + while (!bin().isNull()) { + binaryString += this->tok(); + this->next(); + } + if (move_back) { + for (int i = 0; i < binaryString.length() + 2; i++) { + this->prev(); + } + } + return ParseResult(binaryString.toInt(&ok, 2), 1); + } + else { + this->prev(); + this->prev(); + return ParseResult(-1, -1); + } + } + else { + this->prev(); + return ParseResult(-1, -1); + } + } + else { + return ParseResult(-1, -1); + } +} + +ParseResult MathParser::wholehex(bool move_back = true) +{ + QString hexString = ""; + bool ok; + if (this->tok() == "0") { + this->next(); + if (this->tok() == "x") { + this->next(); + if (!hex().isNull()) { + hexString += this->tok(); + this->next(); + while (!hex().isNull()) { + hexString += this->tok(); + this->next(); + } + if (move_back) { + for (int i = 0; i < hexString.length() + 2; i++) { + this->prev(); + } + } + return ParseResult(hexString.toLongLong(&ok, 16), 1); + } + else { + this->prev(); + this->prev(); + return ParseResult(-1, -1); + } + } + else { + this->prev(); + return ParseResult(-1, -1); + } + } + else { + return ParseResult(-1, -1); + } +} + +ParseResult MathParser::wholeoct(bool move_back = true) +{ + QString octString = ""; + bool ok; + if (this->tok() == "0") { + this->next(); + if (this->tok() == "o") { + this->next(); + if (!oct().isNull()) { + octString += this->tok(); + this->next(); + while (!oct().isNull()) { + octString += this->tok(); + this->next(); + } + if (move_back) { + for (int i = 0; i < octString.length() + 2; i++) { + this->prev(); + } + } + return ParseResult(octString.toLongLong(&ok, 8), 1); + } + else { + this->prev(); + this->prev(); + return ParseResult(-1, -1); + } + } + else { + this->prev(); + return ParseResult(-1, -1); + } + } + else { + return ParseResult(-1, -1); + } +} + +ParseResult MathParser::wholedec(bool move_back = true) +{ + QString decString = ""; + if (!dec().isNull()) { + decString += this->tok(); + this->next(); + while (!dec().isNull()) { + decString += this->tok(); + this->next(); + } + if (move_back) { + for (int i = 0; i < decString.length(); i++) { + this->prev(); + } + } + return ParseResult(decString.toLongLong(), 1); + } + return ParseResult(-1, -1); +} + +ParseResult MathParser::whole(bool move_back = true) +{ + if (wholebin().isValid()) { + return wholebin(move_back); + } + if (wholeoct().isValid()) { + return wholeoct(move_back); + } + if (wholehex().isValid()) { + return wholehex(move_back); + } + if (wholedec().isValid()) { + return wholedec(move_back); + } + return ParseResult(-1, -1); +} + +ParseResult MathParser::factor(bool move_back = true) +{ + if (whole().getVal() != -1) { + return whole(move_back); + } + return ParseResult(-1, -1); +} + +// +qlonglong applyOp(qlonglong a, qlonglong b, QChar op) +{ + if (op == '*') { + + return a * b; + } + else if (op == '/') { + return a / b; + } + else if (op == '+') { + return a + b; + } + else if (op == '-') { + return a - b; + } + + return 0; +} + +int precedence(char op) +{ + if (op == '+' || op == '-') { + return 1; + } + if (op == '*' || op == '/') { + return 2; + } + return 0; +} + +ParseResult MathParser::factors() +{ + QVector nums; + QVector allOps; + + if (factor().isValid()) { + nums.push_back(factor(false).getVal()); + + while (!op().isNull()) { + allOps.push_back(op()); + this->next(); + + if (factor().isValid()) { + nums.push_back(factor(false).getVal()); + } + else { + return ParseResult(-1, -1); + } + } + // Calculation + + qlonglong result = nums[0]; + if (allOps.size() != 0) { + for (int i = 0; i < allOps.size(); i++) { + if (allOps[i] == "*" || allOps[i] == "/") { + result = applyOp(nums[i], nums[i + 1], allOps[i]); + nums.erase(nums.begin() + i); + nums.erase(nums.begin() + i); + nums.insert(nums.begin() + i, result); + allOps.erase(allOps.begin() + i); + i--; + } + } + for (int i = 0; i < allOps.size(); i++) { + if (allOps[i] == "+" || allOps[i] == "-") { + result = applyOp(nums[i], nums[i + 1], allOps[i]); + nums.erase(nums.begin() + i); + nums.erase(nums.begin() + i); + nums.insert(nums.begin() + i, result); + allOps.erase(allOps.begin() + i); + i--; + } + } + } + + return ParseResult(result, 1); + + } + return ParseResult(-1, -1); +} + +int MathParser::unit() +{ + // Bits + if (this->tok() == "B") { + this->next(); + if (this->tok() == "i") { + this->next(); + if (this->tok() == "t") { + this->next(); + if (this->tok() == "s") { + return 1; + } + else { + this->prev(); + this->prev(); + this->prev(); + } + } + else { + this->prev(); + this->prev(); + } + } + else { + this->prev(); + } + } + + // Bytes + if (this->tok() == "B") { + this->next(); + if (this->tok() == "y") { + this->next(); + if (this->tok() == "t") { + this->next(); + if (this->tok() == "e") { + this->next(); + if (this->tok() == "s") { + return 8; + } + else { + this->prev(); + this->prev(); + this->prev(); + this->prev(); + } + } + else { + this->prev(); + this->prev(); + this->prev(); + } + } + else { + this->prev(); + this->prev(); + } + } + else { + this->prev(); + } + } + + return -1; +} + +ParseResult MathParser::base() +{ + qlonglong f = factors().getVal(); + if (f != -1) { + if (this->tok() == ';') { + return ParseResult(f, 1); // Bits is default unit + } + else { + int u = this->unit(); + if (u != -1) { + return ParseResult(f, u); + } + else { + this->prev(); + this->prev(); + return ParseResult(-1, -1); + } + } + } + return ParseResult(-1, -1); +} + +ParseResult MathParser::parseInput(QString tokens, ParseType t, int start) +{ + this->tokens = tokens.simplified().replace(" ", ""); + this->pos = start; + ParseResult result; + switch (t) { + case Default: + result = base(); + break; + + case No_Unit: + result = factors(); + break; + + case Hex: + result = wholehex(false); + break; + + case Bin: + result = wholebin(false); + break; + + case Dec: + result = wholedec(false); + break; + + case Oct: + result = wholeoct(false); + break; + + case No_Math: + result = whole(false); + break; + } + return result; +} + +ParseResult MathParser::parseInput(QString tokens) +{ + return this->parseInput(tokens, ParseType::Default); +} diff --git a/src/hobbits-core/mathparser.h b/src/hobbits-core/mathparser.h new file mode 100644 index 00000000..f8e9d6ce --- /dev/null +++ b/src/hobbits-core/mathparser.h @@ -0,0 +1,46 @@ +#ifndef MATHPARSER_H +#define MATHPARSER_H +#include "parseresult.h" +#include + +enum ParseType { + Default = 0, + No_Unit = 1, + Hex = 2, + Bin = 3, + Dec = 4, + Oct = 5, + No_Math = 6 +}; + +class MathParser +{ +public: + MathParser(); + + QString tokens; + int pos; + QChar tok(); + void next(); + void prev(); + ParseResult parseInput(QString tokens, ParseType t, int start = 0); + ParseResult parseInput(QString tokens); + +private: + ParseResult base(); // value, multiplier + ParseResult factors(); + int unit(); // return multiplier + ParseResult factor(bool); + QChar op(); + ParseResult whole(bool); + ParseResult wholehex(bool); + ParseResult wholedec(bool); + ParseResult wholeoct(bool); + ParseResult wholebin(bool); + QChar hex(); + QChar dec(); + QChar oct(); + QChar bin(); + +}; +#endif // MATHPARSER_H diff --git a/src/hobbits-core/operatoractor.cpp b/src/hobbits-core/operatoractor.cpp new file mode 100644 index 00000000..496f3c99 --- /dev/null +++ b/src/hobbits-core/operatoractor.cpp @@ -0,0 +1,169 @@ +#include "operatoractor.h" +#include "pluginaction.h" +#include "pluginactionlineage.h" +#include "pluginactionmanager.h" +#include "settingsmanager.h" + +OperatorActor::OperatorActor(PluginActionManager *manager, QSharedPointer pluginManager) : + QObject(), + m_pluginManager(pluginManager), + m_manager(manager) +{ +} + +QSharedPointer>> OperatorActor::operatorFullAct( + QSharedPointer op, + QList> inputContainers, + QSharedPointer bitContainerManager, + QString outputName, + QJsonObject pluginState, + QMap outputIdMap) +{ + + if (m_future.isRunning() || m_manager->isBusy()) { + return QSharedPointer>>(); + } + + QList> inputContainersConst; + for (QSharedPointer input : inputContainers) { + inputContainersConst.append(input); + } + + if (pluginState.isEmpty()) { + pluginState = op->getStateFromUi(); + if (pluginState.isEmpty() || pluginState.contains("error")) { + if (pluginState.contains("error")) { + emit reportError(QString("Plugin '%1' reported an error with its current state: '%2'").arg( + op->getName()).arg(pluginState.value("error").toString())); + } + else if (pluginState.isEmpty()) { + emit reportError(QString( + "Plugin '%1' is in an invalid state and can't be excecuted. Double check the input fields.").arg( + op->getName())); + } + return QSharedPointer>>(); + } + } + + QSharedPointer progress(new ActionProgress()); + + SettingsManager::getInstance().setPrivateSetting( + SettingsData::PLUGIN_RUNNING_KEY, + QVariant(m_pluginManager->getPluginLocation(op->getName()))); + + m_future = QtConcurrent::run( + QThreadPool::globalInstance(), + OperatorActor::operatorCall, + op, + inputContainersConst, + pluginState, + progress); + + m_actionWatcher = QSharedPointer>>( + new ActionWatcher>( + m_future, + progress)); + m_op = op; + m_inputContainers = inputContainers; + m_outputName = outputName; + m_bitContainerManager = bitContainerManager; + m_pluginState = pluginState; + m_outputIdMap = outputIdMap; + + connect(m_actionWatcher->watcher(), SIGNAL(finished()), this, SLOT(postProcess())); + + m_manager->registerOperatorWatcher(m_actionWatcher); + + return m_actionWatcher; +} + +void OperatorActor::postProcess() +{ + disconnect(m_actionWatcher->watcher(), SIGNAL(finished()), this, SLOT(postProcess())); + + QSharedPointer result = m_future.result(); + + if (result.isNull()) { + emit reportError(QString("Plugin '%1' failed to execute. Double check the input fields.").arg(m_op->getName())); + return; + } + + if (result->getPluginState().contains("error")) { + emit reportError(QString("Plugin '%1' reported an error with its processing: %2").arg(m_op->getName()).arg( + result->getPluginState().value("error").toString())); + return; + } + + // Apply action lineage + if (!result->getPluginState().isEmpty()) { + for (int i = 0; i < result->getOutputContainers().size(); i++) { + QSharedPointer action = + QSharedPointer( + new PluginAction( + PluginAction::Operator, + m_op->getName(), + result->getPluginState())); + QSharedPointer lineage = PluginActionLineage::create(action)->setOutputPos(i); + if (!m_inputContainers.isEmpty()) { + // TODO: multiple parents + lineage->setParent(m_inputContainers.at(0)->getActionLineage()); + } + result->getOutputContainers().at(i)->setActionLineage(lineage); + } + } + + // Adjust the output containers' IDs if they are being forced + for (int i = 0; i < result->getOutputContainers().size(); i++) { + if (m_outputIdMap.contains(i)) { + result->getOutputContainers().at(i)->m_id = m_outputIdMap.value(i); + } + } + m_outputIdMap.clear(); + + // Set Parent/Child Relationships + for (QSharedPointer inputContainer : m_inputContainers) { + for (QSharedPointer outputContainer : result->getOutputContainers()) { + inputContainer->addChild(outputContainer->getId()); + outputContainer->addParent(inputContainer->getId()); + } + } + + // Add output containers to container manager + if (result->getOutputContainers().size() > 0) { + if (m_outputName.isEmpty()) { + if (result->getPluginState().contains("container_name") + && result->getPluginState().value("container_name").isString()) { + m_outputName = result->getPluginState().value("container_name").toString(); + } + else { + m_outputName = m_op->getName() + " Output"; + } + } + int number = 1; + QModelIndex lastAdded; + for (QSharedPointer output : result->getOutputContainers()) { + if (result->getOutputContainers().length() > 1) { + output->setName(QString("%2: %1").arg(m_outputName).arg(number)); + } + else { + output->setName(m_outputName); + } + number++; + lastAdded = m_bitContainerManager->getTreeModel()->addContainer(output); + } + if (lastAdded.isValid()) { + m_bitContainerManager->getCurrSelectionModel()->setCurrentIndex( + lastAdded, + QItemSelectionModel::ClearAndSelect); + } + } +} + +QSharedPointer OperatorActor::operatorCall( + QSharedPointer op, + QList> inputContainers, + QJsonObject pluginState, + QSharedPointer progressTracker) +{ + return op->operateOnContainers(inputContainers, pluginState, progressTracker); +} diff --git a/src/hobbits-core/operatoractor.h b/src/hobbits-core/operatoractor.h new file mode 100644 index 00000000..8fd1ab47 --- /dev/null +++ b/src/hobbits-core/operatoractor.h @@ -0,0 +1,58 @@ +#ifndef OPERATORACTOR_H +#define OPERATORACTOR_H + +#include "actionwatcher.h" +#include "bitcontainermanager.h" +#include "pluginmanager.h" +#include +#include +#include +#include + +class PluginActionManager; + +class OperatorActor : public QObject +{ + Q_OBJECT + +public: + explicit OperatorActor(PluginActionManager *manager, QSharedPointer pluginManager); + + QSharedPointer>> operatorFullAct( + QSharedPointer op, + QList> inputContainers, + QSharedPointer bitContainerManager, + QString outputName, + QJsonObject pluginState = QJsonObject(), + QMap outputIdMap = QMap()); + +signals: + void reportError(QString); + +public slots: +private slots: + void postProcess(); + +private: + static QSharedPointer operatorCall( + QSharedPointer op, + QList> inputContainers, + QJsonObject pluginState, + QSharedPointer progressTracker); + + QFuture> m_future; + QSharedPointer m_op; + QList> m_inputContainers; + QSharedPointer m_bitContainerManager; + QSharedPointer m_pluginManager; + QString m_outputName; + QJsonObject m_pluginState; + QMap m_outputIdMap; + + QSharedPointer>> m_actionWatcher; + + PluginActionManager *m_manager; + +}; + +#endif // OPERATORACTOR_H diff --git a/src/hobbits-core/operatorinterface.h b/src/hobbits-core/operatorinterface.h new file mode 100644 index 00000000..d5f148a9 --- /dev/null +++ b/src/hobbits-core/operatorinterface.h @@ -0,0 +1,37 @@ +#ifndef OPERATORINTERFACE_H +#define OPERATORINTERFACE_H + +#include "actionprogress.h" +#include "bitcontainer.h" +#include "operatorresult.h" +#include "plugincallback.h" +#include + +class OperatorInterface +{ +public: + virtual ~OperatorInterface() + { + } + + virtual QString getName() = 0; + virtual OperatorInterface* createDefaultOperator() = 0; + + virtual void applyToWidget(QWidget *widget) = 0; + virtual void provideCallback(QSharedPointer pluginCallback) = 0; + virtual QJsonObject getStateFromUi() = 0; + virtual bool canRecallPluginState(const QJsonObject &pluginState) = 0; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) = 0; + + virtual int getMinInputContainers() = 0; + virtual int getMaxInputContainers() = 0; + virtual QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) = 0; + +}; + +Q_DECLARE_INTERFACE(OperatorInterface, "hobbits.OperatorInterface.3") + +#endif // OPERATORINTERFACE_H diff --git a/src/hobbits-core/operatorresult.cpp b/src/hobbits-core/operatorresult.cpp new file mode 100644 index 00000000..424e4ae8 --- /dev/null +++ b/src/hobbits-core/operatorresult.cpp @@ -0,0 +1,28 @@ +#include "operatorresult.h" + +OperatorResult::OperatorResult() +{ + +} + +OperatorResult* OperatorResult::setOutputContainers(QList> outputContainers) +{ + m_outputContainers = outputContainers; + return this; +} + +QList> OperatorResult::getOutputContainers() const +{ + return m_outputContainers; +} + +OperatorResult* OperatorResult::setPluginState(QJsonObject pluginState) +{ + m_pluginState = pluginState; + return this; +} + +const QJsonObject OperatorResult::getPluginState() const +{ + return m_pluginState; +} diff --git a/src/hobbits-core/operatorresult.h b/src/hobbits-core/operatorresult.h new file mode 100644 index 00000000..873df0f5 --- /dev/null +++ b/src/hobbits-core/operatorresult.h @@ -0,0 +1,28 @@ +#ifndef OPERATORRESULT_H +#define OPERATORRESULT_H + +#include +#include +#include + +#include "bitcontainer.h" + +class OperatorResult +{ +public: + OperatorResult(); + + OperatorResult* setOutputContainers(QList> outputContainers); + QList> getOutputContainers() const; + + OperatorResult* setPluginState(QJsonObject pluginState); + const QJsonObject getPluginState() const; + +private: + QList> m_outputContainers; + int m_maxFrameWidth; + + QJsonObject m_pluginState; +}; + +#endif // OPERATORRESULT_H diff --git a/src/hobbits-core/parseresult.cpp b/src/hobbits-core/parseresult.cpp new file mode 100644 index 00000000..ed81e202 --- /dev/null +++ b/src/hobbits-core/parseresult.cpp @@ -0,0 +1,34 @@ +#include "parseresult.h" + +ParseResult::ParseResult() +{ +} + +ParseResult::ParseResult(qlonglong val, int mult) +{ + this->_val = val; + this->_mult = mult; +} + +qlonglong ParseResult::getVal() +{ + return this->_val; +} + +int ParseResult::getMult() +{ + return this->_mult; +} + +qlonglong ParseResult::getResult() +{ + if (this->isValid()) { + return this->_val * this->_mult; + } + return -1; +} + +bool ParseResult::isValid() +{ + return (this->_mult >= 0 && this->_val >= 0); // No negative results allowed either. +} diff --git a/src/hobbits-core/parseresult.h b/src/hobbits-core/parseresult.h new file mode 100644 index 00000000..9fbd5efa --- /dev/null +++ b/src/hobbits-core/parseresult.h @@ -0,0 +1,21 @@ +#ifndef PARSERESULT_H +#define PARSERESULT_H +#include + +class ParseResult +{ +public: + ParseResult(); + ParseResult(qlonglong, int); + + qlonglong getVal(); // Returns evaluated value of math expression + int getMult(); // Returns unit multiplier + qlonglong getResult(); // Returns total # of bits + bool isValid(); // Checks if expression was valid + +private: + qlonglong _val; + int _mult; +}; + +#endif // PARSERESULT_H diff --git a/src/hobbits-core/pluginaction.cpp b/src/hobbits-core/pluginaction.cpp new file mode 100644 index 00000000..8121a6b5 --- /dev/null +++ b/src/hobbits-core/pluginaction.cpp @@ -0,0 +1,106 @@ +#include "pluginaction.h" +#include "pluginactionlineage.h" + +#include "bitcontainermanager.h" + +#include + +PluginAction::PluginAction(PluginType pluginType, QString pluginName, QJsonObject pluginState) : + m_pluginType(pluginType), + m_pluginName(pluginName), + m_pluginState(pluginState) +{ + +} + +PluginAction::PluginType PluginAction::getPluginType() const +{ + return m_pluginType; +} + +QString PluginAction::getPluginName() const +{ + return m_pluginName; +} + +QJsonObject PluginAction::getPluginState() const +{ + return m_pluginState; +} + +QJsonObject PluginAction::serialize() const +{ + QJsonObject obj; + + obj.insert("type", m_pluginType); + obj.insert("name", m_pluginName); + obj.insert("state", m_pluginState); + + return obj; +} + +QSharedPointer PluginAction::deserialize(QJsonObject data) +{ + QSharedPointer nullAction; + + if (!(data.contains("type") + && data.contains("name") + && data.contains("state"))) { + return nullAction; + } + + if (!(data.value("type").type() == QJsonValue::Type::Double + && data.value("state").type() == QJsonValue::Type::Object + && data.value("name").type() == QJsonValue::Type::String)) { + return nullAction; + } + + PluginType pluginType = static_cast(data.value("type").toInt()); + QString pluginName = data.value("name").toString(); + QJsonObject pluginState = data.value("state").toObject(); + + return QSharedPointer(new PluginAction(pluginType, pluginName, pluginState)); +} + +QSharedPointer>> PluginAction::operatorAct( + QSharedPointer actor, + QSharedPointer pluginManager, + QList> inputContainers, + QSharedPointer bitContainerManager, + QString outputName, + QMap outputIdMap) const +{ + QSharedPointer>> nullResult; + QSharedPointer plugin = pluginManager->getOperator(m_pluginName); + if (plugin.isNull()) { + return nullResult; + } + if (!plugin->canRecallPluginState(m_pluginState)) { + return nullResult; + } + + return actor->operatorFullAct( + plugin, + inputContainers, + bitContainerManager, + outputName, + m_pluginState, + outputIdMap); +} + +QSharedPointer>> PluginAction::analyzerAct( + QSharedPointer actor, + QSharedPointer pluginManager, + QSharedPointer container) const +{ + QSharedPointer>> nullResult; + QSharedPointer plugin = pluginManager->getAnalyzer(m_pluginName); + if (plugin.isNull()) { + return nullResult; + } + if (!plugin->canRecallPluginState(m_pluginState)) { + return nullResult; + } + + return actor->analyzerFullAct(plugin, container, m_pluginState); +} diff --git a/src/hobbits-core/pluginaction.h b/src/hobbits-core/pluginaction.h new file mode 100644 index 00000000..b24bef60 --- /dev/null +++ b/src/hobbits-core/pluginaction.h @@ -0,0 +1,64 @@ +#ifndef PLUGINACTION_H +#define PLUGINACTION_H + +#include "actionwatcher.h" +#include "analyzeractor.h" +#include "operatoractor.h" +#include "pluginmanager.h" +#include +#include + +class BitContainerManager; +class PluginAction +{ +public: + enum PluginType { + Framer = 1 /*Deprecated*/, + Operator = 2, + Analyzer = 3 + }; + + PluginAction(PluginType pluginType, QString pluginName, QJsonObject pluginState); + + PluginType getPluginType() const; + QString getPluginName() const; + QJsonObject getPluginState() const; + + QJsonObject serialize() const; + + static QSharedPointer deserialize(QJsonObject data); + + QSharedPointer>> operatorAct( + QSharedPointer actor, + QSharedPointer pluginManager, + QList> inputContainers, + QSharedPointer bitContainerManager, + QString outputName = "", + QMap outputIdMap = QMap()) const; + QSharedPointer>> analyzerAct( + QSharedPointer actor, + QSharedPointer pluginManager, + QSharedPointer container) const; + + inline bool operator==(const PluginAction &other) const + { + return ( + m_pluginName == other.getPluginName() + && m_pluginType == other.getPluginType() + && m_pluginState == other.getPluginState() + ); + } + +private: + PluginType m_pluginType; + QString m_pluginName; + QJsonObject m_pluginState; +}; + + +inline uint qHash(const PluginAction &key, uint seed) +{ + return qHash(key.getPluginState(), seed) ^ uint(key.getPluginType()) ^ qHash(key.getPluginName(), seed); +} + +#endif // PLUGINACTION_H diff --git a/src/hobbits-core/pluginactionlineage.cpp b/src/hobbits-core/pluginactionlineage.cpp new file mode 100644 index 00000000..3a847520 --- /dev/null +++ b/src/hobbits-core/pluginactionlineage.cpp @@ -0,0 +1,171 @@ +#include "pluginactionlineage.h" +#include + +PluginActionLineage::PluginActionLineage(QSharedPointer pluginAction) : + m_pluginAction(pluginAction), + m_outputPosition(0) +{ +} + +QSharedPointer PluginActionLineage::create(QSharedPointer pluginAction) +{ + return QSharedPointer(new PluginActionLineage(pluginAction)); +} + +QSharedPointer PluginActionLineage::fromLineage( + QSharedPointer lineage, + int offset, + int length) +{ + QList> lineageParts = lineage->getLineage(); + int lastIndex = lineageParts.size(); + if (length >= 0) { + lastIndex = offset + length; + } + QSharedPointer lastOne; + for ( ; offset < lineageParts.size() && offset < lastIndex; offset++) { + QSharedPointer l = PluginActionLineage::create(lineageParts.at(offset)->getPluginAction()); + if (!lastOne.isNull()) { + l->setParent(lastOne); + l->setOutputPos(lineageParts.at(offset)->getOutputPosition()); + } + lastOne = l; + } + + return lastOne; +} + +QSharedPointer PluginActionLineage::mergeIntoTree( + QList> branchingLineages) +{ + + QSharedPointer node = QSharedPointer( + new PluginActionLineage::TreeNode()); + + if (branchingLineages.size() == 1) { + node->lineage = branchingLineages.at(0); + return node; + } + + QMultiHash> childBranches; + for (int i = 0; i < branchingLineages.size(); i++) { + auto singleLineage = branchingLineages.at(i)->getLineage(); + auto firstAction = *singleLineage.at(0)->getPluginAction().data(); + childBranches.insert(firstAction, branchingLineages.at(i)); + } + + if (childBranches.uniqueKeys().size() == 1) { + // find the depth of the common lineage + int sharedLength = 0; + bool shared = true; + QSet actions; + while (shared) { + sharedLength++; + actions.clear(); + for (auto lineage : branchingLineages) { + auto singleLineage = lineage->getLineage(); + if (singleLineage.size() <= sharedLength) { + shared = false; + break; + } + actions.insert(*singleLineage.at(sharedLength)->getPluginAction().data()); + if (actions.size() > 1) { + shared = false; + break; + } + } + } + + node->lineage = PluginActionLineage::fromLineage(branchingLineages.at(0), 0, sharedLength); + QList> childList; + for (auto lineage : branchingLineages) { + childList.append(PluginActionLineage::fromLineage(lineage, sharedLength)); + } + node->children.append(PluginActionLineage::mergeIntoTree(childList)); + } + else { + for (PluginAction key : childBranches.uniqueKeys()) { + node->children.append(PluginActionLineage::mergeIntoTree(childBranches.values(key))); + } + } + + return node; +} + +QSharedPointer PluginActionLineage::setParent(QSharedPointer parent) +{ + m_parent = parent; + return sharedFromThis(); +} + +QSharedPointer PluginActionLineage::setOutputPos(int outputPosition) +{ + m_outputPosition = outputPosition; + return sharedFromThis(); +} + +QSharedPointer PluginActionLineage::getPluginAction() const +{ + return m_pluginAction; +} + +int PluginActionLineage::getOutputPosition() const +{ + return m_outputPosition; +} + +QJsonObject PluginActionLineage::serialize() const +{ + QJsonObject obj; + QJsonArray actions; + for (auto action : getLineage()) { + QJsonObject actionObject; + actionObject.insert("action", action->m_pluginAction->serialize()); + actionObject.insert("outputPosition", action->m_outputPosition); + actions.append(actionObject); + } + obj.insert("actions", actions); + return obj; +} + +QSharedPointer PluginActionLineage::deserialize(QJsonObject data) +{ + QSharedPointer nullLineage; + + if (!(data.contains("actions") + && data.value("actions").isArray())) { + return nullLineage; + } + + QSharedPointer lineage; + for (auto action : data.value("actions").toArray()) { + if (!action.isObject()) { + return nullLineage; + } + QJsonObject actionObject = action.toObject(); + if (!(actionObject.contains("action") + && actionObject.value("action").isObject() + && actionObject.contains("outputPosition") + && actionObject.value("outputPosition").isDouble())) { + return nullLineage; + } + QSharedPointer pluginAction = PluginAction::deserialize(actionObject.value("action").toObject()); + if (pluginAction.isNull()) { + return nullLineage; + } + int outputPosition = int(actionObject.value("outputPosition").toDouble()); + lineage = PluginActionLineage::create(pluginAction)->setParent(lineage)->setOutputPos(outputPosition); + } + + return lineage; +} + +QList> PluginActionLineage::getLineage() const +{ + QList> lineage; + if (!m_parent.isNull()) { + lineage = m_parent->getLineage(); + } + lineage.append(this->sharedFromThis()); + return lineage; +} diff --git a/src/hobbits-core/pluginactionlineage.h b/src/hobbits-core/pluginactionlineage.h new file mode 100644 index 00000000..2225beda --- /dev/null +++ b/src/hobbits-core/pluginactionlineage.h @@ -0,0 +1,43 @@ +#ifndef PLUGINACTIONLINEAGE_H +#define PLUGINACTIONLINEAGE_H + +#include "pluginaction.h" +#include + +class PluginActionLineage : public QEnableSharedFromThis +{ +private: + PluginActionLineage(QSharedPointer pluginAction); + +public: + struct TreeNode { + QSharedPointer lineage; + QList> children; + }; + + static QSharedPointer create(QSharedPointer pluginAction); + static QSharedPointer fromLineage( + QSharedPointer lineage, + int offset = 0, + int length = -1); + + static QSharedPointer mergeIntoTree(QList> branchingLineages); + + QSharedPointer setParent(QSharedPointer parent); + QSharedPointer setOutputPos(int outputPosition); + + QSharedPointer getPluginAction() const; + int getOutputPosition() const; + + QJsonObject serialize() const; + static QSharedPointer deserialize(QJsonObject data); + + QList> getLineage() const; + +private: + QSharedPointer m_pluginAction; + int m_outputPosition; + QSharedPointer m_parent; +}; + +#endif // PLUGINACTIONLINEAGE_H diff --git a/src/hobbits-core/pluginactionmanager.cpp b/src/hobbits-core/pluginactionmanager.cpp new file mode 100644 index 00000000..8f99a530 --- /dev/null +++ b/src/hobbits-core/pluginactionmanager.cpp @@ -0,0 +1,274 @@ +#include "pluginactionlineage.h" +#include "pluginactionmanager.h" +#include "settingsmanager.h" + +PluginActionManager::PluginActionManager(QSharedPointer pluginManager) : + QObject(nullptr), + m_pluginManager(pluginManager), + m_operatorActor(new OperatorActor(this, pluginManager)), + m_analyzerActor(new AnalyzerActor(this, pluginManager)), + m_activeLineage(false), + m_lineageStep(0) +{ + connect(m_operatorActor.data(), SIGNAL(reportError(QString)), this, SIGNAL(reportError(QString))); + connect(m_analyzerActor.data(), SIGNAL(reportError(QString)), this, SIGNAL(reportError(QString))); +} + +bool PluginActionManager::isBusy() const +{ + return !(m_currOperatorWatcher.isNull() + && m_currAnalyzerWatcher.isNull()); +} + +bool PluginActionManager::hasActiveLineage() const +{ + return m_activeLineage; +} + +void PluginActionManager::cancelAction() +{ + if (!m_currOperatorWatcher.isNull()) { + m_currOperatorWatcher->progress()->setCancelled(true); + m_lineageQueue.clear(); + } + else if (!m_currAnalyzerWatcher.isNull()) { + m_currAnalyzerWatcher->progress()->setCancelled(true); + m_lineageQueue.clear(); + } +} + +bool PluginActionManager::registerOperatorWatcher( + QSharedPointer>> watcher) +{ + if (this->isBusy()) { + return false; + } + m_currOperatorWatcher = watcher; + connect(m_currOperatorWatcher->watcher(), SIGNAL(finished()), this, SLOT(operatorWatcherFinished())); + connect( + m_currOperatorWatcher->progress().data(), + SIGNAL(progressPercentChanged(int)), + this, + SIGNAL(actionWatcherProgress(int))); + emit actionWatcherStarted(); + return true; +} + +bool PluginActionManager::registerAnalyzerWatcher( + QSharedPointer>> watcher) +{ + if (this->isBusy()) { + return false; + } + m_currAnalyzerWatcher = watcher; + connect(m_currAnalyzerWatcher->watcher(), SIGNAL(finished()), this, SLOT(analyzerWatcherFinished())); + connect( + m_currAnalyzerWatcher->progress().data(), + SIGNAL(progressPercentChanged(int)), + this, + SIGNAL(actionWatcherProgress(int))); + emit actionWatcherStarted(); + return true; +} + +void PluginActionManager::applyLineage( + QUuid containerId, + QSharedPointer lineage, + QSharedPointer bitContainerManager, + QString baseName, + QMap lineageOverride) +{ + if (m_activeLineage) { + QSharedPointer actionToQueue = + QSharedPointer(new PluginActionManager::LineageAction()); + actionToQueue->containerId = containerId; + actionToQueue->lineage = lineage; + actionToQueue->bitContainerManager = bitContainerManager; + actionToQueue->baseName = baseName; + actionToQueue->outputIdOverride = lineageOverride; + m_lineageQueue.append(actionToQueue); + } + else { + QSharedPointer container = bitContainerManager->getContainerById(containerId); + if (container.isNull()) { + emit reportError(QString( + "Could not apply planned actions - could not find a bit container with the specified ID")); + finishLineage(); + return; + } + m_activeLineage = true; + m_currLineage = lineage->getLineage(); + m_lineageStep = 0; + m_lineageBitContainerManager = bitContainerManager; + m_lineageBaseName = baseName; + m_currLineageContainers.clear(); + m_currLineageContainers.append(container); + m_currOutputIdOverride = lineageOverride; + this->continueLineage(); + } +} + +void PluginActionManager::finishLineage() +{ + m_activeLineage = false; + if (m_lineageQueue.isEmpty()) { + emit lineageQueueCompleted(); + return; + } + QSharedPointer queuedAction = m_lineageQueue.takeFirst(); + applyLineage( + queuedAction->containerId, + queuedAction->lineage, + queuedAction->bitContainerManager, + queuedAction->baseName, + queuedAction->outputIdOverride); +} + +QSharedPointer PluginActionManager::operatorActor() +{ + return m_operatorActor; +} + +QSharedPointer PluginActionManager::analyzerActor() +{ + return m_analyzerActor; +} + +void PluginActionManager::operatorWatcherFinished() +{ + disconnect(m_currOperatorWatcher->watcher(), SIGNAL(finished()), this, SLOT(operatorWatcherFinished())); + disconnect( + m_currOperatorWatcher->progress().data(), + SIGNAL(progressPercentChanged(int)), + this, + SIGNAL(actionWatcherProgress(int))); + + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant()); + emit actionWatcherFinished(); + + auto result = m_currOperatorWatcher->result(); + m_currOperatorWatcher = QSharedPointer>>(); + + if (m_activeLineage) { + if (result.isNull() || result->getPluginState().contains("error")) { + emit reportError(QString("Plugin execution failed, aborting any planned executions")); + finishLineage(); + } + else { + m_currLineageContainers = result->getOutputContainers(); + continueLineage(); + } + } +} + +void PluginActionManager::analyzerWatcherFinished() +{ + disconnect(m_currAnalyzerWatcher->watcher(), SIGNAL(finished()), this, SLOT(analyzerWatcherFinished())); + disconnect( + m_currAnalyzerWatcher->progress().data(), + SIGNAL(progressPercentChanged(int)), + this, + SIGNAL(actionWatcherProgress(int))); + + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant()); + emit actionWatcherFinished(); + + auto result = m_currAnalyzerWatcher->result(); + m_currAnalyzerWatcher = QSharedPointer>>(); + + if (m_activeLineage) { + if (result.isNull() || result->getPluginState().contains("error")) { + emit reportError(QString("Plugin execution failed, aborting any planned executions")); + finishLineage(); + } + continueLineage(); + } +} + +void PluginActionManager::continueLineage() +{ + if (m_lineageStep >= m_currLineage.size() || m_lineageStep < 0) { + finishLineage(); + return; + } + + auto lineageStep = m_currLineage.at(m_lineageStep); + m_lineageStep++; + + if (lineageStep->getPluginAction()->getPluginType() == PluginAction::Framer) { + emit reportError(QString("Failed to initiate '%1' step - plugin type '%2' (Framer) is deprecated").arg( + lineageStep->getPluginAction()->getPluginName()).arg(lineageStep->getPluginAction()->getPluginType())); + finishLineage(); + return; + } + else if (lineageStep->getPluginAction()->getPluginType() == PluginAction::Analyzer) { + if (!(lineageStep->getOutputPosition() >= 0 + && lineageStep->getOutputPosition() < m_currLineageContainers.size())) { + emit reportError(QString("Failed to initiate '%1' step - required output container is unavailable").arg( + lineageStep->getPluginAction()->getPluginName())); + finishLineage(); + return; + } + auto watcher = lineageStep->getPluginAction()->analyzerAct( + m_analyzerActor, + m_pluginManager, + m_currLineageContainers.at(0)); + if (watcher.isNull()) { + emit reportError(QString("Failed to run '%1' step - plugin failed to initialize on the given state").arg( + lineageStep->getPluginAction()->getPluginName())); + finishLineage(); + } + } + else if (lineageStep->getPluginAction()->getPluginType() == PluginAction::Operator) { + if (lineageStep->getOutputPosition() >= m_currLineageContainers.size()) { + emit reportError(QString("Failed to initiate '%1' step - required output container is unavailable").arg( + lineageStep->getPluginAction()->getPluginName())); + finishLineage(); + return; + } + // TODO multiple parents + if (lineageStep->getOutputPosition() == -1) { + m_currLineageContainers.clear(); + } + else { + QSharedPointer parent = m_currLineageContainers.at(lineageStep->getOutputPosition()); + m_currLineageContainers.clear(); + m_currLineageContainers.append(parent); + } + +// QList > constContainers; +// for (auto container : m_currLineageContainers) { +// constContainers.append(container); +// } + + QMap outputOverride; + bool finalOp = true; + for (int i = m_lineageStep; i < m_currLineage.size(); i++) { + if (m_currLineage.at(i)->getPluginAction()->getPluginType() == PluginAction::Operator) { + finalOp = false; + break; + } + } + if (finalOp) { + outputOverride = m_currOutputIdOverride; + } + auto watcher = lineageStep->getPluginAction()->operatorAct( + m_operatorActor, + m_pluginManager, + m_currLineageContainers, + m_lineageBitContainerManager, + QString(), + outputOverride); + if (watcher.isNull()) { + emit reportError(QString("Failed to run '%1' step - plugin failed to initialize on the given state").arg( + lineageStep->getPluginAction()->getPluginName())); + finishLineage(); + } + } + else { + emit reportError(QString("Failed to initiate '%1' step - plugin type '%2' is unknown").arg( + lineageStep->getPluginAction()->getPluginName()).arg(lineageStep->getPluginAction()->getPluginType())); + finishLineage(); + return; + } +} diff --git a/src/hobbits-core/pluginactionmanager.h b/src/hobbits-core/pluginactionmanager.h new file mode 100644 index 00000000..78eb192b --- /dev/null +++ b/src/hobbits-core/pluginactionmanager.h @@ -0,0 +1,83 @@ +#ifndef PLUGINACTIONMANAGER_H +#define PLUGINACTIONMANAGER_H + +#include +#include + +#include "actionwatcher.h" +#include "analyzeractor.h" +#include "operatoractor.h" +#include "pluginmanager.h" +#include + +class PluginActionManager : public QObject +{ + Q_OBJECT + +public: + PluginActionManager(QSharedPointer pluginManager); + + struct LineageAction { + QUuid containerId; + QSharedPointer lineage; + QSharedPointer bitContainerManager; + QString baseName; + QMap outputIdOverride; + }; + + bool isBusy() const; + bool hasActiveLineage() const; + + bool registerOperatorWatcher(QSharedPointer>> watcher); + bool registerAnalyzerWatcher(QSharedPointer>> watcher); + + void applyLineage( + QUuid containerId, + QSharedPointer lineage, + QSharedPointer bitContainerManager, + QString baseName, + QMap outputIdOverride = QMap()); + + QSharedPointer operatorActor(); + QSharedPointer analyzerActor(); + +private slots: + void operatorWatcherFinished(); + void analyzerWatcherFinished(); + +public slots: + void cancelAction(); + +signals: + void actionWatcherStarted(); + void actionWatcherProgress(int); + void actionWatcherFinished(); + void lineageQueueCompleted(); + + void reportError(QString); + +private: + void continueLineage(); + void finishLineage(); + + QSharedPointer m_pluginManager; + + QSharedPointer m_operatorActor; + QSharedPointer m_analyzerActor; + + bool m_activeLineage; + QList> m_currLineage; + QList> m_currLineageContainers; + QSharedPointer m_lineageBitContainerManager; + QString m_lineageBaseName; + int m_lineageStep; + QMap m_currOutputIdOverride; + + QQueue> m_lineageQueue; + + QSharedPointer>> m_currOperatorWatcher; + QSharedPointer>> m_currAnalyzerWatcher; + +}; + +#endif // PLUGINACTIONMANAGER_H diff --git a/src/hobbits-core/plugincallback.cpp b/src/hobbits-core/plugincallback.cpp new file mode 100644 index 00000000..3979e6fe --- /dev/null +++ b/src/hobbits-core/plugincallback.cpp @@ -0,0 +1,12 @@ +#include "plugincallback.h" + +PluginCallback::PluginCallback(QObject *parent) : + QObject(parent) +{ + +} + +void PluginCallback::requestRun(QString pluginName, QJsonObject pluginState) +{ + emit runRequested(pluginName, pluginState); +} diff --git a/src/hobbits-core/plugincallback.h b/src/hobbits-core/plugincallback.h new file mode 100644 index 00000000..b918a524 --- /dev/null +++ b/src/hobbits-core/plugincallback.h @@ -0,0 +1,22 @@ +#ifndef PLUGINCALLBACK_H +#define PLUGINCALLBACK_H + +#include +#include + +class PluginCallback : public QObject +{ + Q_OBJECT + +public: + explicit PluginCallback(QObject *parent = nullptr); + +signals: + void runRequested(QString pluginName, QJsonObject pluginState); + +public slots: + void requestRun(QString pluginName, QJsonObject pluginState = QJsonObject()); + +}; + +#endif // PLUGINCALLBACK_H diff --git a/src/hobbits-core/pluginmanager.cpp b/src/hobbits-core/pluginmanager.cpp new file mode 100644 index 00000000..0558985d --- /dev/null +++ b/src/hobbits-core/pluginmanager.cpp @@ -0,0 +1,227 @@ +#include "pluginmanager.h" +#include "settingsmanager.h" +#include +#include + +PluginManager::PluginManager() +{ + +} + +QMap PluginManager::loadPluginsFromDirectory(QDir directory, QStringList &warnings) +{ + QMap plugins; + + QList pluginDirs; + pluginDirs.append(directory); + // Check subdirs, 1 deep + for (QString subDir : directory.entryList(QDir::Dirs | QDir::NoDotAndDotDot)) { + pluginDirs.append(directory.absoluteFilePath(subDir)); + } + + for (QDir pluginDir : pluginDirs) { + for (QString dfileName : pluginDir.entryList(QDir::Files)) { + if (!(dfileName.endsWith(".so") + || dfileName.endsWith(".dll") + || dfileName.endsWith(".dylib"))) { + continue; + } + QString fileName = pluginDir.absoluteFilePath(dfileName); + QPluginLoader loader(fileName); + QObject *plugin = loader.instance(); + if (plugin) { + plugins.insert(fileName, plugin); + } + else { + warnings.append(QString("File '%1' could not be loaded as a plugin - skipping...").arg(fileName)); + } + } + } + + return plugins; +} + +QStringList PluginManager::loadPlugins(const QString &pluginPath) +{ + QStringList warnings; + + QMap plugins; + QSet blacklist; + QVariant oldBlacklist = SettingsManager::getInstance().getPluginLoaderSetting(SettingsData::PLUGIN_BLACKLIST_KEY); + if (oldBlacklist.isValid() && oldBlacklist.canConvert()) { + blacklist = oldBlacklist.toStringList().toSet(); + } + + // Load analyzer plugins + QDir analyzersDir(pluginPath + "/analyzers"); + plugins = loadPluginsFromDirectory(analyzersDir, warnings); + for (QString pluginFile : plugins.keys()) { + if (blacklist.contains(pluginFile)) { + continue; + } + QObject *plugin = plugins.value(pluginFile); + AnalyzerInterface *analyzer = qobject_cast(plugin); + if (analyzer) { + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant(pluginFile)); + QSharedPointer analyzerInstance = QSharedPointer( + analyzer->createDefaultAnalyzer()); + if (m_analyzers.contains(analyzerInstance->getName())) { + warnings.append( + QString("Duplicate Analyzer plugin found with name '%1' - skipping...").arg( + analyzerInstance->getName())); + } + else { + m_analyzers.insert(analyzerInstance->getName(), analyzerInstance); + m_loadedPluginLocations.insert(analyzerInstance->getName(), pluginFile); + } + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant()); + } + else { + warnings.append( + QString("Plugin loaded from '%1' was not a valid Analyzer implementation - skipping...").arg( + pluginFile)); + } + } + + // Load Operator plugins + QDir operatorsDir(pluginPath + "/operators"); + plugins = loadPluginsFromDirectory(operatorsDir, warnings); + for (QString pluginFile : plugins.keys()) { + if (blacklist.contains(pluginFile)) { + continue; + } + QObject *plugin = plugins.value(pluginFile); + OperatorInterface *op = qobject_cast(plugin); + if (op) { + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant(pluginFile)); + QSharedPointer opInstance = + QSharedPointer(op->createDefaultOperator()); + if (m_operators.contains(opInstance->getName())) { + warnings.append( + QString("Duplicate Operator plugin found with name '%1' - skipping...").arg( + opInstance-> + getName())); + } + else { + m_operators.insert(opInstance->getName(), opInstance); + m_loadedPluginLocations.insert(opInstance->getName(), pluginFile); + } + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant()); + } + else { + warnings.append( + QString("Plugin loaded from '%1' was not a valid Operator implementation - skipping...").arg( + pluginFile)); + } + } + + // Load Display plugins + QDir displaysDir(pluginPath + "/displays"); + plugins = loadPluginsFromDirectory(displaysDir, warnings); + for (QString pluginFile : plugins.keys()) { + if (blacklist.contains(pluginFile)) { + continue; + } + QObject *plugin = plugins.value(pluginFile); + DisplayInterface *display = qobject_cast(plugin); + if (display) { + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant(pluginFile)); + QSharedPointer displayInstance = QSharedPointer( + display->createDefaultDisplay()); + if (m_displays.contains(displayInstance->getName())) { + warnings.append( + QString("Duplicate Display plugin found with name '%1' - skipping...").arg( + displayInstance->getName())); + } + else { + m_displays.insert(displayInstance->getName(), displayInstance); + m_loadedPluginLocations.insert(displayInstance->getName(), pluginFile); + } + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant()); + } + else { + warnings.append( + QString("Plugin loaded from '%1' was not a valid Display implementation - skipping...").arg( + pluginFile)); + } + } + + // Load Import/Export plugins + QDir importExportDir(pluginPath + "/importerexporters"); + plugins = loadPluginsFromDirectory(importExportDir, warnings); + for (QString pluginFile : plugins.keys()) { + if (blacklist.contains(pluginFile)) { + continue; + } + QObject *plugin = plugins.value(pluginFile); + ImportExportInterface *importerExporter = qobject_cast(plugin); + if (importerExporter) { + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant(pluginFile)); + QSharedPointer importerExporterInstance = QSharedPointer( + importerExporter->createDefaultImporterExporter()); + if (m_displays.contains(importerExporterInstance->getName())) { + warnings.append( + QString("Duplicate Import/Export plugin found with name '%1' - skipping...").arg( + importerExporterInstance->getName())); + } + else { + m_importerExporters.insert(importerExporterInstance->getName(), importerExporterInstance); + m_loadedPluginLocations.insert(importerExporterInstance->getName(), pluginFile); + } + SettingsManager::getInstance().setPrivateSetting(SettingsData::PLUGIN_RUNNING_KEY, QVariant()); + } + else { + warnings.append( + QString( + "Plugin loaded from '%1' was not a valid Import/Export implementation - skipping...").arg( + pluginFile)); + } + } + + return warnings; +} + +QList> PluginManager::getAllOperators() const +{ + return m_operators.values(); +} + +QList> PluginManager::getAllAnalyzers() const +{ + return m_analyzers.values(); +} + +QList> PluginManager::getAllDisplays() const +{ + return m_displays.values(); +} + +QList> PluginManager::getAllImporterExporters() const +{ + return m_importerExporters.values(); +} + +QSharedPointer PluginManager::getOperator(const QString &name) const +{ + return m_operators.value(name, QSharedPointer()); +} + +QSharedPointer PluginManager::getAnalyzer(const QString &name) const +{ + return m_analyzers.value(name, QSharedPointer()); +} + +QSharedPointer PluginManager::getDisplay(const QString &name) const +{ + return m_displays.value(name, QSharedPointer()); +} + +QSharedPointer PluginManager::getImporterExporter(const QString &name) const +{ + return m_importerExporters.value(name, QSharedPointer()); +} + +QString PluginManager::getPluginLocation(const QString &name) const +{ + return m_loadedPluginLocations.value(name); +} diff --git a/src/hobbits-core/pluginmanager.h b/src/hobbits-core/pluginmanager.h new file mode 100644 index 00000000..1ede0c23 --- /dev/null +++ b/src/hobbits-core/pluginmanager.h @@ -0,0 +1,45 @@ +#ifndef PLUGINMANAGER_H +#define PLUGINMANAGER_H + +#include +#include +#include + +#include "analyzerinterface.h" +#include "displayinterface.h" +#include "importexportinterface.h" +#include "operatorinterface.h" + +class PluginManager +{ +public: + PluginManager(); + + // Returns a list of warnings + QStringList loadPlugins(const QString &pluginPath); + + QList> getAllOperators() const; + QList> getAllAnalyzers() const; + QList> getAllDisplays() const; + QList> getAllImporterExporters() const; + + QSharedPointer getOperator(const QString &name) const; + QSharedPointer getAnalyzer(const QString &name) const; + QSharedPointer getDisplay(const QString &name) const; + QSharedPointer getImporterExporter(const QString &name) const; + + QString getPluginLocation(const QString &name) const; + +private: + QMap> m_operators; + QMap> m_analyzers; + QMap> m_displays; + QMap> m_importerExporters; + + QMap m_loadedPluginLocations; + + QMap loadPluginsFromDirectory(QDir directory, QStringList &warnings); + +}; + +#endif // PLUGINMANAGER_H diff --git a/src/hobbits-core/range.cpp b/src/hobbits-core/range.cpp new file mode 100644 index 00000000..37a2f065 --- /dev/null +++ b/src/hobbits-core/range.cpp @@ -0,0 +1,104 @@ +#include "range.h" + +#include + +Range::Range() : + m_start(0), + m_end(0) +{ + +} + +Range::Range(int start, int end) : + m_start(start), + m_end(end) +{ + +} + +bool Range::operator<(const Range &other) +{ + return m_start < other.start(); +} + +int Range::size() const +{ + return m_end - m_start + 1; +} + +int Range::start() const +{ + return m_start; +} + +int Range::end() const +{ + return m_end; +} + +unsigned int Range::compare(const Range &other) const +{ + unsigned int result = Range::Unknown; + + if (other.start() == this->start() && other.end() == this->end()) { + result |= Range::Equal; + } + + if ((other.start() >= this->start() && other.start() <= this->end()) + || (other.end() >= this->start() && other.end() <= this->end()) + || (this->start() >= other.start() && this->start() <= other.end()) + || (this->end() >= other.start() && this->end() <= other.end())) { + result |= Range::Overlapping; + } + else { + result |= Range::Separate; + } + + if (other.end() > this->end()) { + result |= Range::After; + } + + if (other.start() < this->start()) { + result |= Range::Before; + } + + return result; +} + +Range Range::getOverlap(const Range &other) const +{ + unsigned int comparison = this->compare(other); + Q_ASSERT_X(comparison & Range::Overlapping, "Frame", "Cannot get overlap for non-overlapping frames"); + int start = other.start(); + int end = other.end(); + if (comparison & Range::Before) { + start = this->start(); + } + if (comparison & Range::After) { + end = this->end(); + } + + return Range(start, end); +} + +void Range::translate(int offset) +{ + m_start += offset; + m_end += offset; +} + +QDataStream& operator<<(QDataStream &stream, const Range &range) +{ + stream << range.m_start; + stream << range.m_end; + + return stream; +} + +QDataStream& operator>>(QDataStream &stream, Range &range) +{ + stream >> range.m_start; + stream >> range.m_end; + + return stream; +} diff --git a/src/hobbits-core/range.h b/src/hobbits-core/range.h new file mode 100644 index 00000000..f34c1111 --- /dev/null +++ b/src/hobbits-core/range.h @@ -0,0 +1,45 @@ +#ifndef RANGE_H +#define RANGE_H + +#include + +class Range +{ +public: + Range(); + Range(int start, int end); + Range(const Range &other) = default; + + bool operator<(const Range &other); + + int size() const; + + int start() const; + int end() const; + + unsigned int compare(const Range&) const; + + Range getOverlap(const Range &other) const; + + void translate(int offset); + + enum Comparison { + Unknown = 0x00, + Separate = 0x01, + Overlapping = 0x02, + Before = 0x04, + After = 0x08, + Within = 0x10, + Around = 0x20, + Equal = 0x40 + }; + + friend QDataStream& operator<<(QDataStream&, const Range&); + friend QDataStream& operator>>(QDataStream&, Range&); + +private: + int m_start; + int m_end; +}; + +#endif // RANGE_H diff --git a/src/hobbits-core/settingsdata.cpp b/src/hobbits-core/settingsdata.cpp new file mode 100644 index 00000000..b57d7a10 --- /dev/null +++ b/src/hobbits-core/settingsdata.cpp @@ -0,0 +1,139 @@ +#include "settingsdata.h" + +#include +#include +#include +#include + +const QString SettingsData::ONE_COLOR_KEY = "1-Bit Color"; +const QString SettingsData::ZERO_COLOR_KEY = "0-Bit Color"; +const QString SettingsData::BYTE_HUE_SAT_KEY = "Byte Hue and Saturation"; +const QString SettingsData::HIGHLIGHT_1_COLOR_KEY = "Highlight 1"; +const QString SettingsData::HIGHLIGHT_2_COLOR_KEY = "Highlight 2"; +const QString SettingsData::HIGHLIGHT_3_COLOR_KEY = "Highlight 3"; +const QString SettingsData::HIGHLIGHT_4_COLOR_KEY = "Highlight 4"; +const QString SettingsData::HIGHLIGHT_5_COLOR_KEY = "Highlight 5"; +const QString SettingsData::PLUGIN_PATH_KEY = "Plugin Path"; +const QString SettingsData::PLUGIN_BLACKLIST_KEY = "Plugin Blacklist"; +const QString SettingsData::OPERATOR_DISPLAY_ORDER_KEY = "Operator Display Order"; +const QString SettingsData::ANALYZER_DISPLAY_ORDER_KEY = "Analyzer Display Order"; +const QString SettingsData::DISPLAY_DISPLAY_ORDER_KEY = "Display Display Order"; +const QString SettingsData::WINDOW_SIZE_KEY = "window_size"; +const QString SettingsData::WINDOW_POSITION_KEY = "window_position"; +const QString SettingsData::LAST_TEMPLATE_PATH_KEY = "last_template_path"; +const QString SettingsData::LAST_IMPORT_EXPORT_PATH_KEY = "last_import_export_path"; +const QString SettingsData::LAST_CONTAINER_PATH_KEY = "last_container_path"; +const QString SettingsData::PLUGIN_RUNNING_KEY = "plugin_running"; + +SettingsData::SettingsData() +{ + m_privateSettings.insert(WINDOW_SIZE_KEY, QSize(640, 480)); + m_privateSettings.insert(WINDOW_POSITION_KEY, QPoint(100, 100)); + m_privateSettings.insert(LAST_TEMPLATE_PATH_KEY, QDir::homePath()); + m_privateSettings.insert(LAST_IMPORT_EXPORT_PATH_KEY, QDir::homePath()); + m_privateSettings.insert(LAST_CONTAINER_PATH_KEY, QDir::homePath()); + + m_uiSettings.insert(ONE_COLOR_KEY, QColor(Qt::black)); + m_uiSettings.insert(ZERO_COLOR_KEY, QColor(255, 255, 200)); + m_uiSettings.insert(BYTE_HUE_SAT_KEY, QColor::fromHsl(120, 200, 128)); + m_uiSettings.insert(HIGHLIGHT_1_COLOR_KEY, QColor(100, 220, 100, 85)); + m_uiSettings.insert(HIGHLIGHT_2_COLOR_KEY, QColor(100, 0, 255, 50)); + m_uiSettings.insert(HIGHLIGHT_3_COLOR_KEY, QColor(0, 150, 230, 100)); + m_uiSettings.insert(HIGHLIGHT_4_COLOR_KEY, QColor(200, 140, 0, 100)); + m_uiSettings.insert(HIGHLIGHT_5_COLOR_KEY, QColor(250, 50, 0, 100)); + + m_pluginLoaderSettings.insert( + PLUGIN_PATH_KEY, + "../hobbits-plugins:../plugins:plugins:~/.local/share/hobbits/plugins"); + m_pluginLoaderSettings.insert(PLUGIN_BLACKLIST_KEY, QStringList({})); + m_pluginLoaderSettings.insert( + OPERATOR_DISPLAY_ORDER_KEY, + QStringList({"Take Skip", "Header Framer", "Bit Error", "LFSR"})); + m_pluginLoaderSettings.insert( + ANALYZER_DISPLAY_ORDER_KEY, + QStringList({ "Find", "Width Framer", "Flexible Framer" })); + m_pluginLoaderSettings.insert(DISPLAY_DISPLAY_ORDER_KEY, QStringList({"Bit Raster", "Hex", "Binary", "ASCII"})); +} + +SettingsData::SettingsData(const SettingsData &other) +{ + m_privateSettings = other.m_privateSettings; + m_uiSettings = other.m_uiSettings; + m_pluginSettings = other.m_pluginSettings; + m_pluginLoaderSettings = other.m_pluginLoaderSettings; +} + +SettingsData& SettingsData::operator=(const SettingsData &other) +{ + if (this != &other) { + m_privateSettings = other.m_privateSettings; + m_uiSettings = other.m_uiSettings; + m_pluginSettings = other.m_pluginSettings; + m_pluginLoaderSettings = other.m_pluginLoaderSettings; + } + return *this; +} + +QList SettingsData::getPrivateSettingKeys() const +{ + return m_privateSettings.keys(); +} + +QVariant SettingsData::getPrivateSetting(const QString &key, const QVariant &defaultValue) +{ + return m_privateSettings.value(key, defaultValue); +} + +void SettingsData::setPrivateSetting(const QString &key, const QVariant &value) +{ + m_privateSettings.remove(key); + m_privateSettings.insert(key, value); +} + +QList SettingsData::getUiSettingKeys() const +{ + return m_uiSettings.keys(); +} + +QVariant SettingsData::getUiSetting(const QString &key, const QVariant &defaultValue) +{ + return m_uiSettings.value(key, defaultValue); +} + +void SettingsData::setUiSetting(const QString &key, const QVariant &value) +{ + m_uiSettings.remove(key); + m_uiSettings.insert(key, value); +} + +QList SettingsData::getPluginLoaderSettingKeys() const +{ + return m_pluginLoaderSettings.keys(); +} + +QVariant SettingsData::getPluginLoaderSetting(const QString &key, const QVariant &defaultValue) +{ + return m_pluginLoaderSettings.value(key, defaultValue); +} + +void SettingsData::setPluginLoaderSetting(const QString &key, const QVariant &value) +{ + m_pluginLoaderSettings.remove(key); + m_pluginLoaderSettings.insert(key, value); +} + +QList SettingsData::getPluginSettingKeys() const +{ + return m_pluginSettings.keys(); +} + +QVariant SettingsData::getPluginSetting(const QString &key, const QVariant &defaultValue) +{ + return m_pluginSettings.value(key, defaultValue); +} + +void SettingsData::setPluginSetting(const QString &key, const QVariant &value) +{ + m_pluginSettings.remove(key); + m_pluginSettings.insert(key, value); +} diff --git a/src/hobbits-core/settingsdata.h b/src/hobbits-core/settingsdata.h new file mode 100644 index 00000000..794d5c48 --- /dev/null +++ b/src/hobbits-core/settingsdata.h @@ -0,0 +1,58 @@ +#ifndef SETTINGSDATA_H +#define SETTINGSDATA_H + +#include +#include + +class SettingsData +{ +public: + SettingsData(); + SettingsData(const SettingsData &other); + + SettingsData& operator=(const SettingsData&); + + QList getPrivateSettingKeys() const; + QVariant getPrivateSetting(const QString &key, const QVariant &defaultValue = QVariant()); + void setPrivateSetting(const QString &key, const QVariant &value); + + QList getUiSettingKeys() const; + QVariant getUiSetting(const QString &key, const QVariant &defaultValue = QVariant()); + void setUiSetting(const QString &key, const QVariant &value); + + QList getPluginLoaderSettingKeys() const; + QVariant getPluginLoaderSetting(const QString &key, const QVariant &defaultValue = QVariant()); + void setPluginLoaderSetting(const QString &key, const QVariant &value); + + QList getPluginSettingKeys() const; + QVariant getPluginSetting(const QString &key, const QVariant &defaultValue = QVariant()); + void setPluginSetting(const QString &key, const QVariant &value); + + static const QString ONE_COLOR_KEY; + static const QString ZERO_COLOR_KEY; + static const QString BYTE_HUE_SAT_KEY; + static const QString HIGHLIGHT_1_COLOR_KEY; + static const QString HIGHLIGHT_2_COLOR_KEY; + static const QString HIGHLIGHT_3_COLOR_KEY; + static const QString HIGHLIGHT_4_COLOR_KEY; + static const QString HIGHLIGHT_5_COLOR_KEY; + static const QString WINDOW_SIZE_KEY; + static const QString WINDOW_POSITION_KEY; + static const QString PLUGIN_PATH_KEY; + static const QString PLUGIN_BLACKLIST_KEY; + static const QString OPERATOR_DISPLAY_ORDER_KEY; + static const QString ANALYZER_DISPLAY_ORDER_KEY; + static const QString DISPLAY_DISPLAY_ORDER_KEY; + static const QString LAST_TEMPLATE_PATH_KEY; + static const QString LAST_IMPORT_EXPORT_PATH_KEY; + static const QString LAST_CONTAINER_PATH_KEY; + static const QString PLUGIN_RUNNING_KEY; + +private: + QMap m_privateSettings; + QMap m_uiSettings; + QMap m_pluginLoaderSettings; + QMap m_pluginSettings; +}; + +#endif // SETTINGSDATA_H diff --git a/src/hobbits-core/settingsmanager.cpp b/src/hobbits-core/settingsmanager.cpp new file mode 100644 index 00000000..6786c606 --- /dev/null +++ b/src/hobbits-core/settingsmanager.cpp @@ -0,0 +1,163 @@ +#include "settingsmanager.h" +#include + +SettingsManager::SettingsManager() : + m_hasRead(false) +{ + +} + +SettingsManager& SettingsManager::getInstance() +{ + static SettingsManager instance; + return instance; +} + +void SettingsManager::setConfigFilePath(const QString &configFilePath) +{ + m_configFilePath = configFilePath; +} + +void SettingsManager::writeSettings() +{ + if (m_configFilePath.isEmpty()) { + QSettings settings("Hobbits", "Hobbits GUI"); + writeToSettings(settings); + } + else { + QSettings settings(m_configFilePath, QSettings::IniFormat); + writeToSettings(settings); + } +} + +void SettingsManager::readSettings() +{ + if (m_hasRead) { + return; + } + + if (m_configFilePath.isEmpty()) { + QSettings settings("Hobbits", "Hobbits GUI"); + readFromSettings(settings); + } + else { + QSettings settings(m_configFilePath, QSettings::IniFormat); + readFromSettings(settings); + } +} + +void SettingsManager::writeToSettings(QSettings &settings) +{ + settings.beginGroup("UI"); + for (QString key : m_data.getUiSettingKeys()) { + settings.setValue(key, m_data.getUiSetting(key)); + } + settings.endGroup(); + + settings.beginGroup("Plugin Loader"); + for (QString key : m_data.getPluginLoaderSettingKeys()) { + settings.setValue(key, m_data.getPluginLoaderSetting(key)); + } + settings.endGroup(); + + settings.beginGroup("Plugin Settings"); + for (QString key : m_data.getPluginSettingKeys()) { + settings.setValue(key, m_data.getPluginSetting(key)); + } + settings.endGroup(); + + settings.beginGroup("Private"); + for (QString key : m_data.getPrivateSettingKeys()) { + settings.setValue(key, m_data.getPrivateSetting(key)); + } + settings.endGroup(); +} + +void SettingsManager::readFromSettings(QSettings &settings) +{ + settings.beginGroup("UI"); + for (QString key : settings.allKeys()) { + m_data.setUiSetting(key, settings.value(key)); + } + settings.endGroup(); + + settings.beginGroup("Plugin Loader"); + for (QString key : settings.allKeys()) { + m_data.setPluginLoaderSetting(key, settings.value(key)); + } + settings.endGroup(); + + settings.beginGroup("Plugin Settings"); + for (QString key : settings.allKeys()) { + m_data.setPluginSetting(key, settings.value(key)); + } + settings.endGroup(); + + settings.beginGroup("Private"); + for (QString key : settings.allKeys()) { + m_data.setPrivateSetting(key, settings.value(key)); + } + settings.endGroup(); +} + +SettingsData SettingsManager::getAll() const +{ + return m_data; +} + +void SettingsManager::setAll(const SettingsData &data) +{ + m_data = data; + writeSettings(); +} + +QVariant SettingsManager::getPrivateSetting(const QString &key) +{ + readSettings(); + return m_data.getPrivateSetting(key); + +} + +void SettingsManager::setPrivateSetting(const QString &key, const QVariant &value) +{ + m_data.setPrivateSetting(key, value); + writeSettings(); +} + +QVariant SettingsManager::getUiSetting(const QString &key) +{ + readSettings(); + return m_data.getUiSetting(key); + +} + +void SettingsManager::setUiSetting(const QString &key, const QVariant &value) +{ + m_data.setUiSetting(key, value); + writeSettings(); +} + +QVariant SettingsManager::getPluginLoaderSetting(const QString &key) +{ + readSettings(); + return m_data.getPluginLoaderSetting(key); + +} + +void SettingsManager::setPluginLoaderSetting(const QString &key, const QVariant &value) +{ + m_data.setPluginLoaderSetting(key, value); + writeSettings(); +} + +QVariant SettingsManager::getPluginSetting(const QString &key) +{ + readSettings(); + return m_data.getPluginSetting(key); +} + +void SettingsManager::setPluginSetting(const QString &key, const QVariant &value) +{ + m_data.setPluginSetting(key, value); + writeSettings(); +} diff --git a/src/hobbits-core/settingsmanager.h b/src/hobbits-core/settingsmanager.h new file mode 100644 index 00000000..4df60391 --- /dev/null +++ b/src/hobbits-core/settingsmanager.h @@ -0,0 +1,51 @@ +#ifndef SETTINGSMANAGER_H +#define SETTINGSMANAGER_H + +#include "settingsdata.h" +#include + +class SettingsManager +{ +public: + static SettingsManager& getInstance(); + + void setConfigFilePath(const QString &configFilePath); + + void writeSettings(); + void readSettings(); + + SettingsData getAll() const; + void setAll(const SettingsData &data); + + QVariant getPrivateSetting(const QString &key); + void setPrivateSetting(const QString &key, const QVariant &value); + + QVariant getUiSetting(const QString &key); + void setUiSetting(const QString &key, const QVariant &value); + + QVariant getPluginLoaderSetting(const QString &key); + void setPluginLoaderSetting(const QString &key, const QVariant &value); + + QVariant getPluginSetting(const QString &key); + void setPluginSetting(const QString &key, const QVariant &value); + +private: + SettingsManager(); + + void writeToSettings(QSettings &settings); + void readFromSettings(QSettings &settings); + + bool m_hasRead; + + QString m_configFilePath; + + SettingsData m_data; + +public: + SettingsManager(SettingsManager const&) = delete; + + void operator=(SettingsManager const&) = delete; + +}; + +#endif // SETTINGSMANAGER_H diff --git a/src/hobbits-core/templatefilehandler.cpp b/src/hobbits-core/templatefilehandler.cpp new file mode 100644 index 00000000..41af1652 --- /dev/null +++ b/src/hobbits-core/templatefilehandler.cpp @@ -0,0 +1,136 @@ +#include "pluginactionlineage.h" +#include "settingsmanager.h" +#include "templatefilehandler.h" +#include + +TemplateFileHandler::TemplateFileHandler() +{ + +} + +bool TemplateFileHandler::writeTemplate( + QString fileName, + QList> lineages, + QStringList *warnings) +{ + + if (lineages.isEmpty()) { + if (warnings) { + warnings->append(QString("Cannot export an empty template").arg(fileName)); + } + return false; + } + + QFile file(fileName); + if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + if (warnings) { + warnings->append(QString("Failed to open file '%1' for writing").arg(fileName)); + } + return false; + } + + SettingsManager::getInstance().setPrivateSetting( + SettingsData::LAST_TEMPLATE_PATH_KEY, + QFileInfo(file).dir().path()); + + QJsonDocument serialized; + QJsonObject root; + QJsonArray lineageArray; + for (auto lineage : lineages) { + lineageArray.append(lineage->serialize()); + } + root.insert("lineages", lineageArray); + serialized = QJsonDocument(root); + + file.write(serialized.toJson(QJsonDocument::Indented)); + file.close(); + + return true; +} + +void TemplateFileHandler::applyMultipleLineages( + QUuid targetId, + QSharedPointer lineageTree, + QString name, + QSharedPointer bitManager, + QSharedPointer pluginActionManager) +{ + if (!lineageTree->lineage.isNull()) { + // TODO: handle multiple outputs from a single op + QUuid outputId = QUuid::createUuid(); + QMap outputIdOverride; + outputIdOverride.insert(0, outputId); + pluginActionManager->applyLineage(targetId, lineageTree->lineage, bitManager, name, outputIdOverride); + + targetId = outputId; + } + for (auto child : lineageTree->children) { + applyMultipleLineages(targetId, child, name, bitManager, pluginActionManager); + } +} + +bool TemplateFileHandler::applyTemplateToContainer( + QString fileName, + QSharedPointer bitContainer, + QSharedPointer bitManager, + QSharedPointer pluginActionManager, + QStringList *warnings) +{ + if (bitContainer.isNull()) { + if (warnings) { + warnings->append(QString("Cannot apply a template to a null bit container").arg(fileName)); + } + return false; + } + + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + if (warnings) { + warnings->append(QString("Template file '%1' cannot be opened").arg(fileName)); + } + return false; + } + QJsonDocument serialized = QJsonDocument::fromJson(file.read(5 * 1000 * 1000)); + QFileInfo fileInfo(file.fileName()); + QString templateName = fileInfo.fileName().section(".", 0, 0); + + SettingsManager::getInstance().setPrivateSetting(SettingsData::LAST_TEMPLATE_PATH_KEY, fileInfo.dir().path()); + + QJsonObject lineageObject = serialized.object(); + if (lineageObject.contains("lineages") && lineageObject.value("lineages").isArray()) { + QList> lineages; + for (auto lineage : lineageObject.value("lineages").toArray()) { + if (!lineage.isObject()) { + if (warnings) { + warnings->append(QString("Template file '%1' is not formatted correctly").arg(fileName)); + } + return false; + } + auto actionLineage = PluginActionLineage::deserialize(lineage.toObject()); + if (actionLineage.isNull()) { + if (warnings) { + warnings->append(QString("Template file '%1' is not formatted correctly").arg(fileName)); + } + return false; + } + lineages.append(actionLineage); + } + applyMultipleLineages( + bitContainer->getId(), + PluginActionLineage::mergeIntoTree(lineages), + templateName, + bitManager, + pluginActionManager); + } + else { + auto actionLineage = PluginActionLineage::deserialize(lineageObject); + if (actionLineage.isNull()) { + if (warnings) { + warnings->append(QString("Template file '%1' is not formatted correctly").arg(fileName)); + } + return false; + } + pluginActionManager->applyLineage(bitContainer->getId(), actionLineage, bitManager, templateName); + } + return true; +} diff --git a/src/hobbits-core/templatefilehandler.h b/src/hobbits-core/templatefilehandler.h new file mode 100644 index 00000000..d135c6ed --- /dev/null +++ b/src/hobbits-core/templatefilehandler.h @@ -0,0 +1,38 @@ +#ifndef TEMPLATEFILEHANDLER_H +#define TEMPLATEFILEHANDLER_H + +#include "bitcontainer.h" +#include "bitcontainermanager.h" +#include "pluginactionlineage.h" +#include "pluginactionmanager.h" +#include +#include + +class TemplateFileHandler +{ +private: + TemplateFileHandler(); + +public: + static bool writeTemplate( + QString fileName, + QList> lineages, + QStringList *warnings = nullptr); + + static void applyMultipleLineages( + QUuid targetId, + QSharedPointer lineageTree, + QString name, + QSharedPointer bitManager, + QSharedPointer pluginActionManager); + + static bool applyTemplateToContainer( + QString fileName, + QSharedPointer bitContainer, + QSharedPointer bitManager, + QSharedPointer pluginActionManager, + QStringList *warnings = nullptr); + +}; + +#endif // TEMPLATEFILEHANDLER_H diff --git a/src/hobbits-gui/hobbits-gui.pro b/src/hobbits-gui/hobbits-gui.pro new file mode 100644 index 00000000..a82f4200 --- /dev/null +++ b/src/hobbits-gui/hobbits-gui.pro @@ -0,0 +1,54 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-06-04T10:55:32 +# +#------------------------------------------------- + +QT += core gui + +greaterThan(QT_MAJOR_VERSION, 4): QT += widgets + +TARGET = hobbits +TEMPLATE = app + +DEFINES += "HOBBITS_GUI_VERSION=\"\\\"Totally Rad Developer Version\\\"\"" + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +CONFIG += c++11 + +SOURCES += \ + hobbitsguiinfo.cpp \ + main.cpp \ + mainwindow.cpp \ + preferencesdialog.cpp + +HEADERS += \ + hobbitsguiinfo.h \ + mainwindow.h \ + preferencesdialog.h + +FORMS += \ + mainwindow.ui \ + preferencesdialog.ui + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../hobbits-core/ -lhobbits-core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../hobbits-core/ -lhobbits-core +else:unix: LIBS += -L$$OUT_PWD/../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../hobbits-core +DEPENDPATH += $$PWD/../hobbits-core + +RESOURCES += \ + icons.qrc + +INSTALLS = diff --git a/src/hobbits-gui/hobbitsguiinfo.cpp b/src/hobbits-gui/hobbitsguiinfo.cpp new file mode 100644 index 00000000..7eb46835 --- /dev/null +++ b/src/hobbits-gui/hobbitsguiinfo.cpp @@ -0,0 +1,11 @@ +#include "hobbitsguiinfo.h" + +QString HobbitsGuiInfo::getGuiVersion() +{ +#ifdef HOBBITS_GUI_VERSION + QString guiVersion = HOBBITS_GUI_VERSION; +#else + QString guiVersion = "Elusive Mystery Version"; +#endif + return guiVersion; +} diff --git a/src/hobbits-gui/hobbitsguiinfo.h b/src/hobbits-gui/hobbitsguiinfo.h new file mode 100644 index 00000000..8c861646 --- /dev/null +++ b/src/hobbits-gui/hobbitsguiinfo.h @@ -0,0 +1,14 @@ +#ifndef HOBBITSGUIINFO_H +#define HOBBITSGUIINFO_H + + +#include + +namespace HobbitsGuiInfo +{ + +QString getGuiVersion(); + +} + +#endif // HOBBITSGUIINFO_H diff --git a/src/hobbits-gui/icons.qrc b/src/hobbits-gui/icons.qrc new file mode 100644 index 00000000..a6115238 --- /dev/null +++ b/src/hobbits-gui/icons.qrc @@ -0,0 +1,6 @@ + + + images/icons/trash.png + images/icons/HobbitsRingSmall.png + + diff --git a/src/hobbits-gui/images/icons/HobbitsRingSmall.png b/src/hobbits-gui/images/icons/HobbitsRingSmall.png new file mode 100644 index 00000000..5fb11ced Binary files /dev/null and b/src/hobbits-gui/images/icons/HobbitsRingSmall.png differ diff --git a/src/hobbits-gui/images/icons/trash.png b/src/hobbits-gui/images/icons/trash.png new file mode 100644 index 00000000..cf390544 Binary files /dev/null and b/src/hobbits-gui/images/icons/trash.png differ diff --git a/src/hobbits-gui/main.cpp b/src/hobbits-gui/main.cpp new file mode 100644 index 00000000..df6e83b5 --- /dev/null +++ b/src/hobbits-gui/main.cpp @@ -0,0 +1,82 @@ +#include "hobbitsguiinfo.h" +#include "mainwindow.h" +#include +#include +#include +#ifdef Q_OS_UNIX +#include +#include +#endif + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + QCoreApplication::setApplicationName("hobbits"); + QCoreApplication::setApplicationVersion(HobbitsGuiInfo::getGuiVersion()); + + QCommandLineParser parser; + parser.setApplicationDescription("GUI for bitstream analysis and visualization"); + parser.addHelpOption(); + parser.addVersionOption(); + + QCommandLineOption inputFileOption( + QStringList() << "i" << "input", + QCoreApplication::translate("main", "File to open on application startup (use '-' for piped input)"), + QCoreApplication::translate("main", "file")); + parser.addOption(inputFileOption); + + + QCommandLineOption extraPluginPathOption( + QStringList() << "p" << "extra-plugin-path", + QCoreApplication::translate("main", "An additional path to search for plugins in"), + QCoreApplication::translate("main", "path")); + parser.addOption(extraPluginPathOption); + + + QCommandLineOption configFilePathOption( + QStringList() << "c" << "config", + QCoreApplication::translate("main", "Override the default config file location"), + QCoreApplication::translate("main", "file")); + parser.addOption(configFilePathOption); + + parser.process(a); + + QByteArray pipedInData; +#ifdef Q_OS_UNIX + if (parser.isSet(inputFileOption) && parser.value(inputFileOption) == "-") { + while (!std::cin.eof()) { + char arr[1024]; + std::cin.read(arr, sizeof(arr)); + int s = std::cin.gcount(); + pipedInData.append(arr, s); + } + } +#endif + + QString extraPluginPath; + QString configFilePath; + + if (parser.isSet(extraPluginPathOption)) { + extraPluginPath = parser.value(extraPluginPathOption); + } + + if (parser.isSet(configFilePathOption)) { + configFilePath = parser.value(configFilePathOption); + } + + MainWindow w(extraPluginPath, configFilePath); + w.setWindowIcon(QIcon(":/hobbitsgui/images/icons/HobbitsRingSmall.png")); + w.show(); + + if (!pipedInData.isEmpty()) { + w.importBytes(pipedInData, "Piped Input"); + } + else { + if (parser.isSet(inputFileOption)) { + w.importBitfile(parser.value(inputFileOption)); + } + } + + return a.exec(); +} diff --git a/src/hobbits-gui/mainwindow.cpp b/src/hobbits-gui/mainwindow.cpp new file mode 100644 index 00000000..1faa0cb6 --- /dev/null +++ b/src/hobbits-gui/mainwindow.cpp @@ -0,0 +1,1013 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "hobbitscoreinfo.h" +#include "hobbitsguiinfo.h" +#include "pluginactionlineage.h" + +#include "preferencesdialog.h" +#include "settingsmanager.h" +#include "templatefilehandler.h" + +MainWindow::MainWindow(QString extraPluginPath, QString configFilePath, QWidget *parent) : + QMainWindow(parent), + ui(new Ui::MainWindow), + m_extraPluginPath(extraPluginPath), + m_bitContainerManager(QSharedPointer(new BitContainerManager())), + m_pluginManager(QSharedPointer(new PluginManager())), + m_pluginActionManager(new PluginActionManager(m_pluginManager)), + m_pluginActionProgress(new QProgressBar()), + m_pluginActionCancel(new QPushButton()), + m_displayTabsSplitter(new QSplitter(Qt::Horizontal)), + m_splitViewMenu(new QMenu("Split View")) +{ + ui->setupUi(this); + + if (!configFilePath.isEmpty()) { + SettingsManager::getInstance().setConfigFilePath(configFilePath); + } + resize(SettingsManager::getInstance().getPrivateSetting(SettingsData::WINDOW_SIZE_KEY).toSize()); + move( + SettingsManager::getInstance().getPrivateSetting( + SettingsData::WINDOW_POSITION_KEY).toPoint()); + + // Populate View Menu + ui->menu_View->addAction(ui->dock_bitContainerSelect->toggleViewAction()); + ui->menu_View->addAction(ui->dock_operatorPlugins->toggleViewAction()); + ui->menu_View->addSeparator(); + ui->menu_View->addAction(ui->dock_findBits->toggleViewAction()); + ui->menu_View->addSeparator(); + ui->menu_View->addMenu(m_splitViewMenu); + + ui->dock_bitContainerSelect->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_B)); + ui->dock_operatorPlugins->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_O)); + ui->dock_findBits->toggleViewAction()->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_A)); + + // Configure Bit Container View + ui->tv_bitContainers->setModel(m_bitContainerManager->getTreeModel().data()); + + ui->tv_bitContainers->setSelectionModel(m_bitContainerManager->getCurrSelectionModel().data()); + + connect( + m_bitContainerManager.data(), + &BitContainerManager::currSelectionChanged, + this, + &MainWindow::activateBitContainer); + + connect( + ui->tb_removeBitContainer, + &QPushButton::pressed, + this, + &MainWindow::deleteCurrentBitcontainer); + + // Configure Plugin Action Management + connect( + ui->operatorTabs, + &QTabWidget::currentChanged, + this, + &MainWindow::checkOperatorInput); + + connect( + m_pluginActionManager.data(), + &PluginActionManager::actionWatcherStarted, + this, + &MainWindow::pluginActionStarted); + connect( + m_pluginActionManager.data(), + &PluginActionManager::actionWatcherProgress, + this, + &MainWindow::pluginActionProgress); + connect( + m_pluginActionManager.data(), + &PluginActionManager::actionWatcherFinished, + this, + &MainWindow::pluginActionFinished); + + connect( + m_pluginActionManager.data(), + SIGNAL(reportError(QString)), + this, + SLOT(warningMessage(QString))); + + m_pluginActionProgress->setVisible(false); + ui->statusBar->addPermanentWidget(m_pluginActionProgress); + + m_pluginActionCancel->setVisible(false); + m_pluginActionCancel->setText("Cancel"); + ui->statusBar->addPermanentWidget(m_pluginActionCancel); + connect( + m_pluginActionCancel, + &QPushButton::pressed, + m_pluginActionManager.data(), + &PluginActionManager::cancelAction); + + m_analyzerPluginCallback = QSharedPointer(new PluginCallback()); + connect( + m_analyzerPluginCallback.data(), + &PluginCallback::runRequested, + this, + &MainWindow::requestAnalyzerRun); + + m_operatorPluginCallback = QSharedPointer(new PluginCallback()); + connect( + m_operatorPluginCallback.data(), + &PluginCallback::runRequested, + this, + &MainWindow::requestOperatorRun); + + // + populateRecentTemplatesMenu(); + + loadPlugins(); + + initializeImporterExporters(); + + m_displayHandle = QSharedPointer( + new DisplayHandle( + m_bitContainerManager, + ui->displayVScroll, + ui->displayHScroll)); + connect( + m_displayHandle.data(), + &DisplayHandle::newBitHover, + this, + &MainWindow::setHoverBit); + initializeDisplays(); + + checkOperatorInput(); + + activateBitContainer(); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +const QString SPLIT_DISPLAY_SIZE_KEY = "split_display_size_list"; +void MainWindow::closeEvent(QCloseEvent *event) +{ + SettingsManager::getInstance().setPrivateSetting(SettingsData::WINDOW_SIZE_KEY, size()); + SettingsManager::getInstance().setPrivateSetting(SettingsData::WINDOW_POSITION_KEY, pos()); + + QStringList sizesAsStrings; + for (int splitSize : m_displayTabsSplitter->sizes()) { + sizesAsStrings.append(QString("%1").arg(splitSize)); + } + SettingsManager::getInstance().setPrivateSetting(SPLIT_DISPLAY_SIZE_KEY, sizesAsStrings); + + event->accept(); +} + +void MainWindow::initializeDisplays() +{ + ui->displayTabsLayout->addWidget(m_displayTabsSplitter); + m_displayTabsSplitter->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding)); + + // initialize 1 no matter what + addDisplayGroup(); + + // Add others and sizes if they were saved in the config + QVariant savedSizes = SettingsManager::getInstance().getPrivateSetting(SPLIT_DISPLAY_SIZE_KEY); + if (!savedSizes.isNull() && savedSizes.canConvert()) { + QList splitSizes; + bool ok = true; + for (QString sizeString : savedSizes.toStringList()) { + int splitSize = sizeString.toInt(&ok); + if (!ok) { + break; + } + splitSizes.append(splitSize); + } + if (ok && splitSizes.size() > 1) { + for (int i = 1; i < splitSizes.size(); i++) { + addDisplayGroup(); + } + m_displayTabsSplitter->setSizes(splitSizes); + } + } +} + +void MainWindow::addDisplayGroup() +{ + QTabWidget *tabs = new QTabWidget(this); + tabs->setElideMode(Qt::ElideLeft); + tabs->setDocumentMode(true); + + m_displayTabsSplitter->addWidget(tabs); + + // Instantiate displays for this display set + QList> displays; + QSet queued; + for (QString pluginString : SettingsManager::getInstance().getPluginLoaderSetting( + SettingsData::DISPLAY_DISPLAY_ORDER_KEY).toStringList()) { + QSharedPointer plugin = m_pluginManager->getDisplay(pluginString.trimmed()); + if (!plugin.isNull()) { + displays.append(QSharedPointer(plugin->createDefaultDisplay())); + queued.insert(pluginString.trimmed()); + } + } + for (QSharedPointer plugin : m_pluginManager->getAllDisplays()) { + if (!queued.contains(plugin->getName())) { + displays.append(QSharedPointer(plugin->createDefaultDisplay())); + } + } + + // Add the widgets to the tabs + tabs->setUpdatesEnabled(false); + QPair>, QTabWidget*> displayMap; + displayMap.second = tabs; + for (QSharedPointer displayPlugin : displays) { + QWidget *display = displayPlugin->getDisplayWidget(m_displayHandle); + int idx = tabs->addTab(display, displayPlugin->getName()); + displayMap.first.insert(idx, displayPlugin); + } + tabs->setUpdatesEnabled(true); + + // Add this display group to the list of displays + m_displayMaps.append(displayMap); + + // Set up the display controls when necessary + if (displayMap.first.size() > 0) { + tabs->setCurrentIndex(0); + checkCurrentDisplays(); + } + connect(tabs, SIGNAL(currentChanged(int)), this, SLOT(checkCurrentDisplays())); + + setupSplitViewMenu(); +} + +void MainWindow::removeDisplayGroup(int idx) +{ + if (idx <= 0 || idx >= m_displayMaps.length()) { + warningMessage(QString("Cannot delete display group %1").arg(idx + 1)); + return; + } + + m_displayMaps.at(idx).second->deleteLater(); + m_displayMaps.removeAt(idx); + checkCurrentDisplays(); + + setupSplitViewMenu(); +} + +void MainWindow::setupSplitViewMenu() +{ + m_splitViewMenu->clear(); + + if (m_displayMaps.size() < 5) { + m_splitViewMenu->addAction( + "Add split view", + [this]() { + this->addDisplayGroup(); + })->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_V)); + } + + for (int i = 1; i < m_displayMaps.size(); i++) { + QAction *remove = m_splitViewMenu->addAction( + QString("Remove split view %1").arg(i + 1), + [this, i]() { + this->removeDisplayGroup(i); + }); + if (i == m_displayMaps.size() - 1) { + remove->setShortcut(QKeySequence(Qt::CTRL + Qt::SHIFT + Qt::Key_X)); + } + } +} + +void MainWindow::initializeImporterExporters() +{ + ui->menu_Import_Bits_From->clear(); + ui->menu_Export_Bits_To->clear(); + for (QSharedPointer plugin : m_pluginManager->getAllImporterExporters()) { + if (plugin->canImport()) { + ui->menu_Import_Bits_From->setEnabled(true); + ui->menu_Import_Bits_From->addAction( + plugin->getName(), + [this, plugin]() { + QSharedPointer container = plugin->importBits(QMap(), this); + if (!container.isNull()) { + QModelIndex addedIndex = this->m_bitContainerManager->getTreeModel()->addContainer(container); + this->m_bitContainerManager->getCurrSelectionModel()->setCurrentIndex( + addedIndex, + QItemSelectionModel::ClearAndSelect); + } + }); + } + if (plugin->canExport()) { + ui->menu_Export_Bits_To->setEnabled(true); + ui->menu_Export_Bits_To->addAction( + plugin->getName(), + [this, plugin]() { + plugin->exportBits(this->currContainer(), QMap(), this); + }); + } + } +} + +void MainWindow::warningMessage(QString message, QString windowTitle) +{ + QMessageBox msg; + msg.setWindowTitle(windowTitle); + msg.setText(message); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); +} + +void MainWindow::checkCurrentDisplays() +{ + + for (auto controls : m_currControlWidgets) { + controls->setVisible(false); + ui->displayControlsLayout->removeWidget(controls); + } + m_currControlWidgets.clear(); + + QSet focusDisplays; + for (auto displayMap : m_displayMaps) { + QSharedPointer currDisplay = displayMap.first.value( + displayMap.second->currentIndex()); + if (!currDisplay.isNull()) { + focusDisplays.insert(currDisplay.data()); + + m_currControlWidgets.append(currDisplay->getControlsWidget(m_displayHandle)); + } + } + m_displayHandle->setFocusDisplays(focusDisplays); + + for (auto controls : m_currControlWidgets) { + ui->displayControlsLayout->addWidget(controls); + controls->setVisible(true); + } +} + +QSharedPointer MainWindow::currContainer() +{ + return m_bitContainerManager->getCurrentContainer(); +} + +void MainWindow::importBitfile(QString file) +{ + QMap args; + args.insert("filename", file); + + QSharedPointer fileDataImporter = m_pluginManager->getImporterExporter("File Data"); + if (fileDataImporter.isNull()) { + warningMessage("Could not import bit file without 'File Data' plugin"); + return; + } + QSharedPointer container = fileDataImporter->importBits(args, this); + if (!container.isNull()) { + QModelIndex addedIndex = this->m_bitContainerManager->getTreeModel()->addContainer(container); + this->m_bitContainerManager->getCurrSelectionModel()->setCurrentIndex( + addedIndex, + QItemSelectionModel::ClearAndSelect); + } +} + +void MainWindow::importBytes(QByteArray rawBytes, QString name) +{ + QSharedPointer bitContainer = QSharedPointer(new BitContainer()); + bitContainer->setBytes(rawBytes); + + bitContainer->setName(name); + + QModelIndex addedIndex = m_bitContainerManager->getTreeModel()->addContainer(bitContainer); + m_bitContainerManager->getCurrSelectionModel()->setCurrentIndex( + addedIndex, + QItemSelectionModel::ClearAndSelect); +} + +void MainWindow::on_pb_operate_clicked() +{ + QSharedPointer op = getCurrentOperator(); + QJsonObject pluginState = op->getStateFromUi(); + this->requestOperatorRun(op->getName(), pluginState); +} + +void MainWindow::checkOperatorInput() +{ + QSharedPointer op = getCurrentOperator(); + if (op.isNull()) { + ui->pb_operate->setEnabled(false); + return; + } + + QJsonObject pluginState = op->getStateFromUi(); + if (pluginState.isEmpty()) { + ui->pb_operate->setEnabled(false); + } + else { + ui->pb_operate->setEnabled(true); + } +} + +QSharedPointer MainWindow::getCurrentOperator() +{ + return m_operatorMap.value(ui->operatorTabs->currentIndex(), QSharedPointer()); +} + +void MainWindow::loadPlugins() +{ + QVariant badPluginPath = SettingsManager::getInstance().getPrivateSetting( + SettingsData::PLUGIN_RUNNING_KEY); + if (badPluginPath.isValid()) { + if (QMessageBox::question( + nullptr, + "Blacklist Plugin?", + QString( + "The plugin at '%1' was running when the application terminated. Do you want to blacklist this plugin? You can always edit the blacklist in the Preferences.") + .arg( + badPluginPath.toString()))) { + QVariant oldBlacklist = SettingsManager::getInstance().getPluginLoaderSetting( + SettingsData::PLUGIN_BLACKLIST_KEY); + QStringList blacklist; + if (oldBlacklist.isValid() && oldBlacklist.canConvert()) { + blacklist = oldBlacklist.toStringList(); + } + blacklist.append(badPluginPath.toString()); + SettingsManager::getInstance().setPluginLoaderSetting(SettingsData::PLUGIN_BLACKLIST_KEY, blacklist); + } + } + + QStringList warnings; + QStringList pluginPaths; + if (!m_extraPluginPath.isEmpty()) { + pluginPaths.append(m_extraPluginPath.split(":")); + } + pluginPaths.append( + SettingsManager::getInstance().getPluginLoaderSetting( + SettingsData::PLUGIN_PATH_KEY).toString().split(":")); + for (QString pluginPath : pluginPaths) { + + if (pluginPath.startsWith("~/")) { + pluginPath.replace(0, 1, QDir::homePath()); + } + else if (!pluginPath.startsWith("/")) { + pluginPath = QApplication::applicationDirPath() + "/" + pluginPath; + } + warnings.append(m_pluginManager->loadPlugins(pluginPath)); + } + + if (!warnings.isEmpty()) { + QMessageBox msg; + msg.setWindowTitle("Plugin Load Warnings"); + msg.setText(warnings.join("\n")); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); + } + + + QSet queued; + + ui->operatorTabs->setUpdatesEnabled(false); + QList> operators; + queued.clear(); + for (QString pluginString : SettingsManager::getInstance().getPluginLoaderSetting( + SettingsData::OPERATOR_DISPLAY_ORDER_KEY).toStringList()) { + QSharedPointer plugin = m_pluginManager->getOperator(pluginString.trimmed()); + if (!plugin.isNull()) { + operators.append(plugin); + queued.insert(pluginString.trimmed()); + } + } + for (QSharedPointer plugin : m_pluginManager->getAllOperators()) { + if (!queued.contains(plugin->getName())) { + operators.append(plugin); + } + } + for (QSharedPointer op : operators) { + QWidget *opUi = new QWidget(); + op->applyToWidget(opUi); + opUi->setAutoFillBackground(true); + int idx = ui->operatorTabs->addTab(opUi, op->getName()); + m_operatorMap.insert(idx, op); + op->provideCallback(m_operatorPluginCallback); + } + ui->operatorTabs->setUpdatesEnabled(true); + + ui->analyzerTabs->setUpdatesEnabled(false); + QList> analyzers; + queued.clear(); + for (QString pluginString : SettingsManager::getInstance().getPluginLoaderSetting( + SettingsData::ANALYZER_DISPLAY_ORDER_KEY).toStringList()) { + QSharedPointer plugin = m_pluginManager->getAnalyzer(pluginString.trimmed()); + if (!plugin.isNull()) { + analyzers.append(plugin); + queued.insert(pluginString.trimmed()); + } + } + for (QSharedPointer plugin : m_pluginManager->getAllAnalyzers()) { + if (!queued.contains(plugin->getName())) { + analyzers.append(plugin); + } + } + + for (QSharedPointer analyzer : analyzers) { + QWidget *analysisUi = new QWidget(); + analyzer->applyToWidget(analysisUi); + analysisUi->setAutoFillBackground(true); + int idx = ui->analyzerTabs->addTab(analysisUi, analyzer->getName()); + m_analyzerMap.insert(idx, analyzer); + analyzer->provideCallback(m_analyzerPluginCallback); + } + ui->analyzerTabs->setUpdatesEnabled(true); +} + +void MainWindow::activateBitContainer() +{ + QSharedPointer bitContainer = currContainer(); + if (bitContainer.isNull()) { + ui->tb_removeBitContainer->setEnabled(false); + } + else { + ui->tb_removeBitContainer->setEnabled(true); + } + setCurrentBitContainer(); +} + +void MainWindow::setCurrentBitContainer() +{ + QSharedPointer container = currContainer(); + + disconnect(this, SIGNAL(containerFocusRequested(int,int))); + + if (!container.isNull()) { + connect( + container.data(), + SIGNAL(focusRequested(int,int)), + this, + SLOT( + containerFocusRequested( + int, + int))); + } + + for (QSharedPointer analyzer : m_pluginManager->getAllAnalyzers()) { + if (currContainer().isNull()) { + analyzer->previewBits(QSharedPointer()); + } + else { + analyzer->previewBits( + QSharedPointer( + new BitContainerPreview( + currContainer()))); + } + } + + if (!currContainer().isNull()) { + // Set the last analyzer plugin settings used on this container + if (!currContainer()->getActionLineage().isNull()) { + QSet usedPlugin; + auto lineage = currContainer()->getActionLineage()->getLineage(); + for (int i = lineage.size() - 1; i >= 0; i--) { + if (lineage.at(i)->getPluginAction()->getPluginType() == PluginAction::Analyzer) { + QString pluginName = lineage.at(i)->getPluginAction()->getPluginName(); + if (usedPlugin.contains(pluginName)) { + break; + } + QJsonObject pluginState = lineage.at(i)->getPluginAction()->getPluginState(); + + auto analyzer = m_pluginManager->getAnalyzer(pluginName); + if (!analyzer.isNull()) { + if (analyzer->setPluginStateInUi(pluginState)) { + usedPlugin.insert(pluginName); + } + } + } + else { + break; + } + } + } + + // Set the last operator plugin settings used on this container + if (!currContainer()->getChildUuids().isEmpty()) { + QSet usedPlugin; + auto childId = currContainer()->getChildUuids().constEnd(); + while (childId != currContainer()->getChildUuids().constBegin()) { + --childId; + auto child = m_bitContainerManager->getTreeModel()->getContainerById(*childId); + if (!child.isNull() && !child->getActionLineage().isNull()) { + auto lineage = child->getActionLineage()->getLineage(); + for (int i = lineage.size() - 1; i >= 0; i--) { + if (lineage.at(i)->getPluginAction()->getPluginType() == PluginAction::Operator) { + QString pluginName = lineage.at(i)->getPluginAction()->getPluginName(); + if (usedPlugin.contains(pluginName)) { + break; + } + QJsonObject pluginState = lineage.at(i)->getPluginAction()->getPluginState(); + + auto op = m_pluginManager->getOperator(pluginName); + if (!op.isNull()) { + if (op->setPluginStateInUi(pluginState)) { + usedPlugin.insert(pluginName); + } + } + break; + } + } + } + } + } + } +} + +void MainWindow::containerFocusRequested(int bitOffset, int frameOffset) +{ + m_displayHandle->setOffsets(bitOffset, frameOffset); +} + +void MainWindow::deleteCurrentBitcontainer() +{ + if (!currContainer().isNull()) { + QMessageBox::StandardButton reply; + reply = QMessageBox::question( + this, + "Delete Bits Confirmation", + QString("Are you sure you want to delete bit container '%1'?").arg(currContainer()->getName()), + QMessageBox::Yes | QMessageBox::No); + if (reply != QMessageBox::Yes) { + return; + } + + ui->tb_removeBitContainer->setEnabled(false); + m_bitContainerManager->deleteCurrentContainer(); + activateBitContainer(); + } +} + +void MainWindow::requestAnalyzerRun(QString pluginName, QJsonObject pluginState) +{ + if (!currContainer().isNull()) { + QSharedPointer plugin = m_pluginManager->getAnalyzer(pluginName); + if (!plugin.isNull()) { + m_pluginActionManager->analyzerActor()->analyzerFullAct(plugin, currContainer(), pluginState); + } + } +} + +void MainWindow::requestOperatorRun(QString pluginName, QJsonObject pluginState) +{ + if (!currContainer().isNull()) { + QSharedPointer plugin = m_pluginManager->getOperator(pluginName); + if (!plugin.isNull()) { + QList> inputContainers; + if (plugin->getMaxInputContainers() == 1 && plugin->getMinInputContainers() == 1) { + inputContainers.append(currContainer()); + } + else if (plugin->getMaxInputContainers() != 0) { + // TODO: operator selection dialogue - need a multi-input plugin to test it out + warningMessage( + "Plugins with variable or more than 1 input are not yet supported by the GUI", + "Unsupported Plugin Charactaristics"); + return; + } + if (pluginState.isEmpty()) { + pluginState = plugin->getStateFromUi(); + if (pluginState.isEmpty()) { + warningMessage( + "The plugin is unable to act due to a bad input value", + "Bad Plugin Input"); + return; + } + } + + m_pluginActionManager->operatorActor()->operatorFullAct( + plugin, + inputContainers, + m_bitContainerManager, + ui->le_outputName->text()); + } + } +} + +void MainWindow::on_pb_analyze_clicked() +{ + if (!currContainer().isNull()) { + m_pluginActionManager->analyzerActor()->analyzerFullAct( + m_analyzerMap.value(ui->analyzerTabs->currentIndex(), QSharedPointer()), + currContainer()); + } +} + +void MainWindow::setHoverBit(bool hovering, int bitOffset, int frameOffset) +{ + if (!hovering || frameOffset < 0 || bitOffset < 0 || currContainer().isNull()) { + this->statusBar()->showMessage(""); + } + else { + int totalBitOffset = currContainer()->getFrames().at(frameOffset).start() + bitOffset; + int totalByteOffset = totalBitOffset / 8; + this->statusBar()->showMessage( + QString("Bit Offset: %L1 Byte Offset: %L2 Frame Offset: %L3 Frame Bit Offset: %L4").arg( + totalBitOffset).arg(totalByteOffset).arg(frameOffset).arg(bitOffset)); + } +} + +void MainWindow::on_action_Save_Current_Container_triggered() +{ + if (currContainer().isNull()) { + warningMessage( + "You must first select a bit container in order to save it.", + "Cannot Save Container"); + return; + } + + QString fileName = QFileDialog::getSaveFileName( + this, + tr("Save Bit Container"), + SettingsManager::getInstance().getPrivateSetting( + SettingsData::LAST_CONTAINER_PATH_KEY).toString(), + tr("Hobbits Bit Containers (*.hobbits_bits)")); + if (!fileName.endsWith(".hobbits_bits")) { + fileName += ".hobbits_bits"; + } + + QFile file(fileName); + SettingsManager::getInstance().setPrivateSetting( + SettingsData::LAST_CONTAINER_PATH_KEY, + QFileInfo(file).dir().path()); + + if (!file.open(QIODevice::WriteOnly)) { + warningMessage( + QString("Could not open file '%1' for writing").arg(fileName), + "Cannot Save Container"); + return; + } + + QDataStream out(&file); + out << *currContainer().data(); + file.close(); + + if (out.status() != QDataStream::Status::Ok) { + warningMessage( + QString("Encountered errors while writing to file '%1'").arg(fileName), + "Error Saving Container"); + } + +} + +QStringList MainWindow::openHobbitsBits(QString fileName) +{ + QFile file(fileName); + SettingsManager::getInstance().setPrivateSetting( + SettingsData::LAST_CONTAINER_PATH_KEY, + QFileInfo(file).dir().path()); + + if (!file.open(QIODevice::ReadOnly)) { + return QStringList(QString("Could not open hobbits bits file '%1'").arg(fileName)); + } + + + QSharedPointer bitContainer = QSharedPointer(new BitContainer()); + QDataStream in(&file); + in >> *bitContainer.data(); + file.close(); + + if (in.status() != QDataStream::Status::Ok) { + return QStringList( + QString("Failure opening hobbits container file '%1'\nMaybe you meant to 'Import Bits'?").arg( + fileName)); + } + + bitContainer->setName(QFileInfo(file).completeBaseName()); + + QModelIndex addedIndex = m_bitContainerManager->getTreeModel()->addContainer(bitContainer); + m_bitContainerManager->getCurrSelectionModel()->setCurrentIndex( + addedIndex, + QItemSelectionModel::ClearAndSelect); + + return QStringList(); +} + +void MainWindow::on_actionOpen_Container_triggered() +{ + QString fileName = QFileDialog::getOpenFileName( + this, + tr("Open Bit Container"), + SettingsManager::getInstance().getPrivateSetting( + SettingsData::LAST_CONTAINER_PATH_KEY).toString(), + tr("Hobbits Bit Containers (*.hobbits_bits)")); + + if (fileName.isEmpty()) { + return; + } + + QStringList errors = openHobbitsBits(fileName); + + if (!errors.isEmpty()) { + warningMessage(errors.join("\n\n"), "Error Opening Container"); + } +} + +void MainWindow::on_action_Export_Template_triggered() +{ + if (currContainer().isNull() || (currContainer()->getActionLineage().isNull() + && currContainer()->getChildUuids().isEmpty())) { + warningMessage( + "You must first select a bit container with children or a history of plugin operations in order to save a template.", + "Cannot Save Template"); + return; + } + + bool useChildren = false; + if (currContainer()->getActionLineage().isNull()) { + useChildren = true; + } + else if (currContainer()->getChildUuids().isEmpty()) { + useChildren = false; + } + else { + QMessageBox pickMode; + pickMode.setWindowTitle("Select template type"); + pickMode.setText( + "Do you want to create a template for the actions that created the selected container, or for the actions that created its children?"); + pickMode.addButton("Selected Container", QMessageBox::ButtonRole::AcceptRole); + QPushButton *children = pickMode.addButton("Its Children", QMessageBox::ButtonRole::RejectRole); + pickMode.exec(); + useChildren = pickMode.clickedButton() == children; + } + + QString fileName = QFileDialog::getSaveFileName( + this, + tr("Save Template"), + SettingsManager::getInstance().getPrivateSetting( + SettingsData::LAST_TEMPLATE_PATH_KEY).toString(), + tr("Hobbits Templates (*.hobbits_template)")); + if (!fileName.endsWith(".hobbits_template")) { + fileName += ".hobbits_template"; + } + + QList> lineages; + if (useChildren) { + QQueue containers; + containers.append(currContainer()->getId()); + while (!containers.isEmpty()) { + QUuid id = containers.takeFirst(); + QSharedPointer container = m_bitContainerManager->getTreeModel()->getContainerById( + id); + if (container.isNull()) { + continue; + } + if (container->getChildUuids().isEmpty()) { + lineages.append(container->getActionLineage()); + } + else { + containers.append(container->getChildUuids()); + } + } + } + else { + lineages.append(currContainer()->getActionLineage()); + } + + QStringList warnings; + if (TemplateFileHandler::writeTemplate(fileName, lineages, &warnings)) { + populateRecentTemplatesMenu(fileName); + if (!warnings.isEmpty()) { + warningMessage(warnings.join("\n\n"), "Failed to Export Template"); + } + } + else if (!warnings.isEmpty()) { + warningMessage(warnings.join("\n\n")); + } +} + +void MainWindow::applyTemplateToCurrentContainer(QString fileName) +{ + QStringList warnings; + if (!TemplateFileHandler::applyTemplateToContainer( + fileName, + currContainer(), + m_bitContainerManager, + m_pluginActionManager, + &warnings)) { + warningMessage(warnings.join("\n\n"), "Failed to Apply Template"); + } + else if (!warnings.isEmpty()) { + warningMessage(warnings.join("\n\n")); + } +} + +void MainWindow::on_actionApply_Template_triggered() +{ + if (currContainer().isNull()) { + warningMessage( + "You must first select a bit container to apply the template to.", + "Cannot Apply Template"); + return; + } + + QString fileName = QFileDialog::getOpenFileName( + this, + tr("Apply Template"), + SettingsManager::getInstance().getPrivateSetting( + SettingsData::LAST_TEMPLATE_PATH_KEY).toString(), + tr("Hobbits Templates (*.hobbits_template)")); + if (fileName.isEmpty()) { + return; + } + + applyTemplateToCurrentContainer(fileName); +} + +void MainWindow::pluginActionStarted() +{ + ui->pb_analyze->setEnabled(false); + ui->pb_operate->setEnabled(false); + m_pluginActionProgress->setValue(0); + m_pluginActionProgress->setVisible(true); + m_pluginActionCancel->setVisible(true); +} + +void MainWindow::pluginActionFinished() +{ + ui->pb_analyze->setEnabled(true); + ui->pb_operate->setEnabled(true); + m_pluginActionProgress->setVisible(false); + m_pluginActionCancel->setVisible(false); + + for (QSharedPointer analyzer : m_pluginManager->getAllAnalyzers()) { + analyzer->previewBits( + QSharedPointer( + new BitContainerPreview( + currContainer()))); + } +} + +void MainWindow::pluginActionProgress(int progress) +{ + m_pluginActionProgress->setValue(progress); +} + +void MainWindow::on_action_About_triggered() +{ + QString coreLibVersion = HobbitsCoreInfo::getLibVersion(); + QString guiVersion = HobbitsGuiInfo::getGuiVersion(); + + QMessageBox msg; + msg.setWindowTitle("About Hobbits"); + msg.setText( + QString("Hobbits GUI Version: %1\nHobbits Core Version: %2").arg(guiVersion).arg( + coreLibVersion)); + msg.setDefaultButton(QMessageBox::Ok); + msg.setIconPixmap(QIcon(":/hobbitsgui/images/icons/HobbitsRingSmall.png").pixmap(64, 64)); + msg.exec(); +} + +void MainWindow::on_actionPreferences_triggered() +{ + PreferencesDialog dialog(m_pluginManager); + dialog.exec(); +} + +void MainWindow::populateRecentTemplatesMenu(QString addition, QString removal) +{ + QString key = "recently_used_templates"; + + QStringList recentlyUsed; + QVariant currentSetting = SettingsManager::getInstance().getPrivateSetting(key); + if (!currentSetting.isNull() && currentSetting.canConvert()) { + recentlyUsed = currentSetting.toStringList(); + } + + if (!removal.isEmpty()) { + recentlyUsed.removeAll(removal); + } + + if (!addition.isEmpty()) { + recentlyUsed.removeAll(addition); + recentlyUsed.insert(0, addition); + } + + recentlyUsed = recentlyUsed.mid(0, 10); + + SettingsManager::getInstance().setPrivateSetting(key, recentlyUsed); + + ui->menuApply_Recent_Template->clear(); + for (QString templateFile : recentlyUsed) { + ui->menuApply_Recent_Template->addAction( + templateFile, + [this, templateFile]() { + this->applyTemplateToCurrentContainer(templateFile); + }); + } + + ui->menuApply_Recent_Template->setEnabled(recentlyUsed.length() > 0); +} + +void MainWindow::on_tb_scrollReset_clicked() +{ + m_displayHandle->setOffsets(0, 0); +} diff --git a/src/hobbits-gui/mainwindow.h b/src/hobbits-gui/mainwindow.h new file mode 100644 index 00000000..8f496899 --- /dev/null +++ b/src/hobbits-gui/mainwindow.h @@ -0,0 +1,121 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "bitcontainermanager.h" +#include "displayinterface.h" +#include "operatorinterface.h" +#include "pluginactionlineage.h" +#include "pluginactionmanager.h" +#include "pluginmanager.h" + +#include +#include +#include +#include + +namespace Ui +{ +class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QString extraPluginPath, QString configFilePath, QWidget *parent = nullptr); + + ~MainWindow() override; + + QSharedPointer getCurrentOperator(); + +protected: + void closeEvent(QCloseEvent *event) override; + +signals: +public slots: + void on_pb_operate_clicked(); + void on_pb_analyze_clicked(); + + void loadPlugins(); + + void importBitfile(QString file); + void importBytes(QByteArray rawBytes, QString name); + + QStringList openHobbitsBits(QString fileName); + + void checkOperatorInput(); + void checkCurrentDisplays(); + + void activateBitContainer(); + void setCurrentBitContainer(); + void deleteCurrentBitcontainer(); + + void setHoverBit(bool hovering, int bitOffset, int frameOffset); + + void requestAnalyzerRun(QString pluginName, QJsonObject pluginState); + void requestOperatorRun(QString pluginName, QJsonObject pluginState); + + QSharedPointer currContainer(); + + void applyTemplateToCurrentContainer(QString fileName); + + void warningMessage(QString message, QString windowTitle = "Warning"); + +private slots: + void on_action_Export_Template_triggered(); + + void on_actionApply_Template_triggered(); + + void pluginActionStarted(); + void pluginActionFinished(); + void pluginActionProgress(int); + + void on_action_About_triggered(); + + void initializeDisplays(); + void addDisplayGroup(); + void removeDisplayGroup(int idx); + void initializeImporterExporters(); + + void containerFocusRequested(int bitOffset, int frameOffset); + + void on_action_Save_Current_Container_triggered(); + + void on_actionOpen_Container_triggered(); + + void on_actionPreferences_triggered(); + + void populateRecentTemplatesMenu(QString addition = QString(), QString removal = QString()); + + void on_tb_scrollReset_clicked(); + + void setupSplitViewMenu(); + +private: + Ui::MainWindow *ui; + + QString m_extraPluginPath; + + QSharedPointer m_bitContainerManager; + QSharedPointer m_pluginManager; + QSharedPointer m_pluginActionManager; + + QSharedPointer m_operatorPluginCallback; + QSharedPointer m_analyzerPluginCallback; + + QProgressBar *m_pluginActionProgress; + QPushButton *m_pluginActionCancel; + + QMap> m_operatorMap; + QMap> m_analyzerMap; + + QList>, QTabWidget*>> m_displayMaps; + QSplitter *m_displayTabsSplitter; + QSharedPointer m_displayHandle; + QList m_currControlWidgets; + + QMenu *m_splitViewMenu; +}; + +#endif // MAINWINDOW_H diff --git a/src/hobbits-gui/mainwindow.ui b/src/hobbits-gui/mainwindow.ui new file mode 100644 index 00000000..326bd2cf --- /dev/null +++ b/src/hobbits-gui/mainwindow.ui @@ -0,0 +1,458 @@ + + + MainWindow + + + + 0 + 0 + 1286 + 772 + + + + Hobbits + + + + + + + + + + + 0 + + + + + + + false + + + + 0 + 14 + + + + 0 + + + Qt::Horizontal + + + + + + + + + 2 + + + 0 + + + + + false + + + 0 + + + Qt::Vertical + + + + + + + true + + + + 14 + 14 + + + + + + + + 0 + 0 + + + + + + + + + + + + + + + + TopToolBarArea + + + false + + + + + + QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable + + + Bit Containers + + + 1 + + + + + + + + + false + + + Remove + + + + :/hobbitsgui/images/icons/trash.png:/hobbitsgui/images/icons/trash.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + 524287 + 524287 + + + + false + + + QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable + + + Operator Plugins + + + 8 + + + + + + + + + + + -1 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Output Name + + + le_outputName + + + + + + + + + + Apply Operation + + + + + + + + + + + + + + + QDockWidget::DockWidgetClosable|QDockWidget::DockWidgetMovable + + + Analysis Plugins + + + 2 + + + + + + + -1 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + true + + + Analyze + + + + + + + + + + + + 0 + 0 + 1286 + 29 + + + + + &File + + + + &Import Bits From + + + + + + &Export Bits To + + + + + + false + + + Apply Recent Template + + + + + + + + + + + + + + + &View + + + + + &Help + + + + + + &Edit + + + + + + + + + + + Save &Template... + + + + + &Apply Template... + + + + + Change True Color + + + + + Change False Color + + + + + Set "1" Color + + + + + Set "0" Color + + + + + Match Color... + + + + + Match Focus Color... + + + + + &About + + + + + &Save Current Container... + + + + + &Open Container... + + + + + Manual Highlight Color... + + + + + ... + + + + + ... + + + + + Preferences... + + + + + blah + + + + + + operatorTabs + le_outputName + pb_operate + + + + + + + le_outputName + returnPressed() + pb_operate + click() + + + 944 + 714 + + + 1171 + 721 + + + + + diff --git a/src/hobbits-gui/preferencesdialog.cpp b/src/hobbits-gui/preferencesdialog.cpp new file mode 100644 index 00000000..ca5e2b72 --- /dev/null +++ b/src/hobbits-gui/preferencesdialog.cpp @@ -0,0 +1,279 @@ +#include "preferencesdialog.h" +#include "settingsmanager.h" +#include "ui_preferencesdialog.h" +#include +#include +#include +#include +#include +#include +#include +#include + +PreferencesDialog::PreferencesDialog(QSharedPointer pluginManager, QWidget *parent) : + QDialog(parent), + ui(new Ui::PreferencesDialog), + m_pluginManager(pluginManager) +{ + ui->setupUi(this); + ui->pb_apply->setDisabled(true); + this->setWindowTitle("Hobbits Preferences"); + + m_data = SettingsManager::getInstance().getAll(); + + ui->lw_groups->addItem("Display"); + ui->lw_groups->addItem("Plugin Loader"); + ui->lw_groups->addItem("Plugin Settings"); + + ui->lw_groups->setCurrentRow(0); + on_lw_groups_currentRowChanged(0); +} + +PreferencesDialog::~PreferencesDialog() +{ + delete ui; +} + +QPixmap PreferencesDialog::getColorPixmap(QColor color) +{ + QPixmap colorPix(32, 32); + colorPix.fill(Qt::white); + QPainter painter(&colorPix); + painter.setPen(Qt::transparent); + painter.setBrush(Qt::gray); + painter.drawRect(0, 0, 8, 8); + painter.drawRect(0, 16, 8, 8); + painter.drawRect(8, 8, 8, 8); + painter.drawRect(8, 24, 8, 8); + painter.drawRect(16, 0, 8, 8); + painter.drawRect(16, 16, 8, 8); + painter.drawRect(24, 8, 8, 8); + painter.drawRect(24, 24, 8, 8); + painter.setBrush(color); + painter.drawRect(0, 0, 32, 32); + + return colorPix; +} + +QLayoutItem* PreferencesDialog::createEditor( + std::function getter, + std::function setter, + QWidget *parent) +{ + QVariant value = getter(); + if (value.type() == QVariant::String) { + QLineEdit *editor = new QLineEdit(value.toString(), parent); + connect( + editor, + &QLineEdit::textChanged, + [setter](QString text) { + setter(QVariant(text)); + }); + return new QWidgetItem(editor); + } + else if (value.type() == QVariant::StringList) { + QLineEdit *editor = new QLineEdit(value.toStringList().join(","), parent); + connect( + editor, + &QLineEdit::textChanged, + [setter](QString text) { + setter(QVariant(text.split(","))); + }); + return new QWidgetItem(editor); + } + else if (value.type() == QVariant::Int) { + QSpinBox *editor = new QSpinBox(parent); + editor->setRange(INT_MIN, INT_MAX); + editor->setValue(value.toInt()); + connect( + editor, + QOverload::of(&QSpinBox::valueChanged), + [setter](int newVal) { + setter(QVariant(newVal)); + }); + return new QWidgetItem(editor); + } + else if (value.type() == QVariant::Double) { + QDoubleSpinBox *editor = new QDoubleSpinBox(parent); + editor->setValue(value.toInt()); + connect( + editor, + QOverload::of(&QDoubleSpinBox::valueChanged), + [setter](double newVal) { + setter(QVariant(newVal)); + }); + return new QWidgetItem(editor); + } + else if (int(value.type()) == QMetaType::QColor) { + QPushButton *dialogLauncher = new QPushButton("Select Color...", parent); + QLabel *colorChip = new QLabel(parent); + QHBoxLayout *layout = new QHBoxLayout(); + layout->setContentsMargins(16, 0, 0, 0); + layout->setSpacing(8); + layout->addWidget(colorChip); + layout->addWidget(dialogLauncher); + layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum)); + + QColor color = value.value(); + colorChip->setPixmap(getColorPixmap(color)); + connect( + dialogLauncher, + &QPushButton::clicked, + [getter, setter, parent, colorChip]() { + QColor selectedColor = QColorDialog::getColor( + getter().value(), + parent, + "Choose Color", + QColorDialog::ShowAlphaChannel); + if (selectedColor.isValid()) { + setter(QVariant(selectedColor)); + colorChip->setPixmap(getColorPixmap(selectedColor)); + } + }); + + return layout; + } + else { + QLineEdit *readOnly = new QLineEdit(value.toString(), parent); + readOnly->setReadOnly(true); + + return new QWidgetItem(readOnly); + } +} + +QWidget* PreferencesDialog::createUiPage() +{ + QWidget *uiSettingsWidget = new QWidget(this); + QFormLayout *uiSettingsLayout = new QFormLayout(); + + for (QString key : m_data.getUiSettingKeys()) { + QVariant value = m_data.getUiSetting(key); + QLayoutItem *editor = createEditor( + [this, key]() { + return m_data.getUiSetting(key); + }, + [this, key](QVariant newValue) { + m_data.setUiSetting(key, newValue); + ui->pb_apply->setDisabled(false); + }, + uiSettingsWidget); + QHBoxLayout *editorLayout = new QHBoxLayout(); + editorLayout->addItem(editor); + uiSettingsLayout->addRow(key, editorLayout); + } + + uiSettingsWidget->setLayout(uiSettingsLayout); + + return uiSettingsWidget; +} + +QWidget* PreferencesDialog::createPluginPage() +{ + QWidget *uiSettingsWidget = new QWidget(this); + QFormLayout *uiSettingsLayout = new QFormLayout(); + + for (QString key : m_data.getPluginSettingKeys()) { + QVariant value = m_data.getPluginSetting(key); + QLayoutItem *editor = createEditor( + [this, key]() { + return m_data.getPluginSetting(key); + }, + [this, key](QVariant newValue) { + m_data.setPluginSetting(key, newValue); + ui->pb_apply->setDisabled(false); + }, + uiSettingsWidget); + QHBoxLayout *editorLayout = new QHBoxLayout(); + editorLayout->addItem(editor); + uiSettingsLayout->addRow(key, editorLayout); + } + + uiSettingsWidget->setLayout(uiSettingsLayout); + + return uiSettingsWidget; +} + +QWidget* PreferencesDialog::createPluginLoaderPage() +{ + QWidget *uiSettingsWidget = new QWidget(this); + QFormLayout *uiSettingsLayout = new QFormLayout(); + + for (QString key : m_data.getPluginLoaderSettingKeys()) { + QVariant value = m_data.getPluginLoaderSetting(key); + QLayoutItem *editor = createEditor( + [this, key]() { + return m_data.getPluginLoaderSetting(key); + }, + [this, key](QVariant newValue) { + m_data.setPluginLoaderSetting(key, newValue); + ui->pb_apply->setDisabled(false); + }, + uiSettingsWidget); + QHBoxLayout *editorLayout = new QHBoxLayout(); + editorLayout->addItem(editor); + uiSettingsLayout->addRow(key, editorLayout); + } + + uiSettingsLayout->addRow("Loaded plugins:", new QLabel()); + for (auto plugin : m_pluginManager->getAllDisplays()) { + QLineEdit *path = new QLineEdit(m_pluginManager->getPluginLocation(plugin->getName())); + path->setReadOnly(true); + path->setDisabled(true); + uiSettingsLayout->addRow(plugin->getName(), path); + } + for (auto plugin : m_pluginManager->getAllImporterExporters()) { + QLineEdit *path = new QLineEdit(m_pluginManager->getPluginLocation(plugin->getName())); + path->setReadOnly(true); + path->setDisabled(true); + uiSettingsLayout->addRow(plugin->getName(), path); + } + for (auto plugin : m_pluginManager->getAllAnalyzers()) { + QLineEdit *path = new QLineEdit(m_pluginManager->getPluginLocation(plugin->getName())); + path->setReadOnly(true); + path->setDisabled(true); + uiSettingsLayout->addRow(plugin->getName(), path); + } + for (auto plugin : m_pluginManager->getAllOperators()) { + QLineEdit *path = new QLineEdit(m_pluginManager->getPluginLocation(plugin->getName())); + path->setReadOnly(true); + path->setDisabled(true); + uiSettingsLayout->addRow(plugin->getName(), path); + } + + uiSettingsWidget->setLayout(uiSettingsLayout); + + return uiSettingsWidget; +} + +void PreferencesDialog::on_pb_apply_clicked() +{ + SettingsManager::getInstance().setAll(m_data); + ui->pb_apply->setDisabled(true); +} + +void PreferencesDialog::on_pb_cancel_clicked() +{ + this->reject(); +} + +void PreferencesDialog::on_pb_ok_clicked() +{ + this->on_pb_apply_clicked(); + this->accept(); +} + +void PreferencesDialog::on_lw_groups_currentRowChanged(int currentRow) +{ + Q_UNUSED(currentRow) + if (ui->lw_groups->currentItem()->text() == "Display") { + ui->scrollArea->setWidget(createUiPage()); + } + else if (ui->lw_groups->currentItem()->text() == "Plugin Loader") { + ui->scrollArea->setWidget(createPluginLoaderPage()); + } + else if (ui->lw_groups->currentItem()->text() == "Plugin Settings") { + ui->scrollArea->setWidget(createPluginPage()); + } + else { + } +} diff --git a/src/hobbits-gui/preferencesdialog.h b/src/hobbits-gui/preferencesdialog.h new file mode 100644 index 00000000..89b11966 --- /dev/null +++ b/src/hobbits-gui/preferencesdialog.h @@ -0,0 +1,47 @@ +#ifndef PREFERENCESDIALOG_H +#define PREFERENCESDIALOG_H + +#include "pluginmanager.h" +#include "settingsdata.h" +#include +#include + +namespace Ui +{ +class PreferencesDialog; +} + +class PreferencesDialog : public QDialog +{ + Q_OBJECT + +public: + explicit PreferencesDialog(QSharedPointer pluginManager, QWidget *parent = nullptr); + + ~PreferencesDialog(); + +private slots: + void on_pb_apply_clicked(); + + void on_pb_cancel_clicked(); + + void on_pb_ok_clicked(); + + void on_lw_groups_currentRowChanged(int currentRow); + + QWidget* createUiPage(); + QWidget* createPluginPage(); + QWidget* createPluginLoaderPage(); + + QLayoutItem* createEditor(std::function getter, std::function setter, QWidget *parent); + +private: + static QPixmap getColorPixmap(QColor color); + + Ui::PreferencesDialog *ui; + + SettingsData m_data; + QSharedPointer m_pluginManager; +}; + +#endif // PREFERENCESDIALOG_H diff --git a/src/hobbits-gui/preferencesdialog.ui b/src/hobbits-gui/preferencesdialog.ui new file mode 100644 index 00000000..3441214c --- /dev/null +++ b/src/hobbits-gui/preferencesdialog.ui @@ -0,0 +1,100 @@ + + + PreferencesDialog + + + + 0 + 0 + 926 + 565 + + + + Dialog + + + + + + + + + 0 + 0 + + + + + 75 + true + + + + + + + + + + true + + + + + 0 + 0 + 644 + 501 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply + + + + + + + Cancel + + + + + + + Ok + + + + + + + + + + + + + + diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/FindAnalyzer.pro b/src/hobbits-plugins/analyzers/FindAnalyzer/FindAnalyzer.pro new file mode 100644 index 00000000..633eb8e2 --- /dev/null +++ b/src/hobbits-plugins/analyzers/FindAnalyzer/FindAnalyzer.pro @@ -0,0 +1,50 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-08-08T12:20:27 +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = $$qtLibraryTarget(FindAnalyzer) +TEMPLATE = lib +CONFIG += c++11 plugin + +DEFINES += FINDANALYZER_LIBRARY + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + findanalyzer.cpp + +HEADERS += \ + findanalyzer.h + +FORMS += \ + findanalyzer.ui + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} + +DISTFILES += + +RESOURCES += \ + icons.qrc diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.cpp b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.cpp new file mode 100644 index 00000000..f8feba1f --- /dev/null +++ b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.cpp @@ -0,0 +1,262 @@ +#include "findanalyzer.h" +#include "ui_findanalyzer.h" + +FindAnalyzer::FindAnalyzer() : + ui(new Ui::FindAnalyzer()), + m_focusIndex(0) +{ +} + +FindAnalyzer::~FindAnalyzer() +{ + delete ui; +} + +QString FindAnalyzer::getName() +{ + return "Find"; +} + +void FindAnalyzer::previewBits(QSharedPointer container) +{ + m_previewContainer = container; + m_focusIndex = 0; + initializeFindView(); + resolveFindFocusHighlight(); +} + +void FindAnalyzer::provideCallback(QSharedPointer pluginCallback) +{ + m_pluginCallback = pluginCallback; +} + +void FindAnalyzer::triggerRun() +{ + if (!m_pluginCallback.isNull()) { + m_pluginCallback->requestRun(this->getName(), this->getStateFromUi()); + } +} + +AnalyzerInterface* FindAnalyzer::createDefaultAnalyzer() +{ + return new FindAnalyzer(); +} + +void FindAnalyzer::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); + connect(ui->tb_findNext, SIGNAL(pressed()), this, SLOT(focusOnNext())); + connect(ui->tb_findPrevious, SIGNAL(pressed()), this, SLOT(focusOnPrevious())); + connect(ui->lw_findSummary, SIGNAL(currentRowChanged(int)), this, SLOT(focusOnIndex(int))); + connect(ui->le_searchString, SIGNAL(returnPressed()), this, SLOT(triggerRun())); +} + +bool FindAnalyzer::canRecallPluginState(const QJsonObject &pluginState) +{ + return pluginState.contains("search_string"); +} + +QJsonObject FindAnalyzer::getStateFromUi() +{ + if (ui->le_searchString->text().isEmpty()) { + return QJsonObject(); + } + QJsonObject result; + result.insert("search_string", ui->le_searchString->text()); + return result; + +} + +bool FindAnalyzer::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + ui->le_searchString->setText(pluginState.value("search_string").toString()); + return true; +} + +BitArray FindAnalyzer::getFindBits(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return BitArray(); + } + return BitArray::fromString(pluginState.value("search_string").toString()); +} + +QSharedPointer FindAnalyzer::analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + QList results; + BitArray findBits = getFindBits(recallablePluginState); + QSharedPointer bits = container->getBaseBits(); + + QSharedPointer nullResult; + if (findBits.size() < 1) { + return nullResult; + } + + int lastPercent = 0; + for (int start = 0; start < bits->size(); start++) { + if (findBits.size() > bits->size() - start) { + break; + } + bool match = true; + for (int i = 0; i < findBits.size() && start + i < bits->size(); i++) { + if (bits->at(start + i) != findBits.at(i)) { + match = false; + break; + } + } + if (match) { + int end = start + findBits.size() - 1; + results.append(Range(start, end)); + start = end; + end = end + findBits.size() - 1; + } + + int nextPercent = int(double(start) / double(bits->size()) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new AnalyzerResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + + QSharedPointer analyzerResult = QSharedPointer(new AnalyzerResult()); + analyzerResult->addRanges("find", results); + + QString resultHead = "Results for pattern '"; + for (int i = 0; i < findBits.size(); i++) { + if (findBits.at(i)) { + resultHead += "1"; + } + else { + resultHead += "0"; + } + } + resultHead += "'"; + + QStringList result; + int prev = 0; + for (Range range : results) { + result.append(QString("bit: %1 , delta: %2").arg(range.start()).arg(range.start() - prev)); + prev = range.start(); + } + + analyzerResult->addMetadata("find-results-head", resultHead); + analyzerResult->addMetadata("find-results", result); + analyzerResult->setPluginState(recallablePluginState); + + return analyzerResult; +} + +void FindAnalyzer::resolveFindFocusHighlight() +{ + QList findFocus; + if (m_previewContainer.isNull() || m_previewContainer->getHighlights("find").isEmpty()) { + m_focusIndex = 0; + } + else { + if (m_focusIndex >= m_previewContainer->getHighlights("find").size()) { + m_focusIndex = 0; + } + else if (m_focusIndex < 0) { + m_focusIndex = m_previewContainer->getHighlights("find").size() - 1; + } + Range focus = m_previewContainer->getHighlights("find").at(m_focusIndex); + findFocus.append(focus); + + int containingFrame = m_previewContainer->getFrameOffsetContaining(focus); + if (containingFrame >= 0) { + int bitOffset = qMax(0, focus.start() - m_previewContainer->getFrames().at(containingFrame).start() - 16); + int frameOffset = qMax(0, containingFrame - 16); + + QList findFocus; + findFocus.append(focus); + m_previewContainer->setHighlights("find-focus", findFocus); + + m_previewContainer->requestFocus(bitOffset, frameOffset); + } + } + + checkFindButtons(); +} + +void FindAnalyzer::checkFindButtons() +{ + if (m_previewContainer.isNull() || m_previewContainer->getHighlights("find").isEmpty()) { + ui->tb_findNext->hide(); + ui->tb_findPrevious->hide(); + ui->lb_findGo->hide(); + ui->lb_findSummary->hide(); + ui->lw_findSummary->hide(); + return; + } + ui->tb_findNext->show(); + ui->tb_findPrevious->show(); + ui->lb_findGo->show(); + ui->lw_findSummary->setCurrentRow(m_focusIndex); + ui->lb_findGo->setText( + QString("%1 of %2").arg( + m_focusIndex + + 1).arg(m_previewContainer->getHighlights("find").size())); +} + +void FindAnalyzer::initializeFindView() +{ + if (m_previewContainer.isNull() || m_previewContainer->getHighlights("find").isEmpty()) { + return; + } + m_focusIndex = 0; + QString results_head = m_previewContainer->getMetadata("find-results-head").first(); + if (results_head.isEmpty()) { + ui->lb_findSummary->hide(); + ui->lw_findSummary->hide(); + // ui->verticalSpacer->changeSize(20, 20, QSizePolicy::Expanding); + } + else { + ui->lb_findSummary->setText(results_head); + ui->lb_findSummary->show(); + ui->lw_findSummary->clear(); + ui->lw_findSummary->addItems(m_previewContainer->getMetadata("find-results")); + ui->lw_findSummary->setCurrentRow(m_focusIndex); + ui->lw_findSummary->show(); + // ui->verticalSpacer->changeSize(20, 20, QSizePolicy::Ignored); + } +} + +void FindAnalyzer::focusOnPrevious() +{ + m_focusIndex -= 1; + resolveFindFocusHighlight(); +} + +void FindAnalyzer::focusOnNext() +{ + m_focusIndex += 1; + resolveFindFocusHighlight(); +} + +void FindAnalyzer::focusOnIndex(int index) +{ + if (index == -1) { + m_focusIndex = 0; + resolveFindFocusHighlight(); + } + else { + m_focusIndex = index; + resolveFindFocusHighlight(); + } +} diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.h b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.h new file mode 100644 index 00000000..8ce1523c --- /dev/null +++ b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.h @@ -0,0 +1,57 @@ +#ifndef FINDANALYZER_H +#define FINDANALYZER_H + +#include "analyzerinterface.h" + +namespace Ui +{ +class FindAnalyzer; +} + +class FindAnalyzer : public QObject, AnalyzerInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.AnalyzerInterface.FindAnalyzer") + Q_INTERFACES(AnalyzerInterface) + +public: + FindAnalyzer(); + + ~FindAnalyzer() override; + + QString getName() override; + void previewBits(QSharedPointer container) override; + void provideCallback(QSharedPointer pluginCallback) override; + + AnalyzerInterface* createDefaultAnalyzer() override; + void applyToWidget(QWidget *widget) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + QSharedPointer analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +public slots: + void resolveFindFocusHighlight(); + void checkFindButtons(); + void initializeFindView(); + void focusOnPrevious(); + void focusOnNext(); + void focusOnIndex(int index); + + void triggerRun(); + +private: + BitArray getFindBits(const QJsonObject &pluginState); + + Ui::FindAnalyzer *ui; + + int m_focusIndex; + QSharedPointer m_previewContainer; + QSharedPointer m_pluginCallback; +}; + +#endif // FINDANALYZER_H diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.ui b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.ui new file mode 100644 index 00000000..9738e61b --- /dev/null +++ b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer.ui @@ -0,0 +1,136 @@ + + + FindAnalyzer + + + + 0 + 0 + 540 + 479 + + + + Form + + + + + + Search String (e.g. 0xf6f6 or 0b110) + + + + + + + + + No Results + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Previous + + + + :/findanalyzer/images/icons/arrow-left.png:/findanalyzer/images/icons/arrow-left.png + + + + 9 + 9 + + + + + + + + No Results + + + false + + + Qt::AlignCenter + + + + + + + Next + + + + :/findanalyzer/images/icons/arrow-right.png:/findanalyzer/images/icons/arrow-right.png + + + + 9 + 9 + + + + + + + + + + + 0 + 0 + + + + true + + + true + + + 0 + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 20 + + + + + + + + + + + diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer_global.h b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer_global.h new file mode 100644 index 00000000..7c57db31 --- /dev/null +++ b/src/hobbits-plugins/analyzers/FindAnalyzer/findanalyzer_global.h @@ -0,0 +1,12 @@ +#ifndef FINDANALYZER_GLOBAL_H +#define FINDANALYZER_GLOBAL_H + +#include + +#if defined (FINDANALYZER_LIBRARY) +# define FINDANALYZERSHARED_EXPORT Q_DECL_EXPORT +#else +# define FINDANALYZERSHARED_EXPORT Q_DECL_IMPORT +#endif + +#endif // FINDANALYZER_GLOBAL_H diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/icons.qrc b/src/hobbits-plugins/analyzers/FindAnalyzer/icons.qrc new file mode 100644 index 00000000..d9d15224 --- /dev/null +++ b/src/hobbits-plugins/analyzers/FindAnalyzer/icons.qrc @@ -0,0 +1,6 @@ + + + images/icons/arrow-right.png + images/icons/arrow-left.png + + diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/images/icons/arrow-left.png b/src/hobbits-plugins/analyzers/FindAnalyzer/images/icons/arrow-left.png new file mode 100644 index 00000000..9da2ae48 Binary files /dev/null and b/src/hobbits-plugins/analyzers/FindAnalyzer/images/icons/arrow-left.png differ diff --git a/src/hobbits-plugins/analyzers/FindAnalyzer/images/icons/arrow-right.png b/src/hobbits-plugins/analyzers/FindAnalyzer/images/icons/arrow-right.png new file mode 100644 index 00000000..d993cc06 Binary files /dev/null and b/src/hobbits-plugins/analyzers/FindAnalyzer/images/icons/arrow-right.png differ diff --git a/src/hobbits-plugins/analyzers/WidthFramer/WidthFramer.pro b/src/hobbits-plugins/analyzers/WidthFramer/WidthFramer.pro new file mode 100644 index 00000000..e30a92fd --- /dev/null +++ b/src/hobbits-plugins/analyzers/WidthFramer/WidthFramer.pro @@ -0,0 +1,65 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-09-18T16:02:26.935Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = WidthFramer +TEMPLATE = lib + +DEFINES += WIDTHFRAMER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += widthframer.cpp \ + peakselector.cpp + +HEADERS += widthframer.h \ + peakselector.h + +FORMS += widthframer.ui + +DISTFILES += + +win32 { + LIBS += -L$$OUT_PWD/../../../../windows -lfftw3-3 + INCLUDEPATH += $$OUT_PWD/../../../../windows + DEPENDPATH += $$OUT_PWD/../../../../windows + + DEFINES += FFTW_AUTOCORRELATION +} +unix { + packagesExist(fftw3) { + LIBS += -lfftw3 + DEFINES += FFTW_AUTOCORRELATION + } + else { + warning("The FFTW3 package could not be found, so WidthFramer will build without autocorrelation") + } +} + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} + diff --git a/src/hobbits-plugins/analyzers/WidthFramer/peakselector.cpp b/src/hobbits-plugins/analyzers/WidthFramer/peakselector.cpp new file mode 100644 index 00000000..72beb395 --- /dev/null +++ b/src/hobbits-plugins/analyzers/WidthFramer/peakselector.cpp @@ -0,0 +1,210 @@ +#include "peakselector.h" +#include +#include +#include + +PeakSelector::PeakSelector(QWidget *parent) : + QWidget(parent), + m_dataYMax(0), + m_dataYMin(0), + m_startDrag(-1), + m_endDrag(-1), + m_disabled(true), + m_hasNan(false) +{ + this->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + m_hScroll = new QScrollBar(Qt::Horizontal, this); + + connect(m_hScroll, SIGNAL(valueChanged(int)), this, SLOT(repaint())); + + m_zoomSlider = new QSlider(Qt::Horizontal, this); + m_zoomSlider->setValue(1); + m_zoomSlider->setMinimum(1); + m_zoomSlider->setMaximum(200); + + m_zoomSlider->setInvertedControls(true); + + connect(m_zoomSlider, SIGNAL(valueChanged(int)), this, SLOT(adjustScroll())); +} + +QSlider* PeakSelector::getZoomSlider() +{ + return m_zoomSlider; +} + +QScrollBar* PeakSelector::getHScroll() +{ + return m_hScroll; +} + +void PeakSelector::adjustScroll() +{ + m_hScroll->setMinimum(0); + m_hScroll->setMaximum(m_data.size() / m_zoomSlider->value()); + + repaint(); +} + +void PeakSelector::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + painter.fillRect(0, 0, this->width(), this->height(), Qt::white); + + if (m_data.isEmpty()) { + return; + } + + if (m_hasNan) { + painter.drawText(5, this->height() / 2, "Error: Data from autocorrelation contains NaN."); + return; + } + + double yOffset = -1 * m_dataYMin; + double yScale = (this->height() * .9) / (m_dataYMax - m_dataYMin); + + int xOffset = 1 + m_hScroll->value() * m_zoomSlider->value(); + + + QPainterPath path; + if (xOffset < m_data.size()) { + double yValue = (m_data.at(xOffset).y() + yOffset) * yScale; + yValue = this->height() - yValue; + path.moveTo(0, yValue); + } + for (int i = 0; i < this->width() && i * m_zoomSlider->value() + xOffset < m_data.size(); i++) { + int start = i * m_zoomSlider->value(); + double min = m_data.at(xOffset + start).y(); + double max = m_data.at(xOffset + start).y(); + for (int ii = 1; ii < m_zoomSlider->value() && xOffset + start + ii < m_data.size(); ii++) { + min = qMin(min, m_data.at(xOffset + start + ii).y()); + max = qMax(max, m_data.at(xOffset + start + ii).y()); + } + double minY = this->height() - (min + yOffset) * yScale; + double maxY = this->height() - (max + yOffset) * yScale; + path.lineTo(i + 1, minY); + path.lineTo(i + 1, maxY); + } + + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setPen(Qt::blue); + painter.drawPath(path); + + if (m_startDrag >= 0) { + QPointF peak = getPeak(); + if (!peak.isNull()) { + double yValue = (peak.y() + yOffset) * yScale; + yValue = this->height() - yValue; + painter.setBrush(QBrush(qRgba(50, 250, 50, 100))); + painter.drawEllipse(QPointF((peak.x() - xOffset) / m_zoomSlider->value(), yValue), 3, 3); + } + + painter.setOpacity(0.15); + painter.fillRect(m_startDrag, 0, m_endDrag - m_startDrag, this->height(), Qt::black); + + } +} + +QPointF PeakSelector::getPeak() +{ + if (m_startDrag < 0) { + return QPointF(); + } + + int start = m_startDrag + m_hScroll->value(); + int end = m_endDrag + m_hScroll->value(); + + if (start > end) { + start = end; + end = m_startDrag + m_hScroll->value(); + } + + start *= m_zoomSlider->value(); + end *= m_zoomSlider->value(); + + if (start < 0) { + return QPointF(); + } + + if (start >= m_data.size()) { + return QPointF(); + } + + if (end >= m_data.size()) { + end = m_data.size() - 1; + } + + int maxIdx = start; + double maxY = m_data.at(start).y(); + for (int i = start + 1; i < end; i++) { + if (m_data.at(i).y() > maxY) { + maxIdx = i; + maxY = m_data.at(i).y(); + } + } + + return (QPointF(maxIdx, maxY)); +} + +void PeakSelector::setData(QVector data) +{ + m_hasNan = false; + + m_data = data; + m_dataYMin = 0; + m_dataYMax = 0; + for (QPointF point : m_data) { + if (point.y() < m_dataYMin) { + m_dataYMin = point.y(); + } + else if (point.y() > m_dataYMax) { + m_dataYMax = point.y(); + } + if (std::isnan(point.y())) { + m_hasNan = true; + } + } + + m_disabled = m_hasNan; + + adjustScroll(); +} + +void PeakSelector::mouseMoveEvent(QMouseEvent *event) +{ + if (m_disabled) { + return; + } + if (m_startDrag >= 0) { + m_endDrag = event->x(); + this->repaint(); + } +} + +void PeakSelector::mousePressEvent(QMouseEvent *event) +{ + if (m_disabled) { + return; + } + m_startDrag = event->x(); + m_endDrag = event->x(); +} + +void PeakSelector::mouseReleaseEvent(QMouseEvent *event) +{ + if (m_disabled) { + return; + } + m_endDrag = event->x(); + + QPointF peak = getPeak(); + + m_startDrag = -1; + m_endDrag = -1; + + if (!peak.isNull()) { + emit peakSelected(peak); + } + + this->repaint(); +} diff --git a/src/hobbits-plugins/analyzers/WidthFramer/peakselector.h b/src/hobbits-plugins/analyzers/WidthFramer/peakselector.h new file mode 100644 index 00000000..98878afc --- /dev/null +++ b/src/hobbits-plugins/analyzers/WidthFramer/peakselector.h @@ -0,0 +1,48 @@ +#ifndef PEAKSELECTOR_H +#define PEAKSELECTOR_H + +#include +#include +#include + +class PeakSelector : public QWidget +{ + Q_OBJECT + +public: + explicit PeakSelector(QWidget *parent = nullptr); + + QScrollBar* getHScroll(); + QSlider* getZoomSlider(); + +protected: + void paintEvent(QPaintEvent *event) override; + + void mouseMoveEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + +public slots: + void setData(QVector data); + QPointF getPeak(); + + void adjustScroll(); + +signals: + void peakSelected(QPointF); + +private: + QVector m_data; + double m_dataYMax; + double m_dataYMin; + QScrollBar *m_hScroll; + QSlider *m_zoomSlider; + + int m_startDrag; + int m_endDrag; + + bool m_disabled; + bool m_hasNan; +}; + +#endif // PEAKSELECTOR_H diff --git a/src/hobbits-plugins/analyzers/WidthFramer/widthframer.cpp b/src/hobbits-plugins/analyzers/WidthFramer/widthframer.cpp new file mode 100644 index 00000000..2b67da3d --- /dev/null +++ b/src/hobbits-plugins/analyzers/WidthFramer/widthframer.cpp @@ -0,0 +1,266 @@ +#include "algorithm" +#include "cmath" +#include "math.h" +#include "mathparser.h" +#include "parseresult.h" +#include "ui_widthframer.h" +#include "widthframer.h" +#include + +WidthFramer::WidthFramer() : + ui(new Ui::WidthFramer()), + m_peakSelector(nullptr), + m_listModel(new QStringListModel()) +{ +#ifdef FFTW_AUTOCORRELATION + m_fftSize = 1 << 19; + m_fft_in = reinterpret_cast(fftw_malloc(sizeof(fftw_complex) * unsigned(m_fftSize))); + m_fft_out = reinterpret_cast(fftw_malloc(sizeof(fftw_complex) * unsigned(m_fftSize))); + + m_fft_plan1 = fftw_plan_dft_1d(m_fftSize, m_fft_in, m_fft_out, FFTW_FORWARD, FFTW_ESTIMATE); + m_fft_plan2 = fftw_plan_dft_1d(m_fftSize, m_fft_in, m_fft_out, FFTW_BACKWARD, FFTW_ESTIMATE); +#endif +} + +WidthFramer::~WidthFramer() +{ +#ifdef FFTW_AUTOCORRELATION + fftw_destroy_plan(m_fft_plan1); + fftw_destroy_plan(m_fft_plan2); + fftw_free(m_fft_in); + fftw_free(m_fft_out); +#endif + + if (m_peakSelector) { + delete m_peakSelector; + } + delete ui; +} + +AnalyzerInterface* WidthFramer::createDefaultAnalyzer() +{ + return new WidthFramer(); +} + +QString WidthFramer::getName() +{ + return "Width Framer"; +} + +void WidthFramer::applyToWidget(QWidget *widget) +{ + if (!m_peakSelector) { + m_peakSelector = new PeakSelector(); + connect(m_peakSelector, SIGNAL(peakSelected(QPointF)), this, SLOT(getPeak(QPointF))); + } + ui->setupUi(widget); + QVBoxLayout *layout = new QVBoxLayout(); + ui->tab->setLayout(layout); + layout->addWidget(m_peakSelector); + layout->addWidget(m_peakSelector->getZoomSlider()); + layout->addWidget(m_peakSelector->getHScroll()); + ui->lv_correlations->setModel(m_listModel); + + connect(ui->sb_width, SIGNAL(returnPressed()), this, SLOT(requestRun())); + + connect(ui->lv_correlations, SIGNAL(clicked(QModelIndex)), this, SLOT(widthSelected(QModelIndex))); + connect(ui->rb_all, SIGNAL(toggled(bool)), this, SLOT(setupScoreList(bool))); + connect(ui->rb_top100, SIGNAL(toggled(bool)), this, SLOT(setupScoreList(bool))); +} + +void WidthFramer::widthSelected(QModelIndex index) +{ + if (!index.isValid()) { + return; + } + + if (index.row() >= 0 && index.row() < m_autocorrelation.size()) { + ui->sb_width->setText(QString("%1").arg(m_autocorrelation.at(index.row()).x())); + requestRun(); + } +} + +bool sortPoints( + const QPointF &a, + const QPointF &b) +{ + return (b.y() < a.y()); +} + +void WidthFramer::previewBits(QSharedPointer container) +{ + + if (container.isNull()) { + m_peakSelector->setData(QVector()); + } + else { + m_autocorrelation = autocorrelate(container->getBaseBits()); + m_peakSelector->setData(m_autocorrelation); + + std::sort(m_autocorrelation.begin(), m_autocorrelation.end(), sortPoints); + setupScoreList(); + } + +} + +void WidthFramer::setupScoreList(bool toggled) +{ + if (!toggled) { + return; + } + QStringList pointList; + if (ui->rb_top100->isChecked()) { + for (int i = 0; i < 100 && i < m_autocorrelation.size(); i++) { + pointList << QString("%1 : %2").arg(m_autocorrelation.at(i).x()).arg(m_autocorrelation.at(i).y()); + } + } + else { + for (QPointF point : m_autocorrelation) { + pointList << QString("%1 : %2").arg(point.x()).arg(point.y()); + } + } + m_listModel->setStringList(pointList); +} + +void WidthFramer::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of analyzeBits + m_pluginCallback = pluginCallback; +} + +void WidthFramer::requestRun() +{ + if (m_pluginCallback.isNull()) { + return; + } + + m_pluginCallback->requestRun(getName(), getStateFromUi()); +} + +QJsonObject WidthFramer::getStateFromUi() +{ + QJsonObject pluginState; + MathParser mp; + ParseResult a = mp.parseInput(ui->sb_width->text()); + int frameWidth; + + if (a.isValid()) { + frameWidth = a.getResult(); + } + else { + QJsonObject errorState; + errorState.insert("error", QString("Invalid width value: '%1'").arg(ui->sb_width->text())); + return errorState; + } + + pluginState.insert("width", frameWidth); + + return pluginState; +} + +bool WidthFramer::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + ui->sb_width->setText(QString("%1").arg(pluginState.value("width").toInt())); + + return true; +} + +bool WidthFramer::canRecallPluginState(const QJsonObject &pluginState) +{ + return pluginState.contains("width") && pluginState.value("width").isDouble(); +} + +QSharedPointer WidthFramer::analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + QSharedPointer result(new AnalyzerResult()); + + if (!canRecallPluginState(recallablePluginState)) { + return std::move(result); + } + + int frameWidth = recallablePluginState.value("width").toInt(); + QList frames; + + QSharedPointer bits = container->getBaseBits(); + + int lastPercent = 0; + for (int i = 0; i < bits->size(); i += frameWidth) { + int width = qMin(frameWidth - 1, bits->size() - i - 1); + frames.append(Frame(bits, i, i + width)); + + int nextPercent = int(double(i) / double(bits->size()) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new AnalyzerResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + + result->setPluginState(recallablePluginState); + result->addRanges("frames", frames); + result->addMetadata("max_frame_width", QStringList(QString("%1").arg(frameWidth))); + + return std::move(result); +} + +void WidthFramer::getPeak(QPointF peak) +{ + int value = int(peak.x()); + QString value_string = QString::number(value); + ui->sb_width->setText(value_string); + requestRun(); +} + +QVector WidthFramer::autocorrelate(QSharedPointer bits) +{ +#ifdef FFTW_AUTOCORRELATION + int N = m_fftSize; + + // prepare and run first FFT + for (int i = 0; i < N; i++) { + if (i < bits->size()) { + m_fft_in[i][0] = bits->at(i) ? 1 : -1; + m_fft_in[i][1] = 0; + } + } + fftw_execute(m_fft_plan1); + + // prepare and run second FFT + for (int i = 0; i < N; i++) { + double re = m_fft_out[i][0]; + double im = m_fft_out[i][1]; + m_fft_in[i][0] = (re * re + im * im) / static_cast(N); + m_fft_in[i][1] = 0.0; + } + fftw_execute(m_fft_plan2); + + // get results + QVector results(N / 2); + results.insert(0, QPointF(0, 0)); + for (int i = 1; i < N / 2; i++) { + const double re = abs(m_fft_out[i][0] / static_cast(N)); + results[i] = QPointF(i, re); + } + + // clean up + + return results; +#else + return QVector(); +#endif +} diff --git a/src/hobbits-plugins/analyzers/WidthFramer/widthframer.h b/src/hobbits-plugins/analyzers/WidthFramer/widthframer.h new file mode 100644 index 00000000..77d85afe --- /dev/null +++ b/src/hobbits-plugins/analyzers/WidthFramer/widthframer.h @@ -0,0 +1,77 @@ +#ifndef WIDTHFRAMER_H +#define WIDTHFRAMER_H + +#include "analyzerinterface.h" +#include "peakselector.h" + +#ifdef FFTW_AUTOCORRELATION +#include +#endif + +namespace Ui +{ +class WidthFramer; + +} + +class WidthFramer : public QObject, AnalyzerInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.AnalyzerInterface.WidthFramer") + Q_INTERFACES(AnalyzerInterface) + +public: + WidthFramer(); + + ~WidthFramer() override; + + QString getName() override; + void previewBits(QSharedPointer container) override; + void provideCallback(QSharedPointer pluginCallback) override; + + AnalyzerInterface* createDefaultAnalyzer() override; + void applyToWidget(QWidget *widget) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + QSharedPointer analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + + int getAutoWidth(QSharedPointer bits, int start, int end); + QVector autocorrelate(QSharedPointer bits); + +public slots: + void getPeak(QPointF); + + void setupScoreList(bool toggled = true); + +private slots: + void requestRun(); + void widthSelected(QModelIndex index); + +private: + Ui::WidthFramer *ui; + QSharedPointer m_pluginCallback; + + BitArray getFrameAlignBits(); + + PeakSelector *m_peakSelector; + + QVector m_autocorrelation; + + QStringListModel *m_listModel; + +#ifdef FFTW_AUTOCORRELATION + fftw_complex *m_fft_in; + fftw_complex *m_fft_out; + int m_fftSize; + fftw_plan m_fft_plan1; + fftw_plan m_fft_plan2; +#endif + +}; + +#endif // WIDTHFRAMER_H diff --git a/src/hobbits-plugins/analyzers/WidthFramer/widthframer.ui b/src/hobbits-plugins/analyzers/WidthFramer/widthframer.ui new file mode 100644 index 00000000..fa440a51 --- /dev/null +++ b/src/hobbits-plugins/analyzers/WidthFramer/widthframer.ui @@ -0,0 +1,115 @@ + + + WidthFramer + + + + 0 + 0 + 424 + 306 + + + + Form + + + + + + + + Width + + + + + + + border-color: rgb(250, 0, 0); + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + + Graph + + + + + List + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + All + + + false + + + + + + + Top 100 + + + true + + + + + + + + + true + + + + + + + + + + + + diff --git a/src/hobbits-plugins/analyzers/analyzers.pro b/src/hobbits-plugins/analyzers/analyzers.pro new file mode 100644 index 00000000..e4f9415c --- /dev/null +++ b/src/hobbits-plugins/analyzers/analyzers.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + FindAnalyzer \ + WidthFramer diff --git a/src/hobbits-plugins/displays/AsciiView/AsciiView.pro b/src/hobbits-plugins/displays/AsciiView/AsciiView.pro new file mode 100644 index 00000000..e2f61955 --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/AsciiView.pro @@ -0,0 +1,45 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-09-17T14:16:13.806Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = AsciiView +TEMPLATE = lib + +DEFINES += ASCIIVIEW_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += asciiview.cpp asciiviewwidget.cpp asciiviewcontrols.cpp + +HEADERS += asciiview.h asciiviewwidget.h asciiviewcontrols.h + +FORMS += asciiviewcontrols.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/displays + INSTALLS += target +} diff --git a/src/hobbits-plugins/displays/AsciiView/asciiview.cpp b/src/hobbits-plugins/displays/AsciiView/asciiview.cpp new file mode 100644 index 00000000..622e353c --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiview.cpp @@ -0,0 +1,42 @@ +#include "asciiview.h" + +AsciiView::AsciiView() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* AsciiView::createDefaultDisplay() +{ + return new AsciiView(); +} + +QString AsciiView::getName() +{ + return "ASCII"; +} + +QWidget* AsciiView::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* AsciiView::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void AsciiView::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new AsciiViewWidget(displayHandle, this); + m_controlsWidget = new AsciiViewControls(); + + connect(m_controlsWidget, SIGNAL(fontSizeChanged(int)), m_displayWidget, SLOT(setFontSize(int))); + connect(m_controlsWidget, SIGNAL(columnGroupingChanged(int)), m_displayWidget, SLOT(setColumnGrouping(int))); + connect(m_controlsWidget, SIGNAL(showHeadersChanged(bool)), m_displayWidget, SLOT(setShowHeaders(bool))); + } +} diff --git a/src/hobbits-plugins/displays/AsciiView/asciiview.h b/src/hobbits-plugins/displays/AsciiView/asciiview.h new file mode 100644 index 00000000..b72e9ef4 --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiview.h @@ -0,0 +1,32 @@ +#ifndef ASCIIVIEW_H +#define ASCIIVIEW_H + +#include "asciiviewcontrols.h" +#include "asciiviewwidget.h" +#include "displayinterface.h" + + +class AsciiView : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.AsciiView") + Q_INTERFACES(DisplayInterface) + +public: + AsciiView(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + + AsciiViewWidget *m_displayWidget; + AsciiViewControls *m_controlsWidget; +}; + +#endif // ASCIIVIEW_H diff --git a/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.cpp b/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.cpp new file mode 100644 index 00000000..3b1d364b --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.cpp @@ -0,0 +1,17 @@ +#include "asciiviewcontrols.h" +#include "ui_asciiviewcontrols.h" + + +AsciiViewControls::AsciiViewControls() : + ui(new Ui::AsciiViewControls()) +{ + ui->setupUi(this); + + connect(ui->sb_columnGrouping, SIGNAL(valueChanged(int)), this, SIGNAL(columnGroupingChanged(int))); + connect(ui->hs_fontSize, SIGNAL(valueChanged(int)), this, SIGNAL(fontSizeChanged(int))); +} + +void AsciiViewControls::on_cb_showHeaders_stateChanged(int state) +{ + emit showHeadersChanged(state != Qt::Unchecked); +} diff --git a/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.h b/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.h new file mode 100644 index 00000000..a489855a --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.h @@ -0,0 +1,30 @@ +#ifndef ASCIIVIEWCONTROLS_H +#define ASCIIVIEWCONTROLS_H + +#include + +namespace Ui +{ +class AsciiViewControls; +} + +class AsciiViewControls : public QWidget +{ + Q_OBJECT + +public: + AsciiViewControls(); + +signals: + void fontSizeChanged(int); + void columnGroupingChanged(int); + void showHeadersChanged(bool); + +private slots: + void on_cb_showHeaders_stateChanged(int state); + +private: + Ui::AsciiViewControls *ui; +}; + +#endif // ASCIIVIEWCONTROLS_H diff --git a/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.ui b/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.ui new file mode 100644 index 00000000..85779c34 --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiviewcontrols.ui @@ -0,0 +1,90 @@ + + + AsciiViewControls + + + + 0 + 0 + 581 + 56 + + + + + + + + + Column Grouping + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sb_columnGrouping + + + + + + + 1 + + + 10000 + + + 1 + + + + + + + Font Size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + hs_fontSize + + + + + + + 4 + + + 24 + + + 2 + + + 10 + + + Qt::Horizontal + + + + + + + Show Headers + + + true + + + + + + + + + + diff --git a/src/hobbits-plugins/displays/AsciiView/asciiviewwidget.cpp b/src/hobbits-plugins/displays/AsciiView/asciiviewwidget.cpp new file mode 100644 index 00000000..543a7d25 --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiviewwidget.cpp @@ -0,0 +1,40 @@ +#include "asciiviewwidget.h" + +#include +#include + +AsciiViewWidget::AsciiViewWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef) : + DisplayBaseText(displayHandle, pluginRef, 1) +{ + +} + +QString AsciiViewWidget::getDisplayChars(Frame frame, int offset) +{ + QString frameString = ""; + if (offset + 7 >= frame.size()) { + // partial char + frameString += '.'; + } + else { + char byte = 0; + for (int bit = 0; bit < 8; bit++) { + byte <<= 1; + if (frame.at(offset + bit)) { + byte |= 0x01; + } + } + if (isprint(byte)) { + frameString += byte; + } + else { + frameString += '.'; + } + } + return frameString; +} + +int AsciiViewWidget::bitsPerChar() +{ + return 8; +} diff --git a/src/hobbits-plugins/displays/AsciiView/asciiviewwidget.h b/src/hobbits-plugins/displays/AsciiView/asciiviewwidget.h new file mode 100644 index 00000000..123f94e1 --- /dev/null +++ b/src/hobbits-plugins/displays/AsciiView/asciiviewwidget.h @@ -0,0 +1,18 @@ +#ifndef ASCIIVIEWWIDGET_H +#define ASCIIVIEWWIDGET_H + +#include "displaybasetext.h" + +class AsciiViewWidget : public DisplayBaseText +{ + Q_OBJECT + +public: + AsciiViewWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef); + + QString getDisplayChars(Frame frame, int offset) override; + int bitsPerChar() override; + +}; + +#endif // ASCIIVIEWWIDGET_H diff --git a/src/hobbits-plugins/displays/BinaryView/BinaryView.pro b/src/hobbits-plugins/displays/BinaryView/BinaryView.pro new file mode 100644 index 00000000..ac87d843 --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/BinaryView.pro @@ -0,0 +1,45 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-09-17T14:03:36.448Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = BinaryView +TEMPLATE = lib + +DEFINES += BINARYVIEW_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += binaryview.cpp binaryviewwidget.cpp binaryviewcontrols.cpp + +HEADERS += binaryview.h binaryviewwidget.h binaryviewcontrols.h + +FORMS += binaryviewcontrols.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/displays + INSTALLS += target +} diff --git a/src/hobbits-plugins/displays/BinaryView/binaryview.cpp b/src/hobbits-plugins/displays/BinaryView/binaryview.cpp new file mode 100644 index 00000000..2b74e1d8 --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryview.cpp @@ -0,0 +1,42 @@ +#include "binaryview.h" + +BinaryView::BinaryView() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* BinaryView::createDefaultDisplay() +{ + return new BinaryView(); +} + +QString BinaryView::getName() +{ + return "Binary"; +} + +QWidget* BinaryView::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* BinaryView::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void BinaryView::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new BinaryViewWidget(displayHandle, this); + m_controlsWidget = new BinaryViewControls(); + + connect(m_controlsWidget, SIGNAL(fontSizeChanged(int)), m_displayWidget, SLOT(setFontSize(int))); + connect(m_controlsWidget, SIGNAL(columnGroupingChanged(int)), m_displayWidget, SLOT(setColumnGrouping(int))); + connect(m_controlsWidget, SIGNAL(showHeadersChanged(bool)), m_displayWidget, SLOT(setShowHeaders(bool))); + } +} diff --git a/src/hobbits-plugins/displays/BinaryView/binaryview.h b/src/hobbits-plugins/displays/BinaryView/binaryview.h new file mode 100644 index 00000000..c390c7aa --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryview.h @@ -0,0 +1,32 @@ +#ifndef BINARYVIEW_H +#define BINARYVIEW_H + +#include "binaryviewcontrols.h" +#include "binaryviewwidget.h" +#include "displayinterface.h" + + +class BinaryView : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.BinaryView") + Q_INTERFACES(DisplayInterface) + +public: + BinaryView(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + + BinaryViewWidget *m_displayWidget; + BinaryViewControls *m_controlsWidget; +}; + +#endif // BINARYVIEW_H diff --git a/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.cpp b/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.cpp new file mode 100644 index 00000000..4bbcdfd2 --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.cpp @@ -0,0 +1,17 @@ +#include "binaryviewcontrols.h" +#include "ui_binaryviewcontrols.h" + + +BinaryViewControls::BinaryViewControls() : + ui(new Ui::BinaryViewControls()) +{ + ui->setupUi(this); + + connect(ui->sb_columnGrouping, SIGNAL(valueChanged(int)), this, SIGNAL(columnGroupingChanged(int))); + connect(ui->hs_fontSize, SIGNAL(valueChanged(int)), this, SIGNAL(fontSizeChanged(int))); +} + +void BinaryViewControls::on_cb_showHeaders_stateChanged(int state) +{ + emit showHeadersChanged(state != Qt::Unchecked); +} diff --git a/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.h b/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.h new file mode 100644 index 00000000..3d4a02ea --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.h @@ -0,0 +1,30 @@ +#ifndef BINARYVIEWCONTROLS_H +#define BINARYVIEWCONTROLS_H + +#include + +namespace Ui +{ +class BinaryViewControls; +} + +class BinaryViewControls : public QWidget +{ + Q_OBJECT + +public: + BinaryViewControls(); + +signals: + void fontSizeChanged(int); + void columnGroupingChanged(int); + void showHeadersChanged(bool); + +private slots: + void on_cb_showHeaders_stateChanged(int state); + +private: + Ui::BinaryViewControls *ui; +}; + +#endif // BINARYVIEWCONTROLS_H diff --git a/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.ui b/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.ui new file mode 100644 index 00000000..34046e01 --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryviewcontrols.ui @@ -0,0 +1,90 @@ + + + BinaryViewControls + + + + 0 + 0 + 581 + 56 + + + + + + + + + Column Grouping + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sb_columnGrouping + + + + + + + 1 + + + 10000 + + + 4 + + + + + + + Font Size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + hs_fontSize + + + + + + + 4 + + + 24 + + + 2 + + + 10 + + + Qt::Horizontal + + + + + + + Show Headers + + + true + + + + + + + + + + diff --git a/src/hobbits-plugins/displays/BinaryView/binaryviewwidget.cpp b/src/hobbits-plugins/displays/BinaryView/binaryviewwidget.cpp new file mode 100644 index 00000000..ba6c72e7 --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryviewwidget.cpp @@ -0,0 +1,25 @@ +#include "binaryviewwidget.h" + +#include +#include + +BinaryViewWidget::BinaryViewWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef) : + DisplayBaseText(displayHandle, pluginRef, 4) +{ + +} + +QString BinaryViewWidget::getDisplayChars(Frame frame, int offset) +{ + if (frame.at(offset)) { + return "1"; + } + else { + return "0"; + } +} + +int BinaryViewWidget::bitsPerChar() +{ + return 1; +} diff --git a/src/hobbits-plugins/displays/BinaryView/binaryviewwidget.h b/src/hobbits-plugins/displays/BinaryView/binaryviewwidget.h new file mode 100644 index 00000000..2a94697e --- /dev/null +++ b/src/hobbits-plugins/displays/BinaryView/binaryviewwidget.h @@ -0,0 +1,18 @@ +#ifndef BINARYVIEWWIDGET_H +#define BINARYVIEWWIDGET_H + +#include "displaybasetext.h" + +class BinaryViewWidget : public DisplayBaseText +{ + Q_OBJECT + +public: + BinaryViewWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef); + + QString getDisplayChars(Frame frame, int offset) override; + int bitsPerChar() override; + +}; + +#endif // BINARYVIEWWIDGET_H diff --git a/src/hobbits-plugins/displays/BitRaster/BitRaster.pro b/src/hobbits-plugins/displays/BitRaster/BitRaster.pro new file mode 100644 index 00000000..236c59f2 --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/BitRaster.pro @@ -0,0 +1,45 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-09-17T12:58:25.493Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = BitRaster +TEMPLATE = lib + +DEFINES += BITRASTER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += bitraster.cpp bitrasterwidget.cpp bitrastercontrols.cpp + +HEADERS += bitraster.h bitrasterwidget.h bitrastercontrols.h + +FORMS += bitrastercontrols.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/displays + INSTALLS += target +} diff --git a/src/hobbits-plugins/displays/BitRaster/bitraster.cpp b/src/hobbits-plugins/displays/BitRaster/bitraster.cpp new file mode 100644 index 00000000..dc97493f --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitraster.cpp @@ -0,0 +1,41 @@ +#include "bitraster.h" + +BitRaster::BitRaster() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* BitRaster::createDefaultDisplay() +{ + return new BitRaster(); +} + +QString BitRaster::getName() +{ + return "Bit Raster"; +} + +QWidget* BitRaster::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* BitRaster::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void BitRaster::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new BitRasterWidget(displayHandle, this); + m_controlsWidget = new BitRasterControls(); + + connect(m_controlsWidget, SIGNAL(scaleSet(int)), m_displayWidget, SLOT(setScale(int))); + connect(m_controlsWidget, SIGNAL(showHeadersChanged(bool)), m_displayWidget, SLOT(setShowHeaders(bool))); + } +} diff --git a/src/hobbits-plugins/displays/BitRaster/bitraster.h b/src/hobbits-plugins/displays/BitRaster/bitraster.h new file mode 100644 index 00000000..78e5542e --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitraster.h @@ -0,0 +1,32 @@ +#ifndef BITRASTER_H +#define BITRASTER_H + +#include "bitrastercontrols.h" +#include "bitrasterwidget.h" +#include "displayinterface.h" + + +class BitRaster : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.BitRaster") + Q_INTERFACES(DisplayInterface) + +public: + BitRaster(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + + BitRasterWidget *m_displayWidget; + BitRasterControls *m_controlsWidget; +}; + +#endif // BITRASTER_H diff --git a/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.cpp b/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.cpp new file mode 100644 index 00000000..83d55670 --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.cpp @@ -0,0 +1,16 @@ +#include "bitrastercontrols.h" +#include "ui_bitrastercontrols.h" + + +BitRasterControls::BitRasterControls() : + ui(new Ui::BitRasterControls()) +{ + ui->setupUi(this); + + connect(ui->hs_scale, SIGNAL(valueChanged(int)), this, SIGNAL(scaleSet(int))); +} + +void BitRasterControls::on_cb_showHeaders_stateChanged(int state) +{ + emit showHeadersChanged(state != Qt::Unchecked); +} diff --git a/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.h b/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.h new file mode 100644 index 00000000..5af40186 --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.h @@ -0,0 +1,29 @@ +#ifndef BITRASTERCONTROLS_H +#define BITRASTERCONTROLS_H + +#include + +namespace Ui +{ +class BitRasterControls; +} + +class BitRasterControls : public QWidget +{ + Q_OBJECT + +public: + BitRasterControls(); + +signals: + void scaleSet(int); + void showHeadersChanged(bool); + +public slots: + void on_cb_showHeaders_stateChanged(int state); + +private: + Ui::BitRasterControls *ui; +}; + +#endif // BITRASTERCONTROLS_H diff --git a/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.ui b/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.ui new file mode 100644 index 00000000..7fc24d29 --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitrastercontrols.ui @@ -0,0 +1,58 @@ + + + BitRasterControls + + + + 0 + 0 + 685 + 57 + + + + + + + + + Zoom + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 16 + + + 3 + + + Qt::Horizontal + + + + + + + Show Headers + + + true + + + + + + + + + + diff --git a/src/hobbits-plugins/displays/BitRaster/bitrasterwidget.cpp b/src/hobbits-plugins/displays/BitRaster/bitrasterwidget.cpp new file mode 100644 index 00000000..6f31423d --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitrasterwidget.cpp @@ -0,0 +1,209 @@ +#include "bitrasterwidget.h" + +#include "settingsmanager.h" +#include +#include +#include +#include + +#include + +BitRasterWidget::BitRasterWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef) : + DisplayBase(displayHandle, pluginRef), + m_scale(3), + m_showFrameOffsets(true), + m_showColumnOffsets(true), + m_displayOffset(0, 0), + m_headerFontSize(0, 0) +{ + +} + +void BitRasterWidget::paintEvent(QPaintEvent*) +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + prepareHeaders(); + + int displayWidth = (this->width() - m_displayOffset.x()) / m_scale; + int displayHeight = (this->height() - m_displayOffset.y()) / m_scale; + + + QPainter painter(this); + + painter.save(); + if (m_showFrameOffsets) { + painter.fillRect(0, 0, m_displayOffset.x(), height(), Qt::lightGray); + } + if (m_showColumnOffsets) { + painter.fillRect(0, 0, width(), m_displayOffset.y(), Qt::lightGray); + } + + QFont font = QFont("monospace", 10); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + if (m_showFrameOffsets) { + int increment = qCeil(double(m_headerFontSize.height()) / double(m_scale)); + for (int i = 0; i <= displayHeight; i += increment) { + if (i + m_displayHandle->getFrameOffset() >= m_displayHandle->getContainer()->getFrames().size()) { + break; + } + + int yOffset = -2; + + if (increment > 1) { + yOffset = -1 * qRound(double(m_headerFontSize.height() - m_scale) / 2.0); + painter.fillRect( + m_displayOffset.x() - m_headerFontSize.width() / 2, + m_displayOffset.y() + (i * m_scale), + m_headerFontSize.width() / 2, + m_scale, + Qt::darkGray); + } + + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + m_headerFontSize.width() / 2, + m_displayOffset.y() + (i * m_scale) + yOffset, + m_displayOffset.x() - m_headerFontSize.width(), + m_headerFontSize.height(), + Qt::AlignRight | Qt::AlignTop, + QString("%1").arg(i + m_displayHandle->getFrameOffset())); + } + } + painter.restore(); + + painter.save(); + if (m_showColumnOffsets) { + int increment = qCeil(double(m_headerFontSize.height()) / double(m_scale)); + for (int i = 0; i < displayWidth; i += increment) { + if (i + m_displayHandle->getBitOffset() >= m_displayHandle->getContainer()->getMaxFrameWidth()) { + break; + } + + int yOffset = -2; + + if (increment > 1) { + yOffset = -1 * qRound(double(m_headerFontSize.height() - m_scale) / 2.0); + painter.fillRect( + m_displayOffset.x() + i * m_scale, + m_displayOffset.y() - m_headerFontSize.width() / 2, + m_scale, + m_headerFontSize.width() / 2, + Qt::darkGray); + } + + painter.save(); + painter.rotate(-90); + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + -1 * m_displayOffset.y() + m_headerFontSize.width() / 2, + m_displayOffset.x() + i * m_scale + yOffset, + m_displayOffset.y() - m_headerFontSize.width(), + m_headerFontSize.height(), + Qt::AlignLeft, + QString("%1").arg(i + m_displayHandle->getBitOffset())); + painter.restore(); + } + } + painter.restore(); + + QImage raster = m_displayHandle->getContainer()->getRasterImage( + m_displayHandle->getBitOffset(), + m_displayHandle->getFrameOffset(), + displayWidth, + displayHeight); + + painter.save(); + painter.translate(m_displayOffset); + painter.scale(m_scale, m_scale); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawImage(0, 0, raster); + + painter.resetTransform(); + painter.translate(m_displayOffset); + drawHighlights( + &painter, + m_scale, + m_scale, + m_displayHandle->getFrameOffset(), + m_displayHandle->getBitOffset(), + displayWidth, + displayHeight, + 1); + + painter.restore(); +} + +void BitRasterWidget::mouseMoveEvent(QMouseEvent *event) +{ + sendHoverUpdate(event, m_scale, m_scale, 1, 1, m_displayOffset); +} + +void BitRasterWidget::prepareHeaders() +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + QPainter painter(this); + QFont font = QFont("monospace", 10); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + QFontMetrics fontMetrics = QFontMetrics(font, painter.device()); + int fontWidth = fontMetrics.width("0"); + int fontHeight = fontMetrics.height(); + m_headerFontSize.setWidth(fontWidth); + m_headerFontSize.setHeight(fontHeight); + + if (m_showFrameOffsets) { + int totalFrames = m_displayHandle->getContainer()->getFrames().size(); + int maxChars = qFloor(log10(totalFrames)) + 1; + m_displayOffset.setX(qRound(fontWidth * (maxChars + 1.5))); + } + else { + m_displayOffset.setX(0); + } + + if (m_showColumnOffsets) { + int maxWidth = m_displayHandle->getContainer()->getMaxFrameWidth(); + int maxChars = qFloor(log10(maxWidth)) + 1; + m_displayOffset.setY(fontWidth * (maxChars + 1)); + } + else { + m_displayOffset.setY(0); + } +} + +void BitRasterWidget::adjustScrollbars() +{ + if (!m_displayHandle->getCurrentFocusDisplays().contains(m_pluginRef)) { + return; + } + DisplayBase::adjustScrollbars(); + + repaint(); + + if (m_displayHandle->getCurrentFocusDisplays().size() == 1) { + int w = (width() - m_displayOffset.x()) / m_scale; + int h = (height() - m_displayOffset.y()) / m_scale; + + m_displayHandle->getVScroll()->setPageStep(h); + m_displayHandle->getHScroll()->setPageStep(w); + } +} + +void BitRasterWidget::setScale(int scale) +{ + m_scale = scale; + this->adjustScrollbars(); +} + +void BitRasterWidget::setShowHeaders(bool showHeaders) +{ + m_showFrameOffsets = showHeaders; + m_showColumnOffsets = showHeaders; + adjustScrollbars(); +} diff --git a/src/hobbits-plugins/displays/BitRaster/bitrasterwidget.h b/src/hobbits-plugins/displays/BitRaster/bitrasterwidget.h new file mode 100644 index 00000000..62ffdea5 --- /dev/null +++ b/src/hobbits-plugins/displays/BitRaster/bitrasterwidget.h @@ -0,0 +1,35 @@ +#ifndef BITRASTERWIDGET_H +#define BITRASTERWIDGET_H + +#include "displaybase.h" + +class BitRasterWidget : public DisplayBase +{ + Q_OBJECT + +public: + BitRasterWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef); + + void paintEvent(QPaintEvent*) override; + void mouseMoveEvent(QMouseEvent *event) override; + +public slots: + void setScale(int scale); + void setShowHeaders(bool showHeaders); + +private: + void prepareHeaders(); + + int m_scale; + bool m_showFrameOffsets; + bool m_showColumnOffsets; + + QPoint m_displayOffset; + QSize m_headerFontSize; + +protected slots: + void adjustScrollbars() override; + +}; + +#endif // BITRASTERWIDGET_H diff --git a/src/hobbits-plugins/displays/ByteRaster/ByteRaster.pro b/src/hobbits-plugins/displays/ByteRaster/ByteRaster.pro new file mode 100644 index 00000000..fa857900 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/ByteRaster.pro @@ -0,0 +1,45 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-10-02T23:44:51.176Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = ByteRaster +TEMPLATE = lib + +DEFINES += BYTERASTER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += byteraster.cpp byterasterwidget.cpp byterastercontrols.cpp + +HEADERS += byteraster.h byterasterwidget.h byterastercontrols.h + +FORMS += byterastercontrols.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = /opt/hobbits/plugins/displays + INSTALLS += target +} diff --git a/src/hobbits-plugins/displays/ByteRaster/byteraster.cpp b/src/hobbits-plugins/displays/ByteRaster/byteraster.cpp new file mode 100644 index 00000000..8a7530a4 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byteraster.cpp @@ -0,0 +1,41 @@ +#include "byteraster.h" + +ByteRaster::ByteRaster() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* ByteRaster::createDefaultDisplay() +{ + return new ByteRaster(); +} + +QString ByteRaster::getName() +{ + return "Byte Raster"; +} + +QWidget* ByteRaster::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* ByteRaster::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void ByteRaster::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new ByteRasterWidget(displayHandle, this); + m_controlsWidget = new ByteRasterControls(); + + connect(m_controlsWidget, SIGNAL(scaleSet(int)), m_displayWidget, SLOT(setScale(int))); + connect(m_controlsWidget, SIGNAL(showHeadersChanged(bool)), m_displayWidget, SLOT(setShowHeaders(bool))); + } +} diff --git a/src/hobbits-plugins/displays/ByteRaster/byteraster.h b/src/hobbits-plugins/displays/ByteRaster/byteraster.h new file mode 100644 index 00000000..ceedef8b --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byteraster.h @@ -0,0 +1,32 @@ +#ifndef BYTERASTER_H +#define BYTERASTER_H + +#include "byterastercontrols.h" +#include "byterasterwidget.h" +#include "displayinterface.h" + + +class ByteRaster : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.ByteRaster") + Q_INTERFACES(DisplayInterface) + +public: + ByteRaster(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + + ByteRasterWidget *m_displayWidget; + ByteRasterControls *m_controlsWidget; +}; + +#endif // BYTERASTER_H diff --git a/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.cpp b/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.cpp new file mode 100644 index 00000000..bd516b74 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.cpp @@ -0,0 +1,16 @@ +#include "byterastercontrols.h" +#include "ui_byterastercontrols.h" + + +ByteRasterControls::ByteRasterControls() : + ui(new Ui::ByteRasterControls()) +{ + ui->setupUi(this); + + connect(ui->hs_scale, SIGNAL(valueChanged(int)), this, SIGNAL(scaleSet(int))); +} + +void ByteRasterControls::on_cb_showHeaders_stateChanged(int state) +{ + emit showHeadersChanged(state != Qt::Unchecked); +} diff --git a/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.h b/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.h new file mode 100644 index 00000000..5890cc52 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.h @@ -0,0 +1,29 @@ +#ifndef BYTERASTERCONTROLS_H +#define BYTERASTERCONTROLS_H + +#include + +namespace Ui +{ +class ByteRasterControls; +} + +class ByteRasterControls : public QWidget +{ + Q_OBJECT + +public: + ByteRasterControls(); + +signals: + void scaleSet(int); + void showHeadersChanged(bool); + +public slots: + void on_cb_showHeaders_stateChanged(int state); + +private: + Ui::ByteRasterControls *ui; +}; + +#endif // BYTERASTERCONTROLS_H diff --git a/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.ui b/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.ui new file mode 100644 index 00000000..aa26e478 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byterastercontrols.ui @@ -0,0 +1,58 @@ + + + ByteRasterControls + + + + 0 + 0 + 559 + 39 + + + + + + + + + Zoom + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 16 + + + 3 + + + Qt::Horizontal + + + + + + + Show Headers + + + true + + + + + + + + + + diff --git a/src/hobbits-plugins/displays/ByteRaster/byterasterwidget.cpp b/src/hobbits-plugins/displays/ByteRaster/byterasterwidget.cpp new file mode 100644 index 00000000..6178e1a7 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byterasterwidget.cpp @@ -0,0 +1,208 @@ +#include "byterasterwidget.h" + +#include "settingsmanager.h" +#include +#include +#include +#include + +ByteRasterWidget::ByteRasterWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef) : + DisplayBase(displayHandle, pluginRef), + m_scale(3), + m_showFrameOffsets(true), + m_showColumnOffsets(true), + m_displayOffset(0, 0), + m_headerFontSize(0, 0) +{ + +} + +void ByteRasterWidget::paintEvent(QPaintEvent*) +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + prepareHeaders(); + + int displayWidth = (this->width() - m_displayOffset.x()) / m_scale; + int displayHeight = (this->height() - m_displayOffset.y()) / m_scale; + + + QPainter painter(this); + + painter.save(); + if (m_showFrameOffsets) { + painter.fillRect(0, 0, m_displayOffset.x(), height(), Qt::lightGray); + } + if (m_showColumnOffsets) { + painter.fillRect(0, 0, width(), m_displayOffset.y(), Qt::lightGray); + } + + QFont font = QFont("monospace", 10); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + if (m_showFrameOffsets) { + int increment = qCeil(double(m_headerFontSize.height()) / double(m_scale)); + for (int i = 0; i <= displayHeight; i += increment) { + if (i + m_displayHandle->getFrameOffset() >= m_displayHandle->getContainer()->getFrames().size()) { + break; + } + + int yOffset = -2; + + if (increment > 1) { + yOffset = -1 * qRound(double(m_headerFontSize.height() - m_scale) / 2.0); + painter.fillRect( + m_displayOffset.x() - m_headerFontSize.width() / 2, + m_displayOffset.y() + (i * m_scale), + m_headerFontSize.width() / 2, + m_scale, + Qt::darkGray); + } + + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + m_headerFontSize.width() / 2, + m_displayOffset.y() + (i * m_scale) + yOffset, + m_displayOffset.x() - m_headerFontSize.width(), + m_headerFontSize.height(), + Qt::AlignRight | Qt::AlignTop, + QString("%1").arg(i + m_displayHandle->getFrameOffset())); + } + } + painter.restore(); + + painter.save(); + if (m_showColumnOffsets) { + int increment = qCeil(double(m_headerFontSize.height()) / double(m_scale)); + for (int i = 0; i < displayWidth; i += increment) { + if (i * 8 + m_displayHandle->getBitOffset() >= m_displayHandle->getContainer()->getMaxFrameWidth()) { + break; + } + + int yOffset = -2; + + if (increment > 1) { + yOffset = -1 * qRound(double(m_headerFontSize.height() - m_scale) / 2.0); + painter.fillRect( + m_displayOffset.x() + i * m_scale, + m_displayOffset.y() - m_headerFontSize.width() / 2, + m_scale, + m_headerFontSize.width() / 2, + Qt::darkGray); + } + + painter.save(); + painter.rotate(-90); + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + -1 * m_displayOffset.y() + m_headerFontSize.width() / 2, + m_displayOffset.x() + i * m_scale + yOffset, + m_displayOffset.y() - m_headerFontSize.width(), + m_headerFontSize.height(), + Qt::AlignLeft, + QString("%1").arg(i * 8 + m_displayHandle->getBitOffset())); + painter.restore(); + } + } + painter.restore(); + + QImage raster = m_displayHandle->getContainer()->getByteRasterImage( + m_displayHandle->getBitOffset(), + m_displayHandle->getFrameOffset(), + displayWidth, + displayHeight); + + painter.save(); + painter.translate(m_displayOffset); + painter.scale(m_scale, m_scale); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawImage(0, 0, raster); + + int byteOffset = m_displayHandle->getBitOffset() / 8; + int adjustedBitOffset = byteOffset * 8; + + painter.resetTransform(); + painter.translate(m_displayOffset); + drawHighlights( + &painter, + double(m_scale) / 8.0, + m_scale, + m_displayHandle->getFrameOffset(), + adjustedBitOffset, + displayWidth * 8, + displayHeight, + 1); + + painter.restore(); +} + +void ByteRasterWidget::mouseMoveEvent(QMouseEvent *event) +{ + sendHoverUpdate(event, m_scale, m_scale, 1, 8, m_displayOffset); +} + +void ByteRasterWidget::prepareHeaders() +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + QPainter painter(this); + QFont font = QFont("monospace", 10); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + QFontMetrics fontMetrics = QFontMetrics(font, painter.device()); + int fontWidth = fontMetrics.width("0"); + int fontHeight = fontMetrics.height(); + m_headerFontSize.setWidth(fontWidth); + m_headerFontSize.setHeight(fontHeight); + + if (m_showFrameOffsets) { + int totalFrames = m_displayHandle->getContainer()->getFrames().size(); + int maxChars = qFloor(log10(totalFrames)) + 1; + m_displayOffset.setX(qRound(fontWidth * (maxChars + 1.5))); + } + else { + m_displayOffset.setX(0); + } + + if (m_showColumnOffsets) { + int maxWidth = m_displayHandle->getContainer()->getMaxFrameWidth(); + int maxChars = qFloor(log10(maxWidth)) + 1; + m_displayOffset.setY(fontWidth * (maxChars + 1)); + } + else { + m_displayOffset.setY(0); + } +} + +void ByteRasterWidget::adjustScrollbars() +{ + if (!m_displayHandle->getCurrentFocusDisplays().contains(m_pluginRef)) { + return; + } + DisplayBase::adjustScrollbars(); + + repaint(); + + if (m_displayHandle->getCurrentFocusDisplays().size() == 1) { + m_displayHandle->getHScroll()->setSingleStep(8); + m_displayHandle->getVScroll()->setPageStep(8 * (height() - m_displayOffset.y()) / m_scale); + m_displayHandle->getHScroll()->setPageStep(8 * (width() - m_displayOffset.x()) / m_scale); + } +} + +void ByteRasterWidget::setScale(int scale) +{ + m_scale = scale; + this->adjustScrollbars(); +} + +void ByteRasterWidget::setShowHeaders(bool showHeaders) +{ + m_showFrameOffsets = showHeaders; + m_showColumnOffsets = showHeaders; + adjustScrollbars(); +} diff --git a/src/hobbits-plugins/displays/ByteRaster/byterasterwidget.h b/src/hobbits-plugins/displays/ByteRaster/byterasterwidget.h new file mode 100644 index 00000000..f90e0895 --- /dev/null +++ b/src/hobbits-plugins/displays/ByteRaster/byterasterwidget.h @@ -0,0 +1,35 @@ +#ifndef BYTERASTERWIDGET_H +#define BYTERASTERWIDGET_H + +#include "displaybase.h" + +class ByteRasterWidget : public DisplayBase +{ + Q_OBJECT + +public: + ByteRasterWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef); + + void paintEvent(QPaintEvent*) override; + void mouseMoveEvent(QMouseEvent *event) override; + +public slots: + void setScale(int scale); + void setShowHeaders(bool showHeaders); + +private: + void prepareHeaders(); + + int m_scale; + bool m_showFrameOffsets; + bool m_showColumnOffsets; + + QPoint m_displayOffset; + QSize m_headerFontSize; + +protected slots: + void adjustScrollbars() override; + +}; + +#endif // BYTERASTERWIDGET_H diff --git a/src/hobbits-plugins/displays/HexView/HexView.pro b/src/hobbits-plugins/displays/HexView/HexView.pro new file mode 100644 index 00000000..e20b4727 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/HexView.pro @@ -0,0 +1,45 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-09-17T13:30:48.235Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = HexView +TEMPLATE = lib + +DEFINES += HEXVIEW_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += hexview.cpp hexviewwidget.cpp hexviewcontrols.cpp + +HEADERS += hexview.h hexviewwidget.h hexviewcontrols.h + +FORMS += hexviewcontrols.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/displays + INSTALLS += target +} diff --git a/src/hobbits-plugins/displays/HexView/hexview.cpp b/src/hobbits-plugins/displays/HexView/hexview.cpp new file mode 100644 index 00000000..da8fdba0 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexview.cpp @@ -0,0 +1,42 @@ +#include "hexview.h" + +HexView::HexView() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* HexView::createDefaultDisplay() +{ + return new HexView(); +} + +QString HexView::getName() +{ + return "Hex"; +} + +QWidget* HexView::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* HexView::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void HexView::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new HexViewWidget(displayHandle, this); + m_controlsWidget = new HexViewControls(); + + connect(m_controlsWidget, SIGNAL(fontSizeChanged(int)), m_displayWidget, SLOT(setFontSize(int))); + connect(m_controlsWidget, SIGNAL(columnGroupingChanged(int)), m_displayWidget, SLOT(setColumnGrouping(int))); + connect(m_controlsWidget, SIGNAL(showHeadersChanged(bool)), m_displayWidget, SLOT(setShowHeaders(bool))); + } +} diff --git a/src/hobbits-plugins/displays/HexView/hexview.h b/src/hobbits-plugins/displays/HexView/hexview.h new file mode 100644 index 00000000..6361e11d --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexview.h @@ -0,0 +1,32 @@ +#ifndef HEXVIEW_H +#define HEXVIEW_H + +#include "displayinterface.h" +#include "hexviewcontrols.h" +#include "hexviewwidget.h" + + +class HexView : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.HexView") + Q_INTERFACES(DisplayInterface) + +public: + HexView(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + + HexViewWidget *m_displayWidget; + HexViewControls *m_controlsWidget; +}; + +#endif // HEXVIEW_H diff --git a/src/hobbits-plugins/displays/HexView/hexviewcontrols.cpp b/src/hobbits-plugins/displays/HexView/hexviewcontrols.cpp new file mode 100644 index 00000000..3e5c2252 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexviewcontrols.cpp @@ -0,0 +1,17 @@ +#include "hexviewcontrols.h" +#include "ui_hexviewcontrols.h" + + +HexViewControls::HexViewControls() : + ui(new Ui::HexViewControls()) +{ + ui->setupUi(this); + + connect(ui->sb_columnGrouping, SIGNAL(valueChanged(int)), this, SIGNAL(columnGroupingChanged(int))); + connect(ui->hs_fontSize, SIGNAL(valueChanged(int)), this, SIGNAL(fontSizeChanged(int))); +} + +void HexViewControls::on_cb_showHeaders_stateChanged(int state) +{ + emit showHeadersChanged(state != Qt::Unchecked); +} diff --git a/src/hobbits-plugins/displays/HexView/hexviewcontrols.h b/src/hobbits-plugins/displays/HexView/hexviewcontrols.h new file mode 100644 index 00000000..3a5c96b2 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexviewcontrols.h @@ -0,0 +1,30 @@ +#ifndef HEXVIEWCONTROLS_H +#define HEXVIEWCONTROLS_H + +#include + +namespace Ui +{ +class HexViewControls; +} + +class HexViewControls : public QWidget +{ + Q_OBJECT + +public: + HexViewControls(); + +signals: + void fontSizeChanged(int); + void columnGroupingChanged(int); + void showHeadersChanged(bool); + +private slots: + void on_cb_showHeaders_stateChanged(int state); + +private: + Ui::HexViewControls *ui; +}; + +#endif // HEXVIEWCONTROLS_H diff --git a/src/hobbits-plugins/displays/HexView/hexviewcontrols.ui b/src/hobbits-plugins/displays/HexView/hexviewcontrols.ui new file mode 100644 index 00000000..f962b9d0 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexviewcontrols.ui @@ -0,0 +1,94 @@ + + + HexViewControls + + + + 0 + 0 + 784 + 56 + + + + + + + + + Column Grouping + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sb_columnGrouping + + + + + + + 1 + + + 10000 + + + 2 + + + + + + + Font Size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + hs_fontSize + + + + + + + 4 + + + 24 + + + 2 + + + 10 + + + Qt::Horizontal + + + + + + + Show Headers + + + true + + + + + + + + + sb_columnGrouping + hs_fontSize + + + + diff --git a/src/hobbits-plugins/displays/HexView/hexviewwidget.cpp b/src/hobbits-plugins/displays/HexView/hexviewwidget.cpp new file mode 100644 index 00000000..3d168e30 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexviewwidget.cpp @@ -0,0 +1,44 @@ +#include "hexviewwidget.h" + +#include +#include + +HexViewWidget::HexViewWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef) : + DisplayBaseText(displayHandle, pluginRef, 2) +{ + +} + +QString HexViewWidget::getDisplayChars(Frame frame, int offset) +{ + QString frameString = ""; + if (offset + 3 >= frame.size()) { + // partial nibble + for (int bit = 0; bit < 4 && bit + offset < frame.size(); bit++) { + if (frame.at(offset + bit)) { + frameString += '.'; + } + else { + frameString += 'o'; + } + } + } + else { + int nibble = 0; + for (int bit = 0; bit < 4; bit++) { + nibble <<= 1; + if (frame.at(offset + bit)) { + nibble |= 0x01; + } + } + QString nibString; + nibString.setNum(nibble, 16); + frameString += nibString; + } + return frameString; +} + +int HexViewWidget::bitsPerChar() +{ + return 4; +} diff --git a/src/hobbits-plugins/displays/HexView/hexviewwidget.h b/src/hobbits-plugins/displays/HexView/hexviewwidget.h new file mode 100644 index 00000000..0c384884 --- /dev/null +++ b/src/hobbits-plugins/displays/HexView/hexviewwidget.h @@ -0,0 +1,18 @@ +#ifndef HEXVIEWWIDGET_H +#define HEXVIEWWIDGET_H + +#include "displaybasetext.h" + +class HexViewWidget : public DisplayBaseText +{ + Q_OBJECT + +public: + HexViewWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef); + + QString getDisplayChars(Frame frame, int offset) override; + int bitsPerChar() override; + +}; + +#endif // HEXVIEWWIDGET_H diff --git a/src/hobbits-plugins/displays/SymbolRaster/SymbolRaster.pro b/src/hobbits-plugins/displays/SymbolRaster/SymbolRaster.pro new file mode 100644 index 00000000..5054b094 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/SymbolRaster.pro @@ -0,0 +1,49 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-11-05T17:25:58.922Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = SymbolRaster +TEMPLATE = lib + +DEFINES += SYMBOLRASTER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += symbolraster.cpp symbolrasterwidget.cpp symbolrastercontrols.cpp \ + colordialogdelegate.cpp \ + colormapmodel.cpp + +HEADERS += symbolraster.h symbolrasterwidget.h symbolrastercontrols.h \ + colordialogdelegate.h \ + colormapmodel.h + +FORMS += symbolrastercontrols.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/displays + INSTALLS += target +} diff --git a/src/hobbits-plugins/displays/SymbolRaster/colordialogdelegate.cpp b/src/hobbits-plugins/displays/SymbolRaster/colordialogdelegate.cpp new file mode 100644 index 00000000..cd07d8d9 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/colordialogdelegate.cpp @@ -0,0 +1,72 @@ +#include "colordialogdelegate.h" +#include +#include +#include + +void ColorDialogDelegate::paint( + QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (index.data().canConvert()) { + QColor color = qvariant_cast(index.data()); + + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + } + + painter->setPen(Qt::transparent); + painter->setBrush(Qt::white); + painter->drawRect(option.rect.x() + 2, option.rect.y() + 2, option.rect.width() - 4, option.rect.height() - 4); + painter->setBrush(color); + painter->drawRect(option.rect.x() + 2, option.rect.y() + 2, option.rect.width() - 4, option.rect.height() - 4); + } + else { + QStyledItemDelegate::paint(painter, option, index); + } +} + +QSize ColorDialogDelegate::sizeHint( + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + return QStyledItemDelegate::sizeHint(option, index); +} + +QWidget* ColorDialogDelegate::createEditor( + QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + if (index.data().canConvert()) { + QColorDialog *dialog = new QColorDialog(parent); + return dialog; + } + return QStyledItemDelegate::createEditor(parent, option, index); +} + +void ColorDialogDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (index.data().canConvert()) { + QColor color = qvariant_cast(index.data()); + QColorDialog *dialog = qobject_cast(editor); + dialog->setCurrentColor(color); + } + else { + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void ColorDialogDelegate::setModelData( + QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const +{ + if (index.data().canConvert()) { + QColorDialog *dialog = qobject_cast(editor); + model->setData(index, dialog->currentColor()); + } + else { + QStyledItemDelegate::setModelData(editor, model, index); + } +} diff --git a/src/hobbits-plugins/displays/SymbolRaster/colordialogdelegate.h b/src/hobbits-plugins/displays/SymbolRaster/colordialogdelegate.h new file mode 100644 index 00000000..047f5354 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/colordialogdelegate.h @@ -0,0 +1,33 @@ +#ifndef COLORDIALOGDELEGATE_H +#define COLORDIALOGDELEGATE_H + +#include + +class ColorDialogDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + using QStyledItemDelegate::QStyledItemDelegate; + + void paint( + QPainter *painter, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QSize sizeHint( + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QWidget* createEditor( + QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData( + QWidget *editor, + QAbstractItemModel *model, + const QModelIndex &index) const override; + +private slots: +}; + +#endif // COLORDIALOGDELEGATE_H diff --git a/src/hobbits-plugins/displays/SymbolRaster/colormapmodel.cpp b/src/hobbits-plugins/displays/SymbolRaster/colormapmodel.cpp new file mode 100644 index 00000000..4bca8987 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/colormapmodel.cpp @@ -0,0 +1,181 @@ +#include "colormapmodel.h" +#include + +#include "settingsmanager.h" +#include +#include +#include + +const QString COLOR[8] = { + "symbol_raster_color_1", + "symbol_raster_color_2", + "symbol_raster_color_3", + "symbol_raster_color_4", + "symbol_raster_color_5", + "symbol_raster_color_6", + "symbol_raster_color_7", + "symbol_raster_color_8" +}; + +ColorMapModel::ColorMapModel(QObject *parent) : + QAbstractTableModel(parent), + m_remapLength(2) +{ + QList> defaultColors = { + {COLOR[0], "#d1bbd7"}, + {COLOR[1], "#882e72"}, + {COLOR[2], "#5289c7"}, + {COLOR[3], "#4eb265"}, + {COLOR[4], "#cae0ab"}, + {COLOR[5], "#f6c141"}, + {COLOR[6], "#e8601c"}, + {COLOR[7], "#ec050c"} + }; + + for (auto pair : defaultColors) { + QVariant colorVar = SettingsManager::getInstance().getPrivateSetting(pair.first); + QColor c; + if (colorVar.isNull() || !colorVar.canConvert()) { + c.setNamedColor(pair.second); + SettingsManager::getInstance().setPrivateSetting(pair.first, QVariant(c)); + m_defaultColors.append(c); + } + else { + m_defaultColors.append(colorVar.value()); + } + } + + initializeMappings(); +} + +void ColorMapModel::initializeMappings() +{ + + beginRemoveRows(QModelIndex(), 0, m_mappings.length() - 1); + m_mappings.clear(); + endRemoveRows(); + + int symbolsToMap = (1 << m_remapLength); + + beginInsertRows(QModelIndex(), 0, symbolsToMap - 1); + for (int i = 0; i < symbolsToMap; i++) { + QString mappingString = ""; + for (int bit = m_remapLength - 1; bit >= 0; bit--) { + if (i & (1 << bit)) { + mappingString += "1"; + } + else { + mappingString += "0"; + } + } + m_mappings.append(QPair(mappingString, m_defaultColors.at(i % m_defaultColors.size()))); + } + endInsertRows(); +} + +void ColorMapModel::setRemapLength(int length) +{ + this->m_remapLength = length; + initializeMappings(); +} + +QList> ColorMapModel::getMappings() +{ + return m_mappings; +} + +QVariant ColorMapModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Orientation::Horizontal && role == Qt::DisplayRole) { + if (section == 0) { + return QVariant("Symbol"); + } + else if (section == 1) { + return QVariant("Color"); + } + } + + return QVariant(); +} + +int ColorMapModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_mappings.size(); +} + +int ColorMapModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return 2; +} + +QVariant ColorMapModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + if (index.column() == 0) { + return m_mappings.at(index.row()).first; + } + else if (index.column() == 1) { + return m_mappings.at(index.row()).second; + } + } + else if (role == Qt::FontRole) { + return QFont("monospace", 12); + } + else if (role == Qt::BackgroundRole) { + return m_mappings.at(index.row()).second; + } + else if (role == Qt::ForegroundRole) { + QColor color = m_mappings.at(index.row()).second; + if (color.lightnessF() > 0.5) { + return QColor(Qt::black); + } + else { + return QColor(Qt::white); + } + } + return QVariant(); +} + +bool ColorMapModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (data(index, role) != value && index.column() == 1) { + if (!value.canConvert()) { + return false; + } + m_mappings.replace( + index.row(), + QPair(m_mappings.at(index.row()).first, value.value())); + + SettingsManager::getInstance().setPrivateSetting(COLOR[index.row() % 8], value); + + emit dataChanged(index, index, QVector() << role); + return true; + } + return false; +} + +Qt::ItemFlags ColorMapModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + if (index.column() == 1) { + return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + else { + return Qt::NoItemFlags; + } +} diff --git a/src/hobbits-plugins/displays/SymbolRaster/colormapmodel.h b/src/hobbits-plugins/displays/SymbolRaster/colormapmodel.h new file mode 100644 index 00000000..660e5ddf --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/colormapmodel.h @@ -0,0 +1,46 @@ +#ifndef COLORMAPMODEL_H +#define COLORMAPMODEL_H + +#include +#include + +class ColorMapModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit ColorMapModel(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData( + const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + QList> getMappings(); + +public slots: + void setRemapLength(int length); + +private: + void initializeMappings(); + + int m_remapLength; + QList> m_mappings; + QList m_defaultColors; + +}; + + +#endif // COLORMAPMODEL_H diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolraster.cpp b/src/hobbits-plugins/displays/SymbolRaster/symbolraster.cpp new file mode 100644 index 00000000..4539d274 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolraster.cpp @@ -0,0 +1,48 @@ +#include "symbolraster.h" + +SymbolRaster::SymbolRaster() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* SymbolRaster::createDefaultDisplay() +{ + return new SymbolRaster(); +} + +QString SymbolRaster::getName() +{ + return "Symbol Raster"; +} + +QWidget* SymbolRaster::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* SymbolRaster::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void SymbolRaster::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new SymbolRasterWidget(displayHandle, this); + m_controlsWidget = new SymbolRasterControls(); + + connect(m_controlsWidget, SIGNAL(scaleSet(int)), m_displayWidget, SLOT(setScale(int))); + connect(m_controlsWidget, SIGNAL(showHeadersChanged(bool)), m_displayWidget, SLOT(setShowHeaders(bool))); + connect( + m_controlsWidget, + SIGNAL(colorMappingChanged(QList>)), + m_displayWidget, + SLOT(setColorMapping(QList>))); + + m_controlsWidget->updateColorMap(); + } +} diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolraster.h b/src/hobbits-plugins/displays/SymbolRaster/symbolraster.h new file mode 100644 index 00000000..e9bdddee --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolraster.h @@ -0,0 +1,32 @@ +#ifndef SYMBOLRASTER_H +#define SYMBOLRASTER_H + +#include "displayinterface.h" +#include "symbolrastercontrols.h" +#include "symbolrasterwidget.h" + + +class SymbolRaster : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.SymbolRaster") + Q_INTERFACES(DisplayInterface) + +public: + SymbolRaster(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + + SymbolRasterWidget *m_displayWidget; + SymbolRasterControls *m_controlsWidget; +}; + +#endif // SYMBOLRASTER_H diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.cpp b/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.cpp new file mode 100644 index 00000000..02bf4c83 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.cpp @@ -0,0 +1,36 @@ +#include "colordialogdelegate.h" +#include "symbolrastercontrols.h" +#include "ui_symbolrastercontrols.h" + +SymbolRasterControls::SymbolRasterControls() : + ui(new Ui::SymbolRasterControls()), + m_colorMapModel(new ColorMapModel()) +{ + ui->setupUi(this); + m_colorMapModel->setRemapLength(ui->sb_bitLength->value()); + + ui->tv_mappings->setItemDelegateForColumn(1, new ColorDialogDelegate); + ui->tv_mappings->setEditTriggers( + QAbstractItemView::EditTrigger::SelectedClicked | QAbstractItemView::EditTrigger::DoubleClicked + | QAbstractItemView::EditTrigger::AnyKeyPressed); + ui->tv_mappings->setModel(m_colorMapModel); + connect(ui->sb_bitLength, SIGNAL(valueChanged(int)), m_colorMapModel, SLOT(setRemapLength(int))); + connect( + m_colorMapModel, + SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&,const QVector&)), + this, + SLOT(updateColorMap())); + connect(m_colorMapModel, SIGNAL(rowsInserted(const QModelIndex&,int,int)), this, SLOT(updateColorMap())); + + connect(ui->hs_scale, SIGNAL(valueChanged(int)), this, SIGNAL(scaleSet(int))); +} + +void SymbolRasterControls::updateColorMap() +{ + emit colorMappingChanged(m_colorMapModel->getMappings()); +} + +void SymbolRasterControls::on_cb_showHeaders_stateChanged(int state) +{ + emit showHeadersChanged(state != Qt::Unchecked); +} diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.h b/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.h new file mode 100644 index 00000000..015b4390 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.h @@ -0,0 +1,33 @@ +#ifndef SYMBOLRASTERCONTROLS_H +#define SYMBOLRASTERCONTROLS_H + +#include "colormapmodel.h" +#include + +namespace Ui +{ +class SymbolRasterControls; +} + +class SymbolRasterControls : public QWidget +{ + Q_OBJECT + +public: + SymbolRasterControls(); + +public slots: + void on_cb_showHeaders_stateChanged(int state); + void updateColorMap(); + +signals: + void scaleSet(int); + void showHeadersChanged(bool); + void colorMappingChanged(QList>); + +private: + Ui::SymbolRasterControls *ui; + ColorMapModel *m_colorMapModel; +}; + +#endif // SYMBOLRASTERCONTROLS_H diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.ui b/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.ui new file mode 100644 index 00000000..8b09ff76 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolrastercontrols.ui @@ -0,0 +1,119 @@ + + + SymbolRasterControls + + + + 0 + 0 + 953 + 182 + + + + + 16777215 + 220 + + + + + + + + + Zoom + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 1 + + + 16 + + + 3 + + + Qt::Horizontal + + + + + + + Show Headers + + + true + + + + + + + + + + + + 16777215 + 16777215 + + + + + + + + + + + + Symbol Length: + + + + + + + 1 + + + 8 + + + 2 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolrasterwidget.cpp b/src/hobbits-plugins/displays/SymbolRaster/symbolrasterwidget.cpp new file mode 100644 index 00000000..ff092a1c --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolrasterwidget.cpp @@ -0,0 +1,275 @@ +#include "symbolrasterwidget.h" + +#include "settingsmanager.h" +#include +#include +#include + +SymbolRasterWidget::SymbolRasterWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef) : + DisplayBase(displayHandle, pluginRef), + m_scale(3), + m_showFrameOffsets(true), + m_showColumnOffsets(true), + m_displayOffset(0, 0), + m_headerFontSize(0, 0), + m_symbolLength(1) +{ + +} + +QImage SymbolRasterWidget::getSymbolMapImage( + QSharedPointer bitContainer, + int x, + int y, + int w, + int h) const +{ + QImage raster(w, h, QImage::Format_ARGB32); + raster.fill(qRgba(0x66, 0x66, 0x66, 0x66)); + + int frameOffset = y; + int symbolOffset = x / m_symbolLength; + int bitOffset = symbolOffset * m_symbolLength; + + if (frameOffset < 0) { + return raster; + } + + for (int i = 0; i < h; i++) { + if (i + frameOffset >= bitContainer->getFrames().size()) { + break; + } + Frame frame = bitContainer->getFrames().at(i + frameOffset); + + for (int ii = 0; ii < w * m_symbolLength; ii += m_symbolLength) { + if (ii + bitOffset + m_symbolLength > frame.size()) { + break; + } + + quint64 symbolVal = 0; + for (int bit = m_symbolLength - 1; bit >= 0; bit--) { + if (frame.at(ii + bit + bitOffset)) { + symbolVal |= 0x01 << (m_symbolLength - bit - 1); + } + } + + if (m_colorMapping.contains(symbolVal)) { + raster.setPixel(ii / m_symbolLength, i, m_colorMapping.value(symbolVal).rgba()); + } + + } + } + return raster; +} + +void SymbolRasterWidget::paintEvent(QPaintEvent*) +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + prepareHeaders(); + + int displayWidth = (this->width() - m_displayOffset.x()) / m_scale; + int displayHeight = (this->height() - m_displayOffset.y()) / m_scale; + + + QPainter painter(this); + + painter.save(); + if (m_showFrameOffsets) { + painter.fillRect(0, 0, m_displayOffset.x(), height(), Qt::lightGray); + } + if (m_showColumnOffsets) { + painter.fillRect(0, 0, width(), m_displayOffset.y(), Qt::lightGray); + } + + QFont font = QFont("monospace", 10); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + if (m_showFrameOffsets) { + int increment = qCeil(double(m_headerFontSize.height()) / double(m_scale)); + for (int i = 0; i <= displayHeight; i += increment) { + if (i + m_displayHandle->getFrameOffset() >= m_displayHandle->getContainer()->getFrames().size()) { + break; + } + + int yOffset = -2; + + if (increment > 1) { + yOffset = -1 * qRound(double(m_headerFontSize.height() - m_scale) / 2.0); + painter.fillRect( + m_displayOffset.x() - m_headerFontSize.width() / 2, + m_displayOffset.y() + (i * m_scale), + m_headerFontSize.width() / 2, + m_scale, + Qt::darkGray); + } + + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + m_headerFontSize.width() / 2, + m_displayOffset.y() + (i * m_scale) + yOffset, + m_displayOffset.x() - m_headerFontSize.width(), + m_headerFontSize.height(), + Qt::AlignRight | Qt::AlignTop, + QString("%1").arg(i + m_displayHandle->getFrameOffset())); + } + } + painter.restore(); + + painter.save(); + if (m_showColumnOffsets) { + int increment = qCeil(double(m_headerFontSize.height()) / double(m_scale)); + for (int i = 0; i < displayWidth; i += increment) { + if (i * m_symbolLength + m_displayHandle->getBitOffset() + >= m_displayHandle->getContainer()->getMaxFrameWidth()) { + break; + } + + int yOffset = -2; + + if (increment > 1) { + yOffset = -1 * qRound(double(m_headerFontSize.height() - m_scale) / 2.0); + painter.fillRect( + m_displayOffset.x() + i * m_scale, + m_displayOffset.y() - m_headerFontSize.width() / 2, + m_scale, + m_headerFontSize.width() / 2, + Qt::darkGray); + } + + painter.save(); + painter.rotate(-90); + painter.setPen(Qt::darkGray); + painter.setFont(font); + painter.drawText( + -1 * m_displayOffset.y() + m_headerFontSize.width() / 2, + m_displayOffset.x() + i * m_scale + yOffset, + m_displayOffset.y() - m_headerFontSize.width(), + m_headerFontSize.height(), + Qt::AlignLeft, + QString("%1").arg(i * m_symbolLength + m_displayHandle->getBitOffset())); + painter.restore(); + } + } + painter.restore(); + + QImage raster = this->getSymbolMapImage( + m_displayHandle->getContainer(), + m_displayHandle->getBitOffset(), + m_displayHandle->getFrameOffset(), + displayWidth, + displayHeight); + + painter.save(); + painter.translate(m_displayOffset); + painter.scale(m_scale, m_scale); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawImage(0, 0, raster); + + int symbolOffset = m_displayHandle->getBitOffset() / m_symbolLength; + int adjustedBitOffset = symbolOffset * m_symbolLength; + + painter.resetTransform(); + painter.translate(m_displayOffset); + drawHighlights( + &painter, + double(m_scale) / double(m_symbolLength), + m_scale, + m_displayHandle->getFrameOffset(), + adjustedBitOffset, + displayWidth * m_symbolLength, + displayHeight, + 1); + + painter.restore(); +} + +void SymbolRasterWidget::mouseMoveEvent(QMouseEvent *event) +{ + sendHoverUpdate(event, m_scale, m_scale, 1, m_symbolLength, m_displayOffset); +} + +void SymbolRasterWidget::prepareHeaders() +{ + if (m_displayHandle->getContainer().isNull()) { + return; + } + + QPainter painter(this); + QFont font = QFont("monospace", 10); + font.setStyleStrategy(QFont::ForceIntegerMetrics); + QFontMetrics fontMetrics = QFontMetrics(font, painter.device()); + int fontWidth = fontMetrics.width("0"); + int fontHeight = fontMetrics.height(); + m_headerFontSize.setWidth(fontWidth); + m_headerFontSize.setHeight(fontHeight); + + if (m_showFrameOffsets) { + int totalFrames = m_displayHandle->getContainer()->getFrames().size(); + int maxChars = qFloor(log10(totalFrames)) + 1; + m_displayOffset.setX(qRound(fontWidth * (maxChars + 1.5))); + } + else { + m_displayOffset.setX(0); + } + + if (m_showColumnOffsets) { + int maxWidth = m_displayHandle->getContainer()->getMaxFrameWidth(); + int maxChars = qFloor(log10(maxWidth)) + 1; + m_displayOffset.setY(fontWidth * (maxChars + 1)); + } + else { + m_displayOffset.setY(0); + } +} + +void SymbolRasterWidget::adjustScrollbars() +{ + if (!m_displayHandle->getCurrentFocusDisplays().contains(m_pluginRef)) { + return; + } + DisplayBase::adjustScrollbars(); + + repaint(); + + if (m_displayHandle->getCurrentFocusDisplays().size() == 1) { + m_displayHandle->getHScroll()->setSingleStep(m_symbolLength); + m_displayHandle->getVScroll()->setPageStep((height() - m_displayOffset.y()) / m_scale); + m_displayHandle->getHScroll()->setPageStep(m_symbolLength * (width() - m_displayOffset.x()) / m_scale); + } +} + +void SymbolRasterWidget::setScale(int scale) +{ + m_scale = scale; + this->adjustScrollbars(); +} + +void SymbolRasterWidget::setShowHeaders(bool showHeaders) +{ + m_showFrameOffsets = showHeaders; + m_showColumnOffsets = showHeaders; + adjustScrollbars(); +} + +void SymbolRasterWidget::setColorMapping(QList> mapping) +{ + m_colorMapping.clear(); + if (mapping.length() < 1) { + return; + } + + m_symbolLength = mapping.at(0).first.length(); + + for (QPair pair : mapping) { + bool ok = false; + quint64 symbolValue = pair.first.toULongLong(&ok, 2); + if (ok) { + m_colorMapping.insert(symbolValue, pair.second); + } + } + + adjustScrollbars(); +} diff --git a/src/hobbits-plugins/displays/SymbolRaster/symbolrasterwidget.h b/src/hobbits-plugins/displays/SymbolRaster/symbolrasterwidget.h new file mode 100644 index 00000000..60c2c372 --- /dev/null +++ b/src/hobbits-plugins/displays/SymbolRaster/symbolrasterwidget.h @@ -0,0 +1,39 @@ +#ifndef SYMBOLRASTERWIDGET_H +#define SYMBOLRASTERWIDGET_H + +#include "displaybase.h" + +class SymbolRasterWidget : public DisplayBase +{ + Q_OBJECT + +public: + SymbolRasterWidget(QSharedPointer displayHandle, DisplayInterface *pluginRef); + + void paintEvent(QPaintEvent*) override; + void mouseMoveEvent(QMouseEvent *event) override; + +public slots: + void setColorMapping(QList> mapping); + void setScale(int scale); + void setShowHeaders(bool showHeaders); + +private: + void prepareHeaders(); + QImage getSymbolMapImage(QSharedPointer bitContainer, int x, int y, int w, int h) const; + + int m_scale; + bool m_showFrameOffsets; + bool m_showColumnOffsets; + + QPoint m_displayOffset; + QSize m_headerFontSize; + int m_symbolLength; + QMap m_colorMapping; + +protected slots: + void adjustScrollbars() override; + +}; + +#endif // SYMBOLRASTERWIDGET_H diff --git a/src/hobbits-plugins/displays/displays.pro b/src/hobbits-plugins/displays/displays.pro new file mode 100644 index 00000000..5aadb73a --- /dev/null +++ b/src/hobbits-plugins/displays/displays.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + AsciiView \ + BinaryView \ + BitRaster \ + ByteRaster \ + HexView \ + SymbolRaster diff --git a/src/hobbits-plugins/hobbits-plugins.pro b/src/hobbits-plugins/hobbits-plugins.pro new file mode 100644 index 00000000..3ee4dd3f --- /dev/null +++ b/src/hobbits-plugins/hobbits-plugins.pro @@ -0,0 +1,9 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + analyzers \ + displays \ + importerexporters \ + operators + + diff --git a/src/hobbits-plugins/importerexporters/FileData/FileData.pro b/src/hobbits-plugins/importerexporters/FileData/FileData.pro new file mode 100644 index 00000000..25dee186 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/FileData.pro @@ -0,0 +1,52 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2020-01-10T18:20:08.248Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = FileData +TEMPLATE = lib + +DEFINES += FILEDATA_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + filedata.cpp \ + importbitswizard.cpp + +HEADERS += \ + filedata.h \ + importbitswizard.h + +FORMS += \ + filedata.ui \ + importbitswizard.ui + +DISTFILES += + + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} diff --git a/src/hobbits-plugins/importerexporters/FileData/filedata.cpp b/src/hobbits-plugins/importerexporters/FileData/filedata.cpp new file mode 100644 index 00000000..dadd4d5b --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/filedata.cpp @@ -0,0 +1,116 @@ +#include "filedata.h" +#include "importbitswizard.h" +#include "settingsmanager.h" +#include "ui_filedata.h" +#include +#include + +FileData::FileData() : + ui(new Ui::FileData()) +{ + +} + +FileData::~FileData() +{ + delete ui; +} + +ImportExportInterface* FileData::createDefaultImporterExporter() +{ + return new FileData(); +} + +QString FileData::getName() +{ + return "File Data"; +} + +bool FileData::canExport() +{ + return true; +} + +bool FileData::canImport() +{ + return true; +} + +QSharedPointer FileData::importBits(QMap args, QWidget *parent) +{ + + QSharedPointer nullResult; + + QString fileName; + + if (args.contains("filename")) { + fileName = args.value("filename"); + } + else { + fileName = QFileDialog::getOpenFileName( + parent, + tr("Import Bits"), + SettingsManager::getInstance().getPrivateSetting(SettingsData::LAST_IMPORT_EXPORT_PATH_KEY).toString(), + tr("All Files (*)")); + } + + if (fileName.isEmpty()) { + return nullResult; + } + + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox msg(parent); + msg.setWindowTitle("Import Bits Error"); + msg.setText(QString("Failed to import bit file: '%1'").arg(fileName)); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); + return nullResult; + } + SettingsManager::getInstance().setPrivateSetting( + SettingsData::LAST_IMPORT_EXPORT_PATH_KEY, + QFileInfo(file).dir().path()); + + ImportBitsWizard *importWizard = new ImportBitsWizard(fileName, parent); + + importWizard->setModal(true); + + if (importWizard->exec() == QDialog::Accepted) { + auto container = importWizard->getImportedBitContainer(); + delete importWizard; + return container; + } + else { + delete importWizard; + return nullResult; + } +} + +void FileData::exportBits(QSharedPointer container, QMap args, QWidget *parent) +{ + QString fileName; + + if (args.contains("filename")) { + fileName = args.value("filename"); + } + else { + fileName = QFileDialog::getSaveFileName( + parent, + tr("Export Bits"), + SettingsManager::getInstance().getPrivateSetting(SettingsData::LAST_IMPORT_EXPORT_PATH_KEY).toString(), + tr("All Files (*)")); + } + QFile file(fileName); + if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + return; + } + SettingsManager::getInstance().setPrivateSetting( + SettingsData::LAST_IMPORT_EXPORT_PATH_KEY, + QFileInfo(file).dir().path()); + + QByteArray bytes = container->getBaseBits()->getBytes(); + qint64 len = container->getBaseBits()->size() / 8 + (container->getBaseBits()->size() % 8 ? 1 : 0); + file.write(bytes.data(), len); + file.close(); +} diff --git a/src/hobbits-plugins/importerexporters/FileData/filedata.h b/src/hobbits-plugins/importerexporters/FileData/filedata.h new file mode 100644 index 00000000..4d8e9670 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/filedata.h @@ -0,0 +1,40 @@ +#ifndef FILEDATA_H +#define FILEDATA_H + +#include "importexportinterface.h" + + +namespace Ui +{ +class FileData; +} + +class FileData : public QObject, ImportExportInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.ImportExportInterface.FileData") + Q_INTERFACES(ImportExportInterface) + +public: + FileData(); + + ~FileData(); + + ImportExportInterface* createDefaultImporterExporter() override; + + QString getName() override; + + bool canExport() override; + bool canImport() override; + + QSharedPointer importBits(QMap args, QWidget *parent) override; + void exportBits( + QSharedPointer container, + QMap args, + QWidget *parent) override; + +private: + Ui::FileData *ui; +}; + +#endif // FILEDATA_H diff --git a/src/hobbits-plugins/importerexporters/FileData/filedata.ui b/src/hobbits-plugins/importerexporters/FileData/filedata.ui new file mode 100644 index 00000000..d90cbf1c --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/filedata.ui @@ -0,0 +1,20 @@ + + + FileData + + + + 0 + 0 + 604 + 147 + + + + Form + + + + + + diff --git a/src/hobbits-plugins/importerexporters/FileData/importbitswizard.cpp b/src/hobbits-plugins/importerexporters/FileData/importbitswizard.cpp new file mode 100644 index 00000000..03c16918 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/importbitswizard.cpp @@ -0,0 +1,72 @@ +#include "importbitswizard.h" +#include "ui_importbitswizard.h" + +#include "settingsmanager.h" + +#include +#include + +ImportBitsWizard::ImportBitsWizard(QString fileName, QWidget *parent) : + QDialog(parent), + ui(new Ui::ImportBitsWizard), + m_fileName(fileName) +{ + ui->setupUi(this); + + QFile file(fileName); + file.open(QFile::ReadOnly); + QByteArray previewData = file.read(100 * 256); + file.close(); + + BitContainer previewContainer; + previewContainer.setBytes(previewData); + + ui->lb_imagePreview->setPixmap(previewContainer.getThumbnail()); + + QFileInfo fileInfo(file); + m_fileSizeKb = double(fileInfo.size()) / 1000.0; + + ui->lb_fileName->setText(QString("File: %1").arg(fileInfo.fileName())); + ui->lb_fileSize->setText(QString("File Size: %1 kB").arg(m_fileSizeKb)); + + ui->sb_startKb->setValue(0); + ui->sb_startKb->setMaximum(m_fileSizeKb); + + adjustMaxBits(); + ui->sb_takeKb->setValue(ui->sb_takeKb->maximum()); + + this->setWindowTitle("Import Bits"); + + connect(ui->sb_startKb, SIGNAL(valueChanged(double)), this, SLOT(adjustMaxBits())); + + ui->le_templateName->setVisible(false); +} + +ImportBitsWizard::~ImportBitsWizard() +{ + delete ui; +} + +void ImportBitsWizard::adjustMaxBits() +{ + double maxKb = qMin(double(MAX_BIT_CONTAINER_SIZE) / 1000.0 / 8.0, m_fileSizeKb - ui->sb_startKb->value()); + ui->sb_takeKb->setMinimum(0.001); + ui->sb_takeKb->setMaximum(maxKb); +} + +QSharedPointer ImportBitsWizard::getImportedBitContainer() +{ + QFile file(m_fileName); + + qint64 byteOffset = qint64(ui->sb_startKb->value() * 1000); + qint64 bytesToTake = qint64(ui->sb_takeKb->value() * 1000); + + file.open(QFile::ReadOnly); + file.seek(byteOffset); + QByteArray data = file.read(bytesToTake); + QSharedPointer container = QSharedPointer(new BitContainer()); + container->setBytes(data, data.size() * 8); + container->setName(QFileInfo(file).baseName()); + + return container; +} diff --git a/src/hobbits-plugins/importerexporters/FileData/importbitswizard.h b/src/hobbits-plugins/importerexporters/FileData/importbitswizard.h new file mode 100644 index 00000000..499b4d52 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/importbitswizard.h @@ -0,0 +1,34 @@ +#ifndef IMPORTBITSWIZARD_H +#define IMPORTBITSWIZARD_H + +#include +#include + +#include "bitcontainer.h" + +namespace Ui +{ +class ImportBitsWizard; +} + +class ImportBitsWizard : public QDialog +{ + Q_OBJECT + +public: + explicit ImportBitsWizard(QString fileName, QWidget *parent = nullptr); + + ~ImportBitsWizard(); + + QSharedPointer getImportedBitContainer(); + +public slots: + void adjustMaxBits(); + +private: + Ui::ImportBitsWizard *ui; + QString m_fileName; + double m_fileSizeKb; +}; + +#endif // IMPORTBITSWIZARD_H diff --git a/src/hobbits-plugins/importerexporters/FileData/importbitswizard.ui b/src/hobbits-plugins/importerexporters/FileData/importbitswizard.ui new file mode 100644 index 00000000..4f3862d3 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/FileData/importbitswizard.ui @@ -0,0 +1,187 @@ + + + ImportBitsWizard + + + + 0 + 0 + 517 + 378 + + + + Dialog + + + + + + + 0 + 0 + + + + + 256 + 100 + + + + + 256 + 100 + + + + + + + + + + + File: + + + + + + + File Size: + + + + + + + + + Kilobyte offset: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + Kilobytes to Take: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + true + + + + + + + + + + Apply Template... + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + dialogButtons + accepted() + ImportBitsWizard + accept() + + + 248 + 254 + + + 157 + 274 + + + + + dialogButtons + rejected() + ImportBitsWizard + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/hobbits-plugins/importerexporters/HexString/HexString.pro b/src/hobbits-plugins/importerexporters/HexString/HexString.pro new file mode 100644 index 00000000..e9b3aba3 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HexString/HexString.pro @@ -0,0 +1,49 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2020-01-10T20:44:05.485Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = HexString +TEMPLATE = lib + +DEFINES += HEXSTRING_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += hexstring.cpp \ + hexstringimporter.cpp + +HEADERS += hexstring.h \ + hexstringimporter.h + +FORMS += \ + hexstringimporter.ui + +DISTFILES += + + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} diff --git a/src/hobbits-plugins/importerexporters/HexString/hexstring.cpp b/src/hobbits-plugins/importerexporters/HexString/hexstring.cpp new file mode 100644 index 00000000..2f069999 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HexString/hexstring.cpp @@ -0,0 +1,80 @@ +#include "hexstring.h" +#include "hexstringimporter.h" +#include "settingsmanager.h" +#include + +HexString::HexString() +{ + +} + +HexString::~HexString() +{ +} + +ImportExportInterface* HexString::createDefaultImporterExporter() +{ + return new HexString(); +} + +QString HexString::getName() +{ + return "Hex String"; +} + +bool HexString::canExport() +{ + return true; +} + +bool HexString::canImport() +{ + return true; +} + +QSharedPointer HexString::importBits(QMap args, QWidget *parent) +{ + Q_UNUSED(args) + + QSharedPointer container; + + HexStringImporter *importer = new HexStringImporter(parent); + + if (importer->exec()) { + container = importer->getContainer(); + delete importer; + } + + return container; +} + +void HexString::exportBits(QSharedPointer container, QMap args, QWidget *parent) +{ + Q_UNUSED(parent) + Q_UNUSED(args) + + QString fileName; + + if (args.contains("filename")) { + fileName = args.value("filename"); + } + else { + fileName = QFileDialog::getSaveFileName( + parent, + tr("Export Bits"), + SettingsManager::getInstance().getPrivateSetting(SettingsData::LAST_IMPORT_EXPORT_PATH_KEY).toString(), + tr("All Files (*)")); + } + QFile file(fileName); + if (!file.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + return; + } + SettingsManager::getInstance().setPrivateSetting( + SettingsData::LAST_IMPORT_EXPORT_PATH_KEY, + QFileInfo(file).dir().path()); + + qint64 len = container->getBaseBits()->size() / 8 + (container->getBaseBits()->size() % 8 ? 1 : 0); + QByteArray bytes = container->getBaseBits()->getBytes().mid(0, len).toHex(' '); + file.write(bytes.data()); + file.close(); +} diff --git a/src/hobbits-plugins/importerexporters/HexString/hexstring.h b/src/hobbits-plugins/importerexporters/HexString/hexstring.h new file mode 100644 index 00000000..8cf61e9b --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HexString/hexstring.h @@ -0,0 +1,34 @@ +#ifndef HEXSTRING_H +#define HEXSTRING_H + +#include "importexportinterface.h" + + +class HexString : public QObject, ImportExportInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.ImportExportInterface.HexString") + Q_INTERFACES(ImportExportInterface) + +public: + HexString(); + + ~HexString(); + + ImportExportInterface* createDefaultImporterExporter() override; + + QString getName() override; + + bool canExport() override; + bool canImport() override; + + QSharedPointer importBits(QMap args, QWidget *parent) override; + void exportBits( + QSharedPointer container, + QMap args, + QWidget *parent) override; + +private: +}; + +#endif // HEXSTRING_H diff --git a/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.cpp b/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.cpp new file mode 100644 index 00000000..b64bedd1 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.cpp @@ -0,0 +1,88 @@ +#include "hexstringimporter.h" +#include "settingsmanager.h" +#include "ui_hexstringimporter.h" +#include +#include + +HexStringImporter::HexStringImporter(QWidget *parent) : + QDialog(parent), + ui(new Ui::HexStringImporter) +{ + ui->setupUi(this); +} + +HexStringImporter::~HexStringImporter() +{ + delete ui; +} + +QSharedPointer HexStringImporter::getContainer() const +{ + return m_container; +} + +void HexStringImporter::on_te_hexString_textChanged() +{ + ui->pb_submitInput->setEnabled(!ui->te_hexString->toPlainText().isEmpty()); +} + +void HexStringImporter::on_pb_selectFile_pressed() +{ + m_container = QSharedPointer(); + QString fileName = QFileDialog::getOpenFileName( + this, + tr("Import Hex String File"), + SettingsManager::getInstance().getPrivateSetting(SettingsData::LAST_IMPORT_EXPORT_PATH_KEY).toString(), + tr("All Files (*)")); + + if (fileName.isEmpty()) { + return; + } + + QFile file(fileName); + + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox msg(this); + msg.setWindowTitle("Import Bits Error"); + msg.setText(QString("Failed to import bit file: '%1'").arg(fileName)); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); + return; + } + + QByteArray hexEncoded = file.readAll(); + QByteArray data = QByteArray::fromHex(hexEncoded); + + if (data.isEmpty()) { + QMessageBox msg(this); + msg.setWindowTitle("Import Bits Error"); + msg.setText(QString("Failed to import hex-encoded string data from: '%1'").arg(fileName)); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); + return; + } + + m_container = QSharedPointer(new BitContainer()); + m_container->setBytes(data); + + this->accept(); +} + +void HexStringImporter::on_pb_submitInput_pressed() +{ + QByteArray data = QByteArray::fromHex(ui->te_hexString->toPlainText().toLatin1()); + + if (data.isEmpty()) { + QMessageBox msg(this); + msg.setWindowTitle("Import Bits Error"); + msg.setText(QString("Failed to import hex-encoded string data")); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); + return; + } + + m_container = QSharedPointer(new BitContainer()); + m_container->setBytes(data); + + this->accept(); +} diff --git a/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.h b/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.h new file mode 100644 index 00000000..fb9a9785 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.h @@ -0,0 +1,34 @@ +#ifndef HEXSTRINGIMPORTER_H +#define HEXSTRINGIMPORTER_H + +#include "bitcontainer.h" +#include + +namespace Ui +{ +class HexStringImporter; +} + +class HexStringImporter : public QDialog +{ + Q_OBJECT + +public: + explicit HexStringImporter(QWidget *parent = nullptr); + + ~HexStringImporter(); + + QSharedPointer getContainer() const; + +private slots: + void on_te_hexString_textChanged(); + void on_pb_selectFile_pressed(); + void on_pb_submitInput_pressed(); + +private: + Ui::HexStringImporter *ui; + + QSharedPointer m_container; +}; + +#endif // HEXSTRINGIMPORTER_H diff --git a/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.ui b/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.ui new file mode 100644 index 00000000..ad6be6b4 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HexString/hexstringimporter.ui @@ -0,0 +1,104 @@ + + + HexStringImporter + + + + 0 + 0 + 394 + 227 + + + + Dialog + + + + + + Select a file with hex string-encoded data... + + + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + or + + + + + + + Qt::Horizontal + + + + + + + + + Enter a hex string: + + + te_hexString + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + Submit + + + + + + + + + pb_selectFile + te_hexString + pb_submitInput + + + + diff --git a/src/hobbits-plugins/importerexporters/HttpData/HttpData.pro b/src/hobbits-plugins/importerexporters/HttpData/HttpData.pro new file mode 100644 index 00000000..701e0fd0 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HttpData/HttpData.pro @@ -0,0 +1,49 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2020-01-11T00:02:36.234Z +# +#------------------------------------------------- + +QT += widgets network + +QT -= gui + +TARGET = HttpData +TEMPLATE = lib + +DEFINES += HTTPDATA_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += httpdata.cpp \ + httptransceiver.cpp + +HEADERS += httpdata.h \ + httptransceiver.h + +FORMS += \ + httptransceiver.ui + +DISTFILES += + + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} diff --git a/src/hobbits-plugins/importerexporters/HttpData/httpdata.cpp b/src/hobbits-plugins/importerexporters/HttpData/httpdata.cpp new file mode 100644 index 00000000..f6e8f42e --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HttpData/httpdata.cpp @@ -0,0 +1,64 @@ +#include "httpdata.h" + +HttpData::HttpData() : + http(nullptr) +{ + +} + +HttpData::~HttpData() +{ + if (http) { + delete http; + } +} + +ImportExportInterface* HttpData::createDefaultImporterExporter() +{ + return new HttpData(); +} + +QString HttpData::getName() +{ + return "HTTP Data (REST)"; +} + +bool HttpData::canExport() +{ + return true; +} + +bool HttpData::canImport() +{ + return true; +} + +QSharedPointer HttpData::importBits(QMap args, QWidget *parent) +{ + Q_UNUSED(args) + Q_UNUSED(parent) + + if (!http) { + http = new HttpTransceiver(); + } + http->setDownloadMode(); + if (http->exec()) { + auto container = QSharedPointer(new BitContainer()); + container->setBytes(http->getDownloadedData()); + return container; + } + + return QSharedPointer(); +} + +void HttpData::exportBits(QSharedPointer container, QMap args, QWidget *parent) +{ + Q_UNUSED(args) + Q_UNUSED(parent) + + if (!http) { + http = new HttpTransceiver(); + } + http->setUploadMode(container->getBaseBits()->getBytes()); + http->exec(); +} diff --git a/src/hobbits-plugins/importerexporters/HttpData/httpdata.h b/src/hobbits-plugins/importerexporters/HttpData/httpdata.h new file mode 100644 index 00000000..9afd429b --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HttpData/httpdata.h @@ -0,0 +1,35 @@ +#ifndef HTTPDATA_H +#define HTTPDATA_H + +#include "httptransceiver.h" +#include "importexportinterface.h" + +class HttpData : public QObject, ImportExportInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.ImportExportInterface.HttpData") + Q_INTERFACES(ImportExportInterface) + +public: + HttpData(); + + ~HttpData() override; + + ImportExportInterface* createDefaultImporterExporter() override; + + QString getName() override; + + bool canExport() override; + bool canImport() override; + + QSharedPointer importBits(QMap args, QWidget *parent) override; + void exportBits( + QSharedPointer container, + QMap args, + QWidget *parent) override; + +private: + HttpTransceiver *http; +}; + +#endif // HTTPDATA_H diff --git a/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.cpp b/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.cpp new file mode 100644 index 00000000..60ff54a2 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.cpp @@ -0,0 +1,128 @@ +#include "bitcontainer.h" +#include "httptransceiver.h" +#include "ui_httptransceiver.h" +#include +#include + +HttpTransceiver::HttpTransceiver(QWidget *parent) : + QDialog(parent), + ui(new Ui::HttpTransceiver), + m_netManager(new QNetworkAccessManager(this)), + m_reply(nullptr) +{ + ui->setupUi(this); + + this->setWindowTitle("HTTP Request"); + + setDownloadMode(); + connect( + m_netManager, + &QNetworkAccessManager::finished, + this, + &HttpTransceiver::replyReceived); +} + +HttpTransceiver::~HttpTransceiver() +{ + delete ui; +} + +QByteArray HttpTransceiver::getDownloadedData() +{ + return m_downloadData; +} + +void HttpTransceiver::setUploadMode(QByteArray data) +{ + m_uploadData = data; + ui->cb_verb->clear(); + ui->cb_verb->addItem("POST"); + ui->cb_verb->addItem("PUT"); + ui->cb_verb->setEnabled(true); + ui->le_formDataName->setVisible(true); + ui->lb_formDataName->setVisible(true); +} + +void HttpTransceiver::setDownloadMode() +{ + ui->cb_verb->clear(); + ui->cb_verb->addItem("GET"); + ui->cb_verb->setEnabled(false); + ui->le_formDataName->setVisible(false); + ui->lb_formDataName->setVisible(false); +} + +void HttpTransceiver::on_pb_send_clicked() +{ + ui->pb_send->setEnabled(false); + QNetworkRequest request(QUrl(ui->le_url->text())); + + QNetworkReply *reply; + + if (ui->cb_verb->currentText() == "GET") { + reply = m_netManager->get(request); + + connect(reply, &QNetworkReply::downloadProgress, this, &HttpTransceiver::progressReceived); + } + else { + QHttpMultiPart *multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType); + + QHttpPart part; + part.setHeader( + QNetworkRequest::ContentDispositionHeader, + QVariant(QString("form-data; name=\"%1\"").arg(ui->le_formDataName->text()))); + part.setBody(m_uploadData); + + multiPart->append(part); + + if (ui->cb_verb->currentText() == "POST") { + reply = m_netManager->post(request, multiPart); + } + else if (ui->cb_verb->currentText() == "PUT") { + reply = m_netManager->put(request, multiPart); + } + + multiPart->setParent(reply); + + connect(reply, &QNetworkReply::uploadProgress, this, &HttpTransceiver::progressReceived); + } + + m_reply = reply; + + connect( + reply, + QOverload::of(&QNetworkReply::error), + this, + &HttpTransceiver::handleError); +} + +void HttpTransceiver::progressReceived(qint64 progress, qint64 total) +{ + ui->pr_request->setValue(int(double(progress) / double(total) * 100.0)); +} + +void HttpTransceiver::replyReceived(QNetworkReply *reply) +{ + m_downloadData = reply->read(MAX_BIT_CONTAINER_SIZE); + m_uploadData.clear(); + + if (reply->error() == QNetworkReply::NoError) { + this->accept(); + ui->pr_request->setValue(0); + ui->le_url->clear(); + ui->le_formDataName->clear(); + } + + reply->deleteLater(); + ui->pb_send->setEnabled(true); +} + +void HttpTransceiver::handleError(QNetworkReply::NetworkError error) +{ + Q_UNUSED(error) + QMessageBox msg(this); + msg.setWindowTitle("HTTP Error"); + msg.setText(QString("Failed to perform HTTP request: '%1'").arg(m_reply->errorString())); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); +} diff --git a/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.h b/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.h new file mode 100644 index 00000000..9cb4f3be --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.h @@ -0,0 +1,45 @@ +#ifndef HTTPTRANSCEIVER_H +#define HTTPTRANSCEIVER_H + +#include +#include +#include +#include + +namespace Ui +{ +class HttpTransceiver; +} + +class HttpTransceiver : public QDialog +{ + Q_OBJECT + +public: + explicit HttpTransceiver(QWidget *parent = nullptr); + + ~HttpTransceiver(); + + QByteArray getDownloadedData(); + +public slots: + void setUploadMode(QByteArray data); + void setDownloadMode(); + +private slots: + void on_pb_send_clicked(); + void replyReceived(QNetworkReply*); + + void progressReceived(qint64, qint64); + void handleError(QNetworkReply::NetworkError); + +private: + Ui::HttpTransceiver *ui; + QNetworkAccessManager *m_netManager; + QTemporaryFile *m_downloadFile; + QByteArray m_uploadData; + QByteArray m_downloadData; + QNetworkReply *m_reply; +}; + +#endif // HTTPTRANSCEIVER_H diff --git a/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.ui b/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.ui new file mode 100644 index 00000000..02f67b86 --- /dev/null +++ b/src/hobbits-plugins/importerexporters/HttpData/httptransceiver.ui @@ -0,0 +1,121 @@ + + + HttpTransceiver + + + + 0 + 0 + 471 + 154 + + + + Dialog + + + + + + + + + + + + + + + + + + 85 + 0 + + + + + + + + + + + + URL: + + + + + + + + + + + + + + + + Form Data Name: + + + + + + + + + + + + Qt::Vertical + + + + 20 + 1 + + + + + + + + false + + + 0 + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Send + + + + + + + + + + diff --git a/src/hobbits-plugins/importerexporters/importerexporters.pro b/src/hobbits-plugins/importerexporters/importerexporters.pro new file mode 100644 index 00000000..45f878ca --- /dev/null +++ b/src/hobbits-plugins/importerexporters/importerexporters.pro @@ -0,0 +1,6 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + FileData \ + HexString \ + HttpData diff --git a/src/hobbits-plugins/operators/BitsError/BitsError.pro b/src/hobbits-plugins/operators/BitsError/BitsError.pro new file mode 100644 index 00000000..4c5beb93 --- /dev/null +++ b/src/hobbits-plugins/operators/BitsError/BitsError.pro @@ -0,0 +1,48 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-06-12T12:32:18 +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = BitsError +TEMPLATE = lib +CONFIG += c++11 plugin + +DEFINES += BITSERROR_LIBRARY + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + bitserror.cpp + +HEADERS += \ + bitserror.h + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/operators + INSTALLS += target +} + +FORMS += \ + bitserror.ui + +DISTFILES += diff --git a/src/hobbits-plugins/operators/BitsError/bitserror.cpp b/src/hobbits-plugins/operators/BitsError/bitserror.cpp new file mode 100644 index 00000000..a190b807 --- /dev/null +++ b/src/hobbits-plugins/operators/BitsError/bitserror.cpp @@ -0,0 +1,295 @@ +#include "bitserror.h" +#include "cmath" +#include "ctime" +#include "ui_bitserror.h" +#include +#include +#include +#include +#include +#include + +BitsError::BitsError() : + ui(new Ui::BitsError()) +{ + +} + +QString BitsError::getName() +{ + return "Bit Error"; +} + +void BitsError::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); +} + +void BitsError::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +QSharedPointer BitsError::gaussianFlip( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + QList> output; + QSharedPointer result(new OperatorResult()); + + if (inputContainers.size() != 1) { + result->setOutputContainers(output); + return result; + } + + QSharedPointer _inputBits = inputContainers.at(0)->getBaseBits(); + int bitLength = inputContainers.at(0)->getBaseBits()->size(); + QByteArray inputBuffer(bitLength / 8 + 1, 0x00); + + QSharedPointer outputBits = QSharedPointer(new BitArray(inputBuffer, bitLength)); + + for (int i = 0; i < bitLength; i++) { + if (_inputBits->at(i) == 0) { + outputBits->set(i, false); + } + else { + outputBits->set(i, true); + } + } + // outputBits = _inputBits + + double coeff = (recallablePluginState.value("error_coeff")).toDouble(); + int exp = (recallablePluginState.value("error_exp")).toInt(); + + double TrueBer = ((coeff * (pow(10, exp)))); + + int numBitsToFlip = int(floor(bitLength * TrueBer)); + int incr = bitLength / numBitsToFlip; + int start = 0; + int end = bitLength / 2; + int counter = 0; + int mean = int(floor(end / 2)); + int stddev = int(floor(end / 6)); // Empirical Rule + + std::normal_distribution distribution(0, stddev); + std::default_random_engine generator; + + int lastPercent = 0; + + + while (counter != numBitsToFlip) { + int number = int(distribution(generator)) + mean; + + if (number < 0) { + number += bitLength; + if ((outputBits->at(number - 1) ^ 1) == 1) { + outputBits->set(number - 1, true); + } + else { + outputBits->set(number - 1, false); + } + } + else if (number > bitLength) { + number %= bitLength; + if ((outputBits->at(number - 1) ^ 1) == 1) { + outputBits->set(number - 1, true); + } + else { + outputBits->set(number - 1, false); + } + } + else if (number >= start && number <= end) { + if ((outputBits->at(number - 1) ^ 1) == 1) { + outputBits->set(number - 1, true); + } + else { + outputBits->set(number - 1, false); + } + } + + mean += incr; + start += incr; + end += incr; + counter++; + + int nextPercent = int(double(counter) / double(numBitsToFlip) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new OperatorResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + + QSharedPointer bitContainer = QSharedPointer(new BitContainer()); + bitContainer->setBytes(outputBits->getBytes(), bitLength); + output.append(bitContainer); + + QJsonObject pluginState(recallablePluginState); + pluginState.insert( + "container_name", + QString("%1e%2 BER <- %3") + .arg(recallablePluginState.value("error_coeff").toDouble()) + .arg(recallablePluginState.value("error_exp").toDouble()) + .arg(inputContainers.at(0)->getName())); + + result->setOutputContainers(output)->setPluginState(pluginState); + return result; +} + +QJsonObject BitsError::getStateFromUi() +{ + QJsonObject pluginState; + + pluginState.insert("error_coeff", (ui->coeffInput->text()).toDouble()); + pluginState.insert("error_exp", (ui->expInput->value())); + if (ui->gaussianOpt->isChecked()) { + pluginState.insert("error_type", "gaussian"); + } + else { + pluginState.insert("error_type", "periodic"); + } + return pluginState; +} + +bool BitsError::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + ui->coeffInput->setText(QString("%1").arg(pluginState.value("error_coeff").toDouble())); + ui->expInput->setValue(pluginState.value("error_exp").toInt()); + if (pluginState.value("error_type").toString() == "periodic") { + ui->periodicOpt->click(); + } + else { + ui->gaussianOpt->click(); + } + return true; +} + +bool BitsError::canRecallPluginState(const QJsonObject &pluginState) +{ + if (pluginState.contains("error_coeff") && pluginState.value("error_coeff").isDouble()) { + if (pluginState.contains("error_exp")) { + if ((pluginState.contains("error_type") && pluginState.value("error_type").isString())) { + return true; + } + } + } + return false; +} + +int BitsError::getMinInputContainers() +{ + return 1; +} + +int BitsError::getMaxInputContainers() +{ + return 1; +} + +QSharedPointer BitsError::operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + + QSharedPointer result(new OperatorResult()); + QList> outputContainers; + + QSharedPointer nullResult; + + if (inputContainers.size() != 1) { + return nullResult; + } + + if (recallablePluginState.value("error_type").toString() == "gaussian") { + return gaussianFlip(inputContainers, recallablePluginState, progressTracker); + } + + + QSharedPointer _inputBits = inputContainers.at(0)->getBaseBits(); + + int bitLength = inputContainers.at(0)->getBaseBits()->size(); + QByteArray inputBuffer(bitLength / 8 + 1, 0x00); + + QSharedPointer outputBits = QSharedPointer(new BitArray(inputBuffer, bitLength)); + + for (int i = 0; i < bitLength; i++) { + if (_inputBits->at(i) == 0) { + outputBits->set(i, false); + } + else { + outputBits->set(i, true); + } + } + + + double coeff = recallablePluginState.value("error_coeff").toDouble(); + int exp = int(recallablePluginState.value("error_exp").toDouble()); + + double TrueBer = ((coeff * (pow(10, exp)))); + double ber = 1 / TrueBer; + + + int skipStep = int(floor(ber)); + + int lastPercent = 0; + if (bitLength > 0) { + for (int i = skipStep - 1; i < bitLength; i += skipStep) { + if ((outputBits->at(i) ^ 1) == 0) { + outputBits->set(i, false); + } + else { + outputBits->set(i, true); + } + + int nextPercent = int(double(i) / double(bitLength) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new OperatorResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + } + + QSharedPointer bitContainer = QSharedPointer(new BitContainer()); + bitContainer->setBytes(outputBits->getBytes(), bitLength); + outputContainers.append(bitContainer); + + QJsonObject pluginState(recallablePluginState); + pluginState.insert( + "container_name", + QString("%1e%2 BER <- %3") + .arg(recallablePluginState.value("error_coeff").toDouble()) + .arg(recallablePluginState.value("error_exp").toDouble()) + .arg(inputContainers.at(0)->getName())); + + result->setOutputContainers(outputContainers)->setPluginState(pluginState); + return std::move(result); +} + +OperatorInterface* BitsError::createDefaultOperator() +{ + return new BitsError(); +} diff --git a/src/hobbits-plugins/operators/BitsError/bitserror.h b/src/hobbits-plugins/operators/BitsError/bitserror.h new file mode 100644 index 00000000..a92abdb7 --- /dev/null +++ b/src/hobbits-plugins/operators/BitsError/bitserror.h @@ -0,0 +1,50 @@ +#ifndef BITSERROR_H +#define BITSERROR_H + +#include "operatorinterface.h" + +namespace Ui +{ +class BitsError; +} + +class BitsError : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.BitsError") + Q_INTERFACES(OperatorInterface) + +public: + BitsError(); + + QString getName() override; + + OperatorInterface* createDefaultOperator() override; + void applyToWidget(QWidget *widget) override; + void provideCallback(QSharedPointer pluginCallback) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + + + QSharedPointer gaussianFlip( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker); + +private: + Ui::BitsError *ui; + QSharedPointer m_pluginCallback; + +}; + +#endif // BITSERROR_H diff --git a/src/hobbits-plugins/operators/BitsError/bitserror.ui b/src/hobbits-plugins/operators/BitsError/bitserror.ui new file mode 100644 index 00000000..2906ab96 --- /dev/null +++ b/src/hobbits-plugins/operators/BitsError/bitserror.ui @@ -0,0 +1,169 @@ + + + BitsError + + + + 0 + 0 + 403 + 111 + + + + + 8 + + + + Form + + + + + + + + + 11 + + + + Error Type: + + + Qt::PlainText + + + + + + + true + + + + 11 + + + + Periodic + + + true + + + + + + + + 11 + + + + Gaussian + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 11 + + + + Error Rate: + + + + + + + + 11 + + + + 1 + + + + + + + + 11 + + + + e + + + + + + + + 11 + + + + -5000 + + + -1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/hobbits-plugins/operators/HeaderFramer/HeaderFramer.pro b/src/hobbits-plugins/operators/HeaderFramer/HeaderFramer.pro new file mode 100644 index 00000000..80e6b17a --- /dev/null +++ b/src/hobbits-plugins/operators/HeaderFramer/HeaderFramer.pro @@ -0,0 +1,45 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-12-26T22:23:13.953Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = HeaderFramer +TEMPLATE = lib + +DEFINES += HEADERFRAMER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += headerframer.cpp + +HEADERS += headerframer.h + +FORMS += headerframer.ui + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/operators + INSTALLS += target +} + +DISTFILES += diff --git a/src/hobbits-plugins/operators/HeaderFramer/headerframer.cpp b/src/hobbits-plugins/operators/HeaderFramer/headerframer.cpp new file mode 100644 index 00000000..09cbd123 --- /dev/null +++ b/src/hobbits-plugins/operators/HeaderFramer/headerframer.cpp @@ -0,0 +1,343 @@ +#include "bitarray.h" +#include "headerframer.h" +#include "ui_headerframer.h" +#include +#include + + +HeaderFramer::HeaderFramer() : + ui(new Ui::HeaderFramer()) +{ + +} + +// Return name of operator +QString HeaderFramer::getName() +{ + return "Header Framer"; +} + +void HeaderFramer::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +void HeaderFramer::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); + + connect(ui->le_header, SIGNAL(textChanged(QString)), this, SLOT(validateHeader(QString))); + connect(ui->cb_fixedLength, SIGNAL(stateChanged(int)), this, SLOT(fixedLengthToggled(int))); + + connect(ui->pb_add, SIGNAL(pressed()), this, SLOT(addHeader())); + connect(ui->pb_remove, SIGNAL(pressed()), this, SLOT(removeHeader())); + + connect(ui->tw_headers, SIGNAL(itemSelectionChanged()), this, SLOT(checkSelectedHeader())); + + ui->pb_add->setEnabled(false); + ui->pb_remove->setEnabled(false); + ui->sb_frameLength->setEnabled(false); + ui->tw_headers->setColumnCount(2); + ui->tw_headers->setHorizontalHeaderLabels({"Frame Header", "Frame Length"}); + ui->sb_frameLength->setMaximum(INT32_MAX); +} + +QString HeaderFramer::getHeaderString() +{ + // Parse header + if (ui->le_header->text().isEmpty()) { + return QString(); + } + + QStringList parseErrors; + BitArray header = BitArray::fromString(ui->le_header->text(), parseErrors); + if (!parseErrors.isEmpty()) { + return QString(); + } + + ui->sb_frameLength->setMinimum(header.size()); + + return ui->le_header->text(); +} + +void HeaderFramer::fixedLengthToggled(int fixed) +{ + ui->sb_frameLength->setEnabled(fixed == Qt::Checked); +} + +void HeaderFramer::validateHeader(QString header) +{ + Q_UNUSED(header) + QString headerString = getHeaderString(); + ui->pb_add->setEnabled(!headerString.isEmpty()); +} + +void HeaderFramer::addHeader() +{ + QString headerString = getHeaderString(); + if (headerString.isEmpty()) { + return; + } + + int row = ui->tw_headers->rowCount(); + ui->tw_headers->insertRow(row); + ui->tw_headers->setItem(row, 0, new QTableWidgetItem(headerString)); + if (ui->cb_fixedLength->isChecked()) { + ui->tw_headers->setItem(row, 1, new QTableWidgetItem(QString("%1").arg(ui->sb_frameLength->value()))); + } + else { + ui->tw_headers->setItem(row, 1, new QTableWidgetItem("*")); + } +} + +void HeaderFramer::checkSelectedHeader() +{ + ui->pb_remove->setEnabled(!ui->tw_headers->selectedItems().isEmpty()); +} + +void HeaderFramer::removeHeader() +{ + QSet rows; + for (auto item : ui->tw_headers->selectedItems()) { + rows.insert(item->row()); + } + QList rowList = rows.values(); + std::sort(rowList.begin(), rowList.end()); + for (int i = rowList.length() - 1; i >= 0; i--) { + ui->tw_headers->removeRow(rowList.at(i)); + } +} + +bool HeaderFramer::canRecallPluginState(const QJsonObject &pluginState) +{ + // if pluginState does not have required fields, return false + if (pluginState.isEmpty() == true) { + return false; + } + + if (!pluginState.contains("headers") || !pluginState.value("headers").isArray()) { + return false; + } + + QJsonArray mappingsArray = pluginState.value("headers").toArray(); + if (mappingsArray.isEmpty()) { + return false; + } + for (auto valueRef : mappingsArray) { + if (!valueRef.isObject()) { + return false; + } + QJsonObject mappingValue = valueRef.toObject(); + if (!(mappingValue.contains("header") + && mappingValue.contains("length") + && mappingValue.value("header").isString() + && mappingValue.value("length").isString())) { + return false; + } + } + + return true; +} + +QJsonObject HeaderFramer::getStateFromUi() +{ + QJsonObject pluginState; + + QJsonArray headersArray; + for (int i = 0; i < ui->tw_headers->rowCount(); i++) { + QJsonObject headerData; + headerData.insert("header", ui->tw_headers->item(i, 0)->text()); + headerData.insert("length", ui->tw_headers->item(i, 1)->text()); + headersArray.append(headerData); + } + + pluginState.insert("headers", headersArray); + + return pluginState; +} + +bool HeaderFramer::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + QJsonArray headersArray = pluginState.value("headers").toArray(); + int row = 0; + ui->tw_headers->clearContents(); + ui->tw_headers->setRowCount(0); + for (auto valueRef : headersArray) { + ui->tw_headers->insertRow(ui->tw_headers->rowCount()); + QJsonObject headerData = valueRef.toObject(); + ui->tw_headers->setItem(row, 0, new QTableWidgetItem(headerData.value("header").toString())); + ui->tw_headers->setItem(row, 1, new QTableWidgetItem(headerData.value("length").toString())); + row++; + } + + return true; +} + +int HeaderFramer::getMinInputContainers() +{ + return 1; +} + +int HeaderFramer::getMaxInputContainers() +{ + return 1; +} + +bool headerGreaterThan(QPair a, QPair b) +{ + return a.first.size() > b.first.size(); +} + +QSharedPointer HeaderFramer::operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + + QSharedPointer result(new OperatorResult()); + + if (!canRecallPluginState(recallablePluginState)) { + return result; + } + + if (inputContainers.size() != 1) { + return result; + } + + QList> headers; + QJsonArray headersArray = recallablePluginState.value("headers").toArray(); + for (auto valueRef : headersArray) { + QJsonObject headerData = valueRef.toObject(); + QStringList parseErrors; + BitArray header = BitArray::fromString(headerData.value("header").toString()); + if (!parseErrors.isEmpty()) { + result->setPluginState(QJsonObject({QPair("error", parseErrors.join("\n"))})); + return result; + } + QString lengthString = headerData.value("length").toString(); + int length; + if (lengthString == "*") { + length = -1; + } + else { + bool ok; + length = lengthString.toInt(&ok); + if (!ok) { + result->setPluginState( + QJsonObject( + {QPair( + "error", + QString("Invalid header length value '%1'").arg(lengthString))})); + return result; + } + } + headers.append(QPair(header, length)); + } + + std::sort(headers.begin(), headers.end(), headerGreaterThan); + + QSharedPointer bits = inputContainers.at(0)->getBaseBits(); + + QList frames; + int maxFrameWidth = 0; + int lastPercent = 0; + int start = 0; + int pos = 0; + int outputSize = 0; + bool buildingFrame = false; + for ( ; pos < bits->size(); pos++) { + for (auto headerInfo : headers) { + BitArray header = headerInfo.first; + if (header.size() > bits->size() - pos) { + break; + } + bool match = true; + for (int i = 0; i < header.size() && pos + i < bits->size(); i++) { + if (bits->at(pos + i) != header.at(i)) { + match = false; + break; + } + } + + if (match) { + // We found a header that starts at 'end' + if (buildingFrame) { + // A previous header had an open-ended length and this match finishes that frame + frames.append(Frame(bits, start, pos - 1)); + maxFrameWidth = qMax(maxFrameWidth, frames.last().size()); + outputSize += frames.last().size(); + } + if (headerInfo.second > 0) { + // This header's frame has a static length, so we can just add the whole frame + buildingFrame = false; + int end = pos + headerInfo.second - 1; + if (end < bits->size()) { + frames.append(Frame(bits, pos, end)); + maxFrameWidth = qMax(maxFrameWidth, frames.last().size()); + outputSize += frames.last().size(); + } + pos = end; + } + else { + // This header's frame has an open-ended length, so we need to find the start of the next frame before adding it + start = pos; + pos = pos + header.size() - 1; + buildingFrame = true; + } + break; + } + } + + int nextPercent = int(double(pos) / double(bits->size()) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new OperatorResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + if (buildingFrame) { + frames.append(Frame(bits, start, bits->size() - 1)); + maxFrameWidth = qMax(maxFrameWidth, frames.last().size()); + outputSize += frames.last().size(); + } + + BitArray outputBits(QByteArray(outputSize / 8 + 1, 0x00), outputSize); + QList outputFrames; + int outputIndex = 0; + for (Frame frame : frames) { + for (int i = 0; i < frame.size(); i++) { + outputBits.set(outputIndex + i, frame.at(i)); + } + outputFrames.append(Range(outputIndex, outputIndex + frame.size() - 1)); + outputIndex += frame.size(); + } + + QSharedPointer outputContainer = QSharedPointer(new BitContainer()); + outputContainer->setBytes(outputBits.getBytes(), outputBits.size()); + outputContainer->setHighlights("frames", outputFrames); + outputContainer->setMetadata("max_frame_width", {QString("%1").arg(maxFrameWidth)}); + outputContainer->frameViaHighlights(); + + result->setPluginState(recallablePluginState); + result->setOutputContainers({outputContainer}); + + return result; +} + +OperatorInterface* HeaderFramer::createDefaultOperator() +{ + return new HeaderFramer(); +} diff --git a/src/hobbits-plugins/operators/HeaderFramer/headerframer.h b/src/hobbits-plugins/operators/HeaderFramer/headerframer.h new file mode 100644 index 00000000..c4e6c9f1 --- /dev/null +++ b/src/hobbits-plugins/operators/HeaderFramer/headerframer.h @@ -0,0 +1,56 @@ +#ifndef HEADERFRAMER_H +#define HEADERFRAMER_H + +#include "mathparser.h" +#include "operatorinterface.h" + + +namespace Ui +{ +class HeaderFramer; + +} + +class HeaderFramer : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.HeaderFramer") + Q_INTERFACES(OperatorInterface) + +public: + HeaderFramer(); + + QString getName() override; + + OperatorInterface* createDefaultOperator() override; + void applyToWidget(QWidget *widget) override; + void provideCallback(QSharedPointer pluginCallback) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +private slots: + void fixedLengthToggled(int fixed); + void validateHeader(QString header); + void addHeader(); + void checkSelectedHeader(); + void removeHeader(); + +private: + Ui::HeaderFramer *ui; + QSharedPointer m_pluginCallback; + + QString getHeaderString(); + +}; + +#endif // HEADERFRAMER_H diff --git a/src/hobbits-plugins/operators/HeaderFramer/headerframer.ui b/src/hobbits-plugins/operators/HeaderFramer/headerframer.ui new file mode 100644 index 00000000..dc88e686 --- /dev/null +++ b/src/hobbits-plugins/operators/HeaderFramer/headerframer.ui @@ -0,0 +1,104 @@ + + + HeaderFramer + + + + 0 + 0 + 715 + 146 + + + + Form + + + + + + + + Header (e.g. 0x80ff, 0b011101, XYZ) + + + + + + + Fixed Frame Length: + + + + + + + + + + Add + + + + + + + + + + + + + All Headers: + + + + + + + QAbstractItemView::SelectRows + + + 150 + + + false + + + false + + + + + + + + + + + Remove + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/hobbits-plugins/operators/PrbsGenerator/PrbsGenerator.pro b/src/hobbits-plugins/operators/PrbsGenerator/PrbsGenerator.pro new file mode 100644 index 00000000..3b32085a --- /dev/null +++ b/src/hobbits-plugins/operators/PrbsGenerator/PrbsGenerator.pro @@ -0,0 +1,47 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-06-21T11:30:22 +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = PrbsGenerator +TEMPLATE = lib + +DEFINES += PRBSGENERATOR_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + prbsgenerator.cpp + +HEADERS += \ + prbsgenerator.h +FORMS += \ + prbsgenerator.ui + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/operators + INSTALLS += target +} + +DISTFILES += diff --git a/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.cpp b/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.cpp new file mode 100644 index 00000000..81b61832 --- /dev/null +++ b/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.cpp @@ -0,0 +1,291 @@ +#include "prbsgenerator.h" +#include "ui_prbsgenerator.h" +#include +#include +#include +#include +#include +#include +#include + +PrbsGenerator::PrbsGenerator() : + ui(new Ui::PrbsGenerator()) +{ + +} + +/* + * Returns name of plugin + */ +QString PrbsGenerator::getName() +{ + return "LFSR"; +} + +void PrbsGenerator::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); + connect(ui->btnTaps, SIGNAL(clicked()), this, SLOT(showHelp())); +} + +void PrbsGenerator::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +/* Outputs the ending state of the LFSR after + * howevery many bits are outputted + */ +void PrbsGenerator::updateEndState(QString lfsr) +{ + ui->endStateOutput->setText(lfsr); +} + +/* + * Returns the integers in a String + * sorted from greatest to least + */ +std::vector findIntsSort(QString exp) +{ + + std::vector taps; + + for (int i = 0; i < exp.length(); i++) { + if (exp[i].isDigit()) { + QString val = ""; + while (i < exp.length() && exp[i].isDigit()) { + val += exp[i]; + i++; + } + int tap = val.toInt(); + taps.push_back(tap); + + } + } + std::sort(taps.begin(), taps.end(), std::greater()); + + return taps; +} + +/* Returns the integers in a String + * with no sorting + */ +std::vector findIntsNoSort(QString exp) +{ + + std::vector nums; + + + for (int i = 0; i < exp.length(); i++) { + if (exp[i].isDigit()) { + QString val = ""; + while (i < exp.length() && exp[i].isDigit()) { + val += exp[i]; + i++; + } + int tap = val.toInt(); + nums.push_back(tap); + } + } + + return nums; +} + +/* + * Returns all the operations in a string + * (+, -, *, /) + */ +std::vector findOps(QString exp) +{ + std::vector ops; + + for (QChar x : exp) { + if (x.isDigit() == false) { + ops.push_back(x); + } + } + + return ops; +} + +/* + * Performs given operation on two integers + */ +int applyOp(int a, int b, QChar op) +{ + if (op == '*') { + return a * b; + } + else if (op == '/') { + return a / b; + } + else if (op == '+') { + return a + b; + } + else if (op == '-') { + return a - b; + } + + return 0; +} + +QJsonObject PrbsGenerator::getStateFromUi() +{ + + QJsonObject pluginState; + + pluginState.insert("polynomial", ui->initseedInput->text()); + pluginState.insert("taps", ui->tapInput->text()); + + MathParser mp; + ParseResult a = mp.parseInput(ui->bitsWantedInput->text()); + + if (a.isValid()) { + pluginState.insert("bits_wanted", a.getResult()); + } + else { + + } + return pluginState; +} + +bool PrbsGenerator::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + ui->initseedInput->setText(pluginState.value("polynomial").toString()); + ui->tapInput->setText(pluginState.value("taps").toString()); + ui->bitsWantedInput->setText(QString("%1").arg(pluginState.value("bits_wanted").toInt())); + return true; +} + +bool PrbsGenerator::canRecallPluginState(const QJsonObject &pluginState) +{ + if (pluginState.contains("polynomial") && pluginState.value("polynomial").isString()) { + if (pluginState.contains("bits_wanted") && pluginState.value("bits_wanted").isDouble()) { + if (pluginState.contains("taps") && pluginState.value("taps").isString()) { + return true; + } + } + } + return false; +} + +int PrbsGenerator::getMinInputContainers() +{ + return 0; +} + +int PrbsGenerator::getMaxInputContainers() +{ + return 0; +} + +/* + * Returns the PRBS stream + */ +QSharedPointer PrbsGenerator::operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + Q_UNUSED(inputContainers) + QList> outputContainers; + QSharedPointer result(new OperatorResult()); + + int bitsWanted = recallablePluginState.value("bits_wanted").toInt(); + QString seed = recallablePluginState.value("polynomial").toString(); + QString lfsr = seed; + QString usedTaps = recallablePluginState.value("taps").toString(); + + // Displays warning if any input field is empty + if (bitsWanted == 0 || seed == "" || usedTaps == "") { + QMessageBox::warning(nullptr, "Error", "Error: Invalid Input. Please try again."); + result->setOutputContainers(outputContainers)->setPluginState(recallablePluginState); + return result; + } + + QByteArray outputBuffer(bitsWanted / 8 + 1, 0x00); + + QSharedPointer bitContainer = QSharedPointer(new BitContainer()); + QSharedPointer outputBits = QSharedPointer(new BitArray(outputBuffer, bitsWanted)); + + std::vector allTaps = findIntsSort(recallablePluginState.value("taps").toString()); + int numBits = 0; + int outputIndex = 0; + int lastPercent = 0; + + + do { + int bitToAdd = 0; + for (uint i = 0; i < allTaps.size(); i++) { + + int tapIndex = seed.length() - allTaps[i]; + + QStringRef _bitAtIndex(&lfsr, tapIndex, 1); + int bitAtIndex = _bitAtIndex.toInt(); + + bitToAdd ^= bitAtIndex; + + } + + QStringRef _bitForPRBS(&lfsr, 0, 1); + int bitForPRBS = _bitForPRBS.toInt(); + outputBits->set(outputIndex, bitForPRBS); + + QString appendBit = QString::number(bitToAdd); + lfsr += appendBit; + // + lfsr.remove(0, 1); + + ++outputIndex; + ++numBits; + + int nextPercent = int(double(numBits) / double(bitsWanted) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new OperatorResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + + } while (numBits != bitsWanted); + + + updateEndState(lfsr); + bitContainer->setBytes(outputBits->getBytes(), numBits); + outputContainers.append(bitContainer); + + result->setOutputContainers(outputContainers)->setPluginState(recallablePluginState); + return result; + +} + +OperatorInterface* PrbsGenerator::createDefaultOperator() +{ + return new PrbsGenerator(); +} + +/* + * Displays message box indicating + * how to proeprly input which taps you're using + */ +void PrbsGenerator::showHelp() +{ + QMessageBox msg; + msg.setText("Important Note:"); + msg.setInformativeText("Separate each tap by a ','"); + msg.setDefaultButton(QMessageBox::Close); + msg.exec(); + +} diff --git a/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.h b/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.h new file mode 100644 index 00000000..a91d244c --- /dev/null +++ b/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.h @@ -0,0 +1,49 @@ +#ifndef PRBSGENERATOR_H +#define PRBSGENERATOR_H + +#include "mathparser.h" +#include "operatorinterface.h" + + +namespace Ui +{ +class PrbsGenerator; + +} + +class PrbsGenerator : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.PrbsGenerator") + Q_INTERFACES(OperatorInterface) + +public: + PrbsGenerator(); + + QString getName() override; + + OperatorInterface* createDefaultOperator() override; + void applyToWidget(QWidget *widget) override; + void provideCallback(QSharedPointer pluginCallback) override; + void updateEndState(QString lfsr); + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +private slots: + void showHelp(); + +private: + Ui::PrbsGenerator *ui; + QSharedPointer m_pluginCallback; +}; + +#endif // PRBSGENERATOR_H diff --git a/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.ui b/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.ui new file mode 100644 index 00000000..ca4ffb67 --- /dev/null +++ b/src/hobbits-plugins/operators/PrbsGenerator/prbsgenerator.ui @@ -0,0 +1,97 @@ + + + PrbsGenerator + + + + 0 + 0 + 604 + 147 + + + + Form + + + + + + + + End State: + + + + + + + true + + + + + + + true + + + + + + + Initial Seed: + + + + + + + Taps: + + + + + + + + + + + + + + + + + # Bits Wanted: + + + + + + + ? + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/hobbits-plugins/operators/PythonRunner/PythonRunner.pro b/src/hobbits-plugins/operators/PythonRunner/PythonRunner.pro new file mode 100644 index 00000000..89491fa5 --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/PythonRunner.pro @@ -0,0 +1,51 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-12-12T17:39:34.229Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = PythonRunner +TEMPLATE = lib + +DEFINES += PYTHONRUNNER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += pythonrunner.cpp \ + pythonsyntaxhighlighter.cpp + +HEADERS += pythonrunner.h \ + pythonsyntaxhighlighter.h + +FORMS += pythonrunner.ui + + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/operators + INSTALLS += target +} + +DISTFILES += + +RESOURCES += \ + scripts.qrc diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp new file mode 100644 index 00000000..3e1c20e9 --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.cpp @@ -0,0 +1,261 @@ +#include "pythonrunner.h" +#include "ui_pythonrunner.h" +#include +#include +#include +#include +#include +#include +#include + +#include "pythonsyntaxhighlighter.h" + +#include "settingsmanager.h" + +const QString PYTHON_PATH_KEY = "python_runner_path"; + +PythonRunner::PythonRunner() : + ui(new Ui::PythonRunner()) +{ + +} + +// Return name of operator +QString PythonRunner::getName() +{ + return "Python Runner"; +} + +void PythonRunner::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +void PythonRunner::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); + + ui->te_pluginOutput->hide(); + connect(ui->pb_pythonPathSelect, SIGNAL(pressed()), this, SLOT(openPythonPathDialog())); + ui->le_pythonPath->setText(SettingsManager::getInstance().getPrivateSetting(PYTHON_PATH_KEY).toString()); + + connect(ui->pb_scriptHelp, SIGNAL(pressed()), this, SLOT(openHelpDialog())); + + new PythonSyntaxHighlighter(ui->te_pythonScript->document()); +} + +void PythonRunner::openPythonPathDialog() +{ + QString fileName = QFileDialog::getOpenFileName( + nullptr, + tr("Select Python"), + SettingsManager::getInstance().getPrivateSetting(PYTHON_PATH_KEY).toString(), + tr("Python (python*)")); + if (fileName.isEmpty()) { + return; + } + ui->le_pythonPath->setText(fileName); + SettingsManager::getInstance().setPrivateSetting(PYTHON_PATH_KEY, fileName); +} + +void PythonRunner::openHelpDialog() +{ + QMessageBox msg; + msg.setWindowTitle("Python Runner API"); + QString docs; + docs += "BitContainer.length
"; + docs += "The length of the container in bits

"; + docs += "BitContainer.at(position)
"; + docs += "Gets the bit value at the given position

"; + docs += "BitContainer.set_at(position, value)
"; + docs += "Sets the bit value at the given position

"; + docs += "BitContainer.set_length(length)
"; + docs += "Sets the container's length in bits, initializing it with all 0s

"; + docs += "BitContainer.set_data(bytes, length)
"; + docs += "Initializes the container with the given bit-packed bytes and the given length in bits

"; + docs += "BitContainer.bytes
"; + docs += "The bit-packed bytes of the container (use at own risk)"; + msg.setTextFormat(Qt::TextFormat::RichText); + msg.setText(docs); + msg.setDefaultButton(QMessageBox::Ok); + msg.exec(); +} + +QJsonObject PythonRunner::getStateFromUi() +{ + QJsonObject pluginState; + pluginState.insert("script", ui->te_pythonScript->toPlainText()); + return pluginState; +} + +bool PythonRunner::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + ui->te_pythonScript->setPlainText(pluginState.value("script").toString()); + return true; +} + +bool PythonRunner::canRecallPluginState(const QJsonObject &pluginState) +{ + // if pluginState does not have required fields, return false + if (pluginState.isEmpty() == true) { + return false; + } + if (!(pluginState.contains("script") && pluginState.value("script").isString())) { + return false; + } + + return true; +} + +int PythonRunner::getMinInputContainers() +{ + return 1; +} + +int PythonRunner::getMaxInputContainers() +{ + return 1; +} + +QSharedPointer PythonRunner::operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + + QMetaObject::invokeMethod(this, "clearOutputText", Qt::QueuedConnection); + QSharedPointer nullResult; + if (inputContainers.length() != 1) { + return nullResult; + } + if (!canRecallPluginState(recallablePluginState)) { + return nullResult; + } + + QString pythonPath = SettingsManager::getInstance().getPrivateSetting(PYTHON_PATH_KEY).toString(); + if (pythonPath.isEmpty()) { + return nullResult; + } + + QTemporaryDir dir; + if (!dir.isValid()) { + return nullResult; + } + + QFile inputBitFile(dir.filePath("input_bit_container.json")); + QFile outputBitFile(dir.filePath("output_bit_container.json")); + if (!inputBitFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + return nullResult; + } + inputBitFile.write(inputContainers.at(0)->serializeJson().toJson()); + inputBitFile.close(); + + QString coreScriptName = dir.filePath("core_script.py"); + QFile::copy(":/pythonrunner/scripts/runner.py", coreScriptName); + QFile coreScriptFile(coreScriptName); + coreScriptFile.setPermissions(QFile::ReadOwner | QFile::ExeOwner | QFile::WriteOwner); + + QFile userScriptFile(dir.filePath("user_script.py")); + if (!userScriptFile.open(QIODevice::Truncate | QIODevice::WriteOnly)) { + return nullResult; + } + userScriptFile.write(recallablePluginState.value("script").toString().toLatin1()); + userScriptFile.close(); + + QFile errorFile(dir.filePath("error.log")); + QFile stdoutFile(dir.filePath("stdout.log")); + + QStringList args = {coreScriptFile.fileName(), inputBitFile.fileName(), outputBitFile.fileName()}; + QProcess pythonProcess; + QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); + QProcessEnvironment envUpdate; + envUpdate.insert("PATH", env.value("PATH")); + pythonProcess.setProcessEnvironment(envUpdate); + pythonProcess.setWorkingDirectory(dir.path()); + pythonProcess.setStandardErrorFile(errorFile.fileName()); + pythonProcess.setStandardOutputFile(stdoutFile.fileName()); + pythonProcess.start(pythonPath, args); + + bool hasCancelled = false; + while (!pythonProcess.waitForFinished(250)) { + if (progressTracker->getCancelled() && !hasCancelled) { + QFile abortFile(dir.filePath("abort")); + abortFile.open(QIODevice::WriteOnly); + abortFile.close(); + hasCancelled = true; + } + QFile progressFile(dir.filePath("progress")); + if (progressFile.exists()) { + if (progressFile.open(QIODevice::ReadOnly)) { + QJsonDocument progressData = QJsonDocument::fromJson((progressFile.readAll())); + QJsonObject progressJson = progressData.object(); + if (progressJson.contains("progress") && progressJson.value("progress").isDouble()) { + int progress = int(progressJson.value("progress").toDouble()); + progressTracker->setProgressPercent(progress); + } + } + } + } + + QSharedPointer result(new OperatorResult()); + QJsonObject pluginState = recallablePluginState; + + if (errorFile.open(QIODevice::ReadOnly)) { + QString output = errorFile.readAll(); + if (!output.isEmpty()) { + QMetaObject::invokeMethod( + this, + "updateOutputText", + Qt::QueuedConnection, + Q_ARG(QString, "Python stderr:\n" + output + "\n\n")); + } + errorFile.close(); + } + + if (stdoutFile.open(QIODevice::ReadOnly)) { + QString output = stdoutFile.readAll(); + if (!output.isEmpty()) { + QMetaObject::invokeMethod( + this, + "updateOutputText", + Qt::QueuedConnection, + Q_ARG(QString, "Python stdout:\n" + output + "\n\n")); + } + stdoutFile.close(); + } + + if (!outputBitFile.open(QIODevice::ReadOnly)) { + return nullResult; + } + QJsonDocument outputData = QJsonDocument::fromJson((outputBitFile.readAll())); + QSharedPointer outputContainer = QSharedPointer(new BitContainer()); + + outputContainer->deserializeJson(outputData); + outputBitFile.close(); + + result->setPluginState(pluginState); + result->setOutputContainers({outputContainer}); + return std::move(result); +} + +OperatorInterface* PythonRunner::createDefaultOperator() +{ + return new PythonRunner(); +} + +void PythonRunner::updateOutputText(QString text) +{ + ui->te_pluginOutput->appendPlainText(text); + ui->te_pluginOutput->show(); +} + +void PythonRunner::clearOutputText() +{ + ui->te_pluginOutput->hide(); + ui->te_pluginOutput->clear(); +} diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h new file mode 100644 index 00000000..dcb58fe9 --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.h @@ -0,0 +1,54 @@ +#ifndef PYTHONRUNNER_H +#define PYTHONRUNNER_H + +#include "mathparser.h" +#include "operatorinterface.h" + + +namespace Ui +{ +class PythonRunner; + +} + +class PythonRunner : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.PythonRunner") + Q_INTERFACES(OperatorInterface) + +public: + PythonRunner(); + + QString getName() override; + + OperatorInterface* createDefaultOperator() override; + void applyToWidget(QWidget *widget) override; + void provideCallback(QSharedPointer pluginCallback) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +public slots: + void openPythonPathDialog(); + void openHelpDialog(); + + void updateOutputText(QString); + void clearOutputText(); + +private: + Ui::PythonRunner *ui; + QSharedPointer m_pluginCallback; + QString m_outputText; +}; + +#endif // PYTHONRUNNER_H diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui new file mode 100644 index 00000000..5d7f9d3d --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/pythonrunner.ui @@ -0,0 +1,115 @@ + + + PythonRunner + + + + 0 + 0 + 947 + 335 + + + + Form + + + + + + + + Python Path: + + + le_pythonPath + + + + + + + true + + + + + + + ... + + + + + + + + + + + Script: + + + te_pythonScript + + + + + + + ? + + + + + + + + + + + + Monospace + + + + QPlainTextEdit::NoWrap + + + def operate_on_bits(input_container, output_container, operator_handle): + output_container.set_length(input_container.length) + for i in range(0, input_container.length): + output_container.set_at(i, input_container.at(i)) + if i % 256 == 0: + operator_handle.check_cancelled() + operator_handle.set_progress(i/input_container.length*100) + + + + + + + + Monospace + + + + QPlainTextEdit::NoWrap + + + true + + + + + + + + + le_pythonPath + pb_pythonPathSelect + te_pythonScript + + + + diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonsyntaxhighlighter.cpp b/src/hobbits-plugins/operators/PythonRunner/pythonsyntaxhighlighter.cpp new file mode 100644 index 00000000..3821689d --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/pythonsyntaxhighlighter.cpp @@ -0,0 +1,173 @@ +#include "pythonsyntaxhighlighter.h" + +PythonSyntaxHighlighter::PythonSyntaxHighlighter(QTextDocument *parent) : + QSyntaxHighlighter(parent) +{ + keywords = QStringList() << "and" << "assert" << "break" << "class" << "continue" << "def" + << "del" << "elif" << "else" << "except" << "exec" << "finally" + << "for" << "from" << "global" << "if" << "import" << "in" + << "is" << "lambda" << "not" << "or" << "pass" << "print" + << "raise" << "return" << "try" << "while" << "yield" + << "None" << "True" << "False"; + + operators = QStringList() << "=" + <" << ">=" + <>" << "<<"; + + braces = QStringList() << "{" << "}" << "\\(" << "\\)" << "\\[" << "]"; + + basicStyles.insert("keyword", getTextCharFormat("blue")); + basicStyles.insert("operator", getTextCharFormat("red")); + basicStyles.insert("brace", getTextCharFormat("darkGray")); + basicStyles.insert("defclass", getTextCharFormat("black", "bold")); + basicStyles.insert("brace", getTextCharFormat("darkGray")); + basicStyles.insert("string", getTextCharFormat("magenta")); + basicStyles.insert("string2", getTextCharFormat("darkMagenta")); + basicStyles.insert("comment", getTextCharFormat("darkGreen", "italic")); + basicStyles.insert("self", getTextCharFormat("black", "italic")); + basicStyles.insert("numbers", getTextCharFormat("brown")); + + triSingleQuote.setPattern("'''"); + triDoubleQuote.setPattern("\"\"\""); + + initializeRules(); +} + +void PythonSyntaxHighlighter::initializeRules() +{ + foreach(QString currKeyword, keywords) + { + rules.append(HighlightingRule(QString("\\b%1\\b").arg(currKeyword), 0, basicStyles.value("keyword"))); + } + foreach(QString currOperator, operators) + { + rules.append(HighlightingRule(QString("%1").arg(currOperator), 0, basicStyles.value("operator"))); + } + foreach(QString currBrace, braces) + { + rules.append(HighlightingRule(QString("%1").arg(currBrace), 0, basicStyles.value("brace"))); + } + // 'self' + rules.append(HighlightingRule("\\bself\\b", 0, basicStyles.value("self"))); + + // Double-quoted string, possibly containing escape sequences + // FF: originally in python : r'"[^"\\]*(\\.[^"\\]*)*"' + rules.append(HighlightingRule("\"[^\"\\\\]*(\\\\.[^\"\\\\]*)*\"", 0, basicStyles.value("string"))); + // Single-quoted string, possibly containing escape sequences + // FF: originally in python : r"'[^'\\]*(\\.[^'\\]*)*'" + rules.append(HighlightingRule("'[^'\\\\]*(\\\\.[^'\\\\]*)*'", 0, basicStyles.value("string"))); + + // 'def' followed by an identifier + // FF: originally: r'\bdef\b\s*(\w+)' + rules.append(HighlightingRule("\\bdef\\b\\s*(\\w+)", 1, basicStyles.value("defclass"))); + // 'class' followed by an identifier + // FF: originally: r'\bclass\b\s*(\w+)' + rules.append(HighlightingRule("\\bclass\\b\\s*(\\w+)", 1, basicStyles.value("defclass"))); + + // From '#' until a newline + // FF: originally: r'#[^\\n]*' + rules.append(HighlightingRule("#[^\\n]*", 0, basicStyles.value("comment"))); + + // Numeric literals + rules.append(HighlightingRule("\\b[+-]?[0-9]+[lL]?\\b", 0, basicStyles.value("numbers"))); // r'\b[+-]?[0-9]+[lL]?\b' + rules.append(HighlightingRule("\\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\\b", 0, basicStyles.value("numbers"))); // r'\b[+-]?0[xX][0-9A-Fa-f]+[lL]?\b' + rules.append( + HighlightingRule( + "\\b[+-]?[0-9]+(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b", + 0, + basicStyles.value( + "numbers"))); // r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b' +} + +void PythonSyntaxHighlighter::highlightBlock(const QString &text) +{ + foreach(HighlightingRule currRule, rules) + { + int idx = currRule.pattern.indexIn(text, 0); + while (idx >= 0) { + // Get index of Nth match + idx = currRule.pattern.pos(currRule.nth); + int length = currRule.pattern.cap(currRule.nth).length(); + setFormat(idx, length, currRule.format); + idx = currRule.pattern.indexIn(text, idx + length); + } + } + + setCurrentBlockState(0); + + // Do multi-line strings + bool isInMultilne = matchMultiline(text, triSingleQuote, 1, basicStyles.value("string2")); + if (!isInMultilne) { + isInMultilne = matchMultiline(text, triDoubleQuote, 2, basicStyles.value("string2")); + } +} + +bool PythonSyntaxHighlighter::matchMultiline( + const QString &text, + const QRegExp &delimiter, + const int inState, + const QTextCharFormat &style) +{ + int start = -1; + int add = -1; + int end = -1; + int length = 0; + + // If inside triple-single quotes, start at 0 + if (previousBlockState() == inState) { + start = 0; + add = 0; + } + // Otherwise, look for the delimiter on this line + else { + start = delimiter.indexIn(text); + // Move past this match + add = delimiter.matchedLength(); + } + + // As long as there's a delimiter match on this line... + while (start >= 0) { + // Look for the ending delimiter + end = delimiter.indexIn(text, start + add); + // Ending delimiter on this line? + if (end >= add) { + length = end - start + add + delimiter.matchedLength(); + setCurrentBlockState(0); + } + // No; multi-line string + else { + setCurrentBlockState(inState); + length = text.length() - start + add; + } + // Apply formatting and look for next + setFormat(start, length, style); + start = delimiter.indexIn(text, start + length); + } + // Return True if still inside a multi-line string, False otherwise + if (currentBlockState() == inState) { + return true; + } + else { + return false; + } +} + +const QTextCharFormat PythonSyntaxHighlighter::getTextCharFormat(const QString &colorName, const QString &style) +{ + QTextCharFormat charFormat; + QColor color(colorName); + charFormat.setForeground(color); + if (style.contains("bold", Qt::CaseInsensitive)) { + charFormat.setFontWeight(QFont::Bold); + } + if (style.contains("italic", Qt::CaseInsensitive)) { + charFormat.setFontItalic(true); + } + return charFormat; +} diff --git a/src/hobbits-plugins/operators/PythonRunner/pythonsyntaxhighlighter.h b/src/hobbits-plugins/operators/PythonRunner/pythonsyntaxhighlighter.h new file mode 100644 index 00000000..61ed79a9 --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/pythonsyntaxhighlighter.h @@ -0,0 +1,76 @@ +/* + This is a C++ port of the following PyQt example + http://diotavelli.net/PyQtWiki/Python%20syntax%20highlighting + C++ port by Frankie Simon (docklight.de, www.fuh-edv.de) + + The following free software license applies for this file ("X11 license"): + + Permission is hereby granted, free of charge, to any person obtaining a copy of this software + and associated documentation files (the "Software"), to deal in the Software without restriction, + including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all copies or substantial + portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT + LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PYTHONSYNTAXHIGHLIGHTER_H +#define PYTHONSYNTAXHIGHLIGHTER_H + +#include + +//! Container to describe a highlighting rule. Based on a regular expression, a relevant match # and the format. +class HighlightingRule +{ +public: + HighlightingRule(const QString &patternStr, int n, const QTextCharFormat &matchingFormat) + { + originalRuleStr = patternStr; + pattern = QRegExp(patternStr); + nth = n; + format = matchingFormat; + } + + QString originalRuleStr; + QRegExp pattern; + int nth; + QTextCharFormat format; +}; + +//! Implementation of highlighting for Python code. +class PythonSyntaxHighlighter : public QSyntaxHighlighter +{ + Q_OBJECT + +public: + PythonSyntaxHighlighter(QTextDocument *parent = 0); + +protected: + void highlightBlock(const QString &text); + +private: + QStringList keywords; + QStringList operators; + QStringList braces; + + QHash basicStyles; + + void initializeRules(); + + //! Highlighst multi-line strings, returns true if after processing we are still within the multi-line section. + bool matchMultiline(const QString &text, const QRegExp &delimiter, const int inState, const QTextCharFormat &style); + const QTextCharFormat getTextCharFormat(const QString &colorName, const QString &style = QString()); + + QList rules; + QRegExp triSingleQuote; + QRegExp triDoubleQuote; +}; + +#endif // PYTHONSYNTAXHIGHLIGHTER_H diff --git a/src/hobbits-plugins/operators/PythonRunner/scripts.qrc b/src/hobbits-plugins/operators/PythonRunner/scripts.qrc new file mode 100644 index 00000000..8572b357 --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/scripts.qrc @@ -0,0 +1,5 @@ + + + scripts/runner.py + + diff --git a/src/hobbits-plugins/operators/PythonRunner/scripts/runner.py b/src/hobbits-plugins/operators/PythonRunner/scripts/runner.py new file mode 100644 index 00000000..f7898b84 --- /dev/null +++ b/src/hobbits-plugins/operators/PythonRunner/scripts/runner.py @@ -0,0 +1,83 @@ +from user_script import operate_on_bits +import sys +import json +import base64 +import math +import binascii +import os + +BIT_MASKS = [ + 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 +] + +INVERSE_BIT_MASKS = [ + 0x7f, 0xbf, 0xdf, 0xef, 0xf7, 0xfb, 0xfd, 0xfe +] + +class OperationCancelled(Exception): + """Raised when the operation is cancelled""" + pass + +class BitContainer: + def __init__(self): + self.bytes = bytearray(0) + self._length = 0 + + @property + def length(self): + return self._length + + def set_data(self, byte_data, length): + self.bytes = byte_data + self._length = length + + def set_length(self, length): + self.bytes = bytearray(math.ceil(length/8)) + self._length = length + + def at(self, i): + if self.bytes[int(i/8)] & BIT_MASKS[i%8]: + return True + else: + return False + + def set_at(self, i, val): + if val: + self.bytes[int(i/8)] = self.bytes[int(i/8)] | BIT_MASKS[i%8] + else: + self.bytes[int(i/8)] = self.bytes[int(i/8)] & INVERSE_BIT_MASKS[i%8] + +class OperatorHandle: + def __init__(self): + pass + + def check_cancelled(self): + if os.path.exists('abort'): + raise OperationCancelled("Operation was cancelled") + + def set_progress(self, progress): + with open('progress', 'w') as progress_file: + json.dump({"progress": progress}, progress_file) + +if __name__ == "__main__": + input_filename = sys.argv[1] + output_filename = sys.argv[2] + + with open(input_filename, 'r') as input_file: + input_json = json.load(input_file) + + input_container = BitContainer() + input_container.set_data(bytearray(base64.b64decode(input_json["data"])), input_json["size"]) + + output_container = BitContainer() + operator_handle = OperatorHandle() + + operate_on_bits(input_container, output_container, operator_handle) + + output_json = { + "data": base64.b64encode(output_container.bytes).decode("utf-8"), + "size": output_container.length + } + + with open(output_filename, 'w') as output_file: + json.dump(output_json, output_file) \ No newline at end of file diff --git a/src/hobbits-plugins/operators/QamRemapper/QamRemapper.pro b/src/hobbits-plugins/operators/QamRemapper/QamRemapper.pro new file mode 100644 index 00000000..d14cca83 --- /dev/null +++ b/src/hobbits-plugins/operators/QamRemapper/QamRemapper.pro @@ -0,0 +1,48 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-10-02T14:12:38.304Z +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = QamRemapper +TEMPLATE = lib + +DEFINES += QAMREMAPPER_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += qamremapper.cpp \ + remapmodel.cpp + +HEADERS += qamremapper.h \ + remapmodel.h + +FORMS += qamremapper.ui + + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = /opt/hobbits/plugins/operators + INSTALLS += target +} + +DISTFILES += diff --git a/src/hobbits-plugins/operators/QamRemapper/qamremapper.cpp b/src/hobbits-plugins/operators/QamRemapper/qamremapper.cpp new file mode 100644 index 00000000..c682aeba --- /dev/null +++ b/src/hobbits-plugins/operators/QamRemapper/qamremapper.cpp @@ -0,0 +1,225 @@ +#include "qamremapper.h" +#include "ui_qamremapper.h" +#include +#include +#include + + +QamRemapper::QamRemapper() : + ui(new Ui::QamRemapper()), + m_remapModel(new RemapModel()) +{ + +} + +QamRemapper::~QamRemapper() +{ + delete ui; + delete m_remapModel; +} + +// Return name of operator +QString QamRemapper::getName() +{ + return "QAM Remapper"; +} + +void QamRemapper::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +void QamRemapper::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); + m_remapModel->setRemapLength(ui->sb_bitLength->value()); + ui->tv_mappings->setModel(m_remapModel); + connect(ui->sb_bitLength, SIGNAL(valueChanged(int)), m_remapModel, SLOT(setRemapLength(int))); +} + +void QamRemapper::updateMappings() +{ + +} + +QJsonObject QamRemapper::getStateFromUi() +{ + + QJsonObject pluginState; + + // Pull data from the input fields and input them into pluginState + + QJsonArray mappingsArray; + for (QPair pair : m_remapModel->getMappings()) { + QJsonObject pairValue; + pairValue.insert("old", pair.first); + pairValue.insert("new", pair.second); + mappingsArray.append(pairValue); + } + + pluginState.insert("mappings", mappingsArray); + + return pluginState; +} + +bool QamRemapper::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + QJsonArray mappingsArray = pluginState.value("mappings").toArray(); + ui->sb_bitLength->setValue(int(log2(mappingsArray.size()))); + int row = 0; + for (auto valueRef : mappingsArray) { + QJsonObject mappingValue = valueRef.toObject(); + m_remapModel->setData(m_remapModel->index(row, 0), mappingValue.value("old").toString()); + m_remapModel->setData(m_remapModel->index(row, 1), mappingValue.value("new").toString()); + row++; + } + + return true; +} + +bool QamRemapper::canRecallPluginState(const QJsonObject &pluginState) +{ + + // if pluginState does not have required fields, return false + if (pluginState.isEmpty() == true) { + return false; + } + + if (!pluginState.contains("mappings") || !pluginState.value("mappings").isArray()) { + return false; + } + + QJsonArray mappingsArray = pluginState.value("mappings").toArray(); + for (auto valueRef : mappingsArray) { + if (!valueRef.isObject()) { + return false; + } + QJsonObject mappingValue = valueRef.toObject(); + if (!(mappingValue.contains("old") + && mappingValue.contains("new") + && mappingValue.value("old").isString() + && mappingValue.value("new").isString())) { + return false; + } + } + + return true; +} + +int QamRemapper::getMinInputContainers() +{ + return 1; +} + +int QamRemapper::getMaxInputContainers() +{ + return 1; +} + +QSharedPointer QamRemapper::operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + + QSharedPointer nullResult(new OperatorResult()); + + if (inputContainers.size() != 1) { + return nullResult; + } + + if (!canRecallPluginState(recallablePluginState)) { + return nullResult; + } + + QHash bitMapping; + QJsonArray mappingsArray = recallablePluginState.value("mappings").toArray(); + int bitChunkLength = 0; + for (auto valueRef : mappingsArray) { + + QString oldVal = valueRef.toObject().value("old").toString(); + QString newVal = valueRef.toObject().value("new").toString(); + + bitChunkLength = oldVal.size(); + + QStringList parseErrors; + + BitArray oldBits = BitArray::fromString("0b" + oldVal, parseErrors); + BitArray newBits = BitArray::fromString("0b" + newVal, parseErrors); + + if (!parseErrors.isEmpty()) { + return nullResult; + } + + bitMapping.insert(oldBits, newBits); + } + + QSharedPointer inputBits = inputContainers.at(0)->getBaseBits(); + int inputBitsLength = inputBits->size(); + + BitArray outputArray(QByteArray(inputBitsLength / 8 + (inputBitsLength % 8 == 0 ? 0 : 1), 0), inputBitsLength); + + QJsonObject pluginState = recallablePluginState; + + int lastPercent = 0; + int failedRemappings = 0; + BitArray inputBitChunk(QByteArray(bitChunkLength / 8 + (bitChunkLength % 8 == 0 ? 0 : 1), 0), bitChunkLength); + for (int i = 0; i < outputArray.size() && i + bitChunkLength <= inputBits->size(); i += bitChunkLength) { + for (int ii = 0; ii < bitChunkLength; ii++) { + inputBitChunk.set(ii, inputBits->at(i + ii)); + } + if (bitMapping.contains(inputBitChunk)) { + BitArray mappedValue = bitMapping.value(inputBitChunk); + for (int ii = 0; ii < mappedValue.size(); ii++) { + outputArray.set(i + ii, mappedValue.at(ii)); + } + } + else { + failedRemappings++; + } + + int nextPercent = int(double(i) / double(inputBitsLength) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new OperatorResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + if (failedRemappings > 0) { + pluginState.insert( + "error", + QString( + "Failed at the remapping of %1 sequences - the output data will be incorrect where the remapping failed").arg( + failedRemappings)); + } + + QList> containers; + QSharedPointer container(new BitContainer()); + container->setBytes(outputArray.getBytes(), outputArray.size()); + containers << container; + + pluginState.insert("container_name", QString("%1 <- %2").arg("QAM Remap").arg(inputContainers.at(0)->getName())); + + QSharedPointer result((new OperatorResult())->setOutputContainers(containers)->setPluginState( + pluginState)); + + return result; +} + +OperatorInterface* QamRemapper::createDefaultOperator() +{ + return new QamRemapper(); +} diff --git a/src/hobbits-plugins/operators/QamRemapper/qamremapper.h b/src/hobbits-plugins/operators/QamRemapper/qamremapper.h new file mode 100644 index 00000000..b552b8c6 --- /dev/null +++ b/src/hobbits-plugins/operators/QamRemapper/qamremapper.h @@ -0,0 +1,52 @@ +#ifndef QAMREMAPPER_H +#define QAMREMAPPER_H + +#include "mathparser.h" +#include "operatorinterface.h" +#include "remapmodel.h" + +namespace Ui +{ +class QamRemapper; + +} + +class QamRemapper : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.QamRemapper") + Q_INTERFACES(OperatorInterface) + +public: + QamRemapper(); + + ~QamRemapper() override; + + QString getName() override; + + OperatorInterface* createDefaultOperator() override; + void applyToWidget(QWidget *widget) override; + void provideCallback(QSharedPointer pluginCallback) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +private: + Ui::QamRemapper *ui; + RemapModel *m_remapModel; + QSharedPointer m_pluginCallback; + +private slots: + void updateMappings(); + +}; + +#endif // QAMREMAPPER_H diff --git a/src/hobbits-plugins/operators/QamRemapper/qamremapper.ui b/src/hobbits-plugins/operators/QamRemapper/qamremapper.ui new file mode 100644 index 00000000..fc0d640a --- /dev/null +++ b/src/hobbits-plugins/operators/QamRemapper/qamremapper.ui @@ -0,0 +1,69 @@ + + + QamRemapper + + + + 0 + 0 + 610 + 160 + + + + Form + + + + + + + + + + + + + + + Bit Length + + + + + + + 1 + + + 8 + + + 2 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + diff --git a/src/hobbits-plugins/operators/QamRemapper/remapmodel.cpp b/src/hobbits-plugins/operators/QamRemapper/remapmodel.cpp new file mode 100644 index 00000000..09ff213d --- /dev/null +++ b/src/hobbits-plugins/operators/QamRemapper/remapmodel.cpp @@ -0,0 +1,139 @@ +#include "remapmodel.h" +#include + +#include +#include + +RemapModel::RemapModel(QObject *parent) : + QAbstractTableModel(parent), + m_remapLength(2) +{ + initializeMappings(); +} + +void RemapModel::initializeMappings() +{ + + beginRemoveRows(QModelIndex(), 0, m_mappings.length() - 1); + m_mappings.clear(); + endRemoveRows(); + + int symbolsToMap = (1 << m_remapLength); + + beginInsertRows(QModelIndex(), 0, symbolsToMap - 1); + for (int i = 0; i < symbolsToMap; i++) { + QString mappingString = ""; + for (int bit = m_remapLength - 1; bit >= 0; bit--) { + if (i & (1 << bit)) { + mappingString += "1"; + } + else { + mappingString += "0"; + } + } + m_mappings.append(QPair(mappingString, mappingString)); + } + endInsertRows(); +} + +void RemapModel::setRemapLength(int length) +{ + this->m_remapLength = length; + initializeMappings(); +} + +QList> RemapModel::getMappings() +{ + return m_mappings; +} + +QVariant RemapModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Orientation::Horizontal && role == Qt::DisplayRole) { + if (section == 0) { + return QVariant("Old"); + } + else if (section == 1) { + return QVariant("New"); + } + } + + return QVariant(); +} + +int RemapModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return m_mappings.size(); +} + +int RemapModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) { + return 0; + } + + return 2; +} + +QVariant RemapModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (role == Qt::DisplayRole) { + if (index.column() == 0) { + return m_mappings.at(index.row()).first; + } + else if (index.column() == 1) { + return m_mappings.at(index.row()).second; + } + } + else if (role == Qt::FontRole) { + return QFont("monospace", 12); + } + return QVariant(); +} + +bool RemapModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (data(index, role) != value && index.column() == 1) { + + QString mapValue = value.toString(); + if (mapValue.length() != m_mappings.at(index.row()).first.length()) { + return false; + } + + QRegularExpression binMatcher("^[0-1]+$"); + QRegularExpressionMatch match = binMatcher.match(mapValue); + if (!match.hasMatch()) { + return false; + } + + m_mappings.replace( + index.row(), + QPair(m_mappings.at(index.row()).first, mapValue)); + + emit dataChanged(index, index, QVector() << role); + return true; + } + return false; +} + +Qt::ItemFlags RemapModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) { + return Qt::NoItemFlags; + } + + if (index.column() == 1) { + return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } + else { + return Qt::NoItemFlags; + } +} diff --git a/src/hobbits-plugins/operators/QamRemapper/remapmodel.h b/src/hobbits-plugins/operators/QamRemapper/remapmodel.h new file mode 100644 index 00000000..054bf629 --- /dev/null +++ b/src/hobbits-plugins/operators/QamRemapper/remapmodel.h @@ -0,0 +1,43 @@ +#ifndef REMAPMODEL_H +#define REMAPMODEL_H + +#include + +class RemapModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit RemapModel(QObject *parent = nullptr); + + // Header: + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // Basic functionality: + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + // Editable: + bool setData( + const QModelIndex &index, + const QVariant &value, + int role = Qt::EditRole) override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + + QList> getMappings(); + +public slots: + void setRemapLength(int length); + +private: + void initializeMappings(); + + int m_remapLength; + QList> m_mappings; + +}; + +#endif // REMAPMODEL_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/TakeSkipOperator.pro b/src/hobbits-plugins/operators/TakeSkipOperator/TakeSkipOperator.pro new file mode 100644 index 00000000..b734ab7e --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/TakeSkipOperator.pro @@ -0,0 +1,61 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2019-06-05T17:13:08 +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = $$qtLibraryTarget(TakeSkipOperator) +TEMPLATE = lib +CONFIG += c++11 plugin + +DEFINES += TAKESKIPOPERATOR_LIBRARY + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + bitop.cpp \ + invertop.cpp \ + oneop.cpp \ + reverseop.cpp \ + skipop.cpp \ + takeop.cpp \ + takeskipoperator.cpp \ + zeroop.cpp + +HEADERS += \ + bitop.h \ + invertop.h \ + oneop.h \ + reverseop.h \ + skipop.h \ + takeop.h \ + takeskipoperator.h \ + zeroop.h + +FORMS += \ + takeskipoperator.ui + +DISTFILES += + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = $$(HOME)/.local/share/hobbits/plugins/operators + INSTALLS += target +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/bitop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/bitop.cpp new file mode 100644 index 00000000..078d4619 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/bitop.cpp @@ -0,0 +1,61 @@ +#include "bitop.h" +#include "invertop.h" +#include "oneop.h" +#include "reverseop.h" +#include "skipop.h" +#include "takeop.h" +#include "zeroop.h" +#include +#include +#include + +BitOp::BitOp(int value) : + m_value(value) +{ + +} + +BitOp::~BitOp() +{ + +} + +QList> BitOp::parseOps(QString opString) +{ + QRegularExpression re("\\K([stozri](\\*|\\d+))"); + QRegularExpressionMatchIterator match = re.globalMatch(opString); + + QList> ops; + while (match.hasNext()) { + QString op = match.next().captured(); + int value = 0; + if (op.mid(1) == "*") { + value = INT_MAX; + } + else { + value = op.mid(1).toInt(); + } + if (value < 1) { + continue; + } + if (op.startsWith("t")) { + ops.append(QSharedPointer(new TakeOp(value))); + } + else if (op.startsWith("s")) { + ops.append(QSharedPointer(new SkipOp(value))); + } + else if (op.startsWith("o")) { + ops.append(QSharedPointer(new OneOp(value))); + } + else if (op.startsWith("z")) { + ops.append(QSharedPointer(new ZeroOp(value))); + } + else if (op.startsWith("r")) { + ops.append(QSharedPointer(new ReverseOp(value))); + } + else if (op.startsWith("i")) { + ops.append(QSharedPointer(new InvertOp(value))); + } + } + return ops; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/bitop.h b/src/hobbits-plugins/operators/TakeSkipOperator/bitop.h new file mode 100644 index 00000000..8fc0e274 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/bitop.h @@ -0,0 +1,29 @@ +#ifndef BITOP_H +#define BITOP_H + +#include +#include + +class BitOp +{ +public: + BitOp(int value); + + virtual ~BitOp(); + + virtual void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) = 0; + + static QList> parseOps(QString opString); + + virtual int inputStep() const = 0; + virtual int outputStep() const = 0; + +protected: + int m_value; +}; + +#endif // BITOP_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/invertop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/invertop.cpp new file mode 100644 index 00000000..e9007306 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/invertop.cpp @@ -0,0 +1,30 @@ +#include "invertop.h" + +InvertOp::InvertOp(int value) : + BitOp(value) +{ + +} + +void InvertOp::apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) +{ + for (int i = 0; i < m_value && inputIdx < inputBits->size(); i++) { + outputBits->set(outputIdx, !inputBits->at(inputIdx)); + inputIdx++; + outputIdx++; + } +} + +int InvertOp::inputStep() const +{ + return m_value; +} + +int InvertOp::outputStep() const +{ + return m_value; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/invertop.h b/src/hobbits-plugins/operators/TakeSkipOperator/invertop.h new file mode 100644 index 00000000..398eefb0 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/invertop.h @@ -0,0 +1,23 @@ +#ifndef INVERTOP_H +#define INVERTOP_H + +#include "bitop.h" + + +class InvertOp : public BitOp +{ +public: + InvertOp(int value); + + void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) override; + + int inputStep() const override; + int outputStep() const override; + +}; + +#endif // INVERTOP_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/oneop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/oneop.cpp new file mode 100644 index 00000000..c3a11574 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/oneop.cpp @@ -0,0 +1,31 @@ +#include "oneop.h" + +OneOp::OneOp(int value) : + BitOp(value) +{ + +} + +void OneOp::apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) +{ + Q_UNUSED(inputBits) + Q_UNUSED(inputIdx) + for (int i = 0; i < m_value; i++) { + outputBits->set(outputIdx, true); + outputIdx++; + } +} + +int OneOp::inputStep() const +{ + return 0; +} + +int OneOp::outputStep() const +{ + return m_value; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/oneop.h b/src/hobbits-plugins/operators/TakeSkipOperator/oneop.h new file mode 100644 index 00000000..95b52488 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/oneop.h @@ -0,0 +1,23 @@ +#ifndef ONEOP_H +#define ONEOP_H + +#include "bitop.h" + + +class OneOp : public BitOp +{ +public: + OneOp(int value); + + void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) override; + + int inputStep() const override; + int outputStep() const override; + +}; + +#endif // ONEOP_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/reverseop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/reverseop.cpp new file mode 100644 index 00000000..157af49f --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/reverseop.cpp @@ -0,0 +1,30 @@ +#include "reverseop.h" + +ReverseOp::ReverseOp(int value) : + BitOp(value) +{ + +} + +void ReverseOp::apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) +{ + for (int i = m_value - 1; i >= 0 && inputIdx + i < inputBits->size(); i--) { + outputBits->set(outputIdx, inputBits->at(inputIdx + i)); + outputIdx++; + } + inputIdx = qMin(inputBits->size(), inputIdx + m_value); +} + +int ReverseOp::inputStep() const +{ + return m_value; +} + +int ReverseOp::outputStep() const +{ + return m_value; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/reverseop.h b/src/hobbits-plugins/operators/TakeSkipOperator/reverseop.h new file mode 100644 index 00000000..abca9169 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/reverseop.h @@ -0,0 +1,23 @@ +#ifndef REVERSEOP_H +#define REVERSEOP_H + +#include "bitop.h" + + +class ReverseOp : public BitOp +{ +public: + ReverseOp(int value); + + void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) override; + + int inputStep() const override; + int outputStep() const override; + +}; + +#endif // REVERSEOP_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/skipop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/skipop.cpp new file mode 100644 index 00000000..3f43bf1d --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/skipop.cpp @@ -0,0 +1,29 @@ +#include "skipop.h" + +SkipOp::SkipOp(int value) : + BitOp(value) +{ + +} + +void SkipOp::apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) +{ + Q_UNUSED(outputBits) + Q_UNUSED(outputIdx) + inputIdx += m_value; + inputIdx = qMin(inputIdx, inputBits->size()); +} + +int SkipOp::inputStep() const +{ + return m_value; +} + +int SkipOp::outputStep() const +{ + return 0; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/skipop.h b/src/hobbits-plugins/operators/TakeSkipOperator/skipop.h new file mode 100644 index 00000000..c1eaf15e --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/skipop.h @@ -0,0 +1,22 @@ +#ifndef SKIPOP_H +#define SKIPOP_H + +#include "bitop.h" + +class SkipOp : public BitOp +{ +public: + SkipOp(int value); + + void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) override; + + int inputStep() const override; + int outputStep() const override; + +}; + +#endif // SKIPOP_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/takeop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/takeop.cpp new file mode 100644 index 00000000..4a280863 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/takeop.cpp @@ -0,0 +1,30 @@ +#include "takeop.h" + +TakeOp::TakeOp(int value) : + BitOp(value) +{ + +} + +void TakeOp::apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) +{ + for (int i = 0; i < m_value && inputIdx < inputBits->size(); i++) { + outputBits->set(outputIdx, inputBits->at(inputIdx)); + inputIdx++; + outputIdx++; + } +} + +int TakeOp::inputStep() const +{ + return m_value; +} + +int TakeOp::outputStep() const +{ + return m_value; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/takeop.h b/src/hobbits-plugins/operators/TakeSkipOperator/takeop.h new file mode 100644 index 00000000..372a638c --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/takeop.h @@ -0,0 +1,22 @@ +#ifndef TAKEOP_H +#define TAKEOP_H + +#include "bitop.h" + +class TakeOp : public BitOp +{ +public: + TakeOp(int value); + + void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) override; + + int inputStep() const override; + int outputStep() const override; + +}; + +#endif // TAKEOP_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.cpp new file mode 100644 index 00000000..cac45b00 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.cpp @@ -0,0 +1,175 @@ +#include "bitop.h" +#include "takeskipoperator.h" +#include "ui_takeskipoperator.h" + +#include +#include + +TakeSkipOperator::TakeSkipOperator() : + ui(new Ui::TakeSkipOperator()) +{ +} + +QString TakeSkipOperator::getName() +{ + return "Take Skip"; +} + +void TakeSkipOperator::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +QJsonObject TakeSkipOperator::getStateFromUi() +{ + QJsonObject pluginState; + pluginState.insert("take_skip_string", ui->le_takeSkip->text()); + return pluginState; +} + +bool TakeSkipOperator::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + ui->le_takeSkip->setText(pluginState.value("take_skip_string").toString()); + return true; +} + +bool TakeSkipOperator::canRecallPluginState(const QJsonObject &pluginState) +{ + return pluginState.contains("take_skip_string") && pluginState.value("take_skip_string").isString(); +} + +int TakeSkipOperator::getMinInputContainers() +{ + return 1; +} + +int TakeSkipOperator::getMaxInputContainers() +{ + return 1; +} + +QSharedPointer TakeSkipOperator::operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + QList> outputContainers; + QSharedPointer nullResult; + + if (inputContainers.size() != 1) { + return nullResult; + } + + QList> ops = BitOp::parseOps(recallablePluginState.value("take_skip_string").toString()); + if (ops.isEmpty()) { + return nullResult; + } + + int inputStepTotal = 0; + int outputStepTotal = 0; + for (QSharedPointer op : ops) { + // need to check for integer overflow + int inTotal = op->inputStep() + inputStepTotal; + if (inTotal < op->inputStep() || inTotal < inputStepTotal) { + inputStepTotal = INT_MAX; + } + else { + inputStepTotal = inTotal; + } + int outTotal = op->outputStep() + outputStepTotal; + if (inTotal < op->outputStep() || outTotal < outputStepTotal) { + outputStepTotal = INT_MAX; + } + else { + outputStepTotal = outTotal; + } + } + if (inputStepTotal < 1) { + return nullResult; + } + + int opCycles = inputContainers.at(0)->getBaseBits()->size() / inputStepTotal + + (inputContainers.at(0)->getBaseBits()->size() % inputStepTotal ? 1 : 0); + int outputBufferSize = opCycles * outputStepTotal; + if (outputBufferSize < outputStepTotal) { + outputBufferSize = INT_MAX; + } + outputBufferSize = qMin(outputBufferSize / 8 + (outputBufferSize % 8 ? 1 : 0), MAX_BIT_CONTAINER_SIZE / 8); + + int inputIdx = 0; + int outputIdx = 0; + QSharedPointer inputBits = inputContainers.at(0)->getBaseBits(); + + QByteArray bytes(outputBufferSize, 0x00); + QSharedPointer outputBits = QSharedPointer(new BitArray(bytes, bytes.size() * 8)); + + int lastPercent = 0; + while (inputIdx < inputBits->size()) { + for (QSharedPointer op : ops) { + op->apply(inputBits, outputBits, inputIdx, outputIdx); + } + if (inputIdx > 0) { + int nextPercent = int(double(inputIdx) / double(inputBits->size()) * 100.0); + if (nextPercent > lastPercent) { + lastPercent = nextPercent; + progressTracker->setProgressPercent(nextPercent); + } + } + if (progressTracker->getCancelled()) { + return QSharedPointer( + (new OperatorResult())->setPluginState( + QJsonObject( + {QPair( + "error", + QJsonValue("Processing cancelled"))})) + ); + } + } + + QSharedPointer bitContainer = QSharedPointer(new BitContainer()); + bitContainer->setBytes(outputBits->getBytes(), outputIdx); + outputContainers.append(bitContainer); + QJsonObject pluginState(recallablePluginState); + pluginState.insert( + "container_name", + QString("%1 <- %2").arg(recallablePluginState.value("take_skip_string").toString()).arg( + inputContainers.at(0) + -> + getName())); + return QSharedPointer( + (new OperatorResult())->setOutputContainers( + outputContainers)->setPluginState(pluginState)); +} + +OperatorInterface* TakeSkipOperator::createDefaultOperator() +{ + return new TakeSkipOperator(); +} + +void TakeSkipOperator::requestRun() +{ + if (!m_pluginCallback.isNull()) { + m_pluginCallback->requestRun(getName()); + } +} + +void TakeSkipOperator::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); + connect(ui->btnInfo, SIGNAL(clicked()), this, SLOT(showHelp())); + connect(ui->le_takeSkip, SIGNAL(returnPressed()), this, SLOT(requestRun())); +} + +void TakeSkipOperator::showHelp() +{ + QMessageBox msg; + msg.setText("TakeSkip Commands"); + msg.setInformativeText("t - take\ns - skip\nr - take in reverse\ni - invert\no - append a 1\nz - append a 0"); + msg.setDefaultButton(QMessageBox::Cancel); + msg.exec(); +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.h b/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.h new file mode 100644 index 00000000..9cc52f28 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.h @@ -0,0 +1,45 @@ +#ifndef TAKESKIPOPERATOR_H +#define TAKESKIPOPERATOR_H + +#include "operatorinterface.h" + +namespace Ui +{ +class TakeSkipOperator; +} + +class TakeSkipOperator : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.TakeSkipOperator") + Q_INTERFACES(OperatorInterface) + +public: + TakeSkipOperator(); + + QString getName() override; + OperatorInterface* createDefaultOperator() override; + void applyToWidget(QWidget *widget) override; + void provideCallback(QSharedPointer pluginCallback) override; + + bool canRecallPluginState(const QJsonObject &pluginState) override; + virtual bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList> inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +private slots: + void showHelp(); + void requestRun(); + +private: + Ui::TakeSkipOperator *ui; + QSharedPointer m_pluginCallback; +}; + +#endif // TAKESKIPOPERATOR_H diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.ui b/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.ui new file mode 100644 index 00000000..078def59 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/takeskipoperator.ui @@ -0,0 +1,70 @@ + + + TakeSkipOperator + + + + 0 + 0 + 539 + 56 + + + + Form + + + true + + + + + + + + + + + -1 + + + + + + ? + + + + + + + Take Skip String + + + le_takeSkip + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/zeroop.cpp b/src/hobbits-plugins/operators/TakeSkipOperator/zeroop.cpp new file mode 100644 index 00000000..4a70cb92 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/zeroop.cpp @@ -0,0 +1,31 @@ +#include "zeroop.h" + +ZeroOp::ZeroOp(int value) : + BitOp(value) +{ + +} + +void ZeroOp::apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) +{ + Q_UNUSED(inputBits) + Q_UNUSED(inputIdx) + for (int i = 0; i < m_value; i++) { + outputBits->set(outputIdx, false); + outputIdx++; + } +} + +int ZeroOp::inputStep() const +{ + return 0; +} + +int ZeroOp::outputStep() const +{ + return m_value; +} diff --git a/src/hobbits-plugins/operators/TakeSkipOperator/zeroop.h b/src/hobbits-plugins/operators/TakeSkipOperator/zeroop.h new file mode 100644 index 00000000..52bff687 --- /dev/null +++ b/src/hobbits-plugins/operators/TakeSkipOperator/zeroop.h @@ -0,0 +1,23 @@ +#ifndef ZEROOP_H +#define ZEROOP_H + +#include "bitop.h" + + +class ZeroOp : public BitOp +{ +public: + ZeroOp(int value); + + void apply( + QSharedPointer inputBits, + QSharedPointer outputBits, + int &inputIdx, + int &outputIdx) override; + + int inputStep() const override; + int outputStep() const override; + +}; + +#endif // ZEROOP_H diff --git a/src/hobbits-plugins/operators/operators.pro b/src/hobbits-plugins/operators/operators.pro new file mode 100644 index 00000000..7f84dd5e --- /dev/null +++ b/src/hobbits-plugins/operators/operators.pro @@ -0,0 +1,10 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + \ + BitsError \ + PrbsGenerator \ + PythonRunner \ + QamRemapper \ + TakeSkipOperator \ + HeaderFramer diff --git a/src/hobbits-runner/hobbits-runner.pro b/src/hobbits-runner/hobbits-runner.pro new file mode 100644 index 00000000..51436cb6 --- /dev/null +++ b/src/hobbits-runner/hobbits-runner.pro @@ -0,0 +1,34 @@ +#QT -= gui + +CONFIG += c++11 console +CONFIG -= app_bundle + +DEFINES += "HOBBITS_RUNNER_VERSION=\"\\\"Extra Good Developer Version\\\"\"" + +# The following define makes your compiler emit warnings if you use +# any Qt feature that has been marked deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if it uses deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + hobbitsrunnerinfo.cpp \ + main.cpp + +# Default rules for deployment. +qnx: target.path = /tmp/$${TARGET}/bin +else: unix:!android: target.path = /opt/$${TARGET}/bin +!isEmpty(target.path): INSTALLS += target + +HEADERS += \ + hobbitsrunnerinfo.h + +LIBS += -L$$OUT_PWD/../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../hobbits-core +DEPENDPATH += $$PWD/../hobbits-core diff --git a/src/hobbits-runner/hobbitsrunnerinfo.cpp b/src/hobbits-runner/hobbitsrunnerinfo.cpp new file mode 100644 index 00000000..a6f1a343 --- /dev/null +++ b/src/hobbits-runner/hobbitsrunnerinfo.cpp @@ -0,0 +1,12 @@ +#include "hobbitsrunnerinfo.h" + + +QString HobbitsRunnerInfo::getRunnerVersion() +{ +#ifdef HOBBITS_RUNNER_VERSION + QString version = HOBBITS_RUNNER_VERSION; +#else + QString version = "Non-standard Build Version"; +#endif + return version; +} diff --git a/src/hobbits-runner/hobbitsrunnerinfo.h b/src/hobbits-runner/hobbitsrunnerinfo.h new file mode 100644 index 00000000..ec6e5b51 --- /dev/null +++ b/src/hobbits-runner/hobbitsrunnerinfo.h @@ -0,0 +1,14 @@ +#ifndef HOBBITSRUNNERINFO_H +#define HOBBITSRUNNERINFO_H + + +#include + +namespace HobbitsRunnerInfo +{ + +QString getRunnerVersion(); + +} + +#endif // HOBBITSRUNNERINFO_H diff --git a/src/hobbits-runner/main.cpp b/src/hobbits-runner/main.cpp new file mode 100644 index 00000000..05307999 --- /dev/null +++ b/src/hobbits-runner/main.cpp @@ -0,0 +1,198 @@ +#include +#include +#include +#ifdef Q_OS_UNIX +#include +#include +#endif +#include "hobbitsrunnerinfo.h" +#include "pluginactionlineage.h" +#include "pluginactionmanager.h" +#include "pluginmanager.h" +#include "settingsmanager.h" +#include "templatefilehandler.h" +#include + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + QCoreApplication::setApplicationName("Hobbits Runner"); + QCoreApplication::setApplicationVersion(HobbitsRunnerInfo::getRunnerVersion()); + + QCommandLineParser parser; + parser.setApplicationDescription("Command-line interface for bitstream analysis and processing"); + parser.addHelpOption(); + parser.addVersionOption(); + + + parser.addPositionalArgument( + "mode", + "The mode to run in\ +'run': applies a given template to input data - requires a template parameter"); + + QCommandLineOption inputFileOption( + QStringList() << "i" << "input", + QCoreApplication::translate("main", "File to open on application startup (use '-' for piped input)"), + QCoreApplication::translate("main", "file")); + parser.addOption(inputFileOption); + + QCommandLineOption templateOption( + QStringList() << "t" << "template", + QCoreApplication::translate("main", "Template file to use for processing"), + QCoreApplication::translate("main", "file")); + parser.addOption(templateOption); + + QCommandLineOption outputPrefixOption( + QStringList() << "o" << "output", + QCoreApplication::translate("main", "A path prefix for output files"), + QCoreApplication::translate("main", "file prefix")); + parser.addOption(inputFileOption); + + + QCommandLineOption extraPluginPathOption( + QStringList() << "p" << "extra-plugin-path", + QCoreApplication::translate("main", "An additional path to search for plugins in"), + QCoreApplication::translate("main", "path")); + parser.addOption(extraPluginPathOption); + + + QCommandLineOption configFilePathOption( + QStringList() << "c" << "config", + QCoreApplication::translate("main", "Override the default config file location"), + QCoreApplication::translate("main", "file")); + parser.addOption(configFilePathOption); + + parser.process(a); + + QTextStream out(stdout); + QTextStream err(stderr); + if (parser.positionalArguments().size() != 1) { + err << "Error: Cannot run without specifying a mode" << endl; + err << parser.helpText() << endl; + return -1; + } + + QByteArray pipedInData; +#ifdef Q_OS_UNIX + if (parser.isSet(inputFileOption) && parser.value(inputFileOption) == "-") { + while (!std::cin.eof()) { + char arr[1024]; + std::cin.read(arr, sizeof(arr)); + int s = std::cin.gcount(); + pipedInData.append(arr, s); + } + } +#endif + + + if (parser.isSet(configFilePathOption)) { + SettingsManager::getInstance().setConfigFilePath(parser.value(configFilePathOption)); + } + + // Initialize some stuff + QSharedPointer pluginManager = QSharedPointer(new PluginManager); + QSharedPointer pluginActionManager = QSharedPointer( + new PluginActionManager( + pluginManager)); + + // Load plugins + QStringList pluginPaths; + QStringList warnings; + if (parser.isSet(extraPluginPathOption)) { + pluginPaths.append(parser.value(extraPluginPathOption).split(":")); + } + pluginPaths.append( + SettingsManager::getInstance().getPluginLoaderSetting( + SettingsData::PLUGIN_PATH_KEY).toString().split(":")); + for (QString pluginPath : pluginPaths) { + if (pluginPath.startsWith("~/")) { + pluginPath.replace(0, 1, QDir::homePath()); + } + else if (!pluginPath.startsWith("/")) { + pluginPath = a.applicationDirPath() + "/" + pluginPath; + } + warnings.append(pluginManager->loadPlugins(pluginPath)); + } + for (auto warning : warnings) { + err << "Plugin load warning: " << warning << endl; + } + + // Run + QString mode = parser.positionalArguments().at(0); + if (mode == "run") { + if (!parser.isSet(templateOption) || !parser.isSet(inputFileOption)) { + err << "Error: Cannot run in 'run' mode without a template and input specified" << endl; + err << parser.helpText() << endl; + return -1; + } + QByteArray inputData; + if (pipedInData.isNull()) { + QFile inputFile(parser.value(inputFileOption)); + if (!inputFile.open(QIODevice::ReadOnly)) { + err << "Error: cannot open input file: " << parser.value(inputFileOption) << endl; + return -1; + } + inputData = inputFile.readAll(); + inputFile.close(); + } + else { + inputData = pipedInData; + } + QSharedPointer targetContainer = QSharedPointer(new BitContainer()); + targetContainer->setBytes(inputData); + + QSharedPointer bitManager = QSharedPointer(new BitContainerManager()); + bitManager->getTreeModel()->addContainer(targetContainer); + + QObject::connect( + pluginActionManager.data(), + &PluginActionManager::reportError, + [&err, &a, pluginActionManager](QString error) { + err << "Plugin Action Error: " << error; + pluginActionManager->cancelAction(); + a.exit(-1); + }); + + int outputNumber = 1; + QObject::connect( + bitManager.data(), + &BitContainerManager::currSelectionChanged, + [&a, bitManager, &outputNumber, pluginActionManager](const QItemSelection &selected, + const QItemSelection &deselected) { + Q_UNUSED(selected) + Q_UNUSED(deselected) + auto container = bitManager->getCurrentContainer(); + QFile output(QString("hobbits_output_%1").arg(outputNumber++)); + output.open(QIODevice::WriteOnly); + output.write(container->getBaseBits()->getBytes(), container->getBaseBits()->size() / 8); + }); + + QObject::connect( + pluginActionManager.data(), + &PluginActionManager::lineageQueueCompleted, + [&a]() { + a.exit(); + }); + + warnings.clear(); + if (!TemplateFileHandler::applyTemplateToContainer( + parser.value(templateOption), + targetContainer, + bitManager, + pluginActionManager, + &warnings)) { + err << warnings.join("\n"); + return -1; + } + else if (!warnings.isEmpty()) { + err << warnings.join("\n"); + } + a.exec(); + } + else { + err << QString("Error: Cannot run with mode option '%1'").arg(mode) << endl; + err << parser.helpText() << endl; + return -1; + } +} diff --git a/src/hobbits.pro b/src/hobbits.pro new file mode 100644 index 00000000..26b5048c --- /dev/null +++ b/src/hobbits.pro @@ -0,0 +1,16 @@ +TEMPLATE = subdirs + +SUBDIRS += \ + gui \ + core \ + runner \ + plugins + +core.subdir = hobbits-core +gui.subdir = hobbits-gui +plugins.subdir = hobbits-plugins +runner.subdir = hobbits-runner + +gui.depends = core +plugins.depends = core +runner.depends = core diff --git a/uncrustify.cfg b/uncrustify.cfg new file mode 100644 index 00000000..fe745b15 --- /dev/null +++ b/uncrustify.cfg @@ -0,0 +1,1961 @@ +# +# General options +# + +# The type of line endings. Default=Auto +newlines = auto # auto/lf/crlf/cr + +# The original size of tabs in the input. Default=8 +input_tab_size = 4 # number + +# The size of tabs in the output (only used if align_with_tabs=true). Default=8 +output_tab_size = 4 # number + +# The ASCII value of the string escape char, usually 92 (\) or 94 (^). (Pawn) +string_escape_char = 92 # number + +# Alternate string escape char for Pawn. Only works right before the quote char. +string_escape_char2 = 0 # number + +# Replace tab characters found in string literals with the escape sequence \t instead. +string_replace_tab_chars = false # false/true + +# Allow interpreting '>=' and '>>=' as part of a template in 'void f(list>=val);'. +# If true, 'assert(x<0 && y>=3)' will be broken. Default=False +# Improvements to template detection may make this option obsolete. +tok_split_gte = false # false/true + +# Override the default ' *INDENT-OFF*' in comments for disabling processing of part of the file. +disable_processing_cmt = "" # string + +# Override the default ' *INDENT-ON*' in comments for enabling processing of part of the file. +enable_processing_cmt = "" # string + +# Enable parsing of digraphs. Default=False +enable_digraphs = false # false/true + +# Control what to do with the UTF-8 BOM (recommend 'remove') +utf8_bom = ignore # ignore/add/remove/force + +# If the file contains bytes with values between 128 and 255, but is not UTF-8, then output as UTF-8 +utf8_byte = false # false/true + +# Force the output encoding to UTF-8 +utf8_force = false # false/true + +# +# Indenting +# + +# The number of columns to indent per level. +# Usually 2, 3, 4, or 8. Default=8 +indent_columns = 4 # number + +indent_param = 8 + +# The continuation indent. If non-zero, this overrides the indent of '(' and '=' continuation indents. +# For FreeBSD, this is set to 4. Negative value is absolute and not increased for each ( level +indent_continue = 0 # number + +# How to use tabs when indenting code +# 0=spaces only +# 1=indent with tabs to brace level, align with spaces (default) +# 2=indent and align with tabs, using spaces when not on a tabstop +indent_with_tabs = 0 # number + +# Comments that are not a brace level are indented with tabs on a tabstop. +# Requires indent_with_tabs=2. If false, will use spaces. +indent_cmt_with_tabs = false # false/true + +# Whether to indent strings broken by '\' so that they line up +indent_align_string = true # false/true + +# The number of spaces to indent multi-line XML strings. +# Requires indent_align_string=True +indent_xml_string = 0 # number + +# Spaces to indent '{' from level +indent_brace = 0 # number + +# Whether braces are indented to the body level +indent_braces = false # false/true + +# Disabled indenting function braces if indent_braces is true +indent_braces_no_func = false # false/true + +# Disabled indenting class braces if indent_braces is true +indent_braces_no_class = false # false/true + +# Disabled indenting struct braces if indent_braces is true +indent_braces_no_struct = false # false/true + +# Indent based on the size of the brace parent, i.e. 'if' => 3 spaces, 'for' => 4 spaces, etc. +indent_brace_parent = false # false/true + +# Indent based on the paren open instead of the brace open in '({\n', default is to indent by brace. +indent_paren_open_brace = false # false/true + +# indent a C# delegate by another level, default is to not indent by another level. +indent_cs_delegate_brace = false # false/true + +# Whether the 'namespace' body is indented +indent_namespace = false # false/true + +# Only indent one namespace and no sub-namespaces. +# Requires indent_namespace=true. +indent_namespace_single_indent = false # false/true + +# The number of spaces to indent a namespace block +indent_namespace_level = 0 # number + +# If the body of the namespace is longer than this number, it won't be indented. +# Requires indent_namespace=true. Default=0 (no limit) +indent_namespace_limit = 0 # number + +# Whether the 'extern "C"' body is indented +indent_extern = false # false/true + +# Whether the 'class' body is indented +indent_class = true # false/true + +# Whether to indent the stuff after a leading base class colon +indent_class_colon = true # false/true + +# Indent based on a class colon instead of the stuff after the colon. +# Requires indent_class_colon=true. Default=False +indent_class_on_colon = false # false/true + +# Whether to indent the stuff after a leading class initializer colon +indent_constr_colon = true # false/true + +# Virtual indent from the ':' for member initializers. Default=2 +indent_ctor_init_leading = 2 # number + +# Additional indenting for constructor initializer list +indent_ctor_init = 0 # number + +# False=treat 'else\nif' as 'else if' for indenting purposes +# True=indent the 'if' one level +indent_else_if = false # false/true + +# Amount to indent variable declarations after a open brace. neg=relative, pos=absolute +indent_var_def_blk = 0 # number + +# Indent continued variable declarations instead of aligning. +indent_var_def_cont = false # false/true + +# Indent continued shift expressions ('<<' and '>>') instead of aligning. +# Turn align_left_shift off when enabling this. +indent_shift = false # false/true + +# True: force indentation of function definition to start in column 1 +# False: use the default behavior +indent_func_def_force_col1 = false # false/true + +# True: indent continued function call parameters one indent level +# False: align parameters under the open paren +indent_func_call_param = true # false/true + +# Same as indent_func_call_param, but for function defs +indent_func_def_param = true # false/true + +# Same as indent_func_call_param, but for function protos +indent_func_proto_param = true # false/true + +# Same as indent_func_call_param, but for class declarations +indent_func_class_param = false # false/true + +# Same as indent_func_call_param, but for class variable constructors +indent_func_ctor_var_param = false # false/true + +# Same as indent_func_call_param, but for templates +indent_template_param = false # false/true + +# Double the indent for indent_func_xxx_param options +indent_func_param_double = false # false/true + +# Indentation column for standalone 'const' function decl/proto qualifier +indent_func_const = 0 # number + +# Indentation column for standalone 'throw' function decl/proto qualifier +indent_func_throw = 0 # number + +# The number of spaces to indent a continued '->' or '.' +# Usually set to 0, 1, or indent_columns. +indent_member = 0 # number + +# Spaces to indent single line ('//') comments on lines before code +indent_sing_line_comments = 0 # number + +# If set, will indent trailing single line ('//') comments relative +# to the code instead of trying to keep the same absolute column +indent_relative_single_line_comments = false # false/true + +# Spaces to indent 'case' from 'switch' +# Usually 0 or indent_columns. +indent_switch_case = 0 # number + +# Spaces to shift the 'case' line, without affecting any other lines +# Usually 0. +indent_case_shift = 0 # number + +# Spaces to indent '{' from 'case'. +# By default, the brace will appear under the 'c' in case. +# Usually set to 0 or indent_columns. +indent_case_brace = 0 # number + +# Whether to indent comments found in first column +indent_col1_comment = false # false/true + +# How to indent goto labels +# >0: absolute column where 1 is the leftmost column +# <=0: subtract from brace indent +# Default=1 +indent_label = 1 # number + +# Same as indent_label, but for access specifiers that are followed by a colon. Default=1 +indent_access_spec = 1 # number + +# Indent the code after an access specifier by one level. +# If set, this option forces 'indent_access_spec=0' +indent_access_spec_body = false # false/true + +# If an open paren is followed by a newline, indent the next line so that it lines up after the open paren (not recommended) +indent_paren_nl = false # false/true + +# Controls the indent of a close paren after a newline. +# 0: Indent to body level +# 1: Align under the open paren +# 2: Indent to the brace level +indent_paren_close = 0 # number + +# Controls the indent of a comma when inside a paren.If TRUE, aligns under the open paren +indent_comma_paren = false # false/true + +# Controls the indent of a BOOL operator when inside a paren.If TRUE, aligns under the open paren +indent_bool_paren = false # false/true + +# If 'indent_bool_paren' is true, controls the indent of the first expression. If TRUE, aligns the first expression to the following ones +indent_first_bool_expr = false # false/true + +# If an open square is followed by a newline, indent the next line so that it lines up after the open square (not recommended) +indent_square_nl = false # false/true + +# Don't change the relative indent of ESQL/C 'EXEC SQL' bodies +indent_preserve_sql = false # false/true + +# Align continued statements at the '='. Default=True +# If FALSE or the '=' is followed by a newline, the next line is indent one tab. +indent_align_assign = true # false/true + +# Indent OC blocks at brace level instead of usual rules. +indent_oc_block = false # false/true + +# Indent OC blocks in a message relative to the parameter name. +# 0=use indent_oc_block rules, 1+=spaces to indent +indent_oc_block_msg = 0 # number + +# Minimum indent for subsequent parameters +indent_oc_msg_colon = 0 # number + +# If true, prioritize aligning with initial colon (and stripping spaces from lines, if necessary). +# Default=True. +indent_oc_msg_prioritize_first_colon = true # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented the way that Xcode does by default (from keyword if the parameter is on its own line; otherwise, from the previous indentation level). +indent_oc_block_msg_xcode_style = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is relative to a msg keyword. +indent_oc_block_msg_from_keyword = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is relative to a msg colon. +indent_oc_block_msg_from_colon = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the block caret is. +indent_oc_block_msg_from_caret = false # false/true + +# If indent_oc_block_msg and this option are on, blocks will be indented from where the brace is. +indent_oc_block_msg_from_brace = false # false/true + +# When identing after virtual brace open and newline add further spaces to reach this min. indent. +indent_min_vbrace_open = 0 # number + +# TRUE: When identing after virtual brace open and newline add further spaces after regular indent to reach next tabstop. +indent_vbrace_open_on_tabstop = false # false/true + +# If true, a brace followed by another token (not a newline) will indent all contained lines to match the token.Default=True. +indent_token_after_brace = true # false/true + +# If true, cpp lambda body will be indentedDefault=False. +indent_cpp_lambda_body = false # false/true + +# +# Spacing options +# + +# Add or remove space around arithmetic operator '+', '-', '/', '*', etc +# also '>>>' '<<' '>>' '%' '|' +sp_arith = force # ignore/add/remove/force + +# Add or remove space around assignment operator '=', '+=', etc +sp_assign = force # ignore/add/remove/force + +# Add or remove space around '=' in C++11 lambda capture specifications. Overrides sp_assign +sp_cpp_lambda_assign = remove # ignore/add/remove/force + +# Add or remove space after the capture specification in C++11 lambda. +sp_cpp_lambda_paren = remove # ignore/add/remove/force + +# Add or remove space around assignment operator '=' in a prototype +sp_assign_default = force # ignore/add/remove/force + +# Add or remove space before assignment operator '=', '+=', etc. Overrides sp_assign. +sp_before_assign = ignore # ignore/add/remove/force + +# Add or remove space after assignment operator '=', '+=', etc. Overrides sp_assign. +sp_after_assign = ignore # ignore/add/remove/force + +# Add or remove space in 'NS_ENUM (' +sp_enum_paren = ignore # ignore/add/remove/force + +# Add or remove space around assignment '=' in enum +sp_enum_assign = force # ignore/add/remove/force + +# Add or remove space before assignment '=' in enum. Overrides sp_enum_assign. +sp_enum_before_assign = ignore # ignore/add/remove/force + +# Add or remove space after assignment '=' in enum. Overrides sp_enum_assign. +sp_enum_after_assign = ignore # ignore/add/remove/force + +# Add or remove space around preprocessor '##' concatenation operator. Default=Add +sp_pp_concat = add # ignore/add/remove/force + +# Add or remove space after preprocessor '#' stringify operator. Also affects the '#@' charizing operator. +sp_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space before preprocessor '#' stringify operator as in '#define x(y) L#y'. +sp_before_pp_stringify = ignore # ignore/add/remove/force + +# Add or remove space around boolean operators '&&' and '||' +sp_bool = force # ignore/add/remove/force + +# Add or remove space around compare operator '<', '>', '==', etc +sp_compare = force # ignore/add/remove/force + +# Add or remove space inside '(' and ')' +sp_inside_paren = remove # ignore/add/remove/force + +# Add or remove space between nested parens: '((' vs ') )' +sp_paren_paren = remove # ignore/add/remove/force + +# Add or remove space between back-to-back parens: ')(' vs ') (' +sp_cparen_oparen = remove # ignore/add/remove/force + +# Whether to balance spaces inside nested parens +sp_balance_nested_parens = false # false/true + +# Add or remove space between ')' and '{' +sp_paren_brace = force # ignore/add/remove/force + +# Add or remove space before pointer star '*' +sp_before_ptr_star = force # ignore/add/remove/force + +# Add or remove space before pointer star '*' that isn't followed by a variable name +# If set to 'ignore', sp_before_ptr_star is used instead. +sp_before_unnamed_ptr_star = remove # ignore/add/remove/force + +# Add or remove space between pointer stars '*' +sp_between_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a word. +sp_after_ptr_star = remove # ignore/add/remove/force + +# Add or remove space after pointer star '*', if followed by a qualifier. +sp_after_ptr_star_qualifier = remove # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by a func proto/def. +sp_after_ptr_star_func = force # ignore/add/remove/force + +# Add or remove space after a pointer star '*', if followed by an open paren (function types). +sp_ptr_star_paren = remove # ignore/add/remove/force + +# Add or remove space before a pointer star '*', if followed by a func proto/def. +sp_before_ptr_star_func = remove # ignore/add/remove/force + +# Add or remove space before a reference sign '&' +sp_before_byref = force # ignore/add/remove/force + +# Add or remove space before a reference sign '&' that isn't followed by a variable name +# If set to 'ignore', sp_before_byref is used instead. +sp_before_unnamed_byref = remove # ignore/add/remove/force + +# Add or remove space after reference sign '&', if followed by a word. +sp_after_byref = remove # ignore/add/remove/force + +# Add or remove space after a reference sign '&', if followed by a func proto/def. +sp_after_byref_func = force # ignore/add/remove/force + +# Add or remove space before a reference sign '&', if followed by a func proto/def. +sp_before_byref_func = remove # ignore/add/remove/force + +# Add or remove space between type and word. Default=Force +sp_after_type = remove # ignore/add/remove/force + +# Add or remove space before the paren in the D constructs 'template Foo(' and 'class Foo('. +sp_before_template_paren = ignore # ignore/add/remove/force + +# Add or remove space in 'template <' vs 'template<'. +# If set to ignore, sp_before_angle is used. +sp_template_angle = ignore # ignore/add/remove/force + +# Add or remove space before '<>' +sp_before_angle = remove # ignore/add/remove/force + +# Add or remove space inside '<' and '>' +sp_inside_angle = remove # ignore/add/remove/force + +# Add or remove space after '<>' +sp_after_angle = remove # ignore/add/remove/force + +# Add or remove space between '<>' and '(' as found in 'new List(foo);' +sp_angle_paren = remove # ignore/add/remove/force + +# Add or remove space between '<>' and '()' as found in 'new List();' +sp_angle_paren_empty = remove # ignore/add/remove/force + +# Add or remove space between '<>' and a word as in 'List m;' or 'template static ...' +sp_angle_word = force # ignore/add/remove/force + +# Add or remove space between '>' and '>' in '>>' (template stuff C++/C# only). Default=Add +sp_angle_shift = remove # ignore/add/remove/force + +# Permit removal of the space between '>>' in 'foo >' (C++11 only). Default=False +# sp_angle_shift cannot remove the space without this option. +sp_permit_cpp11_shift = true # false/true + +# Add or remove space before '(' of 'if', 'for', 'switch', 'while', etc. +sp_before_sparen = force # ignore/add/remove/force + +# Add or remove space inside if-condition '(' and ')' +sp_inside_sparen = remove # ignore/add/remove/force + +# Add or remove space before if-condition ')'. Overrides sp_inside_sparen. +sp_inside_sparen_close = ignore # ignore/add/remove/force + +# Add or remove space after if-condition '('. Overrides sp_inside_sparen. +sp_inside_sparen_open = ignore # ignore/add/remove/force + +# Add or remove space after ')' of 'if', 'for', 'switch', and 'while', etc. +sp_after_sparen = force # ignore/add/remove/force + +# Add or remove space between ')' and '{' of 'if', 'for', 'switch', and 'while', etc. +sp_sparen_brace = force # ignore/add/remove/force + +# Add or remove space between 'invariant' and '(' in the D language. +sp_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space after the ')' in 'invariant (C) c' in the D language. +sp_after_invariant_paren = ignore # ignore/add/remove/force + +# Add or remove space before empty statement ';' on 'if', 'for' and 'while' +sp_special_semi = ignore # ignore/add/remove/force + +# Add or remove space before ';'. Default=Remove +sp_before_semi = remove # ignore/add/remove/force + +# Add or remove space before ';' in non-empty 'for' statements +sp_before_semi_for = remove # ignore/add/remove/force + +# Add or remove space before a semicolon of an empty part of a for statement. +sp_before_semi_for_empty = force # ignore/add/remove/force + +# Add or remove space after ';', except when followed by a comment. Default=Add +sp_after_semi = force # ignore/add/remove/force + +# Add or remove space after ';' in non-empty 'for' statements. Default=Force +sp_after_semi_for = force # ignore/add/remove/force + +# Add or remove space after the final semicolon of an empty part of a for statement: for ( ; ; ). +sp_after_semi_for_empty = force # ignore/add/remove/force + +# Add or remove space before '[' (except '[]') +sp_before_square = remove # ignore/add/remove/force + +# Add or remove space before '[]' +sp_before_squares = remove # ignore/add/remove/force + +# Add or remove space inside a non-empty '[' and ']' +sp_inside_square = remove # ignore/add/remove/force + +# Add or remove space after ',' +sp_after_comma = force # ignore/add/remove/force + +# Add or remove space before ','. Default=Remove +sp_before_comma = remove # ignore/add/remove/force + +# Add or remove space between ',' and ']' in multidimensional array type 'int[,,]' +sp_after_mdatype_commas = remove # ignore/add/remove/force + +# Add or remove space between '[' and ',' in multidimensional array type 'int[,,]' +sp_before_mdatype_commas = remove # ignore/add/remove/force + +# Add or remove space between ',' in multidimensional array type 'int[,,]' +sp_between_mdatype_commas = remove # ignore/add/remove/force + +# Add or remove space between an open paren and comma: '(,' vs '( ,'. Default=Force +sp_paren_comma = force # ignore/add/remove/force + +# Add or remove space before the variadic '...' when preceded by a non-punctuator +sp_before_ellipsis = remove # ignore/add/remove/force + +# Add or remove space after class ':' +sp_after_class_colon = force # ignore/add/remove/force + +# Add or remove space before class ':' +sp_before_class_colon = force # ignore/add/remove/force + +# Add or remove space after class constructor ':' +sp_after_constr_colon = force # ignore/add/remove/force + +# Add or remove space before class constructor ':' +sp_before_constr_colon = force # ignore/add/remove/force + +# Add or remove space before case ':'. Default=Remove +sp_before_case_colon = remove # ignore/add/remove/force + +# Add or remove space between 'operator' and operator sign +sp_after_operator = remove # ignore/add/remove/force + +# Add or remove space between the operator symbol and the open paren, as in 'operator ++(' +sp_after_operator_sym = remove # ignore/add/remove/force + +# Add or remove space between the operator symbol and the open paren when the operator has no arguments, as in 'operator *()' +sp_after_operator_sym_empty = remove # ignore/add/remove/force + +# Add or remove space after C/D cast, i.e. 'cast(int)a' vs 'cast(int) a' or '(int)a' vs '(int) a' +sp_after_cast = remove # ignore/add/remove/force + +# Add or remove spaces inside cast parens +sp_inside_paren_cast = remove # ignore/add/remove/force + +# Add or remove space between the type and open paren in a C++ cast, i.e. 'int(exp)' vs 'int (exp)' +sp_cpp_cast_paren = remove # ignore/add/remove/force + +# Add or remove space between 'sizeof' and '(' +sp_sizeof_paren = remove # ignore/add/remove/force + +# Add or remove space after the tag keyword (Pawn) +sp_after_tag = ignore # ignore/add/remove/force + +# Add or remove space inside enum '{' and '}' +sp_inside_braces_enum = ignore # ignore/add/remove/force + +# Add or remove space inside struct/union '{' and '}' +sp_inside_braces_struct = ignore # ignore/add/remove/force + +# Add or remove space inside '{' and '}' +sp_inside_braces = ignore # ignore/add/remove/force + +# Add or remove space inside '{}' +sp_inside_braces_empty = remove # ignore/add/remove/force + +# Add or remove space between return type and function name +# A minimum of 1 is forced except for pointer return types. +sp_type_func = force # ignore/add/remove/force + +# Add or remove space between function name and '(' on function declaration +sp_func_proto_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function declaration without parameters +sp_func_proto_paren_empty = remove # ignore/add/remove/force + +# Add or remove space between function name and '(' on function definition +sp_func_def_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function definition without parameters +sp_func_def_paren_empty = remove # ignore/add/remove/force + +# Add or remove space inside empty function '()' +sp_inside_fparens = remove # ignore/add/remove/force + +# Add or remove space inside function '(' and ')' +sp_inside_fparen = remove # ignore/add/remove/force + +# Add or remove space inside the first parens in the function type: 'void (*x)(...)' +sp_inside_tparen = remove # ignore/add/remove/force + +# Add or remove between the parens in the function type: 'void (*x)(...)' +sp_after_tparen_close = remove # ignore/add/remove/force + +# Add or remove space between ']' and '(' when part of a function call. +sp_square_fparen = remove # ignore/add/remove/force + +# Add or remove space between ')' and '{' of function +sp_fparen_brace = force # ignore/add/remove/force + +# Java: Add or remove space between ')' and '{{' of double brace initializer. +sp_fparen_dbrace = ignore # ignore/add/remove/force + +# Add or remove space between function name and '(' on function calls +sp_func_call_paren = remove # ignore/add/remove/force + +# Add or remove space between function name and '()' on function calls without parameters. +# If set to 'ignore' (the default), sp_func_call_paren is used. +sp_func_call_paren_empty = ignore # ignore/add/remove/force + +# Add or remove space between the user function name and '(' on function calls +# You need to set a keyword to be a user function, like this: 'set func_call_user _' in the config file. +sp_func_call_user_paren = ignore # ignore/add/remove/force + +# Add or remove space between a constructor/destructor and the open paren +sp_func_class_paren = remove # ignore/add/remove/force + +# Add or remove space between a constructor without parameters or destructor and '()' +sp_func_class_paren_empty = remove # ignore/add/remove/force + +# Add or remove space between 'return' and '(' +sp_return_paren = force # ignore/add/remove/force + +# Add or remove space between '__attribute__' and '(' +sp_attribute_paren = remove # ignore/add/remove/force + +# Add or remove space between 'defined' and '(' in '#if defined (FOO)' +sp_defined_paren = force # ignore/add/remove/force + +# Add or remove space between 'throw' and '(' in 'throw (something)' +sp_throw_paren = force # ignore/add/remove/force + +# Add or remove space between 'throw' and anything other than '(' as in '@throw [...];' +sp_after_throw = force # ignore/add/remove/force + +# Add or remove space between 'catch' and '(' in 'catch (something) { }' +# If set to ignore, sp_before_sparen is used. +sp_catch_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'version' and '(' in 'version (something) { }' (D language) +# If set to ignore, sp_before_sparen is used. +sp_version_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'scope' and '(' in 'scope (something) { }' (D language) +# If set to ignore, sp_before_sparen is used. +sp_scope_paren = ignore # ignore/add/remove/force + +# Add or remove space between 'super' and '(' in 'super (something)'. Default=Remove +sp_super_paren = remove # ignore/add/remove/force + +# Add or remove space between 'this' and '(' in 'this (something)'. Default=Remove +sp_this_paren = remove # ignore/add/remove/force + +# Add or remove space between macro and value +sp_macro = ignore # ignore/add/remove/force + +# Add or remove space between macro function ')' and value +sp_macro_func = ignore # ignore/add/remove/force + +# Add or remove space between 'else' and '{' if on the same line +sp_else_brace = force # ignore/add/remove/force + +# Add or remove space between '}' and 'else' if on the same line +sp_brace_else = ignore # ignore/add/remove/force + +# Add or remove space between '}' and the name of a typedef on the same line +sp_brace_typedef = ignore # ignore/add/remove/force + +# Add or remove space between 'catch' and '{' if on the same line +sp_catch_brace = force # ignore/add/remove/force + +# Add or remove space between '}' and 'catch' if on the same line +sp_brace_catch = ignore # ignore/add/remove/force + +# Add or remove space between 'finally' and '{' if on the same line +sp_finally_brace = force # ignore/add/remove/force + +# Add or remove space between '}' and 'finally' if on the same line +sp_brace_finally = ignore # ignore/add/remove/force + +# Add or remove space between 'try' and '{' if on the same line +sp_try_brace = force # ignore/add/remove/force + +# Add or remove space between get/set and '{' if on the same line +sp_getset_brace = ignore # ignore/add/remove/force + +# Add or remove space between a variable and '{' for C++ uniform initialization. Default=Add +sp_word_brace = force # ignore/add/remove/force + +# Add or remove space between a variable and '{' for a namespace. Default=Add +sp_word_brace_ns = force # ignore/add/remove/force + +# Add or remove space before the '::' operator +sp_before_dc = remove # ignore/add/remove/force + +# Add or remove space after the '::' operator +sp_after_dc = remove # ignore/add/remove/force + +# Add or remove around the D named array initializer ':' operator +sp_d_array_colon = ignore # ignore/add/remove/force + +# Add or remove space after the '!' (not) operator. Default=Remove +sp_not = remove # ignore/add/remove/force + +# Add or remove space after the '~' (invert) operator. Default=Remove +sp_inv = remove # ignore/add/remove/force + +# Add or remove space after the '&' (address-of) operator. Default=Remove +# This does not affect the spacing after a '&' that is part of a type. +sp_addr = remove # ignore/add/remove/force + +# Add or remove space around the '.' or '->' operators. Default=Remove +sp_member = remove # ignore/add/remove/force + +# Add or remove space after the '*' (dereference) operator. Default=Remove +# This does not affect the spacing after a '*' that is part of a type. +sp_deref = remove # ignore/add/remove/force + +# Add or remove space after '+' or '-', as in 'x = -5' or 'y = +7'. Default=Remove +sp_sign = remove # ignore/add/remove/force + +# Add or remove space before or after '++' and '--', as in '(--x)' or 'y++;'. Default=Remove +sp_incdec = remove # ignore/add/remove/force + +# Add or remove space before a backslash-newline at the end of a line. Default=Add +sp_before_nl_cont = add # ignore/add/remove/force + +# Add or remove space after the scope '+' or '-', as in '-(void) foo;' or '+(int) bar;' +sp_after_oc_scope = ignore # ignore/add/remove/force + +# Add or remove space after the colon in message specs +# '-(int) f:(int) x;' vs '-(int) f: (int) x;' +sp_after_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space before the colon in message specs +# '-(int) f: (int) x;' vs '-(int) f : (int) x;' +sp_before_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space after the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};' +sp_after_oc_dict_colon = ignore # ignore/add/remove/force + +# Add or remove space before the colon in immutable dictionary expression +# 'NSDictionary *test = @{@"foo" :@"bar"};' +sp_before_oc_dict_colon = ignore # ignore/add/remove/force + +# Add or remove space after the colon in message specs +# '[object setValue:1];' vs '[object setValue: 1];' +sp_after_send_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space before the colon in message specs +# '[object setValue:1];' vs '[object setValue :1];' +sp_before_send_oc_colon = ignore # ignore/add/remove/force + +# Add or remove space after the (type) in message specs +# '-(int)f: (int) x;' vs '-(int)f: (int)x;' +sp_after_oc_type = ignore # ignore/add/remove/force + +# Add or remove space after the first (type) in message specs +# '-(int) f:(int)x;' vs '-(int)f:(int)x;' +sp_after_oc_return_type = ignore # ignore/add/remove/force + +# Add or remove space between '@selector' and '(' +# '@selector(msgName)' vs '@selector (msgName)' +# Also applies to @protocol() constructs +sp_after_oc_at_sel = ignore # ignore/add/remove/force + +# Add or remove space between '@selector(x)' and the following word +# '@selector(foo) a:' vs '@selector(foo)a:' +sp_after_oc_at_sel_parens = ignore # ignore/add/remove/force + +# Add or remove space inside '@selector' parens +# '@selector(foo)' vs '@selector( foo )' +# Also applies to @protocol() constructs +sp_inside_oc_at_sel_parens = ignore # ignore/add/remove/force + +# Add or remove space before a block pointer caret +# '^int (int arg){...}' vs. ' ^int (int arg){...}' +sp_before_oc_block_caret = ignore # ignore/add/remove/force + +# Add or remove space after a block pointer caret +# '^int (int arg){...}' vs. '^ int (int arg){...}' +sp_after_oc_block_caret = ignore # ignore/add/remove/force + +# Add or remove space between the receiver and selector in a message. +# '[receiver selector ...]' +sp_after_oc_msg_receiver = ignore # ignore/add/remove/force + +# Add or remove space after @property. +sp_after_oc_property = ignore # ignore/add/remove/force + +# Add or remove space around the ':' in 'b ? t : f' +sp_cond_colon = force # ignore/add/remove/force + +# Add or remove space before the ':' in 'b ? t : f'. Overrides sp_cond_colon. +sp_cond_colon_before = ignore # ignore/add/remove/force + +# Add or remove space after the ':' in 'b ? t : f'. Overrides sp_cond_colon. +sp_cond_colon_after = ignore # ignore/add/remove/force + +# Add or remove space around the '?' in 'b ? t : f' +sp_cond_question = force # ignore/add/remove/force + +# Add or remove space before the '?' in 'b ? t : f'. Overrides sp_cond_question. +sp_cond_question_before = ignore # ignore/add/remove/force + +# Add or remove space after the '?' in 'b ? t : f'. Overrides sp_cond_question. +sp_cond_question_after = ignore # ignore/add/remove/force + +# In the abbreviated ternary form (a ?: b), add/remove space between ? and :.'. Overrides all other sp_cond_* options. +sp_cond_ternary_short = remove # ignore/add/remove/force + +# Fix the spacing between 'case' and the label. Only 'ignore' and 'force' make sense here. +sp_case_label = force # ignore/add/remove/force + +# Control the space around the D '..' operator. +sp_range = ignore # ignore/add/remove/force + +# Control the spacing after ':' in 'for (TYPE VAR : EXPR)' +sp_after_for_colon = force # ignore/add/remove/force + +# Control the spacing before ':' in 'for (TYPE VAR : EXPR)' +sp_before_for_colon = force # ignore/add/remove/force + +# Control the spacing in 'extern (C)' (D) +sp_extern_paren = ignore # ignore/add/remove/force + +# Control the space after the opening of a C++ comment '// A' vs '//A' +sp_cmt_cpp_start = force # ignore/add/remove/force + +# TRUE: If space is added with sp_cmt_cpp_start, do it after doxygen sequences like '///', '///<', '//!' and '//!<'. +sp_cmt_cpp_doxygen = true # false/true + +# TRUE: If space is added with sp_cmt_cpp_start, do it after Qt translator or meta-data comments like '//:', '//=', and '//~'. +sp_cmt_cpp_qttr = true # false/true + +# Controls the spaces between #else or #endif and a trailing comment +sp_endif_cmt = force # ignore/add/remove/force + +# Controls the spaces after 'new', 'delete' and 'delete[]' +sp_after_new = force # ignore/add/remove/force + +# Controls the spaces between new and '(' in 'new()' +sp_between_new_paren = force # ignore/add/remove/force + +# Controls the spaces before a trailing or embedded comment +sp_before_tr_emb_cmt = ignore # ignore/add/remove/force + +# Number of spaces before a trailing or embedded comment +sp_num_before_tr_emb_cmt = 0 # number + +# Control space between a Java annotation and the open paren. +sp_annotation_paren = ignore # ignore/add/remove/force + +# If true, vbrace tokens are dropped to the previous token and skipped. +sp_skip_vbrace_tokens = false # false/true + +# +# Code alignment (not left column spaces/tabs) +# + +# Whether to keep non-indenting tabs +align_keep_tabs = false # false/true + +# Whether to use tabs for aligning +align_with_tabs = false # false/true + +# Whether to bump out to the next tab when aligning +align_on_tabstop = false # false/true + +# Whether to left-align numbers +#align_number_left = false # false/true + +# Whether to keep whitespace not required for alignment. +align_keep_extra_space = false # false/true + +# Align variable definitions in prototypes and functions +align_func_params = false # false/true + +# Align parameters in single-line functions that have the same name. +# The function names must already be aligned with each other. +align_same_func_call_params = false # false/true + +# The span for aligning variable definitions (0=don't align) +align_var_def_span = 0 # number + +# How to align the star in variable definitions. +# 0=Part of the type 'void * foo;' +# 1=Part of the variable 'void *foo;' +# 2=Dangling 'void *foo;' +align_var_def_star_style = 1 # number + +# How to align the '&' in variable definitions. +# 0=Part of the type +# 1=Part of the variable +# 2=Dangling +align_var_def_amp_style = 1 # number + +# The threshold for aligning variable definitions (0=no limit) +align_var_def_thresh = 0 # number + +# The gap for aligning variable definitions +align_var_def_gap = 0 # number + +# Whether to align the colon in struct bit fields +align_var_def_colon = false # false/true + +# Whether to align any attribute after the variable name +align_var_def_attribute = false # false/true + +# Whether to align inline struct/enum/union variable definitions +align_var_def_inline = false # false/true + +# The span for aligning on '=' in assignments (0=don't align) +align_assign_span = 0 # number + +# The threshold for aligning on '=' in assignments (0=no limit) +align_assign_thresh = 0 # number + +# The span for aligning on '=' in enums (0=don't align) +align_enum_equ_span = 0 # number + +# The threshold for aligning on '=' in enums (0=no limit) +align_enum_equ_thresh = 0 # number + +# The span for aligning class (0=don't align) +align_var_class_span = 0 # number + +# The threshold for aligning class member definitions (0=no limit) +align_var_class_thresh = 0 # number + +# The gap for aligning class member definitions +align_var_class_gap = 0 # number + +# The span for aligning struct/union (0=don't align) +align_var_struct_span = 0 # number + +# The threshold for aligning struct/union member definitions (0=no limit) +align_var_struct_thresh = 0 # number + +# The gap for aligning struct/union member definitions +align_var_struct_gap = 0 # number + +# The span for aligning struct initializer values (0=don't align) +align_struct_init_span = 0 # number + +# The minimum space between the type and the synonym of a typedef +align_typedef_gap = 0 # number + +# The span for aligning single-line typedefs (0=don't align) +align_typedef_span = 0 # number + +# How to align typedef'd functions with other typedefs +# 0: Don't mix them at all +# 1: align the open paren with the types +# 2: align the function type name with the other type names +align_typedef_func = 0 # number + +# Controls the positioning of the '*' in typedefs. Just try it. +# 0: Align on typedef type, ignore '*' +# 1: The '*' is part of type name: typedef int *pint; +# 2: The '*' is part of the type, but dangling: typedef int *pint; +align_typedef_star_style = 0 # number + +# Controls the positioning of the '&' in typedefs. Just try it. +# 0: Align on typedef type, ignore '&' +# 1: The '&' is part of type name: typedef int &pint; +# 2: The '&' is part of the type, but dangling: typedef int &pint; +align_typedef_amp_style = 0 # number + +# The span for aligning comments that end lines (0=don't align) +align_right_cmt_span = 0 # number + +# If aligning comments, mix with comments after '}' and #endif with less than 3 spaces before the comment +align_right_cmt_mix = false # false/true + +# If a trailing comment is more than this number of columns away from the text it follows, +# it will qualify for being aligned. This has to be > 0 to do anything. +align_right_cmt_gap = 0 # number + +# Align trailing comment at or beyond column N; 'pulls in' comments as a bonus side effect (0=ignore) +align_right_cmt_at_col = 0 # number + +# The span for aligning function prototypes (0=don't align) +align_func_proto_span = 0 # number + +# Minimum gap between the return type and the function name. +align_func_proto_gap = 0 # number + +# Align function protos on the 'operator' keyword instead of what follows +align_on_operator = false # false/true + +# Whether to mix aligning prototype and variable declarations. +# If true, align_var_def_XXX options are used instead of align_func_proto_XXX options. +align_mix_var_proto = false # false/true + +# Align single-line functions with function prototypes, uses align_func_proto_span +align_single_line_func = false # false/true + +# Aligning the open brace of single-line functions. +# Requires align_single_line_func=true, uses align_func_proto_span +align_single_line_brace = false # false/true + +# Gap for align_single_line_brace. +align_single_line_brace_gap = 0 # number + +# The span for aligning ObjC msg spec (0=don't align) +align_oc_msg_spec_span = 0 # number + +# Whether to align macros wrapped with a backslash and a newline. +# This will not work right if the macro contains a multi-line comment. +align_nl_cont = false # false/true + +# # Align macro functions and variables together +align_pp_define_together = false # false/true + +# The minimum space between label and value of a preprocessor define +align_pp_define_gap = 0 # number + +# The span for aligning on '#define' bodies (0=don't align, other=number of lines including comments between blocks) +align_pp_define_span = 0 # number + +# Align lines that start with '<<' with previous '<<'. Default=True +align_left_shift = true # false/true + +# Align text after asm volatile () colons. +align_asm_colon = false # false/true + +# Span for aligning parameters in an Obj-C message call on the ':' (0=don't align) +align_oc_msg_colon_span = 0 # number + +# If true, always align with the first parameter, even if it is too short. +align_oc_msg_colon_first = false # false/true + +# Aligning parameters in an Obj-C '+' or '-' declaration on the ':' +align_oc_decl_colon = false # false/true + +# +# Newline adding and removing options +# + +# Whether to collapse empty blocks between '{' and '}' +nl_collapse_empty_body = false # false/true + +# Don't split one-line braced assignments - 'foo_t f = { 1, 2 };' +nl_assign_leave_one_liners = false # false/true + +# Don't split one-line braced statements inside a class xx { } body +nl_class_leave_one_liners = false # false/true + +# Don't split one-line enums: 'enum foo { BAR = 15 };' +nl_enum_leave_one_liners = false # false/true + +# Don't split one-line get or set functions +nl_getset_leave_one_liners = false # false/true + +# Don't split one-line function definitions - 'int foo() { return 0; }' +nl_func_leave_one_liners = false # false/true + +# Don't split one-line C++11 lambdas - '[]() { return 0; }' +nl_cpp_lambda_leave_one_liners = false # false/true + +# Don't split one-line if/else statements - 'if(a) b++;' +nl_if_leave_one_liners = false # false/true + +# Don't split one-line while statements - 'while(a) b++;' +nl_while_leave_one_liners = false # false/true + +# Don't split one-line OC messages +nl_oc_msg_leave_one_liner = false # false/true + +# Add or remove newline between Objective-C block signature and '{' +nl_oc_block_brace = ignore # ignore/add/remove/force + +# Add or remove newlines at the start of the file +nl_start_of_file = remove # ignore/add/remove/force + +# The number of newlines at the start of the file (only used if nl_start_of_file is 'add' or 'force' +nl_start_of_file_min = 0 # number + +# Add or remove newline at the end of the file +nl_end_of_file = force # ignore/add/remove/force + +# The number of newlines at the end of the file (only used if nl_end_of_file is 'add' or 'force') +nl_end_of_file_min = 1 # number + +# Add or remove newline between '=' and '{' +nl_assign_brace = remove # ignore/add/remove/force + +# Add or remove newline between '=' and '[' (D only) +nl_assign_square = ignore # ignore/add/remove/force + +# Add or remove newline after '= [' (D only). Will also affect the newline before the ']' +nl_after_square_assign = ignore # ignore/add/remove/force + +# The number of blank lines after a block of variable definitions at the top of a function body +# 0 = No change (default) +nl_func_var_def_blk = 0 # number + +# The number of newlines before a block of typedefs +# 0 = No change (default) +# the option 'nl_after_access_spec' takes preference over 'nl_typedef_blk_start' +nl_typedef_blk_start = 0 # number + +# The number of newlines after a block of typedefs +# 0 = No change (default) +nl_typedef_blk_end = 0 # number + +# The maximum consecutive newlines within a block of typedefs +# 0 = No change (default) +nl_typedef_blk_in = 0 # number + +# The number of newlines before a block of variable definitions not at the top of a function body +# 0 = No change (default) +# the option 'nl_after_access_spec' takes preference over 'nl_var_def_blk_start' +nl_var_def_blk_start = 0 # number + +# The number of newlines after a block of variable definitions not at the top of a function body +# 0 = No change (default) +nl_var_def_blk_end = 0 # number + +# The maximum consecutive newlines within a block of variable definitions +# 0 = No change (default) +nl_var_def_blk_in = 0 # number + +# Add or remove newline between a function call's ')' and '{', as in: +# list_for_each(item, &list) { } +nl_fcall_brace = force # ignore/add/remove/force + +# Add or remove newline between 'enum' and '{' +nl_enum_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'struct and '{' +nl_struct_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'union' and '{' +nl_union_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'if' and '{' +nl_if_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'else' +nl_brace_else = force # ignore/add/remove/force + +# Add or remove newline between 'else if' and '{' +# If set to ignore, nl_if_brace is used instead +nl_elseif_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'else' and '{' +nl_else_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'else' and 'if' +nl_else_if = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'finally' +nl_brace_finally = force # ignore/add/remove/force + +# Add or remove newline between 'finally' and '{' +nl_finally_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'try' and '{' +nl_try_brace = remove # ignore/add/remove/force + +# Add or remove newline between get/set and '{' +nl_getset_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'for' and '{' +nl_for_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'catch' and '{' +nl_catch_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'catch' +nl_brace_catch = force # ignore/add/remove/force + +# Add or remove newline between '}' and ']' +nl_brace_square = ignore # ignore/add/remove/force + +# Add or remove newline between '}' and ')' in a function invocation +nl_brace_fparen = remove # ignore/add/remove/force + +# Add or remove newline between 'while' and '{' +nl_while_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'scope (x)' and '{' (D) +nl_scope_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'unittest' and '{' (D) +nl_unittest_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'version (x)' and '{' (D) +nl_version_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'using' and '{' +nl_using_brace = remove # ignore/add/remove/force + +# Add or remove newline between two open or close braces. +# Due to general newline/brace handling, REMOVE may not work. +nl_brace_brace = ignore # ignore/add/remove/force + +# Add or remove newline between 'do' and '{' +nl_do_brace = remove # ignore/add/remove/force + +# Add or remove newline between '}' and 'while' of 'do' statement +nl_brace_while = remove # ignore/add/remove/force + +# Add or remove newline between 'switch' and '{' +nl_switch_brace = remove # ignore/add/remove/force + +# Add or remove newline between 'synchronized' and '{' +nl_synchronized_brace = remove # ignore/add/remove/force + +# Add a newline between ')' and '{' if the ')' is on a different line than the if/for/etc. +# Overrides nl_for_brace, nl_if_brace, nl_switch_brace, nl_while_switch and nl_catch_brace. +nl_multi_line_cond = false # false/true + +# Force a newline in a define after the macro name for multi-line defines. +nl_multi_line_define = false # false/true + +# Whether to put a newline before 'case' statement, not after the first 'case' +nl_before_case = true # false/true + +# Add or remove newline between ')' and 'throw' +nl_before_throw = ignore # ignore/add/remove/force + +# Whether to put a newline after 'case' statement +nl_after_case = true # false/true + +# Add or remove a newline between a case ':' and '{'. Overrides nl_after_case. +nl_case_colon_brace = remove # ignore/add/remove/force + +# Newline between namespace and { +nl_namespace_brace = force # ignore/add/remove/force + +# Add or remove newline between 'template<>' and whatever follows. +nl_template_class = force # ignore/add/remove/force + +# Add or remove newline between 'class' and '{' +nl_class_brace = force # ignore/add/remove/force + +# Add or remove newline before/after each ',' in the base class list, +# (tied to pos_class_comma). +nl_class_init_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in the constructor member initialization. +# Related to nl_constr_colon, pos_constr_colon and pos_constr_comma. +nl_constr_init_args = force # ignore/add/remove/force + +# Add or remove newline before first element, after comma, and after last element in enum +nl_enum_own_lines = force # ignore/add/remove/force + +# Add or remove newline between return type and function name in a function definition +nl_func_type_name = remove # ignore/add/remove/force + +# Add or remove newline between return type and function name inside a class {} +# Uses nl_func_type_name or nl_func_proto_type_name if set to ignore. +nl_func_type_name_class = ignore # ignore/add/remove/force + +# Add or remove newline between class specification and '::' in 'void A::f() { }' +# Only appears in separate member implementation (does not appear with in-line implmementation) +nl_func_class_scope = remove # ignore/add/remove/force + +# Add or remove newline between function scope and name +# Controls the newline after '::' in 'void A::f() { }' +nl_func_scope_name = remove # ignore/add/remove/force + +# Add or remove newline between return type and function name in a prototype +nl_func_proto_type_name = remove # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the declaration +nl_func_paren = remove # ignore/add/remove/force + +# Add or remove newline between a function name and the opening '(' in the definition +nl_func_def_paren = remove # ignore/add/remove/force + +# Add or remove newline after '(' in a function declaration +nl_func_decl_start = ignore # ignore/add/remove/force + +# Add or remove newline after '(' in a function definition +nl_func_def_start = ignore # ignore/add/remove/force + +# Overrides nl_func_decl_start when there is only one parameter. +nl_func_decl_start_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_start when there is only one parameter. +nl_func_def_start_single = ignore # ignore/add/remove/force + +# Whether to add newline after '(' in a function declaration if '(' and ')' are in different lines. +nl_func_decl_start_multi_line = true # false/true + +# Whether to add newline after '(' in a function definition if '(' and ')' are in different lines. +nl_func_def_start_multi_line = true # false/true + +# Add or remove newline after each ',' in a function declaration +nl_func_decl_args = ignore # ignore/add/remove/force + +# Add or remove newline after each ',' in a function definition +nl_func_def_args = ignore # ignore/add/remove/force + +# Whether to add newline after each ',' in a function declaration if '(' and ')' are in different lines. +nl_func_decl_args_multi_line = true # false/true + +# Whether to add newline after each ',' in a function definition if '(' and ')' are in different lines. +nl_func_def_args_multi_line = true # false/true + +# Add or remove newline before the ')' in a function declaration +nl_func_decl_end = remove # ignore/add/remove/force + +# Add or remove newline before the ')' in a function definition +nl_func_def_end = remove # ignore/add/remove/force + +# Overrides nl_func_decl_end when there is only one parameter. +nl_func_decl_end_single = ignore # ignore/add/remove/force + +# Overrides nl_func_def_end when there is only one parameter. +nl_func_def_end_single = ignore # ignore/add/remove/force + +# Whether to add newline before ')' in a function declaration if '(' and ')' are in different lines. +nl_func_decl_end_multi_line = false # false/true + +# Whether to add newline before ')' in a function definition if '(' and ')' are in different lines. +nl_func_def_end_multi_line = false # false/true + +# Add or remove newline between '()' in a function declaration. +nl_func_decl_empty = remove # ignore/add/remove/force + +# Add or remove newline between '()' in a function definition. +nl_func_def_empty = remove # ignore/add/remove/force + +# Whether to add newline after '(' in a function call if '(' and ')' are in different lines. +nl_func_call_start_multi_line = true # false/true + +# Whether to add newline after each ',' in a function call if '(' and ')' are in different lines. +nl_func_call_args_multi_line = true # false/true + +# Whether to add newline before ')' in a function call if '(' and ')' are in different lines. +nl_func_call_end_multi_line = false # false/true + +# Whether to put each OC message parameter on a separate line +# See nl_oc_msg_leave_one_liner +nl_oc_msg_args = false # false/true + +# Add or remove newline between function signature and '{' +nl_fdef_brace = force # ignore/add/remove/force + +# Add or remove newline between C++11 lambda signature and '{' +nl_cpp_ldef_brace = remove # ignore/add/remove/force + +# Add or remove a newline between the return keyword and return expression. +nl_return_expr = remove # ignore/add/remove/force + +# Whether to put a newline after semicolons, except in 'for' statements +nl_after_semicolon = true # false/true + +# Java: Control the newline between the ')' and '{{' of the double brace initializer. +nl_paren_dbrace_open = ignore # ignore/add/remove/force + +# Whether to put a newline after brace open. +# This also adds a newline before the matching brace close. +nl_after_brace_open = true # false/true + +# If nl_after_brace_open and nl_after_brace_open_cmt are true, a newline is +# placed between the open brace and a trailing single-line comment. +nl_after_brace_open_cmt = false # false/true + +# Whether to put a newline after a virtual brace open with a non-empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open = false # false/true + +# Whether to put a newline after a virtual brace open with an empty body. +# These occur in un-braced if/while/do/for statement bodies. +nl_after_vbrace_open_empty = false # false/true + +# Whether to put a newline after a brace close. +# Does not apply if followed by a necessary ';'. +nl_after_brace_close = true # false/true + +# Whether to put a newline after a virtual brace close. +# Would add a newline before return in: 'if (foo) a++; return;' +nl_after_vbrace_close = false # false/true + +# Control the newline between the close brace and 'b' in: 'struct { int a; } b;' +# Affects enums, unions and structures. If set to ignore, uses nl_after_brace_close +nl_brace_struct_var = ignore # ignore/add/remove/force + +# Whether to alter newlines in '#define' macros +nl_define_macro = false # false/true + +# Whether to remove blanks after '#ifxx' and '#elxx', or before '#elxx' and '#endif'. Does not affect top-level #ifdefs. +nl_squeeze_ifdef = false # false/true + +# Makes the nl_squeeze_ifdef option affect the top-level #ifdefs as well. +nl_squeeze_ifdef_top_level = false # false/true + +# Add or remove blank line before 'if' +nl_before_if = ignore # ignore/add/remove/force + +# Add or remove blank line after 'if' statement +nl_after_if = ignore # ignore/add/remove/force + +# Add or remove blank line before 'for' +nl_before_for = ignore # ignore/add/remove/force + +# Add or remove blank line after 'for' statement +nl_after_for = ignore # ignore/add/remove/force + +# Add or remove blank line before 'while' +nl_before_while = ignore # ignore/add/remove/force + +# Add or remove blank line after 'while' statement +nl_after_while = ignore # ignore/add/remove/force + +# Add or remove blank line before 'switch' +nl_before_switch = ignore # ignore/add/remove/force + +# Add or remove blank line after 'switch' statement +nl_after_switch = ignore # ignore/add/remove/force + +# Add or remove blank line before 'synchronized' +nl_before_synchronized = ignore # ignore/add/remove/force + +# Add or remove blank line after 'synchronized' statement +nl_after_synchronized = ignore # ignore/add/remove/force + +# Add or remove blank line before 'do' +nl_before_do = ignore # ignore/add/remove/force + +# Add or remove blank line after 'do/while' statement +nl_after_do = ignore # ignore/add/remove/force + +# Whether to double-space commented-entries in struct/union/enum +nl_ds_struct_enum_cmt = false # false/true + +# force nl before } of a struct/union/enum +# (lower priority than 'eat_blanks_before_close_brace') +nl_ds_struct_enum_close_brace = false # false/true + +# Add or remove blank line before 'func_class_def' +nl_before_func_class_def = 0 # number + +# Add or remove blank line before 'func_class_proto' +nl_before_func_class_proto = 0 # number + +# Add or remove a newline before/after a class colon, +# (tied to pos_class_colon). +nl_class_colon = remove # ignore/add/remove/force + +# Add or remove a newline around a class constructor colon. +# Related to nl_constr_init_args, pos_constr_colon and pos_constr_comma. +nl_constr_colon = force # ignore/add/remove/force + +# Change simple unbraced if statements into a one-liner +# 'if(b)\n i++;' => 'if(b) i++;' +nl_create_if_one_liner = false # false/true + +# Change simple unbraced for statements into a one-liner +# 'for (i=0;i<5;i++)\n foo(i);' => 'for (i=0;i<5;i++) foo(i);' +nl_create_for_one_liner = false # false/true + +# Change simple unbraced while statements into a one-liner +# 'while (i<5)\n foo(i++);' => 'while (i<5) foo(i++);' +nl_create_while_one_liner = false # false/true + +# Change a one-liner if statement into simple unbraced if +# 'if(b) i++;' => 'if(b) i++;' +nl_split_if_one_liner = true # false/true + +# Change a one-liner for statement into simple unbraced for +# 'for (i=0;<5;i++) foo(i);' => 'for (i=0;<5;i++) foo(i);' +nl_split_for_one_liner = true # false/true + +# Change simple unbraced while statements into a one-liner while +# 'while (i<5)\n foo(i++);' => 'while (i<5) foo(i++);' +nl_split_while_one_liner = true # false/true + +# +# Positioning options +# + +# The position of arithmetic operators in wrapped expressions +pos_arith = lead # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of assignment in wrapped expressions. +# Do not affect '=' followed by '{' +pos_assign = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of boolean operators in wrapped expressions +pos_bool = lead # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of comparison operators in wrapped expressions +pos_compare = lead # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of conditional (b ? t : f) operators in wrapped expressions +pos_conditional = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in wrapped expressions +pos_comma = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in enum entries +pos_enum_comma = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in the base class list if there are more than one line, +# (tied to nl_class_init_args). +pos_class_comma = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of the comma in the constructor initialization list. +# Related to nl_constr_colon, nl_constr_init_args and pos_constr_colon. +pos_constr_comma = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of trailing/leading class colon, between class and base class list +# (tied to nl_class_colon). +pos_class_colon = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# The position of colons between constructor and member initialization, +# (tied to UO_nl_constr_colon). +# Related to nl_constr_colon, nl_constr_init_args and pos_constr_comma. +pos_constr_colon = trail # ignore/join/lead/lead_break/lead_force/trail/trail_break/trail_force + +# +# Line Splitting options +# + +# Try to limit code width to N number of columns +code_width = 120 # number + +# Whether to fully split long 'for' statements at semi-colons +ls_for_split_full = true # false/true + +# Whether to fully split long function protos/calls at commas +ls_func_split_full = true # false/true + +# Whether to split lines as close to code_width as possible and ignore some groupings +ls_code_width = false # false/true + +# +# Blank line options +# + +# The maximum consecutive newlines (3 = 2 blank lines) +nl_max = 3 # number + +# The number of newlines after a function prototype, if followed by another function prototype +nl_after_func_proto = 0 # number + +# The number of newlines after a function prototype, if not followed by another function prototype +nl_after_func_proto_group = 2 # number + +# The number of newlines after a function class prototype, if followed by another function class prototype +nl_after_func_class_proto = 0 # number + +# The number of newlines after a function class prototype, if not followed by another function class prototype +nl_after_func_class_proto_group = 2 # number + +# The number of newlines before a multi-line function def body +nl_before_func_body_def = 0 # number + +# The number of newlines before a multi-line function prototype body +nl_before_func_body_proto = 0 # number + +# The number of newlines after '}' of a multi-line function body +nl_after_func_body = 2 # number + +# The number of newlines after '}' of a multi-line function body in a class declaration +nl_after_func_body_class = 2 # number + +# The number of newlines after '}' of a single line function body +nl_after_func_body_one_liner = 2 # number + +# The minimum number of newlines before a multi-line comment. +# Doesn't apply if after a brace open or another multi-line comment. +nl_before_block_comment = 0 # number + +# The minimum number of newlines before a single-line C comment. +# Doesn't apply if after a brace open or other single-line C comments. +nl_before_c_comment = 0 # number + +# The minimum number of newlines before a CPP comment. +# Doesn't apply if after a brace open or other CPP comments. +nl_before_cpp_comment = 0 # number + +# Whether to force a newline after a multi-line comment. +nl_after_multiline_comment = false # false/true + +# Whether to force a newline after a label's colon. +nl_after_label_colon = false # false/true + +# The number of newlines after '}' or ';' of a struct/enum/union definition +nl_after_struct = 2 # number + +# The number of newlines before a class definition +nl_before_class = 2 # number + +# The number of newlines after '}' or ';' of a class definition +nl_after_class = 0 # number + +# The number of newlines before a 'private:', 'public:', 'protected:', 'signals:', or 'slots:' label. +# Will not change the newline count if after a brace open. +# 0 = No change. +nl_before_access_spec = 2 # number + +# The number of newlines after a 'private:', 'public:', 'protected:', 'signals:' or 'slots:' label. +# 0 = No change. +# the option 'nl_after_access_spec' takes preference over 'nl_typedef_blk_start' and 'nl_var_def_blk_start' +nl_after_access_spec = 1 # number + +# The number of newlines between a function def and the function comment. +# 0 = No change. +nl_comment_func_def = 0 # number + +# The number of newlines after a try-catch-finally block that isn't followed by a brace close. +# 0 = No change. +nl_after_try_catch_finally = 0 # number + +# The number of newlines before and after a property, indexer or event decl. +# 0 = No change. +nl_around_cs_property = 0 # number + +# The number of newlines between the get/set/add/remove handlers in C#. +# 0 = No change. +nl_between_get_set = 0 # number + +# Add or remove newline between C# property and the '{' +nl_property_brace = ignore # ignore/add/remove/force + +# Whether to remove blank lines after '{' +eat_blanks_after_open_brace = false # false/true + +# Whether to remove blank lines before '}' +eat_blanks_before_close_brace = false # false/true + +# How aggressively to remove extra newlines not in preproc. +# 0: No change +# 1: Remove most newlines not handled by other config +# 2: Remove all newlines and reformat completely by config +nl_remove_extra_newlines = 0 # number + +# Whether to put a blank line before 'return' statements, unless after an open brace. +nl_before_return = false # false/true + +# Whether to put a blank line after 'return' statements, unless followed by a close brace. +nl_after_return = false # false/true + +# Whether to put a newline after a Java annotation statement. +# Only affects annotations that are after a newline. +nl_after_annotation = ignore # ignore/add/remove/force + +# Controls the newline between two annotations. +nl_between_annotation = ignore # ignore/add/remove/force + +# +# Code modifying options (non-whitespace) +# + +# Add or remove braces on single-line 'do' statement +mod_full_brace_do = force # ignore/add/remove/force + +# Add or remove braces on single-line 'for' statement +mod_full_brace_for = force # ignore/add/remove/force + +# Add or remove braces on single-line function definitions. (Pawn) +mod_full_brace_function = ignore # ignore/add/remove/force + +# Add or remove braces on single-line 'if' statement. Will not remove the braces if they contain an 'else'. +mod_full_brace_if = force # ignore/add/remove/force + +# Make all if/elseif/else statements in a chain be braced or not. Overrides mod_full_brace_if. +# If any must be braced, they are all braced. If all can be unbraced, then the braces are removed. +mod_full_brace_if_chain = false # false/true + +# Make all if/elseif/else statements with at least one 'else' or 'else if' fully braced. +# If mod_full_brace_if_chain is used together with this option, all if-else chains will get braces, +# and simple 'if' statements will lose them (if possible). +mod_full_brace_if_chain_only = true # false/true + +# Don't remove braces around statements that span N newlines +mod_full_brace_nl = 0 # number + +# Add or remove braces on single-line 'while' statement +mod_full_brace_while = force # ignore/add/remove/force + +# Add or remove braces on single-line 'using ()' statement +mod_full_brace_using = force # ignore/add/remove/force + +# Add or remove unnecessary paren on 'return' statement +mod_paren_on_return = ignore # ignore/add/remove/force + +# Whether to change optional semicolons to real semicolons +mod_pawn_semicolon = false # false/true + +# Add parens on 'while' and 'if' statement around bools +mod_full_paren_if_bool = false # false/true + +# Whether to remove superfluous semicolons +mod_remove_extra_semicolon = true # false/true + +# If a function body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_function_closebrace_comment = 0 # number + +# If a namespace body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_namespace_closebrace_comment = 0 # number + +# If a class body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_class_closebrace_comment = 0 # number + +# If a switch body exceeds the specified number of newlines and doesn't have a comment after +# the close brace, a comment will be added. +mod_add_long_switch_closebrace_comment = 0 # number + +# If an #ifdef body exceeds the specified number of newlines and doesn't have a comment after +# the #endif, a comment will be added. +mod_add_long_ifdef_endif_comment = 0 # number + +# If an #ifdef or #else body exceeds the specified number of newlines and doesn't have a comment after +# the #else, a comment will be added. +mod_add_long_ifdef_else_comment = 0 # number + +# If TRUE, will sort consecutive single-line 'import' statements [Java, D] +mod_sort_import = false # false/true + +# If TRUE, will sort consecutive single-line 'using' statements [C#] +mod_sort_using = false # false/true + +# If TRUE, will sort consecutive single-line '#include' statements [C/C++] and '#import' statements [Obj-C] +# This is generally a bad idea, as it may break your code. +mod_sort_include = true # false/true + +# If TRUE, it will move a 'break' that appears after a fully braced 'case' before the close brace. +mod_move_case_break = false # false/true + +# Will add or remove the braces around a fully braced case statement. +# Will only remove the braces if there are no variable declarations in the block. +mod_case_brace = ignore # ignore/add/remove/force + +# If TRUE, it will remove a void 'return;' that appears as the last statement in a function. +mod_remove_empty_return = false # false/true + +# If TRUE, it will organize the properties (Obj-C) +mod_sort_oc_properties = false # false/true + +# Determines weight of atomic/nonatomic (Obj-C) +mod_sort_oc_property_thread_safe_weight = 0 # number + +# Determines weight of readwrite (Obj-C) +mod_sort_oc_property_readwrite_weight = 0 # number + +# Determines weight of reference type (retain, copy, assign, weak, strong) (Obj-C) +mod_sort_oc_property_reference_weight = 0 # number + +# Determines weight of getter type (getter=) (Obj-C) +mod_sort_oc_property_getter_weight = 0 # number + +# Determines weight of setter type (setter=) (Obj-C) +mod_sort_oc_property_setter_weight = 0 # number + +# Determines weight of nullability type (nullable/nonnull) (Obj-C) +mod_sort_oc_property_nullability_weight = 0 # number + +# +# Comment modifications +# + +# Try to wrap comments at cmt_width columns +cmt_width = 0 # number + +# Set the comment reflow mode (default: 0) +# 0: no reflowing (apart from the line wrapping due to cmt_width) +# 1: no touching at all +# 2: full reflow +cmt_reflow_mode = 0 # number + +# Whether to convert all tabs to spaces in comments. Default is to leave tabs inside comments alone, unless used for indenting. +cmt_convert_tab_to_spaces = false # false/true + +# If false, disable all multi-line comment changes, including cmt_width. keyword substitution and leading chars. +# Default=True. +cmt_indent_multi = true # false/true + +# Whether to group c-comments that look like they are in a block +cmt_c_group = false # false/true + +# Whether to put an empty '/*' on the first line of the combined c-comment +cmt_c_nl_start = false # false/true + +# Whether to put a newline before the closing '*/' of the combined c-comment +cmt_c_nl_end = false # false/true + +# Whether to group cpp-comments that look like they are in a block +cmt_cpp_group = false # false/true + +# Whether to put an empty '/*' on the first line of the combined cpp-comment +cmt_cpp_nl_start = false # false/true + +# Whether to put a newline before the closing '*/' of the combined cpp-comment +cmt_cpp_nl_end = false # false/true + +# Whether to change cpp-comments into c-comments +cmt_cpp_to_c = false # false/true + +# Whether to put a star on subsequent comment lines +cmt_star_cont = false # false/true + +# The number of spaces to insert at the start of subsequent comment lines +cmt_sp_before_star_cont = 0 # number + +# The number of spaces to insert after the star on subsequent comment lines +cmt_sp_after_star_cont = 0 # number + +# For multi-line comments with a '*' lead, remove leading spaces if the first and last lines of +# the comment are the same length. Default=True +cmt_multi_check_last = true # false/true + +# For multi-line comments with a '*' lead, remove leading spaces if the first and last lines of +# the comment are the same length AND if the length is bigger as the first_len minimum. Default=4 +cmt_multi_first_len_minimum = 4 # number + +# The filename that contains text to insert at the head of a file if the file doesn't start with a C/C++ comment. +# Will substitute $(filename) with the current file's name. +cmt_insert_file_header = "" # string + +# The filename that contains text to insert at the end of a file if the file doesn't end with a C/C++ comment. +# Will substitute $(filename) with the current file's name. +cmt_insert_file_footer = "" # string + +# The filename that contains text to insert before a function implementation if the function isn't preceded with a C/C++ comment. +# Will substitute $(function) with the function name and $(javaparam) with the javadoc @param and @return stuff. +# Will also substitute $(fclass) with the class name: void CFoo::Bar() { ... } +cmt_insert_func_header = "" # string + +# The filename that contains text to insert before a class if the class isn't preceded with a C/C++ comment. +# Will substitute $(class) with the class name. +cmt_insert_class_header = "" # string + +# The filename that contains text to insert before a Obj-C message specification if the method isn't preceded with a C/C++ comment. +# Will substitute $(message) with the function name and $(javaparam) with the javadoc @param and @return stuff. +cmt_insert_oc_msg_header = "" # string + +# If a preprocessor is encountered when stepping backwards from a function name, then +# this option decides whether the comment should be inserted. +# Affects cmt_insert_oc_msg_header, cmt_insert_func_header and cmt_insert_class_header. +cmt_insert_before_preproc = false # false/true + +# If a function is declared inline to a class definition, then +# this option decides whether the comment should be inserted. +# Affects cmt_insert_func_header. +cmt_insert_before_inlines = true # false/true + +# If the function is a constructor/destructor, then +# this option decides whether the comment should be inserted. +# Affects cmt_insert_func_header. +cmt_insert_before_ctor_dtor = false # false/true + +# +# Preprocessor options +# + +# Control indent of preprocessors inside #if blocks at brace level 0 (file-level) +pp_indent = ignore # ignore/add/remove/force + +# Whether to indent #if/#else/#endif at the brace level (true) or from column 1 (false) +pp_indent_at_level = false # false/true + +# Specifies the number of columns to indent preprocessors per level at brace level 0 (file-level). +# If pp_indent_at_level=false, specifies the number of columns to indent preprocessors per level at brace level > 0 (function-level). +# Default=1. +pp_indent_count = 1 # number + +# Add or remove space after # based on pp_level of #if blocks +pp_space = ignore # ignore/add/remove/force + +# Sets the number of spaces added with pp_space +pp_space_count = 0 # number + +# The indent for #region and #endregion in C# and '#pragma region' in C/C++ +pp_indent_region = 0 # number + +# Whether to indent the code between #region and #endregion +pp_region_indent_code = false # false/true + +# If pp_indent_at_level=true, sets the indent for #if, #else and #endif when not at file-level. +# 0: indent preprocessors using output_tab_size. +# >0: column at which all preprocessors will be indented. +pp_indent_if = 0 # number + +# Control whether to indent the code between #if, #else and #endif. +pp_if_indent_code = false # false/true + +# Whether to indent '#define' at the brace level (true) or from column 1 (false) +pp_define_at_level = false # false/true + +# +# Use or Do not Use options +# + +# True: indent_func_call_param will be used (default) +# False: indent_func_call_param will NOT be used +use_indent_func_call_param = true # false/true + +# The value of the indentation for a continuation line is calculate differently if the line is: +# a declaration :your case with QString fileName ... +# an assigment :your case with pSettings = new QSettings( ... +# At the second case the option value might be used twice: +# at the assigment +# at the function call (if present) +# To prevent the double use of the option value, use this option with the value 'true'. +# True: indent_continue will be used only once +# False: indent_continue will be used every time (default) +use_indent_continue_only_once = false # false/true + +# SIGNAL/SLOT Qt macros have special formatting options. See options_for_QT.cpp for details. +# Default=True. +use_options_overriding_for_qt_macros = true # false/true + +# +# Warn levels - 1: error, 2: warning (default), 3: note +# + +# Warning is given if doing tab-to-\t replacement and we have found one in a C# verbatim string literal. +warn_level_tabs_found_in_verbatim_string_literals = 2 # number + +# You can force a token to be a type with the 'type' option. +# Example: +# type myfoo1 myfoo2 +# +# You can create custom macro-based indentation using macro-open, +# macro-else and macro-close. +# Example: +# macro-open BEGIN_TEMPLATE_MESSAGE_MAP +# macro-open BEGIN_MESSAGE_MAP +# macro-close END_MESSAGE_MAP +# +# You can assign any keyword to any type with the set option. +# set func_call_user _ N_ +# +# The full syntax description of all custom definition config entries +# is shown below: +# +# define custom tokens as: +# - embed whitespace in token using '' escape character, or +# put token in quotes +# - these: ' " and ` are recognized as quote delimiters +# +# type token1 token2 token3 ... +# ^ optionally specify multiple tokens on a single line +# define def_token output_token +# ^ output_token is optional, then NULL is assumed +# macro-open token +# macro-close token +# macro-else token +# set id token1 token2 ... +# ^ optionally specify multiple tokens on a single line +# ^ id is one of the names in token_enum.h sans the CT_ prefix, +# e.g. PP_PRAGMA +# +# all tokens are separated by any mix of ',' commas, '=' equal signs +# and whitespace (space, tab) +# +# You can add support for other file extensions using the 'file_ext' command. +# The first arg is the language name used with the '-l' option. +# The remaining args are file extensions, matched with 'endswith'. +# file_ext CPP .ch .cxx .cpp.in +# +# option(s) with 'not default' value: 0 +# \ No newline at end of file diff --git a/uncrustify_wrapper.sh b/uncrustify_wrapper.sh new file mode 100755 index 00000000..ceea889a --- /dev/null +++ b/uncrustify_wrapper.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +uncrustify -c uncrustify.cfg -l CPP --no-backup `find -regex './src/.*\.\(cpp\|h\)$'` \ No newline at end of file diff --git a/windows/fftw3.h b/windows/fftw3.h new file mode 100644 index 00000000..39661d22 --- /dev/null +++ b/windows/fftw3.h @@ -0,0 +1,415 @@ +/* + * Copyright (c) 2003, 2007-14 Matteo Frigo + * Copyright (c) 2003, 2007-14 Massachusetts Institute of Technology + * + * The following statement of license applies *only* to this header file, + * and *not* to the other files distributed with FFTW or derived therefrom: + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE + * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/***************************** NOTE TO USERS ********************************* + * + * THIS IS A HEADER FILE, NOT A MANUAL + * + * If you want to know how to use FFTW, please read the manual, + * online at http://www.fftw.org/doc/ and also included with FFTW. + * For a quick start, see the manual's tutorial section. + * + * (Reading header files to learn how to use a library is a habit + * stemming from code lacking a proper manual. Arguably, it's a + * *bad* habit in most cases, because header files can contain + * interfaces that are not part of the public, stable API.) + * + ****************************************************************************/ + +#ifndef FFTW3_H +#define FFTW3_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + +/* If is included, use the C99 complex type. Otherwise + define a type bit-compatible with C99 complex */ +#if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) +# define FFTW_DEFINE_COMPLEX(R, C) typedef R _Complex C +#else +# define FFTW_DEFINE_COMPLEX(R, C) typedef R C[2] +#endif + +#define FFTW_CONCAT(prefix, name) prefix ## name +#define FFTW_MANGLE_DOUBLE(name) FFTW_CONCAT(fftw_, name) +#define FFTW_MANGLE_FLOAT(name) FFTW_CONCAT(fftwf_, name) +#define FFTW_MANGLE_LONG_DOUBLE(name) FFTW_CONCAT(fftwl_, name) +#define FFTW_MANGLE_QUAD(name) FFTW_CONCAT(fftwq_, name) + +/* IMPORTANT: for Windows compilers, you should add a line +*/ +#define FFTW_DLL +/* + here and in kernel/ifftw.h if you are compiling/using FFTW as a + DLL, in order to do the proper importing/exporting, or + alternatively compile with -DFFTW_DLL or the equivalent + command-line flag. This is not necessary under MinGW/Cygwin, where + libtool does the imports/exports automatically. */ +#if defined(FFTW_DLL) && (defined(_WIN32) || defined(__WIN32__)) + /* annoying Windows syntax for shared-library declarations */ +# if defined(COMPILING_FFTW) /* defined in api.h when compiling FFTW */ +# define FFTW_EXTERN extern __declspec(dllexport) +# else /* user is calling FFTW; import symbol */ +# define FFTW_EXTERN extern __declspec(dllimport) +# endif +#else +# define FFTW_EXTERN extern +#endif + +enum fftw_r2r_kind_do_not_use_me { + FFTW_R2HC=0, FFTW_HC2R=1, FFTW_DHT=2, + FFTW_REDFT00=3, FFTW_REDFT01=4, FFTW_REDFT10=5, FFTW_REDFT11=6, + FFTW_RODFT00=7, FFTW_RODFT01=8, FFTW_RODFT10=9, FFTW_RODFT11=10 +}; + +struct fftw_iodim_do_not_use_me { + int n; /* dimension size */ + int is; /* input stride */ + int os; /* output stride */ +}; + +#include /* for ptrdiff_t */ +struct fftw_iodim64_do_not_use_me { + ptrdiff_t n; /* dimension size */ + ptrdiff_t is; /* input stride */ + ptrdiff_t os; /* output stride */ +}; + +typedef void (*fftw_write_char_func_do_not_use_me)(char c, void *); +typedef int (*fftw_read_char_func_do_not_use_me)(void *); + +/* + huge second-order macro that defines prototypes for all API + functions. We expand this macro for each supported precision + + X: name-mangling macro + R: real data type + C: complex data type +*/ + +#define FFTW_DEFINE_API(X, R, C) \ + \ +FFTW_DEFINE_COMPLEX(R, C); \ + \ +typedef struct X(plan_s) *X(plan); \ + \ +typedef struct fftw_iodim_do_not_use_me X(iodim); \ +typedef struct fftw_iodim64_do_not_use_me X(iodim64); \ + \ +typedef enum fftw_r2r_kind_do_not_use_me X(r2r_kind); \ + \ +typedef fftw_write_char_func_do_not_use_me X(write_char_func); \ +typedef fftw_read_char_func_do_not_use_me X(read_char_func); \ + \ +FFTW_EXTERN void X(execute)(const X(plan) p); \ + \ +FFTW_EXTERN X(plan) X(plan_dft)(int rank, const int *n, \ + C *in, C *out, int sign, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_1d)(int n, C *in, C *out, int sign, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_2d)(int n0, int n1, \ + C *in, C *out, int sign, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_3d)(int n0, int n1, int n2, \ + C *in, C *out, int sign, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_many_dft)(int rank, const int *n, \ + int howmany, \ + C *in, const int *inembed, \ + int istride, int idist, \ + C *out, const int *onembed, \ + int ostride, int odist, \ + int sign, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_dft)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + C *in, C *out, \ + int sign, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru_split_dft)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *ri, R *ii, R *ro, R *io, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_dft)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + C *in, C *out, \ + int sign, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru64_split_dft)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *ri, R *ii, R *ro, R *io, \ + unsigned flags); \ + \ +FFTW_EXTERN void X(execute_dft)(const X(plan) p, C *in, C *out); \ +FFTW_EXTERN void X(execute_split_dft)(const X(plan) p, R *ri, R *ii, \ + R *ro, R *io); \ + \ +FFTW_EXTERN X(plan) X(plan_many_dft_r2c)(int rank, const int *n, \ + int howmany, \ + R *in, const int *inembed, \ + int istride, int idist, \ + C *out, const int *onembed, \ + int ostride, int odist, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_r2c)(int rank, const int *n, \ + R *in, C *out, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_r2c_1d)(int n,R *in,C *out,unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_r2c_2d)(int n0, int n1, \ + R *in, C *out, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_r2c_3d)(int n0, int n1, \ + int n2, \ + R *in, C *out, unsigned flags); \ + \ + \ +FFTW_EXTERN X(plan) X(plan_many_dft_c2r)(int rank, const int *n, \ + int howmany, \ + C *in, const int *inembed, \ + int istride, int idist, \ + R *out, const int *onembed, \ + int ostride, int odist, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_c2r)(int rank, const int *n, \ + C *in, R *out, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_dft_c2r_1d)(int n,C *in,R *out,unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_c2r_2d)(int n0, int n1, \ + C *in, R *out, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_dft_c2r_3d)(int n0, int n1, \ + int n2, \ + C *in, R *out, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_dft_r2c)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *in, C *out, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru_dft_c2r)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + C *in, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_split_dft_r2c)( \ + int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *in, R *ro, R *io, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru_split_dft_c2r)( \ + int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *ri, R *ii, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_dft_r2c)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *in, C *out, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru64_dft_c2r)(int rank, \ + const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + C *in, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_split_dft_r2c)( \ + int rank, const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *in, R *ro, R *io, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_guru64_split_dft_c2r)( \ + int rank, const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *ri, R *ii, R *out, \ + unsigned flags); \ + \ +FFTW_EXTERN void X(execute_dft_r2c)(const X(plan) p, R *in, C *out); \ +FFTW_EXTERN void X(execute_dft_c2r)(const X(plan) p, C *in, R *out); \ + \ +FFTW_EXTERN void X(execute_split_dft_r2c)(const X(plan) p, \ + R *in, R *ro, R *io); \ +FFTW_EXTERN void X(execute_split_dft_c2r)(const X(plan) p, \ + R *ri, R *ii, R *out); \ + \ +FFTW_EXTERN X(plan) X(plan_many_r2r)(int rank, const int *n, \ + int howmany, \ + R *in, const int *inembed, \ + int istride, int idist, \ + R *out, const int *onembed, \ + int ostride, int odist, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_r2r)(int rank, const int *n, R *in, R *out, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_r2r_1d)(int n, R *in, R *out, \ + X(r2r_kind) kind, unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_r2r_2d)(int n0, int n1, R *in, R *out, \ + X(r2r_kind) kind0, X(r2r_kind) kind1, \ + unsigned flags); \ +FFTW_EXTERN X(plan) X(plan_r2r_3d)(int n0, int n1, int n2, \ + R *in, R *out, X(r2r_kind) kind0, \ + X(r2r_kind) kind1, X(r2r_kind) kind2, \ + unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru_r2r)(int rank, const X(iodim) *dims, \ + int howmany_rank, \ + const X(iodim) *howmany_dims, \ + R *in, R *out, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN X(plan) X(plan_guru64_r2r)(int rank, const X(iodim64) *dims, \ + int howmany_rank, \ + const X(iodim64) *howmany_dims, \ + R *in, R *out, \ + const X(r2r_kind) *kind, unsigned flags); \ + \ +FFTW_EXTERN void X(execute_r2r)(const X(plan) p, R *in, R *out); \ + \ +FFTW_EXTERN void X(destroy_plan)(X(plan) p); \ +FFTW_EXTERN void X(forget_wisdom)(void); \ +FFTW_EXTERN void X(cleanup)(void); \ + \ +FFTW_EXTERN void X(set_timelimit)(double t); \ + \ +FFTW_EXTERN void X(plan_with_nthreads)(int nthreads); \ +FFTW_EXTERN int X(init_threads)(void); \ +FFTW_EXTERN void X(cleanup_threads)(void); \ +FFTW_EXTERN void X(make_planner_thread_safe)(void); \ + \ +FFTW_EXTERN int X(export_wisdom_to_filename)(const char *filename); \ +FFTW_EXTERN void X(export_wisdom_to_file)(FILE *output_file); \ +FFTW_EXTERN char *X(export_wisdom_to_string)(void); \ +FFTW_EXTERN void X(export_wisdom)(X(write_char_func) write_char, \ + void *data); \ +FFTW_EXTERN int X(import_system_wisdom)(void); \ +FFTW_EXTERN int X(import_wisdom_from_filename)(const char *filename); \ +FFTW_EXTERN int X(import_wisdom_from_file)(FILE *input_file); \ +FFTW_EXTERN int X(import_wisdom_from_string)(const char *input_string); \ +FFTW_EXTERN int X(import_wisdom)(X(read_char_func) read_char, void *data); \ + \ +FFTW_EXTERN void X(fprint_plan)(const X(plan) p, FILE *output_file); \ +FFTW_EXTERN void X(print_plan)(const X(plan) p); \ +FFTW_EXTERN char *X(sprint_plan)(const X(plan) p); \ + \ +FFTW_EXTERN void *X(malloc)(size_t n); \ +FFTW_EXTERN R *X(alloc_real)(size_t n); \ +FFTW_EXTERN C *X(alloc_complex)(size_t n); \ +FFTW_EXTERN void X(free)(void *p); \ + \ +FFTW_EXTERN void X(flops)(const X(plan) p, \ + double *add, double *mul, double *fmas); \ +FFTW_EXTERN double X(estimate_cost)(const X(plan) p); \ +FFTW_EXTERN double X(cost)(const X(plan) p); \ + \ +FFTW_EXTERN int X(alignment_of)(R *p); \ +FFTW_EXTERN const char X(version)[]; \ +FFTW_EXTERN const char X(cc)[]; \ +FFTW_EXTERN const char X(codelet_optim)[]; + + +/* end of FFTW_DEFINE_API macro */ + +FFTW_DEFINE_API(FFTW_MANGLE_DOUBLE, double, fftw_complex) +FFTW_DEFINE_API(FFTW_MANGLE_FLOAT, float, fftwf_complex) +FFTW_DEFINE_API(FFTW_MANGLE_LONG_DOUBLE, long double, fftwl_complex) + +/* __float128 (quad precision) is a gcc extension on i386, x86_64, and ia64 + for gcc >= 4.6 (compiled in FFTW with --enable-quad-precision) */ +#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) \ + && !(defined(__ICC) || defined(__INTEL_COMPILER) || defined(__CUDACC__) || defined(__PGI)) \ + && (defined(__i386__) || defined(__x86_64__) || defined(__ia64__)) +# if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) +/* note: __float128 is a typedef, which is not supported with the _Complex + keyword in gcc, so instead we use this ugly __attribute__ version. + However, we can't simply pass the __attribute__ version to + FFTW_DEFINE_API because the __attribute__ confuses gcc in pointer + types. Hence redefining FFTW_DEFINE_COMPLEX. Ugh. */ +# undef FFTW_DEFINE_COMPLEX +# define FFTW_DEFINE_COMPLEX(R, C) typedef _Complex float __attribute__((mode(TC))) C +# endif +FFTW_DEFINE_API(FFTW_MANGLE_QUAD, __float128, fftwq_complex) +#endif + +#define FFTW_FORWARD (-1) +#define FFTW_BACKWARD (+1) + +#define FFTW_NO_TIMELIMIT (-1.0) + +/* documented flags */ +#define FFTW_MEASURE (0U) +#define FFTW_DESTROY_INPUT (1U << 0) +#define FFTW_UNALIGNED (1U << 1) +#define FFTW_CONSERVE_MEMORY (1U << 2) +#define FFTW_EXHAUSTIVE (1U << 3) /* NO_EXHAUSTIVE is default */ +#define FFTW_PRESERVE_INPUT (1U << 4) /* cancels FFTW_DESTROY_INPUT */ +#define FFTW_PATIENT (1U << 5) /* IMPATIENT is default */ +#define FFTW_ESTIMATE (1U << 6) +#define FFTW_WISDOM_ONLY (1U << 21) + +/* undocumented beyond-guru flags */ +#define FFTW_ESTIMATE_PATIENT (1U << 7) +#define FFTW_BELIEVE_PCOST (1U << 8) +#define FFTW_NO_DFT_R2HC (1U << 9) +#define FFTW_NO_NONTHREADED (1U << 10) +#define FFTW_NO_BUFFERING (1U << 11) +#define FFTW_NO_INDIRECT_OP (1U << 12) +#define FFTW_ALLOW_LARGE_GENERIC (1U << 13) /* NO_LARGE_GENERIC is default */ +#define FFTW_NO_RANK_SPLITS (1U << 14) +#define FFTW_NO_VRANK_SPLITS (1U << 15) +#define FFTW_NO_VRECURSE (1U << 16) +#define FFTW_NO_SIMD (1U << 17) +#define FFTW_NO_SLOW (1U << 18) +#define FFTW_NO_FIXED_RADIX_LARGE_N (1U << 19) +#define FFTW_ALLOW_PRUNING (1U << 20) + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* FFTW3_H */ diff --git a/windows/libfftw3-3.dll b/windows/libfftw3-3.dll new file mode 100644 index 00000000..75080e49 Binary files /dev/null and b/windows/libfftw3-3.dll differ diff --git a/wizards/analyzerplugin/analyzerplugintemplate.cpp b/wizards/analyzerplugin/analyzerplugintemplate.cpp new file mode 100644 index 00000000..06c75804 --- /dev/null +++ b/wizards/analyzerplugin/analyzerplugintemplate.cpp @@ -0,0 +1,81 @@ +#include "%{HeaderFileName}" +#include "ui_%{HeaderFileName}" + +%{ClassName}::%{ClassName}() : + ui(new Ui::%{ClassName}()) +{ + +} + +%{ClassName}::~%{ClassName}() +{ + delete ui; +} + +AnalyzerInterface* %{ClassName}::createDefaultAnalyzer() +{ + return new %{ClassName}(); +} + +QString %{ClassName}::getName() +{ + return "%{ClassName}"; +} + +void %{ClassName}::provideCallback(QSharedPointer pluginCallback) +{ + // The plugin callback allows the self-triggering of analyzeBits + m_pluginCallback = pluginCallback; +} + +void %{ClassName}::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); +} + +bool %{ClassName}::canRecallPluginState(const QJsonObject &pluginState) +{ + // If pluginState does not have required fields, return false + if(pluginState.isEmpty() == true) { + return false; + } + + return true; +} + +bool %{ClassName}::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + // Set the UI fields based on the plugin state + + return true; +} + +QJsonObject %{ClassName}::getStateFromUi() +{ + QJsonObject pluginState; + + // Pull data from the input fields and input them into pluginState + + return pluginState; +} + +void %{ClassName}::previewBits(QSharedPointer container) +{ + // Prepare operations for the current bit container +} + +QSharedPointer %{ClassName}::analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + QSharedPointer result(new AnalyzerResult()); + + //Perform analysis + + return result; +} diff --git a/wizards/analyzerplugin/analyzerplugintemplate.h b/wizards/analyzerplugin/analyzerplugintemplate.h new file mode 100644 index 00000000..95fa0e17 --- /dev/null +++ b/wizards/analyzerplugin/analyzerplugintemplate.h @@ -0,0 +1,42 @@ +#ifndef %{JS: '%{ClassName}'.toUpperCase()}_H +#define %{JS: '%{ClassName}'.toUpperCase()}_H + +#include "analyzerinterface.h" + +namespace Ui +{ +class %{ClassName}; +} + +class %{ClassName} : public QObject, AnalyzerInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.AnalyzerInterface.%{ClassName}") + Q_INTERFACES(AnalyzerInterface) + +public: + %{ClassName}(); + ~%{ClassName}() override; + + AnalyzerInterface* createDefaultAnalyzer() override; + QString getName() override; + + void provideCallback(QSharedPointer pluginCallback) override; + void applyToWidget(QWidget *widget) override; + + bool canRecallPluginState(const QJsonObject& pluginState) override; + bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + + void previewBits(QSharedPointer container) override; + QSharedPointer analyzeBits( + QSharedPointer container, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +private: + Ui::%{ClassName} *ui; + QSharedPointer m_pluginCallback; +}; + +#endif // %{JS: '%{ClassName}'.toUpperCase()}_H diff --git a/wizards/analyzerplugin/analyzerplugintemplate.pro b/wizards/analyzerplugin/analyzerplugintemplate.pro new file mode 100644 index 00000000..091a16e0 --- /dev/null +++ b/wizards/analyzerplugin/analyzerplugintemplate.pro @@ -0,0 +1,52 @@ +#------------------------------------------------- +# +# Project created by QtCreator %{JS: (new Date().toISOString())} +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = %{ClassName} +TEMPLATE = lib + +DEFINES += %{JS: '%{ClassName}'.toUpperCase()}_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + %{JS: '%{CppFileName}'.toLowerCase()} + +HEADERS += \ + %{JS: '%{HeaderFileName}'.toLowerCase()} + +FORMS += \ + %{JS: '%{UiFileName}'.toLowerCase()} + +DISTFILES += \ + %{JS: '%{JSONFileName}'.toLowerCase()} + + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../../hobbits-core/release/ -lhobbits-core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../../hobbits-core/debug/ -lhobbits-core +else:unix: LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} diff --git a/wizards/analyzerplugin/analyzerplugintemplate.ui b/wizards/analyzerplugin/analyzerplugintemplate.ui new file mode 100644 index 00000000..9dfd4fbd --- /dev/null +++ b/wizards/analyzerplugin/analyzerplugintemplate.ui @@ -0,0 +1,23 @@ + + + %{ClassName} + + + + 0 + 0 + 604 + 147 + + + + Form + + + + + + + + + diff --git a/wizards/analyzerplugin/pluginicon.png b/wizards/analyzerplugin/pluginicon.png new file mode 100644 index 00000000..5fb11ced Binary files /dev/null and b/wizards/analyzerplugin/pluginicon.png differ diff --git a/wizards/analyzerplugin/wizard.json b/wizards/analyzerplugin/wizard.json new file mode 100644 index 00000000..29f95298 --- /dev/null +++ b/wizards/analyzerplugin/wizard.json @@ -0,0 +1,70 @@ +{ + "version": 1, + "supportedProjectTypes": [ "Qt4ProjectManager.Qt4Project" ], + "id": "HobbitsAnalyzer", + "category": "Project", + "trDescription": "Creates a hobbits analyzer plugin. General plugin methods and basic plugin structure are provided.", + "trDisplayName": "Analyzer Plugin", + "trDisplayCategory": "Hobbits Plugin", + "icon": "pluginicon.png", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], + "enabled": "%{JS: [ %{Plugins} ].indexOf('QmakeProjectManager') >= 0}", + "options": + [ + { "key": "ProFileName", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, + { "key": "IsTopLevelProject", "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}'}" }, + { "key": "ClassName", "value": "%{JS: '%{ProjectName}'}" }, + { "key": "CppFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" }, + { "key": "HeaderFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" }, + { "key": "UiFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '.ui')}" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": { "trDescription": "Creates a hobbits analyzer plugin. General plugin methods and basic plugin structure are provided." } + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { "projectFilePath": "%{ProFileName}" } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "analyzerplugintemplate.pro", + "target": "%{ProFileName}", + "openAsProject": true + }, + { + "source": "analyzerplugintemplate.cpp", + "target": "%{CppFileName}" + }, + { + "source": "analyzerplugintemplate.h", + "target": "%{HeaderFileName}" + }, + { + "source": "analyzerplugintemplate.ui", + "target": "%{UiFileName}" + } + + ] + } + ] +} diff --git a/wizards/displayplugin/displaycontrols.cpp b/wizards/displayplugin/displaycontrols.cpp new file mode 100644 index 00000000..cc513abb --- /dev/null +++ b/wizards/displayplugin/displaycontrols.cpp @@ -0,0 +1,9 @@ +#include "%{ControlsHeaderFileName}" +#include "ui_%{ControlsHeaderFileName}" + + +%{ControlWidgetName}::%{ControlWidgetName}() : + ui(new Ui::%{ControlWidgetName}()) +{ + ui->setupUi(this); +} \ No newline at end of file diff --git a/wizards/displayplugin/displaycontrols.h b/wizards/displayplugin/displaycontrols.h new file mode 100644 index 00000000..616f8733 --- /dev/null +++ b/wizards/displayplugin/displaycontrols.h @@ -0,0 +1,22 @@ +#ifndef %{JS: '%{ControlWidgetName}'.toUpperCase()}_H +#define %{JS: '%{ControlWidgetName}'.toUpperCase()}_H + +#include + +namespace Ui +{ +class %{ControlWidgetName}; +} + +class %{ControlWidgetName} : public QWidget +{ + Q_OBJECT + +public: + %{ControlWidgetName}(); + +private: + Ui::%{ControlWidgetName} *ui; +}; + +#endif // %{JS: '%{ControlWidgetName}'.toUpperCase()}_H diff --git a/wizards/displayplugin/displaycontrols.ui b/wizards/displayplugin/displaycontrols.ui new file mode 100644 index 00000000..acbfb9fd --- /dev/null +++ b/wizards/displayplugin/displaycontrols.ui @@ -0,0 +1,21 @@ + + + %{ControlWidgetName} + + + + 0 + 0 + 500 + 100 + + + + + + + + + + + diff --git a/wizards/displayplugin/displayplugintemplate.cpp b/wizards/displayplugin/displayplugintemplate.cpp new file mode 100644 index 00000000..91299338 --- /dev/null +++ b/wizards/displayplugin/displayplugintemplate.cpp @@ -0,0 +1,40 @@ +#include "%{PluginHeaderFileName}" + +%{PluginName}::%{PluginName}() : + m_displayWidget(nullptr), + m_controlsWidget(nullptr) +{ + +} + +DisplayInterface* %{PluginName}::createDefaultDisplay() +{ + return new %{PluginName}(); +} + +QString %{PluginName}::getName() +{ + return "%{PluginName}"; +} + +QWidget* %{PluginName}::getDisplayWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_displayWidget; +} + +QWidget* %{PluginName}::getControlsWidget(QSharedPointer displayHandle) +{ + initialize(displayHandle); + return m_controlsWidget; +} + +void %{PluginName}::initialize(QSharedPointer displayHandle) +{ + if (!m_displayWidget) { + m_displayWidget = new %{DisplayWidgetName}(displayHandle, this); + m_controlsWidget = new %{ControlWidgetName}(); + + // make necessary connections here + } +} diff --git a/wizards/displayplugin/displayplugintemplate.h b/wizards/displayplugin/displayplugintemplate.h new file mode 100644 index 00000000..c8c622c8 --- /dev/null +++ b/wizards/displayplugin/displayplugintemplate.h @@ -0,0 +1,30 @@ +#ifndef %{JS: '%{PluginName}'.toUpperCase()}_H +#define %{JS: '%{PluginName}'.toUpperCase()}_H + +#include "displayinterface.h" +#include "%{ControlsHeaderFileName}" +#include "%{WidgetHeaderFileName}" + +class %{PluginName} : public QObject, DisplayInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.DisplayInterface.%{PluginName}") + Q_INTERFACES(DisplayInterface) + +public: + %{PluginName}(); + + DisplayInterface* createDefaultDisplay(); + + QString getName(); + + QWidget* getDisplayWidget(QSharedPointer displayHandle); + QWidget* getControlsWidget(QSharedPointer displayHandle); + +private: + void initialize(QSharedPointer displayHandle); + %{DisplayWidgetName}* m_displayWidget; + %{ControlWidgetName}* m_controlsWidget; +}; + +#endif // %{JS: '%{PluginName}'.toUpperCase()}_H diff --git a/wizards/displayplugin/displayplugintemplate.pro b/wizards/displayplugin/displayplugintemplate.pro new file mode 100644 index 00000000..45f7ab1d --- /dev/null +++ b/wizards/displayplugin/displayplugintemplate.pro @@ -0,0 +1,55 @@ +#------------------------------------------------- +# +# Project created by QtCreator %{JS: (new Date().toISOString())} +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = %{PluginName} +TEMPLATE = lib + +DEFINES += %{JS: '%{PluginName}'.toUpperCase()}_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + %{PluginCppFileName} \ + %{WidgetCppFileName} \ + %{ControlsCppFileName} + +HEADERS += \ + %{PluginHeaderFileName} \ + %{WidgetHeaderFileName} \ + %{ControlsHeaderFileName} + +FORMS += \ + %{ControlsUiFileName} + +DISTFILES += \ + %{PluginJSONFileName} + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../../hobbits-core/release/ -lhobbits-core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../../hobbits-core/debug/ -lhobbits-core +else:unix: LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/displays + INSTALLS += target +} diff --git a/wizards/displayplugin/displaywidget.cpp b/wizards/displayplugin/displaywidget.cpp new file mode 100644 index 00000000..a59dcf92 --- /dev/null +++ b/wizards/displayplugin/displaywidget.cpp @@ -0,0 +1,34 @@ +#include "%{WidgetHeaderFileName}" + +#include +#include + +%{DisplayWidgetName}::%{DisplayWidgetName}( + QSharedPointer displayHandle, + DisplayInterface *pluginRef, + QWidget *parent) : + DisplayBase(displayHandle, pluginRef, parent) +{ + +} + +void %{DisplayWidgetName}::paintEvent(QPaintEvent*) { + if (m_displayHandle->getContainer().isNull()) { + return; + } + + QImage raster = m_displayHandle->getContainer()->getRasterImage( + m_displayHandle->getBitOffset(), + m_displayHandle->getFrameOffset(), + this->width(), this->height()); + + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, false); + painter.drawImage(0, 0, raster); +} + +void %{DisplayWidgetName}::mouseMoveEvent(QMouseEvent *event) { + sendHoverUpdate(event, 1, 1, 1, 1, QPoint(0, 0)); +} + + diff --git a/wizards/displayplugin/displaywidget.h b/wizards/displayplugin/displaywidget.h new file mode 100644 index 00000000..230a0fe1 --- /dev/null +++ b/wizards/displayplugin/displaywidget.h @@ -0,0 +1,20 @@ +#ifndef %{JS: '%{DisplayWidgetName}'.toUpperCase()}_H +#define %{JS: '%{DisplayWidgetName}'.toUpperCase()}_H + +#include "displaybase.h" + +class %{DisplayWidgetName} : public DisplayBase +{ + Q_OBJECT + +public: + %{DisplayWidgetName}( + QSharedPointer displayHandle, + DisplayInterface *pluginRef, + QWidget *parent = nullptr); + + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *event); +}; + +#endif // %{JS: '%{DisplayWidgetName}'.toUpperCase()}_H \ No newline at end of file diff --git a/wizards/displayplugin/pluginicon.png b/wizards/displayplugin/pluginicon.png new file mode 100644 index 00000000..5fb11ced Binary files /dev/null and b/wizards/displayplugin/pluginicon.png differ diff --git a/wizards/displayplugin/wizard.json b/wizards/displayplugin/wizard.json new file mode 100644 index 00000000..a2208167 --- /dev/null +++ b/wizards/displayplugin/wizard.json @@ -0,0 +1,92 @@ +{ + "version": 1, + "supportedProjectTypes": [ "Qt4ProjectManager.Qt4Project" ], + "id": "HobbitsDisplay", + "category": "Project", + "trDescription": "Creates a hobbits display plugin. General plugin methods and basic plugin structure are provided.", + "trDisplayName": "Display Plugin", + "trDisplayCategory": "Hobbits Plugin", + "icon": "pluginicon.png", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], + "enabled": "%{JS: [ %{Plugins} ].indexOf('QmakeProjectManager') >= 0}", + "options": + [ + { "key": "ProFileName", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, + { "key": "IsTopLevelProject", "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}'}" }, + { "key": "PluginName", "value": "%{JS: '%{ProjectName}'}" }, + { "key": "DisplayWidgetName", "value": "%{JS: '%{ProjectName}' + 'Widget'}" }, + { "key": "ControlWidgetName", "value": "%{JS: '%{ProjectName}' + 'Controls'}" }, + { "key": "PluginCppFileName", "value": "%{JS: Cpp.classToFileName('%{PluginName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" }, + { "key": "PluginHeaderFileName", "value": "%{JS: Cpp.classToFileName('%{PluginName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" }, + { "key": "WidgetCppFileName", "value": "%{JS: Cpp.classToFileName('%{DisplayWidgetName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" }, + { "key": "WidgetHeaderFileName", "value": "%{JS: Cpp.classToFileName('%{DisplayWidgetName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" }, + { "key": "ControlsCppFileName", "value": "%{JS: Cpp.classToFileName('%{ControlWidgetName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" }, + { "key": "ControlsHeaderFileName", "value": "%{JS: Cpp.classToFileName('%{ControlWidgetName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" }, + { "key": "ControlsUiFileName", "value": "%{JS: Cpp.classToFileName('%{ControlWidgetName}', '.ui')}" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": { "trDescription": "Creates a hobbits display plugin. General plugin methods and basic plugin structure are provided." } + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { "projectFilePath": "%{ProFileName}" } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "displayplugintemplate.pro", + "target": "%{ProFileName}", + "openAsProject": true + }, + { + "source": "displayplugintemplate.cpp", + "target": "%{PluginCppFileName}" + }, + { + "source": "displayplugintemplate.h", + "target": "%{PluginHeaderFileName}" + }, + { + "source": "displaywidget.cpp", + "target": "%{WidgetCppFileName}" + }, + { + "source": "displaywidget.h", + "target": "%{WidgetHeaderFileName}" + }, + { + "source": "displaycontrols.cpp", + "target": "%{ControlsCppFileName}" + }, + { + "source": "displaycontrols.h", + "target": "%{ControlsHeaderFileName}" + }, + { + "source": "displaycontrols.ui", + "target": "%{ControlsUiFileName}" + } + + ] + } + ] +} diff --git a/wizards/importexportplugin/importexportplugintemplate.cpp b/wizards/importexportplugin/importexportplugintemplate.cpp new file mode 100644 index 00000000..c15cff6a --- /dev/null +++ b/wizards/importexportplugin/importexportplugintemplate.cpp @@ -0,0 +1,43 @@ +#include "%{HeaderFileName}" + +%{ClassName}::%{ClassName}() +{ + +} + +%{ClassName}::~%{ClassName}() +{ +} + +ImportExportInterface* %{ClassName}::createDefaultImporterExporter() +{ + return new %{ClassName}(); +} + +QString %{ClassName}::getName() +{ + return "%{ClassName}"; +} + +bool %{ClassName}::canExport() +{ + return true; +} + +bool %{ClassName}::canImport() +{ + return true; +} + +QSharedPointer %{ClassName}::importBits(QMap args, QWidget *parent) +{ + QSharedPointer nullContainer; + return nullContainer; +} + +void %{ClassName}::exportBits( + QSharedPointer container, + QMap args, + QWidget *parent) +{ +} diff --git a/wizards/importexportplugin/importexportplugintemplate.h b/wizards/importexportplugin/importexportplugintemplate.h new file mode 100644 index 00000000..5dfea0e7 --- /dev/null +++ b/wizards/importexportplugin/importexportplugintemplate.h @@ -0,0 +1,32 @@ +#ifndef %{JS: '%{ClassName}'.toUpperCase()}_H +#define %{JS: '%{ClassName}'.toUpperCase()}_H + +#include "importexportinterface.h" + +class %{ClassName} : public QObject, ImportExportInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.ImportExportInterface.%{ClassName}") + Q_INTERFACES(ImportExportInterface) + +public: + %{ClassName}(); + ~%{ClassName}(); + + ImportExportInterface* createDefaultImporterExporter() override; + + QString getName() override; + + bool canExport() override; + bool canImport() override; + + QSharedPointer importBits(QMap args, QWidget *parent) override; + void exportBits( + QSharedPointer container, + QMap args, + QWidget *parent) override; + +private: +}; + +#endif // %{JS: '%{ClassName}'.toUpperCase()}_H diff --git a/wizards/importexportplugin/importexportplugintemplate.pro b/wizards/importexportplugin/importexportplugintemplate.pro new file mode 100644 index 00000000..8a1339fb --- /dev/null +++ b/wizards/importexportplugin/importexportplugintemplate.pro @@ -0,0 +1,47 @@ +#------------------------------------------------- +# +# Project created by QtCreator %{JS: (new Date().toISOString())} +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = %{ClassName} +TEMPLATE = lib + +DEFINES += %{JS: '%{ClassName}'.toUpperCase()}_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + %{JS: '%{CppFileName}'.toLowerCase()} + +HEADERS += \ + %{JS: '%{HeaderFileName}'.toLowerCase()} + +DISTFILES += \ + %{JS: '%{JSONFileName}'.toLowerCase()} + + +LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/analyzers + INSTALLS += target +} diff --git a/wizards/importexportplugin/pluginicon.png b/wizards/importexportplugin/pluginicon.png new file mode 100644 index 00000000..5fb11ced Binary files /dev/null and b/wizards/importexportplugin/pluginicon.png differ diff --git a/wizards/importexportplugin/wizard.json b/wizards/importexportplugin/wizard.json new file mode 100644 index 00000000..0089b576 --- /dev/null +++ b/wizards/importexportplugin/wizard.json @@ -0,0 +1,65 @@ +{ + "version": 1, + "supportedProjectTypes": [ "Qt4ProjectManager.Qt4Project" ], + "id": "HobbitsImportExport", + "category": "Project", + "trDescription": "Creates a hobbits import/export plugin. General plugin methods and basic plugin structure are provided.", + "trDisplayName": "Import/Export Plugin", + "trDisplayCategory": "Hobbits Plugin", + "icon": "pluginicon.png", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], + "enabled": "%{JS: [ %{Plugins} ].indexOf('QmakeProjectManager') >= 0}", + "options": + [ + { "key": "ProFileName", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, + { "key": "IsTopLevelProject", "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}'}" }, + { "key": "ClassName", "value": "%{JS: '%{ProjectName}'}" }, + { "key": "CppFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" }, + { "key": "HeaderFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": { "trDescription": "Creates a hobbits import/export plugin. General plugin methods and basic plugin structure are provided." } + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { "projectFilePath": "%{ProFileName}" } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "importexportplugintemplate.pro", + "target": "%{ProFileName}", + "openAsProject": true + }, + { + "source": "importexportplugintemplate.cpp", + "target": "%{CppFileName}" + }, + { + "source": "importexportplugintemplate.h", + "target": "%{HeaderFileName}" + } + + ] + } + ] +} diff --git a/wizards/operatorplugin/operatorplugintemplate.cpp b/wizards/operatorplugin/operatorplugintemplate.cpp new file mode 100644 index 00000000..53f727d4 --- /dev/null +++ b/wizards/operatorplugin/operatorplugintemplate.cpp @@ -0,0 +1,82 @@ +#include "%{JS: '%{HeaderFileName}'.toLowerCase()}" +#include "ui_%{JS: '%{HeaderFileName}'.toLowerCase()}" +#include + +%{ClassName}::%{ClassName}() : + ui(new Ui::%{ClassName}()) +{ + +} + +OperatorInterface* %{ClassName}::createDefaultOperator() +{ + return new %{ClassName}(); +} + +//Return name of operator +QString %{ClassName}::getName() +{ + return "%{ClassName}"; +} + +void %{ClassName}::provideCallback(QSharedPointer pluginCallback) +{ + // the plugin callback allows the self-triggering of operateOnContainers + m_pluginCallback = pluginCallback; +} + +void %{ClassName}::applyToWidget(QWidget *widget) +{ + ui->setupUi(widget); +} + +bool %{ClassName}::canRecallPluginState(const QJsonObject &pluginState) +{ + //if pluginState does not have required fields, return false + if(pluginState.isEmpty()==true){ + return false; + } + + return true; +} + +bool %{ClassName}::setPluginStateInUi(const QJsonObject &pluginState) +{ + if (!canRecallPluginState(pluginState)) { + return false; + } + + // Set the UI fields based on the plugin state + + return true; +} + +QJsonObject %{ClassName}::getStateFromUi() +{ + QJsonObject pluginState; + + //Pull data from the input fields and input them into pluginState + + return pluginState; +} + +int %{ClassName}::getMinInputContainers() +{ + return 1; +} + +int %{ClassName}::getMaxInputContainers() +{ + return 1; +} + +QSharedPointer %{ClassName}::operateOnContainers( + QList > inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) +{ + QSharedPointer result(new OperatorResult()); + //Perform bit operations here + + return result; +} diff --git a/wizards/operatorplugin/operatorplugintemplate.h b/wizards/operatorplugin/operatorplugintemplate.h new file mode 100644 index 00000000..82c232ef --- /dev/null +++ b/wizards/operatorplugin/operatorplugintemplate.h @@ -0,0 +1,44 @@ +#ifndef %{JS: '%{ClassName}'.toUpperCase()}_H +#define %{JS: '%{ClassName}'.toUpperCase()}_H + +#include "operatorinterface.h" + +namespace Ui +{ +class %{ClassName}; + +} + +class %{ClassName} : public QObject, OperatorInterface +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID "hobbits.OperatorInterface.3.%{ClassName}") + Q_INTERFACES(OperatorInterface) + +public: + %{ClassName}(); + + OperatorInterface* createDefaultOperator() override; + QString getName() override; + + void provideCallback(QSharedPointer pluginCallback) override; + void applyToWidget(QWidget *widget) override; + + bool canRecallPluginState(const QJsonObject& pluginState) override; + bool setPluginStateInUi(const QJsonObject &pluginState) override; + QJsonObject getStateFromUi() override; + + int getMinInputContainers() override; + int getMaxInputContainers() override; + + QSharedPointer operateOnContainers( + QList > inputContainers, + const QJsonObject &recallablePluginState, + QSharedPointer progressTracker) override; + +private: + Ui::%{ClassName} *ui; + QSharedPointer m_pluginCallback; +}; + +#endif // %{JS: '%{ClassName}'.toUpperCase()}_H diff --git a/wizards/operatorplugin/operatorplugintemplate.pro b/wizards/operatorplugin/operatorplugintemplate.pro new file mode 100644 index 00000000..d1f727c8 --- /dev/null +++ b/wizards/operatorplugin/operatorplugintemplate.pro @@ -0,0 +1,52 @@ +#------------------------------------------------- +# +# Project created by QtCreator %{JS: (new Date().toISOString())} +# +#------------------------------------------------- + +QT += widgets + +QT -= gui + +TARGET = %{ClassName} +TEMPLATE = lib + +DEFINES += %{JS: '%{ClassName}'.toUpperCase()}_LIBRARY + +CONFIG += c++11 plugin + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which has been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += \ + %{JS: '%{CppFileName}'.toLowerCase()} + +HEADERS += \ + %{JS: '%{HeaderFileName}'.toLowerCase()} + +FORMS += \ + %{JS: '%{UiFileName}'.toLowerCase()} + + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../../hobbits-core/release/ -lhobbits-core +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../../hobbits-core/debug/ -lhobbits-core +else:unix: LIBS += -L$$OUT_PWD/../../../hobbits-core/ -lhobbits-core + +INCLUDEPATH += $$PWD/../../../hobbits-core +DEPENDPATH += $$PWD/../../../hobbits-core + +unix { + target.path = target.path = $$(HOME)/.local/share/hobbits/plugins/operators + INSTALLS += target +} + +DISTFILES += \ + %{JS: '%{JSONFileName}'.toLowerCase()} diff --git a/wizards/operatorplugin/operatorplugintemplate.ui b/wizards/operatorplugin/operatorplugintemplate.ui new file mode 100644 index 00000000..9dfd4fbd --- /dev/null +++ b/wizards/operatorplugin/operatorplugintemplate.ui @@ -0,0 +1,23 @@ + + + %{ClassName} + + + + 0 + 0 + 604 + 147 + + + + Form + + + + + + + + + diff --git a/wizards/operatorplugin/pluginicon.png b/wizards/operatorplugin/pluginicon.png new file mode 100644 index 00000000..5fb11ced Binary files /dev/null and b/wizards/operatorplugin/pluginicon.png differ diff --git a/wizards/operatorplugin/wizard.json b/wizards/operatorplugin/wizard.json new file mode 100644 index 00000000..6b3f3625 --- /dev/null +++ b/wizards/operatorplugin/wizard.json @@ -0,0 +1,70 @@ +{ + "version": 1, + "supportedProjectTypes": [ "Qt4ProjectManager.Qt4Project" ], + "id": "HobbitsOp", + "category": "Project", + "trDescription": "Creates a hobbits operator plugin. General plugin methods and basic plugin structure are provided.", + "trDisplayName": "Operator Plugin", + "trDisplayCategory": "Hobbits Plugin", + "icon": "pluginicon.png", + "featuresRequired": [ "QtSupport.Wizards.FeatureQt" ], + "enabled": "%{JS: [ %{Plugins} ].indexOf('QmakeProjectManager') >= 0}", + "options": + [ + { "key": "ProFileName", "value": "%{JS: Util.fileName('%{ProjectDirectory}/%{ProjectName}', 'pro')}" }, + { "key": "IsTopLevelProject", "value": "%{JS: !'%{Exists:ProjectExplorer.Profile.Ids}'}" }, + { "key": "ClassName", "value": "%{JS: '%{ProjectName}'}" }, + { "key": "CppFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '%{JS: Util.preferredSuffix('text/x-c++src')}')}" }, + { "key": "HeaderFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '%{JS: Util.preferredSuffix('text/x-c++hdr')}')}" }, + { "key": "UiFileName", "value": "%{JS: Cpp.classToFileName('%{ClassName}', '.ui')}" } + ], + + "pages": + [ + { + "trDisplayName": "Project Location", + "trShortTitle": "Location", + "typeId": "Project", + "data": { "trDescription": "Creates a hobbits operator plugin. General plugin methods and basic plugin structure are provided." } + }, + { + "trDisplayName": "Kit Selection", + "trShortTitle": "Kits", + "typeId": "Kits", + "enabled": "%{IsTopLevelProject}", + "data": { "projectFilePath": "%{ProFileName}" } + }, + { + "trDisplayName": "Project Management", + "trShortTitle": "Summary", + "typeId": "Summary" + } + ], + "generators": + [ + { + "typeId": "File", + "data": + [ + { + "source": "operatorplugintemplate.pro", + "target": "%{ProFileName}", + "openAsProject": true + }, + { + "source": "operatorplugintemplate.cpp", + "target": "%{CppFileName}" + }, + { + "source": "operatorplugintemplate.h", + "target": "%{HeaderFileName}" + }, + { + "source": "operatorplugintemplate.ui", + "target": "%{UiFileName}" + } + + ] + } + ] +} diff --git a/wizards/wizard_installer.sh b/wizards/wizard_installer.sh new file mode 100755 index 00000000..805b4490 --- /dev/null +++ b/wizards/wizard_installer.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# make the wizard directory if it doesn't exist +mkdir -p $HOME/.config/QtProject/qtcreator/templates/wizards + +# remove any old wizards +rm -rf $HOME/.config/QtProject/qtcreator/templates/wizards/*plugin + +# copy new wizards to wizard directory +cp -r operatorplugin $HOME/.config/QtProject/qtcreator/templates/wizards +cp -r displayplugin $HOME/.config/QtProject/qtcreator/templates/wizards +cp -r analyzerplugin $HOME/.config/QtProject/qtcreator/templates/wizards +cp -r importexportplugin $HOME/.config/QtProject/qtcreator/templates/wizards