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
existing connection.
- Connections reference port IDs, never port names.
- Graph interaction has separate Pointer and Connect modes. Connections store a
`properties.routing` value (`direct`, `angled`, or `spline`); angled routes
store absolute scene points in `properties.waypoints`.
- Graph interaction has separate Pointer and Connect modes. Port hints are only
visible in Connect mode. Connecting two blocks opens the compatible port-pair
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
`gui/graphics/connection_styles.py`, including color, width, pen style, and
source/target arrowheads. Do not scatter those constants through painters.
- Graph annotations are `box`, `line`, or `text` objects in `Graph.annotations`.
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
their style fields and rendering must remain aligned.
- 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
band or Ctrl-click for multiple selection, Delete to remove items, and the
standard Cut/Copy/Paste shortcuts to duplicate selected component groups.
- Switch the graph header to **Connect**, choose Direct, Angled, or Spline, then
click an output and input. Angled connections accept intermediate corner
clicks, use right-angle segments, and snap to the graph snapping grid.
Right-click cancels an unfinished connection.
- Switch the graph header to **Connect** and click two blocks or their temporary
port hints. A chooser lists every compatible output/input pairing in both
directions and preselects the pairing implied by the clicks. Empty-canvas
clicks add freely angled, grid-snapped vertices; right-click cancels.
- 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
orthogonal corner as a draggable, grid-snapped node.
to add a node, or right-click a node to delete it. Visible wire endpoints are
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
share the Direct, Angled, and Spline routing controls. Annotation context menus
provide the same shape styling as the icon editor and can move annotations
through integer layers below or above graph layer 0.
use the same freely angled polyline and draggable-node behavior as connections.
Annotation context menus provide the same shape styling as the icon editor and
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
subgraph; use **Up** to return.
- 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>
<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/configure.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\
\xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\
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\
\x89\
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\
>L\x06\x80\x01`\xc7\x01\x7f\x03\xdc\xff\x15WD\x01\
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\
\x89\
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\
\x9aw\xed\x7f\xefoJ\x88l\x9a\x02\xc2\xbb\x00\x00\x00\
\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\
\x89\
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\
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
\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\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1875,6 +2271,10 @@ qt_resource_name = b"\
\x00z\
\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\
\x0b\xa9\xab'\
\x00d\
@@ -1889,6 +2289,15 @@ qt_resource_name = b"\
\x09\xd0z\x07\
\x00a\
\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\
\x0b\xe6\x1f\xa7\
\x00d\
@@ -1922,6 +2331,11 @@ qt_resource_name = b"\
\x00d\
\x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\
\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\
\x06\xeb\x97\xe7\
\x00z\
@@ -1948,6 +2362,10 @@ qt_resource_name = b"\
\x00d\
\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\
\x09\xb3>\xc7\
\x00d\
@@ -1965,49 +2383,59 @@ qt_resource_struct = b"\
\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\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\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\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\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\
"

View File

@@ -235,7 +235,6 @@ class DocumentController(QObject):
source: Endpoint,
target: Endpoint,
*,
routing: str = "angled",
waypoints: list[QPointF] | None = None,
) -> str:
if self.active_component_id is None:
@@ -246,14 +245,11 @@ class DocumentController(QObject):
raise ValueError("A connection endpoint no longer exists")
if not PortTypeRegistry.compatible(source_port.type, target_port.type):
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(
str(uuid4()),
source,
target,
properties={
"routing": routing,
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
},
)
@@ -267,7 +263,6 @@ class DocumentController(QObject):
end: QPointF,
*,
text: str = "",
routing: str = "angled",
waypoints: list[QPointF] | None = None,
) -> str:
if self.active_component_id is None:
@@ -297,16 +292,13 @@ class DocumentController(QObject):
layer=-1,
properties={
**style,
"routing": routing,
"waypoints": [{"x": point.x(), "y": point.y()} for point in (waypoints or [])],
},
)
self.undo_stack.push(AddAnnotationCommand(self, self.active_component_id, annotation))
return annotation.id
def set_route_waypoints(
self, item_kind: str, item_id: str, waypoints: list[QPointF], routing: str | None = None
) -> None:
def set_route_waypoints(self, item_kind: str, item_id: str, waypoints: list[QPointF]) -> None:
item = (
self.active_graph.connections
if item_kind == "connection"
@@ -316,8 +308,6 @@ class DocumentController(QObject):
return
old = deepcopy(item.properties)
new = deepcopy(old)
if routing is not None:
new["routing"] = routing
new["waypoints"] = [{"x": point.x(), "y": point.y()} for point in waypoints]
if old != new:
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\
\xfd\x0bVu/\x8d\xbcS'\xfc\x00\x00\x00\x00IE\
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\
\x89\
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\
>L\x06\x80\x01`\xc7\x01\x7f\x03\xdc\xff\x15WD\x01\
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\
\x89\
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\
\x9aw\xed\x7f\xefoJ\x88l\x9a\x02\xc2\xbb\x00\x00\x00\
\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\
\x89\
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\
\xc7\xcc\x7f5p\xf2g\x94\xf7\x1f\xdf\x9a\xd2\x93\xfbC\
\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\
\x89\
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
@@ -1875,6 +2271,10 @@ qt_resource_name = b"\
\x00z\
\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\
\x0b\xa9\xab'\
\x00d\
@@ -1889,6 +2289,15 @@ qt_resource_name = b"\
\x09\xd0z\x07\
\x00a\
\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\
\x0b\xe6\x1f\xa7\
\x00d\
@@ -1922,6 +2331,11 @@ qt_resource_name = b"\
\x00d\
\x00r\x00a\x00w\x00-\x00b\x00e\x00z\x00i\x00e\x00r\x00-\x00c\x00u\x00r\x00v\x00e\
\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\
\x06\xeb\x97\xe7\
\x00z\
@@ -1948,6 +2362,10 @@ qt_resource_name = b"\
\x00d\
\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\
\x09\xb3>\xc7\
\x00d\
@@ -1965,49 +2383,59 @@ qt_resource_struct = b"\
\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\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\x00\x10\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
\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\x006\x00\x00\x00\x00\x00\x01\x00\x00\x05\x86\
\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\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\
"

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)
from bedit.gui.graphics.icon_canvas import IconCanvasView
from . import resources_rc
class Ui_IconEditorDialog(object):
def setupUi(self, IconEditorDialog):
@@ -33,6 +34,9 @@ class Ui_IconEditorDialog(object):
self.shapeToolbarLayout.setObjectName(u"shapeToolbarLayout")
self.pointerButton = QToolButton(IconEditorDialog)
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.setChecked(True)
@@ -45,36 +49,54 @@ class Ui_IconEditorDialog(object):
self.addRectangleButton = QToolButton(IconEditorDialog)
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.shapeToolbarLayout.addWidget(self.addRectangleButton)
self.addCircleButton = QToolButton(IconEditorDialog)
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.shapeToolbarLayout.addWidget(self.addCircleButton)
self.addEllipseButton = QToolButton(IconEditorDialog)
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.shapeToolbarLayout.addWidget(self.addEllipseButton)
self.addLineButton = QToolButton(IconEditorDialog)
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.shapeToolbarLayout.addWidget(self.addLineButton)
self.addTriangleButton = QToolButton(IconEditorDialog)
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.shapeToolbarLayout.addWidget(self.addTriangleButton)
self.addTextButton = QToolButton(IconEditorDialog)
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.shapeToolbarLayout.addWidget(self.addTextButton)
@@ -85,16 +107,25 @@ class Ui_IconEditorDialog(object):
self.zoomInButton = QToolButton(IconEditorDialog)
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.zoomOutButton = QToolButton(IconEditorDialog)
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.centerButton = QToolButton(IconEditorDialog)
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)
@@ -142,18 +173,18 @@ class Ui_IconEditorDialog(object):
self.addLineButton.setText(QCoreApplication.translate("IconEditorDialog", u"Line", None))
self.addTriangleButton.setText(QCoreApplication.translate("IconEditorDialog", u"Triangle", None))
self.addTextButton.setText(QCoreApplication.translate("IconEditorDialog", u"Text", None))
self.zoomInButton.setText(QCoreApplication.translate("IconEditorDialog", u"+", None))
#if QT_CONFIG(tooltip)
self.zoomInButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom in", None))
#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)
self.zoomOutButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Zoom out", None))
#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)
self.centerButton.setToolTip(QCoreApplication.translate("IconEditorDialog", u"Fit and center the icon canvas", None))
#endif // QT_CONFIG(tooltip)
self.centerButton.setText(QCoreApplication.translate("IconEditorDialog", u"Fit", 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))
# retranslateUi

