new connection editing

This commit is contained in:
2026-07-20 13:50:33 +02:00
parent 6a3e9f01d0
commit 8f87a3b477
21 changed files with 1571 additions and 391 deletions

View File

@@ -60,15 +60,18 @@ Keep the root of `src/bedit` minimal. New UI modules should go into the relevant
- Port removal or reorientation must be rejected when it would invalidate an - Port removal or reorientation must be rejected when it would invalidate an
existing connection. existing connection.
- Connections reference port IDs, never port names. - Connections reference port IDs, never port names.
- Graph interaction has separate Pointer and Connect modes. Connections store a - Graph interaction has separate Pointer and Connect modes. Port hints are only
`properties.routing` value (`direct`, `angled`, or `spline`); angled routes visible in Connect mode. Connecting two blocks opens the compatible port-pair
store absolute scene points in `properties.waypoints`. chooser; explicit port clicks determine its default selection. Connections
use one freely angled polyline format with absolute `properties.waypoints`.
Semantic ports choose compatibility, while each rendered endpoint is the
intersection of the owning hitbox and its center-to-adjacent-route-point ray.
- Connection appearance is configured per port type in - Connection appearance is configured per port type in
`gui/graphics/connection_styles.py`, including color, width, pen style, and `gui/graphics/connection_styles.py`, including color, width, pen style, and
source/target arrowheads. Do not scatter those constants through painters. source/target arrowheads. Do not scatter those constants through painters.
- Graph annotations are `box`, `line`, or `text` objects in `Graph.annotations`. - Graph annotations are `box`, `line`, or `text` objects in `Graph.annotations`.
They use integer layers below or above graph layer 0. Annotation lines reuse They use integer layers below or above graph layer 0. Annotation lines reuse
connection routing and absolute `properties.waypoints` semantics. Graph connection polyline and absolute `properties.waypoints` semantics. Graph
annotations and icon elements share `ShapeOptionsDialog` and `shape_pen` so annotations and icon elements share `ShapeOptionsDialog` and `shape_pen` so
their style fields and rendering must remain aligned. their style fields and rendering must remain aligned.
- Icon editing uses a fixed 128×128 coordinate space. - Icon editing uses a fixed 128×128 coordinate space.

View File

@@ -110,17 +110,19 @@ BEdit document used as a copy source. The built-in example defines A, B, and C.
- Components, interface terminals, and connections are selectable. Use a rubber - Components, interface terminals, and connections are selectable. Use a rubber
band or Ctrl-click for multiple selection, Delete to remove items, and the band or Ctrl-click for multiple selection, Delete to remove items, and the
standard Cut/Copy/Paste shortcuts to duplicate selected component groups. standard Cut/Copy/Paste shortcuts to duplicate selected component groups.
- Switch the graph header to **Connect**, choose Direct, Angled, or Spline, then - Switch the graph header to **Connect** and click two blocks or their temporary
click an output and input. Angled connections accept intermediate corner port hints. A chooser lists every compatible output/input pairing in both
clicks, use right-angle segments, and snap to the graph snapping grid. directions and preselects the pairing implied by the clicks. Empty-canvas
Right-click cancels an unfinished connection. clicks add freely angled, grid-snapped vertices; right-click cancels.
- Select a routed connection to reveal its draggable nodes. Right-click a line - Select a routed connection to reveal its draggable nodes. Right-click a line
to add a node, or right-click a node to delete it. Angled routes persist every to add a node, or right-click a node to delete it. Visible wire endpoints are
orthogonal corner as a draggable, grid-snapped node. projected onto each icon's hitbox where the ray from its center toward the
adjacent route point crosses the boundary. Hidden semantic port positions do
not constrain the wire.
- Use **Box**, **Line**, and **Text** to add persistent graph annotations. Lines - Use **Box**, **Line**, and **Text** to add persistent graph annotations. Lines
share the Direct, Angled, and Spline routing controls. Annotation context menus use the same freely angled polyline and draggable-node behavior as connections.
provide the same shape styling as the icon editor and can move annotations Annotation context menus provide the same shape styling as the icon editor and
through integer layers below or above graph layer 0. can move annotations through integer layers below or above graph layer 0.
- Double-click a graph component or select it and use **Down** to open its owned - Double-click a graph component or select it and use **Down** to open its owned
subgraph; use **Up** to return. subgraph; use **Up** to return.
- Graph components show **Pointer**, **Input**, and **Output** tools. Select an - Graph components show **Pointer**, **Input**, and **Output** tools. Select an

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 879 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,5 +1,10 @@
<RCC> <RCC>
<qresource prefix="icons"> <qresource prefix="icons">
<file>icons/draw-triangle.png</file>
<file>icons/draw-ellipse.png</file>
<file>icons/draw-circle.png</file>
<file>icons/draw-path.png</file>
<file>icons/network-connect.png</file>
<file>icons/arrow-down.png</file> <file>icons/arrow-down.png</file>
<file>icons/configure.png</file> <file>icons/configure.png</file>
<file>icons/arrow-up.png</file> <file>icons/arrow-up.png</file>

View File

