mirror of
				https://github.com/RolandWH/SIPPCompare.git
				synced 2025-11-04 12:02:11 +00:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			e379b4a7e2
			...
			submission
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 005f189c76 | |||
| c8d7594af6 | |||
| 756efdf364 | |||
| 2af0ecfd75 | |||
| 8ddc40baf6 | |||
| 8f6f3dab00 | |||
| 4b8bdfbeea | |||
| 09f8b7bba8 | |||
| abbcbe41d0 | |||
| 75e26e229a | |||
| 5eb0c3c4a3 | |||
| 78323c2ad8 | |||
| 2422395759 | |||
| 7cb41652b4 | |||
| ae01d912e1 | |||
| 9a8b0045fa | |||
| 9e79d986e4 | |||
| 71d9590205 | |||
| 3b6b75ee48 | |||
| 
						 | 
					ca8f4409c1 | ||
| ba204becc9 | |||
| 42afd128e5 | |||
| 629ea6833d | |||
| e2ca298919 | |||
| 593dec96d1 | |||
| fa74e9e8da | |||
| 16802648bc | |||
| c151b19a3c | |||
| f2a0735972 | |||
| 348b027ede | |||
| 54b7bdd700 | |||
| 929c3719c0 | |||
| d8c4b5d64d | |||
| 0fa6db3bc0 | |||
| df436fa2b2 | |||
| 1234008813 | |||
| 662a3a5e9c | 
							
								
								
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -2,8 +2,9 @@
 | 
			
		||||
.idea/workspace.xml
 | 
			
		||||
.idea/discord.xml
 | 
			
		||||
.idea/encodings.xml
 | 
			
		||||
build/
 | 
			
		||||
dist/
 | 
			
		||||
*build/
 | 
			
		||||
*dist/
 | 
			
		||||
output/
 | 
			
		||||