View File

@@ -235,33 +235,36 @@ class Ui_MainWindow(object):
self.connectToolButton = QToolButton(self.workspaceHeader)
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.workspaceHeaderLayout.addWidget(self.connectToolButton)
self.boxToolButton = QToolButton(self.workspaceHeader)
self.boxToolButton.setObjectName(u"boxToolButton")
icon16 = QIcon()
icon16.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.boxToolButton.setIcon(icon16)
icon17 = QIcon()
icon17.addFile(u":/icons/icons/draw-rectangle.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.boxToolButton.setIcon(icon17)
self.boxToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.boxToolButton)
self.lineToolButton = QToolButton(self.workspaceHeader)
self.lineToolButton.setObjectName(u"lineToolButton")
icon17 = QIcon()
icon17.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.lineToolButton.setIcon(icon17)
icon18 = QIcon()
icon18.addFile(u":/icons/icons/draw-bezier-curves.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.lineToolButton.setIcon(icon18)
self.lineToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.lineToolButton)
self.textToolButton = QToolButton(self.workspaceHeader)
self.textToolButton.setObjectName(u"textToolButton")
icon18 = QIcon()
icon18.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.textToolButton.setIcon(icon18)
icon19 = QIcon()
icon19.addFile(u":/icons/icons/draw-text.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off)
self.textToolButton.setIcon(icon19)
self.textToolButton.setCheckable(True)
self.workspaceHeaderLayout.addWidget(self.textToolButton)
@@ -272,30 +275,6 @@ class Ui_MainWindow(object):
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)
@@ -545,10 +524,6 @@ class Ui_MainWindow(object):
self.rotateToolButton.setToolTip(QCoreApplication.translate("MainWindow", u"Rotate selected blocks clockwise", None))
#endif // QT_CONFIG(tooltip)
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.emptyWorkspaceLabel.setText(QCoreApplication.translate("MainWindow", u"No document open", None))
self.menuFile.setTitle(QCoreApplication.translate("MainWindow", u"&File", None))

