Compare commits
14 Commits
v0.1.1
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c59c704da3 | ||
|
|
c858706d48 | ||
|
|
724440dcb7 | ||
|
|
2f594dac0a | ||
|
|
939b81385e | ||
|
|
84b69b935b | ||
| a6e84552da | |||
| 015df38328 | |||
| db85277f26 | |||
| 679aaec6d4 | |||
| deeb6376f7 | |||
| 103fc28bc6 | |||
| 79c85ed2e5 | |||
| 38358e431d |
2
.gitmodules
vendored
@@ -15,4 +15,4 @@
|
|||||||
url = https://git.kindred-systems.com/forbes/ztools.git
|
url = https://git.kindred-systems.com/forbes/ztools.git
|
||||||
[submodule "mods/silo"]
|
[submodule "mods/silo"]
|
||||||
path = mods/silo
|
path = mods/silo
|
||||||
url = https://git.kindred-systems.com/kindred/silo.git
|
url = https://git.kindred-systems.com/kindred/silo-mod.git
|
||||||
|
|||||||
@@ -1,6 +1,55 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
<svg
|
||||||
<path d="M12 20 L8 20 A4 4 0 0 1 8 12 L12 12" fill="none" stroke="#89b4fa" stroke-width="2"/>
|
viewBox="0 0 32 32"
|
||||||
<path d="M20 12 L24 12 A4 4 0 0 1 24 20 L20 20" fill="none" stroke="#74c7ec" stroke-width="2"/>
|
version="1.1"
|
||||||
<line x1="12" y1="16" x2="20" y2="16" stroke="#89b4fa" stroke-width="2"/>
|
id="svg3"
|
||||||
|
sodipodi:docname="Link.svg"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs3" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview3"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="25.632621"
|
||||||
|
inkscape:cx="14.005591"
|
||||||
|
inkscape:cy="15.839192"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg3" />
|
||||||
|
<rect
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
|
<path
|
||||||
|
d="M 12.013793,20 H 8.0137931 a 4,4 0 0 1 0,-8 h 3.9999999"
|
||||||
|
fill="none"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
d="m 20.013793,12 h 4 a 4,4 0 0 1 0,8 h -4"
|
||||||
|
fill="none"
|
||||||
|
stroke="#74c7ec"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path2"
|
||||||
|
style="stroke:#89b4fa;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#89b4fa;stroke-width:1.77369;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 9.6965515,16 H 22.303449"
|
||||||
|
id="path3" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 393 B After Width: | Height: | Size: 1.5 KiB |
@@ -1,7 +1,69 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
<svg
|
||||||
<path d="M10 18 L6 18 A4 4 0 0 1 6 10 L10 10" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
|
viewBox="0 0 32 32"
|
||||||
<path d="M18 10 L22 10 A4 4 0 0 1 22 18 L18 18" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
|
version="1.1"
|
||||||
<path d="M24 22 L24 28 L16 24 Z" fill="#a6e3a1"/>
|
id="svg3"
|
||||||
<line x1="24" y1="28" x2="24" y2="20" stroke="#a6e3a1" stroke-width="2"/>
|
sodipodi:docname="LinkImport.svg"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs3" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview3"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="25.632621"
|
||||||
|
inkscape:cx="14.005591"
|
||||||
|
inkscape:cy="15.800179"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg3" />
|
||||||
|
<rect
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
|
<path
|
||||||
|
d="M 12.013793,20 H 8.0137931 a 4,4 0 0 1 0,-8 h 3.9999999"
|
||||||
|
fill="none"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
d="m 20.013793,12 h 4 a 4,4 0 0 1 0,8 h -4"
|
||||||
|
fill="none"
|
||||||
|
stroke="#74c7ec"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path2"
|
||||||
|
style="stroke:#89b4fa;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#89b4fa;stroke-width:1.77369;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 9.6965515,16 H 22.303449"
|
||||||
|
id="path3" />
|
||||||
|
<circle
|
||||||
|
cx="25.513792"
|
||||||
|
cy="-9.2321157"
|
||||||
|
fill="#a6e3a1"
|
||||||
|
id="circle2"
|
||||||
|
style="stroke-width:0.999999"
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
r="5" />
|
||||||
|
<path
|
||||||
|
d="M 25.51379,11.732116 V 6.7321179 m -2.499999,2.4999988 h 5"
|
||||||
|
stroke="#1e1e2e"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
id="path3-7" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 449 B After Width: | Height: | Size: 1.9 KiB |
@@ -1,7 +1,62 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
<svg
|
||||||
<path d="M12 18 L8 18 A4 4 0 0 1 8 10 L12 10" fill="none" stroke="#89b4fa" stroke-width="1.5"/>
|
viewBox="0 0 32 32"
|
||||||
<path d="M18 10 L22 10 A4 4 0 0 1 22 18 L18 18" fill="none" stroke="#74c7ec" stroke-width="1.5"/>
|
version="1.1"
|
||||||
<line x1="12" y1="14" x2="18" y2="14" stroke="#89b4fa" stroke-width="1.5"/>
|
id="svg3"
|
||||||
<rect x="14" y="20" width="12" height="8" rx="1" fill="none" stroke="#f9e2af" stroke-width="1.5" stroke-dasharray="2,2"/>
|
sodipodi:docname="LinkSelect.svg"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs3" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview3"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="25.632621"
|
||||||
|
inkscape:cx="14.005591"
|
||||||
|
inkscape:cy="15.800179"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg3" />
|
||||||
|
<rect
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
|
<path
|
||||||
|
d="M 12.013793,20 H 8.0137931 a 4,4 0 0 1 0,-8 h 3.9999999"
|
||||||
|
fill="none"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
d="m 20.013793,12 h 4 a 4,4 0 0 1 0,8 h -4"
|
||||||
|
fill="none"
|
||||||
|
stroke="#74c7ec"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path2"
|
||||||
|
style="stroke:#89b4fa;stroke-opacity:1" />
|
||||||
|
<path
|
||||||
|
style="fill:none;stroke:#89b4fa;stroke-width:1.77369;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 9.6965515,16 H 22.303449"
|
||||||
|
id="path3" />
|
||||||
|
<rect
|
||||||
|
style="fill:none;stroke:#fab387;stroke-width:1.056;stroke-dasharray:1.05599999, 2.11199999;stroke-linejoin:round;stroke-linecap:round;stroke-dashoffset:15.41759968;stroke-opacity:1"
|
||||||
|
id="rect2"
|
||||||
|
width="28.12822"
|
||||||
|
height="14.785847"
|
||||||
|
x="1.9358902"
|
||||||
|
y="8.6070766" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 523 B After Width: | Height: | Size: 1.8 KiB |
@@ -1,12 +1,78 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
|
<svg
|
||||||
<!-- Block with rounded edge -->
|
viewBox="0 0 32 32"
|
||||||
<path d="M6 24 L6 10 L16 6 L26 10 L26 24 L16 28 Z" fill="#45475a" stroke="#89b4fa" stroke-width="1.5"/>
|
version="1.1"
|
||||||
<path d="M6 10 L16 14 L26 10" stroke="#89b4fa" stroke-width="1.5" fill="none"/>
|
id="svg5"
|
||||||
<path d="M16 14 L16 28" stroke="#89b4fa" stroke-width="1.5"/>
|
sodipodi:docname="PartDesign_Fillet.svg"
|
||||||
<!-- Fillet radius on edge -->
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
<path d="M6 10 Q10 10 12 14" stroke="#a6e3a1" stroke-width="2.5" fill="none" stroke-linecap="round"/>
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
<!-- Radius indicator -->
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
<path d="M6 10 L9 12" stroke="#cdd6f4" stroke-width="1" stroke-dasharray="2,1"/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<text x="4" y="8" font-family="sans-serif" font-size="6" fill="#a6e3a1">R</text>
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs5" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview5"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="22.627417"
|
||||||
|
inkscape:cx="2.8284271"
|
||||||
|
inkscape:cy="17.412504"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg5" />
|
||||||
|
<rect
|
||||||
|
x="2"
|
||||||
|
y="2"
|
||||||
|
width="28"
|
||||||
|
height="28"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
|
<!-- Block with chamfered edge -->
|
||||||
|
<path
|
||||||
|
d="M 6,24 6.1584709,15.885723 10.508095,8.6005883 16,6 26,10 V 24 L 16,28 Z M 20.923535,12.327039 26.006643,9.9805673 Z"
|
||||||
|
fill="#45475a"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path1"
|
||||||
|
sodipodi:nodetypes="ccccccccccc" />
|
||||||
|
<path
|
||||||
|
d="M 16,19.88 V 28"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path3"
|
||||||
|
sodipodi:nodetypes="cc" />
|
||||||
|
<path
|
||||||
|
d="M 5.721457,16 10.416889,7.8973687 21,12 l -3.708852,3.943415 -1.158565,4.20175 z"
|
||||||
|
fill="#a6e3a1"
|
||||||
|
fill-opacity="0.3"
|
||||||
|
id="path5"
|
||||||
|
sodipodi:nodetypes="cccccc"
|
||||||
|
style="opacity:1;fill:#a6e3a1;fill-opacity:1" />
|
||||||
|
<!-- Chamfer cut on edge -->
|
||||||
|
<!-- Chamfer face -->
|
||||||
|
<path
|
||||||
|
d="m 25.554002,10.020735 c 0,0 -2.839088,1.13525 -4.950437,2.544214 -1.652011,1.102435 -2.601422,2.423855 -3.100562,3.159378 -1.011406,1.490386 -1.47202,3.667857 -1.538841,4.165681 -0.0066,0.04949 -0.01392,7.861307 -0.01392,7.861307 m 7.653323,-14.186366"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5847"
|
||||||
|
id="path3-0"
|
||||||
|
sodipodi:nodetypes="csssc"
|
||||||
|
style="fill:none" />
|
||||||
|
<path
|
||||||
|
d="m 16.23145,6.006509 c 0,0 -3.524098,1.0247646 -5.635447,2.4337286 -1.6520107,1.1024351 -2.6014217,2.4238554 -3.1005617,3.1593784 -1.011406,1.490386 -1.47202,3.667857 -1.538841,4.165681 -0.0066,0.04949 0.2070509,8.612608 0.2070509,8.612608"
|
||||||
|
stroke="#89b4fa"
|
||||||
|
stroke-width="1.5847"
|
||||||
|
id="path3-0-6"
|
||||||
|
sodipodi:nodetypes="csssc"
|
||||||
|
style="fill:none" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 753 B After Width: | Height: | Size: 2.6 KiB |
@@ -1,13 +1,64 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
|
<svg
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
version="1.1"
|
||||||
|
id="svg3"
|
||||||
|
sodipodi:docname="PartDesign_NewSketch.svg"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs3" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview3"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="36.25"
|
||||||
|
inkscape:cx="16"
|
||||||
|
inkscape:cy="16"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg3" />
|
||||||
|
<rect
|
||||||
|
x="2"
|
||||||
|
y="2"
|
||||||
|
width="28"
|
||||||
|
height="28"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
<!-- Sketch plane -->
|
<!-- Sketch plane -->
|
||||||
<path d="M4 20 L16 12 L28 20 L16 28 Z" fill="#45475a" stroke="#f9e2af" stroke-width="1.5"/>
|
<path
|
||||||
|
d="m 4,17.158621 12,-8.0000003 12,8.0000003 -12,8 z"
|
||||||
|
fill="#45475a"
|
||||||
|
stroke="#f9e2af"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="path1" />
|
||||||
<!-- Grid on plane -->
|
<!-- Grid on plane -->
|
||||||
<line x1="10" y1="20" x2="22" y2="20" stroke="#6c7086" stroke-width="0.75"/>
|
|
||||||
<line x1="16" y1="16" x2="16" y2="24" stroke="#6c7086" stroke-width="0.75"/>
|
|
||||||
<!-- Sketch geometry -->
|
<!-- Sketch geometry -->
|
||||||
<path d="M12 20 L16 16 L20 20 L16 22 Z" fill="none" stroke="#fab387" stroke-width="1.5"/>
|
|
||||||
<!-- Plus sign for "new" -->
|
<!-- Plus sign for "new" -->
|
||||||
<circle cx="24" cy="8" r="5" fill="#a6e3a1"/>
|
<circle
|
||||||
<path d="M24 5.5 L24 10.5 M21.5 8 L26.5 8" stroke="#1e1e2e" stroke-width="1.5" stroke-linecap="round"/>
|
cx="23.092838"
|
||||||
|
cy="-10.553846"
|
||||||
|
fill="#a6e3a1"
|
||||||
|
id="circle2"
|
||||||
|
style="stroke-width:0.999999"
|
||||||
|
transform="scale(1,-1)"
|
||||||
|
r="5" />
|
||||||
|
<path
|
||||||
|
d="M 23.092837,13.053846 V 8.0538483 m -2.499999,2.4999987 h 5"
|
||||||
|
stroke="#1e1e2e"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
id="path3-7" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 740 B After Width: | Height: | Size: 1.7 KiB |
@@ -1,9 +1,98 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
<svg
|
||||||
<circle cx="16" cy="13" r="7" fill="none" stroke="#f9e2af" stroke-width="1.5"/>
|
viewBox="0 0 32 32"
|
||||||
<path d="M12 20 L12 24 L20 24 L20 20" fill="none" stroke="#fab387" stroke-width="1.5"/>
|
version="1.1"
|
||||||
<line x1="13" y1="27" x2="19" y2="27" stroke="#fab387" stroke-width="1.5"/>
|
id="svg4"
|
||||||
<line x1="16" y1="6" x2="16" y2="4" stroke="#f9e2af" stroke-width="1.5"/>
|
sodipodi:docname="bulb.svg"
|
||||||
<line x1="8" y1="8" x2="6" y2="6" stroke="#f9e2af" stroke-width="1.5"/>
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
<line x1="24" y1="8" x2="26" y2="6" stroke="#f9e2af" stroke-width="1.5"/>
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs4" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview4"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="18.125"
|
||||||
|
inkscape:cx="12.634483"
|
||||||
|
inkscape:cy="17.048276"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg4" />
|
||||||
|
<rect
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
|
<path
|
||||||
|
d="m 13.398145,19.664706 v 4.206463 h 5.120952 v -4.206463"
|
||||||
|
fill="none"
|
||||||
|
stroke="#fab387"
|
||||||
|
stroke-width="1.23069"
|
||||||
|
id="path1" />
|
||||||
|
<line
|
||||||
|
x1="12.95862"
|
||||||
|
y1="26.492428"
|
||||||
|
x2="18.958622"
|
||||||
|
y2="26.492428"
|
||||||
|
stroke="#fab387"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="line1"
|
||||||
|
style="stroke-width:1.3;stroke-dasharray:none" />
|
||||||
|
<line
|
||||||
|
x1="12.95862"
|
||||||
|
y1="28.743103"
|
||||||
|
x2="18.958622"
|
||||||
|
y2="28.743103"
|
||||||
|
stroke="#fab387"
|
||||||
|
stroke-width="1.5"
|
||||||
|
id="line1-2"
|
||||||
|
style="stroke-width:1.2;stroke-dasharray:none" />
|
||||||
|
<line
|
||||||
|
x1="16"
|
||||||
|
y1="8.2896547"
|
||||||
|
x2="16"
|
||||||
|
y2="4"
|
||||||
|
stroke="#f9e2af"
|
||||||
|
stroke-width="2.19678"
|
||||||
|
id="line2"
|
||||||
|
style="stroke-width:1.45;stroke-dasharray:none" />
|
||||||
|
<line
|
||||||
|
x1="21.842089"
|
||||||
|
y1="9.2134304"
|
||||||
|
x2="24.801268"
|
||||||
|
y2="6.1714916"
|
||||||
|
stroke="#f9e2af"
|
||||||
|
stroke-width="2.25021"
|
||||||
|
id="line4"
|
||||||
|
style="stroke-width:1.45;stroke-dasharray:none" />
|
||||||
|
<line
|
||||||
|
x1="9.5786028"
|
||||||
|
y1="9.2134304"
|
||||||
|
x2="6.6194234"
|
||||||
|
y2="6.1714916"
|
||||||
|
stroke="#f9e2af"
|
||||||
|
stroke-width="2.25021"
|
||||||
|
id="line4-5"
|
||||||
|
style="stroke-width:1.45;stroke-dasharray:none" />
|
||||||
|
<ellipse
|
||||||
|
cx="15.710345"
|
||||||
|
cy="15.234483"
|
||||||
|
fill="none"
|
||||||
|
stroke="#f9e2af"
|
||||||
|
stroke-width="1.07682"
|
||||||
|
id="circle1"
|
||||||
|
rx="5.0736599"
|
||||||
|
ry="4.977108" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 599 B After Width: | Height: | Size: 2.4 KiB |
@@ -1,5 +1,48 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect width="32" height="32" rx="4" fill="#313244"/>
|
<svg
|
||||||
<path d="M22 10 A8 8 0 1 1 10 10" fill="none" stroke="#cba6f7" stroke-width="2"/>
|
viewBox="0 0 32 32"
|
||||||
<path d="M22 6 L22 14 L14 10 Z" fill="#b4befe"/>
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
sodipodi:docname="button_rotate.svg"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview2"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="25.632621"
|
||||||
|
inkscape:cx="7.8025576"
|
||||||
|
inkscape:cy="17.282665"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<rect
|
||||||
|
width="32"
|
||||||
|
height="32"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
|
<path
|
||||||
|
d="M22 10 A8 8 0 1 1 10 10"
|
||||||
|
fill="none"
|
||||||
|
stroke="#cba6f7"
|
||||||
|
stroke-width="2"
|
||||||
|
id="path1" />
|
||||||
|
<path
|
||||||
|
d="M 26.440057,9.4371459 19.892123,14.033292 18.569944,5.1872849 Z"
|
||||||
|
fill="#b4befe"
|
||||||
|
id="path2" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 258 B After Width: | Height: | Size: 1.3 KiB |
@@ -1,10 +1,64 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<rect x="2" y="2" width="28" height="28" rx="4" fill="#313244"/>
|
<svg
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
version="1.1"
|
||||||
|
id="svg2"
|
||||||
|
sodipodi:docname="help-browser.svg"
|
||||||
|
inkscape:version="1.4.3 (0d15f75042, 2025-12-25)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg">
|
||||||
|
<defs
|
||||||
|
id="defs2" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="namedview2"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:zoom="36.25"
|
||||||
|
inkscape:cx="16"
|
||||||
|
inkscape:cy="16"
|
||||||
|
inkscape:window-width="2560"
|
||||||
|
inkscape:window-height="1371"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="svg2" />
|
||||||
|
<rect
|
||||||
|
x="2"
|
||||||
|
y="2"
|
||||||
|
width="28"
|
||||||
|
height="28"
|
||||||
|
rx="4"
|
||||||
|
fill="#313244"
|
||||||
|
id="rect1" />
|
||||||
<!-- Circle background -->
|
<!-- Circle background -->
|
||||||
<circle cx="16" cy="16" r="10" fill="#45475a" stroke="#cba6f7" stroke-width="2"/>
|
<circle
|
||||||
|
cx="16.020691"
|
||||||
|
cy="15.889656"
|
||||||
|
r="10"
|
||||||
|
fill="#45475a"
|
||||||
|
stroke="#cba6f7"
|
||||||
|
stroke-width="2"
|
||||||
|
id="circle1" />
|
||||||
<!-- Question mark -->
|
<!-- Question mark -->
|
||||||
<path d="M13 12 C13 9 15 8 17 8 C19 8 21 9.5 21 12 C21 14 19 14.5 17 15.5 L17 18"
|
<path
|
||||||
stroke="#b4befe" stroke-width="2.5" stroke-linecap="round" fill="none"/>
|
d="m 12.000002,12.965517 c 0,-2.9999996 2,-3.9999998 4,-3.9999998 2,0 4,1.4999998 4,3.9999998 0,2 -2,2.5 -4,3.5 v 2.5"
|
||||||
|
stroke="#b4befe"
|
||||||
|
stroke-width="2.5"
|
||||||
|
stroke-linecap="round"
|
||||||
|
fill="none"
|
||||||
|
id="path1" />
|
||||||
<!-- Dot -->
|
<!-- Dot -->
|
||||||
<circle cx="17" cy="22" r="1.5" fill="#b4befe"/>
|
<circle
|
||||||
|
cx="16"
|
||||||
|
cy="22.965519"
|
||||||
|
r="1.5"
|
||||||
|
fill="#b4befe"
|
||||||
|
id="circle2" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 504 B After Width: | Height: | Size: 1.6 KiB |
@@ -22,7 +22,7 @@ requirements:
|
|||||||
- noqt5
|
- noqt5
|
||||||
- python>=3.11,<3.12
|
- python>=3.11,<3.12
|
||||||
- qt6-main>=6.8,<6.9
|
- qt6-main>=6.8,<6.9
|
||||||
- swig
|
- swig >=4.0,<4.4
|
||||||
|
|
||||||
- if: linux and x86_64
|
- if: linux and x86_64
|
||||||
then:
|
then:
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
#include "Workbench.h"
|
#include "Workbench.h"
|
||||||
#include "WorkbenchManager.h"
|
#include "WorkbenchManager.h"
|
||||||
#include "WorkbenchSelector.h"
|
#include "WorkbenchSelector.h"
|
||||||
|
#include "OriginSelectorWidget.h"
|
||||||
#include "ShortcutManager.h"
|
#include "ShortcutManager.h"
|
||||||
#include "Tools.h"
|
#include "Tools.h"
|
||||||
|
|
||||||
@@ -1470,4 +1471,25 @@ void WindowAction::addTo(QWidget* widget)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
OriginSelectorAction::OriginSelectorAction(Command* pcCmd, QObject* parent)
|
||||||
|
: Action(pcCmd, parent)
|
||||||
|
{}
|
||||||
|
|
||||||
|
OriginSelectorAction::~OriginSelectorAction() = default;
|
||||||
|
|
||||||
|
void OriginSelectorAction::addTo(QWidget* widget)
|
||||||
|
{
|
||||||
|
if (widget->inherits("QToolBar")) {
|
||||||
|
auto* toolbar = static_cast<QToolBar*>(widget);
|
||||||
|
auto* selector = new OriginSelectorWidget(widget);
|
||||||
|
toolbar->addWidget(selector);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For menus, just add the action
|
||||||
|
widget->addAction(action());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_Action.cpp"
|
#include "moc_Action.cpp"
|
||||||
|
|||||||
@@ -421,6 +421,25 @@ private:
|
|||||||
Q_DISABLE_COPY(WindowAction)
|
Q_DISABLE_COPY(WindowAction)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// --------------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Action for origin selector widget in toolbars.
|
||||||
|
* Creates OriginSelectorWidget when added to a toolbar.
|
||||||
|
*/
|
||||||
|
class GuiExport OriginSelectorAction: public Action
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OriginSelectorAction(Command* pcCmd, QObject* parent = nullptr);
|
||||||
|
~OriginSelectorAction() override;
|
||||||
|
void addTo(QWidget* widget) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(OriginSelectorAction)
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace Gui
|
} // namespace Gui
|
||||||
|
|
||||||
#endif // GUI_ACTION_H
|
#endif // GUI_ACTION_H
|
||||||
|
|||||||
@@ -1022,6 +1022,7 @@ void Application::createStandardOperations()
|
|||||||
Gui::CreateStructureCommands();
|
Gui::CreateStructureCommands();
|
||||||
Gui::CreateTestCommands();
|
Gui::CreateTestCommands();
|
||||||
Gui::CreateLinkCommands();
|
Gui::CreateLinkCommands();
|
||||||
|
Gui::CreateOriginCommands();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc)
|
void Application::slotNewDocument(const App::Document& Doc, bool isMainDoc)
|
||||||
|
|||||||
@@ -529,6 +529,7 @@ SET(Command_CPP_SRCS
|
|||||||
CommandFeat.cpp
|
CommandFeat.cpp
|
||||||
CommandMacro.cpp
|
CommandMacro.cpp
|
||||||
CommandStd.cpp
|
CommandStd.cpp
|
||||||
|
CommandOrigin.cpp
|
||||||
CommandWindow.cpp
|
CommandWindow.cpp
|
||||||
CommandTest.cpp
|
CommandTest.cpp
|
||||||
CommandView.cpp
|
CommandView.cpp
|
||||||
@@ -592,6 +593,7 @@ SET(Dialog_CPP_SRCS
|
|||||||
Dialogs/DlgObjectSelection.cpp
|
Dialogs/DlgObjectSelection.cpp
|
||||||
Dialogs/DlgAddProperty.cpp
|
Dialogs/DlgAddProperty.cpp
|
||||||
VectorListEditor.cpp
|
VectorListEditor.cpp
|
||||||
|
OriginManagerDialog.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
SET(Dialog_HPP_SRCS
|
SET(Dialog_HPP_SRCS
|
||||||
@@ -634,6 +636,7 @@ SET(Dialog_HPP_SRCS
|
|||||||
Dialogs/DlgObjectSelection.h
|
Dialogs/DlgObjectSelection.h
|
||||||
Dialogs/DlgAddProperty.h
|
Dialogs/DlgAddProperty.h
|
||||||
VectorListEditor.h
|
VectorListEditor.h
|
||||||
|
OriginManagerDialog.h
|
||||||
)
|
)
|
||||||
|
|
||||||
SET(Dialog_SRCS
|
SET(Dialog_SRCS
|
||||||
@@ -1236,6 +1239,7 @@ SET(Widget_CPP_SRCS
|
|||||||
ElideCheckBox.cpp
|
ElideCheckBox.cpp
|
||||||
FontScaledSVG.cpp
|
FontScaledSVG.cpp
|
||||||
SplitButton.cpp
|
SplitButton.cpp
|
||||||
|
OriginSelectorWidget.cpp
|
||||||
)
|
)
|
||||||
SET(Widget_HPP_SRCS
|
SET(Widget_HPP_SRCS
|
||||||
ComboLinks.h
|
ComboLinks.h
|
||||||
@@ -1262,6 +1266,7 @@ SET(Widget_HPP_SRCS
|
|||||||
ElideCheckBox.h
|
ElideCheckBox.h
|
||||||
FontScaledSVG.h
|
FontScaledSVG.h
|
||||||
SplitButton.h
|
SplitButton.h
|
||||||
|
OriginSelectorWidget.h
|
||||||
)
|
)
|
||||||
SET(Widget_SRCS
|
SET(Widget_SRCS
|
||||||
${Widget_CPP_SRCS}
|
${Widget_CPP_SRCS}
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ void CreateWindowStdCommands();
|
|||||||
void CreateStructureCommands();
|
void CreateStructureCommands();
|
||||||
void CreateTestCommands();
|
void CreateTestCommands();
|
||||||
void CreateLinkCommands();
|
void CreateLinkCommands();
|
||||||
|
void CreateOriginCommands();
|
||||||
|
|
||||||
|
|
||||||
/** The CommandBase class
|
/** The CommandBase class
|
||||||
|
|||||||
@@ -49,7 +49,9 @@
|
|||||||
#include "Control.h"
|
#include "Control.h"
|
||||||
#include "DockWindowManager.h"
|
#include "DockWindowManager.h"
|
||||||
#include "FileDialog.h"
|
#include "FileDialog.h"
|
||||||
|
#include "FileOrigin.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
#include "OriginManager.h"
|
||||||
#include "Selection.h"
|
#include "Selection.h"
|
||||||
#include "Dialogs/DlgObjectSelection.h"
|
#include "Dialogs/DlgObjectSelection.h"
|
||||||
#include "Dialogs/DlgProjectInformationImp.h"
|
#include "Dialogs/DlgProjectInformationImp.h"
|
||||||
@@ -95,81 +97,25 @@ void StdCmdOpen::activated(int iMsg)
|
|||||||
{
|
{
|
||||||
Q_UNUSED(iMsg);
|
Q_UNUSED(iMsg);
|
||||||
|
|
||||||
// fill the list of registered endings
|
// Delegate to current origin
|
||||||
QString formatList;
|
FileOrigin* origin = OriginManager::instance()->currentOrigin();
|
||||||
const char* supported = QT_TR_NOOP("Supported formats");
|
if (!origin) {
|
||||||
const char* allFiles = QT_TR_NOOP("All files (*.*)");
|
|
||||||
formatList = QObject::tr(supported);
|
|
||||||
formatList += QLatin1String(" (");
|
|
||||||
|
|
||||||
std::vector<std::string> filetypes = App::GetApplication().getImportTypes();
|
|
||||||
// Make sure FCStd is the very first fileformat
|
|
||||||
auto it = std::ranges::find(filetypes, "FCStd");
|
|
||||||
if (it != filetypes.end()) {
|
|
||||||
filetypes.erase(it);
|
|
||||||
filetypes.insert(filetypes.begin(), "FCStd");
|
|
||||||
}
|
|
||||||
for (it = filetypes.begin(); it != filetypes.end(); ++it) {
|
|
||||||
formatList += QLatin1String(" *.");
|
|
||||||
formatList += QLatin1String(it->c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
formatList += QLatin1String(");;");
|
|
||||||
|
|
||||||
std::map<std::string, std::string> FilterList = App::GetApplication().getImportFilters();
|
|
||||||
std::map<std::string, std::string>::iterator jt;
|
|
||||||
// Make sure the format name for FCStd is the very first in the list
|
|
||||||
for (jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
|
|
||||||
if (jt->first.find("*.FCStd") != std::string::npos) {
|
|
||||||
formatList += QLatin1String(jt->first.c_str());
|
|
||||||
formatList += QLatin1String(";;");
|
|
||||||
FilterList.erase(jt);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
|
|
||||||
formatList += QLatin1String(jt->first.c_str());
|
|
||||||
formatList += QLatin1String(";;");
|
|
||||||
}
|
|
||||||
formatList += QObject::tr(allFiles);
|
|
||||||
|
|
||||||
QString selectedFilter;
|
|
||||||
QStringList fileList = FileDialog::getOpenFileNames(
|
|
||||||
getMainWindow(),
|
|
||||||
QObject::tr("Open Document"),
|
|
||||||
QString(),
|
|
||||||
formatList,
|
|
||||||
&selectedFilter
|
|
||||||
);
|
|
||||||
if (fileList.isEmpty()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// load the files with the associated modules
|
// Check connection for origins that require authentication
|
||||||
SelectModule::Dict dict = SelectModule::importHandler(fileList, selectedFilter);
|
if (origin->requiresAuthentication() &&
|
||||||
if (dict.isEmpty()) {
|
origin->connectionState() != ConnectionState::Connected) {
|
||||||
QMessageBox::critical(
|
QMessageBox::warning(
|
||||||
getMainWindow(),
|
getMainWindow(),
|
||||||
qApp->translate("StdCmdOpen", "Cannot Open File"),
|
qApp->translate("StdCmdOpen", "Not Connected"),
|
||||||
qApp->translate("StdCmdOpen", "Loading the file %1 is not supported").arg(fileList.front())
|
qApp->translate("StdCmdOpen", "Please connect to %1 before opening files.")
|
||||||
|
.arg(QString::fromStdString(origin->name()))
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
|
|
||||||
|
|
||||||
// Set flag indicating that this load/restore has been initiated by the user (not by a macro)
|
origin->openDocumentInteractive();
|
||||||
getGuiApplication()->setStatus(Gui::Application::UserInitiatedOpenDocument, true);
|
|
||||||
|
|
||||||
getGuiApplication()->open(it.key().toUtf8(), it.value().toLatin1());
|
|
||||||
|
|
||||||
getGuiApplication()->setStatus(Gui::Application::UserInitiatedOpenDocument, false);
|
|
||||||
|
|
||||||
App::Document* doc = App::GetApplication().getActiveDocument();
|
|
||||||
|
|
||||||
getGuiApplication()->checkPartialRestore(doc);
|
|
||||||
getGuiApplication()->checkRestoreError(doc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
@@ -715,10 +661,28 @@ StdCmdNew::StdCmdNew()
|
|||||||
void StdCmdNew::activated(int iMsg)
|
void StdCmdNew::activated(int iMsg)
|
||||||
{
|
{
|
||||||
Q_UNUSED(iMsg);
|
Q_UNUSED(iMsg);
|
||||||
QString cmd;
|
|
||||||
cmd = QStringLiteral("App.newDocument()");
|
// Delegate to current origin
|
||||||
runCommand(Command::Doc, cmd.toUtf8());
|
FileOrigin* origin = OriginManager::instance()->currentOrigin();
|
||||||
doCommand(Command::Gui, "Gui.activeDocument().activeView().viewDefaultOrientation()");
|
if (!origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
App::Document* doc = origin->newDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default view orientation for the new document
|
||||||
|
auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||||
|
"User parameter:BaseApp/Preferences/View"
|
||||||
|
);
|
||||||
|
std::string default_view = hGrp->GetASCII("NewDocumentCameraOrientation", "Top");
|
||||||
|
doCommand(
|
||||||
|
Command::Gui,
|
||||||
|
"Gui.activeDocument().activeView().viewDefaultOrientation('%s',0)",
|
||||||
|
default_view.c_str()
|
||||||
|
);
|
||||||
|
|
||||||
ParameterGrp::handle hViewGrp = App::GetApplication().GetParameterGroupByPath(
|
ParameterGrp::handle hViewGrp = App::GetApplication().GetParameterGroupByPath(
|
||||||
"User parameter:BaseApp/Preferences/View"
|
"User parameter:BaseApp/Preferences/View"
|
||||||
@@ -749,12 +713,33 @@ StdCmdSave::StdCmdSave()
|
|||||||
void StdCmdSave::activated(int iMsg)
|
void StdCmdSave::activated(int iMsg)
|
||||||
{
|
{
|
||||||
Q_UNUSED(iMsg);
|
Q_UNUSED(iMsg);
|
||||||
doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"Save\")");
|
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use document's origin for save, not current origin
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
if (!origin) {
|
||||||
|
// Document has no origin yet - use current origin for first save
|
||||||
|
origin = OriginManager::instance()->currentOrigin();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to save the document
|
||||||
|
if (!origin->saveDocument(doc)) {
|
||||||
|
// If save failed (e.g., no filename), try SaveAs
|
||||||
|
origin->saveDocumentAsInteractive(doc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StdCmdSave::isActive()
|
bool StdCmdSave::isActive()
|
||||||
{
|
{
|
||||||
return getGuiApplication()->sendHasMsgToActiveView("Save");
|
return App::GetApplication().getActiveDocument() != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
@@ -778,12 +763,51 @@ StdCmdSaveAs::StdCmdSaveAs()
|
|||||||
void StdCmdSaveAs::activated(int iMsg)
|
void StdCmdSaveAs::activated(int iMsg)
|
||||||
{
|
{
|
||||||
Q_UNUSED(iMsg);
|
Q_UNUSED(iMsg);
|
||||||
doCommand(Command::Gui, "Gui.SendMsgToActiveView(\"SaveAs\")");
|
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* mgr = OriginManager::instance();
|
||||||
|
FileOrigin* currentOrigin = mgr->currentOrigin();
|
||||||
|
FileOrigin* docOrigin = mgr->originForDocument(doc);
|
||||||
|
|
||||||
|
if (!currentOrigin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine workflow based on document and target origins
|
||||||
|
OriginType currentType = currentOrigin->type();
|
||||||
|
OriginType docType = docOrigin ? docOrigin->type() : OriginType::Local;
|
||||||
|
|
||||||
|
if (docOrigin == currentOrigin || !docOrigin) {
|
||||||
|
// Same origin or new document - standard SaveAs
|
||||||
|
currentOrigin->saveDocumentAsInteractive(doc);
|
||||||
|
}
|
||||||
|
else if (currentType == OriginType::PLM && docType == OriginType::Local) {
|
||||||
|
// Local → PLM: Migration workflow
|
||||||
|
// The PLM origin's saveDocumentAsInteractive should handle this
|
||||||
|
currentOrigin->saveDocumentAsInteractive(doc);
|
||||||
|
}
|
||||||
|
else if (currentType == OriginType::Local && docType == OriginType::PLM) {
|
||||||
|
// PLM → Local: Export workflow
|
||||||
|
// Use local origin to save without PLM tracking
|
||||||
|
currentOrigin->saveDocumentAsInteractive(doc);
|
||||||
|
}
|
||||||
|
else if (currentType == OriginType::PLM && docType == OriginType::PLM) {
|
||||||
|
// PLM → Different PLM: Transfer workflow
|
||||||
|
currentOrigin->saveDocumentAsInteractive(doc);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Default: use current origin
|
||||||
|
currentOrigin->saveDocumentAsInteractive(doc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool StdCmdSaveAs::isActive()
|
bool StdCmdSaveAs::isActive()
|
||||||
{
|
{
|
||||||
return getGuiApplication()->sendHasMsgToActiveView("SaveAs");
|
return App::GetApplication().getActiveDocument() != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|||||||
277
src/Gui/CommandOrigin.cpp
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
* Copyright (c) 2025 Kindred Systems *
|
||||||
|
* *
|
||||||
|
* This file is part of FreeCAD. *
|
||||||
|
* *
|
||||||
|
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||||
|
* under the terms of the GNU Lesser General Public License as *
|
||||||
|
* published by the Free Software Foundation, either version 2.1 of the *
|
||||||
|
* License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* FreeCAD is distributed in the hope that it will be useful, but *
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||||
|
* Lesser General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Lesser General Public *
|
||||||
|
* License along with FreeCAD. If not, see *
|
||||||
|
* <https://www.gnu.org/licenses/>. *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file CommandOrigin.cpp
|
||||||
|
* @brief Unified origin commands that work with the current origin
|
||||||
|
*
|
||||||
|
* These commands delegate to the current FileOrigin's extended operations.
|
||||||
|
* They are only active when the current origin supports the required capability.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <App/Application.h>
|
||||||
|
#include <App/Document.h>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "BitmapFactory.h"
|
||||||
|
#include "Command.h"
|
||||||
|
#include "Document.h"
|
||||||
|
#include "FileOrigin.h"
|
||||||
|
#include "MainWindow.h"
|
||||||
|
#include "OriginManager.h"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace Gui;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Origin_Commit
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DEF_STD_CMD_A(OriginCmdCommit)
|
||||||
|
|
||||||
|
OriginCmdCommit::OriginCmdCommit()
|
||||||
|
: Command("Origin_Commit")
|
||||||
|
{
|
||||||
|
sGroup = "File";
|
||||||
|
sMenuText = QT_TR_NOOP("&Commit");
|
||||||
|
sToolTipText = QT_TR_NOOP("Commit changes as a new revision");
|
||||||
|
sWhatsThis = "Origin_Commit";
|
||||||
|
sStatusTip = sToolTipText;
|
||||||
|
sPixmap = "silo-commit";
|
||||||
|
sAccel = "Ctrl+Shift+C";
|
||||||
|
eType = AlterDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginCmdCommit::activated(int /*iMsg*/)
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
if (origin && origin->supportsRevisions()) {
|
||||||
|
origin->commitDocument(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OriginCmdCommit::isActive()
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
return origin && origin->supportsRevisions();
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Origin_Pull
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DEF_STD_CMD_A(OriginCmdPull)
|
||||||
|
|
||||||
|
OriginCmdPull::OriginCmdPull()
|
||||||
|
: Command("Origin_Pull")
|
||||||
|
{
|
||||||
|
sGroup = "File";
|
||||||
|
sMenuText = QT_TR_NOOP("&Pull");
|
||||||
|
sToolTipText = QT_TR_NOOP("Pull a specific revision from the origin");
|
||||||
|
sWhatsThis = "Origin_Pull";
|
||||||
|
sStatusTip = sToolTipText;
|
||||||
|
sPixmap = "silo-pull";
|
||||||
|
sAccel = "Ctrl+Shift+P";
|
||||||
|
eType = AlterDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginCmdPull::activated(int /*iMsg*/)
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
if (origin && origin->supportsRevisions()) {
|
||||||
|
origin->pullDocument(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OriginCmdPull::isActive()
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
return origin && origin->supportsRevisions();
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Origin_Push
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DEF_STD_CMD_A(OriginCmdPush)
|
||||||
|
|
||||||
|
OriginCmdPush::OriginCmdPush()
|
||||||
|
: Command("Origin_Push")
|
||||||
|
{
|
||||||
|
sGroup = "File";
|
||||||
|
sMenuText = QT_TR_NOOP("Pu&sh");
|
||||||
|
sToolTipText = QT_TR_NOOP("Push local changes to the origin");
|
||||||
|
sWhatsThis = "Origin_Push";
|
||||||
|
sStatusTip = sToolTipText;
|
||||||
|
sPixmap = "silo-push";
|
||||||
|
sAccel = "Ctrl+Shift+U";
|
||||||
|
eType = AlterDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginCmdPush::activated(int /*iMsg*/)
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
if (origin && origin->supportsRevisions()) {
|
||||||
|
origin->pushDocument(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OriginCmdPush::isActive()
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
return origin && origin->supportsRevisions();
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Origin_Info
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DEF_STD_CMD_A(OriginCmdInfo)
|
||||||
|
|
||||||
|
OriginCmdInfo::OriginCmdInfo()
|
||||||
|
: Command("Origin_Info")
|
||||||
|
{
|
||||||
|
sGroup = "File";
|
||||||
|
sMenuText = QT_TR_NOOP("&Info");
|
||||||
|
sToolTipText = QT_TR_NOOP("Show document information from origin");
|
||||||
|
sWhatsThis = "Origin_Info";
|
||||||
|
sStatusTip = sToolTipText;
|
||||||
|
sPixmap = "silo-info";
|
||||||
|
eType = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginCmdInfo::activated(int /*iMsg*/)
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
if (origin && origin->supportsPartNumbers()) {
|
||||||
|
origin->showInfo(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OriginCmdInfo::isActive()
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
return origin && origin->supportsPartNumbers();
|
||||||
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Origin_BOM
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DEF_STD_CMD_A(OriginCmdBOM)
|
||||||
|
|
||||||
|
OriginCmdBOM::OriginCmdBOM()
|
||||||
|
: Command("Origin_BOM")
|
||||||
|
{
|
||||||
|
sGroup = "File";
|
||||||
|
sMenuText = QT_TR_NOOP("&Bill of Materials");
|
||||||
|
sToolTipText = QT_TR_NOOP("Show Bill of Materials for this document");
|
||||||
|
sWhatsThis = "Origin_BOM";
|
||||||
|
sStatusTip = sToolTipText;
|
||||||
|
sPixmap = "silo-bom";
|
||||||
|
eType = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginCmdBOM::activated(int /*iMsg*/)
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
if (origin && origin->supportsBOM()) {
|
||||||
|
origin->showBOM(doc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OriginCmdBOM::isActive()
|
||||||
|
{
|
||||||
|
App::Document* doc = App::GetApplication().getActiveDocument();
|
||||||
|
if (!doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* origin = OriginManager::instance()->findOwningOrigin(doc);
|
||||||
|
return origin && origin->supportsBOM();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Command Registration
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
namespace Gui {
|
||||||
|
|
||||||
|
void CreateOriginCommands()
|
||||||
|
{
|
||||||
|
CommandManager& rcCmdMgr = Application::Instance->commandManager();
|
||||||
|
|
||||||
|
rcCmdMgr.addCommand(new OriginCmdCommit());
|
||||||
|
rcCmdMgr.addCommand(new OriginCmdPull());
|
||||||
|
rcCmdMgr.addCommand(new OriginCmdPush());
|
||||||
|
rcCmdMgr.addCommand(new OriginCmdInfo());
|
||||||
|
rcCmdMgr.addCommand(new OriginCmdBOM());
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Gui
|
||||||
@@ -132,6 +132,45 @@ Action* StdCmdWorkbench::createAction()
|
|||||||
return pcAction;
|
return pcAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// Std_Origin
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
DEF_STD_CMD_AC(StdCmdOrigin)
|
||||||
|
|
||||||
|
StdCmdOrigin::StdCmdOrigin()
|
||||||
|
: Command("Std_Origin")
|
||||||
|
{
|
||||||
|
sGroup = "File";
|
||||||
|
sMenuText = QT_TR_NOOP("&Origin");
|
||||||
|
sToolTipText = QT_TR_NOOP("Select file origin (Local Files, Silo, etc.)");
|
||||||
|
sWhatsThis = "Std_Origin";
|
||||||
|
sStatusTip = sToolTipText;
|
||||||
|
sPixmap = "folder";
|
||||||
|
eType = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StdCmdOrigin::activated(int /*iMsg*/)
|
||||||
|
{
|
||||||
|
// Action is handled by OriginSelectorWidget
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StdCmdOrigin::isActive()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Action* StdCmdOrigin::createAction()
|
||||||
|
{
|
||||||
|
Action* pcAction = new OriginSelectorAction(this, getMainWindow());
|
||||||
|
pcAction->setShortcut(QString::fromLatin1(getAccel()));
|
||||||
|
applyCommandData(this->className(), pcAction);
|
||||||
|
if (getPixmap()) {
|
||||||
|
pcAction->setIcon(Gui::BitmapFactory().iconFromTheme(getPixmap()));
|
||||||
|
}
|
||||||
|
return pcAction;
|
||||||
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// Std_RecentFiles
|
// Std_RecentFiles
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
@@ -1057,6 +1096,7 @@ void CreateStdCommands()
|
|||||||
rcCmdMgr.addCommand(new StdCmdDlgCustomize());
|
rcCmdMgr.addCommand(new StdCmdDlgCustomize());
|
||||||
rcCmdMgr.addCommand(new StdCmdCommandLine());
|
rcCmdMgr.addCommand(new StdCmdCommandLine());
|
||||||
rcCmdMgr.addCommand(new StdCmdWorkbench());
|
rcCmdMgr.addCommand(new StdCmdWorkbench());
|
||||||
|
rcCmdMgr.addCommand(new StdCmdOrigin());
|
||||||
rcCmdMgr.addCommand(new StdCmdRecentFiles());
|
rcCmdMgr.addCommand(new StdCmdRecentFiles());
|
||||||
rcCmdMgr.addCommand(new StdCmdRecentMacros());
|
rcCmdMgr.addCommand(new StdCmdRecentMacros());
|
||||||
rcCmdMgr.addCommand(new StdCmdWhatsThis());
|
rcCmdMgr.addCommand(new StdCmdWhatsThis());
|
||||||
|
|||||||
@@ -22,15 +22,22 @@
|
|||||||
|
|
||||||
#include "PreCompiled.h"
|
#include "PreCompiled.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
#include <App/Application.h>
|
#include <App/Application.h>
|
||||||
#include <App/Document.h>
|
#include <App/Document.h>
|
||||||
#include <App/DocumentObject.h>
|
#include <App/DocumentObject.h>
|
||||||
#include <App/PropertyStandard.h>
|
#include <App/PropertyStandard.h>
|
||||||
|
|
||||||
#include "FileOrigin.h"
|
#include "FileOrigin.h"
|
||||||
|
#include "Application.h"
|
||||||
#include "BitmapFactory.h"
|
#include "BitmapFactory.h"
|
||||||
#include "Document.h"
|
#include "Document.h"
|
||||||
#include "Application.h"
|
#include "FileDialog.h"
|
||||||
|
#include "MainWindow.h"
|
||||||
|
|
||||||
|
|
||||||
namespace Gui {
|
namespace Gui {
|
||||||
@@ -99,6 +106,86 @@ App::Document* LocalFileOrigin::openDocument(const std::string& identity)
|
|||||||
return App::GetApplication().openDocument(identity.c_str());
|
return App::GetApplication().openDocument(identity.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
App::Document* LocalFileOrigin::openDocumentInteractive()
|
||||||
|
{
|
||||||
|
// Build file filter list for Open dialog
|
||||||
|
QString formatList;
|
||||||
|
const char* supported = QT_TR_NOOP("Supported formats");
|
||||||
|
const char* allFiles = QT_TR_NOOP("All files (*.*)");
|
||||||
|
formatList = QObject::tr(supported);
|
||||||
|
formatList += QLatin1String(" (");
|
||||||
|
|
||||||
|
std::vector<std::string> filetypes = App::GetApplication().getImportTypes();
|
||||||
|
// Make sure FCStd is the very first fileformat
|
||||||
|
auto it = std::find(filetypes.begin(), filetypes.end(), "FCStd");
|
||||||
|
if (it != filetypes.end()) {
|
||||||
|
filetypes.erase(it);
|
||||||
|
filetypes.insert(filetypes.begin(), "FCStd");
|
||||||
|
}
|
||||||
|
for (it = filetypes.begin(); it != filetypes.end(); ++it) {
|
||||||
|
formatList += QLatin1String(" *.");
|
||||||
|
formatList += QLatin1String(it->c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
formatList += QLatin1String(");;");
|
||||||
|
|
||||||
|
std::map<std::string, std::string> FilterList = App::GetApplication().getImportFilters();
|
||||||
|
// Make sure the format name for FCStd is the very first in the list
|
||||||
|
for (auto jt = FilterList.begin(); jt != FilterList.end(); ++jt) {
|
||||||
|
if (jt->first.find("*.FCStd") != std::string::npos) {
|
||||||
|
formatList += QLatin1String(jt->first.c_str());
|
||||||
|
formatList += QLatin1String(";;");
|
||||||
|
FilterList.erase(jt);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& filter : FilterList) {
|
||||||
|
formatList += QLatin1String(filter.first.c_str());
|
||||||
|
formatList += QLatin1String(";;");
|
||||||
|
}
|
||||||
|
formatList += QObject::tr(allFiles);
|
||||||
|
|
||||||
|
QString selectedFilter;
|
||||||
|
QStringList fileList = FileDialog::getOpenFileNames(
|
||||||
|
getMainWindow(),
|
||||||
|
QObject::tr("Open Document"),
|
||||||
|
QString(),
|
||||||
|
formatList,
|
||||||
|
&selectedFilter
|
||||||
|
);
|
||||||
|
if (fileList.isEmpty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the files with the associated modules
|
||||||
|
SelectModule::Dict dict = SelectModule::importHandler(fileList, selectedFilter);
|
||||||
|
if (dict.isEmpty()) {
|
||||||
|
QMessageBox::critical(
|
||||||
|
getMainWindow(),
|
||||||
|
qApp->translate("StdCmdOpen", "Cannot Open File"),
|
||||||
|
qApp->translate("StdCmdOpen", "Loading the file %1 is not supported").arg(fileList.front())
|
||||||
|
);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
App::Document* lastDoc = nullptr;
|
||||||
|
for (SelectModule::Dict::iterator it = dict.begin(); it != dict.end(); ++it) {
|
||||||
|
// Set flag indicating that this load/restore has been initiated by the user
|
||||||
|
Application::Instance->setStatus(Gui::Application::UserInitiatedOpenDocument, true);
|
||||||
|
|
||||||
|
Application::Instance->open(it.key().toUtf8(), it.value().toLatin1());
|
||||||
|
|
||||||
|
Application::Instance->setStatus(Gui::Application::UserInitiatedOpenDocument, false);
|
||||||
|
|
||||||
|
lastDoc = App::GetApplication().getActiveDocument();
|
||||||
|
|
||||||
|
Application::Instance->checkPartialRestore(lastDoc);
|
||||||
|
Application::Instance->checkRestoreError(lastDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return lastDoc;
|
||||||
|
}
|
||||||
|
|
||||||
bool LocalFileOrigin::saveDocument(App::Document* doc)
|
bool LocalFileOrigin::saveDocument(App::Document* doc)
|
||||||
{
|
{
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
@@ -125,4 +212,20 @@ bool LocalFileOrigin::saveDocumentAs(App::Document* doc, const std::string& newI
|
|||||||
return doc->saveAs(newIdentity.c_str());
|
return doc->saveAs(newIdentity.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LocalFileOrigin::saveDocumentAsInteractive(App::Document* doc)
|
||||||
|
{
|
||||||
|
if (!doc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Gui document for save dialog
|
||||||
|
Gui::Document* guiDoc = Application::Instance->getDocument(doc);
|
||||||
|
if (!guiDoc) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use Gui::Document::saveAs() which handles the file dialog
|
||||||
|
return guiDoc->saveAs();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Gui
|
} // namespace Gui
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ public:
|
|||||||
virtual App::Document* newDocument(const std::string& name = "") = 0;
|
virtual App::Document* newDocument(const std::string& name = "") = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a document by identity.
|
* Open a document by identity (non-interactive).
|
||||||
* Local: Opens file at path
|
* Local: Opens file at path
|
||||||
* PLM: Opens document by UUID (downloads if needed)
|
* PLM: Opens document by UUID (downloads if needed)
|
||||||
* @param identity Document identity (path or UUID)
|
* @param identity Document identity (path or UUID)
|
||||||
@@ -176,9 +176,17 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual App::Document* openDocument(const std::string& identity) = 0;
|
virtual App::Document* openDocument(const std::string& identity) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open a document interactively (shows dialog).
|
||||||
|
* Local: Shows file picker dialog
|
||||||
|
* PLM: Shows search/browse dialog
|
||||||
|
* @return The opened document or nullptr if cancelled/failed
|
||||||
|
*/
|
||||||
|
virtual App::Document* openDocumentInteractive() = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the document.
|
* Save the document.
|
||||||
* Local: Saves to disk
|
* Local: Saves to disk (if path known)
|
||||||
* PLM: Saves to disk and syncs with external system
|
* PLM: Saves to disk and syncs with external system
|
||||||
* @param doc The document to save
|
* @param doc The document to save
|
||||||
* @return true if save succeeded
|
* @return true if save succeeded
|
||||||
@@ -186,14 +194,21 @@ public:
|
|||||||
virtual bool saveDocument(App::Document* doc) = 0;
|
virtual bool saveDocument(App::Document* doc) = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save document with new identity.
|
* Save document with new identity (non-interactive).
|
||||||
* Local: File picker for new path
|
|
||||||
* PLM: Migration or copy workflow
|
|
||||||
* @param doc The document to save
|
* @param doc The document to save
|
||||||
* @param newIdentity New identity (path or part number)
|
* @param newIdentity New identity (path or part number)
|
||||||
* @return true if save succeeded
|
* @return true if save succeeded
|
||||||
*/
|
*/
|
||||||
virtual bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) = 0;
|
virtual bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save document interactively (shows dialog).
|
||||||
|
* Local: Shows file picker for new path
|
||||||
|
* PLM: Shows migration or copy workflow dialog
|
||||||
|
* @param doc The document to save
|
||||||
|
* @return true if save succeeded
|
||||||
|
*/
|
||||||
|
virtual bool saveDocumentAsInteractive(App::Document* doc) = 0;
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
///@name Extended Operations (PLM-specific, default to no-op)
|
///@name Extended Operations (PLM-specific, default to no-op)
|
||||||
@@ -250,8 +265,10 @@ public:
|
|||||||
// Document operations
|
// Document operations
|
||||||
App::Document* newDocument(const std::string& name = "") override;
|
App::Document* newDocument(const std::string& name = "") override;
|
||||||
App::Document* openDocument(const std::string& identity) override;
|
App::Document* openDocument(const std::string& identity) override;
|
||||||
|
App::Document* openDocumentInteractive() override;
|
||||||
bool saveDocument(App::Document* doc) override;
|
bool saveDocument(App::Document* doc) override;
|
||||||
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
||||||
|
bool saveDocumentAsInteractive(App::Document* doc) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Gui
|
} // namespace Gui
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
#include <App/Application.h>
|
#include <App/Application.h>
|
||||||
#include <App/Document.h>
|
#include <App/Document.h>
|
||||||
|
#include <App/DocumentPy.h>
|
||||||
#include <Base/Console.h>
|
#include <Base/Console.h>
|
||||||
#include <Base/Interpreter.h>
|
#include <Base/Interpreter.h>
|
||||||
#include <Base/PyObjectBase.h>
|
#include <Base/PyObjectBase.h>
|
||||||
@@ -41,7 +42,7 @@ void FileOriginPython::addOrigin(const Py::Object& obj)
|
|||||||
{
|
{
|
||||||
// Check if already registered
|
// Check if already registered
|
||||||
if (findOrigin(obj)) {
|
if (findOrigin(obj)) {
|
||||||
Base::Console().Warning("FileOriginPython: Origin already registered\n");
|
Base::Console().warning("FileOriginPython: Origin already registered\n");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ void FileOriginPython::addOrigin(const Py::Object& obj)
|
|||||||
// Cache the ID immediately for registration
|
// Cache the ID immediately for registration
|
||||||
origin->_cachedId = origin->callStringMethod("id");
|
origin->_cachedId = origin->callStringMethod("id");
|
||||||
if (origin->_cachedId.empty()) {
|
if (origin->_cachedId.empty()) {
|
||||||
Base::Console().Error("FileOriginPython: Origin must have non-empty id()\n");
|
Base::Console().error("FileOriginPython: Origin must have non-empty id()\n");
|
||||||
delete origin;
|
delete origin;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -117,7 +118,7 @@ Py::Object FileOriginPython::callMethod(const char* method, const Py::Tuple& arg
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return Py::None();
|
return Py::None();
|
||||||
}
|
}
|
||||||
@@ -139,7 +140,7 @@ bool FileOriginPython::callBoolMethod(const char* method, bool defaultValue) con
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
@@ -158,7 +159,7 @@ std::string FileOriginPython::callStringMethod(const char* method, const std::st
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,7 @@ OriginType FileOriginPython::type() const
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return OriginType::Custom;
|
return OriginType::Custom;
|
||||||
}
|
}
|
||||||
@@ -265,7 +266,7 @@ ConnectionState FileOriginPython::connectionState() const
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return ConnectionState::Connected;
|
return ConnectionState::Connected;
|
||||||
}
|
}
|
||||||
@@ -297,7 +298,7 @@ std::string FileOriginPython::documentIdentity(App::Document* doc) const
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -318,7 +319,7 @@ std::string FileOriginPython::documentDisplayId(App::Document* doc) const
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return documentIdentity(doc);
|
return documentIdentity(doc);
|
||||||
}
|
}
|
||||||
@@ -342,7 +343,7 @@ bool FileOriginPython::ownsDocument(App::Document* doc) const
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -366,7 +367,7 @@ bool FileOriginPython::syncProperties(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -391,7 +392,7 @@ App::Document* FileOriginPython::newDocument(const std::string& name)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -414,7 +415,7 @@ App::Document* FileOriginPython::openDocument(const std::string& identity)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
@@ -438,7 +439,7 @@ bool FileOriginPython::saveDocument(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -463,7 +464,7 @@ bool FileOriginPython::saveDocumentAs(App::Document* doc, const std::string& new
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -488,7 +489,7 @@ bool FileOriginPython::commitDocument(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -512,7 +513,7 @@ bool FileOriginPython::pullDocument(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -536,7 +537,7 @@ bool FileOriginPython::pushDocument(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -554,7 +555,7 @@ void FileOriginPython::showInfo(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,8 +572,53 @@ void FileOriginPython::showBOM(App::Document* doc)
|
|||||||
}
|
}
|
||||||
catch (Py::Exception&) {
|
catch (Py::Exception&) {
|
||||||
Base::PyException e;
|
Base::PyException e;
|
||||||
e.ReportException();
|
e.reportException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
App::Document* FileOriginPython::openDocumentInteractive()
|
||||||
|
{
|
||||||
|
Base::PyGILStateLocker lock;
|
||||||
|
try {
|
||||||
|
if (_inst.hasAttr("openDocumentInteractive")) {
|
||||||
|
Py::Callable func(_inst.getAttr("openDocumentInteractive"));
|
||||||
|
Py::Object result = func.apply(Py::Tuple());
|
||||||
|
if (!result.isNone()) {
|
||||||
|
if (PyObject_TypeCheck(result.ptr(), &(App::DocumentPy::Type))) {
|
||||||
|
return static_cast<App::DocumentPy*>(result.ptr())->getDocumentPtr();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Py::Exception&) {
|
||||||
|
Base::PyException e;
|
||||||
|
e.reportException();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FileOriginPython::saveDocumentAsInteractive(App::Document* doc)
|
||||||
|
{
|
||||||
|
Base::PyGILStateLocker lock;
|
||||||
|
try {
|
||||||
|
if (_inst.hasAttr("saveDocumentAsInteractive")) {
|
||||||
|
Py::Callable func(_inst.getAttr("saveDocumentAsInteractive"));
|
||||||
|
Py::Tuple args(1);
|
||||||
|
args.setItem(0, getDocPyObject(doc));
|
||||||
|
Py::Object result = func.apply(args);
|
||||||
|
if (result.isBoolean()) {
|
||||||
|
return Py::Boolean(result);
|
||||||
|
}
|
||||||
|
if (result.isNumeric()) {
|
||||||
|
return Py::Long(result) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Py::Exception&) {
|
||||||
|
Base::PyException e;
|
||||||
|
e.reportException();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Gui
|
} // namespace Gui
|
||||||
|
|||||||
@@ -115,8 +115,10 @@ public:
|
|||||||
|
|
||||||
App::Document* newDocument(const std::string& name = "") override;
|
App::Document* newDocument(const std::string& name = "") override;
|
||||||
App::Document* openDocument(const std::string& identity) override;
|
App::Document* openDocument(const std::string& identity) override;
|
||||||
|
App::Document* openDocumentInteractive() override;
|
||||||
bool saveDocument(App::Document* doc) override;
|
bool saveDocument(App::Document* doc) override;
|
||||||
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
bool saveDocumentAs(App::Document* doc, const std::string& newIdentity) override;
|
||||||
|
bool saveDocumentAsInteractive(App::Document* doc) override;
|
||||||
|
|
||||||
bool commitDocument(App::Document* doc) override;
|
bool commitDocument(App::Document* doc) override;
|
||||||
bool pullDocument(App::Document* doc) override;
|
bool pullDocument(App::Document* doc) override;
|
||||||
|
|||||||
@@ -185,6 +185,11 @@
|
|||||||
<file>sel-bbox.svg</file>
|
<file>sel-bbox.svg</file>
|
||||||
<file>sel-forward.svg</file>
|
<file>sel-forward.svg</file>
|
||||||
<file>sel-instance.svg</file>
|
<file>sel-instance.svg</file>
|
||||||
|
<file>silo-bom.svg</file>
|
||||||
|
<file>silo-commit.svg</file>
|
||||||
|
<file>silo-info.svg</file>
|
||||||
|
<file>silo-pull.svg</file>
|
||||||
|
<file>silo-push.svg</file>
|
||||||
<file>spaceball_button.svg</file>
|
<file>spaceball_button.svg</file>
|
||||||
<file>SpNav-PanLR.svg</file>
|
<file>SpNav-PanLR.svg</file>
|
||||||
<file>SpNav-PanUD.svg</file>
|
<file>SpNav-PanUD.svg</file>
|
||||||
|
|||||||
12
src/Gui/Icons/silo-bom.svg
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Outer box -->
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" fill="#313244"/>
|
||||||
|
<!-- List lines (BOM rows) -->
|
||||||
|
<line x1="8" y1="8" x2="18" y2="8" stroke="#89dceb" stroke-width="1.5"/>
|
||||||
|
<line x1="8" y1="12" x2="18" y2="12" stroke="#89dceb" stroke-width="1.5"/>
|
||||||
|
<line x1="8" y1="16" x2="18" y2="16" stroke="#89dceb" stroke-width="1.5"/>
|
||||||
|
<!-- Hierarchy dots -->
|
||||||
|
<circle cx="6" cy="8" r="1" fill="#cba6f7"/>
|
||||||
|
<circle cx="6" cy="12" r="1" fill="#cba6f7"/>
|
||||||
|
<circle cx="6" cy="16" r="1" fill="#cba6f7"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 680 B |
8
src/Gui/Icons/silo-commit.svg
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Git commit style -->
|
||||||
|
<circle cx="12" cy="12" r="4" fill="#313244" stroke="#a6e3a1"/>
|
||||||
|
<line x1="12" y1="2" x2="12" y2="8" stroke="#cba6f7"/>
|
||||||
|
<line x1="12" y1="16" x2="12" y2="22" stroke="#cba6f7"/>
|
||||||
|
<!-- Checkmark inside -->
|
||||||
|
<polyline points="9.5 12 11 13.5 14.5 10" stroke="#a6e3a1" stroke-width="1.5" fill="none"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 493 B |
6
src/Gui/Icons/silo-info.svg
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Info circle -->
|
||||||
|
<circle cx="12" cy="12" r="10" fill="#313244"/>
|
||||||
|
<line x1="12" y1="16" x2="12" y2="12" stroke="#89dceb" stroke-width="2"/>
|
||||||
|
<circle cx="12" cy="8" r="0.5" fill="#89dceb" stroke="#89dceb"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 377 B |
7
src/Gui/Icons/silo-pull.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Cloud -->
|
||||||
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" fill="#313244"/>
|
||||||
|
<!-- Download arrow -->
|
||||||
|
<path d="M12 13v5m0 0l-2-2m2 2l2-2" stroke="#89b4fa" stroke-width="2"/>
|
||||||
|
<line x1="12" y1="9" x2="12" y2="13" stroke="#89b4fa" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 428 B |
7
src/Gui/Icons/silo-push.svg
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#cba6f7" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<!-- Cloud -->
|
||||||
|
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z" fill="#313244"/>
|
||||||
|
<!-- Upload arrow -->
|
||||||
|
<path d="M12 18v-5m0 0l-2 2m2-2l2 2" stroke="#a6e3a1" stroke-width="2"/>
|
||||||
|
<line x1="12" y1="13" x2="12" y2="9" stroke="#a6e3a1" stroke-width="2"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 427 B |
@@ -44,7 +44,9 @@
|
|||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "Document.h"
|
#include "Document.h"
|
||||||
#include "FileDialog.h"
|
#include "FileDialog.h"
|
||||||
|
#include "FileOrigin.h"
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
|
#include "OriginManager.h"
|
||||||
#include "ViewProviderDocumentObject.h"
|
#include "ViewProviderDocumentObject.h"
|
||||||
|
|
||||||
|
|
||||||
@@ -522,6 +524,13 @@ QString MDIView::buildWindowTitle() const
|
|||||||
QString windowTitle;
|
QString windowTitle;
|
||||||
if (auto document = getAppDocument()) {
|
if (auto document = getAppDocument()) {
|
||||||
windowTitle.append(QString::fromStdString(document->Label.getStrValue()));
|
windowTitle.append(QString::fromStdString(document->Label.getStrValue()));
|
||||||
|
|
||||||
|
// Append origin suffix for non-local origins
|
||||||
|
FileOrigin* origin = OriginManager::instance()->originForDocument(document);
|
||||||
|
if (origin && origin->type() != OriginType::Local) {
|
||||||
|
windowTitle.append(QStringLiteral(" [%1]")
|
||||||
|
.arg(QString::fromStdString(origin->nickname())));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return windowTitle;
|
return windowTitle;
|
||||||
|
|||||||
@@ -111,20 +111,20 @@ bool OriginManager::registerOrigin(FileOrigin* origin)
|
|||||||
|
|
||||||
std::string originId = origin->id();
|
std::string originId = origin->id();
|
||||||
if (originId.empty()) {
|
if (originId.empty()) {
|
||||||
Base::Console().Warning("OriginManager: Cannot register origin with empty ID\n");
|
Base::Console().warning("OriginManager: Cannot register origin with empty ID\n");
|
||||||
delete origin;
|
delete origin;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if ID already in use
|
// Check if ID already in use
|
||||||
if (_origins.find(originId) != _origins.end()) {
|
if (_origins.find(originId) != _origins.end()) {
|
||||||
Base::Console().Warning("OriginManager: Origin '%s' already registered\n", originId.c_str());
|
Base::Console().warning("OriginManager: Origin '%s' already registered\n", originId.c_str());
|
||||||
delete origin;
|
delete origin;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_origins[originId] = std::unique_ptr<FileOrigin>(origin);
|
_origins[originId] = std::unique_ptr<FileOrigin>(origin);
|
||||||
Base::Console().Log("OriginManager: Registered origin '%s'\n", originId.c_str());
|
Base::Console().log("OriginManager: Registered origin '%s'\n", originId.c_str());
|
||||||
|
|
||||||
signalOriginRegistered(originId);
|
signalOriginRegistered(originId);
|
||||||
return true;
|
return true;
|
||||||
@@ -134,7 +134,7 @@ bool OriginManager::unregisterOrigin(const std::string& id)
|
|||||||
{
|
{
|
||||||
// Cannot unregister the built-in local origin
|
// Cannot unregister the built-in local origin
|
||||||
if (id == LOCAL_ORIGIN_ID) {
|
if (id == LOCAL_ORIGIN_ID) {
|
||||||
Base::Console().Warning("OriginManager: Cannot unregister built-in local origin\n");
|
Base::Console().warning("OriginManager: Cannot unregister built-in local origin\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ bool OriginManager::unregisterOrigin(const std::string& id)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_origins.erase(it);
|
_origins.erase(it);
|
||||||
Base::Console().Log("OriginManager: Unregistered origin '%s'\n", id.c_str());
|
Base::Console().log("OriginManager: Unregistered origin '%s'\n", id.c_str());
|
||||||
|
|
||||||
signalOriginUnregistered(id);
|
signalOriginUnregistered(id);
|
||||||
return true;
|
return true;
|
||||||
@@ -231,6 +231,63 @@ FileOrigin* OriginManager::findOwningOrigin(App::Document* doc) const
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileOrigin* OriginManager::originForDocument(App::Document* doc) const
|
||||||
|
{
|
||||||
|
if (!doc) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check explicit association first
|
||||||
|
auto it = _documentOrigins.find(doc);
|
||||||
|
if (it != _documentOrigins.end()) {
|
||||||
|
FileOrigin* origin = getOrigin(it->second);
|
||||||
|
if (origin) {
|
||||||
|
return origin;
|
||||||
|
}
|
||||||
|
// Origin was unregistered, clear stale association
|
||||||
|
_documentOrigins.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to ownership detection
|
||||||
|
FileOrigin* owner = findOwningOrigin(doc);
|
||||||
|
if (owner) {
|
||||||
|
// Cache the result
|
||||||
|
_documentOrigins[doc] = owner->id();
|
||||||
|
return owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManager::setDocumentOrigin(App::Document* doc, FileOrigin* origin)
|
||||||
|
{
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string originId = origin ? origin->id() : "";
|
||||||
|
|
||||||
|
if (origin) {
|
||||||
|
_documentOrigins[doc] = originId;
|
||||||
|
} else {
|
||||||
|
_documentOrigins.erase(doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
signalDocumentOriginChanged(doc, originId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManager::clearDocumentOrigin(App::Document* doc)
|
||||||
|
{
|
||||||
|
if (!doc) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto it = _documentOrigins.find(doc);
|
||||||
|
if (it != _documentOrigins.end()) {
|
||||||
|
_documentOrigins.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
FileOrigin* OriginManager::originForNewDocument() const
|
FileOrigin* OriginManager::originForNewDocument() const
|
||||||
{
|
{
|
||||||
return currentOrigin();
|
return currentOrigin();
|
||||||
|
|||||||
@@ -121,6 +121,27 @@ public:
|
|||||||
*/
|
*/
|
||||||
FileOrigin* findOwningOrigin(App::Document* doc) const;
|
FileOrigin* findOwningOrigin(App::Document* doc) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the origin associated with a document.
|
||||||
|
* First checks explicit association, then uses findOwningOrigin().
|
||||||
|
* @param doc The document to check
|
||||||
|
* @return The document's origin or nullptr if unknown
|
||||||
|
*/
|
||||||
|
FileOrigin* originForDocument(App::Document* doc) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate a document with an origin.
|
||||||
|
* @param doc The document
|
||||||
|
* @param origin The origin to associate (nullptr to clear)
|
||||||
|
*/
|
||||||
|
void setDocumentOrigin(App::Document* doc, FileOrigin* origin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear document origin association (called when document closes).
|
||||||
|
* @param doc The document being closed
|
||||||
|
*/
|
||||||
|
void clearDocumentOrigin(App::Document* doc);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the appropriate origin for a new document.
|
* Get the appropriate origin for a new document.
|
||||||
* Returns the current origin.
|
* Returns the current origin.
|
||||||
@@ -137,6 +158,8 @@ public:
|
|||||||
fastsignals::signal<void(const std::string&)> signalOriginUnregistered;
|
fastsignals::signal<void(const std::string&)> signalOriginUnregistered;
|
||||||
/** Emitted when current origin changes */
|
/** Emitted when current origin changes */
|
||||||
fastsignals::signal<void(const std::string&)> signalCurrentOriginChanged;
|
fastsignals::signal<void(const std::string&)> signalCurrentOriginChanged;
|
||||||
|
/** Emitted when a document's origin association changes */
|
||||||
|
fastsignals::signal<void(App::Document*, const std::string&)> signalDocumentOriginChanged;
|
||||||
//@}
|
//@}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
@@ -151,6 +174,9 @@ private:
|
|||||||
static OriginManager* _instance;
|
static OriginManager* _instance;
|
||||||
std::map<std::string, std::unique_ptr<FileOrigin>> _origins;
|
std::map<std::string, std::unique_ptr<FileOrigin>> _origins;
|
||||||
std::string _currentOriginId;
|
std::string _currentOriginId;
|
||||||
|
|
||||||
|
// Document-to-origin associations (doc -> origin ID)
|
||||||
|
mutable std::map<App::Document*, std::string> _documentOrigins;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Gui
|
} // namespace Gui
|
||||||
|
|||||||
251
src/Gui/OriginManagerDialog.cpp
Normal file
@@ -0,0 +1,251 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (c) 2025 Kindred Systems *
|
||||||
|
* *
|
||||||
|
* This file is part of the FreeCAD CAx development system. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Library General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU Library General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Library General Public *
|
||||||
|
* License along with this library; see the file COPYING.LIB. If not, *
|
||||||
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||||
|
* Suite 330, Boston, MA 02111-1307, USA *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include "PreCompiled.h"
|
||||||
|
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include <QHBoxLayout>
|
||||||
|
#include <QLabel>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
|
#include "OriginManagerDialog.h"
|
||||||
|
#include "OriginManager.h"
|
||||||
|
#include "FileOrigin.h"
|
||||||
|
#include "BitmapFactory.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Gui {
|
||||||
|
|
||||||
|
OriginManagerDialog::OriginManagerDialog(QWidget* parent)
|
||||||
|
: QDialog(parent)
|
||||||
|
{
|
||||||
|
setupUi();
|
||||||
|
populateOriginList();
|
||||||
|
updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
OriginManagerDialog::~OriginManagerDialog() = default;
|
||||||
|
|
||||||
|
void OriginManagerDialog::setupUi()
|
||||||
|
{
|
||||||
|
setWindowTitle(tr("Manage File Origins"));
|
||||||
|
setMinimumSize(450, 350);
|
||||||
|
|
||||||
|
auto* mainLayout = new QVBoxLayout(this);
|
||||||
|
|
||||||
|
// Description
|
||||||
|
auto* descLabel = new QLabel(tr("Configure file origins for storing and retrieving documents."));
|
||||||
|
descLabel->setWordWrap(true);
|
||||||
|
mainLayout->addWidget(descLabel);
|
||||||
|
|
||||||
|
// Origin list
|
||||||
|
m_originList = new QListWidget(this);
|
||||||
|
m_originList->setIconSize(QSize(24, 24));
|
||||||
|
m_originList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||||
|
connect(m_originList, &QListWidget::itemSelectionChanged,
|
||||||
|
this, &OriginManagerDialog::onOriginSelectionChanged);
|
||||||
|
connect(m_originList, &QListWidget::itemDoubleClicked,
|
||||||
|
this, &OriginManagerDialog::onOriginDoubleClicked);
|
||||||
|
mainLayout->addWidget(m_originList);
|
||||||
|
|
||||||
|
// Action buttons
|
||||||
|
auto* actionLayout = new QHBoxLayout();
|
||||||
|
|
||||||
|
m_addButton = new QPushButton(tr("Add Silo..."));
|
||||||
|
m_addButton->setIcon(BitmapFactory().iconFromTheme("list-add"));
|
||||||
|
connect(m_addButton, &QPushButton::clicked, this, &OriginManagerDialog::onAddSilo);
|
||||||
|
actionLayout->addWidget(m_addButton);
|
||||||
|
|
||||||
|
m_editButton = new QPushButton(tr("Edit..."));
|
||||||
|
m_editButton->setIcon(BitmapFactory().iconFromTheme("document-edit"));
|
||||||
|
connect(m_editButton, &QPushButton::clicked, this, &OriginManagerDialog::onEditOrigin);
|
||||||
|
actionLayout->addWidget(m_editButton);
|
||||||
|
|
||||||
|
m_removeButton = new QPushButton(tr("Remove"));
|
||||||
|
m_removeButton->setIcon(BitmapFactory().iconFromTheme("list-remove"));
|
||||||
|
connect(m_removeButton, &QPushButton::clicked, this, &OriginManagerDialog::onRemoveOrigin);
|
||||||
|
actionLayout->addWidget(m_removeButton);
|
||||||
|
|
||||||
|
actionLayout->addStretch();
|
||||||
|
|
||||||
|
m_defaultButton = new QPushButton(tr("Set as Default"));
|
||||||
|
connect(m_defaultButton, &QPushButton::clicked, this, &OriginManagerDialog::onSetDefault);
|
||||||
|
actionLayout->addWidget(m_defaultButton);
|
||||||
|
|
||||||
|
mainLayout->addLayout(actionLayout);
|
||||||
|
|
||||||
|
// Dialog buttons
|
||||||
|
m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Close);
|
||||||
|
connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
|
mainLayout->addWidget(m_buttonBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::populateOriginList()
|
||||||
|
{
|
||||||
|
m_originList->clear();
|
||||||
|
|
||||||
|
auto* mgr = OriginManager::instance();
|
||||||
|
std::string currentId = mgr->currentOriginId();
|
||||||
|
|
||||||
|
for (const std::string& originId : mgr->originIds()) {
|
||||||
|
FileOrigin* origin = mgr->getOrigin(originId);
|
||||||
|
if (!origin) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* item = new QListWidgetItem(m_originList);
|
||||||
|
item->setIcon(origin->icon());
|
||||||
|
|
||||||
|
QString displayText = QString::fromStdString(origin->name());
|
||||||
|
|
||||||
|
// Add connection status for remote origins
|
||||||
|
if (origin->requiresAuthentication()) {
|
||||||
|
ConnectionState state = origin->connectionState();
|
||||||
|
switch (state) {
|
||||||
|
case ConnectionState::Connected:
|
||||||
|
displayText += tr(" [Connected]");
|
||||||
|
break;
|
||||||
|
case ConnectionState::Connecting:
|
||||||
|
displayText += tr(" [Connecting...]");
|
||||||
|
break;
|
||||||
|
case ConnectionState::Disconnected:
|
||||||
|
displayText += tr(" [Disconnected]");
|
||||||
|
break;
|
||||||
|
case ConnectionState::Error:
|
||||||
|
displayText += tr(" [Error]");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark default origin
|
||||||
|
if (originId == currentId) {
|
||||||
|
displayText += tr(" (Default)");
|
||||||
|
QFont font = item->font();
|
||||||
|
font.setBold(true);
|
||||||
|
item->setFont(font);
|
||||||
|
}
|
||||||
|
|
||||||
|
item->setText(displayText);
|
||||||
|
item->setData(Qt::UserRole, QString::fromStdString(originId));
|
||||||
|
item->setToolTip(QString::fromStdString(origin->name()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::updateButtonStates()
|
||||||
|
{
|
||||||
|
FileOrigin* origin = selectedOrigin();
|
||||||
|
bool hasSelection = (origin != nullptr);
|
||||||
|
bool isLocal = hasSelection && (origin->id() == "local");
|
||||||
|
bool isDefault = hasSelection && (origin->id() == OriginManager::instance()->currentOriginId());
|
||||||
|
|
||||||
|
// Can't edit or remove local origin
|
||||||
|
m_editButton->setEnabled(hasSelection && !isLocal);
|
||||||
|
m_removeButton->setEnabled(hasSelection && !isLocal);
|
||||||
|
m_defaultButton->setEnabled(hasSelection && !isDefault);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileOrigin* OriginManagerDialog::selectedOrigin() const
|
||||||
|
{
|
||||||
|
QListWidgetItem* item = m_originList->currentItem();
|
||||||
|
if (!item) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string originId = item->data(Qt::UserRole).toString().toStdString();
|
||||||
|
return OriginManager::instance()->getOrigin(originId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::onAddSilo()
|
||||||
|
{
|
||||||
|
// TODO: Open SiloConfigDialog for adding new instance
|
||||||
|
QMessageBox::information(this, tr("Add Silo"),
|
||||||
|
tr("Silo configuration dialog not yet implemented.\n\n"
|
||||||
|
"To add a Silo instance, configure it in the Silo workbench preferences."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::onEditOrigin()
|
||||||
|
{
|
||||||
|
FileOrigin* origin = selectedOrigin();
|
||||||
|
if (!origin || origin->id() == "local") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Open SiloConfigDialog for editing
|
||||||
|
QMessageBox::information(this, tr("Edit Origin"),
|
||||||
|
tr("Origin editing not yet implemented.\n\n"
|
||||||
|
"To edit this origin, modify settings in the Silo workbench preferences."));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::onRemoveOrigin()
|
||||||
|
{
|
||||||
|
FileOrigin* origin = selectedOrigin();
|
||||||
|
if (!origin || origin->id() == "local") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString name = QString::fromStdString(origin->name());
|
||||||
|
QMessageBox::StandardButton reply = QMessageBox::question(this,
|
||||||
|
tr("Remove Origin"),
|
||||||
|
tr("Are you sure you want to remove '%1'?\n\n"
|
||||||
|
"This will not delete any files, but you will need to reconfigure "
|
||||||
|
"the connection to use this origin again.").arg(name),
|
||||||
|
QMessageBox::Yes | QMessageBox::No,
|
||||||
|
QMessageBox::No);
|
||||||
|
|
||||||
|
if (reply == QMessageBox::Yes) {
|
||||||
|
std::string originId = origin->id();
|
||||||
|
OriginManager::instance()->unregisterOrigin(originId);
|
||||||
|
populateOriginList();
|
||||||
|
updateButtonStates();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::onSetDefault()
|
||||||
|
{
|
||||||
|
FileOrigin* origin = selectedOrigin();
|
||||||
|
if (!origin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
OriginManager::instance()->setCurrentOrigin(origin->id());
|
||||||
|
populateOriginList();
|
||||||
|
updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::onOriginSelectionChanged()
|
||||||
|
{
|
||||||
|
updateButtonStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginManagerDialog::onOriginDoubleClicked(QListWidgetItem* item)
|
||||||
|
{
|
||||||
|
if (!item) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string originId = item->data(Qt::UserRole).toString().toStdString();
|
||||||
|
if (originId != "local") {
|
||||||
|
onEditOrigin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Gui
|
||||||
74
src/Gui/OriginManagerDialog.h
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (c) 2025 Kindred Systems *
|
||||||
|
* *
|
||||||
|
* This file is part of the FreeCAD CAx development system. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Library General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU Library General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Library General Public *
|
||||||
|
* License along with this library; see the file COPYING.LIB. If not, *
|
||||||
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||||
|
* Suite 330, Boston, MA 02111-1307, USA *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#ifndef GUI_ORIGINMANAGERDIALOG_H
|
||||||
|
#define GUI_ORIGINMANAGERDIALOG_H
|
||||||
|
|
||||||
|
#include <QDialog>
|
||||||
|
#include <QListWidget>
|
||||||
|
#include <QPushButton>
|
||||||
|
#include <QDialogButtonBox>
|
||||||
|
#include <FCGlobal.h>
|
||||||
|
|
||||||
|
namespace Gui {
|
||||||
|
|
||||||
|
class FileOrigin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Dialog for managing file origins
|
||||||
|
*
|
||||||
|
* This dialog allows users to view, add, edit, and remove file origins
|
||||||
|
* (Silo instances). The local filesystem origin cannot be removed.
|
||||||
|
*/
|
||||||
|
class GuiExport OriginManagerDialog : public QDialog
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OriginManagerDialog(QWidget* parent = nullptr);
|
||||||
|
~OriginManagerDialog() override;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onAddSilo();
|
||||||
|
void onEditOrigin();
|
||||||
|
void onRemoveOrigin();
|
||||||
|
void onSetDefault();
|
||||||
|
void onOriginSelectionChanged();
|
||||||
|
void onOriginDoubleClicked(QListWidgetItem* item);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
void populateOriginList();
|
||||||
|
void updateButtonStates();
|
||||||
|
FileOrigin* selectedOrigin() const;
|
||||||
|
|
||||||
|
QListWidget* m_originList;
|
||||||
|
QPushButton* m_addButton;
|
||||||
|
QPushButton* m_editButton;
|
||||||
|
QPushButton* m_removeButton;
|
||||||
|
QPushButton* m_defaultButton;
|
||||||
|
QDialogButtonBox* m_buttonBox;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Gui
|
||||||
|
|
||||||
|
#endif // GUI_ORIGINMANAGERDIALOG_H
|
||||||
270
src/Gui/OriginSelectorWidget.cpp
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (c) 2025 Kindred Systems *
|
||||||
|
* *
|
||||||
|
* This file is part of the FreeCAD CAx development system. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Library General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU Library General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Library General Public *
|
||||||
|
* License along with this library; see the file COPYING.LIB. If not, *
|
||||||
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||||
|
* Suite 330, Boston, MA 02111-1307, USA *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#include "PreCompiled.h"
|
||||||
|
|
||||||
|
#include <QApplication>
|
||||||
|
|
||||||
|
#include "OriginSelectorWidget.h"
|
||||||
|
#include "OriginManager.h"
|
||||||
|
#include "OriginManagerDialog.h"
|
||||||
|
#include "FileOrigin.h"
|
||||||
|
#include "BitmapFactory.h"
|
||||||
|
|
||||||
|
|
||||||
|
namespace Gui {
|
||||||
|
|
||||||
|
OriginSelectorWidget::OriginSelectorWidget(QWidget* parent)
|
||||||
|
: QToolButton(parent)
|
||||||
|
, m_menu(nullptr)
|
||||||
|
, m_originActions(nullptr)
|
||||||
|
, m_manageAction(nullptr)
|
||||||
|
{
|
||||||
|
setupUi();
|
||||||
|
connectSignals();
|
||||||
|
rebuildMenu();
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
OriginSelectorWidget::~OriginSelectorWidget()
|
||||||
|
{
|
||||||
|
disconnectSignals();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::setupUi()
|
||||||
|
{
|
||||||
|
setPopupMode(QToolButton::InstantPopup);
|
||||||
|
setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||||
|
setMinimumWidth(70);
|
||||||
|
setMaximumWidth(120);
|
||||||
|
|
||||||
|
// Create menu
|
||||||
|
m_menu = new QMenu(this);
|
||||||
|
setMenu(m_menu);
|
||||||
|
|
||||||
|
// Create action group for exclusive selection
|
||||||
|
m_originActions = new QActionGroup(this);
|
||||||
|
m_originActions->setExclusive(true);
|
||||||
|
|
||||||
|
// Connect action group to selection handler
|
||||||
|
connect(m_originActions, &QActionGroup::triggered,
|
||||||
|
this, &OriginSelectorWidget::onOriginActionTriggered);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::connectSignals()
|
||||||
|
{
|
||||||
|
auto* mgr = OriginManager::instance();
|
||||||
|
|
||||||
|
// Connect to OriginManager fastsignals
|
||||||
|
m_connRegistered = mgr->signalOriginRegistered.connect(
|
||||||
|
[this](const std::string& id) { onOriginRegistered(id); }
|
||||||
|
);
|
||||||
|
m_connUnregistered = mgr->signalOriginUnregistered.connect(
|
||||||
|
[this](const std::string& id) { onOriginUnregistered(id); }
|
||||||
|
);
|
||||||
|
m_connChanged = mgr->signalCurrentOriginChanged.connect(
|
||||||
|
[this](const std::string& id) { onCurrentOriginChanged(id); }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::disconnectSignals()
|
||||||
|
{
|
||||||
|
m_connRegistered.disconnect();
|
||||||
|
m_connUnregistered.disconnect();
|
||||||
|
m_connChanged.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::onOriginRegistered(const std::string& /*originId*/)
|
||||||
|
{
|
||||||
|
// Rebuild menu to include new origin
|
||||||
|
rebuildMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::onOriginUnregistered(const std::string& /*originId*/)
|
||||||
|
{
|
||||||
|
// Rebuild menu to remove origin
|
||||||
|
rebuildMenu();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::onCurrentOriginChanged(const std::string& /*originId*/)
|
||||||
|
{
|
||||||
|
// Update display and menu checkmarks
|
||||||
|
updateDisplay();
|
||||||
|
|
||||||
|
// Update checked state in menu
|
||||||
|
auto* mgr = OriginManager::instance();
|
||||||
|
std::string currentId = mgr->currentOriginId();
|
||||||
|
|
||||||
|
for (QAction* action : m_originActions->actions()) {
|
||||||
|
std::string actionId = action->data().toString().toStdString();
|
||||||
|
action->setChecked(actionId == currentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::onOriginActionTriggered(QAction* action)
|
||||||
|
{
|
||||||
|
if (!action) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string originId = action->data().toString().toStdString();
|
||||||
|
auto* mgr = OriginManager::instance();
|
||||||
|
|
||||||
|
// Check if origin requires connection
|
||||||
|
FileOrigin* origin = mgr->getOrigin(originId);
|
||||||
|
if (origin && origin->requiresAuthentication()) {
|
||||||
|
ConnectionState state = origin->connectionState();
|
||||||
|
if (state == ConnectionState::Disconnected || state == ConnectionState::Error) {
|
||||||
|
// Try to connect
|
||||||
|
if (!origin->connect()) {
|
||||||
|
// Connection failed - don't switch
|
||||||
|
// Revert the checkmark to current origin
|
||||||
|
std::string currentId = mgr->currentOriginId();
|
||||||
|
for (QAction* a : m_originActions->actions()) {
|
||||||
|
a->setChecked(a->data().toString().toStdString() == currentId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mgr->setCurrentOrigin(originId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::onManageOriginsClicked()
|
||||||
|
{
|
||||||
|
OriginManagerDialog dialog(this);
|
||||||
|
dialog.exec();
|
||||||
|
|
||||||
|
// Refresh the menu in case origins changed
|
||||||
|
rebuildMenu();
|
||||||
|
updateDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::updateDisplay()
|
||||||
|
{
|
||||||
|
FileOrigin* origin = OriginManager::instance()->currentOrigin();
|
||||||
|
if (!origin) {
|
||||||
|
setText(tr("No Origin"));
|
||||||
|
setIcon(QIcon());
|
||||||
|
setToolTip(QString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setText(QString::fromStdString(origin->nickname()));
|
||||||
|
setIcon(iconForOrigin(origin));
|
||||||
|
setToolTip(QString::fromStdString(origin->name()));
|
||||||
|
}
|
||||||
|
|
||||||
|
void OriginSelectorWidget::rebuildMenu()
|
||||||
|
{
|
||||||
|
m_menu->clear();
|
||||||
|
|
||||||
|
// Remove old actions from action group
|
||||||
|
for (QAction* action : m_originActions->actions()) {
|
||||||
|
m_originActions->removeAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* mgr = OriginManager::instance();
|
||||||
|
std::string currentId = mgr->currentOriginId();
|
||||||
|
|
||||||
|
// Add origin entries
|
||||||
|
for (const std::string& originId : mgr->originIds()) {
|
||||||
|
FileOrigin* origin = mgr->getOrigin(originId);
|
||||||
|
if (!origin) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction* action = m_menu->addAction(
|
||||||
|
iconForOrigin(origin),
|
||||||
|
QString::fromStdString(origin->nickname())
|
||||||
|
);
|
||||||
|
action->setCheckable(true);
|
||||||
|
action->setChecked(originId == currentId);
|
||||||
|
action->setData(QString::fromStdString(originId));
|
||||||
|
action->setToolTip(QString::fromStdString(origin->name()));
|
||||||
|
|
||||||
|
m_originActions->addAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add separator and manage action
|
||||||
|
m_menu->addSeparator();
|
||||||
|
|
||||||
|
m_manageAction = m_menu->addAction(
|
||||||
|
BitmapFactory().iconFromTheme("preferences-system"),
|
||||||
|
tr("Manage Origins...")
|
||||||
|
);
|
||||||
|
connect(m_manageAction, &QAction::triggered,
|
||||||
|
this, &OriginSelectorWidget::onManageOriginsClicked);
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon OriginSelectorWidget::iconForOrigin(FileOrigin* origin) const
|
||||||
|
{
|
||||||
|
if (!origin) {
|
||||||
|
return QIcon();
|
||||||
|
}
|
||||||
|
|
||||||
|
QIcon baseIcon = origin->icon();
|
||||||
|
|
||||||
|
// For origins that require authentication, overlay connection status
|
||||||
|
if (origin->requiresAuthentication()) {
|
||||||
|
ConnectionState state = origin->connectionState();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case ConnectionState::Connected:
|
||||||
|
// No overlay needed - use base icon
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ConnectionState::Connecting:
|
||||||
|
// TODO: Animated connecting indicator
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ConnectionState::Disconnected:
|
||||||
|
// Overlay disconnected indicator
|
||||||
|
{
|
||||||
|
QPixmap overlay = BitmapFactory().pixmapFromSvg(
|
||||||
|
"dagViewFail", QSizeF(8, 8));
|
||||||
|
if (!overlay.isNull()) {
|
||||||
|
baseIcon = BitmapFactoryInst::mergePixmap(
|
||||||
|
baseIcon, overlay, BitmapFactoryInst::BottomRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ConnectionState::Error:
|
||||||
|
// Overlay error indicator
|
||||||
|
{
|
||||||
|
QPixmap overlay = BitmapFactory().pixmapFromSvg(
|
||||||
|
"Warning", QSizeF(8, 8));
|
||||||
|
if (!overlay.isNull()) {
|
||||||
|
baseIcon = BitmapFactoryInst::mergePixmap(
|
||||||
|
baseIcon, overlay, BitmapFactoryInst::BottomRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Gui
|
||||||
95
src/Gui/OriginSelectorWidget.h
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
* Copyright (c) 2025 Kindred Systems *
|
||||||
|
* *
|
||||||
|
* This file is part of the FreeCAD CAx development system. *
|
||||||
|
* *
|
||||||
|
* This library is free software; you can redistribute it and/or *
|
||||||
|
* modify it under the terms of the GNU Library General Public *
|
||||||
|
* License as published by the Free Software Foundation; either *
|
||||||
|
* version 2 of the License, or (at your option) any later version. *
|
||||||
|
* *
|
||||||
|
* This library is distributed in the hope that it will be useful, *
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||||
|
* GNU Library General Public License for more details. *
|
||||||
|
* *
|
||||||
|
* You should have received a copy of the GNU Library General Public *
|
||||||
|
* License along with this library; see the file COPYING.LIB. If not, *
|
||||||
|
* write to the Free Software Foundation, Inc., 59 Temple Place, *
|
||||||
|
* Suite 330, Boston, MA 02111-1307, USA *
|
||||||
|
* *
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
#ifndef GUI_ORIGINSELECTORWIDGET_H
|
||||||
|
#define GUI_ORIGINSELECTORWIDGET_H
|
||||||
|
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QActionGroup>
|
||||||
|
#include <fastsignals/signal.h>
|
||||||
|
#include <FCGlobal.h>
|
||||||
|
|
||||||
|
namespace Gui {
|
||||||
|
|
||||||
|
class FileOrigin;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Toolbar widget for selecting the current file origin
|
||||||
|
*
|
||||||
|
* OriginSelectorWidget displays the currently selected origin and provides
|
||||||
|
* a dropdown menu to switch between available origins (Local Files, Silo
|
||||||
|
* instances, etc.).
|
||||||
|
*
|
||||||
|
* Visual design:
|
||||||
|
* Collapsed (toolbar state):
|
||||||
|
* ┌──────────────────┐
|
||||||
|
* │ ☁️ Work ▼ │ ~70-100px wide
|
||||||
|
* └──────────────────┘
|
||||||
|
*
|
||||||
|
* Expanded (dropdown open):
|
||||||
|
* ┌──────────────────┐
|
||||||
|
* │ ✓ ☁️ Work │ ← Current selection (checkmark)
|
||||||
|
* │ ☁️ Prod │
|
||||||
|
* │ 📁 Local │
|
||||||
|
* ├──────────────────┤
|
||||||
|
* │ ⚙️ Manage... │ ← Opens config dialog
|
||||||
|
* └──────────────────┘
|
||||||
|
*/
|
||||||
|
class GuiExport OriginSelectorWidget : public QToolButton
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit OriginSelectorWidget(QWidget* parent = nullptr);
|
||||||
|
~OriginSelectorWidget() override;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void onOriginActionTriggered(QAction* action);
|
||||||
|
void onManageOriginsClicked();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUi();
|
||||||
|
void connectSignals();
|
||||||
|
void disconnectSignals();
|
||||||
|
|
||||||
|
void onOriginRegistered(const std::string& originId);
|
||||||
|
void onOriginUnregistered(const std::string& originId);
|
||||||
|
void onCurrentOriginChanged(const std::string& originId);
|
||||||
|
|
||||||
|
void updateDisplay();
|
||||||
|
void rebuildMenu();
|
||||||
|
QIcon iconForOrigin(FileOrigin* origin) const;
|
||||||
|
|
||||||
|
QMenu* m_menu;
|
||||||
|
QActionGroup* m_originActions;
|
||||||
|
QAction* m_manageAction;
|
||||||
|
|
||||||
|
// Signal connections
|
||||||
|
fastsignals::scoped_connection m_connRegistered;
|
||||||
|
fastsignals::scoped_connection m_connUnregistered;
|
||||||
|
fastsignals::scoped_connection m_connChanged;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Gui
|
||||||
|
|
||||||
|
#endif // GUI_ORIGINSELECTORWIDGET_H
|
||||||
@@ -1142,6 +1142,28 @@ Gui--WorkbenchComboBox::drop-down {
|
|||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Origin Selector */
|
||||||
|
Gui--OriginSelectorWidget {
|
||||||
|
background-color: #313244;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border: 1px solid #45475a;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 70px;
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui--OriginSelectorWidget:hover {
|
||||||
|
border-color: #585b70;
|
||||||
|
background-color: #45475a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui--OriginSelectorWidget::menu-indicator {
|
||||||
|
subcontrol-origin: padding;
|
||||||
|
subcontrol-position: center right;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Task Panel */
|
/* Task Panel */
|
||||||
QSint--ActionGroup {
|
QSint--ActionGroup {
|
||||||
background-color: #313244;
|
background-color: #313244;
|
||||||
|
|||||||
@@ -1163,6 +1163,28 @@ Gui--WorkbenchComboBox::drop-down {
|
|||||||
border-radius: 0 4px 4px 0;
|
border-radius: 0 4px 4px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Origin Selector */
|
||||||
|
Gui--OriginSelectorWidget {
|
||||||
|
background-color: #313244;
|
||||||
|
color: #cdd6f4;
|
||||||
|
border: 1px solid #45475a;
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
min-width: 70px;
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui--OriginSelectorWidget:hover {
|
||||||
|
border-color: #585b70;
|
||||||
|
background-color: #45475a;
|
||||||
|
}
|
||||||
|
|
||||||
|
Gui--OriginSelectorWidget::menu-indicator {
|
||||||
|
subcontrol-origin: padding;
|
||||||
|
subcontrol-position: center right;
|
||||||
|
right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Task Panel */
|
/* Task Panel */
|
||||||
QSint--ActionGroup {
|
QSint--ActionGroup {
|
||||||
background-color: #313244;
|
background-color: #313244;
|
||||||
|
|||||||
@@ -682,7 +682,10 @@ MenuItem* StdWorkbench::setupMenuBar() const
|
|||||||
file->setCommand("&File");
|
file->setCommand("&File");
|
||||||
*file << "Std_New" << "Std_Open" << "Std_RecentFiles" << "Separator" << "Std_CloseActiveWindow"
|
*file << "Std_New" << "Std_Open" << "Std_RecentFiles" << "Separator" << "Std_CloseActiveWindow"
|
||||||
<< "Std_CloseAllWindows" << "Separator" << "Std_Save" << "Std_SaveAs"
|
<< "Std_CloseAllWindows" << "Separator" << "Std_Save" << "Std_SaveAs"
|
||||||
<< "Std_SaveCopy" << "Std_SaveAll" << "Std_Revert" << "Separator" << "Std_Import"
|
<< "Std_SaveCopy" << "Std_SaveAll" << "Std_Revert"
|
||||||
|
<< "Separator" << "Origin_Commit" << "Origin_Pull" << "Origin_Push"
|
||||||
|
<< "Origin_Info" << "Origin_BOM"
|
||||||
|
<< "Separator" << "Std_Import"
|
||||||
<< "Std_Export" << "Std_MergeProjects" << "Std_ProjectInfo"
|
<< "Std_Export" << "Std_MergeProjects" << "Std_ProjectInfo"
|
||||||
<< "Separator" << "Std_Print" << "Std_PrintPreview" << "Std_PrintPdf"
|
<< "Separator" << "Std_Print" << "Std_PrintPreview" << "Std_PrintPdf"
|
||||||
<< "Separator" << "Std_Quit";
|
<< "Separator" << "Std_Quit";
|
||||||
@@ -834,7 +837,13 @@ ToolBarItem* StdWorkbench::setupToolBars() const
|
|||||||
// File
|
// File
|
||||||
auto file = new ToolBarItem(root);
|
auto file = new ToolBarItem(root);
|
||||||
file->setCommand("File");
|
file->setCommand("File");
|
||||||
*file << "Std_New" << "Std_Open" << "Std_Save";
|
*file << "Std_Origin" << "Std_New" << "Std_Open" << "Std_Save";
|
||||||
|
|
||||||
|
// Origin Tools (PLM operations - commands auto-disable when not applicable)
|
||||||
|
auto originTools = new ToolBarItem(root);
|
||||||
|
originTools->setCommand("Origin Tools");
|
||||||
|
*originTools << "Origin_Commit" << "Origin_Pull" << "Origin_Push"
|
||||||
|
<< "Separator" << "Origin_Info" << "Origin_BOM";
|
||||||
|
|
||||||
// Edit
|
// Edit
|
||||||
auto edit = new ToolBarItem(root);
|
auto edit = new ToolBarItem(root);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def setup_kindred_addons():
|
|||||||
# Define built-in addons with their paths relative to mods/
|
# Define built-in addons with their paths relative to mods/
|
||||||
addons = [
|
addons = [
|
||||||
("ztools", "ztools/ztools"), # mods/ztools/ztools/
|
("ztools", "ztools/ztools"), # mods/ztools/ztools/
|
||||||
("silo", "silo/pkg/freecad"), # mods/silo/pkg/freecad/
|
("silo", "silo/freecad"), # mods/silo/freecad/
|
||||||
]
|
]
|
||||||
|
|
||||||
for name, subpath in addons:
|
for name, subpath in addons:
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ def setup_kindred_workbenches():
|
|||||||
|
|
||||||
addons = [
|
addons = [
|
||||||
("ztools", "ztools/ztools"),
|
("ztools", "ztools/ztools"),
|
||||||
("silo", "silo/pkg/freecad"),
|
("silo", "silo/freecad"),
|
||||||
]
|
]
|
||||||
|
|
||||||
for name, subpath in addons:
|
for name, subpath in addons:
|
||||||
|
|||||||