Compare commits
255
Commits
fix_tooltips
...
ice_exec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c22f50963f | ||
|
|
626402965d | ||
|
|
556132c2dd | ||
|
|
a22b7b7189 | ||
|
|
8e41a42ced | ||
|
|
e0f7bdfc13 | ||
|
|
ec0b96215f | ||
|
|
e27a8be526 | ||
|
|
f5354776f1 | ||
|
|
70cd9f185e | ||
|
|
33eaf6f86d | ||
|
|
9413eb2206 | ||
|
|
e986f77e72 | ||
|
|
75095ce874 | ||
|
|
89b5610aa2 | ||
|
|
f721098ae0 | ||
|
|
bfff6454ef | ||
|
|
b90e5e4274 | ||
|
|
bacd4558e1 | ||
|
|
008e698766 | ||
|
|
ed7f066f20 | ||
|
|
d7c81a08ef | ||
|
|
aadb6c86f3 | ||
|
|
5653a66a31 | ||
|
|
99b9c51ad6 | ||
|
|
fe92389eab | ||
|
|
1ca5b217fc | ||
|
|
4e4ab52032 | ||
|
|
4cc513f1d3 | ||
|
|
7904065333 | ||
|
|
961e06c31f | ||
|
|
4132bdaba4 | ||
|
|
1c75f5f530 | ||
|
|
d8e7dd7c3b | ||
|
|
d0377475bb | ||
|
|
7826ff17c0 | ||
|
|
a46982333b | ||
|
|
e2cebaa5c8 | ||
|
|
d03987591c | ||
|
|
e909c37f88 | ||
|
|
e998edc20f | ||
|
|
1c1be2b334 | ||
|
|
5d89257643 | ||
|
|
897e134714 | ||
|
|
e6cb97028b | ||
|
|
6c0dc80d43 | ||
|
|
ce3a4562d9 | ||
|
|
217f0cd4cc | ||
|
|
1e649ccce6 | ||
|
|
cbc69ec5fd | ||
|
|
0d1eedc34a | ||
|
|
4e85bf4520 | ||
|
|
5d0023a80b | ||
|
|
e300de2425 | ||
|
|
569877752e | ||
|
|
4846cf7693 | ||
|
|
16a46955f4 | ||
|
|
13a6d68b60 | ||
|
|
1affd992de | ||
|
|
d951258891 | ||
|
|
f5dfb0264b | ||
|
|
3b8f5c5093 | ||
|
|
253d213818 | ||
|
|
f89cb3d61e | ||
|
|
eb6573399b | ||
|
|
8ba8ce76ce | ||
|
|
e65b8e9d4d | ||
|
|
2fc379fbef | ||
|
|
fb96a75a8e | ||
|
|
240d15af47 | ||
|
|
2530e43797 | ||
|
|
b05d85d6c3 | ||
|
|
52f2e77310 | ||
|
|
bd00c6de3e | ||
|
|
87aea4812f | ||
|
|
21ae3b14c8 | ||
|
|
708ae185f1 | ||
|
|
5c6dbd7bd1 | ||
|
|
f19a280b7f | ||
|
|
47cf9e2fe6 | ||
|
|
df75518389 | ||
|
|
90c371c72f | ||
|
|
9609f7e6cf | ||
|
|
30c83b7c72 | ||
|
|
382d8b468c | ||
|
|
cbe6d07a86 | ||
|
|
6d67700ee6 | ||
|
|
e9cf1db184 | ||
|
|
e4c98d947e | ||
|
|
ab9de97d6e | ||
|
|
4e425a3380 | ||
|
|
ac67c84ce7 | ||
|
|
79120ea7a5 | ||
|
|
a85a7f624a | ||
|
|
19e4da9936 | ||
|
|
1494c37cc7 | ||
|
|
af1fbaf3ed | ||
|
|
82a93e39f8 | ||
|
|
36684f8ee5 | ||
|
|
c8cd2ae1ad | ||
|
|
93afb5b602 | ||
|
|
429b0bb8f8 | ||
|
|
80ce5447e5 | ||
|
|
609b0dceb1 | ||
|
|
ea47042816 | ||
|
|
4b23e17534 | ||
|
|
f69301611a | ||
|
|
c04ac5cd48 | ||
|
|
aa39349fc4 | ||
|
|
d1c915fef3 | ||
|
|
69bdf07423 | ||
|
|
4115a6c790 | ||
|
|
8f8aae57de | ||
|
|
362f75c8b2 | ||
|
|
39e855e691 | ||
|
|
73303ccd33 | ||
|
|
6826494898 | ||
|
|
69fcfbd6ce | ||
|
|
c1d62a2ff6 | ||
|
|
1fa419eae2 | ||
|
|
e3a3eb6d85 | ||
|
|
18a32f9a05 | ||
|
|
b7c127b2d8 | ||
|
|
f83f94fb07 | ||
|
|
23f76a1c80 | ||
|
|
cc0f6ea448 | ||
|
|
cbb6936553 | ||
|
|
633433cab9 | ||
|
|
3ab7ed0b71 | ||
|
|
fc1adf8c28 | ||
|
|
79cf313b5b | ||
|
|
00834a668c | ||
|
|
32c9c23d7e | ||
|
|
5ac8d2ada6 | ||
|
|
54eb6ceaa9 | ||
|
|
540db91311 | ||
|
|
1ee2193c88 | ||
|
|
84877af947 | ||
|
|
388cf92a70 | ||
|
|
840441a749 | ||
|
|
c39a510d09 | ||
|
|
299d46aa2c | ||
|
|
345f6ed9fe | ||
|
|
31aa9d9bf6 | ||
|
|
0c063a3876 | ||
|
|
af253198d2 | ||
|
|
cc381d605c | ||
|
|
c2f954a79d | ||
|
|
e79a73147a | ||
|
|
6a921ae900 | ||
|
|
0f13d27efe | ||
|
|
d52347cd71 | ||
|
|
0ab628fb6c | ||
|
|
1a8a492ef7 | ||
|
|
74066c0ffe | ||
|
|
0269d5fa86 | ||
|
|
84a5a11a9c | ||
|
|
a01e6df7fa | ||
|
|
d4caccdbfb | ||
|
|
4ede8a62ea | ||
|
|
ba11248ac9 | ||
|
|
22cde8aa0d | ||
|
|
3dba49e899 | ||
|
|
23d7a9e1ad | ||
|
|
7bd9d3f4ad | ||
|
|
7740f64bfd | ||
|
|
7ccbb69f5c | ||
|
|
b4ba527c12 | ||
|
|
a62a41a0aa | ||
|
|
904023c5fa | ||
|
|
a9fc3d43a0 | ||
|
|
4febc07aa0 | ||
|
|
198e76f722 | ||
|
|
836301e75f | ||
|
|
ad37a652c5 | ||
|
|
5bed54c953 | ||
|
|
5675c4e117 | ||
|
|
16f1e568a5 | ||
|
|
e375274cbe | ||
|
|
cf3ef9323a | ||
|
|
7e2e2bf1b0 | ||
|
|
781c9dcc86 | ||
|
|
a8a922160d | ||
|
|
367bd0e3b9 | ||
|
|
4215d96ba9 | ||
|
|
50d18eb10d | ||
|
|
b65fedc781 | ||
|
|
88c8663d29 | ||
|
|
8c7dd2460d | ||
|
|
f20ee21ce7 | ||
|
|
a61e21bd87 | ||
|
|
39014696f3 | ||
|
|
af278d2d87 | ||
|
|
636e170f44 | ||
|
|
3d4fe8f832 | ||
|
|
94285918bf | ||
|
|
2ba7f949cc | ||
|
|
fc82ccf221 | ||
|
|
e71b1518f0 | ||
|
|
7738da7d16 | ||
|
|
c91eedcca0 | ||
|
|
4f5f4c61f3 | ||
|
|
2f31fb6284 | ||
|
|
d18caa7ba1 | ||
|
|
ac88586c8f | ||
|
|
968dfbac2a | ||
|
|
0501418da6 | ||
|
|
4efd853c96 | ||
|
|
a384eb9f73 | ||
|
|
4747fe5535 | ||
|
|
94eb1e37cb | ||
|
|
fd1ae60e49 | ||
|
|
62d06180ea | ||
|
|
dba3b9371c | ||
|
|
aadfe2a677 | ||
|
|
c0c34efc47 | ||
|
|
b66fb02174 | ||
|
|
3cf026ff47 | ||
|
|
8619084fa7 | ||
|
|
f6df644ab1 | ||
|
|
1183dee418 | ||
|
|
ffaba90c96 | ||
|
|
9eb760b3c7 | ||
|
|
70fcb30c67 | ||
|
|
553ab7b858 | ||
|
|
8dcf41e9cf | ||
|
|
a0bf0e93f4 | ||
|
|
5529e80e17 | ||
|
|
5df7cec55a | ||
|
|
8f705d42b3 | ||
|
|
fc2f85b842 | ||
|
|
ffe5eee584 | ||
|
|
b7897fc7ac | ||
|
|
b4d18a62c9 | ||
|
|
c6b450f45c | ||
|
|
6a46049610 | ||
|
|
21712711f7 | ||
|
|
9f1bb177b8 | ||
|
|
dcd2cc22e1 | ||
|
|
af91ee1512 | ||
|
|
b4d63aa3cc | ||
|
|
d0e32911ad | ||
|
|
75fa582774 | ||
|
|
69155162ca | ||
|
|
2c865c123a | ||
|
|
0ec955cc90 | ||
|
|
0c4e22537d | ||
|
|
601eb4ece1 | ||
|
|
b84a638199 | ||
|
|
34350fd469 | ||
|
|
a469da5cc6 | ||
|
|
8be8a67f18 | ||
|
|
abc2e55610 | ||
|
|
cfd15d3371 | ||
|
|
44369bae43 |
No files matched your search
@@ -22,7 +22,9 @@ namespace mbts.ui {
|
||||
AddBoxesGeometryKey = nameof(AddBoxesGeometryKey),
|
||||
AddFileGeometryKey = nameof(AddFileGeometryKey),
|
||||
AddFileThinGeometryKey = nameof(AddFileThinGeometryKey),
|
||||
AttachAlgosGeometryKey = nameof(AttachAlgosGeometryKey),
|
||||
CheckMarkGeometryKey = nameof(CheckMarkGeometryKey),
|
||||
DetachAlgosGeometryKey = nameof(DetachAlgosGeometryKey),
|
||||
DiskGeometryKey = nameof(DiskGeometryKey),
|
||||
DiskFileGeometryKey = nameof(DiskFileGeometryKey),
|
||||
DocumentGeometryKey = nameof(DocumentGeometryKey),
|
||||
@@ -39,6 +41,7 @@ namespace mbts.ui {
|
||||
PaymentGeometryKey = nameof(PaymentGeometryKey),
|
||||
PencilGeometryKey = nameof(PencilGeometryKey),
|
||||
PlayGeometryKey = nameof(PlayGeometryKey),
|
||||
ProhibitedGeometryKey = nameof(ProhibitedGeometryKey),
|
||||
StarGeometryKey = nameof(StarGeometryKey),
|
||||
ToolsGeometryKey = nameof(ToolsGeometryKey),
|
||||
TrashGeometryKey = nameof(TrashGeometryKey),
|
||||
@@ -115,6 +118,7 @@ namespace mbts.ui {
|
||||
public static ImageSource AddBoxes => create_icon(AddBoxesGeometryKey);
|
||||
public static ImageSource AddEncircled { get { return load_resource_and_freeze("AddEncircled"); } }
|
||||
public static ImageSource AddFile => create_icon(Smashicons.AddFileGeometryKey);
|
||||
public static ImageSource AttachAlgos => create_icon(AttachAlgosGeometryKey);
|
||||
public static ImageSource Beaker { get { return load_resource_and_freeze("Beaker"); } }
|
||||
public static ImageSource Book { get { return load_resource_and_freeze("Book"); } }
|
||||
|
||||
@@ -139,6 +143,7 @@ namespace mbts.ui {
|
||||
public static ImageSource CheckMark => create_icon(CheckMarkGeometryKey);
|
||||
public static ImageSource Clock { get { return load_resource_and_freeze("Clock"); } }
|
||||
public static ImageSource Defrag { get { return load_resource_and_freeze("Defrag"); } }
|
||||
public static ImageSource DetachAlgos => create_icon(DetachAlgosGeometryKey);
|
||||
public static ImageSource Disk => create_icon(DiskGeometryKey);
|
||||
public static ImageSource DiskFile => create_icon(DiskFileGeometryKey);
|
||||
public static ImageSource Display { get { return load_resource_and_freeze("Display"); } }
|
||||
@@ -158,6 +163,7 @@ namespace mbts.ui {
|
||||
public static ImageSource Knight { get { return load_resource_and_freeze("Knight"); } }
|
||||
public static ImageSource Payment => create_icon(PaymentGeometryKey);
|
||||
public static ImageSource Pencil => create_icon(PencilGeometryKey);
|
||||
public static ImageSource Prohibited => create_icon(ProhibitedGeometryKey);
|
||||
public static ImageSource RedoCircular => create_icon(UndoCircularGeometryKey, Brushes.Black, null, new ScaleTransform(-1, 1));
|
||||
public static ImageSource Spawn { get { return load_resource_and_freeze("Spawn"); } }
|
||||
public static ImageSource Star => create_icon(StarGeometryKey);
|
||||
|
||||
@@ -40,6 +40,69 @@
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Unlink free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.AttachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M51.173,3.801c-5.068-5.068-13.315-5.066-18.384,0l-9.192,9.192c-0.781,0.781-0.781,2.047,0,2.828
|
||||
c0.781,0.781,2.047,0.781,2.828,0l9.192-9.192c1.691-1.69,3.951-2.622,6.363-2.622c2.413,0,4.673,0.932,6.364,2.623
|
||||
s2.623,3.951,2.623,6.364c0,2.412-0.932,4.672-2.623,6.363L36.325,31.379c-3.51,3.508-9.219,3.508-12.729,0
|
||||
c-0.781-0.781-2.047-0.781-2.828,0s-0.781,2.048,0,2.828c2.534,2.534,5.863,3.801,9.192,3.801s6.658-1.267,9.192-3.801
|
||||
l12.021-12.021c2.447-2.446,3.795-5.711,3.795-9.192C54.968,9.512,53.62,6.248,51.173,3.801z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M27.132,40.57l-7.778,7.778c-1.691,1.691-3.951,2.623-6.364,2.623c-2.412,0-4.673-0.932-6.364-2.623
|
||||
c-3.509-3.509-3.509-9.219,0-12.728L17.94,24.306c1.691-1.69,3.951-2.622,6.364-2.622c2.412,0,4.672,0.932,6.363,2.622
|
||||
c0.781,0.781,2.047,0.781,2.828,0s0.781-2.047,0-2.828c-5.067-5.067-13.314-5.068-18.384,0L3.797,32.793
|
||||
c-2.446,2.446-3.794,5.711-3.794,9.192c0,3.48,1.348,6.745,3.795,9.191c2.446,2.447,5.711,3.795,9.191,3.795
|
||||
c3.481,0,6.746-1.348,9.192-3.795l7.778-7.778c0.781-0.781,0.781-2.047,0-2.828S27.913,39.789,27.132,40.57z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/freepik
|
||||
Ttile: Disconnected chains free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<x:Array x:Key="{x:Static mbts:Icons.DetachAlgosGeometryKey}" Type="sys:String">
|
||||
<sys:String>
|
||||
M462.557,77.627c-14.323-25.556-41.488-41.423-70.889-41.423c-13.835,0-27.54,3.585-39.602,10.364
|
||||
l-152.557,85.635l17.55,31.279l152.565-85.635c6.747-3.78,14.371-5.779,22.069-5.779c16.428,0,31.596,8.852,39.594,23.11
|
||||
c5.909,10.535,7.356,22.752,4.072,34.408c-3.268,11.656-10.876,21.346-21.411,27.255l-138.291,77.604
|
||||
c-2.634-16.851-10.494-32.392-22.793-44.699c-21.687-21.663-56.063-29.328-84.935-18.964l-12.485,4.495l12.128,33.734
|
||||
l12.534-4.471c16.127-5.869,35.319-1.496,47.406,10.567c6.974,6.982,11.388,15.826,12.786,25.41
|
||||
c-0.585-0.138-1.171-0.285-1.731-0.447c-11.681-3.284-21.354-10.9-27.271-21.476l-6.519-11.518l-31.303,17.59l6.56,11.518
|
||||
c10.6,18.915,27.93,32.571,48.836,38.44c0.723,0.187,1.455,0.39,2.187,0.561c-1.105,1.398-2.284,2.731-3.536,3.983
|
||||
l-114.32,114.328c-8.551,8.543-19.931,13.241-32.035,13.241c-12.112,0-23.492-4.698-32.027-13.241
|
||||
c-8.535-8.543-13.233-19.907-13.233-32.027c0-12.112,4.698-23.483,13.233-32.018l99.291-99.282l-25.369-25.369l-99.282,99.282
|
||||
C8.446,319.427,0,339.806,0,361.468c0,21.679,8.446,42.049,23.776,57.396c15.339,15.339,35.709,23.768,57.388,23.768
|
||||
s42.049-8.429,57.388-23.768l114.345-114.32c8.372-8.405,14.81-18.566,18.842-29.71c6.625-1.764,12.924-4.3,18.777-7.6
|
||||
l140.99-79.115c18.875-10.608,32.514-27.946,38.375-48.828C475.749,118.4,473.148,96.493,462.557,77.627z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M173.171,141.769c4.503-0.821,7.511-5.145,6.69-9.657l-17.103-94.942
|
||||
c-0.821-4.519-5.162-7.527-9.665-6.722c-4.511,0.821-7.527,5.145-6.706,9.673l17.111,94.925
|
||||
C164.311,139.575,168.644,142.574,173.171,141.769z
|
||||
</sys:String>
|
||||
<sys:String>
|
||||
M43.561,191.776l94.934-17.119c4.519-0.821,7.527-5.145,6.706-9.657
|
||||
c-0.821-4.528-5.145-7.543-9.649-6.722L40.61,175.405c-4.503,0.797-7.519,5.145-6.698,9.657
|
||||
C34.717,189.582,39.058,192.589,43.561,191.776z
|
||||
</sys:String>
|
||||
</x:Array>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/chanut
|
||||
Ttile: Cancel free icon
|
||||
Credit: Icon made by Chanut from www.flaticon.com-->
|
||||
<sys:String x:Key="{x:Static mbts:Icons.ProhibitedGeometryKey}">
|
||||
M32.759,0C14.696,0,0,14.695,0,32.759s14.695,32.759,32.759,32.759s32.759-14.695,32.759-32.759S50.822,0,32.759,0z
|
||||
M6,32.759C6,18.004,18.004,6,32.759,6c6.648,0,12.734,2.443,17.419,6.472L12.472,50.178C8.443,45.493,6,39.407,6,32.759z
|
||||
M32.759,59.518c-5.948,0-11.447-1.953-15.895-5.248l37.405-37.405c3.295,4.448,5.248,9.947,5.248,15.895
|
||||
C59.518,47.514,47.514,59.518,32.759,59.518z
|
||||
</sys:String>
|
||||
|
||||
<!--File (the icon is free to use if we include this information in our credit section)
|
||||
Source: https://www.flaticon.com/authors/smashicons
|
||||
Ttile: File free icon
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
|
||||
namespace mbts.spark {
|
||||
class MarketViewWindow : SparkWindow<MarketViewWindowFactory.StartupData>, ISharedWindow {
|
||||
const string _window_type = "MarketView", _thread_id = "MarketView", _window_type_display_name = "Market View";
|
||||
const string _window_type = "MarketView", _window_type_display_name = "Market View";
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly AllowOnce _init_guard = new AllowOnce("MarketView");
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -32,6 +32,7 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().market_view ? "MarketView" : null;
|
||||
}
|
||||
|
||||
event Action<MarketViewWindowFactory.StartupData> _state_changed;
|
||||
@@ -47,7 +48,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
|
||||
@@ -19,7 +19,7 @@ using static bts.shell.TimeAndSalesWindowFactory;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class TimeAndSalesWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "TimeAndSales", _window_type_display_name = "Time And Sales";
|
||||
readonly MDConsumer _consumer;
|
||||
readonly TimeAndSalesWindowFactory _factory;
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id { get; }
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
|
||||
public override void Dispose() =>
|
||||
|
||||
@@ -16,7 +16,7 @@ using System.Windows;
|
||||
using static bts.shell.SyntheticManagementWindowFactory;
|
||||
|
||||
namespace mbts.spark {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISlowWindow {
|
||||
class SyntheticManagementWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
const string _window_type = "SyntheticManagement";
|
||||
SingleValuePump<SyntheticManagementDialogVM> _vm = new SingleValuePump<SyntheticManagementDialogVM>();
|
||||
StartupData _state;
|
||||
@@ -33,8 +33,20 @@ namespace mbts.spark {
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().synthetic_management ? "SyntheticManagement" : null;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
Log.notnull(_state);
|
||||
|
||||
@@ -101,16 +113,6 @@ namespace mbts.spark {
|
||||
else _view.DataContext = _vm.value;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public override StartupData SaveState => _state = _vm.value.create();
|
||||
public override double MinWidth =>
|
||||
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public override void Dispose() {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoTemplateTreeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
@@ -559,71 +557,14 @@
|
||||
</Grid>
|
||||
</RadioButton>
|
||||
</Grid>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
<usp:AlgoEditorView
|
||||
Grid.Row="2"
|
||||
DataContext="{Binding content_source}"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding content_source.algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoStudioView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
Grid.Column="3"
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
SelectedItem="{Binding selected_definition.algo_root}"
|
||||
ItemsSource="{Binding selected_definition.algos_view}"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
Width="140"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding content_source}" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding selected_definition.templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding selected_definition.algo_root}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="3" Margin="{StaticResource Margin.TP2}" Background="Transparent">
|
||||
<c:DialogSkirt.RightItems>
|
||||
<x:ArrayExtension Type="{x:Type Button}">
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using mbts.spark;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
@@ -18,26 +16,11 @@ namespace mbts.ui.spark {
|
||||
public AlgoStudioView() {
|
||||
InitializeComponent();
|
||||
DataContextChanged += (s, e) => set_controller();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
set_controller();
|
||||
// local methods
|
||||
void set_controller() => Disposable.replace(ref _controller, AlgoStudioViewControllerFactory.create(DataContext, _tree_view, name_text_box, custom_badge_color_picker_button, custom_badge_color_picker_popup));
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoStudioView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
|
||||
public new AlgoStudioVM DataContext {
|
||||
get => base.DataContext as AlgoStudioVM;
|
||||
set => base.DataContext = value;
|
||||
|
||||
@@ -13,7 +13,7 @@ using System.Windows;
|
||||
using StartupData = bts.shell.AlgoStudioWindowFactory.StartupData;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, IShellWindow {
|
||||
internal class AlgoStudioWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string
|
||||
NAME = "Algo Studio",
|
||||
TemplatesEnabledKey = nameof(TemplatesEnabledKey),
|
||||
@@ -37,7 +37,7 @@ namespace mbts.spark {
|
||||
: base(NAME, bus, factory, SparkCommands.ShowAlgoStudio.icon) {
|
||||
_model = model;
|
||||
_state = state;
|
||||
thread_init();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().algo_studio ? "AlgoStudio" : null;
|
||||
}
|
||||
|
||||
public override StartupData SaveState => _state = _vm.create();
|
||||
@@ -45,12 +45,12 @@ namespace mbts.spark {
|
||||
public override string window_type => NAME;
|
||||
public override UIElement Content => _view;
|
||||
public override IEnumerable<SparkCommand> Commands => _commands;
|
||||
public string thread_id => NAME;
|
||||
public override double MinWidth =>
|
||||
SPARK_MENU_COLLAPSED_WIDTH +
|
||||
LAUNCHER_BUTTON_BORDER_WIDTH +
|
||||
DEFAULT_CONTENT_WIDTH;
|
||||
public override double MinHeight => DEFAULT_CONTENT_HEIGHT;
|
||||
public string thread_id { get; }
|
||||
|
||||
bts.config.AlgoStudioOptions _ui_configuration => _model.algo_model.ui_configuration;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using ProtoBuf;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
@@ -10,12 +11,6 @@ namespace mbts.spark {
|
||||
public readonly string name;
|
||||
public readonly AlgoStructureKey structure;
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinitionImmutable definition)
|
||||
: this(definition.CustomCase.type_id(), definition) { }
|
||||
|
||||
public AlgoNameStructureKey(string name, CustomOrderDefinitionImmutable definition)
|
||||
: this(name, new AlgoStructureKey(definition)) { }
|
||||
|
||||
public AlgoNameStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(base_type.type_id(), new AlgoStructureKey(base_type)) { }
|
||||
|
||||
@@ -31,31 +26,22 @@ namespace mbts.spark {
|
||||
public static bool operator !=(AlgoNameStructureKey a, AlgoNameStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
struct AlgoStructureKey : IEquatable<AlgoStructureKey> {
|
||||
[ProtoMember(1)]
|
||||
readonly int[] _structure;
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinition.CustomOneofCase base_type)
|
||||
: this(new[] { (int)base_type }) { }
|
||||
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition) {
|
||||
int[] s = new[] { (int)definition.CustomCase };
|
||||
_build_structure(definition, ref s, 1);
|
||||
_structure = s;
|
||||
}
|
||||
public AlgoStructureKey(CustomOrderDefinitionImmutable definition, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
_structure = AlgoStructureSerializer.to_array(definition, dynamic_type_index);
|
||||
|
||||
public AlgoStructureKey(int[] structure) =>
|
||||
_structure = structure;
|
||||
|
||||
public CustomOrderDefinitionImmutable build_def() {
|
||||
int pos = 0;
|
||||
var mdef = new CustomOrderDefinition();
|
||||
_build_def(_structure, ref pos, ref mdef);
|
||||
return new CustomOrderDefinitionImmutable(mdef);
|
||||
}
|
||||
public IReadOnlyList<int> structure => _structure ?? Array.Empty<int>();
|
||||
|
||||
public bool Equals(AlgoStructureKey other) =>
|
||||
/// <see cref="Enumerable.SequenceEqual{TSource}(IEnumerable{TSource}, IEnumerable{TSource})"/> is not optimized to check Count/Length, since the args are <see cref="IEnumerable{T}"/>.
|
||||
_structure.Length == other._structure.Length &&
|
||||
_structure.SequenceEqual(other._structure);
|
||||
|
||||
@@ -67,35 +53,105 @@ namespace mbts.spark {
|
||||
|
||||
public static bool operator ==(AlgoStructureKey a, AlgoStructureKey b) => a.Equals(b);
|
||||
public static bool operator !=(AlgoStructureKey a, AlgoStructureKey b) => !a.Equals(b);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinitionImmutable definition, ref int[] structure, int pos) =>
|
||||
_build_structure(definition.CloneToMutable(), ref structure, pos);
|
||||
static class AlgoStructureSerializer {
|
||||
const int
|
||||
_dynamic_mask = 1 << 31,
|
||||
_child_mask_offset = 30;
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, int pos) {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
if (cad[di].get_child(algo) is CustomOrderDefinition child_algo) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
int del_mask = 1 << (31 - di);
|
||||
structure[pos++] = (int)child_algo.CustomCase | del_mask;
|
||||
_build_structure(child_algo, ref structure, pos);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ArrayHelper.trim(ref structure, pos);
|
||||
public static CustomOrderDefinitionImmutable build_algoi(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable build_algo(this AlgoStructureKey structure_key, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure_key.structure, dynamic_helper);
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static AlgoStructureKey structure_key(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
new AlgoStructureKey(to_array(algo, dynamic_type_index));
|
||||
|
||||
public static int[] to_array(CustomOrderDefinitionImmutable algo, IMaskIndexInt32<string> dynamic_type_index) =>
|
||||
to_array(algo.CloneToMutable(), dynamic_type_index);
|
||||
|
||||
public static int[] to_array(CustomOrderDefinition algo, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
int[] s = new[] { (int)algo.CustomCase };
|
||||
_build_structure(algo, ref s, dynamic_type_index);
|
||||
return s;
|
||||
}
|
||||
|
||||
static void _build_def(int[] structure, ref int pos, ref CustomOrderDefinition algo) {
|
||||
for (int i = pos; i < structure.Length; i++) {
|
||||
public static CustomOrderDefinitionImmutable from_array(int[] structure, DynamicOrderHelper dynamic_helper) =>
|
||||
parsei(structure, dynamic_helper);
|
||||
|
||||
public static CustomOrderDefinitionImmutable parsei(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) =>
|
||||
new CustomOrderDefinitionImmutable(parse(structure, dynamic_helper));
|
||||
|
||||
public static CustomOrderDefinition parse(IReadOnlyList<int> structure, DynamicOrderHelper dynamic_helper) {
|
||||
if (structure == null || structure.Count == 0)
|
||||
return new CustomOrderDefinition();
|
||||
int pos = 1;
|
||||
var mdef = OrderDataHelper.create_algo((CustomOrderDefinition.CustomOneofCase)structure[0]);
|
||||
_build_def(structure, ref pos, ref mdef, dynamic_helper);
|
||||
return mdef;
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
if (structure == null || structure.Length == 0)
|
||||
structure = new int[1];
|
||||
structure[0] = (int)algo.CustomCase;
|
||||
int p = 1;
|
||||
_build_structure(algo, ref structure, ref p, dynamic_type_index);
|
||||
ArrayHelper.trim(ref structure, p);
|
||||
}
|
||||
|
||||
static void _build_structure(CustomOrderDefinition algo, ref int[] structure, ref int pos, IMaskIndexInt32<string> dynamic_type_index) {
|
||||
IEnumerable<(CustomOrderDefinition child_algo, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = dynamic_type_index.to_int(algo.Dynamic.DynamicOrderType) | _dynamic_mask;
|
||||
children = algo.Dynamic.OrderParams.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef).Select((kv, i) => (kv.Value.AsOrderDef, i));
|
||||
} else {
|
||||
var cad = OrderDataHelper.get_child_algo_data(algo);
|
||||
for (int di = 0; di < cad.Count; di++) {
|
||||
int mask = 1 << (31 - di);
|
||||
if ((structure[i] & mask) == mask) {
|
||||
var type = (CustomOrderDefinition.CustomOneofCase)(structure[i] ^ mask);
|
||||
var child_algo = OrderDataHelper.Defaults.CustomOrderDefinition(type);
|
||||
pos++;
|
||||
_build_def(structure, ref pos, ref child_algo);
|
||||
cad[di].assign_child(algo, child_algo);
|
||||
children = cad.Select((c, i) => (c.get_child(algo), i));
|
||||
children = children.Where(c => c.child_algo != null);
|
||||
}
|
||||
foreach ((var child, int field_pos) in children) {
|
||||
ArrayHelper.ensure_capacity(ref structure, pos + 1);
|
||||
structure[pos++] = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.to_int(child.CustomCase) | _child_algo_mask(field_pos);
|
||||
_build_structure(child, ref structure, ref pos, dynamic_type_index);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a mask for a particular child-algo field of a particular algo definition.
|
||||
/// </summary>
|
||||
/// <param name="field_pos">The index wrt the deterministic enumeration of child algos for a particular definition (e.g., Hedge = 0, Child = 1).</param>
|
||||
static int _child_algo_mask(int field_pos) => 1 << (_child_mask_offset - field_pos);
|
||||
|
||||
static void _build_def(IReadOnlyList<int> structure, ref int pos, ref CustomOrderDefinition algo, DynamicOrderHelper dynamic_helper) {
|
||||
var local_algo = algo;
|
||||
IEnumerable<(Action<CustomOrderDefinition> assign_child, int field_pos)> children;
|
||||
if (algo.CustomCase == CustomOrderDefinition.CustomOneofCase.Dynamic) {
|
||||
var dyn_def = algo.Dynamic;
|
||||
dyn_def.DynamicOrderType = dynamic_helper.dynamic_type_index.from_int(structure[pos++] ^ _dynamic_mask);
|
||||
dynamic_helper.add_dynamic_params(ref dyn_def);
|
||||
children = dyn_def.OrderParams
|
||||
.Where(kv => kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
.Select((kv, i) => (new Action<CustomOrderDefinition>(child_algo => kv.Value.AsOrderDef = child_algo), i));
|
||||
} else {
|
||||
children = OrderDataHelper.get_child_algo_data(algo)
|
||||
.Select((c, i) => (new Action<CustomOrderDefinition>(child_algo => c.assign_child(local_algo, child_algo)), i));
|
||||
}
|
||||
using (var children_en = children.GetEnumerator()) {
|
||||
while (pos < structure.Count && children_en.MoveNext()) {
|
||||
(var assign_child, int field_pos) = children_en.Current;
|
||||
int mask = _child_algo_mask(field_pos);
|
||||
if ((structure[pos] & mask) == mask) {
|
||||
var type = EnumMaskIndexInt32<CustomOrderDefinition.CustomOneofCase>.from_int(structure[pos++] ^ mask);
|
||||
var child_algo = OrderDataHelper.create_algo(type);
|
||||
_build_def(structure, ref pos, ref child_algo, dynamic_helper);
|
||||
assign_child(child_algo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,18 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public static void update_persisted_definition_wrappers(ObservableKeyedCollection<IAlgoVM, AlgoKey> target, TableUpdate<AlgoData, AlgoKey> update, Func<AlgoData, AlgoDefinitionVM> factory) {
|
||||
foreach (var d in update.removed)
|
||||
target.Remove(d.key);
|
||||
foreach (var d in update.changed)
|
||||
target[d.key].load_from(d.value);
|
||||
foreach (var d in update.added) {
|
||||
if (target.TryGetValue(d.key, out IAlgoVM def)) {
|
||||
def.load_from(d.value);
|
||||
} else target.Add(new AlgoVMWrapper(factory(d)));
|
||||
}
|
||||
}
|
||||
|
||||
static class _CompareUtils {
|
||||
public static int compare_modes(AlgoDefinitionVM a, AlgoDefinitionVM b) =>
|
||||
_compare_modes(a.key.mode, b.key.mode);
|
||||
|
||||
@@ -46,7 +46,8 @@ namespace mbts.spark {
|
||||
readonly DeferrableActionWrapper _refresh_on_local_changes;
|
||||
readonly ReactiveThresholdMonitor<AlgoDefinitionTemplateVM> _any_templates_dirty_monitor = new ReactiveThresholdMonitor<AlgoDefinitionTemplateVM>(t => t.observe_is_dirty());
|
||||
readonly Property<AlgoFormats> _selected_format;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
readonly EnumMaskInt32<AlgoEditingPermissions> _permissions_mask = new EnumMaskInt32<AlgoEditingPermissions>();
|
||||
|
||||
int? _favorite_index;
|
||||
Brush _badge_fill;
|
||||
Snapshot? _committed;
|
||||
@@ -54,29 +55,43 @@ namespace mbts.spark {
|
||||
TableUpdateBuffer<AlgoTemplateData, AlgoKey> _template_update_buffer;
|
||||
bool _connected;
|
||||
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format);
|
||||
public static AlgoDefinitionVM create(OrdersModel model, AlgoData data, bool connect = false, AlgoFormats init_format = AlgoFormats.Exchange, AlgoEditingPermissions editing_permissions = (AlgoEditingPermissions)int.MaxValue) {
|
||||
var rv = new AlgoDefinitionVM(model, data, init_format, editing_permissions);
|
||||
if (connect)
|
||||
rv.connect();
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static AlgoDefinitionVM create_placeholder(OrdersModel model, CustomOrderDefinitionImmutable def, string name, AlgoFormats init_format) =>
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format);
|
||||
new AlgoDefinitionVM(model, new AlgoData(def, name, new AlgoKey(Guid.NewGuid())), init_format, (AlgoEditingPermissions)int.MaxValue);
|
||||
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format = AlgoFormats.Exchange) {
|
||||
AlgoDefinitionVM(OrdersModel model, AlgoData def_data, AlgoFormats init_format, AlgoEditingPermissions init_permissions) {
|
||||
// init core data
|
||||
_model = model;
|
||||
_permissions_mask.mask = init_permissions;
|
||||
_permissions_mask.raw = init_permissions;
|
||||
var resources = new CompositeDisposable();
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_values)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues);
|
||||
if (!model.algo_model.ui_configuration.can_modify_definition_structure)
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
else if (model.algo_model.templates_enabled()) {
|
||||
resources.Add(_templates.Subscribe((s, e) => {
|
||||
if (_templates.Count == 0) {
|
||||
editing_permissions_mask |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
} else {
|
||||
editing_permissions_mask ^= (AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure);
|
||||
}
|
||||
}));
|
||||
}
|
||||
key = def_data.key;
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(_algo_roots, AlgoCollectionHelper.algo_vm_comparer);
|
||||
_template_update_buffer = new TableUpdateBuffer<AlgoTemplateData, AlgoKey>(t => t.key);
|
||||
_templates_with_custom = new ObservableAppendCollection<AlgoDefinitionTemplateVM>(templates);
|
||||
templates_view = (ListCollectionView)CollectionViewSource.GetDefaultView(_templates_with_custom);
|
||||
custom_badge_color_palettes.AddRange(ColorPalette.all_presets(model.factory).Select(p => new ColorPaletteVM(p)));
|
||||
_editing_permissions = _get_permissions();
|
||||
_version = new VersionContainer<Snapshot>(_snapshot);
|
||||
var conf = _model.algo_model.ui_configuration;
|
||||
var resources = new CompositeDisposable();
|
||||
// init backing props
|
||||
_name = backing(nameof(name), def_data.name);
|
||||
_abbreviation = backing(nameof(abbreviation), string.Empty);
|
||||
@@ -87,7 +102,7 @@ namespace mbts.spark {
|
||||
_is_selected = backing(nameof(is_selected), true);
|
||||
_is_dirty = backing(nameof(is_dirty), () => key.mode == AlgoKey.Mode.Unpersisted || !_committed.HasValue || !_committed.Value.Equals(_version.value));
|
||||
var factory_def_data = def_data.key.mode == AlgoKey.Mode.Factory ? def_data : new AlgoData(def_data.value);
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data, _editing_permissions)));
|
||||
_algo_root = backing(nameof(algo_root), _algo_roots.GetOrAdd(factory_def_data.key, _ => OrderTypeVMFactory.create(factory_def_data)));
|
||||
_visible_algo_root = backing<IAlgoVM>(nameof(visible_algo_root));
|
||||
_can_redo = backing(nameof(can_redo), () => _edit_stack.get_can_redo(_version.value));
|
||||
_can_undo = backing(nameof(can_undo), () => _edit_stack.get_can_undo(_version.value));
|
||||
@@ -113,7 +128,6 @@ namespace mbts.spark {
|
||||
as_demuxer().subscribe_many(_refresh_on_local_changes.execute, nameof(this.name), nameof(abbreviation), nameof(badge_fill)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.BadgeSelector.GroupName, custom_badge_color.observe_nullable(true).Select(t => t.HasValue ? VisualStateNames.BadgeSelector.Active : VisualStateNames.BadgeSelector.Inactive)),
|
||||
_edit_pane_vs_model.add_source(VisualStateNames.Abbreviation.GroupName, _abbreviation.observe_current(true).Select(a => string.IsNullOrEmpty(a) ? VisualStateNames.Abbreviation.Unpopulated : VisualStateNames.Abbreviation.Populated)),
|
||||
templates.ObserveSelf().Select(_ => _get_permissions()).DistinctUntilChanged().OnDispatcher().Subscribe(on_editing_permissions),
|
||||
_algo_root.observe_current(true).SubscribeHookLatest(vm => {
|
||||
if (vm == null)
|
||||
return Disposable.empty;
|
||||
@@ -152,16 +166,22 @@ namespace mbts.spark {
|
||||
_edit_pane_vs_model.set_state(VisualStateNames.Name.GroupName, VisualStateNames.Name.Inactive);
|
||||
break;
|
||||
}
|
||||
resources.Add(_selected_format.observe_current(true).SubscribeHookLatest(f => {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(f);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(f));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}));
|
||||
var permission_src = _permissions_mask.observe_result();
|
||||
resources.Add(_selected_format.observe_current(true).CombineLatest(permission_src, (fmt, perms) => (fmt, perms))
|
||||
.SubscribeHookLatest(t => {
|
||||
if ((t.perms & AlgoEditingPermissions.AllowEditStructure) != 0) {
|
||||
using (_refresh_on_local_changes.defer_execution()) {
|
||||
var algo_data = model.factory.default_algos(t.fmt);
|
||||
var default_algo = new AlgoKey(OrderDataHelper.get_default_algo(t.fmt));
|
||||
return this.load_algo_data_recursive(algo_data, default_algo);
|
||||
}
|
||||
}
|
||||
return Disposable.empty;
|
||||
}));
|
||||
resources.Add(permission_src.Subscribe(_ => RaisePropertyChanged(nameof(editing_permissions))));
|
||||
resources.Add(this.connect_data(permission_src));
|
||||
resources.Add(this.connect_data(Observable.Return(model)));
|
||||
_dispose = resources;
|
||||
on_editing_permissions(_get_permissions());
|
||||
await algo_root.load_from_async(def_data.value);
|
||||
_version.increment_version();
|
||||
_edit_stack.reset(_version.value);
|
||||
@@ -169,11 +189,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
// local methods
|
||||
void on_editing_permissions(AlgoEditingPermissions eps) {
|
||||
_editing_permissions = eps;
|
||||
foreach (var vm in algo_roots)
|
||||
vm.set_permissions(eps);
|
||||
}
|
||||
void on_can_redo_changed(bool oldval, bool newval) => can_redo_changed?.Invoke();
|
||||
void on_can_undo_changed(bool oldval, bool newval) => can_undo_changed?.Invoke();
|
||||
void on_local_changed() {
|
||||
@@ -230,6 +245,9 @@ namespace mbts.spark {
|
||||
// visual states
|
||||
public ObservableCollection<string> edit_pane_visual_states => _edit_pane_vs_model.states;
|
||||
public ObservableCollection<string> tree_view_visual_states => _tree_view_vs_model.states;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions_mask { get => _permissions_mask.mask; set => _permissions_mask.mask = value; }
|
||||
public AlgoEditingPermissions editing_permissions => _permissions_mask.result;
|
||||
|
||||
#region IAlgoSelector
|
||||
event PropertyChange<IAlgoVM> IAlgoSelector.selected_algo_changed { add => algo_root_changed += value; remove => algo_root_changed -= value; }
|
||||
@@ -248,7 +266,7 @@ namespace mbts.spark {
|
||||
|
||||
void _load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
var orig_algo = algo_root?.id;
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, _editing_permissions, true));
|
||||
_algo_roots.SyncWithDictionary(algo_data, d => OrderTypeVMFactory.create(d, is_expanded: true));
|
||||
if (!try_select(orig_algo) && !try_select(default_algo))
|
||||
algo_root = algo_roots.FirstOrDefault();
|
||||
//
|
||||
@@ -298,6 +316,19 @@ namespace mbts.spark {
|
||||
|
||||
public IDisposable buffer_updates() => _template_update_buffer.buffer_updates();
|
||||
|
||||
public void load_def(CustomOrderDefinitionImmutable def) {
|
||||
var key = OrderDataHelper.get_shallow_factory_key(def);
|
||||
if (algo_root?.id == key) {
|
||||
algo_root.load_from(def);
|
||||
} else if (_algo_roots.TryGetValue(key, out IAlgoVM vm)) {
|
||||
vm.load_from(def);
|
||||
algo_root = vm;
|
||||
} else {
|
||||
algo_roots.Add(vm = _mk_algo_root(def));
|
||||
algo_root = vm;
|
||||
}
|
||||
}
|
||||
|
||||
public void load(AlgoData data) {
|
||||
_assert_can_load(data.key);
|
||||
if (algo_root != null)
|
||||
@@ -309,7 +340,7 @@ namespace mbts.spark {
|
||||
|
||||
// pass a copy of the persisted state - rather than use local values - because if something goes wrong, it is better to be in sync with data fresh from the server/api.
|
||||
public AlgoDefinitionVM to_persisted(AlgoData def) =>
|
||||
new AlgoDefinitionVM(_model, def);
|
||||
new AlgoDefinitionVM(_model, def, selected_format, editing_permissions_mask);
|
||||
|
||||
public AlgoData create_algo_def() =>
|
||||
new AlgoData(algo_root.definition, name, key);
|
||||
@@ -322,16 +353,6 @@ namespace mbts.spark {
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
public override string ToString() => algo_root == null ? $"!:{name}" : $"{algo_root.order_type}:{name}";
|
||||
|
||||
AlgoEditingPermissions _get_permissions() {
|
||||
var permissions = AlgoEditingPermissions.None;
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_values) {
|
||||
permissions |= AlgoEditingPermissions.AllowEditValues | AlgoEditingPermissions.AllowSaveValues;
|
||||
}
|
||||
if (_model.algo_model.ui_configuration.can_modify_definition_structure || (_model.algo_model.templates_enabled() && _templates.Count == 0))
|
||||
permissions |= AlgoEditingPermissions.AllowEditStructure | AlgoEditingPermissions.AllowSaveStructure;
|
||||
return permissions;
|
||||
}
|
||||
|
||||
void _assert_can_load(AlgoKey data_key) {
|
||||
if (key.mode != data_key.mode)
|
||||
throw new InvalidOperationException($"{typeof(AlgoDefinitionVM)} '{nameof(key)}.{nameof(AlgoKey.mode)}' must be {data_key.mode} (actually {key.mode}), to load specified data.");
|
||||
@@ -362,7 +383,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
IAlgoVM _mk_algo_root(CustomOrderDefinitionImmutable definition) =>
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), editing_permissions: _editing_permissions, true);
|
||||
OrderTypeVMFactory.create(new AlgoData(definition), is_expanded: true);
|
||||
|
||||
void _on_metadata(AlgoDefinitionMetadata metadata) {
|
||||
using (_refresh_on_local_changes.supress_execution()) {
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
class AlgoVMWrapper : DisposableViewModelBase, IAlgoVM,
|
||||
IObserver<SecurityMD?>,
|
||||
IObserver<SecurityImmutable?>,
|
||||
IObserver<OrdersModel>,
|
||||
IObserver<AlgoEditingPermissions> {
|
||||
static readonly HashSet<string>
|
||||
_algo_root_echo_props = new HashSet<string> {
|
||||
nameof(is_expanded),
|
||||
nameof(editing_permissions),
|
||||
nameof(definition),
|
||||
nameof(display_name)
|
||||
},
|
||||
_value_echo_props = new HashSet<string> {
|
||||
nameof(AlgoDefinitionVM.name),
|
||||
nameof(AlgoDefinitionVM.abbreviation),
|
||||
nameof(AlgoDefinitionVM.badge_fill),
|
||||
nameof(AlgoDefinitionVM.can_undo),
|
||||
nameof(AlgoDefinitionVM.can_redo)
|
||||
};
|
||||
readonly ObservableAggregateCollection<IAlgoSelector> _child_algo_selectors = new ObservableAggregateCollection<IAlgoSelector>();
|
||||
readonly DisposableContainer _value_hooks = new DisposableContainer();
|
||||
Action _definition_changed = () => { };
|
||||
_ObservedData? _data_cache;
|
||||
AlgoDefinitionVM _value;
|
||||
|
||||
public AlgoVMWrapper(AlgoDefinitionVM algo_definition_vm) {
|
||||
value = algo_definition_vm;
|
||||
_dispose = new CompositeDisposable(_value_hooks) {
|
||||
_child_algo_selectors.clear_collections,
|
||||
() => _value?.Dispose()
|
||||
};
|
||||
}
|
||||
|
||||
public event Action definition_changed { add => _definition_changed += value; remove => _definition_changed -= value; }
|
||||
|
||||
public AlgoDefinitionVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (_value != value) {
|
||||
_value?.Dispose();
|
||||
_value = value;
|
||||
_value_hooks.replace(_set_hooks(value));
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
public AlgoKey id => _value.key;
|
||||
public CustomOrderDefinition.CustomOneofCase order_type => _algo_root?.order_type ?? CustomOrderDefinition.CustomOneofCase.None;
|
||||
public bool is_bts_type => _algo_root?.is_bts_type ?? false;
|
||||
public OrderMetadata meta {
|
||||
get {
|
||||
if (_value != null) {
|
||||
if (_value.custom_badge_color.enabled)
|
||||
return new OrderMetadata { Color = value.custom_badge_color.value.rgb_color(), KeyBinding = value.abbreviation.if_null_or_empty(value.name) };
|
||||
else if (_value.algo_root != null)
|
||||
return _value.algo_root.meta;
|
||||
}
|
||||
return OrderDataHelper.get_metadata(order_type);
|
||||
}
|
||||
}
|
||||
public AlgoEditingPermissions editing_permissions => _value?.editing_permissions ?? AlgoEditingPermissions.None;
|
||||
public bool can_expand => _algo_root?.can_expand ?? false;
|
||||
public bool is_expanded {
|
||||
get => _value.algo_root?.is_expanded ?? false;
|
||||
set {
|
||||
if (_value != null)
|
||||
_value.is_expanded = value;
|
||||
}
|
||||
}
|
||||
public string display_name => _value?.name ?? string.Empty;
|
||||
public CustomOrderDefinitionImmutable definition => _algo_root?.definition ?? new CustomOrderDefinitionImmutable(new CustomOrderDefinition());
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
|
||||
public dynamic children_vm => _algo_root?.children_vm;
|
||||
|
||||
IAlgoVM _algo_root => _value?.algo_root;
|
||||
|
||||
public void assign_to(CustomOrderDefinition value) => _algo_root?.assign_to(value);
|
||||
public bool key_match(KeyEventArgs e) => _algo_root?.key_match(e) ?? false;
|
||||
public void load_from(CustomOrderDefinitionImmutable value) => _algo_root?.load_from(value);
|
||||
public Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition) => _algo_root is IAlgoVM vm ? vm.load_from_async(definition) : Task.FromResult<CustomOrderDefinitionImmutable?>(null);
|
||||
|
||||
IDisposable _set_hooks(AlgoDefinitionVM vm) {
|
||||
var algo_root_subs = new DisposableContainer();
|
||||
vm.PropertyChanged += on_value_prop_changed;
|
||||
return new CompositeDisposable(
|
||||
vm.observe_algo_root().Subscribe(r => {
|
||||
algo_root_subs.replace(null);
|
||||
_child_algo_selectors.clear_collections();
|
||||
if (r != null) {
|
||||
if (_data_cache.HasValue) {
|
||||
_data_cache.Value.forward_to(r);
|
||||
}
|
||||
_child_algo_selectors.add_collection(r.child_algo_selectors);
|
||||
((INotifyPropertyChanged)r).PropertyChanged += on_algo_root_prop_changed;
|
||||
algo_root_subs.replace(new CompositeDisposable(
|
||||
r.subscribe_definition().Subscribe(_ => _definition_changed?.Invoke())) {
|
||||
() => ((INotifyPropertyChanged)r).PropertyChanged -= on_algo_root_prop_changed
|
||||
});
|
||||
}
|
||||
}),
|
||||
algo_root_subs) {
|
||||
() => vm.PropertyChanged -= on_value_prop_changed
|
||||
};
|
||||
void on_algo_root_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_algo_root_echo_props.Contains(e.PropertyName))
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
void on_value_prop_changed(object sender, PropertyChangedEventArgs e) {
|
||||
if (_value_echo_props.Contains(e.PropertyName))
|
||||
RaisePropertyChanged(e.PropertyName);
|
||||
}
|
||||
}
|
||||
|
||||
// permissions
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(value, default, default, default);
|
||||
(_algo_root as IObserver<AlgoEditingPermissions>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
// model
|
||||
void IObserver<OrdersModel>.OnNext(OrdersModel value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, value, default, default);
|
||||
(_algo_root as IObserver<OrdersModel>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<OrdersModel>.OnError(Exception error) { }
|
||||
void IObserver<OrdersModel>.OnCompleted() { }
|
||||
// security
|
||||
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, value, default);
|
||||
(_algo_root as IObserver<SecurityImmutable?>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityImmutable?>.OnCompleted() { }
|
||||
// security md
|
||||
void IObserver<SecurityMD?>.OnNext(SecurityMD? value) {
|
||||
_data_cache = _data_cache?.with(value) ?? new _ObservedData(default, default, default, value);
|
||||
(_algo_root as IObserver<SecurityMD?>)?.OnNext(value);
|
||||
}
|
||||
void IObserver<SecurityMD?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityMD?>.OnCompleted() { }
|
||||
|
||||
struct _ObservedData {
|
||||
public readonly AlgoEditingPermissions permissions;
|
||||
public readonly OrdersModel model;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly SecurityMD? security_md;
|
||||
|
||||
public _ObservedData(AlgoEditingPermissions permissions, OrdersModel model, SecurityImmutable? security, SecurityMD? security_md) {
|
||||
this.permissions = permissions;
|
||||
this.model = model;
|
||||
this.security = security;
|
||||
this.security_md = security_md;
|
||||
}
|
||||
|
||||
public void forward_to(IAlgoVM recipient) {
|
||||
(recipient as IObserver<AlgoEditingPermissions>)?.OnNext(permissions);
|
||||
if (model != null)
|
||||
(recipient as IObserver<OrdersModel>)?.OnNext(model);
|
||||
if (security_md.HasValue)
|
||||
(recipient as IObserver<SecurityMD?>)?.OnNext(security_md);
|
||||
if (security.HasValue)
|
||||
(recipient as IObserver<SecurityImmutable?>)?.OnNext(security);
|
||||
}
|
||||
|
||||
public _ObservedData with(AlgoEditingPermissions? permissions) =>
|
||||
new _ObservedData(permissions ?? this.permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(OrdersModel model) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(SecurityImmutable? security) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
|
||||
public _ObservedData with(SecurityMD? security_md) =>
|
||||
new _ObservedData(permissions, model, security, security_md);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<UserControl
|
||||
x:Class="mbts.ui.spark.AlgoEditorView"
|
||||
x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoPickerNodeView.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/AlgoContentTemplateSelector.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Constants-->
|
||||
<sys:Double x:Key="HorizontalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="HorizontalPush3">12</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush1">4</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush2">8</sys:Double>
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP2" Left="{StaticResource HorizontalPush2}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<Thickness x:Key="Margin.TP2" Top="{StaticResource VerticalPush2}"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter" DefaultBrush="Transparent"/>
|
||||
<!--Default Styles-->
|
||||
<Style x:Key="ContentHostGroupBoxStyle" TargetType="GroupBox">
|
||||
<Setter Property="Padding" Value="8"/>
|
||||
</Style>
|
||||
<Style TargetType="TextBox">
|
||||
<Setter Property="u:TextBoxBehavior.EnterKeyBehavior" Value="CommitAndRelease"/>
|
||||
<Setter Property="u:TextBoxBehavior.ReleaseOnEscape" Value="True"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="uag:AlgoPickerNodeView" BasedOn="{StaticResource {x:Type uag:AlgoPickerNodeView}}">
|
||||
<Setter Property="DropDownStyleKey" Value="{Binding DataContext, RelativeSource={RelativeSource AncestorType=usp:AlgoEditorView}, Converter={x:Static usp:AlgoContentTemplateSelector.AlgoDropDownStyleKeyConverter}}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<GroupBox
|
||||
x:Name="content_host"
|
||||
Grid.Row="2"
|
||||
Background="Transparent"
|
||||
BorderBrush="{Binding algo_root, Converter={StaticResource OrderTypeBrushConverter}}"
|
||||
Margin="0,6,0,0"
|
||||
Padding="12,8"
|
||||
>
|
||||
<GroupBox.Style>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}">
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="BorderBrush" Value="Transparent"/>
|
||||
<Style.Triggers>
|
||||
<Trigger Property="BorderBrush" Value="Transparent">
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
</Trigger>
|
||||
<Trigger Property="usp:AlgoEditorView.IsMouseOverNodeContent" Value="True">
|
||||
<Setter Property="BorderThickness" Value="2"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</GroupBox.Style>
|
||||
<GroupBox.Header>
|
||||
<ComboBox
|
||||
ToolTipService.ShowOnDisabled="True"
|
||||
Width="140"
|
||||
Margin="{StaticResource Margin.LP1}"
|
||||
ItemsSource="{Binding algos_view}"
|
||||
SelectedItem="{Binding algo_root}"
|
||||
>
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:True}">
|
||||
<Setter Property="IsEnabled" Value="True"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding templates.Count, Converter={StaticResource IntIsZeroConverter}, FallbackValue={u:False}}" Value="{u:False}">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
<ComboBox.IsEnabled>
|
||||
<Binding Path="editing_permissions">
|
||||
<Binding.Converter>
|
||||
<cv:EnumHasFlagsConverter>
|
||||
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
</Binding.Converter>
|
||||
</Binding>
|
||||
</ComboBox.IsEnabled>
|
||||
<ComboBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<usp:OrderTypeItem DisplayName="{Binding display_name}" BadgeFill="{Binding meta.Color, Converter={StaticResource ColorToBrushConverter}}"/>
|
||||
</DataTemplate>
|
||||
</ComboBox.ItemTemplate>
|
||||
</ComboBox>
|
||||
</GroupBox.Header>
|
||||
<ContentControl
|
||||
Grid.IsSharedSizeScope="True"
|
||||
Content="{Binding algo_root, Converter={x:Static usp:AlgoContentTemplateSelector.DataContextConverter}}"
|
||||
ContentTemplateSelector="{StaticResource AlgoTemplateSelector}"
|
||||
Margin="{StaticResource Margin.TP1}"
|
||||
Visibility="{Binding algo_root.can_expand, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<ContentControl.Resources>
|
||||
<Style TargetType="GroupBox" BasedOn="{StaticResource ContentHostGroupBoxStyle}"/>
|
||||
</ContentControl.Resources>
|
||||
</ContentControl>
|
||||
</GroupBox>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,28 @@
|
||||
using mbts.ui.extensions;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
partial class AlgoEditorView : UserControl {
|
||||
public AlgoEditorView() {
|
||||
InitializeComponent();
|
||||
content_host.PreviewMouseMove += content_host_PreviewMouseMove;
|
||||
content_host.MouseEnter += content_host_MouseEnter;
|
||||
content_host.MouseLeave += content_host_MouseLeave;
|
||||
// local methods
|
||||
void content_host_PreviewMouseMove(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseEnter(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void content_host_MouseLeave(object sender, MouseEventArgs e) => refresh_is_mouse_over_content_host(e);
|
||||
void refresh_is_mouse_over_content_host(MouseEventArgs e) => content_host.set_current_if_changed(IsMouseOverNodeContentProperty, this.ChildAtPoint<GroupBox>(e.GetPosition(this)) == content_host);
|
||||
}
|
||||
|
||||
#region IsMouseOverNodeContent
|
||||
public static readonly DependencyProperty IsMouseOverNodeContentProperty =
|
||||
DependencyProperty.RegisterAttached("IsMouseOverNodeContent", typeof(bool), typeof(AlgoEditorView), new PropertyMetadata(false));
|
||||
|
||||
public static bool GetIsMouseOverNodeContent(DependencyObject d) => (bool)d.GetValue(IsMouseOverNodeContentProperty);
|
||||
public static void SetIsMouseOverNodeContent(DependencyObject d, bool value) => d.SetValue(IsMouseOverNodeContentProperty, value);
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -14,10 +14,13 @@ using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Windows.Data;
|
||||
@@ -42,7 +45,7 @@ namespace mbts.spark {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
|
||||
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IConnectableObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
|
||||
_model = model;
|
||||
_settings_manager = settings_manager;
|
||||
/// Columns
|
||||
@@ -98,16 +101,17 @@ namespace mbts.spark {
|
||||
// grid settings
|
||||
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
|
||||
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
|
||||
grid_settings_manager.load_from(settings_manager.current_settings());
|
||||
if (_settings_manager != null)
|
||||
grid_settings_manager.load_from(_settings_manager.current_settings());
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
_dispose = new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect(col_tups, columns, ct => ct.col),
|
||||
fills_source.OnDispatcher().Subscribe(t => {
|
||||
_rows.update(t);
|
||||
Set(ref _total_row_count, t.state.Count, nameof(total_row_count));
|
||||
}),
|
||||
state_obs.OnDispatcher().Subscribe(s => {
|
||||
if (s.grid != null) {
|
||||
if (s.grid != null && _settings_manager != null) {
|
||||
// this means we are not using defaults,
|
||||
// Note: settings_manager in turn notifies grid_settings_manager of this update
|
||||
_settings_manager.instance_settings = s.grid;
|
||||
@@ -120,8 +124,8 @@ namespace mbts.spark {
|
||||
};
|
||||
rows_manager.on_settings(fs, s.sorting_criterion);
|
||||
}),
|
||||
settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
fills_source.Connect()
|
||||
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
|
||||
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
|
||||
);
|
||||
// local methods
|
||||
void add_col<TFilter>(Columns col_key, string header, Func<Row, string> get_text, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter, string filter_template, double width) =>
|
||||
@@ -153,12 +157,12 @@ namespace mbts.spark {
|
||||
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
|
||||
_sort_manager.create();
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
static Columns _col_key(ColumnBase column) =>
|
||||
((IKeyedColumn<Columns>)column).key;
|
||||
|
||||
void IHandleGridSorting.on_sorting(object sender, DIYGridSortingEventArgs e) =>
|
||||
_sort_manager.on_sorting(e);
|
||||
|
||||
public FilterStates get_filters() =>
|
||||
_filters_manager.create();
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark {
|
||||
/// <summary>
|
||||
/// This class provides a stable DataContext for a fills view, with an internal <see cref="FillsVM"/> that may be disposed and recreated as needed.
|
||||
/// </summary>
|
||||
class FillsVMContainer : IHandleGridSorting, IDisposable, IWillLetYouKnowWhenIDispose {
|
||||
bool _disposed;
|
||||
FillsVM _value;
|
||||
|
||||
public event Action<bool> OnDisposed;
|
||||
|
||||
public FillsVM value {
|
||||
get => _value;
|
||||
set {
|
||||
if (value != _value) {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
Disposable.replace(ref _value, value);
|
||||
if (_value != null) {
|
||||
rows.add_collection(_value.rows);
|
||||
columns.add_collection(_value.columns);
|
||||
} else {
|
||||
rows.clear_collections();
|
||||
columns.clear_collections();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ObservableAggregateCollection<FillsVM.Row> rows { get; } = new ObservableAggregateCollection<FillsVM.Row>();
|
||||
public ObservableAggregateCollection<DIYGrid.ColumnBase> columns { get; } = new ObservableAggregateCollection<DIYGrid.ColumnBase>();
|
||||
|
||||
public void Dispose() {
|
||||
if (_disposed)
|
||||
throw new ObjectDisposedException(nameof(FillsVMContainer));
|
||||
value = null;
|
||||
OnDisposed?.Invoke(true);
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
public void on_sorting(object sender, DIYGridSortingEventArgs e) {
|
||||
if (!_disposed)
|
||||
_value?.on_sorting(sender, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,6 @@ using StartupData = bts.shell.FillsWindowFactory.StartupData;
|
||||
namespace mbts.spark {
|
||||
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
|
||||
public const string window_type_name = "Fills";
|
||||
const string _thread_id = "Fills";
|
||||
readonly AllowOnce _init_guard = new AllowOnce("Fills");
|
||||
readonly OrdersModel _orders_model;
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
@@ -33,6 +32,7 @@ namespace mbts.spark {
|
||||
_orders_model = orders_model;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
|
||||
}
|
||||
|
||||
event Action<FillsWindowFactory.StartupData> _state_changed;
|
||||
@@ -48,7 +48,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => _thread_id;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_init_guard.check_access();
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace bts.shell {
|
||||
_settings_hub = settings_hub;
|
||||
_orders_model = orders_model;
|
||||
_md_auxiliary_model = md_auxiliary_model;
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.toggles.LaddersOnThreads>().enabled);
|
||||
_ladder_conf = new LadderConfig(Configuration.get<bts.config.LadderKnobs>(), Configuration.get<bts.config.MultiThreadedWindows>().ladder);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
class MiniOrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public MiniOrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,13 @@ using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Media;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
// This interface is used to hide ColumnBase.SortDirection, and offer a SortDirection member that does NOT require a DependencyProperty lookup.
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns>, IGridColumn {
|
||||
string sort_member_path { get; }
|
||||
ListSortDirection? SortDirection { get; set; }
|
||||
IDataGridColumnFilter<OrderSecurityAlgoMeta> filter { get; }
|
||||
IComparer<OrderBookRowVM> comparer { get; }
|
||||
@@ -78,7 +77,6 @@ namespace mbts.spark {
|
||||
Add(symbol_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Symbol,
|
||||
DefaultColumnHeaders.Symbol,
|
||||
nameof(OrderBookRowVM.symbol),
|
||||
r => r.symbol,
|
||||
DefaultColumnWidths.Symbol,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
@@ -92,7 +90,6 @@ namespace mbts.spark {
|
||||
Add(time_created_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.Time,
|
||||
"Time",
|
||||
nameof(OrderBookRowVM.time_created),
|
||||
r => r.time_created_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -104,7 +101,6 @@ namespace mbts.spark {
|
||||
Add(last_change_column = new SimpleTextColumn<TimeFilterTuple>(
|
||||
Columns.LastChange,
|
||||
"Last Change",
|
||||
nameof(OrderBookRowVM.last_change),
|
||||
r => r.last_change_text,
|
||||
DefaultColumnWidths.Time,
|
||||
new TimeFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -116,7 +112,6 @@ namespace mbts.spark {
|
||||
Add(user_column = new SimpleTextColumn<string[]>(
|
||||
Columns.User,
|
||||
DefaultColumnHeaders.User,
|
||||
nameof(OrderBookRowVM.user),
|
||||
r => r.user,
|
||||
DefaultColumnWidths.User,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string>(
|
||||
@@ -129,7 +124,6 @@ namespace mbts.spark {
|
||||
Add(account_column = new SimpleTextColumn<string[]>(
|
||||
Columns.Account,
|
||||
DefaultColumnHeaders.Account,
|
||||
nameof(OrderBookRowVM.account),
|
||||
r => r.account,
|
||||
DefaultColumnWidths.Account,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, string, string[]>(
|
||||
@@ -144,7 +138,6 @@ namespace mbts.spark {
|
||||
Add(exchange_column = new SimpleTextColumn<Venue[]>(
|
||||
Columns.Exchange,
|
||||
DefaultColumnHeaders.Exchange,
|
||||
nameof(OrderBookRowVM.exchange),
|
||||
r => r.exchange.ToString(),
|
||||
DefaultColumnWidths.Exchange,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, Venue>(
|
||||
@@ -157,7 +150,6 @@ namespace mbts.spark {
|
||||
Add(time_in_force_column = new SimpleTextColumn<TimeInForce[]>(
|
||||
Columns.TimeInForce,
|
||||
DefaultColumnHeaders.TimeInForce,
|
||||
nameof(OrderBookRowVM.time_in_force),
|
||||
r => r.time_in_force.ToString(),
|
||||
DefaultColumnWidths.TimeInForce,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, TimeInForce>(
|
||||
@@ -176,14 +168,13 @@ namespace mbts.spark {
|
||||
Add(sub_type_column = new SimpleTextColumn<OrderSubType[]>(
|
||||
Columns.SubType,
|
||||
"Sub Type",
|
||||
nameof(OrderBookRowVM.order_sub_type),
|
||||
r => r.order_sub_type_text,
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderSubType>(
|
||||
EnumHelper.get_values<OrderSubType>().ToList(),
|
||||
ort => ort.order.sub_type(),
|
||||
nameof(OrderBookRowVM.order_sub_type)),
|
||||
SubTypeFilterTemplateKey,
|
||||
OrderBookGridView.SubTypeFilterTemplateKey,
|
||||
_wrap_comparer(r => r.order_sub_type)));
|
||||
// side
|
||||
Add(side_column = new SideColumn(
|
||||
@@ -205,7 +196,6 @@ namespace mbts.spark {
|
||||
Add(price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.Price,
|
||||
"Price",
|
||||
nameof(OrderBookRowVM.price),
|
||||
r => r.price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -221,7 +211,6 @@ namespace mbts.spark {
|
||||
Add(avg_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.AvgFilled,
|
||||
"Avg Price",
|
||||
nameof(OrderBookRowVM.avg_price),
|
||||
r => r.avg_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -233,7 +222,6 @@ namespace mbts.spark {
|
||||
Add(last_price_column = new SimpleTextColumn<DoubleMinMaxState>(
|
||||
Columns.LastPrice,
|
||||
"Last Filled",
|
||||
nameof(OrderBookRowVM.last_price),
|
||||
r => r.last_price_text,
|
||||
DefaultColumnWidths.Price,
|
||||
new DoubleMinMax<OrderSecurityAlgoMeta>(
|
||||
@@ -245,7 +233,6 @@ namespace mbts.spark {
|
||||
Add(status_column = new SimpleTextColumn<OrderStatus[]>(
|
||||
Columns.Status,
|
||||
"Status",
|
||||
nameof(OrderBookRowVM.status),
|
||||
r => r.status.ToString(),
|
||||
80,
|
||||
DiscreteValuesFilterVM.Create<OrderSecurityAlgoMeta, OrderStatus>(
|
||||
@@ -263,7 +250,6 @@ namespace mbts.spark {
|
||||
Add(security_key_column = new SimpleTextColumn<string>(
|
||||
Columns.SecurityKey,
|
||||
DefaultColumnHeaders.SecurityKey,
|
||||
nameof(OrderBookRowVM.security_key),
|
||||
r => r.security_key.ToString(),
|
||||
DefaultColumnWidths.SecurityKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -275,7 +261,6 @@ namespace mbts.spark {
|
||||
Add(order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.OrderKey,
|
||||
DefaultColumnHeaders.OrderKey,
|
||||
nameof(OrderBookRowVM.order_key),
|
||||
r => r.order_key.ToString(),
|
||||
DefaultColumnWidths.OrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -287,7 +272,6 @@ namespace mbts.spark {
|
||||
Add(parent_key_column = new SimpleTextColumn<string>(
|
||||
Columns.ParentKey,
|
||||
"Parent Key",
|
||||
nameof(OrderBookRowVM.parent_order_key),
|
||||
r => r.parent_order_key.ToString(),
|
||||
DefaultColumnWidths.ParentKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
@@ -295,6 +279,17 @@ namespace mbts.spark {
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.parent_order_key)));
|
||||
// attached target order key
|
||||
Add(attached_target_order_key_column = new SimpleTextColumn<string>(
|
||||
Columns.AttachedTargetOrderKey,
|
||||
DefaultColumnHeaders.AttachedTargetOrderKey,
|
||||
r => r.order.attached_target_order_key().ToString(),
|
||||
DefaultColumnWidths.AttachedTargetOrderKey,
|
||||
new RegexFilterVM<OrderSecurityAlgoMeta>(
|
||||
ort => ort.order.attached_target_order_key().ToString(),
|
||||
nameof(OrderBookRowVM.parent_order_key)),
|
||||
DataGridFilterKnownTemplates.Regex,
|
||||
_wrap_comparer(r => r.order.attached_target_order_key())));
|
||||
}
|
||||
|
||||
public event Action filter_changed;
|
||||
@@ -323,10 +318,13 @@ namespace mbts.spark {
|
||||
public SimpleTextColumn<string> security_key_column { get; }
|
||||
public SimpleTextColumn<string> order_key_column { get; }
|
||||
public SimpleTextColumn<string> parent_key_column { get; }
|
||||
public SimpleTextColumn<string> attached_target_order_key_column { get; }
|
||||
|
||||
public (FilterStates filters, IReadOnlyList<SortingCriteria> sorting) create() => (
|
||||
new FilterStates {
|
||||
order_key_regex = order_key_column.filter.get_state(),
|
||||
parent_key_regex = parent_key_column.filter.get_state(),
|
||||
attached_target_order_key_regex = attached_target_order_key_column.filter.get_state(),
|
||||
time_created = time_created_column.filter.get_state(),
|
||||
users = user_column.filter.get_state(),
|
||||
accounts = account_column.filter.get_state(),
|
||||
@@ -364,6 +362,8 @@ namespace mbts.spark {
|
||||
time_in_force_column.filter.load_state(fs.tifs);
|
||||
exchange_column.filter.load_state(fs.exchanges);
|
||||
order_key_column.filter.load_state(fs.order_key_regex);
|
||||
parent_key_column.filter.load_state(fs.parent_key_regex);
|
||||
attached_target_order_key_column.filter.load_state(fs.attached_target_order_key_regex);
|
||||
avg_price_column.filter.load_state(fs.avg_price);
|
||||
last_price_column.filter.load_state(fs.last_price);
|
||||
status_column.filter.load_state(fs.statuses);
|
||||
@@ -480,13 +480,12 @@ namespace mbts.spark {
|
||||
protected readonly Func<OrderBookRowVM, string> _get_text;
|
||||
GlyphContext _root_glyph_context, _child_glyph_context;
|
||||
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, string sort_prop, double width,
|
||||
protected OrderbookColumnBase(Columns col_key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, T> filter, string filter_template, IComparer<OrderBookRowVM> comparer) : base(col_key) {
|
||||
id = key.ToString();
|
||||
Width = width;
|
||||
Header = header;
|
||||
_get_text = get_text;
|
||||
sort_member_path = sort_prop;
|
||||
this.filter = filter;
|
||||
_filter_template = filter_template;
|
||||
_cached_sort_direction = SortDirection;
|
||||
@@ -515,9 +514,9 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SimpleTextColumn(Columns key, string header, string sort_prop, Func<OrderBookRowVM, string> get_text, double width,
|
||||
public SimpleTextColumn(Columns key, string header, Func<OrderBookRowVM, string> get_text, double width,
|
||||
IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, TFilterState> filter, string filter_template, IComparer<OrderBookRowVM> comparer)
|
||||
: base(key, header, get_text, sort_prop, width, filter, filter_template, comparer) { }
|
||||
: base(key, header, get_text, width, filter, filter_template, comparer) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, _get_text(row), position, row_height,
|
||||
@@ -527,7 +526,7 @@ namespace mbts.spark {
|
||||
#region Explicit Column Defs
|
||||
public class SideColumn : OrderbookColumnBase<Side[]>, IRenderColumn<OrderBookRowVM> {
|
||||
public SideColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, Side[]> filter)
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), nameof(OrderBookRowVM.side), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
: base(Columns.Side, "B/S", r => r.side.ToString(), DefaultColumnWidths.Side, filter, DataGridFilterKnownTemplates.EnumValues, _wrap_comparer(r => r.side)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.side.ToString(), position, row_height,
|
||||
@@ -536,9 +535,7 @@ namespace mbts.spark {
|
||||
|
||||
public class SizeColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public SizeColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), nameof(OrderBookRowVM.quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.quantity);
|
||||
}
|
||||
: base(Columns.Size, "Qty", r => r.quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.quantity.ToString(), position, row_height,
|
||||
@@ -547,9 +544,7 @@ namespace mbts.spark {
|
||||
|
||||
public class FilledColumn : OrderbookColumnBase<UIntMinMaxState>, IRenderColumn<OrderBookRowVM> {
|
||||
public FilledColumn(IDataGridColumnPersistentFilter<OrderSecurityAlgoMeta, UIntMinMaxState> filter)
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), nameof(OrderBookRowVM.filled_quantity), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) {
|
||||
sort_member_path = nameof(OrderBookRowVM.filled_quantity);
|
||||
}
|
||||
: base(Columns.Filled, "Filled Qty", r => r.filled_quantity.ToString(), DefaultColumnWidths.Size, filter, DataGridFilterKnownTemplates.UIntMinMax, _wrap_comparer(r => r.filled_quantity)) { }
|
||||
|
||||
public void draw(OrderBookRowVM row, DrawingContext dc, ColumnGeometry position, double row_height) =>
|
||||
CellRender.draw_text(dc, _get_glyph_context(row), font_size, row.filled_quantity.ToString(), position, row_height,
|
||||
@@ -562,7 +557,6 @@ namespace mbts.spark {
|
||||
: base(Columns.OrderType, new Binding(nameof(OrderBookRowVM.badge_fill)), new Binding(nameof(OrderBookRowVM.display_name)), filter) {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
sort_member_path = nameof(OrderBookRowVM.order_type);
|
||||
font_weight_binding = RenderUtils.get_font_weight_binding();
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
@@ -587,8 +581,7 @@ namespace mbts.spark {
|
||||
id = key.ToString();
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = DIYGrid.DefaultColumnWidths.LastMessage;
|
||||
sort_member_path = nameof(OrderBookRowVM.last_msg);
|
||||
Width = DefaultColumnWidths.LastMessage;
|
||||
tooltip_binding = new Binding(nameof(OrderBookRowVM.last_msg));
|
||||
}
|
||||
|
||||
@@ -616,7 +609,6 @@ namespace mbts.spark {
|
||||
this.filter = filter;
|
||||
Header = DefaultColumnHeaders.Security;
|
||||
Width = DefaultColumnWidths.Security;
|
||||
sort_member_path = nameof(OrderBookRowVM.security);
|
||||
_cached_sort_direction = SortDirection;
|
||||
}
|
||||
|
||||
@@ -634,7 +626,7 @@ namespace mbts.spark {
|
||||
toggle_button.SetBinding(ToggleButton.IsCheckedProperty, new Binding(nameof(OrderBookRowVM.is_expanded)));
|
||||
toggle_button.SetBinding(ToggleButton.MarginProperty, new Binding(nameof(OrderBookRowVM.depth_level)) { Converter = _margin_converter });
|
||||
toggle_button.SetBinding(ToggleButton.VisibilityProperty, new Binding(nameof(OrderBookRowVM.is_parent)) { Converter = _expander_visiblilty_converter });
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, OrderBookGridView.ExpandCollapseToggleStyleKey);
|
||||
//
|
||||
var security_view = new TextBlock { Foreground = font_brush };
|
||||
security_view.SetBinding(TextBlock.FontFamilyProperty, new Binding(nameof(font_family)) { Source = this });
|
||||
@@ -651,6 +643,7 @@ namespace mbts.spark {
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) });
|
||||
grid.Children.Add(toggle_button);
|
||||
grid.Children.Add(security_view);
|
||||
|
||||
grid.VerticalAlignment = VerticalAlignment.Center;
|
||||
//
|
||||
var border = new Border { Child = grid };
|
||||
|
||||
@@ -15,7 +15,7 @@ using System.Windows.Media;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM, DIYGrid.IActionableRowVM {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM {
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
readonly OrderBookVM _owner;
|
||||
@@ -65,6 +65,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event Action invalidated;
|
||||
public event Action<(OrderSecurityAlgoMeta oldval, OrderSecurityAlgoMeta newval)> updated;
|
||||
|
||||
// Core
|
||||
public OrderSecurityAlgoMeta order_tuple { get; private set; }
|
||||
@@ -142,8 +143,8 @@ namespace mbts.spark {
|
||||
public ICommand toggle_command => _toggle_expanded_cmd ?? (
|
||||
_toggle_expanded_cmd = new DelegateCommand(() => is_expanded = !is_expanded));
|
||||
|
||||
ICommand DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd ??
|
||||
(_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
public ICommand show_order_modify_cmd =>
|
||||
_show_order_modify_cmd ?? (_show_order_modify_cmd = new DelegateCommand(() => _owner.show_order_modify(this), () => status == OrderStatus.Working));
|
||||
|
||||
public ICommand cancel_command => _cancel_cmd ??
|
||||
(_cancel_cmd = new DelegateCommand(() => _owner.cancel_order(this), () => status == OrderStatus.Working));
|
||||
@@ -185,10 +186,10 @@ namespace mbts.spark {
|
||||
public void update(OrderSecurityAlgoMeta order_tuple) {
|
||||
Log.assert(order_tuple.order.Key == this.order.Key, "OrderBookRowVM: update_order was called with an order with the wrong key.");
|
||||
bool had_security = _security.HasValue;
|
||||
var old_meta = this.order_tuple.meta;
|
||||
var old_order_tuple = this.order_tuple;
|
||||
// _show_order_modify_cmd is lazy (instantiated in the default_command getter); it might be null, don't not force instantiation
|
||||
bool? could_modify_execute = _show_order_modify_cmd?.CanExecute(null);
|
||||
OrderRecordImmutable old_order = order;
|
||||
OrderRecordImmutable old_order = old_order_tuple.order;
|
||||
this.order_tuple = order_tuple;
|
||||
// most cells are "render-cells," which don't use databindings - the only way to update such cells is to raise the invalidated event
|
||||
bool invalidate = !(
|
||||
@@ -215,10 +216,12 @@ namespace mbts.spark {
|
||||
// need to raise invalidated to refresh the price cells (the security.ContractSpec determines how they are formatted - Ticks!)
|
||||
invalidate = true;
|
||||
}
|
||||
if (old_meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
if (old_order_tuple.meta?.badge_color != order_tuple.meta?.badge_color) {
|
||||
_badge_fill = null;
|
||||
RaisePropertyChanged(nameof(badge_fill));
|
||||
}
|
||||
if (old_order_tuple.display_name() != order_tuple.display_name())
|
||||
RaisePropertyChanged(nameof(display_name));
|
||||
if (old_order.status() != order.status()) {
|
||||
invalidate = true;
|
||||
RaisePropertyChanged(nameof(status));
|
||||
@@ -228,6 +231,7 @@ namespace mbts.spark {
|
||||
}
|
||||
if (could_modify_execute.HasValue && could_modify_execute.Value != _show_order_modify_cmd.CanExecute(null))
|
||||
_show_order_modify_cmd.RaiseCanExecuteChanged();
|
||||
updated?.Invoke((old_order_tuple, order_tuple));
|
||||
if (invalidate)
|
||||
raise_invalidated();
|
||||
}
|
||||
|
||||
@@ -1,168 +0,0 @@
|
||||
<UserControl x:Class="mbts.spark.OrderBookView" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:uc="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="600" d:DesignWidth="1080"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}">
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
|
||||
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{uc:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={uc:Nested uc:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.RowTemplateKey}">
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:DIYGrid
|
||||
x:Name="_grid"
|
||||
Grid.Row="0"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows}"
|
||||
RowStyle="{StaticResource {x:Static sp:OrderBookView.RowStyleKey}}"
|
||||
RowTemplate="{StaticResource {x:Static sp:OrderBookView.RowTemplateKey}}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<Style x:Key="{x:Static sp:OrderBookView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
|
||||
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="RenderTransform" TargetName="ExpandPath">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.CommandBindings>
|
||||
<CommandBinding
|
||||
Command="{x:Static c:DIYGrid.DefaultRowCommand}"
|
||||
Executed="{x:Static c:DIYGrid.DefaultRowActionExecuted}"
|
||||
CanExecute="{x:Static c:DIYGrid.DefaultRowActionCanExecute}"
|
||||
/>
|
||||
</c:DIYGrid.CommandBindings>
|
||||
</c:DIYGrid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -2,27 +2,31 @@
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using bts.view;
|
||||
using mbts.persist;
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.spark;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
//using IOrderBookWSM = mbts.persist.IWindowSettingsManager<mbts.spark.GridSettingsWithNotifications<mbts.spark.OrderRecordTuple, mbts.OrderRecordTuplePredicate>>;
|
||||
using OrderBookGridSettings = bts.shell.OrderBookWindowFactory.OrderBookGridSettings;
|
||||
using OrderBookGridSettingsVM = mbts.spark.GridSettingsVM<bts.api.orderman.OrderSecurity, mbts.OrderRecordTuplePredicate, bts.shell.OrderBookWindowFactory.OrderBookGridSettings>;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class OrderBookWindow : SparkWindow<OrderBookWindowFactory.OrderBookStartupData>, ISharedWindow {
|
||||
const string _window_type = "OrderBook", ORDERBOOK_THREAD_ID = "OrderBook", _default_title = "Order Book";
|
||||
const string _window_type = "OrderBook", _default_title = "Order Book";
|
||||
readonly INotificationService _notification_service;
|
||||
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
|
||||
IDisposable _attachment_exit_sub;
|
||||
OrderBookWindowFactory.OrderBookStartupData _state;
|
||||
OrdersModel _model;
|
||||
OrderBookVM _vm;
|
||||
@@ -30,17 +34,6 @@ namespace mbts.spark {
|
||||
OrderBookWindowFactory _factory;
|
||||
SparkCommand[] _commands;
|
||||
|
||||
SparkCommand _show_fills_command
|
||||
, _modify_order_command
|
||||
, _cancel_buy_orders_command
|
||||
, _cancel_sell_orders_command
|
||||
, _cancel_all_orders_command
|
||||
, _cancel_selected_orders_command
|
||||
, _recreate_selected_orders_command
|
||||
, _toggle_collapse_all_command
|
||||
, _toggle_expand_all_command
|
||||
, _show_grid_settings_command;
|
||||
|
||||
public OrderBookWindow(OrdersModel model, OrderBookWindowFactory factory, IBus bus, INotificationService notification_service,
|
||||
OrderBookWindowFactory.OrderBookStartupData state, ImageSource icon, IWindowSettingsManager<OrderBookGridSettings> settings_manager)
|
||||
: base(_default_title, bus, factory, SparkCommands.ShowOrders.icon) {
|
||||
@@ -49,6 +42,7 @@ namespace mbts.spark {
|
||||
_notification_service = notification_service;
|
||||
_state = state;
|
||||
_settings_manager = settings_manager;
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().order_book ? "OrderBook" : null;
|
||||
}
|
||||
|
||||
public override OrderBookWindowFactory.OrderBookStartupData SaveState => _state = _vm?.create();
|
||||
@@ -61,10 +55,11 @@ namespace mbts.spark {
|
||||
+ SPARK_MENU_COLLAPSED_WIDTH
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
public string thread_id => ORDERBOOK_THREAD_ID;
|
||||
public string thread_id { get; }
|
||||
|
||||
public override void Dispose() {
|
||||
_commands.OfType<IDisposable>().DisposeAll();
|
||||
Disposable.dispose(ref _attachment_exit_sub);
|
||||
Disposable.dispose(ref _vm);
|
||||
}
|
||||
|
||||
@@ -73,19 +68,15 @@ namespace mbts.spark {
|
||||
_vm.load_from(_state);
|
||||
_view = new OrderBookView(_vm);
|
||||
|
||||
_show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
var show_fills_command = SparkCommands.ShowFills.complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
|
||||
|
||||
_modify_order_command = SparkCommands.ShowModifyOrder.complete(
|
||||
() => _vm.current_order.Value.Key,
|
||||
can_execute: () => {
|
||||
if (_vm.current_order.HasValue) {
|
||||
return _vm.current_order.Value.State.Status == bts.api.orderman.OrderStatus.Working;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
);
|
||||
var toggle_right_panel_is_visible_command = new SparkCommandCommand(
|
||||
get_toggle_algo_editor_cmd_title(),
|
||||
Icons.ModifyOrder,
|
||||
_vm.toggle_algo_editor_is_visible_cmd,
|
||||
new KeyGesture(Key.M, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
_cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
|
||||
var cancel_buy_orders_command = SparkCommands.CancelBuyOrders.complete(
|
||||
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Buy).Select(o => o.order.Key)),
|
||||
wbids => wbids.Any(),
|
||||
() => {
|
||||
@@ -98,7 +89,7 @@ namespace mbts.spark {
|
||||
}
|
||||
);
|
||||
|
||||
_cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
|
||||
var cancel_sell_orders_command = SparkCommands.CancelSellOrders.complete(
|
||||
_model.working_orders.Select(os => os.Values.Where(o => o.order.side() == Side.Sell).Select(o => o.order.Key)),
|
||||
wasks => wasks.Any(),
|
||||
() => {
|
||||
@@ -111,7 +102,7 @@ namespace mbts.spark {
|
||||
}
|
||||
);
|
||||
|
||||
_cancel_all_orders_command = SparkCommands.CancelOrders.complete(
|
||||
var cancel_all_orders_command = SparkCommands.CancelOrders.complete(
|
||||
_model.working_orders.Select(os => os.Select(o => o.Key)),
|
||||
worders => worders.Any(),
|
||||
() => {
|
||||
@@ -124,27 +115,39 @@ namespace mbts.spark {
|
||||
}
|
||||
);
|
||||
|
||||
_cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
|
||||
var cancel_selected_orders_command = SparkCommands.CancelSelectedOrders.complete(
|
||||
() => _vm.selected_orders.Where(x => x.status() == OrderStatus.Working).Select(x => x.Key),
|
||||
can_execute: () => _vm.selected_orders.Any(x => x.status() == OrderStatus.Working)
|
||||
);
|
||||
|
||||
_recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
|
||||
var recreate_selected_orders_command = SparkCommands.RecreateSelectedOrders.complete(
|
||||
() =>
|
||||
_vm.selected_orders.Where(x => _passes_recreate_search_criteria(x))
|
||||
_vm.selected_orders.Where(x => passes_recreate_search_criteria(x))
|
||||
.Select(x => (x.Def, x.State.Price, x.State.Quantity - x.State.FilledQuantity)),
|
||||
can_execute: () => _vm.selected_orders.Any(x => _passes_recreate_search_criteria(x))
|
||||
can_execute: () => _vm.selected_orders.Any(x => passes_recreate_search_criteria(x))
|
||||
);
|
||||
|
||||
_toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
var attach_selected_orders_command = new SparkCommandCommand(
|
||||
"Attach To Selected Orders",
|
||||
Icons.AttachAlgos,
|
||||
new DelegateCommand(_vm.begin_attach_algos, () => _vm.selected_rows.Count > 0),
|
||||
new KeyGesture(Key.A, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
var detach_selected_orders_command = new SparkCommandCommand(
|
||||
"Detach And Cancel Selected Orders",
|
||||
Icons.DetachAlgos,
|
||||
new DelegateCommand(_vm.detach_selected_algos, () => _vm.selected_rows.Count > 0),
|
||||
new KeyGesture(Key.D, ModifierKeys.Control | ModifierKeys.Shift));
|
||||
|
||||
var toggle_collapse_all_command = new SparkCommandCommand("Collapse All",
|
||||
Icons.RightArrow,
|
||||
_vm.collapse_all_rows_command);
|
||||
|
||||
_toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
var toggle_expand_all_command = new SparkCommandCommand("Expand All",
|
||||
Icons.DownArrow,
|
||||
_vm.expand_all_rows_command);
|
||||
|
||||
_show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var show_grid_settings_command = SparkCommands.window_settings_command(new DialogCommand(() => {
|
||||
var gs_vm = new WindowSettingsDialogVM<OrderBookGridSettings, OrderBookGridSettingsVM>(
|
||||
_settings_manager,
|
||||
us => us.order_book_grid,
|
||||
@@ -169,31 +172,44 @@ namespace mbts.spark {
|
||||
return dialog;
|
||||
}));
|
||||
|
||||
_commands = new SparkCommand[] {
|
||||
_show_fills_command,
|
||||
_modify_order_command,
|
||||
_cancel_selected_orders_command,
|
||||
_cancel_buy_orders_command,
|
||||
_cancel_sell_orders_command,
|
||||
_cancel_all_orders_command,
|
||||
_recreate_selected_orders_command,
|
||||
_toggle_collapse_all_command,
|
||||
_toggle_expand_all_command,
|
||||
_show_grid_settings_command
|
||||
_commands = new SparkCommand[] {
|
||||
show_fills_command,
|
||||
toggle_right_panel_is_visible_command,
|
||||
cancel_selected_orders_command,
|
||||
cancel_buy_orders_command,
|
||||
cancel_sell_orders_command,
|
||||
cancel_all_orders_command,
|
||||
recreate_selected_orders_command,
|
||||
attach_selected_orders_command,
|
||||
detach_selected_orders_command,
|
||||
toggle_collapse_all_command,
|
||||
toggle_expand_all_command,
|
||||
show_grid_settings_command
|
||||
};
|
||||
_vm.selected_order_changed += _vm_selected_order_changed;
|
||||
_vm.as_demuxer().subscribe<bool>(nameof(OrderBookVM.algo_editor_is_visible))
|
||||
.Subscribe(_ => toggle_right_panel_is_visible_command.title = get_toggle_algo_editor_cmd_title());
|
||||
_vm.selected_order_changed += vm_selected_order_changed;
|
||||
// local methods
|
||||
string get_toggle_algo_editor_cmd_title() =>
|
||||
$"{(_vm.algo_editor_is_visible ? "Hide" : "Show")} Order Params";
|
||||
|
||||
bool passes_recreate_search_criteria(OrderRecordImmutable record) =>
|
||||
(record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error) &&
|
||||
!record.has_parent() &&
|
||||
record.State.Quantity > record.State.FilledQuantity;
|
||||
|
||||
void vm_selected_order_changed(OrderRecordImmutable? e) {
|
||||
cancel_selected_orders_command.raiseCanExecuteChanged();
|
||||
recreate_selected_orders_command.raiseCanExecuteChanged();
|
||||
attach_selected_orders_command.raiseCanExecuteChanged();
|
||||
detach_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _passes_recreate_search_criteria(OrderRecordImmutable record) {
|
||||
return (record.status() == OrderStatus.Canceled || record.status() == OrderStatus.Error)
|
||||
&& !record.has_parent()
|
||||
&& record.State.Quantity > record.State.FilledQuantity;
|
||||
}
|
||||
|
||||
private void _vm_selected_order_changed(OrderRecordImmutable? e) {
|
||||
_modify_order_command.raiseCanExecuteChanged();
|
||||
_cancel_selected_orders_command.raiseCanExecuteChanged();
|
||||
_recreate_selected_orders_command.raiseCanExecuteChanged();
|
||||
}
|
||||
protected override void OnParentViewCreated(SparkWindowView parent_view) =>
|
||||
Disposable.replace(ref _attachment_exit_sub, parent_view.SubscribePreviewKeyDown((s, e) => {
|
||||
if (e.Key == Key.Escape || (e.Key == Key.Z && KeyboardExtensions.IsCtrlDown()))
|
||||
_vm.is_attaching_algos = false;
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,11 @@ using mbts.ui.settings;
|
||||
using ProtoBuf;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using Columns = mbts.spark.OrderBookGridView.ColumnKeys;
|
||||
|
||||
namespace bts.shell {
|
||||
internal class OrderBookWindowFactory : ShellWindowFactory<OrderBookWindowFactory.OrderBookStartupData> {
|
||||
@@ -49,24 +50,57 @@ namespace bts.shell {
|
||||
|
||||
[ProtoContract]
|
||||
public class OrderBookStartupData {
|
||||
//[ProtoMember(5)]
|
||||
//public readonly List<SortingCriteria> sort_criteria;
|
||||
const bool _default_algo_editor_is_visible = true;
|
||||
const double
|
||||
_default_right_panel_width = 400,
|
||||
_default_fills_slave_grid_height = 138;
|
||||
// ALREADY USED TAGS
|
||||
// [ProtoMember(5)]
|
||||
// public readonly List<SortingCriteria> sort_criteria;
|
||||
|
||||
// filters
|
||||
[ProtoMember(10)]
|
||||
public FilterStates filters;
|
||||
// grid
|
||||
[ProtoMember(15)]
|
||||
[Obsolete]
|
||||
public GridSettings grid;
|
||||
// algo editor is visible
|
||||
[ProtoMember(16)]
|
||||
[DefaultValue(_default_algo_editor_is_visible)]
|
||||
public bool algo_editor_is_visible { get; set; }
|
||||
// right panel width
|
||||
[ProtoMember(17)]
|
||||
[DefaultValue(_default_right_panel_width)]
|
||||
public double right_panel_width { get; set; }
|
||||
// fills slave grid height
|
||||
[ProtoMember(18)]
|
||||
[DefaultValue(_default_fills_slave_grid_height)]
|
||||
public double fills_slave_grid_height { get; set; }
|
||||
// fills slave grid is visible
|
||||
[ProtoMember(19)]
|
||||
public bool fills_slave_grid_is_visible { get; set; }
|
||||
// predicate animations
|
||||
[ProtoMember(20)]
|
||||
[Obsolete]
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
// use defaults
|
||||
[ProtoMember(25)]
|
||||
public bool use_defaults;
|
||||
|
||||
public OrderBookStartupData() { }
|
||||
public OrderBookStartupData() {
|
||||
algo_editor_is_visible = _default_algo_editor_is_visible;
|
||||
right_panel_width = _default_right_panel_width;
|
||||
fills_slave_grid_height = _default_fills_slave_grid_height;
|
||||
}
|
||||
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
public OrderBookStartupData(FilterStates filters, GridSettings grid = default, bool algo_editor_is_visible = false, double right_panel_width = 0, double fills_slave_grid_height = 0, bool fills_slave_grid_is_visible = false, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, bool use_defaults = false) {
|
||||
this.filters = filters;
|
||||
this.use_defaults = use_defaults;
|
||||
this.algo_editor_is_visible = algo_editor_is_visible;
|
||||
this.right_panel_width = right_panel_width;
|
||||
this.fills_slave_grid_height = fills_slave_grid_height;
|
||||
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
this.grid = grid;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
@@ -83,9 +117,12 @@ namespace bts.shell {
|
||||
|
||||
public static OrderBookStartupData Default =>
|
||||
new OrderBookStartupData(
|
||||
new FilterStates {
|
||||
statuses = new[] { OrderStatus.Working }
|
||||
}, _default_general_grid, use_defaults: true);
|
||||
new FilterStates { statuses = new[] { OrderStatus.Working } },
|
||||
_default_general_grid,
|
||||
_default_algo_editor_is_visible,
|
||||
_default_right_panel_width,
|
||||
_default_fills_slave_grid_height,
|
||||
use_defaults: true);
|
||||
|
||||
public static OrderBookGridSettings DefaultGrid => new OrderBookGridSettings(_default_general_grid);
|
||||
|
||||
@@ -115,6 +152,7 @@ namespace bts.shell {
|
||||
public static GridColumnSettings security_key => new GridColumnSettings(Columns.SecurityKey.ToString(), DIYGrid.DefaultColumnHeaders.SecurityKey, is_visible: false, width: DIYGrid.DefaultColumnWidths.SecurityKey);
|
||||
public static GridColumnSettings order_key => new GridColumnSettings(Columns.OrderKey.ToString(), DIYGrid.DefaultColumnHeaders.OrderKey, width: DIYGrid.DefaultColumnWidths.OrderKey);
|
||||
public static GridColumnSettings parent_key => new GridColumnSettings(Columns.ParentKey.ToString(), "Parent Key", width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
public static GridColumnSettings attached_target_order_key => new GridColumnSettings(Columns.AttachedTargetOrderKey.ToString(), DIYGrid.DefaultColumnHeaders.AttachedTargetOrderKey, width: DIYGrid.DefaultColumnWidths.ParentKey);
|
||||
|
||||
public static IEnumerable<SortingCriteria> sorting_criterion {
|
||||
get {
|
||||
@@ -145,11 +183,12 @@ namespace bts.shell {
|
||||
yield return security_key;
|
||||
yield return order_key;
|
||||
yield return parent_key;
|
||||
yield return attached_target_order_key;
|
||||
}
|
||||
}
|
||||
|
||||
private static HashSet<GridColumnSettings> _mini_mode_columns_to_hide = new HashSet<GridColumnSettings> {
|
||||
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key
|
||||
time_created, exchange, sub_type, quantity, price, avg_price, last_price, status, last_message, security_key, order_key, parent_key, attached_target_order_key
|
||||
};
|
||||
|
||||
public static IEnumerable<GridColumnSettings> mini {
|
||||
@@ -208,11 +247,17 @@ namespace bts.shell {
|
||||
public api.orderman.OrderSubType[] order_sub_types;
|
||||
[ProtoMember(20)]
|
||||
public string[] symbols;
|
||||
[ProtoMember(21)]
|
||||
public string parent_key_regex;
|
||||
[ProtoMember(22)]
|
||||
public string attached_target_order_key_regex;
|
||||
|
||||
public bool Equals(FilterStates other) =>
|
||||
other != null &&
|
||||
security_criteria.safe_equals(other.security_criteria) &&
|
||||
order_key_regex == other.order_key_regex &&
|
||||
parent_key_regex == other.parent_key_regex &&
|
||||
attached_target_order_key_regex == other.attached_target_order_key_regex &&
|
||||
time_created.Equals(other.time_created) &&
|
||||
EqualityComparer.safe_sequence_equals(users, other.users) &&
|
||||
EqualityComparer.safe_sequence_equals(accounts, other.accounts) &&
|
||||
@@ -230,6 +275,32 @@ namespace bts.shell {
|
||||
last_change.Equals(other.last_change) &&
|
||||
EqualityComparer.safe_sequence_equals(order_sub_types, other.order_sub_types) &&
|
||||
EqualityComparer.safe_sequence_equals(symbols, other.symbols);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FilterStates);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
security_criteria?.GetHashCode() ?? 0,
|
||||
order_key_regex?.GetHashCode() ?? 0,
|
||||
parent_key_regex?.GetHashCode() ?? 0,
|
||||
attached_target_order_key_regex?.GetHashCode() ?? 0,
|
||||
time_created.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(users),
|
||||
EqualityComparer.safe_aggregate_hash(accounts),
|
||||
EqualityComparer.safe_aggregate_hash(exchanges),
|
||||
EqualityComparer.safe_aggregate_hash(tifs),
|
||||
EqualityComparer.safe_aggregate_hash(order_types),
|
||||
EqualityComparer.safe_aggregate_hash(sides),
|
||||
qty?.GetHashCode() ?? 0,
|
||||
price?.GetHashCode() ?? 0,
|
||||
qty_filled?.GetHashCode() ?? 0,
|
||||
avg_price?.GetHashCode() ?? 0,
|
||||
last_price?.GetHashCode() ?? 0,
|
||||
EqualityComparer.safe_aggregate_hash(statuses),
|
||||
exchange_message_regex?.GetHashCode() ?? 0,
|
||||
last_change.GetHashCode(),
|
||||
EqualityComparer.safe_aggregate_hash(order_sub_types),
|
||||
EqualityComparer.safe_aggregate_hash(symbols));
|
||||
}
|
||||
|
||||
[ProtoContract]
|
||||
@@ -240,7 +311,7 @@ namespace bts.shell {
|
||||
List<OrderRecordTuplePredicateAnimations> _predicate_animations;
|
||||
|
||||
public OrderBookGridSettings() { }
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null) {
|
||||
public OrderBookGridSettings(GridSettings general = default, IEnumerable<OrderRecordTuplePredicateAnimations> predicate_animations = null, double right_panel_width = double.NaN) {
|
||||
this.general = general;
|
||||
_predicate_animations = new List<OrderRecordTuplePredicateAnimations>();
|
||||
if (predicate_animations != null)
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.controls.adorners;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookView {
|
||||
public class OrderBookAttachmentTargetAdorner : DIYGridRowHighlighterAdorner, IDisposable {
|
||||
IDisposable _attach_op;
|
||||
bool _is_active, _is_disposed;
|
||||
|
||||
#region IsActive
|
||||
public static readonly DependencyProperty IsActiveProperty =
|
||||
WPFHelper.create_dp<bool, OrderBookAttachmentTargetAdorner>(
|
||||
nameof(IsActive),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => {
|
||||
o._is_active = v;
|
||||
Disposable.dispose(ref o._attach_op);
|
||||
if (v && !o._is_disposed) {
|
||||
o.IsHitTestVisible = true;
|
||||
o._attach_op = Disposable.Create(
|
||||
o.SubscribePreviewMouseDown((s, e) => {
|
||||
if (e.ChangedButton == MouseButton.Left) {
|
||||
if (e.ClickCount > 1 && o.TargetRow is OrderBookRowVM r) {
|
||||
e.Handled = true;
|
||||
o._raise_target_row_selected(r);
|
||||
}
|
||||
}
|
||||
}),
|
||||
o.AdornedElement.SubscribeMouseHoverRow<OrderBookRowVM>().Subscribe(row => o.TargetRow = row));
|
||||
} else {
|
||||
o.IsHitTestVisible = false;
|
||||
o.TargetRow = null;
|
||||
}
|
||||
},
|
||||
false);
|
||||
|
||||
public bool IsActive {
|
||||
get => (bool)GetValue(IsActiveProperty);
|
||||
set => SetValue(IsActiveProperty, value);
|
||||
}
|
||||
|
||||
public event Action<OrderBookRowVM> TargetRowSelected;
|
||||
#endregion
|
||||
|
||||
public OrderBookAttachmentTargetAdorner(DIYGrid grid) : base(grid) { }
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
_is_disposed = true;
|
||||
Disposable.dispose(ref _attach_op);
|
||||
}
|
||||
}
|
||||
|
||||
void _raise_target_row_selected(OrderBookRowVM row) => TargetRowSelected?.Invoke(row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<c:DIYGrid
|
||||
x:Class="mbts.spark.OrderBookGridView"
|
||||
x:ClassModifier="internal"
|
||||
x:Name="_grid"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<c:DIYGrid.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--Primitive Resources (Brushes, Colors, etc.)-->
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Fill" Color="#FF595959"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Checked.Stroke" Color="#FF262626"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Stroke" Color="#FF27C7F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Fill" Color="#FFCCEEFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Stroke" Color="#FF1CC4F7"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.MouseOver.Checked.Fill" Color="#FF82DFFB"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Fill" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="TreeViewItem.TreeArrow.Static.Stroke" Color="#FF818181"/>
|
||||
<PathGeometry x:Key="TreeArrow" Figures="M0,0 L0,6 L6,0 z"/>
|
||||
<!--Converters-->
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<!--DataTemplates-->
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookGridView.SubTypeFilterTemplateKey}">
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<!--Styles-->
|
||||
<Style x:Key="{x:Static sp:OrderBookGridView.ExpandCollapseToggleStyleKey}" TargetType="{x:Type ToggleButton}">
|
||||
<Setter Property="Focusable" Value="False"/>
|
||||
<Setter Property="Width" Value="16"/>
|
||||
<Setter Property="Height" Value="16"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type ToggleButton}">
|
||||
<Border Background="Transparent" Height="16" Padding="0,5,5,5" Width="16">
|
||||
<Path x:Name="ExpandPath" Data="{StaticResource TreeArrow}" Fill="{StaticResource TreeViewItem.TreeArrow.Static.Fill}" Stroke="{StaticResource TreeViewItem.TreeArrow.Static.Stroke}">
|
||||
<Path.RenderTransform>
|
||||
<RotateTransform Angle="135" CenterY="3" CenterX="3"/>
|
||||
</Path.RenderTransform>
|
||||
</Path>
|
||||
</Border>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger Property="IsChecked" Value="True">
|
||||
<Setter Property="RenderTransform" TargetName="ExpandPath">
|
||||
<Setter.Value>
|
||||
<RotateTransform Angle="180" CenterY="3" CenterX="3"/>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Fill}"/>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.Static.Checked.Stroke}"/>
|
||||
</Trigger>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Fill}"/>
|
||||
</Trigger>
|
||||
<MultiTrigger>
|
||||
<MultiTrigger.Conditions>
|
||||
<Condition Property="IsMouseOver" Value="True"/>
|
||||
<Condition Property="IsChecked" Value="True"/>
|
||||
</MultiTrigger.Conditions>
|
||||
<Setter Property="Stroke" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Stroke}"/>
|
||||
<Setter Property="Fill" TargetName="ExpandPath" Value="{StaticResource TreeViewItem.TreeArrow.MouseOver.Checked.Fill}"/>
|
||||
</MultiTrigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style TargetType="{x:Type ToolTip}">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="Content" Value="{x:Static sys:String.Empty}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
<Trigger Property="Content" Value="{x:Null}">
|
||||
<Setter Property="Visibility" Value="Collapsed" />
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</c:DIYGrid.Resources>
|
||||
<c:DIYGrid.InputBindings>
|
||||
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
<KeyBinding Key="Return" Command="{x:Static c:DIYGrid.DefaultRowCommand}"/>
|
||||
</c:DIYGrid.InputBindings>
|
||||
<c:DIYGrid.RowStyle>
|
||||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
<MultiDataTrigger>
|
||||
<MultiDataTrigger.Conditions>
|
||||
<Condition Binding="{Binding IsSelected, RelativeSource={RelativeSource Self}}" Value="False"/>
|
||||
<Condition Binding="{Binding status}" Value="{u:Enum Working, Type=order:OrderStatus}"/>
|
||||
</MultiDataTrigger.Conditions>
|
||||
<MultiDataTrigger.Setters>
|
||||
<Setter Property="Background" Value="{x:Static Member=working_order_background_brush, MemberType={u:Nested u:BTSColors.Orders}}"/>
|
||||
</MultiDataTrigger.Setters>
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</c:DIYGrid.RowStyle>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<sp:OrderBookRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
+33
-9
@@ -1,11 +1,7 @@
|
||||
using mbts.ui.controls;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookView : UserControl {
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum Columns { Security, Symbol, Time, LastChange, User, Account, Exchange, TimeInForce, OrderType, SubType, Side, Size, Price, Filled, AvgFilled, LastPrice, Status, LastMessage, SecurityKey, OrderKey, ParentKey, Cancel, EditableQty, EditablePrice }
|
||||
internal partial class OrderBookGridView : DIYGrid {
|
||||
public const string
|
||||
SubTypeFilterTemplateKey = "SubTypeFilterTemplate",
|
||||
ExpandCollapseToggleStyleKey = "ExpandCollapseToggleStyle",
|
||||
@@ -14,13 +10,41 @@ namespace mbts.spark {
|
||||
CVRowStyleKey = "CVRowStyle",
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
// IMPORTANT: these enums are stored as strings in the general-purpose GridColumnSettings proto, and are used to ID THE COLUMNS!
|
||||
// Do NOT RENAME them! However, if you must, be sure to write an IShellWindowMigrationRule or something to account for backwards compatability.
|
||||
public enum ColumnKeys {
|
||||
Security,
|
||||
Symbol,
|
||||
Time,
|
||||
LastChange,
|
||||
User,
|
||||
Account,
|
||||
Exchange,
|
||||
TimeInForce,
|
||||
OrderType,
|
||||
SubType,
|
||||
Side,
|
||||
Size,
|
||||
Price,
|
||||
Filled,
|
||||
AvgFilled,
|
||||
LastPrice,
|
||||
Status,
|
||||
LastMessage,
|
||||
SecurityKey,
|
||||
OrderKey,
|
||||
ParentKey,
|
||||
AttachedTargetOrderKey,
|
||||
Cancel,
|
||||
EditableQty,
|
||||
EditablePrice
|
||||
}
|
||||
|
||||
public OrderBookGridView() =>
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookView.Columns> {
|
||||
class OrderBookRowView : DIYGrid.HighPerformanceRow<OrderBookRowVM, OrderBookGridView.ColumnKeys> {
|
||||
public OrderBookRowView() : base() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,407 @@
|
||||
<UserControl
|
||||
x:Class="mbts.spark.OrderBookView"
|
||||
x:ClassModifier="internal"
|
||||
x:Name="Root"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
Grid.Row="0"
|
||||
FocusManager.IsFocusScope="True"
|
||||
Background="{StaticResource CommonBackground}"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
<ResourceDictionary Source="/Modules/Trading/OrderBook/View/OrderParamsPanel.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="GridSplitterWidth">5</sys:Double>
|
||||
<sys:Double x:Key="FillsSlaveGridMinHeight">64</sys:Double>
|
||||
<SolidColorBrush x:Key="AlgoAttachmentBackground" Color="#55009500"/>
|
||||
<cv:GridLengthConverter x:Key="GridLengthPixelConverter" UnitType="Pixel"/>
|
||||
<Style TargetType="GridSplitter">
|
||||
<Style.Triggers>
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="{StaticResource Control.MediumGrayBrush}"/>
|
||||
<Setter Property="BorderBrush" Value="{StaticResource Control.MediumGrayBrush}"/>
|
||||
</Trigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Bid Price ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestBidSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Bid Size ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Ask Price ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.set_value_command, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding BestAskSize, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="ToolTip">
|
||||
<Setter.Value>
|
||||
<ToolTip DataContext="{Binding PlacementTarget.Parent.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<TextBlock Text="{Binding security, StringFormat='Best Ask Size ({0})'}"/>
|
||||
</ToolTip>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="{x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}">
|
||||
<Setter Property="Command" Value="{Binding DataContext.size_calculator.click, RelativeSource={RelativeSource AncestorType=usp:OrderParamsPanel}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</UserControl.Resources>
|
||||
<AdornerDecorator>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelVisualStateConverter}">
|
||||
<Binding Path="DataContext.algo_editor_is_visible" Source="{u:DirectReference ContentRoot}"/>
|
||||
<Binding Path="DataContext.is_attaching_algos" Source="{u:DirectReference ContentRoot}"/>
|
||||
</MultiBinding>
|
||||
</u:StateDefinition>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
|
||||
<!--Normal State-->
|
||||
<VisualState Name="{x:Static Normal, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}"/>
|
||||
<!--Attach Orders State-->
|
||||
<VisualState Name="{x:Static AttachOrders, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
|
||||
<Storyboard>
|
||||
<!--Orders Grid-->
|
||||
<Int32Animation Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="1"/>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(FrameworkElement.Cursor)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Cursors.Pen}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="attachment_grid_overlay_border" Storyboard.TargetProperty="(UIElement.Opacity)">
|
||||
<DiscreteDoubleKeyFrame KeyTime="0" Value="1"/>
|
||||
</DoubleAnimationUsingKeyFrames>
|
||||
<!--Right Panel-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_host" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_width_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<!--Modify Orders Controls-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="modify_orders_button" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<!--Fills Slave Grid-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_toggle_hyperlink" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Collapsed}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<!--Attach Orders Controls-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attach_orders_dialog_skirt" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="attachment_content_root" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<!--Modify Orders State-->
|
||||
<VisualState Name="{x:Static ModifyOrders, MemberType={u:Nested sp:OrderBookView.VisualStateNames.RightPanel}}">
|
||||
<Storyboard>
|
||||
<!--Orders Grid-->
|
||||
<Int32Animation Storyboard.TargetName="orders_grid" Storyboard.TargetProperty="(Grid.ColumnSpan)" BeginTime="0" Duration="0" To="1"/>
|
||||
<!--Right Panel-->
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_host" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="right_panel_width_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding right_panel_width, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<sp:OrderBookGridView
|
||||
x:Name="orders_grid"
|
||||
Grid.ColumnSpan="2"
|
||||
Background="Transparent"
|
||||
Columns="{Binding columns}"
|
||||
CurrentRow="{Binding current_row, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Rows="{Binding rows}"
|
||||
SelectionHighlight="{Binding grid_settings_manager.selection_highlight, Mode=OneWay}"
|
||||
Cursor="Arrow"
|
||||
/>
|
||||
<Border
|
||||
x:Name="attachment_grid_overlay_border"
|
||||
Panel.ZIndex="1"
|
||||
Width="{Binding ActualWidth, ElementName=orders_grid}"
|
||||
Height="{Binding ActualHeight, ElementName=orders_grid}"
|
||||
Background="{StaticResource AlgoAttachmentBackground}"
|
||||
Opacity="0"
|
||||
IsHitTestVisible="False"
|
||||
/>
|
||||
<GridSplitter
|
||||
x:Name="right_panel_width_splitter"
|
||||
Grid.Column="1"
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Stretch"
|
||||
Width="{StaticResource GridSplitterWidth}"
|
||||
BorderThickness="1,0,0,0"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<Grid x:Name="right_panel_host" Grid.Column="1" Visibility="Collapsed">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid x:Name="right_panel_content_root">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference right_panel_content_root}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.FillsSlaveGridStateConverter}">
|
||||
<Binding Path="DataContext.fills_slave_grid_is_visible" Source="{u:DirectReference right_panel_content_root}"/>
|
||||
<Binding Path="DataContext.is_attaching_algos" Source="{u:DirectReference right_panel_content_root}"/>
|
||||
</MultiBinding>
|
||||
</u:StateDefinition>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}">
|
||||
<Storyboard>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_grid" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="fills_order_params_splitter" Storyboard.TargetProperty="(UIElement.Visibility)">
|
||||
<DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
|
||||
</ObjectAnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.Row)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="2"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
<Int32AnimationUsingKeyFrames Storyboard.TargetName="order_params_group_box" Storyboard.TargetProperty="(Grid.RowSpan)">
|
||||
<DiscreteInt32KeyFrame KeyTime="0" Value="1"/>
|
||||
</Int32AnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested sp:OrderBookView.VisualStateNames.FillsPanel}}"/>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource GridSplitterWidth}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition MinHeight="{StaticResource FillsSlaveGridMinHeight}" Height="{Binding fills_slave_grid_height, Converter={StaticResource GridLengthPixelConverter}, Mode=TwoWay}" MaxHeight="{Binding ActualHeight, ElementName=right_panel_content_root, Converter={x:Static sp:OrderBookView.FillsSlaveGridMaxHeightConverter}}"/>
|
||||
<RowDefinition MinHeight="{x:Static sp:OrderBookView.GridSplitterHeight}" Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock x:Name="fills_toggle_hyperlink" Grid.Column="1">
|
||||
<TextBlock.Margin>
|
||||
<Thickness Left="{StaticResource DialogSkirtPadding.Left}" Top="4"/>
|
||||
</TextBlock.Margin>
|
||||
<Hyperlink Command="{Binding toggle_fills_slave_grid_is_visible_cmd}">
|
||||
<Hyperlink.Inlines>
|
||||
<Run Text="{Binding current_row, Converter={x:Static sp:OrderBookView.OrderDescriptionConverter}, Mode=OneWay}"/>
|
||||
</Hyperlink.Inlines>
|
||||
</Hyperlink>
|
||||
</TextBlock>
|
||||
|
||||
<c:DIYGrid x:Name="fills_grid" Grid.Column="1" Grid.Row="1" Columns="{Binding fills_vm.columns}" Rows="{Binding fills_vm.rows}" Visibility="Collapsed">
|
||||
<c:DIYGrid.Margin>
|
||||
<Thickness Bottom="{x:Static sp:OrderBookView.GridSplitterHeight}"/>
|
||||
</c:DIYGrid.Margin>
|
||||
<c:DIYGrid.RowTemplate>
|
||||
<DataTemplate>
|
||||
<usp:FillsRowView/>
|
||||
</DataTemplate>
|
||||
</c:DIYGrid.RowTemplate>
|
||||
</c:DIYGrid>
|
||||
<GridSplitter
|
||||
x:Name="fills_order_params_splitter"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Panel.ZIndex="1"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Bottom"
|
||||
Height="{x:Static sp:OrderBookView.GridSplitterHeight}"
|
||||
BorderThickness="0,1,0,0"
|
||||
Visibility="Collapsed"
|
||||
/>
|
||||
<GroupBox
|
||||
x:Name="order_params_group_box"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Padding="12"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Order Parameters"
|
||||
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
|
||||
>
|
||||
<usp:OrderParamsPanel
|
||||
x:Name="order_params_panel"
|
||||
DataContext="{Binding order_mod_vm}"
|
||||
Price="{Binding price}"
|
||||
Size="{Binding size_calculator.value}"
|
||||
Accounts="{Binding accounts}"
|
||||
SelectedAccount="{Binding selected_account}"
|
||||
BestBidPrice="{Binding bbo.bid}"
|
||||
BestBidSize="{Binding bbo.bid_size}"
|
||||
BestAskPrice="{Binding bbo.ask}"
|
||||
BestAskSize="{Binding bbo.ask_size}"
|
||||
SizePresets="{Binding size_calculator.buttons}"
|
||||
Security="{Binding security}"
|
||||
AlgoVM="{Binding algo_vm}"
|
||||
IsEnabled="{Binding IsEnabled, ElementName=modify_orders_button}"
|
||||
>
|
||||
<usp:OrderParamsPanel.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
|
||||
</usp:OrderParamsPanel.CommandBindings>
|
||||
</usp:OrderParamsPanel>
|
||||
</GroupBox>
|
||||
<GroupBox
|
||||
x:Name="attachment_content_root"
|
||||
Grid.Column="1"
|
||||
Grid.Row="1"
|
||||
Grid.RowSpan="2"
|
||||
Padding="12"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Left"
|
||||
Header="Attached Order Parameters"
|
||||
HeaderTemplate="{StaticResource BoldHeaderTemplate}"
|
||||
Margin="0,4,0,0"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<usp:OrderParamsPanel
|
||||
x:Name="attached_order_params_panel"
|
||||
DataContext="{Binding attach_vm}"
|
||||
Price="{Binding price}"
|
||||
Size="{Binding size_calculator.value}"
|
||||
Accounts="{Binding accounts}"
|
||||
SelectedAccount="{Binding selected_account}"
|
||||
BestBidPrice="{Binding bbo.bid}"
|
||||
BestBidSize="{Binding bbo.bid_size}"
|
||||
BestAskPrice="{Binding bbo.ask}"
|
||||
BestAskSize="{Binding bbo.ask_size}"
|
||||
SizePresets="{Binding size_calculator.buttons}"
|
||||
Security="{Binding security}"
|
||||
AlgoVM="{Binding algo_vm}"
|
||||
IsEnabled="{Binding IsEnabled, ElementName=send_attached_orders_button}"
|
||||
>
|
||||
<usp:OrderParamsPanel.CommandBindings>
|
||||
<CommandBinding Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}" Executed="{x:Static usp:OrderParamsPanel.SetPriceExecuted}"/>
|
||||
</usp:OrderParamsPanel.CommandBindings>
|
||||
</usp:OrderParamsPanel>
|
||||
</GroupBox>
|
||||
</Grid>
|
||||
<Canvas Grid.Row="1" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=order_params_group_box}" Height="30">
|
||||
<Button
|
||||
x:Name="modify_orders_button"
|
||||
Canvas.Top="{StaticResource DialogSkirtPadding.Top}"
|
||||
Command="{Binding modify_selected_algos_command}"
|
||||
>
|
||||
<Canvas.Left>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelDialogSkirtLeftAnchorConverter}">
|
||||
<Binding Path="Width" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Canvas.Left>
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Modify Orders ({0})'}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
|
||||
<Setter Property="Text" Value="Modify Orders"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
</Canvas>
|
||||
<Canvas Grid.Row="1" HorizontalAlignment="Left" Width="{Binding ActualWidth, ElementName=attachment_content_root}" Height="30">
|
||||
<StackPanel
|
||||
x:Name="attach_orders_dialog_skirt"
|
||||
Orientation="Horizontal"
|
||||
Canvas.Top="{StaticResource DialogSkirtPadding.Top}"
|
||||
Visibility="Collapsed"
|
||||
>
|
||||
<Canvas.Left>
|
||||
<MultiBinding Converter="{x:Static sp:OrderBookView.RightPanelDialogSkirtLeftAnchorConverter}">
|
||||
<Binding Path="Width" RelativeSource="{RelativeSource AncestorType=Canvas}"/>
|
||||
<Binding Path="ActualWidth" RelativeSource="{RelativeSource Self}"/>
|
||||
</MultiBinding>
|
||||
</Canvas.Left>
|
||||
<Button x:Name="send_attached_orders_button" Command="{Binding send_attached_orders_cmd}" Style="{StaticResource {x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}}">
|
||||
<TextBlock>
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Text" Value="{Binding selected_rows.Count, StringFormat='Attach To Orders ({0})'}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsEnabled, RelativeSource={RelativeSource AncestorType=Button}}" Value="False">
|
||||
<Setter Property="Text" Value="Attach To Orders"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
</Button>
|
||||
<Button u:Behavior.CloseWindow="False" Margin="{StaticResource DialogSkirtButtonMargin}" Command="{Binding cancel_attach_orders_cmd}" Style="{StaticResource CancelButton}"/>
|
||||
</StackPanel>
|
||||
</Canvas>
|
||||
|
||||
</Grid>
|
||||
<StatusBar Grid.Row="1" DockPanel.Dock="Bottom">
|
||||
<StatusBarItem HorizontalAlignment="Right">
|
||||
<TextBlock>
|
||||
<Run Text="Orders:"/>
|
||||
<Run Text="{Binding rows.Count, Mode=OneWay}" />
|
||||
<Run Text="of"/>
|
||||
<Run Text="{Binding rows.total_row_count, Mode=OneWay}" />
|
||||
</TextBlock>
|
||||
</StatusBarItem>
|
||||
</StatusBar>
|
||||
</Grid>
|
||||
</AdornerDecorator>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,151 @@
|
||||
//#define EXPAND_ATTACHMENT_PANEL
|
||||
using mbts.ui;
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal partial class OrderBookView : UserControl {
|
||||
public const double
|
||||
GridSplitterWidth = 5,
|
||||
GridSplitterHeight = 5,
|
||||
MinAlgoEditorHeight = 64,
|
||||
MinAttachOrderParamsPanelWidth = 340,
|
||||
AttachOrderParamsPanelWidthRatio = 0.33;
|
||||
|
||||
public static readonly IValueConverter
|
||||
OrderDescriptionConverter = ValueConverter.create<OrderBookRowVM, string>(row => row == null ? string.Empty : row.order.short_description(row.security)),
|
||||
FillsSlaveGridMaxHeightConverter = ValueConverter.create<double, double>(right_panel_height => right_panel_height - (GridSplitterHeight + MinAlgoEditorHeight)),
|
||||
FillsPanelVisualStateConverter = ValueConverter.create<bool, string>(show => show ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive),
|
||||
AttachmentTargetCursorConverter = ValueConverter.create<bool, Cursor>(is_active => is_active ? Cursors.Pen : Cursors.Arrow);
|
||||
|
||||
public static readonly IMultiValueConverter
|
||||
RightPanelVisualStateConverter = ValueConverter.create<bool, bool, string>((is_modifying, is_attaching) =>
|
||||
is_attaching ? VisualStateNames.RightPanel.AttachOrders :
|
||||
(is_modifying ? VisualStateNames.RightPanel.ModifyOrders : VisualStateNames.RightPanel.Normal)),
|
||||
RightPanelDialogSkirtLeftAnchorConverter = ValueConverter.create<double, double, double>((canvas_width, skirt_width) => canvas_width - skirt_width + GridSplitterWidth),
|
||||
FillsSlaveGridStateConverter = ValueConverter.create<bool, bool, string>((is_fills_grid_visible, is_attaching) => is_fills_grid_visible && !is_attaching ? VisualStateNames.FillsPanel.Active : VisualStateNames.FillsPanel.Inactive);
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
DataContext = vm;
|
||||
#if EXPAND_ATTACHMENT_PANEL
|
||||
(RightPanelState right_panel, IDisposable hooks)? attach_op = null;
|
||||
vm.begin_attaching += on_begin_attaching;
|
||||
vm.end_attaching += on_end_attaching;
|
||||
vm.SubscribeDisposed(() => {
|
||||
if (attach_op.HasValue) {
|
||||
vm.right_panel_width = attach_op.Value.right_panel.raw_width;
|
||||
vm.fills_slave_grid_is_visible = attach_op.Value.right_panel.fills_slave_grid_is_visible;
|
||||
}
|
||||
vm.begin_attaching -= on_begin_attaching;
|
||||
vm.end_attaching -= on_end_attaching;
|
||||
});
|
||||
const int delay_ms = 25;
|
||||
async void on_begin_attaching() {
|
||||
if (attach_op.HasValue) {
|
||||
// begin-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
|
||||
return;
|
||||
}
|
||||
attach_op = (new RightPanelState(vm.right_panel_is_visible, vm.fills_slave_grid_is_visible, vm.raw_right_panel_width), orders_grid.SubscribePreviewMouseDown(cancel_on_grid_click));
|
||||
vm.fills_slave_grid_is_visible = false;
|
||||
vm.algo_editor_is_visible = true;
|
||||
double target_width = Math.Max(ActualWidth * AttachOrderParamsPanelWidthRatio, MinAttachOrderParamsPanelWidth);
|
||||
if (vm.right_panel_width < target_width) {
|
||||
foreach (double incr in _get_incremental_widths(attach_op.Value.right_panel.is_visible ? attach_op.Value.right_panel.raw_width : 0, target_width)) {
|
||||
vm.right_panel_width = incr;
|
||||
await Task.Delay(delay_ms);
|
||||
if (!attach_op.HasValue)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
async void on_end_attaching() {
|
||||
if (!attach_op.HasValue) {
|
||||
// end-attaching is already underway (prob won't happen in practice as this event is triggered by user interaction)
|
||||
return;
|
||||
}
|
||||
attach_op.Value.hooks.Dispose();
|
||||
var incr_widths = _get_incremental_widths(vm.right_panel_width, attach_op.Value.right_panel.raw_width);
|
||||
vm.algo_editor_is_visible = attach_op.Value.right_panel.is_visible;
|
||||
vm.fills_slave_grid_is_visible = attach_op.Value.right_panel.fills_slave_grid_is_visible;
|
||||
vm.right_panel_width = attach_op.Value.right_panel.raw_width;
|
||||
attach_op = null;
|
||||
foreach (double incr in incr_widths) {
|
||||
vm.right_panel_width = incr;
|
||||
await Task.Delay(delay_ms);
|
||||
if (attach_op.HasValue)
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
IDisposable attach_op = null;
|
||||
vm.begin_attaching += on_begin_attaching;
|
||||
vm.end_attaching += on_end_attaching;
|
||||
void on_begin_attaching() => Disposable.replace(ref attach_op, orders_grid.SubscribePreviewMouseDown(cancel_on_grid_click));
|
||||
void on_end_attaching() => Disposable.dispose(ref attach_op);
|
||||
#endif
|
||||
void cancel_on_grid_click(object sender, MouseButtonEventArgs e) {
|
||||
if (!KeyboardExtensions.IsCtrlDown() && !KeyboardExtensions.IsShiftDown())
|
||||
vm.is_attaching_algos = false;
|
||||
}
|
||||
}
|
||||
|
||||
static IEnumerable<double> _get_incremental_widths(double from, double to) {
|
||||
var list = new List<double> { to };
|
||||
int incr = 1;
|
||||
if (from < to) {
|
||||
do {
|
||||
list.Add(to - (incr * 0.6666));
|
||||
list.Add(to - incr);
|
||||
from += incr;
|
||||
incr += incr;
|
||||
} while (from < to);
|
||||
} else if (from > to) {
|
||||
do {
|
||||
list.Add(to + incr);
|
||||
incr += incr;
|
||||
to += incr;
|
||||
} while (from > to);
|
||||
}
|
||||
for (int i = list.Count - 1; i >= 0; i--) {
|
||||
yield return list[i];
|
||||
}
|
||||
}
|
||||
|
||||
struct RightPanelState {
|
||||
public readonly bool is_visible, fills_slave_grid_is_visible;
|
||||
public readonly double raw_width;
|
||||
|
||||
public RightPanelState(bool right_panel_is_visible, bool fills_slave_grid_is_visible, double raw_right_panel_width) {
|
||||
this.is_visible = right_panel_is_visible;
|
||||
this.fills_slave_grid_is_visible = fills_slave_grid_is_visible;
|
||||
this.raw_width = raw_right_panel_width;
|
||||
}
|
||||
}
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class RightPanel {
|
||||
public const string GroupName = nameof(RightPanel);
|
||||
|
||||
public static readonly string
|
||||
Normal = $"{GroupName}{nameof(Normal)}",
|
||||
ModifyOrders = $"{GroupName}{nameof(ModifyOrders)}",
|
||||
AttachOrders = $"{GroupName}{nameof(AttachOrders)}";
|
||||
}
|
||||
|
||||
public static class FillsPanel {
|
||||
public const string GroupName = nameof(FillsPanel);
|
||||
|
||||
public static readonly string
|
||||
Active = $"{GroupName}{VisualStates.Active}",
|
||||
Inactive = $"{GroupName}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:order="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ControlTemplate x:Key="OrderParamsPanelTemplate" TargetType="usp:OrderParamsPanel">
|
||||
<Grid Grid.IsSharedSizeScope="True">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<usp:PriceEntryControl
|
||||
ContractSpec="{Binding ContractSpec, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Value="{Binding Price, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"
|
||||
ToolTip="Price"
|
||||
/>
|
||||
<Button
|
||||
x:Name="best_bid_button"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
|
||||
CommandParameter="{Binding BestBidPrice, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
FontWeight="Bold"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidPriceButtonStyleKey}}"
|
||||
>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="BestBidPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
<Button
|
||||
x:Name="best_ask_button"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Command="{x:Static usp:OrderParamsPanel.SetPriceCommand}"
|
||||
CommandParameter="{Binding BestAskPrice, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
FontWeight="Bold"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskPriceButtonStyleKey}}"
|
||||
>
|
||||
<MultiBinding Converter="{StaticResource TickDisplayConverter}">
|
||||
<Binding Path="BestAskPrice" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
<Binding Path="ContractSpec" RelativeSource="{RelativeSource TemplatedParent}"/>
|
||||
</MultiBinding>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush2}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="*"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<c:TickUpDownNullableUInt Value="{Binding Size, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Size"/>
|
||||
<Button
|
||||
x:Name="best_bid_size_button"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding BestBidSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.bid}"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestBidSizeButtonStyleKey}}"
|
||||
/>
|
||||
<Button
|
||||
x:Name="best_ask_size_button"
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Stretch"
|
||||
Content="{Binding BestAskSize, RelativeSource={RelativeSource TemplatedParent}}"
|
||||
Foreground="{u:BTSColor MarketData.ask}"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.BestAskSizeButtonStyleKey}}"
|
||||
/>
|
||||
<ItemsControl Grid.Row="2" Grid.RowSpan="2" Margin="-2,-2,-2,0" ItemsSource="{Binding SizePresets, RelativeSource={RelativeSource TemplatedParent}}" Focusable="False">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<UniformGrid Columns="3" Focusable="False"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="2,4,2,0"
|
||||
Content="{Binding}"
|
||||
Focusable="False"
|
||||
Style="{DynamicResource {x:Static usp:OrderParamsPanel.SizeCalculatorButtonStyleKey}}"
|
||||
/>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
<Grid Grid.Row="2" Grid.RowSpan="2" Grid.Column="2" Grid.ColumnSpan="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<ComboBox Margin="0,2,0,4" ItemsSource="{Binding Accounts, RelativeSource={RelativeSource TemplatedParent}}" SelectedItem="{Binding SelectedAccount, RelativeSource={RelativeSource TemplatedParent}}" ToolTip="Choose Account"/>
|
||||
<ComboBox Grid.Row="1">
|
||||
<ComboBox.Style>
|
||||
<Style TargetType="ComboBox" BasedOn="{StaticResource DisabledPlaceholderComboBox}">
|
||||
<Setter Property="ItemsSource">
|
||||
<Setter.Value>
|
||||
<x:ArrayExtension Type="{x:Type sys:String}">
|
||||
<sys:String>Time In Force (N/A)</sys:String>
|
||||
</x:ArrayExtension>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</ComboBox.Style>
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<usp:AlgoEditorView x:Name="algo_editor" Grid.Row="4" DataContext="{Binding AlgoVM, RelativeSource={RelativeSource TemplatedParent}}"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<Style TargetType="usp:OrderParamsPanel">
|
||||
<Setter Property="MinWidth" Value="300"/>
|
||||
<Setter Property="Template" Value="{StaticResource OrderParamsPanelTemplate}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,188 @@
|
||||
using bts.api;
|
||||
using mbts.spark;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace mbts.ui.spark {
|
||||
class OrderParamsPanel : Control {
|
||||
public const string
|
||||
BestBidPriceButtonStyleKey = nameof(BestBidPriceButtonStyleKey),
|
||||
BestBidSizeButtonStyleKey = nameof(BestBidSizeButtonStyleKey),
|
||||
BestAskPriceButtonStyleKey = nameof(BestAskPriceButtonStyleKey),
|
||||
BestAskSizeButtonStyleKey = nameof(BestAskSizeButtonStyleKey),
|
||||
SizeCalculatorButtonStyleKey = nameof(SizeCalculatorButtonStyleKey);
|
||||
|
||||
public static readonly RoutedCommand SetPriceCommand = new RoutedCommand();
|
||||
|
||||
public static readonly ExecutedRoutedEventHandler SetPriceExecuted =
|
||||
(s, e) => {
|
||||
if (s is OrderParamsPanel opp)
|
||||
opp.SetCurrentValue(PriceProperty, ConvertUtils.try_convert(e.Parameter, out double d) ? d : double.NaN);
|
||||
};
|
||||
|
||||
public static readonly CanExecuteRoutedEventHandler
|
||||
SetPriceCanExecute = (s, e) => e.CanExecute = e.Parameter is double,
|
||||
SetSizeCanExecute = (s, e) => e.CanExecute = e.Parameter is uint;
|
||||
|
||||
static OrderParamsPanel() {
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(OrderParamsPanel), new FrameworkPropertyMetadata(typeof(OrderParamsPanel)));
|
||||
}
|
||||
|
||||
#region Price
|
||||
public static readonly DependencyProperty PriceProperty =
|
||||
WPFHelper.create_dp<double, OrderParamsPanel>(nameof(Price), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, double.NaN);
|
||||
|
||||
public double Price {
|
||||
get => (double)GetValue(PriceProperty);
|
||||
set => SetValue(PriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Size
|
||||
public static readonly DependencyProperty SizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(Size), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault);
|
||||
|
||||
public uint? Size {
|
||||
get => GetValue(SizeProperty) as uint?;
|
||||
set => SetValue(SizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SizePresets
|
||||
public static readonly DependencyProperty SizePresetsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(SizePresets), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable SizePresets {
|
||||
get => GetValue(SizePresetsProperty) as IEnumerable;
|
||||
set => SetValue(SizePresetsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestBidPrice
|
||||
public static readonly DependencyProperty BestBidPriceProperty =
|
||||
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestBidPrice));
|
||||
|
||||
public double? BestBidPrice {
|
||||
get => GetValue(BestBidPriceProperty) as double?;
|
||||
set => SetValue(BestBidPriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestBidSize
|
||||
public static readonly DependencyProperty BestBidSizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestBidSize));
|
||||
|
||||
public uint? BestBidSize {
|
||||
get => GetValue(BestBidSizeProperty) as uint?;
|
||||
set => SetValue(BestBidSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestAskPrice
|
||||
public static readonly DependencyProperty BestAskPriceProperty =
|
||||
WPFHelper.create_dp<double?, OrderParamsPanel>(nameof(BestAskPrice));
|
||||
|
||||
public double? BestAskPrice {
|
||||
get => GetValue(BestAskPriceProperty) as double?;
|
||||
set => SetValue(BestAskPriceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region BestAskSize
|
||||
public static readonly DependencyProperty BestAskSizeProperty =
|
||||
WPFHelper.create_dp<uint?, OrderParamsPanel>(nameof(BestAskSize));
|
||||
|
||||
public uint? BestAskSize {
|
||||
get => GetValue(BestAskSizeProperty) as uint?;
|
||||
set => SetValue(BestAskSizeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Accounts
|
||||
public static readonly DependencyProperty AccountsProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(Accounts), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable Accounts {
|
||||
get => GetValue(AccountsProperty) as IEnumerable;
|
||||
set => SetValue(AccountsProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedAccount
|
||||
public static readonly DependencyProperty SelectedAccountProperty =
|
||||
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedAccount));
|
||||
|
||||
public object SelectedAccount {
|
||||
get => GetValue(SelectedAccountProperty);
|
||||
set => SetValue(SelectedAccountProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TimeInForces
|
||||
public static readonly DependencyProperty TimeInForcesProperty =
|
||||
WPFHelper.create_dp<IEnumerable, OrderParamsPanel>(nameof(TimeInForces), Enumerable.Empty<string>());
|
||||
|
||||
public IEnumerable TimeInForces {
|
||||
get => GetValue(TimeInForcesProperty) as IEnumerable;
|
||||
set => SetValue(TimeInForcesProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region SelectedTimeInForce
|
||||
public static readonly DependencyProperty SelectedTimeInForceProperty =
|
||||
WPFHelper.create_dp<object, OrderParamsPanel>(nameof(SelectedTimeInForce));
|
||||
|
||||
public object SelectedTimeInForce {
|
||||
get => GetValue(SelectedTimeInForceProperty);
|
||||
set => SetValue(SelectedTimeInForceProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Security
|
||||
public static readonly DependencyProperty SecurityProperty =
|
||||
WPFHelper.create_dp<SecurityImmutable?, OrderParamsPanel>(
|
||||
nameof(Security),
|
||||
(o, v) => o.CoerceValue(ContractSpecProperty));
|
||||
|
||||
public SecurityImmutable? Security {
|
||||
get => GetValue(SecurityProperty) as SecurityImmutable?;
|
||||
set => SetValue(SecurityProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region ContractSpec
|
||||
public static readonly DependencyProperty ContractSpecProperty =
|
||||
WPFHelper.create_dp<ContractSpecificationImmutable?, OrderParamsPanel>(
|
||||
nameof(ContractSpec),
|
||||
null,
|
||||
(d, base_value) => d.Security?.ContractSpec ?? base_value ?? SecurityDataHelper.Defaults.ContractSpecificationImmutable);
|
||||
|
||||
public ContractSpecificationImmutable? ContractSpec {
|
||||
get => GetValue(ContractSpecProperty) as ContractSpecificationImmutable?;
|
||||
set => SetValue(ContractSpecProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AlgoVM
|
||||
public static readonly DependencyProperty AlgoVMProperty =
|
||||
WPFHelper.create_dp<AlgoDefinitionVM, OrderParamsPanel>(
|
||||
nameof(AlgoVM),
|
||||
(o, v) => o._algo_editor?.SetCurrentValue(VisibilityProperty, v != null ? Visibility.Visible : Visibility.Collapsed));
|
||||
|
||||
public AlgoDefinitionVM AlgoVM {
|
||||
get => GetValue(AlgoVMProperty) as AlgoDefinitionVM;
|
||||
set => SetValue(AlgoVMProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
AlgoEditorView _algo_editor;
|
||||
public override void OnApplyTemplate() {
|
||||
_algo_editor = GetTemplateChild("algo_editor") as AlgoEditorView;
|
||||
_algo_editor?.SetCurrentValue(VisibilityProperty, AlgoVM != null ? Visibility.Visible : Visibility.Collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,10 +79,12 @@ namespace mbts.spark {
|
||||
RequestClose?.Invoke(this, new EventArgs());
|
||||
}
|
||||
});
|
||||
bbo = new BBOVM();
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
bbo = new BBOVM(security_md_source),
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
bbo,
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
|
||||
@@ -49,7 +49,7 @@ namespace mbts.spark {
|
||||
) {
|
||||
_security = backing(nameof(security), security_md_source.Select(smd => smd.security), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_md_source, props_source.Select(p => p.order_props));
|
||||
bbo = new BBOVM(security_md_source);
|
||||
bbo = new BBOVM();
|
||||
send_order = new DelegateCommand<Side>(side => {
|
||||
bool price_valid = _validate_price(), size_valid = size_calculator.validate();
|
||||
if (price_valid && size_valid && !HasErrors && !properties_vm.HasErrors) {
|
||||
@@ -59,6 +59,8 @@ namespace mbts.spark {
|
||||
});
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
bbo,
|
||||
security_md_source.OnDispatcher().Subscribe(smd => bbo.security_md = smd),
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.utils;
|
||||
using mbts;
|
||||
using mbts.spark;
|
||||
using mbts.ui.controls;
|
||||
@@ -83,25 +84,18 @@ namespace bts.spark {
|
||||
public static string modified_description(OrderRecordImmutable old_order, OrderRecordImmutable new_order, ContractSpecificationImmutable? spec) {
|
||||
double old_price = old_order.State.Price, new_price = new_order.State.Price;
|
||||
uint old_size = old_order.State.Quantity, new_size = new_order.State.Quantity;
|
||||
string modified_size_partial_description(int working_size) {
|
||||
return $"size from {old_size} to {new_size}, working {working_size}";
|
||||
uint old_attached_target_key = old_order.attached_target_order_key(), new_attached_target_key = new_order.attached_target_order_key();
|
||||
return StringBuilderEx.join(prefix, change_descs());
|
||||
//
|
||||
string prefix() => $"({old_order.Key}) Changed";
|
||||
IEnumerable<string> change_descs() {
|
||||
if (old_price != new_price)
|
||||
yield return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
if (old_size != new_size)
|
||||
yield return $"size from {old_size} to {new_size}, working {new_order.working_quantity()}";
|
||||
if (old_attached_target_key != new_attached_target_key)
|
||||
yield return $"attached target from {old_attached_target_key} to {new_attached_target_key}";
|
||||
}
|
||||
string modified_price_partial_description() {
|
||||
return $"price from {spec.format(old_price)} to {spec.format(new_price)}";
|
||||
}
|
||||
if (old_price != new_price || old_size != new_size) {
|
||||
StringBuilder sb = new StringBuilder($"({old_order.Key}) Changed ");
|
||||
if (old_price != new_price) {
|
||||
sb.Append(modified_price_partial_description());
|
||||
if (old_size != new_size)
|
||||
sb.Append($", {modified_size_partial_description(new_order.working_quantity())}");
|
||||
}
|
||||
else
|
||||
sb.Append(modified_size_partial_description(new_order.working_quantity()));
|
||||
return sb.ToString();
|
||||
}
|
||||
else
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
static string _header(uint key) => $"({key})";
|
||||
@@ -184,21 +178,31 @@ namespace bts.spark {
|
||||
format_cancel_error(order, security?.ContractSpec, error));
|
||||
}
|
||||
|
||||
internal void update_order(OrderRecordImmutable order, double price, uint size) {
|
||||
internal void update_order(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "change from");
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, uint size, bool algo_changed) =>
|
||||
_log_order_update(order, price, size, algo_changed, "failed to change from");
|
||||
|
||||
void _log_order_update(OrderRecordImmutable order, double price, uint size, bool algo_changed, string change_desc_prefix) {
|
||||
if (price == order.price() && size == order.working_quantity() && !algo_changed) {
|
||||
// nothing actually changed
|
||||
return;
|
||||
}
|
||||
const string algo_change_text = "change algo parameters";
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var spec = security?.ContractSpec;
|
||||
log_status(ActivityLogCategory.modify,
|
||||
security,
|
||||
$"({order.Key}) change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
|
||||
var desc_builder = new StringBuilderEx();
|
||||
if (price != order.price() || size != order.working_quantity()) {
|
||||
desc_builder.Add(order.State.Quantity.ToString(), "@", spec.format(order.State.Price), "to ", size.ToString(), "@", spec.format(price));
|
||||
if (algo_changed)
|
||||
desc_builder.Add("; ", algo_change_text);
|
||||
} else if (algo_changed) {
|
||||
desc_builder.Add(algo_change_text);
|
||||
}
|
||||
desc_builder.Insert(0, $"{change_desc_prefix} ");
|
||||
desc_builder.Insert(0, $"({order.Key}) ");
|
||||
log_status(ActivityLogCategory.modify, security, desc_builder.ToString());
|
||||
}
|
||||
|
||||
internal void update_order_failed(OrderRecordImmutable order, double price, uint size) {
|
||||
var security = try_get_security(order.Def.SecurityKey);
|
||||
var spec = security?.ContractSpec;
|
||||
log_status(ActivityLogCategory.modify,
|
||||
security,
|
||||
$"({order.Key}) failed to change from {order.State.Quantity}@{spec.format(order.State.Price)} to {size}@{spec.format(price)}");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -346,9 +346,9 @@ namespace mbts.spark {
|
||||
_time_in_force.observe_current(start_immediately);
|
||||
|
||||
public IObservable<IAlgoVM> observe_order_type(bool start_immediately = false) =>
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
_model.algo_model.templates_enabled() ?
|
||||
_selected_algo_template.observe_current(start_immediately).Select(t => t?.algo_root) :
|
||||
_selected_algo_definition.observe_selected_item(start_immediately).Select(d => d?.algo_root);
|
||||
|
||||
public OrderDefinition create(Side side) {
|
||||
if (HasErrors)
|
||||
|
||||
@@ -11,7 +11,7 @@ using AlgoDefi = bts.api.orderman.CustomOrderDefinitionImmutable;
|
||||
using AlgoType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
|
||||
|
||||
namespace mbts.spark {
|
||||
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel> {
|
||||
class DefaultAlgoVM : DisposableViewModelBase, IAlgoVM, IDynamicAlgoVMImpl, IObserver<SecurityMD?>, IObserver<OrdersModel>, IObserver<AlgoEditingPermissions> {
|
||||
static readonly AlgoDefi _empty = new AlgoDefi(new AlgoDef());
|
||||
readonly VersionContainer<AlgoDefi> _definition;
|
||||
AlgoFormats _format;
|
||||
@@ -37,21 +37,15 @@ namespace mbts.spark {
|
||||
impl.is_expanded = value;
|
||||
}
|
||||
}
|
||||
public bool can_edit_values => impl?.can_edit_values ?? false;
|
||||
public bool can_edit_structure => impl?.can_edit_structure ?? false;
|
||||
public bool can_save_values => impl?.can_save_values ?? false;
|
||||
public bool can_save_structure => impl?.can_save_structure ?? false;
|
||||
public AlgoEditingPermissions editing_permissions => _editing_permissions;
|
||||
public string display_name => impl?.display_name ?? "(Default)";
|
||||
public AlgoDefi definition => _definition.value;
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors { get; } = new ObservableCollection<IAlgoSelector>();
|
||||
public dynamic children_vm => impl?.children_vm;
|
||||
|
||||
public void assign_to(AlgoDef value) => value.MergeFrom(definition.CloneToMutable());
|
||||
public bool key_match(KeyEventArgs e) => false;
|
||||
public void load_from(AlgoDefi value) => impl?.load_from(value);
|
||||
public void set_permissions(AlgoEditingPermissions editing_permissions) {
|
||||
_editing_permissions = editing_permissions;
|
||||
impl?.set_permissions(editing_permissions);
|
||||
}
|
||||
public Task<AlgoDefi?> load_from_async(AlgoDefi definition) =>
|
||||
impl?.load_from_async(definition) ?? Task.FromResult<AlgoDefi?>(definition);
|
||||
|
||||
@@ -98,6 +92,13 @@ namespace mbts.spark {
|
||||
_on_impl_definition_changed();
|
||||
}
|
||||
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) {
|
||||
Set(ref _editing_permissions, editing_permissions, nameof(this.editing_permissions));
|
||||
(impl as IObserver<AlgoEditingPermissions>)?.OnNext(editing_permissions);
|
||||
}
|
||||
|
||||
void _on_impl_definition_changed() {
|
||||
var orig_def = definition;
|
||||
_definition.increment_version();
|
||||
@@ -105,6 +106,7 @@ namespace mbts.spark {
|
||||
definition_changed?.Invoke();
|
||||
if (order_type != orig_def.CustomCase)
|
||||
RaisePropertyChanged(nameof(order_type), nameof(meta));
|
||||
RaisePropertyChanged(nameof(children_vm));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.utils;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -11,8 +12,9 @@ using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
public class DynamicOrderHelper
|
||||
{
|
||||
class DynamicOrderHelper {
|
||||
public readonly IMaskIndexInt32<string> dynamic_type_index;
|
||||
|
||||
static readonly IReadOnlyDictionary<DynamicOrderParam.ValueOneofCase, (FieldDescriptor field_descriptor, Type field_type)> _descriptor_case_index =
|
||||
ProtoHelper.get_oneof_descriptor_index<DynamicOrderParam, DynamicOrderParam.ValueOneofCase>(nameof(DynamicOrderParam.ValueCase));
|
||||
|
||||
@@ -21,6 +23,7 @@ namespace mbts.spark {
|
||||
private DynamicOrderHelper(OrderService order_service)
|
||||
{
|
||||
_dynamic_types = order_service.get_registered_dynamic_order_types(new VoidMessage());
|
||||
dynamic_type_index = new MaskIndexInt32<string>(_dynamic_types.DynamicOrderDesc.Select(d => d.DynamicOrderType).ToList());
|
||||
}
|
||||
|
||||
public void add_dynamic_order_defs(List<CustomOrderDefinitionImmutable> defs,
|
||||
@@ -30,9 +33,9 @@ namespace mbts.spark {
|
||||
{
|
||||
var dorder = factory(CustomOrderDefinition.CustomOneofCase.Dynamic);
|
||||
dorder.Dynamic.DynamicOrderType = d.DynamicOrderType;
|
||||
var ddef = dorder.Dynamic;
|
||||
//Add only the descriptor here for reflection later
|
||||
foreach(var p in d.ParamDesc)
|
||||
dorder.Dynamic.OrderParams.Add( p.ParamName, new DynamicOrderParam() );
|
||||
add_dynamic_params(ref ddef);
|
||||
defs.Add(new CustomOrderDefinitionImmutable(dorder));
|
||||
}
|
||||
}
|
||||
@@ -42,39 +45,35 @@ namespace mbts.spark {
|
||||
|
||||
internal DynamicOrderDefinition create_def(string dynamic_type) {
|
||||
var desc = get_descriptor(dynamic_type);
|
||||
var def = new DynamicOrderDefinition();
|
||||
foreach (var p in desc.ParamDesc) {
|
||||
if (_is_child_algo_desc(p)) {
|
||||
def.OrderParams[p.ParamName] = new DynamicOrderParam { AsOrderDef = new CustomOrderDefinition() };
|
||||
} else if (_is_value_param_desc(p)) {
|
||||
var value_case = _get_value_case(p.ParamType.Primitive);
|
||||
if (_descriptor_case_index.TryGetValue(value_case, out (FieldDescriptor fd, Type type) tup)) {
|
||||
var dop = new DynamicOrderParam();
|
||||
tup.fd.Accessor.SetValue(dop, ConvertUtils.get_default(tup.type));
|
||||
def.OrderParams[p.ParamName] = dop;
|
||||
}
|
||||
}
|
||||
}
|
||||
var def = new DynamicOrderDefinition { DynamicOrderType = dynamic_type };
|
||||
add_dynamic_params(ref def);
|
||||
return def;
|
||||
}
|
||||
|
||||
static DynamicOrderParam.ValueOneofCase _get_value_case(DynamicOrderPrimitiveDescriptor primitive_descriptor_case) {
|
||||
switch (primitive_descriptor_case) {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: return DynamicOrderParam.ValueOneofCase.AsBool;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: return DynamicOrderParam.ValueOneofCase.AsBool;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: return DynamicOrderParam.ValueOneofCase.AsInt32;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: return DynamicOrderParam.ValueOneofCase.AsUint32;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return DynamicOrderParam.ValueOneofCase.AsString;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(primitive_descriptor_case, nameof(primitive_descriptor_case));
|
||||
}
|
||||
internal void add_dynamic_params(ref DynamicOrderDefinition dynamic_def) {
|
||||
var desc = get_descriptor(dynamic_def.DynamicOrderType);
|
||||
foreach (var p in desc.ParamDesc)
|
||||
dynamic_def.OrderParams[p.ParamName] = init_param(p);
|
||||
}
|
||||
|
||||
static bool _is_child_algo_desc(DynamicOrderParamDescriptor desc) =>
|
||||
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive &&
|
||||
desc.ParamType.Primitive == DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef;
|
||||
|
||||
static bool _is_value_param_desc(DynamicOrderParamDescriptor desc) =>
|
||||
desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ||
|
||||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
|
||||
internal static DynamicOrderParam init_param(DynamicOrderParamDescriptor descriptor) {
|
||||
var param = new DynamicOrderParam();
|
||||
switch (descriptor.ParamType.DescriptorCase) {
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Array: param.AsArray = new DynamicOrderArray(); break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: param.AsEnumerated = new DynamicOrderEnumerated { EnumValue = descriptor.ParamType.Enumerated.Element.FirstOrDefault() }; break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive:
|
||||
switch (descriptor.ParamType.Primitive) {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveBool: param.AsBool = false; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveDouble: param.AsDouble = 0.0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveInt: param.AsInt32 = 0; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef: param.AsOrderDef = new CustomOrderDefinition(); break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: param.AsString = string.Empty; break;
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveUint: param.AsUint32 = 0u; break;
|
||||
}
|
||||
break;
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Structure: param.AsStruct = new DynamicOrderStruct(); break;
|
||||
}
|
||||
return param;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,21 @@ using Google.Protobuf.Reflection;
|
||||
using mbts.ui.shell;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
abstract class DynamicOrderParamVM : ViewModelBase {
|
||||
DynamicOrderParam _param = new DynamicOrderParam();
|
||||
abstract class DynamicOrderParamVM : DisposableViewModelBase {
|
||||
DynamicOrderParam _param;
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor) =>
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor)
|
||||
: this(descriptor, DynamicOrderHelper.init_param(descriptor)) { }
|
||||
|
||||
protected DynamicOrderParamVM(DynamicOrderParamDescriptor descriptor, DynamicOrderParam param) {
|
||||
this.descriptor = descriptor;
|
||||
_param = param;
|
||||
}
|
||||
|
||||
public DynamicOrderParamDescriptor descriptor { get; set; }
|
||||
public DynamicOrderParam param {
|
||||
@@ -34,6 +42,7 @@ namespace mbts.spark {
|
||||
case DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveString: return new DynamicOrderParamVM<string>(descriptor, DynamicOrderParam.AsStringFieldNumber);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.Primitive, nameof(descriptor));
|
||||
}
|
||||
case DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated: return new DynamicOrderEnumeratedVM(descriptor);
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(descriptor.ParamType.DescriptorCase, nameof(descriptor));
|
||||
}
|
||||
}
|
||||
@@ -55,4 +64,41 @@ namespace mbts.spark {
|
||||
|
||||
protected override void on_param_changed() => RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
|
||||
class DynamicOrderEnumeratedVM : DynamicOrderParamVM {
|
||||
static readonly FieldDescriptor _field_desc = DynamicOrderParam.Descriptor.FindFieldByNumber(DynamicOrderParam.AsEnumeratedFieldNumber);
|
||||
|
||||
public DynamicOrderEnumeratedVM(DynamicOrderParamDescriptor descriptor)
|
||||
: base(descriptor) =>
|
||||
elements = new ObservableCollection<string>(_get_elements(descriptor));
|
||||
|
||||
public ObservableCollection<string> elements { get; }
|
||||
|
||||
public string selected_element {
|
||||
get => param?.ValueCase == DynamicOrderParam.ValueOneofCase.AsEnumerated ? param.AsEnumerated.EnumValue : null;
|
||||
set {
|
||||
if (param == null) {
|
||||
param = new DynamicOrderParam { AsEnumerated = new DynamicOrderEnumerated() };
|
||||
} else if (param.ValueCase != DynamicOrderParam.ValueOneofCase.AsEnumerated) {
|
||||
param.AsEnumerated = new DynamicOrderEnumerated();
|
||||
}
|
||||
if (param.AsEnumerated.EnumValue != value) {
|
||||
param.AsEnumerated.EnumValue = value;
|
||||
RaisePropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void on_param_changed() {
|
||||
_sync_elements();
|
||||
RaisePropertyChanged(nameof(selected_element));
|
||||
}
|
||||
|
||||
static IEnumerable<string> _get_elements(DynamicOrderParamDescriptor descriptor) =>
|
||||
descriptor.ParamType?.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Enumerated ?
|
||||
descriptor.ParamType.Enumerated.Element : Enumerable.Empty<string>();
|
||||
|
||||
void _sync_elements() =>
|
||||
elements.sync_with(_get_elements(descriptor));
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,10 @@ namespace mbts.spark {
|
||||
}),
|
||||
ObservableAutoWrapper.connect_item_hooks(_parameters, p => {
|
||||
p.PropertyChanged += on_param_prop_changed;
|
||||
return Disposable.Create(() => p.PropertyChanged -= on_param_prop_changed);
|
||||
return Disposable.Create(() => {
|
||||
p.PropertyChanged -= on_param_prop_changed;
|
||||
p.Dispose();
|
||||
});
|
||||
}, true));
|
||||
void on_param_prop_changed(object sender, PropertyChangedEventArgs e) => raise_definition_changed();
|
||||
}
|
||||
@@ -61,4 +64,4 @@ namespace mbts.spark {
|
||||
(desc.ParamType.DescriptorCase == DynamicOrderTypeDescriptor.DescriptorOneofCase.Primitive && desc.ParamType.Primitive != DynamicOrderPrimitiveDescriptor.DynamicOrderPrimitiveOrderDef);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,17 +22,14 @@ namespace mbts.spark {
|
||||
CustomOrderDefinition.CustomOneofCase order_type { get; }
|
||||
bool is_bts_type { get; }
|
||||
OrderMetadata meta { get; }
|
||||
AlgoEditingPermissions editing_permissions { get; }
|
||||
bool can_expand { get; }
|
||||
bool is_expanded { get; set; }
|
||||
bool can_edit_values { get; }
|
||||
bool can_edit_structure { get; }
|
||||
bool can_save_values { get; }
|
||||
bool can_save_structure { get; }
|
||||
string display_name { get; }
|
||||
CustomOrderDefinitionImmutable definition { get; }
|
||||
ObservableCollection<IAlgoSelector> child_algo_selectors { get; }
|
||||
dynamic children_vm { get; }
|
||||
bool key_match(KeyEventArgs e);
|
||||
void set_permissions(AlgoEditingPermissions editing_permissions);
|
||||
Task<CustomOrderDefinitionImmutable?> load_from_async(CustomOrderDefinitionImmutable definition);
|
||||
}
|
||||
|
||||
@@ -91,4 +88,4 @@ namespace mbts.spark {
|
||||
ObservableAutoWrapper.connect_item_hooks(vm.child_algo_selectors, s => load_algo_data_recursive(s, algo_data, default_algo)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -56,8 +56,6 @@ namespace mbts.spark {
|
||||
public static IAlgoVM create(AlgoData def_data, AlgoEditingPermissions editing_permissions = default_editing_permissions, bool is_expanded = false) {
|
||||
var vm = create(def_data.key, def_data.name, def_data.value.CustomCase, editing_permissions, is_expanded);
|
||||
vm.load_from(def_data.value);
|
||||
//vm.is_expanded = is_expanded && vm.can_expand;
|
||||
vm.is_expanded = is_expanded && vm.can_expand;
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -84,6 +82,7 @@ namespace mbts.spark {
|
||||
case AlgoType.None: vm = new DefaultAlgoVM(); break;
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
vm.is_expanded = is_expanded && vm.can_expand;
|
||||
return vm;
|
||||
}
|
||||
|
||||
@@ -95,38 +94,32 @@ namespace mbts.spark {
|
||||
public static FieldDescriptor find_descriptor<T>() =>
|
||||
_algo_type_index.TryGetValue(typeof(T), out FieldDescriptor fd) ?
|
||||
fd : throw new NotSupportedException($"No {nameof(AlgoDef)} {nameof(FieldDescriptor)} exists for type '{typeof(T)}.'");
|
||||
|
||||
public static Func<TMutable, TImmutable> immutable_factory<TMutable, TImmutable>() where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
|
||||
_ImmutableActivator<TMutable, TImmutable>.factory;
|
||||
|
||||
static class _ImmutableActivator<TMutable, TImmutable> where TMutable : IMessage, new() where TImmutable : IImmutableMessage {
|
||||
public static readonly Func<TMutable, TImmutable> factory =
|
||||
m => (TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
|
||||
}
|
||||
}
|
||||
|
||||
#region OrderTypeVM Base Classes
|
||||
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM, IObserver<SecurityMD?>, IObserver<OrdersModel> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
/// TODO: maybe use explicit delegates rather than rely on reflection <see cref="_descriptor"/> <see cref="_create_immutable"/>.
|
||||
abstract class OrderTypeVMBase<TMutable, TImmutable> : DisposableViewModelBase, IAlgoVM,
|
||||
IObserver<SecurityMD?>,
|
||||
IObserver<SecurityImmutable?>,
|
||||
IObserver<OrdersModel>,
|
||||
IObserver<AlgoEditingPermissions> where TMutable : class, IMessage, new() where TImmutable : struct, IImmutableMessage {
|
||||
static readonly FieldDescriptor _descriptor = OrderTypeVMUtils.find_descriptor<TMutable>();
|
||||
static readonly Func<TMutable, TImmutable> _create_immutable = OrderTypeVMUtils.immutable_factory<TMutable, TImmutable>();
|
||||
readonly IDictionary<string, object> _children_vm = new ExpandoObject();
|
||||
protected CompositeDisposable _resources = new CompositeDisposable();
|
||||
|
||||
readonly ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string> _auto_props = new ObservableKeyedCollection<(string name, Action<TMutable> assign, Action<TMutable> load), string>(t => t.name);
|
||||
readonly ObservableKeyedCollection<AlgoSelectorData, string> _child_algo_selector_data = new ObservableKeyedCollection<AlgoSelectorData, string>(sd => sd.prop_name);
|
||||
readonly ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData> _child_algo_selectors;
|
||||
readonly Property<bool> _is_expanded, _can_save_values, _can_save_structure;
|
||||
readonly Property<bool> _is_expanded;
|
||||
readonly DeferrableActionWrapper _def_change_notifier;
|
||||
SecurityInput _security_input;
|
||||
AlgoEditingPermissions _editing_permissions;
|
||||
IDisposable _pending_load;
|
||||
|
||||
protected OrderTypeVMBase(AlgoKey id, string display_name, AlgoEditingPermissions editing_permissions) {
|
||||
this.id = id;
|
||||
this.display_name = display_name;
|
||||
this.editing_permissions = editing_permissions;
|
||||
_editing_permissions = editing_permissions;
|
||||
_child_algo_selectors = new ObservableAutoWrapper<IAlgoSelector, AlgoSelectorData>(_child_algo_selector_data, sd => sd.selector);
|
||||
_can_save_values = backing(nameof(can_save_values), true);
|
||||
_can_save_structure = backing(nameof(can_save_structure), true);
|
||||
_is_expanded = backing<bool>(nameof(is_expanded));
|
||||
meta = OrderDataHelper.get_metadata(order_type);
|
||||
_def_change_notifier = new DeferrableActionWrapper(() => definition_changed?.Invoke());
|
||||
@@ -134,10 +127,6 @@ namespace mbts.spark {
|
||||
switch (e.PropertyName) {
|
||||
// ignore props that don't affect the resultant AlgoDef
|
||||
case nameof(is_expanded):
|
||||
case nameof(can_edit_values):
|
||||
case nameof(can_edit_structure):
|
||||
case nameof(can_save_values):
|
||||
case nameof(can_save_structure):
|
||||
return;
|
||||
default:
|
||||
_def_change_notifier.execute();
|
||||
@@ -148,10 +137,7 @@ namespace mbts.spark {
|
||||
CompositeDisposable def_sub = null;
|
||||
sd.selector.selected_algo_changed += subscribe_new_algo;
|
||||
return new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_hooks(sd.selector.algos, a => {
|
||||
a.set_permissions(this.editing_permissions);
|
||||
return a as IDisposable ?? Disposable.empty;
|
||||
}, true)) {
|
||||
ObservableAutoWrapper.try_dispose_on_removed(sd.selector.algos, true)) {
|
||||
() => {
|
||||
sd.selector.selected_algo_changed -= subscribe_new_algo;
|
||||
Disposable.dispose(ref def_sub);
|
||||
@@ -170,6 +156,7 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public event Action definition_changed;
|
||||
event Action<SecurityImmutable?> _security_changed;
|
||||
// core
|
||||
public AlgoDefi definition => new AlgoDefi(this.created_assigned());
|
||||
public AlgoType order_type => OrderDataHelper.get_order_type(new TMutable());
|
||||
@@ -182,18 +169,14 @@ namespace mbts.spark {
|
||||
public dynamic children_vm => _children_vm;
|
||||
public ObservableCollection<IAlgoSelector> child_algo_selectors => _child_algo_selectors;
|
||||
// editing permissions
|
||||
public AlgoEditingPermissions editing_permissions { get; private set; }
|
||||
public bool can_edit_values => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditValues);
|
||||
public bool can_edit_structure => EnumHelper.includes_flag(editing_permissions, AlgoEditingPermissions.AllowEditStructure);
|
||||
public bool can_save_values => _can_save_values.Value;
|
||||
public bool can_save_structure => _can_save_structure.Value;
|
||||
// misc
|
||||
public AlgoEditingPermissions editing_permissions { get => _editing_permissions; private set => Set(ref _editing_permissions, value); }
|
||||
// expand
|
||||
public virtual bool can_expand => !meta.NoProps;
|
||||
public virtual bool is_expanded { get => _is_expanded.Value; set => _is_expanded.Value = value; }
|
||||
// protected (observed props)
|
||||
protected OrdersModel model { get; private set; }
|
||||
protected SecurityMD? security_md { get; private set; }
|
||||
protected SecurityImmutable? security => security_md?.security;
|
||||
protected SecurityMD? security_md => _security_input.security_md;
|
||||
protected SecurityImmutable? security => _security_input.security;
|
||||
|
||||
public void assign_to(AlgoDef value) {
|
||||
var algo = new TMutable();
|
||||
@@ -206,12 +189,6 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
public void set_permissions(AlgoEditingPermissions editing_permissions) {
|
||||
using (detect_property_changes(nameof(can_edit_values), nameof(can_edit_structure), nameof(can_save_values), nameof(can_save_structure)))
|
||||
this.editing_permissions = editing_permissions;
|
||||
on_permissions_changed();
|
||||
}
|
||||
|
||||
public bool key_match(KeyEventArgs key) =>
|
||||
!string.IsNullOrEmpty(display_name) && key.Key == (Key.A + display_name[0] - 'A');
|
||||
|
||||
@@ -219,22 +196,31 @@ namespace mbts.spark {
|
||||
if (value.CustomCase != order_type)
|
||||
return;
|
||||
var algom = _descriptor.Accessor.GetValue(value.CloneToMutable()) as TMutable ?? new TMutable();
|
||||
var security_src = Observable.Create<SecurityImmutable?>(o => {
|
||||
o.OnNext(security);
|
||||
_security_changed += o.OnNext;
|
||||
return Disposable.Create(() => _security_changed -= o.OnNext);
|
||||
});
|
||||
Disposable.replace(ref _pending_load,
|
||||
new CompositeDisposable(
|
||||
ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), sd => {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}),
|
||||
Disposable.Create(
|
||||
ObservableAutoWrapper.connect_item_hooks(
|
||||
_child_algo_selector_data.AsCollection(),
|
||||
sd => security_src.Subscribe(_ => ObservableAutoWrapper.connect_item_actions(_child_algo_selector_data.AsCollection(), selector_load_impl))),
|
||||
ObservableAutoWrapper.connect_item_actions(_auto_props.AsCollection(), p => p.load(algom))));
|
||||
|
||||
void selector_load_impl(AlgoSelectorData sd) {
|
||||
var inner_def = sd.get_order(value);
|
||||
if (inner_def.HasValue) {
|
||||
if (security.HasValue) {
|
||||
inner_def = OrderDataHelper.fit_to_type(inner_def.Value, security.Value.ContractCase);
|
||||
if (!inner_def.HasValue)
|
||||
return;
|
||||
}
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase, is_expanded: is_expanded));
|
||||
vm.load_from(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public virtual async Task<AlgoDefi?> load_from_async(AlgoDefi defi) {
|
||||
@@ -246,7 +232,7 @@ namespace mbts.spark {
|
||||
foreach (var sd in _child_algo_selector_data) {
|
||||
var inner_def = sd.get_order(defi);
|
||||
if (inner_def.HasValue) {
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase));
|
||||
var vm = sd.selector.algos.GetOrAdd(OrderDataHelper.get_shallow_factory_key(inner_def.Value), k => create(k, inner_def.Value.display_name(), inner_def.Value.CustomCase, is_expanded: is_expanded));
|
||||
await vm.load_from_async(inner_def.Value);
|
||||
sd.selector.selected_algo = vm;
|
||||
}
|
||||
@@ -260,11 +246,6 @@ namespace mbts.spark {
|
||||
|
||||
protected virtual Task on_load_from_async(AlgoDefi defi) => Task.CompletedTask;
|
||||
|
||||
protected virtual void on_permissions_changed() {
|
||||
foreach (var vm in child_algo_selectors.SelectMany(s => s.algos.AsCollection()))
|
||||
vm.set_permissions(editing_permissions);
|
||||
}
|
||||
|
||||
protected void raise_definition_changed() =>
|
||||
_def_change_notifier.execute();
|
||||
|
||||
@@ -337,12 +318,11 @@ namespace mbts.spark {
|
||||
protected Property<TImmutableValue> add_property<TMutableValue, TImmutableValue>(string prop_name, int target_field_number, TImmutableValue init_value = default) where TMutableValue : class, IMessage, new() where TImmutableValue : IImmutableMessage {
|
||||
var prop = backing(prop_name, init_value);
|
||||
var field_desc = new TMutable().Descriptor.FindFieldByNumber(target_field_number);
|
||||
var immutable_factory = OrderTypeVMUtils.immutable_factory<TMutableValue, TImmutableValue>();
|
||||
add_property<TMutableValue>(
|
||||
prop_name,
|
||||
mdef => field_desc.Accessor.GetValue(mdef) as TMutableValue,
|
||||
(mdef, mval) => field_desc.Accessor.SetValue(mdef, mval),
|
||||
mval => prop.set(immutable_factory(mval ?? new TMutableValue())),
|
||||
mval => prop.set(ProtoHelper.immutable_factory<TMutableValue, TImmutableValue>(mval ?? new TMutableValue())),
|
||||
() => {
|
||||
// TODO: perhaps CloneToMutable() should be a member of IImmutableMessage.
|
||||
// this method makes things so simple for the caller...but this dynamic cast is a little sketch.
|
||||
@@ -381,6 +361,7 @@ namespace mbts.spark {
|
||||
|
||||
protected virtual void on_model_internal() { }
|
||||
protected virtual void on_security_md_internal() { }
|
||||
protected virtual void on_security_internal() { }
|
||||
|
||||
// model
|
||||
void IObserver<OrdersModel>.OnNext(OrdersModel model) {
|
||||
@@ -400,14 +381,40 @@ namespace mbts.spark {
|
||||
void IObserver<OrdersModel>.OnCompleted() { }
|
||||
// security md
|
||||
void IObserver<SecurityMD?>.OnNext(SecurityMD? security_md) {
|
||||
this.security_md = security_md;
|
||||
using (_def_change_notifier.defer_execution()) {
|
||||
on_security_md_internal();
|
||||
_populate_selectors();
|
||||
}
|
||||
using (_def_change_notifier.defer_execution())
|
||||
using (_update_sec_input())
|
||||
_security_input = _security_input.with_security_md(security_md);
|
||||
}
|
||||
void IObserver<SecurityMD?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityMD?>.OnCompleted() { }
|
||||
// security
|
||||
void IObserver<SecurityImmutable?>.OnNext(SecurityImmutable? security) {
|
||||
using (_def_change_notifier.defer_execution())
|
||||
using (_update_sec_input())
|
||||
_security_input = _security_input.with_security(security);
|
||||
}
|
||||
|
||||
void IObserver<SecurityImmutable?>.OnError(Exception error) { }
|
||||
void IObserver<SecurityImmutable?>.OnCompleted() { }
|
||||
// editing permissions
|
||||
void IObserver<AlgoEditingPermissions>.OnNext(AlgoEditingPermissions editing_permissions) => this.editing_permissions = editing_permissions;
|
||||
void IObserver<AlgoEditingPermissions>.OnError(Exception error) { }
|
||||
void IObserver<AlgoEditingPermissions>.OnCompleted() { }
|
||||
|
||||
IDisposable _update_sec_input() {
|
||||
var old_sec = security;
|
||||
var old_sec_md = security_md;
|
||||
return Disposable.Create(() => {
|
||||
if (security != old_sec) {
|
||||
on_security_internal();
|
||||
if (_editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditStructure))
|
||||
_populate_selectors();
|
||||
_security_changed?.Invoke(security);
|
||||
}
|
||||
if (security_md != old_sec_md)
|
||||
on_security_md_internal();
|
||||
});
|
||||
}
|
||||
|
||||
IDisposable _selector_data_hooks;
|
||||
void _populate_selectors() {
|
||||
@@ -426,6 +433,40 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
// security input may come in 2 forms:
|
||||
// 1) SecurityImmutable?
|
||||
// 2) SecurityMD?
|
||||
// This structure resolves conflicts that may arise from the different inputs
|
||||
struct SecurityInput {
|
||||
readonly SecurityMD? _raw_security_md;
|
||||
readonly SecurityImmutable? _raw_security;
|
||||
|
||||
public readonly SecurityMD? security_md;
|
||||
public readonly SecurityImmutable? security;
|
||||
|
||||
SecurityInput(SecurityImmutable? raw_security, SecurityMD? raw_security_md, SecurityImmutable? security, SecurityMD? security_md) {
|
||||
_raw_security = raw_security;
|
||||
_raw_security_md = raw_security_md;
|
||||
this.security_md = security_md;
|
||||
this.security = security;
|
||||
}
|
||||
|
||||
public SecurityInput with_security(SecurityImmutable? security) =>
|
||||
new SecurityInput(
|
||||
security,
|
||||
_raw_security_md,
|
||||
security,
|
||||
security.HasValue && _raw_security_md.HasValue && _raw_security_md.Value.security.Id != security.Value.Id ?
|
||||
null : _raw_security_md);
|
||||
|
||||
public SecurityInput with_security_md(SecurityMD? security_md) =>
|
||||
new SecurityInput(
|
||||
_raw_security,
|
||||
security_md,
|
||||
security_md?.security ?? _raw_security,
|
||||
security_md);
|
||||
}
|
||||
|
||||
struct AlgoSelectorData {
|
||||
public readonly string prop_name;
|
||||
public readonly AlgoSelector selector;
|
||||
@@ -445,14 +486,15 @@ namespace mbts.spark {
|
||||
IReadOnlyDictionary<AlgoKey, AlgoData> _pending_data;
|
||||
AlgoKey? _pending_default;
|
||||
|
||||
public AlgoSelector() =>
|
||||
public AlgoSelector() {
|
||||
algos_view = ObservableAutoWrapper.create_sorted_wrapper(algos, AlgoCollectionHelper.algo_vm_comparer);
|
||||
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
|
||||
}
|
||||
|
||||
public void load_algo_data(IReadOnlyDictionary<AlgoKey, AlgoData> algo_data, AlgoKey? default_algo) {
|
||||
_pending_data = algo_data;
|
||||
_pending_default = default_algo;
|
||||
_sync_formats();
|
||||
selected_algo_changed += (o, n) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(selected_algo)));
|
||||
}
|
||||
|
||||
public event PropertyChange<IAlgoVM> selected_algo_changed;
|
||||
@@ -513,4 +555,4 @@ namespace mbts.spark {
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.utility;
|
||||
@@ -11,7 +12,7 @@ using SpreaderDefi = bts.api.orderman.SpreaderOrderDefinitionImmutable;
|
||||
|
||||
namespace mbts.spark {
|
||||
static partial class OrderTypeVMFactory {
|
||||
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<SecurityMD?>, IObserver<OrdersModel> {
|
||||
class SpreaderOrderVM : OrderTypeVMBase<SpreaderDef, SpreaderDefi>, IAlgoVM, ILazyAlgo, IObserver<OrdersModel> {
|
||||
readonly Property<SpreaderDefi?> _template;
|
||||
readonly StrategyEditorWrapper _strategy_editor_wrapper;
|
||||
IDisposable _strategy_sub;
|
||||
@@ -78,16 +79,15 @@ namespace mbts.spark {
|
||||
set_error(nameof(template), "Waiting for security and/or strategy.", true);
|
||||
else clear_error(nameof(template));
|
||||
}
|
||||
|
||||
protected override void on_security_md_internal() => _reset_strategy_sub();
|
||||
protected override void on_model_internal() => _reset_strategy_sub();
|
||||
protected override void on_security_internal() => _reset_strategy_sub();
|
||||
|
||||
void _reset_strategy_sub() {
|
||||
Disposable.dispose(ref _strategy_sub);
|
||||
if (model != null) {
|
||||
if (security_md.HasValue) {
|
||||
_strategy_sub = model.spreader.security_strategy(security_md.Value.security.Id)
|
||||
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security_md.Value.security, strategy)))
|
||||
if (security.HasValue) {
|
||||
_strategy_sub = model.spreader.security_strategy(security.Value.Id)
|
||||
.Select(strategy => new SpreaderDefi?(model.factory.spreader_orderi(security.Value, strategy)))
|
||||
.OnDispatcher()
|
||||
.Subscribe(s => {
|
||||
is_loaded = true;
|
||||
|
||||
@@ -130,4 +130,4 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,7 +86,7 @@ namespace mbts.spark {
|
||||
(os, okey, am, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, am.algo, am.meta),
|
||||
(os, okey, mkey) => new OrderSecurityAlgoMeta(os.order, os.sec, null, null),
|
||||
ot => ot.order.Key);
|
||||
on_order_tuples = order_secs_algo_meta_join;
|
||||
on_order_tuples = order_secs_algo_meta_join.Cache();
|
||||
var on_fills =
|
||||
_service.subscribei_fills()
|
||||
.Naggle(interval, scheduler)
|
||||
@@ -101,17 +101,14 @@ namespace mbts.spark {
|
||||
(f, fk, o, ok) => new FillTuple(f, o),
|
||||
(f, fk, ok) => new FillTuple(f, null),
|
||||
f => f.fill.FillKey);
|
||||
on_fill_tuples = fill_order_tuples_join;
|
||||
on_fill_tuples = fill_order_tuples_join.Cache();
|
||||
_dispose = Disposable.Create(
|
||||
on_orders.Select(t => t.order, o => o.Key).Subscribe(_security_orders_demuxer),
|
||||
on_positions.Subscribe(_security_positions_demuxer),
|
||||
order_secs_algo_meta_join.Connect(),
|
||||
algo_meta_join.Connect(),
|
||||
fill_order_tuples_join.Connect(),
|
||||
_bus.subscribe(SparkTags.CreateOrder).Subscribe(a => send_order(a.definition, a.price, a.size).ignore()),
|
||||
_bus.subscribe(SparkTags.RecreateOrder).Subscribe(a => a.ForEach(x => send_order(x.definition, x.price, x.size).ignore())),
|
||||
_bus.subscribe(SparkTags.CancelOrders).Subscribe(x => cancel_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.CrackOrders).Subscribe(x => crack_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.DetachOrders).Subscribe(x => detach_orders(x).ignore()),
|
||||
_bus.subscribe(SparkTags.UpdateOrder).Subscribe(x => this.update_order(x.key, x.price, x.size).ignore())
|
||||
);
|
||||
}
|
||||
@@ -166,27 +163,37 @@ namespace mbts.spark {
|
||||
return success;
|
||||
}
|
||||
|
||||
public async Task<bool> update_order(OrderRecordImmutable order, double price, uint size) {
|
||||
if (order.working_quantity() <= 0) return false;
|
||||
_activity.update_order(order, price, size);
|
||||
public async Task<bool> update_order(OrderRecordImmutable order, double? new_price = null, uint? new_size = null, CustomOrderDefinitionImmutable? new_algo = null) {
|
||||
if (order.working_quantity() <= 0) {
|
||||
// order already filled to completion
|
||||
return false;
|
||||
}
|
||||
var size = new_size ?? (uint)order.working_quantity();
|
||||
var price = new_price ?? order.price();
|
||||
if (size == order.working_quantity() && price == order.price() && !new_algo.HasValue) {
|
||||
// no parameters would be changed by this update
|
||||
return false;
|
||||
}
|
||||
_activity.update_order(order, price, size, new_algo.HasValue);
|
||||
var result = await _service.update_order_async(new Update() {
|
||||
OrderKey = order.Key,
|
||||
Price = price,
|
||||
Quantity = size
|
||||
Quantity = size,
|
||||
Custom = new_algo?.CloneToMutable()
|
||||
});
|
||||
(var key, string msg) = _handle_error(result, "Failed to update order: {0}");
|
||||
bool success = key.HasValue;
|
||||
if (!success) {
|
||||
_activity.update_order_failed(order, price, size);
|
||||
_activity.update_order_failed(order, price, size, new_algo.HasValue);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
public Task<bool[]> crack_orders(IEnumerable<uint> orders) =>
|
||||
Task.WhenAll(orders.Select(crack_order));
|
||||
public Task<bool[]> detach_orders(IEnumerable<uint> orders) =>
|
||||
Task.WhenAll(orders.Select(detach_order));
|
||||
|
||||
public async Task<bool> crack_order(uint order) {
|
||||
var result = await _service.crack_order_async(new Crack() { OrderKey = order });
|
||||
public async Task<bool> detach_order(uint order) {
|
||||
var result = await _service.detach_order_async(new Detach() { OrderKey = order });
|
||||
return _handle_error(result, "Failed to break order: {0}").key.HasValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace mbts {
|
||||
SecurityPredicate _security;
|
||||
SidePredicate _side;
|
||||
DoublePredicate _price, _last_price, _avg_price;
|
||||
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key;
|
||||
UIntPredicate _quantity, _filled_quantity, _security_key, _order_key, _parent_key, _attached_target_order_key;
|
||||
OrderStatusPredicate _order_status;
|
||||
OrderTypePredicate _order_type;
|
||||
OrderSubTypePredicate _order_sub_type;
|
||||
@@ -125,7 +125,9 @@ namespace mbts {
|
||||
[PredicateCase("When order is completed", PredicateCategory.Event)]
|
||||
CompletedEvent,
|
||||
[PredicateCase("When order is canceled", PredicateCategory.Event)]
|
||||
CanceledEvent
|
||||
CanceledEvent,
|
||||
[PredicateCase(is_toastable:false)]
|
||||
AttachedTargetOrderKey
|
||||
}
|
||||
|
||||
[ProtoMember(1)]
|
||||
@@ -176,6 +178,8 @@ namespace mbts {
|
||||
public OrderCanceledEventPredicate canceled_event { get => _canceled_event; set => _set(ref _canceled_event, value); }
|
||||
[ProtoMember(24)]
|
||||
public bool is_toast_enabled { get; set; }
|
||||
[ProtoMember(25)]
|
||||
public UIntPredicate attached_order_target_key { get => _attached_target_order_key; set => _set(ref _attached_target_order_key, value); }
|
||||
|
||||
public PTOC predicate_type_case {
|
||||
get {
|
||||
@@ -202,6 +206,7 @@ namespace mbts {
|
||||
if (_filled_event != null) return PTOC.FilledEvent;
|
||||
if (_completed_event != null) return PTOC.CompletedEvent;
|
||||
if (_canceled_event != null) return PTOC.CanceledEvent;
|
||||
if (_attached_target_order_key != null) return PTOC.AttachedTargetOrderKey;
|
||||
return PTOC.None;
|
||||
}
|
||||
}
|
||||
@@ -231,6 +236,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.create_predicate(data_cache);
|
||||
case PTOC.CompletedEvent: return _completed_event.create_predicate(data_cache);
|
||||
case PTOC.CanceledEvent: return _canceled_event.create(data_cache);
|
||||
case PTOC.AttachedTargetOrderKey: return wrap(_attached_target_order_key, r => r.order.attached_target_order_key());
|
||||
default: return null;
|
||||
}
|
||||
//
|
||||
@@ -279,6 +285,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: return _filled_event.Equals(other._filled_event);
|
||||
case PTOC.CompletedEvent: return _completed_event.Equals(other._completed_event);
|
||||
case PTOC.CanceledEvent: return _canceled_event.Equals(other._canceled_event);
|
||||
case PTOC.AttachedTargetOrderKey: return _attached_target_order_key.Equals(other._attached_target_order_key);
|
||||
case PTOC.None: return true;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
}
|
||||
@@ -313,6 +320,7 @@ namespace mbts {
|
||||
case PTOC.FilledEvent: raw_hash = _filled_event.GetHashCode(); break;
|
||||
case PTOC.CompletedEvent: raw_hash = _completed_event.GetHashCode(); break;
|
||||
case PTOC.CanceledEvent: raw_hash = _canceled_event.GetHashCode(); break;
|
||||
case PTOC.AttachedTargetOrderKey: raw_hash = _attached_target_order_key.GetHashCode(); break;
|
||||
case PTOC.None: raw_hash = 0; break;
|
||||
default: throw ExceptionHelper.unexpected_oneof_case(ptoc);
|
||||
}
|
||||
@@ -347,6 +355,7 @@ namespace mbts {
|
||||
_filled_event = null;
|
||||
_completed_event = null;
|
||||
_canceled_event = null;
|
||||
_attached_target_order_key = null;
|
||||
}
|
||||
field = value;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:control="clr-namespace:mbts.ui.controls">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:control="clr-namespace:mbts.ui.controls"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/Images/Vector/Icons.xaml" />
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml" />
|
||||
@@ -516,9 +519,16 @@
|
||||
<Setter.Value>
|
||||
<DataTemplate>
|
||||
<control:SparkMenuItem
|
||||
Content="{Binding title}" Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||
Command="{Binding command}" Style="{StaticResource SparkMenuButton}" Icon="{Binding icon}"
|
||||
Collapsed="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type control:SparkMenu}}, Path=ShowText, Converter={StaticResource NotConverter}}"
|
||||
/>
|
||||
>
|
||||
<control:SparkMenuItem.Content>
|
||||
<MultiBinding Converter="{x:Static bv:SparkWindowView.HeaderConverter}">
|
||||
<Binding Path="title"/>
|
||||
<Binding Path="key_gesture"/>
|
||||
</MultiBinding>
|
||||
</control:SparkMenuItem.Content>
|
||||
</control:SparkMenuItem>
|
||||
</DataTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using mbts;
|
||||
using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -11,13 +12,15 @@ using static mbts.spark.SparkTags;
|
||||
|
||||
namespace bts.spark {
|
||||
public abstract class SparkCommand {
|
||||
public readonly KeyGesture key_gesture;
|
||||
string _title;
|
||||
bool _show_in_toolbar, _show_in_context_menu;
|
||||
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true) {
|
||||
public SparkCommand(string title, ImageSource icon, bool show_in_toolbar = true, bool show_in_context_menu = true, KeyGesture key_gesture = null) {
|
||||
_title = title;
|
||||
this.icon = icon;
|
||||
_show_in_toolbar = show_in_toolbar;
|
||||
_show_in_context_menu = show_in_context_menu;
|
||||
this.key_gesture = key_gesture;
|
||||
}
|
||||
|
||||
public event Action title_changed, show_in_toolbar_changed, show_in_context_menu_changed;
|
||||
@@ -207,7 +210,8 @@ namespace bts.spark {
|
||||
public class SparkCommandCommand : SparkCommand {
|
||||
readonly ICommand _a;
|
||||
|
||||
public SparkCommandCommand(string title, ImageSource icon, ICommand a) : base(title, icon) {
|
||||
public SparkCommandCommand(string title, ImageSource icon, ICommand a, KeyGesture key_gesture = null)
|
||||
: base(title, icon, key_gesture: key_gesture) {
|
||||
_a = a;
|
||||
}
|
||||
|
||||
@@ -325,6 +329,7 @@ namespace bts.spark {
|
||||
}
|
||||
|
||||
public string title => _cmd.title;
|
||||
public KeyGesture key_gesture => _cmd.key_gesture;
|
||||
public ImageSource icon => _cmd.icon;
|
||||
public bool show_in_toolbar => _cmd.show_in_toolbar;
|
||||
public bool show_in_context_menu => _cmd.show_in_context_menu;
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace mbts.spark
|
||||
public static ITag<IEnumerable<(OrderDefinitionImmutable definition, double price, uint size)>> RecreateOrder { get; } = new SparkTag<IEnumerable<(OrderDefinitionImmutable, double, uint)>>();
|
||||
|
||||
public static ITag<IEnumerable<uint>> CancelOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> CrackOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<IEnumerable<uint>> DetachOrders { get; } = new SparkTag<IEnumerable<uint>>();
|
||||
public static ITag<(uint key, double price, uint size)> UpdateOrder { get; } = new SparkTag<(uint, double, uint)>();
|
||||
|
||||
public static ITag<uint> ShowModifyOrderTag { get; } = new SparkTag<uint>(nameof(ShowModifyOrderTag));
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbts.ui.controls {
|
||||
var rv = new ObservableAggregateCollection<TInner>();
|
||||
var aggregator = new Aggregator<TInner>(rv);
|
||||
foreach (var o in source)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
source.CollectionChanged += _mk_outter_collection_changed_handler(aggregator, select_inner);
|
||||
return rv;
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace mbts.ui.controls {
|
||||
aggregate.Clear();
|
||||
var aggregator = new Aggregator<TInner>(aggregate);
|
||||
foreach (var o in source)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
var handler = _mk_outter_collection_changed_handler(aggregator, select_inner);
|
||||
source.CollectionChanged += handler;
|
||||
return Disposable.Create(() => source.CollectionChanged -= handler);
|
||||
@@ -37,23 +37,23 @@ namespace mbts.ui.controls {
|
||||
add_collections(e.NewStartingIndex, e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
aggregator.clear_collections();
|
||||
aggregator.Clear();
|
||||
foreach (TOutter o in (IEnumerable<TOutter>)s)
|
||||
aggregator.add_collection(select_inner(o));
|
||||
aggregator.Add(select_inner(o));
|
||||
break;
|
||||
}
|
||||
};
|
||||
void add_collections(int new_item_index, IList new_items) {
|
||||
foreach (TOutter o in new_items)
|
||||
aggregator.insert_collection(new_item_index++, select_inner(o));
|
||||
aggregator.Insert(new_item_index++, select_inner(o));
|
||||
}
|
||||
void remove_collections(int old_item_index, IList old_items) {
|
||||
foreach (TOutter o in old_items)
|
||||
aggregator.remove_collection_at(old_item_index);
|
||||
aggregator.RemoveAt(old_item_index);
|
||||
}
|
||||
}
|
||||
|
||||
internal class Aggregator<T> : IDisposable {
|
||||
internal class Aggregator<T> : IDisposable, IList<ObservableCollection<T>>, IReadOnlyList<ObservableCollection<T>> {
|
||||
readonly ObservableCollection<T> _target;
|
||||
readonly List<ObservableCollection<T>> _collections = new List<ObservableCollection<T>>();
|
||||
bool _is_disposed;
|
||||
@@ -61,28 +61,39 @@ namespace mbts.ui.controls {
|
||||
public Aggregator(ObservableCollection<T> target, params ObservableCollection<T>[] collections) {
|
||||
_target = target;
|
||||
foreach (var c in collections)
|
||||
add_collection(c);
|
||||
Add(c);
|
||||
}
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) {
|
||||
public ObservableCollection<T> this[int index] {
|
||||
get => _collections[index];
|
||||
set => Insert(index, value);
|
||||
}
|
||||
|
||||
public int Count => _collections.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Add(ObservableCollection<T> collection) {
|
||||
_check_disposed();
|
||||
_target.AddRange(collection);
|
||||
_collections.Add(collection);
|
||||
collection.CollectionChanged += _on_collection_changed;
|
||||
}
|
||||
|
||||
public void remove_collection(ObservableCollection<T> collection) {
|
||||
public bool Remove(ObservableCollection<T> collection) {
|
||||
int num_before = _collections.Count;
|
||||
_check_disposed();
|
||||
_remove_collection_items(collection, out int _, out int collection_index);
|
||||
_collections.RemoveAt(collection_index);
|
||||
collection.CollectionChanged -= _on_collection_changed;
|
||||
return _collections.Count < num_before;
|
||||
}
|
||||
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) {
|
||||
public void Insert(int index, ObservableCollection<T> collection) {
|
||||
_check_disposed();
|
||||
if (index < 0 || index > _collections.Count) throw new ArgumentOutOfRangeException(nameof(index));
|
||||
if (index == _collections.Count)
|
||||
add_collection(collection);
|
||||
Add(collection);
|
||||
else {
|
||||
_collections.Insert(index, collection);
|
||||
_on_collection_changed(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, collection, 0));
|
||||
@@ -90,12 +101,12 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_collection_at(int index) {
|
||||
public void RemoveAt(int index) {
|
||||
_check_disposed();
|
||||
remove_collection(_collections[index]);
|
||||
Remove(_collections[index]);
|
||||
}
|
||||
|
||||
public void clear_collections() {
|
||||
public void Clear() {
|
||||
_check_disposed();
|
||||
foreach (var c in _collections)
|
||||
c.CollectionChanged -= _on_collection_changed;
|
||||
@@ -103,6 +114,15 @@ namespace mbts.ui.controls {
|
||||
_target.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(ObservableCollection<T> item) =>
|
||||
_collections.Contains(item);
|
||||
|
||||
public void CopyTo(ObservableCollection<T>[] array, int arrayIndex) =>
|
||||
_collections.CopyTo(array, arrayIndex);
|
||||
|
||||
public int IndexOf(ObservableCollection<T> item) =>
|
||||
_collections.IndexOf(item);
|
||||
|
||||
public void Dispose() {
|
||||
if (!_is_disposed) {
|
||||
foreach (var c in _collections)
|
||||
@@ -111,6 +131,9 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<ObservableCollection<T>> GetEnumerator() => _collections.GetEnumerator();
|
||||
IEnumerator IEnumerable.GetEnumerator() => _collections.GetEnumerator();
|
||||
|
||||
void _on_collection_changed(object sender, NotifyCollectionChangedEventArgs e) {
|
||||
_check_disposed();
|
||||
int offset = _get_collection_start(sender);
|
||||
@@ -166,10 +189,12 @@ namespace mbts.ui.controls {
|
||||
public ObservableAggregateCollection(params ObservableCollection<T>[] collections) =>
|
||||
_aggregator = new ObservableAggregateCollection.Aggregator<T>(this, collections);
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) => _aggregator.add_collection(collection);
|
||||
public void remove_collection(ObservableCollection<T> collection) => _aggregator.remove_collection(collection);
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.insert_collection(index, collection);
|
||||
public void remove_collection_at(int index) => _aggregator.remove_collection_at(index);
|
||||
public void clear_collections() => _aggregator.clear_collections();
|
||||
public IReadOnlyList<ObservableCollection<T>> source_collections => _aggregator;
|
||||
|
||||
public void add_collection(ObservableCollection<T> collection) => _aggregator.Add(collection);
|
||||
public void remove_collection(ObservableCollection<T> collection) => _aggregator.Remove(collection);
|
||||
public void insert_collection(int index, ObservableCollection<T> collection) => _aggregator.Insert(index, collection);
|
||||
public void remove_collection_at(int index) => _aggregator.RemoveAt(index);
|
||||
public void clear_collections() => _aggregator.Clear();
|
||||
}
|
||||
}
|
||||
@@ -192,16 +192,16 @@ namespace mbts.ui.utility {
|
||||
}
|
||||
}
|
||||
|
||||
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) {
|
||||
foreach (T v in source)
|
||||
action(v);
|
||||
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
|
||||
}
|
||||
public static IDisposable connect_item_actions<T>(IReadOnlyObservableCollection<T> source, Action<T> action) =>
|
||||
connect_item_actions(source, source, action);
|
||||
|
||||
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) {
|
||||
foreach (T v in source)
|
||||
public static IDisposable connect_item_actions<T>(ObservableCollection<T> source, Action<T> action) =>
|
||||
connect_item_actions(source, source, action);
|
||||
|
||||
public static IDisposable connect_item_actions<T>(INotifyCollectionChanged notifier, IEnumerable<T> collection, Action<T> action) {
|
||||
foreach (T v in collection)
|
||||
action(v);
|
||||
return source.Subscribe((s, e) => CollectionHelper.process_new_items(source, action, e));
|
||||
return notifier.Subscribe((s, e) => CollectionHelper.process_new_items(collection, action, e));
|
||||
}
|
||||
|
||||
public static IDisposable connect_item_hooks<T>(IReadOnlyObservableCollection<T> source, Func<T, IDisposable> get_item_hook) {
|
||||
@@ -218,6 +218,18 @@ namespace mbts.ui.utility {
|
||||
source.Subscribe((s, e) => CollectionHelper.reflect_change(item_hooks, source, get_item_hook, d => d.Dispose(), e)));
|
||||
}
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) where T : IDisposable =>
|
||||
connect_item_hooks(source, v => v);
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose) where T : IDisposable =>
|
||||
connect_item_hooks(source, v => v, clear_source_on_dispose);
|
||||
|
||||
public static IDisposable try_dispose_on_removed<T>(IReadOnlyObservableCollection<T> source) =>
|
||||
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty);
|
||||
|
||||
public static IDisposable try_dispose_on_removed<T>(ObservableCollection<T> source, bool clear_source_on_dispose = false) =>
|
||||
connect_item_hooks(source, v => v as IDisposable ?? Disposable.empty, clear_source_on_dispose);
|
||||
|
||||
class SortedWrapper<T> : IReadOnlyObservableCollection<T> {
|
||||
const int _min_capacity = 4;
|
||||
readonly IComparer<T> _comparer;
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mbts.ui.utilities
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
readonly TSourceCollection _source;
|
||||
readonly IList<T> _target;
|
||||
IDictionary<T, int> _cache = new Dictionary<T, int>();
|
||||
IDictionary<T, int> _cache;
|
||||
IDisposable _sub;
|
||||
|
||||
public DistinctCollectionResolver(TSourceCollection source, IList<T> target, Func<TSource, T> get_value)
|
||||
@@ -38,6 +38,7 @@ namespace mbts.ui.utilities
|
||||
_target = target;
|
||||
_get_value = get_value;
|
||||
_comparer = comparer;
|
||||
_cache = new Dictionary<T, int>(comparer);
|
||||
_sync();
|
||||
_sub = _source.Subscribe(_source_CollectionChanged);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,11 @@ namespace mbts.ui.utility {
|
||||
foreach (var value in source_collection.OfType<INotifyPropertyChanged>())
|
||||
value.PropertyChanged += on_item_PropertyChanged;
|
||||
}
|
||||
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
|
||||
if (source_collection.Count == 0) {
|
||||
filtered_collection.Clear();
|
||||
} else {
|
||||
CollectionHelper.sync_with_never_duplicate(filtered_collection, source_collection.Where(filter));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,4 +217,4 @@ namespace mbts.ui.utility {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,5 +24,8 @@ namespace mbts {
|
||||
return (uint)value;
|
||||
}
|
||||
}
|
||||
|
||||
public static double? nan_nullable(double value) =>
|
||||
double.IsNaN(value) ? (double?)null : value;
|
||||
}
|
||||
}
|
||||
@@ -54,6 +54,17 @@ namespace System.Collections.Generic {
|
||||
// yield return item;
|
||||
//}
|
||||
|
||||
public static T SafeSingleOrDefault<T>(this IEnumerable<T> source) {
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
var rv_candidate = en.Current;
|
||||
if (!en.MoveNext())
|
||||
return rv_candidate;
|
||||
}
|
||||
}
|
||||
return default;
|
||||
}
|
||||
|
||||
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
|
||||
TAccumulate a = seed;
|
||||
foreach (T el in source) {
|
||||
@@ -86,6 +97,28 @@ namespace System.Collections.Generic {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T>(this IEnumerable<T> source, out T result) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
using (var en = source.GetEnumerator()) {
|
||||
if (en.MoveNext()) {
|
||||
result = en.Current;
|
||||
if (!en.MoveNext())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSingle<T, TResult>(this IEnumerable<T> source, Func<T, bool> predicate, Func<T, TResult> convert, out TResult result) {
|
||||
if (TryGetSingle(source.Where(predicate), out T v)) {
|
||||
result = convert(v);
|
||||
return true;
|
||||
}
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static T FirstOrDefault<T>(this IEnumerable<T> source, Func<T, bool> predicate, T default_rv) {
|
||||
if (source == null) throw new ArgumentNullException(nameof(source));
|
||||
foreach (T element in source)
|
||||
|
||||
@@ -1,25 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Threading;
|
||||
using System.Reactive.Linq;
|
||||
using System.Collections;
|
||||
using mbts.utils;
|
||||
using System.Threading.Tasks;
|
||||
using bts;
|
||||
using mbts;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using System.Collections.Specialized;
|
||||
using mbts.spark;
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.utils;
|
||||
|
||||
namespace mbts
|
||||
{
|
||||
namespace mbts {
|
||||
public static class GlobalExtensions
|
||||
{
|
||||
#region AddRange
|
||||
@@ -105,8 +98,8 @@ namespace mbts
|
||||
return items.GroupBy(key_selector).ToDictionary(g => g.Key, g => g.AsEnumerable());
|
||||
}
|
||||
|
||||
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_equality_comparer) {
|
||||
return items.GroupBy(key_selector, key_equality_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate));
|
||||
public static IDictionary<K, V> MapAggregate<K, V, T>(this IEnumerable<T> items, Func<T, K> key_selector, Func<T, V> value_selector, V seed, Func<V, V, V> aggregate, IEqualityComparer<K> key_comparer) {
|
||||
return items.GroupBy(key_selector, key_comparer).ToDictionary(g => g.Key, g => g.Select(value_selector).Aggregate(seed, aggregate), key_comparer);
|
||||
}
|
||||
|
||||
public static int aggregate_hash<T>(this IEnumerable<T> series) where T : class {
|
||||
@@ -238,8 +231,7 @@ namespace mbts
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Collections.Specialized
|
||||
{
|
||||
namespace System.Collections.Specialized {
|
||||
public static class NotifyCollectionChangedExtensions
|
||||
{
|
||||
public static IDisposable SuspendHandler(this INotifyCollectionChanged source, NotifyCollectionChangedEventHandler handler) {
|
||||
@@ -258,8 +250,7 @@ namespace System.Collections.Specialized
|
||||
}
|
||||
}
|
||||
|
||||
namespace System.Reactive.Linq
|
||||
{
|
||||
namespace System.Reactive.Linq {
|
||||
public static class ObservableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
@@ -397,4 +388,4 @@ namespace System.Reactive.Linq
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.ui.shell;
|
||||
using bts.utils;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
@@ -98,8 +99,12 @@ namespace mbts.ui {
|
||||
}
|
||||
}
|
||||
|
||||
public static string diagnostic_name(this IShellWindow window) =>
|
||||
$"[{window.window_type}]{window.Title}";
|
||||
public static string diagnostic_name(this IShellWindow window) {
|
||||
var sb = new StringBuilderEx { "[", window.window_type, "]", window.Title };
|
||||
if (window is ISharedWindow shared_win)
|
||||
sb.Add("(thread: ", shared_win.thread_id, ")");
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static IObservable<string> observe_title(this IShellWindow window) {
|
||||
var src = Observable.FromEvent<Action, string>(cb => () => cb(window.Title), cb => window.TitleChanged += cb, cb => window.TitleChanged -= cb);
|
||||
|
||||
@@ -90,20 +90,25 @@ namespace mbts.utils {
|
||||
}
|
||||
|
||||
// Note: this is not optimized, be wary of use with large collections
|
||||
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src) =>
|
||||
public static void sync_with_never_duplicate<T>(IList<T> dest, IEnumerable<T> src) =>
|
||||
sync_with_never_duplicate(dest, src, EqualityComparer<T>.Default);
|
||||
|
||||
// Note: this is not optimized, be wary of use with large collections
|
||||
public static void sync_with_never_duplicate<T>(ObservableCollection<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
int i = 0;
|
||||
foreach (var val in src.empty_if_null()) {
|
||||
int curr_index = dest.IndexOf(val);
|
||||
public static void sync_with_never_duplicate<T>(IList<T> dest, IEnumerable<T> src, IEqualityComparer<T> comp) {
|
||||
var src_list = src as IList<T> ?? new List<T>(src);
|
||||
var src_set = src as ISet<T> ?? new HashSet<T>(src_list);
|
||||
for (int i = 0; i < dest.Count; i++) {
|
||||
if (!src_set.Contains(dest[i]))
|
||||
dest.RemoveAt(i);
|
||||
}
|
||||
for (int i = 0; i < src_list.Count; i++) {
|
||||
int curr_index = dest.IndexOf(src_list[i]);
|
||||
if (curr_index == i) {
|
||||
continue;
|
||||
} else if (curr_index > 0 && curr_index < dest.Count) {
|
||||
} else if (curr_index >= 0 && curr_index < dest.Count) {
|
||||
dest.RemoveAt(curr_index);
|
||||
}
|
||||
dest.Insert(i++, val);
|
||||
dest.Insert(i, src_list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,11 +117,16 @@ namespace mbts.utils {
|
||||
|
||||
// Note: this is not optimized, be wary of use with large collections
|
||||
public static void sync_with_never_duplicate<T, TSource, TKey>(ObservableCollection<T> dest, IEnumerable<TSource> src, Func<TSource, T> create, Func<T, TKey> key_selector, Func<TSource, TKey> src_key_selector, IEqualityComparer<TKey> key_comparer) {
|
||||
int i = 0;
|
||||
var src_map = src.ToDictionary(src_key_selector);
|
||||
var src_list = src as IList<TSource> ?? new List<TSource>(src);
|
||||
var src_map = src_list.ToDictionary(src_key_selector);
|
||||
for (int i = 0; i < dest.Count; i++) {
|
||||
var k = key_selector(dest[i]);
|
||||
if (!src_map.ContainsKey(k))
|
||||
dest.RemoveAt(i);
|
||||
}
|
||||
var dest_map = dest.ToDictionary(key_selector);
|
||||
foreach (var val in src.empty_if_null()) {
|
||||
TKey key = src_key_selector(val);
|
||||
for (int i = 0; i < src_list.Count; i++) {
|
||||
TKey key = src_key_selector(src_list[i]);
|
||||
if (dest_map.TryGetValue(key, out T dval)) {
|
||||
int curr_index = dest.IndexOf(dval);
|
||||
if (curr_index != i) {
|
||||
@@ -124,9 +134,8 @@ namespace mbts.utils {
|
||||
dest.Insert(i, dval);
|
||||
}
|
||||
} else {
|
||||
dest.Insert(i, create(val));
|
||||
dest.Insert(i, create(src_list[i]));
|
||||
}
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,13 +145,5 @@ namespace mbts.utils {
|
||||
rv[i] = selector(source[i]);
|
||||
return rv;
|
||||
}
|
||||
|
||||
public static IDisposable dispose_on_removed<T>(ObservableCollection<T> collection) =>
|
||||
collection.Subscribe((s, e) => {
|
||||
if (e.OldItems != null) {
|
||||
foreach (T item in e.OldItems)
|
||||
(item as IDisposable)?.Dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using mbts.collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.utils {
|
||||
class CommonValueMonitor<T> {
|
||||
readonly RefCountMap<T> _values;
|
||||
T _default_value;
|
||||
|
||||
public CommonValueMonitor()
|
||||
: this(default, EqualityComparer<T>.Default) { }
|
||||
|
||||
public CommonValueMonitor(T default_value)
|
||||
: this(default_value, EqualityComparer<T>.Default) { }
|
||||
|
||||
public CommonValueMonitor(T default_value, IEqualityComparer<T> comparer) {
|
||||
_default_value = default_value;
|
||||
_values = new RefCountMap<T>(comparer);
|
||||
}
|
||||
|
||||
public event Action<T> common_value_changed;
|
||||
|
||||
public ICollection<T> distinct_values => _values.keys;
|
||||
public T common_value => _values.keys.SafeSingleOrDefault();
|
||||
|
||||
public void add(T value) {
|
||||
if (_values.increment_adds_key(value)) {
|
||||
switch (_values.Count) {
|
||||
case 1: common_value_changed?.Invoke(value); break;
|
||||
case 2: common_value_changed?.Invoke(_default_value); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void remove(T value) {
|
||||
if (_values.decrement_removes_key(value)) {
|
||||
switch (_values.Count) {
|
||||
case 0: common_value_changed?.Invoke(_default_value); break;
|
||||
case 1: common_value_changed?.Invoke(_values.keys.First()); break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CommonValueMonitorMultiplexor<TSource> {
|
||||
int _member_id_counter;
|
||||
readonly Dictionary<int, (Action<TSource> add, Action<TSource> remove)> _members = new Dictionary<int, (Action<TSource> add, Action<TSource> remove)>();
|
||||
|
||||
public void add(TSource value) {
|
||||
foreach ((var add, var remove) in _members.Values)
|
||||
add(value);
|
||||
}
|
||||
|
||||
public void remove(TSource value) {
|
||||
foreach ((var add, var remove) in _members.Values)
|
||||
remove(value);
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value) {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => member_mon.add(get_value(v)),
|
||||
v => member_mon.remove(get_value(v))));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
|
||||
public IDisposable add_member_mon<T>(CommonValueMonitor<T> member_mon, Func<TSource, T> get_value, Func<T, bool> where) {
|
||||
int id = _member_id_counter++;
|
||||
_members.Add(id, (
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
if (where(res))
|
||||
member_mon.add(res);
|
||||
},
|
||||
v => {
|
||||
var res = get_value(v);
|
||||
if (where(res))
|
||||
member_mon.remove(res);
|
||||
}));
|
||||
return Disposable.Create(() => _members.Remove(id));
|
||||
}
|
||||
}
|
||||
|
||||
static class CommonValueMonitorExtensions {
|
||||
public static bool try_get_common_value<T>(this CommonValueMonitor<T> monitor, out T result) =>
|
||||
monitor.distinct_values.TryGetSingle(out result);
|
||||
|
||||
public static T? common_value_nullable<T>(this CommonValueMonitor<T> monitor) where T : struct =>
|
||||
monitor.try_get_common_value(out T result) ? result : (T?)null;
|
||||
|
||||
public static IObservable<T> SubscribeThrottled<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) =>
|
||||
Observable.Create<T>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
o.OnNext(monitor.common_value);
|
||||
monitor.common_value_changed += on_changed;
|
||||
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
|
||||
async void on_changed(T _) {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
do {
|
||||
o.OnNext(monitor.common_value);
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
});
|
||||
|
||||
public static IObservable<T> SubscribeThrottled<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle, T default_value) =>
|
||||
Observable.Create<T>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
notify();
|
||||
monitor.common_value_changed += on_changed;
|
||||
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
|
||||
async void on_changed(T _) {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
do {
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
void notify() =>
|
||||
o.OnNext(monitor.try_get_common_value(out T result) ? result : default_value);
|
||||
});
|
||||
|
||||
public static IObservable<TResult> SubscribeThrottled<T, TResult>(this CommonValueMonitor<T> monitor, TimeSpan throttle, Func<T, TResult> selector, TResult default_value) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
bool busy = false, dirty = false;
|
||||
notify();
|
||||
monitor.common_value_changed += on_changed;
|
||||
return Disposable.Create(() => monitor.common_value_changed -= on_changed);
|
||||
async void on_changed(T _) {
|
||||
if (busy) {
|
||||
dirty = true;
|
||||
return;
|
||||
}
|
||||
dirty = false;
|
||||
do {
|
||||
notify();
|
||||
await Task.Delay(throttle);
|
||||
} while (dirty);
|
||||
busy = false;
|
||||
}
|
||||
void notify() =>
|
||||
o.OnNext(monitor.try_get_common_value(out T result) ? selector(result) : default_value);
|
||||
});
|
||||
|
||||
public static IObservable<T?> SubscribeThrottledNullable<T>(this CommonValueMonitor<T> monitor, TimeSpan throttle) where T : struct =>
|
||||
monitor.SubscribeThrottled<T, T?>(throttle, val => val, null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.utils {
|
||||
class PropertyUpdateThrottle<T> {
|
||||
readonly TimeSpan _throttle;
|
||||
readonly IEqualityComparer<T> _comparer;
|
||||
bool _busy, _dirty;
|
||||
T _value, _last_fired;
|
||||
|
||||
public PropertyUpdateThrottle(TimeSpan throttle)
|
||||
: this(throttle, EqualityComparer<T>.Default) { }
|
||||
|
||||
public PropertyUpdateThrottle(TimeSpan throttle, IEqualityComparer<T> comparer) {
|
||||
_throttle = throttle;
|
||||
_comparer = comparer;
|
||||
}
|
||||
|
||||
public T value {
|
||||
get => _value;
|
||||
set {
|
||||
if (!_comparer.Equals(_value, value)) {
|
||||
_value = value;
|
||||
_fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event PropertyChange<T> changed;
|
||||
|
||||
async void _fire() {
|
||||
if (!_busy) {
|
||||
_busy = true;
|
||||
do {
|
||||
var old = _last_fired;
|
||||
_last_fired = _value;
|
||||
changed?.Invoke(old, _value);
|
||||
_dirty = false;
|
||||
await Task.Delay(_throttle);
|
||||
} while (_dirty);
|
||||
_busy = false;
|
||||
} else {
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,91 +1,115 @@
|
||||
using mbts;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mbts.utils;
|
||||
|
||||
namespace bts.utils {
|
||||
static class ProtoHelper {
|
||||
const string
|
||||
_case_suffix = "Case",
|
||||
_oneof_case_suffix = "OneofCase";
|
||||
|
||||
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
|
||||
_DescriptorImpl<T>.value;
|
||||
|
||||
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
|
||||
prop_name = prop_name.iremove_suffix(_case_suffix);
|
||||
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
|
||||
where T : IMessage, new()
|
||||
where TOneofCase : struct =>
|
||||
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
|
||||
|
||||
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
|
||||
string oneof_name = oneof_case.ToString();
|
||||
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
|
||||
}
|
||||
|
||||
public static Type get_field_type(FieldDescriptor field_descriptor) {
|
||||
switch (field_descriptor.FieldType) {
|
||||
case FieldType.Bool: return typeof(bool);
|
||||
case FieldType.Bytes: return typeof(ByteString);
|
||||
case FieldType.Double: return typeof(double);
|
||||
case FieldType.Float: return typeof(float);
|
||||
case FieldType.UInt32: return typeof(uint);
|
||||
case FieldType.UInt64: return typeof(ulong);
|
||||
// int
|
||||
case FieldType.SFixed32:
|
||||
case FieldType.Fixed32:
|
||||
case FieldType.SInt32:
|
||||
case FieldType.Int32: return typeof(int);
|
||||
// long
|
||||
case FieldType.SFixed64:
|
||||
case FieldType.Fixed64:
|
||||
case FieldType.SInt64:
|
||||
case FieldType.Int64: return typeof(long);
|
||||
//
|
||||
case FieldType.String: return typeof(string);
|
||||
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
|
||||
case FieldType.Message: return field_descriptor.MessageType.ClrType;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
|
||||
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
|
||||
|
||||
static class _DescriptorImpl<T> where T : IMessage, new() {
|
||||
public static readonly MessageDescriptor value = new T().Descriptor;
|
||||
|
||||
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
|
||||
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
|
||||
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
|
||||
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
|
||||
var dci = _descriptor_case_indices;
|
||||
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
|
||||
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
|
||||
/// 1) clone the local copy.
|
||||
/// 2) add the new KeyValue to the clone.
|
||||
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
|
||||
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
|
||||
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
|
||||
dci = dci.ToDictionary();
|
||||
dci.Add(prop_name, rv);
|
||||
/// It is possible for add ops to be overwritten.
|
||||
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
|
||||
_descriptor_case_indices = dci;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
using mbts;
|
||||
using Google.Protobuf;
|
||||
using Google.Protobuf.Reflection;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using mbts.utils;
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using System.Reflection;
|
||||
|
||||
namespace bts.utils {
|
||||
static class ProtoHelper {
|
||||
const string
|
||||
_case_suffix = "Case",
|
||||
_oneof_case_suffix = "OneofCase";
|
||||
|
||||
public static MessageDescriptor Descriptor<T>() where T : IMessage, new() =>
|
||||
_DescriptorImpl<T>.value;
|
||||
|
||||
public static OneofDescriptor get_oneof_descriptor<T>(string prop_name) where T : IMessage, new() {
|
||||
prop_name = prop_name.iremove_suffix(_case_suffix);
|
||||
return Descriptor<T>().Oneofs.FirstOrDefault(d => d.Name.iequals(prop_name));
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_oneof_descriptor_index<T, TOneofCase>(string prop_name)
|
||||
where T : IMessage, new()
|
||||
where TOneofCase : struct =>
|
||||
_DescriptorImpl<T>.OneofImpl<TOneofCase>.get_descriptor_case_index(prop_name);
|
||||
|
||||
public static FieldDescriptor get_oneof_field_descriptor<T, TOneofCase>(TOneofCase oneof_case) where T : IMessage, new() where TOneofCase : struct {
|
||||
string oneof_name = oneof_case.ToString();
|
||||
return Descriptor<T>().Fields.InFieldNumberOrder().FirstOrDefault(fd => fd.Name.Replace("_", "").iequals(oneof_name));
|
||||
}
|
||||
|
||||
public static TImmutable immutable_factory<TMutable, TImmutable>(TMutable m) where TMutable : IMessage, new() where TImmutable : IImmutableMessage =>
|
||||
(TImmutable)Activator.CreateInstance(typeof(TImmutable), m);
|
||||
|
||||
public static IMessage mutable_factory(Type type) =>
|
||||
MutableFactoryStore.get(type)();
|
||||
|
||||
static class MutableFactoryStore {
|
||||
[ThreadStatic]
|
||||
static readonly Dictionary<Type, Func<IMessage>> _factories = new Dictionary<Type, Func<IMessage>>();
|
||||
|
||||
public static Func<IMessage> get(Type type) =>
|
||||
_factories.GetOrAdd(type,
|
||||
t => typeof(MutableFactory<>).MakeGenericType(t)
|
||||
.GetField(nameof(MutableFactory<LimitOrderDefinition>.anonymous_factory), BindingFlags.Static | BindingFlags.Public)
|
||||
.GetValue(null) as Func<IMessage>);
|
||||
}
|
||||
|
||||
static class MutableFactory<T> where T : IMessage<T>, new() {
|
||||
public static readonly Func<IMessage> anonymous_factory = () => new T();
|
||||
}
|
||||
|
||||
public static Type get_field_type(FieldDescriptor field_descriptor) {
|
||||
switch (field_descriptor.FieldType) {
|
||||
case FieldType.Bool: return typeof(bool);
|
||||
case FieldType.Bytes: return typeof(ByteString);
|
||||
case FieldType.Double: return typeof(double);
|
||||
case FieldType.Float: return typeof(float);
|
||||
case FieldType.UInt32: return typeof(uint);
|
||||
case FieldType.UInt64: return typeof(ulong);
|
||||
// int
|
||||
case FieldType.SFixed32:
|
||||
case FieldType.Fixed32:
|
||||
case FieldType.SInt32:
|
||||
case FieldType.Int32: return typeof(int);
|
||||
// long
|
||||
case FieldType.SFixed64:
|
||||
case FieldType.Fixed64:
|
||||
case FieldType.SInt64:
|
||||
case FieldType.Int64: return typeof(long);
|
||||
//
|
||||
case FieldType.String: return typeof(string);
|
||||
case FieldType.Enum: return field_descriptor.EnumType.ClrType;
|
||||
case FieldType.Message: return field_descriptor.MessageType.ClrType;
|
||||
default: throw ExceptionBuilder.Argument.unexpected_case(field_descriptor.FieldType, nameof(field_descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, FieldDescriptor> get_oneof_descriptor_index<TOneofCase>(OneofDescriptor descriptor) where TOneofCase : struct =>
|
||||
descriptor.Fields.ToDictionary(fd => EnumHelper.parse<TOneofCase>(fd.Name.Replace("_", "")));
|
||||
|
||||
static class _DescriptorImpl<T> where T : IMessage, new() {
|
||||
public static readonly MessageDescriptor value = new T().Descriptor;
|
||||
|
||||
public static class OneofImpl<TOneofCase> where TOneofCase : struct {
|
||||
static Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>> _descriptor_case_indices =
|
||||
new Dictionary<string, IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)>>();
|
||||
|
||||
public static IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> get_descriptor_case_index(string prop_name) {
|
||||
/// Use local copy because <see cref="_descriptor_case_indices"/> is updated atomically.
|
||||
var dci = _descriptor_case_indices;
|
||||
if (!dci.TryGetValue(prop_name, out IReadOnlyDictionary<TOneofCase, (FieldDescriptor field_descriptor, Type field_type)> rv)) {
|
||||
/// Update <see cref="_descriptor_case_indices"/> atomically (lock-free).
|
||||
/// 1) clone the local copy.
|
||||
/// 2) add the new KeyValue to the clone.
|
||||
/// 3) assign the clone back to <see cref="_descriptor_case_indices"/>.
|
||||
rv = get_oneof_descriptor_index<TOneofCase>(get_oneof_descriptor<T>(prop_name))
|
||||
.ToDictionary(kv => kv.Key, kv => (kv.Value, get_field_type(kv.Value)));
|
||||
dci = dci.ToDictionary();
|
||||
dci.Add(prop_name, rv);
|
||||
/// It is possible for add ops to be overwritten.
|
||||
/// This is perfectly safe, just means that the 'clone-add-assign' will be repeated the next time a caller requests this key.
|
||||
_descriptor_case_indices = dci;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,11 @@
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:ui="clr-namespace:mbts.ui"
|
||||
>
|
||||
<!--Control Base Colors-->
|
||||
<Color x:Key="Control.MediumGrayColor">#B3B3B3</Color>
|
||||
<Color x:Key="Text.Prompt.Foreground.Color">#FF999999</Color>
|
||||
|
||||
<SolidColorBrush x:Key="Control.MediumGrayBrush" Color="{StaticResource Control.MediumGrayColor}"/>
|
||||
<SolidColorBrush x:Key="CommonBackground" Color="{ui:BTSColor common_background}" />
|
||||
<SolidColorBrush x:Key="ControlBackground" Color="{x:Static mbts:BTSColors.common_background}" />
|
||||
<SolidColorBrush x:Key="AccentColor1" Color="{x:Static mbts:BTSColors.accent1}" />
|
||||
@@ -49,48 +52,48 @@
|
||||
<SolidColorBrush x:Key="CheckBox.Pressed.BorderBrush" Color="{StaticResource CheckBox.Pressed.BorderColor}"/>
|
||||
|
||||
<!--ComboBox-->
|
||||
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
|
||||
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Static.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFF0F0F0" Offset="0.0"/>
|
||||
<GradientStop Color="#FFE5E5E5" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Border" Color="#FFACACAC"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Border" Color="#FFABADB3"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFECF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Border" Color="#FF7EB4EA"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.MouseOver.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFEBF4FC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFDCECFC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.MouseOver.Editable.Button.Border" Color="#FF7EB4EA"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Glyph" Color="#FF000000"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAECFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Border" Color="#FF569DE5"/>
|
||||
<LinearGradientBrush x:Key="ComboBox.Pressed.Editable.Button.Background" EndPoint="0,1" StartPoint="0,0">
|
||||
<GradientStop Color="#FFDAEBFC" Offset="0.0"/>
|
||||
<GradientStop Color="#FFC4E0FC" Offset="1.0"/>
|
||||
</LinearGradientBrush>
|
||||
<SolidColorBrush x:Key="ComboBox.Pressed.Editable.Button.Border" Color="#FF569DE5"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Glyph" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Background" Color="#FFF0F0F0"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Border" Color="#FFD9D9D9"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Background" Color="#FFFFFFFF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Border" Color="#FFBFBFBF"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Background" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Disabled.Editable.Button.Border" Color="Transparent"/>
|
||||
<SolidColorBrush x:Key="ComboBox.Static.Glyph" Color="#FF606060"/>
|
||||
|
||||
<!--ListBox-->
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
class Adorner<T> : Adorner where T : UIElement {
|
||||
public Adorner(T adorned_element) : base(adorned_element) =>
|
||||
AdornedElement = adorned_element;
|
||||
|
||||
public new T AdornedElement { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
using bts.utils;
|
||||
using mbts.ui.extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.ui.controls.adorners {
|
||||
/// <summary>
|
||||
/// This Adorner draws a rectangle over rows when the mouse hovers over them.
|
||||
/// </summary>
|
||||
class DIYGridRowHighlighterAdorner : Adorner<DIYGrid> {
|
||||
DIYGridRowsPresenter _rows_presenter_backing;
|
||||
ScrollContentPresenter _content_presenter_backing;
|
||||
object _target_row;
|
||||
Brush _fill;
|
||||
Pen _stroke;
|
||||
|
||||
#region Fill
|
||||
public static readonly DependencyProperty FillProperty =
|
||||
WPFHelper.create_dp<SolidColorBrush, DIYGridRowHighlighterAdorner>(
|
||||
nameof(Fill),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._fill = v,
|
||||
DIYGrid.DefaultSelectionHighlight);
|
||||
|
||||
public SolidColorBrush Fill {
|
||||
get => GetValue(FillProperty) as SolidColorBrush;
|
||||
set => SetValue(FillProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Stroke
|
||||
public static readonly DependencyProperty StrokeProperty =
|
||||
WPFHelper.create_dp<Pen, DIYGridRowHighlighterAdorner>(
|
||||
nameof(Stroke),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._stroke = v,
|
||||
DIYGrid.DefaultSelectionStroke);
|
||||
|
||||
public Pen Stroke {
|
||||
get => GetValue(StrokeProperty) as Pen;
|
||||
set => SetValue(StrokeProperty, value);
|
||||
}
|
||||
#endregion
|
||||
|
||||
public DIYGridRowHighlighterAdorner(DIYGrid grid) : base(grid) {
|
||||
_target_row = TargetRow;
|
||||
_fill = Fill;
|
||||
_stroke = Stroke;
|
||||
IsHitTestVisible = false;
|
||||
grid.SubscribeMouseEnter(on_mouse_enter);
|
||||
grid.SubscribeMouseLeave(on_mouse_leave);
|
||||
grid.SubscribePreviewMouseMove(on_mouse_move);
|
||||
void on_mouse_enter(object sender, MouseEventArgs e) => InvalidateVisual();
|
||||
void on_mouse_move(object sender, MouseEventArgs e) => InvalidateVisual();
|
||||
void on_mouse_leave(object sender, MouseEventArgs e) => InvalidateVisual();
|
||||
}
|
||||
|
||||
DIYGridRowsPresenter _rows_presenter => _rows_presenter_backing ?? (_rows_presenter_backing = AdornedElement.descendants_of_type<DIYGridRowsPresenter>().FirstOrDefault());
|
||||
ScrollContentPresenter _content_presenter => _content_presenter_backing ?? (_content_presenter_backing = _rows_presenter?.descendants_of_type<ScrollContentPresenter>().empty_if_null().FirstOrDefault());
|
||||
|
||||
public static readonly DependencyProperty TargetRowProperty =
|
||||
WPFHelper.create_dp<object, DIYGridRowHighlighterAdorner>(
|
||||
nameof(TargetRow),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender,
|
||||
(o, v) => o._target_row = v);
|
||||
|
||||
public object TargetRow { get => GetValue(TargetRowProperty); set => SetValue(TargetRowProperty, value); }
|
||||
|
||||
protected override void OnRender(DrawingContext drawingContext) {
|
||||
if (_content_presenter == null || _target_row == null) {
|
||||
base.OnRender(drawingContext);
|
||||
}
|
||||
var pt = Mouse.GetPosition(AdornedElement);
|
||||
if (AdornedElement.RowContainerGenerator.ContainerFromItem(_target_row) is FrameworkElement rview) {
|
||||
var top_left = rview.TranslatePoint(new Point(0, 0), _content_presenter);
|
||||
double x_adj = MathUtils.within_range(0.0, _content_presenter.ActualWidth, top_left.X);
|
||||
double y_adj = MathUtils.within_range(0.0, _content_presenter.ActualHeight, top_left.Y);
|
||||
top_left = _content_presenter.TranslatePoint(new Point(x_adj, y_adj), AdornedElement);
|
||||
drawingContext.DrawRectangle(_fill, _stroke, new Rect(top_left, new Point(top_left.X + _content_presenter.ActualWidth, top_left.Y + rview.ActualHeight)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,12 +48,14 @@ namespace mbts.ui.controls {
|
||||
PnL_Generic = 80,
|
||||
Working = 72,
|
||||
LastMessage = 100,
|
||||
ParentKey = 100;
|
||||
ParentKey = 100,
|
||||
AttachedTargetOrderKey = 100;
|
||||
}
|
||||
|
||||
public static class DefaultColumnHeaders {
|
||||
public const string
|
||||
Account = "Account",
|
||||
AttachedTargetOrderKey = "Attached Target Key",
|
||||
Exchange = "Exchange",
|
||||
FillKey = "Fill Key",
|
||||
FillTime = "Fill Time",
|
||||
|
||||
@@ -19,6 +19,7 @@ using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using mbts.utils;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
interface IHandleGridSorting {
|
||||
@@ -323,6 +324,9 @@ namespace mbts.ui.controls {
|
||||
|
||||
public event DIYGridSortingEventHandler Sorting;
|
||||
|
||||
public ItemContainerGenerator RowContainerGenerator =>
|
||||
_row_presenter.ItemContainerGenerator;
|
||||
|
||||
private void OnCopy(object sender, ExecutedRoutedEventArgs e) {
|
||||
if (_row_presenter != null) {
|
||||
IEnumerable<RowBase> selected_rows = _row_presenter.SelectedItems.Cast<object>()
|
||||
@@ -704,8 +708,8 @@ namespace mbts.ui.controls {
|
||||
(o, v) => {
|
||||
if (DoubleHelper.IsNaNOrInfinity(v))
|
||||
v = 0;
|
||||
if (!o._visible_columns.is_null_or_empty() && o.Columns != null) {
|
||||
double max_font_size = o.Columns.Max(c => c.font_size);
|
||||
if (!o._visible_columns.is_null_or_empty()) {
|
||||
double max_font_size = o._visible_columns.Max(c => c.font_size);
|
||||
double min_row_height = o.RowPadding.Top + max_font_size + o.RowPadding.Bottom;
|
||||
if (v < min_row_height)
|
||||
return min_row_height;
|
||||
@@ -1025,5 +1029,25 @@ namespace mbts.ui.controls {
|
||||
callback(map);
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<T> SubscribeMouseHoverRow<T>(this DIYGrid grid) where T : class =>
|
||||
SubscribeMouseHoverRow(grid, EqualityComparer<T>.Default);
|
||||
|
||||
public static IObservable<T> SubscribeMouseHoverRow<T>(this DIYGrid grid, IEqualityComparer<T> comparer) where T : class =>
|
||||
Observable.Create<T>(o => {
|
||||
T curr_row = null;
|
||||
return Disposable.Create(
|
||||
grid.SubscribeMouseEnter(on_mouse_enter),
|
||||
grid.SubscribeMouseLeave(on_mouse_leave),
|
||||
grid.SubscribePreviewMouseMove(on_mouse_move));
|
||||
void on_mouse_enter(object sender, MouseEventArgs e) => refresh();
|
||||
void on_mouse_move(object sender, MouseEventArgs e) => refresh();
|
||||
void on_mouse_leave(object sender, MouseEventArgs e) => refresh();
|
||||
void refresh() {
|
||||
T row = grid.ChildAtPoint<ListBoxItem>(Mouse.GetPosition(grid)) is var rview && rview != null ? grid.RowContainerGenerator.ItemFromContainer(rview) as T : null;
|
||||
if (!comparer.Equals(curr_row, row))
|
||||
o.OnNext(curr_row = row);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,21 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/Buttons.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<sys:Double x:Key="DialogSkirtPadding.Left">12</sys:Double>
|
||||
<sys:Double x:Key="DialogSkirtPadding.Top">12</sys:Double>
|
||||
<sys:Double x:Key="DialogSkirtPadding.Right">12</sys:Double>
|
||||
<Thickness x:Key="DialogSkirtPadding" Left="{StaticResource DialogSkirtPadding.Left}" Top="{StaticResource DialogSkirtPadding.Top}" Right="{StaticResource DialogSkirtPadding.Right}" Bottom="10"/>
|
||||
<Thickness x:Key="DialogSkirtButtonMargin" Left="4" Top="0" Right="4" Bottom="0"/>
|
||||
<Style x:Key="{x:Static c:DialogSkirt.DialogSkirtButtonStyleKey}" TargetType="Button" BasedOn="{StaticResource CommonButton}">
|
||||
<Setter Property="Content" Value="{Binding name}"/>
|
||||
<Setter Property="IsCancel" Value="{Binding is_cancel}"/>
|
||||
<Setter Property="IsDefault" Value="{Binding is_default}"/>
|
||||
<Setter Property="Margin" Value="4,0"/>
|
||||
<Setter Property="Margin" Value="{StaticResource DialogSkirtButtonMargin}"/>
|
||||
<Setter Property="Command" Value="{Binding command}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding command_parameter}"/>
|
||||
</Style>
|
||||
@@ -19,7 +25,7 @@
|
||||
<Style TargetType="c:DialogSkirt">
|
||||
<Setter Property="Background" Value="{x:Static u:BTSColors.dialog_skirt_brush}"/>
|
||||
<Setter Property="BorderThickness" Value="0"/>
|
||||
<Setter Property="Padding" Value="12,12,12,10"/>
|
||||
<Setter Property="Padding" Value="{StaticResource DialogSkirtPadding}"/>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="c:DialogSkirt">
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:ord="clr-namespace:bts.api.orderman;assembly=spark_api"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:ut="clr-namespace:mbts.ui.utilities"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:sys="clr-namespace:System;assembly=mscorlib"
|
||||
@@ -25,10 +26,31 @@
|
||||
<sys:Double x:Key="VerticalPush3">12</sys:Double>
|
||||
<Thickness x:Key="Margin.LP1" Left="{StaticResource HorizontalPush1}"/>
|
||||
<Thickness x:Key="Margin.LP3" Left="{StaticResource HorizontalPush3}"/>
|
||||
<Thickness x:Key="Margin.TP1" Top="{StaticResource VerticalPush1}"/>
|
||||
<sys:String x:Key="CanEditValuesState">CanEditValues</sys:String>
|
||||
<sys:String x:Key="CannotEditValuesState">CannotEditValues</sys:String>
|
||||
<sys:String x:Key="CanEditStructureState">CanEditStructure</sys:String>
|
||||
<sys:String x:Key="CannotEditStructureState">CannotEditStructure</sys:String>
|
||||
<cv:EnumHasFlagsConverter x:Key="CanEditValuesConverter">
|
||||
<sp:AlgoEditingPermissions>AllowEditValues</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
<cv:EnumHasFlagsConverter x:Key="CanEditStructureConverter">
|
||||
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
<cv:ValueConverterPipe x:Key="ValuesEditingStateConverter">
|
||||
<cv:ValueConverterPipe.Pipe>
|
||||
<cv:EnumHasFlagsConverter>
|
||||
<sp:AlgoEditingPermissions>AllowEditValues</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
<cv:ValueMap Default="{StaticResource CannotEditValuesState}">
|
||||
<cv:KeyValue Key="{u:True}" Value="{StaticResource CanEditValuesState}"/>
|
||||
</cv:ValueMap>
|
||||
</cv:ValueConverterPipe.Pipe>
|
||||
</cv:ValueConverterPipe>
|
||||
<Style x:Key="{x:Static usp:AlgoContentTemplateSelector.DefinitionDropDownStyleKey}" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding can_edit_structure}" Value="{u:False}">
|
||||
<DataTrigger Binding="{Binding editing_permissions, Converter={StaticResource CanEditStructureConverter}}" Value="{u:False}">
|
||||
<Setter Property="ToolTip" Value="Cannot change structure while templates are present."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
@@ -36,7 +58,7 @@
|
||||
<Style x:Key="{x:Static usp:AlgoContentTemplateSelector.TemplateDropDownStyleKey}" TargetType="ComboBox" BasedOn="{StaticResource {x:Type ComboBox}}">
|
||||
<Setter Property="ToolTipService.ShowOnDisabled" Value="True"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding can_edit_structure}" Value="{u:False}">
|
||||
<DataTrigger Binding="{Binding editing_permissions, Converter={StaticResource CanEditStructureConverter}}" Value="{u:False}">
|
||||
<Setter Property="ToolTip" Value="Cannot change structure of template."/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
@@ -62,33 +84,111 @@
|
||||
</usp:AlgoContentTemplateSelector.DefaultOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.StopLimitOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding can_edit_values}" VerticalAlignment="Top">
|
||||
<Grid x:Name="ContentRoot" VerticalAlignment="Top">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="stop_price_value" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">Stop Price</TextBlock>
|
||||
<usp:PriceEntryControl Grid.Column="2" Value="{Binding stop_price, FallbackValue={x:Static sys:Double.NaN}}" ContractSpec="{Binding spec}"/>
|
||||
<usp:PriceEntryControl x:Name="stop_price_value" Grid.Column="2" Value="{Binding stop_price, FallbackValue={x:Static sys:Double.NaN}}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:AlgoContentTemplateSelector.StopLimitOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.StopMarketOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding can_edit_values}" VerticalAlignment="Top">
|
||||
<Grid x:Name="ContentRoot" VerticalAlignment="Top">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="stop_price_value" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">Stop Price</TextBlock>
|
||||
<usp:PriceEntryControl Grid.Column="2" Value="{Binding stop_price}" ContractSpec="{Binding spec}"/>
|
||||
<usp:PriceEntryControl x:Name="stop_price_value" Grid.Column="2" Value="{Binding stop_price}" ContractSpec="{Binding spec}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:AlgoContentTemplateSelector.StopMarketOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.TimeBoxOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_StartMode_ComboBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_AbsoluteStart_DateTimePicker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_RelativeStart_TimeSpanControl" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_StopMode_ComboBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_AbsoluteStop_DateTimePicker" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_RelativeStop_TimeSpanControl" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}" MaxWidth="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
@@ -105,50 +205,51 @@
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Start</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="PART_StartMode_ComboBox"
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
ItemsSource="{Binding start_modes}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding start_mode}"
|
||||
/>
|
||||
<c:DateTimePicker
|
||||
x:Name="PART_AbsoluteStart_DateTimePicker"
|
||||
Grid.Column="4"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
TimeTuple="{Binding absolute_start}"
|
||||
Visibility="{Binding show_absolute_start, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<c:TimeSpanControl
|
||||
x:Name="PART_RelativeStart_TimeSpanControl"
|
||||
Grid.Column="4"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
TimeSpan="{Binding relative_start, Mode=TwoWay}"
|
||||
Visibility="{Binding show_relative_start, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<TextBlock Grid.Row="2" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Stop</TextBlock>
|
||||
<ComboBox
|
||||
x:Name="PART_StopMode_ComboBox"
|
||||
Grid.Column="2"
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
ItemsSource="{Binding stop_modes}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding stop_mode}"
|
||||
/>
|
||||
<c:DateTimePicker
|
||||
x:Name="PART_AbsoluteStop_DateTimePicker"
|
||||
Grid.Row="2"
|
||||
Grid.Column="4"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
TimeTuple="{Binding absolute_stop}"
|
||||
Visibility="{Binding show_absolute_stop, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<c:TimeSpanControl
|
||||
x:Name="PART_RelativeStop_TimeSpanControl"
|
||||
Grid.Row="2"
|
||||
Grid.Column="4"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
TimeSpan="{Binding relative_stop, Mode=TwoWay}"
|
||||
Visibility="{Binding show_relative_stop, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="4"
|
||||
Grid.ColumnSpan="5"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -162,7 +263,40 @@
|
||||
</usp:AlgoContentTemplateSelector.TimeBoxOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.TickOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_DetachOnToward_CheckBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_CrossThreshold_QuantityMeasureControl" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_CrossTicks_TickUpDownUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_MaxChaseTicks_TickUpDownUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickOffsetOnDeploy_TickUpDownUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
|
||||
@@ -181,16 +315,17 @@
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox Grid.ColumnSpan="3" VerticalAlignment="Center" IsEnabled="{Binding can_edit_values}" IsChecked="{Binding detach_on_toward}">Detach on Toward</CheckBox>
|
||||
<CheckBox x:Name="PART_DetachOnToward_CheckBox" Grid.ColumnSpan="3" VerticalAlignment="Center" IsChecked="{Binding detach_on_toward}">Detach on Toward</CheckBox>
|
||||
<TextBlock Grid.Row="2" VerticalAlignment="Center">Cross Thresh</TextBlock>
|
||||
<uag:QuantityMeasureControl Grid.Row="2" Grid.Column="2" IsEnabled="{Binding can_edit_values}" QuantityMeasure="{Binding cross_threshold}"/>
|
||||
<uag:QuantityMeasureControl x:Name="PART_CrossThreshold_QuantityMeasureControl" Grid.Row="2" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
|
||||
<TextBlock Grid.Row="4" VerticalAlignment="Center">Cross Ticks</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="4" Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding cross_ticks}"/>
|
||||
<c:TickUpDownUInt x:Name="PART_CrossTicks_TickUpDownUInt" Grid.Row="4" Grid.Column="2" Value="{Binding cross_ticks}"/>
|
||||
<TextBlock Grid.Row="6" VerticalAlignment="Center">Max Chase Ticks</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="6" Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding max_chase_ticks}"/>
|
||||
<c:TickUpDownUInt x:Name="PART_MaxChaseTicks_TickUpDownUInt" Grid.Row="6" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
|
||||
<TextBlock Grid.Row="8" VerticalAlignment="Center">Initial Tick Offset</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Row="8" Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<c:TickUpDownUInt x:Name="PART_TickOffsetOnDeploy_TickUpDownUInt" Grid.Row="8" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="10"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -204,7 +339,37 @@
|
||||
</usp:AlgoContentTemplateSelector.TickOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.LeanOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_CrossThreshold_QuantityMeasureControl" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_MaxChaseTicks_TickUpDownUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TradeTowardTypes_ComboBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Child_AlgoPickerNodeView" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
@@ -220,21 +385,22 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center">Cross Thresh</TextBlock>
|
||||
<TextBlock VerticalAlignment="Center">Cross Thresh</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="When the size of the opposite-inside market falls below this size threshold the order will payup."/>
|
||||
</StackPanel>
|
||||
<uag:QuantityMeasureControl Grid.Column="2" IsEnabled="{Binding can_edit_values}" QuantityMeasure="{Binding cross_threshold}"/>
|
||||
<uag:QuantityMeasureControl x:Name="PART_CrossThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding cross_threshold}"/>
|
||||
<StackPanel Grid.Row="2" Orientation="Horizontal">
|
||||
<TextBlock VerticalAlignment="Center">Max Chase Ticks</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="The max number of ticks the order will continue to pay up after the initial payup."/>
|
||||
</StackPanel>
|
||||
<c:TickUpDownUInt Grid.Row="2" Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding max_chase_ticks}"/>
|
||||
<c:TickUpDownUInt x:Name="PART_MaxChaseTicks_TickUpDownUInt" Grid.Row="2" Grid.Column="2" Value="{Binding max_chase_ticks}"/>
|
||||
<StackPanel Grid.Row="4" Orientation="Horizontal">
|
||||
<TextBlock>On Toward</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="What should happen when the market moves toward the order."/>
|
||||
</StackPanel>
|
||||
<ComboBox Grid.Row="4" Grid.Column="2" IsEnabled="{Binding can_edit_values}" ItemsSource="{Binding trade_toward_types}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding trade_toward_behavior}"/>
|
||||
<ComboBox x:Name="PART_TradeTowardTypes_ComboBox" Grid.Row="4" Grid.Column="2" ItemsSource="{Binding trade_toward_types}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding trade_toward_behavior}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="6"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -248,7 +414,28 @@
|
||||
</usp:AlgoContentTemplateSelector.LeanOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.JoinOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_JoinThreshold_QuantityMeasureControl" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
@@ -263,8 +450,9 @@
|
||||
<TextBlock VerticalAlignment="Center">Join Thresh</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="The Threshold at which the order will join the market when the market flips (the event where the best offer becomes the best bid in the buy case)."/>
|
||||
</StackPanel>
|
||||
<uag:QuantityMeasureControl Grid.Column="2" IsEnabled="{Binding can_edit_values}" QuantityMeasure="{Binding join_threshold}"/>
|
||||
<uag:QuantityMeasureControl x:Name="PART_JoinThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding join_threshold}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -278,7 +466,28 @@
|
||||
</usp:AlgoContentTemplateSelector.JoinOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.TopOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TopThreshold_QuantityMeasureControl" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
@@ -293,8 +502,9 @@
|
||||
<TextBlock VerticalAlignment="Center">Top Thresh</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="If the order is at the top of book, the quantity remaining below which the order will be canceled."/>
|
||||
</StackPanel>
|
||||
<uag:QuantityMeasureControl Grid.Column="2" IsEnabled="{Binding can_edit_values}" QuantityMeasure="{Binding top_threshold}"/>
|
||||
<uag:QuantityMeasureControl x:Name="PART_TopThreshold_QuantityMeasureControl" Grid.Column="2" QuantityMeasure="{Binding top_threshold}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -308,7 +518,28 @@
|
||||
</usp:AlgoContentTemplateSelector.TopOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.CoverOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_HedgeTicks_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
@@ -325,8 +556,9 @@
|
||||
<TextBlock VerticalAlignment="Center">Hedge Ticks</TextBlock>
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="This is the number of ticks to offset the hedge."/>
|
||||
</StackPanel>
|
||||
<c:TickUpDownInt Grid.Column="2" HorizontalAlignment="Stretch" IsEnabled="{Binding can_edit_values}" Value="{Binding hedge_ticks}"/>
|
||||
<c:TickUpDownInt x:Name="PART_HedgeTicks_TickUpDownInt" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding hedge_ticks}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Hedge_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.hedge.algos}"
|
||||
@@ -336,6 +568,7 @@
|
||||
OrderKindTemplate="{StaticResource OrderKindTemplate}"
|
||||
/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="4"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -349,7 +582,31 @@
|
||||
</usp:AlgoContentTemplateSelector.CoverOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.OCOOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_OtherTicks_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_OtherSideTypes_ComboBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
@@ -368,13 +625,14 @@
|
||||
<TextBlock VerticalAlignment="Center" >Stop Ticks</TextBlock>
|
||||
<c:HelpButton Margin="4,0,0,0" ToolTip="This is the number of ticks away from the main order to start the 'stop' order." />
|
||||
</StackPanel>
|
||||
<c:TickUpDownInt Grid.Column="2" Value="{Binding other_ticks}"/>
|
||||
<c:TickUpDownInt x:Name="PART_OtherTicks_TickUpDownInt" Grid.Column="2" Value="{Binding other_ticks}"/>
|
||||
<StackPanel Orientation="Horizontal" Grid.Row="2" >
|
||||
<TextBlock VerticalAlignment="Center" >Stop Side</TextBlock>
|
||||
<c:HelpButton Margin="4,0,0,0" ToolTip="The Side of the 'stop' order" />
|
||||
</StackPanel>
|
||||
<ComboBox Grid.Row="2" Grid.Column="2" ItemsSource="{Binding other_side_types}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding other_side}"/>
|
||||
<ComboBox x:Name="PART_OtherSideTypes_ComboBox" Grid.Row="2" Grid.Column="2" ItemsSource="{Binding other_side_types}" ItemTemplate="{StaticResource EnumDescriptionTemplate}" SelectedItem="{Binding other_side}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Other_AlgoPickerNodeView"
|
||||
Grid.Row="4"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.other.algos}"
|
||||
@@ -384,6 +642,7 @@
|
||||
OrderKindTemplate="{StaticResource OrderKindTemplate}"
|
||||
/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="6"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -397,7 +656,28 @@
|
||||
</usp:AlgoContentTemplateSelector.OCOOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.OffsetOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TickOffsetOnDeploy_TickUpDownUInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}" />
|
||||
@@ -409,8 +689,9 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center">Tick Offset</TextBlock>
|
||||
<c:TickUpDownUInt Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<c:TickUpDownUInt x:Name="PART_TickOffsetOnDeploy_TickUpDownUInt" Grid.Column="2" Value="{Binding tick_offset_on_deploy}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -424,7 +705,70 @@
|
||||
</usp:AlgoContentTemplateSelector.OffsetOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.TriggerOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
<u:StateDefinition>
|
||||
<MultiBinding Converter="{x:Static usp:AlgoContentTemplateSelector.TriggerTimeoutStateConverter}">
|
||||
<Binding Path="DataContext.is_timeout_checked" Source="{u:DirectReference ContentRoot}"/>
|
||||
<Binding Path="DataContext.editing_permissions" Source="{u:DirectReference ContentRoot}"/>
|
||||
</MultiBinding>
|
||||
</u:StateDefinition>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TimeoutSecs_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TriggerPriceTypes_ComboBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_PayupTicks_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TimeoutEnabled_CheckBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TimeoutSecs_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_PriceoutEnabled_CheckBox" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
<VisualStateGroup Name="{x:Static GroupName, MemberType={u:Nested usp:AlgoContentTemplateSelector.VisualStateNames.TriggerTimeout}}">
|
||||
<VisualState Name="{x:Static Active, MemberType={u:Nested usp:AlgoContentTemplateSelector.VisualStateNames.TriggerTimeout}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TimeoutSecs_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="True"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
<VisualState Name="{x:Static Inactive, MemberType={u:Nested usp:AlgoContentTemplateSelector.VisualStateNames.TriggerTimeout}}">
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_TimeoutSecs_TickUpDownInt" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
@@ -452,9 +796,9 @@
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="This is the side of the market that is observed for the trigger condition. When this market touches (or penetrates the trigger price), the child order is sent to the market." />
|
||||
</StackPanel>
|
||||
<ComboBox
|
||||
x:Name="PART_TriggerPriceTypes_ComboBox"
|
||||
Grid.Row="1"
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding can_edit_values}"
|
||||
ItemsSource="{Binding trigger_price_types}"
|
||||
ItemTemplate="{StaticResource EnumDescriptionTemplate}"
|
||||
SelectedItem="{Binding trigger_price_type}"
|
||||
@@ -475,8 +819,9 @@
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock>Payup\less</TextBlock>
|
||||
<c:TickUpDownInt Grid.Column="2" IsEnabled="{Binding can_edit_values}" Value="{Binding payup_ticks}"/>
|
||||
<c:TickUpDownInt x:Name="PART_PayupTicks_TickUpDownInt" Grid.Column="2" Value="{Binding payup_ticks}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -504,7 +849,7 @@
|
||||
<c:HelpButton Margin="{StaticResource Margin.LP1}" ToolTip="The trigger order can be canceled after a specified amount of time (Timeout) or conditionally (Price Conditional) if the trigger condition ceases to hold." />
|
||||
</StackPanel>
|
||||
</GroupBox.Header>
|
||||
<Grid IsEnabled="{Binding can_edit_values}">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition SharedSizeGroup="c0"/>
|
||||
<ColumnDefinition Width="{Binding Source={StaticResource HorizontalPush1}}"/>
|
||||
@@ -515,11 +860,11 @@
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush1}}"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
<CheckBox IsChecked="{Binding is_timeout_checked}" VerticalContentAlignment="Center">
|
||||
<CheckBox x:Name="PART_TimeoutEnabled_CheckBox" IsChecked="{Binding is_timeout_checked}" VerticalContentAlignment="Center">
|
||||
Timeout (secs)
|
||||
</CheckBox>
|
||||
<c:TickUpDownInt Grid.Column="2" IsEnabled="{Binding is_timeout_checked, Mode=OneWay}" Minimum="0" Value="{Binding timeout_secs}"/>
|
||||
<CheckBox Grid.Row="2" Grid.ColumnSpan="3" IsChecked="{Binding is_priceout_checked}" VerticalAlignment="Center">
|
||||
<c:TickUpDownInt x:Name="PART_TimeoutSecs_TickUpDownInt" Grid.Column="2" Minimum="0" Value="{Binding timeout_secs}"/>
|
||||
<CheckBox x:Name="PART_PriceoutEnabled_CheckBox" Grid.Row="2" Grid.ColumnSpan="3" IsChecked="{Binding is_priceout_checked}" VerticalAlignment="Center">
|
||||
Price Conditional
|
||||
</CheckBox>
|
||||
</Grid>
|
||||
@@ -529,7 +874,28 @@
|
||||
</usp:AlgoContentTemplateSelector.TriggerOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.IcebergOrderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_ShowQuantity_TickUpDownDouble" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="{Binding Source={StaticResource VerticalPush3}}"/>
|
||||
@@ -541,8 +907,9 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="{StaticResource Margin.LP3}">Show Quantity</TextBlock>
|
||||
<c:TickUpDownDouble Grid.Column="2" HorizontalAlignment="Stretch" IsEnabled="{Binding can_edit_values}" Value="{Binding show_quantity}"/>
|
||||
<c:TickUpDownDouble x:Name="PART_ShowQuantity_TickUpDownDouble" Grid.Column="2" HorizontalAlignment="Stretch" Value="{Binding show_quantity}"/>
|
||||
<uag:AlgoPickerNodeView
|
||||
x:Name="PART_Child_AlgoPickerNodeView"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
ItemsSource="{Binding children_vm.child.algos_view}"
|
||||
@@ -556,12 +923,34 @@
|
||||
</usp:AlgoContentTemplateSelector.IcebergOrderTemplate>
|
||||
<usp:AlgoContentTemplateSelector.SpreaderTemplate>
|
||||
<DataTemplate>
|
||||
<Grid IsEnabled="{Binding can_edit_values}">
|
||||
<Grid x:Name="ContentRoot">
|
||||
<u:StateManager.Groups>
|
||||
<u:StateGroupsManager Target="{Binding Source={u:DirectReference ContentRoot}}">
|
||||
<u:StateDefinitionCollection>
|
||||
<u:StateDefinition State="{Binding DataContext.editing_permissions, Source={u:DirectReference ContentRoot}, Converter={StaticResource ValuesEditingStateConverter}}"/>
|
||||
</u:StateDefinitionCollection>
|
||||
</u:StateGroupsManager>
|
||||
</u:StateManager.Groups>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup Name="ValuesEditingStates">
|
||||
<VisualState Name="{StaticResource CanEditValuesState}"/>
|
||||
<VisualState Name="{StaticResource CannotEditValuesState}">
|
||||
<VisualState.Storyboard>
|
||||
<Storyboard>
|
||||
<BooleanAnimationUsingKeyFrames Storyboard.TargetName="PART_Strategy_ExecutionStrategyEditor" Storyboard.TargetProperty="(UIElement.IsEnabled)">
|
||||
<DiscreteBooleanKeyFrame KeyTime="0" Value="False"/>
|
||||
</BooleanAnimationUsingKeyFrames>
|
||||
</Storyboard>
|
||||
</VisualState.Storyboard>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
<usp:ExecutionStrategyEditor
|
||||
x:Name="PART_Strategy_ExecutionStrategyEditor"
|
||||
Margin="6"
|
||||
HorizontalAlignment="Stretch"
|
||||
DataContext="{Binding strategy_editor}"
|
||||
@@ -575,7 +964,7 @@
|
||||
RemoveLegCommand="{Binding remove_leg}"
|
||||
ContractPickerModel="{Binding contract_picker_model}"
|
||||
/>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="-4" RightItems="{Binding skirt_items}"/>
|
||||
<c:DialogSkirt Grid.Row="1" Margin="-4" VerticalAlignment="Top" RightItems="{Binding skirt_items}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:AlgoContentTemplateSelector.SpreaderTemplate>
|
||||
@@ -593,6 +982,11 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<ItemsControl ItemsSource="{Binding parameters}" Grid.ColumnSpan="3">
|
||||
<ItemsControl.ItemContainerStyle>
|
||||
<Style>
|
||||
<Setter Property="Control.Margin" Value="{StaticResource Margin.TP1}"/>
|
||||
</Style>
|
||||
</ItemsControl.ItemContainerStyle>
|
||||
<ItemsControl.ItemTemplateSelector>
|
||||
<usp:DynamicOrderParamTemplateSelector>
|
||||
<usp:DynamicOrderParamTemplateSelector.BoolTemplate>
|
||||
@@ -604,7 +998,12 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<CheckBox HorizontalAlignment="Stretch" Grid.Column="2" IsChecked="{Binding value, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<CheckBox
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
IsChecked="{Binding value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.BoolTemplate>
|
||||
@@ -617,7 +1016,11 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<c:TickUpDownDouble Grid.Column="2" Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<c:TickUpDownDouble
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.DoubleTemplate>
|
||||
@@ -630,7 +1033,11 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<c:TickUpDownInt Grid.Column="2" Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<c:TickUpDownInt
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.IntTemplate>
|
||||
@@ -643,7 +1050,11 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<c:TickUpDownUInt Grid.Column="2" Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<c:TickUpDownUInt
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
Value="{Binding value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.UIntTemplate>
|
||||
@@ -656,7 +1067,12 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<TextBox HorizontalAlignment="Stretch" Grid.Column="2" Text="{Binding value, UpdateSourceTrigger=PropertyChanged}"/>
|
||||
<TextBox
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
Text="{Binding value, UpdateSourceTrigger=PropertyChanged}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.StringTemplate>
|
||||
@@ -669,7 +1085,12 @@
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock VerticalAlignment="Center" Text="{Binding descriptor.ParamName, Converter={x:Static ut:DisplayNameHelper.SnakeToTitleCaseConverter}}"/>
|
||||
<ComboBox Grid.Column="2" ItemsSource="{Binding descriptor.ParamType.Enumerated.Element}" SelectedItem="{Binding value}" />
|
||||
<ComboBox
|
||||
Grid.Column="2"
|
||||
IsEnabled="{Binding DataContext.editing_permissions, RelativeSource={RelativeSource AncestorType=ItemsControl}, Converter={StaticResource CanEditValuesConverter}}"
|
||||
ItemsSource="{Binding elements}"
|
||||
SelectedItem="{Binding selected_element}"
|
||||
/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</usp:DynamicOrderParamTemplateSelector.EnumTemplate>
|
||||
@@ -682,6 +1103,7 @@
|
||||
<uag:AlgoPickerNodeView
|
||||
ItemsSource="{Binding algos_view}"
|
||||
SelectedItem="{Binding selected_algo}"
|
||||
CanEditStructure="{Binding DataContext.editing_permissions, Converter={StaticResource CanEditStructureConverter}, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
||||
ContentTemplateSelector="{DynamicResource AlgoTemplateSelector}"
|
||||
OrderKind="{x:Static usp:AlgoContentTemplateSelector.ChildKind}"
|
||||
OrderKindTemplate="{StaticResource OrderKindTemplate}"
|
||||
|
||||
@@ -9,13 +9,20 @@ namespace mbts.ui.spark {
|
||||
class AlgoContentTemplateSelector : DataTemplateSelector {
|
||||
public const string
|
||||
DefinitionDropDownStyleKey = nameof(DefinitionDropDownStyleKey),
|
||||
TemplateDropDownStyleKey = nameof(TemplateDropDownStyleKey);
|
||||
TemplateDropDownStyleKey = nameof(TemplateDropDownStyleKey),
|
||||
TriggerTimeoutEnabledStateName = nameof(TriggerTimeoutEnabledStateName);
|
||||
|
||||
public static readonly IValueConverter AlgoDropDownStyleKeyConverter = ValueConverter.create(obj => {
|
||||
public static readonly IValueConverter
|
||||
AlgoDropDownStyleKeyConverter = ValueConverter.create(obj => {
|
||||
if (obj is AlgoDefinitionTemplateVM t) return TemplateDropDownStyleKey;
|
||||
if (obj is AlgoDefinitionVM d) return DefinitionDropDownStyleKey;
|
||||
return null;
|
||||
});
|
||||
}),
|
||||
DataContextConverter = ValueConverter.create(obj => obj is AlgoVMWrapper w ? w.value.algo_root : obj);
|
||||
|
||||
public static IMultiValueConverter TriggerTimeoutStateConverter =
|
||||
ValueConverter.create<bool, AlgoEditingPermissions, string>((is_timeout_enabled, editing_permissions) =>
|
||||
is_timeout_enabled && editing_permissions.HasFlag(AlgoEditingPermissions.AllowEditValues) ? VisualStateNames.TriggerTimeout.Active : VisualStateNames.TriggerTimeout.Inactive);
|
||||
|
||||
public static readonly OrderKind ChildKind = new OrderKind("Child", "This is the type of order to be placed when the algo is triggered.");
|
||||
public static readonly OrderKind HedgeKind = new OrderKind("Hedge", "This is the type of the hedge order to send for each fill of Child Order.");
|
||||
@@ -63,6 +70,15 @@ namespace mbts.ui.spark {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static class VisualStateNames {
|
||||
public static class TriggerTimeout {
|
||||
public const string GroupName = nameof(TriggerTimeout);
|
||||
public static readonly string
|
||||
Active = $"{nameof(TriggerTimeout)}{VisualStates.Active}",
|
||||
Inactive = $"{nameof(TriggerTimeout)}{VisualStates.Inactive}";
|
||||
}
|
||||
}
|
||||
|
||||
public class OrderKind {
|
||||
public OrderKind(string name, string description) {
|
||||
this.name = name;
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:sp="clr-namespace:mbts.spark"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:cv="clr-namespace:mbts.ui.converters"
|
||||
xmlns:usp="clr-namespace:mbts.ui.spark"
|
||||
xmlns:uag="clr-namespace:mbts.ui.spark.algo"
|
||||
xmlns:uspcv="clr-namespace:mbts.ui.spark.converters"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml"/>
|
||||
<ResourceDictionary Source="/view/Resources/ComboBoxes.xaml"/>
|
||||
<ResourceDictionary Source="/view/Controls/Order/OrderTypeItem.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<!--<uspcv:OrderTypeBrushConverter x:Key="OrderTypeBrushConverter"/>-->
|
||||
<cv:EnumHasFlagsConverter x:Key="CanEditStructureConverter">
|
||||
<sp:AlgoEditingPermissions>AllowEditStructure</sp:AlgoEditingPermissions>
|
||||
</cv:EnumHasFlagsConverter>
|
||||
<ControlTemplate x:Key="AlgoPickerNodeViewTemplate" TargetType="uag:AlgoPickerNodeView">
|
||||
<GroupBox
|
||||
x:Name="Root"
|
||||
@@ -105,11 +108,11 @@
|
||||
<Setter Property="Background" Value="Transparent"/>
|
||||
<Setter Property="BorderBrush" Value="{Binding SelectedItem, RelativeSource={RelativeSource Self}, Converter={StaticResource OrderTypeBrushConverter}}"/>
|
||||
<Setter Property="BorderThickness" Value="1"/>
|
||||
<Setter Property="CanEditStructure" Value="{Binding can_edit_structure, FallbackValue={u:True}}"/>
|
||||
<Setter Property="CanEditStructure" Value="{Binding editing_permissions, Converter={StaticResource CanEditStructureConverter}}"/>
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch"/>
|
||||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/>
|
||||
<Setter Property="VerticalContentAlignment" Value="Top"/>
|
||||
<Setter Property="Template" Value="{StaticResource AlgoPickerNodeViewTemplate}"/>
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary>
|
||||
@@ -1,7 +1,10 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls">
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="clr-namespace:mbts.ui"
|
||||
xmlns:c="clr-namespace:mbts.ui.controls"
|
||||
xmlns:txt="clr-namespace:mbts.ui.utility.text"
|
||||
>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="/view/Colors.xaml" />
|
||||
<ResourceDictionary Source="/view/Resources/Converters.xaml" />
|
||||
@@ -51,7 +54,12 @@
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="c:TickUpDownDouble">
|
||||
<c:TickUpDown x:Name="PART_tick_up_down">
|
||||
<TextBox Name="PART_TextBox" u:DoubleDisplay.Multiplier="{TemplateBinding Multiplier}" u:DoubleDisplay.Format="{TemplateBinding Format}"/>
|
||||
<TextBox
|
||||
Name="PART_TextBox"
|
||||
u:DoubleDisplay.Multiplier="{TemplateBinding Multiplier}"
|
||||
u:DoubleDisplay.Format="{TemplateBinding Format}"
|
||||
u:TextBoxBehavior.InputFilter="{u:RegexInputFilter Regex={x:Static Decimal, MemberType={u:Nested txt:TextUtils.RegexFilters}}}"
|
||||
/>
|
||||
</c:TickUpDown>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
using mbts.ui.converters;
|
||||
using mbts.ui.extensions;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace mbts.ui.controls {
|
||||
class TickUpDownDouble : Control {
|
||||
IDisposable _text_hooks = null;
|
||||
TextBox _text_box;
|
||||
|
||||
static TickUpDownDouble() =>
|
||||
DefaultStyleKeyProperty.OverrideMetadata(typeof(TickUpDownDouble), new FrameworkPropertyMetadata(typeof(TickUpDownDouble)));
|
||||
|
||||
#region FlushTextImmediately
|
||||
public static readonly DependencyProperty FlushTextImmediatelyProperty =
|
||||
WPFHelper.create_dp<bool, TickUpDownDouble>(
|
||||
nameof(FlushTextImmediately),
|
||||
(o, v) => o._init_text_binding(v));
|
||||
WPFHelper.create_dp<bool, TickUpDownDouble>(nameof(FlushTextImmediately));
|
||||
|
||||
public bool FlushTextImmediately {
|
||||
get => (bool)GetValue(FlushTextImmediatelyProperty);
|
||||
@@ -19,10 +22,19 @@ namespace mbts.ui.controls {
|
||||
}
|
||||
#endregion
|
||||
|
||||
public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(double), typeof(TickUpDownDouble), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnValueChanged));
|
||||
static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
|
||||
}
|
||||
public static readonly DependencyProperty ValueProperty =
|
||||
WPFHelper.create_dp<double, TickUpDownDouble>(
|
||||
nameof(Value),
|
||||
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
|
||||
(o, v) => {
|
||||
if (o._text_box != null) {
|
||||
if (!double.TryParse(o._text_box.Text, out double from_text))
|
||||
from_text = double.NaN;
|
||||
if (!from_text.Equals(v) && !double.IsNaN(v)) {
|
||||
o._text_box.Text = o.Format is string f ? v.ToString(f, CultureInfo.CurrentCulture) : v.ToString();
|
||||
}
|
||||
}
|
||||
});
|
||||
public static readonly DependencyProperty IncrementProperty = DependencyProperty.Register("Increment", typeof(double), typeof(TickUpDownDouble), new FrameworkPropertyMetadata(1.0));
|
||||
public static readonly DependencyProperty MultiplierProperty = DependencyProperty.Register("Multiplier", typeof(double), typeof(TickUpDownDouble), new FrameworkPropertyMetadata(1.0));
|
||||
public static readonly DependencyProperty FormatProperty = DependencyProperty.Register("Format", typeof(string), typeof(TickUpDownDouble));
|
||||
@@ -35,18 +47,23 @@ namespace mbts.ui.controls {
|
||||
public double TickDefaultValue { get => (double)GetValue(TickDefaultValueProperty); set => SetValue(TickDefaultValueProperty, value); }
|
||||
|
||||
public override void OnApplyTemplate() {
|
||||
Disposable.dispose(ref _text_hooks);
|
||||
var tick_up_down = this.assert_template_child<TickUpDown>("PART_tick_up_down");
|
||||
_text_box = this.assert_template_child<TextBox>("PART_TextBox");
|
||||
_text_box.Text = _format_value(Value, Format);
|
||||
tick_up_down.TickUpCommand = new DelegateCommand(_tick_up);
|
||||
tick_up_down.TickDownCommand = new DelegateCommand(_tick_down);
|
||||
_init_text_binding();
|
||||
push_text_to_value();
|
||||
_text_hooks = _text_box.SubscribeTextChanged((s, e) => push_text_to_value());
|
||||
void push_text_to_value() {
|
||||
if (double.TryParse(_text_box.Text, out double d)) {
|
||||
SetCurrentValue(ValueProperty, d);
|
||||
} else {
|
||||
SetCurrentValue(ValueProperty, double.NaN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _init_text_binding() =>
|
||||
_init_text_binding(FlushTextImmediately);
|
||||
|
||||
void _init_text_binding(bool flush_text_immediately) =>
|
||||
TickUpDownHelper.init_text_binding(this, ValueConverters.DoubleTextConverter, flush_text_immediately);
|
||||
|
||||
void _tick_up() {
|
||||
double v = Value;
|
||||
Value = !DoubleHelper.IsNaNOrInfinity(v) ? v + Increment : TickDefaultValue;
|
||||
@@ -58,5 +75,8 @@ namespace mbts.ui.controls {
|
||||
Value = v - Increment;
|
||||
} else Value = TickDefaultValue;
|
||||
}
|
||||
|
||||
static string _format_value(double value, string format) =>
|
||||
format == null ? value.ToString() : value.ToString(format, CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace mbts.ui.converters {
|
||||
[ContentProperty(nameof(Flags))]
|
||||
class EnumHasFlagsConverter : IValueConverter {
|
||||
Func<object, object, bool> _convert_del;
|
||||
|
||||
public IList Flags { get; } = new List<object>();
|
||||
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
|
||||
if (value == null)
|
||||
return false;
|
||||
if (Flags.Count == 0)
|
||||
return true;
|
||||
if (_convert_del == null)
|
||||
_convert_del = EnumHelper.get_has_flags_delegate(value.GetType());
|
||||
foreach (var f in Flags) {
|
||||
if (!_convert_del(value, f))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.ui.extensions;
|
||||
using mbts.ui.utility.text;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
@@ -8,9 +10,22 @@ namespace mbts.ui {
|
||||
enum RelativePosition { Left, Right, Above, Below }
|
||||
|
||||
static class AdornerHelper {
|
||||
public static void attach(this Adorner adorner) {
|
||||
public static void attach(this Adorner adorner) => try_attach(adorner);
|
||||
|
||||
public static async Task attach_async(this Adorner adorner) {
|
||||
if (!adorner.try_attach()) {
|
||||
await adorner.LoadedAsync();
|
||||
adorner.attach();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool try_attach(this Adorner adorner) {
|
||||
var al = AdornerLayer.GetAdornerLayer(adorner.AdornedElement);
|
||||
if (al != null) al.Add(adorner);
|
||||
if (al != null) {
|
||||
al.Add(adorner);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void detach(this Adorner adorner) {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
using System;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using CompositeDisposable = System.Reactive.Disposables.CompositeDisposable;
|
||||
|
||||
namespace mbts.ui.extensions {
|
||||
static class FrameworkElementHelper {
|
||||
@@ -29,5 +32,42 @@ namespace mbts.ui.extensions {
|
||||
element.SizeChanged += callback;
|
||||
return Disposable.Create(() => element.SizeChanged -= callback);
|
||||
}
|
||||
|
||||
public static IDisposable SubscribeDataContextDisposed(this FrameworkElement element, Func<IDisposable> set_hooks) {
|
||||
IDisposable data_context_sub = null;
|
||||
subscribe_data_context(element.DataContext as IWillLetYouKnowWhenIDispose);
|
||||
element.DataContextChanged += on_data_context_changed;
|
||||
return Disposable.Create(() => {
|
||||
element.DataContextChanged -= on_data_context_changed;
|
||||
Disposable.dispose(ref data_context_sub);
|
||||
});
|
||||
void on_data_context_changed(object sender, DependencyPropertyChangedEventArgs e) =>
|
||||
subscribe_data_context(e.NewValue as IWillLetYouKnowWhenIDispose);
|
||||
|
||||
void on_data_context_disposed(bool disposing) =>
|
||||
Disposable.dispose(ref data_context_sub);
|
||||
|
||||
void subscribe_data_context(IWillLetYouKnowWhenIDispose dc) {
|
||||
Disposable.dispose(ref data_context_sub);
|
||||
if (dc != null) {
|
||||
dc.OnDisposed += on_data_context_disposed;
|
||||
data_context_sub = new CompositeDisposable(set_hooks()) {
|
||||
() => dc.OnDisposed -= on_data_context_disposed
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Task<RoutedEventArgs> LoadedAsync(this FrameworkElement element) {
|
||||
if (element.IsLoaded)
|
||||
return Task.FromResult(new RoutedEventArgs(FrameworkElement.LoadedEvent, element));
|
||||
var tcs = new TaskCompletionSource<RoutedEventArgs>();
|
||||
element.Loaded += on_loaded;
|
||||
return tcs.Task;
|
||||
void on_loaded(object sender, RoutedEventArgs e) {
|
||||
element.Loaded -= on_loaded;
|
||||
tcs.TrySetResult(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using mbts.ui.utility.text;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
@@ -108,6 +108,11 @@ namespace mbts.ui.extensions {
|
||||
return Disposable.Create(() => target.PreviewMouseDown -= callback);
|
||||
}
|
||||
|
||||
public static IDisposable SubscribePreviewMouseMove(this UIElement target, MouseEventHandler callback) {
|
||||
target.PreviewMouseMove += callback;
|
||||
return Disposable.Create(() => target.PreviewMouseMove -= callback);
|
||||
}
|
||||
|
||||
public static IDisposable SubscribePreviewDragOver(this UIElement target, DragEventHandler callback) {
|
||||
target.PreviewDragOver += callback;
|
||||
return Disposable.Create(() => target.PreviewDragOver -= callback);
|
||||
@@ -133,6 +138,13 @@ namespace mbts.ui.extensions {
|
||||
return Disposable.Create(() => target.PreviewKeyDown -= callback);
|
||||
}
|
||||
|
||||
public static IObservable<KeyEventArgs> SubscribePreviewKeyDown(this UIElement target) =>
|
||||
Observable.Create<KeyEventArgs>(o => {
|
||||
target.PreviewKeyDown += on_preview_key_down;
|
||||
return Disposable.Create(() => target.PreviewKeyDown -= on_preview_key_down);
|
||||
void on_preview_key_down(object sender, KeyEventArgs e) => o.OnNext(e);
|
||||
});
|
||||
|
||||
public static IDisposable SubscribeIsKeyboardFocusWithinChanged(this UIElement target, DependencyPropertyChangedEventHandler callback) {
|
||||
target.IsKeyboardFocusWithinChanged += callback;
|
||||
return Disposable.Create(() => target.IsKeyboardFocusWithinChanged -= callback);
|
||||
@@ -213,4 +225,4 @@ namespace mbts.ui.extensions {
|
||||
!DoubleHelper.IsNaNOrInfinityOr(curr, 0) && !DoubleHelper.IsNaNOrInfinityOr(max, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,6 +211,32 @@ namespace mbts.ui {
|
||||
foreach (var dp in d_props)
|
||||
BindingOperations.ClearBinding(d_obj, dp);
|
||||
}
|
||||
|
||||
public static IDisposable attach_binding(this DependencyObject target, DependencyProperty target_property, string path, FrameworkElement source_view, IValueConverter converter = null) {
|
||||
object source_vm = source_view.DataContext;
|
||||
try_reset_binding();
|
||||
source_view.DataContextChanged += on_data_context_changed;
|
||||
return Disposable.Create(() => {
|
||||
try {
|
||||
source_view.DataContextChanged -= on_data_context_changed;
|
||||
} catch (Exception ex) {
|
||||
Log.Ui.warn($"Error detaching from {nameof(source_view)}.{nameof(FrameworkElement.DataContextChanged)} event: {ex}.");
|
||||
}
|
||||
});
|
||||
void on_data_context_changed(object sender, DependencyPropertyChangedEventArgs e) {
|
||||
source_vm = e.NewValue;
|
||||
try_reset_binding();
|
||||
}
|
||||
void try_reset_binding() {
|
||||
if (source_vm != null)
|
||||
target.reset_binding(target_property, new Binding(path) { Source = source_vm, Converter = converter });
|
||||
}
|
||||
}
|
||||
|
||||
public static void reset_binding(this DependencyObject target, DependencyProperty target_property, BindingBase binding) {
|
||||
BindingOperations.ClearBinding(target, target_property);
|
||||
BindingOperations.SetBinding(target, target_property, binding);
|
||||
}
|
||||
|
||||
public static void TryRaiseCanExecuteChanged(this ICommand command) {
|
||||
if (command == null) return;
|
||||
|
||||
@@ -593,6 +593,7 @@
|
||||
<Setter Property="Content" Value="Cancel" />
|
||||
<Setter Property="IsCancel" Value="True" />
|
||||
<Setter Property="u:Behavior.CloseWindow" Value="True" />
|
||||
<Setter Property="ToolTip" Value="Cancel (Esc)"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="ComputeButton" TargetType="Button" BasedOn="{StaticResource {x:Type Button}}" x:Shared="False">
|
||||
|
||||
@@ -20,6 +20,18 @@
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<Style x:Key="DisabledPlaceholderComboBox" TargetType="ComboBox">
|
||||
<Setter Property="IsEnabled" Value="False"/>
|
||||
<Setter Property="Foreground" Value="Gray"/>
|
||||
<Setter Property="SelectedIndex" Value="0"/>
|
||||
<Setter Property="ItemsSource">
|
||||
<Setter.Value>
|
||||
<x:ArrayExtension Type="{x:Type sys:String}">
|
||||
<sys:String>(N/A)</sys:String>
|
||||
</x:ArrayExtension>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
<!-- Removed ExpirationLabel
|
||||
<Style x:Key="ExpirationFilter" TargetType="control:MultiSelectDropDown" BasedOn="{StaticResource {x:Type control:MultiSelectDropDown}}">
|
||||
<Setter Property="ItemTemplate">
|
||||
|
||||
@@ -89,6 +89,10 @@ namespace mbts.ui.shell
|
||||
}
|
||||
|
||||
public Dispatcher get_dispatcher(ISharedWindow window) {
|
||||
if (window.thread_id == null) {
|
||||
window.thread_init();
|
||||
return Dispatcher.CurrentDispatcher;
|
||||
}
|
||||
var uid = get(window.thread_id);
|
||||
return attach_window(uid, window);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.spark;
|
||||
using bts.utils;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui;
|
||||
using mbts.ui.utilities;
|
||||
using mbts.ui.utility;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
@@ -84,6 +86,9 @@ namespace mbts.spark {
|
||||
public static TimeInForce time_in_force(this OrderRecordImmutable order) =>
|
||||
order.HasDef ? order.Def.TimeInForce : TimeInForce.Undefined;
|
||||
|
||||
public static uint attached_target_order_key(this OrderRecordImmutable order) =>
|
||||
order.HasDef ? order.Def.AttachTargetOrderKey : 0;
|
||||
|
||||
public static bool is_exchange_order(this OrderRecordImmutable order) {
|
||||
if (order.HasDef && order.Def.HasCustomDef) {
|
||||
switch (order.Def.CustomDef.CustomCase) {
|
||||
@@ -96,6 +101,24 @@ namespace mbts.spark {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool can_attach(this OrderRecordImmutable order) =>
|
||||
order.working_quantity() > 0;
|
||||
|
||||
public static bool can_be_attached_to(this OrderRecordImmutable order) {
|
||||
if (order.HasDef && order.Def.HasCustomDef) {
|
||||
// TODO: implement with real logic (this is impl is a placeholder for development purposes)
|
||||
switch (order.Def.CustomDef.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Iceberg:
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool can_detach(this OrderRecordImmutable order) =>
|
||||
order.attached_target_order_key() != 0;
|
||||
|
||||
public static string display_name(this OrderSecurityAlgoMeta order_tuple) =>
|
||||
display_name(order_tuple.order, order_tuple.algo);
|
||||
|
||||
@@ -141,6 +164,20 @@ namespace mbts.spark {
|
||||
public static string short_description(this OrderRecordImmutable order, ContractSpecificationImmutable? spec) =>
|
||||
$"{(order.HasDef ? order.Def.Side.ToString() : "(unknown-side)")} {(order.HasState ? ($@"{spec.format(order.State.Price)} @ {order.State.Quantity}") : "(unknown-price) @ (unknown-quantity)")}";
|
||||
|
||||
public static string short_description(this OrderRecordImmutable order, SecurityImmutable? security) {
|
||||
var sb = new StringBuilderEx("[", order.Key.ToString(), "] ", "[", camel_to_title_case(order.status().ToString()), "] ", security.display_name(), " ", order.side().ToString(), " ");
|
||||
if (order.HasState) {
|
||||
sb.Add(order.State.Quantity.ToString(), " @ ", security.spec_or_default().format(order.State.Price), " (filled ", order.State.FilledQuantity.ToString());
|
||||
if (order.State.FilledQuantity > 0) {
|
||||
sb.Add(" @ ", security.spec_or_default().format(order.State.AvgPrice));
|
||||
}
|
||||
sb.Add(")");
|
||||
} else {
|
||||
sb.Add("(unknown-quantity) @ (unknown-price)");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static bool has_parent(this OrderRecordImmutable order) =>
|
||||
order.ParentKey != 0;
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ namespace mbts.spark {
|
||||
public bool has_parent => _orders.Values.Any( order => order.has_parent());
|
||||
public bool has_phantom_price => !double.IsNaN(_phantom_price);
|
||||
|
||||
public ICommand detach_command => new DelegateCommand(() => _execute_command(_crack_order), () => !has_parent);
|
||||
public ICommand detach_command => new DelegateCommand(() => _execute_command(_detach_order), () => !has_parent);
|
||||
public ICommand cancel_command => new DelegateCommand(() => _execute_command(_cancel_order));
|
||||
//public ICommand modify_command => new DelegateCommand(() => _execute_command(() => _bus.post(SparkTags.ShowModifyOrderTag, _order.Key)), () => !has_parent);
|
||||
public event Action command_executed;
|
||||
@@ -199,9 +199,9 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
|
||||
void _crack_order() {
|
||||
void _detach_order() {
|
||||
var distinct_parent_keys = _orders.Values.Where(o => o.has_parent()).Select(x => x.ParentKey).Distinct();
|
||||
model.order_gateway.crack_orders(distinct_parent_keys).ignore();
|
||||
model.order_gateway.detach_orders(distinct_parent_keys).ignore();
|
||||
}
|
||||
|
||||
public async void try_set_price(OrderRecordImmutable order, double new_price) {
|
||||
@@ -252,20 +252,20 @@ namespace mbts.spark {
|
||||
// | Cancel | Qty | Price | ...
|
||||
// ------------------------
|
||||
//
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.Price.ToString());
|
||||
var price_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Price.ToString());
|
||||
var price_index = obvm.columns.indexOf(price_col);
|
||||
if (price_index > -1) {
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.EditablePrice.ToString());
|
||||
var existing_editprice_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditablePrice.ToString());
|
||||
var existing_editprice_index = obvm.columns.indexOf(existing_editprice_col);
|
||||
if (existing_editprice_index == -1) {
|
||||
// insert the edit price column at the beginning
|
||||
obvm.columns.Insert(0, new OrderBookVM.EditablePriceColumn());
|
||||
}
|
||||
}
|
||||
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.Size.ToString());
|
||||
var qty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.Size.ToString());
|
||||
var qty_index = obvm.columns.indexOf(qty_col);
|
||||
if (qty_index > -1) {
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookView.Columns.EditableQty.ToString());
|
||||
var existing_editqty_col = obvm.columns.FirstOrDefault(x => x.id == OrderBookGridView.ColumnKeys.EditableQty.ToString());
|
||||
var existing_editqty_index = obvm.columns.indexOf(existing_editqty_col);
|
||||
if (existing_editqty_index == -1) {
|
||||
// insert the edit quantity column at the beginning
|
||||
|
||||
@@ -9,14 +9,12 @@ using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace mbts.spark {
|
||||
internal class PAndLWindow : SparkWindow<PAndLWindowFactory.PAndLStartupData>, ISharedWindow {
|
||||
const string _window_type = "PAndL", PNL_THREAD_ID = "PnL";
|
||||
const string _window_type = "PAndL";
|
||||
readonly IWindowSettingsManager<GridSettings> _settings_manager;
|
||||
PAndLWindowFactory.PAndLStartupData _startup_data;
|
||||
PAndLModel _model;
|
||||
@@ -31,6 +29,7 @@ namespace mbts.spark {
|
||||
_startup_data = startup_data;
|
||||
_settings_manager = settings_manager;
|
||||
Icon.Freeze();
|
||||
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().pnl ? "PnL" : null;
|
||||
}
|
||||
|
||||
public override string window_type => _window_type;
|
||||
@@ -50,7 +49,7 @@ namespace mbts.spark {
|
||||
+ LAUNCHER_BUTTON_BORDER_WIDTH
|
||||
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
|
||||
|
||||
public string thread_id => PNL_THREAD_ID;
|
||||
public string thread_id { get; }
|
||||
|
||||
public void thread_init() {
|
||||
_vm = new PAndLVM(_bus, _model, _startup_data, _settings_manager);
|
||||
@@ -84,4 +83,4 @@ namespace mbts.spark {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,8 +63,11 @@ namespace bts.spark {
|
||||
public virtual double MinWidth => 400;
|
||||
public virtual double MinHeight => Math.Max(ABSOLUTE_MIN_HEIGHT, TITLE_PANEL_HEIGHT + COLUMN_HEADER_HEIGHT + GRID_FOOTER_HEIGHT + Commands.Count() * SPARK_MENU_ITEM_HEIGHT);
|
||||
|
||||
public ShellWindowUI GetContent() =>
|
||||
new ShellWindowUI(new SparkWindowView(_bus, Commands, Content));
|
||||
public ShellWindowUI GetContent() {
|
||||
var parent_view = new SparkWindowView(_bus, Commands, Content);
|
||||
OnParentViewCreated(parent_view);
|
||||
return new ShellWindowUI(parent_view);
|
||||
}
|
||||
|
||||
public void GetState(Stream save_here) =>
|
||||
_save.save(SaveState, save_here);
|
||||
@@ -82,5 +85,7 @@ namespace bts.spark {
|
||||
}
|
||||
|
||||
public abstract void Dispose();
|
||||
|
||||
protected virtual void OnParentViewCreated(SparkWindowView parent_view) { }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mbts="clr-namespace:mbts.ui"
|
||||
xmlns:local="clr-namespace:bts.view"
|
||||
xmlns:bv="clr-namespace:bts.view"
|
||||
mc:Ignorable="d"
|
||||
d:DesignHeight="300" d:DesignWidth="300">
|
||||
<Border BorderThickness="0 1 0 0" BorderBrush="{mbts:BTSColor UI.form_content_border}">
|
||||
@@ -42,7 +42,14 @@
|
||||
<ContextMenu.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="Icon" Value="{StaticResource icon}"></Setter>
|
||||
<Setter Property="Header" Value="{Binding title}" />
|
||||
<Setter Property="Header">
|
||||
<Setter.Value>
|
||||
<MultiBinding Converter="{x:Static bv:SparkWindowView.HeaderConverter}">
|
||||
<Binding Path="title"/>
|
||||
<Binding Path="key_gesture"/>
|
||||
</MultiBinding>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter Property="Command" Value="{Binding command}" />
|
||||
</Style>
|
||||
</ContextMenu.ItemContainerStyle>
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.converters;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace bts.view {
|
||||
public partial class SparkWindowView : UserControl {
|
||||
public static readonly IMultiValueConverter HeaderConverter =
|
||||
ValueConverter.create<string, KeyGesture, string>((title, key_gesture) =>
|
||||
key_gesture == null ? title : $"{title} ({key_gesture.GetDisplayStringForCulture(CultureInfo.CurrentCulture)})");
|
||||
|
||||
SparkMenu _menu;
|
||||
public SparkWindowView(IBus bus, IEnumerable<SparkCommand> commands, UIElement content) {
|
||||
InitializeComponent();
|
||||
@@ -26,6 +33,10 @@ namespace bts.view {
|
||||
_menu.BorderThickness = new Thickness(0, 0, 0, 0);
|
||||
_menu.ShowText = false;
|
||||
_grid.Children.Add(_menu);
|
||||
foreach (var cmd in commands) {
|
||||
if (cmd.key_gesture != null)
|
||||
InputBindings.Add(new KeyBinding(cmd.ICommand(bus), cmd.key_gesture));
|
||||
}
|
||||
if (ContextMenu != null) {
|
||||
// ObservableCollection to avoid WPF Binding Leak
|
||||
var context_menu_items_view = new ListCollectionView(new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)))) {
|
||||
@@ -44,4 +55,4 @@ namespace bts.view {
|
||||
_menu.ShowText = !_menu.ShowText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,45 @@
|
||||
using bts.api;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace mbts.spark {
|
||||
class BBOVM : DisposableViewModelBase {
|
||||
readonly Property<SecurityImmutable> _security;
|
||||
readonly Property<double> _bid, _ask;
|
||||
readonly Property<uint> _bid_size, _ask_size;
|
||||
IDisposable _md_poll;
|
||||
SecurityMD? _security_md;
|
||||
double _bid, _ask;
|
||||
uint _bid_size, _ask_size;
|
||||
ulong _time;
|
||||
|
||||
public BBOVM(IObservable<SecurityMD> security_md)
|
||||
: this(security_md.Select(smd => smd.security).OnDispatcher(), new SecurityMDPump(security_md.Select(smd => smd.md))) { }
|
||||
|
||||
public BBOVM(IObservable<SecurityImmutable> security, ISecurityMDPump md_pump) {
|
||||
_security = backing(nameof(security), security, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
_bid = backing(nameof(bid), md_pump.bid_price, double.NaN);
|
||||
_ask = backing(nameof(ask), md_pump.ask_price, double.NaN);
|
||||
_bid_size = backing(nameof(bid_size), md_pump.bid_size, (uint)0);
|
||||
_ask_size = backing(nameof(ask_size), md_pump.ask_size, (uint)0);
|
||||
public SecurityImmutable? security => _security_md?.security;
|
||||
public SecurityMD? security_md {
|
||||
get => _security_md;
|
||||
set {
|
||||
if (_security_md != value) {
|
||||
_security_md = value;
|
||||
RaisePropertyChanged(nameof(security));
|
||||
_begin_polling();
|
||||
}
|
||||
}
|
||||
}
|
||||
public double bid => _bid;
|
||||
public double ask => _ask;
|
||||
public uint bid_size => _bid_size;
|
||||
public uint ask_size => _ask_size;
|
||||
|
||||
public SecurityImmutable security => _security.Value;
|
||||
public double bid => _bid.Value;
|
||||
public double ask => _ask.Value;
|
||||
public uint bid_size => _bid_size.Value;
|
||||
public uint ask_size => _ask_size.Value;
|
||||
void _begin_polling() {
|
||||
Disposable.dispose(ref _md_poll);
|
||||
if (security_md.HasValue) {
|
||||
var md = security_md.Value.md;
|
||||
_md_poll = WorkLoop.Start($"{nameof(BBOVM)} md poll.", poll);
|
||||
void poll() {
|
||||
if (md.get_bbo(ref _time, out co2.NativePriceSize bid, out co2.NativePriceSize ask)) {
|
||||
Set(ref _bid, bid.price, nameof(bid));
|
||||
Set(ref _bid_size, bid.size, nameof(bid_size));
|
||||
Set(ref _ask, ask.price, nameof(ask));
|
||||
Set(ref _ask_size, ask.size, nameof(ask_size));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,7 +241,7 @@ namespace mbts.spark.utilities {
|
||||
case CustomOrderDefinition.CustomOneofCase.Pricer: return new OrderMetadata { KeyBinding = "C", Color = BTSColors.Orders.pricer_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Offset: return new OrderMetadata { KeyBinding = "O", Color = BTSColors.Orders.tick_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Trigger: return new OrderMetadata { KeyBinding = "T", Color = BTSColors.Orders.trigger_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: goto default;
|
||||
case CustomOrderDefinition.CustomOneofCase.Spreader: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.spreader_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic: return new OrderMetadata { KeyBinding = "", Color = BTSColors.Orders.top_order };
|
||||
case CustomOrderDefinition.CustomOneofCase.None: goto default;
|
||||
default: return _default_order_metadata;
|
||||
@@ -308,6 +308,37 @@ namespace mbts.spark.utilities {
|
||||
f.FieldType == FieldType.Message && f.MessageType.ClrType == typeof(CustomOrderDefinition);
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_children(CustomOrderDefinition algo) {
|
||||
switch (algo.CustomCase) {
|
||||
case CustomOrderDefinition.CustomOneofCase.Dynamic:
|
||||
foreach (var child in enumerate_dynamic_algo_children(algo.Dynamic))
|
||||
yield return child;
|
||||
break;
|
||||
default:
|
||||
foreach (var cad in get_child_algo_data(algo)) {
|
||||
if (cad.get_child(algo) is CustomOrderDefinition child)
|
||||
yield return child;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_dynamic_algo_children(DynamicOrderDefinition dynamic_algo) {
|
||||
foreach (var kv in dynamic_algo.OrderParams) {
|
||||
if (kv.Value.ValueCase == DynamicOrderParam.ValueOneofCase.AsOrderDef)
|
||||
yield return kv.Value.AsOrderDef;
|
||||
}
|
||||
}
|
||||
|
||||
public static IEnumerable<CustomOrderDefinition> enumerate_algo_hierarchy(CustomOrderDefinition algo) {
|
||||
yield return algo;
|
||||
foreach (var child in enumerate_algo_children(algo)) {
|
||||
yield return child;
|
||||
foreach (var grand_child in enumerate_algo_hierarchy(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinitionImmutable defi) =>
|
||||
get_child_algo_data(defi.CloneToMutable());
|
||||
|
||||
@@ -332,6 +363,13 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public static CustomOrderDefinition create_algo(CustomOrderDefinition.CustomOneofCase algo_type) {
|
||||
var algo = new CustomOrderDefinition();
|
||||
if (_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData desc))
|
||||
desc.type_case_descriptor.Accessor.SetValue(algo, ProtoHelper.mutable_factory(ProtoHelper.get_field_type(desc.type_case_descriptor)));
|
||||
return algo;
|
||||
}
|
||||
|
||||
public static IReadOnlyList<ChildAlgoData> get_child_algo_data(CustomOrderDefinition.CustomOneofCase algo_type) =>
|
||||
_algo_type_case_descriptor_data.TryGetValue(algo_type, out AlgoTypeCaseDescriptorData d) ? d.child_algo_data : Array.Empty<ChildAlgoData>();
|
||||
|
||||
@@ -359,6 +397,9 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
}
|
||||
|
||||
public static AlgoFormats get_algo_format(Security.ContractOneofCase? security_type) =>
|
||||
security_type.HasValue ? get_algo_format(security_type.Value) : AlgoFormats.Exchange;
|
||||
|
||||
public static AlgoFormats get_algo_format(Security.ContractOneofCase security_type) =>
|
||||
security_type == Security.ContractOneofCase.Synthetic ? AlgoFormats.Spreader : AlgoFormats.Exchange;
|
||||
|
||||
@@ -852,6 +893,7 @@ namespace mbts.spark.utilities {
|
||||
}
|
||||
|
||||
public static class Defaults {
|
||||
// param types
|
||||
public static QuantityMeasure QuantityMeasure => new QuantityMeasure { Absolute = 1 };
|
||||
public static PriceType PriceType => new PriceType { Status = PriceType.Types.Status.Bid };
|
||||
public static PriceOffset PriceOffset => new PriceOffset { PriceType = PriceType };
|
||||
@@ -865,6 +907,10 @@ namespace mbts.spark.utilities {
|
||||
public static BTSStopOrder BTSStopOrder => new BTSStopOrder { LimitOrder = new LimitOrder(), Price = 1, PriceTrigger = PriceTrigger, OptionalTimedParameters = TimeWindow, OptionalWithatickParameters = WithATick };
|
||||
public static BTSIcebergOrder BTSIcebergOrder => new BTSIcebergOrder { DisplayQuantity = QuantityMeasure, MarketOffset = PriceOffset, PriceTrigger = PriceTrigger, TimedWindowParameters = TimeWindow, WithatickParameters = WithATick };
|
||||
|
||||
// core data types
|
||||
public static AlgoData AlgoData() => new AlgoData(CustomOrderDefinitionImmutable());
|
||||
public static AlgoData AlgoData(CustomOrderDefinition.CustomOneofCase algo_type) => new AlgoData(CustomOrderDefinitionImmutable(algo_type));
|
||||
|
||||
public static OrderDefinition OrderDefinition => new OrderDefinition { CustomDef = new CustomOrderDefinition() };
|
||||
public static OrderRecord OrderRecord => new OrderRecord {
|
||||
Def = OrderDefinition,
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace mbts.ui.utility {
|
||||
@@ -41,6 +42,18 @@ namespace mbts.ui.utility {
|
||||
return Activator.CreateInstance(enum_type);
|
||||
}
|
||||
|
||||
public static T try_convert<T>(object value) {
|
||||
var numeric_type = Enum.GetUnderlyingType(typeof(T));
|
||||
var numeric_value = Convert.ChangeType(value, numeric_type);
|
||||
return (T)Convert.ChangeType(numeric_value, typeof(T));
|
||||
}
|
||||
|
||||
public static Func<object, object, bool> get_has_flags_delegate(Type enum_type) =>
|
||||
EnumDescriptorStore.descriptor(enum_type).has_flags;
|
||||
|
||||
public static bool has_flags<TEnum>(TEnum value, TEnum flags) =>
|
||||
EnumDescriptorStore<TEnum>.descriptor.has_flags(value, flags);
|
||||
|
||||
public static string get_display_name(object enum_obj, TextCapitalizationFormat capitalization_format = TextCapitalizationFormat.Default) {
|
||||
string str = enum_obj.ToString();
|
||||
MemberInfo[] mi = enum_obj.GetType().GetMember(str);
|
||||
@@ -58,13 +71,6 @@ namespace mbts.ui.utility {
|
||||
return mi.Length > 0 ? mi[0].GetCustomAttribute<TAttribute>() : null;
|
||||
}
|
||||
|
||||
public static bool includes_flag<TEnum>(TEnum flags, TEnum value) {
|
||||
if (!typeof(TEnum).IsEnum) throw new InvalidOperationException("Type is not an Enum.");
|
||||
long lflags = Convert.ToInt64(flags);
|
||||
long lvalue = Convert.ToInt64(value);
|
||||
return (lflags & lvalue) == lvalue;
|
||||
}
|
||||
|
||||
// though it isn't hard to write "flags & TEnum.Foo != 0," split_flags offers a less error-prone alternative in terms of typeos.
|
||||
// (less likely that code calling this method will have a typeo, than code checking flags in the traditional manner described above)
|
||||
public static IEnumerable<TEnum> split_flags<TEnum>(TEnum flags) where TEnum : struct {
|
||||
@@ -75,5 +81,193 @@ namespace mbts.ui.utility {
|
||||
yield return e;
|
||||
}
|
||||
}
|
||||
|
||||
public static IObservable<T> observe_result<T>(this EnumMaskInt32<T> mask) where T : struct =>
|
||||
Observable.Create<T>(o => {
|
||||
mask.result_changed += notify;
|
||||
o.OnNext(mask.result);
|
||||
return Disposable.Create(() => mask.result_changed -= notify);
|
||||
void notify(int result) =>
|
||||
o.OnNext(mask.result);
|
||||
});
|
||||
|
||||
struct EnumDescriptor {
|
||||
public readonly Func<object, object, bool> has_flags;
|
||||
|
||||
public EnumDescriptor(Func<object, object, bool> has_flags) =>
|
||||
this.has_flags = has_flags;
|
||||
}
|
||||
|
||||
static class EnumDescriptorStore {
|
||||
[ThreadStatic]
|
||||
static Dictionary<Type, EnumDescriptor> _type_index_backing;
|
||||
|
||||
static Dictionary<Type, EnumDescriptor> _type_index =>
|
||||
_type_index_backing ?? (_type_index_backing = new Dictionary<Type, EnumDescriptor>());
|
||||
|
||||
public static EnumDescriptor descriptor(Type enum_type) =>
|
||||
_type_index.GetOrAdd(enum_type, t => (EnumDescriptor)
|
||||
typeof(EnumDescriptorStore<>).MakeGenericType(enum_type)
|
||||
.GetField(nameof(EnumDescriptorStore<int>.descriptor), BindingFlags.Static | BindingFlags.Public)
|
||||
.GetValue(null));
|
||||
}
|
||||
|
||||
static class EnumDescriptorStore<T> {
|
||||
public static readonly EnumDescriptor descriptor;
|
||||
|
||||
static EnumDescriptorStore() {
|
||||
Func<object, object, bool> has_flags;
|
||||
if (!typeof(T).IsEnum) {
|
||||
// N/A
|
||||
has_flags = Impl.as_none_has_flags;
|
||||
}
|
||||
var numeric_type = Enum.GetUnderlyingType(typeof(T));
|
||||
if (numeric_type == typeof(byte)) {
|
||||
// byte
|
||||
has_flags = Impl.as_byte_has_flags;
|
||||
} else if (numeric_type == typeof(short)) {
|
||||
// short
|
||||
has_flags = Impl.as_int16_has_flags;
|
||||
} else if (numeric_type == typeof(int)) {
|
||||
// int
|
||||
has_flags = Impl.as_int32_has_flags;
|
||||
} else if (numeric_type == typeof(long)) {
|
||||
// long
|
||||
has_flags = Impl.as_int64_has_flags;
|
||||
} else if (numeric_type == typeof(ushort)) {
|
||||
// ushort
|
||||
has_flags = Impl.as_uint16_has_flags;
|
||||
} else if (numeric_type == typeof(uint)) {
|
||||
// uint
|
||||
has_flags = Impl.as_uint32_has_flags;
|
||||
} else if (numeric_type == typeof(ulong)) {
|
||||
// ulong
|
||||
has_flags = Impl.as_uint64_has_flags;
|
||||
} else {
|
||||
// ?
|
||||
has_flags = Impl.as_none_has_flags;
|
||||
}
|
||||
descriptor = new EnumDescriptor(has_flags);
|
||||
}
|
||||
|
||||
static class Impl {
|
||||
public static bool as_none_has_flags(object value, object flags) => false;
|
||||
// byte
|
||||
public static bool as_byte_has_flags(object value, object flags) {
|
||||
byte value_as_byte = (byte)Convert.ChangeType(value, typeof(byte));
|
||||
byte flags_as_byte = (byte)Convert.ChangeType(flags, typeof(byte));
|
||||
return (value_as_byte & flags_as_byte) == flags_as_byte;
|
||||
}
|
||||
// short
|
||||
public static bool as_int16_has_flags(object value, object flags) {
|
||||
short value_as_short = (short)Convert.ChangeType(value, typeof(short));
|
||||
short flags_as_short = (short)Convert.ChangeType(flags, typeof(short));
|
||||
return (value_as_short & flags_as_short) == flags_as_short;
|
||||
}
|
||||
// int
|
||||
public static bool as_int32_has_flags(object value, object flags) {
|
||||
int value_as_byte = (int)Convert.ChangeType(value, typeof(int));
|
||||
int flags_as_byte = (int)Convert.ChangeType(flags, typeof(int));
|
||||
return (value_as_byte & flags_as_byte) == flags_as_byte;
|
||||
}
|
||||
// long
|
||||
public static bool as_int64_has_flags(object value, object flags) {
|
||||
long value_as_long = (long)Convert.ChangeType(value, typeof(long));
|
||||
long flags_as_long = (long)Convert.ChangeType(flags, typeof(long));
|
||||
return (value_as_long & flags_as_long) == flags_as_long;
|
||||
}
|
||||
// ushort
|
||||
public static bool as_uint16_has_flags(object value, object flags) {
|
||||
ushort value_as_ushort = (ushort)Convert.ChangeType(value, typeof(ushort));
|
||||
ushort flags_as_ushort = (ushort)Convert.ChangeType(flags, typeof(ushort));
|
||||
return (value_as_ushort & flags_as_ushort) == flags_as_ushort;
|
||||
}
|
||||
// uint
|
||||
public static bool as_uint32_has_flags(object value, object flags) {
|
||||
uint value_as_uint = (uint)Convert.ChangeType(value, typeof(uint));
|
||||
uint flags_as_uint = (uint)Convert.ChangeType(flags, typeof(uint));
|
||||
return (value_as_uint & flags_as_uint) == flags_as_uint;
|
||||
}
|
||||
// ulong
|
||||
public static bool as_uint64_has_flags(object value, object flags) {
|
||||
ulong value_as_ulong = (ulong)Convert.ChangeType(value, typeof(ulong));
|
||||
ulong flags_as_ulong = (ulong)Convert.ChangeType(flags, typeof(ulong));
|
||||
return (value_as_ulong & flags_as_ulong) == flags_as_ulong;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface IMaskIndexInt32<T> {
|
||||
T from_int(int i);
|
||||
int to_int(T value);
|
||||
}
|
||||
|
||||
class MaskIndexInt32<T> : IMaskIndexInt32<T> {
|
||||
public readonly IReadOnlyDictionary<int, T> lookup;
|
||||
public readonly IReadOnlyDictionary<T, int> reverse_lookup;
|
||||
|
||||
public MaskIndexInt32(IReadOnlyList<T> items) {
|
||||
lookup = items.Select((v, i) => (v, i)).ToDictionary(t => t.i, t => t.v);
|
||||
reverse_lookup = lookup.ToDictionary(kv => kv.Value, kv => kv.Key);
|
||||
}
|
||||
|
||||
public T from_int(int i) =>
|
||||
lookup.TryGetValue(i, out T value) ? value : default;
|
||||
|
||||
public int to_int(T value) =>
|
||||
reverse_lookup.TryGetValue(value, out int i) ? i : 0;
|
||||
}
|
||||
|
||||
static class EnumMaskIndexInt32<T> {
|
||||
static readonly MaskIndexInt32<T> _instance = new MaskIndexInt32<T>(EnumHelper.get_values<T>().ToList());
|
||||
|
||||
public static T from_int(int i) =>
|
||||
_instance.lookup.TryGetValue(i, out T value) ? value : default;
|
||||
|
||||
public static int to_int(T value) =>
|
||||
_instance.reverse_lookup[value];
|
||||
}
|
||||
|
||||
class MaskInt32 {
|
||||
int _mask = int.MaxValue, _raw;
|
||||
|
||||
public event Action<int> result_changed;
|
||||
|
||||
public int mask {
|
||||
get => _mask;
|
||||
set {
|
||||
using (_update())
|
||||
_mask = value;
|
||||
}
|
||||
}
|
||||
public int raw {
|
||||
get => _raw;
|
||||
set {
|
||||
using (_update())
|
||||
_raw = value;
|
||||
}
|
||||
}
|
||||
public int result => _raw & _mask;
|
||||
|
||||
IDisposable _update() {
|
||||
int old_result = result;
|
||||
return Disposable.Create(() => {
|
||||
if (result != old_result)
|
||||
result_changed?.Invoke(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sealed class EnumMaskInt32<T> : MaskInt32 where T : struct {
|
||||
public new T mask {
|
||||
get => (T)(object)base.mask;
|
||||
set => base.mask = (int)(object)value;
|
||||
}
|
||||
public new T raw {
|
||||
get => (T)(object)base.raw;
|
||||
set => base.raw = (int)(object)value;
|
||||
}
|
||||
public new T result => (T)(object)base.result;
|
||||
}
|
||||
}
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
namespace mbts.ui.utility {
|
||||
class PropertyUpdateGate {
|
||||
readonly string _reentrancy_error_msg;
|
||||
|
||||
public PropertyUpdateGate(string reentrancy_error_msg = "Already updating property.") =>
|
||||
_reentrancy_error_msg = reentrancy_error_msg;
|
||||
|
||||
public bool is_updating { get; private set; }
|
||||
|
||||
public IDisposable update() {
|
||||
if (is_updating) throw new InvalidOperationException("Already updating property.");
|
||||
return _update();
|
||||
}
|
||||
public IDisposable update() =>
|
||||
is_updating ? throw new InvalidOperationException(_reentrancy_error_msg) : _update();
|
||||
|
||||
public bool try_update(Action update) {
|
||||
if (!is_updating) {
|
||||
|
||||
@@ -124,6 +124,7 @@ namespace mbts.ui.utility {
|
||||
_factory = factory;
|
||||
_key = key;
|
||||
_eq_comparer = EqualityComparer.Create(key);
|
||||
_comparer = comparer;
|
||||
_sorted_items = new SortedList<TModel>(comparer ?? throw new ArgumentNullException(nameof(comparer)));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using mbts.ui.shell;
|
||||
using mbts.ui.utility;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace mbts.ui {
|
||||
class StateDefinitionCollection : ObservableCollection<StateDefinition> { }
|
||||
|
||||
[ContentProperty(nameof(State))]
|
||||
class StateDefinition : DependencyObject {
|
||||
public static readonly DependencyProperty StateProperty =
|
||||
WPFHelper.create_dp<string, StateDefinition>(nameof(State), (o, oldval, newval) => o.StateChanged?.Invoke(oldval, newval));
|
||||
|
||||
public string State { get => GetValue(StateProperty) as string; set => SetValue(StateProperty, value); }
|
||||
|
||||
public event PropertyChange<string> StateChanged;
|
||||
}
|
||||
|
||||
[ContentProperty(nameof(StateDefinitions))]
|
||||
class StateGroupsManager : DependencyObject {
|
||||
IDisposable _state_definitions_hook;
|
||||
|
||||
#region Target
|
||||
public static readonly DependencyProperty TargetProperty =
|
||||
WPFHelper.create_dp<FrameworkElement, StateGroupsManager>(nameof(Target), (o, v) => _reset(o, v, o.StateDefinitions));
|
||||
|
||||
public FrameworkElement Target { get => GetValue(TargetProperty) as FrameworkElement; set => SetValue(TargetProperty, value); }
|
||||
#endregion
|
||||
|
||||
#region StateDefinitions
|
||||
public static readonly DependencyProperty StateDefinitionsProperty =
|
||||
WPFHelper.create_dp<StateDefinitionCollection, StateGroupsManager>(nameof(StateDefinitions), (o, v) => _reset(o, o.Target, v));
|
||||
|
||||
public StateDefinitionCollection StateDefinitions { get => GetValue(StateDefinitionsProperty) as StateDefinitionCollection; set => SetValue(StateDefinitionsProperty, value); }
|
||||
#endregion
|
||||
|
||||
static void _reset(StateGroupsManager state_group, FrameworkElement target, StateDefinitionCollection state_defs) {
|
||||
if (state_group == null)
|
||||
return;
|
||||
Disposable.dispose(ref state_group._state_definitions_hook);
|
||||
if (state_defs != null) {
|
||||
state_group._state_definitions_hook = ObservableAutoWrapper.connect_item_hooks(state_defs, sd => {
|
||||
StateManager.GoToState(target, sd.State);
|
||||
sd.StateChanged += on_state_changed;
|
||||
return Disposable.Create(() => sd.StateChanged -= on_state_changed);
|
||||
});
|
||||
void on_state_changed(string oldval, string newval) => StateManager.GoToState(target, newval);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace mbts.ui
|
||||
{
|
||||
// With this utility we can create a ViewModel with a ViewState (string) property
|
||||
// Then in the View simply set StateManager.VisualState = "{binding ViewState}"
|
||||
//<VisualStateManager.VisualStateGroups>
|
||||
@@ -20,8 +63,15 @@ namespace mbts.ui
|
||||
// </VisualState>
|
||||
// </VisualStateGroup>
|
||||
//</VisualStateManager.VisualStateGroups>
|
||||
public class StateManager : DependencyObject
|
||||
{
|
||||
class StateManager : DependencyObject {
|
||||
#region Groups
|
||||
public static readonly DependencyProperty GroupsProperty =
|
||||
DependencyProperty.RegisterAttached("Groups", typeof(StateGroupsManager), typeof(StateManager), new PropertyMetadata(null));
|
||||
|
||||
public static StateGroupsManager GetGroups(DependencyObject d) => d.GetValue(GroupsProperty) as StateGroupsManager;
|
||||
public static void SetGroups(DependencyObject d, StateGroupsManager value) => d.SetValue(GroupsProperty, value);
|
||||
#endregion
|
||||
|
||||
public static string GetVisualState(DependencyObject obj) {
|
||||
return (string)obj.GetValue(VisualStateProperty);
|
||||
}
|
||||
@@ -68,7 +118,7 @@ namespace mbts.ui
|
||||
static void GoToState(object o, DependencyPropertyChangedEventArgs args) =>
|
||||
GoToState(o as FrameworkElement, args.NewValue as string);
|
||||
|
||||
static void GoToState(FrameworkElement frameworkElement, string state) {
|
||||
public static void GoToState(FrameworkElement frameworkElement, string state) {
|
||||
if (frameworkElement != null && !string.IsNullOrEmpty(state)) {
|
||||
if (frameworkElement.IsLoaded) {
|
||||
try {
|
||||
|
||||
@@ -136,6 +136,7 @@ namespace mbts.ui.utility.text {
|
||||
public static class RegexFilters {
|
||||
public static readonly Regex
|
||||
PriceInput = new Regex(@"^-?\d*((-|\.)\d*)?$"),
|
||||
Decimal = new Regex(@"^-?\d*\.?\d*$"),
|
||||
Integer = new Regex(@"^\d*$");
|
||||
|
||||
public static class Time {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using bts.utils;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
@@ -277,6 +278,53 @@ namespace bts.api.core {
|
||||
internal Builder to_builder() =>
|
||||
new Builder(this);
|
||||
|
||||
public static class FilterHelper {
|
||||
internal static TableUpdate<T, TKey> on_source_changed(TableUpdate<T, TKey> filtered_target, IEnumerable<T> raw_added, IEnumerable<Change<T>> raw_changes, IEnumerable<T> raw_removed, Func<T, bool> predicate) {
|
||||
var builder = filtered_target._state.ToBuilder();
|
||||
var added = new List<T>(raw_added.Where(predicate));
|
||||
var removed = new List<T>(raw_removed.Where(predicate));
|
||||
var changes = new List<Change<T>>();
|
||||
foreach (var ch in raw_changes) {
|
||||
if (predicate(ch.Old)) {
|
||||
if (predicate(ch.New)) {
|
||||
changes.Add(ch);
|
||||
} else {
|
||||
removed.Add(ch.Old);
|
||||
}
|
||||
} else if (predicate(ch.New)) {
|
||||
added.Add(ch.New);
|
||||
}
|
||||
}
|
||||
var key = filtered_target.key_extractor;
|
||||
builder.RemoveRange(removed.Select(key));
|
||||
foreach (var ch in changes)
|
||||
builder[key(ch.New)] = ch.New;
|
||||
builder.AddRange(added.Select(v => new KeyValuePair<TKey, T>(key(v), v)));
|
||||
return new TableUpdate<T, TKey>(builder.ToImmutable(), added, changes, removed, key);
|
||||
}
|
||||
|
||||
internal static TableUpdate<T, TKey> on_predicate_changed(TableUpdate<T, TKey> filtered_target, IReadOnlyDictionary<TKey, T> unfiltered_state, Func<T, bool> old_predicate, Func<T, bool> new_predicate) {
|
||||
var added = new List<T>();
|
||||
var removed = new List<T>();
|
||||
foreach (var kv in unfiltered_state) {
|
||||
if (old_predicate(kv.Value)) {
|
||||
if (new_predicate(kv.Value)) {
|
||||
continue;
|
||||
} else {
|
||||
removed.Add(kv.Value);
|
||||
}
|
||||
} else if (new_predicate(kv.Value)) {
|
||||
added.Add(kv.Value);
|
||||
}
|
||||
}
|
||||
var builder = filtered_target._state.ToBuilder();
|
||||
var key = filtered_target.key_extractor;
|
||||
builder.RemoveRange(removed.Select(key));
|
||||
builder.AddRange(added.Select(v => new KeyValuePair<TKey, T>(key(v), v)));
|
||||
return new TableUpdate<T, TKey>(builder.ToImmutable(), added, Array.Empty<Change<T>>(), removed, key);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This class is used to aggregate a series of changes into a single update.
|
||||
/// </summary>
|
||||
@@ -512,6 +560,35 @@ namespace bts.api.core {
|
||||
public static IObservable<TableUpdate<T, TKey>> Filter<T, TKey>(this IObservable<TableUpdate<T, TKey>> source, Func<T, bool> predicate, Func<T, TKey> key) where TKey : IEquatable<TKey> =>
|
||||
source.Scan(TableUpdate<T, TKey>.mk_empty(key), (src, tgt) => _apply_predicate(src, tgt, predicate, key));
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> Filter<T, TKey>(this IObservable<TableUpdate<T, TKey>> table_source, IObservable<Func<T, bool>> predicate_source, Func<T, TKey> key) where TKey : IEquatable<TKey> =>
|
||||
Filter(table_source, predicate_source, key, _ => true);
|
||||
|
||||
public static IObservable<TableUpdate<T, TKey>> Filter<T, TKey>(this IObservable<TableUpdate<T, TKey>> table_source, IObservable<Func<T, bool>> predicate_source, Func<T, TKey> key, Func<T, bool> init_predicate) where TKey : IEquatable<TKey> =>
|
||||
table_source.AggregateLatest(predicate_source,
|
||||
(tout, tin, pred) => TableUpdate<T, TKey>.FilterHelper.on_source_changed(tout, tin.New.added, tin.New.changes, tin.New.removed, pred),
|
||||
(tout, tin, pred) => TableUpdate<T, TKey>.FilterHelper.on_predicate_changed(tout, tin.state, pred.Old, pred.New),
|
||||
TableUpdate<T, TKey>.mk_empty(key),
|
||||
TableUpdate<T, TKey>.mk_empty(key),
|
||||
init_predicate);
|
||||
|
||||
public static IObservable<TAggregate> AggregateLatest<TLeft, TRight, TAggregate>(this IObservable<TLeft> source, IObservable<TRight> right,
|
||||
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left, Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right, TAggregate seed, TLeft init_left = default, TRight init_right = default) =>
|
||||
Observable.Create<TAggregate>(o => {
|
||||
Log.DataFlow.diag($"Create AggregateLatest (Left: {typeof(TLeft)}, Right: {typeof(TRight)}).");
|
||||
var aggregator = new ConcurrentAggregator<TLeft, TRight, TAggregate>(update_left, update_right, o.OnNext, seed, init_left, init_right);
|
||||
return new CompositeDisposable(source.Subscribe(aggregator.on_left), right.Subscribe(aggregator.on_right));
|
||||
});
|
||||
|
||||
public static IObservable<TResult> AggregateLatest<TLeft, TRight, TAggregate, TResult>(this IObservable<TLeft> source, IObservable<TRight> right,
|
||||
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left, Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right, TAggregate seed,
|
||||
Func<TAggregate, TResult> result_selector,
|
||||
TLeft init_left = default, TRight init_right = default) =>
|
||||
Observable.Create<TResult>(o => {
|
||||
Log.DataFlow.diag($"Create AggregateLatest (Left: {typeof(TLeft)}, Right: {typeof(TRight)}).");
|
||||
var aggregator = new ConcurrentAggregator<TLeft, TRight, TAggregate>(update_left, update_right, agg => o.OnNext(result_selector(agg)), seed, init_left, init_right);
|
||||
return new CompositeDisposable(source.Subscribe(aggregator.on_left), right.Subscribe(aggregator.on_right));
|
||||
});
|
||||
|
||||
static TableUpdate<T, TKey> _apply_predicate<T, TKey>(TableUpdate<T, TKey> tgt_table, TableUpdate<T, TKey> src_table, Func<T, bool> predicate, Func<T, TKey> key) where TKey : IEquatable<TKey> {
|
||||
var added = src_table.added.Where(predicate).ToList();
|
||||
var removed = src_table.removed.Where(predicate).ToList();
|
||||
@@ -621,22 +698,20 @@ namespace bts.api.core {
|
||||
});
|
||||
}
|
||||
|
||||
public static IConnectableObservable<TableUpdate<(TLeft left, TRight right), TKey>> LeftJoin<TLeft, TRight, TKey>(this IObservable<TableUpdate<TLeft, TKey>> left_source, IObservable<TableUpdate<TRight, TKey>> right_source,
|
||||
public static IObservable<TableUpdate<(TLeft left, TRight right), TKey>> LeftJoin<TLeft, TRight, TKey>(this IObservable<TableUpdate<TLeft, TKey>> left_source, IObservable<TableUpdate<TRight, TKey>> right_source,
|
||||
Func<TLeft, TKey> left_key, Func<TRight, TKey> right_key, TRight right_default = default)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
new TableUpdateLeftJoin<TLeft, TKey, TRight, TKey, (TLeft left, TRight right)>(
|
||||
left_source, right_source, left_key, right_key, left_key, (lv, lk, rv, rk) => (lv, rv), (lv, lk, rk) => (lv, right_default), t => left_key(t.left));
|
||||
LeftJoin<TLeft, TKey, TRight, TKey, (TLeft left, TRight right)>(left_source, right_source, left_key, right_key, left_key, (lv, lk, rv, rk) => (lv, rv), (lv, lk, rk) => (lv, right_default), t => left_key(t.left));
|
||||
|
||||
public static IConnectableObservable<TableUpdate<TResult, TKey>> LeftJoin<TLeft, TRight, TResult, TKey>(this IObservable<TableUpdate<TLeft, TKey>> left_source, IObservable<TableUpdate<TRight, TKey>> right_source,
|
||||
public static IObservable<TableUpdate<TResult, TKey>> LeftJoin<TLeft, TRight, TResult, TKey>(this IObservable<TableUpdate<TLeft, TKey>> left_source, IObservable<TableUpdate<TRight, TKey>> right_source,
|
||||
Func<TLeft, TKey> left_key, Func<TRight, TKey> right_key, Func<TLeft, TKey, TRight, TResult> result_selector, Func<TLeft, TKey, TResult> absent_right_result_selector, Func<TResult, TKey> result_key)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
new TableUpdateLeftJoin<TLeft, TKey, TRight, TKey, TResult>(
|
||||
left_source, right_source, left_key, right_key, left_key, (lv, lk, rv, rk) => result_selector(lv, lk, rv), (lv, lk, rk) => absent_right_result_selector(lv, lk), result_key);
|
||||
LeftJoin(left_source, right_source, left_key, right_key, left_key, (lv, lk, rv, rk) => result_selector(lv, lk, rv), (lv, lk, rk) => absent_right_result_selector(lv, lk), result_key);
|
||||
|
||||
public static IConnectableObservable<TableUpdate<TResult, TLeftKey>> LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TResult>(this IObservable<TableUpdate<TLeft, TLeftKey>> left_source, IObservable<TableUpdate<TRight, TRightKey>> right_source,
|
||||
Func<TLeft, TLeftKey> key, Func<TRight, TRightKey> right_key, Func<TLeft, TRightKey> join_key, Func<TLeft, TLeftKey, TRight, TRightKey, TResult> result_selector, Func<TLeft, TLeftKey, TRightKey, TResult> absent_result_selector, Func<TResult, TLeftKey> result_key)
|
||||
public static IObservable<TableUpdate<TResult, TLeftKey>> LeftJoin<TLeft, TLeftKey, TRight, TRightKey, TResult>(this IObservable<TableUpdate<TLeft, TLeftKey>> left_source, IObservable<TableUpdate<TRight, TRightKey>> right_source,
|
||||
Func<TLeft, TLeftKey> left_key, Func<TRight, TRightKey> right_key, Func<TLeft, TRightKey> join_key, Func<TLeft, TLeftKey, TRight, TRightKey, TResult> result_selector, Func<TLeft, TLeftKey, TRightKey, TResult> absent_right_result_selector, Func<TResult, TLeftKey> result_key)
|
||||
where TLeftKey : IEquatable<TLeftKey>
|
||||
where TRightKey : IEquatable<TRightKey> =>
|
||||
new TableUpdateLeftJoin<TLeft, TLeftKey, TRight, TRightKey, TResult>(left_source, right_source, key, right_key, join_key, result_selector, absent_result_selector, result_key);
|
||||
TableUpdateLeftJoin.Create(left_source, right_source, left_key, right_key, join_key, result_selector, absent_right_result_selector, result_key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,223 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Disposables;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace bts.api.core {
|
||||
class TableUpdateLeftJoin<TLeft, TLeftKey, TRight, TRightKey, TResult> : IConnectableObservable<TableUpdate<TResult, TLeftKey>>
|
||||
where TLeftKey : IEquatable<TLeftKey>
|
||||
where TRightKey : IEquatable<TRightKey> {
|
||||
// left
|
||||
readonly Func<TLeft, TLeftKey> _left_key;
|
||||
readonly IObservable<TableUpdate<TLeft, TLeftKey>> _left_source;
|
||||
// right
|
||||
readonly Func<TRight, TRightKey> _right_key;
|
||||
readonly IObservable<TableUpdate<TRight, TRightKey>> _right_source;
|
||||
// join
|
||||
readonly Func<TLeft, TLeftKey, TRight, TRightKey, TResult> _result_selector;
|
||||
readonly Func<TLeft, TLeftKey, TRightKey, TResult> _absent_right_result_selector;
|
||||
readonly Func<TLeft, TRightKey> _join_key;
|
||||
readonly Dictionary<TRightKey, HashSet<TLeftKey>> _join_index = new Dictionary<TRightKey, HashSet<TLeftKey>>();
|
||||
readonly Subject<TableUpdate<TResult, TLeftKey>> _subject = new Subject<TableUpdate<TResult, TLeftKey>>();
|
||||
// state
|
||||
TableUpdate<TResult, TLeftKey> _table;
|
||||
Dictionary<TRightKey, TRight> _right_state = new Dictionary<TRightKey, TRight>();
|
||||
IReadOnlyDictionary<TLeftKey, TLeft> _left_state = new Dictionary<TLeftKey, TLeft>();
|
||||
|
||||
IDisposable _subscriptions = mbts.Disposable.empty;
|
||||
int _connection; // unconnected = 0, connected == 1
|
||||
Queue<_Op> _buffer = new Queue<_Op>();
|
||||
bool _is_updating, _left_completed, _right_completed;
|
||||
|
||||
public TableUpdateLeftJoin(IObservable<TableUpdate<TLeft, TLeftKey>> left_source, IObservable<TableUpdate<TRight, TRightKey>> right_source, Func<TLeft, TLeftKey> left_key, Func<TRight, TRightKey> right_key,
|
||||
Func<TLeft, TRightKey> join_key, Func<TLeft, TLeftKey, TRight, TRightKey, TResult> result_selector, Func<TLeft, TLeftKey, TRightKey, TResult> absent_right_result_selector, Func<TResult, TLeftKey> result_key) {
|
||||
_left_source = left_source;
|
||||
_left_key = left_key;
|
||||
_right_source = right_source;
|
||||
_right_key = right_key;
|
||||
_join_key = join_key;
|
||||
_result_selector = result_selector;
|
||||
_absent_right_result_selector = absent_right_result_selector;
|
||||
_table = TableUpdate<TResult, TLeftKey>.mk_empty(result_key);
|
||||
}
|
||||
|
||||
public IDisposable Connect() {
|
||||
if (Interlocked.CompareExchange(ref _connection, 1, 0) == 0) {
|
||||
_subscriptions = new CompositeDisposable(
|
||||
_left_source.Subscribe(_on_left_update, _on_left_completed),
|
||||
_right_source.Subscribe(_on_right_update, _on_right_completed));
|
||||
}
|
||||
return Disposable.Create(() => {
|
||||
if (Interlocked.CompareExchange(ref _connection, 0, 1) == 1)
|
||||
_subscriptions.Dispose();
|
||||
});
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<TableUpdate<TResult, TLeftKey>> observer) {
|
||||
lock (this) {
|
||||
if (_is_updating) {
|
||||
var sub = new LazySubscription(() => _subscribe_core(observer));
|
||||
_buffer.Enqueue(new _Op(sub));
|
||||
return sub;
|
||||
static class TableUpdateLeftJoin {
|
||||
public static IObservable<TableUpdate<TResult, TLeftKey>> Create<TLeft, TLeftKey, TRight, TRightKey, TResult>(
|
||||
IObservable<TableUpdate<TLeft, TLeftKey>> left_source,
|
||||
IObservable<TableUpdate<TRight, TRightKey>> right_source,
|
||||
Func<TLeft, TLeftKey> left_key,
|
||||
Func<TRight, TRightKey> right_key,
|
||||
Func<TLeft, TRightKey> join_key,
|
||||
Func<TLeft, TLeftKey, TRight, TRightKey, TResult> result_selector,
|
||||
Func<TLeft, TLeftKey, TRightKey, TResult> absent_right_result_selector,
|
||||
Func<TResult, TLeftKey> result_key)
|
||||
where TLeftKey : IEquatable<TLeftKey>
|
||||
where TRightKey : IEquatable<TRightKey> {
|
||||
return left_source.AggregateLatest<TableUpdate<TLeft, TLeftKey>, TableUpdate<TRight, TRightKey>, (TableUpdate<TResult, TLeftKey> result, Dictionary<TRightKey, HashSet<TLeftKey>> join_index), TableUpdate<TResult, TLeftKey>>(
|
||||
right_source,
|
||||
process_left,
|
||||
process_right,
|
||||
(TableUpdate<TResult, TLeftKey>.mk_empty(result_key), new Dictionary<TRightKey, HashSet<TLeftKey>>()),
|
||||
state => state.result,
|
||||
TableUpdate<TLeft, TLeftKey>.mk_empty(left_key),
|
||||
TableUpdate<TRight, TRightKey>.mk_empty(right_key));
|
||||
// process left
|
||||
(TableUpdate<TResult, TLeftKey> result, Dictionary<TRightKey, HashSet<TLeftKey>> join_index) process_left(
|
||||
(TableUpdate<TResult, TLeftKey> result, Dictionary<TRightKey, HashSet<TLeftKey>> join_index) state,
|
||||
Change<TableUpdate<TLeft, TLeftKey>> change,
|
||||
TableUpdate<TRight, TRightKey> right) {
|
||||
var left_update = change.New;
|
||||
var join_index = state.join_index;
|
||||
var builder = state.result.to_builder();
|
||||
foreach (var v in left_update.removed) {
|
||||
var lk = left_key(v);
|
||||
builder.remove(lk);
|
||||
var rk = join_key(v);
|
||||
var joined = join_index[rk];
|
||||
if (joined.Remove(lk) && joined.Count == 0)
|
||||
join_index.Remove(rk);
|
||||
}
|
||||
return _subscribe_core(observer);
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable _subscribe_core(IObserver<TableUpdate<TResult, TLeftKey>> observer) {
|
||||
observer.OnNext(_table.as_snapshot());
|
||||
return _subject.Subscribe(observer);
|
||||
}
|
||||
|
||||
#region Left
|
||||
void _on_left_completed() {
|
||||
lock (this) {
|
||||
_left_completed = true;
|
||||
if (_right_completed)
|
||||
_subject.OnCompleted();
|
||||
}
|
||||
}
|
||||
void _on_left_update(TableUpdate<TLeft, TLeftKey> left_update) {
|
||||
lock (this) {
|
||||
if (_is_updating) {
|
||||
_buffer.Enqueue(new _Op(left_update));
|
||||
return;
|
||||
foreach (var v in left_update.added.Concat(left_update.changed)) {
|
||||
var lk = left_key(v);
|
||||
var rk = join_key(v);
|
||||
var result = right.state.TryGetValue(rk, out TRight r) ? result_selector(v, lk, r, rk) : absent_right_result_selector(v, lk, rk);
|
||||
builder.add_or_update(lk, result);
|
||||
if (!join_index.TryGetValue(rk, out HashSet<TLeftKey> joined))
|
||||
join_index.Add(rk, joined = new HashSet<TLeftKey>());
|
||||
joined.Add(lk);
|
||||
}
|
||||
_is_updating = true;
|
||||
return (builder.to_table(), join_index);
|
||||
}
|
||||
_process_left(left_update);
|
||||
}
|
||||
|
||||
void _process_left(TableUpdate<TLeft, TLeftKey> left_update) {
|
||||
var builder = _table.to_builder();
|
||||
foreach (var v in left_update.removed) {
|
||||
var lk = _left_key(v);
|
||||
builder.remove(lk);
|
||||
var rk = _join_key(v);
|
||||
var joined = _join_index[rk];
|
||||
if (joined.Remove(lk) && joined.Count == 0)
|
||||
_join_index.Remove(rk);
|
||||
}
|
||||
foreach (var v in left_update.added.Concat(left_update.changed)) {
|
||||
var lk = _left_key(v);
|
||||
var rk = _join_key(v);
|
||||
var result = _right_state.TryGetValue(rk, out TRight r) ? _result_selector(v, lk, r, rk) : _absent_right_result_selector(v, lk, rk);
|
||||
builder.add_or_update(lk, result);
|
||||
if (!_join_index.TryGetValue(rk, out HashSet<TLeftKey> joined))
|
||||
_join_index.Add(rk, joined = new HashSet<TLeftKey>());
|
||||
joined.Add(lk);
|
||||
}
|
||||
_left_state = left_update.state;
|
||||
_table = builder.to_table();
|
||||
_subject.OnNext(_table);
|
||||
_on_op_completed();
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Right
|
||||
void _on_right_completed() {
|
||||
lock (this) {
|
||||
_right_completed = true;
|
||||
if (_left_completed)
|
||||
_subject.OnCompleted();
|
||||
}
|
||||
}
|
||||
|
||||
void _on_right_update(TableUpdate<TRight, TRightKey> right_update) {
|
||||
lock (this) {
|
||||
if (_is_updating) {
|
||||
_buffer.Enqueue(new _Op(right_update));
|
||||
return;
|
||||
}
|
||||
_is_updating = true;
|
||||
}
|
||||
_process_right(right_update);
|
||||
}
|
||||
|
||||
void _process_right(TableUpdate<TRight, TRightKey> right_update) {
|
||||
var builder = _table.to_builder();
|
||||
foreach (var v in right_update.removed) {
|
||||
var rkey = _right_key(v);
|
||||
if (_join_index.TryGetValue(rkey, out HashSet<TLeftKey> lkeys)) {
|
||||
foreach (var k in lkeys) {
|
||||
var lval = _left_state[k];
|
||||
var result = _absent_right_result_selector(lval, k, rkey);
|
||||
builder.add_or_update(k, result);
|
||||
// process right
|
||||
(TableUpdate<TResult, TLeftKey> result, Dictionary<TRightKey, HashSet<TLeftKey>> join_index) process_right(
|
||||
(TableUpdate<TResult, TLeftKey> result, Dictionary<TRightKey, HashSet<TLeftKey>> join_index) state,
|
||||
TableUpdate<TLeft, TLeftKey> left,
|
||||
Change<TableUpdate<TRight, TRightKey>> change) {
|
||||
var right_update = change.New;
|
||||
var join_index = state.join_index;
|
||||
var builder = state.result.to_builder();
|
||||
foreach (var v in right_update.removed) {
|
||||
var rkey = right_key(v);
|
||||
if (join_index.TryGetValue(rkey, out HashSet<TLeftKey> lkeys)) {
|
||||
foreach (var k in lkeys) {
|
||||
var lval = left.state[k];
|
||||
var result = absent_right_result_selector(lval, k, rkey);
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
if (lkeys.Count == 0)
|
||||
join_index.Remove(rkey);
|
||||
}
|
||||
if (lkeys.Count == 0)
|
||||
_join_index.Remove(rkey);
|
||||
}
|
||||
_right_state.Remove(rkey);
|
||||
}
|
||||
foreach (var v in right_update.added.Concat(right_update.changed)) {
|
||||
var rkey = _right_key(v);
|
||||
if (_join_index.TryGetValue(rkey, out HashSet<TLeftKey> lkeys)) {
|
||||
foreach (var k in lkeys) {
|
||||
var lval = _left_state[k];
|
||||
var result = _result_selector(lval, k, v, rkey);
|
||||
builder.add_or_update(k, result);
|
||||
foreach (var v in right_update.added.Concat(right_update.changed)) {
|
||||
var rkey = right_key(v);
|
||||
if (join_index.TryGetValue(rkey, out HashSet<TLeftKey> lkeys)) {
|
||||
foreach (var k in lkeys) {
|
||||
var lval = left.state[k];
|
||||
var result = result_selector(lval, k, v, rkey);
|
||||
builder.add_or_update(k, result);
|
||||
}
|
||||
} else {
|
||||
join_index.Add(rkey, new HashSet<TLeftKey>());
|
||||
}
|
||||
} else {
|
||||
_join_index.Add(rkey, new HashSet<TLeftKey>());
|
||||
}
|
||||
_right_state[rkey] = v;
|
||||
}
|
||||
_table = builder.to_table();
|
||||
if (_table.has_changes()) {
|
||||
_subject.OnNext(_table);
|
||||
}
|
||||
_on_op_completed();
|
||||
}
|
||||
#endregion
|
||||
|
||||
void _on_op_completed() {
|
||||
Log.assert(_is_updating);
|
||||
_Op buffered_op;
|
||||
lock (this) {
|
||||
if (_buffer.Count == 0) {
|
||||
_is_updating = false;
|
||||
return;
|
||||
}
|
||||
buffered_op = _buffer.Dequeue();
|
||||
}
|
||||
switch (buffered_op.update_case) {
|
||||
case _Op.UpdateOneofCase.Left:
|
||||
_process_left(buffered_op.left);
|
||||
break;
|
||||
case _Op.UpdateOneofCase.Right:
|
||||
_process_right(buffered_op.right);
|
||||
break;
|
||||
case _Op.UpdateOneofCase.Subscription:
|
||||
buffered_op.subscription.connect();
|
||||
_on_op_completed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
class _Op {
|
||||
public readonly TableUpdate<TLeft, TLeftKey> left;
|
||||
public readonly TableUpdate<TRight, TRightKey> right;
|
||||
public readonly LazySubscription subscription;
|
||||
|
||||
public _Op(TableUpdate<TLeft, TLeftKey> left) => this.left = left;
|
||||
public _Op(TableUpdate<TRight, TRightKey> right) => this.right = right;
|
||||
public _Op(LazySubscription subscription) =>
|
||||
this.subscription = subscription;
|
||||
|
||||
public enum UpdateOneofCase {
|
||||
Left, Right, Subscription
|
||||
}
|
||||
|
||||
public UpdateOneofCase update_case {
|
||||
get {
|
||||
if (left != null) return UpdateOneofCase.Left;
|
||||
if (right != null) return UpdateOneofCase.Right;
|
||||
if (subscription != null) return UpdateOneofCase.Subscription;
|
||||
throw new InvalidOperationException("Unexpected state.");
|
||||
}
|
||||
return (builder.to_table(), join_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
using bts.api.core;
|
||||
using System;
|
||||
|
||||
namespace bts.utils {
|
||||
class ConcurrentAggregator<TLeft, TRight, TAggregate> {
|
||||
readonly Func<TAggregate, Change<TLeft>, TRight, TAggregate> _update_left;
|
||||
readonly Func<TAggregate, TLeft, Change<TRight>, TAggregate> _update_right;
|
||||
readonly Action<TAggregate> _on_update_completed;
|
||||
readonly ConcurrentOpGate<(TLeft lval, TRight rval, bool is_left)> gate = new ConcurrentOpGate<(TLeft lval, TRight rval, bool is_left)>();
|
||||
|
||||
TAggregate _seed;
|
||||
TLeft _left;
|
||||
TRight _right;
|
||||
|
||||
public ConcurrentAggregator(
|
||||
Func<TAggregate, Change<TLeft>, TRight, TAggregate> update_left, Func<TAggregate, TLeft, Change<TRight>, TAggregate> update_right, Action<TAggregate> on_update_completed, TAggregate seed, TLeft init_left = default, TRight init_right = default) {
|
||||
_update_left = update_left;
|
||||
_update_right = update_right;
|
||||
_on_update_completed = on_update_completed;
|
||||
_seed = seed;
|
||||
_left = init_left;
|
||||
_right = init_right;
|
||||
}
|
||||
|
||||
public void on_left(TLeft lval) {
|
||||
if (gate.try_enter((lval, default, true)))
|
||||
process_left(lval);
|
||||
}
|
||||
public void process_left(TLeft lval) {
|
||||
_seed = _update_left(_seed, new Change<TLeft>(_left, _left = lval), _right);
|
||||
_on_op_completed();
|
||||
}
|
||||
public void on_right(TRight rval) {
|
||||
if (gate.try_enter((default, rval, false)))
|
||||
process_right(rval);
|
||||
}
|
||||
public void process_right(TRight rval) {
|
||||
_seed = _update_right(_seed, _left, new Change<TRight>(_right, _right = rval));
|
||||
_on_op_completed();
|
||||
}
|
||||
|
||||
void _on_op_completed() {
|
||||
_on_update_completed(_seed);
|
||||
if (!gate.try_exit(out (TLeft lval, TRight rval, bool is_left) next_op)) {
|
||||
if (next_op.is_left)
|
||||
process_left(next_op.lval);
|
||||
else process_right(next_op.rval);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace bts.utils {
|
||||
class ConcurrentOpGate<T> {
|
||||
bool _is_busy;
|
||||
readonly Queue<T> _op_queue = new Queue<T>();
|
||||
|
||||
public bool try_enter(T op) {
|
||||
lock (_op_queue) {
|
||||
if (_is_busy) {
|
||||
_op_queue.Enqueue(op);
|
||||
return false;
|
||||
}
|
||||
_is_busy = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool try_exit(out T continue_with) {
|
||||
lock (_op_queue) {
|
||||
if (_op_queue.Count > 0) {
|
||||
continue_with = _op_queue.Dequeue();
|
||||
return false;
|
||||
}
|
||||
_is_busy = false;
|
||||
}
|
||||
continue_with = default;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,9 @@ namespace bts.collections {
|
||||
}
|
||||
}
|
||||
|
||||
public static IList<T> WrapValue<T>(T value) =>
|
||||
new ValueListWrapper<T>(value);
|
||||
|
||||
public static IList<T> WrapType<T>(this IList list) =>
|
||||
new TypeWrapper<T>(list);
|
||||
|
||||
@@ -62,6 +65,36 @@ namespace bts.collections {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// unlike new T[] { value }, this struct prohibits changing the value at index 0.
|
||||
struct ValueListWrapper<T> : IList<T> {
|
||||
readonly T _value;
|
||||
|
||||
public ValueListWrapper(T value) =>
|
||||
_value = value;
|
||||
|
||||
public T this[int index] {
|
||||
get => index == 0 ? _value : throw new IndexOutOfRangeException();
|
||||
set => throw new NotSupportedException();
|
||||
}
|
||||
public int Count => 1;
|
||||
public bool IsReadOnly => true;
|
||||
|
||||
void ICollection<T>.Add(T item) => throw new NotSupportedException();
|
||||
void ICollection<T>.Clear() => throw new NotSupportedException();
|
||||
public bool Contains(T item) => _value.Equals(item);
|
||||
public void CopyTo(T[] array, int arrayIndex) => array[arrayIndex] = _value;
|
||||
public IEnumerator<T> GetEnumerator() {
|
||||
yield return _value;
|
||||
}
|
||||
public int IndexOf(T item) => _value.Equals(item) ? 0 : -1;
|
||||
void IList<T>.Insert(int index, T item) => throw new NotSupportedException();
|
||||
bool ICollection<T>.Remove(T item) => throw new NotSupportedException();
|
||||
void IList<T>.RemoveAt(int index) => throw new NotSupportedException();
|
||||
IEnumerator IEnumerable.GetEnumerator() {
|
||||
yield return _value;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeWrapper<T> : IList<T>, IReadOnlyList<T> {
|
||||
readonly IList _source;
|
||||
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using bts.api.core;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.collections {
|
||||
public struct ImmutableUpdate<T, TKey> {
|
||||
public static readonly ImmutableUpdate<T, TKey> empty =
|
||||
new ImmutableUpdate<T, TKey>(ImmutableDictionary<TKey, T>.Empty, ImmutableDictionary<TKey, T>.Empty, ImmutableDictionary<TKey, Change<T>>.Empty, ImmutableDictionary<TKey, T>.Empty);
|
||||
|
||||
public readonly ImmutableDictionary<TKey, T> added, removed;
|
||||
public readonly ImmutableDictionary<TKey, Change<T>> changes;
|
||||
public readonly ImmutableDictionary<TKey, T> state;
|
||||
|
||||
ImmutableUpdate(ImmutableDictionary<TKey, T> added, ImmutableDictionary<TKey, T> removed, ImmutableDictionary<TKey, Change<T>> changes, ImmutableDictionary<TKey, T> state) {
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
this.changes = changes;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public bool has_changes => changes.Any() || added.Any() || removed.Any();
|
||||
|
||||
public ImmutableUpdate<T, TKey> with_update(ImmutableUpdate<T, TKey> update) {
|
||||
var abuilder = added.ToBuilder();
|
||||
var rbuilder = removed.ToBuilder();
|
||||
var cbuilder = changes.ToBuilder();
|
||||
var sbuilder = state.ToBuilder();
|
||||
foreach (var kv in update.removed) {
|
||||
rbuilder.Add(kv);
|
||||
sbuilder.Remove(kv.Key);
|
||||
}
|
||||
foreach (var kv in update.changes) {
|
||||
cbuilder.Add(kv);
|
||||
sbuilder[kv.Key] = kv.Value.New;
|
||||
}
|
||||
foreach (var kv in update.added) {
|
||||
abuilder.Add(kv);
|
||||
sbuilder.Add(kv);
|
||||
}
|
||||
return new ImmutableUpdate<T, TKey>(abuilder.ToImmutable(), rbuilder.ToImmutable(), cbuilder.ToImmutable(), sbuilder.ToImmutable());
|
||||
}
|
||||
|
||||
public ImmutableUpdate<T, TKey> with_added(IEnumerable<KeyValuePair<TKey, T>> added) {
|
||||
var abuilder = this.added.ToBuilder();
|
||||
var sbuilder = state.ToBuilder();
|
||||
foreach (var kv in added) {
|
||||
abuilder.Add(kv);
|
||||
sbuilder.Add(kv);
|
||||
}
|
||||
return new ImmutableUpdate<T, TKey>(abuilder.ToImmutable(), removed, changes, sbuilder.ToImmutable());
|
||||
}
|
||||
|
||||
public ImmutableUpdate<T, TKey> with_removed(IEnumerable<KeyValuePair<TKey, T>> removed) {
|
||||
var rbuilder = this.removed.ToBuilder();
|
||||
var sbuilder = state.ToBuilder();
|
||||
foreach (var kv in removed) {
|
||||
rbuilder.Add(kv);
|
||||
sbuilder.Remove(kv);
|
||||
}
|
||||
return new ImmutableUpdate<T, TKey>(added, rbuilder.ToImmutable(), changes, sbuilder.ToImmutable());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
/// 'ASSERT_ON_ERROR' is useful for diagnostic purposes.
|
||||
/// CLR Exceptions thrown inside Reactive.Linq operators can be elusive, as cross-thread exception handling is inherently complicated.
|
||||
/// Alternatively, <see cref="bts.Log.assert(bool, string, string, int)"/> immediately crashes the application and captures the call stack.
|
||||
#define ASSERT_ON_ERROR
|
||||
using mbts;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.collections {
|
||||
public class ObservableNode<TKey, TValue> : IObservable<ImmutableUpdate<TValue, TKey>> {
|
||||
public readonly TKey key;
|
||||
public readonly TValue value;
|
||||
public readonly Dictionary<TKey, ObservableNode<TKey, TValue>> children;
|
||||
|
||||
public ObservableNode(TKey key, TValue value)
|
||||
: this(key, value, EqualityComparer<TKey>.Default) { }
|
||||
|
||||
public ObservableNode(TKey key, TValue value, IEqualityComparer<TKey> comparer)
|
||||
: this(key, value, null, new Dictionary<TKey, ObservableNode<TKey, TValue>>(comparer)) { }
|
||||
|
||||
public ObservableNode(TKey key, TValue value, ObservableNode<TKey, TValue> parent, Dictionary<TKey, ObservableNode<TKey, TValue>> children) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.parent = parent;
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
event Action<ObservableNode<TKey, TValue>> _child_added, _child_removed;
|
||||
|
||||
public ObservableNode<TKey, TValue> parent { get; private set; }
|
||||
|
||||
public void add_child(ObservableNode<TKey, TValue> node) {
|
||||
if (node.parent != null) {
|
||||
node.parent.remove_child(node.key);
|
||||
}
|
||||
lock (children) {
|
||||
children.Add(node.key, node);
|
||||
node.parent = this;
|
||||
}
|
||||
_child_added?.Invoke(node);
|
||||
}
|
||||
|
||||
public bool remove_child(TKey key) {
|
||||
ObservableNode<TKey, TValue> n;
|
||||
lock (children) {
|
||||
if (children.TryPopValue(key, out n)) {
|
||||
n.parent = null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_child_removed?.Invoke(n);
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool remove_child(TKey key, out ObservableNode<TKey, TValue> child) {
|
||||
lock (children) {
|
||||
if (children.TryPopValue(key, out child)) {
|
||||
child.parent = null;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
_child_removed?.Invoke(child);
|
||||
return true;
|
||||
}
|
||||
|
||||
public IObservable<ImmutableUpdate<TValue, TKey>> subscribe_keys(ISet<TKey> keys) =>
|
||||
Observable.Create<ImmutableUpdate<TValue, TKey>>(observer => {
|
||||
if (keys.Contains(key)) {
|
||||
return Subscribe(observer);
|
||||
} else {
|
||||
var state = ImmutableUpdate<TValue, TKey>.empty;
|
||||
Dictionary<TKey, IDisposable> subscriptions;
|
||||
lock (children) {
|
||||
subscriptions = children.ToDictionary(kv => kv.Key, kv => subscribe_node(kv.Value));
|
||||
_child_added += on_child_added;
|
||||
_child_removed += on_child_removed;
|
||||
}
|
||||
return Disposable.Create(() => {
|
||||
_child_added -= on_child_added;
|
||||
_child_removed -= on_child_removed;
|
||||
foreach (var s in subscriptions.Values)
|
||||
s.Dispose();
|
||||
subscriptions = null;
|
||||
});
|
||||
|
||||
void on_child_added(ObservableNode<TKey, TValue> node) =>
|
||||
subscriptions.Add(node.key, subscribe_node(node));
|
||||
|
||||
void on_child_removed(ObservableNode<TKey, TValue> node) {
|
||||
if (subscriptions.TryPopValue(node.key, out IDisposable sub)) {
|
||||
sub.Dispose();
|
||||
} else {
|
||||
_on_error("Failed to dispose subscription to child node when child was removed.");
|
||||
}
|
||||
}
|
||||
|
||||
IDisposable subscribe_node(ObservableNode<TKey, TValue> node) {
|
||||
var node_state = ImmutableUpdate<TValue, TKey>.empty;
|
||||
return node.subscribe_keys(keys).Subscribe(u => {
|
||||
node_state = u;
|
||||
if (u.has_changes) {
|
||||
state = state.with_update(u);
|
||||
observer.OnNext(state);
|
||||
}
|
||||
}, () => {
|
||||
if (node_state.state.Any()) {
|
||||
state = state.with_removed(node_state.state);
|
||||
observer.OnNext(state);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
public IDisposable Subscribe(IObserver<ImmutableUpdate<TValue, TKey>> observer) {
|
||||
var state = ImmutableUpdate<TValue, TKey>.empty.with_added(_enumerate_hierarchy(this));
|
||||
observer.OnNext(state);
|
||||
Dictionary<TKey, IDisposable> subscriptions;
|
||||
lock (children) {
|
||||
subscriptions = children.ToDictionary(kv => kv.Key, kv => subscribe_node(kv.Value));
|
||||
_child_added += on_child_added;
|
||||
_child_removed += on_child_removed;
|
||||
}
|
||||
|
||||
return Disposable.Create(() => {
|
||||
_child_added -= on_child_added;
|
||||
_child_removed -= on_child_removed;
|
||||
foreach (var s in subscriptions.Values)
|
||||
s.Dispose();
|
||||
subscriptions = null;
|
||||
});
|
||||
|
||||
void on_child_added(ObservableNode<TKey, TValue> child) {
|
||||
state = state.with_added(_enumerate_hierarchy(child));
|
||||
observer.OnNext(state);
|
||||
subscriptions.Add(child.key, subscribe_node(child));
|
||||
}
|
||||
|
||||
void on_child_removed(ObservableNode<TKey, TValue> child) {
|
||||
if (subscriptions.TryPopValue(child.key, out IDisposable sub)) {
|
||||
state = state.with_removed(_enumerate_hierarchy(child));
|
||||
observer.OnNext(state);
|
||||
sub.Dispose();
|
||||
} else Log.assert(false, "Failed to unsubscribe from child node.");
|
||||
}
|
||||
|
||||
IDisposable subscribe_node(ObservableNode<TKey, TValue> node) =>
|
||||
node.Subscribe(u => {
|
||||
state = state.with_update(u);
|
||||
observer.OnNext(state);
|
||||
});
|
||||
}
|
||||
|
||||
static IEnumerable<KeyValuePair<TKey, TValue>> _enumerate_hierarchy(ObservableNode<TKey, TValue> node) {
|
||||
yield return new KeyValuePair<TKey, TValue>(node.key, node.value);
|
||||
foreach (var child in node.children.Values) {
|
||||
foreach (var grand_child in _enumerate_hierarchy(child))
|
||||
yield return grand_child;
|
||||
}
|
||||
}
|
||||
|
||||
void _on_error(string msg) {
|
||||
#if ASSERT_ON_ERROR
|
||||
Log.assert(false, msg);
|
||||
#else
|
||||
throw new InvalidOperationException(msg);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
Loaded 100 of 279 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user