diff --git a/images/3rd/matplotlib.svg b/images/3rd/matplotlib.svg
new file mode 100644
index 0000000..853eae0
--- /dev/null
+++ b/images/3rd/matplotlib.svg
@@ -0,0 +1,352 @@
+
+
+
diff --git a/images/3rd/packaging.png b/images/3rd/packaging.png
new file mode 100644
index 0000000..682547c
Binary files /dev/null and b/images/3rd/packaging.png differ
diff --git a/module/about/schema.py b/module/about/schema.py
new file mode 100644
index 0000000..6f342f3
--- /dev/null
+++ b/module/about/schema.py
@@ -0,0 +1,8 @@
+class ThirdParty:
+ def __init__(self, name: str, url: str, qrc: str = None):
+ self.name = name
+ self.url = url
+ self.qrc = qrc
+
+ def __repr__(self):
+ return f"ThirdParty(name={self.name}, url={self.url})"
diff --git a/module/resources.py b/module/resources.py
index 3798f93..9871e4a 100644
--- a/module/resources.py
+++ b/module/resources.py
@@ -1,6 +1,6 @@
# Resource object code (Python 3)
# Created by: object code
-# Created by: The Resource Compiler for Qt version 6.9.0
+# Created by: The Resource Compiler for Qt version 6.9.1
# WARNING! All changes made in this file will be lost!
from PySide6 import QtCore
@@ -14171,6 +14171,299 @@ My\xe1#i:\x00\x00\xe8)7|$M'\x00\
\x03\x00\xbd\xb9Z\
+7\xaf\x96\xbb\xfdj\xbby2\xd1\xad\x9a4\xcb\xcd|\
+\xbbXm.\x9fL^\x1e.N\xd2\xa4\xd9\x1ff\x9b\
+\xc5l\xbd\xdd,\x9fL6\xdb\xc9\xa7O\x1f\x9d\xfd\xe2\
+\xd9\x1f\xcf\xbf\xfb\xeb7\x9f7\xfbW\x97\xcd7\x7f\xfe\
+\xec\xab/\xcf\x9b\xc9\xc9t\xfa\xbd=\x9fN\x9f}\xf7\
+\xac\xf9\xf6/_4\xba\xd5\xd3\xe9\xe7\x7f\x98\
+?H\xca\xab\xd5\xf2\xf5g\xdb\x9b'\x13\xd5\xa8\xa6#\
+\xf7\xffM\xba\x95\x8e\xada\x94R$\xd8D\xeaIC\
+\xc0\xe6\xecjy\x98-f\x87\x19>7g\xbb\xc5\xc5\
+\xe9\x9f\x9e\xfd\xbe\x17y1\xaf\xbc\xae_\xee\xd6\x85\xd3\
+b>]\xae\x97W\xcb\xcda_\x10\xf7\x8b\x9e\xcey\
+\xec|\xb7\x9c\x1dV\xaf\x96\xf3\xed\xd5\xd5v\xb3/\xd3\
+6\xfb\xc7\xc3H\xac\xf1V5(35\xe6\x04#N\
+\xf6o6\x87\xd9\xcd\x09\xcd#\xc9\x9a\xb3\xf9\xfc\xf4\xfb\
+\xed\xeeEyh\xce\x16\xf3\xd3\xc3\x9b\xebeC\x02\xef\
+\x96\xfb\xed\xcb\xdd|yT\xd8\xc5\xfcjE#\xa7\xdf\
+\x1eV\xeb\xf5\x97W\xb3\xcb\xe5d\xcaL\x80|\xf9\xd4\
+(,\xaa\xf2\x89\x89\xdf\x19sj\xc2\xa9\x87\x13Y\xe5\
+|<\x9b\x0ec\xea\x8c\x8b\xed\xeejvx\xba\x22N\
+\xa4\xd2\xdf\x00U\x19\xd6\x7fQ\x07\x165lw\x1d\xa1\
+\xc8\xff\xbbK\xa8\xad\x7f\xee \xac\x0e\xeb\xe5\xd3\xafg\
+\x87\xeb\xf5\xf6\xb0^\xfd\xd0\xbc\xb2mh\xd5o\x1b\x02\
+\xb2\x07\x92\xab\xfaU\xc1S\xd6\xe9&\xf5\x5c\xa7c\xb6\
+e\x80\x5c\xb8\x0c\x18\xf4v6\xed\xedKv\x9f\x0a\xc3\
+\x9f-\x96\x17\xfb2b\x7fx\xb3^6\xa4\xb0'\x93\
+\xc3\xf2\xe60\x9d\xef\xf7\x93\xa7\xbf\xfe\xe7\xfe\xb0\xdb\xbe\
+X\x9e\xc0K\x97\x7f\xdf\xae6\xa7\xcdn\xfbr\xb3\xf8\
+\xa4\x11\xf4\xf9\xec\xfa\xb4\xf9\xe1\xe5\xe1\xf0\xaf\xb3ia\
+SV\xe99\x9f]6\xab\xc5\x93\xc9\xc5\xea\xf2\xe5n\
+\xf9\xb7\xe2y\x03\xedzv\x98?\xefI\xcd\x19\x9e\x9e\
+7\xa0~\xcd\xde\xfd\xe8\xab\xb1\x9f\x0b\x82\xa2\xcfj\xf8\
+o\xf8\xfa\x1f\x8fh\xc3C\x02Zp\xbd>m6\xd8\
+\xf7\x9f4\xdb\xeb\xd9\x1c\xbep\xda\xa8\xce\x03\xce\xa6\x97\
+B\x8c\xd9\xcdr_\xa5\x90\x92\x99I\xaf[\x16-\xd9\
+6\xa5\xfe\xdfG\xe7\xa3\xc7\xb7\xfd\xfbq\xd8\xc7a\xff\
+\x9da\x9d\xf3'\x95\x5cp\x06\
+\xdd:\x9dcn\xb4\xf6m\xceIY[X\xf8\xd8\x22\
+\x14\xaa\x84/LhuR.\x80\x07\x05C\x9dC2\
+\x8d\xb6\xbeu\xd6*|\xeflkL\xb0)\xc8\x8f\xc4\
+\x83\x87\x88y\x92\x1b\xaf\xc1\x8b\xb3D\xc4BJZ\xe5\
+\x97\xa0n\xa1\x17\x04L\x8f\xa6M>\x05=\x1a\x08\xfe\
+\xd99\x9b\xb3d\x09\x01c\x80\x84q\xbc\xbe\xd3\xad\x8f\
+\xd9\xdb\x91\xb0\xd6\xb4\xd6\x04\xd2#\xc32\xae\xf5\xd6\x06\
+\xedn\xeb \xb4\xd1)\xab\x93\xd4\x86j=\xb8j-\
+\x95\x11Z\xeb\x94\xd1\xb7\xcc`Zm\x8c/s\xaa\x22\
+NT\xab\xdc-\xe32\xad:\x01\x93X\x0b\x95\x1d\xeb\
+`X\x97\x15P\xd4>H(\x14P\x91T\xfc\x0c\x9a\
+?a6\xeb\xa7N\x11l*k^q\x90\x01sY\
+\xb0*+\x8b\x7f\x1b%?c\xe61e\x08\xad\xb1&\
+Y\xc1b]\xe9\x96l\x1e6\x19\x9b\x91\xcd;\x02-\
+|A\xf8G\xf5\x19\xa9\xc7\xc1\xbf\xc6*g_\x94\x0e\
+Z\xbd\xf6\xa8+\xf7\xc6\xbe\x93\xe3\x1e_\x94?\x9f4\
+\xf4x\xc2\xb9\xae\xcd}\xbd\xd3\xa7\xbb!\xb9\x89\xd2b\
+v\xb3\xaa\xa9o\xf8\xfa\xe6\xb0\x9a\xbf\xa8\xc4\x81Jy\
+\xde,\x98\xfc\xd6\xa4HA\xca\xb4\xd1D3P&\xcd\
+|\xbd\xba>\xa1\xf1\xa8\xcaw\xeb_>\xbe\x9e\xcf\x7f\
+P\x17!\xb8\x94\x7fu4aw\xc5\x05\xa0-\x03\xfd\
+\xbd[m\xec\x7f|9\xdb\xd5z\xae\x22\x14\x1f$\x18\
+s\x1c\x8cy\x1706\xc3R\xd6\xc2E\x13\x9c*\x04\
+\xfd\x10\x00\xd9\xe3\x80\xec;\x00\xea>x\x94\xb9\xe6A\
+\xd8\xc6\x1d\x87\xe2\xde\x01J\xb5\xc8C2\x8d?\x8e\xc7\
+\xbf\x03\x9e\xde(\x0ff\xdb\x84\xe3P\xc2\xfb\x98\x86\xf7\
+\xcf\x03\x00\x14\x8f\x03\x8a\xef\xbcmjh{\x00`\xd2\
+q0\xe9\xbd\x82\xda\x076\xcf\xcfd&3\xceLo\
+~*3\xe5c\xb8\xb3i\x83S\xa2\xce\xae\x04\xb2c\
+\xf6\xce\x10\xc5i\xa7QI$d\xed\xacLD\xc9\x89\
+\x9c\x1cm6\xc8\xb9(\x0fL\xb4\xa9dm\x90\xa9\x04\
+\x08\xb6\x89\xb9EF\xf6\xca6Y\x81s\xa2Z!\xa6\
+\xd6\xe5\x14\xa2'\x9a\x8a.\xa1\xd6\x88\xb1\x0d\xc9\xeb\xae\
+\xbcL\xb95A[\xe3\x9b\x18\xda\x145U\x0f)\xb5\
+$F\x8eD\xa3J\xc3\xc6&\xa1\x8e\xb04\xb2\x89\xbe\
+\x8d)\xa8X\x8a\xcb\x84\x9a%dHMd\xeb\xc39!C\xee\xcdN\xed\x0b\x1a#\x0f\x13\xc7\xe8\
+\x5cq0k\xb5\x8d\xb6A+\xe43m\xad\xae\xd8\xec\
+\x9f\xc8\xd9*\xde\x81\xc8`\x99\x11c\x1d\xad\xc9PY\
+\x92\xad\
++\xe2Je\xc0\x82\x19#\x1e\xaf\xcc\x90\x85\x98\x02\xb3\
+@$@\x8f\xf1\x8f\xc6\x88\x99\x95\x9f\x5c\xa4.>\xb2\
+\x81\x94\x94\xc5\xaf\xb0\xef\x82|0A\xea-]\xa2>\
+\xda\xc4kC\x19M\xc6\xa8\x9e\x10\xa9\x94\x87V\xba\x03\
+,x^\xa6\xf4\x07%\x03\xb6\x87\x03\x18\x87\xe4\x9a\x83\
+\xce\xe4l\x1a!\xdc\xfan\xbai\xb5Cb(gp\
+I\x19\xafIG\xa9\xcdF{\x0a\x0a\x81\xf2\x81\x8d\xe4\
+B\x0e\x1e\xed\xb4\xf2\x8d7\xb0\x96O\xd9\x0fA\x12)\
+:\x18\xdb\xb8\xd4&\x9b\x9c6\xe5\x90'{d\x9f\xd8\
+8\xdf\x06\xacW\x9c\x09\x1e\xed\xac\x0a\xbeq\x16\xf9D\
+\xd9\xd4y\x10\xf2>\xdc\xd4Y:\x911*+\xe4\xe5\
+D\x05\xaf1tJCil\xd8\xa9\xfdC\x97\xc2{\
+\xbc=\x8d\xb12\x17\x86:Z\x90\x91\xb2p\x0c\x94Q\
+0\xce\x11b1\x80'UF\x82y]s\xa4r\x16\
+O\x88\xdcc\xb8\x0d\xaa\x9ayxf\xb5T.B}\
+uA\xa1j\x9a,L\xc0vac\xb1\x05\x19\xa70\
+7\x85G\xe1\x06\xd57\x84\xc3\x08/\xaaP\x85\xcbu\
+\xc7\xbe\xd5\x15\xd9?\x85\xd3J\xbf\x1e\x9e\xbar\xbcG\
+<\x10\x19\xaf\xe0$\x00\x8f\xd7\x15\x88\x85\x94\x02\xb2\x00\
+$0\x8f\xe1\x8f\xc6\xf0L\xc1O\xaeR\x97\x1f[A\
+H+!\xf4\xb8\xef\xc0|0\xf1\xe9-G?\xfa\xe8\
+\xb9\x9c\x86\xaf\x19\xe5\x85\xe7V\x0aJ\xf9dB\xcc\xdd\
++\x09\xe8\xc7\xe4\xe2.\xa8\xcf\x13\x9d\xab\xc2\xc5\x91\x19\
+\x1d\x9d=#oi\x1dUg@\x1b[\xe7}\xa0\xd2\
+\x06\xde\x19\xb5'\xcdZ|\xb4\xc8\x84pz\xb4\x02Z\
+E\xd3y\x8dq`Q\xe2\x84G\x94\xf7\xa6S<\x0a\
+]\xe4\x8eh\x1a\x0b\xab\x1a$\x8f.\x9c\xc5\x94\x1c6\
+\x8bU-\xeaW[r*\xf2\x04\xb2\x08\x9d\xa3\x83\x97\
+1\xbe;\xf2\xcf\x19\xdb\x22iU\x8e\xd7!hT\xa1\
+T\xca\xd6\x1a\xe5\x1a\xb8\x81\xf7n\xb0\xd8\xf0\xd4\xb58\
+=\xe2\x81\xc8p\x99\x11\xa3\x1d\xad\xc9`Y>\xc6\xca\
+@\x18\xea\x08\xb4\x18\xc0\x93*#f\xcek\x8e\xb4.\
+\xc4c\x91{\x10waUc\x0f\x04\xa1\x1cV\x18+\
+\xb1\xae)\x14N2\xb3!\x84u\xd8dlG\x86*\
+\x8cN\x91\x8e\x9d\x81=D\xbaM\xf5%F+\x1c\xaf\
++\xc5\xaaCV/\x95\xae;\xf2\xef\xfa\xd8\x9d;\xf4\
+\x98+\x95!\x0bf\x02\xf3xi\x01Z\x08*P\x0b\
+L\x02\xf6X\x03\xa31b&\xf3\x13\xab\xf0\xf2cC\
+\x08i%\x84\x01\xd5]\xa0\x1f0X\xdd:\x9c\xea\xee\
+\x04\xd8{\xbd\x13\xf0U\x89\xf4\xf0{e\xbb3\x94\xe0\
+\xbdIC\xff\x96L\x0ct\xe4\x92Q\x82&:_\x80\
+2[\xe5J\xb0G\x85\x8e&\xdb\x95\x5c\xa1[\xf4\xd7\
+\xc4\x14\x1f,\xba\x89\x9e\x81F\xaa\xc8\x0e\xe5n2\x98\
+\xe5Q\xfa\x16\xa2S:\x10)\x07zAY\x09\xfc\xf2\
+\xe9=,\xf1X)7?\xf6j*\x08\xfb\xb89\xfd\
+\xad\xf6)7\xa1N\x1bx\xd8\xc8d\xdd\xe5\x93\xab\xd5\
+a\xb9;\xfeZ\xab\xb3\x8f\xbbw\xfb\x18J\xca\xd0#\
+\xc5\x93\x10\xa2R\xba6\x1e&\xa1\xe4i,\xbd\xebs\
+\x9e^\x87c/8\x13\xb4w\x8dE\x07\x02\xa3\xfa\xae\
+\xf1G\x1c\xa2\xce\xcbR\x07\x82\x0d\xd6q\xd0\xa6\xa5~\
+\x0c{\xc8\xa2;4\xda\xf4\xd5\x83W*\xa4R\x8c*\
+T\x0bT8\x846xK\xf6s\x06;\xd0j*p\
+\xde\xd7R\xf3\xe5\xc5\x85\xc9\xf7h)\x7f\xcf\x96\x82r\
+3\x1du{\xca\x06*\xa6>\x95\x22 i4{h\
+\xe0\xa8\x8aB\xc42H\x02\x91\xa22\xda\xc6\xc6Xh\
+\x1e\x06\x0b\x14\xeb#\x9dA\xc5\x06\xc1O\xc5\xec\xba\x92\
+\x1d\xed}T\xb0\x0dL\x93\xa8\xd0J\xb6\x1c\xa8d\x0d\
+#\xa7\xf2&\xb9T\xa1\x01=\x7fr\xdd\xed\x08\xaa\xd5\
+\x12\xaa\xe3\xf76\xd1\xc5EHJ\xdd\xa3\x89\xc2=\x9b\
+\x88\xb4m3\xb6\x0d\xbd\xdfOV\xe9\xbed\xc0\x16C\
+\xaa\xe8\x8e\xcb]\x86\x8a#\xd1\x0c\xf2N.\xa1\x0a\xbd\
+\x82R\x86\xa6c\x06\x92/\xa5UhPuw\x05@\
+F\x8ba\xe8\xb4'\xb7\x06\xd60\x98\x8e\xbck\xbd\x83\
+\xd9\x82\xc56B\x16w\xf4\xce*\x98HnAo\xf2\
+\xa3NT\xe7\xbf\xbf\x8d\xe6\xee^m\x14\xef\xd9F\xde\
+\xd3\xe1\xaf+\xc9\x024\x8b\x8cT\x8c\xe4\x11\x98P\xde\
+\x94#\x1a\xa84\x19\x8b\x9d\x00\xa2\xf2Y\xa7\xeeX\xc6\
+b\xd7\xa1\xda\xf2\x96N\xc4\xc0\xa2d\x17\xafa\xd1\xc2\
+\x00\xd6\xb3>\xe4\xae\xbb\xc16\x04\x8f\xb2\xeb\x94G\x9e\
++\xe5\x82\xa6\x01D\x03't\x9dD\xa3\xab\x11hU\
+\xdf\xdbN&\xc3R\xcb{\xb4S\xbag;\xa1\x96B\
+q\xeb\xfaN\x10\x9d\x22vF\x7f\xdcoS\x8ad?\
+\xd0\xbd\xf3\xe4\xfa\x09AIg\x9f\xbb&1\xba\x88\xca\
+\x816V\x80uBW\x0a\x07\xe4\xb6\x90\xfb\x97BH\
+2t\xce_8\xa4`\xb0\x0b#\xfaxK\x9b\xb0\x14\
+\x8d\xc6\x19p\x8d\xa8\xd0\xe9nL\xdfO'\xb4\xb1\xff\
+A\xd4\x8b\x8b\x8b\x8b8\xbbGK\xe5\xfb.!\x90\xde\
+\xa9\xe5H\xdd\x09F\x0e*h]\x1b\xd4\x88*\x01\xdd\
+\x81\xc9\xa4K\x9d\xba{J\xb0K\xa9',\x9d\xd5\x18\
+\xaa'\xb4\xb5-Z\x91\xf2=6\x12\x1d9w\xa5?\
+\x86Dd\x8d\xf2\xae\x97^\x8c\xc5r\x8a\x03v\xd1\xaa\
+@\xf6B%\x02\xcb\x1b\xdb\x9b3\xd2\x15*P\x11\x0c\
+\xa3G4\xfc\x1fHS\xf5\xad\x8b<.\xb8u\x09\xe9\
+\x9c)\x91r:\xeaa\xba\xb2\x06\xa9R\x0ctO\xd0\
+\x22\xe4\x1b\x07\x07\x05|\x8c\xf3t\xf8\x82\x90\x84\xfe\xd2\
+\xa5\xfe\x92$U\x00\xf0u\xd7\xb8\xd2zzC}\x0a\
+i\xcf\xa5\xae\xccs\xc891\x1d\xbd3\xd4\xf5O\x96\
+^L\x22Q\xa1vW\xd8Z\xb4[J?\xe4\xe8\x8a\
+3\x0a\xc6\x0ck\x87R\xe4)o=\xedQ\xba\x8f\xee\
+B\x7f\xbd\x105\xa5K\xd1\xd3\x1bM\x04aD\xd8@\
+\x89\xd2X\xa4\xbc<\xbe=2\xdc\xef9\x17h{\x1a\
+#\xadL\x18\xe7h=\x09s\x90\x8dQ2\x88#\xf7\
+p\xce\xc5\xd7B1\x95\x8d`]W\x1ci\xbb\xca\xc6\
+\xe2\xf6\xf2\xdf\x01\xc4\xafZ\xbagVIe\x224\xc7\
+\xeb\x095\x97\xce\xb4\xaa\x9fm\x22\x0c%\xacw\xe4n\
+\xcb\xf9\x08e\xf5\x09\x01\x93\xbdGh\x9b]\x8d\x0e\x08\
+\xd8\x05\xd9/\xa5\xb3\x8e<\x9a\xaf\xa2\x9c\x0b\xc4\x95*\
+\x1137\xe9Y\xa3\xb5\xa5\x1f\xb2\xa4\x02\xb4\x00u\xf4\
+\xf6\xc8\xf9x\x04\xcf\x13\xdc\xe4\x1au\xf1[V\x10\xb2\
+J\x04=\xaa#@\xeb\x01\xc1O\x1d\x01\xa0\x1a\xf7\xf1\
+N\xb8qo\x0b6?sV0\xfc|\xa0\xfc\xd7\xff\
+[\x7fGA\x91\xf2\x1b\x0aC%>q\x98\xbc\xf3+\
+\x87\x8f\x11\xeac\x84\xfa\x18\xa1\xfe\x7f\x22\xd4\xc7\x1f\x0f\
+}\x1c\xf6\x01\x87\xddM8\xa8\xeb\xfb_\xc3\x0d9K\
+\xfcn\xef\x8c~\xe1\xf8\xf4\xd1\xbf\x01:\x8f<\x83\
+\x00\x00\x04\xc0\
+\x89\
+PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
+\x00\x000\x00\x00\x000\x08\x02\x00\x00\x00\xd8`n\xd0\
+\x00\x00\x04\x87IDATx^\xed\x95mL[U\
+\x18\x801\xfe\xf0\xa7\xc6e&\xd3-\xdb\x1c \x0e\xdd\
+\xb2\xa8\xf1+F\x9d3\xfe\xd0%#\x9a\x98\x99\x18\xff\
+\xb0\x1ft2M\x99LZ\x91.\xb4`\x08\x1f]\xc6\
+\xc7\x0f\xb1\xc0\xa0\x85\x01\xbd-0J[\xd6i\xa0\x11\
+R>\x1ci\xd6RF[\xa0@kK\xf9\x10h\xcb\
+G\xf3\xfa\x96S\xaf\xf4\x0e\xbc&\xa6\xdbL\xee\x93\xe6\
+\xe6\xe6\xbd\xef9\xf79\xf7\xbc\xefi\x02&h|GB\x0d\x8f\xea\
+`\xea\x148_\x07{*,\xc8\xe8)\x18BEE\
+Emmm\xbb\x09\xc9\xe5rZ\x08\xc9\xc8\xc8@{\
+\xdc\xa3\xce\xce\xce\x92\x92\x92\xca\xcaJ\x89D\x823\x00\
+\x11\xe2]\x1f&M\x9e\x92\xab\xfe\xd9\xdc\x0fK?\xc2\
+b%,V\xc0\xbc\x14|\xb90y\x12\x1c/\xc1x\
+\x12\xd8\x9e\x00\x9f\x84\x9e\xf4^!\x8a\xa2\xfe\xa5\x10\x8f\
+\xc7\xc3O\xd2\xdb\xdb+\x16\x8b\x83\xc1 F\xfa\xfa\xfa\
+\xfe\x16*\xd4Z\x22M.\xa0\xb2[L\xe0\xbd\x00\x9e\
+\xcf\xc0\xfd)\xcc\x9c\x81\xa9\xf7\xc1\xf9\x06\xd8_\x80\xbb\
+\xfba\xf41\xb0$\xc0\x12EO\xba]\x08\xd7\x9a\x95\
+\x95500\x80\xe5\x8cB\xb8)$N\x0b544\
+\xd0Bsss\x98\xe3\xf1x\xb0\x15\xea\xea\xeaH0\
+Fhx\xca\x9f(\x8c4y\x91\xe67p\x9f\x8d\xd4\
+\xaf\xebC\x98|\x17\x9c\xaf\xc0x2\xd8\x9e\x04\xeb#\
+p'\x01,{!\xbcJ\xc6\xc3\x96\x10\x9f\xcf\xd7j\
+\xb5X\xd7\xb5\xb5\xb5\xf9\xf9\xf9\x1b\x1b\x1b\x18/--\
+\xc5\xfa5\x9b\xcd\xd88B\xa1\x90\x16\xca\xcb\xcb\xc3!\
+\x18\xc7\x17\x13\x0f\xec\xbb\xcc\xccLL\xc3`YY\x19\
+n_T\x08\xb9i\x99\x15R\x83z\xf3$\xcc\xa4E\
+\xebw\xf6\x1cxE\xf0\xfbEp\xf3`\x96\x073\xe7\
+a\xf5W\xda\x06\xb6\x84\xca\xcb\xcbGFF\x9a\x9a\x9a\
+\xb0\x93\xc9\x97G\xb0\xd7\xb0\xf3[ZZpS\xf0\xab\
+TWWc\xb0\xbe\xbe^\xa9T\xe2\x1e\xe1\xde\xd1\x1d\
+\x8e8\x9dN\xcc\xc4U\xb9\xddn\x12a\x9c\xd4\x9b0\
+\xf5\x1e8_\x8b\xd4\xaf\xfdy\x18?\x0cw\x93`\xf2\
+\xe3\xd8\x9c((Dw\xefnttt`\x7f\xc1\x96\
+P{{;\xf3\xf1N0\x84\xc2\xe0|\x15\xc6\x13\xc1\
+\xf6xd\x9b\x9co\xc3\xda\x148N\xc5\xe6Da\x15\
+\xc2E\xe7\xe4\xe4X\xadV\xf8\x0fB\x00\x8br\x18\xdb\
+\x13\xa9_3\xfe\x1e\x8d\xd8\xac\xec\xfc_FJ\x87\x19\
+\xdd\xc2\xe7\xf3a\xa1\xa0\xee\xe8\xe8(\x89\xe0\xc9\xb9\xbd\
+\xcb\xfe\x81{\x84\x10\xac\x5c,\x97?\xb4\x10\x18\x8el\
+\xe2\xfd\x85)\x84\xe5\x86\xad\x8b\x15\x1a\x08\x04\xf0J\x1a\
+\xe7~\xc2\x14ZYY\xc1\x13\x02\x8b\x7fll\x0c\xaf\
+x\xc0\xa3\x19#'\xae0\x85\x08\xe1\xbf`>\x88?\
+;\x0b=@8!68!68!68!6\
+8!68!6\x1e:\xa1?\x01S\xe1\x9f|\x8b\
+j\x8f\xf5\x00\x00\x00\x00IEND\xaeB`\x82\
"
qt_resource_name = b"\
@@ -14195,6 +14488,14 @@ qt_resource_name = b"\
\x07\x87WG\
\x00q\
\x00t\x00.\x00p\x00n\x00g\
+\x00\x0e\
+\x03\x9b+G\
+\x00m\
+\x00a\x00t\x00p\x00l\x00o\x00t\x00l\x00i\x00b\x00.\x00s\x00v\x00g\
+\x00\x0d\
+\x04\xbey'\
+\x00p\
+\x00a\x00c\x00k\x00a\x00g\x00i\x00n\x00g\x00.\x00p\x00n\x00g\
"
qt_resource_struct = b"\
@@ -14202,14 +14503,18 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x02\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\
-\x00\x00\x00\x12\x00\x02\x00\x00\x00\x02\x00\x00\x00\x04\
+\x00\x00\x00\x12\x00\x02\x00\x00\x00\x04\x00\x00\x00\x04\
\x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x1e\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
-\x00\x00\x01\x97\x0d\xb6\xa1\x16\
+\x00\x00\x01\x97\xa0\xe5\x95}\
+\x00\x00\x00p\x00\x01\x00\x00\x00\x01\x00\x03t\xf5\
+\x00\x00\x01\x97\xb7\xaf+u\
+\x00\x00\x00\x92\x00\x00\x00\x00\x00\x01\x00\x03\x82I\
+\x00\x00\x01\x97\xb7\xb1\xa2\xd4\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x03f\x83\
-\x00\x00\x01\x97\x12L\xfd\x82\
+\x00\x00\x01\x97\xa0\xe5\x95|\
\x00\x00\x004\x00\x00\x00\x00\x00\x01\x00\x03H\xd3\
-\x00\x00\x01\x96\xed\x00qM\
+\x00\x00\x01\x97\xa0\xe5\x95{\
"
def qInitResources():
diff --git a/resources.qrc b/resources.qrc
index 3c3579d..e3a07ca 100644
--- a/resources.qrc
+++ b/resources.qrc
@@ -4,5 +4,7 @@
./images/logo.png
./images/3rd/qfluentwidgets.png
./images/3rd/qt.png
+ ./images/3rd/matplotlib.svg
+ ./images/3rd/packaging.png
\ No newline at end of file
diff --git a/ui/pyui/about_ui.py b/ui/pyui/about_ui.py
index 71e0d5a..16db79f 100644
--- a/ui/pyui/about_ui.py
+++ b/ui/pyui/about_ui.py
@@ -1,14 +1,100 @@
-from PySide6.QtGui import QDesktopServices, Qt
-from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout
-from qfluentwidgets import PrimaryPushSettingCard, FluentIcon, GroupHeaderCardWidget, PushButton, ImageLabel, TitleLabel
+from PySide6.QtGui import QDesktopServices, Qt, QColor
+from PySide6.QtWidgets import QVBoxLayout, QHBoxLayout, QWidget
+from qfluentwidgets import FluentIcon, GroupHeaderCardWidget, PushButton, ImageLabel, \
+ TitleLabel, HeaderCardWidget, BodyLabel, HyperlinkLabel, SingleDirectionScrollArea
+from qfluentwidgets.components.widgets.card_widget import CardSeparator
+from module.about.schema import ThirdParty
from ui.components.widget import Widget
-from utils.function import DEVELOPMENT_ENV
+from utils.function import RELEASE_ENV
+
+if RELEASE_ENV:
+ from build_info import *
-class AboutWidget(Widget):
- def __init__(self, key: str, parent=None):
- super().__init__(key, parent)
+class AboutCard(HeaderCardWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+ self.setTitle('关于本程序')
+
+ self.vBoxLayout = QVBoxLayout(self)
+ self.vBoxLayout.setContentsMargins(0, 0, 0, 0)
+ self.lineVBoxLayout = QVBoxLayout(self)
+ self.lineVBoxLayout.setContentsMargins(24, 16, 24, 8)
+ self.textVBoxLayout = QVBoxLayout(self)
+ self.textVBoxLayout.setContentsMargins(24, 8, 24, 16)
+ self.viewLayout.setContentsMargins(0, 0, 0, 0)
+
+ self.addLine('程序名称', '教学工具箱')
+ if RELEASE_ENV:
+ self.addLine('程序版本', '1.0.0#' + GIT_HASH)
+ self.addLine('作者', '许方杰')
+ if RELEASE_ENV:
+ self.addLine('构建时间', BUILD_TIME)
+ self.addLine('许可证', 'GNU 通用公共许可证 第三版(GPLv3)')
+ self.addLineUseLink('项目主页', 'https://cantyonion.site/git/cantyonion/JITToolBox')
+
+ self.addText(
+ '教学工具箱是自由软件;您可以依据自由软件基金会发布的 GNU 通用公共许可证第三版条款,重新发布或修改它;许可证应使用第三版或(按您的选择)任何其更新的版本。')
+ self.addText(
+ '教学工具箱是以希望它有用为目的而发布的,但不附带任何担保;甚至没有适销性或特定用途适用性的隐含担保。请参看 GNU GPL 第三版了解更详细的内容。')
+ self.addTextWithLink('您应该已收到一份 GNU 通用公共许可证的副本;如果没有,请查看<',
+ 'https://www.gnu.org/licenses/gpl-3.0.html')
+
+ self.vBoxLayout.addLayout(self.lineVBoxLayout)
+ self.vBoxLayout.addWidget(CardSeparator(self))
+ self.vBoxLayout.addLayout(self.textVBoxLayout)
+ self.viewLayout.addLayout(self.vBoxLayout)
+
+ def addLine(self, title: str, content: str):
+ hBox = QHBoxLayout(self)
+ mTitlte = BodyLabel(title, self)
+ mContent = BodyLabel(content, self)
+ mContent.setTextColor(QColor(96, 96, 96), QColor(206, 206, 206))
+ mTitlte.setFixedWidth(100)
+
+ hBox.addWidget(mTitlte)
+ hBox.addWidget(mContent)
+
+ self.lineVBoxLayout.addLayout(hBox)
+
+ def addLineUseLink(self, title: str, content: str):
+ hBox = QHBoxLayout(self)
+ mTitle = BodyLabel(title, self)
+ mContent = HyperlinkLabel(content, content)
+ mTitle.setFixedWidth(100)
+
+ hBox.addWidget(mTitle)
+ hBox.addWidget(mContent)
+
+ self.lineVBoxLayout.addLayout(hBox)
+ mContent.clicked.connect(lambda: QDesktopServices.openUrl(content))
+
+ def addText(self, text: str):
+ label = BodyLabel(text, self)
+ label.setWordWrap(True)
+ self.textVBoxLayout.addWidget(label)
+
+ def addLink(self, text: str, url: str):
+ link = HyperlinkLabel(url, text)
+ link.setUrl(url)
+ self.textVBoxLayout.addWidget(link)
+
+ def addTextWithLink(self, text: str, url: str):
+ hBox = QHBoxLayout(self)
+ label = BodyLabel(text, self)
+ link = HyperlinkLabel(url, url)
+ link.setContentsMargins(0, 0, 0, 0)
+ hBox.addWidget(label)
+ hBox.addWidget(link)
+ hBox.addWidget(BodyLabel(">。", self))
+ self.textVBoxLayout.addLayout(hBox)
+ link.clicked.connect(lambda: QDesktopServices.openUrl(url))
+
+
+class AboutMain(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
self.logoImage = ImageLabel(':/images/logo.png')
self.logoImage.scaledToHeight(100)
@@ -18,48 +104,46 @@ class AboutWidget(Widget):
self.hBox.addWidget(self.logoImage, 0, Qt.AlignLeft)
self.hBox.addWidget(self.appNameLabel, 1, Qt.AlignLeft)
- build_time_str = ''
- if DEVELOPMENT_ENV:
- from build_info import BUILD_TIME
- build_time_str = f",构建时间:{BUILD_TIME}"
+ third_parties = [
+ ThirdParty("PySide6", "https://qt.io", ":/images/3rd/qt.png"),
+ ThirdParty("QFluentWidgets", "https://qfluentwidgets.com", ":/images/3rd/qfluentwidgets.png"),
+ ThirdParty("openpyxl", "https://openpyxl.readthedocs.io/en/stable"),
+ ThirdParty("python-docx", "https://github.com/python-openxml/python-docx"),
+ ThirdParty("Matplotlib", "https://matplotlib.org", ":/images/3rd/matplotlib.svg"),
+ ThirdParty("packaging", "https://github.com/pypa/packaging", ":/images/3rd/packaging.png"),
+ ThirdParty("pywin32", "https://github.com/mhammond/pywin32")
+ ]
+ sorted(third_parties, key=lambda item: item.name)
- self.version_card = PrimaryPushSettingCard(
- text="获取源码",
- icon=FluentIcon.INFO,
- title="关于",
- content=f"作者:许方杰。当前版本:1.0.0{build_time_str}\n"
- f"本软件使用 GPLv3 开源协议进行分发,作者不对使用本软件造成的任何损失负责。"
- )
- self.button_list = [
- PushButton("访问网站"),
- PushButton("访问网站"),
- PushButton("访问网站"),
- PushButton("访问网站"),
- ]
- self.url_list = [
- "https://qt.io",
- "https://qfluentwidgets.com",
- "https://openpyxl.readthedocs.io/en/stable",
- "https://github.com/python-openxml/python-docx"
- ]
self.group_card = GroupHeaderCardWidget(self)
self.group_card.setTitle("第三方框架")
self.vbox = QVBoxLayout(self)
self.vbox.addLayout(self.hBox)
- self.vbox.addWidget(self.version_card)
+ self.vbox.addWidget(AboutCard(self))
self.vbox.addWidget(self.group_card)
self.vbox.addStretch(1)
- self.group_card.addGroup(":/images/3rd/qt.png", "PySide6", self.url_list[0], self.button_list[0])
- self.group_card.addGroup(":/images/3rd/qfluentwidgets", "QFluentWidgets", self.url_list[1], self.button_list[1])
- self.group_card.addGroup(FluentIcon.LAYOUT, "openpyxl", self.url_list[2], self.button_list[2])
- self.group_card.addGroup(FluentIcon.LAYOUT, "python-docx", self.url_list[3], self.button_list[3])
+ [self.addThirdParty(x) for x in third_parties]
- self.version_card.clicked.connect(
- lambda: QDesktopServices.openUrl("https://cantyonion.site/git/cantyonion/DefenseTopicGenerator")
- )
- self.button_list[0].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[0]))
- self.button_list[1].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[1]))
- self.button_list[2].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[2]))
- self.button_list[3].clicked.connect(lambda: QDesktopServices.openUrl(self.url_list[3]))
+ def addThirdParty(self, third_party: ThirdParty):
+ button = PushButton(FluentIcon.LINK, "访问网站")
+ button.setFixedWidth(120)
+ self.group_card.addGroup(third_party.qrc if third_party.qrc else FluentIcon.LAYOUT, third_party.name,
+ third_party.url, button)
+ button.clicked.connect(lambda: QDesktopServices.openUrl(third_party.url))
+
+
+class AboutWidget(Widget):
+ def __init__(self, key: str, parent=None):
+ super().__init__(key, parent)
+
+ self.scrollArea = SingleDirectionScrollArea(orient=Qt.Vertical)
+ self.scrollArea.setWidget(AboutMain(self))
+ self.scrollArea.setWidgetResizable(True)
+ self.scrollArea.enableTransparentBackground()
+
+ self.vBox = QVBoxLayout(self)
+ self.vBox.setContentsMargins(0, 0, 0, 0)
+ self.vBox.setSpacing(0)
+ self.vBox.addWidget(self.scrollArea)
diff --git a/ui/pyui/sub/picker.py b/ui/pyui/sub/picker.py
index 10c1ea8..9e487b5 100644
--- a/ui/pyui/sub/picker.py
+++ b/ui/pyui/sub/picker.py
@@ -3,7 +3,7 @@ import sys
from PySide6.QtCore import Signal
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QApplication, QGridLayout
from qfluentwidgets import PushButton, SpinBox, PrimaryPushButton, \
- BodyLabel, CardWidget, SimpleCardWidget, FluentStyleSheet
+ BodyLabel, SimpleCardWidget
from qfluentwidgets.components.widgets.card_widget import CardSeparator
from ui.components.widget import RollingTextWidget