First skeleton of a very basic GUI

This adds a few .ui files (created with Qt designer), and a
skeleton to create a basic GUI out of them. Only functionality
so far is the selection of a device.
This commit is contained in:
Joerg Wunsch
2024-03-23 23:49:58 +01:00
parent 9c0623bf1c
commit fef43111e3
4 changed files with 603 additions and 0 deletions

91
src/python/about.ui Normal file
View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>About</class>
<widget class="QDialog" name="About">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
</rect>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>true</bool>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>30</x>
<y>240</y>
<width>341</width>
<height>32</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QTextBrowser" name="textBrowser">
<property name="geometry">
<rect>
<x>10</x>
<y>20</y>
<width>381</width>
<height>211</height>
</rect>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;This is a GUI demonstrator for the SWIG_Avrdude Python bindings.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>About</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>About</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

175
src/python/adgui.py Executable file
View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python
import sys
import os
import pathlib
import re
builddir = None
if os.name == 'posix':
# Linux, *BSD, MacOS
sysname = os.uname()[0].lower()
builddir = f'build_{sysname}/src'
elif os.name == 'nt':
# Windows
for candidate in ['build_msvc/src', 'build_mingw64/src']:
if os.path.exists(candidate):
builddir = candidate
os.add_dll_directory(os.path.realpath(candidate))
break
if builddir == None:
print("Cannot determine build directory, module loading might fail.", file=sys.stderr)
else:
sys.path.append(builddir)
sys.path.append(builddir + '/python')
import swig_avrdude as ad
def avrdude_init():
ad.init_config()
found = False
for d in [builddir, "/etc", "/usr/local/etc"]:
p = pathlib.Path(d + "/avrdude.conf")
if p.is_file():
ad.read_config(d + "/avrdude.conf")
return (True, f"Found avrdude.conf in {d}\n")
return (False, "Sorry, no avrdude.conf could be found.\n")
def classify_devices():
result = {
'at90': [],
'attiny': [],
'atmega': [],
'atxmega': [],
'avr_de': [],
'other': []
}
avr_de_re = re.compile('AVR\d+[DE][A-Z]\d+')
part = ad.lfirst(ad.cvar.part_list)
while part:
p = ad.ldata_avrpart(part)
part = ad.lnext(part)
if not p.id.startswith('.'):
if p.desc.startswith('AT90'):
result['at90'].append(p.desc)
elif p.desc.startswith('ATtiny'):
result['attiny'].append(p.desc)
elif p.desc.startswith('ATmega'):
result['atmega'].append(p.desc)
elif p.desc.startswith('ATxmega'):
result['atxmega'].append(p.desc)
elif avr_de_re.match(p.desc):
result['avr_de'].append(p.desc)
else:
result['other'].append(p.desc)
return result
from PySide2.QtWidgets import *
from PySide2.QtCore import QFile, QIODevice
from ui_adgui import Ui_MainWindow
from ui_about import Ui_About
from ui_device import Ui_Device
class About(QDialog):
def __init__(self):
super().__init__()
self.ui = Ui_About()
self.ui.setupUi(self)
class Device(QDialog):
def __init__(self):
super().__init__()
self.ui = Ui_Device()
self.ui.setupUi(self)
class MainWindow(QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
class adgui():
def __init__(self, argv):
self.logstring = "Welcome to AVRDUDE!\n"
self.app = QApplication(sys.argv)
self.window = MainWindow()
self.window.show()
self.about = About()
self.device = Device()
self.window.ui.actionAbout.triggered.connect(self.about.show)
self.window.ui.actionDevice.triggered.connect(self.device.show)
self.window.ui.loggingArea.setPlainText(self.logstring)
(success, message) = avrdude_init()
self.initialized = success
self.logstring += message
self.window.ui.loggingArea.setPlainText(self.logstring)
if not success:
self.window.ui.actionDevice.setEnabled(False)
self.window.ui.actionProgrammer.setEnabled(False)
# essentially, only Exit and Help work anymore
else:
self.devices = classify_devices()
self.update_device_cb()
self.device.ui.at90.stateChanged.connect(self.update_device_cb)
self.device.ui.attiny.stateChanged.connect(self.update_device_cb)
self.device.ui.atmega.stateChanged.connect(self.update_device_cb)
self.device.ui.atxmega.stateChanged.connect(self.update_device_cb)
self.device.ui.avr_de.stateChanged.connect(self.update_device_cb)
self.device.ui.other.stateChanged.connect(self.update_device_cb)
# rough equivalent of avrdude_message2()
# first argument is either "stdout" or "stderr"
#
# install callback with ad.set_msg_callback(msg_callback)
def msg_callback(self, target: str, lno: int, fname: str, func: str,
msgmode: int, msglvl: int, msg: str):
if ad.cvar.verbose >= msglvl:
s = ""
if msgmode & ad.MSG2_PROGNAME:
s += ad.cvar.progname
if ad.cvar.verbose >= ad.MSG_NOTICE and (msgmode & ad.MSG2_FUNCTION) != 0:
s += " " + func + "()"
if ad.cvar.verbose >= ad.MSG_DEBUG and (msgmode & ad.MSG2_FILELINE) != 0:
n = os.path.basename(fname)
s += f" [{n}:{lno}]"
if (msgmode & ad.MSG2_TYPE) != 0:
s += " " + message_type(msglvl)
s += ": "
elif (msgmode & ad.MSG2_INDENT1) != 0:
s = (len(ad.cvar.progname) + 1) * ' '
elif (msgmode & ad.MSG2_INDENT2) != 0:
s = (len(ad.cvar.progname) + 2) * ' '
s += msg
try:
self.logstring += s
except UnicodeDecodeError:
self.logstring += "message contained invalid characters\n"
self.window.ui.loggingArea.setPlainText(self.logstring)
def update_device_cb(self):
fams = list(self.devices.keys())
#fams.sort()
self.device.ui.devices.clear()
for f in fams:
obj = eval('self.device.ui.' + f + '.isChecked()')
if obj:
for d in self.devices[f]:
self.device.ui.devices.addItem(d)
def main():
gui = adgui(sys.argv)
sys.exit(gui.app.exec_())
if __name__ == "__main__":
main()

180
src/python/adgui.ui Normal file
View File

@@ -0,0 +1,180 @@
<?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>780</width>
<height>746</height>
</rect>
</property>
<property name="windowTitle">
<string>AVRDUDE GUI</string>
</property>
<widget class="QWidget" name="centralwidget">
<widget class="QWidget" name="horizontalLayoutWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>660</y>
<width>781</width>
<height>41</height>
</rect>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="percentage">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>-- %</string>
</property>
</widget>
</item>
<item>
<widget class="QProgressBar" name="progressBar">
<property name="enabled">
<bool>false</bool>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="time">
<property name="minimumSize">
<size>
<width>100</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>--:--</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QPlainTextEdit" name="loggingArea">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>821</width>
<height>651</height>
</rect>
</property>
<property name="acceptDrops">
<bool>false</bool>
</property>
<property name="undoRedoEnabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>780</width>
<height>22</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionDevice"/>
<addaction name="actionProgrammer"/>
<addaction name="separator"/>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
<addaction name="actionDevice_Memories"/>
</widget>
<widget class="QMenu" name="menuSettings">
<property name="title">
<string>Settings</string>
</property>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>Help</string>
</property>
<addaction name="actionAbout"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
<addaction name="menuSettings"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionDevice">
<property name="text">
<string>Device ...</string>
</property>
</action>
<action name="actionProgrammer">
<property name="text">
<string>Programmer ...</string>
</property>
</action>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionDevice_Memories">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Device Memories</string>
</property>
</action>
<action name="actionAbout">
<property name="text">
<string>About ...</string>
</property>
</action>
</widget>
<resources/>
<connections>
<connection>
<sender>actionExit</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>close()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>399</x>
<y>299</y>
</hint>
</hints>
</connection>
</connections>
</ui>

157
src/python/device.ui Normal file
View File

@@ -0,0 +1,157 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Device</class>
<widget class="QDialog" name="Device">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>398</width>
<height>217</height>
</rect>
</property>
<property name="windowTitle">
<string>Device selection</string>
</property>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="geometry">
<rect>
<x>280</x>
<y>140</y>
<width>81</width>
<height>61</height>
</rect>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
<widget class="QComboBox" name="devices">
<property name="geometry">
<rect>
<x>180</x>
<y>50</y>
<width>191</width>
<height>25</height>
</rect>
</property>
</widget>
<widget class="QWidget" name="verticalLayoutWidget">
<property name="geometry">
<rect>
<x>20</x>
<y>10</y>
<width>131</width>
<height>191</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="heading">
<property name="text">
<string>Families enabled:</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="at90">
<property name="text">
<string>AT90</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="attiny">
<property name="text">
<string>ATtiny</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="atmega">
<property name="text">
<string>ATmega</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="atxmega">
<property name="text">
<string>ATxmega</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="avr_de">
<property name="text">
<string>AVR…D/E…</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="other">
<property name="text">
<string>Other</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Device</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Device</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>