src/*/__pycache__/
 | 
			
		||||
src/__pycache__/
 | 
			
		||||
src/__pycache__/
 | 
			
		||||
*.db
 | 
			
		||||
							
								
								
									
										2
									
								
								.idea/SIPPCompare.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/SIPPCompare.iml
									
									
									
										generated
									
									
									
								
							@@ -3,6 +3,8 @@
 | 
			
		||||
  <component name="NewModuleRootManager">
 | 
			
		||||
    <content url="file://$MODULE_DIR$">
 | 
			
		||||
      <excludeFolder url="file://$MODULE_DIR$/.venv" />
 | 
			
		||||
      <excludeFolder url="file://$MODULE_DIR$/build" />
 | 
			
		||||
      <excludeFolder url="file://$MODULE_DIR$/output" />
 | 
			
		||||
    </content>
 | 
			
		||||
    <orderEntry type="jdk" jdkName="Python 3.13 (SIPPCompare)" jdkType="Python SDK" />
 | 
			
		||||
    <orderEntry type="sourceFolder" forTests="false" />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
# -*- mode: python ; coding: utf-8 -*-
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
a = Analysis(
 | 
			
		||||
    ['src\\main.py'],
 | 
			
		||||
    pathex=[],
 | 
			
		||||
    binaries=[],
 | 
			
		||||
    datas=[('gui/*.ui', 'gui'), ('icon2.ico', '.')],
 | 
			
		||||
    hiddenimports=[],
 | 
			
		||||
    hookspath=[],
 | 
			
		||||
    hooksconfig={},
 | 
			
		||||
    runtime_hooks=[],
 | 
			
		||||
    excludes=[],
 | 
			
		||||
    noarchive=False,
 | 
			
		||||
    optimize=0,
 | 
			
		||||
)
 | 
			
		||||
pyz = PYZ(a.pure)
 | 
			
		||||
 | 
			
		||||
exe = EXE(
 | 
			
		||||
    pyz,
 | 
			
		||||
    a.scripts,
 | 
			
		||||
    [],
 | 
			
		||||
    exclude_binaries=True,
 | 
			
		||||
    name='SIPPCompare',
 | 
			
		||||
    debug=False,
 | 
			
		||||
    bootloader_ignore_signals=False,
 | 
			
		||||
    strip=False,
 | 
			
		||||
    upx=True,
 | 
			
		||||
    console=False,
 | 
			
		||||
    disable_windowed_traceback=False,
 | 
			
		||||
    argv_emulation=False,
 | 
			
		||||
    target_arch=None,
 | 
			
		||||
    codesign_identity=None,
 | 
			
		||||
    entitlements_file=None,
 | 
			
		||||
    icon="icon2.ico"
 | 
			
		||||
)
 | 
			
		||||
coll = COLLECT(
 | 
			
		||||
    exe,
 | 
			
		||||
    a.binaries,
 | 
			
		||||
    a.datas,
 | 
			
		||||
    strip=False,
 | 
			
		||||
    upx=True,
 | 
			
		||||
    upx_exclude=[],
 | 
			
		||||
    name='SIPPCompare',
 | 
			
		||||
)
 | 
			
		||||
							
								
								
									
										138
									
								
								flowcharts/main_win.drawio
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								flowcharts/main_win.drawio
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,138 @@
 | 
			
		||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
 | 
			
		||||
  <diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
 | 
			
		||||
    <mxGraphModel dx="2068" dy="1219" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
 | 
			
		||||
      <root>
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-0" target="GkLdqIe8JMVAQNq9l6uQ-4" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="144" y="120" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-0" value="Import user details from database" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.start_1;whiteSpace=wrap;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="94" y="10" width="100" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-7" value="Yes<div><br></div>" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-4" target="GkLdqIe8JMVAQNq9l6uQ-14" edge="1">
 | 
			
		||||
          <mxGeometry x="0.0098" y="15" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="139" y="230" as="sourcePoint" />
 | 
			
		||||
            <mxPoint x="144" y="270" as="targetPoint" />
 | 
			
		||||
            <mxPoint x="1" y="-1" as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-4" value="User input?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="94" y="120" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-9" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-8" target="GkLdqIe8JMVAQNq9l6uQ-4" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="330" y="320" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="250" y="480" />
 | 
			
		||||
              <mxPoint x="250" y="170" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-10" value="No" style="edgeLabel;html=1;align=center;verticalAlign=middle;resizable=0;points=[];" parent="GkLdqIe8JMVAQNq9l6uQ-9" vertex="1" connectable="0">
 | 
			
		||||
          <mxGeometry x="0.0214" y="1" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="1" y="120" as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-12" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-8" target="GkLdqIe8JMVAQNq9l6uQ-11" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-8" value="User input&nbsp;<div>valid?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="94" y="430" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-11" value="Enable calculate button" style="whiteSpace=wrap;html=1;strokeWidth=2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="84" y="584" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-15" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-14" target="GkLdqIe8JMVAQNq9l6uQ-20" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="340" y="320" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="300" y="320" />
 | 
			
		||||
              <mxPoint x="300" y="40" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-14" value="Calculate&nbsp;<div>button&nbsp;</div><div>clicked?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="94" y="270" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-17" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-14" target="GkLdqIe8JMVAQNq9l6uQ-8" edge="1">
 | 
			
		||||
          <mxGeometry x="0.2" y="16" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-18" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-11" target="GkLdqIe8JMVAQNq9l6uQ-4" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="30" y="614" />
 | 
			
		||||
              <mxPoint x="30" y="170" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-20" target="GkLdqIe8JMVAQNq9l6uQ-21" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-20" value="Calculate fund platform fees" style="whiteSpace=wrap;html=1;strokeWidth=2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="340" y="10" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-25" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-21" target="GkLdqIe8JMVAQNq9l6uQ-24" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-21" value="Calculate fund dealing fees" style="whiteSpace=wrap;html=1;strokeWidth=2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="340" y="120" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-26" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-24" target="GkLdqIe8JMVAQNq9l6uQ-27" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="400" y="330" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-24" value="Calculate share platform fees" style="whiteSpace=wrap;html=1;strokeWidth=2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="340" y="230" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-27" target="CeQ2sipdlTVvld8hq7BN-4" edge="1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-27" value="Calculate share dealing fees" style="whiteSpace=wrap;html=1;strokeWidth=2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="340" y="340" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-1" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="GkLdqIe8JMVAQNq9l6uQ-28" target="CeQ2sipdlTVvld8hq7BN-0">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="GkLdqIe8JMVAQNq9l6uQ-28" value="Send results list to output window (display results)" style="whiteSpace=wrap;html=1;strokeWidth=2;" parent="WIyWlLk6GJQsqaUBKTNV-1" vertex="1">
 | 
			
		||||
          <mxGeometry x="520" y="450" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-0" value="Save user details to database" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="520" y="584" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-7" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="CeQ2sipdlTVvld8hq7BN-4" target="CeQ2sipdlTVvld8hq7BN-8">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="400" y="556" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-4" value="Add results for this platform to list" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="340" y="450" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-9" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="CeQ2sipdlTVvld8hq7BN-8" target="GkLdqIe8JMVAQNq9l6uQ-28">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-8" value="Last platform processed?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="350" y="564" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="CeQ2sipdlTVvld8hq7BN-11" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.89;entryY=0.616;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="CeQ2sipdlTVvld8hq7BN-0" target="GkLdqIe8JMVAQNq9l6uQ-4">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="154" y="130" as="targetPoint" />
 | 
			
		||||
            <mxPoint x="154" y="80" as="sourcePoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="580" y="680" />
 | 
			
		||||
              <mxPoint x="220" y="680" />
 | 
			
		||||
              <mxPoint x="220" y="220" />
 | 
			
		||||
              <mxPoint x="183" y="220" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
      </root>
 | 
			
		||||
    </mxGraphModel>
 | 
			
		||||
  </diagram>
 | 
			
		||||
</mxfile>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								flowcharts/main_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flowcharts/main_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 61 KiB  | 
							
								
								
									
										79
									
								
								flowcharts/output_win.drawio
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								flowcharts/output_win.drawio
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
 | 
			
		||||
  <diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
 | 
			
		||||
    <mxGraphModel dx="1426" dy="841" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
 | 
			
		||||
      <root>
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-5" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-0" target="D6qvsPv5HUMZqZDP2d3Z-22">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-0" value="Print results to screen" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.start_1;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="210" y="10" width="100" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-7" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-4" target="D6qvsPv5HUMZqZDP2d3Z-9">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="310" y="170.0000000000001" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-16" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-4" target="D6qvsPv5HUMZqZDP2d3Z-17">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="360" y="175.0000000000001" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-4" value="Save<div>button&nbsp;</div><div>clicked?</div>" style="rhombus;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="210" y="230" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-8" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=0;exitDx=0;exitDy=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-13" target="D6qvsPv5HUMZqZDP2d3Z-22">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="190" y="90" as="sourcePoint" />
 | 
			
		||||
            <mxPoint x="150" y="150" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="90" y="150" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-12" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-9" target="D6qvsPv5HUMZqZDP2d3Z-11">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-21" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-9" target="D6qvsPv5HUMZqZDP2d3Z-13">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-9" value="Output&nbsp;<div>directory&nbsp;</div><div>exists?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="210" y="380" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-14" value="" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-11" target="D6qvsPv5HUMZqZDP2d3Z-13">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-11" value="Create output directory" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="200" y="530" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-13" value="Save results to text file" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="30" y="250" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-19" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-17" target="D6qvsPv5HUMZqZDP2d3Z-18">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-17" value="OK button clicked?" style="rhombus;whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="230" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-18" value="Close results window" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="353" y="390" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-20" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-17" target="D6qvsPv5HUMZqZDP2d3Z-22">
 | 
			
		||||
          <mxGeometry x="-0.6728" relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="414" y="150" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-22" value="Input loop start" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="200" y="120" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="D6qvsPv5HUMZqZDP2d3Z-23" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.502;entryY=0.045;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="D6qvsPv5HUMZqZDP2d3Z-22" target="D6qvsPv5HUMZqZDP2d3Z-4">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
      </root>
 | 
			
		||||
    </mxGraphModel>
 | 
			
		||||
  </diagram>
 | 
			
		||||
</mxfile>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								flowcharts/output_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flowcharts/output_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 35 KiB  | 
							
								
								
									
										242
									
								
								flowcharts/platform_edit_win.drawio
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										242
									
								
								flowcharts/platform_edit_win.drawio
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,242 @@
 | 
			
		||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.15 Chrome/134.0.6998.205 Electron/35.2.1 Safari/537.36" version="26.2.15">
 | 
			
		||||
  <diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
 | 
			
		||||
    <mxGraphModel dx="2066" dy="1219" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1169" pageHeight="1654" math="0" shadow="0">
 | 
			
		||||
      <root>
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-0" value="Retrieve details of platform from DB if applicable" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.start_1;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="532" y="10" width="116" height="80" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-2" value="Any field edited?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="540" y="120" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-3" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-0" target="RMgAkj6IqEivbKdGbQ9N-2">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-58" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-4">
 | 
			
		||||
          <mxGeometry x="-0.3442" y="-10" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="510" y="320" as="targetPoint" />
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-4" value="Any checkbox toggled?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="540" y="270" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-5" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-2" target="RMgAkj6IqEivbKdGbQ9N-4">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-6" value="Save updated platform details to list" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="528" y="900" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-52" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-7" target="RMgAkj6IqEivbKdGbQ9N-51">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-7" value="Add row clicked?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="540" y="430" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-61" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.747;exitY=0.74;exitDx=0;exitDy=0;exitPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-8">
 | 
			
		||||
          <mxGeometry x="-0.847" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="820" y="930" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="670" y="674" />
 | 
			
		||||
              <mxPoint x="670" y="930" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-8" value="Remove row clicked?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="540" y="600" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-13" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-9" target="RMgAkj6IqEivbKdGbQ9N-6">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-9" value="Save button clicked?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="540" y="750" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-10" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-4" target="RMgAkj6IqEivbKdGbQ9N-7">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-11" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-7" target="RMgAkj6IqEivbKdGbQ9N-8">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-12" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-8" target="RMgAkj6IqEivbKdGbQ9N-9">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-19" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-15" target="RMgAkj6IqEivbKdGbQ9N-18">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-15" value="All&nbsp;<div>required fields non zero?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="386" y="120" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-17" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-2" target="RMgAkj6IqEivbKdGbQ9N-15">
 | 
			
		||||
          <mxGeometry x="0.1111" y="-10" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-22" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-18" target="RMgAkj6IqEivbKdGbQ9N-20">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="276" y="320" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="276" y="320" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-18" value="Mark main input not valid" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="216" y="140" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-23" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.37;exitY=0.89;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.25;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-20" target="RMgAkj6IqEivbKdGbQ9N-18">
 | 
			
		||||
          <mxGeometry x="-0.4497" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-24" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-20" target="RMgAkj6IqEivbKdGbQ9N-25">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="436" y="400" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-20" value="Are&nbsp;<div>all ACTIVE optional fields non-zero?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="386" y="270" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-21" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-15" target="RMgAkj6IqEivbKdGbQ9N-20">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-27" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-25" target="RMgAkj6IqEivbKdGbQ9N-26">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-25" value="Each row of<div>fund_plat_fee</div><div>bigger than&nbsp;</div><div>the previous?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;fontSize=11;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="378.5" y="420" width="115" height="120" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-26" value="Mark fund platform fee input not valid" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="216" y="450" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-30" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=1;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-29" target="RMgAkj6IqEivbKdGbQ9N-26">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-29" value="Each&nbsp;<div>fee field in fund_plat fee<div>non zero?</div></div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;fontSize=11;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="386" y="590" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-31" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-25" target="RMgAkj6IqEivbKdGbQ9N-29">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-35" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-32" target="RMgAkj6IqEivbKdGbQ9N-34">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-32" value="Maximum&nbsp;<div>number of rows</div><div>(6) reached?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;fontSize=11;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="386" y="750" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-33" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-29" target="RMgAkj6IqEivbKdGbQ9N-32">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-34" value="Disale add row button" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="376" y="900" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-39" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-37" target="RMgAkj6IqEivbKdGbQ9N-38">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-66" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.75;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-37" target="RMgAkj6IqEivbKdGbQ9N-50">
 | 
			
		||||
          <mxGeometry x="-0.7078" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-37" value="Fund<div>platform fee input marked valid?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;fontSize=11;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="226" y="750" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-43" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-38" target="RMgAkj6IqEivbKdGbQ9N-42">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="276.0000000000002" y="1000" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-38" value="Enable add row button" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="216" y="900" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-40" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-32" target="RMgAkj6IqEivbKdGbQ9N-37">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-46" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-42" target="RMgAkj6IqEivbKdGbQ9N-44">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-47" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.198;entryY=0.298;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-42" target="RMgAkj6IqEivbKdGbQ9N-2">
 | 
			
		||||
          <mxGeometry x="-0.9531" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="536" y="120" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="120" y="1060" />
 | 
			
		||||
              <mxPoint x="120" y="110" />
 | 
			
		||||
              <mxPoint x="560" y="110" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-42" value="Is main input marked valid?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;fontSize=11;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="226" y="1010" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-44" value="Enable save button" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="216" y="1160" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-34" target="RMgAkj6IqEivbKdGbQ9N-42">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="436" y="1060" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-50" value="Disable save button" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="58" y="900" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-54" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-51" target="RMgAkj6IqEivbKdGbQ9N-53">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-51" value="Add new row of widgets to grid layout and list of widgets" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="690" y="450" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-56" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-53" target="RMgAkj6IqEivbKdGbQ9N-55">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-53" value="Expand grid layout to accommodate new row" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="690" y="560" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-57" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-55">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="510" y="170" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="750" y="991" />
 | 
			
		||||
              <mxPoint x="751" y="991" />
 | 
			
		||||
              <mxPoint x="751" y="1220" />
 | 
			
		||||
              <mxPoint x="510" y="1220" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-55" value="Set new rows label to reflect the value of the previous box. Also update max fee label" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="690" y="670" width="120" height="80" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-60" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-9" target="RMgAkj6IqEivbKdGbQ9N-2">
 | 
			
		||||
          <mxGeometry x="-0.9279" relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="880" y="800" />
 | 
			
		||||
              <mxPoint x="880" y="170" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-64" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-62" target="RMgAkj6IqEivbKdGbQ9N-63">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-62" value="Remove each widget in the last row from the grid layout" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="820" y="900" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-65" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="RMgAkj6IqEivbKdGbQ9N-63">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="750" y="1120" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="880" y="1095" />
 | 
			
		||||
              <mxPoint x="881" y="1095" />
 | 
			
		||||
              <mxPoint x="881" y="1120" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="RMgAkj6IqEivbKdGbQ9N-63" value="Contract the grid layout to fit smaller number of rows" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="820" y="1010" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
      </root>
 | 
			
		||||
    </mxGraphModel>
 | 
			
		||||
  </diagram>
 | 
			
		||||
</mxfile>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								flowcharts/platform_edit_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flowcharts/platform_edit_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 136 KiB  | 
							
								
								
									
										176
									
								
								flowcharts/platform_list_win.drawio
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										176
									
								
								flowcharts/platform_list_win.drawio
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,176 @@
 | 
			
		||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
 | 
			
		||||
  <diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
 | 
			
		||||
    <mxGraphModel dx="753" dy="830" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
 | 
			
		||||
      <root>
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-0" value="Get list of platforms from&nbsp;<div><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">DB</span></div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.start_1;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="50" width="100" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-10" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-1" target="uiKHp9CyL3H5hJtIc4hJ-9">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-1" value="Add platform clicked?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="160" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-0" target="uiKHp9CyL3H5hJtIc4hJ-1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-29" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-3">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="510" y="360" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-3" value="Remove<div>platform</div><div>clicked?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="310" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-4" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-1" target="uiKHp9CyL3H5hJtIc4hJ-3">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-16" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-5" target="uiKHp9CyL3H5hJtIc4hJ-15">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-5" value="Edit platform clicked?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="460" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-6" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-3" target="uiKHp9CyL3H5hJtIc4hJ-5">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-27" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-7" target="uiKHp9CyL3H5hJtIc4hJ-26">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-7" value="Save button clicked?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="610" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-8" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-5" target="uiKHp9CyL3H5hJtIc4hJ-7">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-9" value="Show name dialog" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="190" y="130" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-13" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-11" target="uiKHp9CyL3H5hJtIc4hJ-14">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="249.75862068965523" y="370" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-18" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.247;entryY=0.262;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-11" target="uiKHp9CyL3H5hJtIc4hJ-1">
 | 
			
		||||
          <mxGeometry x="-0.572" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="350" y="140" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="160" y="290" />
 | 
			
		||||
              <mxPoint x="160" y="120" />
 | 
			
		||||
              <mxPoint x="388" y="120" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-11" value="Dialog<div>accepted?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="200" y="240" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-12" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-9" target="uiKHp9CyL3H5hJtIc4hJ-11">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-14" target="uiKHp9CyL3H5hJtIc4hJ-15">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-14" value="Add new platform to list" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="190" y="390" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-15" value="Show platform editor" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="190" y="500" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-25" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=1;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-20" target="uiKHp9CyL3H5hJtIc4hJ-24">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-20" value="Platform<div>editor saved?</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="200" y="610" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-21" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-15" target="uiKHp9CyL3H5hJtIc4hJ-20">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-23" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-20" target="uiKHp9CyL3H5hJtIc4hJ-1">
 | 
			
		||||
          <mxGeometry x="-0.9007" relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="250" y="1010" />
 | 
			
		||||
              <mxPoint x="660" y="1010" />
 | 
			
		||||
              <mxPoint x="660" y="210" />
 | 
			
		||||
            </Array>
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-24" value="Delete new platform from list" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="30" y="630" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-26" value="Write updated platform list to DB" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="353" y="760" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-32" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-30" target="uiKHp9CyL3H5hJtIc4hJ-31">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-39" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-30">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="660" y="360" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-30" value="Is platform<div>present in&nbsp;<div>on-disk DB</div></div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="510" y="310" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-31" value="Show warning dialog" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="500" y="460" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-36" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-33" target="uiKHp9CyL3H5hJtIc4hJ-35">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-37" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-33">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="660" y="620" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-33" value="User accepted dialog?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="510" y="570" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-34" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-31" target="uiKHp9CyL3H5hJtIc4hJ-33">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-38" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-35">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="660" y="820" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="560" y="820" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-35" value="Delete platform from DB" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="500" y="720" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-42" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-40" target="uiKHp9CyL3H5hJtIc4hJ-41">
 | 
			
		||||
          <mxGeometry x="-0.0811" y="-10" relative="1" as="geometry">
 | 
			
		||||
            <mxPoint as="offset" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-44" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-40">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="660" y="990" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="413" y="990" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-40" value="Enable&nbsp;<div>checkbox&nbsp;</div><div>toggled</div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="363" y="870" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-43" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-41">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="660" y="920.344827586207" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-41" value="Update platforms enable state in list" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="500" y="890" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="uiKHp9CyL3H5hJtIc4hJ-45" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="uiKHp9CyL3H5hJtIc4hJ-26" target="uiKHp9CyL3H5hJtIc4hJ-40">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
      </root>
 | 
			
		||||
    </mxGraphModel>
 | 
			
		||||
  </diagram>
 | 
			
		||||
</mxfile>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								flowcharts/platform_list_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flowcharts/platform_list_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 85 KiB  | 
							
								
								
									
										83
									
								
								flowcharts/results_win.drawio
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								flowcharts/results_win.drawio
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
<mxfile host="Electron" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/26.2.2 Chrome/134.0.6998.178 Electron/35.1.2 Safari/537.36" version="26.2.2">
 | 
			
		||||
  <diagram id="C5RBs43oDa-KdzZeNtuy" name="Page-1">
 | 
			
		||||
    <mxGraphModel dx="910" dy="1003" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
 | 
			
		||||
      <root>
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="WIyWlLk6GJQsqaUBKTNV-1" parent="WIyWlLk6GJQsqaUBKTNV-0" />
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-0" value="Display/update graph of results" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.start_1;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="220" y="60" width="100" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-4" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0;exitY=0.5;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-1" target="4t4bEUU0-kKJdhU_jB_B-0">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="180" y="90" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="180" y="220" />
 | 
			
		||||
              <mxPoint x="180" y="90" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-6" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-1" target="4t4bEUU0-kKJdhU_jB_B-7">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="270" y="320" as="targetPoint" />
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-1" value="Slider moved?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="220" y="170" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-2" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;exitX=0.5;exitY=1;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-0" target="4t4bEUU0-kKJdhU_jB_B-1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-11" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-7" target="4t4bEUU0-kKJdhU_jB_B-10">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="330" y="370" />
 | 
			
		||||
              <mxPoint x="330" y="230" />
 | 
			
		||||
              <mxPoint x="430" y="230" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-7" value="Output<div>graph button<div>clicked?</div></div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="220" y="320" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-12" value="Yes" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-8" target="4t4bEUU0-kKJdhU_jB_B-10">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="350" y="520" />
 | 
			
		||||
              <mxPoint x="350" y="280" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-8" value="Output&nbsp;<div>CSV button&nbsp;</div><div><span style="background-color: transparent; color: light-dark(rgb(0, 0, 0), rgb(255, 255, 255));">clicked?</span></div>" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="220" y="470" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-9" value="No" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-7" target="4t4bEUU0-kKJdhU_jB_B-8">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-10" value="Show file dialog" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="370" y="250" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-16" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-13" target="4t4bEUU0-kKJdhU_jB_B-15">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-13" value="File writable?" style="strokeWidth=2;html=1;shape=mxgraph.flowchart.decision;whiteSpace=wrap;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="380" y="360" width="100" height="100" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-14" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=0.5;entryY=0;entryDx=0;entryDy=0;entryPerimeter=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-10" target="4t4bEUU0-kKJdhU_jB_B-13">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-17" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;entryX=1;entryY=0.5;entryDx=0;entryDy=0;entryPerimeter=0;exitX=1;exitY=0.5;exitDx=0;exitDy=0;" edge="1" parent="WIyWlLk6GJQsqaUBKTNV-1" source="4t4bEUU0-kKJdhU_jB_B-15" target="4t4bEUU0-kKJdhU_jB_B-1">
 | 
			
		||||
          <mxGeometry relative="1" as="geometry">
 | 
			
		||||
            <mxPoint x="530" y="90" as="targetPoint" />
 | 
			
		||||
            <Array as="points">
 | 
			
		||||
              <mxPoint x="530" y="540" />
 | 
			
		||||
              <mxPoint x="530" y="220" />
 | 
			
		||||
            </Array>
 | 
			
		||||
          </mxGeometry>
 | 
			
		||||
        </mxCell>
 | 
			
		||||
        <mxCell id="4t4bEUU0-kKJdhU_jB_B-15" value="Save results to CSV or PNG file" style="whiteSpace=wrap;html=1;strokeWidth=2;" vertex="1" parent="WIyWlLk6GJQsqaUBKTNV-1">
 | 
			
		||||
          <mxGeometry x="370" y="510" width="120" height="60" as="geometry" />
 | 
			
		||||
        </mxCell>
 | 
			
		||||
      </root>
 | 
			
		||||
    </mxGraphModel>
 | 
			
		||||
  </diagram>
 | 
			
		||||
</mxfile>
 | 
			
		||||
							
								
								
									
										
											BIN
										
									
								
								flowcharts/results_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								flowcharts/results_win.drawio.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 34 KiB  | 
							
								
								
									
										99
									
								
								gui/dialogs/platform_rename.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										99
									
								
								gui/dialogs/platform_rename.ui
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,99 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>PlatformRename</class>
 | 
			
		||||
 <widget class="QDialog" name="PlatformRename">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>300</width>
 | 
			
		||||
    <height>100</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="minimumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>300</width>
 | 
			
		||||
    <height>100</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="maximumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>300</width>
 | 
			
		||||
    <height>100</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>Name Platform</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QLineEdit" name="rename_plat_box">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>7</x>
 | 
			
		||||
     <y>41</y>
 | 
			
		||||
     <width>287</width>
 | 
			
		||||
     <height>22</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>11</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QLabel" name="rename_plat_lab">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>35</x>
 | 
			
		||||
     <y>9</y>
 | 
			
		||||
     <width>241</width>
 | 
			
		||||
     <height>20</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>11</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Enter a new name for the platform</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="rename_plat_ok_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>220</x>
 | 
			
		||||
     <y>70</y>
 | 
			
		||||
     <width>75</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>OK</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <resources/>
 | 
			
		||||
 <connections>
 | 
			
		||||
  <connection>
 | 
			
		||||
   <sender>rename_plat_ok_but</sender>
 | 
			
		||||
   <signal>clicked()</signal>
 | 
			
		||||
   <receiver>PlatformRename</receiver>
 | 
			
		||||
   <slot>accept()</slot>
 | 
			
		||||
   <hints>
 | 
			
		||||
    <hint type="sourcelabel">
 | 
			
		||||
     <x>257</x>
 | 
			
		||||
     <y>71</y>
 | 
			
		||||
    </hint>
 | 
			
		||||
    <hint type="destinationlabel">
 | 
			
		||||
     <x>149</x>
 | 
			
		||||
     <y>44</y>
 | 
			
		||||
    </hint>
 | 
			
		||||
   </hints>
 | 
			
		||||
  </connection>
 | 
			
		||||
 </connections>
 | 
			
		||||
</ui>
 | 
			
		||||
@@ -25,9 +25,6 @@
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>SIPPCompare</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="tabShape">
 | 
			
		||||
   <enum>QTabWidget::TabShape::Rounded</enum>
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QWidget" name="centralwidget">
 | 
			
		||||
   <widget class="QWidget" name="formLayoutWidget">
 | 
			
		||||
    <property name="geometry">
 | 
			
		||||
@@ -35,7 +32,7 @@
 | 
			
		||||
      <x>10</x>
 | 
			
		||||
      <y>0</y>
 | 
			
		||||
      <width>401</width>
 | 
			
		||||
      <height>184</height>
 | 
			
		||||
      <height>188</height>
 | 
			
		||||
     </rect>
 | 
			
		||||
    </property>
 | 
			
		||||
    <layout class="QFormLayout" name="formLayout">
 | 
			
		||||
@@ -70,9 +67,6 @@
 | 
			
		||||
         <pointsize>11</pointsize>
 | 
			
		||||
        </font>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="frame">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="buttonSymbols">
 | 
			
		||||
        <enum>QAbstractSpinBox::ButtonSymbols::NoButtons</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
@@ -145,18 +139,6 @@
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="4" column="1">
 | 
			
		||||
      <widget class="QComboBox" name="fund_trades_combo">
 | 
			
		||||
       <property name="font">
 | 
			
		||||
        <font>
 | 
			
		||||
         <pointsize>11</pointsize>
 | 
			
		||||
        </font>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="editable">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="5" column="0" colspan="2">
 | 
			
		||||
      <widget class="QPushButton" name="calc_but">
 | 
			
		||||
       <property name="enabled">
 | 
			
		||||
@@ -173,14 +155,35 @@
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="3" column="1">
 | 
			
		||||
      <widget class="QComboBox" name="share_trades_combo">
 | 
			
		||||
      <widget class="FastEditQSpinBox" name="share_trades_box">
 | 
			
		||||
       <property name="font">
 | 
			
		||||
        <font>
 | 
			
		||||
         <pointsize>11</pointsize>
 | 
			
		||||
        </font>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="editable">
 | 
			
		||||
        <bool>true</bool>
 | 
			
		||||
       <property name="correctionMode">
 | 
			
		||||
        <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="prefix">
 | 
			
		||||
        <string/>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="maximum">
 | 
			
		||||
        <number>999</number>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
     <item row="4" column="1">
 | 
			
		||||
      <widget class="FastEditQSpinBox" name="fund_trades_box">
 | 
			
		||||
       <property name="font">
 | 
			
		||||
        <font>
 | 
			
		||||
         <pointsize>11</pointsize>
 | 
			
		||||
        </font>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="correctionMode">
 | 
			
		||||
        <enum>QAbstractSpinBox::CorrectionMode::CorrectToNearestValue</enum>
 | 
			
		||||
       </property>
 | 
			
		||||
       <property name="prefix">
 | 
			
		||||
        <string/>
 | 
			
		||||
       </property>
 | 
			
		||||
      </widget>
 | 
			
		||||
     </item>
 | 
			
		||||
@@ -188,9 +191,6 @@
 | 
			
		||||
   </widget>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QStatusBar" name="statusbar">
 | 
			
		||||
   <property name="enabled">
 | 
			
		||||
    <bool>true</bool>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
@@ -220,13 +220,13 @@
 | 
			
		||||
    <property name="title">
 | 
			
		||||
     <string>File</string>
 | 
			
		||||
    </property>
 | 
			
		||||
    <addaction name="actionEdit_Platforms"/>
 | 
			
		||||
    <addaction name="actionList_Platforms"/>
 | 
			
		||||
   </widget>
 | 
			
		||||
   <addaction name="menuPlatforms"/>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <action name="actionEdit_Platforms">
 | 
			
		||||
  <action name="actionList_Platforms">
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Edit Platforms</string>
 | 
			
		||||
    <string>Platform List</string>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
@@ -235,11 +235,18 @@
 | 
			
		||||
   </property>
 | 
			
		||||
  </action>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <customwidgets>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>FastEditQSpinBox</class>
 | 
			
		||||
   <extends>QSpinBox</extends>
 | 
			
		||||
   <header>widgets/fastedit_spinbox</header>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
 </customwidgets>
 | 
			
		||||
 <tabstops>
 | 
			
		||||
  <tabstop>value_input</tabstop>
 | 
			
		||||
  <tabstop>mix_slider</tabstop>
 | 
			
		||||
  <tabstop>share_trades_combo</tabstop>
 | 
			
		||||
  <tabstop>fund_trades_combo</tabstop>
 | 
			
		||||
  <tabstop>share_trades_box</tabstop>
 | 
			
		||||
  <tabstop>fund_trades_box</tabstop>
 | 
			
		||||
  <tabstop>calc_but</tabstop>
 | 
			
		||||
 </tabstops>
 | 
			
		||||
 <resources/>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +1,108 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>ResultsWindow</class>
 | 
			
		||||
 <widget class="QWidget" name="ResultsWindow">
 | 
			
		||||
 <class>OutputWindow</class>
 | 
			
		||||
 <widget class="QWidget" name="OutputWindow">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>400</width>
 | 
			
		||||
    <height>300</height>
 | 
			
		||||
    <width>1330</width>
 | 
			
		||||
    <height>685</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="minimumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>400</width>
 | 
			
		||||
    <height>300</height>
 | 
			
		||||
    <width>1330</width>
 | 
			
		||||
    <height>685</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="maximumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>400</width>
 | 
			
		||||
    <height>300</height>
 | 
			
		||||
    <width>1330</width>
 | 
			
		||||
    <height>685</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>Results</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QTextEdit" name="output">
 | 
			
		||||
  <widget class="MplWidget" name="graphWidget" native="true">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>-10</x>
 | 
			
		||||
     <y>-10</y>
 | 
			
		||||
     <width>1350</width>
 | 
			
		||||
     <height>650</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="save_graph_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>1231</x>
 | 
			
		||||
     <y>642</y>
 | 
			
		||||
     <width>91</width>
 | 
			
		||||
     <height>31</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Save graph</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QSlider" name="time_slider">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>370</x>
 | 
			
		||||
     <y>635</y>
 | 
			
		||||
     <width>581</width>
 | 
			
		||||
     <height>41</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="minimum">
 | 
			
		||||
    <number>1</number>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="maximum">
 | 
			
		||||
    <number>20</number>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="orientation">
 | 
			
		||||
    <enum>Qt::Orientation::Horizontal</enum>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="tickPosition">
 | 
			
		||||
    <enum>QSlider::TickPosition::TicksAbove</enum>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="tickInterval">
 | 
			
		||||
    <number>1</number>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="save_csv_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>1134</x>
 | 
			
		||||
     <y>642</y>
 | 
			
		||||
     <width>91</width>
 | 
			
		||||
     <height>31</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Save CSV</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QLabel" name="time_lab">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>10</x>
 | 
			
		||||
     <y>10</y>
 | 
			
		||||
     <width>380</width>
 | 
			
		||||
     <height>250</height>
 | 
			
		||||
     <y>640</y>
 | 
			
		||||
     <width>341</width>
 | 
			
		||||
     <height>31</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
@@ -39,66 +110,24 @@
 | 
			
		||||
     <pointsize>11</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="res_ok_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>320</x>
 | 
			
		||||
     <y>270</y>
 | 
			
		||||
     <width>75</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>OK</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="res_save_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>240</x>
 | 
			
		||||
     <y>270</y>
 | 
			
		||||
     <width>75</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Save</string>
 | 
			
		||||
    <string>Fees over 1 year(s) (assuming no change in value)</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <customwidgets>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>MplWidget</class>
 | 
			
		||||
   <extends>QWidget</extends>
 | 
			
		||||
   <header>widgets/mpl_widget</header>
 | 
			
		||||
   <container>1</container>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
 </customwidgets>
 | 
			
		||||
 <tabstops>
 | 
			
		||||
  <tabstop>output</tabstop>
 | 
			
		||||
  <tabstop>res_save_but</tabstop>
 | 
			
		||||
  <tabstop>res_ok_but</tabstop>
 | 
			
		||||
  <tabstop>time_slider</tabstop>
 | 
			
		||||
  <tabstop>save_csv_but</tabstop>
 | 
			
		||||
  <tabstop>save_graph_but</tabstop>
 | 
			
		||||
 </tabstops>
 | 
			
		||||
 <resources/>
 | 
			
		||||
 <connections>
 | 
			
		||||
  <connection>
 | 
			
		||||
   <sender>res_ok_but</sender>
 | 
			
		||||
   <signal>clicked(bool)</signal>
 | 
			
		||||
   <receiver>ResultsWindow</receiver>
 | 
			
		||||
   <slot>close()</slot>
 | 
			
		||||
   <hints>
 | 
			
		||||
    <hint type="sourcelabel">
 | 
			
		||||
     <x>357</x>
 | 
			
		||||
     <y>281</y>
 | 
			
		||||
    </hint>
 | 
			
		||||
    <hint type="destinationlabel">
 | 
			
		||||
     <x>199</x>
 | 
			
		||||
     <y>149</y>
 | 
			
		||||
    </hint>
 | 
			
		||||
   </hints>
 | 
			
		||||
  </connection>
 | 
			
		||||
 </connections>
 | 
			
		||||
 <connections/>
 | 
			
		||||
</ui>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,19 +7,31 @@
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>630</width>
 | 
			
		||||
    <height>567</height>
 | 
			
		||||
    <height>580</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="minimumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>630</width>
 | 
			
		||||
    <height>580</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="maximumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>630</width>
 | 
			
		||||
    <height>580</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>Platform Editor</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QWidget" name="gridLayoutWidget">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>10</x>
 | 
			
		||||
     <x>13</x>
 | 
			
		||||
     <y>20</y>
 | 
			
		||||
     <width>611</width>
 | 
			
		||||
     <height>241</height>
 | 
			
		||||
     <width>616</width>
 | 
			
		||||
     <height>242</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <layout class="QGridLayout" name="gridLayout">
 | 
			
		||||
@@ -39,10 +51,13 @@
 | 
			
		||||
     <number>10</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="verticalSpacing">
 | 
			
		||||
     <number>5</number>
 | 
			
		||||
     <number>6</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <item row="0" column="3">
 | 
			
		||||
     <widget class="QCheckBox" name="plat_name_check">
 | 
			
		||||
      <property name="text">
 | 
			
		||||
       <string> </string>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="checked">
 | 
			
		||||
       <bool>true</bool>
 | 
			
		||||
      </property>
 | 
			
		||||
@@ -215,7 +230,7 @@
 | 
			
		||||
       </font>
 | 
			
		||||
      </property>
 | 
			
		||||
      <property name="text">
 | 
			
		||||
       <string>Fund dealing fee*</string>
 | 
			
		||||
       <string>Fund dealing fee</string>
 | 
			
		||||
      </property>
 | 
			
		||||
     </widget>
 | 
			
		||||
    </item>
 | 
			
		||||
@@ -321,7 +336,7 @@
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>482</x>
 | 
			
		||||
     <y>534</y>
 | 
			
		||||
     <y>545</y>
 | 
			
		||||
     <width>141</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
@@ -339,7 +354,7 @@
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>8</x>
 | 
			
		||||
     <y>262</y>
 | 
			
		||||
     <y>549</y>
 | 
			
		||||
     <width>191</width>
 | 
			
		||||
     <height>21</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
@@ -351,7 +366,7 @@
 | 
			
		||||
  <widget class="QLabel" name="active_lab">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>577</x>
 | 
			
		||||
     <x>572</x>
 | 
			
		||||
     <y>10</y>
 | 
			
		||||
     <width>61</width>
 | 
			
		||||
     <height>16</height>
 | 
			
		||||
@@ -367,16 +382,28 @@
 | 
			
		||||
  <widget class="QWidget" name="gridLayoutWidget_2">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>11</x>
 | 
			
		||||
     <y>309</y>
 | 
			
		||||
     <width>611</width>
 | 
			
		||||
     <height>31</height>
 | 
			
		||||
     <x>19</x>
 | 
			
		||||
     <y>307</y>
 | 
			
		||||
     <width>591</width>
 | 
			
		||||
     <height>40</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <layout class="QGridLayout" name="gridLayout_2">
 | 
			
		||||
    <property name="verticalSpacing">
 | 
			
		||||
    <property name="leftMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="topMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="rightMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="bottomMargin">
 | 
			
		||||
     <number>0</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <property name="spacing">
 | 
			
		||||
     <number>6</number>
 | 
			
		||||
    </property>
 | 
			
		||||
    <item row="0" column="3">
 | 
			
		||||
     <widget class="FastEditQDoubleSpinBox" name="first_tier_fee_box">
 | 
			
		||||
      <property name="font">
 | 
			
		||||
@@ -452,7 +479,7 @@
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>532</x>
 | 
			
		||||
     <y>481</y>
 | 
			
		||||
     <y>487</y>
 | 
			
		||||
     <width>91</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
@@ -473,7 +500,7 @@
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>440</x>
 | 
			
		||||
     <y>481</y>
 | 
			
		||||
     <y>487</y>
 | 
			
		||||
     <width>91</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
@@ -490,8 +517,8 @@
 | 
			
		||||
  <widget class="QLabel" name="val_above_lab">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>6</x>
 | 
			
		||||
     <y>479</y>
 | 
			
		||||
     <x>10</x>
 | 
			
		||||
     <y>486</y>
 | 
			
		||||
     <width>421</width>
 | 
			
		||||
     <height>21</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
@@ -505,16 +532,34 @@
 | 
			
		||||
    <string>on the value above £ there is no charge</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QLabel" name="fund_plat_fee_lab">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>10</x>
 | 
			
		||||
     <y>285</y>
 | 
			
		||||
     <width>151</width>
 | 
			
		||||
     <height>16</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>11</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Fund platform fee*</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <customwidgets>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>FastEditQDoubleSpinBox</class>
 | 
			
		||||
   <extends>QDoubleSpinBox</extends>
 | 
			
		||||
   <class>FastEditQSpinBox</class>
 | 
			
		||||
   <extends>QSpinBox</extends>
 | 
			
		||||
   <header>widgets/fastedit_spinbox</header>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
  <customwidget>
 | 
			
		||||
   <class>FastEditQSpinBox</class>
 | 
			
		||||
   <extends>QSpinBox</extends>
 | 
			
		||||
   <class>FastEditQDoubleSpinBox</class>
 | 
			
		||||
   <extends>QDoubleSpinBox</extends>
 | 
			
		||||
   <header>widgets/fastedit_spinbox</header>
 | 
			
		||||
  </customwidget>
 | 
			
		||||
 </customwidgets>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										170
									
								
								gui/platform_list.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								gui/platform_list.ui
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,170 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<ui version="4.0">
 | 
			
		||||
 <class>PlatformList</class>
 | 
			
		||||
 <widget class="QWidget" name="PlatformList">
 | 
			
		||||
  <property name="geometry">
 | 
			
		||||
   <rect>
 | 
			
		||||
    <x>0</x>
 | 
			
		||||
    <y>0</y>
 | 
			
		||||
    <width>263</width>
 | 
			
		||||
    <height>473</height>
 | 
			
		||||
   </rect>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="minimumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>263</width>
 | 
			
		||||
    <height>473</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="maximumSize">
 | 
			
		||||
   <size>
 | 
			
		||||
    <width>263</width>
 | 
			
		||||
    <height>473</height>
 | 
			
		||||
   </size>
 | 
			
		||||
  </property>
 | 
			
		||||
  <property name="windowTitle">
 | 
			
		||||
   <string>Platform List</string>
 | 
			
		||||
  </property>
 | 
			
		||||
  <widget class="QListWidget" name="platListWidget">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>10</x>
 | 
			
		||||
     <y>34</y>
 | 
			
		||||
     <width>243</width>
 | 
			
		||||
     <height>371</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>11</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="add_plat_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>11</x>
 | 
			
		||||
     <y>413</y>
 | 
			
		||||
     <width>121</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Add platform</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="del_plat_but">
 | 
			
		||||
   <property name="enabled">
 | 
			
		||||
    <bool>false</bool>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>135</x>
 | 
			
		||||
     <y>413</y>
 | 
			
		||||
     <width>121</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Remove platform</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="plist_save_but">
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>136</x>
 | 
			
		||||
     <y>442</y>
 | 
			
		||||
     <width>121</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Save</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QPushButton" name="edit_plat_but">
 | 
			
		||||
   <property name="enabled">
 | 
			
		||||
    <bool>false</bool>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>11</x>
 | 
			
		||||
     <y>442</y>
 | 
			
		||||
     <width>121</width>
 | 
			
		||||
     <height>24</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>10</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Edit platform</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
  <widget class="QCheckBox" name="plat_enabled_check">
 | 
			
		||||
   <property name="enabled">
 | 
			
		||||
    <bool>false</bool>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="geometry">
 | 
			
		||||
    <rect>
 | 
			
		||||
     <x>12</x>
 | 
			
		||||
     <y>8</y>
 | 
			
		||||
     <width>231</width>
 | 
			
		||||
     <height>20</height>
 | 
			
		||||
    </rect>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="font">
 | 
			
		||||
    <font>
 | 
			
		||||
     <pointsize>11</pointsize>
 | 
			
		||||
    </font>
 | 
			
		||||
   </property>
 | 
			
		||||
   <property name="text">
 | 
			
		||||
    <string>Platform enabled?</string>
 | 
			
		||||
   </property>
 | 
			
		||||
  </widget>
 | 
			
		||||
 </widget>
 | 
			
		||||
 <tabstops>
 | 
			
		||||
  <tabstop>platListWidget</tabstop>
 | 
			
		||||
  <tabstop>add_plat_but</tabstop>
 | 
			
		||||
  <tabstop>del_plat_but</tabstop>
 | 
			
		||||
  <tabstop>edit_plat_but</tabstop>
 | 
			
		||||
  <tabstop>plist_save_but</tabstop>
 | 
			
		||||
  <tabstop>plat_enabled_check</tabstop>
 | 
			
		||||
 </tabstops>
 | 
			
		||||
 <resources/>
 | 
			
		||||
 <connections>
 | 
			
		||||
  <connection>
 | 
			
		||||
   <sender>plist_save_but</sender>
 | 
			
		||||
   <signal>clicked()</signal>
 | 
			
		||||
   <receiver>PlatformList</receiver>
 | 
			
		||||
   <slot>close()</slot>
 | 
			
		||||
   <hints>
 | 
			
		||||
    <hint type="sourcelabel">
 | 
			
		||||
     <x>196</x>
 | 
			
		||||
     <y>453</y>
 | 
			
		||||
    </hint>
 | 
			
		||||
    <hint type="destinationlabel">
 | 
			
		||||
     <x>131</x>
 | 
			
		||||
     <y>236</y>
 | 
			
		||||
    </hint>
 | 
			
		||||
   </hints>
 | 
			
		||||
  </connection>
 | 
			
		||||
 </connections>
 | 
			
		||||
</ui>
 | 
			
		||||
							
								
								
									
										16
									
								
								src/data_struct.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/data_struct.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
class Platform:
 | 
			
		||||
    def __init__(self, plat_id, fund_plat_fee, plat_name, enabled, fund_deal_fee,
 | 
			
		||||
                 share_plat_fee, share_plat_max_fee, share_deal_fee,
 | 
			
		||||
                 share_deal_reduce_trades, share_deal_reduce_amount,
 | 
			
		||||
        ):
 | 
			
		||||
 | 
			
		||||
        self.plat_id                    = plat_id
 | 
			
		||||
        self.fund_plat_fee              = fund_plat_fee
 | 
			
		||||
        self.plat_name                  = plat_name
 | 
			
		||||
        self.enabled                    = enabled
 | 
			
		||||
        self.fund_deal_fee              = fund_deal_fee
 | 
			
		||||
        self.share_plat_fee             = share_plat_fee
 | 
			
		||||
        self.share_plat_max_fee         = share_plat_max_fee
 | 
			
		||||
        self.share_deal_fee             = share_deal_fee
 | 
			
		||||
        self.share_deal_reduce_trades   = share_deal_reduce_trades
 | 
			
		||||
        self.share_deal_reduce_amount   = share_deal_reduce_amount
 | 
			
		||||
							
								
								
									
										262
									
								
								src/db_handler.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										262
									
								
								src/db_handler.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,262 @@
 | 
			
		||||
import os
 | 
			
		||||
import sqlite3
 | 
			
		||||
 | 
			
		||||
import resource_finder
 | 
			
		||||
from data_struct import Platform
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DBHandler:
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        # Function to create all necessary database tables if the DB file doesn't exist
 | 
			
		||||
        def create_tables():
 | 
			
		||||
            self.cur.execute("""
 | 
			
		||||
                CREATE TABLE "tblPlatforms" (
 | 
			
		||||
                    "PlatformID"	INTEGER NOT NULL UNIQUE,
 | 
			
		||||
                    "PlatformName"	TEXT,
 | 
			
		||||
                    "IsEnabled"	INTEGER NOT NULL,
 | 
			
		||||
                    PRIMARY KEY("PlatformID")
 | 
			
		||||
                )
 | 
			
		||||
            """)
 | 
			
		||||
 | 
			
		||||
            self.cur.execute("""
 | 
			
		||||
                CREATE TABLE "tblFlatPlatFees" (
 | 
			
		||||
                    "PlatformID"	INTEGER NOT NULL UNIQUE,
 | 
			
		||||
                    "SharePlatFee"	REAL NOT NULL,
 | 
			
		||||
                    "SharePlatMaxFee"	REAL,
 | 
			
		||||
                    PRIMARY KEY("PlatformID"),
 | 
			
		||||
                    FOREIGN KEY("PlatformID") REFERENCES "tblPlatforms"("PlatformID")
 | 
			
		||||
                )
 | 
			
		||||
            """)
 | 
			
		||||
 | 
			
		||||
            self.cur.execute("""
 | 
			
		||||
                CREATE TABLE "tblFlatDealFees" (
 | 
			
		||||
                    "PlatformID"	INTEGER NOT NULL UNIQUE,
 | 
			
		||||
                    "FundDealFee"	REAL,
 | 
			
		||||
                    "ShareDealFee"	REAL NOT NULL,
 | 
			
		||||
                    "ShareDealReduceTrades"	REAL,
 | 
			
		||||
                    "ShareDealReduceAmount"	REAL,
 | 
			
		||||
                    PRIMARY KEY("PlatformID"),
 | 
			
		||||
                    FOREIGN KEY("PlatformID") REFERENCES "tblPlatforms"("PlatformID")
 | 
			
		||||
                )
 | 
			
		||||
            """)
 | 
			
		||||
 | 
			
		||||
            self.cur.execute("""
 | 
			
		||||
                CREATE TABLE "tblFundPlatFee" (
 | 
			
		||||
                    "PlatformID"	INTEGER NOT NULL,
 | 
			
		||||
                    "Band"	REAL NOT NULL,
 | 
			
		||||
                    "Fee"	REAL NOT NULL,
 | 
			
		||||
                    PRIMARY KEY("PlatformID","Band","Fee"),
 | 
			
		||||
                    FOREIGN KEY("PlatformID") REFERENCES "tblPlatforms"("PlatformID")
 | 
			
		||||
                )
 | 
			
		||||
            """)
 | 
			
		||||
 | 
			
		||||
            self.cur.execute("""
 | 
			
		||||
                CREATE TABLE "tblUserDetails" (
 | 
			
		||||
                    "UserID"	INTEGER NOT NULL UNIQUE,
 | 
			
		||||
                    "PensionValue"	REAL NOT NULL,
 | 
			
		||||
                    "SliderValue"	INTEGER NOT NULL,
 | 
			
		||||
                    "ShareTrades"	INTEGER NOT NULL,
 | 
			
		||||
                    "FundTrades"	INTEGER NOT NULL,
 | 
			
		||||
                    PRIMARY KEY("UserID")
 | 
			
		||||
                )
 | 
			
		||||
            """)
 | 
			
		||||
 | 
			
		||||
        if not os.path.exists(resource_finder.get_res_path("SIPPCompare.db")):
 | 
			
		||||
            db_exists = False
 | 
			
		||||
        else:
 | 
			
		||||
            db_exists = True
 | 
			
		||||
 | 
			
		||||
        self.conn = sqlite3.connect(resource_finder.get_res_path("SIPPCompare.db"))
 | 
			
		||||
        self.cur = self.conn.cursor()
 | 
			
		||||
        if not db_exists:
 | 
			
		||||
            create_tables()
 | 
			
		||||
 | 
			
		||||
    # Retrieve the list of platform names for list population
 | 
			
		||||
    def retrieve_plat_list(self) -> list:
 | 
			
		||||
        res = self.cur.execute("SELECT PlatformName FROM tblPlatforms").fetchall()
 | 
			
		||||
        plat_name_list = []
 | 
			
		||||
        for platform in res:
 | 
			
		||||
            plat_name_list.append(platform[0])
 | 
			
		||||
 | 
			
		||||
        return plat_name_list
 | 
			
		||||
 | 
			
		||||
    # Write updated platform data to DB when changes are saved
 | 
			
		||||
    def write_platforms(self, plat_list: list[Platform]):
 | 
			
		||||
        for i in range(len(plat_list)):
 | 
			
		||||
            platforms_data = [
 | 
			
		||||
                plat_list[i].plat_id,
 | 
			
		||||
                plat_list[i].plat_name,
 | 
			
		||||
                plat_list[i].enabled
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
            flat_plat_fees_data = [
 | 
			
		||||
                plat_list[i].plat_id,
 | 
			
		||||
                plat_list[i].share_plat_fee,
 | 
			
		||||
                plat_list[i].share_plat_max_fee
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
            flat_deal_fees_data = [
 | 
			
		||||
                plat_list[i].plat_id,
 | 
			
		||||
                plat_list[i].fund_deal_fee,
 | 
			
		||||
                plat_list[i].share_deal_fee,
 | 
			
		||||
                plat_list[i].share_deal_reduce_trades,
 | 
			
		||||
                plat_list[i].share_deal_reduce_amount
 | 
			
		||||
            ]
 | 
			
		||||
 | 
			
		||||
            res = self.cur.execute(f"""
 | 
			
		||||
            SELECT EXISTS(
 | 
			
		||||
                SELECT PlatformID FROM tblPlatforms
 | 
			
		||||
                WHERE PlatformID = {i}
 | 
			
		||||
            )""").fetchall()
 | 
			
		||||
 | 
			
		||||
            if res[0][0] == 1:
 | 
			
		||||
                self.cur.execute(f"""
 | 
			
		||||
                    UPDATE tblPlatforms SET 
 | 
			
		||||
                    PlatformID = ?,
 | 
			
		||||
                    PlatformName = ?,
 | 
			
		||||
                    IsEnabled = ?
 | 
			
		||||
                    WHERE PlatformID = {i}
 | 
			
		||||
                """, platforms_data)
 | 
			
		||||
 | 
			
		||||
                self.cur.execute(f"""
 | 
			
		||||
                    UPDATE tblFlatPlatFees SET 
 | 
			
		||||
                    PlatformID = ?,
 | 
			
		||||
                    SharePlatFee = ?,
 | 
			
		||||
                    SharePlatMaxFee = ?
 | 
			
		||||
                    WHERE PlatformID = {i}
 | 
			
		||||
                """, flat_plat_fees_data)
 | 
			
		||||
 | 
			
		||||
                self.cur.execute(f"""
 | 
			
		||||
                    UPDATE tblFlatDealFees SET
 | 
			
		||||
                    PlatformID = ?,
 | 
			
		||||
                    FundDealFee = ?,
 | 
			
		||||
                    ShareDealFee = ?,
 | 
			
		||||
                    ShareDealReduceTrades = ?,
 | 
			
		||||
                    ShareDealReduceAmount = ?
 | 
			
		||||
                    WHERE PlatformID = {i}
 | 
			
		||||
                """, flat_deal_fees_data)
 | 
			
		||||
 | 
			
		||||
                self.cur.execute(f"DELETE FROM tblFundPlatFee WHERE PlatformID = {i}")
 | 
			
		||||
            else:
 | 
			
		||||
                self.cur.execute("INSERT INTO tblPlatforms VALUES (?, ?, ?)", platforms_data)
 | 
			
		||||
                self.cur.execute("INSERT INTO tblFlatPlatFees VALUES (?, ?, ?)", flat_plat_fees_data)
 | 
			
		||||
                self.cur.execute("INSERT INTO tblFlatDealFees VALUES (?, ?, ?, ?, ?)", flat_deal_fees_data)
 | 
			
		||||
 | 
			
		||||
            exec_str = f"INSERT INTO tblFundPlatFee VALUES\n"
 | 
			
		||||
            for x in range(len(plat_list[i].fund_plat_fee[0])):
 | 
			
		||||
                band = plat_list[i].fund_plat_fee[0][x]
 | 
			
		||||
                fee = plat_list[i].fund_plat_fee[1][x]
 | 
			
		||||
                exec_str += f"({i}, {band}, {fee}),\n"
 | 
			
		||||
            exec_str = exec_str[:-2]
 | 
			
		||||
            self.cur.execute(exec_str)
 | 
			
		||||
 | 
			
		||||
            self.conn.commit()
 | 
			
		||||
 | 
			
		||||
    # Retrieve all info about all platforms in DB and initialise Platform objects
 | 
			
		||||
    def retrieve_platforms(self) -> list[Platform]:
 | 
			
		||||
        # Retrieve all one-to-one relations
 | 
			
		||||
        platforms_res = self.cur.execute("""
 | 
			
		||||
            SELECT
 | 
			
		||||
            -- tblPlatforms
 | 
			
		||||
            tblPlatforms.PlatformID, PlatformName, IsEnabled,
 | 
			
		||||
            -- tblFlatPlatFees
 | 
			
		||||
            SharePlatFee, SharePlatMaxFee,
 | 
			
		||||
            -- tblFlatDealFees
 | 
			
		||||
            FundDealFee,
 | 
			
		||||
            ShareDealFee,
 | 
			
		||||
            ShareDealReduceTrades,
 | 
			
		||||
            ShareDealReduceAmount
 | 
			
		||||
            FROM tblPlatforms
 | 
			
		||||
            INNER JOIN tblFlatPlatFees ON
 | 
			
		||||
            tblPlatforms.PlatformID = tblFlatPlatFees.PlatformID
 | 
			
		||||
            INNER JOIN tblFlatDealFees ON
 | 
			
		||||
            tblPlatforms.PlatformID = tblFlatDealFees.PlatformID
 | 
			
		||||
        """).fetchall()
 | 
			
		||||
 | 
			
		||||
        platforms = []
 | 
			
		||||
        for platform in platforms_res:
 | 
			
		||||
            # plat_id, plat_name, enabled, share_plat_fee, share_plat_max_fee, fund_deal_fee,
 | 
			
		||||
            # share_deal_fee, share_deal_reduce_trades, share_deal_reduce_amount
 | 
			
		||||
            this_platform = [platform[0], platform[1], platform[2], platform[3], platform[4],
 | 
			
		||||
                             platform[5], platform[6], platform[7], platform[8]]
 | 
			
		||||
            platforms.append(this_platform)
 | 
			
		||||
 | 
			
		||||
        # Insert 2D array into each platform data in preparation for fund_plat_fee retrival
 | 
			
		||||
        for platform in platforms:
 | 
			
		||||
            platform.insert(1, [[], []])
 | 
			
		||||
 | 
			
		||||
        # Get all records from tblFundPlatFee, add them to the platforms list based on ID
 | 
			
		||||
        # WARNING: This code is dependent on PlatformID being sequential from 0 in DB records
 | 
			
		||||
        fund_plat_fee_res = self.cur.execute("SELECT * FROM tblFundPlatFee ORDER BY PlatformID ASC").fetchall()
 | 
			
		||||
        for i in range(len(fund_plat_fee_res)):
 | 
			
		||||
            plat_id = fund_plat_fee_res[i][0]
 | 
			
		||||
            platforms[plat_id][1][0].append(fund_plat_fee_res[i][1])
 | 
			
		||||
            platforms[plat_id][1][1].append(fund_plat_fee_res[i][2])
 | 
			
		||||
 | 
			
		||||
        platform_obj_list: list[Platform] = []
 | 
			
		||||
        for platform in platforms:
 | 
			
		||||
            platform_obj_list.append(Platform(
 | 
			
		||||
                platform[0], platform[1], platform[2], platform[3],
 | 
			
		||||
                platform[6], platform[4], platform[5], platform[7],
 | 
			
		||||
                platform[8], platform[9]
 | 
			
		||||
            ))
 | 
			
		||||
 | 
			
		||||
        return platform_obj_list
 | 
			
		||||
 | 
			
		||||
    # This function writes the details the user entered this session to the DB
 | 
			
		||||
    def write_user_details(self, pension_val: float, slider_val: int, share_trades: int, fund_trades: int):
 | 
			
		||||
        # Hardcode UserID as 0
 | 
			
		||||
        user_details_data = (0, pension_val, slider_val, share_trades, fund_trades)
 | 
			
		||||
 | 
			
		||||
        # Check if there is already a record in tblUserDetails
 | 
			
		||||
        res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone()
 | 
			
		||||
        if res[0] == 0:
 | 
			
		||||
            # If there isn't then insert a new record
 | 
			
		||||
            self.cur.execute("INSERT INTO tblUserDetails VALUES (?, ?, ?, ?, ?)", user_details_data)
 | 
			
		||||
        else:
 | 
			
		||||
            # If there is then update the existing record (only ever one record as of now)
 | 
			
		||||
            self.cur.execute("""
 | 
			
		||||
                UPDATE tblUserDetails SET 
 | 
			
		||||
                UserID = ?,
 | 
			
		||||
                PensionValue = ?,
 | 
			
		||||
                SliderValue = ?,
 | 
			
		||||
                ShareTrades = ?,
 | 
			
		||||
                FundTrades = ?
 | 
			
		||||
            """, user_details_data)
 | 
			
		||||
        self.conn.commit()
 | 
			
		||||
 | 
			
		||||
    # Function to retrieve details entered by the user in prev session from DB
 | 
			
		||||
    def retrieve_user_details(self) -> dict:
 | 
			
		||||
        res = self.cur.execute("SELECT EXISTS(SELECT 1 FROM tblUserDetails)").fetchone()
 | 
			
		||||
        if res[0] == 0:
 | 
			
		||||
            return {"NO_RECORD": None}
 | 
			
		||||
 | 
			
		||||
        res = self.cur.execute("SELECT * FROM tblUserDetails")
 | 
			
		||||
        res_tuple: tuple = res.fetchone()
 | 
			
		||||
        user_details_dict: dict[str, float | int] = {
 | 
			
		||||
            "user_id":      res_tuple[0],
 | 
			
		||||
            "pension_val":  res_tuple[1],
 | 
			
		||||
            "slider_val":   res_tuple[2],
 | 
			
		||||
            "share_trades": res_tuple[3],
 | 
			
		||||
            "fund_trades":  res_tuple[4]
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return user_details_dict
 | 
			
		||||
 | 
			
		||||
    # Remove a platform from the DB - update the IDs to keep them sequential
 | 
			
		||||
    def remove_platform(self, index: int):
 | 
			
		||||
        tbl_list = ["tblPlatforms", "tblFlatPlatFees", "tblFlatDealFees", "tblFundPlatFee"]
 | 
			
		||||
        for tbl in tbl_list:
 | 
			
		||||
            self.cur.execute(f"DELETE FROM {tbl} WHERE PlatformID = {index}")
 | 
			
		||||
 | 
			
		||||
        res = self.cur.execute("SELECT PlatformID from tblPlatforms").fetchall()
 | 
			
		||||
        n = len(res)
 | 
			
		||||
        for i in range(n):
 | 
			
		||||
            for tbl in tbl_list:
 | 
			
		||||
                self.cur.execute(f"""
 | 
			
		||||
                    UPDATE {tbl} 
 | 
			
		||||
                    SET PlatformID = {index + i} 
 | 
			
		||||
                    WHERE PlatformID = {index + 1 + i}
 | 
			
		||||
                """)
 | 
			
		||||
 | 
			
		||||
        self.conn.commit()
 | 
			
		||||
							
								
								
									
										30
									
								
								src/main.py
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								src/main.py
									
									
									
									
									
								
							@@ -1,20 +1,24 @@
 | 
			
		||||
from PyQt6.QtWidgets import QApplication
 | 
			
		||||
 | 
			
		||||
## Nuitka compilation options (Windows only)
 | 
			
		||||
# nuitka-project: --mode=onefile
 | 
			
		||||
# nuitka-project: --enable-plugin=pyqt6
 | 
			
		||||
# nuitka-project: --include-module=widgets.mpl_widget
 | 
			
		||||
# nuitka-project: --include-data-files=icon2.ico=icon2.ico
 | 
			
		||||
# nuitka-project: --include-data-dir=gui=gui
 | 
			
		||||
# nuitka-project: --windows-console-mode=disable
 | 
			
		||||
# nuitka-project: --windows-icon-from-ico=icon2.ico
 | 
			
		||||
# nuitka-project: --product-name=SIPPCompare
 | 
			
		||||
# nuitka-project: --file-description=SIPPCompare
 | 
			
		||||
# nuitka-project: --product-version=1.1
 | 
			
		||||
# nuitka-project: --output-dir=build
 | 
			
		||||
# nuitka-project: --output-filename=SIPPCompare
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
import platform_edit
 | 
			
		||||
from PyQt6.QtWidgets import QApplication
 | 
			
		||||
 | 
			
		||||
from main_window import SIPPCompare
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
app = QApplication(sys.argv)
 | 
			
		||||
# Show platform edit window first, before main win
 | 
			
		||||
# When debugging, can be useful to autofill values to save time
 | 
			
		||||
if len(sys.argv) > 1:
 | 
			
		||||
    if sys.argv[1] == "--DEBUG_AUTOFILL":
 | 
			
		||||
        window = platform_edit.PlatformEdit(True)
 | 
			
		||||
    else:
 | 
			
		||||
        window = platform_edit.PlatformEdit(False)
 | 
			
		||||
else:
 | 
			
		||||
    window = platform_edit.PlatformEdit(False)
 | 
			
		||||
 | 
			
		||||
window = SIPPCompare()
 | 
			
		||||
window.show()
 | 
			
		||||
app.exec()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,54 +1,55 @@
 | 
			
		||||
from PyQt6.QtGui import QIntValidator, QIcon
 | 
			
		||||
from PyQt6.QtWidgets import QMainWindow, QWidget
 | 
			
		||||
from PyQt6 import uic
 | 
			
		||||
from PyQt6.QtCore import QTimer
 | 
			
		||||
from PyQt6.QtGui import QIntValidator, QIcon
 | 
			
		||||
from PyQt6.QtWidgets import QMainWindow, QApplication
 | 
			
		||||
 | 
			
		||||
import output_window
 | 
			
		||||
import resource_finder
 | 
			
		||||
from db_handler import DBHandler
 | 
			
		||||
from platform_list import PlatformList
 | 
			
		||||
from output_window import OutputWindow
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SIPPCompare(QMainWindow):
 | 
			
		||||
    # Receive instance of PlatformEdit() as parameter
 | 
			
		||||
    def __init__(self, plat_edit_win: QWidget):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        # Import Qt Designer UI XML file
 | 
			
		||||
        uic.loadUi(resource_finder.get_res_path("gui/main_gui.ui"), self)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
 | 
			
		||||
        # Initialise class variables
 | 
			
		||||
        # Inputs
 | 
			
		||||
        self.optional_boxes             = []
 | 
			
		||||
        self.fund_plat_fee              = 0.0
 | 
			
		||||
        self.plat_name                  = ""
 | 
			
		||||
        self.fund_deal_fee              = 0.0
 | 
			
		||||
        self.share_plat_fee             = 0.0
 | 
			
		||||
        self.share_plat_max_fee         = 0.0
 | 
			
		||||
        self.share_deal_fee             = 0.0
 | 
			
		||||
        self.share_deal_reduce_trades   = 0.0
 | 
			
		||||
        self.share_deal_reduce_amount   = 0.0
 | 
			
		||||
 | 
			
		||||
        ## Initialise class variables
 | 
			
		||||
        # Results
 | 
			
		||||
        self.fund_plat_fees     = 0.0
 | 
			
		||||
        self.fund_deal_fees     = 0.0
 | 
			
		||||
        self.share_plat_fees    = 0.0
 | 
			
		||||
        self.share_deal_fees    = 0.0
 | 
			
		||||
        self.results = []
 | 
			
		||||
 | 
			
		||||
        # Create window objects
 | 
			
		||||
        self.platform_win = plat_edit_win
 | 
			
		||||
        self.output_win = output_window.OutputWindow()
 | 
			
		||||
        self.db = DBHandler()
 | 
			
		||||
        self.platform_list_win = PlatformList(self.db)
 | 
			
		||||
        if len(self.platform_list_win.plat_name_list) == 0:
 | 
			
		||||
            QTimer.singleShot(1, self.platform_list_win.show)
 | 
			
		||||
        self.output_win = None
 | 
			
		||||
 | 
			
		||||
        # Handle events
 | 
			
		||||
        self.calc_but.clicked.connect(self.calculate_fees)
 | 
			
		||||
        # Menu bar entry (File -> Edit Platforms)
 | 
			
		||||
        self.actionEdit_Platforms.triggered.connect(self.show_platform_edit)
 | 
			
		||||
        ## Handle events
 | 
			
		||||
        self.calc_but.clicked.connect(self.indicate_loading)
 | 
			
		||||
        # Menu bar entry (File -> Platform List)
 | 
			
		||||
        self.actionList_Platforms.triggered.connect(self.show_platform_list)
 | 
			
		||||
        # Update percentage mix label when slider moved
 | 
			
		||||
        self.mix_slider.valueChanged.connect(self.update_slider_lab)
 | 
			
		||||
        self.value_input.valueChanged.connect(self.check_valid)
 | 
			
		||||
        self.share_trades_combo.currentTextChanged.connect(self.check_valid)
 | 
			
		||||
        self.fund_trades_combo.currentTextChanged.connect(self.check_valid)
 | 
			
		||||
        # Validate input
 | 
			
		||||
        self.share_trades_box.valueChanged.connect(self.check_valid)
 | 
			
		||||
        self.fund_trades_box.valueChanged.connect(self.check_valid)
 | 
			
		||||
 | 
			
		||||
        # Set validators
 | 
			
		||||
        self.share_trades_combo.setValidator(QIntValidator(0, 999))
 | 
			
		||||
        self.fund_trades_combo.setValidator(QIntValidator(0, 99))
 | 
			
		||||
        ## Restore last session
 | 
			
		||||
        prev_session_data = self.db.retrieve_user_details()
 | 
			
		||||
        if "NO_RECORD" not in prev_session_data:
 | 
			
		||||
            self.value_input.setValue(prev_session_data["pension_val"])
 | 
			
		||||
            self.mix_slider.setValue(prev_session_data["slider_val"])
 | 
			
		||||
            self.share_trades_box.setValue(prev_session_data["share_trades"])
 | 
			
		||||
            self.fund_trades_box.setValue(prev_session_data["fund_trades"])
 | 
			
		||||
            self.calc_but.setFocus()
 | 
			
		||||
 | 
			
		||||
    # Display slider position as mix between two nums (funds/shares)
 | 
			
		||||
    def update_slider_lab(self):
 | 
			
		||||
@@ -56,98 +57,90 @@ class SIPPCompare(QMainWindow):
 | 
			
		||||
        mix_lab_str = f"Investment mix (funds {slider_val}% / shares {100 - slider_val}%)"
 | 
			
		||||
        self.mix_lab.setText(mix_lab_str)
 | 
			
		||||
 | 
			
		||||
    # Ensure that trade fields aren't blank and pension value > 0
 | 
			
		||||
    def check_valid(self):
 | 
			
		||||
        if self.share_trades_combo.currentText() != "" \
 | 
			
		||||
        and self.fund_trades_combo.currentText() != "" \
 | 
			
		||||
        and self.value_input.value() != 0:
 | 
			
		||||
        if self.value_input.value() != 0:
 | 
			
		||||
            self.calc_but.setEnabled(True)
 | 
			
		||||
        else:
 | 
			
		||||
            self.calc_but.setEnabled(False)
 | 
			
		||||
 | 
			
		||||
    # Get variables from platform editor input fields
 | 
			
		||||
    def init_variables(self):
 | 
			
		||||
        self.optional_boxes     = self.platform_win.get_optional_boxes()
 | 
			
		||||
        self.fund_plat_fee      = self.platform_win.get_fund_plat_fee()
 | 
			
		||||
        self.share_plat_fee     = self.platform_win.get_share_plat_fee()
 | 
			
		||||
        self.share_deal_fee     = self.platform_win.get_share_deal_fee()
 | 
			
		||||
    def indicate_loading(self):
 | 
			
		||||
        self.calc_but.setText("Working...")
 | 
			
		||||
        QTimer.singleShot(1, self.calculate_fees)
 | 
			
		||||
 | 
			
		||||
        # TODO: This is HORRIBLE - find better way of doing it! (maybe enums?)
 | 
			
		||||
        if self.optional_boxes[0]:
 | 
			
		||||
            self.plat_name = self.platform_win.get_plat_name()
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat_name = None
 | 
			
		||||
 | 
			
		||||
        if self.optional_boxes[1]:
 | 
			
		||||
            self.fund_deal_fee = self.platform_win.get_fund_deal_fee()
 | 
			
		||||
        else:
 | 
			
		||||
            self.fund_deal_fee = None
 | 
			
		||||
 | 
			
		||||
        if self.optional_boxes[2]:
 | 
			
		||||
            self.share_plat_max_fee = self.platform_win.get_share_plat_max_fee()
 | 
			
		||||
        else:
 | 
			
		||||
            self.share_plat_max_fee = None
 | 
			
		||||
 | 
			
		||||
        if self.optional_boxes[3]:
 | 
			
		||||
            self.share_deal_reduce_trades = self.platform_win.get_share_deal_reduce_trades()
 | 
			
		||||
        else:
 | 
			
		||||
            self.share_deal_reduce_trades = None
 | 
			
		||||
 | 
			
		||||
        if self.optional_boxes[4]:
 | 
			
		||||
            self.share_deal_reduce_amount = self.platform_win.get_share_deal_reduce_amount()
 | 
			
		||||
        else:
 | 
			
		||||
            self.share_deal_reduce_amount = None
 | 
			
		||||
 | 
			
		||||
    # Calculate fees
 | 
			
		||||
    # Calculate fees for all active platforms
 | 
			
		||||
    def calculate_fees(self):
 | 
			
		||||
        self.init_variables()
 | 
			
		||||
        # Set to zero each time to avoid persistence
 | 
			
		||||
        self.fund_plat_fees = 0
 | 
			
		||||
        # Set to empty list each time to avoid persistence
 | 
			
		||||
        self.results = []
 | 
			
		||||
 | 
			
		||||
        # Get user input
 | 
			
		||||
        value_num = float(self.value_input.value())
 | 
			
		||||
        # Funds/shares mix
 | 
			
		||||
        slider_val: int = self.mix_slider.value()
 | 
			
		||||
        funds_value = (slider_val / 100) * value_num
 | 
			
		||||
        fund_trades_num = int(self.fund_trades_combo.currentText())
 | 
			
		||||
        if self.fund_deal_fee is not None:
 | 
			
		||||
            self.fund_deal_fees = fund_trades_num * self.fund_deal_fee
 | 
			
		||||
 | 
			
		||||
        for i in range(1, len(self.fund_plat_fee[0])):
 | 
			
		||||
            band = self.fund_plat_fee[0][i]
 | 
			
		||||
            prev_band = self.fund_plat_fee[0][i - 1]
 | 
			
		||||
            fee = self.fund_plat_fee[1][i]
 | 
			
		||||
            gap = (band - prev_band)
 | 
			
		||||
 | 
			
		||||
            if funds_value > gap:
 | 
			
		||||
                self.fund_plat_fees += gap * (fee / 100)
 | 
			
		||||
                funds_value -= gap
 | 
			
		||||
            else:
 | 
			
		||||
                self.fund_plat_fees += funds_value * (fee / 100)
 | 
			
		||||
                break
 | 
			
		||||
 | 
			
		||||
        fund_trades_num = int(self.fund_trades_box.value())
 | 
			
		||||
        share_trades_num = int(self.share_trades_box.value())
 | 
			
		||||
        shares_value = (1 - (slider_val / 100)) * value_num
 | 
			
		||||
        if self.share_plat_max_fee is not None:
 | 
			
		||||
            if (self.share_plat_fee * shares_value / 12) > self.share_plat_max_fee:
 | 
			
		||||
                self.share_plat_fees = self.share_plat_max_fee * 12
 | 
			
		||||
            else:
 | 
			
		||||
                self.share_plat_fees = self.share_plat_fee * shares_value
 | 
			
		||||
        index = 0
 | 
			
		||||
 | 
			
		||||
        share_trades_num = int(self.share_trades_combo.currentText())
 | 
			
		||||
        if self.share_deal_reduce_trades is not None:
 | 
			
		||||
            if (share_trades_num / 12) >= self.share_deal_reduce_trades:
 | 
			
		||||
                self.share_deal_fees = self.share_deal_reduce_amount * share_trades_num
 | 
			
		||||
            else:
 | 
			
		||||
                self.share_deal_fees = self.share_deal_fee * share_trades_num
 | 
			
		||||
        for platform in self.platform_list_win.plat_list:
 | 
			
		||||
            if not platform.enabled:
 | 
			
		||||
                continue
 | 
			
		||||
 | 
			
		||||
            fund_plat_fees = 0.0
 | 
			
		||||
            fund_deal_fees = 0.0
 | 
			
		||||
            share_plat_fees = 0.0
 | 
			
		||||
            share_deal_fees = 0.0
 | 
			
		||||
            plat_name = platform.plat_name
 | 
			
		||||
            if plat_name is None or plat_name == "":
 | 
			
		||||
                plat_name = f"Unnamed [ID: {index}]"
 | 
			
		||||
 | 
			
		||||
            if platform.fund_deal_fee is not None:
 | 
			
		||||
                fund_deal_fees = fund_trades_num * platform.fund_deal_fee
 | 
			
		||||
 | 
			
		||||
            funds_value = (slider_val / 100) * value_num
 | 
			
		||||
            for i in range(1, len(platform.fund_plat_fee[0])):
 | 
			
		||||
                band = platform.fund_plat_fee[0][i]
 | 
			
		||||
                prev_band = platform.fund_plat_fee[0][i - 1]
 | 
			
		||||
                fee = platform.fund_plat_fee[1][i]
 | 
			
		||||
                gap = (band - prev_band)
 | 
			
		||||
 | 
			
		||||
                if funds_value > gap:
 | 
			
		||||
                    fund_plat_fees += gap * (fee / 100)
 | 
			
		||||
                    funds_value -= gap
 | 
			
		||||
                else:
 | 
			
		||||
                    fund_plat_fees += funds_value * (fee / 100)
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
            if platform.share_plat_max_fee is not None:
 | 
			
		||||
                if (platform.share_plat_fee * shares_value / 12) > platform.share_plat_max_fee:
 | 
			
		||||
                    share_plat_fees = platform.share_plat_max_fee * 12
 | 
			
		||||
                else:
 | 
			
		||||
                    share_plat_fees = platform.share_plat_fee * shares_value
 | 
			
		||||
 | 
			
		||||
            if platform.share_deal_reduce_trades is not None:
 | 
			
		||||
                if (share_trades_num / 12) >= platform.share_deal_reduce_trades:
 | 
			
		||||
                    share_deal_fees = platform.share_deal_reduce_amount * share_trades_num
 | 
			
		||||
                else:
 | 
			
		||||
                    share_deal_fees = platform.share_deal_fee * share_trades_num
 | 
			
		||||
 | 
			
		||||
            self.results.append([fund_plat_fees, fund_deal_fees, share_plat_fees, share_deal_fees, plat_name])
 | 
			
		||||
            index += 1
 | 
			
		||||
 | 
			
		||||
        # Save details entered by user for next session
 | 
			
		||||
        self.db.write_user_details(value_num, slider_val, share_trades_num, fund_trades_num)
 | 
			
		||||
        self.show_output_win()
 | 
			
		||||
 | 
			
		||||
    # Show the output window - this func is called from calculate_fee()
 | 
			
		||||
    def show_output_win(self):
 | 
			
		||||
        # Refresh the results when new fees are calculated
 | 
			
		||||
        self.output_win.display_output(self.fund_plat_fees, self.fund_deal_fees,
 | 
			
		||||
                                       self.share_plat_fees, self.share_deal_fees,
 | 
			
		||||
                                       self.plat_name
 | 
			
		||||
                                       )
 | 
			
		||||
        if self.output_win is None:
 | 
			
		||||
            self.output_win = OutputWindow()
 | 
			
		||||
        years = self.output_win.get_slider_position()
 | 
			
		||||
        self.output_win.display_output(self.results, years)
 | 
			
		||||
        self.calc_but.setText("Calculate")
 | 
			
		||||
        self.output_win.activateWindow()
 | 
			
		||||
        self.output_win.raise_()
 | 
			
		||||
        self.output_win.show()
 | 
			
		||||
        QApplication.alert(self.output_win)
 | 
			
		||||
 | 
			
		||||
    # Show the platform editor window (currently run-time only)
 | 
			
		||||
    def show_platform_edit(self):
 | 
			
		||||
        self.platform_win.show()
 | 
			
		||||
    def show_platform_list(self):
 | 
			
		||||
        self.platform_list_win.show()
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,33 @@
 | 
			
		||||
from PyQt6.QtGui import QIcon
 | 
			
		||||
from PyQt6.QtWidgets import QWidget
 | 
			
		||||
from PyQt6 import uic
 | 
			
		||||
 | 
			
		||||
import datetime
 | 
			
		||||
import os
 | 
			
		||||
from datetime import datetime
 | 
			
		||||
 | 
			
		||||
from PyQt6 import uic
 | 
			
		||||
from PyQt6.QtGui import QIcon, QFont
 | 
			
		||||
from PyQt6.QtWidgets import QWidget, QFileDialog, QMessageBox, QDialogButtonBox
 | 
			
		||||
 | 
			
		||||
import resource_finder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SaveFailure(QMessageBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
        self.setWindowTitle("Save failure")
 | 
			
		||||
 | 
			
		||||
        self.setIcon(QMessageBox.Icon.Critical)
 | 
			
		||||
        font = QFont()
 | 
			
		||||
        font.setPointSize(11)
 | 
			
		||||
        self.setFont(font)
 | 
			
		||||
        self.setText("Failed to save file")
 | 
			
		||||
        self.setDetailedText(
 | 
			
		||||
            "This could be due to a permissions issue, or the file being in use by another process"
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.setStandardButtons(QMessageBox.StandardButton.Ok)
 | 
			
		||||
        font.setPointSize(10)
 | 
			
		||||
        self.findChild(QDialogButtonBox).setFont(font)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OutputWindow(QWidget):
 | 
			
		||||
    def __init__(self):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
@@ -15,50 +35,106 @@ class OutputWindow(QWidget):
 | 
			
		||||
        uic.loadUi(resource_finder.get_res_path("gui/output_window.ui"), self)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
 | 
			
		||||
        # Initialise class variables
 | 
			
		||||
        self.results_str = ""
 | 
			
		||||
        self.platform_name = ""
 | 
			
		||||
        # Define class variables
 | 
			
		||||
        self.save_err_dialog = SaveFailure()
 | 
			
		||||
        self.canvas = self.graphWidget.canvas
 | 
			
		||||
        self.ax = self.canvas.axes
 | 
			
		||||
        self.fig = self.canvas.figure
 | 
			
		||||
        self.results = []
 | 
			
		||||
 | 
			
		||||
        # Handle events
 | 
			
		||||
        self.res_save_but.clicked.connect(self.save_results)
 | 
			
		||||
        self.save_graph_but.clicked.connect(self.save_graph)
 | 
			
		||||
        self.save_csv_but.clicked.connect(self.save_csv)
 | 
			
		||||
        self.time_slider.valueChanged.connect(self.change_time)
 | 
			
		||||
 | 
			
		||||
    def save_results(self):
 | 
			
		||||
        # Use datatime for txt filename
 | 
			
		||||
        cur_time = datetime.datetime.now()
 | 
			
		||||
        if not os.path.exists("output"):
 | 
			
		||||
            os.makedirs("output")
 | 
			
		||||
        filename_str = f"output/"
 | 
			
		||||
        if self.platform_name is not None:
 | 
			
		||||
            filename_str += f"{self.platform_name}"
 | 
			
		||||
        else:
 | 
			
		||||
            filename_str += "Unnamed"
 | 
			
		||||
        filename_str += f"-{cur_time.year}.{cur_time.month}.{cur_time.day}.txt"
 | 
			
		||||
        output_file = open(filename_str, "wt", encoding = "utf-8")
 | 
			
		||||
        output_file.write(self.results_str)
 | 
			
		||||
    def get_slider_position(self) -> int:
 | 
			
		||||
        return self.time_slider.value()
 | 
			
		||||
 | 
			
		||||
    # Display fees in output window as plaintext readout
 | 
			
		||||
    def display_output(self, fund_plat_fees: float, fund_deal_fees: float,
 | 
			
		||||
                       share_plat_fees: float, share_deal_fees: float, plat_name: str):
 | 
			
		||||
        self.platform_name = plat_name
 | 
			
		||||
        if self.platform_name is not None:
 | 
			
		||||
            self.results_str = f"Fees breakdown (Platform \"{self.platform_name}\"):"
 | 
			
		||||
        else:
 | 
			
		||||
            self.results_str = f"Fees breakdown:"
 | 
			
		||||
    def display_output(self, results: list, years: int):
 | 
			
		||||
        self.results = results
 | 
			
		||||
        self.ax.clear()
 | 
			
		||||
        self.ax.cla()
 | 
			
		||||
        self.canvas.draw_idle()
 | 
			
		||||
 | 
			
		||||
        self.results_str += "\n\nPlatform fees:"
 | 
			
		||||
        # :.2f is used in order to display 2 decimal places (currency form)
 | 
			
		||||
        self.results_str += f"\n\tFund platform fees: £{round(fund_plat_fees, 2):.2f}"
 | 
			
		||||
        self.results_str += f"\n\tShare platform fees: £{round(share_plat_fees, 2):.2f}"
 | 
			
		||||
        total_plat_fees = fund_plat_fees + share_plat_fees
 | 
			
		||||
        self.results_str += f"\n\tTotal platform fees: £{round(total_plat_fees, 2):.2f}"
 | 
			
		||||
        names = []
 | 
			
		||||
        values = []
 | 
			
		||||
        for result in results:
 | 
			
		||||
            names.append(result[4])
 | 
			
		||||
            values.append(sum(result[:4]) * years)
 | 
			
		||||
 | 
			
		||||
        self.results_str += "\n\nDealing fees:"
 | 
			
		||||
        self.results_str += f"\n\tFund dealing fees: £{round(fund_deal_fees, 2):.2f}"
 | 
			
		||||
        self.results_str += f"\n\tShare dealing fees: £{round(share_deal_fees, 2):.2f}"
 | 
			
		||||
        total_deal_fees = fund_deal_fees + share_deal_fees
 | 
			
		||||
        self.results_str += f"\n\tTotal dealing fees: £{round(total_deal_fees, 2):.2f}"
 | 
			
		||||
        names = sorted(names, key=lambda x: values[names.index(x)], reverse=True)
 | 
			
		||||
        values = sorted(values, reverse=True)
 | 
			
		||||
 | 
			
		||||
        total_fees = total_plat_fees + total_deal_fees
 | 
			
		||||
        self.results_str += f"\n\nTotal fees: £{round(total_fees, 2):.2f}"
 | 
			
		||||
        h_bars = self.ax.barh(names, values)
 | 
			
		||||
        self.ax.bar_label(h_bars, label_type='center', labels=[f"£{x:,.2f}" for x in h_bars.datavalues])
 | 
			
		||||
 | 
			
		||||
        self.output.setText(self.results_str)
 | 
			
		||||
    def save_graph(self):
 | 
			
		||||
        file_picker = QFileDialog(self)
 | 
			
		||||
        file_picker.setFileMode(QFileDialog.FileMode.AnyFile)
 | 
			
		||||
        file_picker.setDefaultSuffix("png")
 | 
			
		||||
        file_picker.setWindowTitle("Save results as PNG")
 | 
			
		||||
        file_picker.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
 | 
			
		||||
        file_picker.setNameFilter("*.png")
 | 
			
		||||
        file_path = ""
 | 
			
		||||
        cur_time = datetime.now()
 | 
			
		||||
        filename_str = f"{file_path}/SIPPCompare-{cur_time.year}.{cur_time.month}.{cur_time.day}.png"
 | 
			
		||||
        file_picker.selectFile(filename_str)
 | 
			
		||||
        if file_picker.exec():
 | 
			
		||||
            file_path = file_picker.selectedFiles()[0]
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            self.fig.savefig(file_path, dpi=150)
 | 
			
		||||
        except OSError:
 | 
			
		||||
            self.save_err_dialog.exec()
 | 
			
		||||
 | 
			
		||||
    def save_csv(self):
 | 
			
		||||
        # TODO: Sort CSV output, either alphabetically or by total fees
 | 
			
		||||
        file_picker = QFileDialog(self)
 | 
			
		||||
        file_picker.setFileMode(QFileDialog.FileMode.AnyFile)
 | 
			
		||||
        file_picker.setDefaultSuffix("csv")
 | 
			
		||||
        file_picker.setWindowTitle("Save results as CSV")
 | 
			
		||||
        file_picker.setAcceptMode(QFileDialog.AcceptMode.AcceptSave)
 | 
			
		||||
        file_picker.setNameFilter("*.csv")
 | 
			
		||||
        file_path = ""
 | 
			
		||||
        cur_time = datetime.now()
 | 
			
		||||
        filename_str = f"{file_path}/SIPPCompare-{cur_time.year}.{cur_time.month}.{cur_time.day}.csv"
 | 
			
		||||
        file_picker.selectFile(filename_str)
 | 
			
		||||
        if file_picker.exec():
 | 
			
		||||
            file_path = file_picker.selectedFiles()[0]
 | 
			
		||||
 | 
			
		||||
        try:
 | 
			
		||||
            csvfile = open(file_path, "wt")
 | 
			
		||||
            csv_string = (
 | 
			
		||||
                "Platform Name,Fund Platform Fee,Share Platform Fee,Fund Dealing Fee,"
 | 
			
		||||
                "Share Dealing Fee,Total Platform Fees,Total Dealing Fees,Total Fund Fees,"
 | 
			
		||||
                "Total Share Fees,Total Fees"
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            for result in self.results:
 | 
			
		||||
                csv_string += '\n'
 | 
			
		||||
                pn = result[4]
 | 
			
		||||
                fpf = result[0]
 | 
			
		||||
                spf = result[2]
 | 
			
		||||
                fdf = result[1]
 | 
			
		||||
                sdf = result[3]
 | 
			
		||||
 | 
			
		||||
                tpf = fpf + spf
 | 
			
		||||
                tdf = sdf + fdf
 | 
			
		||||
 | 
			
		||||
                tff = fpf + fdf
 | 
			
		||||
                tsf = spf + sdf
 | 
			
		||||
                tf = tff + tsf
 | 
			
		||||
                csv_string += (
 | 
			
		||||
                    f"{pn},\"£{fpf:,.2f}\",\"£{spf:,.2f}\",\"£{fdf:,.2f}\",\"£{sdf:,.2f}\","
 | 
			
		||||
                    f"\"£{tpf:,.2f}\",\"£{tdf:,.2f}\",\"£{tff:,.2f}\",\"£{tsf:,.2f}\",\"£{tf:,.2f}\""
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            csvfile.write(csv_string)
 | 
			
		||||
            csvfile.close()
 | 
			
		||||
        except OSError:
 | 
			
		||||
            self.save_err_dialog.exec()
 | 
			
		||||
 | 
			
		||||
    def change_time(self):
 | 
			
		||||
        years: int = self.time_slider.value()
 | 
			
		||||
        self.time_lab.setText(f"Fees over {years} year(s) (assuming no change in value)")
 | 
			
		||||
        self.display_output(self.results, years)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,39 +1,28 @@
 | 
			
		||||
from PyQt6.QtCore import QRegularExpression
 | 
			
		||||
from PyQt6.QtGui import QRegularExpressionValidator, QFont, QIcon
 | 
			
		||||
from PyQt6.QtWidgets import QWidget, QLabel
 | 
			
		||||
from PyQt6 import uic
 | 
			
		||||
from PyQt6.QtCore import QRegularExpression, QRect
 | 
			
		||||
from PyQt6.QtGui import QRegularExpressionValidator, QFont, QIcon
 | 
			
		||||
from PyQt6.QtWidgets import QLabel, QDialog
 | 
			
		||||
 | 
			
		||||
from widgets.fastedit_spinbox import FastEditQDoubleSpinBox
 | 
			
		||||
import main_window
 | 
			
		||||
import resource_finder
 | 
			
		||||
from db_handler import DBHandler
 | 
			
		||||
from data_struct import Platform
 | 
			
		||||
from widgets.fastedit_spinbox import FastEditQDoubleSpinBox
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlatformEdit(QWidget):
 | 
			
		||||
    def __init__(self, autofill: bool):
 | 
			
		||||
class PlatformEdit(QDialog):
 | 
			
		||||
    def __init__(self, plat: Platform):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        # Import Qt Designer UI XML file
 | 
			
		||||
        uic.loadUi(resource_finder.get_res_path("gui/platform_edit.ui"), self)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
 | 
			
		||||
        # Initialise class variables
 | 
			
		||||
        # Create main window object, passing this instance as param
 | 
			
		||||
        self.main_win = main_window.SIPPCompare(self)
 | 
			
		||||
 | 
			
		||||
        self.fund_plat_fee = []
 | 
			
		||||
        self.plat_name = ""
 | 
			
		||||
        self.fund_deal_fee = 0.0
 | 
			
		||||
        self.share_plat_fee = 0.0
 | 
			
		||||
        self.share_plat_max_fee = 0.0
 | 
			
		||||
        self.share_deal_fee = 0.0
 | 
			
		||||
        self.share_deal_reduce_trades = 0.0
 | 
			
		||||
        self.share_deal_reduce_amount = 0.0
 | 
			
		||||
        self.plat = plat
 | 
			
		||||
        self.widgets_list_list = []
 | 
			
		||||
 | 
			
		||||
        self.fund_fee_rows = 1
 | 
			
		||||
        # Debugging feature: set with "--DEBUG_AUTOFILL" cmd argument
 | 
			
		||||
        self.autofill = autofill
 | 
			
		||||
        if autofill:
 | 
			
		||||
            self.save_but.setEnabled(True)
 | 
			
		||||
        if len(self.plat.fund_plat_fee[0]) > 1:
 | 
			
		||||
            self.fund_fee_rows = len(self.plat.fund_plat_fee[0]) - 1
 | 
			
		||||
        else:
 | 
			
		||||
            self.fund_fee_rows = 1
 | 
			
		||||
 | 
			
		||||
        self.required_fields = [
 | 
			
		||||
            self.share_plat_fee_box,
 | 
			
		||||
@@ -64,6 +53,57 @@ class PlatformEdit(QWidget):
 | 
			
		||||
            False
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
        # Set optional checkboxes based on DB storage
 | 
			
		||||
        if self.plat.plat_name is None:
 | 
			
		||||
            self.check_boxes_ticked[0] = False
 | 
			
		||||
            self.plat_name_check.setChecked(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.check_boxes_ticked[0] = True
 | 
			
		||||
            self.plat_name_check.setChecked(True)
 | 
			
		||||
            self.plat_name_box.setText(self.plat.plat_name)
 | 
			
		||||
 | 
			
		||||
        if self.plat.fund_deal_fee is None:
 | 
			
		||||
            self.check_boxes_ticked[1] = False
 | 
			
		||||
            self.fund_deal_fee_check.setChecked(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.check_boxes_ticked[1] = True
 | 
			
		||||
            self.fund_deal_fee_check.setChecked(True)
 | 
			
		||||
            self.fund_deal_fee_box.setValue(self.plat.fund_deal_fee)
 | 
			
		||||
 | 
			
		||||
        self.share_plat_fee_box.setValue(self.plat.share_plat_fee * 100)
 | 
			
		||||
 | 
			
		||||
        if self.plat.share_plat_max_fee is None:
 | 
			
		||||
            self.check_boxes_ticked[2] = False
 | 
			
		||||
            self.share_plat_max_fee_check.setChecked(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.check_boxes_ticked[2] = True
 | 
			
		||||
            self.share_plat_max_fee_check.setChecked(True)
 | 
			
		||||
            self.share_plat_max_fee_box.setValue(self.plat.share_plat_max_fee)
 | 
			
		||||
 | 
			
		||||
        self.share_deal_fee_box.setValue(self.plat.share_deal_fee)
 | 
			
		||||
 | 
			
		||||
        if self.plat.share_deal_reduce_trades is None:
 | 
			
		||||
            self.check_boxes_ticked[3] = False
 | 
			
		||||
            self.share_deal_reduce_trades_check.setChecked(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.check_boxes_ticked[3] = True
 | 
			
		||||
            self.share_deal_reduce_trades_check.setChecked(True)
 | 
			
		||||
            self.share_deal_reduce_trades_box.setValue(int(self.plat.share_deal_reduce_trades))
 | 
			
		||||
 | 
			
		||||
        if self.plat.share_deal_reduce_trades is None:
 | 
			
		||||
            self.check_boxes_ticked[4] = False
 | 
			
		||||
            self.share_deal_reduce_amount_check.setChecked(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.check_boxes_ticked[4] = True
 | 
			
		||||
            self.share_deal_reduce_amount_check.setChecked(True)
 | 
			
		||||
            self.share_deal_reduce_amount_box.setValue(self.plat.share_deal_reduce_amount)
 | 
			
		||||
 | 
			
		||||
        # Populate fund platform fee rows from DB
 | 
			
		||||
        if len(self.plat.fund_plat_fee[0]) > 1:
 | 
			
		||||
            self.first_tier_box.setValue(self.plat.fund_plat_fee[0][1])
 | 
			
		||||
            self.first_tier_fee_box.setValue(self.plat.fund_plat_fee[1][1])
 | 
			
		||||
        self.add_row(loading=True)
 | 
			
		||||
 | 
			
		||||
        # Handle events
 | 
			
		||||
        for field in self.required_fields:
 | 
			
		||||
            field.valueChanged.connect(self.check_valid)
 | 
			
		||||
@@ -93,7 +133,7 @@ class PlatformEdit(QWidget):
 | 
			
		||||
            QRegularExpressionValidator(QRegularExpression("\\w*"))
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def create_plat_fee_struct(self):
 | 
			
		||||
    def create_plat_fee_struct(self) -> list:
 | 
			
		||||
        plat_fee_struct = [[0], [0]]
 | 
			
		||||
        plat_fee_struct[0].append(self.first_tier_box.value())
 | 
			
		||||
        plat_fee_struct[1].append(self.first_tier_fee_box.value())
 | 
			
		||||
@@ -108,31 +148,36 @@ class PlatformEdit(QWidget):
 | 
			
		||||
 | 
			
		||||
    # Get fee structure variables from user input when "Save" clicked
 | 
			
		||||
    def init_variables(self):
 | 
			
		||||
        # If debugging, save time by hardcoding
 | 
			
		||||
        if self.autofill:
 | 
			
		||||
            self.plat_name                  = "AJBell"
 | 
			
		||||
            self.fund_plat_fee              = [
 | 
			
		||||
                                            [0, 250000,     1000000,    2000000],
 | 
			
		||||
                                            [0, 0.25,       0.1,        0.05]
 | 
			
		||||
                                        ]
 | 
			
		||||
            self.fund_deal_fee              = 1.50
 | 
			
		||||
            self.share_plat_fee             = 0.0025
 | 
			
		||||
            self.share_plat_max_fee         = 3.50
 | 
			
		||||
            self.share_deal_fee             = 5.00
 | 
			
		||||
            self.share_deal_reduce_trades   = 10
 | 
			
		||||
            self.share_deal_reduce_amount   = 3.50
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat_name                  = self.plat_name_box.text()
 | 
			
		||||
            self.fund_plat_fee              = self.create_plat_fee_struct()
 | 
			
		||||
            self.fund_deal_fee              = float(self.fund_deal_fee_box.value())
 | 
			
		||||
            self.share_plat_fee             = float(self.share_plat_fee_box.value()) / 100
 | 
			
		||||
            self.share_plat_max_fee         = float(self.share_plat_max_fee_box.value())
 | 
			
		||||
            self.share_deal_fee             = float(self.share_deal_fee_box.value())
 | 
			
		||||
            self.share_deal_reduce_trades   = float(self.share_deal_reduce_trades_box.value())
 | 
			
		||||
            self.share_deal_reduce_amount   = float(self.share_deal_reduce_amount_box.value())
 | 
			
		||||
        self.plat.fund_plat_fee = self.create_plat_fee_struct()
 | 
			
		||||
        self.plat.share_plat_fee = float(self.share_plat_fee_box.value()) / 100
 | 
			
		||||
        self.plat.share_deal_fee = float(self.share_deal_fee_box.value())
 | 
			
		||||
 | 
			
		||||
        # Once user input is received show main window
 | 
			
		||||
        self.main_win.show()
 | 
			
		||||
        if self.check_boxes_ticked[0]:
 | 
			
		||||
            self.plat.plat_name = self.plat_name_box.text()
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat.plat_name = None
 | 
			
		||||
 | 
			
		||||
        if self.check_boxes_ticked[1]:
 | 
			
		||||
            self.plat.fund_deal_fee = float(self.fund_deal_fee_box.value())
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat.fund_deal_fee = None
 | 
			
		||||
 | 
			
		||||
        if self.check_boxes_ticked[2]:
 | 
			
		||||
            self.plat.share_plat_max_fee = float(self.share_plat_max_fee_box.value())
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat.share_plat_max_fee = None
 | 
			
		||||
 | 
			
		||||
        if self.check_boxes_ticked[3]:
 | 
			
		||||
            self.plat.share_deal_reduce_trades = int(self.share_deal_reduce_trades_box.value())
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat.share_deal_reduce_trades = None
 | 
			
		||||
 | 
			
		||||
        if self.check_boxes_ticked[4]:
 | 
			
		||||
            self.plat.share_deal_reduce_amount = float(self.share_deal_reduce_amount_box.value())
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat.share_deal_reduce_amount = None
 | 
			
		||||
 | 
			
		||||
        self.accept()
 | 
			
		||||
 | 
			
		||||
    # This method does multiple things in order to validate the user's inputs:
 | 
			
		||||
    # 1) Check all required fields have a non-zero value
 | 
			
		||||
@@ -210,54 +255,80 @@ class PlatformEdit(QWidget):
 | 
			
		||||
 | 
			
		||||
        if self.fund_fee_rows > 1:
 | 
			
		||||
            max_band = self.widgets_list_list[self.fund_fee_rows - 2][1].value()
 | 
			
		||||
            if self.fund_fee_rows > 2:
 | 
			
		||||
                prev_band = self.widgets_list_list[self.fund_fee_rows - 3][1].value()
 | 
			
		||||
            else:
 | 
			
		||||
                prev_band = self.first_tier_box.value()
 | 
			
		||||
        else:
 | 
			
		||||
            max_band = self.first_tier_box.value()
 | 
			
		||||
        self.val_above_lab.setText(f"on the value above £{int(max_band)} there is no charge")
 | 
			
		||||
            prev_band = self.first_tier_box.value()
 | 
			
		||||
 | 
			
		||||
    def add_row(self):
 | 
			
		||||
        widgets = []
 | 
			
		||||
        font = QFont()
 | 
			
		||||
        font.setPointSize(11)
 | 
			
		||||
        if max_band > prev_band:
 | 
			
		||||
            self.val_above_lab.setText(f"on the value above £{max_band:.2f} there is no charge")
 | 
			
		||||
        else:
 | 
			
		||||
            self.val_above_lab.setText(f"on the value above £{prev_band:.2f} there is no charge")
 | 
			
		||||
 | 
			
		||||
        widgets.append(QLabel(self.gridLayoutWidget_2))
 | 
			
		||||
        widgets[0].setFont(font)
 | 
			
		||||
    def add_row(self, loading: bool = False):
 | 
			
		||||
        if loading:
 | 
			
		||||
            rows_needed = self.fund_fee_rows - 1
 | 
			
		||||
        else:
 | 
			
		||||
            rows_needed = 1
 | 
			
		||||
 | 
			
		||||
        widgets.append(FastEditQDoubleSpinBox(self.gridLayoutWidget_2))
 | 
			
		||||
        widgets[1].setPrefix("£")
 | 
			
		||||
        widgets[1].setMaximum(9999999)
 | 
			
		||||
        widgets[1].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons)
 | 
			
		||||
        widgets[1].setFont(font)
 | 
			
		||||
        widgets[1].valueChanged.connect(self.check_valid)
 | 
			
		||||
        widgets[1].valueChanged.connect(self.update_tier_labels)
 | 
			
		||||
        for x in range(rows_needed):
 | 
			
		||||
            widgets = []
 | 
			
		||||
            font = QFont()
 | 
			
		||||
            font.setPointSize(11)
 | 
			
		||||
 | 
			
		||||
        widgets.append(QLabel(self.gridLayoutWidget_2))
 | 
			
		||||
        widgets[2].setText(f"the fee is")
 | 
			
		||||
        widgets[2].setFont(font)
 | 
			
		||||
            widgets.append(QLabel(self.gridLayoutWidget_2))
 | 
			
		||||
            widgets[0].setFont(font)
 | 
			
		||||
 | 
			
		||||
        widgets.append(FastEditQDoubleSpinBox(self.gridLayoutWidget_2))
 | 
			
		||||
        widgets[3].setSuffix("%")
 | 
			
		||||
        widgets[3].setMaximum(100)
 | 
			
		||||
        widgets[3].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons)
 | 
			
		||||
        widgets[3].setFont(font)
 | 
			
		||||
        widgets[3].valueChanged.connect(self.check_valid)
 | 
			
		||||
            widgets.append(FastEditQDoubleSpinBox(self.gridLayoutWidget_2))
 | 
			
		||||
            widgets[1].setPrefix("£")
 | 
			
		||||
            widgets[1].setMaximum(9999999)
 | 
			
		||||
            widgets[1].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons)
 | 
			
		||||
            widgets[1].setFont(font)
 | 
			
		||||
            if loading:
 | 
			
		||||
                widgets[1].setValue(self.plat.fund_plat_fee[0][x+2])
 | 
			
		||||
            widgets[1].valueChanged.connect(self.check_valid)
 | 
			
		||||
            widgets[1].valueChanged.connect(self.update_tier_labels)
 | 
			
		||||
 | 
			
		||||
        # TODO: why 28.5?
 | 
			
		||||
        self.gridLayoutWidget_2.setGeometry(11, 309, 611, int(round(28.5 * (self.fund_fee_rows + 1), 0)))
 | 
			
		||||
        for i in range(len(widgets)):
 | 
			
		||||
            self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1)
 | 
			
		||||
            widgets.append(QLabel(self.gridLayoutWidget_2))
 | 
			
		||||
            widgets[2].setText(f"the fee is")
 | 
			
		||||
            widgets[2].setFont(font)
 | 
			
		||||
 | 
			
		||||
        self.fund_fee_rows += 1
 | 
			
		||||
            widgets.append(FastEditQDoubleSpinBox(self.gridLayoutWidget_2))
 | 
			
		||||
            widgets[3].setSuffix("%")
 | 
			
		||||
            widgets[3].setMaximum(100)
 | 
			
		||||
            widgets[3].setButtonSymbols(FastEditQDoubleSpinBox.ButtonSymbols.NoButtons)
 | 
			
		||||
            widgets[3].setFont(font)
 | 
			
		||||
            if loading:
 | 
			
		||||
                widgets[3].setValue(self.plat.fund_plat_fee[1][x+2])
 | 
			
		||||
            widgets[3].valueChanged.connect(self.check_valid)
 | 
			
		||||
 | 
			
		||||
        self.widgets_list_list.append(widgets)
 | 
			
		||||
        cur_label_idx = self.gridLayout_2.indexOf(widgets[0])
 | 
			
		||||
        cur_box_idx = self.gridLayout_2.indexOf(widgets[1])
 | 
			
		||||
        cur_label_pos = list(self.gridLayout_2.getItemPosition(cur_label_idx))[:2]
 | 
			
		||||
        cur_box_pos = list(self.gridLayout_2.getItemPosition(cur_box_idx))[:2]
 | 
			
		||||
            if loading:
 | 
			
		||||
                grid_height = int(round(28.5 * self.fund_fee_rows))
 | 
			
		||||
            else:
 | 
			
		||||
                grid_height = int(round(28.5 * (self.fund_fee_rows + 1)))
 | 
			
		||||
            self.gridLayoutWidget_2.setGeometry(QRect(19, 307, 591, grid_height))
 | 
			
		||||
            for i in range(len(widgets)):
 | 
			
		||||
                if loading:
 | 
			
		||||
                    self.gridLayout_2.addWidget(widgets[i], x + 1, i, 1, 1)
 | 
			
		||||
                else:
 | 
			
		||||
                    self.gridLayout_2.addWidget(widgets[i], self.fund_fee_rows, i, 1, 1)
 | 
			
		||||
 | 
			
		||||
        prev_box_row = cur_box_pos[0] - 1
 | 
			
		||||
        prev_box_item = self.gridLayout_2.itemAtPosition(prev_box_row, cur_box_pos[1]).widget()
 | 
			
		||||
        cur_label_item = self.gridLayout_2.itemAtPosition(cur_label_pos[0], cur_label_pos[1]).widget()
 | 
			
		||||
        cur_label_item.setText(f"between £{int(prev_box_item.value())} and")
 | 
			
		||||
            if not loading:
 | 
			
		||||
                self.fund_fee_rows += 1
 | 
			
		||||
 | 
			
		||||
            self.widgets_list_list.append(widgets)
 | 
			
		||||
            cur_label_idx = self.gridLayout_2.indexOf(widgets[0])
 | 
			
		||||
            cur_box_idx = self.gridLayout_2.indexOf(widgets[1])
 | 
			
		||||
            cur_label_pos = list(self.gridLayout_2.getItemPosition(cur_label_idx))[:2]
 | 
			
		||||
            cur_box_pos = list(self.gridLayout_2.getItemPosition(cur_box_idx))[:2]
 | 
			
		||||
 | 
			
		||||
            prev_box_row = cur_box_pos[0] - 1
 | 
			
		||||
            prev_box_item = self.gridLayout_2.itemAtPosition(prev_box_row, cur_box_pos[1]).widget()
 | 
			
		||||
            cur_label_item = self.gridLayout_2.itemAtPosition(cur_label_pos[0], cur_label_pos[1]).widget()
 | 
			
		||||
            cur_label_item.setText(f"between £{int(prev_box_item.value())} and")
 | 
			
		||||
 | 
			
		||||
        if self.fund_fee_rows > 1:
 | 
			
		||||
            self.del_row_but.setEnabled(True)
 | 
			
		||||
@@ -266,8 +337,9 @@ class PlatformEdit(QWidget):
 | 
			
		||||
            self.add_row_but.setEnabled(False)
 | 
			
		||||
 | 
			
		||||
        self.check_valid()
 | 
			
		||||
        self.update_tier_labels()
 | 
			
		||||
 | 
			
		||||
        # TODO: Tab order
 | 
			
		||||
        # TODO: Tab/focus order
 | 
			
		||||
 | 
			
		||||
    def remove_row(self):
 | 
			
		||||
        for widget in self.widgets_list_list[self.fund_fee_rows - 2]:
 | 
			
		||||
@@ -275,7 +347,7 @@ class PlatformEdit(QWidget):
 | 
			
		||||
            widget.hide()
 | 
			
		||||
        self.widgets_list_list.pop()
 | 
			
		||||
        self.fund_fee_rows -= 1
 | 
			
		||||
        self.gridLayoutWidget_2.setGeometry(11, 309, 611, int(round(28.5 * self.fund_fee_rows, 0)))
 | 
			
		||||
        self.gridLayoutWidget_2.setGeometry(19, 307, 591, int(round(28.5 * self.fund_fee_rows, 0)))
 | 
			
		||||
 | 
			
		||||
        if self.fund_fee_rows < 2:
 | 
			
		||||
            self.del_row_but.setEnabled(False)
 | 
			
		||||
@@ -286,30 +358,6 @@ class PlatformEdit(QWidget):
 | 
			
		||||
        self.check_valid()
 | 
			
		||||
        self.update_tier_labels()
 | 
			
		||||
 | 
			
		||||
    # Getter functions (is this necessary? maybe directly reading class vars would be best...)
 | 
			
		||||
    def get_optional_boxes(self):
 | 
			
		||||
        return self.check_boxes_ticked
 | 
			
		||||
 | 
			
		||||
    def get_plat_name(self):
 | 
			
		||||
        return self.plat_name
 | 
			
		||||
 | 
			
		||||
    def get_fund_plat_fee(self):
 | 
			
		||||
        return self.fund_plat_fee
 | 
			
		||||
 | 
			
		||||
    def get_fund_deal_fee(self):
 | 
			
		||||
        return self.fund_deal_fee
 | 
			
		||||
 | 
			
		||||
    def get_share_plat_fee(self):
 | 
			
		||||
        return self.share_plat_fee
 | 
			
		||||
 | 
			
		||||
    def get_share_plat_max_fee(self):
 | 
			
		||||
        return self.share_plat_max_fee
 | 
			
		||||
 | 
			
		||||
    def get_share_deal_fee(self):
 | 
			
		||||
        return self.share_deal_fee
 | 
			
		||||
 | 
			
		||||
    def get_share_deal_reduce_trades(self):
 | 
			
		||||
        return self.share_deal_reduce_trades
 | 
			
		||||
 | 
			
		||||
    def get_share_deal_reduce_amount(self):
 | 
			
		||||
        return self.share_deal_reduce_amount
 | 
			
		||||
    def closeEvent(self, event):
 | 
			
		||||
        event.ignore()
 | 
			
		||||
        self.reject()
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										185
									
								
								src/platform_list.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								src/platform_list.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,185 @@
 | 
			
		||||
from PyQt6 import uic
 | 
			
		||||
from PyQt6.QtCore import QRegularExpression
 | 
			
		||||
from PyQt6.QtGui import QIcon, QRegularExpressionValidator, QFont
 | 
			
		||||
from PyQt6.QtWidgets import QWidget, QListWidgetItem, QDialog, QDialogButtonBox, QMessageBox
 | 
			
		||||
 | 
			
		||||
import resource_finder
 | 
			
		||||
from db_handler import DBHandler
 | 
			
		||||
from data_struct import Platform
 | 
			
		||||
from platform_edit import PlatformEdit
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlatformRename(QDialog):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        # Import Qt Designer UI XML file
 | 
			
		||||
        uic.loadUi(resource_finder.get_res_path("gui/dialogs/platform_rename.ui"), self)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
 | 
			
		||||
        self.rename_plat_box.setFocus()
 | 
			
		||||
        self.new_name = ""
 | 
			
		||||
 | 
			
		||||
        # Set validators
 | 
			
		||||
        # Regex accepts any characters that match [a-Z], [0-9] or _
 | 
			
		||||
        self.rename_plat_box.setValidator(
 | 
			
		||||
            QRegularExpressionValidator(QRegularExpression("\\w*"))
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        self.rename_plat_ok_but.clicked.connect(self.store_new_name)
 | 
			
		||||
 | 
			
		||||
    def store_new_name(self):
 | 
			
		||||
        self.new_name = self.rename_plat_box.text()
 | 
			
		||||
 | 
			
		||||
    def closeEvent(self, event):
 | 
			
		||||
        event.ignore()
 | 
			
		||||
        self.reject()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class RemoveConfirm(QMessageBox):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
        self.setWindowTitle("Remove platform?")
 | 
			
		||||
        self.setIcon(QMessageBox.Icon.Warning)
 | 
			
		||||
        font = QFont()
 | 
			
		||||
        font.setPointSize(11)
 | 
			
		||||
        self.setFont(font)
 | 
			
		||||
        self.setText("Are you sure you want to remove this platform?")
 | 
			
		||||
        self.setInformativeText("This action is immediate and permanent")
 | 
			
		||||
 | 
			
		||||
        self.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.Cancel)
 | 
			
		||||
        self.setDefaultButton(QMessageBox.StandardButton.Cancel)
 | 
			
		||||
        font.setPointSize(10)
 | 
			
		||||
        self.findChild(QDialogButtonBox).setFont(font)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PlatformList(QWidget):
 | 
			
		||||
    def __init__(self, db: DBHandler):
 | 
			
		||||
        super().__init__()
 | 
			
		||||
        # Import Qt Designer UI XML file
 | 
			
		||||
        uic.loadUi(resource_finder.get_res_path("gui/platform_list.ui"), self)
 | 
			
		||||
        self.setWindowIcon(QIcon(resource_finder.get_res_path("icon2.ico")))
 | 
			
		||||
 | 
			
		||||
        # Initialise class variables
 | 
			
		||||
        self.db = db
 | 
			
		||||
        self.plat_edit_win = None
 | 
			
		||||
        self.plat_list_dialog = None
 | 
			
		||||
        self.del_plat_dialog = RemoveConfirm()
 | 
			
		||||
        self.plat_list = []
 | 
			
		||||
        self.plat_name_list = []
 | 
			
		||||
        self.new_plat_name = ""
 | 
			
		||||
        self.update_plat_list()
 | 
			
		||||
        self.db_indices = [x for x in range(len(self.plat_name_list))]
 | 
			
		||||
 | 
			
		||||
        # Handle events
 | 
			
		||||
        self.add_plat_but.clicked.connect(self.add_platform)
 | 
			
		||||
        self.del_plat_but.clicked.connect(self.remove_platform)
 | 
			
		||||
        self.edit_plat_but.clicked.connect(self.edit_platform)
 | 
			
		||||
        self.plist_save_but.clicked.connect(self.save_platforms)
 | 
			
		||||
        self.plat_enabled_check.checkStateChanged.connect(self.toggle_platform_state)
 | 
			
		||||
        self.platListWidget.currentRowChanged.connect(self.get_enabled_state)
 | 
			
		||||
 | 
			
		||||
    def update_plat_list(self):
 | 
			
		||||
        self.plat_name_list = self.db.retrieve_plat_list()
 | 
			
		||||
        self.plat_list = self.db.retrieve_platforms()
 | 
			
		||||
        self.platListWidget.clear()
 | 
			
		||||
 | 
			
		||||
        for i in range(len(self.plat_name_list)):
 | 
			
		||||
            plat_name = self.plat_name_list[i]
 | 
			
		||||
            item = QListWidgetItem()
 | 
			
		||||
            if plat_name is not None:
 | 
			
		||||
                item.setText(plat_name)
 | 
			
		||||
            else:
 | 
			
		||||
                item.setText(f"Unnamed [ID: {i}]")
 | 
			
		||||
 | 
			
		||||
            self.platListWidget.addItem(item)
 | 
			
		||||
        self.platListWidget.setCurrentRow(0)
 | 
			
		||||
 | 
			
		||||
    def add_platform(self):
 | 
			
		||||
        self.plat_list_dialog = PlatformRename()
 | 
			
		||||
        name_dialog_res = self.plat_list_dialog.exec()
 | 
			
		||||
        if name_dialog_res == QDialog.DialogCode.Accepted:
 | 
			
		||||
            name = self.plat_list_dialog.new_name
 | 
			
		||||
            index = self.platListWidget.count()
 | 
			
		||||
            if name != "":
 | 
			
		||||
                self.platListWidget.addItem(name)
 | 
			
		||||
                name_param = name
 | 
			
		||||
            else:
 | 
			
		||||
                self.platListWidget.addItem(f"Unnamed [ID: {index}]")
 | 
			
		||||
                name_param = None
 | 
			
		||||
 | 
			
		||||
            self.plat_list.append(Platform(
 | 
			
		||||
                index, [[0], [0]], name_param, True, 0, 0, None, 0, None, None)
 | 
			
		||||
            )
 | 
			
		||||
            self.plat_edit_win = PlatformEdit(self.plat_list[index])
 | 
			
		||||
            plat_edit_res = self.plat_edit_win.exec()
 | 
			
		||||
            if plat_edit_res == QDialog.DialogCode.Rejected:
 | 
			
		||||
                self.plat_list.pop()
 | 
			
		||||
                self.platListWidget.takeItem(self.platListWidget.count() - 1)
 | 
			
		||||
                self.platListWidget.setCurrentRow(index - 1)
 | 
			
		||||
            else:
 | 
			
		||||
                index = len(self.plat_list) - 1
 | 
			
		||||
                if self.plat_list[index].plat_name is not None:
 | 
			
		||||
                    item = self.platListWidget.takeItem(index)
 | 
			
		||||
                    item.setText(self.plat_list[index].plat_name)
 | 
			
		||||
                    self.platListWidget.insertItem(index, item)
 | 
			
		||||
                self.platListWidget.setCurrentRow(index)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    def get_enabled_state(self):
 | 
			
		||||
        index = self.platListWidget.currentRow()
 | 
			
		||||
        if len(self.plat_list) > 0:
 | 
			
		||||
            is_enabled = self.plat_list[index].enabled
 | 
			
		||||
            if is_enabled:
 | 
			
		||||
                self.plat_enabled_check.setChecked(True)
 | 
			
		||||
            else:
 | 
			
		||||
                self.plat_enabled_check.setChecked(False)
 | 
			
		||||
        else:
 | 
			
		||||
            self.plat_enabled_check.setChecked(False)
 | 
			
		||||
 | 
			
		||||
        if index in self.db_indices:
 | 
			
		||||
            self.del_plat_but.setEnabled(True)
 | 
			
		||||
        else:
 | 
			
		||||
            self.del_plat_but.setEnabled(False)
 | 
			
		||||
 | 
			
		||||
        valid_selection = False
 | 
			
		||||
        for plat in self.plat_list:
 | 
			
		||||
            if plat.plat_id == index:
 | 
			
		||||
                valid_selection = True
 | 
			
		||||
        if valid_selection:
 | 
			
		||||
            self.edit_plat_but.setEnabled(True)
 | 
			
		||||
            self.plat_enabled_check.setEnabled(True)
 | 
			
		||||
        else:
 | 
			
		||||
            self.edit_plat_but.setEnabled(False)
 | 
			
		||||
            self.plat_enabled_check.setEnabled(False)
 | 
			
		||||
 | 
			
		||||
    def edit_platform(self):
 | 
			
		||||
        index = self.platListWidget.currentRow()
 | 
			
		||||
        self.plat_edit_win = PlatformEdit(self.plat_list[index])
 | 
			
		||||
        plat_edit_res = self.plat_edit_win.exec()
 | 
			
		||||
        if plat_edit_res == QDialog.DialogCode.Accepted:
 | 
			
		||||
            item = self.platListWidget.takeItem(index)
 | 
			
		||||
            item.setText(self.plat_list[index].plat_name)
 | 
			
		||||
            self.platListWidget.insertItem(index, item)
 | 
			
		||||
            self.platListWidget.setCurrentRow(index)
 | 
			
		||||
 | 
			
		||||
    def save_platforms(self):
 | 
			
		||||
        self.db.write_platforms(self.plat_list)
 | 
			
		||||
        self.update_plat_list()
 | 
			
		||||
        self.db_indices = [x for x in range(len(self.plat_name_list))]
 | 
			
		||||
 | 
			
		||||
    def toggle_platform_state(self):
 | 
			
		||||
        index = self.platListWidget.currentRow()
 | 
			
		||||
        state = self.plat_enabled_check.isChecked()
 | 
			
		||||
        if len(self.plat_list) > 0 and index >= 0:
 | 
			
		||||
            self.plat_list[index].enabled = state
 | 
			
		||||
 | 
			
		||||
    def remove_platform(self):
 | 
			
		||||
        index = self.platListWidget.currentRow()
 | 
			
		||||
        del_dialog_res = self.del_plat_dialog.exec()
 | 
			
		||||
        if del_dialog_res == QMessageBox.StandardButton.Yes:
 | 
			
		||||
            self.db.remove_platform(index)
 | 
			
		||||
            self.update_plat_list()
 | 
			
		||||
 | 
			
		||||
    def showEvent(self, event):
 | 
			
		||||
        self.get_enabled_state()
 | 
			
		||||
@@ -2,12 +2,24 @@ import os.path
 | 
			
		||||
import sys
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# If using PyInstaller, use it's temporary path, otherwise use cwd
 | 
			
		||||
# Credit: https://stackoverflow.com/questions/7674790/bundling-data-files-with-pyinstaller-onefile/13790741#13790741
 | 
			
		||||
# Returns the correct path for Nuitka onefile mode, standalone mode or normal Python
 | 
			
		||||
# Credit: https://nuitka.net/user-documentation/common-issue-solutions.html#onefile-finding-files
 | 
			
		||||
def get_res_path(relative_path):
 | 
			
		||||
    try:
 | 
			
		||||
        base_path = sys._MEIPASS
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        base_path = os.path.abspath(".")
 | 
			
		||||
    path_a = ""
 | 
			
		||||
 | 
			
		||||
    return os.path.join(base_path, relative_path)
 | 
			
		||||
    try:
 | 
			
		||||
        path_a = os.path.join(sys.__compiled__.containing_dir, relative_path)
 | 
			
		||||
    except AttributeError:
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    path_b = os.path.join(os.path.dirname(__file__), relative_path)
 | 
			
		||||
    path_c = os.path.join(os.path.dirname(sys.argv[0]), relative_path)
 | 
			
		||||
 | 
			
		||||
    if os.path.isfile(path_a):
 | 
			
		||||
        return path_a
 | 
			
		||||
    elif os.path.isfile(path_b):
 | 
			
		||||
        return path_b
 | 
			
		||||
    elif os.path.isfile(path_c):
 | 
			
		||||
        return path_c
 | 
			
		||||
    else:
 | 
			
		||||
        return os.path.join(os.path.abspath("."), relative_path)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ class FastEditQDoubleSpinBox(QDoubleSpinBox):
 | 
			
		||||
        QTimer.singleShot(0, self.selectAll)
 | 
			
		||||
        super(FastEditQDoubleSpinBox, self).focusInEvent(e)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FastEditQSpinBox(QSpinBox):
 | 
			
		||||
    def focusInEvent(self, e):
 | 
			
		||||
        QTimer.singleShot(0, self.selectAll)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								src/widgets/mpl_widget.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/widgets/mpl_widget.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
 | 
			
		||||
from matplotlib.figure import Figure
 | 
			
		||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MplWidget(QWidget):
 | 
			
		||||
    def __init__(self, parent=None):
 | 
			
		||||
        super().__init__(parent)
 | 
			
		||||
        self.canvas = FigureCanvasQTAgg(Figure(figsize=(15, 8), dpi=100))
 | 
			
		||||
        vertical_layout = QVBoxLayout()
 | 
			
		||||
        vertical_layout.addWidget(self.canvas)
 | 
			
		||||
        self.canvas.axes = self.canvas.figure.add_subplot(1, 1, 1)
 | 
			
		||||
        self.setLayout(vertical_layout)
 | 
			
		||||
		Reference in New Issue
	
	Block a user