├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── example └── main.cpp ├── python ├── README.md ├── input_form_dialog.py └── setup.py └── src ├── InputFormDialog.cpp ├── InputFormDialog.h ├── SetColorButton.cpp └── SetColorButton.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt 14 | 15 | *.moc 16 | moc_*.cpp 17 | qrc_*.cpp 18 | ui_*.h 19 | 20 | # CLion 21 | 22 | build/ 23 | .idea/ 24 | 25 | # Python 26 | *.pyc 27 | *.egg-info -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Serguei Kalentchouk et al. All rights reserved. 2 | # Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | cmake_minimum_required(VERSION 3.9) 4 | 5 | # Define project 6 | project(input-form-dialog) 7 | 8 | # Set properties 9 | set(CMAKE_AUTOMOC ON) 10 | set(CMAKE_CXX_STANDARD 14) 11 | 12 | # To find Qt, add the installation prefix of Qt5 to CMAKE_PREFIX_PATH or set Qt5_DIR 13 | # to a directory containing one of the above files. 14 | find_package(Qt5 COMPONENTS Core Widgets REQUIRED) 15 | 16 | # Create library target 17 | add_library(InputFormDialog SHARED src/InputFormDialog.h src/InputFormDialog.cpp src/SetColorButton.h src/SetColorButton.cpp) 18 | target_link_libraries(InputFormDialog Qt5::Core Qt5::Widgets) 19 | 20 | # Uncomment to create the example app target 21 | #add_executable(InputFormDialogExample example/main.cpp) 22 | #target_link_libraries(InputFormDialogExample InputFormDialog Qt5::Core Qt5::Widgets) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Serguei Kalentchouk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## InputFormDialog 2 | InputFormDialog is a simple library that provides a multi-type input dialog for Qt5. 3 | 4 | The purpose of the InputFormDialog is to compliment the static get methods of the [QInputDialog](http://doc.qt.io/qt-5/qinputdialog.html) such as `QInputDialog::getText` or `QInputDialog::getInt`. 5 | 6 | ### Details 7 | Using InputFormDialog is simple. First create a `FormData` object that will define the form entries and their default values. 8 | 9 | The `FormData` behaves similar to an `std::map`, new entries are added to it by using the subscript operator `FormData::operator[]`. The provided key will be used by the dialog as the label and the edit widget will be chosen based on the value type. 10 | 11 | Currently the following input queries are supported: 12 | - boolean 13 | - color 14 | - numeric 15 | - text 16 | - options list 17 | - 2d/3d vector 18 | 19 | Second, call the `getInput` function by passing the reference to the data object. If the user accepts the dialog then the data object will hold the new values chosen by the user. 20 | 21 | To retrieve the new values from `FormData` you should use the templated `FormData::at` method. This method will try and cast the widget value to the desired type. Note that in a debug build if the provided key does not exist or the cast is impossible then an assertion will be raised. 22 | 23 | Additionally, you might want to create `FormOptions` object and specify some of the options that control the properties of widgets used by the dialog. 24 | 25 | Currently the following options are available: 26 | - set numeric limits, step and precision 27 | - set whether combo box or radio button group is used 28 | - set whether the combo box / radio button group returns current item text or index 29 | 30 | ### Example 31 | ```cpp 32 | #include "InputFormDialog.h" 33 | 34 | // Define form inputs 35 | InputFormDialog::FormData data; 36 | data["Bool"] = true; 37 | data["Color"] = QColor("red"); 38 | data["Int"] = 1; 39 | data["String"] = "Test"; 40 | data["ComboBox"] = QStringList() << "One" << "Two"; 41 | data["Vector2"] = QVector2D(10.0, 5.0); 42 | 43 | // Define form options 44 | InputFormDialog::FormOptions options; 45 | options.listDisplaysAsRadios = true; 46 | options.listReturnsIndex = true; 47 | 48 | // Ask user for input and retrieve data 49 | if (InputFormDialog::getInput("Example", data, options)) 50 | { 51 | qDebug() << data.at("Bool"); 52 | qDebug() << data.at("Color"); 53 | qDebug() << data.at("Int"); 54 | qDebug() << data.at("String"); 55 | qDebug() << data.at("ComboBox"); 56 | qDebug() << data.at("Vector2"); 57 | } 58 | ``` 59 | 60 | -------------------------------------------------------------------------------- /example/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Serguei Kalentchouk. All rights reserved. 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "../src/InputFormDialog.h" 12 | 13 | void ask() 14 | { 15 | // Define form inputs 16 | InputFormDialog::FormData data; 17 | data["Bool"] = true; 18 | data["Color"] = QColor("red"); 19 | data["Int"] = 1; 20 | data["String"] = "Test"; 21 | data["ComboBox"] = QStringList() << "One" << "Two"; 22 | data["Vector2"] = QVector2D(10.0, 5.0); 23 | 24 | // Define form options 25 | InputFormDialog::FormOptions options; 26 | options.listDisplaysAsRadios = true; 27 | options.listReturnsIndex = true; 28 | 29 | // Ask user for input and retrieve data 30 | if (InputFormDialog::getInput("Example", data, options)) 31 | { 32 | qDebug() << data.at("Bool"); 33 | qDebug() << data.at("Color"); 34 | qDebug() << data.at("Int"); 35 | qDebug() << data.at("String"); 36 | qDebug() << data.at("ComboBox"); 37 | qDebug() << data.at("Vector2"); 38 | } 39 | } 40 | 41 | int main(int argc, char* argv[]) 42 | { 43 | QApplication app(argc, argv); 44 | 45 | auto button = new QPushButton("Ask Me"); 46 | QObject::connect(button, &QPushButton::clicked, ask); 47 | button->show(); 48 | 49 | return app.exec(); 50 | } 51 | -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | ## InputFormDialog 2 | InputFormDialog is a simple module that provides a multi-type input dialog for Qt5. 3 | 4 | The purpose of the InputFormDialog is to compliment the static get methods of the [QInputDialog](https://doc-snapshots.qt.io/qtforpython/PySide2/QtWidgets/QInputDialog.html) such as `QInputDialog.getText` or `QInputDialog.getInt`. 5 | 6 | ### Details 7 | Using InputFormDialog is simple. First create a dictionary that will define the form entries and their default values. If the order of input widgets is important, use `OrderedDict`. 8 | 9 | Currently the following input queries are supported: 10 | - boolean 11 | - color 12 | - numeric 13 | - text 14 | - options list 15 | - 2d/3d vector 16 | 17 | Second, call the `get_input()` function and pass the data object as an argument. If the user accepts the dialog then the data object will hold the new values chosen by the user. 18 | 19 | Additionally, you might want to create `FormOptions` object and specify some of the options that control the properties of widgets used by the dialog. 20 | 21 | Currently the following options are available: 22 | - set numeric limits, step and decimal precision 23 | - set whether combo box or radio button group is used 24 | - set whether the combo box / radio button group returns current item text or index 25 | 26 | ### Example 27 | ```python 28 | # QApplication must be running prior to calling get_input 29 | from collections import OrderedDict 30 | from Qt import QtGui, QtWidgets 31 | 32 | from input_form_dialog import FormOptions, get_input 33 | 34 | # Define form inputs 35 | data = OrderedDict() 36 | data["Bool"] = True 37 | data['Color'] = QtGui.QColor('red') 38 | data['Int'] = 1 39 | data['String'] = 'Test' 40 | data['ComboBox'] = ['One', 'Two'] 41 | data['Vector2'] = QtGui.QVector2D(10.0, 5.0) 42 | 43 | # Define form options 44 | options = FormOptions() 45 | options.list_displays_as_radios = True 46 | options.list_returns_index = True 47 | 48 | # Ask user for input and retrieve data 49 | if get_input('Example', data, options): 50 | print(data['Bool']) 51 | print(data['Color']) 52 | print(data['Int']) 53 | print(data['String']) 54 | print(data['ComboBox']) 55 | print(data['Vector2']) 56 | ``` 57 | 58 | -------------------------------------------------------------------------------- /python/input_form_dialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Serguei Kalentchouk. All rights reserved. 2 | # Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | from collections import OrderedDict 4 | from Qt import QtCore, QtGui, QtWidgets 5 | 6 | 7 | class FormOptions(object): 8 | """Form Options 9 | 10 | Attributes: 11 | list_returns_index (bool): If True, combo box query returns current index, 12 | otherwise returns current text 13 | list_displays_as_radios (bool): If True, combo box query is presented as radio button group 14 | numeric_min (int): Minimum value for numeric queries 15 | numeric_max (int): Maximum value for numeric queries 16 | numeric_precision (int): Decimal percision for numeric queries 17 | """ 18 | def __init__(self): 19 | self.list_returns_index = False 20 | self.list_displays_as_radios = False 21 | 22 | self.numeric_min = -100 23 | self.numeric_max = 100 24 | self.numeric_precision = 2 25 | 26 | 27 | class ColorSwatchButton(QtWidgets.QPushButton): 28 | """Collor Swatch Button""" 29 | def __init__(self): 30 | super(ColorSwatchButton, self).__init__() 31 | 32 | self.setAutoFillBackground(True) 33 | self.setFlat(True) 34 | 35 | self._color = QtGui.QColor() 36 | 37 | self.clicked.connect(self.change_color) 38 | 39 | def color(self): 40 | """Get current color 41 | 42 | Returns: 43 | QtGui.QColor: Return current color 44 | """ 45 | return self._color 46 | 47 | def set_color(self, color): 48 | """ 49 | Set current color 50 | 51 | Args: 52 | color (QtGui.QColor): New color value 53 | """ 54 | palette = self.palette() 55 | palette.setColor(QtGui.QPalette.Button, color) 56 | 57 | self.setPalette(palette) 58 | self.update() 59 | 60 | self._color = color 61 | 62 | def change_color(self): 63 | """Change color callback""" 64 | new_color = QtWidgets.QColorDialog.getColor(self._color, self.parentWidget()) 65 | 66 | if new_color != self._color: 67 | self.set_color(new_color) 68 | 69 | 70 | def get_input(title, data, options=FormOptions()): 71 | """ 72 | Get input from user 73 | 74 | If the user accepts the dialog then the current values will be 75 | written back to the data object. 76 | Note that for list queries this will change the value type! 77 | 78 | Args: 79 | title (str): Dialog title 80 | data (dict | OrderedDict): Input data to build tha dialog for 81 | options(FormOptions): Options to control dialog behavior 82 | 83 | Returns: 84 | bool: Returns True if dialog is accepted, False otherwise 85 | """ 86 | dialog = QtWidgets.QDialog() 87 | dialog.setWindowTitle(title) 88 | 89 | layout = QtWidgets.QGridLayout(dialog) 90 | layout.setMargin(2) 91 | layout.setSpacing(4) 92 | 93 | widgets = {} 94 | 95 | for row, key in enumerate(data): 96 | label = QtWidgets.QLabel(key + ':') 97 | layout.addWidget(label) 98 | 99 | value = data[key] 100 | if isinstance(value, bool): 101 | widget = QtWidgets.QCheckBox() 102 | widget.setChecked(value) 103 | layout.addWidget(widget, row, 1) 104 | widgets[key] = widget 105 | elif isinstance(value, QtGui.QColor): 106 | widget = ColorSwatchButton() 107 | widget.set_color(value) 108 | layout.addWidget(widget, row, 1) 109 | widgets[key] = widget 110 | elif isinstance(value, float): 111 | widget = QtWidgets.QDoubleSpinBox() 112 | widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) 113 | widget.setMaximum(options.numeric_max) 114 | widget.setMinimum(options.numeric_min) 115 | widget.setDecimals(options.numeric_precision) 116 | widget.setValue(value) 117 | layout.addWidget(widget, row, 1) 118 | widgets[key] = widget 119 | elif isinstance(value, int): 120 | widget = QtWidgets.QSpinBox() 121 | widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) 122 | widget.setMaximum(options.numeric_max) 123 | widget.setMinimum(options.numeric_min) 124 | widget.setValue(value) 125 | layout.addWidget(widget, row, 1) 126 | widgets[key] = widget 127 | elif isinstance(value, basestring): 128 | widget = QtWidgets.QLineEdit(value) 129 | layout.addWidget(widget, row, 1) 130 | widgets[key] = widget 131 | elif isinstance(value, list): 132 | if options.list_displays_as_radios: 133 | widget = QtWidgets.QWidget() 134 | widget_layout = QtWidgets.QHBoxLayout(widget) 135 | widget_layout.setMargin(2) 136 | widget_layout.setSpacing(2) 137 | 138 | is_checked = False 139 | for item in value: 140 | button = QtWidgets.QRadioButton(item) 141 | widget_layout.addWidget(button) 142 | 143 | if not is_checked: 144 | button.setChecked(True) 145 | is_checked = True 146 | 147 | layout.addWidget(widget, row, 1) 148 | widgets[key] = widget 149 | else: 150 | widget = QtWidgets.QComboBox() 151 | widget.addItems(value) 152 | layout.addWidget(widget, row, 1) 153 | widgets[key] = widget 154 | elif isinstance(value, QtGui.QVector2D) or isinstance(value, QtGui.QVector3D): 155 | widget = QtWidgets.QWidget() 156 | widget_layout = QtWidgets.QHBoxLayout(widget) 157 | widget_layout.setMargin(2) 158 | widget_layout.setSpacing(2) 159 | 160 | x_widget = QtWidgets.QDoubleSpinBox() 161 | x_widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) 162 | x_widget.setMaximum(options.numeric_max) 163 | x_widget.setMinimum(options.numeric_min) 164 | x_widget.setDecimals(options.numeric_precision) 165 | x_widget.setValue(value.x()) 166 | 167 | y_widget = QtWidgets.QDoubleSpinBox() 168 | y_widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) 169 | y_widget.setMaximum(options.numeric_max) 170 | y_widget.setMinimum(options.numeric_min) 171 | y_widget.setDecimals(options.numeric_precision) 172 | y_widget.setValue(value.y()) 173 | 174 | widget_layout.addWidget(x_widget) 175 | widget_layout.addWidget(y_widget) 176 | 177 | if isinstance(value, QtGui.QVector3D): 178 | z_widget = QtWidgets.QDoubleSpinBox() 179 | z_widget.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons) 180 | z_widget.setMaximum(options.numeric_max) 181 | z_widget.setMinimum(options.numeric_min) 182 | z_widget.setDecimals(options.numeric_precision) 183 | z_widget.setValue(value.y()) 184 | widget_layout.addWidget(z_widget) 185 | 186 | layout.addWidget(widget, row, 1) 187 | widgets[key] = widget 188 | else: 189 | raise ValueError('Data of type "{}" is not supported!'.format(value_type)) 190 | 191 | button_layout = QtWidgets.QHBoxLayout() 192 | button_layout.setMargin(2) 193 | button_layout.setSpacing(2) 194 | layout.addLayout(button_layout, row + 1, 0, 1, 2) 195 | 196 | ok_button = QtWidgets.QPushButton('Ok') 197 | ok_button.setDefault(True) 198 | ok_button.clicked.connect(dialog.accept) 199 | button_layout.addWidget(ok_button) 200 | 201 | cancel_button = QtWidgets.QPushButton('Cancel') 202 | cancel_button.clicked.connect(dialog.reject) 203 | button_layout.addWidget(cancel_button) 204 | 205 | if dialog.exec_() != QtWidgets.QDialog.Accepted: 206 | dialog.deleteLater() 207 | return False 208 | 209 | for key in data: 210 | value = data[key] 211 | if isinstance(value, bool): 212 | data[key] = widgets[key].isChecked() 213 | elif isinstance(value, QtGui.QColor): 214 | data[key] = widgets[key].color() 215 | elif isinstance(value, float) or isinstance(value, int): 216 | data[key] = widgets[key].value() 217 | elif isinstance(value, basestring): 218 | data[key] = widgets[key].text() 219 | elif isinstance(value, list): 220 | if options.list_displays_as_radios: 221 | children = widgets[key].children() 222 | for index, child in enumerate(children): 223 | if not isinstance(child, QtWidgets.QRadioButton): 224 | continue 225 | 226 | if child.isChecked(): 227 | if options.list_returns_index: 228 | data[key] = index 229 | else: 230 | data[key] = child.text() 231 | else: 232 | if options.list_returns_index: 233 | data[key] = widgets[key].currentIndex() 234 | else: 235 | data[key] = widgets[key].currentText() 236 | elif isinstance(value, QtGui.QVector2D): 237 | children = widgets[key].children() 238 | data[key] = QtGui.QVector2D(children[1].value(), children[2].value()) 239 | elif isinstance(value, QtGui.QVector3D): 240 | children = widgets[key].children() 241 | data[key] = QtGui.QVector3D(children[1].value(), 242 | children[2].value(), 243 | children[3].value()) 244 | 245 | dialog.deleteLater() 246 | return True 247 | -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | from setuptools import setup 3 | 4 | with open('README.md', 'r') as fh: 5 | long_description = fh.read() 6 | 7 | setup( 8 | author='Serguei Kalentchouk', 9 | description='InputFormDialog provides a simple multi-type input dialog for Qt5', 10 | install_requires=['Qt.py'], 11 | license='MIT', 12 | long_description=long_description, 13 | long_description_content_type='text/markdown', 14 | py_modules=['input_form_dialog'], 15 | name='input_form_dialog', 16 | url='https://github.com/serguei-k/input-form-dialog/tree/master/python', 17 | version='1.0.1', 18 | zip_safe=False 19 | ) 20 | -------------------------------------------------------------------------------- /src/InputFormDialog.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2018 Serguei Kalentchouk. All rights reserved. 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | #include "InputFormDialog.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "SetColorButton.h" 22 | 23 | namespace InputFormDialog 24 | { 25 | 26 | QVariant& 27 | FormData::operator[](const QString& key) 28 | { 29 | for (auto& pair : data_) 30 | { 31 | if (pair.first == key) 32 | { 33 | return pair.second; 34 | } 35 | } 36 | 37 | data_.emplace_back(std::make_pair(key, QVariant())); 38 | return data_.back().second; 39 | } 40 | 41 | std::vector>::iterator 42 | FormData::begin() 43 | { 44 | return data_.begin(); 45 | } 46 | 47 | std::vector>::iterator 48 | FormData::end() 49 | { 50 | return data_.end(); 51 | } 52 | 53 | QVariant 54 | FormData::getValue(const QString &key) const 55 | { 56 | QVariant result; 57 | 58 | for (auto& pair : data_) 59 | { 60 | if (pair.first == key) 61 | { 62 | result = pair.second; 63 | } 64 | } 65 | 66 | return result; 67 | } 68 | 69 | bool 70 | getInput(const QString& title, FormData& data, const FormOptions& options) 71 | { 72 | auto dialog = new QDialog(); 73 | dialog->setWindowTitle(title); 74 | 75 | auto layout = new QGridLayout(dialog); 76 | layout->setMargin(2); 77 | layout->setSpacing(4); 78 | 79 | QMap widgetMap; 80 | 81 | auto row = 0; 82 | for (const auto& pair : data) 83 | { 84 | auto label = new QLabel(pair.first + ":"); 85 | layout->addWidget(label, row, 0); 86 | 87 | switch (pair.second.type()) 88 | { 89 | case QVariant::Bool: 90 | { 91 | auto widget = new QCheckBox(); 92 | widget->setChecked(pair.second.toBool()); 93 | layout->addWidget(widget, row, 1); 94 | widgetMap[pair.first] = widget; 95 | break; 96 | } 97 | case QVariant::Color: 98 | { 99 | auto widget = new SetColorButton(); 100 | widget->setColor(pair.second.value()); 101 | layout->addWidget(widget, row, 1); 102 | widgetMap[pair.first] = widget; 103 | break; 104 | } 105 | case QVariant::Double: 106 | { 107 | auto widget = new QDoubleSpinBox(); 108 | widget->setButtonSymbols(QAbstractSpinBox::NoButtons); 109 | widget->setMaximum(double(options.numericMax)); 110 | widget->setMinimum(double(options.numericMin)); 111 | widget->setDecimals(options.numericPrecision); 112 | widget->setValue(pair.second.toDouble()); 113 | layout->addWidget(widget, row, 1); 114 | widgetMap[pair.first] = widget; 115 | break; 116 | } 117 | case QVariant::Int: 118 | { 119 | auto widget = new QSpinBox(); 120 | widget->setButtonSymbols(QAbstractSpinBox::NoButtons); 121 | widget->setMaximum(options.numericMax); 122 | widget->setMinimum(options.numericMin); 123 | widget->setValue(pair.second.toInt()); 124 | layout->addWidget(widget, row, 1); 125 | widgetMap[pair.first] = widget; 126 | break; 127 | } 128 | case QVariant::String: 129 | { 130 | auto widget = new QLineEdit(pair.second.toString()); 131 | layout->addWidget(widget, row, 1); 132 | widgetMap[pair.first] = widget; 133 | break; 134 | } 135 | case QVariant::StringList: 136 | { 137 | if (options.listDisplaysAsRadios) 138 | { 139 | auto values = pair.second.toStringList(); 140 | 141 | auto widget = new QWidget(); 142 | auto wlayout = new QHBoxLayout(widget); 143 | wlayout->setMargin(2); 144 | wlayout->setSpacing(2); 145 | 146 | auto isChecked = false; 147 | for (const auto& value : values) 148 | { 149 | auto button = new QRadioButton(value); 150 | wlayout->addWidget(button); 151 | 152 | if (!isChecked) 153 | { 154 | button->setChecked(true); 155 | isChecked = true; 156 | } 157 | } 158 | 159 | layout->addWidget(widget, row, 1); 160 | widgetMap[pair.first] = widget; 161 | } 162 | else 163 | { 164 | auto widget = new QComboBox(); 165 | widget->addItems(pair.second.toStringList()); 166 | layout->addWidget(widget, row, 1); 167 | widgetMap[pair.first] = widget; 168 | } 169 | break; 170 | } 171 | case QVariant::Vector2D: 172 | { 173 | auto value = pair.second.value(); 174 | 175 | auto widget = new QWidget(); 176 | auto wlayout = new QHBoxLayout(widget); 177 | wlayout->setMargin(2); 178 | wlayout->setSpacing(2); 179 | 180 | auto xwidget = new QDoubleSpinBox(); 181 | xwidget->setButtonSymbols(QAbstractSpinBox::NoButtons); 182 | xwidget->setMaximum(double(options.numericMax)); 183 | xwidget->setMinimum(double(options.numericMin)); 184 | xwidget->setDecimals(options.numericPrecision); 185 | xwidget->setValue(value.x()); 186 | wlayout->addWidget(xwidget); 187 | 188 | auto ywidget = new QDoubleSpinBox(); 189 | ywidget->setButtonSymbols(QAbstractSpinBox::NoButtons); 190 | ywidget->setMaximum(double(options.numericMax)); 191 | ywidget->setMinimum(double(options.numericMin)); 192 | ywidget->setDecimals(options.numericPrecision); 193 | ywidget->setValue(value.y()); 194 | wlayout->addWidget(ywidget); 195 | 196 | layout->addWidget(widget, row, 1); 197 | widgetMap[pair.first] = widget; 198 | break; 199 | } 200 | case QVariant::Vector3D: 201 | { 202 | auto value = pair.second.value(); 203 | 204 | auto widget = new QWidget(); 205 | auto wlayout = new QHBoxLayout(widget); 206 | wlayout->setMargin(2); 207 | wlayout->setSpacing(2); 208 | 209 | auto xwidget = new QDoubleSpinBox(); 210 | xwidget->setButtonSymbols(QAbstractSpinBox::NoButtons); 211 | xwidget->setMaximum(double(options.numericMax)); 212 | xwidget->setMinimum(double(options.numericMin)); 213 | xwidget->setDecimals(options.numericPrecision); 214 | xwidget->setValue(value.x()); 215 | wlayout->addWidget(xwidget); 216 | 217 | auto ywidget = new QDoubleSpinBox(); 218 | ywidget->setButtonSymbols(QAbstractSpinBox::NoButtons); 219 | ywidget->setMaximum(double(options.numericMax)); 220 | ywidget->setMinimum(double(options.numericMin)); 221 | ywidget->setDecimals(options.numericPrecision); 222 | ywidget->setValue(value.y()); 223 | wlayout->addWidget(ywidget); 224 | 225 | auto zwidget = new QDoubleSpinBox(); 226 | zwidget->setButtonSymbols(QAbstractSpinBox::NoButtons); 227 | zwidget->setMaximum(double(options.numericMax)); 228 | zwidget->setMinimum(double(options.numericMin)); 229 | zwidget->setDecimals(options.numericPrecision); 230 | zwidget->setValue(value.z()); 231 | wlayout->addWidget(zwidget); 232 | 233 | layout->addWidget(widget, row, 1); 234 | widgetMap[pair.first] = widget; 235 | break; 236 | } 237 | default: 238 | break; 239 | } 240 | 241 | row++; 242 | } 243 | 244 | auto btnLayout = new QHBoxLayout(); 245 | btnLayout->setMargin(2); 246 | btnLayout->setSpacing(4); 247 | layout->addLayout(btnLayout, row, 0, 1, 2); 248 | 249 | auto ok = new QPushButton("Ok", dialog); 250 | ok->setDefault(true); 251 | QObject::connect(ok, &QPushButton::clicked, dialog, &QDialog::accept); 252 | btnLayout->addWidget(ok); 253 | 254 | auto cancel = new QPushButton("Cancel", dialog); 255 | QObject::connect(cancel, &QPushButton::clicked, dialog, &QDialog::reject); 256 | btnLayout->addWidget(cancel); 257 | 258 | if (dialog->exec() != QDialog::Accepted) 259 | { 260 | dialog->deleteLater(); 261 | return false; 262 | } 263 | 264 | for (auto& pair : data) 265 | { 266 | switch (pair.second.type()) 267 | { 268 | case QVariant::Bool: 269 | { 270 | const auto widget = qobject_cast(widgetMap[pair.first]); 271 | pair.second = widget->isChecked(); 272 | break; 273 | } 274 | case QVariant::Color: 275 | { 276 | const auto widget = qobject_cast(widgetMap[pair.first]); 277 | pair.second = widget->color(); 278 | break; 279 | } 280 | case QVariant::Double: 281 | { 282 | const auto widget = qobject_cast(widgetMap[pair.first]); 283 | pair.second = widget->value(); 284 | break; 285 | } 286 | case QVariant::Int: 287 | { 288 | const auto widget = qobject_cast(widgetMap[pair.first]); 289 | pair.second = widget->value(); 290 | break; 291 | } 292 | case QVariant::String: 293 | { 294 | const auto widget = qobject_cast(widgetMap[pair.first]); 295 | pair.second = widget->text(); 296 | break; 297 | } 298 | case QVariant::StringList: 299 | { 300 | if (options.listDisplaysAsRadios) 301 | { 302 | const auto children = widgetMap[pair.first]->children(); 303 | 304 | auto index = 0; 305 | for (const auto& child : children) 306 | { 307 | const auto widget = qobject_cast(child); 308 | if (!widget) continue; 309 | 310 | if (widget->isChecked()) 311 | { 312 | if (options.listReturnsIndex) 313 | { 314 | pair.second = index; 315 | } 316 | else 317 | { 318 | pair.second = widget->text(); 319 | } 320 | } 321 | 322 | index++; 323 | } 324 | } 325 | else 326 | { 327 | const auto widget = qobject_cast(widgetMap[pair.first]); 328 | if (options.listReturnsIndex) 329 | { 330 | pair.second = widget->currentIndex(); 331 | } 332 | else 333 | { 334 | pair.second = widget->currentText(); 335 | } 336 | } 337 | break; 338 | } 339 | case QVariant::Vector2D: 340 | { 341 | const auto children = widgetMap[pair.first]->children(); 342 | const auto xwidget = qobject_cast(children.at(1)); 343 | const auto ywidget = qobject_cast(children.at(2)); 344 | pair.second = QVector2D(xwidget->value(), ywidget->value()); 345 | break; 346 | } 347 | case QVariant::Vector3D: 348 | { 349 | const auto children = widgetMap[pair.first]->children(); 350 | const auto xwidget = qobject_cast(children.at(1)); 351 | const auto ywidget = qobject_cast(children.at(2)); 352 | const auto zwidget = qobject_cast(children.at(3)); 353 | pair.second = QVector3D(xwidget->value(), ywidget->value(), zwidget->value()); 354 | break; 355 | } 356 | default: 357 | break; 358 | } 359 | } 360 | 361 | dialog->deleteLater(); 362 | return true; 363 | } 364 | 365 | } 366 | -------------------------------------------------------------------------------- /src/InputFormDialog.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017-2018 Serguei Kalentchouk. All rights reserved. 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace InputFormDialog 15 | { 16 | 17 | class FormData 18 | { 19 | public: 20 | QVariant& operator[](const QString& key); 21 | 22 | std::vector>::iterator begin(); 23 | std::vector>::iterator end(); 24 | 25 | template 26 | T at(const QString& key) const 27 | { 28 | auto value = getValue(key); 29 | 30 | Q_ASSERT_X(!value.isNull(), "FormTemplate::at", "invalid key"); 31 | Q_ASSERT_X(value.canConvert(), "FormTemplate::at", "invalid type"); 32 | 33 | return value.value(); 34 | } 35 | 36 | private: 37 | QVariant getValue(const QString& key) const; 38 | 39 | std::vector> data_; 40 | }; 41 | 42 | struct FormOptions 43 | { 44 | bool listReturnsIndex = false; 45 | bool listDisplaysAsRadios = false; 46 | 47 | int numericMin = -100; 48 | int numericMax = 100; 49 | int numericPrecision = 2; 50 | }; 51 | 52 | bool getInput(const QString& title, FormData& data, const FormOptions& options = FormOptions()); 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/SetColorButton.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Serguei Kalentchouk. All rights reserved. 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | #include "SetColorButton.h" 4 | 5 | #include 6 | 7 | SetColorButton::SetColorButton() : QPushButton() 8 | { 9 | setAutoFillBackground(true); 10 | setFlat(true); 11 | 12 | connect(this, SIGNAL(clicked()), this, SLOT(changeColor())); 13 | } 14 | 15 | void 16 | SetColorButton::changeColor() 17 | { 18 | auto newColor = QColorDialog::getColor(color_, parentWidget()); 19 | if (newColor != color_) 20 | { 21 | setColor(newColor); 22 | } 23 | } 24 | 25 | void 26 | SetColorButton::setColor(const QColor& color) 27 | { 28 | auto pal = palette(); 29 | pal.setColor(QPalette::Button, color); 30 | setPalette(pal); 31 | update(); 32 | 33 | this->color_ = color; 34 | } 35 | 36 | const QColor& 37 | SetColorButton::color() const 38 | { 39 | return color_; 40 | } 41 | -------------------------------------------------------------------------------- /src/SetColorButton.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018 Serguei Kalentchouk. All rights reserved. 2 | // Use of this source code is governed by an MIT license that can be found in the LICENSE file. 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | class SetColorButton : public QPushButton 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | SetColorButton(); 14 | 15 | void setColor(const QColor& color); 16 | const QColor& color() const; 17 | 18 | public slots: 19 | void changeColor(); 20 | 21 | private: 22 | QColor color_; 23 | }; 24 | --------------------------------------------------------------------------------