View File

@@ -1,10 +1,12 @@
import json
from dataclasses import dataclass
from PySide6.QtCore import QMimeData, QPointF, QRectF, Qt, Signal
from PySide6.QtGui import (
QColor,
QDragEnterEvent,
QDropEvent,
QKeyEvent,
QMouseEvent,
QWheelEvent,
QPainter,
@@ -32,17 +34,26 @@ from PySide6.QtWidgets import (
from bedit.core.model import Annotation, Component, Connection, Endpoint, Port
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.graphics.icon_renderer import icon_bounds, paint_icon
from bedit.gui.graphics.icon_editor import ShapeOptionsDialog
from bedit.gui.graphics.icon_renderer import shape_pen
from bedit.gui.graphics.connection_styles import ConnectionStyle, connection_style
from bedit.gui.preferences import application_settings
from bedit.core.port_types import PortTypeRegistry
SELECTION_MIME_TYPE = "application/x-bedit-selection"
@dataclass(frozen=True)
class ConnectionChoice:
source: Endpoint
target: Endpoint
label: str
def _graph_snap_size() -> int:
return application_settings().value("grid/graphSnapSize", 8, type=int)
@@ -690,11 +701,10 @@ class GraphScene(QGraphicsScene):
self.output_items: dict[str, InterfaceTerminalItem] = {}
self.connection_items: dict[str, ConnectionGraphicsItem] = {}
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_preview: QGraphicsPathItem | None = None
self.interaction_mode = "pointer"
self.connection_routing = "angled"
self.drawing_start: QPointF | None = None
self.drawing_waypoints: list[QPointF] = []
self.setSceneRect(-2000, -2000, 4000, 4000)
@@ -713,7 +723,7 @@ class GraphScene(QGraphicsScene):
self.output_items.clear()
self.connection_items.clear()
self.annotation_items.clear()
self.pending_source = None
self.pending_connection_item = None
self.pending_waypoints.clear()
self.pending_preview = None
owner = self.controller.active_component
@@ -753,6 +763,7 @@ class GraphScene(QGraphicsScene):
item = AnnotationGraphicsItem(annotation, self.controller)
self.addItem(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:
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
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:
connection = self.controller.active_graph.connections.get(connection_id)
graphics = self.connection_items.get(connection_id)
if connection is None or graphics is None:
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 = [
QPointF(float(point["x"]), float(point["y"]))
for point in connection.properties.get("waypoints", [])
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_waypoints(waypoints)
@@ -868,112 +921,193 @@ class GraphScene(QGraphicsScene):
QPointF(float(p["x"]), float(p["y"]))
for p in annotation.properties.get("waypoints", [])
]
routing = str(annotation.properties.get("routing", "angled"))
path, points = self._route_path(start, end, routing, waypoints)
path, points = self._route_path(start, end, waypoints)
graphics.setPath(path)
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
def _route_path(
start: QPointF,
end: QPointF,
routing: str,
waypoints: list[QPointF] | None = None,
) -> tuple[QPainterPath, list[QPointF]]:
waypoints = waypoints or []
points = [start, *(waypoints or []), end]
path = QPainterPath(start)
if routing == "angled":
points = GraphScene._orthogonal_points(start, end, waypoints)
for point in points[1:]:
path.lineTo(point)
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]
for point in points[1:]:
path.lineTo(point)
return path, points
def set_interaction_mode(self, mode: str) -> None:
self.interaction_mode = mode
if mode != "connect":
self._clear_pending_source()
self._clear_pending_connection()
self._clear_drawing()
self._set_port_hints_visible(mode == "connect")
def set_connection_routing(self, routing: str) -> None:
self.connection_routing = routing
self._clear_pending_source()
def _set_port_hints_visible(self, visible: bool) -> None:
for component in self.component_items.values():
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
if self.interaction_mode in {"box", "text", "line"}:
self._drawing_press(event)
return
if self.interaction_mode != "connect":
self._clear_pending_source()
self._clear_pending_connection()
super().mousePressEvent(event)
return
if event.button() == Qt.MouseButton.RightButton:
self._clear_pending_source()
self._clear_pending_connection()
event.accept()
return
item = self.itemAt(event.scenePos(), QTransform())
if isinstance(item, ConnectionPortItem):
if item.role == "source":
self._clear_pending_source()
self.pending_source = item
component = self._clicked_component(item)
if component is not None:
clicked = item if isinstance(item, ConnectionPortItem) else component
if self.pending_connection_item is None:
self.pending_connection_item = clicked
self.pending_waypoints = []
item.setBrush(QColor("#f5b642"))
if isinstance(clicked, ConnectionPortItem):
clicked.setBrush(QColor("#f5b642"))
self._create_preview()
elif self.pending_source is not None:
if self.pending_source.endpoint != item.endpoint:
try:
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()
self._update_preview(event.scenePos())
else:
self._finish_block_connection(clicked)
event.accept()
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._update_preview(event.scenePos())
event.accept()
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())
event.accept()
return
@@ -1011,14 +1145,9 @@ class GraphScene(QGraphicsScene):
self.drawing_start = point
self.drawing_waypoints = []
self._create_preview()
elif self.interaction_mode == "line" and self.connection_routing == "angled":
elif self.interaction_mode == "line":
self.drawing_waypoints.append(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()
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
else self.drawing_waypoints
)
completed = self._completed_angled_waypoints(self.drawing_start, end, waypoints)
self.controller.add_annotation(
"line", self.drawing_start, end, routing="angled", waypoints=completed
)
self.controller.add_annotation("line", self.drawing_start, end, waypoints=waypoints)
self._clear_drawing()
event.accept()
return
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:
if self.drawing_start is None or self.pending_preview is None:
return
@@ -1052,13 +1172,11 @@ class GraphScene(QGraphicsScene):
path = QPainterPath()
path.addRect(QRectF(self.drawing_start, end).normalized())
else:
path, _ = self._route_path(
self.drawing_start, end, self.connection_routing, self.drawing_waypoints
)
path, _ = self._route_path(self.drawing_start, end, self.drawing_waypoints)
self.pending_preview.setPath(path)
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.pending_preview = None
self.drawing_start = None
@@ -1118,10 +1236,7 @@ class GraphScene(QGraphicsScene):
key=lambda index: segment_distance(snapped, anchors[index], anchors[index + 1]),
)
points.insert(insertion, snapped)
routing = "angled" if item.properties.get("routing") == "direct" else None
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)
self.controller.set_route_waypoints(item_kind, item_id, points)
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)
@@ -1147,14 +1262,6 @@ class GraphScene(QGraphicsScene):
return
if index < len(points):
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)
def preview_route_node(
@@ -1169,23 +1276,20 @@ class GraphScene(QGraphicsScene):
return
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)
path, _direction_points = self._route_path(
start,
end,
str(item.properties.get("routing", "angled")),
points,
)
path, _direction_points = self._route_path(start, end, points)
graphics.setPath(path)
return
if index >= len(points):
return
points[index] = position
routing = str(item.properties.get("routing", "angled"))
if item_kind == "connection":
graphics = self.connection_items.get(item_id)
if graphics is None:
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)
return
graphics = self.annotation_items.get(item_id)
@@ -1193,7 +1297,7 @@ class GraphScene(QGraphicsScene):
return
start = QPointF(item.x, item.y)
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)
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)
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
preview_cursor = _snapped(cursor) if self.connection_routing == "angled" else cursor
preview_cursor = _snapped(cursor)
path, _points = self._route_path(
self.pending_source.scenePos(),
self._connection_anchor(self.pending_connection_item),
preview_cursor,
self.connection_routing,
self.pending_waypoints,
)
self.pending_preview.setPath(path)
def _clear_pending_source(self) -> None:
if self.pending_source is not None:
self.pending_source.setBrush(QColor("#ffffff"))
def _clear_pending_connection(self) -> None:
if isinstance(self.pending_connection_item, ConnectionPortItem):
self.pending_connection_item.setBrush(QColor("#ffffff"))
if self.pending_preview is not None:
self.removeItem(self.pending_preview)
self.pending_source = None
self.pending_connection_item = None
self.pending_waypoints.clear()
self.pending_preview = None
class GraphWorkspaceView(QGraphicsView):
toolUsed = Signal()
toolModeShortcutRequested = Signal(str)
componentOptionsRequested = Signal(str)
componentPortOptionsRequested = Signal(str)
portOptionsRequested = Signal(str, str)
@@ -1252,6 +1356,7 @@ class GraphWorkspaceView(QGraphicsView):
self.tool_mode = "pointer"
self.paste_count = 0
self.setAcceptDrops(True)
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
self.setMouseTracking(True)
self.setRenderHint(QPainter.RenderHint.Antialiasing)
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)
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:
self.controller = controller
scene = GraphScene(controller, self)
@@ -1445,11 +1567,6 @@ class GraphWorkspaceView(QGraphicsView):
if isinstance(scene, GraphScene):
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
super().mousePressEvent(event)