@@ -215,6 +215,89 @@ ff\xa6\x5cP`\xb7\xadY\x1b\xde\x8b\xcb\x97/\xc3\
\x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\ \x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\
\xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\ \xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\
ND\xaeB`\x82\ ND\xaeB`\x82\
\x00\x00\x05\x08\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x06\x0a\x00\x03\
\x0a\xd2\xe0H^\x00\x00\x01\xfbPLTE\x00\x00\x00\
\xa2\xa2\xa6\xa7\xa7\xab\xa8\xa8\xad\xaf\xaf\xb2\xb1\xb1\xb3\xb3\
\xb3\xb5\xb6\xb6\xbb\xc4\xc4\xc8\xcb\xcb\xcf\xb9\xb9\xbb\xba\xba\
\xbe\xc4\xc4\xca\xaa\xaa\xb0\xb0\xb0\xb4\xac\xac\xb2\xad\xad\xb2\
\xcb\xcb\xd1\xbe\xbe\xc3\xc2\xc2\xc6\xa4\xa4\xa8\xad\xad\xb1\xae\
\xae\xb1\xb5\xb5\xb6\xaf\xaf\xb3\xb0\xb0\xb3\xb4\xb4\xb9\xb4\xb4\
\xba\xca\xca\xce\xa9\xa9\xad\xb2\xb2\xb6\xa7\xa7\xac\xa8\xa8\xac\
\xcb\xcb\xcf\xcb\xcb\xd0\xa1\xa1\xa5\xa2\xa2\xa5\xb2\xb2\xb4\xa2\
\xa2\xa8\xa4\xa4\xa9\xcc\xcc\xd0\x9c\x9c\xa1\x9e\x9e\xa3\xb3\xb3\
\xb8\xb4\xb4\xb9\xc3\xc3\xc7\xc3\xc3\xc8\xac\xac\xb1\xad\xad\xb2\
\xa6\xa6\xa9\xc7\xc7\xcc\xc8\xc8\xcc\xb1\xb1\xb3\xad\xad\xaf\x9d\
\x9d\xa2\x9e\x9e\xa4\xa1\xa1\xa4\xa2\xa2\xa6\xcb\xcb\xcf\xcb\xcb\
\xd0\x9a\x9a\x9e\x9b\x9b\xa0\xc5\xc5\xc7\xd2\xd2\xd3\xd2\xd2\xd4\
\xd3\xd3\xd5\xd6\xd6\xd8\xd7\xd7\xd7\xd7\xd7\xd8\xd9\xd9\xdb\xda\
\xda\xdb\xdc\xdc\xdd\xde\xde\xdf\xdf\xdf\xe1\xe0\xe0\xe1\xe2\xe2\
\xe2\xe2\xe2\xe3\xe2\xe2\xe4\xe3\xe3\xe4\xe3\xe3\xe5\xe6\xe6\xe7\
\xec\xec\xed\xed\xed\xee\xee\xee\xef\xef\xef\xf0\xf0\xf0\xf1\xf1\
\xf1\xf1\x97\x97\x9c\x98\x98\x9e\x99\x99\x9e\x9a\x9a\x9f\xa7\xa7\
\xaa\xa8\xa8\xab\xab\xab\xad\xab\xab\xae\xac\xac\xaf\xad\xad\xaf\
\xad\xad\xb0\xae\xae\xb0\xae\xae\xb1\xaf\xaf\xb1\xb0\xb0\xb4\xb1\
\xb1\xb4\xb3\xb3\xb8\xb5\xb5\xb8\xb7\xb7\xbb\xb8\xb8\xbc\xba\xba\
\xbc\xbb\xbb\xbd\xbb\xbb\xbe\xbc\xbc\xbe\xbd\xbd\xc2\xbf\xbf\xc2\
\xbf\xbf\xc4\xc5\xc5\xc7\xc9\xc9\xcd\xcb\xcb\xcf\xcc\xcc\xd0\xcd\
\xcd\xd0\xce\xce\xd2\xd5\xd5\xd6\xd5\xd5\xd7\xd7\xd7\xd7\xd7\xd7\
\xd9\xdb\xdb\xdd\xe0\xe0\xe0\xe0\xe0\xe1\xe1\xe1\xe1\xe2\xe2\xe2\
\xe3\xe3\xe3\xe4\xe4\xe3\xe4\xe4\xe4\xe4\xe4\xe5\xe5\xe5\xe5\xe6\
\xe6\xe6\xe7\xe7\xe7\xe7\xe7\xe8\xe8\xe8\xe8\xe9\xe9\xe9\xea\xea\
\xea\xeb\xeb\xea\xeb\xeb\xeb\xeb\xeb\xec\xec\xec\xea\xec\xec\xec\
\xec\xec\xed\xed\xed\xec\xed\xed\xed\xed\xed\xee\xee\xee\xee\xef\
\xef\xee\xef\xef\xef\xef\xef\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xf2\xf2\
\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf5\xf5\xf5\xf6\xf6\xf6\xf7\xf7\xf7\
\xf8\xf8\xf8\xf9\xf9\xf9\xfa\xfa\xfa\xfb\xfb\xfb\xfc\xfc\xfc\xfd\
\xfd\xfd\xfe\xfe\xfe\xff\xff\xffN\x89\xad\xb6\x00\x00\x00W\
tRNS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x17\
\x18+2668<<CZZ]^^^^\
aq\x86\x8c\x8c\x8d\x8d\x95\x95\xb1\xcc\xcc\xcc\xd2\xd2\xda\
\xda\xdc\xdc\xea\xea\xeb\xeb\xeb\xf1\xf5\xf6\xf6\xf6\xf6\xf6\xf6\
\xfa\xfa\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\
\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xee\x1d\xc8\xff\x00\
\x00\x00\x01bKGD\xa8P\x086\x92\x00\x00\x02#\
IDAT\x18\x19\x05\xc1\xcd\x8b\xd5e\x1c\x07\xd0\xf3\
\xf9>w\xee\xdc\x19c\x1a4M\x223\x95\x8cj\x91\
\x8bVE\x9b \x08!\xa1E\xabh\xd3\xbf\xd6\xa6\x82\
6-\x0c{\xd9\x19\xb5\x09\x82\xa8\xb0\xec\x05\x99A/\
5\xf9\x92\xe9tg~\xcf\xd39\x00\x00\x00\x00\x00`\
\x03`\x03\x003\x00,N\xae\x1f}\xd9\xd7{\xab\xdb\
\x0f\x01\x08`\xb1\xff\xe4\x89\xd7\xd7\x16\xf3\xb5i\xb5?\
\x1d|\xb9\xdcY\xec\x03\x0d\xb0x\xf6\xad\xe9\xa5\xcd5\
\xd3\xc8l\xb6\xf8\xf3\xb5\x7f\xef\xae\x80\x06\x1c{o\xf5\
\xea\x99>j\x0a\x19\xfd\xa9G\xfez\xe3\x97\x87 \xe0\
\xe8\xbb7\xde<\x9c\xf4>M&m^\xdd\x17O\xbf\
\xff74\xac/\xde\xb9y\xa9\xc7\x88^css\xa6\
s\xfa\xdbW~\xac\x89\x86\xe9|{{\x182Ff\
\x1b\x12r\xd8\xce\x5c\x9d\xdfD\xc3\x13\x97.va\xa4\
\xcf\x9b\xf4\x8c\x8c\xb8w\xea\xe4\xee?\x14\x8e\xef\x8c*\
\xa92\x97\x91\xeaE\xdd\x9f\xfcp\x1cM\xdb\xbax1\
\xc2H\x8d0\x92^\xfd\xc1\x8a3[\xbf\x1e\x8c2m\
o#\xa9\xaa$#\xa1\xba\xc3V\xe9G\xb6'\xc5\xfa\
I\x92J\xabdDF\xd4~\xa92_\xa7\xd8~\x0c\
R\x92\xaa\x18\x91\xc3\x9eD\xd6\xb6)\xce6\x88$ \
\x1cH\xa5j\xe3\x1c\xc5\x00\xaaR\xa9$LSI\xa5\
2(v'0\x12I!\xbd)I\xfe\xdb\xa1\xd8[\
\x82\xa4\xaa\x221zk\xa2\xb2\xda\xa3X}\x02\x88T\
*Q\x9aJ\xea\xea\x8a\x92\xbb\xcf\x00FE%\x86\xd2\
\x94\x8dsw\xa2\xf1`~\xe3\xf9@\x92a\xd0\x8d\xf4\
\x1a\xae\xec\xfeLa\xf7D\x00\x92JR\xa5iGN\
\xef\xa0\xe1\xdet\xfd\x02 b\x18\xe9\xc9\xec\xa3\xdf\xaf\
\xa1P\xd7\x8f}<@\x92H\x952\xbb|\xeaz\xa1\
a\xacn\xbd\xf0\xfds\x80\x11]\xd4\xe5cWn\x0f\
4\xf0\xe0\xd6\xf9o^\x1c\x81\x0c\x8c\xf6\xc1\x89\xcf\x97\
\xa0\x01\xf7\xff\xd8\xb0\xb5@b\xd4ly\xed\xb7\xaf\xf6\
\x80\x06\x98vn.\x96\x9f\x9d\x9dG\xbf\xf3\xe1\xfe\xf2\
\xd3\x9f\xa6\x0e\x04`\xed\xc0\xe3[\x8f^8\xac\xfa\xee\
\xee\xbd\xa5\xb5\x03\x00\x00\xcc\xb1\xb9\x899\x00\x00\x00\x00\
\x00\xfc\x0f\x04u\xc6)\xe3{\x06,\x00\x00\x00\x00I\
END\xaeB`\x82\
\x00\x00\x08h\ \x00\x00\x08h\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -520,6 +603,142 @@ K,~\x0d\x01\x02\xc2\x08\xa0\x0d\x98$\xc1\xd4\xe8C\
S\xa0\x81ahl}\xb5P\xd4\xd7\xb8\xe9z\x86\x0e\ S\xa0\x81ahl}\xb5P\xd4\xd7\xb8\xe9z\x86\x0e\
>L\x06\x80\x01`\xc7\x01\x7f\x03\xdc\xff\x15WD\x01\ >L\x06\x80\x01`\xc7\x01\x7f\x03\xdc\xff\x15WD\x01\
nd\x00\x00\x00\x00IEND\xaeB`\x82\ nd\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x03o\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x02\xecID\
ATx\xda\xed\x97\xddN\x1aA\x14\xc7gvfw\
\xd9E\x10\x10\x95\xa47\xc6\xe2\x17\x9aX\xb7\x8am\xb5\
\xe5\xda\xcb\xbeP\x13_\xc3X\xa9R\x04\xa5\xd6\xa8\xa5\
j\x9b\x18\xb0I\xfb\x16\xbe\x81\xbdjb\xaa\xecN\xcf\
\x0c\xccB\xd8\xc6H\x01\xe9\x85'9\xcc\xecY\xc2\xff\
w>2\xcbb\xc6\x18\xea\xa5)\xe0\x0f\x00\xff\x17\xc0\
\xc9\xc9\xe9\xce\xf1\xf1\xd7T\xcf\x00l\xdbYB\xc8Y\
?::N\xf5\xac\x05cc\x8f\x0dJ\x15\x01q\xdf\
\x00\x98\x7f\x04\x83\xc1\x1a\x04^?<\xfc\xfc\xea\xbe[\
\x800\xc6\x02\x22\x1e\x8f\x1b\x8a\x82\xdf\x1e\x1c\x14_\xde\
7\x80\xf0\xfe\xfe \x1a\x1f\x8f\x1b\xb0\xdd\xd8\xdf\xff\xb4\
\xdcu\x80\xd5\xd5U\xcc\x98\x00p=\x14\xeaG\x93\x93\
\xe3\x00\xc1\xd2\x85\xc2\xfer\xd7+\xc0\xc0`\xf1@L\
MM\x18\x84\x90\x8d\xdd\xdd\x8fK]\x05p\x9cj\x05\
\x9a!\xc2\xe1\x10\x9a\x9e\x9e0\x09\xc1\xe9|\xfe\xc3\x8b\
\xae\x00$\x12\x09l\xdb\xcc\x15\xf5B\x84\x01\x22\x01\x10\
\xca\xbbB\xa1\xf0\xbcK-\xb0\x1bD\xbd\x10\x91H\x18\
\xcd\xcc$L\xdb\xc6\x9b\xb9\x1c@t\xbe\x05\x02\xe0V\
\x88XlX\x9b\x9f\x9f\x0b0\x866\xb3\xd9\x9dg\x1d\
\x03\x18\x1c\x1c\x84\x16\xa0\xfa\x10z!\xb0\xae\xeb>\x15\
lxx\xc8W\x85`[\x99L~\xb1#\x00\xe5r\
u\x08)\xa5\x9e\xac\x094\x9e\x8b+`\xbcS\xdcc\
\xb1!}aa\xae\x8f1'\x93Ng\x16;4\x03\
\x0e\xe2\x1a\x8d\x10\xb4j\xba\xb8\xdf`\x00{\x03\x83\xe9\
X\xd6\x13\x13c%\xb3\xb6\x06\x10\xed\x00\xa4R\x5c\x00\
\x09\x00\x09A\xc0`O\xb96\xc08\xdc\xf9\xa8\xd8\xb6\
}]\xa9Tn\xe0\x1aZ7\x80\x92I\xcb4M5\
\xb3\xb5\xb5\x9dl\xab\x02\xd0^.*\x00\xb86@\x10\
\x08\x0ba\x998\x08_C\xf6\xb6l\x0f\xff\xee\xc0@\
\x04Y\xd6\xac\xa9i\xfa{\x18\xcc\x85v\x00d\xf6\x98\
\x80\xf1\xcc\xe1Z(\xd72\xaf\xf0\x98\x1c\xccf\x88\xd9\
\xd9\x19\xd3\xe7\xd3\xb3\xf9\xfc\xe1|\xcb\x00\x81@\x00S\
\xaarA\x84\xab\xc6\xa4C\xcc\xa9e\xcd\xa4\xe8\xdf \
\x22\x91\x10?\xac\x00\x82f\xf7\xf6\x00\xa2\xf5\x0a\x10\xb1\
rA\xeeP\x09\xbe\x8a\x16@A\x98\x14\xbc\x0d\x22\x1c\
\xe6\xcf\x8e1\xbfa\xe8\xd9b\xf1\xcb\xd3\x96\x00\xa0\x02\
|\x91\x102{\xe1r0\xef\x02\x11\x0a\x89?5~\
J\xc9\xf6\xe9\xe9\x99u'\x80\x8b\x8b\x0b,*P7\
W\x5c\x0ef+\x10\xc1`\x00\x8d\x8e\x8e\x00\x84\x9a;\
;\xfbf\xdd\xa9\x02\x84\xd0\xe6\x90+\xfco\x10}h\
d\xe4\x91_U\x95\xdc\xf9\xf9\x8f9O\xc5\x9b\x8eb\
\xe8\xb7\xf6\xab\x5c\xfe\xeeW<h\xf2\xc7\xc5a\xe4\x89\
+\x0av\x83\x8e\xc3\xe0Z\xde\xc3\xb5\xb9\xa1\x06!\xf8\
\x0dl_\xa3\x06\xf3\xbc\x1b\x96J%zuu%\xfa\
`\x18\x06\x96q8\x86\xdd\xfd\xe5\xa5\xe6\xee5Mm\
\xd8\xff\xc4\xf5a\xae\xc7\xa3\xd1\xa8l\xf1\xf5\xca\xca\xca\
\xefF\xbd\x87\x97\xd3\x9e\x03\xfc\x01x$\x0aI\xcbR\
\xf1S\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x04\xc3\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
B(\x9bx\x00\x00\x00\x07tIME\x07\xd9\x07\x0c\
\x14\x17\x01h\xb8;C\x00\x00\x04CIDATx\
\xda\xdd\xd6\x7fH\xdce\x1c\x07\xf0\xf7\xd7\xdd\xd7;\xcf\
;m\x8av*\x82\x9b\xc6v\x88#%3K\xb1p\
6i\xee&\xbbTJwl:\xd6Z\xb9J\xcd\xd3\
\xb4\xb9\xb2\xd8b\xb8\xa8E0R\x1c\xb2\xb9Y\xdb\x1f\
kZ\x84\x19bA3\xb0\xd9,E\xbc\xfcIj\xb3\
\x89\xe4\xdd\xf9\xfdu\xf7\xf4\x1c\xf9\xc7\xed\x1b\x87\xdf\xd3\
0\xe8\x05o\x9e\xe3\xe0\xf8<\xcf\xe7y\xee\xf9~\xb1\
\xc9Jij\xf1\x1f\xd1[,\x16\x92\x90\x90@\x00,\
\xd2\xec\x05\x15\x80MB\x0b_\x09\x09\x09!6\x9b\x0d\
\xd4\x034\x9d4\xcd\xd8$OTTT\x90\xd8\xd8X\
\x02\xc0;%\xd8\x0c\xd9\xd9\xd9\xf3\xf9\xf9\xf9n\xaf\xc2\
\x9e\xcf6l\x06\x86aj\xea\xeb\xeb\x09m\xbf|\xf5\
i\xd8\xa8\xa6\xdb$g\xad\x83W\x5c\x5cLRSS\
\x89l\xf5\xdd\xa0\xd6}\x08\xcf\xfdD\x8e\xd1\xe2e\x00\
\xfa\xde\xe9#\xf0%>>\xfejdd$\x19\x18\x18\
\x80\x17\x86\xc6\x02\x7f\xd4}\xe5\x84\xc7\xe9~r\xf8L\
?i\x802\x99UUU$..N\xde\xfa\x0b\xf0\
Gu\xd7\x12\x1a\xfa\x08\xfb\xe6\xd7b\xc7\xdb\xdf\x92m\
\xa0\x1az\x04%\x07\xef\xf7\x82\x82\x02\xf9\xc1\xf3\x8c*\
P\x8a\xb7 @\x9d\xcc9\x1d\x1d\x8d\xbb\xd9\x22\xce\
\xee\x98\x00\xf5Vv \xd6\xf0Ffffdww\
7#k\xbd\x95F\x82R\x95]\xf6G\xaan\xfe\xd9\
\x0e\xaa\xea\xc6\x22\x14\x0a)))!\xe9\xe9\xe9\xf2\x83\
\xb7\x04\xa5J[\xc6\xf0\xf2\xb5\x85\xe4\xf2\xeb\x0b7A\
\x9d\xf8l\x0eJm\xdf\xbe\xfd\xcb\xea\xeaj\xb7J\xa5\
\x92\xef\xbd\x19JX>\x19\xc1\xd1K\x93\xea#m\x13\
\xbf\x80:vy\x0aJXU*P\x99\xd5V\xab\xe7\
\xbe\x97\xaf~\x18\xfe8\xd42\xd6_\xda6\x11U\xda\
:\x06\x7fT\xc6\xc4,\x1f-,\xf4\x14$\xb2<\x0c\
\x1fT\xf0r\xa0\xe9\x07ht\xfa,^\x10\x86\xaf\x94\
=4\x07?\x94\xb3\xac5G\xa7\xd3%\xcd\xcc@m\
0\xe0\xfc\xfc<V\x8b\x7fA3\x08\x1f\x18\xc8\x98?\
\x18\xb0\x05l\x09\xdc\xc5\x10\xd1\xf9iy\xca\xda[F\
s\x07\xd0\x1c\x0c\x0f_\xc96\x1a\xf1\xdb\xe2\x22\xf4,\
\x8b\xb4\xa8(\x98o\xddB\xe7\xd2R\x04\x80?\x14u\
\xc0tn\xc0 J.\xc7\xe7\xaf$9\xa1P\x9b\xa7\
\xf5ju\xf3NZ\x9ca\x18Haa\x08\xa6\x13\x10\
\xddn<\xedr}\xd4\xe9\xbb\xf8?\xef\x01ae\xe5\
\xa0\xc0\xf3\x17\xa1PY@\x00\x9e\x03v\xec\xdc\xba\xb5\
8:(\x08\xe3\x92\x04=\xc7!2,\x8c\xf4\x8e\x8e\
\x8a'\x96\x97\xcb_\x03\xa0|\x02\x02\x9f&\xf0\xc2m\
(\xd4BW\x99\xa0V\xb7?\x16\x1dM\xee\xd1\xe2\x8c\
\xdd\x8eX\xad\x16K\xcb\xcb\xcc\xd0\xec\xec\x0b\xa0\xde\xf7\
g\x02\xbc\xc0\x1b9\x9e\x9b\x87r\xc9\xe3EE)\xdf\
\x89\xa2C\xb3\xb0\x80\x88\x88\x08\x12F\x1f>\x83\x83\x83\
\xe3\xb5\xa2\xd8\x0a\x0f?; \x09\x1c\xa7\x85B\x06\x83\
\xe1u5\xcb\x92\x17\x87\x86t\xfbGF\x9c,\xcf\xaf\
\xdc\x9b\x9cd\x86\xef\xde-ldY\xf8m\xd7K\x1d\
=I\xc7\xdb\xcd;\x0e7C\x01&77\x97\x18\x8d\
\xc6\xfb\x1e6)*U\x1bd\x94w\x80\xe7z\x04\xce\
\xf9\xcch\xeb\x11(PNo<x^2\xbd\xcc\xff\
(I\x16\xf8\x81\x81\x97\xf8C-\xb1.\x978MD\
W\xa0(\xf1\xe2\xec\xb5\x0a\xdf\x9b\x9f\x9c<\x17\x1a\x1a\
\xfa`oo/\xb3\xda\x01\xcf\x18C3\xbb\xee\x0e\xfc\
z\xb1lF\xe0V\xbe\x11%\xa1]^\x5c.##\
\xe3\xc6\xe8\xe8(\xe3\xb5\x10\xb3\xac\xb8\xff\xa2\xccM\x08\
\xdf\x7f68\xdct\x86\x84\x99\xce\x9e\x06\xa5\xdfs\x0a\
rZ\xadV\xd7\xd5\xd5Ezzz\xdct\x22w\x00\
\xbc\x07\x99uu`\xeez%\xb4z\xd6!\x08|\x12\
\xcf;j\xf4{N\xf6\x11\xa8\x0c\xa0\x02\xb3j\x81U\
N\xa7\xd3>==\xfd\xa1\xc3\xe1\x98\x92$\xe9I\x00\
\xd6\x0dM\xc0d21\xf0\x8c\xfb\xf2\x98\x99K\xaf\x22\
\x83\xf9~\xd8\xf1s\x83\x8a\xe3%\xb7\x9d\x13\xe7\xb6d\
\xd5v\x0ab\xf0\x81\xd0\x9c\xba P\x8d'k\xa25\
\x1a\xcd\x14\xfd\xdd\xb6\xc4\xc4\xc4El\x84\xe7\xaf\x04*\
//\xef\xfeq\xef\xdf\xdf?\x9b\xb7\x9b\xe0\xd1w\x1b\
\x9e\xdf\xf7\x14\xc1\xe3u\x04\xe9V\xdb\xf1S\x1f_\xc5\
\xbf\xc9\xd3\x01\x9f\xa3\x0f\x17Z/\xe3\x7f\xe1/\x17\x85\
\xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\
`\x82\
\x00\x00\x02V\ \x00\x00\x02V\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1210,6 +1429,113 @@ G%\xc7A \x00\x1d\xd0\x80I\xe0\x03\x10;3\x85\
\xfaS\x08V\x01()\xc7\xe4\xb9C\x95,\x01\xd77\ \xfaS\x08V\x01()\xc7\xe4\xb9C\x95,\x01\xd77\
\x9aw\xed\x7f\xefoJ\x88l\x9a\x02\xc2\xbb\x00\x00\x00\ \x9aw\xed\x7f\xefoJ\x88l\x9a\x02\xc2\xbb\x00\x00\x00\
\x00\x00IEND\xaeB`\x82\ \x00\x00IEND\xaeB`\x82\
\x00\x00\x06\x8f\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x06\x0cID\
ATx\xda\xddWYLUW\x14=NiSQ\
\x8b\xad\xb1\xad\x16\x8d\x1dR\xd39\xa6\xa6\x95\xb6Im\
b\xda\xaf\xfe\xe0w\xd3~\x01%6V\x1cA\x8b\x8a\
\x10)NLB\xa1\x10@\x85\x08\x05\xc2P\xa8\x81\xfa\
\xe1\x00\xca\xa48\x81\xa2(\x93\x82\x82\xcc\xd3\xe3\x9d\xdd\
\xb5N^o.\x06\x1b\xb0$&}\xc9\x0a\xbcs\xef\
;{\xed\xbd\xd7\xdeg\x1f%\x22O\x15\xff/\x02\xf8\
L\x0b\x0a\x0ar{*\x046n\xdc\xf8Ahhh\
[zz\xfahHHH*\x88\xcc|\xf4\x9d\x83\x07\
\x0f.\xf2\xf6\xf6\x0e^\xb5j\xd5\xb7nnn\xeeS\
F`\xd3\xa6M\xcb\xf7\xed\xdb\xd7WYY)W\xaf\
^\x95K\x97.i\x18\xabpEczuu\xf5G\
\x87\x0e\x1d\xfa\x04k\xad>>>G===W\xcc\
\x993gj\x08l\xd9\xb2\xe5\xf5\xb0\xb0\xb0\xee\x0b\x17\
.\xc8\xe5\xcb\x97\xa5\xa6\xa6Fjkk\xe5\xc6\x8d\x1b\
:>>\xbe\xbd\xb4\xb4\xb4\x0c\xebCg\xce\x9c\x19\x01\
\x89R___\xf7)K\xc1\x86\x0d\x1b\x96\xec\xdd\xbb\
\xb7\x83F\xeb\xea\xea\xe4\xc1\x83\x07RUU%\xf0X\
\xea\xeb\xeb\xe5\xf6\xed\xdb\x92\x96\x96\xa6\xcf\x9f?/\xd7\
\xaf_\x97k\xd7\xae\x05N\x95\x06\x18\xf6W\x90\xf3v\
\x84[n\xde\xbci\x0c\xb6\xb6\xb6Jww\xb70\x1a\
$u\xeb\xd6-\xae\x19\x12\x88Joss\xf3\xe2)\
!\xb0u\xeb\xd6\x05{\xf6\xeci\xb9r\xe5\x8a\xdc\xb9\
s\x87\xde\x1b\x83\x0d\x0d\x0d\xd2\xde\xden\x91\xf8\xe7y\
cc\xa3NHH\xe8\x1cO\x98O\x92s\xf7\xdd\xbb\
w7 \x9c\x1a\x1e\x19\x0f\x07\x07\x07\x19^\x8a\x8f\x06\
\x99\x0aC\xe2\xe2\xc5\x8bF\x0fMMM\xd4\x84\x13\xa4\
S\xff\x13\x01z\xb0k\xd7\xaeZ(]\xdf\xbd{\xd7\
\x0aqGG\x07I\xd0\x98\xf1\x9a\xc4\x1e>|H\x12\
\x8c\x0c\xf3\xcf\xf7\x18\x95\xd1\x80\x80\x80\x1f\x9e\x98\xc0\xe6\
\xcd\x9b\xbf\xce\xcd\xcd\x1d\xbe\x7f\xff\xbe\xf4\xf5\xf51\xb4\
F\xf9mmm4h\x91\x00,=\xf4\xf4\xf4Xz\
\xe0zjj\xea\xc0\xda\xb5kgL\x86\x00=\x9f\xbe\
m\xdb\xb6H\xd4\xf1pdd\xa4\x93\xb5\xce\xcd\x1d\x0e\
\x87\x09/\xbf\x83\x14\xd7H\x82\x9a`\xc8I\x8cD\xcd\
:{\xc4\xe9\xd3\xa7%11q\x88\xfbM\x98\x00\xdb\
\xeb\xf6\xed\xdb\xd3\xa1r\xa7\xe0\x93\x94\x94$qqq\
\x9a\xf9\xee\xed\xed\xb5H\xd0\xbb\xce\xceN\xb3644\
\xc4\xb0\xd3k\x920d\xd0\x07$##\xc3\x89\xea\xf9\
~R)\x80\xe7\xbf\xc1\xc3Q|\xe4\xde\xbd{\xc6\xa3\
\xa8\xa8(\x1d\x1b\x1b\xabY\xef\x03\x03\x03\x16\x09\x1a\xea\
\xea\xea\x92\xfe\xfe~\x92\xa00\x99\x22\xe3yff\xa6\
\x13)\xfciR\x22\x84`\x22(\x1cnF#$A\
\xcf\xe8exx\xb8\x8e\x89\x89\xd1\x0c-\xc3\xceg\x14\
\x1f\xbd\xe6s\x86\x9d\xbd\xe1\xd4\xa9S\x92\x95\x95\xa5Q\
\xba?O\xea0\xc2\x0fB\xce\x9d;\xe7\xa0\xc7\x14\x12\
=\xa5\x91\x91\x91\x11\xe6\xd8D\x03\x15\xa1\xa1\x09]V\
V\xc6u\x8b\x04\x1b\x13SB\xcf\xb3\xb3\xb3u``\
`\xd8dNC\xd6z\x00\x98;l\xa2b.\xe9\x19\
\x8dX\xe1\xa5!DI\xa3\xc7\xeb\xb3g\xcf2J$\
\xcbr3\x9e\xe7\xe4\xe4\xe8\x1d;v\x1c\xb6\x8c\xc5\xa9\
Y*Je\x02_>\x8e\x00\x8f\xd5\x1fKJJ\x1c\
\x14\x14@\xcf\xad\xf2\xa2\xd74@\x12\x5c\xa7\xfaYb\
\xf8\x8d\xde\xbf\x7f\xbf.**\x221\xcb8\xc4{\xc4\
2t\x5c\xcdP\xb1*\x03\x10\xa0O\x1dV\x9e\x5c\x7f\
\xb4\xbf\x7fWXX8\xc2\x92b[%\xd8\xd5h\x8c\
\xc2\xa2A6 \x0a\x8d$\x18\x11V\x03N<Y\xbf\
~=C\xcdc\x98\x82\xd3;w\xee\xccf\x05\x891\
\xa2\xa6\xa9x\x95\x0c\x88\x0d\xc5|6\xa6\xd6\x0f\x1c8\
\xd0\xc7\x06\xc2~\xee2h\x95\x12I\xd0 U\xdd\xd2\
\xd2b\xba\xdf\xb0cX\xca\x9a\xca\xa4\xbc\xbc\x5c\x8a\x8b\
\x8b\x05\xbaq\xe2hv \xec\xb1cj=Q\xc5\x00\
bC\x8dJP\xf3\xc7\x10`w\xc2\x0f\xfb\x11~\x0a\
\x89\xe1\xb7\x0cB\xcd\xf4\x9c$\xac\xf6\xda\xd8\xd4(^\
\xc5^27m\xae\x9c\xa8?!'O\x9ed\x7f\x18\
e\xb7\xb4G\x15~\xff\xa2R\x94\xd8P\xa7\x8e\xa9\x85\
|6^\x0a|q\xd08\x90\x06\x1e*$A\x83\x0c\
3k\x9c\x9eS\x0f\xa6\xedz\x15z\x89:\xaa\x0c\xe6\
\xa5\xcf\x93\xa8\xdc(\x09\x0e\x0e\xae\xb7\x9fxx\x16\x04\
\x88\x0d\x0d\xea\x88Z\xfc\xafU\xe0\xef\xef\x9f\x8cM\x9c\
(\x1fj\x80$\x98s\x9el\xec\x01l8\x86Dh\
u\xa8\xa8tea\xf6\xb1\xd9\xce\xa5\xbf.]im\
\x9c\xa6\xfc\xb9nC3\x08,{\xd4\xde\xe3N\xbc\x0e\
\xe4Sc\xc0d\x09\x92\x84\xc99;\x1f\xbfs\xd2\xe1\
\x9a_\x89\x9f\xa0\xa8,@\xe3-\xeaw\xb5\x0c\xff\xfb\
\x00bC\x1b\xf0\xd6\x84\xfa\x00\xa7\x16N/h\xc1\x1a\
#\x97\xa4\xa4\xa40\xe7\xc6 \xab\x83\x9d\x8f\xe9\xa0\xf0\
\x0a\x0a\x0adu\xe2\xeaA\x95\xad\xc4\x866@\xdb\xbe\
w\xaa,\xf5\xfe\x84\x1b\x11\xe76\x86\x9as\x1c\x1a\x92\
^\xb7n\x9d`\xb8\x94\x8a\x8a\x0a\x920bd\xe7\xcb\
\xcf\xcf\x17L\xc2]\x9c\x0bU\x9e\x0a\x07d\x1c\xf4\x00\
+'|/\xe0\xc4\xca\xc9\x95\x13,'YN\xb4P\
u\xbb\x9f\x9f\x9f\x8e\x8e\x8efk55\x9f\x97\x97'\
,YN\xc4\xd6F\x05*Z\xfd\xa1\xc4\x86~\xac}\
>\xa1\x8b\x09gt\xce\xea\x9c\xd99\xbbs\x86\xe7,\
\xcf\x99\x9e\xb3=\xaa\xa3\x9c$0RIrr\xb2D\
DD\x0c\xf2.`\xdf\xc84\x9b\x22\x95\xa4\xfeT\x02\
\x0c\xa9\x13j\xcd\x84oF\xbc\xa5\xf0\xb6\xc2[\x0bo\
/\xe3\x09\x13\x06S\x00'\xdak'oA\xe3n\x16\
\xa4\xa6\xab\x12\x95\xaa\xfeR\xdf<\xd1\xd5\x0c\x9f\x99\xc0\
s\xc0<\xe0E\xe0e\xc0\x03X\x06\xbc\xe9\xe1\xe1\xb1\
\x02\x7f\xdf\x01\xde\x05\xde\xb3\x81\xdf\xdf\x06\x96\xf3=\xe0\
5`)\xf0*\xf0\x12\xb0\x00x\x1ep\x03\x9e\x01\xc6\
\x8ed\xec\xd9\x5c\x04\x9e\x05\xe6\x02/\xb8\x8c/\x01\xde\
pm\xfe!\xf01\xf0\x19\xb0\x1aX\x03|\xe5\xfa\xfb\
\x05\xf0)\xb0\x92\xef\xf1}\x12q\x91X\x04,\x04\xe6\
so\x97\x8dY\x80i\xd5\x7f\x03\x81\xe3\xecT\x9c\xdf\
M_\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x06\xf7\ \x00\x00\x06\xf7\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1739,6 +2065,76 @@ K\x80@\x89\x15\x8d\xc04\xd5\xb5^\xaf\x1bx\xfa\x19\
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\ \x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\ \xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\ \xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x04<\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
B(\x9bx\x00\x00\x00\x07tIME\x07\xd9\x06\x0a\
\x00\x09%\x83\xde\x9d\x8d\x00\x00\x03\xbcIDATx\
\xda\xedV\xcdKTQ\x14\xbfw\xe6\xcdG\xcf)\xc6\
\x19B\xc5\x8a\x1aE\x86\xa4\x85\x91!\x13\x99\x8b(\x0d\
\x94\xc12\x93\x04\xc9\x16m\x82P\xe8\x0f\x88v-l\
\x1bA\xad\x22RC\xcbA\xa6,\xa5\x19\xd0\x81\xc6>\
\x16-\xc2\x92 \x0cc2\xc7\xf2c\x9c\xcf\xdb9\xd1\
y\xbc\xde\xbc)\xc6 \x10\xe6\x07\x87{\xdf\xbc;\xe7\
\xf7;\xe7\xdc{\xeec\x05\x14\xb0\xa9\x10\x0e\xbf\xfa\xe7\
5Z\xf0<\xc8w\xc0\xd0$\x84h\xe0\x9c\xef\x87\xb9\
\x13l;\x98\x00\xfb\x02\xf6\x15\xdeM\xc3\xbbg0\xf7\
\xd7\xd6\xd6\xccOM=g\x1e\xcf\xc1\x8d\x09\x08\x04&\
\xd9\x91#\x87X(4}.\x93I\xf5\x18\x8d\xd2\xbe\
m\xdb\xb62Y\xb6\x0a\xab\xd5\xcc\x8dF#\x032\x06\
\xa4,\x9dN\xb3D\x22\xc5\xd6\xd7\xe3\x02\x8c\xc7bk\
,\x99\xcc\xbc2\x18\x0c}\x1eO\xed\x1d\xf2\x95\x97\x80\
`p\xca\x93J\xa5\xee\xdb\xed\xf6\xb2\xd2\xd2\xed\xc2f\
+\xe2\xf0\x8c\x84\x8a!p\xccd28*s\x14\x86\
b\xbe\x7f_\xe6\xd1\xe8\xb7\x8f\x92$\x9d\xac\xaf\xf7L\
\xeb\xf1\x18\xd4\x0f~\xffS\x86\x98\x98\x08^\x81\xa8&\
\xab\xab\xdd\xa5nw%+*\x9299V\x1b\x01\xe7\
@\xc2,\x16\x0b\xb3\xd9l\xb0\xbe\x889\x9d\x0e\xeer\
\xedfUU\x15;\xc1Wx|<p\x998rd\
\x80D<\xb9%\xcbrw]\xdd\x01\x8a\x0cGL3\
\xce\xb32\x80\xa5\x80T+\xd9@\xe0zz\xa62\xbd\
~\xfd\x06\xca\x94\xb8\xde\xd8x\xb47g\x06|\xbeG\
\x1d0tC\xba\xd019G\xc3yV\xc4f\xb3Y\
!W\x8bR\xafC1kkk\xcc\xe5\xda\x85A\xf4\
\xf8|\x8f\x9bu\x05\x0c\x0c\xf8L\xb0\xf0\xee\xe1\xc3\x1e\
\x01@\xc7\xe8\x00G2E\x94\xc9d\xa2\xc8r\x12S\
&b\xb1\x98\x92\x89\xca\xca=\xb0Ic#\xba\x02\x84\
H\x5cv:\x8b\x7f\xd6\x9bj\x0cd\xba\x02\x88P\xbb\
\x1fH\x14!\x1e\x8f\xff\xf6N\x96\xb7p(/\x04;\
tI+\x00\xeb\xd3ZQ\xe1R;\xa0\x0c\xa8\xc5\xa8\
I\xf5\xa2\xa7r\xa0?\x12\xae\x18\xfe^R\xe2\x14\xf1\
x\xc2\xab\x15\x80\xc7\xc6]\x5cl'\xc5\x04\x85\x1c@\
\x02\xfe&\x04k\xad=1\xf4?\xc8\xb0\x8d\x83\x007\
\xad\x95hB;\x9e\xa0#\x02\x1dS$h\xea\x9d\xae\
\xee\x09\xea}\xa0\x99c\x10\xb8&\xcd\xb32`6\x9b\
f\xa2\xd1\xa5\x5c\x22\xb4\x91SZ\xd5u\xd7\x0a\xd2]\
\xbb\xb2\x12\x13\xb0\x89g\xb4\x02\xf0X=x\xfbv\x86\
\xfe\xac\x07r\xa4\x15\x82\xa3.\xb9\xd6\xf0\xf4\xcc\xcd}\
\xe2\x92d|\x98%\x80s\xc3\xb5H$\xc2\x96\x97W\
\x84N\x16\x88@wSjE\x91\x11\xe8yuuU\
,-}\xc32]\xcf\x12\xd0\xd9\xd9\xbe\x0e\xad\xf4\xfc\
\xd8\xd88\xd7\xa4U[K\xdd\x1e\xa1\xfe]k\xf4.\
\x1c~\xc9\xa1y\x9d\xee\xea:\x9b\xd1\xed\x84\x1d\x1dm\
\xb7\xd3\xe9T\xbf\xdf?\x86\x02\xf2\x11\xa1\xee\x19\xba%\
\x08\x85\x9ec\xe47\x81c0g+\xee\xef\xbf\xcf\xda\
\xdbO\x9d\x81T\xf5\x8d\x8c\x8c\xb2\xc5\xc5EA\xa9\xcf\
!B7\x1b\xf4\x1e\xdb5\xdc\x88\x22\x18\x9c\x84\xbe\x10\
\xbf\x0a\xbe/\x00\xc7\x9f/\xa3\xc1\xc1!\xd6\xd6\xd6\xca\
\x86\x87G\x8e%\x93\x89{eee\xc5\xd5\xd5{\x05\
\xf4\x08\xaew\xec\x10tQ\xa9\x8f\xf2\xc2\xc2\x82\x98\x9d\
\xfd\xc0#\x91/\x118am^oK0\xaf\xef\x81\
\xa1\xa1\x07\xac\xb5\xd5\xcb \x13\x17S\xa9d\xaf\xd5j\
\xddS^^\x8e\xd7\xacp8\x1c\xdcb\xa1\x8bH\xe9\
\xf9\x90\xb1\xa8\x00b>?\xff\x19\xdb\xf0;I2\xf7\
\xb5\xb4\x9c\xb8A\xbe6\xfaIF7\xa5[\x88\xccq\
\xa0k\x00\xc2\x1a\xfc\x1c\x83h\xe5_%Y\xc1\xa0a\
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x02\x92\ \x00\x00\x02\x92\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1875,6 +2271,10 @@ qt_resource_name = b"\
\x00z\ \x00z\
\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\ \x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\
\ \
\x00\x0f\
\x0bb\xb8g\
\x00d\
\x00r\x00a\x00w\x00-\x00c\x00i\x00r\x00c\x00l\x00e\x00.\x00p\x00n\x00g\
\x00\x14\ \x00\x14\
\x0b\xa9\xab'\ \x0b\xa9\xab'\
\x00d\ \x00d\
@@ -1889,6 +2289,15 @@ qt_resource_name = b"\
\x09\xd0z\x07\ \x09\xd0z\x07\
\x00a\ \x00a\
\x00r\x00r\x00o\x00w\x00-\x00u\x00p\x00.\x00p\x00n\x00g\ \x00r\x00r\x00o\x00w\x00-\x00u\x00p\x00.\x00p\x00n\x00g\
\x00\x11\
\x06\xfb\x85G\
\x00d\
\x00r\x00a\x00w\x00-\x00t\x00r\x00i\x00a\x00n\x00g\x00l\x00e\x00.\x00p\x00n\x00g\
\
\x00\x0d\
\x0f\xba\x1e'\
\x00d\
\x00r\x00a\x00w\x00-\x00p\x00a\x00t\x00h\x00.\x00p\x00n\x00g\
\x00\x0d\ \x00\x0d\
\x0b\xe6\x1f\xa7\ \x0b\xe6\x1f\xa7\
\x00d\ \x00d\
@@ -1922,6 +2331,11 @@ qt_resource_name = b"\
\x00d\ \x00d\
\x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\ \x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\
\x00s\x00.\x00p\x00n\x00g\ \x00s\x00.\x00p\x00n\x00g\
\x00\x13\
\x04\xc4\x04G\
\x00n\
\x00e\x00t\x00w\x00o\x00r\x00k\x00-\x00c\x00o\x00n\x00n\x00e\x00c\x00t\x00.\x00p\
\x00n\x00g\
\x00\x0c\ \x00\x0c\
\x06\xeb\x97\xe7\ \x06\xeb\x97\xe7\
\x00z\ \x00z\
@@ -1948,6 +2362,10 @@ qt_resource_name = b"\
\x00d\ \x00d\
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\ \x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
\ \
\x00\x10\
\x03\xe6\xd3g\
\x00d\
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
\x00\x12\ \x00\x12\
\x09\xb3>\xc7\ \x09\xb3>\xc7\
\x00d\ \x00d\
@@ -1965,49 +2383,59 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x15\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x1a\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01r\x00\x00\x00\x00\x00\x01\x00\x00:\x16\ \x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00G\x5c\
\x00\x00\x01\x9f{C\xf1'\ \x00\x00\x01\x9f{C\xf1'\
\x00\x00\x02\x02\x00\x00\x00\x00\x00\x01\x00\x00Pr\ \x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00dK\
\x00\x00\x01\x9f{0\xc99\ \x00\x00\x01\x9f{0\xc99\
\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00/`\ \x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<\xa6\
\x00\x00\x01\x9f\x7f&\x83\xcd\ \x00\x00\x01\x9f\x7f&\x83\xcd\
\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00'x\ \x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x004\xbe\
\x00\x00\x01\x9f{C\xf1\x18\ \x00\x00\x01\x9f{C\xf1\x18\
\x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00`\xe1\ \x00\x00\x03R\x00\x00\x00\x00\x00\x01\x00\x00}\x9f\
\x00\x00\x01\x9f\x7fY\xceg\
\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00V\xbd\
\x00\x00\x01\x9f\x7fV\xd5\xc0\
\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00t\xba\
\x00\x00\x01\x9f\x7f&\x83r\ \x00\x00\x01\x9f\x7f&\x83r\
\x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x00Iw\ \x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x00]P\
\x00\x00\x01\x9f\x7f&\x83\xb1\ \x00\x00\x01\x9f\x7f&\x83\xb1\
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x006\x96\ \x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00$|\
\x00\x00\x01\x9f\x7fY\xce\x82\
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00C\xdc\
\x00\x00\x01\x9f{C\xf1.\ \x00\x00\x01\x9f{C\xf1.\
\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x01\x00\x00!\xca\ \x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x00/\x10\
\x00\x00\x01\x9f{{\xa5\xd5\ \x00\x00\x01\x9f{{\xa5\xd5\
\x00\x00\x02\xba\x00\x00\x00\x00\x00\x01\x00\x00i\xc6\ \x00\x00\x03x\x00\x00\x00\x00\x00\x01\x00\x00\x81\xdf\
\x00\x00\x01\x9f\x7f&\x83\x10\ \x00\x00\x01\x9f\x7f&\x83\x10\
\x00\x00\x00\xba\x00\x00\x00\x00\x00\x01\x00\x00\x1b\xcd\ \x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00 \xd9\
\x00\x00\x01\x9f\x7f&\x83~\ \x00\x00\x01\x9f\x7f&\x83~\
\x00\x00\x02L\x00\x00\x00\x00\x00\x01\x00\x00\x5c\xfe\ \x00\x00\x02\xe4\x00\x00\x00\x00\x00\x01\x00\x00p\xd7\
\x00\x00\x01\x9f\x7f&\x83T\ \x00\x00\x01\x9f\x7f&\x83T\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\ \x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
\x00\x00\x01\x9f\x7fY\xce^\
\x00\x00\x00\x82\x00\x00\x00\x00\x00\x01\x00\x00\x11\xcd\
\x00\x00\x01\x9f{0\xc9B\ \x00\x00\x01\x9f{0\xc9B\
\x00\x00\x00\xd8\x00\x00\x00\x00\x00\x01\x00\x00\x1fp\ \x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00,\xb6\
\x00\x00\x01\x9f\x7f&\x83)\ \x00\x00\x01\x9f\x7f&\x83)\
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x00W|\ \x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00kU\
\x00\x00\x01\x9f{C\xf1N\ \x00\x00\x01\x9f{C\xf1N\
\x00\x00\x01\xb2\x00\x00\x00\x00\x00\x01\x00\x00EH\ \x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00R\x8e\
\x00\x00\x01\x9f\x7f&\x82\xf2\ \x00\x00\x01\x9f\x7f&\x82\xf2\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x15-\ \x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x1a9\
\x00\x00\x01\x9f{\x8d\xf34\ \x00\x00\x01\x9f{\x8d\xf34\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9f{0\xc9+\ \x00\x00\x01\x9f{0\xc9+\
\x00\x00\x01\x92\x00\x00\x00\x00\x00\x01\x00\x00=v\ \x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00J\xbc\
\x00\x00\x01\x9f{C\xf1=\ \x00\x00\x01\x9f{C\xf1=\
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\ \x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\x00\x00\x01\x9f\x7f&\x83\x99\ \x00\x00\x01\x9f\x7f&\x83\x99\
\x00\x00\x02\xe4\x00\x00\x00\x00\x00\x01\x00\x00l\x5c\ \x00\x00\x03\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x84u\
\x00\x00\x01\x9f{{\xa5\xe3\ \x00\x00\x01\x9f{{\xa5\xe3\
\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x00d\xd3\ \x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x00'\xef\
\x00\x00\x01\x9f\x7fV\xd5\xe5\
\x00\x00\x03*\x00\x00\x00\x00\x00\x01\x00\x00x\xac\
\x00\x00\x01\x9f{0\xc9R\ \x00\x00\x01\x9f{0\xc9R\
" "

View File

@@ -235,7 +235,6 @@ class DocumentController(QObject):
source: Endpoint, source: Endpoint,
target: Endpoint, target: Endpoint,
*, *,
routing: str = "angled",
waypoints: list[QPointF] | None = None, waypoints: list[QPointF] | None = None,
) -> str: ) -> str:
if self.active_component_id is None: if self.active_component_id is None:
@@ -246,14 +245,11 @@ class DocumentController(QObject):
raise ValueError("A connection endpoint no longer exists") raise ValueError("A connection endpoint no longer exists")
if not PortTypeRegistry.compatible(source_port.type, target_port.type): if not PortTypeRegistry.compatible(source_port.type, target_port.type):
raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}") raise ValueError(f"Cannot connect {source_port.type!r} to {target_port.type!r}")
if routing not in {"direct", "angled", "spline"}:
raise ValueError(f"Unknown connection routing: {routing}")
connection = Connection( connection = Connection(
str(uuid4()), str(uuid4()),
source, source,
target, target,
properties={ properties={
"routing": routing,
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])], "waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
}, },
) )
@@ -267,7 +263,6 @@ class DocumentController(QObject):
end: QPointF, end: QPointF,
*, *,
text: str = "", text: str = "",
routing: str = "angled",
waypoints: list[QPointF] | None = None, waypoints: list[QPointF] | None = None,
) -> str: ) -> str:
if self.active_component_id is None: if self.active_component_id is None:
@@ -297,16 +292,13 @@ class DocumentController(QObject):
layer=-1, layer=-1,
properties={ properties={
**style, **style,
"routing": routing,
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])], "waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
}, },
) )
self.undo_stack.push(AddAnnotationCommand(self, self.active_component_id, annotation)) self.undo_stack.push(AddAnnotationCommand(self, self.active_component_id, annotation))
return annotation.id return annotation.id
def set_route_waypoints( def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
self, item_kind: str, item_id: str, waypoints: list[QPointF], routing: str | None = None
) -> None:
item = ( item = (
self.active_graph.connections self.active_graph.connections
if item_kind == "connection" if item_kind == "connection"
@@ -316,8 +308,6 @@ class DocumentController(QObject):
return return
old = deepcopy(item.properties) old = deepcopy(item.properties)
new = deepcopy(old) new = deepcopy(old)
if routing is not None:
new["routing"] = routing
new["waypoints"] = [{"x": point.x(), "y": point.y()} for point in waypoints] new["waypoints"] = [{"x": point.x(), "y": point.y()} for point in waypoints]
if old != new: if old != new:
self.undo_stack.push( self.undo_stack.push(

View File

@@ -0,0 +1,18 @@
from PySide6.QtWidgets import QDialog
from bedit.gui.generated.ui_connection_chooser_dialog import Ui_ConnectionChooserDialog
class ConnectionChooserDialog(QDialog):
def __init__(self, labels: list[str], default_index: int = 0, parent=None) -> None:
super().__init__(parent)
self.ui = Ui_ConnectionChooserDialog()
self.ui.setupUi(self)
self.ui.connectionList.addItems(labels)
if labels:
self.ui.connectionList.setCurrentRow(max(0, min(default_index, len(labels) - 1)))
self.ui.buttonBox.button(self.ui.buttonBox.StandardButton.Ok).setEnabled(bool(labels))
@property
def selected_index(self) -> int:
return self.ui.connectionList.currentRow()

View File

@@ -215,6 +215,89 @@ ff\xa6\x5cP`\xb7\xadY\x1b\xde\x8b\xcb\x97/\xc3\
\x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\ \x80\x90\x84\xfe\x83\xfe\x0fl\x00\x00/\xbf\xf2\xf2\x7f\x9c\
\xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\ \xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\
ND\xaeB`\x82\ ND\xaeB`\x82\
\x00\x00\x05\x08\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x03\x00\x00\x00D\xa4\x8a\xc6\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01B(\
\x9bx\x00\x00\x00\x07tIME\x07\xd9\x06\x0a\x00\x03\
\x0a\xd2\xe0H^\x00\x00\x01\xfbPLTE\x00\x00\x00\
\xa2\xa2\xa6\xa7\xa7\xab\xa8\xa8\xad\xaf\xaf\xb2\xb1\xb1\xb3\xb3\
\xb3\xb5\xb6\xb6\xbb\xc4\xc4\xc8\xcb\xcb\xcf\xb9\xb9\xbb\xba\xba\
\xbe\xc4\xc4\xca\xaa\xaa\xb0\xb0\xb0\xb4\xac\xac\xb2\xad\xad\xb2\
\xcb\xcb\xd1\xbe\xbe\xc3\xc2\xc2\xc6\xa4\xa4\xa8\xad\xad\xb1\xae\
\xae\xb1\xb5\xb5\xb6\xaf\xaf\xb3\xb0\xb0\xb3\xb4\xb4\xb9\xb4\xb4\
\xba\xca\xca\xce\xa9\xa9\xad\xb2\xb2\xb6\xa7\xa7\xac\xa8\xa8\xac\
\xcb\xcb\xcf\xcb\xcb\xd0\xa1\xa1\xa5\xa2\xa2\xa5\xb2\xb2\xb4\xa2\
\xa2\xa8\xa4\xa4\xa9\xcc\xcc\xd0\x9c\x9c\xa1\x9e\x9e\xa3\xb3\xb3\
\xb8\xb4\xb4\xb9\xc3\xc3\xc7\xc3\xc3\xc8\xac\xac\xb1\xad\xad\xb2\
\xa6\xa6\xa9\xc7\xc7\xcc\xc8\xc8\xcc\xb1\xb1\xb3\xad\xad\xaf\x9d\
\x9d\xa2\x9e\x9e\xa4\xa1\xa1\xa4\xa2\xa2\xa6\xcb\xcb\xcf\xcb\xcb\
\xd0\x9a\x9a\x9e\x9b\x9b\xa0\xc5\xc5\xc7\xd2\xd2\xd3\xd2\xd2\xd4\
\xd3\xd3\xd5\xd6\xd6\xd8\xd7\xd7\xd7\xd7\xd7\xd8\xd9\xd9\xdb\xda\
\xda\xdb\xdc\xdc\xdd\xde\xde\xdf\xdf\xdf\xe1\xe0\xe0\xe1\xe2\xe2\
\xe2\xe2\xe2\xe3\xe2\xe2\xe4\xe3\xe3\xe4\xe3\xe3\xe5\xe6\xe6\xe7\
\xec\xec\xed\xed\xed\xee\xee\xee\xef\xef\xef\xf0\xf0\xf0\xf1\xf1\
\xf1\xf1\x97\x97\x9c\x98\x98\x9e\x99\x99\x9e\x9a\x9a\x9f\xa7\xa7\
\xaa\xa8\xa8\xab\xab\xab\xad\xab\xab\xae\xac\xac\xaf\xad\xad\xaf\
\xad\xad\xb0\xae\xae\xb0\xae\xae\xb1\xaf\xaf\xb1\xb0\xb0\xb4\xb1\
\xb1\xb4\xb3\xb3\xb8\xb5\xb5\xb8\xb7\xb7\xbb\xb8\xb8\xbc\xba\xba\
\xbc\xbb\xbb\xbd\xbb\xbb\xbe\xbc\xbc\xbe\xbd\xbd\xc2\xbf\xbf\xc2\
\xbf\xbf\xc4\xc5\xc5\xc7\xc9\xc9\xcd\xcb\xcb\xcf\xcc\xcc\xd0\xcd\
\xcd\xd0\xce\xce\xd2\xd5\xd5\xd6\xd5\xd5\xd7\xd7\xd7\xd7\xd7\xd7\
\xd9\xdb\xdb\xdd\xe0\xe0\xe0\xe0\xe0\xe1\xe1\xe1\xe1\xe2\xe2\xe2\
\xe3\xe3\xe3\xe4\xe4\xe3\xe4\xe4\xe4\xe4\xe4\xe5\xe5\xe5\xe5\xe6\
\xe6\xe6\xe7\xe7\xe7\xe7\xe7\xe8\xe8\xe8\xe8\xe9\xe9\xe9\xea\xea\
\xea\xeb\xeb\xea\xeb\xeb\xeb\xeb\xeb\xec\xec\xec\xea\xec\xec\xec\
\xec\xec\xed\xed\xed\xec\xed\xed\xed\xed\xed\xee\xee\xee\xee\xef\
\xef\xee\xef\xef\xef\xef\xef\xf0\xf0\xf0\xf0\xf1\xf1\xf1\xf2\xf2\
\xf2\xf3\xf3\xf3\xf4\xf4\xf4\xf5\xf5\xf5\xf6\xf6\xf6\xf7\xf7\xf7\
\xf8\xf8\xf8\xf9\xf9\xf9\xfa\xfa\xfa\xfb\xfb\xfb\xfc\xfc\xfc\xfd\
\xfd\xfd\xfe\xfe\xfe\xff\xff\xffN\x89\xad\xb6\x00\x00\x00W\
tRNS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0d\x17\
\x18+2668<<CZZ]^^^^\
aq\x86\x8c\x8c\x8d\x8d\x95\x95\xb1\xcc\xcc\xcc\xd2\xd2\xda\
\xda\xdc\xdc\xea\xea\xeb\xeb\xeb\xf1\xf5\xf6\xf6\xf6\xf6\xf6\xf6\
\xfa\xfa\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\
\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xfe\xee\x1d\xc8\xff\x00\
\x00\x00\x01bKGD\xa8P\x086\x92\x00\x00\x02#\
IDAT\x18\x19\x05\xc1\xcd\x8b\xd5e\x1c\x07\xd0\xf3\
\xf9>w\xee\xdc\x19c\x1a4M\x223\x95\x8cj\x91\
\x8bVE\x9b \x08!\xa1E\xabh\xd3\xbf\xd6\xa6\x82\
6-\x0c{\xd9\x19\xb5\x09\x82\xa8\xb0\xec\x05\x99A/\
5\xf9\x92\xe9tg~\xcf\xd39\x00\x00\x00\x00\x00`\
\x03`\x03\x003\x00,N\xae\x1f}\xd9\xd7{\xab\xdb\
\x0f\x01\x08`\xb1\xff\xe4\x89\xd7\xd7\x16\xf3\xb5i\xb5?\
\x1d|\xb9\xdcY\xec\x03\x0d\xb0x\xf6\xad\xe9\xa5\xcd5\
\xd3\xc8l\xb6\xf8\xf3\xb5\x7f\xef\xae\x80\x06\x1c{o\xf5\
\xea\x99>j\x0a\x19\xfd\xa9G\xfez\xe3\x97\x87 \xe0\
\xe8\xbb7\xde<\x9c\xf4>M&m^\xdd\x17O\xbf\
\xff74\xac/\xde\xb9y\xa9\xc7\x88^css\xa6\
s\xfa\xdbW~\xac\x89\x86\xe9|{{\x182Ff\
\x1b\x12r\xd8\xce\x5c\x9d\xdfD\xc3\x13\x97.va\xa4\
\xcf\x9b\xf4\x8c\x8c\xb8w\xea\xe4\xee?\x14\x8e\xef\x8c*\
\xa92\x97\x91\xeaE\xdd\x9f\xfcp\x1cM\xdb\xbax1\
\xc2H\x8d0\x92^\xfd\xc1\x8a3[\xbf\x1e\x8c2m\
o#\xa9\xaa$#\xa1\xba\xc3V\xe9G\xb6'\xc5\xfa\
I\x92J\xabdDF\xd4~\xa92_\xa7\xd8~\x0c\
R\x92\xaa\x18\x91\xc3\x9eD\xd6\xb6)\xce6\x88$ \
\x1cH\xa5j\xe3\x1c\xc5\x00\xaaR\xa9$LSI\xa5\
2(v'0\x12I!\xbd)I\xfe\xdb\xa1\xd8[\
\x82\xa4\xaa\x221zk\xa2\xb2\xda\xa3X}\x02\x88T\
*Q\x9aJ\xea\xea\x8a\x92\xbb\xcf\x00FE%\x86\xd2\
\x94\x8dsw\xa2\xf1`~\xe3\xf9@\x92a\xd0\x8d\xf4\
\x1a\xae\xec\xfeLa\xf7D\x00\x92JR\xa5iGN\
\xef\xa0\xe1\xdet\xfd\x02 b\x18\xe9\xc9\xec\xa3\xdf\xaf\
\xa1P\xd7\x8f}<@\x92H\x952\xbb|\xeaz\xa1\
a\xacn\xbd\xf0\xfds\x80\x11]\xd4\xe5cWn\x0f\
4\xf0\xe0\xd6\xf9o^\x1c\x81\x0c\x8c\xf6\xc1\x89\xcf\x97\
\xa0\x01\xf7\xff\xd8\xb0\xb5@b\xd4ly\xed\xb7\xaf\xf6\
\x80\x06\x98vn.\x96\x9f\x9d\x9dG\xbf\xf3\xe1\xfe\xf2\
\xd3\x9f\xa6\x0e\x04`\xed\xc0\xe3[\x8f^8\xac\xfa\xee\
\xee\xbd\xa5\xb5\x03\x00\x00\xcc\xb1\xb9\x899\x00\x00\x00\x00\
\x00\xfc\x0f\x04u\xc6)\xe3{\x06,\x00\x00\x00\x00I\
END\xaeB`\x82\
\x00\x00\x08h\ \x00\x00\x08h\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -520,6 +603,142 @@ K,~\x0d\x01\x02\xc2\x08\xa0\x0d\x98$\xc1\xd4\xe8C\
S\xa0\x81ahl}\xb5P\xd4\xd7\xb8\xe9z\x86\x0e\ S\xa0\x81ahl}\xb5P\xd4\xd7\xb8\xe9z\x86\x0e\
>L\x06\x80\x01`\xc7\x01\x7f\x03\xdc\xff\x15WD\x01\ >L\x06\x80\x01`\xc7\x01\x7f\x03\xdc\xff\x15WD\x01\
nd\x00\x00\x00\x00IEND\xaeB`\x82\ nd\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x03o\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x02\xecID\
ATx\xda\xed\x97\xddN\x1aA\x14\xc7gvfw\
\xd9E\x10\x10\x95\xa47\xc6\xe2\x17\x9aX\xb7\x8am\xb5\
\xe5\xda\xcb\xbeP\x13_\xc3X\xa9R\x04\xa5\xd6\xa8\xa5\
j\x9b\x18\xb0I\xfb\x16\xbe\x81\xbdjb\xaa\xecN\xcf\
\x0c\xccB\xd8\xc6H\x01\xe9\x85'9\xcc\xecY\xc2\xff\
w>2\xcbb\xc6\x18\xea\xa5)\xe0\x0f\x00\xff\x17\xc0\
\xc9\xc9\xe9\xce\xf1\xf1\xd7T\xcf\x00l\xdbYB\xc8Y\
?::N\xf5\xac\x05cc\x8f\x0dJ\x15\x01q\xdf\
\x00\x98\x7f\x04\x83\xc1\x1a\x04^?<\xfc\xfc\xea\xbe[\
\x800\xc6\x02\x22\x1e\x8f\x1b\x8a\x82\xdf\x1e\x1c\x14_\xde\
7\x80\xf0\xfe\xfe \x1a\x1f\x8f\x1b\xb0\xdd\xd8\xdf\xff\xb4\
\xdcu\x80\xd5\xd5U\xcc\x98\x00p=\x14\xeaG\x93\x93\
\xe3\x00\xc1\xd2\x85\xc2\xfer\xd7+\xc0\xc0`\xf1@L\
MM\x18\x84\x90\x8d\xdd\xdd\x8fK]\x05p\x9cj\x05\
\x9a!\xc2\xe1\x10\x9a\x9e\x9e0\x09\xc1\xe9|\xfe\xc3\x8b\
\xae\x00$\x12\x09l\xdb\xcc\x15\xf5B\x84\x01\x22\x01\x10\
\xca\xbbB\xa1\xf0\xbcK-\xb0\x1bD\xbd\x10\x91H\x18\
\xcd\xcc$L\xdb\xc6\x9b\xb9\x1c@t\xbe\x05\x02\xe0V\
\x88XlX\x9b\x9f\x9f\x0b0\x866\xb3\xd9\x9dg\x1d\
\x03\x18\x1c\x1c\x84\x16\xa0\xfa\x10z!\xb0\xae\xeb>\x15\
lxx\xc8W\x85`[\x99L~\xb1#\x00\xe5r\
u\x08)\xa5\x9e\xac\x094\x9e\x8b+`\xbcS\xdcc\
\xb1!}aa\xae\x8f1'\x93Ng\x16;4\x03\
\x0e\xe2\x1a\x8d\x10\xb4j\xba\xb8\xdf`\x00{\x03\x83\xe9\
X\xd6\x13\x13c%\xb3\xb6\x06\x10\xed\x00\xa4R\x5c\x00\
\x09\x00\x09A\xc0`O\xb96\xc08\xdc\xf9\xa8\xd8\xb6\
}]\xa9Tn\xe0\x1aZ7\x80\x92I\xcb4M5\
\xb3\xb5\xb5\x9dl\xab\x02\xd0^.*\x00\xb86@\x10\
\x08\x0ba\x998\x08_C\xf6\xb6l\x0f\xff\xee\xc0@\
\x04Y\xd6\xac\xa9i\xfa{\x18\xcc\x85v\x00d\xf6\x98\
\x80\xf1\xcc\xe1Z(\xd72\xaf\xf0\x98\x1c\xccf\x88\xd9\
\xd9\x19\xd3\xe7\xd3\xb3\xf9\xfc\xe1|\xcb\x00\x81@\x00S\
\xaarA\x84\xab\xc6\xa4C\xcc\xa9e\xcd\xa4\xe8\xdf \
\x22\x91\x10?\xac\x00\x82f\xf7\xf6\x00\xa2\xf5\x0a\x10\xb1\
rA\xeeP\x09\xbe\x8a\x16@A\x98\x14\xbc\x0d\x22\x1c\
\xe6\xcf\x8e1\xbfa\xe8\xd9b\xf1\xcb\xd3\x96\x00\xa0\x02\
|\x91\x102{\xe1r0\xef\x02\x11\x0a\x89?5~\
J\xc9\xf6\xe9\xe9\x99u'\x80\x8b\x8b\x0b,*P7\
W\x5c\x0ef+\x10\xc1`\x00\x8d\x8e\x8e\x00\x84\x9a;\
;\xfbf\xdd\xa9\x02\x84\xd0\xe6\x90+\xfco\x10}h\
d\xe4\x91_U\x95\xdc\xf9\xf9\x8f9O\xc5\x9b\x8eb\
\xe8\xb7\xf6\xab\x5c\xfe\xeeW<h\xf2\xc7\xc5a\xe4\x89\
+\x0av\x83\x8e\xc3\xe0Z\xde\xc3\xb5\xb9\xa1\x06!\xf8\
\x0dl_\xa3\x06\xf3\xbc\x1b\x96J%zuu%\xfa\
`\x18\x06\x96q8\x86\xdd\xfd\xe5\xa5\xe6\xee5Mm\
\xd8\xff\xc4\xf5a\xae\xc7\xa3\xd1\xa8l\xf1\xf5\xca\xca\xca\
\xefF\xbd\x87\x97\xd3\x9e\x03\xfc\x01x$\x0aI\xcbR\
\xf1S\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x04\xc3\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
B(\x9bx\x00\x00\x00\x07tIME\x07\xd9\x07\x0c\
\x14\x17\x01h\xb8;C\x00\x00\x04CIDATx\
\xda\xdd\xd6\x7fH\xdce\x1c\x07\xf0\xf7\xd7\xdd\xd7;\xcf\
;m\x8av*\x82\x9b\xc6v\x88#%3K\xb1p\
6i\xee&\xbbTJwl:\xd6Z\xb9J\xcd\xd3\
\xb4\xb9\xb2\xd8b\xb8\xa8E0R\x1c\xb2\xb9Y\xdb\x1f\
kZ\x84\x19bA3\xb0\xd9,E\xbc\xfcIj\xb3\
\x89\xe4\xdd\xf9\xfdu\xf7\xf4\x1c\xf9\xc7\xed\x1b\x87\xdf\xd3\
0\xe8\x05o\x9e\xe3\xe0\xf8<\xcf\xe7y\xee\xf9~\xb1\
\xc9Jij\xf1\x1f\xd1[,\x16\x92\x90\x90@\x00,\
\xd2\xec\x05\x15\x80MB\x0b_\x09\x09\x09!6\x9b\x0d\
\xd4\x034\x9d4\xcd\xd8$OTTT\x90\xd8\xd8X\
\x02\xc0;%\xd8\x0c\xd9\xd9\xd9\xf3\xf9\xf9\xf9n\xaf\xc2\
\x9e\xcf6l\x06\x86aj\xea\xeb\xeb\x09m\xbf|\xf5\
i\xd8\xa8\xa6\xdb$g\xad\x83W\x5c\x5cLRSS\
\x89l\xf5\xdd\xa0\xd6}\x08\xcf\xfdD\x8e\xd1\xe2e\x00\
\xfa\xde\xe9#\xf0%>>\xfejdd$\x19\x18\x18\
\x80\x17\x86\xc6\x02\x7f\xd4}\xe5\x84\xc7\xe9~r\xf8L\
?i\x802\x99UUU$..N\xde\xfa\x0b\xf0\
Gu\xd7\x12\x1a\xfa\x08\xfb\xe6\xd7b\xc7\xdb\xdf\x92m\
\xa0\x1az\x04%\x07\xef\xf7\x82\x82\x02\xf9\xc1\xf3\x8c*\
P\x8a\xb7 @\x9d\xcc9\x1d\x1d\x8d\xbb\xd9\x22\xce\
\xee\x98\x00\xf5Vv \xd6\xf0Ffffdww\
7#k\xbd\x95F\x82R\x95]\xf6G\xaan\xfe\xd9\
\x0e\xaa\xea\xc6\x22\x14\x0a)))!\xe9\xe9\xe9\xf2\x83\
\xb7\x04\xa5J[\xc6\xf0\xf2\xb5\x85\xe4\xf2\xeb\x0b7A\
\x9d\xf8l\x0eJm\xdf\xbe\xfd\xcb\xea\xeaj\xb7J\xa5\
\x92\xef\xbd\x19JX>\x19\xc1\xd1K\x93\xea#m\x13\
\xbf\x80:vy\x0aJXU*P\x99\xd5V\xab\xe7\
\xbe\x97\xaf~\x18\xfe8\xd42\xd6_\xda6\x11U\xda\
:\x06\x7fT\xc6\xc4,\x1f-,\xf4\x14$\xb2<\x0c\
\x1fT\xf0r\xa0\xe9\x07ht\xfa,^\x10\x86\xaf\x94\
=4\x07?\x94\xb3\xac5G\xa7\xd3%\xcd\xcc@m\
0\xe0\xfc\xfc<V\x8b\x7fA3\x08\x1f\x18\xc8\x98?\
\x18\xb0\x05l\x09\xdc\xc5\x10\xd1\xf9iy\xca\xda[F\
s\x07\xd0\x1c\x0c\x0f_\xc96\x1a\xf1\xdb\xe2\x22\xf4,\
\x8b\xb4\xa8(\x98o\xddB\xe7\xd2R\x04\x80?\x14u\
\xc0tn\xc0 J.\xc7\xe7\xaf$9\xa1P\x9b\xa7\
\xf5ju\xf3NZ\x9ca\x18Haa\x08\xa6\x13\x10\
\xddn<\xedr}\xd4\xe9\xbb\xf8?\xef\x01ae\xe5\
\xa0\xc0\xf3\x17\xa1PY@\x00\x9e\x03v\xec\xdc\xba\xb5\
8:(\x08\xe3\x92\x04=\xc7!2,\x8c\xf4\x8e\x8e\
\x8a'\x96\x97\xcb_\x03\xa0|\x02\x02\x9f&\xf0\xc2m\
(\xd4BW\x99\xa0V\xb7?\x16\x1dM\xee\xd1\xe2\x8c\
\xdd\x8eX\xad\x16K\xcb\xcb\xcc\xd0\xec\xec\x0b\xa0\xde\xf7\
g\x02\xbc\xc0\x1b9\x9e\x9b\x87r\xc9\xe3EE)\xdf\
\x89\xa2C\xb3\xb0\x80\x88\x88\x08\x12F\x1f>\x83\x83\x83\
\xe3\xb5\xa2\xd8\x0a\x0f?; \x09\x1c\xa7\x85B\x06\x83\
\xe1u5\xcb\x92\x17\x87\x86t\xfbGF\x9c,\xcf\xaf\
\xdc\x9b\x9cd\x86\xef\xde-ldY\xf8m\xd7K\x1d\
=I\xc7\xdb\xcd;\x0e7C\x01&77\x97\x18\x8d\
\xc6\xfb\x1e6)*U\x1bd\x94w\x80\xe7z\x04\xce\
\xf9\xcch\xeb\x11(PNo<x^2\xbd\xcc\xff\
(I\x16\xf8\x81\x81\x97\xf8C-\xb1.\x978MD\
W\xa0(\xf1\xe2\xec\xb5\x0a\xdf\x9b\x9f\x9c<\x17\x1a\x1a\
\xfa`oo/\xb3\xda\x01\xcf\x18C3\xbb\xee\x0e\xfc\
z\xb1lF\xe0V\xbe\x11%\xa1]^\x5c.##\
\xe3\xc6\xe8\xe8(\xe3\xb5\x10\xb3\xac\xb8\xff\xa2\xccM\x08\
\xdf\x7f68\xdct\x86\x84\x99\xce\x9e\x06\xa5\xdfs\x0a\
rZ\xadV\xd7\xd5\xd5Ezzz\xdct\x22w\x00\
\xbc\x07\x99uu`\xeez%\xb4z\xd6!\x08|\x12\
\xcf;j\xf4{N\xf6\x11\xa8\x0c\xa0\x02\xb3j\x81U\
N\xa7\xd3>==\xfd\xa1\xc3\xe1\x98\x92$\xe9I\x00\
\xd6\x0dM\xc0d21\xf0\x8c\xfb\xf2\x98\x99K\xaf\x22\
\x83\xf9~\xd8\xf1s\x83\x8a\xe3%\xb7\x9d\x13\xe7\xb6d\
\xd5v\x0ab\xf0\x81\xd0\x9c\xba P\x8d'k\xa25\
\x1a\xcd\x14\xfd\xdd\xb6\xc4\xc4\xc4El\x84\xe7\xaf\x04*\
//\xef\xfeq\xef\xdf\xdf?\x9b\xb7\x9b\xe0\xd1w\x1b\
\x9e\xdf\xf7\x14\xc1\xe3u\x04\xe9V\xdb\xf1S\x1f_\xc5\
\xbf\xc9\xd3\x01\x9f\xa3\x0f\x17Z/\xe3\x7f\xe1/\x17\x85\
\xd6\x06q(\x0e\x10\x00\x00\x00\x00IEND\xaeB\
`\x82\
\x00\x00\x02V\ \x00\x00\x02V\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1210,6 +1429,113 @@ G%\xc7A \x00\x1d\xd0\x80I\xe0\x03\x10;3\x85\
\xfaS\x08V\x01()\xc7\xe4\xb9C\x95,\x01\xd77\ \xfaS\x08V\x01()\xc7\xe4\xb9C\x95,\x01\xd77\
\x9aw\xed\x7f\xefoJ\x88l\x9a\x02\xc2\xbb\x00\x00\x00\ \x9aw\xed\x7f\xefoJ\x88l\x9a\x02\xc2\xbb\x00\x00\x00\
\x00\x00IEND\xaeB`\x82\ \x00\x00IEND\xaeB`\x82\
\x00\x00\x06\x8f\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x04sBIT\x08\x08\x08\x08|\x08d\x88\
\x00\x00\x00\x09pHYs\x00\x00\x03v\x00\x00\x03v\
\x01}\xd5\x82\xcc\x00\x00\x00\x19tEXtSof\
tware\x00www.inksca\
pe.org\x9b\xee<\x1a\x00\x00\x06\x0cID\
ATx\xda\xddWYLUW\x14=NiSQ\
\x8b\xad\xb1\xad\x16\x8d\x1dR\xd39\xa6\xa6\x95\xb6Im\
b\xda\xaf\xfe\xe0w\xd3~\x01%6V\x1cA\x8b\x8a\
\x10)NLB\xa1\x10@\x85\x08\x05\xc2P\xa8\x81\xfa\
\xe1\x00\xca\xa48\x81\xa2(\x93\x82\x82\xcc\xd3\xe3\x9d\xdd\
\xb5N^o.\x06\x1b\xb0$&}\xc9\x0a\xbcs\xef\
;{\xed\xbd\xd7\xdeg\x1f%\x22O\x15\xff/\x02\xf8\
L\x0b\x0a\x0ar{*\x046n\xdc\xf8Ahhh\
[zz\xfahHHH*\x88\xcc|\xf4\x9d\x83\x07\
\x0f.\xf2\xf6\xf6\x0e^\xb5j\xd5\xb7nnn\xeeS\
F`\xd3\xa6M\xcb\xf7\xed\xdb\xd7WYY)W\xaf\
^\x95K\x97.i\x18\xabpEczuu\xf5G\
\x87\x0e\x1d\xfa\x04k\xad>>>G===W\xcc\
\x993gj\x08l\xd9\xb2\xe5\xf5\xb0\xb0\xb0\xee\x0b\x17\
.\xc8\xe5\xcb\x97\xa5\xa6\xa6Fjkk\xe5\xc6\x8d\x1b\
:>>\xbe\xbd\xb4\xb4\xb4\x0c\xebCg\xce\x9c\x19\x01\
\x89R___\xf7)K\xc1\x86\x0d\x1b\x96\xec\xdd\xbb\
\xb7\x83F\xeb\xea\xea\xe4\xc1\x83\x07RUU%\xf0X\
\xea\xeb\xeb\xe5\xf6\xed\xdb\x92\x96\x96\xa6\xcf\x9f?/\xd7\
\xaf_\x97k\xd7\xae\x05N\x95\x06\x18\xf6W\x90\xf3v\
\x84[n\xde\xbci\x0c\xb6\xb6\xb6Jww\xb70\x1a\
$u\xeb\xd6-\xae\x19\x12\x88Joss\xf3\xe2)\
!\xb0u\xeb\xd6\x05{\xf6\xeci\xb9r\xe5\x8a\xdc\xb9\
s\x87\xde\x1b\x83\x0d\x0d\x0d\xd2\xde\xden\x91\xf8\xe7y\
cc\xa3NHH\xe8\x1cO\x98O\x92s\xf7\xdd\xbb\
w7 \x9c\x1a\x1e\x19\x0f\x07\x07\x07\x19^\x8a\x8f\x06\
\x99\x0aC\xe2\xe2\xc5\x8bF\x0fMMM\xd4\x84\x13\xa4\
S\xff\x13\x01z\xb0k\xd7\xaeZ(]\xdf\xbd{\xd7\
\x0aqGG\x07I\xd0\x98\xf1\x9a\xc4\x1e>|H\x12\
\x8c\x0c\xf3\xcf\xf7\x18\x95\xd1\x80\x80\x80\x1f\x9e\x98\xc0\xe6\
\xcd\x9b\xbf\xce\xcd\xcd\x1d\xbe\x7f\xff\xbe\xf4\xf5\xf51\xb4\
F\xf9mmm4h\x91\x00,=\xf4\xf4\xf4Xz\
\xe0zjj\xea\xc0\xda\xb5kgL\x86\x00=\x9f\xbe\
m\xdb\xb6H\xd4\xf1pdd\xa4\x93\xb5\xce\xcd\x1d\x0e\
\x87\x09/\xbf\x83\x14\xd7H\x82\x9a`\xc8I\x8cD\xcd\
:{\xc4\xe9\xd3\xa7%11q\x88\xfbM\x98\x00\xdb\
\xeb\xf6\xed\xdb\xd3\xa1r\xa7\xe0\x93\x94\x94$qqq\
\x9a\xf9\xee\xed\xed\xb5H\xd0\xbb\xce\xceN\xb3644\
\xc4\xb0\xd3k\x920d\xd0\x07$##\xc3\x89\xea\xf9\
~R)\x80\xe7\xbf\xc1\xc3Q|\xe4\xde\xbd{\xc6\xa3\
\xa8\xa8(\x1d\x1b\x1b\xabY\xef\x03\x03\x03\x16\x09\x1a\xea\
\xea\xea\x92\xfe\xfe~\x92\xa00\x99\x22\xe3yff\xa6\
\x13)\xfciR\x22\x84`\x22(\x1cnF#$A\
\xcf\xe8exx\xb8\x8e\x89\x89\xd1\x0c-\xc3\xceg\x14\
\x1f\xbd\xe6s\x86\x9d\xbd\xe1\xd4\xa9S\x92\x95\x95\xa5Q\
\xba?O\xea0\xc2\x0fB\xce\x9d;\xe7\xa0\xc7\x14\x12\
=\xa5\x91\x91\x91\x11\xe6\xd8D\x03\x15\xa1\xa1\x09]V\
V\xc6u\x8b\x04\x1b\x13SB\xcf\xb3\xb3\xb3u``\
`\xd8dNC\xd6z\x00\x98;l\xa2b.\xe9\x19\
\x8dX\xe1\xa5!DI\xa3\xc7\xeb\xb3g\xcf2J$\
\xcbr3\x9e\xe7\xe4\xe4\xe8\x1d;v\x1c\xb6\x8c\xc5\xa9\
Y*Je\x02_>\x8e\x00\x8f\xd5\x1fKJJ\x1c\
\x14\x14@\xcf\xad\xf2\xa2\xd74@\x12\x5c\xa7\xfaYb\
\xf8\x8d\xde\xbf\x7f\xbf.**\x221\xcb8\xc4{\xc4\
2t\x5c\xcdP\xb1*\x03\x10\xa0O\x1dV\x9e\x5c\x7f\
\xb4\xbf\x7fWXX8\xc2\x92b[%\xd8\xd5h\x8c\
\xc2\xa2A6 \x0a\x8d$\x18\x11V\x03N<Y\xbf\
~=C\xcdc\x98\x82\xd3;w\xee\xccf\x05\x891\
\xa2\xa6\xa9x\x95\x0c\x88\x0d\xc5|6\xa6\xd6\x0f\x1c8\
\xd0\xc7\x06\xc2~\xee2h\x95\x12I\xd0 U\xdd\xd2\
\xd2b\xba\xdf\xb0cX\xca\x9a\xca\xa4\xbc\xbc\x5c\x8a\x8b\
\x8b\x05\xbaq\xe2hv \xec\xb1cj=Q\xc5\x00\
bC\x8dJP\xf3\xc7\x10`w\xc2\x0f\xfb\x11~\x0a\
\x89\xe1\xb7\x0cB\xcd\xf4\x9c$\xac\xf6\xda\xd8\xd4(^\
\xc5^27m\xae\x9c\xa8?!'O\x9ed\x7f\x18\
e\xb7\xb4G\x15~\xff\xa2R\x94\xd8P\xa7\x8e\xa9\x85\
|6^\x0a|q\xd08\x90\x06\x1e*$A\x83\x0c\
3k\x9c\x9eS\x0f\xa6\xedz\x15z\x89:\xaa\x0c\xe6\
\xa5\xcf\x93\xa8\xdc(\x09\x0e\x0e\xae\xb7\x9fxx\x16\x04\
\x88\x0d\x0d\xea\x88Z\xfc\xafU\xe0\xef\xef\x9f\x8cM\x9c\
(\x1fj\x80$\x98s\x9el\xec\x01l8\x86Dh\
u\xa8\xa8tea\xf6\xb1\xd9\xce\xa5\xbf.]im\
\x9c\xa6\xfc\xb9nC3\x08,{\xd4\xde\xe3N\xbc\x0e\
\xe4Sc\xc0d\x09\x92\x84\xc99;\x1f\xbfs\xd2\xe1\
\x9a_\x89\x9f\xa0\xa8,@\xe3-\xeaw\xb5\x0c\xff\xfb\
\x00bC\x1b\xf0\xd6\x84\xfa\x00\xa7\x16N/h\xc1\x1a\
#\x97\xa4\xa4\xa40\xe7\xc6 \xab\x83\x9d\x8f\xe9\xa0\xf0\
\x0a\x0a\x0adu\xe2\xeaA\x95\xad\xc4\x866@\xdb\xbe\
w\xaa,\xf5\xfe\x84\x1b\x11\xe76\x86\x9as\x1c\x1a\x92\
^\xb7n\x9d`\xb8\x94\x8a\x8a\x0a\x920bd\xe7\xcb\
\xcf\xcf\x17L\xc2]\x9c\x0bU\x9e\x0a\x07d\x1c\xf4\x00\
+'|/\xe0\xc4\xca\xc9\x95\x13,'YN\xb4P\
u\xbb\x9f\x9f\x9f\x8e\x8e\x8efk55\x9f\x97\x97'\
,YN\xc4\xd6F\x05*Z\xfd\xa1\xc4\x86~\xac}\
>\xa1\x8b\x09gt\xce\xea\x9c\xd99\xbbs\x86\xe7,\
\xcf\x99\x9e\xb3=\xaa\xa3\x9c$0RIrr\xb2D\
DD\x0c\xf2.`\xdf\xc84\x9b\x22\x95\xa4\xfeT\x02\
\x0c\xa9\x13j\xcd\x84oF\xbc\xa5\xf0\xb6\xc2[\x0bo\
/\xe3\x09\x13\x06S\x00'\xdak'oA\xe3n\x16\
\xa4\xa6\xab\x12\x95\xaa\xfeR\xdf<\xd1\xd5\x0c\x9f\x99\xc0\
s\xc0<\xe0E\xe0e\xc0\x03X\x06\xbc\xe9\xe1\xe1\xb1\
\x02\x7f\xdf\x01\xde\x05\xde\xb3\x81\xdf\xdf\x06\x96\xf3=\xe0\
5`)\xf0*\xf0\x12\xb0\x00x\x1ep\x03\x9e\x01\xc6\
\x8ed\xec\xd9\x5c\x04\x9e\x05\xe6\x02/\xb8\x8c/\x01\xde\
pm\xfe!\xf01\xf0\x19\xb0\x1aX\x03|\xe5\xfa\xfb\
\x05\xf0)\xb0\x92\xef\xf1}\x12q\x91X\x04,\x04\xe6\
so\x97\x8dY\x80i\xd5\x7f\x03\x81\xe3\xecT\x9c\xdf\
M_\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x06\xf7\ \x00\x00\x06\xf7\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1739,6 +2065,76 @@ K\x80@\x89\x15\x8d\xc04\xd5\xb5^\xaf\x1bx\xfa\x19\
\x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\ \x84\xe7\x04\xcf\x88\xfd\xfd\xfd\xe4\xe8\xe8\xc8\x9dL&\x14\
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\ \xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
\xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\ \xb7\xa7\x00\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x04<\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
\x00\x00 \x00\x00\x00 \x08\x06\x00\x00\x00szz\xf4\
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
\x06bKGD\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
\x00\x00\x09pHYs\x00\x00\x0d\xd7\x00\x00\x0d\xd7\x01\
B(\x9bx\x00\x00\x00\x07tIME\x07\xd9\x06\x0a\
\x00\x09%\x83\xde\x9d\x8d\x00\x00\x03\xbcIDATx\
\xda\xedV\xcdKTQ\x14\xbfw\xe6\xcdG\xcf)\xc6\
\x19B\xc5\x8a\x1aE\x86\xa4\x85\x91!\x13\x99\x8b(\x0d\
\x94\xc12\x93\x04\xc9\x16m\x82P\xe8\x0f\x88v-l\
\x1bA\xad\x22RC\xcbA\xa6,\xa5\x19\xd0\x81\xc6>\
\x16-\xc2\x92 \x0cc2\xc7\xf2c\x9c\xcf\xdb9\xd1\
y\xbc\xde\xbc)\xc6 \x10\xe6\x07\x87{\xdf\xbc;\xe7\
\xf7;\xe7\xdc{\xeec\x05\x14\xb0\xa9\x10\x0e\xbf\xfa\xe7\
5Z\xf0<\xc8w\xc0\xd0$\x84h\xe0\x9c\xef\x87\xb9\
\x13l;\x98\x00\xfb\x02\xf6\x15\xdeM\xc3\xbbg0\xf7\
\xd7\xd6\xd6\xccOM=g\x1e\xcf\xc1\x8d\x09\x08\x04&\
\xd9\x91#\x87X(4}.\x93I\xf5\x18\x8d\xd2\xbe\
m\xdb\xb62Y\xb6\x0a\xab\xd5\xcc\x8dF#\x032\x06\
\xa4,\x9dN\xb3D\x22\xc5\xd6\xd7\xe3\x02\x8c\xc7bk\
,\x99\xcc\xbc2\x18\x0c}\x1eO\xed\x1d\xf2\x95\x97\x80\
`p\xca\x93J\xa5\xee\xdb\xed\xf6\xb2\xd2\xd2\xed\xc2f\
+\xe2\xf0\x8c\x84\x8a!p\xccd28*s\x14\x86\
b\xbe\x7f_\xe6\xd1\xe8\xb7\x8f\x92$\x9d\xac\xaf\xf7L\
\xeb\xf1\x18\xd4\x0f~\xffS\x86\x98\x98\x08^\x81\xa8&\
\xab\xab\xdd\xa5nw%+*\x9299V\x1b\x01\xe7\
@\xc2,\x16\x0b\xb3\xd9l\xb0\xbe\x889\x9d\x0e\xeer\
\xedfUU\x15;\xc1Wx|<p\x998rd\
\x80D<\xb9%\xcbrw]\xdd\x01\x8a\x0cGL3\
\xce\xb32\x80\xa5\x80T+\xd9@\xe0zz\xa62\xbd\
~\xfd\x06\xca\x94\xb8\xde\xd8x\xb47g\x06|\xbeG\
\x1d0tC\xba\xd019G\xc3yV\xc4f\xb3Y\
!W\x8bR\xafC1kkk\xcc\xe5\xda\x85A\xf4\
\xf8|\x8f\x9bu\x05\x0c\x0c\xf8L\xb0\xf0\xee\xe1\xc3\x1e\
\x01@\xc7\xe8\x00G2E\x94\xc9d\xa2\xc8r\x12S\
&b\xb1\x98\x92\x89\xca\xca=\xb0Ic#\xba\x02\x84\
H\x5cv:\x8b\x7f\xd6\x9bj\x0cd\xba\x02\x88P\xbb\
\x1fH\x14!\x1e\x8f\xff\xf6N\x96\xb7p(/\x04;\
tI+\x00\xeb\xd3ZQ\xe1R;\xa0\x0c\xa8\xc5\xa8\
I\xf5\xa2\xa7r\xa0?\x12\xae\x18\xfe^R\xe2\x14\xf1\
x\xc2\xab\x15\x80\xc7\xc6]\x5cl'\xc5\x04\x85\x1c@\
\x02\xfe&\x04k\xad=1\xf4?\xc8\xb0\x8d\x83\x007\
\xad\x95hB;\x9e\xa0#\x02\x1dS$h\xea\x9d\xae\
\xee\x09\xea}\xa0\x99c\x10\xb8&\xcd\xb32`6\x9b\
f\xa2\xd1\xa5\x5c\x22\xb4\x91SZ\xd5u\xd7\x0a\xd2]\
\xbb\xb2\x12\x13\xb0\x89g\xb4\x02\xf0X=x\xfbv\x86\
\xfe\xac\x07r\xa4\x15\x82\xa3.\xb9\xd6\xf0\xf4\xcc\xcd}\
\xe2\x92d|\x98%\x80s\xc3\xb5H$\xc2\x96\x97W\
\x84N\x16\x88@wSjE\x91\x11\xe8yuuU\
,-}\xc32]\xcf\x12\xd0\xd9\xd9\xbe\x0e\xad\xf4\xfc\
\xd8\xd88\xd7\xa4U[K\xdd\x1e\xa1\xfe]k\xf4.\
\x1c~\xc9\xa1y\x9d\xee\xea:\x9b\xd1\xed\x84\x1d\x1dm\
\xb7\xd3\xe9T\xbf\xdf?\x86\x02\xf2\x11\xa1\xee\x19\xba%\
\x08\x85\x9ec\xe47\x81c0g+\xee\xef\xbf\xcf\xda\
\xdbO\x9d\x81T\xf5\x8d\x8c\x8c\xb2\xc5\xc5EA\xa9\xcf\
!B7\x1b\xf4\x1e\xdb5\xdc\x88\x22\x18\x9c\x84\xbe\x10\
\xbf\x0a\xbe/\x00\xc7\x9f/\xa3\xc1\xc1!\xd6\xd6\xd6\xca\
\x86\x87G\x8e%\x93\x89{eee\xc5\xd5\xd5{\x05\
\xf4\x08\xaew\xec\x10tQ\xa9\x8f\xf2\xc2\xc2\x82\x98\x9d\
\xfd\xc0#\x91/\x118am^oK0\xaf\xef\x81\
\xa1\xa1\x07\xac\xb5\xd5\xcb \x13\x17S\xa9d\xaf\xd5j\
\xddS^^\x8e\xd7\xacp8\x1c\xdcb\xa1\x8bH\xe9\
\xf9\x90\xb1\xa8\x00b>?\xff\x19\xdb\xf0;I2\xf7\
\xb5\xb4\x9c\xb8A\xbe6\xfaIF7\xa5[\x88\xccq\
\xa0k\x00\xc2\x1a\xfc\x1c\x83h\xe5_%Y\xc1\xa0a\
|\x01\x85\x09\x800\x7fss\xd3{\xf6\x7fABG\
Y\x01\x05l*\xfc\x00!\x00\x12\xf1%U\xb6\x0e\x00\
\x00\x00\x00IEND\xaeB`\x82\
\x00\x00\x02\x92\ \x00\x00\x02\x92\
\x89\ \x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\ PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1875,6 +2271,10 @@ qt_resource_name = b"\
\x00z\ \x00z\
\x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\ \x00o\x00o\x00m\x00-\x00o\x00r\x00i\x00g\x00i\x00n\x00a\x00l\x00.\x00p\x00n\x00g\
\ \
\x00\x0f\
\x0bb\xb8g\
\x00d\
\x00r\x00a\x00w\x00-\x00c\x00i\x00r\x00c\x00l\x00e\x00.\x00p\x00n\x00g\
\x00\x14\ \x00\x14\
\x0b\xa9\xab'\ \x0b\xa9\xab'\
\x00d\ \x00d\
@@ -1889,6 +2289,15 @@ qt_resource_name = b"\
\x09\xd0z\x07\ \x09\xd0z\x07\
\x00a\ \x00a\
\x00r\x00r\x00o\x00w\x00-\x00u\x00p\x00.\x00p\x00n\x00g\ \x00r\x00r\x00o\x00w\x00-\x00u\x00p\x00.\x00p\x00n\x00g\
\x00\x11\
\x06\xfb\x85G\
\x00d\
\x00r\x00a\x00w\x00-\x00t\x00r\x00i\x00a\x00n\x00g\x00l\x00e\x00.\x00p\x00n\x00g\
\
\x00\x0d\
\x0f\xba\x1e'\
\x00d\
\x00r\x00a\x00w\x00-\x00p\x00a\x00t\x00h\x00.\x00p\x00n\x00g\
\x00\x0d\ \x00\x0d\
\x0b\xe6\x1f\xa7\ \x0b\xe6\x1f\xa7\
\x00d\ \x00d\
@@ -1922,6 +2331,11 @@ qt_resource_name = b"\
\x00d\ \x00d\
\x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\ \x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\
\x00s\x00.\x00p\x00n\x00g\ \x00s\x00.\x00p\x00n\x00g\
\x00\x13\
\x04\xc4\x04G\
\x00n\
\x00e\x00t\x00w\x00o\x00r\x00k\x00-\x00c\x00o\x00n\x00n\x00e\x00c\x00t\x00.\x00p\
\x00n\x00g\
\x00\x0c\ \x00\x0c\
\x06\xeb\x97\xe7\ \x06\xeb\x97\xe7\
\x00z\ \x00z\
@@ -1948,6 +2362,10 @@ qt_resource_name = b"\
\x00d\ \x00d\
\x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\ \x00o\x00c\x00u\x00m\x00e\x00n\x00t\x00-\x00s\x00a\x00v\x00e\x00.\x00p\x00n\x00g\
\ \
\x00\x10\
\x03\xe6\xd3g\
\x00d\
\x00r\x00a\x00w\x00-\x00e\x00l\x00l\x00i\x00p\x00s\x00e\x00.\x00p\x00n\x00g\
\x00\x12\ \x00\x12\
\x09\xb3>\xc7\ \x09\xb3>\xc7\
\x00d\ \x00d\
@@ -1965,49 +2383,59 @@ qt_resource_struct = b"\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x15\x00\x00\x00\x03\ \x00\x00\x00\x00\x00\x02\x00\x00\x00\x1a\x00\x00\x00\x03\
\x00\x00\x00\x00\x00\x00\x00\x00\ \x00\x00\x00\x00\x00\x00\x00\x00\
\x00\x00\x01r\x00\x00\x00\x00\x00\x01\x00\x00:\x16\ \x00\x00\x01\xde\x00\x00\x00\x00\x00\x01\x00\x00G\x5c\
\x00\x00\x01\x9f{C\xf1'\ \x00\x00\x01\x9f{C\xf1'\
\x00\x00\x02\x02\x00\x00\x00\x00\x00\x01\x00\x00Pr\ \x00\x00\x02\x9a\x00\x00\x00\x00\x00\x01\x00\x00dK\
\x00\x00\x01\x9f{0\xc99\ \x00\x00\x01\x9f{0\xc99\
\x00\x00\x018\x00\x00\x00\x00\x00\x01\x00\x00/`\ \x00\x00\x01\xa4\x00\x00\x00\x00\x00\x01\x00\x00<\xa6\
\x00\x00\x01\x9f\x7f&\x83\xcd\ \x00\x00\x01\x9f\x7f&\x83\xcd\
\x00\x00\x01\x18\x00\x00\x00\x00\x00\x01\x00\x00'x\ \x00\x00\x01\x84\x00\x00\x00\x00\x00\x01\x00\x004\xbe\
\x00\x00\x01\x9f{C\xf1\x18\ \x00\x00\x01\x9f{C\xf1\x18\
\x00\x00\x02p\x00\x00\x00\x00\x00\x01\x00\x00`\xe1\ \x00\x00\x03R\x00\x00\x00\x00\x00\x01\x00\x00}\x9f\
\x00\x00\x01\x9f\x7fY\xceg\
\x00\x00\x02P\x00\x00\x00\x00\x00\x01\x00\x00V\xbd\
\x00\x00\x01\x9f\x7fV\xd5\xc0\
\x00\x00\x03\x08\x00\x00\x00\x00\x00\x01\x00\x00t\xba\
\x00\x00\x01\x9f\x7f&\x83r\ \x00\x00\x01\x9f\x7f&\x83r\
\x00\x00\x01\xe4\x00\x00\x00\x00\x00\x01\x00\x00Iw\ \x00\x00\x02|\x00\x00\x00\x00\x00\x01\x00\x00]P\
\x00\x00\x01\x9f\x7f&\x83\xb1\ \x00\x00\x01\x9f\x7f&\x83\xb1\
\x00\x00\x01T\x00\x00\x00\x00\x00\x01\x00\x006\x96\ \x00\x00\x00\xfc\x00\x00\x00\x00\x00\x01\x00\x00$|\
\x00\x00\x01\x9f\x7fY\xce\x82\
\x00\x00\x01\xc0\x00\x00\x00\x00\x00\x01\x00\x00C\xdc\
\x00\x00\x01\x9f{C\xf1.\ \x00\x00\x01\x9f{C\xf1.\
\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x01\x00\x00!\xca\ \x00\x00\x01d\x00\x00\x00\x00\x00\x01\x00\x00/\x10\
\x00\x00\x01\x9f{{\xa5\xd5\ \x00\x00\x01\x9f{{\xa5\xd5\
\x00\x00\x02\xba\x00\x00\x00\x00\x00\x01\x00\x00i\xc6\ \x00\x00\x03x\x00\x00\x00\x00\x00\x01\x00\x00\x81\xdf\
\x00\x00\x01\x9f\x7f&\x83\x10\ \x00\x00\x01\x9f\x7f&\x83\x10\
\x00\x00\x00\xba\x00\x00\x00\x00\x00\x01\x00\x00\x1b\xcd\ \x00\x00\x00\xde\x00\x00\x00\x00\x00\x01\x00\x00 \xd9\
\x00\x00\x01\x9f\x7f&\x83~\ \x00\x00\x01\x9f\x7f&\x83~\
\x00\x00\x02L\x00\x00\x00\x00\x00\x01\x00\x00\x5c\xfe\ \x00\x00\x02\xe4\x00\x00\x00\x00\x00\x01\x00\x00p\xd7\
\x00\x00\x01\x9f\x7f&\x83T\ \x00\x00\x01\x9f\x7f&\x83T\
\x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\ \x00\x00\x00^\x00\x00\x00\x00\x00\x01\x00\x00\x0c\xc1\
\x00\x00\x01\x9f\x7fY\xce^\
\x00\x00\x00\x82\x00\x00\x00\x00\x00\x01\x00\x00\x11\xcd\
\x00\x00\x01\x9f{0\xc9B\ \x00\x00\x01\x9f{0\xc9B\
\x00\x00\x00\xd8\x00\x00\x00\x00\x00\x01\x00\x00\x1fp\ \x00\x00\x01D\x00\x00\x00\x00\x00\x01\x00\x00,\xb6\
\x00\x00\x01\x9f\x7f&\x83)\ \x00\x00\x01\x9f\x7f&\x83)\
\x00\x00\x02*\x00\x00\x00\x00\x00\x01\x00\x00W|\ \x00\x00\x02\xc2\x00\x00\x00\x00\x00\x01\x00\x00kU\
\x00\x00\x01\x9f{C\xf1N\ \x00\x00\x01\x9f{C\xf1N\
\x00\x00\x01\xb2\x00\x00\x00\x00\x00\x01\x00\x00EH\ \x00\x00\x02\x1e\x00\x00\x00\x00\x00\x01\x00\x00R\x8e\
\x00\x00\x01\x9f\x7f&\x82\xf2\ \x00\x00\x01\x9f\x7f&\x82\xf2\
\x00\x00\x00\x8c\x00\x00\x00\x00\x00\x01\x00\x00\x15-\ \x00\x00\x00\xb0\x00\x00\x00\x00\x00\x01\x00\x00\x1a9\
\x00\x00\x01\x9f{\x8d\xf34\ \x00\x00\x01\x9f{\x8d\xf34\
\x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ \x00\x00\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\x00\x00\x01\x9f{0\xc9+\ \x00\x00\x01\x9f{0\xc9+\
\x00\x00\x01\x92\x00\x00\x00\x00\x00\x01\x00\x00=v\ \x00\x00\x01\xfe\x00\x00\x00\x00\x00\x01\x00\x00J\xbc\
\x00\x00\x01\x9f{C\xf1=\ \x00\x00\x01\x9f{C\xf1=\
\x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\ \x00\x00\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\x00\x00\x01\x9f\x7f&\x83\x99\ \x00\x00\x01\x9f\x7f&\x83\x99\
\x00\x00\x02\xe4\x00\x00\x00\x00\x00\x01\x00\x00l\x5c\ \x00\x00\x03\xa2\x00\x00\x00\x00\x00\x01\x00\x00\x84u\
\x00\x00\x01\x9f{{\xa5\xe3\ \x00\x00\x01\x9f{{\xa5\xe3\
\x00\x00\x02\x92\x00\x00\x00\x00\x00\x01\x00\x00d\xd3\ \x00\x00\x01$\x00\x00\x00\x00\x00\x01\x00\x00'\xef\
\x00\x00\x01\x9f\x7fV\xd5\xe5\
\x00\x00\x03*\x00\x00\x00\x00\x00\x01\x00\x00x\xac\
\x00\x00\x01\x9f{0\xc9R\ \x00\x00\x01\x9f{0\xc9R\
" "

View File

@@ -0,0 +1,59 @@
# -*- coding: utf-8 -*-
################################################################################
## Form generated from reading UI file 'connection_chooser_dialog.ui'
##
## Created by: Qt User Interface Compiler version 6.11.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogButtonBox,
QLabel, QListWidget, QListWidgetItem, QSizePolicy,
QVBoxLayout, QWidget)
class Ui_ConnectionChooserDialog(object):
def setupUi(self, ConnectionChooserDialog):
if not ConnectionChooserDialog.objectName():
ConnectionChooserDialog.setObjectName(u"ConnectionChooserDialog")
ConnectionChooserDialog.resize(460, 280)
self.dialogLayout = QVBoxLayout(ConnectionChooserDialog)
self.dialogLayout.setObjectName(u"dialogLayout")
self.promptLabel = QLabel(ConnectionChooserDialog)
self.promptLabel.setObjectName(u"promptLabel")
self.dialogLayout.addWidget(self.promptLabel)
self.connectionList = QListWidget(ConnectionChooserDialog)
self.connectionList.setObjectName(u"connectionList")
self.connectionList.setAlternatingRowColors(True)
self.dialogLayout.addWidget(self.connectionList)
self.buttonBox = QDialogButtonBox(ConnectionChooserDialog)
self.buttonBox.setObjectName(u"buttonBox")
self.buttonBox.setStandardButtons(QDialogButtonBox.StandardButton.Cancel|QDialogButtonBox.StandardButton.Ok)
self.dialogLayout.addWidget(self.buttonBox)
self.retranslateUi(ConnectionChooserDialog)
self.buttonBox.accepted.connect(ConnectionChooserDialog.accept)
self.buttonBox.rejected.connect(ConnectionChooserDialog.reject)
self.connectionList.itemDoubleClicked.connect(ConnectionChooserDialog.accept)
QMetaObject.connectSlotsByName(ConnectionChooserDialog)
# setupUi
def retranslateUi(self, ConnectionChooserDialog):
ConnectionChooserDialog.setWindowTitle(QCoreApplication.translate("ConnectionChooserDialog", u"Select a Connection", None))
self.promptLabel.setText(QCoreApplication.translate("ConnectionChooserDialog", u"Select a connection:", None))
# retranslateUi

View File

@@ -21,6 +21,7 @@ from PySide6.QtWidgets import (QAbstractButton, QApplication, QDialog, QDialogBu
QWidget) QWidget)
from bedit.gui.graphics.icon_canvas import IconCanvasView from bedit.gui.graphics.icon_canvas import IconCanvasView
from . import resources_rc
class Ui_IconEditorDialog(object): class Ui_IconEditorDialog(object):
def setupUi(self, IconEditorDialog): def setupUi(self, IconEditorDialog):
@@ -33,6 +34,9 @@ class Ui_IconEditorDialog(object):
self.shapeToolbarLayout.setObjectName(u"shapeToolbarLayout") self.shapeToolbarLayout.setObjectName(u"shapeToolbarLayout")
self.pointerButton = QToolButton(IconEditorDialog) self.pointerButton = QToolButton(IconEditorDialog)
self.pointerButton.setObjectName(u"pointerButton") self.pointerButton.setObjectName(u"pointerButton")
icon = QIcon()
icon.addFile(u":/icons/icons/edit-select.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.pointerButton.setIcon(icon)
self.pointerButton.setCheckable(True) self.pointerButton.setCheckable(True)
self.pointerButton.setChecked(True) self.pointerButton.setChecked(True)
@@ -45,36 +49,54 @@ class Ui_IconEditorDialog(object):
self.addRectangleButton = QToolButton(IconEditorDialog) self.addRectangleButton = QToolButton(IconEditorDialog)
self.addRectangleButton.setObjectName(u"addRectangleButton") self.addRectangleButton.setObjectName(u"addRectangleButton")
icon1 = QIcon()
icon1.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.addRectangleButton.setIcon(icon1)
self.addRectangleButton.setCheckable(True) self.addRectangleButton.setCheckable(True)
self.shapeToolbarLayout.addWidget(self.addRectangleButton) self.shapeToolbarLayout.addWidget(self.addRectangleButton)
self.addCircleButton = QToolButton(IconEditorDialog) self.addCircleButton = QToolButton(IconEditorDialog)
self.addCircleButton.setObjectName(u"addCircleButton") self.addCircleButton.setObjectName(u"addCircleButton")
icon2 = QIcon()
icon2.addFile(u":/icons/icons/draw-circle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.addCircleButton.setIcon(icon2)
self.addCircleButton.setCheckable(True) self.addCircleButton.setCheckable(True)
self.shapeToolbarLayout.addWidget(self.addCircleButton) self.shapeToolbarLayout.addWidget(self.addCircleButton)
self.addEllipseButton = QToolButton(IconEditorDialog) self.addEllipseButton = QToolButton(IconEditorDialog)
self.addEllipseButton.setObjectName(u"addEllipseButton") self.addEllipseButton.setObjectName(u"addEllipseButton")
icon3 = QIcon()
icon3.addFile(u":/icons/icons/draw-ellipse.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.addEllipseButton.setIcon(icon3)
self.addEllipseButton.setCheckable(True) self.addEllipseButton.setCheckable(True)
self.shapeToolbarLayout.addWidget(self.addEllipseButton) self.shapeToolbarLayout.addWidget(self.addEllipseButton)
self.addLineButton = QToolButton(IconEditorDialog) self.addLineButton = QToolButton(IconEditorDialog)
self.addLineButton.setObjectName(u"addLineButton") self.addLineButton.setObjectName(u"addLineButton")
icon4 = QIcon()
icon4.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.addLineButton.setIcon(icon4)
self.addLineButton.setCheckable(True) self.addLineButton.setCheckable(True)
self.shapeToolbarLayout.addWidget(self.addLineButton) self.shapeToolbarLayout.addWidget(self.addLineButton)
self.addTriangleButton = QToolButton(IconEditorDialog) self.addTriangleButton = QToolButton(IconEditorDialog)
self.addTriangleButton.setObjectName(u"addTriangleButton") self.addTriangleButton.setObjectName(u"addTriangleButton")
icon5 = QIcon()
icon5.addFile(u":/icons/icons/draw-triangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.addTriangleButton.setIcon(icon5)
self.addTriangleButton.setCheckable(True) self.addTriangleButton.setCheckable(True)
self.shapeToolbarLayout.addWidget(self.addTriangleButton) self.shapeToolbarLayout.addWidget(self.addTriangleButton)
self.addTextButton = QToolButton(IconEditorDialog) self.addTextButton = QToolButton(IconEditorDialog)
self.addTextButton.setObjectName(u"addTextButton") self.addTextButton.setObjectName(u"addTextButton")
icon6 = QIcon()
icon6.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.addTextButton.setIcon(icon6)
self.addTextButton.setCheckable(True) self.addTextButton.setCheckable(True)
self.shapeToolbarLayout.addWidget(self.addTextButton) self.shapeToolbarLayout.addWidget(self.addTextButton)
@@ -85,16 +107,25 @@ class Ui_IconEditorDialog(object):
self.zoomInButton = QToolButton(IconEditorDialog) self.zoomInButton = QToolButton(IconEditorDialog)
self.zoomInButton.setObjectName(u"zoomInButton") self.zoomInButton.setObjectName(u"zoomInButton")
icon7 = QIcon()
icon7.addFile(u":/icons/icons/zoom-in.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.zoomInButton.setIcon(icon7)
self.shapeToolbarLayout.addWidget(self.zoomInButton) self.shapeToolbarLayout.addWidget(self.zoomInButton)
self.zoomOutButton = QToolButton(IconEditorDialog) self.zoomOutButton = QToolButton(IconEditorDialog)
self.zoomOutButton.setObjectName(u"zoomOutButton") self.zoomOutButton.setObjectName(u"zoomOutButton")
icon8 = QIcon()
icon8.addFile(u":/icons/icons/zoom-out.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.zoomOutButton.setIcon(icon8)
self.shapeToolbarLayout.addWidget(self.zoomOutButton) self.shapeToolbarLayout.addWidget(self.zoomOutButton)
self.centerButton = QToolButton(IconEditorDialog) self.centerButton = QToolButton(IconEditorDialog)
self.centerButton.setObjectName(u"centerButton") self.centerButton.setObjectName(u"centerButton")
icon9 = QIcon()
icon9.addFile(u":/icons/icons/zoom-original.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.centerButton.setIcon(icon9)
self.shapeToolbarLayout.addWidget(self.centerButton) self.shapeToolbarLayout.addWidget(self.centerButton)
@@ -142,18 +173,18 @@ class Ui_IconEditorDialog(object):
self.addLineButton.setText(QCoreApplication.translate("IconEditorDialog", u"Line", None)) self.addLineButton.setText(QCoreApplication.translate("IconEditorDialog", u"Line", None))
self.addTriangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Triangle", None)) self.addTriangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Triangle", None))
self.addTextButton.setText(QCoreApplication.translate("IconEditorDialog", u"Text", None)) self.addTextButton.setText(QCoreApplication.translate("IconEditorDialog", u"Text", None))
self.zoomInButton.setText(QCoreApplication.translate("IconEditorDialog", u"+", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.zoomInButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom in", None)) self.zoomInButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom in", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.zoomOutButton.setText(QCoreApplication.translate("IconEditorDialog", u"\u2212", None)) self.zoomInButton.setText(QCoreApplication.translate("IconEditorDialog", u"+", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.zoomOutButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom out", None)) self.zoomOutButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom out", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.centerButton.setText(QCoreApplication.translate("IconEditorDialog", u"Fit", None)) self.zoomOutButton.setText(QCoreApplication.translate("IconEditorDialog", u"\u2212", None))
#if QT_CONFIG(tooltip) #if QT_CONFIG(tooltip)
self.centerButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Fit and center the icon canvas", None)) self.centerButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Fit and center the icon canvas", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.centerButton.setText(QCoreApplication.translate("IconEditorDialog", u"Fit", None))
self.deleteSelectedButton.setText(QCoreApplication.translate("IconEditorDialog", u"Delete selected", None)) self.deleteSelectedButton.setText(QCoreApplication.translate("IconEditorDialog", u"Delete selected", None))
self.portHintLabel.setText(QCoreApplication.translate("IconEditorDialog", u"Green points are inputs; red points are outputs. Drag them to place connection anchors.", None)) self.portHintLabel.setText(QCoreApplication.translate("IconEditorDialog", u"Green points are inputs; red points are outputs. Drag them to place connection anchors.", None))
# retranslateUi # retranslateUi

View File

@@ -235,33 +235,36 @@ class Ui_MainWindow(object):
self.connectToolButton = QToolButton(self.workspaceHeader) self.connectToolButton = QToolButton(self.workspaceHeader)
self.connectToolButton.setObjectName(u"connectToolButton") self.connectToolButton.setObjectName(u"connectToolButton")
icon16 = QIcon()
icon16.addFile(u":/icons/icons/network-connect.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.connectToolButton.setIcon(icon16)
self.connectToolButton.setCheckable(True) self.connectToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.connectToolButton) self.workspaceHeaderLayout.addWidget(self.connectToolButton)
self.boxToolButton = QToolButton(self.workspaceHeader) self.boxToolButton = QToolButton(self.workspaceHeader)
self.boxToolButton.setObjectName(u"boxToolButton") self.boxToolButton.setObjectName(u"boxToolButton")
icon16 = QIcon() icon17 = QIcon()
icon16.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon17.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.boxToolButton.setIcon(icon16) self.boxToolButton.setIcon(icon17)
self.boxToolButton.setCheckable(True) self.boxToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.boxToolButton) self.workspaceHeaderLayout.addWidget(self.boxToolButton)
self.lineToolButton = QToolButton(self.workspaceHeader) self.lineToolButton = QToolButton(self.workspaceHeader)
self.lineToolButton.setObjectName(u"lineToolButton") self.lineToolButton.setObjectName(u"lineToolButton")
icon17 = QIcon() icon18 = QIcon()
icon17.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon18.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.lineToolButton.setIcon(icon17) self.lineToolButton.setIcon(icon18)
self.lineToolButton.setCheckable(True) self.lineToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.lineToolButton) self.workspaceHeaderLayout.addWidget(self.lineToolButton)
self.textToolButton = QToolButton(self.workspaceHeader) self.textToolButton = QToolButton(self.workspaceHeader)
self.textToolButton.setObjectName(u"textToolButton") self.textToolButton.setObjectName(u"textToolButton")
icon18 = QIcon() icon19 = QIcon()
icon18.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off) icon19.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.textToolButton.setIcon(icon18) self.textToolButton.setIcon(icon19)
self.textToolButton.setCheckable(True) self.textToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.textToolButton) self.workspaceHeaderLayout.addWidget(self.textToolButton)
@@ -272,30 +275,6 @@ class Ui_MainWindow(object):
self.workspaceHeaderLayout.addWidget(self.rotateToolButton) self.workspaceHeaderLayout.addWidget(self.rotateToolButton)
self.routingLabel = QLabel(self.workspaceHeader)
self.routingLabel.setObjectName(u"routingLabel")
self.workspaceHeaderLayout.addWidget(self.routingLabel)
self.directRoutingButton = QToolButton(self.workspaceHeader)
self.directRoutingButton.setObjectName(u"directRoutingButton")
self.directRoutingButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.directRoutingButton)
self.angledRoutingButton = QToolButton(self.workspaceHeader)
self.angledRoutingButton.setObjectName(u"angledRoutingButton")
self.angledRoutingButton.setCheckable(True)
self.angledRoutingButton.setChecked(True)
self.workspaceHeaderLayout.addWidget(self.angledRoutingButton)
self.splineRoutingButton = QToolButton(self.workspaceHeader)
self.splineRoutingButton.setObjectName(u"splineRoutingButton")
self.splineRoutingButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.splineRoutingButton)
self.workspaceEditorLayout.addWidget(self.workspaceHeader) self.workspaceEditorLayout.addWidget(self.workspaceHeader)
@@ -545,10 +524,6 @@ class Ui_MainWindow(object):
self.rotateToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise", None)) self.rotateToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise", None))
#endif // QT_CONFIG(tooltip) #endif // QT_CONFIG(tooltip)
self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None)) self.rotateToolButton.setText(QCoreApplication.translate("MainWindow", u"Rotate", None))
self.routingLabel.setText(QCoreApplication.translate("MainWindow", u"Line:", None))
self.directRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Direct", None))
self.angledRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Angled", None))
self.splineRoutingButton.setText(QCoreApplication.translate("MainWindow", u"Spline", None))
self.jsonEditor.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Component JSON", None)) self.jsonEditor.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Component JSON", None))
self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None)) self.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None))
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None)) self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))

View File

@@ -1,10 +1,12 @@
import json import json
from dataclasses import dataclass
from PySide6.QtCore import QMimeData, QPointF, QRectF, Qt, Signal from PySide6.QtCore import QMimeData, QPointF, QRectF, Qt, Signal
from PySide6.QtGui import ( from PySide6.QtGui import (
QColor, QColor,
QDragEnterEvent, QDragEnterEvent,
QDropEvent, QDropEvent,
QKeyEvent,
QMouseEvent, QMouseEvent,
QWheelEvent, QWheelEvent,
QPainter, QPainter,
@@ -32,17 +34,26 @@ from PySide6.QtWidgets import (
from bedit.core.model import Annotation, Component, Connection, Endpoint, Port from bedit.core.model import Annotation, Component, Connection, Endpoint, Port
from bedit.gui.controllers.document import DocumentController from bedit.gui.controllers.document import DocumentController
from bedit.gui.dialogs.connection_chooser import ConnectionChooserDialog
from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE from bedit.gui.models.library_tree import COMPONENT_MIME_TYPE
from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon from bedit.gui.graphics.icon_renderer import icon_bounds, paint_icon
from bedit.gui.graphics.icon_editor import ShapeOptionsDialog from bedit.gui.graphics.icon_editor import ShapeOptionsDialog
from bedit.gui.graphics.icon_renderer import shape_pen from bedit.gui.graphics.icon_renderer import shape_pen
from bedit.gui.graphics.connection_styles import ConnectionStyle, connection_style from bedit.gui.graphics.connection_styles import ConnectionStyle, connection_style
from bedit.gui.preferences import application_settings from bedit.gui.preferences import application_settings
from bedit.core.port_types import PortTypeRegistry
SELECTION_MIME_TYPE = "application/x-bedit-selection" SELECTION_MIME_TYPE = "application/x-bedit-selection"
@dataclass(frozen=True)
class ConnectionChoice:
source: Endpoint
target: Endpoint
label: str
def _graph_snap_size() -> int: def _graph_snap_size() -> int:
return application_settings().value("grid/graphSnapSize", 8, type=int) return application_settings().value("grid/graphSnapSize", 8, type=int)
@@ -690,11 +701,10 @@ class GraphScene(QGraphicsScene):
self.output_items: dict[str, InterfaceTerminalItem] = {} self.output_items: dict[str, InterfaceTerminalItem] = {}
self.connection_items: dict[str, ConnectionGraphicsItem] = {} self.connection_items: dict[str, ConnectionGraphicsItem] = {}
self.annotation_items: dict[str, QGraphicsItem] = {} self.annotation_items: dict[str, QGraphicsItem] = {}
self.pending_source: ConnectionPortItem | None = None self.pending_connection_item: ComponentGraphicsItem | ConnectionPortItem | None = None
self.pending_waypoints: list[QPointF] = [] self.pending_waypoints: list[QPointF] = []
self.pending_preview: QGraphicsPathItem | None = None self.pending_preview: QGraphicsPathItem | None = None
self.interaction_mode = "pointer" self.interaction_mode = "pointer"
self.connection_routing = "angled"
self.drawing_start: QPointF | None = None self.drawing_start: QPointF | None = None
self.drawing_waypoints: list[QPointF] = [] self.drawing_waypoints: list[QPointF] = []
self.setSceneRect(-2000, -2000, 4000, 4000) self.setSceneRect(-2000, -2000, 4000, 4000)
@@ -713,7 +723,7 @@ class GraphScene(QGraphicsScene):
self.output_items.clear() self.output_items.clear()
self.connection_items.clear() self.connection_items.clear()
self.annotation_items.clear() self.annotation_items.clear()
self.pending_source = None self.pending_connection_item = None
self.pending_waypoints.clear() self.pending_waypoints.clear()
self.pending_preview = None self.pending_preview = None
owner = self.controller.active_component owner = self.controller.active_component
@@ -753,6 +763,7 @@ class GraphScene(QGraphicsScene):
item = AnnotationGraphicsItem(annotation, self.controller) item = AnnotationGraphicsItem(annotation, self.controller)
self.addItem(item) self.addItem(item)
self.annotation_items[annotation.id] = item self.annotation_items[annotation.id] = item
self._set_port_hints_visible(self.interaction_mode == "connect")
def set_component_position(self, component_id: str, position: QPointF) -> None: def set_component_position(self, component_id: str, position: QPointF) -> None:
item = self.component_items.get(component_id) item = self.component_items.get(component_id)
@@ -837,23 +848,65 @@ class GraphScene(QGraphicsScene):
ports = component.output_ports if role == "source" else component.input_ports ports = component.output_ports if role == "source" else component.input_ports
return ports.get(endpoint.port or "") return ports.get(endpoint.port or "")
@staticmethod
def _hitbox_intersection(component: ComponentGraphicsItem, reference: QPointF) -> QPointF:
rect = component.hitbox.normalized()
local = component.mapFromScene(reference)
center = rect.center()
delta = local - center
if delta.isNull():
return component.mapToScene(QPointF(rect.right(), center.y()))
scales: list[float] = []
if delta.x() != 0:
scales.append((rect.width() / 2) / abs(delta.x()))
if delta.y() != 0:
scales.append((rect.height() / 2) / abs(delta.y()))
return component.mapToScene(center + delta * min(scales))
def _endpoint_center(self, endpoint: Endpoint, role: str) -> QPointF | None:
if endpoint.block is not None:
component = self.component_items.get(endpoint.block)
return component.mapToScene(component.hitbox.center()) if component else None
port = self._endpoint_item(endpoint, role)
return port.scenePos() if port else None
def _visible_endpoint(
self, endpoint: Endpoint, role: str, reference: QPointF
) -> QPointF | None:
if endpoint.block is not None:
component = self.component_items.get(endpoint.block)
return self._hitbox_intersection(component, reference) if component else None
port = self._endpoint_item(endpoint, role)
return port.scenePos() if port else None
def _connection_geometry(
self, connection: Connection, waypoints: list[QPointF]
) -> tuple[QPointF, QPointF] | None:
source_center = self._endpoint_center(connection.source, "source")
target_center = self._endpoint_center(connection.target, "target")
if source_center is None or target_center is None:
return None
start_reference = waypoints[0] if waypoints else target_center
end_reference = waypoints[-1] if waypoints else source_center
start = self._visible_endpoint(connection.source, "source", start_reference)
end = self._visible_endpoint(connection.target, "target", end_reference)
return (start, end) if start is not None and end is not None else None
def update_connection(self, connection_id: str) -> None: def update_connection(self, connection_id: str) -> None:
connection = self.controller.active_graph.connections.get(connection_id) connection = self.controller.active_graph.connections.get(connection_id)
graphics = self.connection_items.get(connection_id) graphics = self.connection_items.get(connection_id)
if connection is None or graphics is None: if connection is None or graphics is None:
return return
source = self._endpoint_item(connection.source, "source")
target = self._endpoint_item(connection.target, "target")
if source is None or target is None:
return
start, end = source.scenePos(), target.scenePos()
routing = str(connection.properties.get("routing", "angled"))
waypoints = [ waypoints = [
QPointF(float(point["x"]), float(point["y"])) QPointF(float(point["x"]), float(point["y"]))
for point in connection.properties.get("waypoints", []) for point in connection.properties.get("waypoints", [])
if isinstance(point, dict) and "x" in point and "y" in point if isinstance(point, dict) and "x" in point and "y" in point
] ]
path, direction_points = self._route_path(start, end, routing, waypoints) geometry = self._connection_geometry(connection, waypoints)
if geometry is None:
return
start, end = geometry
path, direction_points = self._route_path(start, end, waypoints)
graphics.set_connection_path(path, direction_points) graphics.set_connection_path(path, direction_points)
graphics.set_waypoints(waypoints) graphics.set_waypoints(waypoints)
@@ -868,112 +921,193 @@ class GraphScene(QGraphicsScene):
QPointF(float(p["x"]), float(p["y"])) QPointF(float(p["x"]), float(p["y"]))
for p in annotation.properties.get("waypoints", []) for p in annotation.properties.get("waypoints", [])
] ]
routing = str(annotation.properties.get("routing", "angled")) path, points = self._route_path(start, end, waypoints)
path, points = self._route_path(start, end, routing, waypoints)
graphics.setPath(path) graphics.setPath(path)
graphics.set_waypoints(waypoints, start, end) graphics.set_waypoints(waypoints, start, end)
@staticmethod
def _orthogonal_points(start: QPointF, end: QPointF, waypoints: list[QPointF]) -> list[QPointF]:
points = [start]
for waypoint in [*waypoints, end]:
previous = points[-1]
if previous.x() != waypoint.x() and previous.y() != waypoint.y():
elbow = QPointF(waypoint.x(), previous.y())
points.append(elbow)
if waypoint != points[-1]:
points.append(waypoint)
return points
@staticmethod @staticmethod
def _route_path( def _route_path(
start: QPointF, start: QPointF,
end: QPointF, end: QPointF,
routing: str,
waypoints: list[QPointF] | None = None, waypoints: list[QPointF] | None = None,
) -> tuple[QPainterPath, list[QPointF]]: ) -> tuple[QPainterPath, list[QPointF]]:
waypoints = waypoints or [] points = [start, *(waypoints or []), end]
path = QPainterPath(start) path = QPainterPath(start)
if routing == "angled":
points = GraphScene._orthogonal_points(start, end, waypoints)
for point in points[1:]: for point in points[1:]:
path.lineTo(point) path.lineTo(point)
return path, points return path, points
if routing == "direct":
path.lineTo(end)
return path, [start, end]
if waypoints:
points = [start, *waypoints, end]
for index, point in enumerate(points[1:-1], start=1):
following = points[index + 1]
midpoint = QPointF((point.x() + following.x()) / 2, (point.y() + following.y()) / 2)
path.quadTo(point, midpoint)
path.quadTo(points[-2], end)
return path, points
distance = max(50.0, abs(end.x() - start.x()) * 0.5)
first_control, second_control = start + QPointF(distance, 0), end - QPointF(distance, 0)
path.cubicTo(first_control, second_control, end)
return path, [start, first_control, second_control, end]
def set_interaction_mode(self, mode: str) -> None: def set_interaction_mode(self, mode: str) -> None:
self.interaction_mode = mode self.interaction_mode = mode
if mode != "connect": if mode != "connect":
self._clear_pending_source() self._clear_pending_connection()
self._clear_drawing() self._clear_drawing()
self._set_port_hints_visible(mode == "connect")
def set_connection_routing(self, routing: str) -> None: def _set_port_hints_visible(self, visible: bool) -> None:
self.connection_routing = routing for component in self.component_items.values():
self._clear_pending_source() for port in (*component.input_ports.values(), *component.output_ports.values()):
port.setVisible(visible)
for terminal in (*self.input_items.values(), *self.output_items.values()):
terminal.connection_port.setVisible(visible)
@staticmethod
def _clicked_component(
item: QGraphicsItem | None,
) -> ComponentGraphicsItem | None:
if isinstance(item, ComponentGraphicsItem):
return item
if isinstance(item, ConnectionPortItem) and isinstance(
item.parentItem(), ComponentGraphicsItem
):
return item.parentItem()
return None
@staticmethod
def _click_endpoint(item: QGraphicsItem | None) -> Endpoint | None:
return item.endpoint if isinstance(item, ConnectionPortItem) else None
@staticmethod
def _connection_anchor(
item: ComponentGraphicsItem | ConnectionPortItem,
) -> QPointF:
return (
item.scenePos()
if isinstance(item, ConnectionPortItem)
else item.sceneBoundingRect().center()
)
def _connection_choices(
self,
first: ComponentGraphicsItem,
second: ComponentGraphicsItem,
) -> list[ConnectionChoice]:
choices: list[ConnectionChoice] = []
def add_pairs(
source_item: ComponentGraphicsItem, target_item: ComponentGraphicsItem
) -> None:
for output in source_item.component.outputs:
for input_port in target_item.component.inputs:
if not PortTypeRegistry.compatible(output.type, input_port.type):
continue
choices.append(
ConnectionChoice(
Endpoint(block=source_item.component_id, port=output.id),
Endpoint(block=target_item.component_id, port=input_port.id),
f"{source_item.component.name}.{output.name}"
f"{target_item.component.name}.{input_port.name}",
)
)
add_pairs(first, second)
add_pairs(second, first)
return choices
@staticmethod
def _default_choice_index(
choices: list[ConnectionChoice],
first_component: ComponentGraphicsItem,
second_component: ComponentGraphicsItem,
first_endpoint: Endpoint | None,
second_endpoint: Endpoint | None,
) -> int:
def score(choice: ConnectionChoice) -> int:
value = 0
if first_endpoint is not None and first_endpoint in (choice.source, choice.target):
value += 8
if second_endpoint is not None and second_endpoint in (choice.source, choice.target):
value += 8
if choice.source.block == first_component.component_id:
value += 2
if choice.target.block == second_component.component_id:
value += 1
return value
return max(range(len(choices)), key=lambda index: score(choices[index]))
def _finish_block_connection(
self,
second_item: ComponentGraphicsItem | ConnectionPortItem,
) -> None:
first_item = self.pending_connection_item
if first_item is None:
return
first_component = self._clicked_component(first_item)
second_component = self._clicked_component(second_item)
if (
first_component is None
or second_component is None
or first_component is second_component
):
self._clear_pending_connection()
return
choices = self._connection_choices(first_component, second_component)
if not choices:
QToolTip.showText(
self.views()[0].mapToGlobal(self.views()[0].viewport().rect().center())
if self.views()
else QPointF().toPoint(),
"These blocks have no compatible input/output pairs",
)
self._clear_pending_connection()
return
default = self._default_choice_index(
choices,
first_component,
second_component,
self._click_endpoint(first_item),
self._click_endpoint(second_item),
)
dialog = ConnectionChooserDialog(
[choice.label for choice in choices],
default,
self.views()[0] if self.views() else None,
)
if dialog.exec() == dialog.DialogCode.Accepted:
choice = choices[dialog.selected_index]
try:
self.controller.connect(
choice.source, choice.target, waypoints=self.pending_waypoints
)
except ValueError as error:
QToolTip.showText(dialog.mapToGlobal(dialog.rect().center()), str(error))
self._clear_pending_connection()
def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802 def mousePressEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
if self.interaction_mode in {"box", "text", "line"}: if self.interaction_mode in {"box", "text", "line"}:
self._drawing_press(event) self._drawing_press(event)
return return
if self.interaction_mode != "connect": if self.interaction_mode != "connect":
self._clear_pending_source() self._clear_pending_connection()
super().mousePressEvent(event) super().mousePressEvent(event)
return return
if event.button() == Qt.MouseButton.RightButton: if event.button() == Qt.MouseButton.RightButton:
self._clear_pending_source() self._clear_pending_connection()
event.accept() event.accept()
return return
item = self.itemAt(event.scenePos(), QTransform()) item = self.itemAt(event.scenePos(), QTransform())
if isinstance(item, ConnectionPortItem): component = self._clicked_component(item)
if item.role == "source": if component is not None:
self._clear_pending_source() clicked = item if isinstance(item, ConnectionPortItem) else component
self.pending_source = item if self.pending_connection_item is None:
self.pending_connection_item = clicked
self.pending_waypoints = [] self.pending_waypoints = []
item.setBrush(QColor("#f5b642")) if isinstance(clicked, ConnectionPortItem):
clicked.setBrush(QColor("#f5b642"))
self._create_preview() self._create_preview()
elif self.pending_source is not None: self._update_preview(event.scenePos())
if self.pending_source.endpoint != item.endpoint: else:
try: self._finish_block_connection(clicked)
self.controller.connect(
self.pending_source.endpoint,
item.endpoint,
routing=self.connection_routing,
waypoints=(
self._completed_angled_waypoints(
self.pending_source.scenePos(),
item.scenePos(),
self.pending_waypoints,
)
if self.connection_routing == "angled"
else self.pending_waypoints
),
)
except ValueError as error:
QToolTip.showText(event.screenPos(), str(error))
self._clear_pending_source()
event.accept() event.accept()
return return
if self.pending_source is not None and self.connection_routing == "angled": if self.pending_connection_item is not None:
self.pending_waypoints.append(_snapped(event.scenePos())) self.pending_waypoints.append(_snapped(event.scenePos()))
self._update_preview(event.scenePos()) self._update_preview(event.scenePos())
event.accept() event.accept()
def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802 def mouseMoveEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
if self.interaction_mode == "connect" and self.pending_source is not None: if self.interaction_mode == "connect" and self.pending_connection_item is not None:
self._update_preview(event.scenePos()) self._update_preview(event.scenePos())
event.accept() event.accept()
return return
@@ -1011,14 +1145,9 @@ class GraphScene(QGraphicsScene):
self.drawing_start = point self.drawing_start = point
self.drawing_waypoints = [] self.drawing_waypoints = []
self._create_preview() self._create_preview()
elif self.interaction_mode == "line" and self.connection_routing == "angled": elif self.interaction_mode == "line":
self.drawing_waypoints.append(point) self.drawing_waypoints.append(point)
self._update_drawing_preview(point) self._update_drawing_preview(point)
elif self.interaction_mode == "line":
self.controller.add_annotation(
"line", self.drawing_start, point, routing=self.connection_routing
)
self._clear_drawing()
event.accept() event.accept()
def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802 def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None: # noqa: N802
@@ -1029,21 +1158,12 @@ class GraphScene(QGraphicsScene):
if self.drawing_waypoints and self.drawing_waypoints[-1] == end if self.drawing_waypoints and self.drawing_waypoints[-1] == end
else self.drawing_waypoints else self.drawing_waypoints
) )
completed = self._completed_angled_waypoints(self.drawing_start, end, waypoints) self.controller.add_annotation("line", self.drawing_start, end, waypoints=waypoints)
self.controller.add_annotation(
"line", self.drawing_start, end, routing="angled", waypoints=completed
)
self._clear_drawing() self._clear_drawing()
event.accept() event.accept()
return return
super().mouseDoubleClickEvent(event) super().mouseDoubleClickEvent(event)
@staticmethod
def _completed_angled_waypoints(
start: QPointF, end: QPointF, waypoints: list[QPointF]
) -> list[QPointF]:
return GraphScene._orthogonal_points(start, end, waypoints)[1:-1]
def _update_drawing_preview(self, cursor: QPointF) -> None: def _update_drawing_preview(self, cursor: QPointF) -> None:
if self.drawing_start is None or self.pending_preview is None: if self.drawing_start is None or self.pending_preview is None:
return return
@@ -1052,13 +1172,11 @@ class GraphScene(QGraphicsScene):
path = QPainterPath() path = QPainterPath()
path.addRect(QRectF(self.drawing_start, end).normalized()) path.addRect(QRectF(self.drawing_start, end).normalized())
else: else:
path, _ = self._route_path( path, _ = self._route_path(self.drawing_start, end, self.drawing_waypoints)
self.drawing_start, end, self.connection_routing, self.drawing_waypoints
)
self.pending_preview.setPath(path) self.pending_preview.setPath(path)
def _clear_drawing(self) -> None: def _clear_drawing(self) -> None:
if self.pending_preview is not None and self.pending_source is None: if self.pending_preview is not None and self.pending_connection_item is None:
self.removeItem(self.pending_preview) self.removeItem(self.pending_preview)
self.pending_preview = None self.pending_preview = None
self.drawing_start = None self.drawing_start = None
@@ -1118,10 +1236,7 @@ class GraphScene(QGraphicsScene):
key=lambda index: segment_distance(snapped, anchors[index], anchors[index + 1]), key=lambda index: segment_distance(snapped, anchors[index], anchors[index + 1]),
) )
points.insert(insertion, snapped) points.insert(insertion, snapped)
routing = "angled" if item.properties.get("routing") == "direct" else None self.controller.set_route_waypoints(item_kind, item_id, points)
if routing == "angled" or item.properties.get("routing") == "angled":
points = self._orthogonal_points(start, end, points)[1:-1]
self.controller.set_route_waypoints(item_kind, item_id, points, routing)
def move_route_node(self, item_kind: str, item_id: str, index: int, position: QPointF) -> None: def move_route_node(self, item_kind: str, item_id: str, index: int, position: QPointF) -> None:
item, points = self._route_values(item_kind, item_id) item, points = self._route_values(item_kind, item_id)
@@ -1147,14 +1262,6 @@ class GraphScene(QGraphicsScene):
return return
if index < len(points): if index < len(points):
points[index] = position points[index] = position
if item.properties.get("routing") == "angled":
if item_kind == "annotation":
start = QPointF(item.x, item.y)
end = QPointF(item.x + item.width, item.y + item.height)
else:
graphics = self.connection_items[item_id]
start, end = graphics.start, graphics.end
points = self._orthogonal_points(start, end, points)[1:-1]
self.controller.set_route_waypoints(item_kind, item_id, points) self.controller.set_route_waypoints(item_kind, item_id, points)
def preview_route_node( def preview_route_node(
@@ -1169,23 +1276,20 @@ class GraphScene(QGraphicsScene):
return return
start = position if index == -1 else QPointF(item.x, item.y) start = position if index == -1 else QPointF(item.x, item.y)
end = position if index == -2 else QPointF(item.x + item.width, item.y + item.height) end = position if index == -2 else QPointF(item.x + item.width, item.y + item.height)
path, _direction_points = self._route_path( path, _direction_points = self._route_path(start, end, points)
start,
end,
str(item.properties.get("routing", "angled")),
points,
)
graphics.setPath(path) graphics.setPath(path)
return return
if index >= len(points): if index >= len(points):
return return
points[index] = position points[index] = position
routing = str(item.properties.get("routing", "angled"))
if item_kind == "connection": if item_kind == "connection":
graphics = self.connection_items.get(item_id) graphics = self.connection_items.get(item_id)
if graphics is None: if graphics is None:
return return
path, direction_points = self._route_path(graphics.start, graphics.end, routing, points) geometry = self._connection_geometry(item, points)
if geometry is None:
return
path, direction_points = self._route_path(*geometry, points)
graphics.set_connection_path(path, direction_points) graphics.set_connection_path(path, direction_points)
return return
graphics = self.annotation_items.get(item_id) graphics = self.annotation_items.get(item_id)
@@ -1193,7 +1297,7 @@ class GraphScene(QGraphicsScene):
return return
start = QPointF(item.x, item.y) start = QPointF(item.x, item.y)
end = QPointF(item.x + item.width, item.y + item.height) end = QPointF(item.x + item.width, item.y + item.height)
path, _direction_points = self._route_path(start, end, routing, points) path, _direction_points = self._route_path(start, end, points)
graphics.setPath(path) graphics.setPath(path)
def delete_route_node(self, item_kind: str, item_id: str, index: int) -> None: def delete_route_node(self, item_kind: str, item_id: str, index: int) -> None:
@@ -1217,29 +1321,29 @@ class GraphScene(QGraphicsScene):
self.addItem(self.pending_preview) self.addItem(self.pending_preview)
def _update_preview(self, cursor: QPointF) -> None: def _update_preview(self, cursor: QPointF) -> None:
if self.pending_source is None or self.pending_preview is None: if self.pending_connection_item is None or self.pending_preview is None:
return return
preview_cursor = _snapped(cursor) if self.connection_routing == "angled" else cursor preview_cursor = _snapped(cursor)
path, _points = self._route_path( path, _points = self._route_path(
self.pending_source.scenePos(), self._connection_anchor(self.pending_connection_item),
preview_cursor, preview_cursor,
self.connection_routing,
self.pending_waypoints, self.pending_waypoints,
) )
self.pending_preview.setPath(path) self.pending_preview.setPath(path)
def _clear_pending_source(self) -> None: def _clear_pending_connection(self) -> None:
if self.pending_source is not None: if isinstance(self.pending_connection_item, ConnectionPortItem):
self.pending_source.setBrush(QColor("#ffffff")) self.pending_connection_item.setBrush(QColor("#ffffff"))
if self.pending_preview is not None: if self.pending_preview is not None:
self.removeItem(self.pending_preview) self.removeItem(self.pending_preview)
self.pending_source = None self.pending_connection_item = None
self.pending_waypoints.clear() self.pending_waypoints.clear()
self.pending_preview = None self.pending_preview = None
class GraphWorkspaceView(QGraphicsView): class GraphWorkspaceView(QGraphicsView):
toolUsed = Signal() toolUsed = Signal()
toolModeShortcutRequested = Signal(str)
componentOptionsRequested = Signal(str) componentOptionsRequested = Signal(str)
componentPortOptionsRequested = Signal(str) componentPortOptionsRequested = Signal(str)
portOptionsRequested = Signal(str, str) portOptionsRequested = Signal(str, str)
@@ -1252,6 +1356,7 @@ class GraphWorkspaceView(QGraphicsView):
self.tool_mode = "pointer" self.tool_mode = "pointer"
self.paste_count = 0 self.paste_count = 0
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setMouseTracking(True) self.setMouseTracking(True)
self.setRenderHint(QPainter.RenderHint.Antialiasing) self.setRenderHint(QPainter.RenderHint.Antialiasing)
self.setDragMode(QGraphicsView.DragMode.RubberBandDrag) self.setDragMode(QGraphicsView.DragMode.RubberBandDrag)
@@ -1304,6 +1409,23 @@ class GraphWorkspaceView(QGraphicsView):
self._zoom(1.2 if event.angleDelta().y() > 0 else 1 / 1.2) self._zoom(1.2 if event.angleDelta().y() > 0 else 1 / 1.2)
event.accept() event.accept()
def keyPressEvent(self, event: QKeyEvent) -> None: # noqa: N802
if not event.isAutoRepeat() and event.key() == Qt.Key.Key_Space:
self.toolModeShortcutRequested.emit(
"connect" if self.tool_mode == "pointer" else "pointer"
)
event.accept()
return
if (
not event.isAutoRepeat()
and event.key() == Qt.Key.Key_Escape
and self.tool_mode != "pointer"
):
self.toolModeShortcutRequested.emit("pointer")
event.accept()
return
super().keyPressEvent(event)
def set_model(self, controller: DocumentController) -> None: def set_model(self, controller: DocumentController) -> None:
self.controller = controller self.controller = controller
scene = GraphScene(controller, self) scene = GraphScene(controller, self)
@@ -1445,11 +1567,6 @@ class GraphWorkspaceView(QGraphicsView):
if isinstance(scene, GraphScene): if isinstance(scene, GraphScene):
scene.set_interaction_mode(mode) scene.set_interaction_mode(mode)
def set_connection_routing(self, routing: str) -> None:
scene = self.scene()
if isinstance(scene, GraphScene):
scene.set_connection_routing(routing)
def mousePressEvent(self, event: QMouseEvent) -> None: # noqa: N802 def mousePressEvent(self, event: QMouseEvent) -> None: # noqa: N802
super().mousePressEvent(event) super().mousePressEvent(event)

View File

@@ -92,6 +92,7 @@ class MainWindow(QMainWindow):
self.ui.graphView.selectionAvailabilityChanged.connect( self.ui.graphView.selectionAvailabilityChanged.connect(
lambda _available: self._update_edit_actions() lambda _available: self._update_edit_actions()
) )
self.ui.graphView.toolModeShortcutRequested.connect(self.set_graph_tool)
self.mode_button_group = QButtonGroup(self) self.mode_button_group = QButtonGroup(self)
self.mode_button_group.setExclusive(True) self.mode_button_group.setExclusive(True)
self.mode_button_group.addButton(self.ui.pointerToolButton) self.mode_button_group.addButton(self.ui.pointerToolButton)
@@ -99,14 +100,6 @@ class MainWindow(QMainWindow):
self.mode_button_group.addButton(self.ui.boxToolButton) self.mode_button_group.addButton(self.ui.boxToolButton)
self.mode_button_group.addButton(self.ui.lineToolButton) self.mode_button_group.addButton(self.ui.lineToolButton)
self.mode_button_group.addButton(self.ui.textToolButton) self.mode_button_group.addButton(self.ui.textToolButton)
self.routing_button_group = QButtonGroup(self)
self.routing_button_group.setExclusive(True)
for button in (
self.ui.directRoutingButton,
self.ui.angledRoutingButton,
self.ui.splineRoutingButton,
):
self.routing_button_group.addButton(button)
self.ui.navigateUpButton.clicked.connect(self.navigate_up) self.ui.navigateUpButton.clicked.connect(self.navigate_up)
self.ui.navigateDownButton.clicked.connect(self.navigate_down) self.ui.navigateDownButton.clicked.connect(self.navigate_down)
self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer")) self.ui.pointerToolButton.clicked.connect(lambda: self.set_graph_tool("pointer"))
@@ -115,9 +108,6 @@ class MainWindow(QMainWindow):
self.ui.lineToolButton.clicked.connect(lambda: self.set_graph_tool("line")) self.ui.lineToolButton.clicked.connect(lambda: self.set_graph_tool("line"))
self.ui.textToolButton.clicked.connect(lambda: self.set_graph_tool("text")) self.ui.textToolButton.clicked.connect(lambda: self.set_graph_tool("text"))
self.ui.rotateToolButton.clicked.connect(self.ui.graphView.rotate_selected) self.ui.rotateToolButton.clicked.connect(self.ui.graphView.rotate_selected)
self.ui.directRoutingButton.clicked.connect(lambda: self.set_connection_routing("direct"))
self.ui.angledRoutingButton.clicked.connect(lambda: self.set_connection_routing("angled"))
self.ui.splineRoutingButton.clicked.connect(lambda: self.set_connection_routing("spline"))
self.ui.applyJsonButton.clicked.connect(self.apply_json) self.ui.applyJsonButton.clicked.connect(self.apply_json)
self.document_controller.activeGraphChanged.connect(self._active_graph_changed) self.document_controller.activeGraphChanged.connect(self._active_graph_changed)
@@ -255,10 +245,6 @@ class MainWindow(QMainWindow):
self.ui.lineToolButton, self.ui.lineToolButton,
self.ui.textToolButton, self.ui.textToolButton,
self.ui.rotateToolButton, self.ui.rotateToolButton,
self.ui.routingLabel,
self.ui.directRoutingButton,
self.ui.angledRoutingButton,
self.ui.splineRoutingButton,
): ):
widget.setVisible(visible) widget.setVisible(visible)
@@ -272,15 +258,6 @@ class MainWindow(QMainWindow):
"text": self.ui.textToolButton, "text": self.ui.textToolButton,
}[mode].setChecked(True) }[mode].setChecked(True)
def set_connection_routing(self, routing: str) -> None:
self.ui.graphView.set_connection_routing(routing)
buttons = {
"direct": self.ui.directRoutingButton,
"angled": self.ui.angledRoutingButton,
"spline": self.ui.splineRoutingButton,
}
buttons[routing].setChecked(True)
def _load_source_json(self) -> None: def _load_source_json(self) -> None:
component = self.document_controller.active_component component = self.document_controller.active_component
if component is None: if component is None:

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConnectionChooserDialog</class>
<widget class="QDialog" name="ConnectionChooserDialog">
<property name="geometry"><rect><x>0</x><y>0</y><width>460</width><height>280</height></rect></property>
<property name="windowTitle"><string>Select a Connection</string></property>
<layout class="QVBoxLayout" name="dialogLayout">
<item><widget class="QLabel" name="promptLabel"><property name="text"><string>Select a connection:</string></property></widget></item>
<item><widget class="QListWidget" name="connectionList"><property name="alternatingRowColors"><bool>true</bool></property></widget></item>
<item><widget class="QDialogButtonBox" name="buttonBox"><property name="standardButtons"><set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set></property></widget></item>
</layout>
</widget>
<resources/>
<connections>
<connection><sender>buttonBox</sender><signal>accepted()</signal><receiver>ConnectionChooserDialog</receiver><slot>accept()</slot><hints/></connection>
<connection><sender>buttonBox</sender><signal>rejected()</signal><receiver>ConnectionChooserDialog</receiver><slot>reject()</slot><hints/></connection>
<connection><sender>connectionList</sender><signal>itemDoubleClicked(QListWidgetItem*)</signal><receiver>ConnectionChooserDialog</receiver><slot>accept()</slot><hints/></connection>
</connections>
</ui>

View File

@@ -2,35 +2,260 @@
<ui version="4.0"> <ui version="4.0">
<class>IconEditorDialog</class> <class>IconEditorDialog</class>
<widget class="QDialog" name="IconEditorDialog"> <widget class="QDialog" name="IconEditorDialog">
<property name="geometry"><rect><x>0</x><y>0</y><width>850</width><height>600</height></rect></property> <property name="geometry">
<property name="windowTitle"><string>Icon Editor</string></property> <rect>
<x>0</x>
<y>0</y>
<width>850</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Icon Editor</string>
</property>
<layout class="QVBoxLayout" name="dialogLayout"> <layout class="QVBoxLayout" name="dialogLayout">
<item> <item>
<layout class="QHBoxLayout" name="shapeToolbarLayout"> <layout class="QHBoxLayout" name="shapeToolbarLayout">
<item><widget class="QToolButton" name="pointerButton"><property name="text"><string>Pointer</string></property><property name="checkable"><bool>true</bool></property><property name="checked"><bool>true</bool></property></widget></item> <item>
<item><widget class="QLabel" name="addShapeLabel"><property name="text"><string>Add:</string></property></widget></item> <widget class="QToolButton" name="pointerButton">
<item><widget class="QToolButton" name="addRectangleButton"><property name="text"><string>Rectangle</string></property><property name="checkable"><bool>true</bool></property></widget></item> <property name="text">
<item><widget class="QToolButton" name="addCircleButton"><property name="text"><string>Circle</string></property><property name="checkable"><bool>true</bool></property></widget></item> <string>Pointer</string>
<item><widget class="QToolButton" name="addEllipseButton"><property name="text"><string>Ellipse</string></property><property name="checkable"><bool>true</bool></property></widget></item> </property>
<item><widget class="QToolButton" name="addLineButton"><property name="text"><string>Line</string></property><property name="checkable"><bool>true</bool></property></widget></item> <property name="icon">
<item><widget class="QToolButton" name="addTriangleButton"><property name="text"><string>Triangle</string></property><property name="checkable"><bool>true</bool></property></widget></item> <iconset resource="../resources/resources.qrc">
<item><widget class="QToolButton" name="addTextButton"><property name="text"><string>Text</string></property><property name="checkable"><bool>true</bool></property></widget></item> <normaloff>:/icons/icons/edit-select.png</normaloff>:/icons/icons/edit-select.png</iconset>
<item><spacer name="toolbarSpacer"><property name="orientation"><enum>Qt::Orientation::Horizontal</enum></property><property name="sizeHint" stdset="0"><size><width>40</width><height>20</height></size></property></spacer></item> </property>
<item><widget class="QToolButton" name="zoomInButton"><property name="text"><string>+</string></property><property name="toolTip"><string>Zoom in</string></property></widget></item> <property name="checkable">
<item><widget class="QToolButton" name="zoomOutButton"><property name="text"><string></string></property><property name="toolTip"><string>Zoom out</string></property></widget></item> <bool>true</bool>
<item><widget class="QToolButton" name="centerButton"><property name="text"><string>Fit</string></property><property name="toolTip"><string>Fit and center the icon canvas</string></property></widget></item> </property>
<item><widget class="QPushButton" name="deleteSelectedButton"><property name="text"><string>Delete selected</string></property></widget></item> <property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="addShapeLabel">
<property name="text">
<string>Add:</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addRectangleButton">
<property name="text">
<string>Rectangle</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/draw-rectangle.png</normaloff>:/icons/icons/draw-rectangle.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addCircleButton">
<property name="text">
<string>Circle</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/draw-circle.png</normaloff>:/icons/icons/draw-circle.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addEllipseButton">
<property name="text">
<string>Ellipse</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/draw-ellipse.png</normaloff>:/icons/icons/draw-ellipse.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addLineButton">
<property name="text">
<string>Line</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/draw-bezier-curves.png</normaloff>:/icons/icons/draw-bezier-curves.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addTriangleButton">
<property name="text">
<string>Triangle</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/draw-triangle.png</normaloff>:/icons/icons/draw-triangle.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="addTextButton">
<property name="text">
<string>Text</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/draw-text.png</normaloff>:/icons/icons/draw-text.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="toolbarSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="zoomInButton">
<property name="toolTip">
<string>Zoom in</string>
</property>
<property name="text">
<string>+</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/zoom-in.png</normaloff>:/icons/icons/zoom-in.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="zoomOutButton">
<property name="toolTip">
<string>Zoom out</string>
</property>
<property name="text">
<string></string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/zoom-out.png</normaloff>:/icons/icons/zoom-out.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="centerButton">
<property name="toolTip">
<string>Fit and center the icon canvas</string>
</property>
<property name="text">
<string>Fit</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/zoom-original.png</normaloff>:/icons/icons/zoom-original.png</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteSelectedButton">
<property name="text">
<string>Delete selected</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item><widget class="IconCanvasView" name="iconView"><property name="dragMode"><enum>QGraphicsView::DragMode::RubberBandDrag</enum></property></widget></item> <item>
<item><widget class="QLabel" name="portHintLabel"><property name="text"><string>Green points are inputs; red points are outputs. Drag them to place connection anchors.</string></property><property name="wordWrap"><bool>true</bool></property></widget></item> <widget class="IconCanvasView" name="iconView">
<item><widget class="QDialogButtonBox" name="buttonBox"><property name="standardButtons"><set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set></property></widget></item> <property name="dragMode">
<enum>QGraphicsView::DragMode::RubberBandDrag</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="portHintLabel">
<property name="text">
<string>Green points are inputs; red points are outputs. Drag them to place connection anchors.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok</set>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets><customwidget><class>IconCanvasView</class><extends>QGraphicsView</extends><header>bedit.gui.graphics.icon_canvas</header></customwidget></customwidgets> <customwidgets>
<resources/> <customwidget>
<class>IconCanvasView</class>
<extends>QGraphicsView</extends>
<header>bedit.gui.graphics.icon_canvas</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections> <connections>
<connection><sender>buttonBox</sender><signal>accepted()</signal><receiver>IconEditorDialog</receiver><slot>accept()</slot><hints/></connection> <connection>
<connection><sender>buttonBox</sender><signal>rejected()</signal><receiver>IconEditorDialog</receiver><slot>reject()</slot><hints/></connection> <sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>IconEditorDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>IconEditorDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>20</x>
<y>20</y>
</hint>
<hint type="destinationlabel">
<x>20</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections> </connections>
</ui> </ui>

View File

@@ -296,6 +296,10 @@
<property name="text"> <property name="text">
<string>Connect</string> <string>Connect</string>
</property> </property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/network-connect.png</normaloff>:/icons/icons/network-connect.png</iconset>
</property>
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
@@ -366,46 +370,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QLabel" name="routingLabel">
<property name="text">
<string>Line:</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="directRoutingButton">
<property name="text">
<string>Direct</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="angledRoutingButton">
<property name="text">
<string>Angled</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="splineRoutingButton">
<property name="text">
<string>Spline</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>

View File

@@ -63,8 +63,8 @@
"id": "af73487a-4a2d-4908-b919-d0a2eaa75ce2", "id": "af73487a-4a2d-4908-b919-d0a2eaa75ce2",
"name": "A", "name": "A",
"position": { "position": {
"x": -192.0, "x": -224.0,
"y": -192.0 "y": -168.0
}, },
"rotation": 0.0, "rotation": 0.0,
"interface": { "interface": {
@@ -157,8 +157,8 @@
"id": "b89a77a1-d773-4ee3-8c3f-74377c29b4b2", "id": "b89a77a1-d773-4ee3-8c3f-74377c29b4b2",
"name": "B", "name": "B",
"position": { "position": {
"x": 128.0, "x": 96.0,
"y": -128.0 "y": -288.0
}, },
"rotation": 0.0, "rotation": 0.0,
"interface": { "interface": {
@@ -266,7 +266,7 @@
], ],
"connections": [ "connections": [
{ {
"id": "c59eac1f-5f24-45de-90ee-bfd039894ed9", "id": "87490296-e353-43ea-8b59-55329950fdce",
"source": { "source": {
"block": "af73487a-4a2d-4908-b919-d0a2eaa75ce2", "block": "af73487a-4a2d-4908-b919-d0a2eaa75ce2",
"port": "port-de109124" "port": "port-de109124"
@@ -277,85 +277,24 @@
}, },
"name": "", "name": "",
"properties": { "properties": {
"routing": "angled",
"waypoints": [ "waypoints": [
{ {
"x": -64.0, "x": -64.0,
"y": -128.0 "y": -192.0
}, },
{ {
"x": -64.0, "x": -64.0,
"y": 64.0 "y": -256.0
},
{
"x": 0.0,
"y": 64.0
},
{
"x": 0.0,
"y": 120.0
}, },
{ {
"x": 64.0, "x": 64.0,
"y": 120.0 "y": -256.0
},
{
"x": 64.0,
"y": -80.0
} }
] ]
} }
} }
], ],
"annotations": [ "annotations": []
{
"id": "bfe7d48b-fe54-4378-ab1b-f9b46c2efa87",
"kind": "box",
"position": {
"x": 48.0,
"y": -328.0
},
"size": {
"width": 304.0,
"height": 160.0
},
"text": "",
"layer": -1,
"properties": {
"stroke": "#303030",
"lineWidth": 1.5,
"lineStyle": "solid",
"fill": "#dbeafe",
"cornerRadius": 0.0,
"routing": "angled",
"waypoints": []
}
},
{
"id": "aa69f7b3-f5af-426b-9eb3-c63a268346b5",
"kind": "text",
"position": {
"x": -272.0,
"y": -216.0
},
"size": {
"width": 400.0,
"height": 48.0
},
"text": "This is an annotation",
"layer": -1,
"properties": {
"stroke": "#023010",
"lineWidth": 3.0,
"lineStyle": "dash",
"fill": "none",
"fontSize": 18.0,
"color": "#023010",
"routing": "angled",
"waypoints": []
}
}
]
} }
} }
} }