mirror of
https://github.com/CopterExpress/clever-show.git
synced 2026-05-26 07:07:58 +00:00
Rename Server -> server
This commit is contained in:
7
server/README.md
Normal file
7
server/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Server of clever-show
|
||||
|
||||
Application for creating and running drone shows, adjusting drones, animations and music.
|
||||
|
||||
Documentation is located here:
|
||||
* English
|
||||
* [Russian](../docs/ru/server.md)
|
||||
11
server/chrony.conf
Normal file
11
server/chrony.conf
Normal file
@@ -0,0 +1,11 @@
|
||||
pool 0.ru.pool.ntp.org iburst minpoll 10
|
||||
pool 1.ru.pool.ntp.org iburst minpoll 10
|
||||
pool 2.ru.pool.ntp.org iburst minpoll 10
|
||||
pool 3.ru.pool.ntp.org iburst minpoll 10
|
||||
driftfile /var/lib/chrony/drift
|
||||
local stratum 8
|
||||
allow 192.168.0.0/16
|
||||
makestep 1.0 3
|
||||
smoothtime 50000 0.01
|
||||
rtcsync
|
||||
|
||||
55
server/config/spec/configspec_server.ini
Normal file
55
server/config/spec/configspec_server.ini
Normal file
@@ -0,0 +1,55 @@
|
||||
config_name = string(default='server')
|
||||
config_version = float(default='1.0')
|
||||
|
||||
[SERVER]
|
||||
port = integer(default=25000)
|
||||
buffer_size = integer(default=1024)
|
||||
|
||||
[CLIENT]
|
||||
clever_dir = string(default=/home/pi/catkin_ws/src/clever/clover)
|
||||
|
||||
[TABLE]
|
||||
# True -> clients are removed on disconnection
|
||||
# False -> disconnected clients indicated
|
||||
remove_disconnected = boolean(default=False)
|
||||
[[PRESETS]]
|
||||
current = string(default="DEFAULT")
|
||||
[[[DEFAULT]]]
|
||||
copter_id = preset_param(default=list(True, 100))
|
||||
git_version = preset_param(default=list(True, 75))
|
||||
config_version = preset_param(default=list(True, 105))
|
||||
animation_info = preset_param(default=list(True, 100))
|
||||
battery = preset_param(default=list(True, 100))
|
||||
fcu_status = preset_param(default=list(True, 100))
|
||||
calibration_status = preset_param(default=list(True, 65))
|
||||
mode = preset_param(default=list(True, 100))
|
||||
selfcheck = preset_param(default=list(True, 65))
|
||||
current_position = preset_param(default=list(True, 250))
|
||||
start_position = preset_param(default=list(True, 240))
|
||||
last_task = preset_param(default=list(True, 275))
|
||||
time_delta = preset_param(default=list(True, 70))
|
||||
[[[__many__]]]
|
||||
__many__ = preset_param
|
||||
|
||||
[CHECKS]
|
||||
check_git_version = boolean(default=True)
|
||||
check_current_position = boolean(default=True)
|
||||
# in percents; set 0 to disable this check
|
||||
battery_min = float(default=50.0, min=0, max=100)
|
||||
# in meters; set 0 to disable this check
|
||||
start_pos_delta_max = float(default=1.0, min=0)
|
||||
# in seconds
|
||||
time_delta_max = float(default=1.0, min=0)
|
||||
|
||||
[BROADCAST]
|
||||
send = boolean(default=True)
|
||||
listen = boolean(default=True)
|
||||
port = integer(default=8181)
|
||||
send_ip = string(default=255.255.255.255)
|
||||
# delay for message sending in seconds
|
||||
delay = float(default=5.0, min=0)
|
||||
|
||||
[NTP]
|
||||
use = boolean(default=False)
|
||||
host = string(default=ntp1.stratum2.ru)
|
||||
port = integer(default=123)
|
||||
72
server/config_editor.py
Normal file
72
server/config_editor.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'config_editor.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.14.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_config_dialog(object):
|
||||
def setupUi(self, config_dialog):
|
||||
config_dialog.setObjectName("config_dialog")
|
||||
config_dialog.resize(600, 700)
|
||||
config_dialog.setModal(False)
|
||||
self.gridLayout = QtWidgets.QGridLayout(config_dialog)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.config_view = QtWidgets.QTreeView(config_dialog)
|
||||
self.config_view.setEditTriggers(QtWidgets.QAbstractItemView.DoubleClicked|QtWidgets.QAbstractItemView.EditKeyPressed|QtWidgets.QAbstractItemView.SelectedClicked)
|
||||
self.config_view.setObjectName("config_view")
|
||||
self.config_view.header().setCascadingSectionResizes(False)
|
||||
self.config_view.header().setDefaultSectionSize(250)
|
||||
self.gridLayout.addWidget(self.config_view, 0, 0, 1, 1)
|
||||
self.gridLayout_2 = QtWidgets.QGridLayout()
|
||||
self.gridLayout_2.setObjectName("gridLayout_2")
|
||||
spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
|
||||
self.gridLayout_2.addItem(spacerItem, 0, 2, 1, 1)
|
||||
self.do_restart = QtWidgets.QCheckBox(config_dialog)
|
||||
self.do_restart.setObjectName("do_restart")
|
||||
self.gridLayout_2.addWidget(self.do_restart, 0, 1, 1, 1)
|
||||
self.buttonBox = QtWidgets.QDialogButtonBox(config_dialog)
|
||||
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Save)
|
||||
self.buttonBox.setObjectName("buttonBox")
|
||||
self.gridLayout_2.addWidget(self.buttonBox, 0, 4, 1, 1)
|
||||
self.do_coloring = QtWidgets.QCheckBox(config_dialog)
|
||||
self.do_coloring.setChecked(True)
|
||||
self.do_coloring.setObjectName("do_coloring")
|
||||
self.gridLayout_2.addWidget(self.do_coloring, 0, 0, 1, 1)
|
||||
self.save_as_button = QtWidgets.QPushButton(config_dialog)
|
||||
self.save_as_button.setObjectName("save_as_button")
|
||||
self.gridLayout_2.addWidget(self.save_as_button, 0, 3, 1, 1)
|
||||
self.gridLayout.addLayout(self.gridLayout_2, 2, 0, 1, 1)
|
||||
self.line = QtWidgets.QFrame(config_dialog)
|
||||
self.line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.gridLayout.addWidget(self.line, 1, 0, 1, 1)
|
||||
|
||||
self.retranslateUi(config_dialog)
|
||||
self.buttonBox.accepted.connect(config_dialog.accept)
|
||||
self.buttonBox.rejected.connect(config_dialog.reject)
|
||||
QtCore.QMetaObject.connectSlotsByName(config_dialog)
|
||||
|
||||
def retranslateUi(self, config_dialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
config_dialog.setWindowTitle(_translate("config_dialog", "Config Editor"))
|
||||
self.do_restart.setText(_translate("config_dialog", "Restart"))
|
||||
self.do_restart.setShortcut(_translate("config_dialog", "R"))
|
||||
self.do_coloring.setText(_translate("config_dialog", "Color Indication"))
|
||||
self.save_as_button.setText(_translate("config_dialog", "Save as"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
config_dialog = QtWidgets.QDialog()
|
||||
ui = Ui_config_dialog()
|
||||
ui.setupUi(config_dialog)
|
||||
config_dialog.show()
|
||||
sys.exit(app.exec_())
|
||||
128
server/config_editor.ui
Normal file
128
server/config_editor.ui
Normal file
@@ -0,0 +1,128 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>config_dialog</class>
|
||||
<widget class="QDialog" name="config_dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>600</width>
|
||||
<height>700</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Config Editor</string>
|
||||
</property>
|
||||
<property name="modal">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTreeView" name="config_view">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked</set>
|
||||
</property>
|
||||
<attribute name="headerCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="headerDefaultSectionSize">
|
||||
<number>250</number>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="do_restart">
|
||||
<property name="text">
|
||||
<string>Restart</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>R</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="4">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="do_coloring">
|
||||
<property name="text">
|
||||
<string>Color Indication</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QPushButton" name="save_as_button">
|
||||
<property name="text">
|
||||
<string>Save as</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>config_dialog</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>260</x>
|
||||
<y>237</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>157</x>
|
||||
<y>246</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>config_dialog</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>260</x>
|
||||
<y>239</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>267</x>
|
||||
<y>246</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
||||
954
server/config_editor_models.py
Normal file
954
server/config_editor_models.py
Normal file
@@ -0,0 +1,954 @@
|
||||
import pickle
|
||||
import logging
|
||||
from ast import literal_eval
|
||||
from functools import partial
|
||||
from copy import deepcopy
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QCursor, QKeySequence
|
||||
from PyQt5.QtWidgets import QAbstractItemView, QTreeView, QMenu, QAction, QMessageBox, QInputDialog, QFileDialog, \
|
||||
QShortcut
|
||||
|
||||
import config_editor
|
||||
|
||||
import sys
|
||||
import os, inspect # Add parent dir to PATH to import messaging_lib
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
|
||||
parent_dir = os.path.dirname(current_dir)
|
||||
sys.path.insert(0, parent_dir)
|
||||
|
||||
import config
|
||||
|
||||
|
||||
states_colors = {
|
||||
'normal': Qt.white,
|
||||
'unchanged': Qt.blue,
|
||||
'default': Qt.cyan,
|
||||
'edited': Qt.yellow,
|
||||
'added': Qt.green,
|
||||
'deleted': Qt.red,
|
||||
}
|
||||
|
||||
StateRole = 999
|
||||
TypeRole = 998
|
||||
|
||||
|
||||
def convert_type(data):
|
||||
try:
|
||||
data = literal_eval(data) if data else None
|
||||
except (SyntaxError, ValueError):
|
||||
data = str(data)
|
||||
return data
|
||||
|
||||
|
||||
class ConfigModelItem:
|
||||
def __init__(self, values=(None, None, None, None), item_type='option',
|
||||
state='normal', default=None, parent=None):
|
||||
self.spec_default = default
|
||||
self.itemData = list(values)
|
||||
self.state = state
|
||||
self.type = item_type
|
||||
|
||||
if isinstance(self.data(1), (list, tuple)):
|
||||
self.type = 'list'
|
||||
|
||||
self.default_values = deepcopy(self.itemData)
|
||||
self.default_state = state
|
||||
|
||||
self.childItems = []
|
||||
self.parentItem = parent
|
||||
|
||||
self.setup_type()
|
||||
|
||||
if self.parentItem is not None:
|
||||
self.parentItem.appendChild(self)
|
||||
|
||||
def setup_type(self):
|
||||
if self.type == 'section':
|
||||
self.itemData[1:1] = ('<section>',)
|
||||
self.spec_default = self.data(1)
|
||||
|
||||
elif self.type == 'list':
|
||||
self._setup_list(self.get_list_items())
|
||||
|
||||
def _get_list_spec(self):
|
||||
data = self.data(1)
|
||||
comments = self.data(2)
|
||||
if comments:
|
||||
try:
|
||||
raw_spec = comments.split('\n')[-1].split()[1:]
|
||||
if raw_spec[0] == '__list__': # and len(raw_spec[1:]) == len(data):
|
||||
return raw_spec[1:]
|
||||
except IndexError:
|
||||
pass
|
||||
return list(map(str, range(len(data))))
|
||||
|
||||
def get_list_items(self):
|
||||
spec = self._get_list_spec()
|
||||
values = self.data(1)
|
||||
if isinstance(self.spec_default, list):
|
||||
defaults = self.spec_default
|
||||
else:
|
||||
defaults = (None, )*len(spec)
|
||||
|
||||
self.itemData[1] = '<list: {}>'.format(' '.join(spec))
|
||||
# self.spec_default = self.itemData[1]
|
||||
|
||||
for key, value, default in zip(spec, values, defaults):
|
||||
yield ConfigModelItem((key, value, None, None), item_type='list_item',
|
||||
state=self.state, default=default)
|
||||
|
||||
def _setup_list(self, items): # use only at initialization
|
||||
for child in items:
|
||||
self.appendChild(child)
|
||||
|
||||
@property
|
||||
def is_section(self): # probably deprecated
|
||||
return self.type == 'section'
|
||||
|
||||
def appendChild(self, item):
|
||||
self.childItems.append(item)
|
||||
item.parentItem = self
|
||||
|
||||
def addChildren(self, items, row):
|
||||
if row == -1:
|
||||
row = 0
|
||||
self.childItems[row:row] = items
|
||||
|
||||
for item in items:
|
||||
item.parentItem = self
|
||||
|
||||
def child(self, row):
|
||||
return self.childItems[row]
|
||||
|
||||
def childCount(self):
|
||||
return len(self.childItems)
|
||||
|
||||
def columnCount(self):
|
||||
return len(self.itemData)
|
||||
|
||||
def data(self, column):
|
||||
try:
|
||||
return self.itemData[column]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def set_data(self, data, column):
|
||||
old_data = self.data(column)
|
||||
if old_data is None:
|
||||
data = convert_type(data)
|
||||
|
||||
if data == '<list>':
|
||||
data = []
|
||||
|
||||
try:
|
||||
self.itemData[column] = data
|
||||
except IndexError:
|
||||
return False
|
||||
|
||||
if old_data != data:
|
||||
self.set_state('edited')
|
||||
self.check_state()
|
||||
|
||||
return True
|
||||
|
||||
def check_state(self):
|
||||
if self.spec_default is not None and self.data(1) == self.spec_default \
|
||||
and self.data(0) == self.default_values[0] and self.type != 'section':
|
||||
self.set_state('default')
|
||||
# print('def', self.data(1), self.data(0), self.spec_default)
|
||||
|
||||
child_states = [child.state for child in self.childItems]
|
||||
if any(state in child_states for state in ['edited', 'added', 'deleted']):
|
||||
self.state = 'edited'
|
||||
if len(set(child_states)) == 1: # if all states equal
|
||||
self.set_state(child_states[0], set_children=False)
|
||||
# print(child_states)
|
||||
|
||||
if self.parentItem is not None:
|
||||
self.parentItem.check_state()
|
||||
|
||||
def set_state(self, state, set_children=True):
|
||||
if self.state == 'unchanged' and state == 'default':
|
||||
return
|
||||
|
||||
if self.state == 'added' and state in ('edited', 'unchanged', 'default', 'normal'):
|
||||
return
|
||||
|
||||
self.state = state
|
||||
|
||||
if set_children: # to prevent cycle state set
|
||||
for child in self.childItems:
|
||||
child.set_state(state)
|
||||
|
||||
# if state == 'edited':
|
||||
# self.parentItem.state = state
|
||||
|
||||
def set_type(self, item_type):
|
||||
self.type = item_type
|
||||
|
||||
def parent(self):
|
||||
return self.parentItem
|
||||
|
||||
def row(self):
|
||||
if self.parentItem is not None:
|
||||
return self.parentItem.childItems.index(self)
|
||||
return 0
|
||||
|
||||
def removeChild(self, position):
|
||||
if position < 0 or position > len(self.childItems):
|
||||
return False
|
||||
child = self.childItems.pop(position)
|
||||
child.parentItem = None
|
||||
return True
|
||||
|
||||
def __repr__(self):
|
||||
return str(self.itemData)
|
||||
|
||||
|
||||
def ensure_unique_names(item, include_self=True):
|
||||
name = item.data(0)
|
||||
siblings_names = [child.data(0) for child in item.parent().childItems]
|
||||
if not include_self:
|
||||
siblings_names.remove(name)
|
||||
|
||||
while name in siblings_names:
|
||||
if '_copy' in name:
|
||||
spl = name.split('_copy')
|
||||
num = int(spl[1]) if spl[1] else 0
|
||||
num += 1
|
||||
name = spl[0] + '_copy' + str(num)
|
||||
else:
|
||||
name = name + '_copy'
|
||||
|
||||
item.set_data(name, 0)
|
||||
|
||||
|
||||
class ConfigModel(QtCore.QAbstractItemModel):
|
||||
def __init__(self, parent=None, widget=None,
|
||||
headers=("Option", "Value", 'Comment', 'Inline Comment')):
|
||||
self.rootItem = ConfigModelItem(headers)
|
||||
super(ConfigModel, self).__init__(parent)
|
||||
self.widget = widget
|
||||
|
||||
self.do_color = True
|
||||
|
||||
self.initial_comment = ''
|
||||
self.final_comment = ''
|
||||
|
||||
@QtCore.pyqtSlot(int)
|
||||
def enable_color(self, value):
|
||||
self.do_color = value
|
||||
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex(), (Qt.BackgroundRole, ))
|
||||
|
||||
def headerData(self, section, orientation, role):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.rootItem.data(section)
|
||||
|
||||
def columnCount(self, parent):
|
||||
return self.rootItem.columnCount()
|
||||
|
||||
def rowCount(self, parent):
|
||||
if parent.column() > 0:
|
||||
return 0
|
||||
|
||||
if not parent.isValid():
|
||||
parentItem = self.rootItem
|
||||
else:
|
||||
parentItem = parent.internalPointer()
|
||||
|
||||
return parentItem.childCount()
|
||||
|
||||
def childrenIndexes(self, parent):
|
||||
column = parent.column()
|
||||
parent = self.index(parent.row(), 0, parent.parent())
|
||||
for i in range(self.rowCount(parent)):
|
||||
yield self.index(i, column, parent)
|
||||
|
||||
def index(self, row, column, parent):
|
||||
if not self.hasIndex(row, column, parent):
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
parentItem = self.nodeFromIndex(parent)
|
||||
childItem = parentItem.child(row)
|
||||
|
||||
if childItem:
|
||||
return self.createIndex(row, column, childItem)
|
||||
else:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
def parent(self, index):
|
||||
if not index.isValid():
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
childItem = index.internalPointer()
|
||||
if not isinstance(childItem, ConfigModelItem):
|
||||
# print(childItem, index.column()), # index.row(), index.parent().internalPointer())
|
||||
return QtCore.QModelIndex()
|
||||
parentItem = childItem.parent()
|
||||
|
||||
if parentItem == self.rootItem: #or parentItem is None:
|
||||
return QtCore.QModelIndex()
|
||||
|
||||
return self.createIndex(parentItem.row(), 0, parentItem)
|
||||
|
||||
def modifyCol(self, index, col):
|
||||
return self.index(index.row(), col, index.parent())
|
||||
|
||||
def nodeFromIndex(self, index):
|
||||
if index.isValid():
|
||||
return index.internalPointer()
|
||||
return self.rootItem
|
||||
|
||||
def data(self, index, role):
|
||||
if not index.isValid():
|
||||
return None
|
||||
|
||||
item = index.internalPointer()
|
||||
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole:
|
||||
return item.data(index.column())
|
||||
|
||||
if role == Qt.BackgroundRole and self.do_color:
|
||||
return QtGui.QBrush(states_colors[item.state])
|
||||
|
||||
if role == StateRole:
|
||||
return item.state
|
||||
if role == TypeRole:
|
||||
return item.type
|
||||
|
||||
return None
|
||||
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid():
|
||||
return False
|
||||
|
||||
item = index.internalPointer()
|
||||
|
||||
if role == Qt.EditRole:
|
||||
column = index.column()
|
||||
|
||||
if column == 0 and value != item.data(column):
|
||||
if not self.widget.edit_caution():
|
||||
return False
|
||||
|
||||
item.set_data(value, column)
|
||||
|
||||
if column == 0:
|
||||
ensure_unique_names(item, include_self=False)
|
||||
|
||||
elif column == 1 and isinstance(item.data(1), (list, tuple)) \
|
||||
and item.type not in ('list', 'list_item'):
|
||||
|
||||
item.set_type('list')
|
||||
self.insertItems(0, list(item.get_list_items()), index)
|
||||
self.widget.ui.config_view.expandAll()
|
||||
|
||||
elif role == StateRole:
|
||||
item.set_state(value)
|
||||
|
||||
elif role == TypeRole:
|
||||
# if value != item.type and value == 'list': # when list is created:
|
||||
# pass
|
||||
item.set_type(value)
|
||||
|
||||
self.dataChanged.emit(index, index, (role,))
|
||||
|
||||
return True
|
||||
|
||||
def flags(self, index):
|
||||
if not index.isValid():
|
||||
return QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled # Qt.NoItemFlags
|
||||
item = index.internalPointer()
|
||||
|
||||
flags = Qt.ItemIsEnabled | Qt.ItemIsSelectable
|
||||
|
||||
if index.column() == 0:
|
||||
if item.type != 'list_item':
|
||||
flags |= int(Qt.ItemIsDragEnabled)
|
||||
|
||||
if item.type == 'section':
|
||||
flags |= int(Qt.ItemIsDropEnabled)
|
||||
|
||||
not_section = not (index.column() > 0 and item.type == 'section')
|
||||
not_list_item = not (index.column() > 1 and item.type == 'list_item')
|
||||
not_list_val = not (index.column() == 1 and item.type == 'list')
|
||||
|
||||
if not_section and not_list_item and not_list_val:
|
||||
flags |= Qt.ItemIsEditable
|
||||
|
||||
return flags
|
||||
|
||||
def supportedDropActions(self):
|
||||
return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction
|
||||
|
||||
def mimeTypes(self):
|
||||
return ['app/configitem']
|
||||
|
||||
def mimeData(self, indexes):
|
||||
mimedata = QtCore.QMimeData()
|
||||
index = indexes[0]
|
||||
mimedata.setData('app/configitem', pickle.dumps(self.nodeFromIndex(index)))
|
||||
return mimedata
|
||||
|
||||
def dropMimeData(self, mimedata, action, row, column, parentIndex):
|
||||
if action == Qt.IgnoreAction:
|
||||
return True
|
||||
|
||||
droppedNode = deepcopy(pickle.loads(mimedata.data('app/configitem')))
|
||||
|
||||
self.insertItems(row, [droppedNode], parentIndex)
|
||||
self.dataChanged.emit(parentIndex, parentIndex)
|
||||
|
||||
self.widget.ui.config_view.expandAll()
|
||||
|
||||
if action & Qt.CopyAction:
|
||||
return False # to not delete original item
|
||||
return True
|
||||
|
||||
def removeRows(self, row, count, parent):
|
||||
self.beginRemoveRows(parent, row, row + count - 1)
|
||||
parentItem = self.nodeFromIndex(parent)
|
||||
for _ in range(count):
|
||||
parentItem.removeChild(row)
|
||||
|
||||
self.endRemoveRows()
|
||||
return True
|
||||
|
||||
def removeRow(self, index):
|
||||
parent = index.parent()
|
||||
self.beginRemoveRows(parent, index.row(), index.row())
|
||||
|
||||
parentItem = self.nodeFromIndex(parent)
|
||||
parentItem.removeChild(index.row())
|
||||
|
||||
self.endRemoveRows()
|
||||
return True
|
||||
|
||||
def insertItems(self, row, items, parentIndex):
|
||||
parent = self.nodeFromIndex(parentIndex)
|
||||
self.beginInsertRows(parentIndex, row, row + len(items) - 1) # parentIndex or QtCore.QModelIndex()
|
||||
|
||||
parent.addChildren(items, row)
|
||||
|
||||
self.endInsertRows()
|
||||
self.update_all()
|
||||
|
||||
return True
|
||||
|
||||
def update_all(self):
|
||||
self.dataChanged.emit(QtCore.QModelIndex(), QtCore.QModelIndex())
|
||||
|
||||
def dict_setup(self, data: dict, parent=None, convert_types=False):
|
||||
if parent is None:
|
||||
parent = self.rootItem
|
||||
|
||||
for key, value in data.items():
|
||||
if isinstance(value, dict):
|
||||
item = ConfigModelItem((key,), parent=parent, item_type='section')
|
||||
self.dict_setup(value, parent=item)
|
||||
else:
|
||||
if convert_types:
|
||||
value = convert_type(value)
|
||||
parent.appendChild(ConfigModelItem((key, value, '', '')))
|
||||
|
||||
def config_dict_setup(self, data: dict, convert_types=False, parent=None):
|
||||
if parent is None:
|
||||
parent = self.rootItem
|
||||
|
||||
self.initial_comment = '\n'.join(data.pop('initial_comment', ['']))
|
||||
self.final_comment = '\n'.join(data.pop('final_comment', ['']))
|
||||
|
||||
for key, item in data.items():
|
||||
if '__value__' in item:
|
||||
value = item.get('__value__')
|
||||
if convert_types:
|
||||
value = convert_type(value)
|
||||
|
||||
default = item['default']
|
||||
comments = '\n'.join(item.get('comments', '')) or ''
|
||||
inline_comment = item.get('inline_comment', '') or ''
|
||||
|
||||
if item['unchanged']:
|
||||
state = 'unchanged'
|
||||
elif value == default:
|
||||
state = 'default'
|
||||
else:
|
||||
state = 'normal'
|
||||
|
||||
parent.appendChild(ConfigModelItem((key, value, comments, inline_comment),
|
||||
state=state, default=default))
|
||||
|
||||
else:
|
||||
section = ConfigModelItem((key,), parent=parent, item_type='section')
|
||||
self.config_dict_setup(item, convert_types=convert_types, parent=section)
|
||||
section.check_state()
|
||||
|
||||
def to_dict(self, parent=None) -> dict:
|
||||
if parent is None:
|
||||
parent = self.rootItem
|
||||
|
||||
data = {}
|
||||
for item in parent.childItems:
|
||||
item_name, item_data = item.data(0), item.data(1)
|
||||
if item.is_section:
|
||||
data[item_name] = self.to_dict(item)
|
||||
else:
|
||||
data[item_name] = item_data
|
||||
|
||||
return data
|
||||
|
||||
def to_config_dict(self, parent=None) -> dict:
|
||||
data = {}
|
||||
|
||||
if parent is None:
|
||||
parent = self.rootItem
|
||||
data['initial_comment'] = self.initial_comment.split('\n')
|
||||
data['final_comment'] = self.final_comment.split('\n')
|
||||
|
||||
for item in parent.childItems:
|
||||
key = item.data(0)
|
||||
|
||||
if item.is_section:
|
||||
d = self.to_config_dict(item)
|
||||
if d: # to prevent empty sections
|
||||
data[key] = d
|
||||
|
||||
elif item.state not in ('unchanged', 'deleted'):
|
||||
if item.type == 'list':
|
||||
value = [child.data(1) for child in item.childItems]
|
||||
else:
|
||||
value = item.data(1)
|
||||
|
||||
d = {'__value__': value}
|
||||
|
||||
comment = item.data(2)
|
||||
if comment:
|
||||
d.update({'comments': comment.split('\n')})
|
||||
|
||||
inline_comment = item.data(3)
|
||||
if inline_comment:
|
||||
d.update({'inline_comment': inline_comment})
|
||||
|
||||
data[key] = d
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def dict(self):
|
||||
return self.to_dict()
|
||||
|
||||
|
||||
class ConfigTreeWidget(QTreeView):
|
||||
def __init__(self):
|
||||
QTreeView.__init__(self)
|
||||
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.open_menu)
|
||||
|
||||
self.setSelectionMode(self.SingleSelection)
|
||||
# self.setSelectionBehavior(self.SelectItems)
|
||||
|
||||
self.setDragDropMode(QAbstractItemView.DragDrop)
|
||||
self.setDefaultDropAction(Qt.MoveAction)
|
||||
self.setDragEnabled(True)
|
||||
self.setAcceptDrops(True)
|
||||
self.setDropIndicatorShown(True)
|
||||
self.setAnimated(True)
|
||||
|
||||
self.duplicate_shortcut = QShortcut(QKeySequence('Shift+D'), self)
|
||||
self.duplicate_shortcut.activated.connect(self.with_selected(self.duplicate))
|
||||
self.exclude_shortcut = QShortcut(QKeySequence('Alt+Del'), self)
|
||||
self.exclude_shortcut.activated.connect(self.with_selected(self.exclude))
|
||||
self.remove_shortcut = QShortcut(QKeySequence('Del'), self)
|
||||
self.remove_shortcut.activated.connect(self.with_selected(self.remove))
|
||||
self.clear_shortcut = QShortcut(QKeySequence('Shift+R'), self)
|
||||
self.clear_shortcut.activated.connect(self.with_selected(self.reset_item, 'clear_value'))
|
||||
self.default_shortcut = QShortcut(QKeySequence('Ctrl+R'), self)
|
||||
self.default_shortcut.activated.connect(self.with_selected(self.reset_item, 'default'))
|
||||
self.reset_shortcut = QShortcut(QKeySequence('Alt+R'), self)
|
||||
self.reset_shortcut.activated.connect(self.with_selected(self.reset_item, 'all'))
|
||||
self.item_shortcut = QShortcut(QKeySequence('Shift+A'), self)
|
||||
self.item_shortcut.activated.connect(self.with_selected(self.add_item, False))
|
||||
self.section_shortcut = QShortcut(QKeySequence('Ctrl+A'), self)
|
||||
self.section_shortcut.activated.connect(self.with_selected(self.add_item, True))
|
||||
|
||||
def with_selected(self, f, *args, **kwargs):
|
||||
def decorated():
|
||||
index = self.selectedIndexes()[0]
|
||||
return f(index, *args, **kwargs)
|
||||
|
||||
return decorated
|
||||
|
||||
def open_menu(self, point):
|
||||
index = self.indexAt(point)
|
||||
item = index.internalPointer()
|
||||
|
||||
menu = QMenu()
|
||||
|
||||
duplicate = QAction("Duplicate")
|
||||
duplicate.setShortcut(self.duplicate_shortcut.key())
|
||||
duplicate.triggered.connect(partial(self.duplicate, index))
|
||||
menu.addAction(duplicate)
|
||||
|
||||
exclude = QAction("Toggle exclude")
|
||||
exclude.setShortcut(self.exclude_shortcut.key())
|
||||
exclude.triggered.connect(partial(self.exclude, index))
|
||||
menu.addAction(exclude)
|
||||
|
||||
remove = QAction("Remove from config")
|
||||
remove.setShortcut(self.remove_shortcut.key())
|
||||
remove.triggered.connect(partial(self.remove, index))
|
||||
menu.addAction(remove)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
clear = QAction("Clear item value")
|
||||
clear.setShortcut(self.clear_shortcut.key())
|
||||
clear.triggered.connect(partial(self.reset_item, index, 'clear_value'))
|
||||
menu.addAction(clear)
|
||||
|
||||
reset_default = QAction("Reset value to default")
|
||||
reset_default.setShortcut(self.default_shortcut.key())
|
||||
reset_default.triggered.connect(partial(self.reset_item, index, 'default'))
|
||||
menu.addAction(reset_default)
|
||||
|
||||
reset_all = QAction("Reset all changes")
|
||||
reset_all.setShortcut(self.reset_shortcut.key())
|
||||
reset_all.triggered.connect(partial(self.reset_item, index, 'all'))
|
||||
menu.addAction(reset_all)
|
||||
|
||||
menu.addSeparator()
|
||||
|
||||
add_option = QAction("Add option")
|
||||
add_option.setShortcut(self.item_shortcut.key())
|
||||
add_option.triggered.connect(partial(self.add_item, index, False))
|
||||
menu.addAction(add_option)
|
||||
|
||||
add_section = QAction("Add section")
|
||||
add_section.setShortcut(self.section_shortcut.key())
|
||||
add_section.triggered.connect(partial(self.add_item, index, True))
|
||||
menu.addAction(add_section)
|
||||
|
||||
if item is None:
|
||||
clear.setDisabled(True)
|
||||
reset_all.setDisabled(True)
|
||||
reset_default.setDisabled(True)
|
||||
|
||||
duplicate.setDisabled(True)
|
||||
remove.setDisabled(True)
|
||||
exclude.setDisabled(True)
|
||||
else:
|
||||
if item.type in ('list', 'list_item'):
|
||||
add_section.setDisabled(True)
|
||||
|
||||
if item.type == 'list':
|
||||
clear.setDisabled(True) # Temporary, cuz buggg
|
||||
|
||||
# if item.type == 'section':
|
||||
# clear.setDisabled(True)
|
||||
|
||||
menu.exec_(QCursor.pos())
|
||||
|
||||
def duplicate(self, index):
|
||||
item = deepcopy(index.internalPointer())
|
||||
item.set_state('added')
|
||||
ensure_unique_names(item)
|
||||
self.model().insertItems(index.row() + 1, [item], index.parent())
|
||||
self.expandAll() # fixes not expanded duplicated section
|
||||
|
||||
def remove(self, index):
|
||||
self.model().removeRow(index)
|
||||
|
||||
def exclude(self, index):
|
||||
item = self.model().nodeFromIndex(index)
|
||||
if item.state == 'deleted':
|
||||
self.model().setData(index, item.previous_state, StateRole)
|
||||
else:
|
||||
self.model().setData(index, 'deleted', StateRole)
|
||||
|
||||
def add_item(self, index, is_section):
|
||||
parentItem = self.model().nodeFromIndex(index)
|
||||
|
||||
if parentItem.type in ('list', 'list_item'):
|
||||
if is_section:
|
||||
return
|
||||
item_type = 'list_item'
|
||||
else:
|
||||
item_type = 'section' if is_section else 'option'
|
||||
|
||||
prompt = 'Enter {} name'.format(item_type.replace('_', ' '))
|
||||
text, ok = QInputDialog.getText(self, prompt, prompt)
|
||||
if not ok:
|
||||
return
|
||||
|
||||
if parentItem.type in ('list', 'section'): # to append at first index in section or list
|
||||
row = 0
|
||||
parent = index
|
||||
else:
|
||||
row = index.row()
|
||||
parent = index.parent()
|
||||
if row == -1: # to append at last position e.g. at root
|
||||
row = parentItem.childCount()
|
||||
else:
|
||||
row += 1 # to append under current position
|
||||
|
||||
item = ConfigModelItem((text, None, '', ''), item_type=item_type, state='added')
|
||||
self.model().insertItems(row, [item], parent)
|
||||
|
||||
ensure_unique_names(item, include_self=False)
|
||||
# parent.internalPointer().set_state('edited')
|
||||
self.expandAll()
|
||||
|
||||
def reset_item(self, index, reset_type):
|
||||
item = index.internalPointer()
|
||||
model = self.model()
|
||||
itemdataindex = model.modifyCol(index, 1)
|
||||
|
||||
if reset_type == 'all':
|
||||
for i, default in enumerate(item.default_values):
|
||||
model.setData(model.modifyCol(index, i), default)
|
||||
|
||||
model.setData(index, item.default_state, role=StateRole)
|
||||
|
||||
elif reset_type == 'default':
|
||||
# if item.type == 'list' and \
|
||||
# not isinstance(item.spec_default, (list, tuple)):
|
||||
# self.reset_item(item, 'clear_value')
|
||||
|
||||
model.setData(itemdataindex, item.spec_default)
|
||||
|
||||
if item.default_state == 'unchanged':
|
||||
model.setData(index, 'unchanged', role=StateRole)
|
||||
|
||||
elif reset_type == 'clear_value':
|
||||
item_type = model.data(itemdataindex, TypeRole)
|
||||
if item_type == 'list':
|
||||
return
|
||||
if item_type != 'section':
|
||||
model.setData(itemdataindex, None)
|
||||
|
||||
# if model.data(itemdataindex, TypeRole) == 'list': # TODO
|
||||
# model.removeRows(0, item.childCount(), index)
|
||||
# model.setData(index, 'option', role=TypeRole)
|
||||
# return
|
||||
|
||||
for child in model.childrenIndexes(index):
|
||||
self.reset_item(child, reset_type)
|
||||
|
||||
|
||||
class ConfigDialog(QtWidgets.QDialog):
|
||||
copter_editor_signal = QtCore.pyqtSignal(object, object)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ConfigDialog, self).__init__(parent)
|
||||
self.ui = config_editor.Ui_config_dialog()
|
||||
self.model = ConfigModel(widget=self)
|
||||
self._filename = None
|
||||
self.unsaved = False
|
||||
self.setupUi()
|
||||
self.copter_editor_signal.connect(self._call_copter_dialog)
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self._filename or 'Untitled.ini'
|
||||
|
||||
def setupModel(self, data, pure_dict=False, convert_types=False):
|
||||
if pure_dict:
|
||||
self.model.dict_setup(data, convert_types=convert_types)
|
||||
else:
|
||||
self.model.config_dict_setup(data, convert_types=convert_types)
|
||||
|
||||
self.ui.config_view.expandAll()
|
||||
self.ui.config_view.resizeColumnToContents(0)
|
||||
self.ui.config_view.resizeColumnToContents(1)
|
||||
|
||||
self.model.dataChanged.connect(self.unsaved_call) # connect after setup
|
||||
|
||||
def setupUi(self):
|
||||
self.ui.setupUi(self)
|
||||
|
||||
self.ui.config_view = ConfigTreeWidget()
|
||||
self.ui.config_view.setObjectName("config_view")
|
||||
self.ui.config_view.setModel(self.model)
|
||||
self.ui.gridLayout.addWidget(self.ui.config_view, 0, 0, 1, 1)
|
||||
self.ui.config_view.expandAll()
|
||||
|
||||
self.ui.do_coloring.stateChanged.connect(self.model.enable_color)
|
||||
self.ui.save_as_button.clicked.connect(self.save_as)
|
||||
|
||||
# self.ui.delete_button.pressed.connect(self.remove_selected)
|
||||
|
||||
def update_title(self):
|
||||
self.setWindowTitle(f"Config editor - {self.filename}" + "*"*self.unsaved)
|
||||
|
||||
def unsaved_call(self):
|
||||
self.unsaved = True
|
||||
self.update_title()
|
||||
self.model.dataChanged.disconnect(self.unsaved_call)
|
||||
|
||||
def closeEvent(self, event):
|
||||
if not self.unsaved or self.result():
|
||||
event.accept()
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(self, "Confirm exit", "There are unsaved changes in config file. "
|
||||
"Are you sure you want to exit?",
|
||||
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
|
||||
|
||||
if reply != QMessageBox.Yes:
|
||||
event.ignore()
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
def edit_caution(self):
|
||||
reply = QMessageBox().warning(self, "Editing caution",
|
||||
"Are you sure you want to edit section/option name? "
|
||||
"Proceed with caution!",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No
|
||||
)
|
||||
return reply == QMessageBox.Yes
|
||||
|
||||
def save_as(self):
|
||||
save_path = QFileDialog.getSaveFileName(self, "Save as configuration file (.ini)",
|
||||
directory=self.filename+'.ini',
|
||||
options=QFileDialog.DontConfirmOverwrite,
|
||||
filter="Config files (*.ini);;All files (*.*)")[0]
|
||||
if not save_path:
|
||||
return
|
||||
|
||||
split_path = save_path.split('.')
|
||||
|
||||
if not (len(split_path) > 1 and split_path[-1] == 'ini'):
|
||||
save_path += '.ini'
|
||||
|
||||
cfg = config.ConfigManager()
|
||||
cfg.load_from_dict(self.model.to_config_dict())
|
||||
cfg.config.filename = save_path
|
||||
cfg.write()
|
||||
|
||||
@pyqtSlot()
|
||||
def run(self):
|
||||
self.show()
|
||||
self.exec()
|
||||
return self.result()
|
||||
|
||||
def validation_loop(self, cfg, configspec=None): # modifies cfg object
|
||||
filename = cfg.config.filename
|
||||
while True:
|
||||
if not self.run():
|
||||
return False
|
||||
|
||||
try:
|
||||
cfg.load_from_dict(self.model.to_config_dict(), configspec=configspec)
|
||||
except config.ValidationError as error:
|
||||
msg = "Can not validate. Proceed with editing? Errors: \n" + "\n".join(error.flatten_errors())
|
||||
reply = QMessageBox.warning(self, "Validation error!", msg, QMessageBox.Yes | QMessageBox.Cancel)
|
||||
|
||||
if reply == QMessageBox.Cancel:
|
||||
return False
|
||||
|
||||
else:
|
||||
return True
|
||||
|
||||
finally:
|
||||
if filename is not None:
|
||||
cfg.config.filename = filename
|
||||
|
||||
def call_copter_dialog(self, client, value):
|
||||
self.copter_editor_signal.emit(client, value)
|
||||
|
||||
@pyqtSlot(object, object)
|
||||
def _call_copter_dialog(self, client, value):
|
||||
logging.info("Opening copter config dialog")
|
||||
config_dict, spec_dict = value["config"], value["configspec"]
|
||||
cfg = config.ConfigManager()
|
||||
cfg.load_from_dict(config_dict, spec_dict)
|
||||
|
||||
def save_callback():
|
||||
edited_dict = cfg.full_dict(include_defaults=False)
|
||||
client.send_message("config", kwargs={"config": edited_dict, "mode": "rewrite"})
|
||||
|
||||
def restart_callback():
|
||||
client.send_message("service_restart", kwargs={"name": "clever-show"})
|
||||
|
||||
if not self.call_config_dialog(cfg, save_callback, restart_callback, f"{client.copter_id}"):
|
||||
return False
|
||||
return True
|
||||
|
||||
def call_config_dialog(self, cfg: config.ConfigManager, on_save=None, on_restart=None, name="Untitled.ini"):
|
||||
self.setupModel(cfg.full_dict(include_defaults=True), convert_types=(not cfg.validated))
|
||||
self.ui.do_restart.setEnabled(on_restart is not None)
|
||||
self._filename = name
|
||||
self.update_title()
|
||||
|
||||
if not self.validation_loop(cfg, cfg.config.configspec):
|
||||
return False
|
||||
|
||||
if on_save is not None:
|
||||
on_save()
|
||||
|
||||
if on_restart is not None and self.ui.do_restart.isChecked():
|
||||
on_restart()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def call_standalone_dialog(cls):
|
||||
dialog = cls()
|
||||
dialog._call_standalone_dialog()
|
||||
|
||||
def _call_standalone_dialog(self):
|
||||
path = QFileDialog.getOpenFileName(self, "Select configuration or specification file",
|
||||
filter="Config and spec files (*.ini)")[0]
|
||||
if not path:
|
||||
return False
|
||||
|
||||
cfg = config.ConfigManager()
|
||||
try:
|
||||
cfg.load_from_file(path)
|
||||
except ValueError as error: # When file do not exist or not validated properly
|
||||
QMessageBox.warning(self, "Error while opening file!",
|
||||
"Config cannot be opened or validated: {}".format(error))
|
||||
return False
|
||||
|
||||
def save_callback():
|
||||
if cfg.config.filename is None:
|
||||
save_path = QFileDialog.getSaveFileName(self, "Save configuration file",
|
||||
directory=self.filename,
|
||||
filter="Config files (*.ini)")[0]
|
||||
if not save_path:
|
||||
return False
|
||||
else:
|
||||
save_path = cfg.config.filename
|
||||
|
||||
cfg.config.filename = save_path
|
||||
cfg.write()
|
||||
|
||||
if cfg.config.filename is not None:
|
||||
name = os.path.split(cfg.config.filename)[1]
|
||||
else: # when editing only configspec-based file
|
||||
name = os.path.split(path)[1]
|
||||
|
||||
if not self.call_config_dialog(cfg, on_save=save_callback, name=name):
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def except_hook(cls, exception, traceback):
|
||||
print(cls, exception, traceback)
|
||||
sys.__excepthook__(cls, exception, traceback)
|
||||
|
||||
sys.excepthook = except_hook
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
ui = ConfigDialog()
|
||||
ui.call_standalone_dialog()
|
||||
# d = {'section': {'opt': 1, "opt222": 'text'}}
|
||||
# ui.setupModel(d, pure_dict=True)
|
||||
# ui.show()
|
||||
# app.exec()
|
||||
# print(ui.model.to_config_dict())
|
||||
557
server/copter_table.py
Normal file
557
server/copter_table.py
Normal file
@@ -0,0 +1,557 @@
|
||||
from functools import partial
|
||||
from copy import deepcopy
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from PyQt5.QtCore import Qt as Qt, QObject, QEvent, QModelIndex
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QCursor
|
||||
from PyQt5.QtWidgets import QTableView, QMessageBox, QMenu, QAction, QWidgetAction, QListWidget, \
|
||||
QAbstractItemView, QListWidgetItem, QVBoxLayout, QHBoxLayout, QPushButton, QInputDialog, QLineEdit, QApplication
|
||||
|
||||
from config_editor_models import ConfigDialog
|
||||
import copter_table_models as table
|
||||
|
||||
|
||||
def save_preset(config, current, header_dict):
|
||||
presets = config.table_presets
|
||||
|
||||
for key in presets[HeaderEditWidget.default]:
|
||||
if key not in presets[current] and not header_dict[key][0]:
|
||||
header_dict.pop(key)
|
||||
|
||||
presets[current] = header_dict
|
||||
# config.write()
|
||||
|
||||
|
||||
class HeaderViewFilter(QObject):
|
||||
def __init__(self, parent, header, *args):
|
||||
super().__init__(parent, *args)
|
||||
self.header = header
|
||||
self._parent = parent
|
||||
|
||||
def eventFilter(self, object, event):
|
||||
if event.type() == QEvent.Enter:
|
||||
# logicalIndex = self.header.logicalIndexAt(event.pos())
|
||||
self.parent().cellHover.emit(QModelIndex())
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class CopterTableWidget(QTableView):
|
||||
override_cursors = {
|
||||
"copter_id": Qt.IBeamCursor,
|
||||
"config_version": Qt.OpenHandCursor,
|
||||
"selfcheck": Qt.PointingHandCursor,
|
||||
}
|
||||
|
||||
cellHover = QtCore.pyqtSignal(QModelIndex)
|
||||
cellEntered = QtCore.pyqtSignal(int, int)
|
||||
cellExited = QtCore.pyqtSignal(int, int)
|
||||
|
||||
def __init__(self, model: table.CopterDataModel, config):
|
||||
QTableView.__init__(self)
|
||||
|
||||
self.config = config
|
||||
self.model = model
|
||||
|
||||
self.proxy_model = table.CopterProxyModel()
|
||||
self.proxy_model.setSourceModel(self.model)
|
||||
self.proxy_model.setDynamicSortFilter(True)
|
||||
|
||||
# Initiate table and table self.model
|
||||
self.setModel(self.proxy_model)
|
||||
|
||||
self.columns = self.model.columns # [header.strip() for header in self.model.headers] # header keys
|
||||
self.current_columns = self.columns[:]
|
||||
|
||||
self._last_hover_index = QtCore.QModelIndex()
|
||||
self._previous_cursor = None
|
||||
|
||||
self.cellHover.connect(self.cell_hover)
|
||||
self.cellExited.connect(self.cell_exited)
|
||||
self.cellEntered.connect(self.cell_entered)
|
||||
|
||||
header = self.horizontalHeader()
|
||||
self.filter = HeaderViewFilter(self, header)
|
||||
header.installEventFilter(self.filter)
|
||||
header.setCascadingSectionResizes(False)
|
||||
header.setStretchLastSection(True)
|
||||
header.setSectionsMovable(True)
|
||||
header.sectionMoved.connect(self.moved)
|
||||
header.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
header.customContextMenuRequested.connect(self.showHeaderMenu)
|
||||
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.customContextMenuRequested.connect(self.open_menu)
|
||||
|
||||
# Adjust properties
|
||||
self.setTextElideMode(QtCore.Qt.ElideMiddle)
|
||||
self.setWordWrap(True)
|
||||
self.setSortingEnabled(True)
|
||||
self.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
||||
self.resizeColumnsToContents()
|
||||
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
||||
self.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
||||
self.doubleClicked.connect(self.on_double_click)
|
||||
self.setDragDropMode(QAbstractItemView.DragDrop)
|
||||
self.setMouseTracking(True)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
super().mousePressEvent(event)
|
||||
index = self.indexAt(event.pos())
|
||||
if index.column() == -1 and index.row() == -1:
|
||||
self.clearSelection()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
self.cell_hover(self.indexAt(event.pos()))
|
||||
super().mouseMoveEvent(event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self.cell_hover(QtCore.QModelIndex())
|
||||
|
||||
def dragEnterEvent(self, *args, **kwargs):
|
||||
self.cell_hover(QtCore.QModelIndex())
|
||||
super().dragEnterEvent(*args, **kwargs)
|
||||
|
||||
def cell_hover(self, index):
|
||||
if index != self._last_hover_index:
|
||||
self.cellExited.emit(self._last_hover_index.row(), self._last_hover_index.column())
|
||||
self.cellEntered.emit(index.row(), index.column())
|
||||
|
||||
self._last_hover_index = QtCore.QPersistentModelIndex(index)
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def cell_entered(self, row, column):
|
||||
if column != -1 and self.columns[column] in self.override_cursors:
|
||||
self._previous_cursor = QApplication.overrideCursor()
|
||||
if self._previous_cursor is None:
|
||||
QApplication.setOverrideCursor(self.override_cursors[self.columns[column]])
|
||||
|
||||
@pyqtSlot(int, int)
|
||||
def cell_exited(self, row, column):
|
||||
# if self._previous_cursor is not None:
|
||||
# QApplication.setOverrideCursor(self._previous_cursor)
|
||||
if self._previous_cursor is None:
|
||||
QApplication.restoreOverrideCursor()
|
||||
|
||||
def moved(self, logical_index, old_index, new_index):
|
||||
name = self.current_columns.pop(old_index)
|
||||
self.current_columns.insert(new_index, name)
|
||||
|
||||
def set_column_order(self, order):
|
||||
if set(order) != set(self.current_columns):
|
||||
raise ValueError
|
||||
|
||||
for index_to, item in enumerate(order):
|
||||
index_from = self.current_columns.index(item)
|
||||
if index_to != index_from:
|
||||
self.horizontalHeader().moveSection(index_from, index_to)
|
||||
|
||||
def load_columns(self, item_dict: dict = None):
|
||||
presets = self.config.table_presets
|
||||
if item_dict is None:
|
||||
item_dict = presets[self.config.table_presets_current]
|
||||
|
||||
item_dict.update({key: (False, presets[HeaderEditWidget.default][key][1])
|
||||
for key in presets[HeaderEditWidget.default] if key not in item_dict})
|
||||
|
||||
self.set_column_order(item_dict.keys())
|
||||
# self.set_column_widths({key: val[1] for key, val in item_dict.items()})
|
||||
|
||||
for name, value in item_dict.items(): # for index, name in enumerate(self.columns):
|
||||
index = self.columns.index(name)
|
||||
show, width = value
|
||||
self.setColumnHidden(index, not show) # self.setColumnHidden(index, not item_dict.get(name, False))
|
||||
self.setColumnWidth(index, width)
|
||||
|
||||
def _get_column_item(self, column):
|
||||
index = self.columns.index(column)
|
||||
presets = self.config.table_presets
|
||||
show = not self.isColumnHidden(index)
|
||||
# columnWidth is 0 when hidden, trying to get previous width from config or default
|
||||
width = self.columnWidth(index) or \
|
||||
presets[self.config.table_presets_current].get(column, 0)[1] or \
|
||||
presets[HeaderEditWidget.default][column][1]
|
||||
return show, width
|
||||
|
||||
@property
|
||||
def item_dict(self):
|
||||
return {column: self._get_column_item(column) for column in self.current_columns}
|
||||
|
||||
def save_columns(self):
|
||||
current = self.config.table_presets_current
|
||||
header_dict = self.item_dict
|
||||
save_preset(self.config, current, header_dict)
|
||||
|
||||
def select_all(self, state):
|
||||
for i in range(self.model.rowCount()):
|
||||
self.model.update_data(i, 0, state, Qt.CheckStateRole)
|
||||
|
||||
def toggle_select(self):
|
||||
if len(list(self.model.user_selected())) == self.model.rowCount(): # if all items are selected
|
||||
state = Qt.Unchecked
|
||||
else:
|
||||
state = Qt.Checked
|
||||
self.select_all(state)
|
||||
|
||||
@pyqtSlot(QtCore.QModelIndex)
|
||||
def on_double_click(self, index):
|
||||
if self.model.is_column(index, "selfcheck"):
|
||||
data = self.proxy_model.data(index, role=table.ModelDataRole)
|
||||
if data and data != "OK":
|
||||
self._show_info("Selfcheck info", data)
|
||||
|
||||
def _show_info(self, title, data):
|
||||
dialog = QMessageBox()
|
||||
dialog.setIcon(QMessageBox.NoIcon)
|
||||
dialog.setStandardButtons(QMessageBox.Ok)
|
||||
dialog.setWindowTitle(title)
|
||||
dialog.setText("\n".join(data[:10]))
|
||||
dialog.setDetailedText("\n".join(data))
|
||||
dialog.exec()
|
||||
|
||||
def showHeaderMenu(self, event):
|
||||
self.save_columns()
|
||||
menu = QMenu(self)
|
||||
header_view = HeaderEditWidget(self, self.config, menu_mode=True, parent=menu)
|
||||
# header_view.setFixedHeight((header_view.geometry().height()-2) * len(header_view.columns))
|
||||
# box.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
action = QWidgetAction(menu)
|
||||
action.setDefaultWidget(header_view)
|
||||
menu.addAction(action)
|
||||
menu.exec_(QCursor.pos())
|
||||
header_view.save_preset()
|
||||
|
||||
@pyqtSlot(QtCore.QPoint)
|
||||
def open_menu(self, point):
|
||||
menu = QMenu(self)
|
||||
id = self.indexAt(point).siblingAtColumn(0).data()
|
||||
item = self.model.get_row_by_attr('copter_id', id)
|
||||
|
||||
edit_config = QAction("Edit config")
|
||||
edit_config.triggered.connect(partial(self.edit_copter_config, item))
|
||||
menu.addAction(edit_config)
|
||||
|
||||
copy_config = QAction("Copy config to selected")
|
||||
copy_config.triggered.connect(partial(self.copy_config, item))
|
||||
menu.addAction(copy_config)
|
||||
|
||||
if item is None:
|
||||
edit_config.setDisabled(True)
|
||||
copy_config.setDisabled(True)
|
||||
|
||||
menu.exec_(QCursor.pos())
|
||||
|
||||
@pyqtSlot()
|
||||
def edit_copter_config(self, copter):
|
||||
dialog = ConfigDialog()
|
||||
copter.client.get_response("config", dialog.call_copter_dialog, request_kwargs={'send_configspec': True})
|
||||
|
||||
@pyqtSlot()
|
||||
def copy_config(self, copter):
|
||||
def send_callback(client, value):
|
||||
config = value["config"]
|
||||
config.pop("PRIVATE", None) # delete private section
|
||||
|
||||
for _copter in self.model.user_selected():
|
||||
if _copter.client is client:
|
||||
continue # don't send config back to the same copter
|
||||
_copter.client.send_message("config", kwargs={"config": config, "mode": "modify"})
|
||||
|
||||
copter.client.get_response("config", send_callback, request_kwargs={'send_configspec': False})
|
||||
|
||||
# def _selfcheck_shortener(self, data): # TODO!!!
|
||||
# shortened = []
|
||||
# for line in data:
|
||||
# if len(line) > 89:
|
||||
# pass
|
||||
# return shortened
|
||||
|
||||
|
||||
class HeaderListWidget(QListWidget):
|
||||
ColumnKeyRole = Qt.UserRole + 1000
|
||||
ColumnWidthRole = Qt.UserRole + 1001
|
||||
|
||||
dropped = QtCore.pyqtSignal(bool)
|
||||
|
||||
def __init__(self, parent=None, default_items=None):
|
||||
super().__init__(parent)
|
||||
if default_items is not None:
|
||||
self.populate_items(default_items)
|
||||
|
||||
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setDefaultDropAction(Qt.MoveAction)
|
||||
|
||||
def populate_items(self, item_dict: dict):
|
||||
self.clear()
|
||||
for name, value in item_dict.items():
|
||||
visible, width = value
|
||||
flags = Qt.ItemIsUserCheckable | Qt.ItemIsSelectable | Qt.ItemIsDragEnabled | Qt.ItemIsEnabled
|
||||
state = Qt.Checked if visible else Qt.Unchecked
|
||||
|
||||
item = QListWidgetItem(table.CopterDataModel.columns_dict.get(name, "").strip() or name, self)
|
||||
item.setFlags(flags)
|
||||
item.setCheckState(state)
|
||||
item.setData(self.ColumnKeyRole, name)
|
||||
item.setData(self.ColumnWidthRole, width)
|
||||
|
||||
@property
|
||||
def item_dict(self):
|
||||
return {self.item(i).data(self.ColumnKeyRole):
|
||||
(bool(self.item(i).checkState()), self.item(i).data(self.ColumnWidthRole))
|
||||
for i in range(self.count())}
|
||||
|
||||
def dropEvent(self, event: QtGui.QDropEvent):
|
||||
super().dropEvent(event)
|
||||
self.dropped.emit(True)
|
||||
|
||||
|
||||
class ActiveHeaderListWidget(HeaderListWidget):
|
||||
def __init__(self, source: CopterTableWidget, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self.source_widget = source
|
||||
|
||||
self.current_columns = source.current_columns
|
||||
self.columns = source.columns
|
||||
|
||||
self._populate_from_widget()
|
||||
|
||||
self.itemChanged.connect(self.on_itemChanged)
|
||||
|
||||
def _populate_from_widget(self):
|
||||
self.populate_items(self.source_widget.item_dict)
|
||||
|
||||
@pyqtSlot(QListWidgetItem)
|
||||
def on_itemChanged(self, item):
|
||||
key = item.data(HeaderListWidget.ColumnKeyRole)
|
||||
if key is None:
|
||||
return
|
||||
self.source_widget.setColumnHidden(self.columns.index(key), not bool(item.checkState()))
|
||||
|
||||
def dropEvent(self, event: QtGui.QDropEvent):
|
||||
super().dropEvent(event)
|
||||
column_order = [self.item(i).data(HeaderListWidget.ColumnKeyRole) for i in range(self.count())]
|
||||
self.source_widget.set_column_order(column_order)
|
||||
|
||||
|
||||
class HeaderEditWidget(QtWidgets.QWidget):
|
||||
add_new_text = "< add new >"
|
||||
default = "DEFAULT"
|
||||
|
||||
saved_signal = QtCore.pyqtSignal(bool)
|
||||
|
||||
def __init__(self, source, config, menu_mode=False, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
# self.auto_apply = auto_apply
|
||||
self.source = source # source = copter table
|
||||
self.config = config
|
||||
self.menu_mode = menu_mode
|
||||
|
||||
self.preset_widget = QtWidgets.QComboBox()
|
||||
self.header_widget = ActiveHeaderListWidget(self.source) \
|
||||
if self.menu_mode else HeaderListWidget()
|
||||
# self.header_widget.itemChanged.connect(partial(self.saved_signal.emit, False))
|
||||
self.header_widget.model().dataChanged.connect(partial(self.saved_signal.emit, False))
|
||||
self.header_widget.dropped.connect(partial(self.saved_signal.emit, False))
|
||||
|
||||
self.previous = self.config.table_presets_current
|
||||
self.save = True
|
||||
|
||||
self.setupUi()
|
||||
|
||||
@pyqtSlot()
|
||||
def call_dialog(self):
|
||||
self.save_preset()
|
||||
self.save = False
|
||||
HeaderEditDialog(self.source, self.config).exec()
|
||||
|
||||
def setupUi(self):
|
||||
self.header_widget.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
|
||||
|
||||
self.update_preset_list()
|
||||
self.preset_widget.currentTextChanged.connect(self.on_preset_changed)
|
||||
self.on_preset_changed(self.previous) # to init
|
||||
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(self.header_widget)
|
||||
vbox.addWidget(self.preset_widget)
|
||||
|
||||
hbox = QHBoxLayout()
|
||||
if not self.menu_mode:
|
||||
add_button = QPushButton("Add")
|
||||
add_button.clicked.connect(self.add_preset)
|
||||
remove_button = QPushButton("Remove")
|
||||
remove_button.setToolTip("Permanently remove preset from config")
|
||||
remove_button.clicked.connect(self.remove_preset)
|
||||
save_button = QPushButton("Save")
|
||||
save_button.clicked.connect(self.save_preset)
|
||||
apply_button = QPushButton("Apply")
|
||||
apply_button.clicked.connect(self.apply_preset)
|
||||
apply_button.setDefault(True)
|
||||
apply_button.setFocus()
|
||||
|
||||
hbox.addWidget(add_button)
|
||||
hbox.addWidget(remove_button)
|
||||
hbox.addStretch()
|
||||
hbox.addWidget(save_button)
|
||||
hbox.addWidget(apply_button)
|
||||
else:
|
||||
dialog_button = QPushButton("Manage presets")
|
||||
dialog_button.clicked.connect(self.call_dialog)
|
||||
hbox.addWidget(dialog_button)
|
||||
|
||||
vbox.addLayout(hbox)
|
||||
self.setLayout(vbox)
|
||||
|
||||
def update_preset_list(self):
|
||||
self.preset_widget.clear()
|
||||
for name, preset in self.config.table_presets.items():
|
||||
if isinstance(preset, dict): # looking only for preset sections
|
||||
self.preset_widget.addItem(name)
|
||||
|
||||
self.preset_widget.addItem(self.add_new_text)
|
||||
self.preset_widget.setCurrentText(self.previous)
|
||||
|
||||
def on_preset_changed(self, index):
|
||||
if not index:
|
||||
return
|
||||
|
||||
if index == self.add_new_text:
|
||||
self.add_preset()
|
||||
return
|
||||
|
||||
self.previous = index
|
||||
presets = self.config.table_presets
|
||||
item_dict = {key: value for key, value in presets[index].items()}
|
||||
item_dict.update({key: (False, presets[self.default][key][1])
|
||||
for key in presets[self.default] if key not in item_dict})
|
||||
|
||||
if self.menu_mode:
|
||||
self.source.set_column_order(list(item_dict.keys())) # hidden\shown is hold by header widget's itemChanged
|
||||
for name, value in item_dict.items():
|
||||
self.source.setColumnWidth(self.source.columns.index(name), value[1])
|
||||
|
||||
self.config.table_presets_current = index
|
||||
self.header_widget.populate_items(item_dict)
|
||||
self.saved_signal.emit(True)
|
||||
|
||||
def add_preset(self):
|
||||
name, ok = QInputDialog.getText(None, "Enter new preset name", "Name:",
|
||||
QLineEdit.Normal, "")
|
||||
if not ok or not name:
|
||||
self.preset_widget.setCurrentText(self.previous)
|
||||
return
|
||||
|
||||
name = name.strip()
|
||||
if name in self.config.table_presets or name == self.default or name == self.add_new_text:
|
||||
QMessageBox.warning(None, "Preset already exists!", "Preset already exists!")
|
||||
self.preset_widget.setCurrentText(self.previous)
|
||||
return
|
||||
|
||||
self.config.table_presets[name] = deepcopy(dict(self.config.table_presets[self.default]))
|
||||
# self.config.write()
|
||||
|
||||
self.update_preset_list()
|
||||
self.preset_widget.setCurrentText(name)
|
||||
|
||||
def remove_preset(self):
|
||||
if self.preset_widget.currentText() == self.default:
|
||||
QMessageBox.warning(None, "Can't delete default preset!", "Can't delete default preset!")
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(None, "Action can't be undone", "Remove anyway?",
|
||||
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
|
||||
|
||||
if reply != QMessageBox.Yes:
|
||||
return
|
||||
|
||||
self.config.table_presets.pop(self.preset_widget.currentText())
|
||||
# self.config.write()
|
||||
|
||||
self.previous = self.default
|
||||
self.update_preset_list()
|
||||
|
||||
@pyqtSlot()
|
||||
def save_preset(self):
|
||||
if not self.save: # don't save after calling dialog to avoid overrides
|
||||
return
|
||||
|
||||
current = self.preset_widget.currentText()
|
||||
header_dict = self.header_widget.item_dict
|
||||
save_preset(self.config, current, header_dict)
|
||||
self.saved_signal.emit(True)
|
||||
|
||||
@pyqtSlot()
|
||||
def apply_preset(self):
|
||||
self.config.table_presets_current = self.preset_widget.currentText()
|
||||
self.save_preset()
|
||||
self.source.load_columns()
|
||||
|
||||
|
||||
class HeaderEditDialog(QtWidgets.QDialog):
|
||||
def __init__(self, source, config, parent=None):
|
||||
super(HeaderEditDialog, self).__init__(parent=None)
|
||||
self.widget = HeaderEditWidget(source, config, menu_mode=False)
|
||||
self.setupUI()
|
||||
self.unsaved = False
|
||||
|
||||
self.widget.saved_signal.connect(self.update_title)
|
||||
self.update_title(True)
|
||||
|
||||
def setupUI(self):
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.widget)
|
||||
self.setLayout(layout)
|
||||
|
||||
@pyqtSlot(bool)
|
||||
def update_title(self, saved):
|
||||
unsaved = not saved
|
||||
self.unsaved = unsaved
|
||||
self.setWindowTitle(f"Column preset editor - {self.widget.preset_widget.currentText()}"
|
||||
+ "*" * unsaved)
|
||||
|
||||
def closeEvent(self, event):
|
||||
if not self.unsaved:
|
||||
event.accept()
|
||||
return
|
||||
|
||||
reply = QMessageBox.question(self, "Confirm exit", "There are unsaved changes in current preset. "
|
||||
"Are you sure you want to exit?",
|
||||
QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
|
||||
|
||||
if reply != QMessageBox.Yes:
|
||||
event.ignore()
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
|
||||
|
||||
def except_hook(cls, exception, traceback):
|
||||
sys.__excepthook__(cls, exception, traceback)
|
||||
|
||||
|
||||
sys.excepthook = except_hook # for debugging (exceptions traceback)
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
import copter_table_models
|
||||
|
||||
model = copter_table_models.CopterDataModel()
|
||||
# for i in range(10):
|
||||
# model.add_client(copter_table_models.StatedCopterData())
|
||||
|
||||
import config
|
||||
|
||||
c = config.ConfigManager()
|
||||
c.load_config_and_spec("config\server.ini")
|
||||
# print(c.config)
|
||||
# print(c._name_dict)
|
||||
w1 = CopterTableWidget(model, c)
|
||||
w = HeaderEditWidget(w1, c)
|
||||
print(w1.item_dict)
|
||||
# print(*w1.current_columns, sep='\n')
|
||||
# w.show()
|
||||
app.exec()
|
||||
723
server/copter_table_models.py
Normal file
723
server/copter_table_models.py
Normal file
@@ -0,0 +1,723 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import math
|
||||
import time
|
||||
import subprocess
|
||||
from contextlib import suppress
|
||||
from functools import partialmethod
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
from PyQt5.QtCore import Qt as Qt, QUrl, QDir
|
||||
|
||||
from config import ConfigManager
|
||||
|
||||
# Additional custom roles to interact with various table data
|
||||
ModelDataRole = 998
|
||||
ModelStateRole = 999
|
||||
|
||||
|
||||
def get_git_version(): # TODO import from animation
|
||||
try:
|
||||
return subprocess.check_output("git log --pretty=format:'%h' -n 1", shell=True).decode('UTF-8')
|
||||
except subprocess.CalledProcessError: # when no git repository info present
|
||||
return None # todo probably add special file
|
||||
|
||||
|
||||
class CheckState:
|
||||
def __init__(self, bool_state, color):
|
||||
self._bool = bool_state
|
||||
self.color = color
|
||||
self.brush = QtGui.QBrush(self.color)
|
||||
|
||||
def __bool__(self):
|
||||
return self._bool
|
||||
|
||||
|
||||
# State objects providing both boolean and color information for table
|
||||
# Add more if required
|
||||
true_state = CheckState(True, Qt.green)
|
||||
false_state = CheckState(False, Qt.red)
|
||||
missing_state = CheckState(False, Qt.yellow)
|
||||
outdated_state = CheckState(False, Qt.magenta)
|
||||
|
||||
|
||||
class ModelChecks:
|
||||
checks_dict = {}
|
||||
|
||||
battery_min = 50.0
|
||||
start_pos_delta_max = 1.0
|
||||
time_delta_max = 1.0
|
||||
check_current_pos = True
|
||||
check_git = True
|
||||
|
||||
@classmethod
|
||||
def column_check(cls, column, pass_context=False):
|
||||
def inner(f):
|
||||
def wrapper(item, context=None):
|
||||
if item is None:
|
||||
return None
|
||||
if pass_context:
|
||||
return f(item, context)
|
||||
return f(item)
|
||||
|
||||
cls.checks_dict[column] = wrapper
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
@classmethod
|
||||
def check(cls, column, context):
|
||||
if isinstance(column, int):
|
||||
column = context.columns[column]
|
||||
item = context[column]
|
||||
try:
|
||||
return cls.checks_dict[column](item, context)
|
||||
except KeyError: # When there is no check
|
||||
return None if item is None else true_state # item is not None
|
||||
|
||||
|
||||
@ModelChecks.column_check("git_version")
|
||||
def check_ver(item):
|
||||
if not ModelChecks.check_git:
|
||||
return True
|
||||
|
||||
version = get_git_version()
|
||||
if version is not None:
|
||||
return version == item
|
||||
return True
|
||||
|
||||
|
||||
@ModelChecks.column_check("animation_info")
|
||||
def check_anim(item):
|
||||
if item:
|
||||
return str(item[1]) == 'OK'
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
@ModelChecks.column_check("battery")
|
||||
def check_bat(item):
|
||||
if item == "NO_INFO":
|
||||
return False
|
||||
return item[1] * 100 > ModelChecks.battery_min
|
||||
|
||||
|
||||
@ModelChecks.column_check("fcu_status")
|
||||
def check_sys_status(item):
|
||||
return item == "STANDBY"
|
||||
|
||||
|
||||
@ModelChecks.column_check("calibration_status")
|
||||
def check_cal_status(item):
|
||||
return item == "OK"
|
||||
|
||||
|
||||
@ModelChecks.column_check("mode")
|
||||
def check_mode(item):
|
||||
return (item != "NO_FCU") and not ("CMODE" in item)
|
||||
|
||||
|
||||
@ModelChecks.column_check("selfcheck")
|
||||
def check_selfcheck(item):
|
||||
return item == "OK"
|
||||
|
||||
|
||||
@ModelChecks.column_check("current_position")
|
||||
def check_pos(item):
|
||||
if not ModelChecks.check_current_pos:
|
||||
return True
|
||||
if item == 'NO_POS':
|
||||
return False
|
||||
return not math.isnan(item[0])
|
||||
|
||||
|
||||
# @ModelChecks.column_check("last_task")
|
||||
# def check_task(item):
|
||||
# return True
|
||||
|
||||
|
||||
@ModelChecks.column_check('time_delta')
|
||||
def check_time_delta(item):
|
||||
return abs(item) < ModelChecks.time_delta_max
|
||||
|
||||
|
||||
@ModelChecks.column_check("start_position", pass_context=True)
|
||||
def check_start_pos(item, context):
|
||||
|
||||
if len(item) == 6:
|
||||
if not item[4] in ["takeoff", "fly"]:
|
||||
return False
|
||||
|
||||
if ModelChecks.start_pos_delta_max == 0:
|
||||
return True
|
||||
|
||||
if context.current_position is None:
|
||||
return item != 'NO_POS' # maybe should return true
|
||||
|
||||
delta = get_distance(get_position(context.current_position),
|
||||
get_position(context.start_position))
|
||||
|
||||
if math.isnan(delta):
|
||||
return False
|
||||
|
||||
return delta < ModelChecks.start_pos_delta_max
|
||||
|
||||
|
||||
def get_position(position):
|
||||
if not isinstance(position, str) and position[0] != float('nan'):
|
||||
return position[:3]
|
||||
return [float('nan')] * 3
|
||||
|
||||
|
||||
def get_distance(pos1, pos2): # todo as common function
|
||||
if any(math.isnan(x) for x in pos1 + pos2):
|
||||
return float('nan')
|
||||
return math.sqrt(sum(map(lambda p: (p[0] - p[1]) ** 2, zip(pos1, pos2)))) # point distance formula
|
||||
|
||||
|
||||
class CopterData:
|
||||
def __init__(self, columns=(), **kwargs):
|
||||
self.columns = columns
|
||||
for column in columns:
|
||||
setattr(self, column, None)
|
||||
|
||||
for attr, value in kwargs.items():
|
||||
setattr(self, attr, value)
|
||||
|
||||
def __getitem__(self, key):
|
||||
if key in self.columns:
|
||||
return getattr(self, key)
|
||||
return getattr(self, self.columns[key])
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
if key in self.columns:
|
||||
setattr(self, key, value)
|
||||
else:
|
||||
setattr(self, self.columns[key], value)
|
||||
|
||||
def __repr__(self):
|
||||
return str({key: self[key] for key in self.columns})
|
||||
|
||||
|
||||
class StatedCopterData(CopterData):
|
||||
def __init__(self, columns=(), checks_defaults=None, checks_class=ModelChecks, **kwargs):
|
||||
if checks_defaults is None:
|
||||
checks_defaults = {}
|
||||
|
||||
self.__dict__['states'] = CopterData(columns, **checks_defaults)
|
||||
self.__dict__['checks'] = checks_class
|
||||
self.__dict__['all_checks'] = None
|
||||
|
||||
super().__init__(columns, **kwargs)
|
||||
|
||||
def __setattr__(self, key, value):
|
||||
self.__dict__[key] = value
|
||||
|
||||
if key in self.columns:
|
||||
with suppress(KeyError):
|
||||
self.states.__dict__[key] = \
|
||||
self.checks.check(key, self)
|
||||
self.states.__dict__["all_checks"] = all([self.states[i] for i in self.checks.checks_dict.keys()])
|
||||
|
||||
|
||||
class ModelFormatter:
|
||||
view_formatters = {}
|
||||
place_formatters = {}
|
||||
VIEW_FORMATTER = 1
|
||||
PLACE_FORMATTER = 2
|
||||
|
||||
@classmethod
|
||||
def get_formatter(cls, formatter_type):
|
||||
if formatter_type == cls.PLACE_FORMATTER:
|
||||
return cls.place_formatters
|
||||
if formatter_type == cls.VIEW_FORMATTER:
|
||||
return cls.view_formatters
|
||||
raise ValueError('Unknown formatter type')
|
||||
|
||||
@classmethod
|
||||
def format(cls, column, value, formatter_type):
|
||||
formatters_dict = cls.get_formatter(formatter_type)
|
||||
if isinstance(column, int):
|
||||
column = CopterDataModel.columns[column]
|
||||
try:
|
||||
return formatters_dict[column](value)
|
||||
except KeyError:
|
||||
return value # when there is no formatter for the column
|
||||
|
||||
format_place = partialmethod(format, formatter_type=PLACE_FORMATTER)
|
||||
format_view = partialmethod(format, formatter_type=VIEW_FORMATTER)
|
||||
|
||||
@classmethod
|
||||
def column_formatter(cls, column, formatter_type):
|
||||
def inner(f):
|
||||
formatters_dict = cls.get_formatter(formatter_type)
|
||||
formatters_dict[column] = f
|
||||
|
||||
def wrapper(value):
|
||||
return f(value)
|
||||
|
||||
return wrapper
|
||||
|
||||
return inner
|
||||
|
||||
place_formatter = partialmethod(column_formatter, formatter_type=PLACE_FORMATTER)
|
||||
view_formatter = partialmethod(column_formatter, formatter_type=VIEW_FORMATTER)
|
||||
|
||||
|
||||
@ModelFormatter.place_formatter("copter_id")
|
||||
def place_id(value):
|
||||
value = str(value).strip()
|
||||
# check user hostname spelling http://man7.org/linux/man-pages/man7/hostname.7.html
|
||||
# '-' (hyphen) not first; latin letters/numbers/hyphens; length form 1 to 63
|
||||
# or matches command pattern
|
||||
if re.match("^(?!-)[A-Za-z0-9-]{1,63}$", value) or re.match("^/[A-Za-z0-9]*$", value):
|
||||
return value
|
||||
else:
|
||||
msgbox = QtWidgets.QMessageBox()
|
||||
msgbox.setWindowTitle("Wrong input for the copter name!")
|
||||
msgbox.setIcon(QtWidgets.QMessageBox.Critical)
|
||||
msgbox.setText(
|
||||
"Wrong input for the copter name!\n"
|
||||
"Please use only A-Z, a-z, 0-9, and '-' chars.\n"
|
||||
"Don't use '-' as first char.")
|
||||
msgbox.exec_()
|
||||
return None
|
||||
|
||||
@ModelFormatter.view_formatter("animation_info")
|
||||
def view_animation_info(value):
|
||||
try:
|
||||
id, state = value
|
||||
except ValueError:
|
||||
return ""
|
||||
else:
|
||||
if state == 'OK':
|
||||
return id
|
||||
else:
|
||||
return state
|
||||
|
||||
@ModelFormatter.place_formatter("battery")
|
||||
def place_battery(value):
|
||||
if isinstance(value, list):
|
||||
battery_v, battery_p = value
|
||||
if math.isnan(battery_v) or math.isnan(battery_p):
|
||||
return "NO_INFO"
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.view_formatter("battery")
|
||||
def view_battery(value):
|
||||
if isinstance(value, list):
|
||||
battery_v, battery_p = value
|
||||
return f"{battery_v:4.1f}V {min(battery_p, 1):4.0%}"
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.view_formatter("selfcheck")
|
||||
def view_selfcheck(value):
|
||||
if isinstance(value, list):
|
||||
if len(value) == 1 and len(value[0]) <= 8:
|
||||
return value[0]
|
||||
return "ERROR"
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.view_formatter("current_position")
|
||||
def view_current_position(value):
|
||||
if isinstance(value, list):
|
||||
x, y, z, yaw, frame = value
|
||||
return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {frame}"
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.view_formatter("start_position")
|
||||
def view_start_position(value):
|
||||
if isinstance(value, list):
|
||||
x, y, z, yaw, action, delay = value
|
||||
if action in ['fly', 'takeoff']:
|
||||
return f"{x: .2f} {y: .2f} {z: .2f} {yaw: .0f} {action} {delay: .1f}"
|
||||
else:
|
||||
return f"{action}"
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.place_formatter("last_task")
|
||||
def place_last_task(value):
|
||||
if value is None: # TODO possible behaviour deviation
|
||||
return 'No task'
|
||||
return value
|
||||
|
||||
|
||||
@ModelFormatter.place_formatter("time_delta")
|
||||
def place_time_delta(value):
|
||||
return abs(value - time.time())
|
||||
|
||||
|
||||
@ModelFormatter.view_formatter("time_delta")
|
||||
def view_time_delta(value):
|
||||
return f"{value:.3f}"
|
||||
|
||||
|
||||
class CopterDataModel(QtCore.QAbstractTableModel):
|
||||
columns_dict = {'copter_id': 'copter ID',
|
||||
'git_version': 'version',
|
||||
'config_version': 'configuration',
|
||||
'animation_info': 'animation ID',
|
||||
'battery': ' battery ',
|
||||
'fcu_status': 'FCU status',
|
||||
'calibration_status': 'sensors',
|
||||
'mode': ' mode ',
|
||||
'selfcheck': ' checks ',
|
||||
'current_position': 'current x y z yaw frame_id',
|
||||
'start_position': 'start x y z yaw action delay',
|
||||
'last_task': 'last task',
|
||||
'time_delta': 'dt',
|
||||
}
|
||||
|
||||
columns = list(columns_dict.keys())
|
||||
|
||||
selected_ready_signal = QtCore.pyqtSignal(bool)
|
||||
selected_takeoff_ready_signal = QtCore.pyqtSignal(bool)
|
||||
selected_flip_ready_signal = QtCore.pyqtSignal(bool) # TODO fix this signals
|
||||
selected_calibrating_signal = QtCore.pyqtSignal(bool)
|
||||
selected_calibration_ready_signal = QtCore.pyqtSignal(bool)
|
||||
|
||||
update_data_signal = QtCore.pyqtSignal(int, int, QtCore.QVariant, QtCore.QVariant)
|
||||
add_client_signal = QtCore.pyqtSignal(object)
|
||||
remove_row_signal = QtCore.pyqtSignal(int)
|
||||
remove_client_signal = QtCore.pyqtSignal(object)
|
||||
|
||||
def __init__(self, checks=ModelChecks, formatter=ModelFormatter, data_model=StatedCopterData, parent=None):
|
||||
super(CopterDataModel, self).__init__(parent)
|
||||
self.headers = list(self.columns_dict.values())
|
||||
self.data_contents = []
|
||||
|
||||
self.checks = checks
|
||||
self.formatter = formatter
|
||||
self.data_model = data_model
|
||||
|
||||
self.update_data_signal.connect(self._update_data)
|
||||
self.add_client_signal.connect(self._add_client)
|
||||
self.remove_row_signal.connect(self._remove_row)
|
||||
self.remove_client_signal.connect(self._remove_row_data)
|
||||
|
||||
def insertRows(self, contents, position='last', parent=QtCore.QModelIndex()):
|
||||
rows = len(contents)
|
||||
position = len(self.data_contents) if position == 'last' else position
|
||||
self.beginInsertRows(parent, position, position + rows - 1)
|
||||
self.data_contents[position:position] = contents
|
||||
|
||||
self.endInsertRows()
|
||||
|
||||
def removeRows(self, position, rows=1, index=QtCore.QModelIndex()):
|
||||
self.beginRemoveRows(QtCore.QModelIndex(), position, position + rows - 1)
|
||||
self.data_contents = self.data_contents[:position] + self.data_contents[position + rows:]
|
||||
self.endRemoveRows()
|
||||
self.emit_signals()
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def is_column(cls, index, column_name):
|
||||
return index.column() == cls.columns.index(column_name)
|
||||
|
||||
def user_selected(self, contents=()):
|
||||
return self.filter(lambda x: x.states.checked == Qt.Checked, contents)
|
||||
|
||||
def filter(self, f, contents=()):
|
||||
contents = contents or self.data_contents
|
||||
return filter(f, contents)
|
||||
|
||||
def selected_check(self, f, selected=()):
|
||||
selected = selected or set(self.user_selected())
|
||||
return bool(selected) and all(f(item) for item in selected) # selected.issubset(self.filter(f))
|
||||
|
||||
def get_row_data(self, index):
|
||||
row = index.row()
|
||||
if row == -1:
|
||||
return None
|
||||
try:
|
||||
return self.data_contents[row]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
def get_row_index(self, row_data):
|
||||
try:
|
||||
return self.data_contents.index(row_data)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def get_row_by_attr(self, attr, value):
|
||||
try:
|
||||
return next(filter(lambda x: getattr(x, attr, None) == value, self.data_contents))
|
||||
except StopIteration:
|
||||
return None
|
||||
|
||||
def rowCount(self, n=None):
|
||||
return len(self.data_contents)
|
||||
|
||||
def columnCount(self, n=None):
|
||||
return len(self.headers)
|
||||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return self.headers[section]
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
row = index.row()
|
||||
col = index.column()
|
||||
if role == Qt.DisplayRole or role == Qt.EditRole: # Separate editRole in case of editing non-text
|
||||
item = self.data_contents[row][col]
|
||||
return str(self.formatter.format_view(self.columns[col], item)) if item is not None else ""
|
||||
elif role == ModelDataRole:
|
||||
return self.data_contents[row][col]
|
||||
|
||||
elif role == Qt.BackgroundRole:
|
||||
state = self.data_contents[row].states[col]
|
||||
if state is None:
|
||||
state = missing_state
|
||||
elif isinstance(state, bool):
|
||||
state = true_state if state else false_state
|
||||
return state.brush
|
||||
|
||||
elif role == Qt.CheckStateRole and col == 0:
|
||||
return self.data_contents[row].states.checked
|
||||
|
||||
if role == QtCore.Qt.TextAlignmentRole and col != 0:
|
||||
return QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter
|
||||
|
||||
def emit_signals(self):
|
||||
selected = set(self.user_selected())
|
||||
|
||||
self.selected_ready_signal.emit(self.selected_check(lambda x: x.states.all_checks, selected))
|
||||
self.selected_takeoff_ready_signal.emit(self.selected_check(takeoff_checks, selected))
|
||||
self.selected_flip_ready_signal.emit(self.selected_check(flip_checks, selected))
|
||||
self.selected_calibrating_signal.emit(self.selected_check(calibrating_check, selected))
|
||||
self.selected_calibration_ready_signal.emit(self.selected_check(calibration_ready_check, selected))
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def setData(self, index, value, role=Qt.EditRole):
|
||||
if not index.isValid():
|
||||
return False
|
||||
|
||||
col = index.column()
|
||||
row = index.row()
|
||||
|
||||
if role == Qt.CheckStateRole:
|
||||
self.data_contents[row].states.checked = value
|
||||
elif role == Qt.EditRole: # For user/outer actions with data, place modifiers applied
|
||||
formatted_value = self.formatter.format_place(self.columns[col], value)
|
||||
if formatted_value is None: # todo use new := syntax
|
||||
return False
|
||||
|
||||
self.data_contents[row][col] = formatted_value
|
||||
|
||||
if col == 0:
|
||||
self.data_contents[row].client.send_message("id", kwargs={"new_id": formatted_value})
|
||||
|
||||
elif role == ModelDataRole: # For inner setting\editing of raw data
|
||||
self.data_contents[row][col] = value
|
||||
elif role == ModelStateRole:
|
||||
self.data_contents[row].states[col] = value
|
||||
else:
|
||||
return False
|
||||
|
||||
self.emit_signals()
|
||||
self.dataChanged.emit(index, index, (role,))
|
||||
return True
|
||||
|
||||
def flags(self, index):
|
||||
roles = Qt.ItemIsSelectable | Qt.ItemIsEnabled
|
||||
if index.column() == 0:
|
||||
roles |= Qt.ItemIsUserCheckable | Qt.ItemIsEditable
|
||||
if self.is_column(index, "config_version"):
|
||||
roles |= Qt.ItemIsDragEnabled | Qt.ItemIsDropEnabled
|
||||
return roles
|
||||
|
||||
def supportedDropActions(self):
|
||||
return Qt.CopyAction | Qt.MoveAction
|
||||
|
||||
def mimeTypes(self):
|
||||
return ['text/uri-list']
|
||||
|
||||
def mimeData(self, indexes):
|
||||
index = indexes[0]
|
||||
if self.is_column(index, "config_version"):
|
||||
return self._config_mime(index)
|
||||
|
||||
return None
|
||||
|
||||
def _config_mime(self, index):
|
||||
mimedata = QtCore.QMimeData()
|
||||
path = os.path.join(QDir.tempPath(), "config_{}.ini".format(
|
||||
self.data_contents[index.row()].copter_id))
|
||||
|
||||
with suppress(OSError): # remove if file exists
|
||||
os.remove(path)
|
||||
|
||||
self.data_contents[index.row()].client.get_file("config/client.ini", path, )
|
||||
mimedata.setData("application/copter_row_info",
|
||||
bytes(self.data_contents[index.row()].copter_id, encoding="UTF-8"))
|
||||
mimedata.setUrls([QUrl.fromLocalFile(path)])
|
||||
|
||||
return mimedata
|
||||
|
||||
def dropMimeData(self, mimedata, action, row, column, index):
|
||||
if action == Qt.IgnoreAction:
|
||||
return True
|
||||
|
||||
if self.is_column(index, "config_version"):
|
||||
if not mimedata.hasUrls():
|
||||
return False
|
||||
if str(mimedata.data("application/copter_row_info")) == self.data_contents[index.row()].copter_id:
|
||||
return False # to protect from dropping to the same cell
|
||||
|
||||
# print(mimedata.hasUrls(), mimedata.urls, mimedata.formats())
|
||||
return self.drop_config(mimedata.urls()[0].toLocalFile(), index.row())
|
||||
|
||||
return True
|
||||
|
||||
def drop_config(self, path, row):
|
||||
if not ConfigManager.config_exists(path):
|
||||
return False
|
||||
config = ConfigManager()
|
||||
config.load_only_config(path)
|
||||
config_dict = config.full_dict(include_defaults=False)
|
||||
config_dict.pop("PRIVATE", None)
|
||||
|
||||
self.data_contents[row].client.send_message("config", kwargs={
|
||||
"config": config_dict, "mode": "rewrite"})
|
||||
return False
|
||||
|
||||
# Thread-safe wrappers
|
||||
def add_client(self, **kwargs):
|
||||
default_states = {"checked": 0, "copter_id": True}
|
||||
self.add_client_signal.emit(self.data_model(self.columns, default_states, **kwargs))
|
||||
|
||||
def remove_client_data(self, row_data):
|
||||
self.remove_client_signal.emit(row_data)
|
||||
|
||||
def remove_row(self, row):
|
||||
self.remove_row_signal.emit(row)
|
||||
|
||||
def update_data(self, row, col, data, role=ModelDataRole):
|
||||
self.update_data_signal.emit(row, col, data, role)
|
||||
|
||||
@QtCore.pyqtSlot(int, int, QtCore.QVariant, QtCore.QVariant)
|
||||
def _update_data(self, row, col, value, role=Qt.EditRole):
|
||||
self.setData(self.index(row, col), value, role)
|
||||
|
||||
@QtCore.pyqtSlot(object)
|
||||
def _add_client(self, client):
|
||||
self.insertRows([client])
|
||||
|
||||
@QtCore.pyqtSlot(int) # Probably deprecated now
|
||||
def _remove_row(self, row):
|
||||
self.removeRows(row)
|
||||
|
||||
@QtCore.pyqtSlot(object)
|
||||
def _remove_row_data(self, data):
|
||||
row = self.get_row_index(data)
|
||||
if row is not None:
|
||||
self.removeRows(row)
|
||||
|
||||
|
||||
def check_checklist(copter_item, checklist=()):
|
||||
return all(copter_item.states[col] for col in checklist)
|
||||
|
||||
|
||||
def takeoff_checks(copter_item):
|
||||
checklist = ("battery", "fcu_status", "mode", "selfcheck", "current_position")
|
||||
return check_checklist(copter_item, checklist)
|
||||
|
||||
|
||||
def flip_checks(copter_item):
|
||||
checklist = ("battery", "mode", "current_position")
|
||||
if not check_checklist(copter_item, checklist):
|
||||
return False
|
||||
if copter_item["fcu_status"] != "ACTIVE":
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
# for col in checklist:
|
||||
# if not copter_item.state[col]: # ModelChecks.check(col, copter_item):
|
||||
# return False
|
||||
|
||||
def calibrating_check(copter_item):
|
||||
return copter_item["calibration_status"] == "CALIBRATING"
|
||||
|
||||
|
||||
def calibration_ready_check(copter_item):
|
||||
if not copter_item.states["fcu_status"]: # ModelChecks.check("fcu_status", copter_item):
|
||||
return False
|
||||
return not calibrating_check(copter_item)
|
||||
|
||||
|
||||
class CopterProxyModel(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, parent=None):
|
||||
super(CopterProxyModel, self).__init__(parent)
|
||||
|
||||
@staticmethod
|
||||
def human_sort_prepare(item):
|
||||
if item:
|
||||
item = [int(x) if x.isdigit() else x.lower() for x in re.split('([0-9]+)', str(item))]
|
||||
else:
|
||||
item = []
|
||||
return item
|
||||
|
||||
def lessThan(self, left, right):
|
||||
leftData = self.sourceModel().data(left)
|
||||
rightData = self.sourceModel().data(right)
|
||||
|
||||
return self.human_sort_prepare(leftData) < self.human_sort_prepare(rightData)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import time
|
||||
|
||||
|
||||
def timer():
|
||||
idc = 1001
|
||||
while True:
|
||||
myModel.setData(myModel.index(0, 0), idc)
|
||||
idc += 1
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
app = QtWidgets.QApplication.instance()
|
||||
if app is None:
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
tableView = QtWidgets.QTableView()
|
||||
myModel = CopterDataModel()
|
||||
proxyModel = CopterProxyModel()
|
||||
|
||||
proxyModel.setDynamicSortFilter(True)
|
||||
proxyModel.setSourceModel(myModel)
|
||||
|
||||
tableView.setModel(proxyModel)
|
||||
|
||||
tableView.verticalHeader().hide()
|
||||
tableView.setSortingEnabled(True)
|
||||
|
||||
tableView.show()
|
||||
|
||||
msgs = []
|
||||
msg = "[{}]: Failure: {}".format("FCU connection", "Angular velocities estimation is not available")
|
||||
msgs.append(msg)
|
||||
msg = "[{}]: Failure: {}".format("FCU connection1", "Angular velocities estimation is not available")
|
||||
msgs.append(msg)
|
||||
msg = "[{}]: Failure: {}".format("FCU connection2", "Angular velocities estimation is not available")
|
||||
msgs.append(msg)
|
||||
|
||||
# myModel._add_client(StatedCopterData(copter_id=1000, checked=0, selfcheck=msgs, time_utc=1))
|
||||
# myModel._add_client(StatedCopterData(checked=2, selfcheck="OK", time_utc=2))
|
||||
# myModel._add_client(StatedCopterData(checked=2, selfcheck="not ok", time_utc="no"))
|
||||
myModel.add_client(copter_id=1000, client=None, git_version='11318ca', selfcheck=msgs)
|
||||
# myModel.setData(myModel.index(0, 1), "test")
|
||||
|
||||
# t = threading.Thread(target=timer, daemon=True)
|
||||
# t.start()
|
||||
print(QtCore.QT_VERSION_STR)
|
||||
print(get_git_version())
|
||||
myModel.update_data(0, 3, [1, 2], role=Qt.EditRole)
|
||||
app.exec_()
|
||||
BIN
server/icons/coex_splash.jpg
Normal file
BIN
server/icons/coex_splash.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
BIN
server/icons/image.ico
Normal file
BIN
server/icons/image.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.6 KiB |
431
server/server_gui.py
Normal file
431
server/server_gui.py
Normal file
@@ -0,0 +1,431 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'server_gui.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_MainWindow(object):
|
||||
def setupUi(self, MainWindow):
|
||||
MainWindow.setObjectName("MainWindow")
|
||||
MainWindow.resize(1360, 869)
|
||||
self.centralwidget = QtWidgets.QWidget(MainWindow)
|
||||
self.centralwidget.setEnabled(True)
|
||||
self.centralwidget.setObjectName("centralwidget")
|
||||
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
|
||||
self.gridLayout.setObjectName("gridLayout")
|
||||
self.horizontalLayout = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
|
||||
self.horizontalLayout.setObjectName("horizontalLayout")
|
||||
self.tableView = QtWidgets.QTableView(self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.tableView.sizePolicy().hasHeightForWidth())
|
||||
self.tableView.setSizePolicy(sizePolicy)
|
||||
self.tableView.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
|
||||
self.tableView.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
|
||||
self.tableView.setTextElideMode(QtCore.Qt.ElideMiddle)
|
||||
self.tableView.setSortingEnabled(True)
|
||||
self.tableView.setWordWrap(True)
|
||||
self.tableView.setObjectName("tableView")
|
||||
self.tableView.horizontalHeader().setCascadingSectionResizes(False)
|
||||
self.tableView.horizontalHeader().setDefaultSectionSize(50)
|
||||
self.tableView.horizontalHeader().setMinimumSectionSize(50)
|
||||
self.tableView.horizontalHeader().setStretchLastSection(True)
|
||||
self.tableView.verticalHeader().setVisible(False)
|
||||
self.horizontalLayout.addWidget(self.tableView)
|
||||
self.verticalLayout = QtWidgets.QVBoxLayout()
|
||||
self.verticalLayout.setSizeConstraint(QtWidgets.QLayout.SetMaximumSize)
|
||||
self.verticalLayout.setObjectName("verticalLayout")
|
||||
self.formLayout = QtWidgets.QFormLayout()
|
||||
self.formLayout.setLabelAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
|
||||
self.formLayout.setFormAlignment(QtCore.Qt.AlignHCenter|QtCore.Qt.AlignTop)
|
||||
self.formLayout.setObjectName("formLayout")
|
||||
self.start_text = QtWidgets.QLabel(self.centralwidget)
|
||||
self.start_text.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.start_text.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
|
||||
self.start_text.setObjectName("start_text")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.start_text)
|
||||
self.start_delay_spin = QtWidgets.QSpinBox(self.centralwidget)
|
||||
self.start_delay_spin.setObjectName("start_delay_spin")
|
||||
self.formLayout.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.start_delay_spin)
|
||||
self.music_delay_spin = QtWidgets.QDoubleSpinBox(self.centralwidget)
|
||||
self.music_delay_spin.setDecimals(1)
|
||||
self.music_delay_spin.setMaximum(1000.0)
|
||||
self.music_delay_spin.setObjectName("music_delay_spin")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.music_delay_spin)
|
||||
self.music_checkbox = QtWidgets.QCheckBox(self.centralwidget)
|
||||
sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
|
||||
sizePolicy.setHorizontalStretch(0)
|
||||
sizePolicy.setVerticalStretch(0)
|
||||
sizePolicy.setHeightForWidth(self.music_checkbox.sizePolicy().hasHeightForWidth())
|
||||
self.music_checkbox.setSizePolicy(sizePolicy)
|
||||
self.music_checkbox.setFocusPolicy(QtCore.Qt.NoFocus)
|
||||
self.music_checkbox.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu)
|
||||
self.music_checkbox.setLayoutDirection(QtCore.Qt.LeftToRight)
|
||||
self.music_checkbox.setAutoFillBackground(False)
|
||||
self.music_checkbox.setText("")
|
||||
self.music_checkbox.setChecked(False)
|
||||
self.music_checkbox.setObjectName("music_checkbox")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.music_checkbox)
|
||||
self.music_text = QtWidgets.QLabel(self.centralwidget)
|
||||
self.music_text.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.music_text.setObjectName("music_text")
|
||||
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.music_text)
|
||||
self.music_play_text = QtWidgets.QLabel(self.centralwidget)
|
||||
self.music_play_text.setLayoutDirection(QtCore.Qt.RightToLeft)
|
||||
self.music_play_text.setObjectName("music_play_text")
|
||||
self.formLayout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.music_play_text)
|
||||
self.verticalLayout.addLayout(self.formLayout)
|
||||
self.line = QtWidgets.QFrame(self.centralwidget)
|
||||
self.line.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line.setObjectName("line")
|
||||
self.verticalLayout.addWidget(self.line)
|
||||
self.formLayout_2 = QtWidgets.QFormLayout()
|
||||
self.formLayout_2.setLabelAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_2.setFormAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_2.setObjectName("formLayout_2")
|
||||
self.check_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.check_button.setEnabled(True)
|
||||
self.check_button.setObjectName("check_button")
|
||||
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.check_button)
|
||||
self.start_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.start_button.setEnabled(True)
|
||||
self.start_button.setFlat(False)
|
||||
self.start_button.setObjectName("start_button")
|
||||
self.formLayout_2.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.start_button)
|
||||
self.pause_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.pause_button.setObjectName("pause_button")
|
||||
self.formLayout_2.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.pause_button)
|
||||
self.verticalLayout.addLayout(self.formLayout_2)
|
||||
self.line_5 = QtWidgets.QFrame(self.centralwidget)
|
||||
self.line_5.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line_5.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line_5.setObjectName("line_5")
|
||||
self.verticalLayout.addWidget(self.line_5)
|
||||
self.formLayout_5 = QtWidgets.QFormLayout()
|
||||
self.formLayout_5.setLabelAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_5.setFormAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_5.setContentsMargins(-1, 0, -1, -1)
|
||||
self.formLayout_5.setObjectName("formLayout_5")
|
||||
self.land_selected_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.land_selected_button.setObjectName("land_selected_button")
|
||||
self.formLayout_5.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.land_selected_button)
|
||||
self.land_all_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.land_all_button.setObjectName("land_all_button")
|
||||
self.formLayout_5.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.land_all_button)
|
||||
self.verticalLayout.addLayout(self.formLayout_5)
|
||||
self.line_6 = QtWidgets.QFrame(self.centralwidget)
|
||||
self.line_6.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line_6.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line_6.setObjectName("line_6")
|
||||
self.verticalLayout.addWidget(self.line_6)
|
||||
self.formLayout_7 = QtWidgets.QFormLayout()
|
||||
self.formLayout_7.setFormAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignVCenter)
|
||||
self.formLayout_7.setContentsMargins(-1, 0, -1, -1)
|
||||
self.formLayout_7.setObjectName("formLayout_7")
|
||||
self.visual_land_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.visual_land_button.setObjectName("visual_land_button")
|
||||
self.formLayout_7.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.visual_land_button)
|
||||
self.emergency_land_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.emergency_land_button.setObjectName("emergency_land_button")
|
||||
self.formLayout_7.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.emergency_land_button)
|
||||
self.verticalLayout.addLayout(self.formLayout_7)
|
||||
self.line_2 = QtWidgets.QFrame(self.centralwidget)
|
||||
self.line_2.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line_2.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line_2.setObjectName("line_2")
|
||||
self.verticalLayout.addWidget(self.line_2)
|
||||
self.formLayout_3 = QtWidgets.QFormLayout()
|
||||
self.formLayout_3.setLabelAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_3.setFormAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_3.setObjectName("formLayout_3")
|
||||
self.disarm_all_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.disarm_all_button.setObjectName("disarm_all_button")
|
||||
self.formLayout_3.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.disarm_all_button)
|
||||
self.disarm_selected_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.disarm_selected_button.setObjectName("disarm_selected_button")
|
||||
self.formLayout_3.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.disarm_selected_button)
|
||||
self.verticalLayout.addLayout(self.formLayout_3)
|
||||
self.line_3 = QtWidgets.QFrame(self.centralwidget)
|
||||
self.line_3.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line_3.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line_3.setObjectName("line_3")
|
||||
self.verticalLayout.addWidget(self.line_3)
|
||||
self.formLayout_4 = QtWidgets.QFormLayout()
|
||||
self.formLayout_4.setLabelAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_4.setFormAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_4.setObjectName("formLayout_4")
|
||||
self.leds_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.leds_button.setObjectName("leds_button")
|
||||
self.formLayout_4.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.leds_button)
|
||||
self.takeoff_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.takeoff_button.setEnabled(True)
|
||||
self.takeoff_button.setObjectName("takeoff_button")
|
||||
self.formLayout_4.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.takeoff_button)
|
||||
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
|
||||
self.horizontalLayout_2.setContentsMargins(-1, 0, -1, -1)
|
||||
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
|
||||
self.z_checkbox = QtWidgets.QCheckBox(self.centralwidget)
|
||||
self.z_checkbox.setLayoutDirection(QtCore.Qt.LeftToRight)
|
||||
self.z_checkbox.setObjectName("z_checkbox")
|
||||
self.horizontalLayout_2.addWidget(self.z_checkbox)
|
||||
self.z_spin = QtWidgets.QDoubleSpinBox(self.centralwidget)
|
||||
self.z_spin.setLayoutDirection(QtCore.Qt.LeftToRight)
|
||||
self.z_spin.setDecimals(1)
|
||||
self.z_spin.setProperty("value", 1.0)
|
||||
self.z_spin.setObjectName("z_spin")
|
||||
self.horizontalLayout_2.addWidget(self.z_spin)
|
||||
self.formLayout_4.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2)
|
||||
self.flip_button = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.flip_button.setObjectName("flip_button")
|
||||
self.formLayout_4.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.flip_button)
|
||||
self.verticalLayout.addLayout(self.formLayout_4)
|
||||
self.line_4 = QtWidgets.QFrame(self.centralwidget)
|
||||
self.line_4.setFrameShape(QtWidgets.QFrame.HLine)
|
||||
self.line_4.setFrameShadow(QtWidgets.QFrame.Sunken)
|
||||
self.line_4.setObjectName("line_4")
|
||||
self.verticalLayout.addWidget(self.line_4)
|
||||
self.formLayout_6 = QtWidgets.QFormLayout()
|
||||
self.formLayout_6.setLabelAlignment(QtCore.Qt.AlignCenter)
|
||||
self.formLayout_6.setFormAlignment(QtCore.Qt.AlignBottom|QtCore.Qt.AlignHCenter)
|
||||
self.formLayout_6.setObjectName("formLayout_6")
|
||||
self.reboot_fcu = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.reboot_fcu.setObjectName("reboot_fcu")
|
||||
self.formLayout_6.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.reboot_fcu)
|
||||
self.calibrate_gyro = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.calibrate_gyro.setObjectName("calibrate_gyro")
|
||||
self.formLayout_6.setWidget(1, QtWidgets.QFormLayout.FieldRole, self.calibrate_gyro)
|
||||
self.calibrate_level = QtWidgets.QPushButton(self.centralwidget)
|
||||
self.calibrate_level.setObjectName("calibrate_level")
|
||||
self.formLayout_6.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.calibrate_level)
|
||||
self.verticalLayout.addLayout(self.formLayout_6)
|
||||
self.horizontalLayout.addLayout(self.verticalLayout)
|
||||
self.horizontalLayout.setStretch(0, 1)
|
||||
self.gridLayout.addLayout(self.horizontalLayout, 0, 0, 1, 1)
|
||||
MainWindow.setCentralWidget(self.centralwidget)
|
||||
self.menubar = QtWidgets.QMenuBar(MainWindow)
|
||||
self.menubar.setGeometry(QtCore.QRect(0, 0, 1360, 22))
|
||||
self.menubar.setObjectName("menubar")
|
||||
self.menuOptions = QtWidgets.QMenu(self.menubar)
|
||||
self.menuOptions.setObjectName("menuOptions")
|
||||
self.menuMusic_2 = QtWidgets.QMenu(self.menuOptions)
|
||||
self.menuMusic_2.setObjectName("menuMusic_2")
|
||||
self.menuTable = QtWidgets.QMenu(self.menubar)
|
||||
self.menuTable.setObjectName("menuTable")
|
||||
self.menuDrone_2 = QtWidgets.QMenu(self.menubar)
|
||||
self.menuDrone_2.setObjectName("menuDrone_2")
|
||||
self.menuSend = QtWidgets.QMenu(self.menuDrone_2)
|
||||
self.menuSend.setObjectName("menuSend")
|
||||
self.menuRestart = QtWidgets.QMenu(self.menuDrone_2)
|
||||
self.menuRestart.setObjectName("menuRestart")
|
||||
self.menuDeveloper_mode = QtWidgets.QMenu(self.menuDrone_2)
|
||||
self.menuDeveloper_mode.setObjectName("menuDeveloper_mode")
|
||||
MainWindow.setMenuBar(self.menubar)
|
||||
self.action_send_animations = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_animations.setObjectName("action_send_animations")
|
||||
self.action_send_configurations = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_configurations.setObjectName("action_send_configurations")
|
||||
self.action_send_aruco_map = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_aruco_map.setObjectName("action_send_aruco_map")
|
||||
self.action_update_client_repo = QtWidgets.QAction(MainWindow)
|
||||
self.action_update_client_repo.setObjectName("action_update_client_repo")
|
||||
self.actionSend_launch_file_for_clever = QtWidgets.QAction(MainWindow)
|
||||
self.actionSend_launch_file_for_clever.setObjectName("actionSend_launch_file_for_clever")
|
||||
self.action_send_launch_file = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_launch_file.setObjectName("action_send_launch_file")
|
||||
self.action_restart_clever = QtWidgets.QAction(MainWindow)
|
||||
self.action_restart_clever.setObjectName("action_restart_clever")
|
||||
self.action_restart_clever_show = QtWidgets.QAction(MainWindow)
|
||||
self.action_restart_clever_show.setObjectName("action_restart_clever_show")
|
||||
self.action_select_all_rows = QtWidgets.QAction(MainWindow)
|
||||
self.action_select_all_rows.setObjectName("action_select_all_rows")
|
||||
self.action_set_start_to_current_position = QtWidgets.QAction(MainWindow)
|
||||
self.action_set_start_to_current_position.setObjectName("action_set_start_to_current_position")
|
||||
self.action_reset_start = QtWidgets.QAction(MainWindow)
|
||||
self.action_reset_start.setObjectName("action_reset_start")
|
||||
self.action_set_z_offset_to_ground = QtWidgets.QAction(MainWindow)
|
||||
self.action_set_z_offset_to_ground.setObjectName("action_set_z_offset_to_ground")
|
||||
self.action_reset_z_offset = QtWidgets.QAction(MainWindow)
|
||||
self.action_reset_z_offset.setObjectName("action_reset_z_offset")
|
||||
self.action_select_music_file = QtWidgets.QAction(MainWindow)
|
||||
self.action_select_music_file.setObjectName("action_select_music_file")
|
||||
self.action_play_music = QtWidgets.QAction(MainWindow)
|
||||
self.action_play_music.setObjectName("action_play_music")
|
||||
self.action_test_music_after = QtWidgets.QAction(MainWindow)
|
||||
self.action_test_music_after.setObjectName("action_test_music_after")
|
||||
self.actionFill = QtWidgets.QAction(MainWindow)
|
||||
self.actionFill.setObjectName("actionFill")
|
||||
self.action_send_any_file = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_any_file.setObjectName("action_send_any_file")
|
||||
self.action_send_any_command = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_any_command.setObjectName("action_send_any_command")
|
||||
self.action_stop_music = QtWidgets.QAction(MainWindow)
|
||||
self.action_stop_music.setObjectName("action_stop_music")
|
||||
self.action_remove_row = QtWidgets.QAction(MainWindow)
|
||||
self.action_remove_row.setObjectName("action_remove_row")
|
||||
self.action_send_calibrations = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_calibrations.setObjectName("action_send_calibrations")
|
||||
self.action_reboot_all = QtWidgets.QAction(MainWindow)
|
||||
self.action_reboot_all.setObjectName("action_reboot_all")
|
||||
self.action_restart_chrony = QtWidgets.QAction(MainWindow)
|
||||
self.action_restart_chrony.setObjectName("action_restart_chrony")
|
||||
self.action_send_fcu_parameters = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_fcu_parameters.setObjectName("action_send_fcu_parameters")
|
||||
self.action_toggle_select = QtWidgets.QAction(MainWindow)
|
||||
self.action_toggle_select.setObjectName("action_toggle_select")
|
||||
self.action_select_all = QtWidgets.QAction(MainWindow)
|
||||
self.action_select_all.setObjectName("action_select_all")
|
||||
self.action_deselect_all = QtWidgets.QAction(MainWindow)
|
||||
self.action_deselect_all.setObjectName("action_deselect_all")
|
||||
self.action_edit_server_config = QtWidgets.QAction(MainWindow)
|
||||
self.action_edit_server_config.setObjectName("action_edit_server_config")
|
||||
self.action_edit_any_config = QtWidgets.QAction(MainWindow)
|
||||
self.action_edit_any_config.setObjectName("action_edit_any_config")
|
||||
self.action_update_server_git = QtWidgets.QAction(MainWindow)
|
||||
self.action_update_server_git.setEnabled(False)
|
||||
self.action_update_server_git.setVisible(False)
|
||||
self.action_update_server_git.setObjectName("action_update_server_git")
|
||||
self.action_retrive_any_file = QtWidgets.QAction(MainWindow)
|
||||
self.action_retrive_any_file.setObjectName("action_retrive_any_file")
|
||||
self.action_restart_server = QtWidgets.QAction(MainWindow)
|
||||
self.action_restart_server.setObjectName("action_restart_server")
|
||||
self.action_configure_columns = QtWidgets.QAction(MainWindow)
|
||||
self.action_configure_columns.setObjectName("action_configure_columns")
|
||||
self.actionSomething = QtWidgets.QAction(MainWindow)
|
||||
self.actionSomething.setObjectName("actionSomething")
|
||||
self.action_send_animation = QtWidgets.QAction(MainWindow)
|
||||
self.action_send_animation.setObjectName("action_send_animation")
|
||||
self.menuMusic_2.addAction(self.action_select_music_file)
|
||||
self.menuMusic_2.addAction(self.action_play_music)
|
||||
self.menuMusic_2.addAction(self.action_stop_music)
|
||||
self.menuOptions.addAction(self.menuMusic_2.menuAction())
|
||||
self.menuOptions.addSeparator()
|
||||
self.menuOptions.addAction(self.action_edit_server_config)
|
||||
self.menuOptions.addAction(self.action_edit_any_config)
|
||||
self.menuOptions.addSeparator()
|
||||
self.menuOptions.addAction(self.action_update_server_git)
|
||||
self.menuOptions.addAction(self.action_restart_server)
|
||||
self.menuTable.addAction(self.action_toggle_select)
|
||||
self.menuTable.addAction(self.action_select_all)
|
||||
self.menuTable.addAction(self.action_deselect_all)
|
||||
self.menuTable.addSeparator()
|
||||
self.menuTable.addAction(self.action_remove_row)
|
||||
self.menuTable.addSeparator()
|
||||
self.menuTable.addAction(self.action_configure_columns)
|
||||
self.menuSend.addAction(self.action_send_animations)
|
||||
self.menuSend.addAction(self.action_send_calibrations)
|
||||
self.menuSend.addSeparator()
|
||||
self.menuSend.addAction(self.action_send_aruco_map)
|
||||
self.menuSend.addAction(self.action_send_animation)
|
||||
self.menuSend.addAction(self.action_send_configurations)
|
||||
self.menuSend.addAction(self.action_send_launch_file)
|
||||
self.menuSend.addAction(self.action_send_fcu_parameters)
|
||||
self.menuSend.addSeparator()
|
||||
self.menuSend.addAction(self.action_send_any_file)
|
||||
self.menuSend.addAction(self.action_send_any_command)
|
||||
self.menuRestart.addAction(self.action_restart_chrony)
|
||||
self.menuRestart.addAction(self.action_restart_clever)
|
||||
self.menuRestart.addAction(self.action_restart_clever_show)
|
||||
self.menuRestart.addSeparator()
|
||||
self.menuDeveloper_mode.addAction(self.action_update_client_repo)
|
||||
self.menuDrone_2.addAction(self.menuSend.menuAction())
|
||||
self.menuDrone_2.addAction(self.action_retrive_any_file)
|
||||
self.menuDrone_2.addAction(self.menuRestart.menuAction())
|
||||
self.menuDrone_2.addSeparator()
|
||||
self.menuDrone_2.addAction(self.action_set_start_to_current_position)
|
||||
self.menuDrone_2.addAction(self.action_reset_start)
|
||||
self.menuDrone_2.addAction(self.action_set_z_offset_to_ground)
|
||||
self.menuDrone_2.addAction(self.action_reset_z_offset)
|
||||
self.menuDrone_2.addSeparator()
|
||||
self.menuDrone_2.addAction(self.menuDeveloper_mode.menuAction())
|
||||
self.menuDrone_2.addSeparator()
|
||||
self.menuDrone_2.addAction(self.action_reboot_all)
|
||||
self.menubar.addAction(self.menuDrone_2.menuAction())
|
||||
self.menubar.addAction(self.menuOptions.menuAction())
|
||||
self.menubar.addAction(self.menuTable.menuAction())
|
||||
|
||||
self.retranslateUi(MainWindow)
|
||||
QtCore.QMetaObject.connectSlotsByName(MainWindow)
|
||||
MainWindow.setTabOrder(self.start_delay_spin, self.tableView)
|
||||
|
||||
def retranslateUi(self, MainWindow):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
MainWindow.setWindowTitle(_translate("MainWindow", "Clever Drone Show"))
|
||||
self.start_text.setText(_translate("MainWindow", " Start after"))
|
||||
self.start_delay_spin.setSuffix(_translate("MainWindow", " s"))
|
||||
self.music_delay_spin.setSuffix(_translate("MainWindow", " s"))
|
||||
self.music_text.setText(_translate("MainWindow", " Music after"))
|
||||
self.music_play_text.setText(_translate("MainWindow", " Play music"))
|
||||
self.check_button.setText(_translate("MainWindow", "Preflight check"))
|
||||
self.start_button.setText(_translate("MainWindow", "Start animation"))
|
||||
self.pause_button.setText(_translate("MainWindow", "Pause"))
|
||||
self.land_selected_button.setText(_translate("MainWindow", "Land selected"))
|
||||
self.land_all_button.setText(_translate("MainWindow", "Land ALL"))
|
||||
self.visual_land_button.setText(_translate("MainWindow", "Visual land"))
|
||||
self.emergency_land_button.setText(_translate("MainWindow", "Emergency land"))
|
||||
self.disarm_all_button.setText(_translate("MainWindow", "Disarm ALL"))
|
||||
self.disarm_selected_button.setText(_translate("MainWindow", "Disarm selected"))
|
||||
self.leds_button.setText(_translate("MainWindow", "Test leds"))
|
||||
self.takeoff_button.setText(_translate("MainWindow", "Takeoff"))
|
||||
self.z_checkbox.setText(_translate("MainWindow", " Z ="))
|
||||
self.z_spin.setSuffix(_translate("MainWindow", " m"))
|
||||
self.flip_button.setText(_translate("MainWindow", "Flip"))
|
||||
self.reboot_fcu.setText(_translate("MainWindow", "Reboot FCU"))
|
||||
self.calibrate_gyro.setText(_translate("MainWindow", "Calibrate gyro"))
|
||||
self.calibrate_level.setText(_translate("MainWindow", "Calibrate level"))
|
||||
self.menuOptions.setTitle(_translate("MainWindow", "server"))
|
||||
self.menuMusic_2.setTitle(_translate("MainWindow", "Music"))
|
||||
self.menuTable.setTitle(_translate("MainWindow", "Table"))
|
||||
self.menuDrone_2.setTitle(_translate("MainWindow", "Selected drones"))
|
||||
self.menuSend.setTitle(_translate("MainWindow", "Send"))
|
||||
self.menuRestart.setTitle(_translate("MainWindow", "Restart service"))
|
||||
self.menuDeveloper_mode.setTitle(_translate("MainWindow", "Developer mode"))
|
||||
self.action_send_animations.setText(_translate("MainWindow", "Animations"))
|
||||
self.action_send_configurations.setText(_translate("MainWindow", "Configuration"))
|
||||
self.action_send_aruco_map.setText(_translate("MainWindow", "Aruco map"))
|
||||
self.action_update_client_repo.setText(_translate("MainWindow", "Update clever-show git"))
|
||||
self.actionSend_launch_file_for_clever.setText(_translate("MainWindow", "Send launch file for clever"))
|
||||
self.action_send_launch_file.setText(_translate("MainWindow", "Launch files folder"))
|
||||
self.action_restart_clever.setText(_translate("MainWindow", "clever"))
|
||||
self.action_restart_clever_show.setText(_translate("MainWindow", "clever-show"))
|
||||
self.action_select_all_rows.setText(_translate("MainWindow", "Select all drones"))
|
||||
self.action_select_all_rows.setShortcut(_translate("MainWindow", "Ctrl+A"))
|
||||
self.action_set_start_to_current_position.setText(_translate("MainWindow", "Set start X Y to current position"))
|
||||
self.action_reset_start.setText(_translate("MainWindow", "Reset start position"))
|
||||
self.action_set_z_offset_to_ground.setText(_translate("MainWindow", "Set Z offset to ground"))
|
||||
self.action_reset_z_offset.setText(_translate("MainWindow", "Reset Z offset"))
|
||||
self.action_select_music_file.setText(_translate("MainWindow", "Select file"))
|
||||
self.action_play_music.setText(_translate("MainWindow", "Play"))
|
||||
self.action_test_music_after.setText(_translate("MainWindow", "Test music after"))
|
||||
self.actionFill.setText(_translate("MainWindow", "fill"))
|
||||
self.action_send_any_file.setText(_translate("MainWindow", "File"))
|
||||
self.action_send_any_command.setText(_translate("MainWindow", "Command"))
|
||||
self.action_stop_music.setText(_translate("MainWindow", "Stop"))
|
||||
self.action_remove_row.setText(_translate("MainWindow", "Remove selected drones"))
|
||||
self.action_remove_row.setShortcut(_translate("MainWindow", "Ctrl+Del"))
|
||||
self.action_send_calibrations.setText(_translate("MainWindow", "Camera calibrations"))
|
||||
self.action_reboot_all.setText(_translate("MainWindow", "Reboot"))
|
||||
self.action_restart_chrony.setText(_translate("MainWindow", "chrony"))
|
||||
self.action_send_fcu_parameters.setText(_translate("MainWindow", "FCU parameters file"))
|
||||
self.action_toggle_select.setText(_translate("MainWindow", "Toggle select"))
|
||||
self.action_toggle_select.setShortcut(_translate("MainWindow", "Ctrl+A"))
|
||||
self.action_select_all.setText(_translate("MainWindow", "Select all"))
|
||||
self.action_select_all.setShortcut(_translate("MainWindow", "Shift+A"))
|
||||
self.action_deselect_all.setText(_translate("MainWindow", "Deselect all"))
|
||||
self.action_deselect_all.setShortcut(_translate("MainWindow", "Alt+A"))
|
||||
self.action_edit_server_config.setText(_translate("MainWindow", "Edit server config"))
|
||||
self.action_edit_any_config.setText(_translate("MainWindow", "Edit any config"))
|
||||
self.action_update_server_git.setText(_translate("MainWindow", "Update server git"))
|
||||
self.action_retrive_any_file.setText(_translate("MainWindow", "Retrive file"))
|
||||
self.action_restart_server.setText(_translate("MainWindow", "Restart server"))
|
||||
self.action_configure_columns.setText(_translate("MainWindow", "Configure columns"))
|
||||
self.actionSomething.setText(_translate("MainWindow", "something"))
|
||||
self.action_send_animation.setText(_translate("MainWindow", "Animation"))
|
||||
724
server/server_gui.ui
Normal file
724
server/server_gui.ui
Normal file
@@ -0,0 +1,724 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1360</width>
|
||||
<height>869</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Clever Drone Show</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QTableView" name="tableView">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectItems</enum>
|
||||
</property>
|
||||
<property name="textElideMode">
|
||||
<enum>Qt::ElideMiddle</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderCascadingSectionResizes">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderDefaultSectionSize">
|
||||
<number>50</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderMinimumSectionSize">
|
||||
<number>50</number>
|
||||
</attribute>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="sizeConstraint">
|
||||
<enum>QLayout::SetMaximumSize</enum>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignHCenter|Qt::AlignTop</set>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="start_text">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Start after</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="start_delay_spin">
|
||||
<property name="suffix">
|
||||
<string> s</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="music_delay_spin">
|
||||
<property name="suffix">
|
||||
<string> s</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>1000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="music_checkbox">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::NoFocus</enum>
|
||||
</property>
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::DefaultContextMenu</enum>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="music_text">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Music after</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="music_play_text">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::RightToLeft</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Play music</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="check_button">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preflight check</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="start_button">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Start animation</string>
|
||||
</property>
|
||||
<property name="flat">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="pause_button">
|
||||
<property name="text">
|
||||
<string>Pause</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_5">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_5">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="land_selected_button">
|
||||
<property name="text">
|
||||
<string>Land selected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="land_all_button">
|
||||
<property name="text">
|
||||
<string>Land ALL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_6">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_7">
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="visual_land_button">
|
||||
<property name="text">
|
||||
<string>Visual land</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="emergency_land_button">
|
||||
<property name="text">
|
||||
<string>Emergency land</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_3">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="disarm_all_button">
|
||||
<property name="text">
|
||||
<string>Disarm ALL</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="disarm_selected_button">
|
||||
<property name="text">
|
||||
<string>Disarm selected</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_4">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="leds_button">
|
||||
<property name="text">
|
||||
<string>Test leds</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="takeoff_button">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Takeoff</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="z_checkbox">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string> Z =</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDoubleSpinBox" name="z_spin">
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> m</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QPushButton" name="flip_button">
|
||||
<property name="text">
|
||||
<string>Flip</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Line" name="line_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout_6">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="formAlignment">
|
||||
<set>Qt::AlignBottom|Qt::AlignHCenter</set>
|
||||
</property>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="reboot_fcu">
|
||||
<property name="text">
|
||||
<string>Reboot FCU</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="calibrate_gyro">
|
||||
<property name="text">
|
||||
<string>Calibrate gyro</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QPushButton" name="calibrate_level">
|
||||
<property name="text">
|
||||
<string>Calibrate level</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1360</width>
|
||||
<height>22</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuOptions">
|
||||
<property name="title">
|
||||
<string>Server</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuMusic_2">
|
||||
<property name="title">
|
||||
<string>Music</string>
|
||||
</property>
|
||||
<addaction name="action_select_music_file"/>
|
||||
<addaction name="action_play_music"/>
|
||||
<addaction name="action_stop_music"/>
|
||||
</widget>
|
||||
<addaction name="menuMusic_2"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_edit_server_config"/>
|
||||
<addaction name="action_edit_any_config"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_update_server_git"/>
|
||||
<addaction name="action_restart_server"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTable">
|
||||
<property name="title">
|
||||
<string>Table</string>
|
||||
</property>
|
||||
<addaction name="action_toggle_select"/>
|
||||
<addaction name="action_select_all"/>
|
||||
<addaction name="action_deselect_all"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_remove_row"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_configure_columns"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuDrone_2">
|
||||
<property name="title">
|
||||
<string>Selected drones</string>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuSend">
|
||||
<property name="title">
|
||||
<string>Send</string>
|
||||
</property>
|
||||
<addaction name="action_send_animations"/>
|
||||
<addaction name="action_send_calibrations"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_send_aruco_map"/>
|
||||
<addaction name="action_send_animation"/>
|
||||
<addaction name="action_send_configurations"/>
|
||||
<addaction name="action_send_launch_file"/>
|
||||
<addaction name="action_send_fcu_parameters"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_send_any_file"/>
|
||||
<addaction name="action_send_any_command"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuRestart">
|
||||
<property name="title">
|
||||
<string>Restart service</string>
|
||||
</property>
|
||||
<addaction name="action_restart_chrony"/>
|
||||
<addaction name="action_restart_clever"/>
|
||||
<addaction name="action_restart_clever_show"/>
|
||||
<addaction name="separator"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuDeveloper_mode">
|
||||
<property name="title">
|
||||
<string>Developer mode</string>
|
||||
</property>
|
||||
<addaction name="action_update_client_repo"/>
|
||||
</widget>
|
||||
<addaction name="menuSend"/>
|
||||
<addaction name="action_retrive_any_file"/>
|
||||
<addaction name="menuRestart"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_set_start_to_current_position"/>
|
||||
<addaction name="action_reset_start"/>
|
||||
<addaction name="action_set_z_offset_to_ground"/>
|
||||
<addaction name="action_reset_z_offset"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="menuDeveloper_mode"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="action_reboot_all"/>
|
||||
</widget>
|
||||
<addaction name="menuDrone_2"/>
|
||||
<addaction name="menuOptions"/>
|
||||
<addaction name="menuTable"/>
|
||||
</widget>
|
||||
<action name="action_send_animations">
|
||||
<property name="text">
|
||||
<string>Animations</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_configurations">
|
||||
<property name="text">
|
||||
<string>Configuration</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_aruco_map">
|
||||
<property name="text">
|
||||
<string>Aruco map</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_update_client_repo">
|
||||
<property name="text">
|
||||
<string>Update clever-show git</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSend_launch_file_for_clever">
|
||||
<property name="text">
|
||||
<string>Send launch file for clever</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_launch_file">
|
||||
<property name="text">
|
||||
<string>Launch files folder</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_restart_clever">
|
||||
<property name="text">
|
||||
<string>clever</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_restart_clever_show">
|
||||
<property name="text">
|
||||
<string>clever-show</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_select_all_rows">
|
||||
<property name="text">
|
||||
<string>Select all drones</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_set_start_to_current_position">
|
||||
<property name="text">
|
||||
<string>Set start X Y to current position</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_reset_start">
|
||||
<property name="text">
|
||||
<string>Reset start position</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_set_z_offset_to_ground">
|
||||
<property name="text">
|
||||
<string>Set Z offset to ground</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_reset_z_offset">
|
||||
<property name="text">
|
||||
<string>Reset Z offset</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_select_music_file">
|
||||
<property name="text">
|
||||
<string>Select file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_play_music">
|
||||
<property name="text">
|
||||
<string>Play</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_test_music_after">
|
||||
<property name="text">
|
||||
<string>Test music after</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionFill">
|
||||
<property name="text">
|
||||
<string>fill</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_any_file">
|
||||
<property name="text">
|
||||
<string>File</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_any_command">
|
||||
<property name="text">
|
||||
<string>Command</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_stop_music">
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_remove_row">
|
||||
<property name="text">
|
||||
<string>Remove selected drones</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+Del</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_calibrations">
|
||||
<property name="text">
|
||||
<string>Camera calibrations</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_reboot_all">
|
||||
<property name="text">
|
||||
<string>Reboot</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_restart_chrony">
|
||||
<property name="text">
|
||||
<string>chrony</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_fcu_parameters">
|
||||
<property name="text">
|
||||
<string>FCU parameters file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_toggle_select">
|
||||
<property name="text">
|
||||
<string>Toggle select</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_select_all">
|
||||
<property name="text">
|
||||
<string>Select all</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Shift+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_deselect_all">
|
||||
<property name="text">
|
||||
<string>Deselect all</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Alt+A</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_edit_server_config">
|
||||
<property name="text">
|
||||
<string>Edit server config</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_edit_any_config">
|
||||
<property name="text">
|
||||
<string>Edit any config</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_update_server_git">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update server git</string>
|
||||
</property>
|
||||
<property name="visible">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_retrive_any_file">
|
||||
<property name="text">
|
||||
<string>Retrive file</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_restart_server">
|
||||
<property name="text">
|
||||
<string>Restart server</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_configure_columns">
|
||||
<property name="text">
|
||||
<string>Configure columns</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionSomething">
|
||||
<property name="text">
|
||||
<string>something</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_send_animation">
|
||||
<property name="text">
|
||||
<string>Animation</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>start_delay_spin</tabstop>
|
||||
<tabstop>tableView</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
73
server/visual_land.py
Normal file
73
server/visual_land.py
Normal file
@@ -0,0 +1,73 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Form implementation generated from reading ui file 'visual_land.ui'
|
||||
#
|
||||
# Created by: PyQt5 UI code generator 5.13.0
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class Ui_Dialog(object):
|
||||
def setupUi(self, Dialog):
|
||||
Dialog.setObjectName("Dialog")
|
||||
Dialog.resize(746, 620)
|
||||
Dialog.setStyleSheet("")
|
||||
self.two_button = QtWidgets.QPushButton(Dialog)
|
||||
self.two_button.setGeometry(QtCore.QRect(420, 120, 231, 171))
|
||||
self.two_button.setSizeIncrement(QtCore.QSize(16, 16))
|
||||
self.two_button.setStyleSheet("QPushButton{\n"
|
||||
"color: white;\n"
|
||||
"font-weight: 600;\n"
|
||||
"font-size: 25pt;\n"
|
||||
"background-color: red;\n"
|
||||
"}")
|
||||
self.two_button.setObjectName("two_button")
|
||||
self.label = QtWidgets.QLabel(Dialog)
|
||||
self.label.setGeometry(QtCore.QRect(60, 30, 631, 51))
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(20)
|
||||
self.label.setFont(font)
|
||||
self.label.setLayoutDirection(QtCore.Qt.LeftToRight)
|
||||
self.label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
self.label.setObjectName("label")
|
||||
self.one_button = QtWidgets.QPushButton(Dialog)
|
||||
self.one_button.setGeometry(QtCore.QRect(90, 120, 231, 171))
|
||||
self.one_button.setSizeIncrement(QtCore.QSize(16, 16))
|
||||
self.one_button.setStyleSheet("QPushButton{\n"
|
||||
"color: white;\n"
|
||||
"font-weight: 600;\n"
|
||||
"font-size: 25pt;\n"
|
||||
"background-color: green;\n"
|
||||
"}")
|
||||
self.one_button.setObjectName("one_button")
|
||||
self.land_emergency_button = QtWidgets.QPushButton(Dialog)
|
||||
self.land_emergency_button.setGeometry(QtCore.QRect(90, 340, 561, 81))
|
||||
self.land_emergency_button.setStyleSheet("QPushButton{\n"
|
||||
"font-weight: 600;\n"
|
||||
"font-size: 25pt;\n"
|
||||
"background-color: white;\n"
|
||||
"}")
|
||||
self.land_emergency_button.setObjectName("land_emergency_button")
|
||||
self.disarm_emergency_button = QtWidgets.QPushButton(Dialog)
|
||||
self.disarm_emergency_button.setGeometry(QtCore.QRect(90, 460, 561, 81))
|
||||
self.disarm_emergency_button.setStyleSheet("QPushButton{\n"
|
||||
"font-weight: 600;\n"
|
||||
"font-size: 25pt;\n"
|
||||
"background-color: white;\n"
|
||||
"}")
|
||||
self.disarm_emergency_button.setObjectName("disarm_emergency_button")
|
||||
|
||||
self.retranslateUi(Dialog)
|
||||
QtCore.QMetaObject.connectSlotsByName(Dialog)
|
||||
|
||||
def retranslateUi(self, Dialog):
|
||||
_translate = QtCore.QCoreApplication.translate
|
||||
Dialog.setWindowTitle(_translate("Dialog", "Visual land"))
|
||||
self.two_button.setText(_translate("Dialog", "2"))
|
||||
self.label.setText(_translate("Dialog", "Select the group with the defective copter"))
|
||||
self.one_button.setText(_translate("Dialog", "1"))
|
||||
self.land_emergency_button.setText(_translate("Dialog", "Land"))
|
||||
self.disarm_emergency_button.setText(_translate("Dialog", "Disarm"))
|
||||
140
server/visual_land.ui
Normal file
140
server/visual_land.ui
Normal file
@@ -0,0 +1,140 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Dialog</class>
|
||||
<widget class="QDialog" name="Dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>746</width>
|
||||
<height>620</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Visual land</string>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<widget class="QPushButton" name="two_button">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>420</x>
|
||||
<y>120</y>
|
||||
<width>231</width>
|
||||
<height>171</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizeIncrement">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton{
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 25pt;
|
||||
background-color: red;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>2</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>60</x>
|
||||
<y>30</y>
|
||||
<width>631</width>
|
||||
<height>51</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>20</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="layoutDirection">
|
||||
<enum>Qt::LeftToRight</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Select the group with the defective copter</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="one_button">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>90</x>
|
||||
<y>120</y>
|
||||
<width>231</width>
|
||||
<height>171</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizeIncrement">
|
||||
<size>
|
||||
<width>16</width>
|
||||
<height>16</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton{
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
font-size: 25pt;
|
||||
background-color: green;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>1</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="land_emergency_button">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>90</x>
|
||||
<y>340</y>
|
||||
<width>561</width>
|
||||
<height>81</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton{
|
||||
font-weight: 600;
|
||||
font-size: 25pt;
|
||||
background-color: white;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Land</string>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="disarm_emergency_button">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>90</x>
|
||||
<y>460</y>
|
||||
<width>561</width>
|
||||
<height>81</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">QPushButton{
|
||||
font-weight: 600;
|
||||
font-size: 25pt;
|
||||
background-color: white;
|
||||
}</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disarm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
114
server/visual_land_dialog.py
Normal file
114
server/visual_land_dialog.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
import visual_land
|
||||
import math
|
||||
import logging
|
||||
import sys
|
||||
from functools import partial
|
||||
|
||||
from lib import b_partial
|
||||
|
||||
# TODO: previous step and reset
|
||||
class VisualLandDialog(QtWidgets.QDialog):
|
||||
def __init__(self, model):
|
||||
super(VisualLandDialog, self).__init__()
|
||||
|
||||
self.ui = visual_land.Ui_Dialog()
|
||||
self.setupUi()
|
||||
|
||||
self.model = model
|
||||
self.row_min = 0
|
||||
self.row_max = self.model.rowCount() - 1
|
||||
self._finished = False
|
||||
|
||||
def setupUi(self):
|
||||
self.ui.setupUi(self)
|
||||
self.ui.one_button.clicked.connect(partial(self.selection_choice, 1))
|
||||
self.ui.two_button.clicked.connect(partial(self.selection_choice, 2))
|
||||
self.ui.land_emergency_button.clicked.connect(b_partial(self.send_to_selected, "land"))
|
||||
self.ui.disarm_emergency_button.clicked.connect(b_partial(self.send_to_selected, "disarm"))
|
||||
|
||||
self.ui.one_button.setShortcut(QKeySequence("1"))
|
||||
self.ui.two_button.setShortcut(QKeySequence("2"))
|
||||
self.ui.land_emergency_button.setShortcut(QKeySequence("L"))
|
||||
self.ui.disarm_emergency_button.setShortcut(QKeySequence("D"))
|
||||
|
||||
@property
|
||||
def row_mid(self):
|
||||
return int(math.ceil((self.row_min + self.row_max) / 2.0))
|
||||
|
||||
def send_to_row(self, row, message, args=(), kwargs=None):
|
||||
logging.debug(f"Send {message}: {args}, {kwargs} to {row}")
|
||||
self.model.data_contents[row].client.send_message(message, args=args, kwargs=kwargs)
|
||||
# test[row] = args, kwargs # for testing
|
||||
# print(test)
|
||||
|
||||
def clear_leds(self, rows):
|
||||
for row in rows:
|
||||
self.send_to_row(row, "led_fill")
|
||||
|
||||
def start(self):
|
||||
self.show()
|
||||
self.send_led_indication()
|
||||
|
||||
self.exec()
|
||||
|
||||
def send_led_indication(self):
|
||||
for row in range(self.row_min, self.row_mid):
|
||||
self.send_to_row(row, "led_fill", kwargs={"green": 255})
|
||||
|
||||
for row in range(self.row_mid, self.row_max + 1):
|
||||
self.send_to_row(row, "led_fill", kwargs={"red": 255})
|
||||
|
||||
@pyqtSlot()
|
||||
def selection_choice(self, choice):
|
||||
if self.row_min == self.row_max:
|
||||
# self.ui.one_button.setDisabled(True) # maybe?
|
||||
# self.ui.two_button.setDisabled(True)
|
||||
self.send_to_selected("land")
|
||||
return
|
||||
|
||||
if choice == 1:
|
||||
to_clear = range(self.row_mid, self.row_max + 1)
|
||||
self.row_max = self.row_mid - 1
|
||||
|
||||
elif choice == 2:
|
||||
to_clear = range(self.row_min, self.row_mid)
|
||||
self.row_min = self.row_mid
|
||||
|
||||
else:
|
||||
return
|
||||
|
||||
self.clear_leds(to_clear)
|
||||
self.send_led_indication()
|
||||
|
||||
@pyqtSlot()
|
||||
def send_to_selected(self, message, args=(), kwargs=None):
|
||||
for row in range(self.row_min, self.row_max + 1):
|
||||
self.send_to_row(row, message, args, kwargs)
|
||||
|
||||
self._finished = True
|
||||
self.close()
|
||||
|
||||
def closeEvent(self, event):
|
||||
if not self._finished:
|
||||
self.clear_leds(range(self.row_min, self.row_max + 1))
|
||||
|
||||
event.accept()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
import copter_table_models
|
||||
model = copter_table_models.CopterDataModel()
|
||||
for i in range(10):
|
||||
model.add_client()
|
||||
|
||||
dialog = VisualLandDialog(model)
|
||||
test = list(range(10))
|
||||
|
||||
dialog.start()
|
||||
Reference in New Issue
Block a user