View File

@@ -92,6 +92,7 @@ class MainWindow(QMainWindow):
self.ui.graphView.selectionAvailabilityChanged.connect(
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.setExclusive(True)
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.lineToolButton)
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.navigateDownButton.clicked.connect(self.navigate_down)
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.textToolButton.clicked.connect(lambda: self.set_graph_tool("text"))
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.document_controller.activeGraphChanged.connect(self._active_graph_changed)
@@ -255,10 +245,6 @@ class MainWindow(QMainWindow):
self.ui.lineToolButton,
self.ui.textToolButton,
self.ui.rotateToolButton,
self.ui.routingLabel,
self.ui.directRoutingButton,
self.ui.angledRoutingButton,
self.ui.splineRoutingButton,
):
widget.setVisible(visible)
@@ -272,15 +258,6 @@ class MainWindow(QMainWindow):
"text": self.ui.textToolButton,
}[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:
component = self.document_controller.active_component
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">
<class>IconEditorDialog</class>
<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="windowTitle"><string>Icon Editor</string></property>
<property name="geometry">
<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">
<item>
<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><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="checkable"><bool>true</bool></property></widget></item>
<item><widget class="QToolButton" name="addCircleButton"><property name="text"><string>Circle</string></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="checkable"><bool>true</bool></property></widget></item>
<item><widget class="QToolButton" name="addLineButton"><property name="text"><string>Line</string></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="checkable"><bool>true</bool></property></widget></item>
<item><widget class="QToolButton" name="addTextButton"><property name="text"><string>Text</string></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="text"><string>+</string></property><property name="toolTip"><string>Zoom in</string></property></widget></item>
<item><widget class="QToolButton" name="zoomOutButton"><property name="text"><string></string></property><property name="toolTip"><string>Zoom out</string></property></widget></item>
<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>
<item><widget class="QPushButton" name="deleteSelectedButton"><property name="text"><string>Delete selected</string></property></widget></item>
<item>
<widget class="QToolButton" name="pointerButton">
<property name="text">
<string>Pointer</string>
</property>
<property name="icon">
<iconset resource="../resources/resources.qrc">
<normaloff>:/icons/icons/edit-select.png</normaloff>:/icons/icons/edit-select.png</iconset>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<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>
</item>
<item><widget class="IconCanvasView" name="iconView"><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>
<item>
<widget class="IconCanvasView" name="iconView">
<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>
</widget>
<customwidgets><customwidget><class>IconCanvasView</class><extends>QGraphicsView</extends><header>bedit.gui.graphics.icon_canvas</header></customwidget></customwidgets>
<resources/>
<customwidgets>
<customwidget>
<class>IconCanvasView</class>
<extends>QGraphicsView</extends>
<header>bedit.gui.graphics.icon_canvas</header>
</customwidget>
</customwidgets>
<resources>
<include location="../resources/resources.qrc"/>
</resources>
<connections>
<connection><sender>buttonBox</sender><signal>accepted()</signal><receiver>IconEditorDialog</receiver><slot>accept()</slot><hints/></connection>
<connection><sender>buttonBox</sender><signal>rejected()</signal><receiver>IconEditorDialog</receiver><slot>reject()</slot><hints/></connection>
<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>
</ui>

View File

@@ -296,6 +296,10 @@
<property name="text">
<string>Connect</string>
</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">
<bool>true</bool>
</property>
@@ -366,46 +370,6 @@
</property>
</widget>
</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>
</widget>
</item>

View File

@@ -63,8 +63,8 @@
"id": "af73487a-4a2d-4908-b919-d0a2eaa75ce2",
"name": "A",
"position": {
"x": -192.0,
"y": -192.0
"x": -224.0,
"y": -168.0
},
"rotation": 0.0,
"interface": {
@@ -157,8 +157,8 @@
"id": "b89a77a1-d773-4ee3-8c3f-74377c29b4b2",
"name": "B",
"position": {
"x": 128.0,
"y": -128.0
"x": 96.0,
"y": -288.0
},
"rotation": 0.0,
"interface": {
@@ -266,7 +266,7 @@
],
"connections": [
{
"id": "c59eac1f-5f24-45de-90ee-bfd039894ed9",
"id": "87490296-e353-43ea-8b59-55329950fdce",
"source": {
"block": "af73487a-4a2d-4908-b919-d0a2eaa75ce2",
"port": "port-de109124"
@@ -277,85 +277,24 @@
},
"name": "",
"properties": {
"routing": "angled",
"waypoints": [
{
"x": -64.0,
"y": -128.0
"y": -192.0
},
{
"x": -64.0,
"y": 64.0
},
{
"x": 0.0,
"y": 64.0
},
{
"x": 0.0,
"y": 120.0
"y": -256.0
},
{
"x": 64.0,
"y": 120.0
},
{
"x": 64.0,
"y": -80.0
"y": -256.0
}
]
}
}
],
"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": []
}
}
]
"annotations": []
}
}
}